4 # Copyright (C) 2010 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 """Module for query operations"""
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
39 NQ_GROUP) = range(1, 5)
42 FIELD_NAME_RE = re.compile(r"^[a-z0-9/._]+$")
43 TITLE_RE = re.compile(r"^[^\s]+$")
45 #: Verification function for each field type
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,
57 def _GetUnknownField(ctx, item): # pylint: disable-msg=W0613
58 """Gets the contents of an unknown field.
61 return (constants.QRFS_UNKNOWN, None)
64 def _GetQueryFields(fielddefs, selected):
65 """Calculates the internal list of selected fields.
67 Unknown fields are returned as L{constants.QFT_UNKNOWN}.
70 @param fielddefs: Field definitions
71 @type selected: list of strings
72 @param selected: List of selected fields
79 fdef = fielddefs[name]
81 fdef = (_MakeField(name, name, constants.QFT_UNKNOWN),
82 None, _GetUnknownField)
91 def GetAllFields(fielddefs):
92 """Extract L{objects.QueryFieldDefinition} from field definitions.
94 @rtype: list of L{objects.QueryFieldDefinition}
97 return [fdef for (fdef, _, _) in fielddefs]
101 def __init__(self, fieldlist, selected):
102 """Initializes this class.
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}).
111 Users of this class can call L{RequestedData} before preparing the data
112 container to determine what data is needed.
114 @type fieldlist: dictionary
115 @param fieldlist: Field definitions
116 @type selected: list of strings
117 @param selected: List of selected fields
120 self._fields = _GetQueryFields(fieldlist, selected)
122 def RequestedData(self):
123 """Gets requested kinds of data.
128 return frozenset(datakind
129 for (_, datakind, _) in self._fields
130 if datakind is not None)
133 """Returns the list of fields for this query.
135 Includes unknown fields.
137 @rtype: List of L{objects.QueryFieldDefinition}
140 return GetAllFields(self._fields)
142 def Query(self, ctx):
145 @param ctx: Data container passed to field retrieval functions, must
146 support iteration using C{__iter__}
149 result = [[fn(ctx, item) for (_, _, fn) in self._fields]
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))
161 def OldStyleQuery(self, ctx):
162 """Query with "old" query result format.
164 See L{Query.Query} for arguments.
167 unknown = set(fdef.name
168 for (fdef, _, _) in self._fields
169 if fdef.kind == constants.QFT_UNKNOWN)
171 raise errors.OpPrereqError("Unknown output fields selected: %s" %
172 (utils.CommaJoin(unknown), ),
175 return [[value for (_, value) in row]
176 for row in self.Query(ctx)]
179 def _VerifyResultRow(fields, row):
180 """Verifies the contents of a query result row.
183 @param fields: Field definitions for result
184 @type row: list of tuples
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)))
196 def _PrepareFieldList(fields):
197 """Prepares field list for use by L{Query}.
199 Converts the list to a dictionary and does some verification.
201 @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data kind,
203 @param fields: List of fields
205 @return: Field dictionary for L{Query}
208 assert len(set(fdef.title.lower()
209 for (fdef, _, _) in fields)) == len(fields), \
210 "Duplicate title found"
215 (fdef, _, fn) = field
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)
221 assert fdef.name not in result, "Duplicate field name found"
223 result[fdef.name] = field
225 assert len(result) == len(fields)
226 assert compat.all(name == fdef.name
227 for (name, (fdef, _, _)) in result.items())
232 def _MakeField(name, title, kind):
233 """Wrapper for creating L{objects.QueryFieldDefinition} instances.
235 @param name: Field name as a regular expression
236 @param title: Human-readable title
237 @param kind: Field type
240 return objects.QueryFieldDefinition(name=name, title=title, kind=kind)
243 def _GetNodeRole(node, master_name):
244 """Determine node role.
246 @type node: L{objects.Node}
247 @param node: Node object
248 @type master_name: string
249 @param master_name: Master node name
252 if node.name == master_name:
254 elif node.master_candidate:
264 def _GetItemAttr(attr):
265 """Returns a field function to return an attribute of the item.
267 @param attr: Attribute name
270 getter = operator.attrgetter(attr)
271 return lambda _, item: (constants.QRFS_NORMAL, getter(item))
275 """Data container for node data queries.
278 def __init__(self, nodes, live_data, master_name, node_to_primary,
279 node_to_secondary, groups):
280 """Initializes this class.
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
290 # Used for individual rows
291 self.curlive_data = None
294 """Iterate over all nodes.
296 This function has side-effects and only one instance of the resulting
297 generator should be used at a time.
300 for node in self.nodes:
302 self.curlive_data = self.live_data.get(node.name, None)
304 self.curlive_data = None
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),
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"),
337 def _GetNodeGroup(ctx, node):
338 """Returns the name of a node's group.
340 @type ctx: L{NodeQueryData}
341 @type node: L{objects.Node}
342 @param node: Node object
345 ng = ctx.groups.get(node.group, None)
347 # Nodes always have a group, or the configuration is corrupt
348 return (constants.QRFS_UNAVAIL, None)
350 return (constants.QRFS_NORMAL, ng.name)
353 def _GetLiveNodeField(field, kind, ctx, _):
354 """Gets the value of a "live" field from L{NodeQueryData}.
356 @param field: Live field name
357 @param kind: Data kind, one of L{constants.QFT_ALL}
358 @type ctx: L{NodeQueryData}
361 if not ctx.curlive_data:
362 return (constants.QRFS_NODATA, None)
365 value = ctx.curlive_data[field]
367 return (constants.QRFS_UNAVAIL, None)
369 if kind == constants.QFT_TEXT:
370 return (constants.QRFS_NORMAL, value)
372 assert kind in (constants.QFT_NUMBER, constants.QFT_UNIT)
374 # Try to convert into number
376 return (constants.QRFS_NORMAL, int(value))
377 except (ValueError, TypeError):
378 logging.exception("Failed to convert node field '%s' (value %r) to int",
380 return (constants.QRFS_UNAVAIL, None)
383 def _BuildNodeFields():
384 """Builds list of fields for node queries.
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)),
404 def _GetLength(getter):
405 return lambda ctx, node: (constants.QRFS_NORMAL,
406 len(getter(ctx)[node.name]))
408 def _GetList(getter):
409 return lambda ctx, node: (constants.QRFS_NORMAL,
410 list(getter(ctx)[node.name]))
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"))]:
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)),
426 fields.extend([(_MakeField(name, title, kind), NQ_CONFIG, _GetItemAttr(name))
427 for (name, (title, kind)) in _NODE_SIMPLE_FIELDS.items()])
429 # Add fields requiring live data
431 (_MakeField(name, title, kind), NQ_LIVE,
432 compat.partial(_GetLiveNodeField, nfield, kind))
433 for (name, (title, kind, nfield)) in _NODE_LIVE_FIELDS.items()
436 return _PrepareFieldList(fields)
439 #: Fields available for node queries
440 NODE_FIELDS = _BuildNodeFields()