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