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