Support query of node field 'powered'
[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,
40  NQ_OOB) = range(1, 6)
41
42 (IQ_CONFIG,
43  IQ_LIVE,
44  IQ_DISKUSAGE) = range(100, 103)
45
46 (LQ_MODE,
47  LQ_OWNER,
48  LQ_PENDING) = range(10, 13)
49
50 (GQ_CONFIG,
51  GQ_NODE,
52  GQ_INST) = range(200, 203)
53
54
55 FIELD_NAME_RE = re.compile(r"^[a-z0-9/._]+$")
56 TITLE_RE = re.compile(r"^[^\s]+$")
57
58 #: Verification function for each field type
59 _VERIFY_FN = {
60   constants.QFT_UNKNOWN: ht.TNone,
61   constants.QFT_TEXT: ht.TString,
62   constants.QFT_BOOL: ht.TBool,
63   constants.QFT_NUMBER: ht.TInt,
64   constants.QFT_UNIT: ht.TInt,
65   constants.QFT_TIMESTAMP: ht.TOr(ht.TInt, ht.TFloat),
66   constants.QFT_OTHER: lambda _: True,
67   }
68
69
70 def _GetUnknownField(ctx, item): # pylint: disable-msg=W0613
71   """Gets the contents of an unknown field.
72
73   """
74   return (constants.QRFS_UNKNOWN, None)
75
76
77 def _GetQueryFields(fielddefs, selected):
78   """Calculates the internal list of selected fields.
79
80   Unknown fields are returned as L{constants.QFT_UNKNOWN}.
81
82   @type fielddefs: dict
83   @param fielddefs: Field definitions
84   @type selected: list of strings
85   @param selected: List of selected fields
86
87   """
88   result = []
89
90   for name in selected:
91     try:
92       fdef = fielddefs[name]
93     except KeyError:
94       fdef = (_MakeField(name, name, constants.QFT_UNKNOWN),
95               None, _GetUnknownField)
96
97     assert len(fdef) == 3
98
99     result.append(fdef)
100
101   return result
102
103
104 def GetAllFields(fielddefs):
105   """Extract L{objects.QueryFieldDefinition} from field definitions.
106
107   @rtype: list of L{objects.QueryFieldDefinition}
108
109   """
110   return [fdef for (fdef, _, _) in fielddefs]
111
112
113 class Query:
114   def __init__(self, fieldlist, selected):
115     """Initializes this class.
116
117     The field definition is a dictionary with the field's name as a key and a
118     tuple containing, in order, the field definition object
119     (L{objects.QueryFieldDefinition}, the data kind to help calling code
120     collect data and a retrieval function. The retrieval function is called
121     with two parameters, in order, the data container and the item in container
122     (see L{Query.Query}).
123
124     Users of this class can call L{RequestedData} before preparing the data
125     container to determine what data is needed.
126
127     @type fieldlist: dictionary
128     @param fieldlist: Field definitions
129     @type selected: list of strings
130     @param selected: List of selected fields
131
132     """
133     self._fields = _GetQueryFields(fieldlist, selected)
134
135   def RequestedData(self):
136     """Gets requested kinds of data.
137
138     @rtype: frozenset
139
140     """
141     return frozenset(datakind
142                      for (_, datakind, _) in self._fields
143                      if datakind is not None)
144
145   def GetFields(self):
146     """Returns the list of fields for this query.
147
148     Includes unknown fields.
149
150     @rtype: List of L{objects.QueryFieldDefinition}
151
152     """
153     return GetAllFields(self._fields)
154
155   def Query(self, ctx):
156     """Execute a query.
157
158     @param ctx: Data container passed to field retrieval functions, must
159       support iteration using C{__iter__}
160
161     """
162     result = [[fn(ctx, item) for (_, _, fn) in self._fields]
163               for item in ctx]
164
165     # Verify result
166     if __debug__:
167       for (idx, row) in enumerate(result):
168         assert _VerifyResultRow(self._fields, row), \
169                ("Inconsistent result for fields %s in row %s: %r" %
170                 (GetAllFields(self._fields), idx, row))
171
172     return result
173
174   def OldStyleQuery(self, ctx):
175     """Query with "old" query result format.
176
177     See L{Query.Query} for arguments.
178
179     """
180     unknown = set(fdef.name
181                   for (fdef, _, _) in self._fields
182                   if fdef.kind == constants.QFT_UNKNOWN)
183     if unknown:
184       raise errors.OpPrereqError("Unknown output fields selected: %s" %
185                                  (utils.CommaJoin(unknown), ),
186                                  errors.ECODE_INVAL)
187
188     return [[value for (_, value) in row]
189             for row in self.Query(ctx)]
190
191
192 def _VerifyResultRow(fields, row):
193   """Verifies the contents of a query result row.
194
195   @type fields: list
196   @param fields: Field definitions for result
197   @type row: list of tuples
198   @param row: Row data
199
200   """
201   return (len(row) == len(fields) and
202           compat.all((status == constants.QRFS_NORMAL and
203                       _VERIFY_FN[fdef.kind](value)) or
204                      # Value for an abnormal status must be None
205                      (status != constants.QRFS_NORMAL and value is None)
206                      for ((status, value), (fdef, _, _)) in zip(row, fields)))
207
208
209 def _PrepareFieldList(fields):
210   """Prepares field list for use by L{Query}.
211
212   Converts the list to a dictionary and does some verification.
213
214   @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data kind,
215     retrieval function)
216   @param fields: List of fields
217   @rtype: dict
218   @return: Field dictionary for L{Query}
219
220   """
221   if __debug__:
222     duplicates = utils.FindDuplicates(fdef.title.lower()
223                                       for (fdef, _, _) in fields)
224     assert not duplicates, "Duplicate title(s) found: %r" % duplicates
225
226   result = {}
227
228   for field in fields:
229     (fdef, _, fn) = field
230
231     assert fdef.name and fdef.title, "Name and title are required"
232     assert FIELD_NAME_RE.match(fdef.name)
233     assert TITLE_RE.match(fdef.title)
234     assert callable(fn)
235     assert fdef.name not in result, \
236            "Duplicate field name '%s' found" % fdef.name
237
238     result[fdef.name] = field
239
240   assert len(result) == len(fields)
241   assert compat.all(name == fdef.name
242                     for (name, (fdef, _, _)) in result.items())
243
244   return result
245
246
247 def GetQueryResponse(query, ctx):
248   """Prepares the response for a query.
249
250   @type query: L{Query}
251   @param ctx: Data container, see L{Query.Query}
252
253   """
254   return objects.QueryResponse(data=query.Query(ctx),
255                                fields=query.GetFields()).ToDict()
256
257
258 def QueryFields(fielddefs, selected):
259   """Returns list of available fields.
260
261   @type fielddefs: dict
262   @param fielddefs: Field definitions
263   @type selected: list of strings
264   @param selected: List of selected fields
265   @return: List of L{objects.QueryFieldDefinition}
266
267   """
268   if selected is None:
269     # Client requests all fields, sort by name
270     fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
271                            key=operator.attrgetter("name"))
272   else:
273     # Keep order as requested by client
274     fdefs = Query(fielddefs, selected).GetFields()
275
276   return objects.QueryFieldsResponse(fields=fdefs).ToDict()
277
278
279 def _MakeField(name, title, kind):
280   """Wrapper for creating L{objects.QueryFieldDefinition} instances.
281
282   @param name: Field name as a regular expression
283   @param title: Human-readable title
284   @param kind: Field type
285
286   """
287   return objects.QueryFieldDefinition(name=name, title=title, kind=kind)
288
289
290 def _GetNodeRole(node, master_name):
291   """Determine node role.
292
293   @type node: L{objects.Node}
294   @param node: Node object
295   @type master_name: string
296   @param master_name: Master node name
297
298   """
299   if node.name == master_name:
300     return "M"
301   elif node.master_candidate:
302     return "C"
303   elif node.drained:
304     return "D"
305   elif node.offline:
306     return "O"
307   else:
308     return "R"
309
310
311 def _GetItemAttr(attr):
312   """Returns a field function to return an attribute of the item.
313
314   @param attr: Attribute name
315
316   """
317   getter = operator.attrgetter(attr)
318   return lambda _, item: (constants.QRFS_NORMAL, getter(item))
319
320
321 def _GetItemTimestamp(getter):
322   """Returns function for getting timestamp of item.
323
324   @type getter: callable
325   @param getter: Function to retrieve timestamp attribute
326
327   """
328   def fn(_, item):
329     """Returns a timestamp of item.
330
331     """
332     timestamp = getter(item)
333     if timestamp is None:
334       # Old configs might not have all timestamps
335       return (constants.QRFS_UNAVAIL, None)
336     else:
337       return (constants.QRFS_NORMAL, timestamp)
338
339   return fn
340
341
342 def _GetItemTimestampFields(datatype):
343   """Returns common timestamp fields.
344
345   @param datatype: Field data type for use by L{Query.RequestedData}
346
347   """
348   return [
349     (_MakeField("ctime", "CTime", constants.QFT_TIMESTAMP), datatype,
350      _GetItemTimestamp(operator.attrgetter("ctime"))),
351     (_MakeField("mtime", "MTime", constants.QFT_TIMESTAMP), datatype,
352      _GetItemTimestamp(operator.attrgetter("mtime"))),
353     ]
354
355
356 class NodeQueryData:
357   """Data container for node data queries.
358
359   """
360   def __init__(self, nodes, live_data, master_name, node_to_primary,
361                node_to_secondary, groups, oob_support):
362     """Initializes this class.
363
364     """
365     self.nodes = nodes
366     self.live_data = live_data
367     self.master_name = master_name
368     self.node_to_primary = node_to_primary
369     self.node_to_secondary = node_to_secondary
370     self.groups = groups
371     self.oob_support = oob_support
372
373     # Used for individual rows
374     self.curlive_data = None
375
376   def __iter__(self):
377     """Iterate over all nodes.
378
379     This function has side-effects and only one instance of the resulting
380     generator should be used at a time.
381
382     """
383     for node in self.nodes:
384       if self.live_data:
385         self.curlive_data = self.live_data.get(node.name, None)
386       else:
387         self.curlive_data = None
388       yield node
389
390
391 #: Fields that are direct attributes of an L{objects.Node} object
392 _NODE_SIMPLE_FIELDS = {
393   "drained": ("Drained", constants.QFT_BOOL),
394   "master_candidate": ("MasterC", constants.QFT_BOOL),
395   "master_capable": ("MasterCapable", constants.QFT_BOOL),
396   "name": ("Node", constants.QFT_TEXT),
397   "offline": ("Offline", constants.QFT_BOOL),
398   "serial_no": ("SerialNo", constants.QFT_NUMBER),
399   "uuid": ("UUID", constants.QFT_TEXT),
400   "vm_capable": ("VMCapable", constants.QFT_BOOL),
401   }
402
403
404 #: Fields requiring talking to the node
405 _NODE_LIVE_FIELDS = {
406   "bootid": ("BootID", constants.QFT_TEXT, "bootid"),
407   "cnodes": ("CNodes", constants.QFT_NUMBER, "cpu_nodes"),
408   "csockets": ("CSockets", constants.QFT_NUMBER, "cpu_sockets"),
409   "ctotal": ("CTotal", constants.QFT_NUMBER, "cpu_total"),
410   "dfree": ("DFree", constants.QFT_UNIT, "vg_free"),
411   "dtotal": ("DTotal", constants.QFT_UNIT, "vg_size"),
412   "mfree": ("MFree", constants.QFT_UNIT, "memory_free"),
413   "mnode": ("MNode", constants.QFT_UNIT, "memory_dom0"),
414   "mtotal": ("MTotal", constants.QFT_UNIT, "memory_total"),
415   }
416
417
418 def _GetNodeGroup(ctx, node):
419   """Returns the name of a node's group.
420
421   @type ctx: L{NodeQueryData}
422   @type node: L{objects.Node}
423   @param node: Node object
424
425   """
426   ng = ctx.groups.get(node.group, None)
427   if ng is None:
428     # Nodes always have a group, or the configuration is corrupt
429     return (constants.QRFS_UNAVAIL, None)
430
431   return (constants.QRFS_NORMAL, ng.name)
432
433
434 def _GetNodePower(ctx, node):
435   """Returns the node powered state
436
437   @type ctx: L{NodeQueryData}
438   @type node: L{objects.Node}
439   @param node: Node object
440
441   """
442   if ctx.oob_support[node.name]:
443     return (constants.QRFS_NORMAL, node.powered)
444
445   return (constants.QRFS_UNAVAIL, None)
446
447
448 def _GetLiveNodeField(field, kind, ctx, node):
449   """Gets the value of a "live" field from L{NodeQueryData}.
450
451   @param field: Live field name
452   @param kind: Data kind, one of L{constants.QFT_ALL}
453   @type ctx: L{NodeQueryData}
454   @type node: L{objects.Node}
455   @param node: Node object
456
457   """
458   if node.offline:
459     return (constants.QRFS_OFFLINE, None)
460
461   if not ctx.curlive_data:
462     return (constants.QRFS_NODATA, None)
463
464   try:
465     value = ctx.curlive_data[field]
466   except KeyError:
467     return (constants.QRFS_UNAVAIL, None)
468
469   if kind == constants.QFT_TEXT:
470     return (constants.QRFS_NORMAL, value)
471
472   assert kind in (constants.QFT_NUMBER, constants.QFT_UNIT)
473
474   # Try to convert into number
475   try:
476     return (constants.QRFS_NORMAL, int(value))
477   except (ValueError, TypeError):
478     logging.exception("Failed to convert node field '%s' (value %r) to int",
479                       value, field)
480     return (constants.QRFS_UNAVAIL, None)
481
482
483 def _BuildNodeFields():
484   """Builds list of fields for node queries.
485
486   """
487   fields = [
488     (_MakeField("pip", "PrimaryIP", constants.QFT_TEXT), NQ_CONFIG,
489      lambda ctx, node: (constants.QRFS_NORMAL, node.primary_ip)),
490     (_MakeField("sip", "SecondaryIP", constants.QFT_TEXT), NQ_CONFIG,
491      lambda ctx, node: (constants.QRFS_NORMAL, node.secondary_ip)),
492     (_MakeField("tags", "Tags", constants.QFT_OTHER), NQ_CONFIG,
493      lambda ctx, node: (constants.QRFS_NORMAL, list(node.GetTags()))),
494     (_MakeField("master", "IsMaster", constants.QFT_BOOL), NQ_CONFIG,
495      lambda ctx, node: (constants.QRFS_NORMAL, node.name == ctx.master_name)),
496     (_MakeField("role", "Role", constants.QFT_TEXT), NQ_CONFIG,
497      lambda ctx, node: (constants.QRFS_NORMAL,
498                         _GetNodeRole(node, ctx.master_name))),
499     (_MakeField("group", "Group", constants.QFT_TEXT), NQ_GROUP, _GetNodeGroup),
500     (_MakeField("group.uuid", "GroupUUID", constants.QFT_TEXT),
501      NQ_CONFIG, lambda ctx, node: (constants.QRFS_NORMAL, node.group)),
502     (_MakeField("powered", "Powered", constants.QFT_BOOL), NQ_OOB,
503       _GetNodePower),
504     ]
505
506   def _GetLength(getter):
507     return lambda ctx, node: (constants.QRFS_NORMAL,
508                               len(getter(ctx)[node.name]))
509
510   def _GetList(getter):
511     return lambda ctx, node: (constants.QRFS_NORMAL,
512                               list(getter(ctx)[node.name]))
513
514   # Add fields operating on instance lists
515   for prefix, titleprefix, getter in \
516       [("p", "Pri", operator.attrgetter("node_to_primary")),
517        ("s", "Sec", operator.attrgetter("node_to_secondary"))]:
518     fields.extend([
519       (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(),
520                   constants.QFT_NUMBER),
521        NQ_INST, _GetLength(getter)),
522       (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
523                   constants.QFT_OTHER),
524        NQ_INST, _GetList(getter)),
525       ])
526
527   # Add simple fields
528   fields.extend([(_MakeField(name, title, kind), NQ_CONFIG, _GetItemAttr(name))
529                  for (name, (title, kind)) in _NODE_SIMPLE_FIELDS.items()])
530
531   # Add fields requiring live data
532   fields.extend([
533     (_MakeField(name, title, kind), NQ_LIVE,
534      compat.partial(_GetLiveNodeField, nfield, kind))
535     for (name, (title, kind, nfield)) in _NODE_LIVE_FIELDS.items()
536     ])
537
538   # Add timestamps
539   fields.extend(_GetItemTimestampFields(NQ_CONFIG))
540
541   return _PrepareFieldList(fields)
542
543
544 class InstanceQueryData:
545   """Data container for instance data queries.
546
547   """
548   def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
549                live_data):
550     """Initializes this class.
551
552     @param instances: List of instance objects
553     @param cluster: Cluster object
554     @type disk_usage: dict; instance name as key
555     @param disk_usage: Per-instance disk usage
556     @type offline_nodes: list of strings
557     @param offline_nodes: List of offline nodes
558     @type bad_nodes: list of strings
559     @param bad_nodes: List of faulty nodes
560     @type live_data: dict; instance name as key
561     @param live_data: Per-instance live data
562
563     """
564     assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
565            "Offline nodes not included in bad nodes"
566     assert not (set(live_data.keys()) & set(bad_nodes)), \
567            "Found live data for bad or offline nodes"
568
569     self.instances = instances
570     self.cluster = cluster
571     self.disk_usage = disk_usage
572     self.offline_nodes = offline_nodes
573     self.bad_nodes = bad_nodes
574     self.live_data = live_data
575
576     # Used for individual rows
577     self.inst_hvparams = None
578     self.inst_beparams = None
579     self.inst_nicparams = None
580
581   def __iter__(self):
582     """Iterate over all instances.
583
584     This function has side-effects and only one instance of the resulting
585     generator should be used at a time.
586
587     """
588     for inst in self.instances:
589       self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
590       self.inst_beparams = self.cluster.FillBE(inst)
591       self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
592                              for nic in inst.nics]
593
594       yield inst
595
596
597 def _GetInstOperState(ctx, inst):
598   """Get instance's operational status.
599
600   @type ctx: L{InstanceQueryData}
601   @type inst: L{objects.Instance}
602   @param inst: Instance object
603
604   """
605   # Can't use QRFS_OFFLINE here as it would describe the instance to be offline
606   # when we actually don't know due to missing data
607   if inst.primary_node in ctx.bad_nodes:
608     return (constants.QRFS_NODATA, None)
609   else:
610     return (constants.QRFS_NORMAL, bool(ctx.live_data.get(inst.name)))
611
612
613 def _GetInstLiveData(name):
614   """Build function for retrieving live data.
615
616   @type name: string
617   @param name: Live data field name
618
619   """
620   def fn(ctx, inst):
621     """Get live data for an instance.
622
623     @type ctx: L{InstanceQueryData}
624     @type inst: L{objects.Instance}
625     @param inst: Instance object
626
627     """
628     if (inst.primary_node in ctx.bad_nodes or
629         inst.primary_node in ctx.offline_nodes):
630       # Can't use QRFS_OFFLINE here as it would describe the instance to be
631       # offline when we actually don't know due to missing data
632       return (constants.QRFS_NODATA, None)
633
634     if inst.name in ctx.live_data:
635       data = ctx.live_data[inst.name]
636       if name in data:
637         return (constants.QRFS_NORMAL, data[name])
638
639     return (constants.QRFS_UNAVAIL, None)
640
641   return fn
642
643
644 def _GetInstStatus(ctx, inst):
645   """Get instance status.
646
647   @type ctx: L{InstanceQueryData}
648   @type inst: L{objects.Instance}
649   @param inst: Instance object
650
651   """
652   if inst.primary_node in ctx.offline_nodes:
653     return (constants.QRFS_NORMAL, "ERROR_nodeoffline")
654
655   if inst.primary_node in ctx.bad_nodes:
656     return (constants.QRFS_NORMAL, "ERROR_nodedown")
657
658   if bool(ctx.live_data.get(inst.name)):
659     if inst.admin_up:
660       return (constants.QRFS_NORMAL, "running")
661     else:
662       return (constants.QRFS_NORMAL, "ERROR_up")
663
664   if inst.admin_up:
665     return (constants.QRFS_NORMAL, "ERROR_down")
666
667   return (constants.QRFS_NORMAL, "ADMIN_down")
668
669
670 def _GetInstDiskSize(index):
671   """Build function for retrieving disk size.
672
673   @type index: int
674   @param index: Disk index
675
676   """
677   def fn(_, inst):
678     """Get size of a disk.
679
680     @type inst: L{objects.Instance}
681     @param inst: Instance object
682
683     """
684     try:
685       return (constants.QRFS_NORMAL, inst.disks[index].size)
686     except IndexError:
687       return (constants.QRFS_UNAVAIL, None)
688
689   return fn
690
691
692 def _GetInstNic(index, cb):
693   """Build function for calling another function with an instance NIC.
694
695   @type index: int
696   @param index: NIC index
697   @type cb: callable
698   @param cb: Callback
699
700   """
701   def fn(ctx, inst):
702     """Call helper function with instance NIC.
703
704     @type ctx: L{InstanceQueryData}
705     @type inst: L{objects.Instance}
706     @param inst: Instance object
707
708     """
709     try:
710       nic = inst.nics[index]
711     except IndexError:
712       return (constants.QRFS_UNAVAIL, None)
713
714     return cb(ctx, index, nic)
715
716   return fn
717
718
719 def _GetInstNicIp(ctx, _, nic): # pylint: disable-msg=W0613
720   """Get a NIC's IP address.
721
722   @type ctx: L{InstanceQueryData}
723   @type nic: L{objects.NIC}
724   @param nic: NIC object
725
726   """
727   if nic.ip is None:
728     return (constants.QRFS_UNAVAIL, None)
729   else:
730     return (constants.QRFS_NORMAL, nic.ip)
731
732
733 def _GetInstNicBridge(ctx, index, _):
734   """Get a NIC's bridge.
735
736   @type ctx: L{InstanceQueryData}
737   @type index: int
738   @param index: NIC index
739
740   """
741   assert len(ctx.inst_nicparams) >= index
742
743   nicparams = ctx.inst_nicparams[index]
744
745   if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
746     return (constants.QRFS_NORMAL, nicparams[constants.NIC_LINK])
747   else:
748     return (constants.QRFS_UNAVAIL, None)
749
750
751 def _GetInstAllNicBridges(ctx, inst):
752   """Get all network bridges for an instance.
753
754   @type ctx: L{InstanceQueryData}
755   @type inst: L{objects.Instance}
756   @param inst: Instance object
757
758   """
759   assert len(ctx.inst_nicparams) == len(inst.nics)
760
761   result = []
762
763   for nicp in ctx.inst_nicparams:
764     if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
765       result.append(nicp[constants.NIC_LINK])
766     else:
767       result.append(None)
768
769   assert len(result) == len(inst.nics)
770
771   return (constants.QRFS_NORMAL, result)
772
773
774 def _GetInstNicParam(name):
775   """Build function for retrieving a NIC parameter.
776
777   @type name: string
778   @param name: Parameter name
779
780   """
781   def fn(ctx, index, _):
782     """Get a NIC's bridge.
783
784     @type ctx: L{InstanceQueryData}
785     @type inst: L{objects.Instance}
786     @param inst: Instance object
787     @type nic: L{objects.NIC}
788     @param nic: NIC object
789
790     """
791     assert len(ctx.inst_nicparams) >= index
792     return (constants.QRFS_NORMAL, ctx.inst_nicparams[index][name])
793
794   return fn
795
796
797 def _GetInstanceNetworkFields():
798   """Get instance fields involving network interfaces.
799
800   @return: List of field definitions used as input for L{_PrepareFieldList}
801
802   """
803   nic_mac_fn = lambda ctx, _, nic: (constants.QRFS_NORMAL, nic.mac)
804   nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
805   nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
806
807   fields = [
808     # First NIC (legacy)
809     (_MakeField("ip", "IP_address", constants.QFT_TEXT), IQ_CONFIG,
810      _GetInstNic(0, _GetInstNicIp)),
811     (_MakeField("mac", "MAC_address", constants.QFT_TEXT), IQ_CONFIG,
812      _GetInstNic(0, nic_mac_fn)),
813     (_MakeField("bridge", "Bridge", constants.QFT_TEXT), IQ_CONFIG,
814      _GetInstNic(0, _GetInstNicBridge)),
815     (_MakeField("nic_mode", "NIC_Mode", constants.QFT_TEXT), IQ_CONFIG,
816      _GetInstNic(0, nic_mode_fn)),
817     (_MakeField("nic_link", "NIC_Link", constants.QFT_TEXT), IQ_CONFIG,
818      _GetInstNic(0, nic_link_fn)),
819
820     # All NICs
821     (_MakeField("nic.count", "NICs", constants.QFT_NUMBER), IQ_CONFIG,
822      lambda ctx, inst: (constants.QRFS_NORMAL, len(inst.nics))),
823     (_MakeField("nic.macs", "NIC_MACs", constants.QFT_OTHER), IQ_CONFIG,
824      lambda ctx, inst: (constants.QRFS_NORMAL, [nic.mac for nic in inst.nics])),
825     (_MakeField("nic.ips", "NIC_IPs", constants.QFT_OTHER), IQ_CONFIG,
826      lambda ctx, inst: (constants.QRFS_NORMAL, [nic.ip for nic in inst.nics])),
827     (_MakeField("nic.modes", "NIC_modes", constants.QFT_OTHER), IQ_CONFIG,
828      lambda ctx, inst: (constants.QRFS_NORMAL,
829                         [nicp[constants.NIC_MODE]
830                          for nicp in ctx.inst_nicparams])),
831     (_MakeField("nic.links", "NIC_links", constants.QFT_OTHER), IQ_CONFIG,
832      lambda ctx, inst: (constants.QRFS_NORMAL,
833                         [nicp[constants.NIC_LINK]
834                          for nicp in ctx.inst_nicparams])),
835     (_MakeField("nic.bridges", "NIC_bridges", constants.QFT_OTHER), IQ_CONFIG,
836      _GetInstAllNicBridges),
837     ]
838
839   # NICs by number
840   for i in range(constants.MAX_NICS):
841     fields.extend([
842       (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, constants.QFT_TEXT),
843        IQ_CONFIG, _GetInstNic(i, _GetInstNicIp)),
844       (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, constants.QFT_TEXT),
845        IQ_CONFIG, _GetInstNic(i, nic_mac_fn)),
846       (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, constants.QFT_TEXT),
847        IQ_CONFIG, _GetInstNic(i, nic_mode_fn)),
848       (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, constants.QFT_TEXT),
849        IQ_CONFIG, _GetInstNic(i, nic_link_fn)),
850       (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, constants.QFT_TEXT),
851        IQ_CONFIG, _GetInstNic(i, _GetInstNicBridge)),
852       ])
853
854   return fields
855
856
857 def _GetInstDiskUsage(ctx, inst):
858   """Get disk usage for an instance.
859
860   @type ctx: L{InstanceQueryData}
861   @type inst: L{objects.Instance}
862   @param inst: Instance object
863
864   """
865   usage = ctx.disk_usage[inst.name]
866
867   if usage is None:
868     usage = 0
869
870   return (constants.QRFS_NORMAL, usage)
871
872
873 def _GetInstanceDiskFields():
874   """Get instance fields involving disks.
875
876   @return: List of field definitions used as input for L{_PrepareFieldList}
877
878   """
879   fields = [
880     (_MakeField("disk_usage", "DiskUsage", constants.QFT_UNIT), IQ_DISKUSAGE,
881      _GetInstDiskUsage),
882     (_MakeField("sda_size", "LegacyDisk/0", constants.QFT_UNIT), IQ_CONFIG,
883      _GetInstDiskSize(0)),
884     (_MakeField("sdb_size", "LegacyDisk/1", constants.QFT_UNIT), IQ_CONFIG,
885      _GetInstDiskSize(1)),
886     (_MakeField("disk.count", "Disks", constants.QFT_NUMBER), IQ_CONFIG,
887      lambda ctx, inst: (constants.QRFS_NORMAL, len(inst.disks))),
888     (_MakeField("disk.sizes", "Disk_sizes", constants.QFT_OTHER), IQ_CONFIG,
889      lambda ctx, inst: (constants.QRFS_NORMAL,
890                         [disk.size for disk in inst.disks])),
891     ]
892
893   # Disks by number
894   fields.extend([
895     (_MakeField("disk.size/%s" % i, "Disk/%s" % i, constants.QFT_UNIT),
896      IQ_CONFIG, _GetInstDiskSize(i))
897     for i in range(constants.MAX_DISKS)
898     ])
899
900   return fields
901
902
903 def _GetInstanceParameterFields():
904   """Get instance fields involving parameters.
905
906   @return: List of field definitions used as input for L{_PrepareFieldList}
907
908   """
909   # TODO: Consider moving titles closer to constants
910   be_title = {
911     constants.BE_AUTO_BALANCE: "Auto_balance",
912     constants.BE_MEMORY: "Configured_memory",
913     constants.BE_VCPUS: "VCPUs",
914     }
915
916   hv_title = {
917     constants.HV_ACPI: "ACPI",
918     constants.HV_BOOT_ORDER: "Boot_order",
919     constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
920     constants.HV_DISK_TYPE: "Disk_type",
921     constants.HV_INITRD_PATH: "Initrd_path",
922     constants.HV_KERNEL_PATH: "Kernel_path",
923     constants.HV_NIC_TYPE: "NIC_type",
924     constants.HV_PAE: "PAE",
925     constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
926     }
927
928   fields = [
929     # Filled parameters
930     (_MakeField("hvparams", "HypervisorParameters", constants.QFT_OTHER),
931      IQ_CONFIG, lambda ctx, _: (constants.QRFS_NORMAL, ctx.inst_hvparams)),
932     (_MakeField("beparams", "BackendParameters", constants.QFT_OTHER),
933      IQ_CONFIG, lambda ctx, _: (constants.QRFS_NORMAL, ctx.inst_beparams)),
934     (_MakeField("vcpus", "LegacyVCPUs", constants.QFT_NUMBER), IQ_CONFIG,
935      lambda ctx, _: (constants.QRFS_NORMAL,
936                      ctx.inst_beparams[constants.BE_VCPUS])),
937
938     # Unfilled parameters
939     (_MakeField("custom_hvparams", "CustomHypervisorParameters",
940                 constants.QFT_OTHER),
941      IQ_CONFIG, lambda ctx, inst: (constants.QRFS_NORMAL, inst.hvparams)),
942     (_MakeField("custom_beparams", "CustomBackendParameters",
943                 constants.QFT_OTHER),
944      IQ_CONFIG, lambda ctx, inst: (constants.QRFS_NORMAL, inst.beparams)),
945     (_MakeField("custom_nicparams", "CustomNicParameters",
946                 constants.QFT_OTHER),
947      IQ_CONFIG, lambda ctx, inst: (constants.QRFS_NORMAL,
948                                    [nic.nicparams for nic in inst.nics])),
949     ]
950
951   # HV params
952   def _GetInstHvParam(name):
953     return lambda ctx, _: (constants.QRFS_NORMAL,
954                            ctx.inst_hvparams.get(name, None))
955
956   fields.extend([
957     # For now all hypervisor parameters are exported as QFT_OTHER
958     (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
959                 constants.QFT_OTHER),
960      IQ_CONFIG, _GetInstHvParam(name))
961     for name in constants.HVS_PARAMETERS
962     if name not in constants.HVC_GLOBALS
963     ])
964
965   # BE params
966   def _GetInstBeParam(name):
967     return lambda ctx, _: (constants.QRFS_NORMAL,
968                            ctx.inst_beparams.get(name, None))
969
970   fields.extend([
971     # For now all backend parameters are exported as QFT_OTHER
972     (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
973                 constants.QFT_OTHER),
974      IQ_CONFIG, _GetInstBeParam(name))
975     for name in constants.BES_PARAMETERS
976     ])
977
978   return fields
979
980
981 _INST_SIMPLE_FIELDS = {
982   "disk_template": ("Disk_template", constants.QFT_TEXT),
983   "hypervisor": ("Hypervisor", constants.QFT_TEXT),
984   "name": ("Node", constants.QFT_TEXT),
985   # Depending on the hypervisor, the port can be None
986   "network_port": ("Network_port", constants.QFT_OTHER),
987   "os": ("OS", constants.QFT_TEXT),
988   "serial_no": ("SerialNo", constants.QFT_NUMBER),
989   "uuid": ("UUID", constants.QFT_TEXT),
990   }
991
992
993 def _BuildInstanceFields():
994   """Builds list of fields for instance queries.
995
996   """
997   fields = [
998     (_MakeField("pnode", "Primary_node", constants.QFT_TEXT), IQ_CONFIG,
999      lambda ctx, inst: (constants.QRFS_NORMAL, inst.primary_node)),
1000     (_MakeField("snodes", "Secondary_Nodes", constants.QFT_OTHER), IQ_CONFIG,
1001      lambda ctx, inst: (constants.QRFS_NORMAL, list(inst.secondary_nodes))),
1002     (_MakeField("admin_state", "Autostart", constants.QFT_BOOL), IQ_CONFIG,
1003      lambda ctx, inst: (constants.QRFS_NORMAL, inst.admin_up)),
1004     (_MakeField("tags", "Tags", constants.QFT_OTHER), IQ_CONFIG,
1005      lambda ctx, inst: (constants.QRFS_NORMAL, list(inst.GetTags()))),
1006     ]
1007
1008   # Add simple fields
1009   fields.extend([(_MakeField(name, title, kind), IQ_CONFIG, _GetItemAttr(name))
1010                  for (name, (title, kind)) in _INST_SIMPLE_FIELDS.items()])
1011
1012   # Fields requiring talking to the node
1013   fields.extend([
1014     (_MakeField("oper_state", "Running", constants.QFT_BOOL), IQ_LIVE,
1015      _GetInstOperState),
1016     (_MakeField("oper_ram", "RuntimeMemory", constants.QFT_UNIT), IQ_LIVE,
1017      _GetInstLiveData("memory")),
1018     (_MakeField("oper_vcpus", "RuntimeVCPUs", constants.QFT_NUMBER), IQ_LIVE,
1019      _GetInstLiveData("vcpus")),
1020     (_MakeField("status", "Status", constants.QFT_TEXT), IQ_LIVE,
1021      _GetInstStatus),
1022     ])
1023
1024   fields.extend(_GetInstanceParameterFields())
1025   fields.extend(_GetInstanceDiskFields())
1026   fields.extend(_GetInstanceNetworkFields())
1027   fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1028
1029   return _PrepareFieldList(fields)
1030
1031
1032 class LockQueryData:
1033   """Data container for lock data queries.
1034
1035   """
1036   def __init__(self, lockdata):
1037     """Initializes this class.
1038
1039     """
1040     self.lockdata = lockdata
1041
1042   def __iter__(self):
1043     """Iterate over all locks.
1044
1045     """
1046     return iter(self.lockdata)
1047
1048
1049 def _GetLockOwners(_, data):
1050   """Returns a sorted list of a lock's current owners.
1051
1052   """
1053   (_, _, owners, _) = data
1054
1055   if owners:
1056     owners = utils.NiceSort(owners)
1057
1058   return (constants.QRFS_NORMAL, owners)
1059
1060
1061 def _GetLockPending(_, data):
1062   """Returns a sorted list of a lock's pending acquires.
1063
1064   """
1065   (_, _, _, pending) = data
1066
1067   if pending:
1068     pending = [(mode, utils.NiceSort(names))
1069                for (mode, names) in pending]
1070
1071   return (constants.QRFS_NORMAL, pending)
1072
1073
1074 def _BuildLockFields():
1075   """Builds list of fields for lock queries.
1076
1077   """
1078   return _PrepareFieldList([
1079     (_MakeField("name", "Name", constants.QFT_TEXT), None,
1080      lambda ctx, (name, mode, owners, pending): (constants.QRFS_NORMAL, name)),
1081     (_MakeField("mode", "Mode", constants.QFT_OTHER), LQ_MODE,
1082      lambda ctx, (name, mode, owners, pending): (constants.QRFS_NORMAL, mode)),
1083     (_MakeField("owner", "Owner", constants.QFT_OTHER), LQ_OWNER,
1084      _GetLockOwners),
1085     (_MakeField("pending", "Pending", constants.QFT_OTHER), LQ_PENDING,
1086      _GetLockPending),
1087     ])
1088
1089
1090 class GroupQueryData:
1091   """Data container for node group data queries.
1092
1093   """
1094   def __init__(self, groups, group_to_nodes, group_to_instances):
1095     """Initializes this class.
1096
1097     @param groups: List of node group objects
1098     @type group_to_nodes: dict; group UUID as key
1099     @param group_to_nodes: Per-group list of nodes
1100     @type group_to_instances: dict; group UUID as key
1101     @param group_to_instances: Per-group list of (primary) instances
1102
1103     """
1104     self.groups = groups
1105     self.group_to_nodes = group_to_nodes
1106     self.group_to_instances = group_to_instances
1107
1108   def __iter__(self):
1109     """Iterate over all node groups.
1110
1111     """
1112     return iter(self.groups)
1113
1114
1115 _GROUP_SIMPLE_FIELDS = {
1116   "alloc_policy": ("AllocPolicy", constants.QFT_TEXT),
1117   "name": ("Group", constants.QFT_TEXT),
1118   "serial_no": ("SerialNo", constants.QFT_NUMBER),
1119   "uuid": ("UUID", constants.QFT_TEXT),
1120   }
1121
1122
1123 def _BuildGroupFields():
1124   """Builds list of fields for node group queries.
1125
1126   """
1127   # Add simple fields
1128   fields = [(_MakeField(name, title, kind), GQ_CONFIG, _GetItemAttr(name))
1129             for (name, (title, kind)) in _GROUP_SIMPLE_FIELDS.items()]
1130
1131   def _GetLength(getter):
1132     return lambda ctx, group: (constants.QRFS_NORMAL,
1133                                len(getter(ctx)[group.uuid]))
1134
1135   def _GetSortedList(getter):
1136     return lambda ctx, group: (constants.QRFS_NORMAL,
1137                                utils.NiceSort(getter(ctx)[group.uuid]))
1138
1139   group_to_nodes = operator.attrgetter("group_to_nodes")
1140   group_to_instances = operator.attrgetter("group_to_instances")
1141
1142   # Add fields for nodes
1143   fields.extend([
1144     (_MakeField("node_cnt", "Nodes", constants.QFT_NUMBER),
1145      GQ_NODE, _GetLength(group_to_nodes)),
1146     (_MakeField("node_list", "NodeList", constants.QFT_OTHER),
1147      GQ_NODE, _GetSortedList(group_to_nodes)),
1148     ])
1149
1150   # Add fields for instances
1151   fields.extend([
1152     (_MakeField("pinst_cnt", "Instances", constants.QFT_NUMBER),
1153      GQ_INST, _GetLength(group_to_instances)),
1154     (_MakeField("pinst_list", "InstanceList", constants.QFT_OTHER),
1155      GQ_INST, _GetSortedList(group_to_instances)),
1156     ])
1157
1158   fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1159
1160   return _PrepareFieldList(fields)
1161
1162
1163 #: Fields available for node queries
1164 NODE_FIELDS = _BuildNodeFields()
1165
1166 #: Fields available for instance queries
1167 INSTANCE_FIELDS = _BuildInstanceFields()
1168
1169 #: Fields available for lock queries
1170 LOCK_FIELDS = _BuildLockFields()
1171
1172 #: Fields available for node group queries
1173 GROUP_FIELDS = _BuildGroupFields()
1174
1175 #: All available field lists
1176 ALL_FIELD_LISTS = [NODE_FIELDS, INSTANCE_FIELDS, LOCK_FIELDS, GROUP_FIELDS]