Fix handling of ^C in the CLI scripts
[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 "M"
385   elif node.master_candidate:
386     return "C"
387   elif node.drained:
388     return "D"
389   elif node.offline:
390     return "O"
391   else:
392     return "R"
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 _NODE_LIVE_FIELDS = {
491   "bootid": ("BootID", QFT_TEXT, "bootid"),
492   "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes"),
493   "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets"),
494   "ctotal": ("CTotal", QFT_NUMBER, "cpu_total"),
495   "dfree": ("DFree", QFT_UNIT, "vg_free"),
496   "dtotal": ("DTotal", QFT_UNIT, "vg_size"),
497   "mfree": ("MFree", QFT_UNIT, "memory_free"),
498   "mnode": ("MNode", QFT_UNIT, "memory_dom0"),
499   "mtotal": ("MTotal", QFT_UNIT, "memory_total"),
500   }
501
502
503 def _GetGroup(cb):
504   """Build function for calling another function with an node group.
505
506   @param cb: The callback to be called with the nodegroup
507
508   """
509   def fn(ctx, node):
510     """Get group data for a node.
511
512     @type ctx: L{NodeQueryData}
513     @type inst: L{objects.Node}
514     @param inst: Node object
515
516     """
517     ng = ctx.groups.get(node.group, None)
518     if ng is None:
519       # Nodes always have a group, or the configuration is corrupt
520       return _FS_UNAVAIL
521
522     return cb(ctx, node, ng)
523
524   return fn
525
526
527 def _GetNodeGroup(ctx, node, ng): # pylint: disable-msg=W0613
528   """Returns the name of a node's group.
529
530   @type ctx: L{NodeQueryData}
531   @type node: L{objects.Node}
532   @param node: Node object
533   @type ng: L{objects.NodeGroup}
534   @param ng: The node group this node belongs to
535
536   """
537   return ng.name
538
539
540 def _GetNodePower(ctx, node):
541   """Returns the node powered state
542
543   @type ctx: L{NodeQueryData}
544   @type node: L{objects.Node}
545   @param node: Node object
546
547   """
548   if ctx.oob_support[node.name]:
549     return node.powered
550
551   return _FS_UNAVAIL
552
553
554 def _GetNdParams(ctx, node, ng):
555   """Returns the ndparams for this node.
556
557   @type ctx: L{NodeQueryData}
558   @type node: L{objects.Node}
559   @param node: Node object
560   @type ng: L{objects.NodeGroup}
561   @param ng: The node group this node belongs to
562
563   """
564   return ctx.cluster.SimpleFillND(ng.FillND(node))
565
566
567 def _GetLiveNodeField(field, kind, ctx, node):
568   """Gets the value of a "live" field from L{NodeQueryData}.
569
570   @param field: Live field name
571   @param kind: Data kind, one of L{constants.QFT_ALL}
572   @type ctx: L{NodeQueryData}
573   @type node: L{objects.Node}
574   @param node: Node object
575
576   """
577   if node.offline:
578     return _FS_OFFLINE
579
580   if not ctx.curlive_data:
581     return _FS_NODATA
582
583   try:
584     value = ctx.curlive_data[field]
585   except KeyError:
586     return _FS_UNAVAIL
587
588   if kind == QFT_TEXT:
589     return value
590
591   assert kind in (QFT_NUMBER, QFT_UNIT)
592
593   # Try to convert into number
594   try:
595     return int(value)
596   except (ValueError, TypeError):
597     logging.exception("Failed to convert node field '%s' (value %r) to int",
598                       value, field)
599     return _FS_UNAVAIL
600
601
602 def _BuildNodeFields():
603   """Builds list of fields for node queries.
604
605   """
606   fields = [
607     (_MakeField("pip", "PrimaryIP", QFT_TEXT), NQ_CONFIG,
608      _GetItemAttr("primary_ip")),
609     (_MakeField("sip", "SecondaryIP", QFT_TEXT), NQ_CONFIG,
610      _GetItemAttr("secondary_ip")),
611     (_MakeField("tags", "Tags", QFT_OTHER), NQ_CONFIG,
612      lambda ctx, node: list(node.GetTags())),
613     (_MakeField("master", "IsMaster", QFT_BOOL), NQ_CONFIG,
614      lambda ctx, node: node.name == ctx.master_name),
615     (_MakeField("role", "Role", QFT_TEXT), NQ_CONFIG,
616      lambda ctx, node: _GetNodeRole(node, ctx.master_name)),
617     (_MakeField("group", "Group", QFT_TEXT), NQ_GROUP,
618      _GetGroup(_GetNodeGroup)),
619     (_MakeField("group.uuid", "GroupUUID", QFT_TEXT),
620      NQ_CONFIG, _GetItemAttr("group")),
621     (_MakeField("powered", "Powered", QFT_BOOL), NQ_OOB, _GetNodePower),
622     (_MakeField("ndparams", "NodeParameters", QFT_OTHER), NQ_GROUP,
623       _GetGroup(_GetNdParams)),
624     (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER),
625       NQ_GROUP, _GetItemAttr("ndparams")),
626     ]
627
628   def _GetLength(getter):
629     return lambda ctx, node: len(getter(ctx)[node.name])
630
631   def _GetList(getter):
632     return lambda ctx, node: list(getter(ctx)[node.name])
633
634   # Add fields operating on instance lists
635   for prefix, titleprefix, getter in \
636       [("p", "Pri", operator.attrgetter("node_to_primary")),
637        ("s", "Sec", operator.attrgetter("node_to_secondary"))]:
638     fields.extend([
639       (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER),
640        NQ_INST, _GetLength(getter)),
641       (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
642                   QFT_OTHER),
643        NQ_INST, _GetList(getter)),
644       ])
645
646   # Add simple fields
647   fields.extend([(_MakeField(name, title, kind), NQ_CONFIG, _GetItemAttr(name))
648                  for (name, (title, kind)) in _NODE_SIMPLE_FIELDS.items()])
649
650   # Add fields requiring live data
651   fields.extend([
652     (_MakeField(name, title, kind), NQ_LIVE,
653      compat.partial(_GetLiveNodeField, nfield, kind))
654     for (name, (title, kind, nfield)) in _NODE_LIVE_FIELDS.items()
655     ])
656
657   # Add timestamps
658   fields.extend(_GetItemTimestampFields(NQ_CONFIG))
659
660   return _PrepareFieldList(fields, [])
661
662
663 class InstanceQueryData:
664   """Data container for instance data queries.
665
666   """
667   def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
668                live_data, wrongnode_inst, console):
669     """Initializes this class.
670
671     @param instances: List of instance objects
672     @param cluster: Cluster object
673     @type disk_usage: dict; instance name as key
674     @param disk_usage: Per-instance disk usage
675     @type offline_nodes: list of strings
676     @param offline_nodes: List of offline nodes
677     @type bad_nodes: list of strings
678     @param bad_nodes: List of faulty nodes
679     @type live_data: dict; instance name as key
680     @param live_data: Per-instance live data
681     @type wrongnode_inst: set
682     @param wrongnode_inst: Set of instances running on wrong node(s)
683     @type console: dict; instance name as key
684     @param console: Per-instance console information
685
686     """
687     assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
688            "Offline nodes not included in bad nodes"
689     assert not (set(live_data.keys()) & set(bad_nodes)), \
690            "Found live data for bad or offline nodes"
691
692     self.instances = instances
693     self.cluster = cluster
694     self.disk_usage = disk_usage
695     self.offline_nodes = offline_nodes
696     self.bad_nodes = bad_nodes
697     self.live_data = live_data
698     self.wrongnode_inst = wrongnode_inst
699     self.console = console
700
701     # Used for individual rows
702     self.inst_hvparams = None
703     self.inst_beparams = None
704     self.inst_nicparams = None
705
706   def __iter__(self):
707     """Iterate over all instances.
708
709     This function has side-effects and only one instance of the resulting
710     generator should be used at a time.
711
712     """
713     for inst in self.instances:
714       self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
715       self.inst_beparams = self.cluster.FillBE(inst)
716       self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
717                              for nic in inst.nics]
718
719       yield inst
720
721
722 def _GetInstOperState(ctx, inst):
723   """Get instance's operational status.
724
725   @type ctx: L{InstanceQueryData}
726   @type inst: L{objects.Instance}
727   @param inst: Instance object
728
729   """
730   # Can't use RS_OFFLINE here as it would describe the instance to
731   # be offline when we actually don't know due to missing data
732   if inst.primary_node in ctx.bad_nodes:
733     return _FS_NODATA
734   else:
735     return bool(ctx.live_data.get(inst.name))
736
737
738 def _GetInstLiveData(name):
739   """Build function for retrieving live data.
740
741   @type name: string
742   @param name: Live data field name
743
744   """
745   def fn(ctx, inst):
746     """Get live data for an instance.
747
748     @type ctx: L{InstanceQueryData}
749     @type inst: L{objects.Instance}
750     @param inst: Instance object
751
752     """
753     if (inst.primary_node in ctx.bad_nodes or
754         inst.primary_node in ctx.offline_nodes):
755       # Can't use RS_OFFLINE here as it would describe the instance to be
756       # offline when we actually don't know due to missing data
757       return _FS_NODATA
758
759     if inst.name in ctx.live_data:
760       data = ctx.live_data[inst.name]
761       if name in data:
762         return data[name]
763
764     return _FS_UNAVAIL
765
766   return fn
767
768
769 def _GetInstStatus(ctx, inst):
770   """Get instance status.
771
772   @type ctx: L{InstanceQueryData}
773   @type inst: L{objects.Instance}
774   @param inst: Instance object
775
776   """
777   if inst.primary_node in ctx.offline_nodes:
778     return "ERROR_nodeoffline"
779
780   if inst.primary_node in ctx.bad_nodes:
781     return "ERROR_nodedown"
782
783   if bool(ctx.live_data.get(inst.name)):
784     if inst.name in ctx.wrongnode_inst:
785       return "ERROR_wrongnode"
786     elif inst.admin_up:
787       return "running"
788     else:
789       return "ERROR_up"
790
791   if inst.admin_up:
792     return "ERROR_down"
793
794   return "ADMIN_down"
795
796
797 def _GetInstDiskSize(index):
798   """Build function for retrieving disk size.
799
800   @type index: int
801   @param index: Disk index
802
803   """
804   def fn(_, inst):
805     """Get size of a disk.
806
807     @type inst: L{objects.Instance}
808     @param inst: Instance object
809
810     """
811     try:
812       return inst.disks[index].size
813     except IndexError:
814       return _FS_UNAVAIL
815
816   return fn
817
818
819 def _GetInstNic(index, cb):
820   """Build function for calling another function with an instance NIC.
821
822   @type index: int
823   @param index: NIC index
824   @type cb: callable
825   @param cb: Callback
826
827   """
828   def fn(ctx, inst):
829     """Call helper function with instance NIC.
830
831     @type ctx: L{InstanceQueryData}
832     @type inst: L{objects.Instance}
833     @param inst: Instance object
834
835     """
836     try:
837       nic = inst.nics[index]
838     except IndexError:
839       return _FS_UNAVAIL
840
841     return cb(ctx, index, nic)
842
843   return fn
844
845
846 def _GetInstNicIp(ctx, _, nic): # pylint: disable-msg=W0613
847   """Get a NIC's IP address.
848
849   @type ctx: L{InstanceQueryData}
850   @type nic: L{objects.NIC}
851   @param nic: NIC object
852
853   """
854   if nic.ip is None:
855     return _FS_UNAVAIL
856   else:
857     return nic.ip
858
859
860 def _GetInstNicBridge(ctx, index, _):
861   """Get a NIC's bridge.
862
863   @type ctx: L{InstanceQueryData}
864   @type index: int
865   @param index: NIC index
866
867   """
868   assert len(ctx.inst_nicparams) >= index
869
870   nicparams = ctx.inst_nicparams[index]
871
872   if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
873     return nicparams[constants.NIC_LINK]
874   else:
875     return _FS_UNAVAIL
876
877
878 def _GetInstAllNicBridges(ctx, inst):
879   """Get all network bridges for an instance.
880
881   @type ctx: L{InstanceQueryData}
882   @type inst: L{objects.Instance}
883   @param inst: Instance object
884
885   """
886   assert len(ctx.inst_nicparams) == len(inst.nics)
887
888   result = []
889
890   for nicp in ctx.inst_nicparams:
891     if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
892       result.append(nicp[constants.NIC_LINK])
893     else:
894       result.append(None)
895
896   assert len(result) == len(inst.nics)
897
898   return result
899
900
901 def _GetInstNicParam(name):
902   """Build function for retrieving a NIC parameter.
903
904   @type name: string
905   @param name: Parameter name
906
907   """
908   def fn(ctx, index, _):
909     """Get a NIC's bridge.
910
911     @type ctx: L{InstanceQueryData}
912     @type inst: L{objects.Instance}
913     @param inst: Instance object
914     @type nic: L{objects.NIC}
915     @param nic: NIC object
916
917     """
918     assert len(ctx.inst_nicparams) >= index
919     return ctx.inst_nicparams[index][name]
920
921   return fn
922
923
924 def _GetInstanceNetworkFields():
925   """Get instance fields involving network interfaces.
926
927   @return: List of field definitions used as input for L{_PrepareFieldList}
928
929   """
930   nic_mac_fn = lambda ctx, _, nic: nic.mac
931   nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
932   nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
933
934   fields = [
935     # First NIC (legacy)
936     (_MakeField("ip", "IP_address", QFT_TEXT), IQ_CONFIG,
937      _GetInstNic(0, _GetInstNicIp)),
938     (_MakeField("mac", "MAC_address", QFT_TEXT), IQ_CONFIG,
939      _GetInstNic(0, nic_mac_fn)),
940     (_MakeField("bridge", "Bridge", QFT_TEXT), IQ_CONFIG,
941      _GetInstNic(0, _GetInstNicBridge)),
942     (_MakeField("nic_mode", "NIC_Mode", QFT_TEXT), IQ_CONFIG,
943      _GetInstNic(0, nic_mode_fn)),
944     (_MakeField("nic_link", "NIC_Link", QFT_TEXT), IQ_CONFIG,
945      _GetInstNic(0, nic_link_fn)),
946
947     # All NICs
948     (_MakeField("nic.count", "NICs", QFT_NUMBER), IQ_CONFIG,
949      lambda ctx, inst: len(inst.nics)),
950     (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER), IQ_CONFIG,
951      lambda ctx, inst: [nic.mac for nic in inst.nics]),
952     (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER), IQ_CONFIG,
953      lambda ctx, inst: [nic.ip for nic in inst.nics]),
954     (_MakeField("nic.modes", "NIC_modes", QFT_OTHER), IQ_CONFIG,
955      lambda ctx, inst: [nicp[constants.NIC_MODE]
956                         for nicp in ctx.inst_nicparams]),
957     (_MakeField("nic.links", "NIC_links", QFT_OTHER), IQ_CONFIG,
958      lambda ctx, inst: [nicp[constants.NIC_LINK]
959                         for nicp in ctx.inst_nicparams]),
960     (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER), IQ_CONFIG,
961      _GetInstAllNicBridges),
962     ]
963
964   # NICs by number
965   for i in range(constants.MAX_NICS):
966     fields.extend([
967       (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT),
968        IQ_CONFIG, _GetInstNic(i, _GetInstNicIp)),
969       (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT),
970        IQ_CONFIG, _GetInstNic(i, nic_mac_fn)),
971       (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT),
972        IQ_CONFIG, _GetInstNic(i, nic_mode_fn)),
973       (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT),
974        IQ_CONFIG, _GetInstNic(i, nic_link_fn)),
975       (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT),
976        IQ_CONFIG, _GetInstNic(i, _GetInstNicBridge)),
977       ])
978
979   return fields
980
981
982 def _GetInstDiskUsage(ctx, inst):
983   """Get disk usage for an instance.
984
985   @type ctx: L{InstanceQueryData}
986   @type inst: L{objects.Instance}
987   @param inst: Instance object
988
989   """
990   usage = ctx.disk_usage[inst.name]
991
992   if usage is None:
993     usage = 0
994
995   return usage
996
997
998 def _GetInstanceConsole(ctx, inst):
999   """Get console information for instance.
1000
1001   @type ctx: L{InstanceQueryData}
1002   @type inst: L{objects.Instance}
1003   @param inst: Instance object
1004
1005   """
1006   consinfo = ctx.console[inst.name]
1007
1008   if consinfo is None:
1009     return _FS_UNAVAIL
1010
1011   return consinfo
1012
1013
1014 def _GetInstanceDiskFields():
1015   """Get instance fields involving disks.
1016
1017   @return: List of field definitions used as input for L{_PrepareFieldList}
1018
1019   """
1020   fields = [
1021     (_MakeField("disk_usage", "DiskUsage", QFT_UNIT), IQ_DISKUSAGE,
1022      _GetInstDiskUsage),
1023     (_MakeField("disk.count", "Disks", QFT_NUMBER), IQ_CONFIG,
1024      lambda ctx, inst: len(inst.disks)),
1025     (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER), IQ_CONFIG,
1026      lambda ctx, inst: [disk.size for disk in inst.disks]),
1027     ]
1028
1029   # Disks by number
1030   fields.extend([
1031     (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT),
1032      IQ_CONFIG, _GetInstDiskSize(i))
1033     for i in range(constants.MAX_DISKS)
1034     ])
1035
1036   return fields
1037
1038
1039 def _GetInstanceParameterFields():
1040   """Get instance fields involving parameters.
1041
1042   @return: List of field definitions used as input for L{_PrepareFieldList}
1043
1044   """
1045   # TODO: Consider moving titles closer to constants
1046   be_title = {
1047     constants.BE_AUTO_BALANCE: "Auto_balance",
1048     constants.BE_MEMORY: "ConfigMemory",
1049     constants.BE_VCPUS: "ConfigVCPUs",
1050     }
1051
1052   hv_title = {
1053     constants.HV_ACPI: "ACPI",
1054     constants.HV_BOOT_ORDER: "Boot_order",
1055     constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1056     constants.HV_DISK_TYPE: "Disk_type",
1057     constants.HV_INITRD_PATH: "Initrd_path",
1058     constants.HV_KERNEL_PATH: "Kernel_path",
1059     constants.HV_NIC_TYPE: "NIC_type",
1060     constants.HV_PAE: "PAE",
1061     constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1062     }
1063
1064   fields = [
1065     # Filled parameters
1066     (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER),
1067      IQ_CONFIG, lambda ctx, _: ctx.inst_hvparams),
1068     (_MakeField("beparams", "BackendParameters", QFT_OTHER),
1069      IQ_CONFIG, lambda ctx, _: ctx.inst_beparams),
1070
1071     # Unfilled parameters
1072     (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER),
1073      IQ_CONFIG, _GetItemAttr("hvparams")),
1074     (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER),
1075      IQ_CONFIG, _GetItemAttr("beparams")),
1076     (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER),
1077      IQ_CONFIG, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1078     ]
1079
1080   # HV params
1081   def _GetInstHvParam(name):
1082     return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1083
1084   fields.extend([
1085     (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1086                 _VTToQFT[kind]),
1087      IQ_CONFIG, _GetInstHvParam(name))
1088     for name, kind in constants.HVS_PARAMETER_TYPES.items()
1089     if name not in constants.HVC_GLOBALS
1090     ])
1091
1092   # BE params
1093   def _GetInstBeParam(name):
1094     return lambda ctx, _: ctx.inst_beparams.get(name, None)
1095
1096   fields.extend([
1097     (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1098                 _VTToQFT[kind]), IQ_CONFIG,
1099      _GetInstBeParam(name))
1100     for name, kind in constants.BES_PARAMETER_TYPES.items()
1101     ])
1102
1103   return fields
1104
1105
1106 _INST_SIMPLE_FIELDS = {
1107   "disk_template": ("Disk_template", QFT_TEXT),
1108   "hypervisor": ("Hypervisor", QFT_TEXT),
1109   "name": ("Node", QFT_TEXT),
1110   # Depending on the hypervisor, the port can be None
1111   "network_port": ("Network_port", QFT_OTHER),
1112   "os": ("OS", QFT_TEXT),
1113   "serial_no": ("SerialNo", QFT_NUMBER),
1114   "uuid": ("UUID", QFT_TEXT),
1115   }
1116
1117
1118 def _BuildInstanceFields():
1119   """Builds list of fields for instance queries.
1120
1121   """
1122   fields = [
1123     (_MakeField("pnode", "Primary_node", QFT_TEXT), IQ_CONFIG,
1124      _GetItemAttr("primary_node")),
1125     (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER), IQ_CONFIG,
1126      lambda ctx, inst: list(inst.secondary_nodes)),
1127     (_MakeField("admin_state", "Autostart", QFT_BOOL), IQ_CONFIG,
1128      _GetItemAttr("admin_up")),
1129     (_MakeField("tags", "Tags", QFT_OTHER), IQ_CONFIG,
1130      lambda ctx, inst: list(inst.GetTags())),
1131     (_MakeField("console", "Console", QFT_OTHER), IQ_CONSOLE,
1132      _GetInstanceConsole),
1133     ]
1134
1135   # Add simple fields
1136   fields.extend([(_MakeField(name, title, kind), IQ_CONFIG, _GetItemAttr(name))
1137                  for (name, (title, kind)) in _INST_SIMPLE_FIELDS.items()])
1138
1139   # Fields requiring talking to the node
1140   fields.extend([
1141     (_MakeField("oper_state", "Running", QFT_BOOL), IQ_LIVE,
1142      _GetInstOperState),
1143     (_MakeField("oper_ram", "Memory", QFT_UNIT), IQ_LIVE,
1144      _GetInstLiveData("memory")),
1145     (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER), IQ_LIVE,
1146      _GetInstLiveData("vcpus")),
1147     (_MakeField("status", "Status", QFT_TEXT), IQ_LIVE, _GetInstStatus),
1148     ])
1149
1150   fields.extend(_GetInstanceParameterFields())
1151   fields.extend(_GetInstanceDiskFields())
1152   fields.extend(_GetInstanceNetworkFields())
1153   fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1154
1155   aliases = [
1156     ("vcpus", "be/vcpus"),
1157     ("sda_size", "disk.size/0"),
1158     ("sdb_size", "disk.size/1"),
1159     ]
1160
1161   return _PrepareFieldList(fields, aliases)
1162
1163
1164 class LockQueryData:
1165   """Data container for lock data queries.
1166
1167   """
1168   def __init__(self, lockdata):
1169     """Initializes this class.
1170
1171     """
1172     self.lockdata = lockdata
1173
1174   def __iter__(self):
1175     """Iterate over all locks.
1176
1177     """
1178     return iter(self.lockdata)
1179
1180
1181 def _GetLockOwners(_, data):
1182   """Returns a sorted list of a lock's current owners.
1183
1184   """
1185   (_, _, owners, _) = data
1186
1187   if owners:
1188     owners = utils.NiceSort(owners)
1189
1190   return owners
1191
1192
1193 def _GetLockPending(_, data):
1194   """Returns a sorted list of a lock's pending acquires.
1195
1196   """
1197   (_, _, _, pending) = data
1198
1199   if pending:
1200     pending = [(mode, utils.NiceSort(names))
1201                for (mode, names) in pending]
1202
1203   return pending
1204
1205
1206 def _BuildLockFields():
1207   """Builds list of fields for lock queries.
1208
1209   """
1210   return _PrepareFieldList([
1211     (_MakeField("name", "Name", QFT_TEXT), None,
1212      lambda ctx, (name, mode, owners, pending): name),
1213     (_MakeField("mode", "Mode", QFT_OTHER), LQ_MODE,
1214      lambda ctx, (name, mode, owners, pending): mode),
1215     (_MakeField("owner", "Owner", QFT_OTHER), LQ_OWNER, _GetLockOwners),
1216     (_MakeField("pending", "Pending", QFT_OTHER), LQ_PENDING, _GetLockPending),
1217     ], [])
1218
1219
1220 class GroupQueryData:
1221   """Data container for node group data queries.
1222
1223   """
1224   def __init__(self, groups, group_to_nodes, group_to_instances):
1225     """Initializes this class.
1226
1227     @param groups: List of node group objects
1228     @type group_to_nodes: dict; group UUID as key
1229     @param group_to_nodes: Per-group list of nodes
1230     @type group_to_instances: dict; group UUID as key
1231     @param group_to_instances: Per-group list of (primary) instances
1232
1233     """
1234     self.groups = groups
1235     self.group_to_nodes = group_to_nodes
1236     self.group_to_instances = group_to_instances
1237
1238   def __iter__(self):
1239     """Iterate over all node groups.
1240
1241     """
1242     return iter(self.groups)
1243
1244
1245 _GROUP_SIMPLE_FIELDS = {
1246   "alloc_policy": ("AllocPolicy", QFT_TEXT),
1247   "name": ("Group", QFT_TEXT),
1248   "serial_no": ("SerialNo", QFT_NUMBER),
1249   "uuid": ("UUID", QFT_TEXT),
1250   "ndparams": ("NDParams", QFT_OTHER),
1251   }
1252
1253
1254 def _BuildGroupFields():
1255   """Builds list of fields for node group queries.
1256
1257   """
1258   # Add simple fields
1259   fields = [(_MakeField(name, title, kind), GQ_CONFIG, _GetItemAttr(name))
1260             for (name, (title, kind)) in _GROUP_SIMPLE_FIELDS.items()]
1261
1262   def _GetLength(getter):
1263     return lambda ctx, group: len(getter(ctx)[group.uuid])
1264
1265   def _GetSortedList(getter):
1266     return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
1267
1268   group_to_nodes = operator.attrgetter("group_to_nodes")
1269   group_to_instances = operator.attrgetter("group_to_instances")
1270
1271   # Add fields for nodes
1272   fields.extend([
1273     (_MakeField("node_cnt", "Nodes", QFT_NUMBER),
1274      GQ_NODE, _GetLength(group_to_nodes)),
1275     (_MakeField("node_list", "NodeList", QFT_OTHER),
1276      GQ_NODE, _GetSortedList(group_to_nodes)),
1277     ])
1278
1279   # Add fields for instances
1280   fields.extend([
1281     (_MakeField("pinst_cnt", "Instances", QFT_NUMBER),
1282      GQ_INST, _GetLength(group_to_instances)),
1283     (_MakeField("pinst_list", "InstanceList", QFT_OTHER),
1284      GQ_INST, _GetSortedList(group_to_instances)),
1285     ])
1286
1287   fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1288
1289   return _PrepareFieldList(fields, [])
1290
1291
1292 #: Fields available for node queries
1293 NODE_FIELDS = _BuildNodeFields()
1294
1295 #: Fields available for instance queries
1296 INSTANCE_FIELDS = _BuildInstanceFields()
1297
1298 #: Fields available for lock queries
1299 LOCK_FIELDS = _BuildLockFields()
1300
1301 #: Fields available for node group queries
1302 GROUP_FIELDS = _BuildGroupFields()
1303
1304 #: All available field lists
1305 ALL_FIELD_LISTS = [NODE_FIELDS, INSTANCE_FIELDS, LOCK_FIELDS, GROUP_FIELDS]