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