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