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-msg=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
197 elif isinstance(query, basestring):
198 # 2.1 beta1 and beta2 mode, to be deprecated for 2.2
199 instances_list = [query]
201 mode = constants.CONFD_REQQ_IP
203 logging.debug("Invalid query argument type for: %s", query)
204 return QUERY_ARGUMENT_ERROR
208 for instance_ip in instances_list:
209 if not isinstance(instance_ip, basestring):
210 logging.debug("Invalid IP type for: %s", instance_ip)
211 return QUERY_ARGUMENT_ERROR
213 instance = self.reader.GetInstanceByLinkIp(instance_ip, network_link)
215 logging.debug("Unknown instance IP: %s", instance_ip)
216 pnodes_list.append(QUERY_UNKNOWN_ENTRY_ERROR)
219 pnode = self.reader.GetInstancePrimaryNode(instance)
221 logging.error("Instance '%s' doesn't have an associated primary"
223 pnodes_list.append(QUERY_INTERNAL_ERROR)
226 pnode_primary_ip = self.reader.GetNodePrimaryIp(pnode)
227 if not pnode_primary_ip:
228 logging.error("Primary node '%s' doesn't have an associated"
229 " primary IP", pnode)
230 pnodes_list.append(QUERY_INTERNAL_ERROR)
233 pnodes_list.append((constants.CONFD_REPL_STATUS_OK, pnode_primary_ip))
235 # If a single ip was requested, return a single answer, otherwise
236 # the whole list, with a success status (since each entry has its
237 # own success/failure)
238 if mode == constants.CONFD_REQQ_IP:
239 return pnodes_list[0]
241 return constants.CONFD_REPL_STATUS_OK, pnodes_list
244 class NodesPipsQuery(ConfdQuery):
245 """A query for nodes primary IPs.
247 It returns the list of nodes primary IPs.
250 def Exec(self, query):
251 """NodesPipsQuery main execution.
255 status = constants.CONFD_REPL_STATUS_OK
256 answer = self.reader.GetNodesPrimaryIps()
258 status = constants.CONFD_REPL_STATUS_ERROR
259 answer = "non-empty node primary IPs query"
261 return status, answer
264 class MasterCandidatesPipsQuery(ConfdQuery):
265 """A query for master candidates primary IPs.
267 It returns the list of master candidates primary IPs.
270 def Exec(self, query):
271 """MasterCandidatesPipsQuery main execution.
275 status = constants.CONFD_REPL_STATUS_OK
276 answer = self.reader.GetMasterCandidatesPrimaryIps()
278 status = constants.CONFD_REPL_STATUS_ERROR
279 answer = "non-empty master candidates primary IPs query"
281 return status, answer
284 class InstancesIpsQuery(ConfdQuery):
285 """A query for instances IPs.
287 It returns the list of IPs of NICs connected to the requested link or all the
288 instances IPs if no link is submitted.
291 def Exec(self, query):
292 """InstancesIpsQuery main execution.
296 status = constants.CONFD_REPL_STATUS_OK
297 answer = self.reader.GetInstancesIps(link)
299 return status, answer