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