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