gnt-cluster modify: Add "--submit" option
[ganeti-local] / lib / query.py
1 #
2 #
3
4 # Copyright (C) 2010, 2011, 2012 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           - Human-readable description, must not end with punctuation or
36             contain newlines
37       - Data request type, see e.g. C{NQ_*}
38       - OR-ed flags, see C{QFF_*}
39       - A retrieval function, see L{Query.__init__} for description
40     - Pass list of fields through L{_PrepareFieldList} for preparation and
41       checks
42   - Instantiate L{Query} with prepared field list definition and selected fields
43   - Call L{Query.RequestedData} to determine what data to collect/compute
44   - Call L{Query.Query} or L{Query.OldStyleQuery} with collected data and use
45     result
46       - Data container must support iteration using C{__iter__}
47       - Items are passed to retrieval functions and can have any format
48   - Call L{Query.GetFields} to get list of definitions for selected fields
49
50 @attention: Retrieval functions must be idempotent. They can be called multiple
51   times, in any order and any number of times.
52
53 """
54
55 import logging
56 import operator
57 import re
58
59 from ganeti import constants
60 from ganeti import errors
61 from ganeti import utils
62 from ganeti import compat
63 from ganeti import objects
64 from ganeti import ht
65 from ganeti import qlang
66
67 from ganeti.constants import (QFT_UNKNOWN, QFT_TEXT, QFT_BOOL, QFT_NUMBER,
68                               QFT_UNIT, QFT_TIMESTAMP, QFT_OTHER,
69                               RS_NORMAL, RS_UNKNOWN, RS_NODATA,
70                               RS_UNAVAIL, RS_OFFLINE)
71
72
73 # Constants for requesting data from the caller/data provider. Each property
74 # collected/computed separately by the data provider should have its own to
75 # only collect the requested data and not more.
76
77 (NQ_CONFIG,
78  NQ_INST,
79  NQ_LIVE,
80  NQ_GROUP,
81  NQ_OOB) = range(1, 6)
82
83 (IQ_CONFIG,
84  IQ_LIVE,
85  IQ_DISKUSAGE,
86  IQ_CONSOLE,
87  IQ_NODES) = range(100, 105)
88
89 (LQ_MODE,
90  LQ_OWNER,
91  LQ_PENDING) = range(10, 13)
92
93 (GQ_CONFIG,
94  GQ_NODE,
95  GQ_INST) = range(200, 203)
96
97 # Query field flags
98 QFF_HOSTNAME = 0x01
99 QFF_IP_ADDRESS = 0x02
100 # Next values: 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200
101 QFF_ALL = (QFF_HOSTNAME | QFF_IP_ADDRESS)
102
103 FIELD_NAME_RE = re.compile(r"^[a-z0-9/._]+$")
104 TITLE_RE = re.compile(r"^[^\s]+$")
105 DOC_RE = re.compile(r"^[A-Z].*[^.,?!]$")
106
107 #: Verification function for each field type
108 _VERIFY_FN = {
109   QFT_UNKNOWN: ht.TNone,
110   QFT_TEXT: ht.TString,
111   QFT_BOOL: ht.TBool,
112   QFT_NUMBER: ht.TInt,
113   QFT_UNIT: ht.TInt,
114   QFT_TIMESTAMP: ht.TNumber,
115   QFT_OTHER: lambda _: True,
116   }
117
118 # Unique objects for special field statuses
119 _FS_UNKNOWN = object()
120 _FS_NODATA = object()
121 _FS_UNAVAIL = object()
122 _FS_OFFLINE = object()
123
124 #: List of all special status
125 _FS_ALL = frozenset([_FS_UNKNOWN, _FS_NODATA, _FS_UNAVAIL, _FS_OFFLINE])
126
127 #: VType to QFT mapping
128 _VTToQFT = {
129   # TODO: fix validation of empty strings
130   constants.VTYPE_STRING: QFT_OTHER, # since VTYPE_STRINGs can be empty
131   constants.VTYPE_MAYBE_STRING: QFT_OTHER,
132   constants.VTYPE_BOOL: QFT_BOOL,
133   constants.VTYPE_SIZE: QFT_UNIT,
134   constants.VTYPE_INT: QFT_NUMBER,
135   }
136
137 _SERIAL_NO_DOC = "%s object serial number, incremented on each modification"
138
139 # TODO: Consider moving titles closer to constants
140 NDP_TITLE = {
141   constants.ND_OOB_PROGRAM: "OutOfBandProgram",
142   constants.ND_SPINDLE_COUNT: "SpindleCount",
143   }
144
145
146 def _GetUnknownField(ctx, item): # pylint: disable=W0613
147   """Gets the contents of an unknown field.
148
149   """
150   return _FS_UNKNOWN
151
152
153 def _GetQueryFields(fielddefs, selected):
154   """Calculates the internal list of selected fields.
155
156   Unknown fields are returned as L{constants.QFT_UNKNOWN}.
157
158   @type fielddefs: dict
159   @param fielddefs: Field definitions
160   @type selected: list of strings
161   @param selected: List of selected fields
162
163   """
164   result = []
165
166   for name in selected:
167     try:
168       fdef = fielddefs[name]
169     except KeyError:
170       fdef = (_MakeField(name, name, QFT_UNKNOWN, "Unknown field '%s'" % name),
171               None, 0, _GetUnknownField)
172
173     assert len(fdef) == 4
174
175     result.append(fdef)
176
177   return result
178
179
180 def GetAllFields(fielddefs):
181   """Extract L{objects.QueryFieldDefinition} from field definitions.
182
183   @rtype: list of L{objects.QueryFieldDefinition}
184
185   """
186   return [fdef for (fdef, _, _, _) in fielddefs]
187
188
189 class _FilterHints:
190   """Class for filter analytics.
191
192   When filters are used, the user of the L{Query} class usually doesn't know
193   exactly which items will be necessary for building the result. It therefore
194   has to prepare and compute the input data for potentially returning
195   everything.
196
197   There are two ways to optimize this. The first, and simpler, is to assign
198   each field a group of data, so that the caller can determine which
199   computations are necessary depending on the data groups requested. The list
200   of referenced groups must also be computed for fields referenced in the
201   filter.
202
203   The second is restricting the items based on a primary key. The primary key
204   is usually a unique name (e.g. a node name). This class extracts all
205   referenced names from a filter. If it encounters any filter condition which
206   disallows such a list to be determined (e.g. a non-equality filter), all
207   names will be requested.
208
209   The end-effect is that any operation other than L{qlang.OP_OR} and
210   L{qlang.OP_EQUAL} will make the query more expensive.
211
212   """
213   def __init__(self, namefield):
214     """Initializes this class.
215
216     @type namefield: string
217     @param namefield: Field caller is interested in
218
219     """
220     self._namefield = namefield
221
222     #: Whether all names need to be requested (e.g. if a non-equality operator
223     #: has been used)
224     self._allnames = False
225
226     #: Which names to request
227     self._names = None
228
229     #: Data kinds referenced by the filter (used by L{Query.RequestedData})
230     self._datakinds = set()
231
232   def RequestedNames(self):
233     """Returns all requested values.
234
235     Returns C{None} if list of values can't be determined (e.g. encountered
236     non-equality operators).
237
238     @rtype: list
239
240     """
241     if self._allnames or self._names is None:
242       return None
243
244     return utils.UniqueSequence(self._names)
245
246   def ReferencedData(self):
247     """Returns all kinds of data referenced by the filter.
248
249     """
250     return frozenset(self._datakinds)
251
252   def _NeedAllNames(self):
253     """Changes internal state to request all names.
254
255     """
256     self._allnames = True
257     self._names = None
258
259   def NoteLogicOp(self, op):
260     """Called when handling a logic operation.
261
262     @type op: string
263     @param op: Operator
264
265     """
266     if op != qlang.OP_OR:
267       self._NeedAllNames()
268
269   def NoteUnaryOp(self, op): # pylint: disable=W0613
270     """Called when handling an unary operation.
271
272     @type op: string
273     @param op: Operator
274
275     """
276     self._NeedAllNames()
277
278   def NoteBinaryOp(self, op, datakind, name, value):
279     """Called when handling a binary operation.
280
281     @type op: string
282     @param op: Operator
283     @type name: string
284     @param name: Left-hand side of operator (field name)
285     @param value: Right-hand side of operator
286
287     """
288     if datakind is not None:
289       self._datakinds.add(datakind)
290
291     if self._allnames:
292       return
293
294     # If any operator other than equality was used, all names need to be
295     # retrieved
296     if op == qlang.OP_EQUAL and name == self._namefield:
297       if self._names is None:
298         self._names = []
299       self._names.append(value)
300     else:
301       self._NeedAllNames()
302
303
304 def _WrapLogicOp(op_fn, sentences, ctx, item):
305   """Wrapper for logic operator functions.
306
307   """
308   return op_fn(fn(ctx, item) for fn in sentences)
309
310
311 def _WrapUnaryOp(op_fn, inner, ctx, item):
312   """Wrapper for unary operator functions.
313
314   """
315   return op_fn(inner(ctx, item))
316
317
318 def _WrapBinaryOp(op_fn, retrieval_fn, value, ctx, item):
319   """Wrapper for binary operator functions.
320
321   """
322   return op_fn(retrieval_fn(ctx, item), value)
323
324
325 def _WrapNot(fn, lhs, rhs):
326   """Negates the result of a wrapped function.
327
328   """
329   return not fn(lhs, rhs)
330
331
332 def _PrepareRegex(pattern):
333   """Compiles a regular expression.
334
335   """
336   try:
337     return re.compile(pattern)
338   except re.error, err:
339     raise errors.ParameterError("Invalid regex pattern (%s)" % err)
340
341
342 class _FilterCompilerHelper:
343   """Converts a query filter to a callable usable for filtering.
344
345   """
346   # String statement has no effect, pylint: disable=W0105
347
348   #: How deep filters can be nested
349   _LEVELS_MAX = 10
350
351   # Unique identifiers for operator groups
352   (_OPTYPE_LOGIC,
353    _OPTYPE_UNARY,
354    _OPTYPE_BINARY) = range(1, 4)
355
356   """Functions for equality checks depending on field flags.
357
358   List of tuples containing flags and a callable receiving the left- and
359   right-hand side of the operator. The flags are an OR-ed value of C{QFF_*}
360   (e.g. L{QFF_HOSTNAME}).
361
362   Order matters. The first item with flags will be used. Flags are checked
363   using binary AND.
364
365   """
366   _EQUALITY_CHECKS = [
367     (QFF_HOSTNAME,
368      lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
369                                                case_sensitive=False),
370      None),
371     (None, operator.eq, None),
372     ]
373
374   """Known operators
375
376   Operator as key (C{qlang.OP_*}), value a tuple of operator group
377   (C{_OPTYPE_*}) and a group-specific value:
378
379     - C{_OPTYPE_LOGIC}: Callable taking any number of arguments; used by
380       L{_HandleLogicOp}
381     - C{_OPTYPE_UNARY}: Always C{None}; details handled by L{_HandleUnaryOp}
382     - C{_OPTYPE_BINARY}: Callable taking exactly two parameters, the left- and
383       right-hand side of the operator, used by L{_HandleBinaryOp}
384
385   """
386   _OPS = {
387     # Logic operators
388     qlang.OP_OR: (_OPTYPE_LOGIC, compat.any),
389     qlang.OP_AND: (_OPTYPE_LOGIC, compat.all),
390
391     # Unary operators
392     qlang.OP_NOT: (_OPTYPE_UNARY, None),
393     qlang.OP_TRUE: (_OPTYPE_UNARY, None),
394
395     # Binary operators
396     qlang.OP_EQUAL: (_OPTYPE_BINARY, _EQUALITY_CHECKS),
397     qlang.OP_NOT_EQUAL:
398       (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn), valprepfn)
399                         for (flags, fn, valprepfn) in _EQUALITY_CHECKS]),
400     qlang.OP_REGEXP: (_OPTYPE_BINARY, [
401       (None, lambda lhs, rhs: rhs.search(lhs), _PrepareRegex),
402       ]),
403     qlang.OP_CONTAINS: (_OPTYPE_BINARY, [
404       (None, operator.contains, None),
405       ]),
406     }
407
408   def __init__(self, fields):
409     """Initializes this class.
410
411     @param fields: Field definitions (return value of L{_PrepareFieldList})
412
413     """
414     self._fields = fields
415     self._hints = None
416     self._op_handler = None
417
418   def __call__(self, hints, qfilter):
419     """Converts a query filter into a callable function.
420
421     @type hints: L{_FilterHints} or None
422     @param hints: Callbacks doing analysis on filter
423     @type qfilter: list
424     @param qfilter: Filter structure
425     @rtype: callable
426     @return: Function receiving context and item as parameters, returning
427              boolean as to whether item matches filter
428
429     """
430     self._op_handler = {
431       self._OPTYPE_LOGIC:
432         (self._HandleLogicOp, getattr(hints, "NoteLogicOp", None)),
433       self._OPTYPE_UNARY:
434         (self._HandleUnaryOp, getattr(hints, "NoteUnaryOp", None)),
435       self._OPTYPE_BINARY:
436         (self._HandleBinaryOp, getattr(hints, "NoteBinaryOp", None)),
437       }
438
439     try:
440       filter_fn = self._Compile(qfilter, 0)
441     finally:
442       self._op_handler = None
443
444     return filter_fn
445
446   def _Compile(self, qfilter, level):
447     """Inner function for converting filters.
448
449     Calls the correct handler functions for the top-level operator. This
450     function is called recursively (e.g. for logic operators).
451
452     """
453     if not (isinstance(qfilter, (list, tuple)) and qfilter):
454       raise errors.ParameterError("Invalid filter on level %s" % level)
455
456     # Limit recursion
457     if level >= self._LEVELS_MAX:
458       raise errors.ParameterError("Only up to %s levels are allowed (filter"
459                                   " nested too deep)" % self._LEVELS_MAX)
460
461     # Create copy to be modified
462     operands = qfilter[:]
463     op = operands.pop(0)
464
465     try:
466       (kind, op_data) = self._OPS[op]
467     except KeyError:
468       raise errors.ParameterError("Unknown operator '%s'" % op)
469
470     (handler, hints_cb) = self._op_handler[kind]
471
472     return handler(hints_cb, level, op, op_data, operands)
473
474   def _LookupField(self, name):
475     """Returns a field definition by name.
476
477     """
478     try:
479       return self._fields[name]
480     except KeyError:
481       raise errors.ParameterError("Unknown field '%s'" % name)
482
483   def _HandleLogicOp(self, hints_fn, level, op, op_fn, operands):
484     """Handles logic operators.
485
486     @type hints_fn: callable
487     @param hints_fn: Callback doing some analysis on the filter
488     @type level: integer
489     @param level: Current depth
490     @type op: string
491     @param op: Operator
492     @type op_fn: callable
493     @param op_fn: Function implementing operator
494     @type operands: list
495     @param operands: List of operands
496
497     """
498     if hints_fn:
499       hints_fn(op)
500
501     return compat.partial(_WrapLogicOp, op_fn,
502                           [self._Compile(op, level + 1) for op in operands])
503
504   def _HandleUnaryOp(self, hints_fn, level, op, op_fn, operands):
505     """Handles unary operators.
506
507     @type hints_fn: callable
508     @param hints_fn: Callback doing some analysis on the filter
509     @type level: integer
510     @param level: Current depth
511     @type op: string
512     @param op: Operator
513     @type op_fn: callable
514     @param op_fn: Function implementing operator
515     @type operands: list
516     @param operands: List of operands
517
518     """
519     assert op_fn is None
520
521     if hints_fn:
522       hints_fn(op)
523
524     if len(operands) != 1:
525       raise errors.ParameterError("Unary operator '%s' expects exactly one"
526                                   " operand" % op)
527
528     if op == qlang.OP_TRUE:
529       (_, _, _, retrieval_fn) = self._LookupField(operands[0])
530
531       op_fn = operator.truth
532       arg = retrieval_fn
533     elif op == qlang.OP_NOT:
534       op_fn = operator.not_
535       arg = self._Compile(operands[0], level + 1)
536     else:
537       raise errors.ProgrammerError("Can't handle operator '%s'" % op)
538
539     return compat.partial(_WrapUnaryOp, op_fn, arg)
540
541   def _HandleBinaryOp(self, hints_fn, level, op, op_data, operands):
542     """Handles binary operators.
543
544     @type hints_fn: callable
545     @param hints_fn: Callback doing some analysis on the filter
546     @type level: integer
547     @param level: Current depth
548     @type op: string
549     @param op: Operator
550     @param op_data: Functions implementing operators
551     @type operands: list
552     @param operands: List of operands
553
554     """
555     # Unused arguments, pylint: disable=W0613
556     try:
557       (name, value) = operands
558     except (ValueError, TypeError):
559       raise errors.ParameterError("Invalid binary operator, expected exactly"
560                                   " two operands")
561
562     (fdef, datakind, field_flags, retrieval_fn) = self._LookupField(name)
563
564     assert fdef.kind != QFT_UNKNOWN
565
566     # TODO: Type conversions?
567
568     verify_fn = _VERIFY_FN[fdef.kind]
569     if not verify_fn(value):
570       raise errors.ParameterError("Unable to compare field '%s' (type '%s')"
571                                   " with '%s', expected %s" %
572                                   (name, fdef.kind, value.__class__.__name__,
573                                    verify_fn))
574
575     if hints_fn:
576       hints_fn(op, datakind, name, value)
577
578     for (fn_flags, fn, valprepfn) in op_data:
579       if fn_flags is None or fn_flags & field_flags:
580         # Prepare value if necessary (e.g. compile regular expression)
581         if valprepfn:
582           value = valprepfn(value)
583
584         return compat.partial(_WrapBinaryOp, fn, retrieval_fn, value)
585
586     raise errors.ProgrammerError("Unable to find operator implementation"
587                                  " (op '%s', flags %s)" % (op, field_flags))
588
589
590 def _CompileFilter(fields, hints, qfilter):
591   """Converts a query filter into a callable function.
592
593   See L{_FilterCompilerHelper} for details.
594
595   @rtype: callable
596
597   """
598   return _FilterCompilerHelper(fields)(hints, qfilter)
599
600
601 class Query:
602   def __init__(self, fieldlist, selected, qfilter=None, namefield=None):
603     """Initializes this class.
604
605     The field definition is a dictionary with the field's name as a key and a
606     tuple containing, in order, the field definition object
607     (L{objects.QueryFieldDefinition}, the data kind to help calling code
608     collect data and a retrieval function. The retrieval function is called
609     with two parameters, in order, the data container and the item in container
610     (see L{Query.Query}).
611
612     Users of this class can call L{RequestedData} before preparing the data
613     container to determine what data is needed.
614
615     @type fieldlist: dictionary
616     @param fieldlist: Field definitions
617     @type selected: list of strings
618     @param selected: List of selected fields
619
620     """
621     assert namefield is None or namefield in fieldlist
622
623     self._fields = _GetQueryFields(fieldlist, selected)
624
625     self._filter_fn = None
626     self._requested_names = None
627     self._filter_datakinds = frozenset()
628
629     if qfilter is not None:
630       # Collect requested names if wanted
631       if namefield:
632         hints = _FilterHints(namefield)
633       else:
634         hints = None
635
636       # Build filter function
637       self._filter_fn = _CompileFilter(fieldlist, hints, qfilter)
638       if hints:
639         self._requested_names = hints.RequestedNames()
640         self._filter_datakinds = hints.ReferencedData()
641
642     if namefield is None:
643       self._name_fn = None
644     else:
645       (_, _, _, self._name_fn) = fieldlist[namefield]
646
647   def RequestedNames(self):
648     """Returns all names referenced in the filter.
649
650     If there is no filter or operators are preventing determining the exact
651     names, C{None} is returned.
652
653     """
654     return self._requested_names
655
656   def RequestedData(self):
657     """Gets requested kinds of data.
658
659     @rtype: frozenset
660
661     """
662     return (self._filter_datakinds |
663             frozenset(datakind for (_, datakind, _, _) in self._fields
664                       if datakind is not None))
665
666   def GetFields(self):
667     """Returns the list of fields for this query.
668
669     Includes unknown fields.
670
671     @rtype: List of L{objects.QueryFieldDefinition}
672
673     """
674     return GetAllFields(self._fields)
675
676   def Query(self, ctx, sort_by_name=True):
677     """Execute a query.
678
679     @param ctx: Data container passed to field retrieval functions, must
680       support iteration using C{__iter__}
681     @type sort_by_name: boolean
682     @param sort_by_name: Whether to sort by name or keep the input data's
683       ordering
684
685     """
686     sort = (self._name_fn and sort_by_name)
687
688     result = []
689
690     for idx, item in enumerate(ctx):
691       if not (self._filter_fn is None or self._filter_fn(ctx, item)):
692         continue
693
694       row = [_ProcessResult(fn(ctx, item)) for (_, _, _, fn) in self._fields]
695
696       # Verify result
697       if __debug__:
698         _VerifyResultRow(self._fields, row)
699
700       if sort:
701         (status, name) = _ProcessResult(self._name_fn(ctx, item))
702         assert status == constants.RS_NORMAL
703         # TODO: Are there cases where we wouldn't want to use NiceSort?
704         result.append((utils.NiceSortKey(name), idx, row))
705       else:
706         result.append(row)
707
708     if not sort:
709       return result
710
711     # TODO: Would "heapq" be more efficient than sorting?
712
713     # Sorting in-place instead of using "sorted()"
714     result.sort()
715
716     assert not result or (len(result[0]) == 3 and len(result[-1]) == 3)
717
718     return map(operator.itemgetter(2), result)
719
720   def OldStyleQuery(self, ctx, sort_by_name=True):
721     """Query with "old" query result format.
722
723     See L{Query.Query} for arguments.
724
725     """
726     unknown = set(fdef.name for (fdef, _, _, _) in self._fields
727                   if fdef.kind == QFT_UNKNOWN)
728     if unknown:
729       raise errors.OpPrereqError("Unknown output fields selected: %s" %
730                                  (utils.CommaJoin(unknown), ),
731                                  errors.ECODE_INVAL)
732
733     return [[value for (_, value) in row]
734             for row in self.Query(ctx, sort_by_name=sort_by_name)]
735
736
737 def _ProcessResult(value):
738   """Converts result values into externally-visible ones.
739
740   """
741   if value is _FS_UNKNOWN:
742     return (RS_UNKNOWN, None)
743   elif value is _FS_NODATA:
744     return (RS_NODATA, None)
745   elif value is _FS_UNAVAIL:
746     return (RS_UNAVAIL, None)
747   elif value is _FS_OFFLINE:
748     return (RS_OFFLINE, None)
749   else:
750     return (RS_NORMAL, value)
751
752
753 def _VerifyResultRow(fields, row):
754   """Verifies the contents of a query result row.
755
756   @type fields: list
757   @param fields: Field definitions for result
758   @type row: list of tuples
759   @param row: Row data
760
761   """
762   assert len(row) == len(fields)
763   errs = []
764   for ((status, value), (fdef, _, _, _)) in zip(row, fields):
765     if status == RS_NORMAL:
766       if not _VERIFY_FN[fdef.kind](value):
767         errs.append("normal field %s fails validation (value is %s)" %
768                     (fdef.name, value))
769     elif value is not None:
770       errs.append("abnormal field %s has a non-None value" % fdef.name)
771   assert not errs, ("Failed validation: %s in row %s" %
772                     (utils.CommaJoin(errs), row))
773
774
775 def _FieldDictKey((fdef, _, flags, fn)):
776   """Generates key for field dictionary.
777
778   """
779   assert fdef.name and fdef.title, "Name and title are required"
780   assert FIELD_NAME_RE.match(fdef.name)
781   assert TITLE_RE.match(fdef.title)
782   assert (DOC_RE.match(fdef.doc) and len(fdef.doc.splitlines()) == 1 and
783           fdef.doc.strip() == fdef.doc), \
784          "Invalid description for field '%s'" % fdef.name
785   assert callable(fn)
786   assert (flags & ~QFF_ALL) == 0, "Unknown flags for field '%s'" % fdef.name
787
788   return fdef.name
789
790
791 def _PrepareFieldList(fields, aliases):
792   """Prepares field list for use by L{Query}.
793
794   Converts the list to a dictionary and does some verification.
795
796   @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data
797       kind, retrieval function)
798   @param fields: List of fields, see L{Query.__init__} for a better
799       description
800   @type aliases: list of tuples; (alias, target)
801   @param aliases: list of tuples containing aliases; for each
802       alias/target pair, a duplicate will be created in the field list
803   @rtype: dict
804   @return: Field dictionary for L{Query}
805
806   """
807   if __debug__:
808     duplicates = utils.FindDuplicates(fdef.title.lower()
809                                       for (fdef, _, _, _) in fields)
810     assert not duplicates, "Duplicate title(s) found: %r" % duplicates
811
812   result = utils.SequenceToDict(fields, key=_FieldDictKey)
813
814   for alias, target in aliases:
815     assert alias not in result, "Alias %s overrides an existing field" % alias
816     assert target in result, "Missing target %s for alias %s" % (target, alias)
817     (fdef, k, flags, fn) = result[target]
818     fdef = fdef.Copy()
819     fdef.name = alias
820     result[alias] = (fdef, k, flags, fn)
821
822   assert len(result) == len(fields) + len(aliases)
823   assert compat.all(name == fdef.name
824                     for (name, (fdef, _, _, _)) in result.items())
825
826   return result
827
828
829 def GetQueryResponse(query, ctx, sort_by_name=True):
830   """Prepares the response for a query.
831
832   @type query: L{Query}
833   @param ctx: Data container, see L{Query.Query}
834   @type sort_by_name: boolean
835   @param sort_by_name: Whether to sort by name or keep the input data's
836     ordering
837
838   """
839   return objects.QueryResponse(data=query.Query(ctx, sort_by_name=sort_by_name),
840                                fields=query.GetFields()).ToDict()
841
842
843 def QueryFields(fielddefs, selected):
844   """Returns list of available fields.
845
846   @type fielddefs: dict
847   @param fielddefs: Field definitions
848   @type selected: list of strings
849   @param selected: List of selected fields
850   @return: List of L{objects.QueryFieldDefinition}
851
852   """
853   if selected is None:
854     # Client requests all fields, sort by name
855     fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
856                            key=operator.attrgetter("name"))
857   else:
858     # Keep order as requested by client
859     fdefs = Query(fielddefs, selected).GetFields()
860
861   return objects.QueryFieldsResponse(fields=fdefs).ToDict()
862
863
864 def _MakeField(name, title, kind, doc):
865   """Wrapper for creating L{objects.QueryFieldDefinition} instances.
866
867   @param name: Field name as a regular expression
868   @param title: Human-readable title
869   @param kind: Field type
870   @param doc: Human-readable description
871
872   """
873   return objects.QueryFieldDefinition(name=name, title=title, kind=kind,
874                                       doc=doc)
875
876
877 def _GetNodeRole(node, master_name):
878   """Determine node role.
879
880   @type node: L{objects.Node}
881   @param node: Node object
882   @type master_name: string
883   @param master_name: Master node name
884
885   """
886   if node.name == master_name:
887     return constants.NR_MASTER
888   elif node.master_candidate:
889     return constants.NR_MCANDIDATE
890   elif node.drained:
891     return constants.NR_DRAINED
892   elif node.offline:
893     return constants.NR_OFFLINE
894   else:
895     return constants.NR_REGULAR
896
897
898 def _GetItemAttr(attr):
899   """Returns a field function to return an attribute of the item.
900
901   @param attr: Attribute name
902
903   """
904   getter = operator.attrgetter(attr)
905   return lambda _, item: getter(item)
906
907
908 def _GetNDParam(name):
909   """Return a field function to return an ND parameter out of the context.
910
911   """
912   def _helper(ctx, _):
913     if ctx.ndparams is None:
914       return _FS_UNAVAIL
915     else:
916       return ctx.ndparams.get(name, None)
917   return _helper
918
919
920 def _BuildNDFields(is_group):
921   """Builds all the ndparam fields.
922
923   @param is_group: whether this is called at group or node level
924
925   """
926   if is_group:
927     field_kind = GQ_CONFIG
928   else:
929     field_kind = NQ_GROUP
930   return [(_MakeField("ndp/%s" % name, NDP_TITLE.get(name, "ndp/%s" % name),
931                       _VTToQFT[kind], "The \"%s\" node parameter" % name),
932            field_kind, 0, _GetNDParam(name))
933           for name, kind in constants.NDS_PARAMETER_TYPES.items()]
934
935
936 def _ConvWrapInner(convert, fn, ctx, item):
937   """Wrapper for converting values.
938
939   @param convert: Conversion function receiving value as single parameter
940   @param fn: Retrieval function
941
942   """
943   value = fn(ctx, item)
944
945   # Is the value an abnormal status?
946   if compat.any(value is fs for fs in _FS_ALL):
947     # Return right away
948     return value
949
950   # TODO: Should conversion function also receive context, item or both?
951   return convert(value)
952
953
954 def _ConvWrap(convert, fn):
955   """Convenience wrapper for L{_ConvWrapInner}.
956
957   @param convert: Conversion function receiving value as single parameter
958   @param fn: Retrieval function
959
960   """
961   return compat.partial(_ConvWrapInner, convert, fn)
962
963
964 def _GetItemTimestamp(getter):
965   """Returns function for getting timestamp of item.
966
967   @type getter: callable
968   @param getter: Function to retrieve timestamp attribute
969
970   """
971   def fn(_, item):
972     """Returns a timestamp of item.
973
974     """
975     timestamp = getter(item)
976     if timestamp is None:
977       # Old configs might not have all timestamps
978       return _FS_UNAVAIL
979     else:
980       return timestamp
981
982   return fn
983
984
985 def _GetItemTimestampFields(datatype):
986   """Returns common timestamp fields.
987
988   @param datatype: Field data type for use by L{Query.RequestedData}
989
990   """
991   return [
992     (_MakeField("ctime", "CTime", QFT_TIMESTAMP, "Creation timestamp"),
993      datatype, 0, _GetItemTimestamp(operator.attrgetter("ctime"))),
994     (_MakeField("mtime", "MTime", QFT_TIMESTAMP, "Modification timestamp"),
995      datatype, 0, _GetItemTimestamp(operator.attrgetter("mtime"))),
996     ]
997
998
999 class NodeQueryData:
1000   """Data container for node data queries.
1001
1002   """
1003   def __init__(self, nodes, live_data, master_name, node_to_primary,
1004                node_to_secondary, groups, oob_support, cluster):
1005     """Initializes this class.
1006
1007     """
1008     self.nodes = nodes
1009     self.live_data = live_data
1010     self.master_name = master_name
1011     self.node_to_primary = node_to_primary
1012     self.node_to_secondary = node_to_secondary
1013     self.groups = groups
1014     self.oob_support = oob_support
1015     self.cluster = cluster
1016
1017     # Used for individual rows
1018     self.curlive_data = None
1019     self.ndparams = None
1020
1021   def __iter__(self):
1022     """Iterate over all nodes.
1023
1024     This function has side-effects and only one instance of the resulting
1025     generator should be used at a time.
1026
1027     """
1028     for node in self.nodes:
1029       group = self.groups.get(node.group, None)
1030       if group is None:
1031         self.ndparams = None
1032       else:
1033         self.ndparams = self.cluster.FillND(node, group)
1034       if self.live_data:
1035         self.curlive_data = self.live_data.get(node.name, None)
1036       else:
1037         self.curlive_data = None
1038       yield node
1039
1040
1041 #: Fields that are direct attributes of an L{objects.Node} object
1042 _NODE_SIMPLE_FIELDS = {
1043   "drained": ("Drained", QFT_BOOL, 0, "Whether node is drained"),
1044   "master_candidate": ("MasterC", QFT_BOOL, 0,
1045                        "Whether node is a master candidate"),
1046   "master_capable": ("MasterCapable", QFT_BOOL, 0,
1047                      "Whether node can become a master candidate"),
1048   "name": ("Node", QFT_TEXT, QFF_HOSTNAME, "Node name"),
1049   "offline": ("Offline", QFT_BOOL, 0, "Whether node is marked offline"),
1050   "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Node"),
1051   "uuid": ("UUID", QFT_TEXT, 0, "Node UUID"),
1052   "vm_capable": ("VMCapable", QFT_BOOL, 0, "Whether node can host instances"),
1053   }
1054
1055
1056 #: Fields requiring talking to the node
1057 # Note that none of these are available for non-vm_capable nodes
1058 _NODE_LIVE_FIELDS = {
1059   "bootid": ("BootID", QFT_TEXT, "bootid",
1060              "Random UUID renewed for each system reboot, can be used"
1061              " for detecting reboots by tracking changes"),
1062   "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes",
1063              "Number of NUMA domains on node (if exported by hypervisor)"),
1064   "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets",
1065                "Number of physical CPU sockets (if exported by hypervisor)"),
1066   "ctotal": ("CTotal", QFT_NUMBER, "cpu_total", "Number of logical processors"),
1067   "dfree": ("DFree", QFT_UNIT, "vg_free",
1068             "Available disk space in volume group"),
1069   "dtotal": ("DTotal", QFT_UNIT, "vg_size",
1070              "Total disk space in volume group used for instance disk"
1071              " allocation"),
1072   "mfree": ("MFree", QFT_UNIT, "memory_free",
1073             "Memory available for instance allocations"),
1074   "mnode": ("MNode", QFT_UNIT, "memory_dom0",
1075             "Amount of memory used by node (dom0 for Xen)"),
1076   "mtotal": ("MTotal", QFT_UNIT, "memory_total",
1077              "Total amount of memory of physical machine"),
1078   }
1079
1080
1081 def _GetGroup(cb):
1082   """Build function for calling another function with an node group.
1083
1084   @param cb: The callback to be called with the nodegroup
1085
1086   """
1087   def fn(ctx, node):
1088     """Get group data for a node.
1089
1090     @type ctx: L{NodeQueryData}
1091     @type inst: L{objects.Node}
1092     @param inst: Node object
1093
1094     """
1095     ng = ctx.groups.get(node.group, None)
1096     if ng is None:
1097       # Nodes always have a group, or the configuration is corrupt
1098       return _FS_UNAVAIL
1099
1100     return cb(ctx, node, ng)
1101
1102   return fn
1103
1104
1105 def _GetNodeGroup(ctx, node, ng): # pylint: disable=W0613
1106   """Returns the name of a node's group.
1107
1108   @type ctx: L{NodeQueryData}
1109   @type node: L{objects.Node}
1110   @param node: Node object
1111   @type ng: L{objects.NodeGroup}
1112   @param ng: The node group this node belongs to
1113
1114   """
1115   return ng.name
1116
1117
1118 def _GetNodePower(ctx, node):
1119   """Returns the node powered state
1120
1121   @type ctx: L{NodeQueryData}
1122   @type node: L{objects.Node}
1123   @param node: Node object
1124
1125   """
1126   if ctx.oob_support[node.name]:
1127     return node.powered
1128
1129   return _FS_UNAVAIL
1130
1131
1132 def _GetNdParams(ctx, node, ng):
1133   """Returns the ndparams for this node.
1134
1135   @type ctx: L{NodeQueryData}
1136   @type node: L{objects.Node}
1137   @param node: Node object
1138   @type ng: L{objects.NodeGroup}
1139   @param ng: The node group this node belongs to
1140
1141   """
1142   return ctx.cluster.SimpleFillND(ng.FillND(node))
1143
1144
1145 def _GetLiveNodeField(field, kind, ctx, node):
1146   """Gets the value of a "live" field from L{NodeQueryData}.
1147
1148   @param field: Live field name
1149   @param kind: Data kind, one of L{constants.QFT_ALL}
1150   @type ctx: L{NodeQueryData}
1151   @type node: L{objects.Node}
1152   @param node: Node object
1153
1154   """
1155   if node.offline:
1156     return _FS_OFFLINE
1157
1158   if not node.vm_capable:
1159     return _FS_UNAVAIL
1160
1161   if not ctx.curlive_data:
1162     return _FS_NODATA
1163
1164   try:
1165     value = ctx.curlive_data[field]
1166   except KeyError:
1167     return _FS_UNAVAIL
1168
1169   if kind == QFT_TEXT:
1170     return value
1171
1172   assert kind in (QFT_NUMBER, QFT_UNIT)
1173
1174   # Try to convert into number
1175   try:
1176     return int(value)
1177   except (ValueError, TypeError):
1178     logging.exception("Failed to convert node field '%s' (value %r) to int",
1179                       value, field)
1180     return _FS_UNAVAIL
1181
1182
1183 def _GetNodeHvState(_, node):
1184   """Converts node's hypervisor state for query result.
1185
1186   """
1187   hv_state = node.hv_state
1188
1189   if hv_state is None:
1190     return _FS_UNAVAIL
1191
1192   return dict((name, value.ToDict()) for (name, value) in hv_state.items())
1193
1194
1195 def _GetNodeDiskState(_, node):
1196   """Converts node's disk state for query result.
1197
1198   """
1199   disk_state = node.disk_state
1200
1201   if disk_state is None:
1202     return _FS_UNAVAIL
1203
1204   return dict((disk_kind, dict((name, value.ToDict())
1205                                for (name, value) in kind_state.items()))
1206               for (disk_kind, kind_state) in disk_state.items())
1207
1208
1209 def _BuildNodeFields():
1210   """Builds list of fields for node queries.
1211
1212   """
1213   fields = [
1214     (_MakeField("pip", "PrimaryIP", QFT_TEXT, "Primary IP address"),
1215      NQ_CONFIG, 0, _GetItemAttr("primary_ip")),
1216     (_MakeField("sip", "SecondaryIP", QFT_TEXT, "Secondary IP address"),
1217      NQ_CONFIG, 0, _GetItemAttr("secondary_ip")),
1218     (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0,
1219      lambda ctx, node: list(node.GetTags())),
1220     (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"),
1221      NQ_CONFIG, 0, lambda ctx, node: node.name == ctx.master_name),
1222     (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0,
1223      _GetGroup(_GetNodeGroup)),
1224     (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"),
1225      NQ_CONFIG, 0, _GetItemAttr("group")),
1226     (_MakeField("powered", "Powered", QFT_BOOL,
1227                 "Whether node is thought to be powered on"),
1228      NQ_OOB, 0, _GetNodePower),
1229     (_MakeField("ndparams", "NodeParameters", QFT_OTHER,
1230                 "Merged node parameters"),
1231      NQ_GROUP, 0, _GetGroup(_GetNdParams)),
1232     (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER,
1233                 "Custom node parameters"),
1234       NQ_GROUP, 0, _GetItemAttr("ndparams")),
1235     (_MakeField("hv_state", "HypervisorState", QFT_OTHER, "Hypervisor state"),
1236      NQ_CONFIG, 0, _GetNodeHvState),
1237     (_MakeField("disk_state", "DiskState", QFT_OTHER, "Disk state"),
1238      NQ_CONFIG, 0, _GetNodeDiskState),
1239     ]
1240
1241   fields.extend(_BuildNDFields(False))
1242
1243   # Node role
1244   role_values = (constants.NR_MASTER, constants.NR_MCANDIDATE,
1245                  constants.NR_REGULAR, constants.NR_DRAINED,
1246                  constants.NR_OFFLINE)
1247   role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate,"
1248               " \"%s\" for regular, \"%s\" for a drained, \"%s\" for offline" %
1249               role_values)
1250   fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
1251                  lambda ctx, node: _GetNodeRole(node, ctx.master_name)))
1252   assert set(role_values) == constants.NR_ALL
1253
1254   def _GetLength(getter):
1255     return lambda ctx, node: len(getter(ctx)[node.name])
1256
1257   def _GetList(getter):
1258     return lambda ctx, node: list(getter(ctx)[node.name])
1259
1260   # Add fields operating on instance lists
1261   for prefix, titleprefix, docword, getter in \
1262       [("p", "Pri", "primary", operator.attrgetter("node_to_primary")),
1263        ("s", "Sec", "secondary", operator.attrgetter("node_to_secondary"))]:
1264     # TODO: Allow filterting by hostname in list
1265     fields.extend([
1266       (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER,
1267                   "Number of instances with this node as %s" % docword),
1268        NQ_INST, 0, _GetLength(getter)),
1269       (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
1270                   QFT_OTHER,
1271                   "List of instances with this node as %s" % docword),
1272        NQ_INST, 0, _GetList(getter)),
1273       ])
1274
1275   # Add simple fields
1276   fields.extend([
1277     (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1278     for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()
1279     ])
1280
1281   # Add fields requiring live data
1282   fields.extend([
1283     (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1284      compat.partial(_GetLiveNodeField, nfield, kind))
1285     for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()
1286     ])
1287
1288   # Add timestamps
1289   fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1290
1291   return _PrepareFieldList(fields, [])
1292
1293
1294 class InstanceQueryData:
1295   """Data container for instance data queries.
1296
1297   """
1298   def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
1299                live_data, wrongnode_inst, console, nodes, groups):
1300     """Initializes this class.
1301
1302     @param instances: List of instance objects
1303     @param cluster: Cluster object
1304     @type disk_usage: dict; instance name as key
1305     @param disk_usage: Per-instance disk usage
1306     @type offline_nodes: list of strings
1307     @param offline_nodes: List of offline nodes
1308     @type bad_nodes: list of strings
1309     @param bad_nodes: List of faulty nodes
1310     @type live_data: dict; instance name as key
1311     @param live_data: Per-instance live data
1312     @type wrongnode_inst: set
1313     @param wrongnode_inst: Set of instances running on wrong node(s)
1314     @type console: dict; instance name as key
1315     @param console: Per-instance console information
1316     @type nodes: dict; node name as key
1317     @param nodes: Node objects
1318
1319     """
1320     assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
1321            "Offline nodes not included in bad nodes"
1322     assert not (set(live_data.keys()) & set(bad_nodes)), \
1323            "Found live data for bad or offline nodes"
1324
1325     self.instances = instances
1326     self.cluster = cluster
1327     self.disk_usage = disk_usage
1328     self.offline_nodes = offline_nodes
1329     self.bad_nodes = bad_nodes
1330     self.live_data = live_data
1331     self.wrongnode_inst = wrongnode_inst
1332     self.console = console
1333     self.nodes = nodes
1334     self.groups = groups
1335
1336     # Used for individual rows
1337     self.inst_hvparams = None
1338     self.inst_beparams = None
1339     self.inst_osparams = None
1340     self.inst_nicparams = None
1341
1342   def __iter__(self):
1343     """Iterate over all instances.
1344
1345     This function has side-effects and only one instance of the resulting
1346     generator should be used at a time.
1347
1348     """
1349     for inst in self.instances:
1350       self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1351       self.inst_beparams = self.cluster.FillBE(inst)
1352       self.inst_osparams = self.cluster.SimpleFillOS(inst.os, inst.osparams)
1353       self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1354                              for nic in inst.nics]
1355
1356       yield inst
1357
1358
1359 def _GetInstOperState(ctx, inst):
1360   """Get instance's operational status.
1361
1362   @type ctx: L{InstanceQueryData}
1363   @type inst: L{objects.Instance}
1364   @param inst: Instance object
1365
1366   """
1367   # Can't use RS_OFFLINE here as it would describe the instance to
1368   # be offline when we actually don't know due to missing data
1369   if inst.primary_node in ctx.bad_nodes:
1370     return _FS_NODATA
1371   else:
1372     return bool(ctx.live_data.get(inst.name))
1373
1374
1375 def _GetInstLiveData(name):
1376   """Build function for retrieving live data.
1377
1378   @type name: string
1379   @param name: Live data field name
1380
1381   """
1382   def fn(ctx, inst):
1383     """Get live data for an instance.
1384
1385     @type ctx: L{InstanceQueryData}
1386     @type inst: L{objects.Instance}
1387     @param inst: Instance object
1388
1389     """
1390     if (inst.primary_node in ctx.bad_nodes or
1391         inst.primary_node in ctx.offline_nodes):
1392       # Can't use RS_OFFLINE here as it would describe the instance to be
1393       # offline when we actually don't know due to missing data
1394       return _FS_NODATA
1395
1396     if inst.name in ctx.live_data:
1397       data = ctx.live_data[inst.name]
1398       if name in data:
1399         return data[name]
1400
1401     return _FS_UNAVAIL
1402
1403   return fn
1404
1405
1406 def _GetInstStatus(ctx, inst):
1407   """Get instance status.
1408
1409   @type ctx: L{InstanceQueryData}
1410   @type inst: L{objects.Instance}
1411   @param inst: Instance object
1412
1413   """
1414   if inst.primary_node in ctx.offline_nodes:
1415     return constants.INSTST_NODEOFFLINE
1416
1417   if inst.primary_node in ctx.bad_nodes:
1418     return constants.INSTST_NODEDOWN
1419
1420   if bool(ctx.live_data.get(inst.name)):
1421     if inst.name in ctx.wrongnode_inst:
1422       return constants.INSTST_WRONGNODE
1423     elif inst.admin_state == constants.ADMINST_UP:
1424       return constants.INSTST_RUNNING
1425     else:
1426       return constants.INSTST_ERRORUP
1427
1428   if inst.admin_state == constants.ADMINST_UP:
1429     return constants.INSTST_ERRORDOWN
1430   elif inst.admin_state == constants.ADMINST_DOWN:
1431     return constants.INSTST_ADMINDOWN
1432
1433   return constants.INSTST_ADMINOFFLINE
1434
1435
1436 def _GetInstDiskSize(index):
1437   """Build function for retrieving disk size.
1438
1439   @type index: int
1440   @param index: Disk index
1441
1442   """
1443   def fn(_, inst):
1444     """Get size of a disk.
1445
1446     @type inst: L{objects.Instance}
1447     @param inst: Instance object
1448
1449     """
1450     try:
1451       return inst.disks[index].size
1452     except IndexError:
1453       return _FS_UNAVAIL
1454
1455   return fn
1456
1457
1458 def _GetInstNic(index, cb):
1459   """Build function for calling another function with an instance NIC.
1460
1461   @type index: int
1462   @param index: NIC index
1463   @type cb: callable
1464   @param cb: Callback
1465
1466   """
1467   def fn(ctx, inst):
1468     """Call helper function with instance NIC.
1469
1470     @type ctx: L{InstanceQueryData}
1471     @type inst: L{objects.Instance}
1472     @param inst: Instance object
1473
1474     """
1475     try:
1476       nic = inst.nics[index]
1477     except IndexError:
1478       return _FS_UNAVAIL
1479
1480     return cb(ctx, index, nic)
1481
1482   return fn
1483
1484
1485 def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613
1486   """Get a NIC's IP address.
1487
1488   @type ctx: L{InstanceQueryData}
1489   @type nic: L{objects.NIC}
1490   @param nic: NIC object
1491
1492   """
1493   if nic.ip is None:
1494     return _FS_UNAVAIL
1495   else:
1496     return nic.ip
1497
1498
1499 def _GetInstNicBridge(ctx, index, _):
1500   """Get a NIC's bridge.
1501
1502   @type ctx: L{InstanceQueryData}
1503   @type index: int
1504   @param index: NIC index
1505
1506   """
1507   assert len(ctx.inst_nicparams) >= index
1508
1509   nicparams = ctx.inst_nicparams[index]
1510
1511   if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1512     return nicparams[constants.NIC_LINK]
1513   else:
1514     return _FS_UNAVAIL
1515
1516
1517 def _GetInstAllNicBridges(ctx, inst):
1518   """Get all network bridges for an instance.
1519
1520   @type ctx: L{InstanceQueryData}
1521   @type inst: L{objects.Instance}
1522   @param inst: Instance object
1523
1524   """
1525   assert len(ctx.inst_nicparams) == len(inst.nics)
1526
1527   result = []
1528
1529   for nicp in ctx.inst_nicparams:
1530     if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1531       result.append(nicp[constants.NIC_LINK])
1532     else:
1533       result.append(None)
1534
1535   assert len(result) == len(inst.nics)
1536
1537   return result
1538
1539
1540 def _GetInstNicParam(name):
1541   """Build function for retrieving a NIC parameter.
1542
1543   @type name: string
1544   @param name: Parameter name
1545
1546   """
1547   def fn(ctx, index, _):
1548     """Get a NIC's bridge.
1549
1550     @type ctx: L{InstanceQueryData}
1551     @type inst: L{objects.Instance}
1552     @param inst: Instance object
1553     @type nic: L{objects.NIC}
1554     @param nic: NIC object
1555
1556     """
1557     assert len(ctx.inst_nicparams) >= index
1558     return ctx.inst_nicparams[index][name]
1559
1560   return fn
1561
1562
1563 def _GetInstanceNetworkFields():
1564   """Get instance fields involving network interfaces.
1565
1566   @return: Tuple containing list of field definitions used as input for
1567     L{_PrepareFieldList} and a list of aliases
1568
1569   """
1570   nic_mac_fn = lambda ctx, _, nic: nic.mac
1571   nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1572   nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1573
1574   fields = [
1575     # All NICs
1576     (_MakeField("nic.count", "NICs", QFT_NUMBER,
1577                 "Number of network interfaces"),
1578      IQ_CONFIG, 0, lambda ctx, inst: len(inst.nics)),
1579     (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER,
1580                 "List containing each network interface's MAC address"),
1581      IQ_CONFIG, 0, lambda ctx, inst: [nic.mac for nic in inst.nics]),
1582     (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
1583                 "List containing each network interface's IP address"),
1584      IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
1585     (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
1586                 "List containing each network interface's mode"), IQ_CONFIG, 0,
1587      lambda ctx, inst: [nicp[constants.NIC_MODE]
1588                         for nicp in ctx.inst_nicparams]),
1589     (_MakeField("nic.links", "NIC_links", QFT_OTHER,
1590                 "List containing each network interface's link"), IQ_CONFIG, 0,
1591      lambda ctx, inst: [nicp[constants.NIC_LINK]
1592                         for nicp in ctx.inst_nicparams]),
1593     (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
1594                 "List containing each network interface's bridge"),
1595      IQ_CONFIG, 0, _GetInstAllNicBridges),
1596     ]
1597
1598   # NICs by number
1599   for i in range(constants.MAX_NICS):
1600     numtext = utils.FormatOrdinal(i + 1)
1601     fields.extend([
1602       (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT,
1603                   "IP address of %s network interface" % numtext),
1604        IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)),
1605       (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
1606                   "MAC address of %s network interface" % numtext),
1607        IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
1608       (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
1609                   "Mode of %s network interface" % numtext),
1610        IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
1611       (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT,
1612                   "Link of %s network interface" % numtext),
1613        IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)),
1614       (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
1615                   "Bridge of %s network interface" % numtext),
1616        IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
1617       ])
1618
1619   aliases = [
1620     # Legacy fields for first NIC
1621     ("ip", "nic.ip/0"),
1622     ("mac", "nic.mac/0"),
1623     ("bridge", "nic.bridge/0"),
1624     ("nic_mode", "nic.mode/0"),
1625     ("nic_link", "nic.link/0"),
1626     ]
1627
1628   return (fields, aliases)
1629
1630
1631 def _GetInstDiskUsage(ctx, inst):
1632   """Get disk usage for an instance.
1633
1634   @type ctx: L{InstanceQueryData}
1635   @type inst: L{objects.Instance}
1636   @param inst: Instance object
1637
1638   """
1639   usage = ctx.disk_usage[inst.name]
1640
1641   if usage is None:
1642     usage = 0
1643
1644   return usage
1645
1646
1647 def _GetInstanceConsole(ctx, inst):
1648   """Get console information for instance.
1649
1650   @type ctx: L{InstanceQueryData}
1651   @type inst: L{objects.Instance}
1652   @param inst: Instance object
1653
1654   """
1655   consinfo = ctx.console[inst.name]
1656
1657   if consinfo is None:
1658     return _FS_UNAVAIL
1659
1660   return consinfo
1661
1662
1663 def _GetInstanceDiskFields():
1664   """Get instance fields involving disks.
1665
1666   @return: List of field definitions used as input for L{_PrepareFieldList}
1667
1668   """
1669   fields = [
1670     (_MakeField("disk_usage", "DiskUsage", QFT_UNIT,
1671                 "Total disk space used by instance on each of its nodes;"
1672                 " this is not the disk size visible to the instance, but"
1673                 " the usage on the node"),
1674      IQ_DISKUSAGE, 0, _GetInstDiskUsage),
1675     (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"),
1676      IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
1677     (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
1678      IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
1679     ]
1680
1681   # Disks by number
1682   fields.extend([
1683     (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1684                 "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
1685      IQ_CONFIG, 0, _GetInstDiskSize(i))
1686     for i in range(constants.MAX_DISKS)
1687     ])
1688
1689   return fields
1690
1691
1692 def _GetInstanceParameterFields():
1693   """Get instance fields involving parameters.
1694
1695   @return: List of field definitions used as input for L{_PrepareFieldList}
1696
1697   """
1698   # TODO: Consider moving titles closer to constants
1699   be_title = {
1700     constants.BE_AUTO_BALANCE: "Auto_balance",
1701     constants.BE_MAXMEM: "ConfigMaxMem",
1702     constants.BE_MINMEM: "ConfigMinMem",
1703     constants.BE_VCPUS: "ConfigVCPUs",
1704     }
1705
1706   hv_title = {
1707     constants.HV_ACPI: "ACPI",
1708     constants.HV_BOOT_ORDER: "Boot_order",
1709     constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1710     constants.HV_DISK_TYPE: "Disk_type",
1711     constants.HV_INITRD_PATH: "Initrd_path",
1712     constants.HV_KERNEL_PATH: "Kernel_path",
1713     constants.HV_NIC_TYPE: "NIC_type",
1714     constants.HV_PAE: "PAE",
1715     constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1716     }
1717
1718   fields = [
1719     # Filled parameters
1720     (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
1721                 "Hypervisor parameters (merged)"),
1722      IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
1723     (_MakeField("beparams", "BackendParameters", QFT_OTHER,
1724                 "Backend parameters (merged)"),
1725      IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
1726     (_MakeField("osparams", "OpSysParameters", QFT_OTHER,
1727                 "Operating system parameters (merged)"),
1728      IQ_CONFIG, 0, lambda ctx, _: ctx.inst_osparams),
1729
1730     # Unfilled parameters
1731     (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
1732                 "Custom hypervisor parameters"),
1733      IQ_CONFIG, 0, _GetItemAttr("hvparams")),
1734     (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
1735                 "Custom backend parameters",),
1736      IQ_CONFIG, 0, _GetItemAttr("beparams")),
1737     (_MakeField("custom_osparams", "CustomOpSysParameters", QFT_OTHER,
1738                 "Custom operating system parameters",),
1739      IQ_CONFIG, 0, _GetItemAttr("osparams")),
1740     (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
1741                 "Custom network interface parameters"),
1742      IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1743     ]
1744
1745   # HV params
1746   def _GetInstHvParam(name):
1747     return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1748
1749   fields.extend([
1750     (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1751                 _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1752      IQ_CONFIG, 0, _GetInstHvParam(name))
1753     for name, kind in constants.HVS_PARAMETER_TYPES.items()
1754     if name not in constants.HVC_GLOBALS
1755     ])
1756
1757   # BE params
1758   def _GetInstBeParam(name):
1759     return lambda ctx, _: ctx.inst_beparams.get(name, None)
1760
1761   fields.extend([
1762     (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1763                 _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1764      IQ_CONFIG, 0, _GetInstBeParam(name))
1765     for name, kind in constants.BES_PARAMETER_TYPES.items()
1766     ])
1767
1768   return fields
1769
1770
1771 _INST_SIMPLE_FIELDS = {
1772   "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"),
1773   "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"),
1774   "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"),
1775   # Depending on the hypervisor, the port can be None
1776   "network_port": ("Network_port", QFT_OTHER, 0,
1777                    "Instance network port if available (e.g. for VNC console)"),
1778   "os": ("OS", QFT_TEXT, 0, "Operating system"),
1779   "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"),
1780   "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"),
1781   }
1782
1783
1784 def _GetInstNodeGroup(ctx, default, node_name):
1785   """Gets group UUID of an instance node.
1786
1787   @type ctx: L{InstanceQueryData}
1788   @param default: Default value
1789   @type node_name: string
1790   @param node_name: Node name
1791
1792   """
1793   try:
1794     node = ctx.nodes[node_name]
1795   except KeyError:
1796     return default
1797   else:
1798     return node.group
1799
1800
1801 def _GetInstNodeGroupName(ctx, default, node_name):
1802   """Gets group name of an instance node.
1803
1804   @type ctx: L{InstanceQueryData}
1805   @param default: Default value
1806   @type node_name: string
1807   @param node_name: Node name
1808
1809   """
1810   try:
1811     node = ctx.nodes[node_name]
1812   except KeyError:
1813     return default
1814
1815   try:
1816     group = ctx.groups[node.group]
1817   except KeyError:
1818     return default
1819
1820   return group.name
1821
1822
1823 def _BuildInstanceFields():
1824   """Builds list of fields for instance queries.
1825
1826   """
1827   fields = [
1828     (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
1829      IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")),
1830     (_MakeField("pnode.group", "PrimaryNodeGroup", QFT_TEXT,
1831                 "Primary node's group"),
1832      IQ_NODES, 0,
1833      lambda ctx, inst: _GetInstNodeGroupName(ctx, _FS_UNAVAIL,
1834                                              inst.primary_node)),
1835     (_MakeField("pnode.group.uuid", "PrimaryNodeGroupUUID", QFT_TEXT,
1836                 "Primary node's group UUID"),
1837      IQ_NODES, 0,
1838      lambda ctx, inst: _GetInstNodeGroup(ctx, _FS_UNAVAIL, inst.primary_node)),
1839     # TODO: Allow filtering by secondary node as hostname
1840     (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
1841                 "Secondary nodes; usually this will just be one node"),
1842      IQ_CONFIG, 0, lambda ctx, inst: list(inst.secondary_nodes)),
1843     (_MakeField("snodes.group", "SecondaryNodesGroups", QFT_OTHER,
1844                 "Node groups of secondary nodes"),
1845      IQ_NODES, 0,
1846      lambda ctx, inst: map(compat.partial(_GetInstNodeGroupName, ctx, None),
1847                            inst.secondary_nodes)),
1848     (_MakeField("snodes.group.uuid", "SecondaryNodesGroupsUUID", QFT_OTHER,
1849                 "Node group UUIDs of secondary nodes"),
1850      IQ_NODES, 0,
1851      lambda ctx, inst: map(compat.partial(_GetInstNodeGroup, ctx, None),
1852                            inst.secondary_nodes)),
1853     (_MakeField("admin_state", "InstanceState", QFT_TEXT,
1854                 "Desired state of instance"),
1855      IQ_CONFIG, 0, _GetItemAttr("admin_state")),
1856     (_MakeField("admin_up", "Autostart", QFT_BOOL,
1857                 "Desired state of instance"),
1858      IQ_CONFIG, 0, lambda ctx, inst: inst.admin_state == constants.ADMINST_UP),
1859     (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
1860      lambda ctx, inst: list(inst.GetTags())),
1861     (_MakeField("console", "Console", QFT_OTHER,
1862                 "Instance console information"), IQ_CONSOLE, 0,
1863      _GetInstanceConsole),
1864     ]
1865
1866   # Add simple fields
1867   fields.extend([
1868     (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1869     for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()
1870     ])
1871
1872   # Fields requiring talking to the node
1873   fields.extend([
1874     (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
1875      IQ_LIVE, 0, _GetInstOperState),
1876     (_MakeField("oper_ram", "Memory", QFT_UNIT,
1877                 "Actual memory usage as seen by hypervisor"),
1878      IQ_LIVE, 0, _GetInstLiveData("memory")),
1879     (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
1880                 "Actual number of VCPUs as seen by hypervisor"),
1881      IQ_LIVE, 0, _GetInstLiveData("vcpus")),
1882     ])
1883
1884   # Status field
1885   status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
1886                    constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
1887                    constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
1888                    constants.INSTST_NODEOFFLINE, constants.INSTST_ADMINOFFLINE)
1889   status_doc = ("Instance status; \"%s\" if instance is set to be running"
1890                 " and actually is, \"%s\" if instance is stopped and"
1891                 " is not running, \"%s\" if instance running, but not on its"
1892                 " designated primary node, \"%s\" if instance should be"
1893                 " stopped, but is actually running, \"%s\" if instance should"
1894                 " run, but doesn't, \"%s\" if instance's primary node is down,"
1895                 " \"%s\" if instance's primary node is marked offline,"
1896                 " \"%s\" if instance is offline and does not use dynamic"
1897                 " resources" % status_values)
1898   fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
1899                  IQ_LIVE, 0, _GetInstStatus))
1900   assert set(status_values) == constants.INSTST_ALL, \
1901          "Status documentation mismatch"
1902
1903   (network_fields, network_aliases) = _GetInstanceNetworkFields()
1904
1905   fields.extend(network_fields)
1906   fields.extend(_GetInstanceParameterFields())
1907   fields.extend(_GetInstanceDiskFields())
1908   fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1909
1910   aliases = [
1911     ("vcpus", "be/vcpus"),
1912     ("be/memory", "be/maxmem"),
1913     ("sda_size", "disk.size/0"),
1914     ("sdb_size", "disk.size/1"),
1915     ] + network_aliases
1916
1917   return _PrepareFieldList(fields, aliases)
1918
1919
1920 class LockQueryData:
1921   """Data container for lock data queries.
1922
1923   """
1924   def __init__(self, lockdata):
1925     """Initializes this class.
1926
1927     """
1928     self.lockdata = lockdata
1929
1930   def __iter__(self):
1931     """Iterate over all locks.
1932
1933     """
1934     return iter(self.lockdata)
1935
1936
1937 def _GetLockOwners(_, data):
1938   """Returns a sorted list of a lock's current owners.
1939
1940   """
1941   (_, _, owners, _) = data
1942
1943   if owners:
1944     owners = utils.NiceSort(owners)
1945
1946   return owners
1947
1948
1949 def _GetLockPending(_, data):
1950   """Returns a sorted list of a lock's pending acquires.
1951
1952   """
1953   (_, _, _, pending) = data
1954
1955   if pending:
1956     pending = [(mode, utils.NiceSort(names))
1957                for (mode, names) in pending]
1958
1959   return pending
1960
1961
1962 def _BuildLockFields():
1963   """Builds list of fields for lock queries.
1964
1965   """
1966   return _PrepareFieldList([
1967     # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
1968     (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
1969      lambda ctx, (name, mode, owners, pending): name),
1970     (_MakeField("mode", "Mode", QFT_OTHER,
1971                 "Mode in which the lock is currently acquired"
1972                 " (exclusive or shared)"),
1973      LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
1974     (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
1975      LQ_OWNER, 0, _GetLockOwners),
1976     (_MakeField("pending", "Pending", QFT_OTHER,
1977                 "Threads waiting for the lock"),
1978      LQ_PENDING, 0, _GetLockPending),
1979     ], [])
1980
1981
1982 class GroupQueryData:
1983   """Data container for node group data queries.
1984
1985   """
1986   def __init__(self, cluster, groups, group_to_nodes, group_to_instances):
1987     """Initializes this class.
1988
1989     @param cluster: Cluster object
1990     @param groups: List of node group objects
1991     @type group_to_nodes: dict; group UUID as key
1992     @param group_to_nodes: Per-group list of nodes
1993     @type group_to_instances: dict; group UUID as key
1994     @param group_to_instances: Per-group list of (primary) instances
1995
1996     """
1997     self.groups = groups
1998     self.group_to_nodes = group_to_nodes
1999     self.group_to_instances = group_to_instances
2000     self.cluster = cluster
2001
2002     # Used for individual rows
2003     self.group_ipolicy = None
2004     self.ndparams = None
2005
2006   def __iter__(self):
2007     """Iterate over all node groups.
2008
2009     This function has side-effects and only one instance of the resulting
2010     generator should be used at a time.
2011
2012     """
2013     for group in self.groups:
2014       self.group_ipolicy = self.cluster.SimpleFillIPolicy(group.ipolicy)
2015       self.ndparams = self.cluster.SimpleFillND(group.ndparams)
2016       yield group
2017
2018
2019 _GROUP_SIMPLE_FIELDS = {
2020   "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
2021   "name": ("Group", QFT_TEXT, "Group name"),
2022   "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
2023   "uuid": ("UUID", QFT_TEXT, "Group UUID"),
2024   }
2025
2026
2027 def _BuildGroupFields():
2028   """Builds list of fields for node group queries.
2029
2030   """
2031   # Add simple fields
2032   fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
2033              _GetItemAttr(name))
2034             for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
2035
2036   def _GetLength(getter):
2037     return lambda ctx, group: len(getter(ctx)[group.uuid])
2038
2039   def _GetSortedList(getter):
2040     return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
2041
2042   group_to_nodes = operator.attrgetter("group_to_nodes")
2043   group_to_instances = operator.attrgetter("group_to_instances")
2044
2045   # Add fields for nodes
2046   fields.extend([
2047     (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
2048      GQ_NODE, 0, _GetLength(group_to_nodes)),
2049     (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
2050      GQ_NODE, 0, _GetSortedList(group_to_nodes)),
2051     ])
2052
2053   # Add fields for instances
2054   fields.extend([
2055     (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
2056                 "Number of primary instances"),
2057      GQ_INST, 0, _GetLength(group_to_instances)),
2058     (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
2059                 "List of primary instances"),
2060      GQ_INST, 0, _GetSortedList(group_to_instances)),
2061     ])
2062
2063   # Other fields
2064   fields.extend([
2065     (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), GQ_CONFIG, 0,
2066      lambda ctx, group: list(group.GetTags())),
2067     (_MakeField("ipolicy", "InstancePolicy", QFT_OTHER,
2068                 "Instance policy limitations (merged)"),
2069      GQ_CONFIG, 0, lambda ctx, _: ctx.group_ipolicy),
2070     (_MakeField("custom_ipolicy", "CustomInstancePolicy", QFT_OTHER,
2071                 "Custom instance policy limitations"),
2072      GQ_CONFIG, 0, _GetItemAttr("ipolicy")),
2073     (_MakeField("custom_ndparams", "CustomNDParams", QFT_OTHER,
2074                 "Custom node parameters"),
2075      GQ_CONFIG, 0, _GetItemAttr("ndparams")),
2076     (_MakeField("ndparams", "NDParams", QFT_OTHER,
2077                 "Node parameters"),
2078      GQ_CONFIG, 0, lambda ctx, _: ctx.ndparams),
2079     ])
2080
2081   # ND parameters
2082   fields.extend(_BuildNDFields(True))
2083
2084   fields.extend(_GetItemTimestampFields(GQ_CONFIG))
2085
2086   return _PrepareFieldList(fields, [])
2087
2088
2089 class OsInfo(objects.ConfigObject):
2090   __slots__ = [
2091     "name",
2092     "valid",
2093     "hidden",
2094     "blacklisted",
2095     "variants",
2096     "api_versions",
2097     "parameters",
2098     "node_status",
2099     ]
2100
2101
2102 def _BuildOsFields():
2103   """Builds list of fields for operating system queries.
2104
2105   """
2106   fields = [
2107     (_MakeField("name", "Name", QFT_TEXT, "Operating system name"),
2108      None, 0, _GetItemAttr("name")),
2109     (_MakeField("valid", "Valid", QFT_BOOL,
2110                 "Whether operating system definition is valid"),
2111      None, 0, _GetItemAttr("valid")),
2112     (_MakeField("hidden", "Hidden", QFT_BOOL,
2113                 "Whether operating system is hidden"),
2114      None, 0, _GetItemAttr("hidden")),
2115     (_MakeField("blacklisted", "Blacklisted", QFT_BOOL,
2116                 "Whether operating system is blacklisted"),
2117      None, 0, _GetItemAttr("blacklisted")),
2118     (_MakeField("variants", "Variants", QFT_OTHER,
2119                 "Operating system variants"),
2120      None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("variants"))),
2121     (_MakeField("api_versions", "ApiVersions", QFT_OTHER,
2122                 "Operating system API versions"),
2123      None, 0, _ConvWrap(sorted, _GetItemAttr("api_versions"))),
2124     (_MakeField("parameters", "Parameters", QFT_OTHER,
2125                 "Operating system parameters"),
2126      None, 0, _ConvWrap(compat.partial(utils.NiceSort, key=compat.fst),
2127                         _GetItemAttr("parameters"))),
2128     (_MakeField("node_status", "NodeStatus", QFT_OTHER,
2129                 "Status from node"),
2130      None, 0, _GetItemAttr("node_status")),
2131     ]
2132
2133   return _PrepareFieldList(fields, [])
2134
2135
2136 def _JobUnavailInner(fn, ctx, (job_id, job)): # pylint: disable=W0613
2137   """Return L{_FS_UNAVAIL} if job is None.
2138
2139   When listing specifc jobs (e.g. "gnt-job list 1 2 3"), a job may not be
2140   found, in which case this function converts it to L{_FS_UNAVAIL}.
2141
2142   """
2143   if job is None:
2144     return _FS_UNAVAIL
2145   else:
2146     return fn(job)
2147
2148
2149 def _JobUnavail(inner):
2150   """Wrapper for L{_JobUnavailInner}.
2151
2152   """
2153   return compat.partial(_JobUnavailInner, inner)
2154
2155
2156 def _PerJobOpInner(fn, job):
2157   """Executes a function per opcode in a job.
2158
2159   """
2160   return map(fn, job.ops)
2161
2162
2163 def _PerJobOp(fn):
2164   """Wrapper for L{_PerJobOpInner}.
2165
2166   """
2167   return _JobUnavail(compat.partial(_PerJobOpInner, fn))
2168
2169
2170 def _JobTimestampInner(fn, job):
2171   """Converts unavailable timestamp to L{_FS_UNAVAIL}.
2172
2173   """
2174   timestamp = fn(job)
2175
2176   if timestamp is None:
2177     return _FS_UNAVAIL
2178   else:
2179     return timestamp
2180
2181
2182 def _JobTimestamp(fn):
2183   """Wrapper for L{_JobTimestampInner}.
2184
2185   """
2186   return _JobUnavail(compat.partial(_JobTimestampInner, fn))
2187
2188
2189 def _BuildJobFields():
2190   """Builds list of fields for job queries.
2191
2192   """
2193   fields = [
2194     (_MakeField("id", "ID", QFT_TEXT, "Job ID"),
2195      None, 0, lambda _, (job_id, job): job_id),
2196     (_MakeField("status", "Status", QFT_TEXT, "Job status"),
2197      None, 0, _JobUnavail(lambda job: job.CalcStatus())),
2198     (_MakeField("priority", "Priority", QFT_NUMBER,
2199                 ("Current job priority (%s to %s)" %
2200                  (constants.OP_PRIO_LOWEST, constants.OP_PRIO_HIGHEST))),
2201      None, 0, _JobUnavail(lambda job: job.CalcPriority())),
2202     (_MakeField("ops", "OpCodes", QFT_OTHER, "List of all opcodes"),
2203      None, 0, _PerJobOp(lambda op: op.input.__getstate__())),
2204     (_MakeField("opresult", "OpCode_result", QFT_OTHER,
2205                 "List of opcodes results"),
2206      None, 0, _PerJobOp(operator.attrgetter("result"))),
2207     (_MakeField("opstatus", "OpCode_status", QFT_OTHER,
2208                 "List of opcodes status"),
2209      None, 0, _PerJobOp(operator.attrgetter("status"))),
2210     (_MakeField("oplog", "OpCode_log", QFT_OTHER,
2211                 "List of opcode output logs"),
2212      None, 0, _PerJobOp(operator.attrgetter("log"))),
2213     (_MakeField("opstart", "OpCode_start", QFT_OTHER,
2214                 "List of opcode start timestamps (before acquiring locks)"),
2215      None, 0, _PerJobOp(operator.attrgetter("start_timestamp"))),
2216     (_MakeField("opexec", "OpCode_exec", QFT_OTHER,
2217                 "List of opcode execution start timestamps (after acquiring"
2218                 " locks)"),
2219      None, 0, _PerJobOp(operator.attrgetter("exec_timestamp"))),
2220     (_MakeField("opend", "OpCode_end", QFT_OTHER,
2221                 "List of opcode execution end timestamps"),
2222      None, 0, _PerJobOp(operator.attrgetter("end_timestamp"))),
2223     (_MakeField("oppriority", "OpCode_prio", QFT_OTHER,
2224                 "List of opcode priorities"),
2225      None, 0, _PerJobOp(operator.attrgetter("priority"))),
2226     (_MakeField("received_ts", "Received", QFT_OTHER,
2227                 "Timestamp of when job was received"),
2228      None, 0, _JobTimestamp(operator.attrgetter("received_timestamp"))),
2229     (_MakeField("start_ts", "Start", QFT_OTHER,
2230                 "Timestamp of job start"),
2231      None, 0, _JobTimestamp(operator.attrgetter("start_timestamp"))),
2232     (_MakeField("end_ts", "End", QFT_OTHER,
2233                 "Timestamp of job end"),
2234      None, 0, _JobTimestamp(operator.attrgetter("end_timestamp"))),
2235     (_MakeField("summary", "Summary", QFT_OTHER,
2236                 "List of per-opcode summaries"),
2237      None, 0, _PerJobOp(lambda op: op.input.Summary())),
2238     ]
2239
2240   return _PrepareFieldList(fields, [])
2241
2242
2243 #: Fields available for node queries
2244 NODE_FIELDS = _BuildNodeFields()
2245
2246 #: Fields available for instance queries
2247 INSTANCE_FIELDS = _BuildInstanceFields()
2248
2249 #: Fields available for lock queries
2250 LOCK_FIELDS = _BuildLockFields()
2251
2252 #: Fields available for node group queries
2253 GROUP_FIELDS = _BuildGroupFields()
2254
2255 #: Fields available for operating system queries
2256 OS_FIELDS = _BuildOsFields()
2257
2258 #: Fields available for job queries
2259 JOB_FIELDS = _BuildJobFields()
2260
2261 #: All available resources
2262 ALL_FIELDS = {
2263   constants.QR_INSTANCE: INSTANCE_FIELDS,
2264   constants.QR_NODE: NODE_FIELDS,
2265   constants.QR_LOCK: LOCK_FIELDS,
2266   constants.QR_GROUP: GROUP_FIELDS,
2267   constants.QR_OS: OS_FIELDS,
2268   constants.QR_JOB: JOB_FIELDS,
2269   }
2270
2271 #: All available field lists
2272 ALL_FIELD_LISTS = ALL_FIELDS.values()