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