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 qlang
67 from ganeti.constants import (QFT_UNKNOWN, QFT_TEXT, QFT_BOOL, QFT_NUMBER,
68 QFT_UNIT, QFT_TIMESTAMP, QFT_OTHER,
69 RS_NORMAL, RS_UNKNOWN, RS_NODATA,
70 RS_UNAVAIL, RS_OFFLINE)
73 # Constants for requesting data from the caller/data provider. Each property
74 # collected/computed separately by the data provider should have its own to
75 # only collect the requested data and not more.
87 IQ_NODES) = range(100, 105)
91 LQ_PENDING) = range(10, 13)
95 GQ_INST) = range(200, 203)
100 # Next values: 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200
101 QFF_ALL = (QFF_HOSTNAME | QFF_IP_ADDRESS)
103 FIELD_NAME_RE = re.compile(r"^[a-z0-9/._]+$")
104 TITLE_RE = re.compile(r"^[^\s]+$")
105 DOC_RE = re.compile(r"^[A-Z].*[^.,?!]$")
107 #: Verification function for each field type
109 QFT_UNKNOWN: ht.TNone,
110 QFT_TEXT: ht.TString,
114 QFT_TIMESTAMP: ht.TNumber,
115 QFT_OTHER: lambda _: True,
118 # Unique objects for special field statuses
119 _FS_UNKNOWN = object()
120 _FS_NODATA = object()
121 _FS_UNAVAIL = object()
122 _FS_OFFLINE = object()
124 #: List of all special status
125 _FS_ALL = frozenset([_FS_UNKNOWN, _FS_NODATA, _FS_UNAVAIL, _FS_OFFLINE])
127 #: VType to QFT mapping
129 # TODO: fix validation of empty strings
130 constants.VTYPE_STRING: QFT_OTHER, # since VTYPE_STRINGs can be empty
131 constants.VTYPE_MAYBE_STRING: QFT_OTHER,
132 constants.VTYPE_BOOL: QFT_BOOL,
133 constants.VTYPE_SIZE: QFT_UNIT,
134 constants.VTYPE_INT: QFT_NUMBER,
137 _SERIAL_NO_DOC = "%s object serial number, incremented on each modification"
139 # TODO: Consider moving titles closer to constants
141 constants.ND_OOB_PROGRAM: "OutOfBandProgram",
142 constants.ND_SPINDLE_COUNT: "SpindleCount",
146 def _GetUnknownField(ctx, item): # pylint: disable=W0613
147 """Gets the contents of an unknown field.
153 def _GetQueryFields(fielddefs, selected):
154 """Calculates the internal list of selected fields.
156 Unknown fields are returned as L{constants.QFT_UNKNOWN}.
158 @type fielddefs: dict
159 @param fielddefs: Field definitions
160 @type selected: list of strings
161 @param selected: List of selected fields
166 for name in selected:
168 fdef = fielddefs[name]
170 fdef = (_MakeField(name, name, QFT_UNKNOWN, "Unknown field '%s'" % name),
171 None, 0, _GetUnknownField)
173 assert len(fdef) == 4
180 def GetAllFields(fielddefs):
181 """Extract L{objects.QueryFieldDefinition} from field definitions.
183 @rtype: list of L{objects.QueryFieldDefinition}
186 return [fdef for (fdef, _, _, _) in fielddefs]
190 """Class for filter analytics.
192 When filters are used, the user of the L{Query} class usually doesn't know
193 exactly which items will be necessary for building the result. It therefore
194 has to prepare and compute the input data for potentially returning
197 There are two ways to optimize this. The first, and simpler, is to assign
198 each field a group of data, so that the caller can determine which
199 computations are necessary depending on the data groups requested. The list
200 of referenced groups must also be computed for fields referenced in the
203 The second is restricting the items based on a primary key. The primary key
204 is usually a unique name (e.g. a node name). This class extracts all
205 referenced names from a filter. If it encounters any filter condition which
206 disallows such a list to be determined (e.g. a non-equality filter), all
207 names will be requested.
209 The end-effect is that any operation other than L{qlang.OP_OR} and
210 L{qlang.OP_EQUAL} will make the query more expensive.
213 def __init__(self, namefield):
214 """Initializes this class.
216 @type namefield: string
217 @param namefield: Field caller is interested in
220 self._namefield = namefield
222 #: Whether all names need to be requested (e.g. if a non-equality operator
224 self._allnames = False
226 #: Which names to request
229 #: Data kinds referenced by the filter (used by L{Query.RequestedData})
230 self._datakinds = set()
232 def RequestedNames(self):
233 """Returns all requested values.
235 Returns C{None} if list of values can't be determined (e.g. encountered
236 non-equality operators).
241 if self._allnames or self._names is None:
244 return utils.UniqueSequence(self._names)
246 def ReferencedData(self):
247 """Returns all kinds of data referenced by the filter.
250 return frozenset(self._datakinds)
252 def _NeedAllNames(self):
253 """Changes internal state to request all names.
256 self._allnames = True
259 def NoteLogicOp(self, op):
260 """Called when handling a logic operation.
266 if op != qlang.OP_OR:
269 def NoteUnaryOp(self, op): # pylint: disable=W0613
270 """Called when handling an unary operation.
278 def NoteBinaryOp(self, op, datakind, name, value):
279 """Called when handling a binary operation.
284 @param name: Left-hand side of operator (field name)
285 @param value: Right-hand side of operator
288 if datakind is not None:
289 self._datakinds.add(datakind)
294 # If any operator other than equality was used, all names need to be
296 if op == qlang.OP_EQUAL and name == self._namefield:
297 if self._names is None:
299 self._names.append(value)
304 def _WrapLogicOp(op_fn, sentences, ctx, item):
305 """Wrapper for logic operator functions.
308 return op_fn(fn(ctx, item) for fn in sentences)
311 def _WrapUnaryOp(op_fn, inner, ctx, item):
312 """Wrapper for unary operator functions.
315 return op_fn(inner(ctx, item))
318 def _WrapBinaryOp(op_fn, retrieval_fn, value, ctx, item):
319 """Wrapper for binary operator functions.
322 return op_fn(retrieval_fn(ctx, item), value)
325 def _WrapNot(fn, lhs, rhs):
326 """Negates the result of a wrapped function.
329 return not fn(lhs, rhs)
332 def _PrepareRegex(pattern):
333 """Compiles a regular expression.
337 return re.compile(pattern)
338 except re.error, err:
339 raise errors.ParameterError("Invalid regex pattern (%s)" % err)
342 class _FilterCompilerHelper:
343 """Converts a query filter to a callable usable for filtering.
346 # String statement has no effect, pylint: disable=W0105
348 #: How deep filters can be nested
351 # Unique identifiers for operator groups
354 _OPTYPE_BINARY) = range(1, 4)
356 """Functions for equality checks depending on field flags.
358 List of tuples containing flags and a callable receiving the left- and
359 right-hand side of the operator. The flags are an OR-ed value of C{QFF_*}
360 (e.g. L{QFF_HOSTNAME}).
362 Order matters. The first item with flags will be used. Flags are checked
368 lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
369 case_sensitive=False),
371 (None, operator.eq, None),
376 Operator as key (C{qlang.OP_*}), value a tuple of operator group
377 (C{_OPTYPE_*}) and a group-specific value:
379 - C{_OPTYPE_LOGIC}: Callable taking any number of arguments; used by
381 - C{_OPTYPE_UNARY}: Always C{None}; details handled by L{_HandleUnaryOp}
382 - C{_OPTYPE_BINARY}: Callable taking exactly two parameters, the left- and
383 right-hand side of the operator, used by L{_HandleBinaryOp}
388 qlang.OP_OR: (_OPTYPE_LOGIC, compat.any),
389 qlang.OP_AND: (_OPTYPE_LOGIC, compat.all),
392 qlang.OP_NOT: (_OPTYPE_UNARY, None),
393 qlang.OP_TRUE: (_OPTYPE_UNARY, None),
396 qlang.OP_EQUAL: (_OPTYPE_BINARY, _EQUALITY_CHECKS),
398 (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn), valprepfn)
399 for (flags, fn, valprepfn) in _EQUALITY_CHECKS]),
400 qlang.OP_REGEXP: (_OPTYPE_BINARY, [
401 (None, lambda lhs, rhs: rhs.search(lhs), _PrepareRegex),
403 qlang.OP_CONTAINS: (_OPTYPE_BINARY, [
404 (None, operator.contains, None),
408 def __init__(self, fields):
409 """Initializes this class.
411 @param fields: Field definitions (return value of L{_PrepareFieldList})
414 self._fields = fields
416 self._op_handler = None
418 def __call__(self, hints, qfilter):
419 """Converts a query filter into a callable function.
421 @type hints: L{_FilterHints} or None
422 @param hints: Callbacks doing analysis on filter
424 @param qfilter: Filter structure
426 @return: Function receiving context and item as parameters, returning
427 boolean as to whether item matches filter
432 (self._HandleLogicOp, getattr(hints, "NoteLogicOp", None)),
434 (self._HandleUnaryOp, getattr(hints, "NoteUnaryOp", None)),
436 (self._HandleBinaryOp, getattr(hints, "NoteBinaryOp", None)),
440 filter_fn = self._Compile(qfilter, 0)
442 self._op_handler = None
446 def _Compile(self, qfilter, level):
447 """Inner function for converting filters.
449 Calls the correct handler functions for the top-level operator. This
450 function is called recursively (e.g. for logic operators).
453 if not (isinstance(qfilter, (list, tuple)) and qfilter):
454 raise errors.ParameterError("Invalid filter on level %s" % level)
457 if level >= self._LEVELS_MAX:
458 raise errors.ParameterError("Only up to %s levels are allowed (filter"
459 " nested too deep)" % self._LEVELS_MAX)
461 # Create copy to be modified
462 operands = qfilter[:]
466 (kind, op_data) = self._OPS[op]
468 raise errors.ParameterError("Unknown operator '%s'" % op)
470 (handler, hints_cb) = self._op_handler[kind]
472 return handler(hints_cb, level, op, op_data, operands)
474 def _LookupField(self, name):
475 """Returns a field definition by name.
479 return self._fields[name]
481 raise errors.ParameterError("Unknown field '%s'" % name)
483 def _HandleLogicOp(self, hints_fn, level, op, op_fn, operands):
484 """Handles logic operators.
486 @type hints_fn: callable
487 @param hints_fn: Callback doing some analysis on the filter
489 @param level: Current depth
492 @type op_fn: callable
493 @param op_fn: Function implementing operator
495 @param operands: List of operands
501 return compat.partial(_WrapLogicOp, op_fn,
502 [self._Compile(op, level + 1) for op in operands])
504 def _HandleUnaryOp(self, hints_fn, level, op, op_fn, operands):
505 """Handles unary operators.
507 @type hints_fn: callable
508 @param hints_fn: Callback doing some analysis on the filter
510 @param level: Current depth
513 @type op_fn: callable
514 @param op_fn: Function implementing operator
516 @param operands: List of operands
524 if len(operands) != 1:
525 raise errors.ParameterError("Unary operator '%s' expects exactly one"
528 if op == qlang.OP_TRUE:
529 (_, _, _, retrieval_fn) = self._LookupField(operands[0])
531 op_fn = operator.truth
533 elif op == qlang.OP_NOT:
534 op_fn = operator.not_
535 arg = self._Compile(operands[0], level + 1)
537 raise errors.ProgrammerError("Can't handle operator '%s'" % op)
539 return compat.partial(_WrapUnaryOp, op_fn, arg)
541 def _HandleBinaryOp(self, hints_fn, level, op, op_data, operands):
542 """Handles binary operators.
544 @type hints_fn: callable
545 @param hints_fn: Callback doing some analysis on the filter
547 @param level: Current depth
550 @param op_data: Functions implementing operators
552 @param operands: List of operands
555 # Unused arguments, pylint: disable=W0613
557 (name, value) = operands
558 except (ValueError, TypeError):
559 raise errors.ParameterError("Invalid binary operator, expected exactly"
562 (fdef, datakind, field_flags, retrieval_fn) = self._LookupField(name)
564 assert fdef.kind != QFT_UNKNOWN
566 # TODO: Type conversions?
568 verify_fn = _VERIFY_FN[fdef.kind]
569 if not verify_fn(value):
570 raise errors.ParameterError("Unable to compare field '%s' (type '%s')"
571 " with '%s', expected %s" %
572 (name, fdef.kind, value.__class__.__name__,
576 hints_fn(op, datakind, name, value)
578 for (fn_flags, fn, valprepfn) in op_data:
579 if fn_flags is None or fn_flags & field_flags:
580 # Prepare value if necessary (e.g. compile regular expression)
582 value = valprepfn(value)
584 return compat.partial(_WrapBinaryOp, fn, retrieval_fn, value)
586 raise errors.ProgrammerError("Unable to find operator implementation"
587 " (op '%s', flags %s)" % (op, field_flags))
590 def _CompileFilter(fields, hints, qfilter):
591 """Converts a query filter into a callable function.
593 See L{_FilterCompilerHelper} for details.
598 return _FilterCompilerHelper(fields)(hints, qfilter)
602 def __init__(self, fieldlist, selected, qfilter=None, namefield=None):
603 """Initializes this class.
605 The field definition is a dictionary with the field's name as a key and a
606 tuple containing, in order, the field definition object
607 (L{objects.QueryFieldDefinition}, the data kind to help calling code
608 collect data and a retrieval function. The retrieval function is called
609 with two parameters, in order, the data container and the item in container
610 (see L{Query.Query}).
612 Users of this class can call L{RequestedData} before preparing the data
613 container to determine what data is needed.
615 @type fieldlist: dictionary
616 @param fieldlist: Field definitions
617 @type selected: list of strings
618 @param selected: List of selected fields
621 assert namefield is None or namefield in fieldlist
623 self._fields = _GetQueryFields(fieldlist, selected)
625 self._filter_fn = None
626 self._requested_names = None
627 self._filter_datakinds = frozenset()
629 if qfilter is not None:
630 # Collect requested names if wanted
632 hints = _FilterHints(namefield)
636 # Build filter function
637 self._filter_fn = _CompileFilter(fieldlist, hints, qfilter)
639 self._requested_names = hints.RequestedNames()
640 self._filter_datakinds = hints.ReferencedData()
642 if namefield is None:
645 (_, _, _, self._name_fn) = fieldlist[namefield]
647 def RequestedNames(self):
648 """Returns all names referenced in the filter.
650 If there is no filter or operators are preventing determining the exact
651 names, C{None} is returned.
654 return self._requested_names
656 def RequestedData(self):
657 """Gets requested kinds of data.
662 return (self._filter_datakinds |
663 frozenset(datakind for (_, datakind, _, _) in self._fields
664 if datakind is not None))
667 """Returns the list of fields for this query.
669 Includes unknown fields.
671 @rtype: List of L{objects.QueryFieldDefinition}
674 return GetAllFields(self._fields)
676 def Query(self, ctx, sort_by_name=True):
679 @param ctx: Data container passed to field retrieval functions, must
680 support iteration using C{__iter__}
681 @type sort_by_name: boolean
682 @param sort_by_name: Whether to sort by name or keep the input data's
686 sort = (self._name_fn and sort_by_name)
690 for idx, item in enumerate(ctx):
691 if not (self._filter_fn is None or self._filter_fn(ctx, item)):
694 row = [_ProcessResult(fn(ctx, item)) for (_, _, _, fn) in self._fields]
698 _VerifyResultRow(self._fields, row)
701 (status, name) = _ProcessResult(self._name_fn(ctx, item))
702 assert status == constants.RS_NORMAL
703 # TODO: Are there cases where we wouldn't want to use NiceSort?
704 result.append((utils.NiceSortKey(name), idx, row))
711 # TODO: Would "heapq" be more efficient than sorting?
713 # Sorting in-place instead of using "sorted()"
716 assert not result or (len(result[0]) == 3 and len(result[-1]) == 3)
718 return map(operator.itemgetter(2), result)
720 def OldStyleQuery(self, ctx, sort_by_name=True):
721 """Query with "old" query result format.
723 See L{Query.Query} for arguments.
726 unknown = set(fdef.name for (fdef, _, _, _) in self._fields
727 if fdef.kind == QFT_UNKNOWN)
729 raise errors.OpPrereqError("Unknown output fields selected: %s" %
730 (utils.CommaJoin(unknown), ),
733 return [[value for (_, value) in row]
734 for row in self.Query(ctx, sort_by_name=sort_by_name)]
737 def _ProcessResult(value):
738 """Converts result values into externally-visible ones.
741 if value is _FS_UNKNOWN:
742 return (RS_UNKNOWN, None)
743 elif value is _FS_NODATA:
744 return (RS_NODATA, None)
745 elif value is _FS_UNAVAIL:
746 return (RS_UNAVAIL, None)
747 elif value is _FS_OFFLINE:
748 return (RS_OFFLINE, None)
750 return (RS_NORMAL, value)
753 def _VerifyResultRow(fields, row):
754 """Verifies the contents of a query result row.
757 @param fields: Field definitions for result
758 @type row: list of tuples
762 assert len(row) == len(fields)
764 for ((status, value), (fdef, _, _, _)) in zip(row, fields):
765 if status == RS_NORMAL:
766 if not _VERIFY_FN[fdef.kind](value):
767 errs.append("normal field %s fails validation (value is %s)" %
769 elif value is not None:
770 errs.append("abnormal field %s has a non-None value" % fdef.name)
771 assert not errs, ("Failed validation: %s in row %s" %
772 (utils.CommaJoin(errs), row))
775 def _FieldDictKey((fdef, _, flags, fn)):
776 """Generates key for field dictionary.
779 assert fdef.name and fdef.title, "Name and title are required"
780 assert FIELD_NAME_RE.match(fdef.name)
781 assert TITLE_RE.match(fdef.title)
782 assert (DOC_RE.match(fdef.doc) and len(fdef.doc.splitlines()) == 1 and
783 fdef.doc.strip() == fdef.doc), \
784 "Invalid description for field '%s'" % fdef.name
786 assert (flags & ~QFF_ALL) == 0, "Unknown flags for field '%s'" % fdef.name
791 def _PrepareFieldList(fields, aliases):
792 """Prepares field list for use by L{Query}.
794 Converts the list to a dictionary and does some verification.
796 @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data
797 kind, retrieval function)
798 @param fields: List of fields, see L{Query.__init__} for a better
800 @type aliases: list of tuples; (alias, target)
801 @param aliases: list of tuples containing aliases; for each
802 alias/target pair, a duplicate will be created in the field list
804 @return: Field dictionary for L{Query}
808 duplicates = utils.FindDuplicates(fdef.title.lower()
809 for (fdef, _, _, _) in fields)
810 assert not duplicates, "Duplicate title(s) found: %r" % duplicates
812 result = utils.SequenceToDict(fields, key=_FieldDictKey)
814 for alias, target in aliases:
815 assert alias not in result, "Alias %s overrides an existing field" % alias
816 assert target in result, "Missing target %s for alias %s" % (target, alias)
817 (fdef, k, flags, fn) = result[target]
820 result[alias] = (fdef, k, flags, fn)
822 assert len(result) == len(fields) + len(aliases)
823 assert compat.all(name == fdef.name
824 for (name, (fdef, _, _, _)) in result.items())
829 def GetQueryResponse(query, ctx, sort_by_name=True):
830 """Prepares the response for a query.
832 @type query: L{Query}
833 @param ctx: Data container, see L{Query.Query}
834 @type sort_by_name: boolean
835 @param sort_by_name: Whether to sort by name or keep the input data's
839 return objects.QueryResponse(data=query.Query(ctx, sort_by_name=sort_by_name),
840 fields=query.GetFields()).ToDict()
843 def QueryFields(fielddefs, selected):
844 """Returns list of available fields.
846 @type fielddefs: dict
847 @param fielddefs: Field definitions
848 @type selected: list of strings
849 @param selected: List of selected fields
850 @return: List of L{objects.QueryFieldDefinition}
854 # Client requests all fields, sort by name
855 fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
856 key=operator.attrgetter("name"))
858 # Keep order as requested by client
859 fdefs = Query(fielddefs, selected).GetFields()
861 return objects.QueryFieldsResponse(fields=fdefs).ToDict()
864 def _MakeField(name, title, kind, doc):
865 """Wrapper for creating L{objects.QueryFieldDefinition} instances.
867 @param name: Field name as a regular expression
868 @param title: Human-readable title
869 @param kind: Field type
870 @param doc: Human-readable description
873 return objects.QueryFieldDefinition(name=name, title=title, kind=kind,
877 def _GetNodeRole(node, master_name):
878 """Determine node role.
880 @type node: L{objects.Node}
881 @param node: Node object
882 @type master_name: string
883 @param master_name: Master node name
886 if node.name == master_name:
887 return constants.NR_MASTER
888 elif node.master_candidate:
889 return constants.NR_MCANDIDATE
891 return constants.NR_DRAINED
893 return constants.NR_OFFLINE
895 return constants.NR_REGULAR
898 def _GetItemAttr(attr):
899 """Returns a field function to return an attribute of the item.
901 @param attr: Attribute name
904 getter = operator.attrgetter(attr)
905 return lambda _, item: getter(item)
908 def _GetNDParam(name):
909 """Return a field function to return an ND parameter out of the context.
913 if ctx.ndparams is None:
916 return ctx.ndparams.get(name, None)
920 def _BuildNDFields(is_group):
921 """Builds all the ndparam fields.
923 @param is_group: whether this is called at group or node level
927 field_kind = GQ_CONFIG
929 field_kind = NQ_GROUP
930 return [(_MakeField("ndp/%s" % name, NDP_TITLE.get(name, "ndp/%s" % name),
931 _VTToQFT[kind], "The \"%s\" node parameter" % name),
932 field_kind, 0, _GetNDParam(name))
933 for name, kind in constants.NDS_PARAMETER_TYPES.items()]
936 def _ConvWrapInner(convert, fn, ctx, item):
937 """Wrapper for converting values.
939 @param convert: Conversion function receiving value as single parameter
940 @param fn: Retrieval function
943 value = fn(ctx, item)
945 # Is the value an abnormal status?
946 if compat.any(value is fs for fs in _FS_ALL):
950 # TODO: Should conversion function also receive context, item or both?
951 return convert(value)
954 def _ConvWrap(convert, fn):
955 """Convenience wrapper for L{_ConvWrapInner}.
957 @param convert: Conversion function receiving value as single parameter
958 @param fn: Retrieval function
961 return compat.partial(_ConvWrapInner, convert, fn)
964 def _GetItemTimestamp(getter):
965 """Returns function for getting timestamp of item.
967 @type getter: callable
968 @param getter: Function to retrieve timestamp attribute
972 """Returns a timestamp of item.
975 timestamp = getter(item)
976 if timestamp is None:
977 # Old configs might not have all timestamps
985 def _GetItemTimestampFields(datatype):
986 """Returns common timestamp fields.
988 @param datatype: Field data type for use by L{Query.RequestedData}
992 (_MakeField("ctime", "CTime", QFT_TIMESTAMP, "Creation timestamp"),
993 datatype, 0, _GetItemTimestamp(operator.attrgetter("ctime"))),
994 (_MakeField("mtime", "MTime", QFT_TIMESTAMP, "Modification timestamp"),
995 datatype, 0, _GetItemTimestamp(operator.attrgetter("mtime"))),
1000 """Data container for node data queries.
1003 def __init__(self, nodes, live_data, master_name, node_to_primary,
1004 node_to_secondary, groups, oob_support, cluster):
1005 """Initializes this class.
1009 self.live_data = live_data
1010 self.master_name = master_name
1011 self.node_to_primary = node_to_primary
1012 self.node_to_secondary = node_to_secondary
1013 self.groups = groups
1014 self.oob_support = oob_support
1015 self.cluster = cluster
1017 # Used for individual rows
1018 self.curlive_data = None
1019 self.ndparams = None
1022 """Iterate over all nodes.
1024 This function has side-effects and only one instance of the resulting
1025 generator should be used at a time.
1028 for node in self.nodes:
1029 group = self.groups.get(node.group, None)
1031 self.ndparams = None
1033 self.ndparams = self.cluster.FillND(node, group)
1035 self.curlive_data = self.live_data.get(node.name, None)
1037 self.curlive_data = None
1041 #: Fields that are direct attributes of an L{objects.Node} object
1042 _NODE_SIMPLE_FIELDS = {
1043 "drained": ("Drained", QFT_BOOL, 0, "Whether node is drained"),
1044 "master_candidate": ("MasterC", QFT_BOOL, 0,
1045 "Whether node is a master candidate"),
1046 "master_capable": ("MasterCapable", QFT_BOOL, 0,
1047 "Whether node can become a master candidate"),
1048 "name": ("Node", QFT_TEXT, QFF_HOSTNAME, "Node name"),
1049 "offline": ("Offline", QFT_BOOL, 0, "Whether node is marked offline"),
1050 "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Node"),
1051 "uuid": ("UUID", QFT_TEXT, 0, "Node UUID"),
1052 "vm_capable": ("VMCapable", QFT_BOOL, 0, "Whether node can host instances"),
1056 #: Fields requiring talking to the node
1057 # Note that none of these are available for non-vm_capable nodes
1058 _NODE_LIVE_FIELDS = {
1059 "bootid": ("BootID", QFT_TEXT, "bootid",
1060 "Random UUID renewed for each system reboot, can be used"
1061 " for detecting reboots by tracking changes"),
1062 "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes",
1063 "Number of NUMA domains on node (if exported by hypervisor)"),
1064 "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets",
1065 "Number of physical CPU sockets (if exported by hypervisor)"),
1066 "ctotal": ("CTotal", QFT_NUMBER, "cpu_total", "Number of logical processors"),
1067 "dfree": ("DFree", QFT_UNIT, "vg_free",
1068 "Available disk space in volume group"),
1069 "dtotal": ("DTotal", QFT_UNIT, "vg_size",
1070 "Total disk space in volume group used for instance disk"
1072 "mfree": ("MFree", QFT_UNIT, "memory_free",
1073 "Memory available for instance allocations"),
1074 "mnode": ("MNode", QFT_UNIT, "memory_dom0",
1075 "Amount of memory used by node (dom0 for Xen)"),
1076 "mtotal": ("MTotal", QFT_UNIT, "memory_total",
1077 "Total amount of memory of physical machine"),
1082 """Build function for calling another function with an node group.
1084 @param cb: The callback to be called with the nodegroup
1088 """Get group data for a node.
1090 @type ctx: L{NodeQueryData}
1091 @type inst: L{objects.Node}
1092 @param inst: Node object
1095 ng = ctx.groups.get(node.group, None)
1097 # Nodes always have a group, or the configuration is corrupt
1100 return cb(ctx, node, ng)
1105 def _GetNodeGroup(ctx, node, ng): # pylint: disable=W0613
1106 """Returns the name of a node's group.
1108 @type ctx: L{NodeQueryData}
1109 @type node: L{objects.Node}
1110 @param node: Node object
1111 @type ng: L{objects.NodeGroup}
1112 @param ng: The node group this node belongs to
1118 def _GetNodePower(ctx, node):
1119 """Returns the node powered state
1121 @type ctx: L{NodeQueryData}
1122 @type node: L{objects.Node}
1123 @param node: Node object
1126 if ctx.oob_support[node.name]:
1132 def _GetNdParams(ctx, node, ng):
1133 """Returns the ndparams for this node.
1135 @type ctx: L{NodeQueryData}
1136 @type node: L{objects.Node}
1137 @param node: Node object
1138 @type ng: L{objects.NodeGroup}
1139 @param ng: The node group this node belongs to
1142 return ctx.cluster.SimpleFillND(ng.FillND(node))
1145 def _GetLiveNodeField(field, kind, ctx, node):
1146 """Gets the value of a "live" field from L{NodeQueryData}.
1148 @param field: Live field name
1149 @param kind: Data kind, one of L{constants.QFT_ALL}
1150 @type ctx: L{NodeQueryData}
1151 @type node: L{objects.Node}
1152 @param node: Node object
1158 if not node.vm_capable:
1161 if not ctx.curlive_data:
1165 value = ctx.curlive_data[field]
1169 if kind == QFT_TEXT:
1172 assert kind in (QFT_NUMBER, QFT_UNIT)
1174 # Try to convert into number
1177 except (ValueError, TypeError):
1178 logging.exception("Failed to convert node field '%s' (value %r) to int",
1183 def _GetNodeHvState(_, node):
1184 """Converts node's hypervisor state for query result.
1187 hv_state = node.hv_state
1189 if hv_state is None:
1192 return dict((name, value.ToDict()) for (name, value) in hv_state.items())
1195 def _GetNodeDiskState(_, node):
1196 """Converts node's disk state for query result.
1199 disk_state = node.disk_state
1201 if disk_state is None:
1204 return dict((disk_kind, dict((name, value.ToDict())
1205 for (name, value) in kind_state.items()))
1206 for (disk_kind, kind_state) in disk_state.items())
1209 def _BuildNodeFields():
1210 """Builds list of fields for node queries.
1214 (_MakeField("pip", "PrimaryIP", QFT_TEXT, "Primary IP address"),
1215 NQ_CONFIG, 0, _GetItemAttr("primary_ip")),
1216 (_MakeField("sip", "SecondaryIP", QFT_TEXT, "Secondary IP address"),
1217 NQ_CONFIG, 0, _GetItemAttr("secondary_ip")),
1218 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0,
1219 lambda ctx, node: list(node.GetTags())),
1220 (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"),
1221 NQ_CONFIG, 0, lambda ctx, node: node.name == ctx.master_name),
1222 (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0,
1223 _GetGroup(_GetNodeGroup)),
1224 (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"),
1225 NQ_CONFIG, 0, _GetItemAttr("group")),
1226 (_MakeField("powered", "Powered", QFT_BOOL,
1227 "Whether node is thought to be powered on"),
1228 NQ_OOB, 0, _GetNodePower),
1229 (_MakeField("ndparams", "NodeParameters", QFT_OTHER,
1230 "Merged node parameters"),
1231 NQ_GROUP, 0, _GetGroup(_GetNdParams)),
1232 (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER,
1233 "Custom node parameters"),
1234 NQ_GROUP, 0, _GetItemAttr("ndparams")),
1235 (_MakeField("hv_state", "HypervisorState", QFT_OTHER, "Hypervisor state"),
1236 NQ_CONFIG, 0, _GetNodeHvState),
1237 (_MakeField("disk_state", "DiskState", QFT_OTHER, "Disk state"),
1238 NQ_CONFIG, 0, _GetNodeDiskState),
1241 fields.extend(_BuildNDFields(False))
1244 role_values = (constants.NR_MASTER, constants.NR_MCANDIDATE,
1245 constants.NR_REGULAR, constants.NR_DRAINED,
1246 constants.NR_OFFLINE)
1247 role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate,"
1248 " \"%s\" for regular, \"%s\" for a drained, \"%s\" for offline" %
1250 fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
1251 lambda ctx, node: _GetNodeRole(node, ctx.master_name)))
1252 assert set(role_values) == constants.NR_ALL
1254 def _GetLength(getter):
1255 return lambda ctx, node: len(getter(ctx)[node.name])
1257 def _GetList(getter):
1258 return lambda ctx, node: list(getter(ctx)[node.name])
1260 # Add fields operating on instance lists
1261 for prefix, titleprefix, docword, getter in \
1262 [("p", "Pri", "primary", operator.attrgetter("node_to_primary")),
1263 ("s", "Sec", "secondary", operator.attrgetter("node_to_secondary"))]:
1264 # TODO: Allow filterting by hostname in list
1266 (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER,
1267 "Number of instances with this node as %s" % docword),
1268 NQ_INST, 0, _GetLength(getter)),
1269 (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
1271 "List of instances with this node as %s" % docword),
1272 NQ_INST, 0, _GetList(getter)),
1277 (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1278 for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()
1281 # Add fields requiring live data
1283 (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1284 compat.partial(_GetLiveNodeField, nfield, kind))
1285 for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()
1289 fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1291 return _PrepareFieldList(fields, [])
1294 class InstanceQueryData:
1295 """Data container for instance data queries.
1298 def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
1299 live_data, wrongnode_inst, console, nodes, groups):
1300 """Initializes this class.
1302 @param instances: List of instance objects
1303 @param cluster: Cluster object
1304 @type disk_usage: dict; instance name as key
1305 @param disk_usage: Per-instance disk usage
1306 @type offline_nodes: list of strings
1307 @param offline_nodes: List of offline nodes
1308 @type bad_nodes: list of strings
1309 @param bad_nodes: List of faulty nodes
1310 @type live_data: dict; instance name as key
1311 @param live_data: Per-instance live data
1312 @type wrongnode_inst: set
1313 @param wrongnode_inst: Set of instances running on wrong node(s)
1314 @type console: dict; instance name as key
1315 @param console: Per-instance console information
1316 @type nodes: dict; node name as key
1317 @param nodes: Node objects
1320 assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
1321 "Offline nodes not included in bad nodes"
1322 assert not (set(live_data.keys()) & set(bad_nodes)), \
1323 "Found live data for bad or offline nodes"
1325 self.instances = instances
1326 self.cluster = cluster
1327 self.disk_usage = disk_usage
1328 self.offline_nodes = offline_nodes
1329 self.bad_nodes = bad_nodes
1330 self.live_data = live_data
1331 self.wrongnode_inst = wrongnode_inst
1332 self.console = console
1334 self.groups = groups
1336 # Used for individual rows
1337 self.inst_hvparams = None
1338 self.inst_beparams = None
1339 self.inst_osparams = None
1340 self.inst_nicparams = None
1343 """Iterate over all instances.
1345 This function has side-effects and only one instance of the resulting
1346 generator should be used at a time.
1349 for inst in self.instances:
1350 self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1351 self.inst_beparams = self.cluster.FillBE(inst)
1352 self.inst_osparams = self.cluster.SimpleFillOS(inst.os, inst.osparams)
1353 self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1354 for nic in inst.nics]
1359 def _GetInstOperState(ctx, inst):
1360 """Get instance's operational status.
1362 @type ctx: L{InstanceQueryData}
1363 @type inst: L{objects.Instance}
1364 @param inst: Instance object
1367 # Can't use RS_OFFLINE here as it would describe the instance to
1368 # be offline when we actually don't know due to missing data
1369 if inst.primary_node in ctx.bad_nodes:
1372 return bool(ctx.live_data.get(inst.name))
1375 def _GetInstLiveData(name):
1376 """Build function for retrieving live data.
1379 @param name: Live data field name
1383 """Get live data for an instance.
1385 @type ctx: L{InstanceQueryData}
1386 @type inst: L{objects.Instance}
1387 @param inst: Instance object
1390 if (inst.primary_node in ctx.bad_nodes or
1391 inst.primary_node in ctx.offline_nodes):
1392 # Can't use RS_OFFLINE here as it would describe the instance to be
1393 # offline when we actually don't know due to missing data
1396 if inst.name in ctx.live_data:
1397 data = ctx.live_data[inst.name]
1406 def _GetInstStatus(ctx, inst):
1407 """Get instance status.
1409 @type ctx: L{InstanceQueryData}
1410 @type inst: L{objects.Instance}
1411 @param inst: Instance object
1414 if inst.primary_node in ctx.offline_nodes:
1415 return constants.INSTST_NODEOFFLINE
1417 if inst.primary_node in ctx.bad_nodes:
1418 return constants.INSTST_NODEDOWN
1420 if bool(ctx.live_data.get(inst.name)):
1421 if inst.name in ctx.wrongnode_inst:
1422 return constants.INSTST_WRONGNODE
1423 elif inst.admin_state == constants.ADMINST_UP:
1424 return constants.INSTST_RUNNING
1426 return constants.INSTST_ERRORUP
1428 if inst.admin_state == constants.ADMINST_UP:
1429 return constants.INSTST_ERRORDOWN
1430 elif inst.admin_state == constants.ADMINST_DOWN:
1431 return constants.INSTST_ADMINDOWN
1433 return constants.INSTST_ADMINOFFLINE
1436 def _GetInstDiskSize(index):
1437 """Build function for retrieving disk size.
1440 @param index: Disk index
1444 """Get size of a disk.
1446 @type inst: L{objects.Instance}
1447 @param inst: Instance object
1451 return inst.disks[index].size
1458 def _GetInstNic(index, cb):
1459 """Build function for calling another function with an instance NIC.
1462 @param index: NIC index
1468 """Call helper function with instance NIC.
1470 @type ctx: L{InstanceQueryData}
1471 @type inst: L{objects.Instance}
1472 @param inst: Instance object
1476 nic = inst.nics[index]
1480 return cb(ctx, index, nic)
1485 def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613
1486 """Get a NIC's IP address.
1488 @type ctx: L{InstanceQueryData}
1489 @type nic: L{objects.NIC}
1490 @param nic: NIC object
1499 def _GetInstNicBridge(ctx, index, _):
1500 """Get a NIC's bridge.
1502 @type ctx: L{InstanceQueryData}
1504 @param index: NIC index
1507 assert len(ctx.inst_nicparams) >= index
1509 nicparams = ctx.inst_nicparams[index]
1511 if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1512 return nicparams[constants.NIC_LINK]
1517 def _GetInstAllNicBridges(ctx, inst):
1518 """Get all network bridges for an instance.
1520 @type ctx: L{InstanceQueryData}
1521 @type inst: L{objects.Instance}
1522 @param inst: Instance object
1525 assert len(ctx.inst_nicparams) == len(inst.nics)
1529 for nicp in ctx.inst_nicparams:
1530 if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1531 result.append(nicp[constants.NIC_LINK])
1535 assert len(result) == len(inst.nics)
1540 def _GetInstNicParam(name):
1541 """Build function for retrieving a NIC parameter.
1544 @param name: Parameter name
1547 def fn(ctx, index, _):
1548 """Get a NIC's bridge.
1550 @type ctx: L{InstanceQueryData}
1551 @type inst: L{objects.Instance}
1552 @param inst: Instance object
1553 @type nic: L{objects.NIC}
1554 @param nic: NIC object
1557 assert len(ctx.inst_nicparams) >= index
1558 return ctx.inst_nicparams[index][name]
1563 def _GetInstanceNetworkFields():
1564 """Get instance fields involving network interfaces.
1566 @return: Tuple containing list of field definitions used as input for
1567 L{_PrepareFieldList} and a list of aliases
1570 nic_mac_fn = lambda ctx, _, nic: nic.mac
1571 nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1572 nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1576 (_MakeField("nic.count", "NICs", QFT_NUMBER,
1577 "Number of network interfaces"),
1578 IQ_CONFIG, 0, lambda ctx, inst: len(inst.nics)),
1579 (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER,
1580 "List containing each network interface's MAC address"),
1581 IQ_CONFIG, 0, lambda ctx, inst: [nic.mac for nic in inst.nics]),
1582 (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
1583 "List containing each network interface's IP address"),
1584 IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
1585 (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
1586 "List containing each network interface's mode"), IQ_CONFIG, 0,
1587 lambda ctx, inst: [nicp[constants.NIC_MODE]
1588 for nicp in ctx.inst_nicparams]),
1589 (_MakeField("nic.links", "NIC_links", QFT_OTHER,
1590 "List containing each network interface's link"), IQ_CONFIG, 0,
1591 lambda ctx, inst: [nicp[constants.NIC_LINK]
1592 for nicp in ctx.inst_nicparams]),
1593 (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
1594 "List containing each network interface's bridge"),
1595 IQ_CONFIG, 0, _GetInstAllNicBridges),
1599 for i in range(constants.MAX_NICS):
1600 numtext = utils.FormatOrdinal(i + 1)
1602 (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT,
1603 "IP address of %s network interface" % numtext),
1604 IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)),
1605 (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
1606 "MAC address of %s network interface" % numtext),
1607 IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
1608 (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
1609 "Mode of %s network interface" % numtext),
1610 IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
1611 (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT,
1612 "Link of %s network interface" % numtext),
1613 IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)),
1614 (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
1615 "Bridge of %s network interface" % numtext),
1616 IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
1620 # Legacy fields for first NIC
1622 ("mac", "nic.mac/0"),
1623 ("bridge", "nic.bridge/0"),
1624 ("nic_mode", "nic.mode/0"),
1625 ("nic_link", "nic.link/0"),
1628 return (fields, aliases)
1631 def _GetInstDiskUsage(ctx, inst):
1632 """Get disk usage for an instance.
1634 @type ctx: L{InstanceQueryData}
1635 @type inst: L{objects.Instance}
1636 @param inst: Instance object
1639 usage = ctx.disk_usage[inst.name]
1647 def _GetInstanceConsole(ctx, inst):
1648 """Get console information for instance.
1650 @type ctx: L{InstanceQueryData}
1651 @type inst: L{objects.Instance}
1652 @param inst: Instance object
1655 consinfo = ctx.console[inst.name]
1657 if consinfo is None:
1663 def _GetInstanceDiskFields():
1664 """Get instance fields involving disks.
1666 @return: List of field definitions used as input for L{_PrepareFieldList}
1670 (_MakeField("disk_usage", "DiskUsage", QFT_UNIT,
1671 "Total disk space used by instance on each of its nodes;"
1672 " this is not the disk size visible to the instance, but"
1673 " the usage on the node"),
1674 IQ_DISKUSAGE, 0, _GetInstDiskUsage),
1675 (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"),
1676 IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
1677 (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
1678 IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
1683 (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1684 "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
1685 IQ_CONFIG, 0, _GetInstDiskSize(i))
1686 for i in range(constants.MAX_DISKS)
1692 def _GetInstanceParameterFields():
1693 """Get instance fields involving parameters.
1695 @return: List of field definitions used as input for L{_PrepareFieldList}
1698 # TODO: Consider moving titles closer to constants
1700 constants.BE_AUTO_BALANCE: "Auto_balance",
1701 constants.BE_MAXMEM: "ConfigMaxMem",
1702 constants.BE_MINMEM: "ConfigMinMem",
1703 constants.BE_VCPUS: "ConfigVCPUs",
1707 constants.HV_ACPI: "ACPI",
1708 constants.HV_BOOT_ORDER: "Boot_order",
1709 constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1710 constants.HV_DISK_TYPE: "Disk_type",
1711 constants.HV_INITRD_PATH: "Initrd_path",
1712 constants.HV_KERNEL_PATH: "Kernel_path",
1713 constants.HV_NIC_TYPE: "NIC_type",
1714 constants.HV_PAE: "PAE",
1715 constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1720 (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
1721 "Hypervisor parameters (merged)"),
1722 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
1723 (_MakeField("beparams", "BackendParameters", QFT_OTHER,
1724 "Backend parameters (merged)"),
1725 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
1726 (_MakeField("osparams", "OpSysParameters", QFT_OTHER,
1727 "Operating system parameters (merged)"),
1728 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_osparams),
1730 # Unfilled parameters
1731 (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
1732 "Custom hypervisor parameters"),
1733 IQ_CONFIG, 0, _GetItemAttr("hvparams")),
1734 (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
1735 "Custom backend parameters",),
1736 IQ_CONFIG, 0, _GetItemAttr("beparams")),
1737 (_MakeField("custom_osparams", "CustomOpSysParameters", QFT_OTHER,
1738 "Custom operating system parameters",),
1739 IQ_CONFIG, 0, _GetItemAttr("osparams")),
1740 (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
1741 "Custom network interface parameters"),
1742 IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1746 def _GetInstHvParam(name):
1747 return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1750 (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1751 _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1752 IQ_CONFIG, 0, _GetInstHvParam(name))
1753 for name, kind in constants.HVS_PARAMETER_TYPES.items()
1754 if name not in constants.HVC_GLOBALS
1758 def _GetInstBeParam(name):
1759 return lambda ctx, _: ctx.inst_beparams.get(name, None)
1762 (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1763 _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1764 IQ_CONFIG, 0, _GetInstBeParam(name))
1765 for name, kind in constants.BES_PARAMETER_TYPES.items()
1771 _INST_SIMPLE_FIELDS = {
1772 "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"),
1773 "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"),
1774 "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"),
1775 # Depending on the hypervisor, the port can be None
1776 "network_port": ("Network_port", QFT_OTHER, 0,
1777 "Instance network port if available (e.g. for VNC console)"),
1778 "os": ("OS", QFT_TEXT, 0, "Operating system"),
1779 "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"),
1780 "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"),
1784 def _GetInstNodeGroup(ctx, default, node_name):
1785 """Gets group UUID of an instance node.
1787 @type ctx: L{InstanceQueryData}
1788 @param default: Default value
1789 @type node_name: string
1790 @param node_name: Node name
1794 node = ctx.nodes[node_name]
1801 def _GetInstNodeGroupName(ctx, default, node_name):
1802 """Gets group name of an instance node.
1804 @type ctx: L{InstanceQueryData}
1805 @param default: Default value
1806 @type node_name: string
1807 @param node_name: Node name
1811 node = ctx.nodes[node_name]
1816 group = ctx.groups[node.group]
1823 def _BuildInstanceFields():
1824 """Builds list of fields for instance queries.
1828 (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
1829 IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")),
1830 (_MakeField("pnode.group", "PrimaryNodeGroup", QFT_TEXT,
1831 "Primary node's group"),
1833 lambda ctx, inst: _GetInstNodeGroupName(ctx, _FS_UNAVAIL,
1834 inst.primary_node)),
1835 (_MakeField("pnode.group.uuid", "PrimaryNodeGroupUUID", QFT_TEXT,
1836 "Primary node's group UUID"),
1838 lambda ctx, inst: _GetInstNodeGroup(ctx, _FS_UNAVAIL, inst.primary_node)),
1839 # TODO: Allow filtering by secondary node as hostname
1840 (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
1841 "Secondary nodes; usually this will just be one node"),
1842 IQ_CONFIG, 0, lambda ctx, inst: list(inst.secondary_nodes)),
1843 (_MakeField("snodes.group", "SecondaryNodesGroups", QFT_OTHER,
1844 "Node groups of secondary nodes"),
1846 lambda ctx, inst: map(compat.partial(_GetInstNodeGroupName, ctx, None),
1847 inst.secondary_nodes)),
1848 (_MakeField("snodes.group.uuid", "SecondaryNodesGroupsUUID", QFT_OTHER,
1849 "Node group UUIDs of secondary nodes"),
1851 lambda ctx, inst: map(compat.partial(_GetInstNodeGroup, ctx, None),
1852 inst.secondary_nodes)),
1853 (_MakeField("admin_state", "InstanceState", QFT_TEXT,
1854 "Desired state of instance"),
1855 IQ_CONFIG, 0, _GetItemAttr("admin_state")),
1856 (_MakeField("admin_up", "Autostart", QFT_BOOL,
1857 "Desired state of instance"),
1858 IQ_CONFIG, 0, lambda ctx, inst: inst.admin_state == constants.ADMINST_UP),
1859 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
1860 lambda ctx, inst: list(inst.GetTags())),
1861 (_MakeField("console", "Console", QFT_OTHER,
1862 "Instance console information"), IQ_CONSOLE, 0,
1863 _GetInstanceConsole),
1868 (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1869 for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()
1872 # Fields requiring talking to the node
1874 (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
1875 IQ_LIVE, 0, _GetInstOperState),
1876 (_MakeField("oper_ram", "Memory", QFT_UNIT,
1877 "Actual memory usage as seen by hypervisor"),
1878 IQ_LIVE, 0, _GetInstLiveData("memory")),
1879 (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
1880 "Actual number of VCPUs as seen by hypervisor"),
1881 IQ_LIVE, 0, _GetInstLiveData("vcpus")),
1885 status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
1886 constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
1887 constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
1888 constants.INSTST_NODEOFFLINE, constants.INSTST_ADMINOFFLINE)
1889 status_doc = ("Instance status; \"%s\" if instance is set to be running"
1890 " and actually is, \"%s\" if instance is stopped and"
1891 " is not running, \"%s\" if instance running, but not on its"
1892 " designated primary node, \"%s\" if instance should be"
1893 " stopped, but is actually running, \"%s\" if instance should"
1894 " run, but doesn't, \"%s\" if instance's primary node is down,"
1895 " \"%s\" if instance's primary node is marked offline,"
1896 " \"%s\" if instance is offline and does not use dynamic"
1897 " resources" % status_values)
1898 fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
1899 IQ_LIVE, 0, _GetInstStatus))
1900 assert set(status_values) == constants.INSTST_ALL, \
1901 "Status documentation mismatch"
1903 (network_fields, network_aliases) = _GetInstanceNetworkFields()
1905 fields.extend(network_fields)
1906 fields.extend(_GetInstanceParameterFields())
1907 fields.extend(_GetInstanceDiskFields())
1908 fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1911 ("vcpus", "be/vcpus"),
1912 ("be/memory", "be/maxmem"),
1913 ("sda_size", "disk.size/0"),
1914 ("sdb_size", "disk.size/1"),
1917 return _PrepareFieldList(fields, aliases)
1920 class LockQueryData:
1921 """Data container for lock data queries.
1924 def __init__(self, lockdata):
1925 """Initializes this class.
1928 self.lockdata = lockdata
1931 """Iterate over all locks.
1934 return iter(self.lockdata)
1937 def _GetLockOwners(_, data):
1938 """Returns a sorted list of a lock's current owners.
1941 (_, _, owners, _) = data
1944 owners = utils.NiceSort(owners)
1949 def _GetLockPending(_, data):
1950 """Returns a sorted list of a lock's pending acquires.
1953 (_, _, _, pending) = data
1956 pending = [(mode, utils.NiceSort(names))
1957 for (mode, names) in pending]
1962 def _BuildLockFields():
1963 """Builds list of fields for lock queries.
1966 return _PrepareFieldList([
1967 # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
1968 (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
1969 lambda ctx, (name, mode, owners, pending): name),
1970 (_MakeField("mode", "Mode", QFT_OTHER,
1971 "Mode in which the lock is currently acquired"
1972 " (exclusive or shared)"),
1973 LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
1974 (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
1975 LQ_OWNER, 0, _GetLockOwners),
1976 (_MakeField("pending", "Pending", QFT_OTHER,
1977 "Threads waiting for the lock"),
1978 LQ_PENDING, 0, _GetLockPending),
1982 class GroupQueryData:
1983 """Data container for node group data queries.
1986 def __init__(self, cluster, groups, group_to_nodes, group_to_instances):
1987 """Initializes this class.
1989 @param cluster: Cluster object
1990 @param groups: List of node group objects
1991 @type group_to_nodes: dict; group UUID as key
1992 @param group_to_nodes: Per-group list of nodes
1993 @type group_to_instances: dict; group UUID as key
1994 @param group_to_instances: Per-group list of (primary) instances
1997 self.groups = groups
1998 self.group_to_nodes = group_to_nodes
1999 self.group_to_instances = group_to_instances
2000 self.cluster = cluster
2002 # Used for individual rows
2003 self.group_ipolicy = None
2004 self.ndparams = None
2007 """Iterate over all node groups.
2009 This function has side-effects and only one instance of the resulting
2010 generator should be used at a time.
2013 for group in self.groups:
2014 self.group_ipolicy = self.cluster.SimpleFillIPolicy(group.ipolicy)
2015 self.ndparams = self.cluster.SimpleFillND(group.ndparams)
2019 _GROUP_SIMPLE_FIELDS = {
2020 "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
2021 "name": ("Group", QFT_TEXT, "Group name"),
2022 "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
2023 "uuid": ("UUID", QFT_TEXT, "Group UUID"),
2027 def _BuildGroupFields():
2028 """Builds list of fields for node group queries.
2032 fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
2034 for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
2036 def _GetLength(getter):
2037 return lambda ctx, group: len(getter(ctx)[group.uuid])
2039 def _GetSortedList(getter):
2040 return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
2042 group_to_nodes = operator.attrgetter("group_to_nodes")
2043 group_to_instances = operator.attrgetter("group_to_instances")
2045 # Add fields for nodes
2047 (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
2048 GQ_NODE, 0, _GetLength(group_to_nodes)),
2049 (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
2050 GQ_NODE, 0, _GetSortedList(group_to_nodes)),
2053 # Add fields for instances
2055 (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
2056 "Number of primary instances"),
2057 GQ_INST, 0, _GetLength(group_to_instances)),
2058 (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
2059 "List of primary instances"),
2060 GQ_INST, 0, _GetSortedList(group_to_instances)),
2065 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), GQ_CONFIG, 0,
2066 lambda ctx, group: list(group.GetTags())),
2067 (_MakeField("ipolicy", "InstancePolicy", QFT_OTHER,
2068 "Instance policy limitations (merged)"),
2069 GQ_CONFIG, 0, lambda ctx, _: ctx.group_ipolicy),
2070 (_MakeField("custom_ipolicy", "CustomInstancePolicy", QFT_OTHER,
2071 "Custom instance policy limitations"),
2072 GQ_CONFIG, 0, _GetItemAttr("ipolicy")),
2073 (_MakeField("custom_ndparams", "CustomNDParams", QFT_OTHER,
2074 "Custom node parameters"),
2075 GQ_CONFIG, 0, _GetItemAttr("ndparams")),
2076 (_MakeField("ndparams", "NDParams", QFT_OTHER,
2078 GQ_CONFIG, 0, lambda ctx, _: ctx.ndparams),
2082 fields.extend(_BuildNDFields(True))
2084 fields.extend(_GetItemTimestampFields(GQ_CONFIG))
2086 return _PrepareFieldList(fields, [])
2089 class OsInfo(objects.ConfigObject):
2102 def _BuildOsFields():
2103 """Builds list of fields for operating system queries.
2107 (_MakeField("name", "Name", QFT_TEXT, "Operating system name"),
2108 None, 0, _GetItemAttr("name")),
2109 (_MakeField("valid", "Valid", QFT_BOOL,
2110 "Whether operating system definition is valid"),
2111 None, 0, _GetItemAttr("valid")),
2112 (_MakeField("hidden", "Hidden", QFT_BOOL,
2113 "Whether operating system is hidden"),
2114 None, 0, _GetItemAttr("hidden")),
2115 (_MakeField("blacklisted", "Blacklisted", QFT_BOOL,
2116 "Whether operating system is blacklisted"),
2117 None, 0, _GetItemAttr("blacklisted")),
2118 (_MakeField("variants", "Variants", QFT_OTHER,
2119 "Operating system variants"),
2120 None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("variants"))),
2121 (_MakeField("api_versions", "ApiVersions", QFT_OTHER,
2122 "Operating system API versions"),
2123 None, 0, _ConvWrap(sorted, _GetItemAttr("api_versions"))),
2124 (_MakeField("parameters", "Parameters", QFT_OTHER,
2125 "Operating system parameters"),
2126 None, 0, _ConvWrap(compat.partial(utils.NiceSort, key=compat.fst),
2127 _GetItemAttr("parameters"))),
2128 (_MakeField("node_status", "NodeStatus", QFT_OTHER,
2129 "Status from node"),
2130 None, 0, _GetItemAttr("node_status")),
2133 return _PrepareFieldList(fields, [])
2136 #: Fields available for node queries
2137 NODE_FIELDS = _BuildNodeFields()
2139 #: Fields available for instance queries
2140 INSTANCE_FIELDS = _BuildInstanceFields()
2142 #: Fields available for lock queries
2143 LOCK_FIELDS = _BuildLockFields()
2145 #: Fields available for node group queries
2146 GROUP_FIELDS = _BuildGroupFields()
2148 #: Fields available for operating system queries
2149 OS_FIELDS = _BuildOsFields()
2151 #: All available resources
2153 constants.QR_INSTANCE: INSTANCE_FIELDS,
2154 constants.QR_NODE: NODE_FIELDS,
2155 constants.QR_LOCK: LOCK_FIELDS,
2156 constants.QR_GROUP: GROUP_FIELDS,
2157 constants.QR_OS: OS_FIELDS,
2160 #: All available field lists
2161 ALL_FIELD_LISTS = ALL_FIELDS.values()