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