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