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