4 # Copyright (C) 2010, 2011, 2012 Google Inc.
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.
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.
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
22 """Module for query operations
26 - Add field definitions
27 - See how L{NODE_FIELDS} is built
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
34 - Value data type, e.g. L{constants.QFT_NUMBER}
35 - Human-readable description, must not end with punctuation or
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
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
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
50 @attention: Retrieval functions must be idempotent. They can be called multiple
51 times, in any order and any number of times.
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
65 from ganeti import runtime
66 from ganeti import qlang
67 from ganeti import jstore
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)
75 # Constants for requesting data from the caller/data provider. Each property
76 # collected/computed separately by the data provider should have its own to
77 # only collect the requested data and not more.
89 IQ_NODES) = range(100, 105)
93 LQ_PENDING) = range(10, 13)
98 GQ_DISKPARAMS) = range(200, 204)
102 CQ_WATCHER_PAUSE) = range(300, 303)
106 QFF_IP_ADDRESS = 0x02
108 QFF_SPLIT_TIMESTAMP = 0x08
109 # Next values: 0x10, 0x20, 0x40, 0x80, 0x100, 0x200
110 QFF_ALL = (QFF_HOSTNAME | QFF_IP_ADDRESS | QFF_JOB_ID | QFF_SPLIT_TIMESTAMP)
112 FIELD_NAME_RE = re.compile(r"^[a-z0-9/._]+$")
113 TITLE_RE = re.compile(r"^[^\s]+$")
114 DOC_RE = re.compile(r"^[A-Z].*[^.,?!]$")
116 #: Verification function for each field type
118 QFT_UNKNOWN: ht.TNone,
119 QFT_TEXT: ht.TString,
123 QFT_TIMESTAMP: ht.TNumber,
124 QFT_OTHER: lambda _: True,
127 # Unique objects for special field statuses
128 _FS_UNKNOWN = object()
129 _FS_NODATA = object()
130 _FS_UNAVAIL = object()
131 _FS_OFFLINE = object()
133 #: List of all special status
134 _FS_ALL = frozenset([_FS_UNKNOWN, _FS_NODATA, _FS_UNAVAIL, _FS_OFFLINE])
136 #: VType to QFT mapping
138 # TODO: fix validation of empty strings
139 constants.VTYPE_STRING: QFT_OTHER, # since VTYPE_STRINGs can be empty
140 constants.VTYPE_MAYBE_STRING: QFT_OTHER,
141 constants.VTYPE_BOOL: QFT_BOOL,
142 constants.VTYPE_SIZE: QFT_UNIT,
143 constants.VTYPE_INT: QFT_NUMBER,
146 _SERIAL_NO_DOC = "%s object serial number, incremented on each modification"
148 # TODO: Consider moving titles closer to constants
150 constants.ND_OOB_PROGRAM: "OutOfBandProgram",
151 constants.ND_SPINDLE_COUNT: "SpindleCount",
155 def _GetUnknownField(ctx, item): # pylint: disable=W0613
156 """Gets the contents of an unknown field.
162 def _GetQueryFields(fielddefs, selected):
163 """Calculates the internal list of selected fields.
165 Unknown fields are returned as L{constants.QFT_UNKNOWN}.
167 @type fielddefs: dict
168 @param fielddefs: Field definitions
169 @type selected: list of strings
170 @param selected: List of selected fields
175 for name in selected:
177 fdef = fielddefs[name]
179 fdef = (_MakeField(name, name, QFT_UNKNOWN, "Unknown field '%s'" % name),
180 None, 0, _GetUnknownField)
182 assert len(fdef) == 4
189 def GetAllFields(fielddefs):
190 """Extract L{objects.QueryFieldDefinition} from field definitions.
192 @rtype: list of L{objects.QueryFieldDefinition}
195 return [fdef for (fdef, _, _, _) in fielddefs]
199 """Class for filter analytics.
201 When filters are used, the user of the L{Query} class usually doesn't know
202 exactly which items will be necessary for building the result. It therefore
203 has to prepare and compute the input data for potentially returning
206 There are two ways to optimize this. The first, and simpler, is to assign
207 each field a group of data, so that the caller can determine which
208 computations are necessary depending on the data groups requested. The list
209 of referenced groups must also be computed for fields referenced in the
212 The second is restricting the items based on a primary key. The primary key
213 is usually a unique name (e.g. a node name). This class extracts all
214 referenced names from a filter. If it encounters any filter condition which
215 disallows such a list to be determined (e.g. a non-equality filter), all
216 names will be requested.
218 The end-effect is that any operation other than L{qlang.OP_OR} and
219 L{qlang.OP_EQUAL} will make the query more expensive.
222 def __init__(self, namefield):
223 """Initializes this class.
225 @type namefield: string
226 @param namefield: Field caller is interested in
229 self._namefield = namefield
231 #: Whether all names need to be requested (e.g. if a non-equality operator
233 self._allnames = False
235 #: Which names to request
238 #: Data kinds referenced by the filter (used by L{Query.RequestedData})
239 self._datakinds = set()
241 def RequestedNames(self):
242 """Returns all requested values.
244 Returns C{None} if list of values can't be determined (e.g. encountered
245 non-equality operators).
250 if self._allnames or self._names is None:
253 return utils.UniqueSequence(self._names)
255 def ReferencedData(self):
256 """Returns all kinds of data referenced by the filter.
259 return frozenset(self._datakinds)
261 def _NeedAllNames(self):
262 """Changes internal state to request all names.
265 self._allnames = True
268 def NoteLogicOp(self, op):
269 """Called when handling a logic operation.
275 if op != qlang.OP_OR:
278 def NoteUnaryOp(self, op): # pylint: disable=W0613
279 """Called when handling an unary operation.
287 def NoteBinaryOp(self, op, datakind, name, value):
288 """Called when handling a binary operation.
293 @param name: Left-hand side of operator (field name)
294 @param value: Right-hand side of operator
297 if datakind is not None:
298 self._datakinds.add(datakind)
303 # If any operator other than equality was used, all names need to be
305 if op == qlang.OP_EQUAL and name == self._namefield:
306 if self._names is None:
308 self._names.append(value)
313 def _WrapLogicOp(op_fn, sentences, ctx, item):
314 """Wrapper for logic operator functions.
317 return op_fn(fn(ctx, item) for fn in sentences)
320 def _WrapUnaryOp(op_fn, inner, ctx, item):
321 """Wrapper for unary operator functions.
324 return op_fn(inner(ctx, item))
327 def _WrapBinaryOp(op_fn, retrieval_fn, value, ctx, item):
328 """Wrapper for binary operator functions.
331 return op_fn(retrieval_fn(ctx, item), value)
334 def _WrapNot(fn, lhs, rhs):
335 """Negates the result of a wrapped function.
338 return not fn(lhs, rhs)
341 def _PrepareRegex(pattern):
342 """Compiles a regular expression.
346 return re.compile(pattern)
347 except re.error, err:
348 raise errors.ParameterError("Invalid regex pattern (%s)" % err)
351 def _PrepareSplitTimestamp(value):
352 """Prepares a value for comparison by L{_MakeSplitTimestampComparison}.
355 if ht.TNumber(value):
358 return utils.MergeTime(value)
361 def _MakeSplitTimestampComparison(fn):
362 """Compares split timestamp values after converting to float.
365 return lambda lhs, rhs: fn(utils.MergeTime(lhs), rhs)
368 def _MakeComparisonChecks(fn):
369 """Prepares flag-specific comparisons using a comparison function.
373 (QFF_SPLIT_TIMESTAMP, _MakeSplitTimestampComparison(fn),
374 _PrepareSplitTimestamp),
375 (QFF_JOB_ID, lambda lhs, rhs: fn(jstore.ParseJobId(lhs), rhs),
381 class _FilterCompilerHelper:
382 """Converts a query filter to a callable usable for filtering.
385 # String statement has no effect, pylint: disable=W0105
387 #: How deep filters can be nested
390 # Unique identifiers for operator groups
393 _OPTYPE_BINARY) = range(1, 4)
395 """Functions for equality checks depending on field flags.
397 List of tuples containing flags and a callable receiving the left- and
398 right-hand side of the operator. The flags are an OR-ed value of C{QFF_*}
399 (e.g. L{QFF_HOSTNAME} or L{QFF_SPLIT_TIMESTAMP}).
401 Order matters. The first item with flags will be used. Flags are checked
407 lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
408 case_sensitive=False),
410 (QFF_SPLIT_TIMESTAMP, _MakeSplitTimestampComparison(operator.eq),
411 _PrepareSplitTimestamp),
412 (None, operator.eq, None),
417 Operator as key (C{qlang.OP_*}), value a tuple of operator group
418 (C{_OPTYPE_*}) and a group-specific value:
420 - C{_OPTYPE_LOGIC}: Callable taking any number of arguments; used by
422 - C{_OPTYPE_UNARY}: Always C{None}; details handled by L{_HandleUnaryOp}
423 - C{_OPTYPE_BINARY}: Callable taking exactly two parameters, the left- and
424 right-hand side of the operator, used by L{_HandleBinaryOp}
429 qlang.OP_OR: (_OPTYPE_LOGIC, compat.any),
430 qlang.OP_AND: (_OPTYPE_LOGIC, compat.all),
433 qlang.OP_NOT: (_OPTYPE_UNARY, None),
434 qlang.OP_TRUE: (_OPTYPE_UNARY, None),
437 qlang.OP_EQUAL: (_OPTYPE_BINARY, _EQUALITY_CHECKS),
439 (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn), valprepfn)
440 for (flags, fn, valprepfn) in _EQUALITY_CHECKS]),
441 qlang.OP_LT: (_OPTYPE_BINARY, _MakeComparisonChecks(operator.lt)),
442 qlang.OP_LE: (_OPTYPE_BINARY, _MakeComparisonChecks(operator.le)),
443 qlang.OP_GT: (_OPTYPE_BINARY, _MakeComparisonChecks(operator.gt)),
444 qlang.OP_GE: (_OPTYPE_BINARY, _MakeComparisonChecks(operator.ge)),
445 qlang.OP_REGEXP: (_OPTYPE_BINARY, [
446 (None, lambda lhs, rhs: rhs.search(lhs), _PrepareRegex),
448 qlang.OP_CONTAINS: (_OPTYPE_BINARY, [
449 (None, operator.contains, None),
453 def __init__(self, fields):
454 """Initializes this class.
456 @param fields: Field definitions (return value of L{_PrepareFieldList})
459 self._fields = fields
461 self._op_handler = None
463 def __call__(self, hints, qfilter):
464 """Converts a query filter into a callable function.
466 @type hints: L{_FilterHints} or None
467 @param hints: Callbacks doing analysis on filter
469 @param qfilter: Filter structure
471 @return: Function receiving context and item as parameters, returning
472 boolean as to whether item matches filter
477 (self._HandleLogicOp, getattr(hints, "NoteLogicOp", None)),
479 (self._HandleUnaryOp, getattr(hints, "NoteUnaryOp", None)),
481 (self._HandleBinaryOp, getattr(hints, "NoteBinaryOp", None)),
485 filter_fn = self._Compile(qfilter, 0)
487 self._op_handler = None
491 def _Compile(self, qfilter, level):
492 """Inner function for converting filters.
494 Calls the correct handler functions for the top-level operator. This
495 function is called recursively (e.g. for logic operators).
498 if not (isinstance(qfilter, (list, tuple)) and qfilter):
499 raise errors.ParameterError("Invalid filter on level %s" % level)
502 if level >= self._LEVELS_MAX:
503 raise errors.ParameterError("Only up to %s levels are allowed (filter"
504 " nested too deep)" % self._LEVELS_MAX)
506 # Create copy to be modified
507 operands = qfilter[:]
511 (kind, op_data) = self._OPS[op]
513 raise errors.ParameterError("Unknown operator '%s'" % op)
515 (handler, hints_cb) = self._op_handler[kind]
517 return handler(hints_cb, level, op, op_data, operands)
519 def _LookupField(self, name):
520 """Returns a field definition by name.
524 return self._fields[name]
526 raise errors.ParameterError("Unknown field '%s'" % name)
528 def _HandleLogicOp(self, hints_fn, level, op, op_fn, operands):
529 """Handles logic operators.
531 @type hints_fn: callable
532 @param hints_fn: Callback doing some analysis on the filter
534 @param level: Current depth
537 @type op_fn: callable
538 @param op_fn: Function implementing operator
540 @param operands: List of operands
546 return compat.partial(_WrapLogicOp, op_fn,
547 [self._Compile(op, level + 1) for op in operands])
549 def _HandleUnaryOp(self, hints_fn, level, op, op_fn, operands):
550 """Handles unary operators.
552 @type hints_fn: callable
553 @param hints_fn: Callback doing some analysis on the filter
555 @param level: Current depth
558 @type op_fn: callable
559 @param op_fn: Function implementing operator
561 @param operands: List of operands
569 if len(operands) != 1:
570 raise errors.ParameterError("Unary operator '%s' expects exactly one"
573 if op == qlang.OP_TRUE:
574 (_, _, _, retrieval_fn) = self._LookupField(operands[0])
576 op_fn = operator.truth
578 elif op == qlang.OP_NOT:
579 op_fn = operator.not_
580 arg = self._Compile(operands[0], level + 1)
582 raise errors.ProgrammerError("Can't handle operator '%s'" % op)
584 return compat.partial(_WrapUnaryOp, op_fn, arg)
586 def _HandleBinaryOp(self, hints_fn, level, op, op_data, operands):
587 """Handles binary operators.
589 @type hints_fn: callable
590 @param hints_fn: Callback doing some analysis on the filter
592 @param level: Current depth
595 @param op_data: Functions implementing operators
597 @param operands: List of operands
600 # Unused arguments, pylint: disable=W0613
602 (name, value) = operands
603 except (ValueError, TypeError):
604 raise errors.ParameterError("Invalid binary operator, expected exactly"
607 (fdef, datakind, field_flags, retrieval_fn) = self._LookupField(name)
609 assert fdef.kind != QFT_UNKNOWN
611 # TODO: Type conversions?
613 verify_fn = _VERIFY_FN[fdef.kind]
614 if not verify_fn(value):
615 raise errors.ParameterError("Unable to compare field '%s' (type '%s')"
616 " with '%s', expected %s" %
617 (name, fdef.kind, value.__class__.__name__,
621 hints_fn(op, datakind, name, value)
623 for (fn_flags, fn, valprepfn) in op_data:
624 if fn_flags is None or fn_flags & field_flags:
625 # Prepare value if necessary (e.g. compile regular expression)
627 value = valprepfn(value)
629 return compat.partial(_WrapBinaryOp, fn, retrieval_fn, value)
631 raise errors.ProgrammerError("Unable to find operator implementation"
632 " (op '%s', flags %s)" % (op, field_flags))
635 def _CompileFilter(fields, hints, qfilter):
636 """Converts a query filter into a callable function.
638 See L{_FilterCompilerHelper} for details.
643 return _FilterCompilerHelper(fields)(hints, qfilter)
647 def __init__(self, fieldlist, selected, qfilter=None, namefield=None):
648 """Initializes this class.
650 The field definition is a dictionary with the field's name as a key and a
651 tuple containing, in order, the field definition object
652 (L{objects.QueryFieldDefinition}, the data kind to help calling code
653 collect data and a retrieval function. The retrieval function is called
654 with two parameters, in order, the data container and the item in container
655 (see L{Query.Query}).
657 Users of this class can call L{RequestedData} before preparing the data
658 container to determine what data is needed.
660 @type fieldlist: dictionary
661 @param fieldlist: Field definitions
662 @type selected: list of strings
663 @param selected: List of selected fields
666 assert namefield is None or namefield in fieldlist
668 self._fields = _GetQueryFields(fieldlist, selected)
670 self._filter_fn = None
671 self._requested_names = None
672 self._filter_datakinds = frozenset()
674 if qfilter is not None:
675 # Collect requested names if wanted
677 hints = _FilterHints(namefield)
681 # Build filter function
682 self._filter_fn = _CompileFilter(fieldlist, hints, qfilter)
684 self._requested_names = hints.RequestedNames()
685 self._filter_datakinds = hints.ReferencedData()
687 if namefield is None:
690 (_, _, _, self._name_fn) = fieldlist[namefield]
692 def RequestedNames(self):
693 """Returns all names referenced in the filter.
695 If there is no filter or operators are preventing determining the exact
696 names, C{None} is returned.
699 return self._requested_names
701 def RequestedData(self):
702 """Gets requested kinds of data.
707 return (self._filter_datakinds |
708 frozenset(datakind for (_, datakind, _, _) in self._fields
709 if datakind is not None))
712 """Returns the list of fields for this query.
714 Includes unknown fields.
716 @rtype: List of L{objects.QueryFieldDefinition}
719 return GetAllFields(self._fields)
721 def Query(self, ctx, sort_by_name=True):
724 @param ctx: Data container passed to field retrieval functions, must
725 support iteration using C{__iter__}
726 @type sort_by_name: boolean
727 @param sort_by_name: Whether to sort by name or keep the input data's
731 sort = (self._name_fn and sort_by_name)
735 for idx, item in enumerate(ctx):
736 if not (self._filter_fn is None or self._filter_fn(ctx, item)):
739 row = [_ProcessResult(fn(ctx, item)) for (_, _, _, fn) in self._fields]
743 _VerifyResultRow(self._fields, row)
746 (status, name) = _ProcessResult(self._name_fn(ctx, item))
747 assert status == constants.RS_NORMAL
748 # TODO: Are there cases where we wouldn't want to use NiceSort?
749 # Answer: if the name field is non-string...
750 result.append((utils.NiceSortKey(name), idx, row))
757 # TODO: Would "heapq" be more efficient than sorting?
759 # Sorting in-place instead of using "sorted()"
762 assert not result or (len(result[0]) == 3 and len(result[-1]) == 3)
764 return map(operator.itemgetter(2), result)
766 def OldStyleQuery(self, ctx, sort_by_name=True):
767 """Query with "old" query result format.
769 See L{Query.Query} for arguments.
772 unknown = set(fdef.name for (fdef, _, _, _) in self._fields
773 if fdef.kind == QFT_UNKNOWN)
775 raise errors.OpPrereqError("Unknown output fields selected: %s" %
776 (utils.CommaJoin(unknown), ),
779 return [[value for (_, value) in row]
780 for row in self.Query(ctx, sort_by_name=sort_by_name)]
783 def _ProcessResult(value):
784 """Converts result values into externally-visible ones.
787 if value is _FS_UNKNOWN:
788 return (RS_UNKNOWN, None)
789 elif value is _FS_NODATA:
790 return (RS_NODATA, None)
791 elif value is _FS_UNAVAIL:
792 return (RS_UNAVAIL, None)
793 elif value is _FS_OFFLINE:
794 return (RS_OFFLINE, None)
796 return (RS_NORMAL, value)
799 def _VerifyResultRow(fields, row):
800 """Verifies the contents of a query result row.
803 @param fields: Field definitions for result
804 @type row: list of tuples
808 assert len(row) == len(fields)
810 for ((status, value), (fdef, _, _, _)) in zip(row, fields):
811 if status == RS_NORMAL:
812 if not _VERIFY_FN[fdef.kind](value):
813 errs.append("normal field %s fails validation (value is %s)" %
815 elif value is not None:
816 errs.append("abnormal field %s has a non-None value" % fdef.name)
817 assert not errs, ("Failed validation: %s in row %s" %
818 (utils.CommaJoin(errs), row))
821 def _FieldDictKey((fdef, _, flags, fn)):
822 """Generates key for field dictionary.
825 assert fdef.name and fdef.title, "Name and title are required"
826 assert FIELD_NAME_RE.match(fdef.name)
827 assert TITLE_RE.match(fdef.title)
828 assert (DOC_RE.match(fdef.doc) and len(fdef.doc.splitlines()) == 1 and
829 fdef.doc.strip() == fdef.doc), \
830 "Invalid description for field '%s'" % fdef.name
832 assert (flags & ~QFF_ALL) == 0, "Unknown flags for field '%s'" % fdef.name
837 def _PrepareFieldList(fields, aliases):
838 """Prepares field list for use by L{Query}.
840 Converts the list to a dictionary and does some verification.
842 @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data
843 kind, retrieval function)
844 @param fields: List of fields, see L{Query.__init__} for a better
846 @type aliases: list of tuples; (alias, target)
847 @param aliases: list of tuples containing aliases; for each
848 alias/target pair, a duplicate will be created in the field list
850 @return: Field dictionary for L{Query}
854 duplicates = utils.FindDuplicates(fdef.title.lower()
855 for (fdef, _, _, _) in fields)
856 assert not duplicates, "Duplicate title(s) found: %r" % duplicates
858 result = utils.SequenceToDict(fields, key=_FieldDictKey)
860 for alias, target in aliases:
861 assert alias not in result, "Alias %s overrides an existing field" % alias
862 assert target in result, "Missing target %s for alias %s" % (target, alias)
863 (fdef, k, flags, fn) = result[target]
866 result[alias] = (fdef, k, flags, fn)
868 assert len(result) == len(fields) + len(aliases)
869 assert compat.all(name == fdef.name
870 for (name, (fdef, _, _, _)) in result.items())
875 def GetQueryResponse(query, ctx, sort_by_name=True):
876 """Prepares the response for a query.
878 @type query: L{Query}
879 @param ctx: Data container, see L{Query.Query}
880 @type sort_by_name: boolean
881 @param sort_by_name: Whether to sort by name or keep the input data's
885 return objects.QueryResponse(data=query.Query(ctx, sort_by_name=sort_by_name),
886 fields=query.GetFields()).ToDict()
889 def QueryFields(fielddefs, selected):
890 """Returns list of available fields.
892 @type fielddefs: dict
893 @param fielddefs: Field definitions
894 @type selected: list of strings
895 @param selected: List of selected fields
896 @return: List of L{objects.QueryFieldDefinition}
900 # Client requests all fields, sort by name
901 fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
902 key=operator.attrgetter("name"))
904 # Keep order as requested by client
905 fdefs = Query(fielddefs, selected).GetFields()
907 return objects.QueryFieldsResponse(fields=fdefs).ToDict()
910 def _MakeField(name, title, kind, doc):
911 """Wrapper for creating L{objects.QueryFieldDefinition} instances.
913 @param name: Field name as a regular expression
914 @param title: Human-readable title
915 @param kind: Field type
916 @param doc: Human-readable description
919 return objects.QueryFieldDefinition(name=name, title=title, kind=kind,
923 def _StaticValueInner(value, ctx, _): # pylint: disable=W0613
924 """Returns a static value.
930 def _StaticValue(value):
931 """Prepares a function to return a static value.
934 return compat.partial(_StaticValueInner, value)
937 def _GetNodeRole(node, master_name):
938 """Determine node role.
940 @type node: L{objects.Node}
941 @param node: Node object
942 @type master_name: string
943 @param master_name: Master node name
946 if node.name == master_name:
947 return constants.NR_MASTER
948 elif node.master_candidate:
949 return constants.NR_MCANDIDATE
951 return constants.NR_DRAINED
953 return constants.NR_OFFLINE
955 return constants.NR_REGULAR
958 def _GetItemAttr(attr):
959 """Returns a field function to return an attribute of the item.
961 @param attr: Attribute name
964 getter = operator.attrgetter(attr)
965 return lambda _, item: getter(item)
968 def _GetNDParam(name):
969 """Return a field function to return an ND parameter out of the context.
973 if ctx.ndparams is None:
976 return ctx.ndparams.get(name, None)
980 def _BuildNDFields(is_group):
981 """Builds all the ndparam fields.
983 @param is_group: whether this is called at group or node level
987 field_kind = GQ_CONFIG
989 field_kind = NQ_GROUP
990 return [(_MakeField("ndp/%s" % name, NDP_TITLE.get(name, "ndp/%s" % name),
991 _VTToQFT[kind], "The \"%s\" node parameter" % name),
992 field_kind, 0, _GetNDParam(name))
993 for name, kind in constants.NDS_PARAMETER_TYPES.items()]
996 def _ConvWrapInner(convert, fn, ctx, item):
997 """Wrapper for converting values.
999 @param convert: Conversion function receiving value as single parameter
1000 @param fn: Retrieval function
1003 value = fn(ctx, item)
1005 # Is the value an abnormal status?
1006 if compat.any(value is fs for fs in _FS_ALL):
1010 # TODO: Should conversion function also receive context, item or both?
1011 return convert(value)
1014 def _ConvWrap(convert, fn):
1015 """Convenience wrapper for L{_ConvWrapInner}.
1017 @param convert: Conversion function receiving value as single parameter
1018 @param fn: Retrieval function
1021 return compat.partial(_ConvWrapInner, convert, fn)
1024 def _GetItemTimestamp(getter):
1025 """Returns function for getting timestamp of item.
1027 @type getter: callable
1028 @param getter: Function to retrieve timestamp attribute
1032 """Returns a timestamp of item.
1035 timestamp = getter(item)
1036 if timestamp is None:
1037 # Old configs might not have all timestamps
1045 def _GetItemTimestampFields(datatype):
1046 """Returns common timestamp fields.
1048 @param datatype: Field data type for use by L{Query.RequestedData}
1052 (_MakeField("ctime", "CTime", QFT_TIMESTAMP, "Creation timestamp"),
1053 datatype, 0, _GetItemTimestamp(operator.attrgetter("ctime"))),
1054 (_MakeField("mtime", "MTime", QFT_TIMESTAMP, "Modification timestamp"),
1055 datatype, 0, _GetItemTimestamp(operator.attrgetter("mtime"))),
1059 class NodeQueryData:
1060 """Data container for node data queries.
1063 def __init__(self, nodes, live_data, master_name, node_to_primary,
1064 node_to_secondary, groups, oob_support, cluster):
1065 """Initializes this class.
1069 self.live_data = live_data
1070 self.master_name = master_name
1071 self.node_to_primary = node_to_primary
1072 self.node_to_secondary = node_to_secondary
1073 self.groups = groups
1074 self.oob_support = oob_support
1075 self.cluster = cluster
1077 # Used for individual rows
1078 self.curlive_data = None
1079 self.ndparams = None
1082 """Iterate over all nodes.
1084 This function has side-effects and only one instance of the resulting
1085 generator should be used at a time.
1088 for node in self.nodes:
1089 group = self.groups.get(node.group, None)
1091 self.ndparams = None
1093 self.ndparams = self.cluster.FillND(node, group)
1095 self.curlive_data = self.live_data.get(node.name, None)
1097 self.curlive_data = None
1101 #: Fields that are direct attributes of an L{objects.Node} object
1102 _NODE_SIMPLE_FIELDS = {
1103 "drained": ("Drained", QFT_BOOL, 0, "Whether node is drained"),
1104 "master_candidate": ("MasterC", QFT_BOOL, 0,
1105 "Whether node is a master candidate"),
1106 "master_capable": ("MasterCapable", QFT_BOOL, 0,
1107 "Whether node can become a master candidate"),
1108 "name": ("Node", QFT_TEXT, QFF_HOSTNAME, "Node name"),
1109 "offline": ("Offline", QFT_BOOL, 0, "Whether node is marked offline"),
1110 "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Node"),
1111 "uuid": ("UUID", QFT_TEXT, 0, "Node UUID"),
1112 "vm_capable": ("VMCapable", QFT_BOOL, 0, "Whether node can host instances"),
1116 #: Fields requiring talking to the node
1117 # Note that none of these are available for non-vm_capable nodes
1118 _NODE_LIVE_FIELDS = {
1119 "bootid": ("BootID", QFT_TEXT, "bootid",
1120 "Random UUID renewed for each system reboot, can be used"
1121 " for detecting reboots by tracking changes"),
1122 "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes",
1123 "Number of NUMA domains on node (if exported by hypervisor)"),
1124 "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets",
1125 "Number of physical CPU sockets (if exported by hypervisor)"),
1126 "ctotal": ("CTotal", QFT_NUMBER, "cpu_total", "Number of logical processors"),
1127 "dfree": ("DFree", QFT_UNIT, "vg_free",
1128 "Available disk space in volume group"),
1129 "dtotal": ("DTotal", QFT_UNIT, "vg_size",
1130 "Total disk space in volume group used for instance disk"
1132 "mfree": ("MFree", QFT_UNIT, "memory_free",
1133 "Memory available for instance allocations"),
1134 "mnode": ("MNode", QFT_UNIT, "memory_dom0",
1135 "Amount of memory used by node (dom0 for Xen)"),
1136 "mtotal": ("MTotal", QFT_UNIT, "memory_total",
1137 "Total amount of memory of physical machine"),
1142 """Build function for calling another function with an node group.
1144 @param cb: The callback to be called with the nodegroup
1148 """Get group data for a node.
1150 @type ctx: L{NodeQueryData}
1151 @type inst: L{objects.Node}
1152 @param inst: Node object
1155 ng = ctx.groups.get(node.group, None)
1157 # Nodes always have a group, or the configuration is corrupt
1160 return cb(ctx, node, ng)
1165 def _GetNodeGroup(ctx, node, ng): # pylint: disable=W0613
1166 """Returns the name of a node's group.
1168 @type ctx: L{NodeQueryData}
1169 @type node: L{objects.Node}
1170 @param node: Node object
1171 @type ng: L{objects.NodeGroup}
1172 @param ng: The node group this node belongs to
1178 def _GetNodePower(ctx, node):
1179 """Returns the node powered state
1181 @type ctx: L{NodeQueryData}
1182 @type node: L{objects.Node}
1183 @param node: Node object
1186 if ctx.oob_support[node.name]:
1192 def _GetNdParams(ctx, node, ng):
1193 """Returns the ndparams for this node.
1195 @type ctx: L{NodeQueryData}
1196 @type node: L{objects.Node}
1197 @param node: Node object
1198 @type ng: L{objects.NodeGroup}
1199 @param ng: The node group this node belongs to
1202 return ctx.cluster.SimpleFillND(ng.FillND(node))
1205 def _GetLiveNodeField(field, kind, ctx, node):
1206 """Gets the value of a "live" field from L{NodeQueryData}.
1208 @param field: Live field name
1209 @param kind: Data kind, one of L{constants.QFT_ALL}
1210 @type ctx: L{NodeQueryData}
1211 @type node: L{objects.Node}
1212 @param node: Node object
1218 if not node.vm_capable:
1221 if not ctx.curlive_data:
1225 value = ctx.curlive_data[field]
1229 if kind == QFT_TEXT:
1232 assert kind in (QFT_NUMBER, QFT_UNIT)
1234 # Try to convert into number
1237 except (ValueError, TypeError):
1238 logging.exception("Failed to convert node field '%s' (value %r) to int",
1243 def _GetNodeHvState(_, node):
1244 """Converts node's hypervisor state for query result.
1247 hv_state = node.hv_state
1249 if hv_state is None:
1252 return dict((name, value.ToDict()) for (name, value) in hv_state.items())
1255 def _GetNodeDiskState(_, node):
1256 """Converts node's disk state for query result.
1259 disk_state = node.disk_state
1261 if disk_state is None:
1264 return dict((disk_kind, dict((name, value.ToDict())
1265 for (name, value) in kind_state.items()))
1266 for (disk_kind, kind_state) in disk_state.items())
1269 def _BuildNodeFields():
1270 """Builds list of fields for node queries.
1274 (_MakeField("pip", "PrimaryIP", QFT_TEXT, "Primary IP address"),
1275 NQ_CONFIG, 0, _GetItemAttr("primary_ip")),
1276 (_MakeField("sip", "SecondaryIP", QFT_TEXT, "Secondary IP address"),
1277 NQ_CONFIG, 0, _GetItemAttr("secondary_ip")),
1278 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0,
1279 lambda ctx, node: list(node.GetTags())),
1280 (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"),
1281 NQ_CONFIG, 0, lambda ctx, node: node.name == ctx.master_name),
1282 (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0,
1283 _GetGroup(_GetNodeGroup)),
1284 (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"),
1285 NQ_CONFIG, 0, _GetItemAttr("group")),
1286 (_MakeField("powered", "Powered", QFT_BOOL,
1287 "Whether node is thought to be powered on"),
1288 NQ_OOB, 0, _GetNodePower),
1289 (_MakeField("ndparams", "NodeParameters", QFT_OTHER,
1290 "Merged node parameters"),
1291 NQ_GROUP, 0, _GetGroup(_GetNdParams)),
1292 (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER,
1293 "Custom node parameters"),
1294 NQ_GROUP, 0, _GetItemAttr("ndparams")),
1295 (_MakeField("hv_state", "HypervisorState", QFT_OTHER, "Hypervisor state"),
1296 NQ_CONFIG, 0, _GetNodeHvState),
1297 (_MakeField("disk_state", "DiskState", QFT_OTHER, "Disk state"),
1298 NQ_CONFIG, 0, _GetNodeDiskState),
1301 fields.extend(_BuildNDFields(False))
1304 role_values = (constants.NR_MASTER, constants.NR_MCANDIDATE,
1305 constants.NR_REGULAR, constants.NR_DRAINED,
1306 constants.NR_OFFLINE)
1307 role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate,"
1308 " \"%s\" for regular, \"%s\" for drained, \"%s\" for offline" %
1310 fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
1311 lambda ctx, node: _GetNodeRole(node, ctx.master_name)))
1312 assert set(role_values) == constants.NR_ALL
1314 def _GetLength(getter):
1315 return lambda ctx, node: len(getter(ctx)[node.name])
1317 def _GetList(getter):
1318 return lambda ctx, node: list(getter(ctx)[node.name])
1320 # Add fields operating on instance lists
1321 for prefix, titleprefix, docword, getter in \
1322 [("p", "Pri", "primary", operator.attrgetter("node_to_primary")),
1323 ("s", "Sec", "secondary", operator.attrgetter("node_to_secondary"))]:
1324 # TODO: Allow filterting by hostname in list
1326 (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER,
1327 "Number of instances with this node as %s" % docword),
1328 NQ_INST, 0, _GetLength(getter)),
1329 (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
1331 "List of instances with this node as %s" % docword),
1332 NQ_INST, 0, _GetList(getter)),
1337 (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1338 for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()
1341 # Add fields requiring live data
1343 (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1344 compat.partial(_GetLiveNodeField, nfield, kind))
1345 for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()
1349 fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1351 return _PrepareFieldList(fields, [])
1354 class InstanceQueryData:
1355 """Data container for instance data queries.
1358 def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
1359 live_data, wrongnode_inst, console, nodes, groups):
1360 """Initializes this class.
1362 @param instances: List of instance objects
1363 @param cluster: Cluster object
1364 @type disk_usage: dict; instance name as key
1365 @param disk_usage: Per-instance disk usage
1366 @type offline_nodes: list of strings
1367 @param offline_nodes: List of offline nodes
1368 @type bad_nodes: list of strings
1369 @param bad_nodes: List of faulty nodes
1370 @type live_data: dict; instance name as key
1371 @param live_data: Per-instance live data
1372 @type wrongnode_inst: set
1373 @param wrongnode_inst: Set of instances running on wrong node(s)
1374 @type console: dict; instance name as key
1375 @param console: Per-instance console information
1376 @type nodes: dict; node name as key
1377 @param nodes: Node objects
1380 assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
1381 "Offline nodes not included in bad nodes"
1382 assert not (set(live_data.keys()) & set(bad_nodes)), \
1383 "Found live data for bad or offline nodes"
1385 self.instances = instances
1386 self.cluster = cluster
1387 self.disk_usage = disk_usage
1388 self.offline_nodes = offline_nodes
1389 self.bad_nodes = bad_nodes
1390 self.live_data = live_data
1391 self.wrongnode_inst = wrongnode_inst
1392 self.console = console
1394 self.groups = groups
1396 # Used for individual rows
1397 self.inst_hvparams = None
1398 self.inst_beparams = None
1399 self.inst_osparams = None
1400 self.inst_nicparams = None
1403 """Iterate over all instances.
1405 This function has side-effects and only one instance of the resulting
1406 generator should be used at a time.
1409 for inst in self.instances:
1410 self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1411 self.inst_beparams = self.cluster.FillBE(inst)
1412 self.inst_osparams = self.cluster.SimpleFillOS(inst.os, inst.osparams)
1413 self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1414 for nic in inst.nics]
1419 def _GetInstOperState(ctx, inst):
1420 """Get instance's operational status.
1422 @type ctx: L{InstanceQueryData}
1423 @type inst: L{objects.Instance}
1424 @param inst: Instance object
1427 # Can't use RS_OFFLINE here as it would describe the instance to
1428 # be offline when we actually don't know due to missing data
1429 if inst.primary_node in ctx.bad_nodes:
1432 return bool(ctx.live_data.get(inst.name))
1435 def _GetInstLiveData(name):
1436 """Build function for retrieving live data.
1439 @param name: Live data field name
1443 """Get live data for an instance.
1445 @type ctx: L{InstanceQueryData}
1446 @type inst: L{objects.Instance}
1447 @param inst: Instance object
1450 if (inst.primary_node in ctx.bad_nodes or
1451 inst.primary_node in ctx.offline_nodes):
1452 # Can't use RS_OFFLINE here as it would describe the instance to be
1453 # offline when we actually don't know due to missing data
1456 if inst.name in ctx.live_data:
1457 data = ctx.live_data[inst.name]
1466 def _GetInstStatus(ctx, inst):
1467 """Get instance status.
1469 @type ctx: L{InstanceQueryData}
1470 @type inst: L{objects.Instance}
1471 @param inst: Instance object
1474 if inst.primary_node in ctx.offline_nodes:
1475 return constants.INSTST_NODEOFFLINE
1477 if inst.primary_node in ctx.bad_nodes:
1478 return constants.INSTST_NODEDOWN
1480 if bool(ctx.live_data.get(inst.name)):
1481 if inst.name in ctx.wrongnode_inst:
1482 return constants.INSTST_WRONGNODE
1483 elif inst.admin_state == constants.ADMINST_UP:
1484 return constants.INSTST_RUNNING
1486 return constants.INSTST_ERRORUP
1488 if inst.admin_state == constants.ADMINST_UP:
1489 return constants.INSTST_ERRORDOWN
1490 elif inst.admin_state == constants.ADMINST_DOWN:
1491 return constants.INSTST_ADMINDOWN
1493 return constants.INSTST_ADMINOFFLINE
1496 def _GetInstDiskSize(index):
1497 """Build function for retrieving disk size.
1500 @param index: Disk index
1504 """Get size of a disk.
1506 @type inst: L{objects.Instance}
1507 @param inst: Instance object
1511 return inst.disks[index].size
1518 def _GetInstNic(index, cb):
1519 """Build function for calling another function with an instance NIC.
1522 @param index: NIC index
1528 """Call helper function with instance NIC.
1530 @type ctx: L{InstanceQueryData}
1531 @type inst: L{objects.Instance}
1532 @param inst: Instance object
1536 nic = inst.nics[index]
1540 return cb(ctx, index, nic)
1545 def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613
1546 """Get a NIC's IP address.
1548 @type ctx: L{InstanceQueryData}
1549 @type nic: L{objects.NIC}
1550 @param nic: NIC object
1559 def _GetInstNicBridge(ctx, index, _):
1560 """Get a NIC's bridge.
1562 @type ctx: L{InstanceQueryData}
1564 @param index: NIC index
1567 assert len(ctx.inst_nicparams) >= index
1569 nicparams = ctx.inst_nicparams[index]
1571 if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1572 return nicparams[constants.NIC_LINK]
1577 def _GetInstAllNicBridges(ctx, inst):
1578 """Get all network bridges for an instance.
1580 @type ctx: L{InstanceQueryData}
1581 @type inst: L{objects.Instance}
1582 @param inst: Instance object
1585 assert len(ctx.inst_nicparams) == len(inst.nics)
1589 for nicp in ctx.inst_nicparams:
1590 if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1591 result.append(nicp[constants.NIC_LINK])
1595 assert len(result) == len(inst.nics)
1600 def _GetInstNicParam(name):
1601 """Build function for retrieving a NIC parameter.
1604 @param name: Parameter name
1607 def fn(ctx, index, _):
1608 """Get a NIC's bridge.
1610 @type ctx: L{InstanceQueryData}
1611 @type inst: L{objects.Instance}
1612 @param inst: Instance object
1613 @type nic: L{objects.NIC}
1614 @param nic: NIC object
1617 assert len(ctx.inst_nicparams) >= index
1618 return ctx.inst_nicparams[index][name]
1623 def _GetInstanceNetworkFields():
1624 """Get instance fields involving network interfaces.
1626 @return: Tuple containing list of field definitions used as input for
1627 L{_PrepareFieldList} and a list of aliases
1630 nic_mac_fn = lambda ctx, _, nic: nic.mac
1631 nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1632 nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1636 (_MakeField("nic.count", "NICs", QFT_NUMBER,
1637 "Number of network interfaces"),
1638 IQ_CONFIG, 0, lambda ctx, inst: len(inst.nics)),
1639 (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER,
1640 "List containing each network interface's MAC address"),
1641 IQ_CONFIG, 0, lambda ctx, inst: [nic.mac for nic in inst.nics]),
1642 (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
1643 "List containing each network interface's IP address"),
1644 IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
1645 (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
1646 "List containing each network interface's mode"), IQ_CONFIG, 0,
1647 lambda ctx, inst: [nicp[constants.NIC_MODE]
1648 for nicp in ctx.inst_nicparams]),
1649 (_MakeField("nic.links", "NIC_links", QFT_OTHER,
1650 "List containing each network interface's link"), IQ_CONFIG, 0,
1651 lambda ctx, inst: [nicp[constants.NIC_LINK]
1652 for nicp in ctx.inst_nicparams]),
1653 (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
1654 "List containing each network interface's bridge"),
1655 IQ_CONFIG, 0, _GetInstAllNicBridges),
1659 for i in range(constants.MAX_NICS):
1660 numtext = utils.FormatOrdinal(i + 1)
1662 (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT,
1663 "IP address of %s network interface" % numtext),
1664 IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)),
1665 (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
1666 "MAC address of %s network interface" % numtext),
1667 IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
1668 (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
1669 "Mode of %s network interface" % numtext),
1670 IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
1671 (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT,
1672 "Link of %s network interface" % numtext),
1673 IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)),
1674 (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
1675 "Bridge of %s network interface" % numtext),
1676 IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
1680 # Legacy fields for first NIC
1682 ("mac", "nic.mac/0"),
1683 ("bridge", "nic.bridge/0"),
1684 ("nic_mode", "nic.mode/0"),
1685 ("nic_link", "nic.link/0"),
1688 return (fields, aliases)
1691 def _GetInstDiskUsage(ctx, inst):
1692 """Get disk usage for an instance.
1694 @type ctx: L{InstanceQueryData}
1695 @type inst: L{objects.Instance}
1696 @param inst: Instance object
1699 usage = ctx.disk_usage[inst.name]
1707 def _GetInstanceConsole(ctx, inst):
1708 """Get console information for instance.
1710 @type ctx: L{InstanceQueryData}
1711 @type inst: L{objects.Instance}
1712 @param inst: Instance object
1715 consinfo = ctx.console[inst.name]
1717 if consinfo is None:
1723 def _GetInstanceDiskFields():
1724 """Get instance fields involving disks.
1726 @return: List of field definitions used as input for L{_PrepareFieldList}
1730 (_MakeField("disk_usage", "DiskUsage", QFT_UNIT,
1731 "Total disk space used by instance on each of its nodes;"
1732 " this is not the disk size visible to the instance, but"
1733 " the usage on the node"),
1734 IQ_DISKUSAGE, 0, _GetInstDiskUsage),
1735 (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"),
1736 IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
1737 (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
1738 IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
1743 (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1744 "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
1745 IQ_CONFIG, 0, _GetInstDiskSize(i))
1746 for i in range(constants.MAX_DISKS)
1752 def _GetInstanceParameterFields():
1753 """Get instance fields involving parameters.
1755 @return: List of field definitions used as input for L{_PrepareFieldList}
1758 # TODO: Consider moving titles closer to constants
1760 constants.BE_AUTO_BALANCE: "Auto_balance",
1761 constants.BE_MAXMEM: "ConfigMaxMem",
1762 constants.BE_MINMEM: "ConfigMinMem",
1763 constants.BE_VCPUS: "ConfigVCPUs",
1767 constants.HV_ACPI: "ACPI",
1768 constants.HV_BOOT_ORDER: "Boot_order",
1769 constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1770 constants.HV_DISK_TYPE: "Disk_type",
1771 constants.HV_INITRD_PATH: "Initrd_path",
1772 constants.HV_KERNEL_PATH: "Kernel_path",
1773 constants.HV_NIC_TYPE: "NIC_type",
1774 constants.HV_PAE: "PAE",
1775 constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1776 constants.HV_PASSTHROUGH: "pci_pass",
1777 constants.HV_CPU_TYPE: "cpu_type",
1782 (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
1783 "Hypervisor parameters (merged)"),
1784 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
1785 (_MakeField("beparams", "BackendParameters", QFT_OTHER,
1786 "Backend parameters (merged)"),
1787 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
1788 (_MakeField("osparams", "OpSysParameters", QFT_OTHER,
1789 "Operating system parameters (merged)"),
1790 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_osparams),
1792 # Unfilled parameters
1793 (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
1794 "Custom hypervisor parameters"),
1795 IQ_CONFIG, 0, _GetItemAttr("hvparams")),
1796 (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
1797 "Custom backend parameters",),
1798 IQ_CONFIG, 0, _GetItemAttr("beparams")),
1799 (_MakeField("custom_osparams", "CustomOpSysParameters", QFT_OTHER,
1800 "Custom operating system parameters",),
1801 IQ_CONFIG, 0, _GetItemAttr("osparams")),
1802 (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
1803 "Custom network interface parameters"),
1804 IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1808 def _GetInstHvParam(name):
1809 return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1812 (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1813 _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1814 IQ_CONFIG, 0, _GetInstHvParam(name))
1815 for name, kind in constants.HVS_PARAMETER_TYPES.items()
1816 if name not in constants.HVC_GLOBALS
1820 def _GetInstBeParam(name):
1821 return lambda ctx, _: ctx.inst_beparams.get(name, None)
1824 (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1825 _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1826 IQ_CONFIG, 0, _GetInstBeParam(name))
1827 for name, kind in constants.BES_PARAMETER_TYPES.items()
1833 _INST_SIMPLE_FIELDS = {
1834 "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"),
1835 "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"),
1836 "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"),
1837 # Depending on the hypervisor, the port can be None
1838 "network_port": ("Network_port", QFT_OTHER, 0,
1839 "Instance network port if available (e.g. for VNC console)"),
1840 "os": ("OS", QFT_TEXT, 0, "Operating system"),
1841 "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"),
1842 "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"),
1846 def _GetInstNodeGroup(ctx, default, node_name):
1847 """Gets group UUID of an instance node.
1849 @type ctx: L{InstanceQueryData}
1850 @param default: Default value
1851 @type node_name: string
1852 @param node_name: Node name
1856 node = ctx.nodes[node_name]
1863 def _GetInstNodeGroupName(ctx, default, node_name):
1864 """Gets group name of an instance node.
1866 @type ctx: L{InstanceQueryData}
1867 @param default: Default value
1868 @type node_name: string
1869 @param node_name: Node name
1873 node = ctx.nodes[node_name]
1878 group = ctx.groups[node.group]
1885 def _BuildInstanceFields():
1886 """Builds list of fields for instance queries.
1890 (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
1891 IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")),
1892 (_MakeField("pnode.group", "PrimaryNodeGroup", QFT_TEXT,
1893 "Primary node's group"),
1895 lambda ctx, inst: _GetInstNodeGroupName(ctx, _FS_UNAVAIL,
1896 inst.primary_node)),
1897 (_MakeField("pnode.group.uuid", "PrimaryNodeGroupUUID", QFT_TEXT,
1898 "Primary node's group UUID"),
1900 lambda ctx, inst: _GetInstNodeGroup(ctx, _FS_UNAVAIL, inst.primary_node)),
1901 # TODO: Allow filtering by secondary node as hostname
1902 (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
1903 "Secondary nodes; usually this will just be one node"),
1904 IQ_CONFIG, 0, lambda ctx, inst: list(inst.secondary_nodes)),
1905 (_MakeField("snodes.group", "SecondaryNodesGroups", QFT_OTHER,
1906 "Node groups of secondary nodes"),
1908 lambda ctx, inst: map(compat.partial(_GetInstNodeGroupName, ctx, None),
1909 inst.secondary_nodes)),
1910 (_MakeField("snodes.group.uuid", "SecondaryNodesGroupsUUID", QFT_OTHER,
1911 "Node group UUIDs of secondary nodes"),
1913 lambda ctx, inst: map(compat.partial(_GetInstNodeGroup, ctx, None),
1914 inst.secondary_nodes)),
1915 (_MakeField("admin_state", "InstanceState", QFT_TEXT,
1916 "Desired state of instance"),
1917 IQ_CONFIG, 0, _GetItemAttr("admin_state")),
1918 (_MakeField("admin_up", "Autostart", QFT_BOOL,
1919 "Desired state of instance"),
1920 IQ_CONFIG, 0, lambda ctx, inst: inst.admin_state == constants.ADMINST_UP),
1921 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
1922 lambda ctx, inst: list(inst.GetTags())),
1923 (_MakeField("console", "Console", QFT_OTHER,
1924 "Instance console information"), IQ_CONSOLE, 0,
1925 _GetInstanceConsole),
1930 (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1931 for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()
1934 # Fields requiring talking to the node
1936 (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
1937 IQ_LIVE, 0, _GetInstOperState),
1938 (_MakeField("oper_ram", "Memory", QFT_UNIT,
1939 "Actual memory usage as seen by hypervisor"),
1940 IQ_LIVE, 0, _GetInstLiveData("memory")),
1941 (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
1942 "Actual number of VCPUs as seen by hypervisor"),
1943 IQ_LIVE, 0, _GetInstLiveData("vcpus")),
1947 status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
1948 constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
1949 constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
1950 constants.INSTST_NODEOFFLINE, constants.INSTST_ADMINOFFLINE)
1951 status_doc = ("Instance status; \"%s\" if instance is set to be running"
1952 " and actually is, \"%s\" if instance is stopped and"
1953 " is not running, \"%s\" if instance running, but not on its"
1954 " designated primary node, \"%s\" if instance should be"
1955 " stopped, but is actually running, \"%s\" if instance should"
1956 " run, but doesn't, \"%s\" if instance's primary node is down,"
1957 " \"%s\" if instance's primary node is marked offline,"
1958 " \"%s\" if instance is offline and does not use dynamic"
1959 " resources" % status_values)
1960 fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
1961 IQ_LIVE, 0, _GetInstStatus))
1962 assert set(status_values) == constants.INSTST_ALL, \
1963 "Status documentation mismatch"
1965 (network_fields, network_aliases) = _GetInstanceNetworkFields()
1967 fields.extend(network_fields)
1968 fields.extend(_GetInstanceParameterFields())
1969 fields.extend(_GetInstanceDiskFields())
1970 fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1973 ("vcpus", "be/vcpus"),
1974 ("be/memory", "be/maxmem"),
1975 ("sda_size", "disk.size/0"),
1976 ("sdb_size", "disk.size/1"),
1979 return _PrepareFieldList(fields, aliases)
1982 class LockQueryData:
1983 """Data container for lock data queries.
1986 def __init__(self, lockdata):
1987 """Initializes this class.
1990 self.lockdata = lockdata
1993 """Iterate over all locks.
1996 return iter(self.lockdata)
1999 def _GetLockOwners(_, data):
2000 """Returns a sorted list of a lock's current owners.
2003 (_, _, owners, _) = data
2006 owners = utils.NiceSort(owners)
2011 def _GetLockPending(_, data):
2012 """Returns a sorted list of a lock's pending acquires.
2015 (_, _, _, pending) = data
2018 pending = [(mode, utils.NiceSort(names))
2019 for (mode, names) in pending]
2024 def _BuildLockFields():
2025 """Builds list of fields for lock queries.
2028 return _PrepareFieldList([
2029 # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
2030 (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
2031 lambda ctx, (name, mode, owners, pending): name),
2032 (_MakeField("mode", "Mode", QFT_OTHER,
2033 "Mode in which the lock is currently acquired"
2034 " (exclusive or shared)"),
2035 LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
2036 (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
2037 LQ_OWNER, 0, _GetLockOwners),
2038 (_MakeField("pending", "Pending", QFT_OTHER,
2039 "Threads waiting for the lock"),
2040 LQ_PENDING, 0, _GetLockPending),
2044 class GroupQueryData:
2045 """Data container for node group data queries.
2048 def __init__(self, cluster, groups, group_to_nodes, group_to_instances,
2050 """Initializes this class.
2052 @param cluster: Cluster object
2053 @param groups: List of node group objects
2054 @type group_to_nodes: dict; group UUID as key
2055 @param group_to_nodes: Per-group list of nodes
2056 @type group_to_instances: dict; group UUID as key
2057 @param group_to_instances: Per-group list of (primary) instances
2058 @type want_diskparams: bool
2059 @param want_diskparams: Whether diskparamters should be calculated
2062 self.groups = groups
2063 self.group_to_nodes = group_to_nodes
2064 self.group_to_instances = group_to_instances
2065 self.cluster = cluster
2066 self.want_diskparams = want_diskparams
2068 # Used for individual rows
2069 self.group_ipolicy = None
2070 self.ndparams = None
2071 self.group_dp = None
2074 """Iterate over all node groups.
2076 This function has side-effects and only one instance of the resulting
2077 generator should be used at a time.
2080 for group in self.groups:
2081 self.group_ipolicy = self.cluster.SimpleFillIPolicy(group.ipolicy)
2082 self.ndparams = self.cluster.SimpleFillND(group.ndparams)
2083 if self.want_diskparams:
2084 self.group_dp = self.cluster.SimpleFillDP(group.diskparams)
2086 self.group_dp = None
2090 _GROUP_SIMPLE_FIELDS = {
2091 "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
2092 "name": ("Group", QFT_TEXT, "Group name"),
2093 "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
2094 "uuid": ("UUID", QFT_TEXT, "Group UUID"),
2098 def _BuildGroupFields():
2099 """Builds list of fields for node group queries.
2103 fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
2105 for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
2107 def _GetLength(getter):
2108 return lambda ctx, group: len(getter(ctx)[group.uuid])
2110 def _GetSortedList(getter):
2111 return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
2113 group_to_nodes = operator.attrgetter("group_to_nodes")
2114 group_to_instances = operator.attrgetter("group_to_instances")
2116 # Add fields for nodes
2118 (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
2119 GQ_NODE, 0, _GetLength(group_to_nodes)),
2120 (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
2121 GQ_NODE, 0, _GetSortedList(group_to_nodes)),
2124 # Add fields for instances
2126 (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
2127 "Number of primary instances"),
2128 GQ_INST, 0, _GetLength(group_to_instances)),
2129 (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
2130 "List of primary instances"),
2131 GQ_INST, 0, _GetSortedList(group_to_instances)),
2136 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), GQ_CONFIG, 0,
2137 lambda ctx, group: list(group.GetTags())),
2138 (_MakeField("ipolicy", "InstancePolicy", QFT_OTHER,
2139 "Instance policy limitations (merged)"),
2140 GQ_CONFIG, 0, lambda ctx, _: ctx.group_ipolicy),
2141 (_MakeField("custom_ipolicy", "CustomInstancePolicy", QFT_OTHER,
2142 "Custom instance policy limitations"),
2143 GQ_CONFIG, 0, _GetItemAttr("ipolicy")),
2144 (_MakeField("custom_ndparams", "CustomNDParams", QFT_OTHER,
2145 "Custom node parameters"),
2146 GQ_CONFIG, 0, _GetItemAttr("ndparams")),
2147 (_MakeField("ndparams", "NDParams", QFT_OTHER,
2149 GQ_CONFIG, 0, lambda ctx, _: ctx.ndparams),
2150 (_MakeField("diskparams", "DiskParameters", QFT_OTHER,
2151 "Disk parameters (merged)"),
2152 GQ_DISKPARAMS, 0, lambda ctx, _: ctx.group_dp),
2153 (_MakeField("custom_diskparams", "CustomDiskParameters", QFT_OTHER,
2154 "Custom disk parameters"),
2155 GQ_CONFIG, 0, _GetItemAttr("diskparams")),
2159 fields.extend(_BuildNDFields(True))
2161 fields.extend(_GetItemTimestampFields(GQ_CONFIG))
2163 return _PrepareFieldList(fields, [])
2166 class OsInfo(objects.ConfigObject):
2179 def _BuildOsFields():
2180 """Builds list of fields for operating system queries.
2184 (_MakeField("name", "Name", QFT_TEXT, "Operating system name"),
2185 None, 0, _GetItemAttr("name")),
2186 (_MakeField("valid", "Valid", QFT_BOOL,
2187 "Whether operating system definition is valid"),
2188 None, 0, _GetItemAttr("valid")),
2189 (_MakeField("hidden", "Hidden", QFT_BOOL,
2190 "Whether operating system is hidden"),
2191 None, 0, _GetItemAttr("hidden")),
2192 (_MakeField("blacklisted", "Blacklisted", QFT_BOOL,
2193 "Whether operating system is blacklisted"),
2194 None, 0, _GetItemAttr("blacklisted")),
2195 (_MakeField("variants", "Variants", QFT_OTHER,
2196 "Operating system variants"),
2197 None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("variants"))),
2198 (_MakeField("api_versions", "ApiVersions", QFT_OTHER,
2199 "Operating system API versions"),
2200 None, 0, _ConvWrap(sorted, _GetItemAttr("api_versions"))),
2201 (_MakeField("parameters", "Parameters", QFT_OTHER,
2202 "Operating system parameters"),
2203 None, 0, _ConvWrap(compat.partial(utils.NiceSort, key=compat.fst),
2204 _GetItemAttr("parameters"))),
2205 (_MakeField("node_status", "NodeStatus", QFT_OTHER,
2206 "Status from node"),
2207 None, 0, _GetItemAttr("node_status")),
2210 return _PrepareFieldList(fields, [])
2213 def _JobUnavailInner(fn, ctx, (job_id, job)): # pylint: disable=W0613
2214 """Return L{_FS_UNAVAIL} if job is None.
2216 When listing specifc jobs (e.g. "gnt-job list 1 2 3"), a job may not be
2217 found, in which case this function converts it to L{_FS_UNAVAIL}.
2226 def _JobUnavail(inner):
2227 """Wrapper for L{_JobUnavailInner}.
2230 return compat.partial(_JobUnavailInner, inner)
2233 def _PerJobOpInner(fn, job):
2234 """Executes a function per opcode in a job.
2237 return map(fn, job.ops)
2241 """Wrapper for L{_PerJobOpInner}.
2244 return _JobUnavail(compat.partial(_PerJobOpInner, fn))
2247 def _JobTimestampInner(fn, job):
2248 """Converts unavailable timestamp to L{_FS_UNAVAIL}.
2253 if timestamp is None:
2259 def _JobTimestamp(fn):
2260 """Wrapper for L{_JobTimestampInner}.
2263 return _JobUnavail(compat.partial(_JobTimestampInner, fn))
2266 def _BuildJobFields():
2267 """Builds list of fields for job queries.
2271 (_MakeField("id", "ID", QFT_NUMBER, "Job ID"),
2272 None, QFF_JOB_ID, lambda _, (job_id, job): job_id),
2273 (_MakeField("status", "Status", QFT_TEXT, "Job status"),
2274 None, 0, _JobUnavail(lambda job: job.CalcStatus())),
2275 (_MakeField("priority", "Priority", QFT_NUMBER,
2276 ("Current job priority (%s to %s)" %
2277 (constants.OP_PRIO_LOWEST, constants.OP_PRIO_HIGHEST))),
2278 None, 0, _JobUnavail(lambda job: job.CalcPriority())),
2279 (_MakeField("ops", "OpCodes", QFT_OTHER, "List of all opcodes"),
2280 None, 0, _PerJobOp(lambda op: op.input.__getstate__())),
2281 (_MakeField("opresult", "OpCode_result", QFT_OTHER,
2282 "List of opcodes results"),
2283 None, 0, _PerJobOp(operator.attrgetter("result"))),
2284 (_MakeField("opstatus", "OpCode_status", QFT_OTHER,
2285 "List of opcodes status"),
2286 None, 0, _PerJobOp(operator.attrgetter("status"))),
2287 (_MakeField("oplog", "OpCode_log", QFT_OTHER,
2288 "List of opcode output logs"),
2289 None, 0, _PerJobOp(operator.attrgetter("log"))),
2290 (_MakeField("opstart", "OpCode_start", QFT_OTHER,
2291 "List of opcode start timestamps (before acquiring locks)"),
2292 None, 0, _PerJobOp(operator.attrgetter("start_timestamp"))),
2293 (_MakeField("opexec", "OpCode_exec", QFT_OTHER,
2294 "List of opcode execution start timestamps (after acquiring"
2296 None, 0, _PerJobOp(operator.attrgetter("exec_timestamp"))),
2297 (_MakeField("opend", "OpCode_end", QFT_OTHER,
2298 "List of opcode execution end timestamps"),
2299 None, 0, _PerJobOp(operator.attrgetter("end_timestamp"))),
2300 (_MakeField("oppriority", "OpCode_prio", QFT_OTHER,
2301 "List of opcode priorities"),
2302 None, 0, _PerJobOp(operator.attrgetter("priority"))),
2303 (_MakeField("summary", "Summary", QFT_OTHER,
2304 "List of per-opcode summaries"),
2305 None, 0, _PerJobOp(lambda op: op.input.Summary())),
2309 for (name, attr, title, desc) in [
2310 ("received_ts", "received_timestamp", "Received",
2311 "Timestamp of when job was received"),
2312 ("start_ts", "start_timestamp", "Start", "Timestamp of job start"),
2313 ("end_ts", "end_timestamp", "End", "Timestamp of job end"),
2315 getter = operator.attrgetter(attr)
2317 (_MakeField(name, title, QFT_OTHER,
2318 "%s (tuple containing seconds and microseconds)" % desc),
2319 None, QFF_SPLIT_TIMESTAMP, _JobTimestamp(getter)),
2322 return _PrepareFieldList(fields, [])
2325 def _GetExportName(_, (node_name, expname)): # pylint: disable=W0613
2326 """Returns an export name if available.
2335 def _BuildExportFields():
2336 """Builds list of fields for exports.
2340 (_MakeField("node", "Node", QFT_TEXT, "Node name"),
2341 None, QFF_HOSTNAME, lambda _, (node_name, expname): node_name),
2342 (_MakeField("export", "Export", QFT_TEXT, "Export name"),
2343 None, 0, _GetExportName),
2346 return _PrepareFieldList(fields, [])
2349 _CLUSTER_VERSION_FIELDS = {
2350 "software_version": ("SoftwareVersion", QFT_TEXT, constants.RELEASE_VERSION,
2351 "Software version"),
2352 "protocol_version": ("ProtocolVersion", QFT_NUMBER,
2353 constants.PROTOCOL_VERSION,
2354 "RPC protocol version"),
2355 "config_version": ("ConfigVersion", QFT_NUMBER, constants.CONFIG_VERSION,
2356 "Configuration format version"),
2357 "os_api_version": ("OsApiVersion", QFT_NUMBER, max(constants.OS_API_VERSIONS),
2358 "API version for OS template scripts"),
2359 "export_version": ("ExportVersion", QFT_NUMBER, constants.EXPORT_VERSION,
2360 "Import/export file format version"),
2364 _CLUSTER_SIMPLE_FIELDS = {
2365 "cluster_name": ("Name", QFT_TEXT, QFF_HOSTNAME, "Cluster name"),
2366 "master_node": ("Master", QFT_TEXT, QFF_HOSTNAME, "Master node name"),
2367 "volume_group_name": ("VgName", QFT_TEXT, 0, "LVM volume group name"),
2371 class ClusterQueryData:
2372 def __init__(self, cluster, drain_flag, watcher_pause):
2373 """Initializes this class.
2375 @type cluster: L{objects.Cluster}
2376 @param cluster: Instance of cluster object
2377 @type drain_flag: bool
2378 @param drain_flag: Whether job queue is drained
2379 @type watcher_pause: number
2380 @param watcher_pause: Until when watcher is paused (Unix timestamp)
2383 self._cluster = cluster
2384 self.drain_flag = drain_flag
2385 self.watcher_pause = watcher_pause
2388 return iter([self._cluster])
2391 def _ClusterWatcherPause(ctx, _):
2392 """Returns until when watcher is paused (if available).
2395 if ctx.watcher_pause is None:
2398 return ctx.watcher_pause
2401 def _BuildClusterFields():
2402 """Builds list of fields for cluster information.
2406 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), CQ_CONFIG, 0,
2407 lambda ctx, cluster: list(cluster.GetTags())),
2408 (_MakeField("architecture", "ArchInfo", QFT_OTHER,
2409 "Architecture information"), None, 0,
2410 lambda ctx, _: runtime.GetArchInfo()),
2411 (_MakeField("drain_flag", "QueueDrained", QFT_BOOL,
2412 "Flag whether job queue is drained"), CQ_QUEUE_DRAINED, 0,
2413 lambda ctx, _: ctx.drain_flag),
2414 (_MakeField("watcher_pause", "WatcherPause", QFT_TIMESTAMP,
2415 "Until when watcher is paused"), CQ_WATCHER_PAUSE, 0,
2416 _ClusterWatcherPause),
2421 (_MakeField(name, title, kind, doc), CQ_CONFIG, flags, _GetItemAttr(name))
2422 for (name, (title, kind, flags, doc)) in _CLUSTER_SIMPLE_FIELDS.items()
2427 (_MakeField(name, title, kind, doc), None, 0, _StaticValue(value))
2428 for (name, (title, kind, value, doc)) in _CLUSTER_VERSION_FIELDS.items()
2432 fields.extend(_GetItemTimestampFields(CQ_CONFIG))
2434 return _PrepareFieldList(fields, [
2435 ("name", "cluster_name"),
2439 #: Fields for cluster information
2440 CLUSTER_FIELDS = _BuildClusterFields()
2442 #: Fields available for node queries
2443 NODE_FIELDS = _BuildNodeFields()
2445 #: Fields available for instance queries
2446 INSTANCE_FIELDS = _BuildInstanceFields()
2448 #: Fields available for lock queries
2449 LOCK_FIELDS = _BuildLockFields()
2451 #: Fields available for node group queries
2452 GROUP_FIELDS = _BuildGroupFields()
2454 #: Fields available for operating system queries
2455 OS_FIELDS = _BuildOsFields()
2457 #: Fields available for job queries
2458 JOB_FIELDS = _BuildJobFields()
2460 #: Fields available for exports
2461 EXPORT_FIELDS = _BuildExportFields()
2463 #: All available resources
2465 constants.QR_CLUSTER: CLUSTER_FIELDS,
2466 constants.QR_INSTANCE: INSTANCE_FIELDS,
2467 constants.QR_NODE: NODE_FIELDS,
2468 constants.QR_LOCK: LOCK_FIELDS,
2469 constants.QR_GROUP: GROUP_FIELDS,
2470 constants.QR_OS: OS_FIELDS,
2471 constants.QR_JOB: JOB_FIELDS,
2472 constants.QR_EXPORT: EXPORT_FIELDS,
2475 #: All available field lists
2476 ALL_FIELD_LISTS = ALL_FIELDS.values()