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