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)
77 NETQ_INST) = range(300, 304)
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.
93 IQ_NODES) = range(100, 105)
97 LQ_PENDING) = range(10, 13)
102 GQ_DISKPARAMS) = range(200, 204)
106 CQ_WATCHER_PAUSE) = range(300, 303)
108 (JQ_ARCHIVED, ) = range(400, 401)
112 QFF_IP_ADDRESS = 0x02
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)
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].*[^.,?!]$")
122 #: Verification function for each field type
124 QFT_UNKNOWN: ht.TNone,
125 QFT_TEXT: ht.TString,
129 QFT_TIMESTAMP: ht.TNumber,
130 QFT_OTHER: lambda _: True,
133 # Unique objects for special field statuses
134 _FS_UNKNOWN = object()
135 _FS_NODATA = object()
136 _FS_UNAVAIL = object()
137 _FS_OFFLINE = object()
139 #: List of all special status
140 _FS_ALL = frozenset([_FS_UNKNOWN, _FS_NODATA, _FS_UNAVAIL, _FS_OFFLINE])
142 #: VType to QFT mapping
144 # TODO: fix validation of empty strings
145 constants.VTYPE_STRING: QFT_OTHER, # since VTYPE_STRINGs can be empty
146 constants.VTYPE_MAYBE_STRING: QFT_OTHER,
147 constants.VTYPE_BOOL: QFT_BOOL,
148 constants.VTYPE_SIZE: QFT_UNIT,
149 constants.VTYPE_INT: QFT_NUMBER,
152 _SERIAL_NO_DOC = "%s object serial number, incremented on each modification"
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, datakind): # pylint: disable=W0613
279 """Called when handling an unary operation.
285 if datakind is not None:
286 self._datakinds.add(datakind)
290 def NoteBinaryOp(self, op, datakind, name, value):
291 """Called when handling a binary operation.
296 @param name: Left-hand side of operator (field name)
297 @param value: Right-hand side of operator
300 if datakind is not None:
301 self._datakinds.add(datakind)
306 # If any operator other than equality was used, all names need to be
308 if op == qlang.OP_EQUAL and name == self._namefield:
309 if self._names is None:
311 self._names.append(value)
316 def _WrapLogicOp(op_fn, sentences, ctx, item):
317 """Wrapper for logic operator functions.
320 return op_fn(fn(ctx, item) for fn in sentences)
323 def _WrapUnaryOp(op_fn, inner, ctx, item):
324 """Wrapper for unary operator functions.
327 return op_fn(inner(ctx, item))
330 def _WrapBinaryOp(op_fn, retrieval_fn, value, ctx, item):
331 """Wrapper for binary operator functions.
334 return op_fn(retrieval_fn(ctx, item), value)
337 def _WrapNot(fn, lhs, rhs):
338 """Negates the result of a wrapped function.
341 return not fn(lhs, rhs)
344 def _PrepareRegex(pattern):
345 """Compiles a regular expression.
349 return re.compile(pattern)
350 except re.error, err:
351 raise errors.ParameterError("Invalid regex pattern (%s)" % err)
354 def _PrepareSplitTimestamp(value):
355 """Prepares a value for comparison by L{_MakeSplitTimestampComparison}.
358 if ht.TNumber(value):
361 return utils.MergeTime(value)
364 def _MakeSplitTimestampComparison(fn):
365 """Compares split timestamp values after converting to float.
368 return lambda lhs, rhs: fn(utils.MergeTime(lhs), rhs)
371 def _MakeComparisonChecks(fn):
372 """Prepares flag-specific comparisons using a comparison function.
376 (QFF_SPLIT_TIMESTAMP, _MakeSplitTimestampComparison(fn),
377 _PrepareSplitTimestamp),
378 (QFF_JOB_ID, lambda lhs, rhs: fn(jstore.ParseJobId(lhs), rhs),
384 class _FilterCompilerHelper:
385 """Converts a query filter to a callable usable for filtering.
388 # String statement has no effect, pylint: disable=W0105
390 #: How deep filters can be nested
393 # Unique identifiers for operator groups
396 _OPTYPE_BINARY) = range(1, 4)
398 """Functions for equality checks depending on field flags.
400 List of tuples containing flags and a callable receiving the left- and
401 right-hand side of the operator. The flags are an OR-ed value of C{QFF_*}
402 (e.g. L{QFF_HOSTNAME} or L{QFF_SPLIT_TIMESTAMP}).
404 Order matters. The first item with flags will be used. Flags are checked
410 lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
411 case_sensitive=False),
413 (QFF_SPLIT_TIMESTAMP, _MakeSplitTimestampComparison(operator.eq),
414 _PrepareSplitTimestamp),
415 (None, operator.eq, None),
420 Operator as key (C{qlang.OP_*}), value a tuple of operator group
421 (C{_OPTYPE_*}) and a group-specific value:
423 - C{_OPTYPE_LOGIC}: Callable taking any number of arguments; used by
425 - C{_OPTYPE_UNARY}: Always C{None}; details handled by L{_HandleUnaryOp}
426 - C{_OPTYPE_BINARY}: Callable taking exactly two parameters, the left- and
427 right-hand side of the operator, used by L{_HandleBinaryOp}
432 qlang.OP_OR: (_OPTYPE_LOGIC, compat.any),
433 qlang.OP_AND: (_OPTYPE_LOGIC, compat.all),
436 qlang.OP_NOT: (_OPTYPE_UNARY, None),
437 qlang.OP_TRUE: (_OPTYPE_UNARY, None),
440 qlang.OP_EQUAL: (_OPTYPE_BINARY, _EQUALITY_CHECKS),
442 (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn), valprepfn)
443 for (flags, fn, valprepfn) in _EQUALITY_CHECKS]),
444 qlang.OP_LT: (_OPTYPE_BINARY, _MakeComparisonChecks(operator.lt)),
445 qlang.OP_LE: (_OPTYPE_BINARY, _MakeComparisonChecks(operator.le)),
446 qlang.OP_GT: (_OPTYPE_BINARY, _MakeComparisonChecks(operator.gt)),
447 qlang.OP_GE: (_OPTYPE_BINARY, _MakeComparisonChecks(operator.ge)),
448 qlang.OP_REGEXP: (_OPTYPE_BINARY, [
449 (None, lambda lhs, rhs: rhs.search(lhs), _PrepareRegex),
451 qlang.OP_CONTAINS: (_OPTYPE_BINARY, [
452 (None, operator.contains, None),
456 def __init__(self, fields):
457 """Initializes this class.
459 @param fields: Field definitions (return value of L{_PrepareFieldList})
462 self._fields = fields
464 self._op_handler = None
466 def __call__(self, hints, qfilter):
467 """Converts a query filter into a callable function.
469 @type hints: L{_FilterHints} or None
470 @param hints: Callbacks doing analysis on filter
472 @param qfilter: Filter structure
474 @return: Function receiving context and item as parameters, returning
475 boolean as to whether item matches filter
480 (self._HandleLogicOp, getattr(hints, "NoteLogicOp", None)),
482 (self._HandleUnaryOp, getattr(hints, "NoteUnaryOp", None)),
484 (self._HandleBinaryOp, getattr(hints, "NoteBinaryOp", None)),
488 filter_fn = self._Compile(qfilter, 0)
490 self._op_handler = None
494 def _Compile(self, qfilter, level):
495 """Inner function for converting filters.
497 Calls the correct handler functions for the top-level operator. This
498 function is called recursively (e.g. for logic operators).
501 if not (isinstance(qfilter, (list, tuple)) and qfilter):
502 raise errors.ParameterError("Invalid filter on level %s" % level)
505 if level >= self._LEVELS_MAX:
506 raise errors.ParameterError("Only up to %s levels are allowed (filter"
507 " nested too deep)" % self._LEVELS_MAX)
509 # Create copy to be modified
510 operands = qfilter[:]
514 (kind, op_data) = self._OPS[op]
516 raise errors.ParameterError("Unknown operator '%s'" % op)
518 (handler, hints_cb) = self._op_handler[kind]
520 return handler(hints_cb, level, op, op_data, operands)
522 def _LookupField(self, name):
523 """Returns a field definition by name.
527 return self._fields[name]
529 raise errors.ParameterError("Unknown field '%s'" % name)
531 def _HandleLogicOp(self, hints_fn, level, op, op_fn, operands):
532 """Handles logic operators.
534 @type hints_fn: callable
535 @param hints_fn: Callback doing some analysis on the filter
537 @param level: Current depth
540 @type op_fn: callable
541 @param op_fn: Function implementing operator
543 @param operands: List of operands
549 return compat.partial(_WrapLogicOp, op_fn,
550 [self._Compile(op, level + 1) for op in operands])
552 def _HandleUnaryOp(self, hints_fn, level, op, op_fn, operands):
553 """Handles unary operators.
555 @type hints_fn: callable
556 @param hints_fn: Callback doing some analysis on the filter
558 @param level: Current depth
561 @type op_fn: callable
562 @param op_fn: Function implementing operator
564 @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 (_, datakind, _, retrieval_fn) = self._LookupField(operands[0])
577 hints_fn(op, datakind)
579 op_fn = operator.truth
581 elif op == qlang.OP_NOT:
585 op_fn = operator.not_
586 arg = self._Compile(operands[0], level + 1)
588 raise errors.ProgrammerError("Can't handle operator '%s'" % op)
590 return compat.partial(_WrapUnaryOp, op_fn, arg)
592 def _HandleBinaryOp(self, hints_fn, level, op, op_data, operands):
593 """Handles binary operators.
595 @type hints_fn: callable
596 @param hints_fn: Callback doing some analysis on the filter
598 @param level: Current depth
601 @param op_data: Functions implementing operators
603 @param operands: List of operands
606 # Unused arguments, pylint: disable=W0613
608 (name, value) = operands
609 except (ValueError, TypeError):
610 raise errors.ParameterError("Invalid binary operator, expected exactly"
613 (fdef, datakind, field_flags, retrieval_fn) = self._LookupField(name)
615 assert fdef.kind != QFT_UNKNOWN
617 # TODO: Type conversions?
619 verify_fn = _VERIFY_FN[fdef.kind]
620 if not verify_fn(value):
621 raise errors.ParameterError("Unable to compare field '%s' (type '%s')"
622 " with '%s', expected %s" %
623 (name, fdef.kind, value.__class__.__name__,
627 hints_fn(op, datakind, name, value)
629 for (fn_flags, fn, valprepfn) in op_data:
630 if fn_flags is None or fn_flags & field_flags:
631 # Prepare value if necessary (e.g. compile regular expression)
633 value = valprepfn(value)
635 return compat.partial(_WrapBinaryOp, fn, retrieval_fn, value)
637 raise errors.ProgrammerError("Unable to find operator implementation"
638 " (op '%s', flags %s)" % (op, field_flags))
641 def _CompileFilter(fields, hints, qfilter):
642 """Converts a query filter into a callable function.
644 See L{_FilterCompilerHelper} for details.
649 return _FilterCompilerHelper(fields)(hints, qfilter)
653 def __init__(self, fieldlist, selected, qfilter=None, namefield=None):
654 """Initializes this class.
656 The field definition is a dictionary with the field's name as a key and a
657 tuple containing, in order, the field definition object
658 (L{objects.QueryFieldDefinition}, the data kind to help calling code
659 collect data and a retrieval function. The retrieval function is called
660 with two parameters, in order, the data container and the item in container
661 (see L{Query.Query}).
663 Users of this class can call L{RequestedData} before preparing the data
664 container to determine what data is needed.
666 @type fieldlist: dictionary
667 @param fieldlist: Field definitions
668 @type selected: list of strings
669 @param selected: List of selected fields
672 assert namefield is None or namefield in fieldlist
674 self._fields = _GetQueryFields(fieldlist, selected)
676 self._filter_fn = None
677 self._requested_names = None
678 self._filter_datakinds = frozenset()
680 if qfilter is not None:
681 # Collect requested names if wanted
683 hints = _FilterHints(namefield)
687 # Build filter function
688 self._filter_fn = _CompileFilter(fieldlist, hints, qfilter)
690 self._requested_names = hints.RequestedNames()
691 self._filter_datakinds = hints.ReferencedData()
693 if namefield is None:
696 (_, _, _, self._name_fn) = fieldlist[namefield]
698 def RequestedNames(self):
699 """Returns all names referenced in the filter.
701 If there is no filter or operators are preventing determining the exact
702 names, C{None} is returned.
705 return self._requested_names
707 def RequestedData(self):
708 """Gets requested kinds of data.
713 return (self._filter_datakinds |
714 frozenset(datakind for (_, datakind, _, _) in self._fields
715 if datakind is not None))
718 """Returns the list of fields for this query.
720 Includes unknown fields.
722 @rtype: List of L{objects.QueryFieldDefinition}
725 return GetAllFields(self._fields)
727 def Query(self, ctx, sort_by_name=True):
730 @param ctx: Data container passed to field retrieval functions, must
731 support iteration using C{__iter__}
732 @type sort_by_name: boolean
733 @param sort_by_name: Whether to sort by name or keep the input data's
737 sort = (self._name_fn and sort_by_name)
741 for idx, item in enumerate(ctx):
742 if not (self._filter_fn is None or self._filter_fn(ctx, item)):
745 row = [_ProcessResult(fn(ctx, item)) for (_, _, _, fn) in self._fields]
749 _VerifyResultRow(self._fields, row)
752 (status, name) = _ProcessResult(self._name_fn(ctx, item))
753 assert status == constants.RS_NORMAL
754 # TODO: Are there cases where we wouldn't want to use NiceSort?
755 # Answer: if the name field is non-string...
756 result.append((utils.NiceSortKey(name), idx, row))
763 # TODO: Would "heapq" be more efficient than sorting?
765 # Sorting in-place instead of using "sorted()"
768 assert not result or (len(result[0]) == 3 and len(result[-1]) == 3)
770 return map(operator.itemgetter(2), result)
772 def OldStyleQuery(self, ctx, sort_by_name=True):
773 """Query with "old" query result format.
775 See L{Query.Query} for arguments.
778 unknown = set(fdef.name for (fdef, _, _, _) in self._fields
779 if fdef.kind == QFT_UNKNOWN)
781 raise errors.OpPrereqError("Unknown output fields selected: %s" %
782 (utils.CommaJoin(unknown), ),
785 return [[value for (_, value) in row]
786 for row in self.Query(ctx, sort_by_name=sort_by_name)]
789 def _ProcessResult(value):
790 """Converts result values into externally-visible ones.
793 if value is _FS_UNKNOWN:
794 return (RS_UNKNOWN, None)
795 elif value is _FS_NODATA:
796 return (RS_NODATA, None)
797 elif value is _FS_UNAVAIL:
798 return (RS_UNAVAIL, None)
799 elif value is _FS_OFFLINE:
800 return (RS_OFFLINE, None)
802 return (RS_NORMAL, value)
805 def _VerifyResultRow(fields, row):
806 """Verifies the contents of a query result row.
809 @param fields: Field definitions for result
810 @type row: list of tuples
814 assert len(row) == len(fields)
816 for ((status, value), (fdef, _, _, _)) in zip(row, fields):
817 if status == RS_NORMAL:
818 if not _VERIFY_FN[fdef.kind](value):
819 errs.append("normal field %s fails validation (value is %s)" %
821 elif value is not None:
822 errs.append("abnormal field %s has a non-None value" % fdef.name)
823 assert not errs, ("Failed validation: %s in row %s" %
824 (utils.CommaJoin(errs), row))
827 def _FieldDictKey((fdef, _, flags, fn)):
828 """Generates key for field dictionary.
831 assert fdef.name and fdef.title, "Name and title are required"
832 assert FIELD_NAME_RE.match(fdef.name)
833 assert TITLE_RE.match(fdef.title)
834 assert (DOC_RE.match(fdef.doc) and len(fdef.doc.splitlines()) == 1 and
835 fdef.doc.strip() == fdef.doc), \
836 "Invalid description for field '%s'" % fdef.name
838 assert (flags & ~QFF_ALL) == 0, "Unknown flags for field '%s'" % fdef.name
843 def _PrepareFieldList(fields, aliases):
844 """Prepares field list for use by L{Query}.
846 Converts the list to a dictionary and does some verification.
848 @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data
849 kind, retrieval function)
850 @param fields: List of fields, see L{Query.__init__} for a better
852 @type aliases: list of tuples; (alias, target)
853 @param aliases: list of tuples containing aliases; for each
854 alias/target pair, a duplicate will be created in the field list
856 @return: Field dictionary for L{Query}
860 duplicates = utils.FindDuplicates(fdef.title.lower()
861 for (fdef, _, _, _) in fields)
862 assert not duplicates, "Duplicate title(s) found: %r" % duplicates
864 result = utils.SequenceToDict(fields, key=_FieldDictKey)
866 for alias, target in aliases:
867 assert alias not in result, "Alias %s overrides an existing field" % alias
868 assert target in result, "Missing target %s for alias %s" % (target, alias)
869 (fdef, k, flags, fn) = result[target]
872 result[alias] = (fdef, k, flags, fn)
874 assert len(result) == len(fields) + len(aliases)
875 assert compat.all(name == fdef.name
876 for (name, (fdef, _, _, _)) in result.items())
881 def GetQueryResponse(query, ctx, sort_by_name=True):
882 """Prepares the response for a query.
884 @type query: L{Query}
885 @param ctx: Data container, see L{Query.Query}
886 @type sort_by_name: boolean
887 @param sort_by_name: Whether to sort by name or keep the input data's
891 return objects.QueryResponse(data=query.Query(ctx, sort_by_name=sort_by_name),
892 fields=query.GetFields()).ToDict()
895 def QueryFields(fielddefs, selected):
896 """Returns list of available fields.
898 @type fielddefs: dict
899 @param fielddefs: Field definitions
900 @type selected: list of strings
901 @param selected: List of selected fields
902 @return: List of L{objects.QueryFieldDefinition}
906 # Client requests all fields, sort by name
907 fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
908 key=operator.attrgetter("name"))
910 # Keep order as requested by client
911 fdefs = Query(fielddefs, selected).GetFields()
913 return objects.QueryFieldsResponse(fields=fdefs).ToDict()
916 def _MakeField(name, title, kind, doc):
917 """Wrapper for creating L{objects.QueryFieldDefinition} instances.
919 @param name: Field name as a regular expression
920 @param title: Human-readable title
921 @param kind: Field type
922 @param doc: Human-readable description
925 return objects.QueryFieldDefinition(name=name, title=title, kind=kind,
929 def _StaticValueInner(value, ctx, _): # pylint: disable=W0613
930 """Returns a static value.
936 def _StaticValue(value):
937 """Prepares a function to return a static value.
940 return compat.partial(_StaticValueInner, value)
943 def _GetNodeRole(node, master_name):
944 """Determine node role.
946 @type node: L{objects.Node}
947 @param node: Node object
948 @type master_name: string
949 @param master_name: Master node name
952 if node.name == master_name:
953 return constants.NR_MASTER
954 elif node.master_candidate:
955 return constants.NR_MCANDIDATE
957 return constants.NR_DRAINED
959 return constants.NR_OFFLINE
961 return constants.NR_REGULAR
964 def _GetItemAttr(attr):
965 """Returns a field function to return an attribute of the item.
967 @param attr: Attribute name
970 getter = operator.attrgetter(attr)
971 return lambda _, item: getter(item)
974 def _GetNDParam(name):
975 """Return a field function to return an ND parameter out of the context.
979 if ctx.ndparams is None:
982 return ctx.ndparams.get(name, None)
986 def _BuildNDFields(is_group):
987 """Builds all the ndparam fields.
989 @param is_group: whether this is called at group or node level
993 field_kind = GQ_CONFIG
995 field_kind = NQ_GROUP
996 return [(_MakeField("ndp/%s" % name,
997 constants.NDS_PARAMETER_TITLES.get(name,
999 _VTToQFT[kind], "The \"%s\" node parameter" % name),
1000 field_kind, 0, _GetNDParam(name))
1001 for name, kind in constants.NDS_PARAMETER_TYPES.items()]
1004 def _ConvWrapInner(convert, fn, ctx, item):
1005 """Wrapper for converting values.
1007 @param convert: Conversion function receiving value as single parameter
1008 @param fn: Retrieval function
1011 value = fn(ctx, item)
1013 # Is the value an abnormal status?
1014 if compat.any(value is fs for fs in _FS_ALL):
1018 # TODO: Should conversion function also receive context, item or both?
1019 return convert(value)
1022 def _ConvWrap(convert, fn):
1023 """Convenience wrapper for L{_ConvWrapInner}.
1025 @param convert: Conversion function receiving value as single parameter
1026 @param fn: Retrieval function
1029 return compat.partial(_ConvWrapInner, convert, fn)
1032 def _GetItemTimestamp(getter):
1033 """Returns function for getting timestamp of item.
1035 @type getter: callable
1036 @param getter: Function to retrieve timestamp attribute
1040 """Returns a timestamp of item.
1043 timestamp = getter(item)
1044 if timestamp is None:
1045 # Old configs might not have all timestamps
1053 def _GetItemTimestampFields(datatype):
1054 """Returns common timestamp fields.
1056 @param datatype: Field data type for use by L{Query.RequestedData}
1060 (_MakeField("ctime", "CTime", QFT_TIMESTAMP, "Creation timestamp"),
1061 datatype, 0, _GetItemTimestamp(operator.attrgetter("ctime"))),
1062 (_MakeField("mtime", "MTime", QFT_TIMESTAMP, "Modification timestamp"),
1063 datatype, 0, _GetItemTimestamp(operator.attrgetter("mtime"))),
1067 class NodeQueryData:
1068 """Data container for node data queries.
1071 def __init__(self, nodes, live_data, master_name, node_to_primary,
1072 node_to_secondary, groups, oob_support, cluster):
1073 """Initializes this class.
1077 self.live_data = live_data
1078 self.master_name = master_name
1079 self.node_to_primary = node_to_primary
1080 self.node_to_secondary = node_to_secondary
1081 self.groups = groups
1082 self.oob_support = oob_support
1083 self.cluster = cluster
1085 # Used for individual rows
1086 self.curlive_data = None
1087 self.ndparams = None
1090 """Iterate over all nodes.
1092 This function has side-effects and only one instance of the resulting
1093 generator should be used at a time.
1096 for node in self.nodes:
1097 group = self.groups.get(node.group, None)
1099 self.ndparams = None
1101 self.ndparams = self.cluster.FillND(node, group)
1103 self.curlive_data = self.live_data.get(node.name, None)
1105 self.curlive_data = None
1109 #: Fields that are direct attributes of an L{objects.Node} object
1110 _NODE_SIMPLE_FIELDS = {
1111 "drained": ("Drained", QFT_BOOL, 0, "Whether node is drained"),
1112 "master_candidate": ("MasterC", QFT_BOOL, 0,
1113 "Whether node is a master candidate"),
1114 "master_capable": ("MasterCapable", QFT_BOOL, 0,
1115 "Whether node can become a master candidate"),
1116 "name": ("Node", QFT_TEXT, QFF_HOSTNAME, "Node name"),
1117 "offline": ("Offline", QFT_BOOL, 0, "Whether node is marked offline"),
1118 "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Node"),
1119 "uuid": ("UUID", QFT_TEXT, 0, "Node UUID"),
1120 "vm_capable": ("VMCapable", QFT_BOOL, 0, "Whether node can host instances"),
1124 #: Fields requiring talking to the node
1125 # Note that none of these are available for non-vm_capable nodes
1126 _NODE_LIVE_FIELDS = {
1127 "bootid": ("BootID", QFT_TEXT, "bootid",
1128 "Random UUID renewed for each system reboot, can be used"
1129 " for detecting reboots by tracking changes"),
1130 "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes",
1131 "Number of NUMA domains on node (if exported by hypervisor)"),
1132 "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets",
1133 "Number of physical CPU sockets (if exported by hypervisor)"),
1134 "ctotal": ("CTotal", QFT_NUMBER, "cpu_total", "Number of logical processors"),
1135 "dfree": ("DFree", QFT_UNIT, "vg_free",
1136 "Available disk space in volume group"),
1137 "dtotal": ("DTotal", QFT_UNIT, "vg_size",
1138 "Total disk space in volume group used for instance disk"
1140 "mfree": ("MFree", QFT_UNIT, "memory_free",
1141 "Memory available for instance allocations"),
1142 "mnode": ("MNode", QFT_UNIT, "memory_dom0",
1143 "Amount of memory used by node (dom0 for Xen)"),
1144 "mtotal": ("MTotal", QFT_UNIT, "memory_total",
1145 "Total amount of memory of physical machine"),
1150 """Build function for calling another function with an node group.
1152 @param cb: The callback to be called with the nodegroup
1156 """Get group data for a node.
1158 @type ctx: L{NodeQueryData}
1159 @type inst: L{objects.Node}
1160 @param inst: Node object
1163 ng = ctx.groups.get(node.group, None)
1165 # Nodes always have a group, or the configuration is corrupt
1168 return cb(ctx, node, ng)
1173 def _GetNodeGroup(ctx, node, ng): # pylint: disable=W0613
1174 """Returns the name of a node's group.
1176 @type ctx: L{NodeQueryData}
1177 @type node: L{objects.Node}
1178 @param node: Node object
1179 @type ng: L{objects.NodeGroup}
1180 @param ng: The node group this node belongs to
1186 def _GetNodePower(ctx, node):
1187 """Returns the node powered state
1189 @type ctx: L{NodeQueryData}
1190 @type node: L{objects.Node}
1191 @param node: Node object
1194 if ctx.oob_support[node.name]:
1200 def _GetNdParams(ctx, node, ng):
1201 """Returns the ndparams for this node.
1203 @type ctx: L{NodeQueryData}
1204 @type node: L{objects.Node}
1205 @param node: Node object
1206 @type ng: L{objects.NodeGroup}
1207 @param ng: The node group this node belongs to
1210 return ctx.cluster.SimpleFillND(ng.FillND(node))
1213 def _GetLiveNodeField(field, kind, ctx, node):
1214 """Gets the value of a "live" field from L{NodeQueryData}.
1216 @param field: Live field name
1217 @param kind: Data kind, one of L{constants.QFT_ALL}
1218 @type ctx: L{NodeQueryData}
1219 @type node: L{objects.Node}
1220 @param node: Node object
1226 if not node.vm_capable:
1229 if not ctx.curlive_data:
1233 value = ctx.curlive_data[field]
1237 if kind == QFT_TEXT:
1240 assert kind in (QFT_NUMBER, QFT_UNIT)
1242 # Try to convert into number
1245 except (ValueError, TypeError):
1246 logging.exception("Failed to convert node field '%s' (value %r) to int",
1251 def _GetNodeHvState(_, node):
1252 """Converts node's hypervisor state for query result.
1255 hv_state = node.hv_state
1257 if hv_state is None:
1260 return dict((name, value.ToDict()) for (name, value) in hv_state.items())
1263 def _GetNodeDiskState(_, node):
1264 """Converts node's disk state for query result.
1267 disk_state = node.disk_state
1269 if disk_state is None:
1272 return dict((disk_kind, dict((name, value.ToDict())
1273 for (name, value) in kind_state.items()))
1274 for (disk_kind, kind_state) in disk_state.items())
1277 def _BuildNodeFields():
1278 """Builds list of fields for node queries.
1282 (_MakeField("pip", "PrimaryIP", QFT_TEXT, "Primary IP address"),
1283 NQ_CONFIG, 0, _GetItemAttr("primary_ip")),
1284 (_MakeField("sip", "SecondaryIP", QFT_TEXT, "Secondary IP address"),
1285 NQ_CONFIG, 0, _GetItemAttr("secondary_ip")),
1286 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0,
1287 lambda ctx, node: list(node.GetTags())),
1288 (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"),
1289 NQ_CONFIG, 0, lambda ctx, node: node.name == ctx.master_name),
1290 (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0,
1291 _GetGroup(_GetNodeGroup)),
1292 (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"),
1293 NQ_CONFIG, 0, _GetItemAttr("group")),
1294 (_MakeField("powered", "Powered", QFT_BOOL,
1295 "Whether node is thought to be powered on"),
1296 NQ_OOB, 0, _GetNodePower),
1297 (_MakeField("ndparams", "NodeParameters", QFT_OTHER,
1298 "Merged node parameters"),
1299 NQ_GROUP, 0, _GetGroup(_GetNdParams)),
1300 (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER,
1301 "Custom node parameters"),
1302 NQ_GROUP, 0, _GetItemAttr("ndparams")),
1303 (_MakeField("hv_state", "HypervisorState", QFT_OTHER, "Hypervisor state"),
1304 NQ_CONFIG, 0, _GetNodeHvState),
1305 (_MakeField("disk_state", "DiskState", QFT_OTHER, "Disk state"),
1306 NQ_CONFIG, 0, _GetNodeDiskState),
1309 fields.extend(_BuildNDFields(False))
1312 role_values = (constants.NR_MASTER, constants.NR_MCANDIDATE,
1313 constants.NR_REGULAR, constants.NR_DRAINED,
1314 constants.NR_OFFLINE)
1315 role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate,"
1316 " \"%s\" for regular, \"%s\" for drained, \"%s\" for offline" %
1318 fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
1319 lambda ctx, node: _GetNodeRole(node, ctx.master_name)))
1320 assert set(role_values) == constants.NR_ALL
1322 def _GetLength(getter):
1323 return lambda ctx, node: len(getter(ctx)[node.name])
1325 def _GetList(getter):
1326 return lambda ctx, node: list(getter(ctx)[node.name])
1328 # Add fields operating on instance lists
1329 for prefix, titleprefix, docword, getter in \
1330 [("p", "Pri", "primary", operator.attrgetter("node_to_primary")),
1331 ("s", "Sec", "secondary", operator.attrgetter("node_to_secondary"))]:
1332 # TODO: Allow filterting by hostname in list
1334 (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER,
1335 "Number of instances with this node as %s" % docword),
1336 NQ_INST, 0, _GetLength(getter)),
1337 (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
1339 "List of instances with this node as %s" % docword),
1340 NQ_INST, 0, _GetList(getter)),
1345 (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1346 for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()])
1348 # Add fields requiring live data
1350 (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1351 compat.partial(_GetLiveNodeField, nfield, kind))
1352 for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()])
1355 fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1357 return _PrepareFieldList(fields, [])
1360 class InstanceQueryData:
1361 """Data container for instance data queries.
1364 def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
1365 live_data, wrongnode_inst, console, nodes, groups):
1366 """Initializes this class.
1368 @param instances: List of instance objects
1369 @param cluster: Cluster object
1370 @type disk_usage: dict; instance name as key
1371 @param disk_usage: Per-instance disk usage
1372 @type offline_nodes: list of strings
1373 @param offline_nodes: List of offline nodes
1374 @type bad_nodes: list of strings
1375 @param bad_nodes: List of faulty nodes
1376 @type live_data: dict; instance name as key
1377 @param live_data: Per-instance live data
1378 @type wrongnode_inst: set
1379 @param wrongnode_inst: Set of instances running on wrong node(s)
1380 @type console: dict; instance name as key
1381 @param console: Per-instance console information
1382 @type nodes: dict; node name as key
1383 @param nodes: Node objects
1386 assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
1387 "Offline nodes not included in bad nodes"
1388 assert not (set(live_data.keys()) & set(bad_nodes)), \
1389 "Found live data for bad or offline nodes"
1391 self.instances = instances
1392 self.cluster = cluster
1393 self.disk_usage = disk_usage
1394 self.offline_nodes = offline_nodes
1395 self.bad_nodes = bad_nodes
1396 self.live_data = live_data
1397 self.wrongnode_inst = wrongnode_inst
1398 self.console = console
1400 self.groups = groups
1402 # Used for individual rows
1403 self.inst_hvparams = None
1404 self.inst_beparams = None
1405 self.inst_osparams = None
1406 self.inst_nicparams = None
1409 """Iterate over all instances.
1411 This function has side-effects and only one instance of the resulting
1412 generator should be used at a time.
1415 for inst in self.instances:
1416 self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1417 self.inst_beparams = self.cluster.FillBE(inst)
1418 self.inst_osparams = self.cluster.SimpleFillOS(inst.os, inst.osparams)
1419 self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1420 for nic in inst.nics]
1425 def _GetInstOperState(ctx, inst):
1426 """Get instance's operational status.
1428 @type ctx: L{InstanceQueryData}
1429 @type inst: L{objects.Instance}
1430 @param inst: Instance object
1433 # Can't use RS_OFFLINE here as it would describe the instance to
1434 # be offline when we actually don't know due to missing data
1435 if inst.primary_node in ctx.bad_nodes:
1438 return bool(ctx.live_data.get(inst.name))
1441 def _GetInstLiveData(name):
1442 """Build function for retrieving live data.
1445 @param name: Live data field name
1449 """Get live data for an instance.
1451 @type ctx: L{InstanceQueryData}
1452 @type inst: L{objects.Instance}
1453 @param inst: Instance object
1456 if (inst.primary_node in ctx.bad_nodes or
1457 inst.primary_node in ctx.offline_nodes):
1458 # Can't use RS_OFFLINE here as it would describe the instance to be
1459 # offline when we actually don't know due to missing data
1462 if inst.name in ctx.live_data:
1463 data = ctx.live_data[inst.name]
1472 def _GetInstStatus(ctx, inst):
1473 """Get instance status.
1475 @type ctx: L{InstanceQueryData}
1476 @type inst: L{objects.Instance}
1477 @param inst: Instance object
1480 if inst.primary_node in ctx.offline_nodes:
1481 return constants.INSTST_NODEOFFLINE
1483 if inst.primary_node in ctx.bad_nodes:
1484 return constants.INSTST_NODEDOWN
1486 if bool(ctx.live_data.get(inst.name)):
1487 if inst.name in ctx.wrongnode_inst:
1488 return constants.INSTST_WRONGNODE
1489 elif inst.admin_state == constants.ADMINST_UP:
1490 return constants.INSTST_RUNNING
1492 return constants.INSTST_ERRORUP
1494 if inst.admin_state == constants.ADMINST_UP:
1495 return constants.INSTST_ERRORDOWN
1496 elif inst.admin_state == constants.ADMINST_DOWN:
1497 return constants.INSTST_ADMINDOWN
1499 return constants.INSTST_ADMINOFFLINE
1502 def _GetInstDiskSize(index):
1503 """Build function for retrieving disk size.
1506 @param index: Disk index
1510 """Get size of a disk.
1512 @type inst: L{objects.Instance}
1513 @param inst: Instance object
1517 return inst.disks[index].size
1524 def _GetInstNic(index, cb):
1525 """Build function for calling another function with an instance NIC.
1528 @param index: NIC index
1534 """Call helper function with instance NIC.
1536 @type ctx: L{InstanceQueryData}
1537 @type inst: L{objects.Instance}
1538 @param inst: Instance object
1542 nic = inst.nics[index]
1546 return cb(ctx, index, nic)
1551 def _GetInstNicNetwork(ctx, _, nic): # pylint: disable=W0613
1552 """Get a NIC's Network.
1554 @type ctx: L{InstanceQueryData}
1555 @type nic: L{objects.NIC}
1556 @param nic: NIC object
1559 if nic.network is None:
1565 def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613
1566 """Get a NIC's IP address.
1568 @type ctx: L{InstanceQueryData}
1569 @type nic: L{objects.NIC}
1570 @param nic: NIC object
1579 def _GetInstNicBridge(ctx, index, _):
1580 """Get a NIC's bridge.
1582 @type ctx: L{InstanceQueryData}
1584 @param index: NIC index
1587 assert len(ctx.inst_nicparams) >= index
1589 nicparams = ctx.inst_nicparams[index]
1591 if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1592 return nicparams[constants.NIC_LINK]
1597 def _GetInstAllNicBridges(ctx, inst):
1598 """Get all network bridges for an instance.
1600 @type ctx: L{InstanceQueryData}
1601 @type inst: L{objects.Instance}
1602 @param inst: Instance object
1605 assert len(ctx.inst_nicparams) == len(inst.nics)
1609 for nicp in ctx.inst_nicparams:
1610 if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1611 result.append(nicp[constants.NIC_LINK])
1615 assert len(result) == len(inst.nics)
1620 def _GetInstNicParam(name):
1621 """Build function for retrieving a NIC parameter.
1624 @param name: Parameter name
1627 def fn(ctx, index, _):
1628 """Get a NIC's bridge.
1630 @type ctx: L{InstanceQueryData}
1631 @type inst: L{objects.Instance}
1632 @param inst: Instance object
1633 @type nic: L{objects.NIC}
1634 @param nic: NIC object
1637 assert len(ctx.inst_nicparams) >= index
1638 return ctx.inst_nicparams[index][name]
1643 def _GetInstanceNetworkFields():
1644 """Get instance fields involving network interfaces.
1646 @return: Tuple containing list of field definitions used as input for
1647 L{_PrepareFieldList} and a list of aliases
1650 nic_mac_fn = lambda ctx, _, nic: nic.mac
1651 nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1652 nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1656 (_MakeField("nic.count", "NICs", QFT_NUMBER,
1657 "Number of network interfaces"),
1658 IQ_CONFIG, 0, lambda ctx, inst: len(inst.nics)),
1659 (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER,
1660 "List containing each network interface's MAC address"),
1661 IQ_CONFIG, 0, lambda ctx, inst: [nic.mac for nic in inst.nics]),
1662 (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
1663 "List containing each network interface's IP address"),
1664 IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
1665 (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
1666 "List containing each network interface's mode"), IQ_CONFIG, 0,
1667 lambda ctx, inst: [nicp[constants.NIC_MODE]
1668 for nicp in ctx.inst_nicparams]),
1669 (_MakeField("nic.links", "NIC_links", QFT_OTHER,
1670 "List containing each network interface's link"), IQ_CONFIG, 0,
1671 lambda ctx, inst: [nicp[constants.NIC_LINK]
1672 for nicp in ctx.inst_nicparams]),
1673 (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
1674 "List containing each network interface's bridge"),
1675 IQ_CONFIG, 0, _GetInstAllNicBridges),
1676 (_MakeField("nic.networks", "NIC_networks", QFT_OTHER,
1677 "List containing each interface's network"), IQ_CONFIG, 0,
1678 lambda ctx, inst: [nic.network for nic in inst.nics]),
1682 for i in range(constants.MAX_NICS):
1683 numtext = utils.FormatOrdinal(i + 1)
1685 (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT,
1686 "IP address of %s network interface" % numtext),
1687 IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)),
1688 (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
1689 "MAC address of %s network interface" % numtext),
1690 IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
1691 (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
1692 "Mode of %s network interface" % numtext),
1693 IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
1694 (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT,
1695 "Link of %s network interface" % numtext),
1696 IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)),
1697 (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
1698 "Bridge of %s network interface" % numtext),
1699 IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
1700 (_MakeField("nic.network/%s" % i, "NicNetwork/%s" % i, QFT_TEXT,
1701 "Network of %s network interface" % numtext),
1702 IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicNetwork)),
1706 # Legacy fields for first NIC
1708 ("mac", "nic.mac/0"),
1709 ("bridge", "nic.bridge/0"),
1710 ("nic_mode", "nic.mode/0"),
1711 ("nic_link", "nic.link/0"),
1712 ("nic_network", "nic.network/0"),
1715 return (fields, aliases)
1718 def _GetInstDiskUsage(ctx, inst):
1719 """Get disk usage for an instance.
1721 @type ctx: L{InstanceQueryData}
1722 @type inst: L{objects.Instance}
1723 @param inst: Instance object
1726 usage = ctx.disk_usage[inst.name]
1734 def _GetInstanceConsole(ctx, inst):
1735 """Get console information for instance.
1737 @type ctx: L{InstanceQueryData}
1738 @type inst: L{objects.Instance}
1739 @param inst: Instance object
1742 consinfo = ctx.console[inst.name]
1744 if consinfo is None:
1750 def _GetInstanceDiskFields():
1751 """Get instance fields involving disks.
1753 @return: List of field definitions used as input for L{_PrepareFieldList}
1757 (_MakeField("disk_usage", "DiskUsage", QFT_UNIT,
1758 "Total disk space used by instance on each of its nodes;"
1759 " this is not the disk size visible to the instance, but"
1760 " the usage on the node"),
1761 IQ_DISKUSAGE, 0, _GetInstDiskUsage),
1762 (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"),
1763 IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
1764 (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
1765 IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
1770 (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1771 "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
1772 IQ_CONFIG, 0, _GetInstDiskSize(i))
1773 for i in range(constants.MAX_DISKS)])
1778 def _GetInstanceParameterFields():
1779 """Get instance fields involving parameters.
1781 @return: List of field definitions used as input for L{_PrepareFieldList}
1786 (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
1787 "Hypervisor parameters (merged)"),
1788 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
1789 (_MakeField("beparams", "BackendParameters", QFT_OTHER,
1790 "Backend parameters (merged)"),
1791 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
1792 (_MakeField("osparams", "OpSysParameters", QFT_OTHER,
1793 "Operating system parameters (merged)"),
1794 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_osparams),
1796 # Unfilled parameters
1797 (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
1798 "Custom hypervisor parameters"),
1799 IQ_CONFIG, 0, _GetItemAttr("hvparams")),
1800 (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
1801 "Custom backend parameters",),
1802 IQ_CONFIG, 0, _GetItemAttr("beparams")),
1803 (_MakeField("custom_osparams", "CustomOpSysParameters", QFT_OTHER,
1804 "Custom operating system parameters",),
1805 IQ_CONFIG, 0, _GetItemAttr("osparams")),
1806 (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
1807 "Custom network interface parameters"),
1808 IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1812 def _GetInstHvParam(name):
1813 return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1816 (_MakeField("hv/%s" % name,
1817 constants.HVS_PARAMETER_TITLES.get(name, "hv/%s" % name),
1818 _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1819 IQ_CONFIG, 0, _GetInstHvParam(name))
1820 for name, kind in constants.HVS_PARAMETER_TYPES.items()
1821 if name not in constants.HVC_GLOBALS])
1824 def _GetInstBeParam(name):
1825 return lambda ctx, _: ctx.inst_beparams.get(name, None)
1828 (_MakeField("be/%s" % name,
1829 constants.BES_PARAMETER_TITLES.get(name, "be/%s" % name),
1830 _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1831 IQ_CONFIG, 0, _GetInstBeParam(name))
1832 for name, kind in constants.BES_PARAMETER_TYPES.items()])
1837 _INST_SIMPLE_FIELDS = {
1838 "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"),
1839 "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"),
1840 "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"),
1841 # Depending on the hypervisor, the port can be None
1842 "network_port": ("Network_port", QFT_OTHER, 0,
1843 "Instance network port if available (e.g. for VNC console)"),
1844 "os": ("OS", QFT_TEXT, 0, "Operating system"),
1845 "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"),
1846 "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"),
1850 def _GetInstNodeGroup(ctx, default, node_name):
1851 """Gets group UUID of an instance node.
1853 @type ctx: L{InstanceQueryData}
1854 @param default: Default value
1855 @type node_name: string
1856 @param node_name: Node name
1860 node = ctx.nodes[node_name]
1867 def _GetInstNodeGroupName(ctx, default, node_name):
1868 """Gets group name of an instance node.
1870 @type ctx: L{InstanceQueryData}
1871 @param default: Default value
1872 @type node_name: string
1873 @param node_name: Node name
1877 node = ctx.nodes[node_name]
1882 group = ctx.groups[node.group]
1889 def _BuildInstanceFields():
1890 """Builds list of fields for instance queries.
1894 (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
1895 IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")),
1896 (_MakeField("pnode.group", "PrimaryNodeGroup", QFT_TEXT,
1897 "Primary node's group"),
1899 lambda ctx, inst: _GetInstNodeGroupName(ctx, _FS_UNAVAIL,
1900 inst.primary_node)),
1901 (_MakeField("pnode.group.uuid", "PrimaryNodeGroupUUID", QFT_TEXT,
1902 "Primary node's group UUID"),
1904 lambda ctx, inst: _GetInstNodeGroup(ctx, _FS_UNAVAIL, inst.primary_node)),
1905 # TODO: Allow filtering by secondary node as hostname
1906 (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
1907 "Secondary nodes; usually this will just be one node"),
1908 IQ_CONFIG, 0, lambda ctx, inst: list(inst.secondary_nodes)),
1909 (_MakeField("snodes.group", "SecondaryNodesGroups", QFT_OTHER,
1910 "Node groups of secondary nodes"),
1912 lambda ctx, inst: map(compat.partial(_GetInstNodeGroupName, ctx, None),
1913 inst.secondary_nodes)),
1914 (_MakeField("snodes.group.uuid", "SecondaryNodesGroupsUUID", QFT_OTHER,
1915 "Node group UUIDs of secondary nodes"),
1917 lambda ctx, inst: map(compat.partial(_GetInstNodeGroup, ctx, None),
1918 inst.secondary_nodes)),
1919 (_MakeField("admin_state", "InstanceState", QFT_TEXT,
1920 "Desired state of instance"),
1921 IQ_CONFIG, 0, _GetItemAttr("admin_state")),
1922 (_MakeField("admin_up", "Autostart", QFT_BOOL,
1923 "Desired state of instance"),
1924 IQ_CONFIG, 0, lambda ctx, inst: inst.admin_state == constants.ADMINST_UP),
1925 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
1926 lambda ctx, inst: list(inst.GetTags())),
1927 (_MakeField("console", "Console", QFT_OTHER,
1928 "Instance console information"), IQ_CONSOLE, 0,
1929 _GetInstanceConsole),
1934 (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1935 for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()])
1937 # Fields requiring talking to the node
1939 (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
1940 IQ_LIVE, 0, _GetInstOperState),
1941 (_MakeField("oper_ram", "Memory", QFT_UNIT,
1942 "Actual memory usage as seen by hypervisor"),
1943 IQ_LIVE, 0, _GetInstLiveData("memory")),
1944 (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
1945 "Actual number of VCPUs as seen by hypervisor"),
1946 IQ_LIVE, 0, _GetInstLiveData("vcpus")),
1950 status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
1951 constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
1952 constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
1953 constants.INSTST_NODEOFFLINE, constants.INSTST_ADMINOFFLINE)
1954 status_doc = ("Instance status; \"%s\" if instance is set to be running"
1955 " and actually is, \"%s\" if instance is stopped and"
1956 " is not running, \"%s\" if instance running, but not on its"
1957 " designated primary node, \"%s\" if instance should be"
1958 " stopped, but is actually running, \"%s\" if instance should"
1959 " run, but doesn't, \"%s\" if instance's primary node is down,"
1960 " \"%s\" if instance's primary node is marked offline,"
1961 " \"%s\" if instance is offline and does not use dynamic"
1962 " resources" % status_values)
1963 fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
1964 IQ_LIVE, 0, _GetInstStatus))
1965 assert set(status_values) == constants.INSTST_ALL, \
1966 "Status documentation mismatch"
1968 (network_fields, network_aliases) = _GetInstanceNetworkFields()
1970 fields.extend(network_fields)
1971 fields.extend(_GetInstanceParameterFields())
1972 fields.extend(_GetInstanceDiskFields())
1973 fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1976 ("vcpus", "be/vcpus"),
1977 ("be/memory", "be/maxmem"),
1978 ("sda_size", "disk.size/0"),
1979 ("sdb_size", "disk.size/1"),
1982 return _PrepareFieldList(fields, aliases)
1985 class LockQueryData:
1986 """Data container for lock data queries.
1989 def __init__(self, lockdata):
1990 """Initializes this class.
1993 self.lockdata = lockdata
1996 """Iterate over all locks.
1999 return iter(self.lockdata)
2002 def _GetLockOwners(_, data):
2003 """Returns a sorted list of a lock's current owners.
2006 (_, _, owners, _) = data
2009 owners = utils.NiceSort(owners)
2014 def _GetLockPending(_, data):
2015 """Returns a sorted list of a lock's pending acquires.
2018 (_, _, _, pending) = data
2021 pending = [(mode, utils.NiceSort(names))
2022 for (mode, names) in pending]
2027 def _BuildLockFields():
2028 """Builds list of fields for lock queries.
2031 return _PrepareFieldList([
2032 # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
2033 (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
2034 lambda ctx, (name, mode, owners, pending): name),
2035 (_MakeField("mode", "Mode", QFT_OTHER,
2036 "Mode in which the lock is currently acquired"
2037 " (exclusive or shared)"),
2038 LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
2039 (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
2040 LQ_OWNER, 0, _GetLockOwners),
2041 (_MakeField("pending", "Pending", QFT_OTHER,
2042 "Threads waiting for the lock"),
2043 LQ_PENDING, 0, _GetLockPending),
2047 class GroupQueryData:
2048 """Data container for node group data queries.
2051 def __init__(self, cluster, groups, group_to_nodes, group_to_instances,
2053 """Initializes this class.
2055 @param cluster: Cluster object
2056 @param groups: List of node group objects
2057 @type group_to_nodes: dict; group UUID as key
2058 @param group_to_nodes: Per-group list of nodes
2059 @type group_to_instances: dict; group UUID as key
2060 @param group_to_instances: Per-group list of (primary) instances
2061 @type want_diskparams: bool
2062 @param want_diskparams: Whether diskparamters should be calculated
2065 self.groups = groups
2066 self.group_to_nodes = group_to_nodes
2067 self.group_to_instances = group_to_instances
2068 self.cluster = cluster
2069 self.want_diskparams = want_diskparams
2071 # Used for individual rows
2072 self.group_ipolicy = None
2073 self.ndparams = None
2074 self.group_dp = None
2077 """Iterate over all node groups.
2079 This function has side-effects and only one instance of the resulting
2080 generator should be used at a time.
2083 for group in self.groups:
2084 self.group_ipolicy = self.cluster.SimpleFillIPolicy(group.ipolicy)
2085 self.ndparams = self.cluster.SimpleFillND(group.ndparams)
2086 if self.want_diskparams:
2087 self.group_dp = self.cluster.SimpleFillDP(group.diskparams)
2089 self.group_dp = None
2093 _GROUP_SIMPLE_FIELDS = {
2094 "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
2095 "name": ("Group", QFT_TEXT, "Group name"),
2096 "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
2097 "uuid": ("UUID", QFT_TEXT, "Group UUID"),
2101 def _BuildGroupFields():
2102 """Builds list of fields for node group queries.
2106 fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
2108 for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
2110 def _GetLength(getter):
2111 return lambda ctx, group: len(getter(ctx)[group.uuid])
2113 def _GetSortedList(getter):
2114 return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
2116 group_to_nodes = operator.attrgetter("group_to_nodes")
2117 group_to_instances = operator.attrgetter("group_to_instances")
2119 # Add fields for nodes
2121 (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
2122 GQ_NODE, 0, _GetLength(group_to_nodes)),
2123 (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
2124 GQ_NODE, 0, _GetSortedList(group_to_nodes)),
2127 # Add fields for instances
2129 (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
2130 "Number of primary instances"),
2131 GQ_INST, 0, _GetLength(group_to_instances)),
2132 (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
2133 "List of primary instances"),
2134 GQ_INST, 0, _GetSortedList(group_to_instances)),
2139 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), GQ_CONFIG, 0,
2140 lambda ctx, group: list(group.GetTags())),
2141 (_MakeField("ipolicy", "InstancePolicy", QFT_OTHER,
2142 "Instance policy limitations (merged)"),
2143 GQ_CONFIG, 0, lambda ctx, _: ctx.group_ipolicy),
2144 (_MakeField("custom_ipolicy", "CustomInstancePolicy", QFT_OTHER,
2145 "Custom instance policy limitations"),
2146 GQ_CONFIG, 0, _GetItemAttr("ipolicy")),
2147 (_MakeField("custom_ndparams", "CustomNDParams", QFT_OTHER,
2148 "Custom node parameters"),
2149 GQ_CONFIG, 0, _GetItemAttr("ndparams")),
2150 (_MakeField("ndparams", "NDParams", QFT_OTHER,
2152 GQ_CONFIG, 0, lambda ctx, _: ctx.ndparams),
2153 (_MakeField("diskparams", "DiskParameters", QFT_OTHER,
2154 "Disk parameters (merged)"),
2155 GQ_DISKPARAMS, 0, lambda ctx, _: ctx.group_dp),
2156 (_MakeField("custom_diskparams", "CustomDiskParameters", QFT_OTHER,
2157 "Custom disk parameters"),
2158 GQ_CONFIG, 0, _GetItemAttr("diskparams")),
2162 fields.extend(_BuildNDFields(True))
2164 fields.extend(_GetItemTimestampFields(GQ_CONFIG))
2166 return _PrepareFieldList(fields, [])
2169 class OsInfo(objects.ConfigObject):
2182 def _BuildOsFields():
2183 """Builds list of fields for operating system queries.
2187 (_MakeField("name", "Name", QFT_TEXT, "Operating system name"),
2188 None, 0, _GetItemAttr("name")),
2189 (_MakeField("valid", "Valid", QFT_BOOL,
2190 "Whether operating system definition is valid"),
2191 None, 0, _GetItemAttr("valid")),
2192 (_MakeField("hidden", "Hidden", QFT_BOOL,
2193 "Whether operating system is hidden"),
2194 None, 0, _GetItemAttr("hidden")),
2195 (_MakeField("blacklisted", "Blacklisted", QFT_BOOL,
2196 "Whether operating system is blacklisted"),
2197 None, 0, _GetItemAttr("blacklisted")),
2198 (_MakeField("variants", "Variants", QFT_OTHER,
2199 "Operating system variants"),
2200 None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("variants"))),
2201 (_MakeField("api_versions", "ApiVersions", QFT_OTHER,
2202 "Operating system API versions"),
2203 None, 0, _ConvWrap(sorted, _GetItemAttr("api_versions"))),
2204 (_MakeField("parameters", "Parameters", QFT_OTHER,
2205 "Operating system parameters"),
2206 None, 0, _ConvWrap(compat.partial(utils.NiceSort, key=compat.fst),
2207 _GetItemAttr("parameters"))),
2208 (_MakeField("node_status", "NodeStatus", QFT_OTHER,
2209 "Status from node"),
2210 None, 0, _GetItemAttr("node_status")),
2213 return _PrepareFieldList(fields, [])
2216 def _JobUnavailInner(fn, ctx, (job_id, job)): # pylint: disable=W0613
2217 """Return L{_FS_UNAVAIL} if job is None.
2219 When listing specifc jobs (e.g. "gnt-job list 1 2 3"), a job may not be
2220 found, in which case this function converts it to L{_FS_UNAVAIL}.
2229 def _JobUnavail(inner):
2230 """Wrapper for L{_JobUnavailInner}.
2233 return compat.partial(_JobUnavailInner, inner)
2236 def _PerJobOpInner(fn, job):
2237 """Executes a function per opcode in a job.
2240 return map(fn, job.ops)
2244 """Wrapper for L{_PerJobOpInner}.
2247 return _JobUnavail(compat.partial(_PerJobOpInner, fn))
2250 def _JobTimestampInner(fn, job):
2251 """Converts unavailable timestamp to L{_FS_UNAVAIL}.
2256 if timestamp is None:
2262 def _JobTimestamp(fn):
2263 """Wrapper for L{_JobTimestampInner}.
2266 return _JobUnavail(compat.partial(_JobTimestampInner, fn))
2269 def _BuildJobFields():
2270 """Builds list of fields for job queries.
2274 (_MakeField("id", "ID", QFT_NUMBER, "Job ID"),
2275 None, QFF_JOB_ID, lambda _, (job_id, job): job_id),
2276 (_MakeField("status", "Status", QFT_TEXT, "Job status"),
2277 None, 0, _JobUnavail(lambda job: job.CalcStatus())),
2278 (_MakeField("priority", "Priority", QFT_NUMBER,
2279 ("Current job priority (%s to %s)" %
2280 (constants.OP_PRIO_LOWEST, constants.OP_PRIO_HIGHEST))),
2281 None, 0, _JobUnavail(lambda job: job.CalcPriority())),
2282 (_MakeField("archived", "Archived", QFT_BOOL, "Whether job is archived"),
2283 JQ_ARCHIVED, 0, lambda _, (job_id, job): job.archived),
2284 (_MakeField("ops", "OpCodes", QFT_OTHER, "List of all opcodes"),
2285 None, 0, _PerJobOp(lambda op: op.input.__getstate__())),
2286 (_MakeField("opresult", "OpCode_result", QFT_OTHER,
2287 "List of opcodes results"),
2288 None, 0, _PerJobOp(operator.attrgetter("result"))),
2289 (_MakeField("opstatus", "OpCode_status", QFT_OTHER,
2290 "List of opcodes status"),
2291 None, 0, _PerJobOp(operator.attrgetter("status"))),
2292 (_MakeField("oplog", "OpCode_log", QFT_OTHER,
2293 "List of opcode output logs"),
2294 None, 0, _PerJobOp(operator.attrgetter("log"))),
2295 (_MakeField("opstart", "OpCode_start", QFT_OTHER,
2296 "List of opcode start timestamps (before acquiring locks)"),
2297 None, 0, _PerJobOp(operator.attrgetter("start_timestamp"))),
2298 (_MakeField("opexec", "OpCode_exec", QFT_OTHER,
2299 "List of opcode execution start timestamps (after acquiring"
2301 None, 0, _PerJobOp(operator.attrgetter("exec_timestamp"))),
2302 (_MakeField("opend", "OpCode_end", QFT_OTHER,
2303 "List of opcode execution end timestamps"),
2304 None, 0, _PerJobOp(operator.attrgetter("end_timestamp"))),
2305 (_MakeField("oppriority", "OpCode_prio", QFT_OTHER,
2306 "List of opcode priorities"),
2307 None, 0, _PerJobOp(operator.attrgetter("priority"))),
2308 (_MakeField("summary", "Summary", QFT_OTHER,
2309 "List of per-opcode summaries"),
2310 None, 0, _PerJobOp(lambda op: op.input.Summary())),
2314 for (name, attr, title, desc) in [
2315 ("received_ts", "received_timestamp", "Received",
2316 "Timestamp of when job was received"),
2317 ("start_ts", "start_timestamp", "Start", "Timestamp of job start"),
2318 ("end_ts", "end_timestamp", "End", "Timestamp of job end"),
2320 getter = operator.attrgetter(attr)
2322 (_MakeField(name, title, QFT_OTHER,
2323 "%s (tuple containing seconds and microseconds)" % desc),
2324 None, QFF_SPLIT_TIMESTAMP, _JobTimestamp(getter)),
2327 return _PrepareFieldList(fields, [])
2330 def _GetExportName(_, (node_name, expname)): # pylint: disable=W0613
2331 """Returns an export name if available.
2340 def _BuildExportFields():
2341 """Builds list of fields for exports.
2345 (_MakeField("node", "Node", QFT_TEXT, "Node name"),
2346 None, QFF_HOSTNAME, lambda _, (node_name, expname): node_name),
2347 (_MakeField("export", "Export", QFT_TEXT, "Export name"),
2348 None, 0, _GetExportName),
2351 return _PrepareFieldList(fields, [])
2354 _CLUSTER_VERSION_FIELDS = {
2355 "software_version": ("SoftwareVersion", QFT_TEXT, constants.RELEASE_VERSION,
2356 "Software version"),
2357 "protocol_version": ("ProtocolVersion", QFT_NUMBER,
2358 constants.PROTOCOL_VERSION,
2359 "RPC protocol version"),
2360 "config_version": ("ConfigVersion", QFT_NUMBER, constants.CONFIG_VERSION,
2361 "Configuration format version"),
2362 "os_api_version": ("OsApiVersion", QFT_NUMBER, max(constants.OS_API_VERSIONS),
2363 "API version for OS template scripts"),
2364 "export_version": ("ExportVersion", QFT_NUMBER, constants.EXPORT_VERSION,
2365 "Import/export file format version"),
2369 _CLUSTER_SIMPLE_FIELDS = {
2370 "cluster_name": ("Name", QFT_TEXT, QFF_HOSTNAME, "Cluster name"),
2371 "master_node": ("Master", QFT_TEXT, QFF_HOSTNAME, "Master node name"),
2372 "volume_group_name": ("VgName", QFT_TEXT, 0, "LVM volume group name"),
2376 class ClusterQueryData:
2377 def __init__(self, cluster, drain_flag, watcher_pause):
2378 """Initializes this class.
2380 @type cluster: L{objects.Cluster}
2381 @param cluster: Instance of cluster object
2382 @type drain_flag: bool
2383 @param drain_flag: Whether job queue is drained
2384 @type watcher_pause: number
2385 @param watcher_pause: Until when watcher is paused (Unix timestamp)
2388 self._cluster = cluster
2389 self.drain_flag = drain_flag
2390 self.watcher_pause = watcher_pause
2393 return iter([self._cluster])
2396 def _ClusterWatcherPause(ctx, _):
2397 """Returns until when watcher is paused (if available).
2400 if ctx.watcher_pause is None:
2403 return ctx.watcher_pause
2406 def _BuildClusterFields():
2407 """Builds list of fields for cluster information.
2411 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), CQ_CONFIG, 0,
2412 lambda ctx, cluster: list(cluster.GetTags())),
2413 (_MakeField("architecture", "ArchInfo", QFT_OTHER,
2414 "Architecture information"), None, 0,
2415 lambda ctx, _: runtime.GetArchInfo()),
2416 (_MakeField("drain_flag", "QueueDrained", QFT_BOOL,
2417 "Flag whether job queue is drained"), CQ_QUEUE_DRAINED, 0,
2418 lambda ctx, _: ctx.drain_flag),
2419 (_MakeField("watcher_pause", "WatcherPause", QFT_TIMESTAMP,
2420 "Until when watcher is paused"), CQ_WATCHER_PAUSE, 0,
2421 _ClusterWatcherPause),
2426 (_MakeField(name, title, kind, doc), CQ_CONFIG, flags, _GetItemAttr(name))
2427 for (name, (title, kind, flags, doc)) in _CLUSTER_SIMPLE_FIELDS.items()
2432 (_MakeField(name, title, kind, doc), None, 0, _StaticValue(value))
2433 for (name, (title, kind, value, doc)) in _CLUSTER_VERSION_FIELDS.items()])
2436 fields.extend(_GetItemTimestampFields(CQ_CONFIG))
2438 return _PrepareFieldList(fields, [
2439 ("name", "cluster_name")])
2442 class NetworkQueryData:
2443 """Data container for network data queries.
2446 def __init__(self, networks, network_to_groups,
2447 network_to_instances, stats):
2448 """Initializes this class.
2450 @param networks: List of network objects
2451 @type network_to_groups: dict; network UUID as key
2452 @param network_to_groups: Per-network list of groups
2453 @type network_to_instances: dict; network UUID as key
2454 @param network_to_instances: Per-network list of instances
2455 @type stats: dict; network UUID as key
2456 @param stats: Per-network usage statistics
2459 self.networks = networks
2460 self.network_to_groups = network_to_groups
2461 self.network_to_instances = network_to_instances
2465 """Iterate over all networks.
2468 for net in self.networks:
2470 self.curstats = self.stats.get(net.uuid, None)
2472 self.curstats = None
2476 _NETWORK_SIMPLE_FIELDS = {
2477 "name": ("Network", QFT_TEXT, 0, "The network"),
2478 "network": ("Subnet", QFT_TEXT, 0, "The subnet"),
2479 "gateway": ("Gateway", QFT_OTHER, 0, "The gateway"),
2480 "network6": ("IPv6Subnet", QFT_OTHER, 0, "The ipv6 subnet"),
2481 "gateway6": ("IPv6Gateway", QFT_OTHER, 0, "The ipv6 gateway"),
2482 "mac_prefix": ("MacPrefix", QFT_OTHER, 0, "The mac prefix"),
2483 "network_type": ("NetworkType", QFT_OTHER, 0, "The network type"),
2487 _NETWORK_STATS_FIELDS = {
2488 "free_count": ("FreeCount", QFT_NUMBER, 0, "How many addresses are free"),
2489 "reserved_count": ("ReservedCount", QFT_NUMBER, 0,
2490 "How many addresses are reserved"),
2491 "map": ("Map", QFT_TEXT, 0, "The actual mapping"),
2492 "external_reservations": ("ExternalReservations", QFT_TEXT, 0,
2493 "The external reservations"),
2497 def _GetNetworkStatsField(field, kind, ctx):
2498 """Gets the value of a "stats" field from L{NetworkQueryData}.
2500 @param field: Field name
2501 @param kind: Data kind, one of L{constants.QFT_ALL}
2502 @type ctx: L{NetworkQueryData}
2507 value = ctx.curstats[field]
2511 if kind == QFT_TEXT:
2514 assert kind in (QFT_NUMBER, QFT_UNIT)
2516 # Try to convert into number
2519 except (ValueError, TypeError):
2520 logging.exception("Failed to convert network field '%s' (value %r) to int",
2525 def _BuildNetworkFields():
2526 """Builds list of fields for network queries.
2530 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
2531 lambda ctx, inst: list(inst.GetTags())),
2536 (_MakeField(name, title, kind, doc),
2537 NETQ_CONFIG, 0, _GetItemAttr(name))
2538 for (name, (title, kind, _, doc)) in _NETWORK_SIMPLE_FIELDS.items()])
2540 def _GetLength(getter):
2541 return lambda ctx, network: len(getter(ctx)[network.uuid])
2543 def _GetSortedList(getter):
2544 return lambda ctx, network: utils.NiceSort(getter(ctx)[network.uuid])
2546 network_to_groups = operator.attrgetter("network_to_groups")
2547 network_to_instances = operator.attrgetter("network_to_instances")
2549 # Add fields for node groups
2551 (_MakeField("group_cnt", "NodeGroups", QFT_NUMBER, "Number of nodegroups"),
2552 NETQ_GROUP, 0, _GetLength(network_to_groups)),
2553 (_MakeField("group_list", "GroupList", QFT_OTHER, "List of nodegroups"),
2554 NETQ_GROUP, 0, _GetSortedList(network_to_groups)),
2557 # Add fields for instances
2559 (_MakeField("inst_cnt", "Instances", QFT_NUMBER, "Number of instances"),
2560 NETQ_INST, 0, _GetLength(network_to_instances)),
2561 (_MakeField("inst_list", "InstanceList", QFT_OTHER, "List of instances"),
2562 NETQ_INST, 0, _GetSortedList(network_to_instances)),
2565 # Add fields for usage statistics
2567 (_MakeField(name, title, kind, doc), NETQ_STATS, 0,
2568 compat.partial(_GetNetworkStatsField, name, kind))
2569 for (name, (title, kind, _, doc)) in _NETWORK_STATS_FIELDS.items()])
2571 return _PrepareFieldList(fields, [])
2573 #: Fields for cluster information
2574 CLUSTER_FIELDS = _BuildClusterFields()
2576 #: Fields available for node queries
2577 NODE_FIELDS = _BuildNodeFields()
2579 #: Fields available for instance queries
2580 INSTANCE_FIELDS = _BuildInstanceFields()
2582 #: Fields available for lock queries
2583 LOCK_FIELDS = _BuildLockFields()
2585 #: Fields available for node group queries
2586 GROUP_FIELDS = _BuildGroupFields()
2588 #: Fields available for operating system queries
2589 OS_FIELDS = _BuildOsFields()
2591 #: Fields available for job queries
2592 JOB_FIELDS = _BuildJobFields()
2594 #: Fields available for exports
2595 EXPORT_FIELDS = _BuildExportFields()
2597 #: Fields available for network queries
2598 NETWORK_FIELDS = _BuildNetworkFields()
2600 #: All available resources
2602 constants.QR_CLUSTER: CLUSTER_FIELDS,
2603 constants.QR_INSTANCE: INSTANCE_FIELDS,
2604 constants.QR_NODE: NODE_FIELDS,
2605 constants.QR_LOCK: LOCK_FIELDS,
2606 constants.QR_GROUP: GROUP_FIELDS,
2607 constants.QR_OS: OS_FIELDS,
2608 constants.QR_JOB: JOB_FIELDS,
2609 constants.QR_EXPORT: EXPORT_FIELDS,
2610 constants.QR_NETWORK: NETWORK_FIELDS,
2613 #: All available field lists
2614 ALL_FIELD_LISTS = ALL_FIELDS.values()