Merge branch 'devel-2.4'
[ganeti-local] / lib / confd / querylib.py
1 #
2 #
3
4 # Copyright (C) 2009, Google Inc.
5 #
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.
10 #
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.
15 #
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
19 # 02110-1301, USA.
20
21
22 """Ganeti configuration daemon queries library.
23
24 """
25
26 import logging
27
28 from ganeti import constants
29
30
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)
38
39
40 class ConfdQuery(object):
41   """Confd Query base class.
42
43   """
44   def __init__(self, reader):
45     """Constructor for Confd Query
46
47     @type reader: L{ssconf.SimpleConfigReader}
48     @param reader: ConfigReader to use to access the config
49
50     """
51     self.reader = reader
52
53   def Exec(self, query): # pylint: disable-msg=R0201,W0613
54     """Process a single UDP request from a client.
55
56     Different queries should override this function, which by defaults returns
57     a "non-implemented" answer.
58
59     @type query: (undefined)
60     @param query: ConfdRequest 'query' field
61     @rtype: (integer, undefined)
62     @return: status and answer to give to the client
63
64     """
65     status = constants.CONFD_REPL_STATUS_NOTIMPLEMENTED
66     answer = 'not implemented'
67     return status, answer
68
69
70 class PingQuery(ConfdQuery):
71   """An empty confd query.
72
73   It will return success on an empty argument, and an error on any other
74   argument.
75
76   """
77   def Exec(self, query):
78     """PingQuery main execution.
79
80     """
81     if query is None:
82       status = constants.CONFD_REPL_STATUS_OK
83       answer = 'ok'
84     else:
85       status = constants.CONFD_REPL_STATUS_ERROR
86       answer = 'non-empty ping query'
87
88     return status, answer
89
90
91 class ClusterMasterQuery(ConfdQuery):
92   """Cluster master query.
93
94   It accepts no arguments, and returns the current cluster master.
95
96   """
97   def _GetMasterNode(self):
98     return self.reader.GetMasterNode()
99
100   def Exec(self, query):
101     """ClusterMasterQuery main execution
102
103     """
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
111
112         answer = []
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()))
120       else:
121         logging.debug("missing FIELDS in query dict")
122         return QUERY_ARGUMENT_ERROR
123     elif not query:
124       status = constants.CONFD_REPL_STATUS_OK
125       answer = self.reader.GetMasterNode()
126     else:
127       logging.debug("Invalid master query argument: not dict or empty")
128       return QUERY_ARGUMENT_ERROR
129
130     return status, answer
131
132
133 class NodeRoleQuery(ConfdQuery):
134   """A query for the role of a node.
135
136   It will return one of CONFD_NODE_ROLE_*, or an error for non-existing nodes.
137
138   """
139   def Exec(self, query):
140     """EmptyQuery main execution
141
142     """
143     node = query
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)
149     if flags is None:
150       return QUERY_UNKNOWN_ENTRY_ERROR
151
152     master_candidate, drained, offline = flags
153     if master_candidate:
154       answer = constants.CONFD_NODE_ROLE_CANDIDATE
155     elif drained:
156       answer = constants.CONFD_NODE_ROLE_DRAINED
157     elif offline:
158       answer = constants.CONFD_NODE_ROLE_OFFLINE
159     else:
160       answer = constants.CONFD_NODE_ROLE_REGULAR
161
162     return constants.CONFD_REPL_STATUS_OK, answer
163
164
165 class InstanceIpToNodePrimaryIpQuery(ConfdQuery):
166   """A query for the location of one or more instance's ips.
167
168   """
169   def Exec(self, query):
170     """InstanceIpToNodePrimaryIpQuery main execution.
171
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)...])
180
181     """
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
189       else:
190         logging.debug("missing IP or IPLIST in query dict")
191         return QUERY_ARGUMENT_ERROR
192
193       if constants.CONFD_REQQ_LINK in query:
194         network_link = query[constants.CONFD_REQQ_LINK]
195       else:
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]
200       network_link = None
201       mode = constants.CONFD_REQQ_IP
202     else:
203       logging.debug("Invalid query argument type for: %s", query)
204       return QUERY_ARGUMENT_ERROR
205
206     pnodes_list = []
207
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
212
213       instance = self.reader.GetInstanceByLinkIp(instance_ip, network_link)
214       if not instance:
215         logging.debug("Unknown instance IP: %s", instance_ip)
216         pnodes_list.append(QUERY_UNKNOWN_ENTRY_ERROR)
217         continue
218
219       pnode = self.reader.GetInstancePrimaryNode(instance)
220       if not pnode:
221         logging.error("Instance '%s' doesn't have an associated primary"
222                       " node", instance)
223         pnodes_list.append(QUERY_INTERNAL_ERROR)
224         continue
225
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)
231         continue
232
233       pnodes_list.append((constants.CONFD_REPL_STATUS_OK, pnode_primary_ip))
234
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]
240
241     return constants.CONFD_REPL_STATUS_OK, pnodes_list
242
243
244 class NodesPipsQuery(ConfdQuery):
245   """A query for nodes primary IPs.
246
247   It returns the list of nodes primary IPs.
248
249   """
250   def Exec(self, query):
251     """NodesPipsQuery main execution.
252
253     """
254     if query is None:
255       status = constants.CONFD_REPL_STATUS_OK
256       answer = self.reader.GetNodesPrimaryIps()
257     else:
258       status = constants.CONFD_REPL_STATUS_ERROR
259       answer = "non-empty node primary IPs query"
260
261     return status, answer
262
263
264 class MasterCandidatesPipsQuery(ConfdQuery):
265   """A query for master candidates primary IPs.
266
267   It returns the list of master candidates primary IPs.
268
269   """
270   def Exec(self, query):
271     """MasterCandidatesPipsQuery main execution.
272
273     """
274     if query is None:
275       status = constants.CONFD_REPL_STATUS_OK
276       answer = self.reader.GetMasterCandidatesPrimaryIps()
277     else:
278       status = constants.CONFD_REPL_STATUS_ERROR
279       answer = "non-empty master candidates primary IPs query"
280
281     return status, answer
282
283
284 class InstancesIpsQuery(ConfdQuery):
285   """A query for instances IPs.
286
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.
289
290   """
291   def Exec(self, query):
292     """InstancesIpsQuery main execution.
293
294     """
295     link = query
296     status = constants.CONFD_REPL_STATUS_OK
297     answer = self.reader.GetInstancesIps(link)
298
299     return status, answer