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