Inter-node RPC timeout design
[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):
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 Exec(self, query):
98     """ClusterMasterQuery main execution
99
100     """
101     if query is None:
102       status = constants.CONFD_REPL_STATUS_OK
103       answer = self.reader.GetMasterNode()
104     else:
105       status = constants.CONFD_REPL_STATUS_ERROR
106       answer = 'master query accepts no query argument'
107
108     return status, answer
109
110
111 class NodeRoleQuery(ConfdQuery):
112   """A query for the role of a node.
113
114   It will return one of CONFD_NODE_ROLE_*, or an error for non-existing nodes.
115
116   """
117   def Exec(self, query):
118     """EmptyQuery main execution
119
120     """
121     node = query
122     if self.reader.GetMasterNode() == node:
123       status = constants.CONFD_REPL_STATUS_OK
124       answer = constants.CONFD_NODE_ROLE_MASTER
125       return status, answer
126     flags = self.reader.GetNodeStatusFlags(node)
127     if flags is None:
128       return QUERY_UNKNOWN_ENTRY_ERROR
129
130     master_candidate, drained, offline = flags
131     if master_candidate:
132       answer = constants.CONFD_NODE_ROLE_CANDIDATE
133     elif drained:
134       answer = constants.CONFD_NODE_ROLE_DRAINED
135     elif offline:
136       answer = constants.CONFD_NODE_ROLE_OFFLINE
137     else:
138       answer = constants.CONFD_NODE_ROLE_REGULAR
139
140     return constants.CONFD_REPL_STATUS_OK, answer
141
142
143 class InstanceIpToNodePrimaryIpQuery(ConfdQuery):
144   """A query for the location of one or more instance's ips.
145
146   """
147   def Exec(self, query):
148     """InstanceIpToNodePrimaryIpQuery main execution.
149
150     @type query: string or dict
151     @param query: instance ip or dict containing:
152                   constants.CONFD_REQQ_LINK: nic link (optional)
153                   constants.CONFD_REQQ_IPLIST: list of ips
154                   constants.CONFD_REQQ_IP: single ip
155                   (one IP type request is mandatory)
156     @rtype: (integer, ...)
157     @return: ((status, answer) or (success, [(status, answer)...])
158
159     """
160     if isinstance(query, dict):
161       if constants.CONFD_REQQ_IP in query:
162         instances_list = [query[constants.CONFD_REQQ_IP]]
163         mode = constants.CONFD_REQQ_IP
164       elif constants.CONFD_REQQ_IPLIST in query:
165         instances_list = query[constants.CONFD_REQQ_IPLIST]
166         mode = constants.CONFD_REQQ_IPLIST
167       else:
168         status = constants.CONFD_REPL_STATUS_ERROR
169         logging.debug("missing IP or IPLIST in query dict")
170         return QUERY_ARGUMENT_ERROR
171
172       if constants.CONFD_REQQ_LINK in query:
173         network_link = query[constants.CONFD_REQQ_LINK]
174       else:
175         network_link = None # default will be used
176     elif isinstance(query, basestring):
177       # 2.1 beta1 and beta2 mode, to be deprecated for 2.2
178       instances_list = [query]
179       network_link = None
180       mode = constants.CONFD_REQQ_IP
181     else:
182       logging.debug("Invalid query argument type for: %s" % query)
183       return QUERY_ARGUMENT_ERROR
184
185     pnodes_list = []
186
187     for instance_ip in instances_list:
188       if not isinstance(instance_ip, basestring):
189         logging.debug("Invalid IP type for: %s" % instance_ip)
190         return QUERY_ARGUMENT_ERROR
191
192       instance = self.reader.GetInstanceByLinkIp(instance_ip, network_link)
193       if not instance:
194         logging.debug("Unknown instance IP: %s" % instance_ip)
195         pnodes_list.append(QUERY_UNKNOWN_ENTRY_ERROR)
196         continue
197
198       pnode = self.reader.GetInstancePrimaryNode(instance)
199       if not pnode:
200         logging.error("Instance '%s' doesn't have an associated primary"
201                       " node" % instance)
202         pnodes_list.append(QUERY_INTERNAL_ERROR)
203         continue
204
205       pnode_primary_ip = self.reader.GetNodePrimaryIp(pnode)
206       if not pnode_primary_ip:
207         logging.error("Primary node '%s' doesn't have an associated"
208                       " primary IP" % pnode)
209         pnodes_list.append(QUERY_INTERNAL_ERROR)
210         continue
211
212       pnodes_list.append((constants.CONFD_REPL_STATUS_OK, pnode_primary_ip))
213
214     # If a single ip was requested, return a single answer, otherwise the whole
215     # list, with a success status (since each entry has its own success/failure)
216     if mode == constants.CONFD_REQQ_IP:
217       return pnodes_list[0]
218
219     return constants.CONFD_REPL_STATUS_OK, pnodes_list
220
221
222 class NodesPipsQuery(ConfdQuery):
223   """A query for nodes primary IPs.
224
225   It returns the list of nodes primary IPs.
226
227   """
228   def Exec(self, query):
229     """NodesPipsQuery main execution.
230
231     """
232     if query is None:
233       status = constants.CONFD_REPL_STATUS_OK
234       answer = self.reader.GetNodesPrimaryIps()
235     else:
236       status = constants.CONFD_REPL_STATUS_ERROR
237       answer = "non-empty node primary IPs query"
238
239     return status, answer
240
241
242 class MasterCandidatesPipsQuery(ConfdQuery):
243   """A query for master candidates primary IPs.
244
245   It returns the list of master candidates primary IPs.
246
247   """
248   def Exec(self, query):
249     """MasterCandidatesPipsQuery main execution.
250
251     """
252     if query is None:
253       status = constants.CONFD_REPL_STATUS_OK
254       answer = self.reader.GetMasterCandidatesPrimaryIps()
255     else:
256       status = constants.CONFD_REPL_STATUS_ERROR
257       answer = "non-empty master candidates primary IPs query"
258
259     return status, answer
260
261
262 class InstancesIpsQuery(ConfdQuery):
263   """A query for instances IPs.
264
265   It returns the list of IPs of NICs connected to the requested link or all the
266   instances IPs if no link is submitted.
267
268   """
269   def Exec(self, query):
270     """InstancesIpsQuery main execution.
271
272     """
273     link = query
274     status = constants.CONFD_REPL_STATUS_OK
275     answer = self.reader.GetInstancesIps(link)
276
277     return status, answer