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