4 # Copyright (C) 2009 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Ganeti configuration daemon queries library.
28 from ganeti import constants
31 # constants for some common errors to return from a query
32 QUERY_UNKNOWN_ENTRY_ERROR = (constants.CONFD_REPL_STATUS_ERROR,
33 constants.CONFD_ERROR_UNKNOWN_ENTRY)
34 QUERY_INTERNAL_ERROR = (constants.CONFD_REPL_STATUS_ERROR,
35 constants.CONFD_ERROR_INTERNAL)
36 QUERY_ARGUMENT_ERROR = (constants.CONFD_REPL_STATUS_ERROR,
37 constants.CONFD_ERROR_ARGUMENT)
40 class ConfdQuery(object):
41 """Confd Query base class.
44 def __init__(self, reader):
45 """Constructor for Confd Query
47 @type reader: L{ssconf.SimpleConfigReader}
48 @param reader: ConfigReader to use to access the config
53 def Exec(self, query): # pylint: disable=R0201,W0613
54 """Process a single UDP request from a client.
56 Different queries should override this function, which by defaults returns
57 a "non-implemented" answer.
59 @type query: (undefined)
60 @param query: ConfdRequest 'query' field
61 @rtype: (integer, undefined)
62 @return: status and answer to give to the client
65 status = constants.CONFD_REPL_STATUS_NOTIMPLEMENTED
66 answer = 'not implemented'
70 class PingQuery(ConfdQuery):
71 """An empty confd query.
73 It will return success on an empty argument, and an error on any other
77 def Exec(self, query):
78 """PingQuery main execution.
82 status = constants.CONFD_REPL_STATUS_OK
85 status = constants.CONFD_REPL_STATUS_ERROR
86 answer = 'non-empty ping query'
91 class ClusterMasterQuery(ConfdQuery):
92 """Cluster master query.
94 It accepts no arguments, and returns the current cluster master.
97 def _GetMasterNode(self):
98 return self.reader.GetMasterNode()
100 def Exec(self, query):
101 """ClusterMasterQuery main execution
104 if isinstance(query, dict):
105 if constants.CONFD_REQQ_FIELDS in query:
106 status = constants.CONFD_REPL_STATUS_OK
107 req_fields = query[constants.CONFD_REQQ_FIELDS]
108 if not isinstance(req_fields, (list, tuple)):
109 logging.debug("FIELDS request should be a list")
110 return QUERY_ARGUMENT_ERROR
113 for field in req_fields:
114 if field == constants.CONFD_REQFIELD_NAME:
115 answer.append(self._GetMasterNode())
116 elif field == constants.CONFD_REQFIELD_IP:
117 answer.append(self.reader.GetMasterIP())
118 elif field == constants.CONFD_REQFIELD_MNODE_PIP:
119 answer.append(self.reader.GetNodePrimaryIp(self._GetMasterNode()))
121 logging.debug("missing FIELDS in query dict")
122 return QUERY_ARGUMENT_ERROR
124 status = constants.CONFD_REPL_STATUS_OK
125 answer = self.reader.GetMasterNode()
127 logging.debug("Invalid master query argument: not dict or empty")
128 return QUERY_ARGUMENT_ERROR
130 return status, answer
133 class NodeRoleQuery(ConfdQuery):
134 """A query for the role of a node.
136 It will return one of CONFD_NODE_ROLE_*, or an error for non-existing nodes.
139 def Exec(self, query):
140 """EmptyQuery main execution
144 if self.reader.GetMasterNode() == node:
145 status = constants.CONFD_REPL_STATUS_OK
146 answer = constants.CONFD_NODE_ROLE_MASTER
147 return status, answer
148 flags = self.reader.GetNodeStatusFlags(node)
150 return QUERY_UNKNOWN_ENTRY_ERROR
152 master_candidate, drained, offline = flags
154 answer = constants.CONFD_NODE_ROLE_CANDIDATE
156 answer = constants.CONFD_NODE_ROLE_DRAINED
158 answer = constants.CONFD_NODE_ROLE_OFFLINE
160 answer = constants.CONFD_NODE_ROLE_REGULAR
162 return constants.CONFD_REPL_STATUS_OK, answer
165 class InstanceIpToNodePrimaryIpQuery(ConfdQuery):
166 """A query for the location of one or more instance's ips.
169 def Exec(self, query):
170 """InstanceIpToNodePrimaryIpQuery main execution.
172 @type query: string or dict
173 @param query: instance ip or dict containing:
174 constants.CONFD_REQQ_LINK: nic link (optional)
175 constants.CONFD_REQQ_IPLIST: list of ips
176 constants.CONFD_REQQ_IP: single ip
177 (one IP type request is mandatory)
178 @rtype: (integer, ...)
179 @return: ((status, answer) or (success, [(status, answer)...])
182 if isinstance(query, dict):
183 if constants.CONFD_REQQ_IP in query:
184 instances_list = [query[constants.CONFD_REQQ_IP]]
185 mode = constants.CONFD_REQQ_IP
186 elif constants.CONFD_REQQ_IPLIST in query:
187 instances_list = query[constants.CONFD_REQQ_IPLIST]
188 mode = constants.CONFD_REQQ_IPLIST
190 logging.debug("missing IP or IPLIST in query dict")
191 return QUERY_ARGUMENT_ERROR
193 if constants.CONFD_REQQ_LINK in query:
194 network_link = query[constants.CONFD_REQQ_LINK]
196 network_link = None # default will be used
198 logging.debug("Invalid query argument type for: %s", query)
199 return QUERY_ARGUMENT_ERROR
203 for instance_ip in instances_list:
204 if not isinstance(instance_ip, basestring):
205 logging.debug("Invalid IP type for: %s", instance_ip)
206 return QUERY_ARGUMENT_ERROR
208 instance = self.reader.GetInstanceByLinkIp(instance_ip, network_link)
210 logging.debug("Unknown instance IP: %s", instance_ip)
211 pnodes_list.append(QUERY_UNKNOWN_ENTRY_ERROR)
214 pnode = self.reader.GetInstancePrimaryNode(instance)
216 logging.error("Instance '%s' doesn't have an associated primary"
218 pnodes_list.append(QUERY_INTERNAL_ERROR)
221 pnode_primary_ip = self.reader.GetNodePrimaryIp(pnode)
222 if not pnode_primary_ip:
223 logging.error("Primary node '%s' doesn't have an associated"
224 " primary IP", pnode)
225 pnodes_list.append(QUERY_INTERNAL_ERROR)
228 pnodes_list.append((constants.CONFD_REPL_STATUS_OK, pnode_primary_ip))
230 # If a single ip was requested, return a single answer, otherwise
231 # the whole list, with a success status (since each entry has its
232 # own success/failure)
233 if mode == constants.CONFD_REQQ_IP:
234 return pnodes_list[0]
236 return constants.CONFD_REPL_STATUS_OK, pnodes_list
239 class NodesPipsQuery(ConfdQuery):
240 """A query for nodes primary IPs.
242 It returns the list of nodes primary IPs.
245 def Exec(self, query):
246 """NodesPipsQuery main execution.
250 status = constants.CONFD_REPL_STATUS_OK
251 answer = self.reader.GetNodesPrimaryIps()
253 status = constants.CONFD_REPL_STATUS_ERROR
254 answer = "non-empty node primary IPs query"
256 return status, answer
259 class MasterCandidatesPipsQuery(ConfdQuery):
260 """A query for master candidates primary IPs.
262 It returns the list of master candidates primary IPs.
265 def Exec(self, query):
266 """MasterCandidatesPipsQuery main execution.
270 status = constants.CONFD_REPL_STATUS_OK
271 answer = self.reader.GetMasterCandidatesPrimaryIps()
273 status = constants.CONFD_REPL_STATUS_ERROR
274 answer = "non-empty master candidates primary IPs query"
276 return status, answer
279 class InstancesIpsQuery(ConfdQuery):
280 """A query for instances IPs.
282 It returns the list of IPs of NICs connected to the requested link or all the
283 instances IPs if no link is submitted.
286 def Exec(self, query):
287 """InstancesIpsQuery main execution.
291 status = constants.CONFD_REPL_STATUS_OK
292 answer = self.reader.GetInstancesIps(link)
294 return status, answer