Querying node groups: LU/Opcode
[ganeti-local] / lib / query.py
1 #
2 #
3
4 # Copyright (C) 2010 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 """Module for query operations"""
23
24 import logging
25 import operator
26 import re
27
28 from ganeti import constants
29 from ganeti import errors
30 from ganeti import utils
31 from ganeti import compat
32 from ganeti import objects
33 from ganeti import ht
34
35
36 (NQ_CONFIG,
37  NQ_INST,
38  NQ_LIVE,
39  NQ_GROUP) = range(1, 5)
40
41
42 FIELD_NAME_RE = re.compile(r"^[a-z0-9/._]+$")
43 TITLE_RE = re.compile(r"^[^\s]+$")
44
45 #: Verification function for each field type
46 _VERIFY_FN = {
47   constants.QFT_UNKNOWN: ht.TNone,
48   constants.QFT_TEXT: ht.TString,
49   constants.QFT_BOOL: ht.TBool,
50   constants.QFT_NUMBER: ht.TInt,
51   constants.QFT_UNIT: ht.TInt,
52   constants.QFT_TIMESTAMP: ht.TOr(ht.TInt, ht.TFloat),
53   constants.QFT_OTHER: lambda _: True,
54   }
55
56
57 def _GetUnknownField(ctx, item): # pylint: disable-msg=W0613
58   """Gets the contents of an unknown field.
59
60   """
61   return (constants.QRFS_UNKNOWN, None)
62
63
64 def _GetQueryFields(fielddefs, selected):
65   """Calculates the internal list of selected fields.
66
67   Unknown fields are returned as L{constants.QFT_UNKNOWN}.
68
69   @type fielddefs: dict
70   @param fielddefs: Field definitions
71   @type selected: list of strings
72   @param selected: List of selected fields
73
74   """
75   result = []
76
77   for name in selected:
78     try:
79       fdef = fielddefs[name]
80     except KeyError:
81       fdef = (_MakeField(name, name, constants.QFT_UNKNOWN),
82               None, _GetUnknownField)
83
84     assert len(fdef) == 3
85
86     result.append(fdef)
87
88   return result
89
90
91 def GetAllFields(fielddefs):
92   """Extract L{objects.QueryFieldDefinition} from field definitions.
93
94   @rtype: list of L{objects.QueryFieldDefinition}
95
96   """
97   return [fdef for (fdef, _, _) in fielddefs]
98
99
100 class Query:
101   def __init__(self, fieldlist, selected):
102     """Initializes this class.
103
104     The field definition is a dictionary with the field's name as a key and a
105     tuple containing, in order, the field definition object
106     (L{objects.QueryFieldDefinition}, the data kind to help calling code
107     collect data and a retrieval function. The retrieval function is called
108     with two parameters, in order, the data container and the item in container
109     (see L{Query.Query}).
110
111     Users of this class can call L{RequestedData} before preparing the data
112     container to determine what data is needed.
113
114     @type fieldlist: dictionary
115     @param fieldlist: Field definitions
116     @type selected: list of strings
117     @param selected: List of selected fields
118
119     """
120     self._fields = _GetQueryFields(fieldlist, selected)
121
122   def RequestedData(self):
123     """Gets requested kinds of data.
124
125     @rtype: frozenset
126
127     """
128     return frozenset(datakind
129                      for (_, datakind, _) in self._fields
130                      if datakind is not None)
131
132   def GetFields(self):
133     """Returns the list of fields for this query.
134
135     Includes unknown fields.
136
137     @rtype: List of L{objects.QueryFieldDefinition}
138
139     """
140     return GetAllFields(self._fields)
141
142   def Query(self, ctx):
143     """Execute a query.
144
145     @param ctx: Data container passed to field retrieval functions, must
146       support iteration using C{__iter__}
147
148     """
149     result = [[fn(ctx, item) for (_, _, fn) in self._fields]
150               for item in ctx]
151
152     # Verify result
153     if __debug__:
154       for (idx, row) in enumerate(result):
155         assert _VerifyResultRow(self._fields, row), \
156                ("Inconsistent result for fields %s in row %s: %r" %
157                 (self._fields, idx, row))
158
159     return result
160
161   def OldStyleQuery(self, ctx):
162     """Query with "old" query result format.
163
164     See L{Query.Query} for arguments.
165
166     """
167     unknown = set(fdef.name
168                   for (fdef, _, _) in self._fields
169                   if fdef.kind == constants.QFT_UNKNOWN)
170     if unknown:
171       raise errors.OpPrereqError("Unknown output fields selected: %s" %
172                                  (utils.CommaJoin(unknown), ),
173                                  errors.ECODE_INVAL)
174
175     return [[value for (_, value) in row]
176             for row in self.Query(ctx)]
177
178
179 def _VerifyResultRow(fields, row):
180   """Verifies the contents of a query result row.
181
182   @type fields: list
183   @param fields: Field definitions for result
184   @type row: list of tuples
185   @param row: Row data
186
187   """
188   return (len(row) == len(fields) and
189           compat.all((status == constants.QRFS_NORMAL and
190                       _VERIFY_FN[fdef.kind](value)) or
191                      # Value for an abnormal status must be None
192                      (status != constants.QRFS_NORMAL and value is None)
193                      for ((status, value), (fdef, _, _)) in zip(row, fields)))
194
195
196 def _PrepareFieldList(fields):
197   """Prepares field list for use by L{Query}.
198
199   Converts the list to a dictionary and does some verification.
200
201   @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data kind,
202     retrieval function)
203   @param fields: List of fields
204   @rtype: dict
205   @return: Field dictionary for L{Query}
206
207   """
208   assert len(set(fdef.title.lower()
209                  for (fdef, _, _) in fields)) == len(fields), \
210          "Duplicate title found"
211
212   result = {}
213
214   for field in fields:
215     (fdef, _, fn) = field
216
217     assert fdef.name and fdef.title, "Name and title are required"
218     assert FIELD_NAME_RE.match(fdef.name)
219     assert TITLE_RE.match(fdef.title)
220     assert callable(fn)
221     assert fdef.name not in result, "Duplicate field name found"
222
223     result[fdef.name] = field
224
225   assert len(result) == len(fields)
226   assert compat.all(name == fdef.name
227                     for (name, (fdef, _, _)) in result.items())
228
229   return result
230
231
232 def _MakeField(name, title, kind):
233   """Wrapper for creating L{objects.QueryFieldDefinition} instances.
234
235   @param name: Field name as a regular expression
236   @param title: Human-readable title
237   @param kind: Field type
238
239   """
240   return objects.QueryFieldDefinition(name=name, title=title, kind=kind)
241
242
243 def _GetNodeRole(node, master_name):
244   """Determine node role.
245
246   @type node: L{objects.Node}
247   @param node: Node object
248   @type master_name: string
249   @param master_name: Master node name
250
251   """
252   if node.name == master_name:
253     return "M"
254   elif node.master_candidate:
255     return "C"
256   elif node.drained:
257     return "D"
258   elif node.offline:
259     return "O"
260   else:
261     return "R"
262
263
264 def _GetItemAttr(attr):
265   """Returns a field function to return an attribute of the item.
266
267   @param attr: Attribute name
268
269   """
270   getter = operator.attrgetter(attr)
271   return lambda _, item: (constants.QRFS_NORMAL, getter(item))
272
273
274 class NodeQueryData:
275   """Data container for node data queries.
276
277   """
278   def __init__(self, nodes, live_data, master_name, node_to_primary,
279                node_to_secondary, groups):
280     """Initializes this class.
281
282     """
283     self.nodes = nodes
284     self.live_data = live_data
285     self.master_name = master_name
286     self.node_to_primary = node_to_primary
287     self.node_to_secondary = node_to_secondary
288     self.groups = groups
289
290     # Used for individual rows
291     self.curlive_data = None
292
293   def __iter__(self):
294     """Iterate over all nodes.
295
296     This function has side-effects and only one instance of the resulting
297     generator should be used at a time.
298
299     """
300     for node in self.nodes:
301       if self.live_data:
302         self.curlive_data = self.live_data.get(node.name, None)
303       else:
304         self.curlive_data = None
305       yield node
306
307
308 #: Fields that are direct attributes of an L{objects.Node} object
309 _NODE_SIMPLE_FIELDS = {
310   "ctime": ("CTime", constants.QFT_TIMESTAMP),
311   "drained": ("Drained", constants.QFT_BOOL),
312   "master_candidate": ("MasterC", constants.QFT_BOOL),
313   "master_capable": ("MasterCapable", constants.QFT_BOOL),
314   "mtime": ("MTime", constants.QFT_TIMESTAMP),
315   "name": ("Node", constants.QFT_TEXT),
316   "offline": ("Offline", constants.QFT_BOOL),
317   "serial_no": ("SerialNo", constants.QFT_NUMBER),
318   "uuid": ("UUID", constants.QFT_TEXT),
319   "vm_capable": ("VMCapable", constants.QFT_BOOL),
320   }
321
322
323 #: Fields requiring talking to the node
324 _NODE_LIVE_FIELDS = {
325   "bootid": ("BootID", constants.QFT_TEXT, "bootid"),
326   "cnodes": ("CNodes", constants.QFT_NUMBER, "cpu_nodes"),
327   "csockets": ("CSockets", constants.QFT_NUMBER, "cpu_sockets"),
328   "ctotal": ("CTotal", constants.QFT_NUMBER, "cpu_total"),
329   "dfree": ("DFree", constants.QFT_UNIT, "vg_free"),
330   "dtotal": ("DTotal", constants.QFT_UNIT, "vg_size"),
331   "mfree": ("MFree", constants.QFT_UNIT, "memory_free"),
332   "mnode": ("MNode", constants.QFT_UNIT, "memory_dom0"),
333   "mtotal": ("MTotal", constants.QFT_UNIT, "memory_total"),
334   }
335
336
337 def _GetNodeGroup(ctx, node):
338   """Returns the name of a node's group.
339
340   @type ctx: L{NodeQueryData}
341   @type node: L{objects.Node}
342   @param node: Node object
343
344   """
345   ng = ctx.groups.get(node.group, None)
346   if ng is None:
347     # Nodes always have a group, or the configuration is corrupt
348     return (constants.QRFS_UNAVAIL, None)
349
350   return (constants.QRFS_NORMAL, ng.name)
351
352
353 def _GetLiveNodeField(field, kind, ctx, _):
354   """Gets the value of a "live" field from L{NodeQueryData}.
355
356   @param field: Live field name
357   @param kind: Data kind, one of L{constants.QFT_ALL}
358   @type ctx: L{NodeQueryData}
359
360   """
361   if not ctx.curlive_data:
362     return (constants.QRFS_NODATA, None)
363
364   try:
365     value = ctx.curlive_data[field]
366   except KeyError:
367     return (constants.QRFS_UNAVAIL, None)
368
369   if kind == constants.QFT_TEXT:
370     return (constants.QRFS_NORMAL, value)
371
372   assert kind in (constants.QFT_NUMBER, constants.QFT_UNIT)
373
374   # Try to convert into number
375   try:
376     return (constants.QRFS_NORMAL, int(value))
377   except (ValueError, TypeError):
378     logging.exception("Failed to convert node field '%s' (value %r) to int",
379                       value, field)
380     return (constants.QRFS_UNAVAIL, None)
381
382
383 def _BuildNodeFields():
384   """Builds list of fields for node queries.
385
386   """
387   fields = [
388     (_MakeField("pip", "PrimaryIP", constants.QFT_TEXT), NQ_CONFIG,
389      lambda ctx, node: (constants.QRFS_NORMAL, node.primary_ip)),
390     (_MakeField("sip", "SecondaryIP", constants.QFT_TEXT), NQ_CONFIG,
391      lambda ctx, node: (constants.QRFS_NORMAL, node.secondary_ip)),
392     (_MakeField("tags", "Tags", constants.QFT_OTHER), NQ_CONFIG,
393      lambda ctx, node: (constants.QRFS_NORMAL, list(node.GetTags()))),
394     (_MakeField("master", "IsMaster", constants.QFT_BOOL), NQ_CONFIG,
395      lambda ctx, node: (constants.QRFS_NORMAL, node.name == ctx.master_name)),
396     (_MakeField("role", "Role", constants.QFT_TEXT), NQ_CONFIG,
397      lambda ctx, node: (constants.QRFS_NORMAL,
398                         _GetNodeRole(node, ctx.master_name))),
399     (_MakeField("group", "Group", constants.QFT_TEXT), NQ_GROUP, _GetNodeGroup),
400     (_MakeField("group.uuid", "GroupUUID", constants.QFT_TEXT),
401      NQ_CONFIG, lambda ctx, node: (constants.QRFS_NORMAL, node.group)),
402     ]
403
404   def _GetLength(getter):
405     return lambda ctx, node: (constants.QRFS_NORMAL,
406                               len(getter(ctx)[node.name]))
407
408   def _GetList(getter):
409     return lambda ctx, node: (constants.QRFS_NORMAL,
410                               list(getter(ctx)[node.name]))
411
412   # Add fields operating on instance lists
413   for prefix, titleprefix, getter in \
414       [("p", "Pri", operator.attrgetter("node_to_primary")),
415        ("s", "Sec", operator.attrgetter("node_to_secondary"))]:
416     fields.extend([
417       (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(),
418                   constants.QFT_NUMBER),
419        NQ_INST, _GetLength(getter)),
420       (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
421                   constants.QFT_OTHER),
422        NQ_INST, _GetList(getter)),
423       ])
424
425   # Add simple fields
426   fields.extend([(_MakeField(name, title, kind), NQ_CONFIG, _GetItemAttr(name))
427                  for (name, (title, kind)) in _NODE_SIMPLE_FIELDS.items()])
428
429   # Add fields requiring live data
430   fields.extend([
431     (_MakeField(name, title, kind), NQ_LIVE,
432      compat.partial(_GetLiveNodeField, nfield, kind))
433     for (name, (title, kind, nfield)) in _NODE_LIVE_FIELDS.items()
434     ])
435
436   return _PrepareFieldList(fields)
437
438
439 #: Fields available for node queries
440 NODE_FIELDS = _BuildNodeFields()