4 # Copyright (C) 2010, 2011 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"
140 def _GetUnknownField(ctx, item): # pylint: disable=W0613
141 """Gets the contents of an unknown field.
147 def _GetQueryFields(fielddefs, selected):
148 """Calculates the internal list of selected fields.
150 Unknown fields are returned as L{constants.QFT_UNKNOWN}.
152 @type fielddefs: dict
153 @param fielddefs: Field definitions
154 @type selected: list of strings
155 @param selected: List of selected fields
160 for name in selected:
162 fdef = fielddefs[name]
164 fdef = (_MakeField(name, name, QFT_UNKNOWN, "Unknown field '%s'" % name),
165 None, 0, _GetUnknownField)
167 assert len(fdef) == 4
174 def GetAllFields(fielddefs):
175 """Extract L{objects.QueryFieldDefinition} from field definitions.
177 @rtype: list of L{objects.QueryFieldDefinition}
180 return [fdef for (fdef, _, _, _) in fielddefs]
184 """Class for filter analytics.
186 When filters are used, the user of the L{Query} class usually doesn't know
187 exactly which items will be necessary for building the result. It therefore
188 has to prepare and compute the input data for potentially returning
191 There are two ways to optimize this. The first, and simpler, is to assign
192 each field a group of data, so that the caller can determine which
193 computations are necessary depending on the data groups requested. The list
194 of referenced groups must also be computed for fields referenced in the
197 The second is restricting the items based on a primary key. The primary key
198 is usually a unique name (e.g. a node name). This class extracts all
199 referenced names from a filter. If it encounters any filter condition which
200 disallows such a list to be determined (e.g. a non-equality filter), all
201 names will be requested.
203 The end-effect is that any operation other than L{qlang.OP_OR} and
204 L{qlang.OP_EQUAL} will make the query more expensive.
207 def __init__(self, namefield):
208 """Initializes this class.
210 @type namefield: string
211 @param namefield: Field caller is interested in
214 self._namefield = namefield
216 #: Whether all names need to be requested (e.g. if a non-equality operator
218 self._allnames = False
220 #: Which names to request
223 #: Data kinds referenced by the filter (used by L{Query.RequestedData})
224 self._datakinds = set()
226 def RequestedNames(self):
227 """Returns all requested values.
229 Returns C{None} if list of values can't be determined (e.g. encountered
230 non-equality operators).
235 if self._allnames or self._names is None:
238 return utils.UniqueSequence(self._names)
240 def ReferencedData(self):
241 """Returns all kinds of data referenced by the filter.
244 return frozenset(self._datakinds)
246 def _NeedAllNames(self):
247 """Changes internal state to request all names.
250 self._allnames = True
253 def NoteLogicOp(self, op):
254 """Called when handling a logic operation.
260 if op != qlang.OP_OR:
263 def NoteUnaryOp(self, op): # pylint: disable=W0613
264 """Called when handling an unary operation.
272 def NoteBinaryOp(self, op, datakind, name, value):
273 """Called when handling a binary operation.
278 @param name: Left-hand side of operator (field name)
279 @param value: Right-hand side of operator
282 if datakind is not None:
283 self._datakinds.add(datakind)
288 # If any operator other than equality was used, all names need to be
290 if op == qlang.OP_EQUAL and name == self._namefield:
291 if self._names is None:
293 self._names.append(value)
298 def _WrapLogicOp(op_fn, sentences, ctx, item):
299 """Wrapper for logic operator functions.
302 return op_fn(fn(ctx, item) for fn in sentences)
305 def _WrapUnaryOp(op_fn, inner, ctx, item):
306 """Wrapper for unary operator functions.
309 return op_fn(inner(ctx, item))
312 def _WrapBinaryOp(op_fn, retrieval_fn, value, ctx, item):
313 """Wrapper for binary operator functions.
316 return op_fn(retrieval_fn(ctx, item), value)
319 def _WrapNot(fn, lhs, rhs):
320 """Negates the result of a wrapped function.
323 return not fn(lhs, rhs)
326 def _PrepareRegex(pattern):
327 """Compiles a regular expression.
331 return re.compile(pattern)
332 except re.error, err:
333 raise errors.ParameterError("Invalid regex pattern (%s)" % err)
336 class _FilterCompilerHelper:
337 """Converts a query filter to a callable usable for filtering.
340 # String statement has no effect, pylint: disable=W0105
342 #: How deep filters can be nested
345 # Unique identifiers for operator groups
348 _OPTYPE_BINARY) = range(1, 4)
350 """Functions for equality checks depending on field flags.
352 List of tuples containing flags and a callable receiving the left- and
353 right-hand side of the operator. The flags are an OR-ed value of C{QFF_*}
354 (e.g. L{QFF_HOSTNAME}).
356 Order matters. The first item with flags will be used. Flags are checked
362 lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
363 case_sensitive=False),
365 (None, operator.eq, None),
370 Operator as key (C{qlang.OP_*}), value a tuple of operator group
371 (C{_OPTYPE_*}) and a group-specific value:
373 - C{_OPTYPE_LOGIC}: Callable taking any number of arguments; used by
375 - C{_OPTYPE_UNARY}: Always C{None}; details handled by L{_HandleUnaryOp}
376 - C{_OPTYPE_BINARY}: Callable taking exactly two parameters, the left- and
377 right-hand side of the operator, used by L{_HandleBinaryOp}
382 qlang.OP_OR: (_OPTYPE_LOGIC, compat.any),
383 qlang.OP_AND: (_OPTYPE_LOGIC, compat.all),
386 qlang.OP_NOT: (_OPTYPE_UNARY, None),
387 qlang.OP_TRUE: (_OPTYPE_UNARY, None),
390 qlang.OP_EQUAL: (_OPTYPE_BINARY, _EQUALITY_CHECKS),
392 (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn), valprepfn)
393 for (flags, fn, valprepfn) in _EQUALITY_CHECKS]),
394 qlang.OP_REGEXP: (_OPTYPE_BINARY, [
395 (None, lambda lhs, rhs: rhs.search(lhs), _PrepareRegex),
397 qlang.OP_CONTAINS: (_OPTYPE_BINARY, [
398 (None, operator.contains, None),
402 def __init__(self, fields):
403 """Initializes this class.
405 @param fields: Field definitions (return value of L{_PrepareFieldList})
408 self._fields = fields
410 self._op_handler = None
412 def __call__(self, hints, qfilter):
413 """Converts a query filter into a callable function.
415 @type hints: L{_FilterHints} or None
416 @param hints: Callbacks doing analysis on filter
418 @param qfilter: Filter structure
420 @return: Function receiving context and item as parameters, returning
421 boolean as to whether item matches filter
426 (self._HandleLogicOp, getattr(hints, "NoteLogicOp", None)),
428 (self._HandleUnaryOp, getattr(hints, "NoteUnaryOp", None)),
430 (self._HandleBinaryOp, getattr(hints, "NoteBinaryOp", None)),
434 filter_fn = self._Compile(qfilter, 0)
436 self._op_handler = None
440 def _Compile(self, qfilter, level):
441 """Inner function for converting filters.
443 Calls the correct handler functions for the top-level operator. This
444 function is called recursively (e.g. for logic operators).
447 if not (isinstance(qfilter, (list, tuple)) and qfilter):
448 raise errors.ParameterError("Invalid filter on level %s" % level)
451 if level >= self._LEVELS_MAX:
452 raise errors.ParameterError("Only up to %s levels are allowed (filter"
453 " nested too deep)" % self._LEVELS_MAX)
455 # Create copy to be modified
456 operands = qfilter[:]
460 (kind, op_data) = self._OPS[op]
462 raise errors.ParameterError("Unknown operator '%s'" % op)
464 (handler, hints_cb) = self._op_handler[kind]
466 return handler(hints_cb, level, op, op_data, operands)
468 def _LookupField(self, name):
469 """Returns a field definition by name.
473 return self._fields[name]
475 raise errors.ParameterError("Unknown field '%s'" % name)
477 def _HandleLogicOp(self, hints_fn, level, op, op_fn, operands):
478 """Handles logic operators.
480 @type hints_fn: callable
481 @param hints_fn: Callback doing some analysis on the filter
483 @param level: Current depth
486 @type op_fn: callable
487 @param op_fn: Function implementing operator
489 @param operands: List of operands
495 return compat.partial(_WrapLogicOp, op_fn,
496 [self._Compile(op, level + 1) for op in operands])
498 def _HandleUnaryOp(self, hints_fn, level, op, op_fn, operands):
499 """Handles unary operators.
501 @type hints_fn: callable
502 @param hints_fn: Callback doing some analysis on the filter
504 @param level: Current depth
507 @type op_fn: callable
508 @param op_fn: Function implementing operator
510 @param operands: List of operands
518 if len(operands) != 1:
519 raise errors.ParameterError("Unary operator '%s' expects exactly one"
522 if op == qlang.OP_TRUE:
523 (_, _, _, retrieval_fn) = self._LookupField(operands[0])
525 op_fn = operator.truth
527 elif op == qlang.OP_NOT:
528 op_fn = operator.not_
529 arg = self._Compile(operands[0], level + 1)
531 raise errors.ProgrammerError("Can't handle operator '%s'" % op)
533 return compat.partial(_WrapUnaryOp, op_fn, arg)
535 def _HandleBinaryOp(self, hints_fn, level, op, op_data, operands):
536 """Handles binary operators.
538 @type hints_fn: callable
539 @param hints_fn: Callback doing some analysis on the filter
541 @param level: Current depth
544 @param op_data: Functions implementing operators
546 @param operands: List of operands
549 # Unused arguments, pylint: disable=W0613
551 (name, value) = operands
552 except (ValueError, TypeError):
553 raise errors.ParameterError("Invalid binary operator, expected exactly"
556 (fdef, datakind, field_flags, retrieval_fn) = self._LookupField(name)
558 assert fdef.kind != QFT_UNKNOWN
560 # TODO: Type conversions?
562 verify_fn = _VERIFY_FN[fdef.kind]
563 if not verify_fn(value):
564 raise errors.ParameterError("Unable to compare field '%s' (type '%s')"
565 " with '%s', expected %s" %
566 (name, fdef.kind, value.__class__.__name__,
570 hints_fn(op, datakind, name, value)
572 for (fn_flags, fn, valprepfn) in op_data:
573 if fn_flags is None or fn_flags & field_flags:
574 # Prepare value if necessary (e.g. compile regular expression)
576 value = valprepfn(value)
578 return compat.partial(_WrapBinaryOp, fn, retrieval_fn, value)
580 raise errors.ProgrammerError("Unable to find operator implementation"
581 " (op '%s', flags %s)" % (op, field_flags))
584 def _CompileFilter(fields, hints, qfilter):
585 """Converts a query filter into a callable function.
587 See L{_FilterCompilerHelper} for details.
592 return _FilterCompilerHelper(fields)(hints, qfilter)
596 def __init__(self, fieldlist, selected, qfilter=None, namefield=None):
597 """Initializes this class.
599 The field definition is a dictionary with the field's name as a key and a
600 tuple containing, in order, the field definition object
601 (L{objects.QueryFieldDefinition}, the data kind to help calling code
602 collect data and a retrieval function. The retrieval function is called
603 with two parameters, in order, the data container and the item in container
604 (see L{Query.Query}).
606 Users of this class can call L{RequestedData} before preparing the data
607 container to determine what data is needed.
609 @type fieldlist: dictionary
610 @param fieldlist: Field definitions
611 @type selected: list of strings
612 @param selected: List of selected fields
615 assert namefield is None or namefield in fieldlist
617 self._fields = _GetQueryFields(fieldlist, selected)
619 self._filter_fn = None
620 self._requested_names = None
621 self._filter_datakinds = frozenset()
623 if qfilter is not None:
624 # Collect requested names if wanted
626 hints = _FilterHints(namefield)
630 # Build filter function
631 self._filter_fn = _CompileFilter(fieldlist, hints, qfilter)
633 self._requested_names = hints.RequestedNames()
634 self._filter_datakinds = hints.ReferencedData()
636 if namefield is None:
639 (_, _, _, self._name_fn) = fieldlist[namefield]
641 def RequestedNames(self):
642 """Returns all names referenced in the filter.
644 If there is no filter or operators are preventing determining the exact
645 names, C{None} is returned.
648 return self._requested_names
650 def RequestedData(self):
651 """Gets requested kinds of data.
656 return (self._filter_datakinds |
657 frozenset(datakind for (_, datakind, _, _) in self._fields
658 if datakind is not None))
661 """Returns the list of fields for this query.
663 Includes unknown fields.
665 @rtype: List of L{objects.QueryFieldDefinition}
668 return GetAllFields(self._fields)
670 def Query(self, ctx, sort_by_name=True):
673 @param ctx: Data container passed to field retrieval functions, must
674 support iteration using C{__iter__}
675 @type sort_by_name: boolean
676 @param sort_by_name: Whether to sort by name or keep the input data's
680 sort = (self._name_fn and sort_by_name)
684 for idx, item in enumerate(ctx):
685 if not (self._filter_fn is None or self._filter_fn(ctx, item)):
688 row = [_ProcessResult(fn(ctx, item)) for (_, _, _, fn) in self._fields]
692 _VerifyResultRow(self._fields, row)
695 (status, name) = _ProcessResult(self._name_fn(ctx, item))
696 assert status == constants.RS_NORMAL
697 # TODO: Are there cases where we wouldn't want to use NiceSort?
698 result.append((utils.NiceSortKey(name), idx, row))
705 # TODO: Would "heapq" be more efficient than sorting?
707 # Sorting in-place instead of using "sorted()"
710 assert not result or (len(result[0]) == 3 and len(result[-1]) == 3)
712 return map(operator.itemgetter(2), result)
714 def OldStyleQuery(self, ctx, sort_by_name=True):
715 """Query with "old" query result format.
717 See L{Query.Query} for arguments.
720 unknown = set(fdef.name for (fdef, _, _, _) in self._fields
721 if fdef.kind == QFT_UNKNOWN)
723 raise errors.OpPrereqError("Unknown output fields selected: %s" %
724 (utils.CommaJoin(unknown), ),
727 return [[value for (_, value) in row]
728 for row in self.Query(ctx, sort_by_name=sort_by_name)]
731 def _ProcessResult(value):
732 """Converts result values into externally-visible ones.
735 if value is _FS_UNKNOWN:
736 return (RS_UNKNOWN, None)
737 elif value is _FS_NODATA:
738 return (RS_NODATA, None)
739 elif value is _FS_UNAVAIL:
740 return (RS_UNAVAIL, None)
741 elif value is _FS_OFFLINE:
742 return (RS_OFFLINE, None)
744 return (RS_NORMAL, value)
747 def _VerifyResultRow(fields, row):
748 """Verifies the contents of a query result row.
751 @param fields: Field definitions for result
752 @type row: list of tuples
756 assert len(row) == len(fields)
758 for ((status, value), (fdef, _, _, _)) in zip(row, fields):
759 if status == RS_NORMAL:
760 if not _VERIFY_FN[fdef.kind](value):
761 errs.append("normal field %s fails validation (value is %s)" %
763 elif value is not None:
764 errs.append("abnormal field %s has a non-None value" % fdef.name)
765 assert not errs, ("Failed validation: %s in row %s" %
766 (utils.CommaJoin(errs), row))
769 def _FieldDictKey((fdef, _, flags, fn)):
770 """Generates key for field dictionary.
773 assert fdef.name and fdef.title, "Name and title are required"
774 assert FIELD_NAME_RE.match(fdef.name)
775 assert TITLE_RE.match(fdef.title)
776 assert (DOC_RE.match(fdef.doc) and len(fdef.doc.splitlines()) == 1 and
777 fdef.doc.strip() == fdef.doc), \
778 "Invalid description for field '%s'" % fdef.name
780 assert (flags & ~QFF_ALL) == 0, "Unknown flags for field '%s'" % fdef.name
785 def _PrepareFieldList(fields, aliases):
786 """Prepares field list for use by L{Query}.
788 Converts the list to a dictionary and does some verification.
790 @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data
791 kind, retrieval function)
792 @param fields: List of fields, see L{Query.__init__} for a better
794 @type aliases: list of tuples; (alias, target)
795 @param aliases: list of tuples containing aliases; for each
796 alias/target pair, a duplicate will be created in the field list
798 @return: Field dictionary for L{Query}
802 duplicates = utils.FindDuplicates(fdef.title.lower()
803 for (fdef, _, _, _) in fields)
804 assert not duplicates, "Duplicate title(s) found: %r" % duplicates
806 result = utils.SequenceToDict(fields, key=_FieldDictKey)
808 for alias, target in aliases:
809 assert alias not in result, "Alias %s overrides an existing field" % alias
810 assert target in result, "Missing target %s for alias %s" % (target, alias)
811 (fdef, k, flags, fn) = result[target]
814 result[alias] = (fdef, k, flags, fn)
816 assert len(result) == len(fields) + len(aliases)
817 assert compat.all(name == fdef.name
818 for (name, (fdef, _, _, _)) in result.items())
823 def GetQueryResponse(query, ctx, sort_by_name=True):
824 """Prepares the response for a query.
826 @type query: L{Query}
827 @param ctx: Data container, see L{Query.Query}
828 @type sort_by_name: boolean
829 @param sort_by_name: Whether to sort by name or keep the input data's
833 return objects.QueryResponse(data=query.Query(ctx, sort_by_name=sort_by_name),
834 fields=query.GetFields()).ToDict()
837 def QueryFields(fielddefs, selected):
838 """Returns list of available fields.
840 @type fielddefs: dict
841 @param fielddefs: Field definitions
842 @type selected: list of strings
843 @param selected: List of selected fields
844 @return: List of L{objects.QueryFieldDefinition}
848 # Client requests all fields, sort by name
849 fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
850 key=operator.attrgetter("name"))
852 # Keep order as requested by client
853 fdefs = Query(fielddefs, selected).GetFields()
855 return objects.QueryFieldsResponse(fields=fdefs).ToDict()
858 def _MakeField(name, title, kind, doc):
859 """Wrapper for creating L{objects.QueryFieldDefinition} instances.
861 @param name: Field name as a regular expression
862 @param title: Human-readable title
863 @param kind: Field type
864 @param doc: Human-readable description
867 return objects.QueryFieldDefinition(name=name, title=title, kind=kind,
871 def _GetNodeRole(node, master_name):
872 """Determine node role.
874 @type node: L{objects.Node}
875 @param node: Node object
876 @type master_name: string
877 @param master_name: Master node name
880 if node.name == master_name:
881 return constants.NR_MASTER
882 elif node.master_candidate:
883 return constants.NR_MCANDIDATE
885 return constants.NR_DRAINED
887 return constants.NR_OFFLINE
889 return constants.NR_REGULAR
892 def _GetItemAttr(attr):
893 """Returns a field function to return an attribute of the item.
895 @param attr: Attribute name
898 getter = operator.attrgetter(attr)
899 return lambda _, item: getter(item)
902 def _ConvWrapInner(convert, fn, ctx, item):
903 """Wrapper for converting values.
905 @param convert: Conversion function receiving value as single parameter
906 @param fn: Retrieval function
909 value = fn(ctx, item)
911 # Is the value an abnormal status?
912 if compat.any(value is fs for fs in _FS_ALL):
916 # TODO: Should conversion function also receive context, item or both?
917 return convert(value)
920 def _ConvWrap(convert, fn):
921 """Convenience wrapper for L{_ConvWrapInner}.
923 @param convert: Conversion function receiving value as single parameter
924 @param fn: Retrieval function
927 return compat.partial(_ConvWrapInner, convert, fn)
930 def _GetItemTimestamp(getter):
931 """Returns function for getting timestamp of item.
933 @type getter: callable
934 @param getter: Function to retrieve timestamp attribute
938 """Returns a timestamp of item.
941 timestamp = getter(item)
942 if timestamp is None:
943 # Old configs might not have all timestamps
951 def _GetItemTimestampFields(datatype):
952 """Returns common timestamp fields.
954 @param datatype: Field data type for use by L{Query.RequestedData}
958 (_MakeField("ctime", "CTime", QFT_TIMESTAMP, "Creation timestamp"),
959 datatype, 0, _GetItemTimestamp(operator.attrgetter("ctime"))),
960 (_MakeField("mtime", "MTime", QFT_TIMESTAMP, "Modification timestamp"),
961 datatype, 0, _GetItemTimestamp(operator.attrgetter("mtime"))),
966 """Data container for node data queries.
969 def __init__(self, nodes, live_data, master_name, node_to_primary,
970 node_to_secondary, groups, oob_support, cluster):
971 """Initializes this class.
975 self.live_data = live_data
976 self.master_name = master_name
977 self.node_to_primary = node_to_primary
978 self.node_to_secondary = node_to_secondary
980 self.oob_support = oob_support
981 self.cluster = cluster
983 # Used for individual rows
984 self.curlive_data = None
987 """Iterate over all nodes.
989 This function has side-effects and only one instance of the resulting
990 generator should be used at a time.
993 for node in self.nodes:
995 self.curlive_data = self.live_data.get(node.name, None)
997 self.curlive_data = None
1001 #: Fields that are direct attributes of an L{objects.Node} object
1002 _NODE_SIMPLE_FIELDS = {
1003 "drained": ("Drained", QFT_BOOL, 0, "Whether node is drained"),
1004 "master_candidate": ("MasterC", QFT_BOOL, 0,
1005 "Whether node is a master candidate"),
1006 "master_capable": ("MasterCapable", QFT_BOOL, 0,
1007 "Whether node can become a master candidate"),
1008 "name": ("Node", QFT_TEXT, QFF_HOSTNAME, "Node name"),
1009 "offline": ("Offline", QFT_BOOL, 0, "Whether node is marked offline"),
1010 "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Node"),
1011 "uuid": ("UUID", QFT_TEXT, 0, "Node UUID"),
1012 "vm_capable": ("VMCapable", QFT_BOOL, 0, "Whether node can host instances"),
1016 #: Fields requiring talking to the node
1017 # Note that none of these are available for non-vm_capable nodes
1018 _NODE_LIVE_FIELDS = {
1019 "bootid": ("BootID", QFT_TEXT, "bootid",
1020 "Random UUID renewed for each system reboot, can be used"
1021 " for detecting reboots by tracking changes"),
1022 "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes",
1023 "Number of NUMA domains on node (if exported by hypervisor)"),
1024 "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets",
1025 "Number of physical CPU sockets (if exported by hypervisor)"),
1026 "ctotal": ("CTotal", QFT_NUMBER, "cpu_total", "Number of logical processors"),
1027 "dfree": ("DFree", QFT_UNIT, "vg_free",
1028 "Available disk space in volume group"),
1029 "dtotal": ("DTotal", QFT_UNIT, "vg_size",
1030 "Total disk space in volume group used for instance disk"
1032 "mfree": ("MFree", QFT_UNIT, "memory_free",
1033 "Memory available for instance allocations"),
1034 "mnode": ("MNode", QFT_UNIT, "memory_dom0",
1035 "Amount of memory used by node (dom0 for Xen)"),
1036 "mtotal": ("MTotal", QFT_UNIT, "memory_total",
1037 "Total amount of memory of physical machine"),
1042 """Build function for calling another function with an node group.
1044 @param cb: The callback to be called with the nodegroup
1048 """Get group data for a node.
1050 @type ctx: L{NodeQueryData}
1051 @type inst: L{objects.Node}
1052 @param inst: Node object
1055 ng = ctx.groups.get(node.group, None)
1057 # Nodes always have a group, or the configuration is corrupt
1060 return cb(ctx, node, ng)
1065 def _GetNodeGroup(ctx, node, ng): # pylint: disable=W0613
1066 """Returns the name of a node's group.
1068 @type ctx: L{NodeQueryData}
1069 @type node: L{objects.Node}
1070 @param node: Node object
1071 @type ng: L{objects.NodeGroup}
1072 @param ng: The node group this node belongs to
1078 def _GetNodePower(ctx, node):
1079 """Returns the node powered state
1081 @type ctx: L{NodeQueryData}
1082 @type node: L{objects.Node}
1083 @param node: Node object
1086 if ctx.oob_support[node.name]:
1092 def _GetNdParams(ctx, node, ng):
1093 """Returns the ndparams for this node.
1095 @type ctx: L{NodeQueryData}
1096 @type node: L{objects.Node}
1097 @param node: Node object
1098 @type ng: L{objects.NodeGroup}
1099 @param ng: The node group this node belongs to
1102 return ctx.cluster.SimpleFillND(ng.FillND(node))
1105 def _GetLiveNodeField(field, kind, ctx, node):
1106 """Gets the value of a "live" field from L{NodeQueryData}.
1108 @param field: Live field name
1109 @param kind: Data kind, one of L{constants.QFT_ALL}
1110 @type ctx: L{NodeQueryData}
1111 @type node: L{objects.Node}
1112 @param node: Node object
1118 if not node.vm_capable:
1121 if not ctx.curlive_data:
1125 value = ctx.curlive_data[field]
1129 if kind == QFT_TEXT:
1132 assert kind in (QFT_NUMBER, QFT_UNIT)
1134 # Try to convert into number
1137 except (ValueError, TypeError):
1138 logging.exception("Failed to convert node field '%s' (value %r) to int",
1143 def _BuildNodeFields():
1144 """Builds list of fields for node queries.
1148 (_MakeField("pip", "PrimaryIP", QFT_TEXT, "Primary IP address"),
1149 NQ_CONFIG, 0, _GetItemAttr("primary_ip")),
1150 (_MakeField("sip", "SecondaryIP", QFT_TEXT, "Secondary IP address"),
1151 NQ_CONFIG, 0, _GetItemAttr("secondary_ip")),
1152 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0,
1153 lambda ctx, node: list(node.GetTags())),
1154 (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"),
1155 NQ_CONFIG, 0, lambda ctx, node: node.name == ctx.master_name),
1156 (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0,
1157 _GetGroup(_GetNodeGroup)),
1158 (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"),
1159 NQ_CONFIG, 0, _GetItemAttr("group")),
1160 (_MakeField("powered", "Powered", QFT_BOOL,
1161 "Whether node is thought to be powered on"),
1162 NQ_OOB, 0, _GetNodePower),
1163 (_MakeField("ndparams", "NodeParameters", QFT_OTHER,
1164 "Merged node parameters"),
1165 NQ_GROUP, 0, _GetGroup(_GetNdParams)),
1166 (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER,
1167 "Custom node parameters"),
1168 NQ_GROUP, 0, _GetItemAttr("ndparams")),
1172 role_values = (constants.NR_MASTER, constants.NR_MCANDIDATE,
1173 constants.NR_REGULAR, constants.NR_DRAINED,
1174 constants.NR_OFFLINE)
1175 role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate,"
1176 " \"%s\" for regular, \"%s\" for a drained, \"%s\" for offline" %
1178 fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
1179 lambda ctx, node: _GetNodeRole(node, ctx.master_name)))
1180 assert set(role_values) == constants.NR_ALL
1182 def _GetLength(getter):
1183 return lambda ctx, node: len(getter(ctx)[node.name])
1185 def _GetList(getter):
1186 return lambda ctx, node: list(getter(ctx)[node.name])
1188 # Add fields operating on instance lists
1189 for prefix, titleprefix, docword, getter in \
1190 [("p", "Pri", "primary", operator.attrgetter("node_to_primary")),
1191 ("s", "Sec", "secondary", operator.attrgetter("node_to_secondary"))]:
1192 # TODO: Allow filterting by hostname in list
1194 (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER,
1195 "Number of instances with this node as %s" % docword),
1196 NQ_INST, 0, _GetLength(getter)),
1197 (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
1199 "List of instances with this node as %s" % docword),
1200 NQ_INST, 0, _GetList(getter)),
1205 (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1206 for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()
1209 # Add fields requiring live data
1211 (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1212 compat.partial(_GetLiveNodeField, nfield, kind))
1213 for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()
1217 fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1219 return _PrepareFieldList(fields, [])
1222 class InstanceQueryData:
1223 """Data container for instance data queries.
1226 def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
1227 live_data, wrongnode_inst, console, nodes, groups):
1228 """Initializes this class.
1230 @param instances: List of instance objects
1231 @param cluster: Cluster object
1232 @type disk_usage: dict; instance name as key
1233 @param disk_usage: Per-instance disk usage
1234 @type offline_nodes: list of strings
1235 @param offline_nodes: List of offline nodes
1236 @type bad_nodes: list of strings
1237 @param bad_nodes: List of faulty nodes
1238 @type live_data: dict; instance name as key
1239 @param live_data: Per-instance live data
1240 @type wrongnode_inst: set
1241 @param wrongnode_inst: Set of instances running on wrong node(s)
1242 @type console: dict; instance name as key
1243 @param console: Per-instance console information
1244 @type nodes: dict; node name as key
1245 @param nodes: Node objects
1248 assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
1249 "Offline nodes not included in bad nodes"
1250 assert not (set(live_data.keys()) & set(bad_nodes)), \
1251 "Found live data for bad or offline nodes"
1253 self.instances = instances
1254 self.cluster = cluster
1255 self.disk_usage = disk_usage
1256 self.offline_nodes = offline_nodes
1257 self.bad_nodes = bad_nodes
1258 self.live_data = live_data
1259 self.wrongnode_inst = wrongnode_inst
1260 self.console = console
1262 self.groups = groups
1264 # Used for individual rows
1265 self.inst_hvparams = None
1266 self.inst_beparams = None
1267 self.inst_osparams = None
1268 self.inst_nicparams = None
1271 """Iterate over all instances.
1273 This function has side-effects and only one instance of the resulting
1274 generator should be used at a time.
1277 for inst in self.instances:
1278 self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1279 self.inst_beparams = self.cluster.FillBE(inst)
1280 self.inst_osparams = self.cluster.SimpleFillOS(inst.os, inst.osparams)
1281 self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1282 for nic in inst.nics]
1287 def _GetInstOperState(ctx, inst):
1288 """Get instance's operational status.
1290 @type ctx: L{InstanceQueryData}
1291 @type inst: L{objects.Instance}
1292 @param inst: Instance object
1295 # Can't use RS_OFFLINE here as it would describe the instance to
1296 # be offline when we actually don't know due to missing data
1297 if inst.primary_node in ctx.bad_nodes:
1300 return bool(ctx.live_data.get(inst.name))
1303 def _GetInstLiveData(name):
1304 """Build function for retrieving live data.
1307 @param name: Live data field name
1311 """Get live data for an instance.
1313 @type ctx: L{InstanceQueryData}
1314 @type inst: L{objects.Instance}
1315 @param inst: Instance object
1318 if (inst.primary_node in ctx.bad_nodes or
1319 inst.primary_node in ctx.offline_nodes):
1320 # Can't use RS_OFFLINE here as it would describe the instance to be
1321 # offline when we actually don't know due to missing data
1324 if inst.name in ctx.live_data:
1325 data = ctx.live_data[inst.name]
1334 def _GetInstStatus(ctx, inst):
1335 """Get instance status.
1337 @type ctx: L{InstanceQueryData}
1338 @type inst: L{objects.Instance}
1339 @param inst: Instance object
1342 if inst.primary_node in ctx.offline_nodes:
1343 return constants.INSTST_NODEOFFLINE
1345 if inst.primary_node in ctx.bad_nodes:
1346 return constants.INSTST_NODEDOWN
1348 if bool(ctx.live_data.get(inst.name)):
1349 if inst.name in ctx.wrongnode_inst:
1350 return constants.INSTST_WRONGNODE
1351 elif inst.admin_state == constants.ADMINST_UP:
1352 return constants.INSTST_RUNNING
1354 return constants.INSTST_ERRORUP
1356 if inst.admin_state == constants.ADMINST_UP:
1357 return constants.INSTST_ERRORDOWN
1358 elif inst.admin_state == constants.ADMINST_DOWN:
1359 return constants.INSTST_ADMINDOWN
1361 return constants.INSTST_ADMINOFFLINE
1364 def _GetInstDiskSize(index):
1365 """Build function for retrieving disk size.
1368 @param index: Disk index
1372 """Get size of a disk.
1374 @type inst: L{objects.Instance}
1375 @param inst: Instance object
1379 return inst.disks[index].size
1386 def _GetInstNic(index, cb):
1387 """Build function for calling another function with an instance NIC.
1390 @param index: NIC index
1396 """Call helper function with instance NIC.
1398 @type ctx: L{InstanceQueryData}
1399 @type inst: L{objects.Instance}
1400 @param inst: Instance object
1404 nic = inst.nics[index]
1408 return cb(ctx, index, nic)
1413 def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613
1414 """Get a NIC's IP address.
1416 @type ctx: L{InstanceQueryData}
1417 @type nic: L{objects.NIC}
1418 @param nic: NIC object
1427 def _GetInstNicBridge(ctx, index, _):
1428 """Get a NIC's bridge.
1430 @type ctx: L{InstanceQueryData}
1432 @param index: NIC index
1435 assert len(ctx.inst_nicparams) >= index
1437 nicparams = ctx.inst_nicparams[index]
1439 if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1440 return nicparams[constants.NIC_LINK]
1445 def _GetInstAllNicBridges(ctx, inst):
1446 """Get all network bridges for an instance.
1448 @type ctx: L{InstanceQueryData}
1449 @type inst: L{objects.Instance}
1450 @param inst: Instance object
1453 assert len(ctx.inst_nicparams) == len(inst.nics)
1457 for nicp in ctx.inst_nicparams:
1458 if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1459 result.append(nicp[constants.NIC_LINK])
1463 assert len(result) == len(inst.nics)
1468 def _GetInstNicParam(name):
1469 """Build function for retrieving a NIC parameter.
1472 @param name: Parameter name
1475 def fn(ctx, index, _):
1476 """Get a NIC's bridge.
1478 @type ctx: L{InstanceQueryData}
1479 @type inst: L{objects.Instance}
1480 @param inst: Instance object
1481 @type nic: L{objects.NIC}
1482 @param nic: NIC object
1485 assert len(ctx.inst_nicparams) >= index
1486 return ctx.inst_nicparams[index][name]
1491 def _GetInstanceNetworkFields():
1492 """Get instance fields involving network interfaces.
1494 @return: Tuple containing list of field definitions used as input for
1495 L{_PrepareFieldList} and a list of aliases
1498 nic_mac_fn = lambda ctx, _, nic: nic.mac
1499 nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1500 nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1504 (_MakeField("nic.count", "NICs", QFT_NUMBER,
1505 "Number of network interfaces"),
1506 IQ_CONFIG, 0, lambda ctx, inst: len(inst.nics)),
1507 (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER,
1508 "List containing each network interface's MAC address"),
1509 IQ_CONFIG, 0, lambda ctx, inst: [nic.mac for nic in inst.nics]),
1510 (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
1511 "List containing each network interface's IP address"),
1512 IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
1513 (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
1514 "List containing each network interface's mode"), IQ_CONFIG, 0,
1515 lambda ctx, inst: [nicp[constants.NIC_MODE]
1516 for nicp in ctx.inst_nicparams]),
1517 (_MakeField("nic.links", "NIC_links", QFT_OTHER,
1518 "List containing each network interface's link"), IQ_CONFIG, 0,
1519 lambda ctx, inst: [nicp[constants.NIC_LINK]
1520 for nicp in ctx.inst_nicparams]),
1521 (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
1522 "List containing each network interface's bridge"),
1523 IQ_CONFIG, 0, _GetInstAllNicBridges),
1527 for i in range(constants.MAX_NICS):
1528 numtext = utils.FormatOrdinal(i + 1)
1530 (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT,
1531 "IP address of %s network interface" % numtext),
1532 IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)),
1533 (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
1534 "MAC address of %s network interface" % numtext),
1535 IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
1536 (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
1537 "Mode of %s network interface" % numtext),
1538 IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
1539 (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT,
1540 "Link of %s network interface" % numtext),
1541 IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)),
1542 (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
1543 "Bridge of %s network interface" % numtext),
1544 IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
1548 # Legacy fields for first NIC
1550 ("mac", "nic.mac/0"),
1551 ("bridge", "nic.bridge/0"),
1552 ("nic_mode", "nic.mode/0"),
1553 ("nic_link", "nic.link/0"),
1556 return (fields, aliases)
1559 def _GetInstDiskUsage(ctx, inst):
1560 """Get disk usage for an instance.
1562 @type ctx: L{InstanceQueryData}
1563 @type inst: L{objects.Instance}
1564 @param inst: Instance object
1567 usage = ctx.disk_usage[inst.name]
1575 def _GetInstanceConsole(ctx, inst):
1576 """Get console information for instance.
1578 @type ctx: L{InstanceQueryData}
1579 @type inst: L{objects.Instance}
1580 @param inst: Instance object
1583 consinfo = ctx.console[inst.name]
1585 if consinfo is None:
1591 def _GetInstanceDiskFields():
1592 """Get instance fields involving disks.
1594 @return: List of field definitions used as input for L{_PrepareFieldList}
1598 (_MakeField("disk_usage", "DiskUsage", QFT_UNIT,
1599 "Total disk space used by instance on each of its nodes;"
1600 " this is not the disk size visible to the instance, but"
1601 " the usage on the node"),
1602 IQ_DISKUSAGE, 0, _GetInstDiskUsage),
1603 (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"),
1604 IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
1605 (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
1606 IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
1611 (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1612 "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
1613 IQ_CONFIG, 0, _GetInstDiskSize(i))
1614 for i in range(constants.MAX_DISKS)
1620 def _GetInstanceParameterFields():
1621 """Get instance fields involving parameters.
1623 @return: List of field definitions used as input for L{_PrepareFieldList}
1626 # TODO: Consider moving titles closer to constants
1628 constants.BE_AUTO_BALANCE: "Auto_balance",
1629 constants.BE_MEMORY: "ConfigMemory",
1630 constants.BE_VCPUS: "ConfigVCPUs",
1634 constants.HV_ACPI: "ACPI",
1635 constants.HV_BOOT_ORDER: "Boot_order",
1636 constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1637 constants.HV_DISK_TYPE: "Disk_type",
1638 constants.HV_INITRD_PATH: "Initrd_path",
1639 constants.HV_KERNEL_PATH: "Kernel_path",
1640 constants.HV_NIC_TYPE: "NIC_type",
1641 constants.HV_PAE: "PAE",
1642 constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1647 (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
1648 "Hypervisor parameters (merged)"),
1649 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
1650 (_MakeField("beparams", "BackendParameters", QFT_OTHER,
1651 "Backend parameters (merged)"),
1652 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
1653 (_MakeField("osparams", "OpSysParameters", QFT_OTHER,
1654 "Operating system parameters (merged)"),
1655 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_osparams),
1657 # Unfilled parameters
1658 (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
1659 "Custom hypervisor parameters"),
1660 IQ_CONFIG, 0, _GetItemAttr("hvparams")),
1661 (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
1662 "Custom backend parameters",),
1663 IQ_CONFIG, 0, _GetItemAttr("beparams")),
1664 (_MakeField("custom_osparams", "CustomOpSysParameters", QFT_OTHER,
1665 "Custom operating system parameters",),
1666 IQ_CONFIG, 0, _GetItemAttr("osparams")),
1667 (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
1668 "Custom network interface parameters"),
1669 IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1673 def _GetInstHvParam(name):
1674 return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1677 (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1678 _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1679 IQ_CONFIG, 0, _GetInstHvParam(name))
1680 for name, kind in constants.HVS_PARAMETER_TYPES.items()
1681 if name not in constants.HVC_GLOBALS
1685 def _GetInstBeParam(name):
1686 return lambda ctx, _: ctx.inst_beparams.get(name, None)
1689 (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1690 _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1691 IQ_CONFIG, 0, _GetInstBeParam(name))
1692 for name, kind in constants.BES_PARAMETER_TYPES.items()
1698 _INST_SIMPLE_FIELDS = {
1699 "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"),
1700 "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"),
1701 "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"),
1702 # Depending on the hypervisor, the port can be None
1703 "network_port": ("Network_port", QFT_OTHER, 0,
1704 "Instance network port if available (e.g. for VNC console)"),
1705 "os": ("OS", QFT_TEXT, 0, "Operating system"),
1706 "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"),
1707 "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"),
1711 def _GetInstNodeGroup(ctx, default, node_name):
1712 """Gets group UUID of an instance node.
1714 @type ctx: L{InstanceQueryData}
1715 @param default: Default value
1716 @type node_name: string
1717 @param node_name: Node name
1721 node = ctx.nodes[node_name]
1728 def _GetInstNodeGroupName(ctx, default, node_name):
1729 """Gets group name of an instance node.
1731 @type ctx: L{InstanceQueryData}
1732 @param default: Default value
1733 @type node_name: string
1734 @param node_name: Node name
1738 node = ctx.nodes[node_name]
1743 group = ctx.groups[node.group]
1750 def _BuildInstanceFields():
1751 """Builds list of fields for instance queries.
1755 (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
1756 IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")),
1757 (_MakeField("pnode.group", "PrimaryNodeGroup", QFT_TEXT,
1758 "Primary node's group"),
1760 lambda ctx, inst: _GetInstNodeGroupName(ctx, _FS_UNAVAIL,
1761 inst.primary_node)),
1762 (_MakeField("pnode.group.uuid", "PrimaryNodeGroupUUID", QFT_TEXT,
1763 "Primary node's group UUID"),
1765 lambda ctx, inst: _GetInstNodeGroup(ctx, _FS_UNAVAIL, inst.primary_node)),
1766 # TODO: Allow filtering by secondary node as hostname
1767 (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
1768 "Secondary nodes; usually this will just be one node"),
1769 IQ_CONFIG, 0, lambda ctx, inst: list(inst.secondary_nodes)),
1770 (_MakeField("snodes.group", "SecondaryNodesGroups", QFT_OTHER,
1771 "Node groups of secondary nodes"),
1773 lambda ctx, inst: map(compat.partial(_GetInstNodeGroupName, ctx, None),
1774 inst.secondary_nodes)),
1775 (_MakeField("snodes.group.uuid", "SecondaryNodesGroupsUUID", QFT_OTHER,
1776 "Node group UUIDs of secondary nodes"),
1778 lambda ctx, inst: map(compat.partial(_GetInstNodeGroup, ctx, None),
1779 inst.secondary_nodes)),
1780 (_MakeField("admin_state", "InstanceState", QFT_TEXT,
1781 "Desired state of instance"),
1782 IQ_CONFIG, 0, _GetItemAttr("admin_state")),
1783 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
1784 lambda ctx, inst: list(inst.GetTags())),
1785 (_MakeField("console", "Console", QFT_OTHER,
1786 "Instance console information"), IQ_CONSOLE, 0,
1787 _GetInstanceConsole),
1792 (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1793 for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()
1796 # Fields requiring talking to the node
1798 (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
1799 IQ_LIVE, 0, _GetInstOperState),
1800 (_MakeField("oper_ram", "Memory", QFT_UNIT,
1801 "Actual memory usage as seen by hypervisor"),
1802 IQ_LIVE, 0, _GetInstLiveData("memory")),
1803 (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
1804 "Actual number of VCPUs as seen by hypervisor"),
1805 IQ_LIVE, 0, _GetInstLiveData("vcpus")),
1809 status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
1810 constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
1811 constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
1812 constants.INSTST_NODEOFFLINE, constants.INSTST_ADMINOFFLINE)
1813 status_doc = ("Instance status; \"%s\" if instance is set to be running"
1814 " and actually is, \"%s\" if instance is stopped and"
1815 " is not running, \"%s\" if instance running, but not on its"
1816 " designated primary node, \"%s\" if instance should be"
1817 " stopped, but is actually running, \"%s\" if instance should"
1818 " run, but doesn't, \"%s\" if instance's primary node is down,"
1819 " \"%s\" if instance's primary node is marked offline,"
1820 " \"%s\" if instance is offline and does not use dynamic"
1821 " resources" % status_values)
1822 fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
1823 IQ_LIVE, 0, _GetInstStatus))
1824 assert set(status_values) == constants.INSTST_ALL, \
1825 "Status documentation mismatch"
1827 (network_fields, network_aliases) = _GetInstanceNetworkFields()
1829 fields.extend(network_fields)
1830 fields.extend(_GetInstanceParameterFields())
1831 fields.extend(_GetInstanceDiskFields())
1832 fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1835 ("vcpus", "be/vcpus"),
1836 ("sda_size", "disk.size/0"),
1837 ("sdb_size", "disk.size/1"),
1840 return _PrepareFieldList(fields, aliases)
1843 class LockQueryData:
1844 """Data container for lock data queries.
1847 def __init__(self, lockdata):
1848 """Initializes this class.
1851 self.lockdata = lockdata
1854 """Iterate over all locks.
1857 return iter(self.lockdata)
1860 def _GetLockOwners(_, data):
1861 """Returns a sorted list of a lock's current owners.
1864 (_, _, owners, _) = data
1867 owners = utils.NiceSort(owners)
1872 def _GetLockPending(_, data):
1873 """Returns a sorted list of a lock's pending acquires.
1876 (_, _, _, pending) = data
1879 pending = [(mode, utils.NiceSort(names))
1880 for (mode, names) in pending]
1885 def _BuildLockFields():
1886 """Builds list of fields for lock queries.
1889 return _PrepareFieldList([
1890 # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
1891 (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
1892 lambda ctx, (name, mode, owners, pending): name),
1893 (_MakeField("mode", "Mode", QFT_OTHER,
1894 "Mode in which the lock is currently acquired"
1895 " (exclusive or shared)"),
1896 LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
1897 (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
1898 LQ_OWNER, 0, _GetLockOwners),
1899 (_MakeField("pending", "Pending", QFT_OTHER,
1900 "Threads waiting for the lock"),
1901 LQ_PENDING, 0, _GetLockPending),
1905 class GroupQueryData:
1906 """Data container for node group data queries.
1909 def __init__(self, groups, group_to_nodes, group_to_instances):
1910 """Initializes this class.
1912 @param groups: List of node group objects
1913 @type group_to_nodes: dict; group UUID as key
1914 @param group_to_nodes: Per-group list of nodes
1915 @type group_to_instances: dict; group UUID as key
1916 @param group_to_instances: Per-group list of (primary) instances
1919 self.groups = groups
1920 self.group_to_nodes = group_to_nodes
1921 self.group_to_instances = group_to_instances
1924 """Iterate over all node groups.
1927 return iter(self.groups)
1930 _GROUP_SIMPLE_FIELDS = {
1931 "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
1932 "name": ("Group", QFT_TEXT, "Group name"),
1933 "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
1934 "uuid": ("UUID", QFT_TEXT, "Group UUID"),
1935 "ndparams": ("NDParams", QFT_OTHER, "Node parameters"),
1939 def _BuildGroupFields():
1940 """Builds list of fields for node group queries.
1944 fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
1946 for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
1948 def _GetLength(getter):
1949 return lambda ctx, group: len(getter(ctx)[group.uuid])
1951 def _GetSortedList(getter):
1952 return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
1954 group_to_nodes = operator.attrgetter("group_to_nodes")
1955 group_to_instances = operator.attrgetter("group_to_instances")
1957 # Add fields for nodes
1959 (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
1960 GQ_NODE, 0, _GetLength(group_to_nodes)),
1961 (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
1962 GQ_NODE, 0, _GetSortedList(group_to_nodes)),
1965 # Add fields for instances
1967 (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
1968 "Number of primary instances"),
1969 GQ_INST, 0, _GetLength(group_to_instances)),
1970 (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
1971 "List of primary instances"),
1972 GQ_INST, 0, _GetSortedList(group_to_instances)),
1977 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), GQ_CONFIG, 0,
1978 lambda ctx, group: list(group.GetTags())),
1981 fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1983 return _PrepareFieldList(fields, [])
1986 class OsInfo(objects.ConfigObject):
1999 def _BuildOsFields():
2000 """Builds list of fields for operating system queries.
2004 (_MakeField("name", "Name", QFT_TEXT, "Operating system name"),
2005 None, 0, _GetItemAttr("name")),
2006 (_MakeField("valid", "Valid", QFT_BOOL,
2007 "Whether operating system definition is valid"),
2008 None, 0, _GetItemAttr("valid")),
2009 (_MakeField("hidden", "Hidden", QFT_BOOL,
2010 "Whether operating system is hidden"),
2011 None, 0, _GetItemAttr("hidden")),
2012 (_MakeField("blacklisted", "Blacklisted", QFT_BOOL,
2013 "Whether operating system is blacklisted"),
2014 None, 0, _GetItemAttr("blacklisted")),
2015 (_MakeField("variants", "Variants", QFT_OTHER,
2016 "Operating system variants"),
2017 None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("variants"))),
2018 (_MakeField("api_versions", "ApiVersions", QFT_OTHER,
2019 "Operating system API versions"),
2020 None, 0, _ConvWrap(sorted, _GetItemAttr("api_versions"))),
2021 (_MakeField("parameters", "Parameters", QFT_OTHER,
2022 "Operating system parameters"),
2023 None, 0, _ConvWrap(compat.partial(utils.NiceSort, key=compat.fst),
2024 _GetItemAttr("parameters"))),
2025 (_MakeField("node_status", "NodeStatus", QFT_OTHER,
2026 "Status from node"),
2027 None, 0, _GetItemAttr("node_status")),
2030 return _PrepareFieldList(fields, [])
2033 #: Fields available for node queries
2034 NODE_FIELDS = _BuildNodeFields()
2036 #: Fields available for instance queries
2037 INSTANCE_FIELDS = _BuildInstanceFields()
2039 #: Fields available for lock queries
2040 LOCK_FIELDS = _BuildLockFields()
2042 #: Fields available for node group queries
2043 GROUP_FIELDS = _BuildGroupFields()
2045 #: Fields available for operating system queries
2046 OS_FIELDS = _BuildOsFields()
2048 #: All available resources
2050 constants.QR_INSTANCE: INSTANCE_FIELDS,
2051 constants.QR_NODE: NODE_FIELDS,
2052 constants.QR_LOCK: LOCK_FIELDS,
2053 constants.QR_GROUP: GROUP_FIELDS,
2054 constants.QR_OS: OS_FIELDS,
2057 #: All available field lists
2058 ALL_FIELD_LISTS = ALL_FIELDS.values()