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