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.
86 IQ_CONSOLE) = range(100, 104)
90 LQ_PENDING) = range(10, 13)
94 GQ_INST) = range(200, 203)
99 # Next values: 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200
100 QFF_ALL = (QFF_HOSTNAME | QFF_IP_ADDRESS)
102 FIELD_NAME_RE = re.compile(r"^[a-z0-9/._]+$")
103 TITLE_RE = re.compile(r"^[^\s]+$")
104 DOC_RE = re.compile(r"^[A-Z].*[^.,?!]$")
106 #: Verification function for each field type
108 QFT_UNKNOWN: ht.TNone,
109 QFT_TEXT: ht.TString,
113 QFT_TIMESTAMP: ht.TOr(ht.TInt, ht.TFloat),
114 QFT_OTHER: lambda _: True,
117 # Unique objects for special field statuses
118 _FS_UNKNOWN = object()
119 _FS_NODATA = object()
120 _FS_UNAVAIL = object()
121 _FS_OFFLINE = object()
123 #: List of all special status
124 _FS_ALL = frozenset([_FS_UNKNOWN, _FS_NODATA, _FS_UNAVAIL, _FS_OFFLINE])
126 #: VType to QFT mapping
128 # TODO: fix validation of empty strings
129 constants.VTYPE_STRING: QFT_OTHER, # since VTYPE_STRINGs can be empty
130 constants.VTYPE_MAYBE_STRING: QFT_OTHER,
131 constants.VTYPE_BOOL: QFT_BOOL,
132 constants.VTYPE_SIZE: QFT_UNIT,
133 constants.VTYPE_INT: QFT_NUMBER,
136 _SERIAL_NO_DOC = "%s object serial number, incremented on each modification"
139 def _GetUnknownField(ctx, item): # pylint: disable-msg=W0613
140 """Gets the contents of an unknown field.
146 def _GetQueryFields(fielddefs, selected):
147 """Calculates the internal list of selected fields.
149 Unknown fields are returned as L{constants.QFT_UNKNOWN}.
151 @type fielddefs: dict
152 @param fielddefs: Field definitions
153 @type selected: list of strings
154 @param selected: List of selected fields
159 for name in selected:
161 fdef = fielddefs[name]
163 fdef = (_MakeField(name, name, QFT_UNKNOWN, "Unknown field '%s'" % name),
164 None, 0, _GetUnknownField)
166 assert len(fdef) == 4
173 def GetAllFields(fielddefs):
174 """Extract L{objects.QueryFieldDefinition} from field definitions.
176 @rtype: list of L{objects.QueryFieldDefinition}
179 return [fdef for (fdef, _, _, _) in fielddefs]
183 """Class for filter analytics.
185 When filters are used, the user of the L{Query} class usually doesn't know
186 exactly which items will be necessary for building the result. It therefore
187 has to prepare and compute the input data for potentially returning
190 There are two ways to optimize this. The first, and simpler, is to assign
191 each field a group of data, so that the caller can determine which
192 computations are necessary depending on the data groups requested. The list
193 of referenced groups must also be computed for fields referenced in the
196 The second is restricting the items based on a primary key. The primary key
197 is usually a unique name (e.g. a node name). This class extracts all
198 referenced names from a filter. If it encounters any filter condition which
199 disallows such a list to be determined (e.g. a non-equality filter), all
200 names will be requested.
202 The end-effect is that any operation other than L{qlang.OP_OR} and
203 L{qlang.OP_EQUAL} will make the query more expensive.
206 def __init__(self, namefield):
207 """Initializes this class.
209 @type namefield: string
210 @param namefield: Field caller is interested in
213 self._namefield = namefield
215 #: Whether all names need to be requested (e.g. if a non-equality operator
217 self._allnames = False
219 #: Which names to request
222 #: Data kinds referenced by the filter (used by L{Query.RequestedData})
223 self._datakinds = set()
225 def RequestedNames(self):
226 """Returns all requested values.
228 Returns C{None} if list of values can't be determined (e.g. encountered
229 non-equality operators).
234 if self._allnames or self._names is None:
237 return utils.UniqueSequence(self._names)
239 def ReferencedData(self):
240 """Returns all kinds of data referenced by the filter.
243 return frozenset(self._datakinds)
245 def _NeedAllNames(self):
246 """Changes internal state to request all names.
249 self._allnames = True
252 def NoteLogicOp(self, op):
253 """Called when handling a logic operation.
259 if op != qlang.OP_OR:
262 def NoteUnaryOp(self, op): # pylint: disable-msg=W0613
263 """Called when handling an unary operation.
271 def NoteBinaryOp(self, op, datakind, name, value):
272 """Called when handling a binary operation.
277 @param name: Left-hand side of operator (field name)
278 @param value: Right-hand side of operator
281 if datakind is not None:
282 self._datakinds.add(datakind)
287 # If any operator other than equality was used, all names need to be
289 if op == qlang.OP_EQUAL and name == self._namefield:
290 if self._names is None:
292 self._names.append(value)
297 def _WrapLogicOp(op_fn, sentences, ctx, item):
298 """Wrapper for logic operator functions.
301 return op_fn(fn(ctx, item) for fn in sentences)
304 def _WrapUnaryOp(op_fn, inner, ctx, item):
305 """Wrapper for unary operator functions.
308 return op_fn(inner(ctx, item))
311 def _WrapBinaryOp(op_fn, retrieval_fn, value, ctx, item):
312 """Wrapper for binary operator functions.
315 return op_fn(retrieval_fn(ctx, item), value)
318 def _WrapNot(fn, lhs, rhs):
319 """Negates the result of a wrapped function.
322 return not fn(lhs, rhs)
325 class _FilterCompilerHelper:
326 """Converts a query filter to a callable usable for filtering.
329 # String statement has no effect, pylint: disable-msg=W0105
331 #: How deep filters can be nested
334 # Unique identifiers for operator groups
337 _OPTYPE_BINARY) = range(1, 4)
339 """Functions for equality checks depending on field flags.
341 List of tuples containing flags and a callable receiving the left- and
342 right-hand side of the operator. The flags are an OR-ed value of C{QFF_*}
343 (e.g. L{QFF_HOSTNAME}).
345 Order matters. The first item with flags will be used. Flags are checked
351 lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
352 case_sensitive=False)),
358 Operator as key (C{qlang.OP_*}), value a tuple of operator group
359 (C{_OPTYPE_*}) and a group-specific value:
361 - C{_OPTYPE_LOGIC}: Callable taking any number of arguments; used by
363 - C{_OPTYPE_UNARY}: Always C{None}; details handled by L{_HandleUnaryOp}
364 - C{_OPTYPE_BINARY}: Callable taking exactly two parameters, the left- and
365 right-hand side of the operator, used by L{_HandleBinaryOp}
370 qlang.OP_OR: (_OPTYPE_LOGIC, compat.any),
371 qlang.OP_AND: (_OPTYPE_LOGIC, compat.all),
374 qlang.OP_NOT: (_OPTYPE_UNARY, None),
375 qlang.OP_TRUE: (_OPTYPE_UNARY, None),
378 qlang.OP_EQUAL: (_OPTYPE_BINARY, _EQUALITY_CHECKS),
380 (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn))
381 for (flags, fn) in _EQUALITY_CHECKS]),
382 qlang.OP_GLOB: (_OPTYPE_BINARY, NotImplemented),
383 qlang.OP_REGEXP: (_OPTYPE_BINARY, NotImplemented),
384 qlang.OP_CONTAINS: (_OPTYPE_BINARY, [
385 (None, operator.contains),
389 def __init__(self, fields):
390 """Initializes this class.
392 @param fields: Field definitions (return value of L{_PrepareFieldList})
395 self._fields = fields
397 self._op_handler = None
399 def __call__(self, hints, filter_):
400 """Converts a query filter into a callable function.
402 @type hints: L{_FilterHints} or None
403 @param hints: Callbacks doing analysis on filter
405 @param filter_: Filter structure
407 @return: Function receiving context and item as parameters, returning
408 boolean as to whether item matches filter
413 (self._HandleLogicOp, getattr(hints, "NoteLogicOp", None)),
415 (self._HandleUnaryOp, getattr(hints, "NoteUnaryOp", None)),
417 (self._HandleBinaryOp, getattr(hints, "NoteBinaryOp", None)),
421 filter_fn = self._Compile(filter_, 0)
423 self._op_handler = None
427 def _Compile(self, filter_, level):
428 """Inner function for converting filters.
430 Calls the correct handler functions for the top-level operator. This
431 function is called recursively (e.g. for logic operators).
434 if not (isinstance(filter_, (list, tuple)) and filter_):
435 raise errors.ParameterError("Invalid filter on level %s" % level)
438 if level >= self._LEVELS_MAX:
439 raise errors.ParameterError("Only up to %s levels are allowed (filter"
440 " nested too deep)" % self._LEVELS_MAX)
442 # Create copy to be modified
443 operands = filter_[:]
447 (kind, op_data) = self._OPS[op]
449 raise errors.ParameterError("Unknown operator '%s'" % op)
451 (handler, hints_cb) = self._op_handler[kind]
453 return handler(hints_cb, level, op, op_data, operands)
455 def _LookupField(self, name):
456 """Returns a field definition by name.
460 return self._fields[name]
462 raise errors.ParameterError("Unknown field '%s'" % name)
464 def _HandleLogicOp(self, hints_fn, level, op, op_fn, operands):
465 """Handles logic operators.
467 @type hints_fn: callable
468 @param hints_fn: Callback doing some analysis on the filter
470 @param level: Current depth
473 @type op_fn: callable
474 @param op_fn: Function implementing operator
476 @param operands: List of operands
482 return compat.partial(_WrapLogicOp, op_fn,
483 [self._Compile(op, level + 1) for op in operands])
485 def _HandleUnaryOp(self, hints_fn, level, op, op_fn, operands):
486 """Handles unary operators.
488 @type hints_fn: callable
489 @param hints_fn: Callback doing some analysis on the filter
491 @param level: Current depth
494 @type op_fn: callable
495 @param op_fn: Function implementing operator
497 @param operands: List of operands
505 if len(operands) != 1:
506 raise errors.ParameterError("Unary operator '%s' expects exactly one"
509 if op == qlang.OP_TRUE:
510 (_, _, _, retrieval_fn) = self._LookupField(operands[0])
512 op_fn = operator.truth
514 elif op == qlang.OP_NOT:
515 op_fn = operator.not_
516 arg = self._Compile(operands[0], level + 1)
518 raise errors.ProgrammerError("Can't handle operator '%s'" % op)
520 return compat.partial(_WrapUnaryOp, op_fn, arg)
522 def _HandleBinaryOp(self, hints_fn, level, op, op_data, operands):
523 """Handles binary operators.
525 @type hints_fn: callable
526 @param hints_fn: Callback doing some analysis on the filter
528 @param level: Current depth
531 @param op_data: Functions implementing operators
533 @param operands: List of operands
536 # Unused arguments, pylint: disable-msg=W0613
538 (name, value) = operands
539 except (ValueError, TypeError):
540 raise errors.ParameterError("Invalid binary operator, expected exactly"
543 (fdef, datakind, field_flags, retrieval_fn) = self._LookupField(name)
545 assert fdef.kind != QFT_UNKNOWN
547 # TODO: Type conversions?
549 verify_fn = _VERIFY_FN[fdef.kind]
550 if not verify_fn(value):
551 raise errors.ParameterError("Unable to compare field '%s' (type '%s')"
552 " with '%s', expected %s" %
553 (name, fdef.kind, value.__class__.__name__,
557 hints_fn(op, datakind, name, value)
559 for (fn_flags, fn) in op_data:
560 if fn_flags is None or fn_flags & field_flags:
561 return compat.partial(_WrapBinaryOp, fn, retrieval_fn, value)
563 raise errors.ProgrammerError("Unable to find operator implementation"
564 " (op '%s', flags %s)" % (op, field_flags))
567 def _CompileFilter(fields, hints, filter_):
568 """Converts a query filter into a callable function.
570 See L{_FilterCompilerHelper} for details.
575 return _FilterCompilerHelper(fields)(hints, filter_)
579 def __init__(self, fieldlist, selected, filter_=None, namefield=None):
580 """Initializes this class.
582 The field definition is a dictionary with the field's name as a key and a
583 tuple containing, in order, the field definition object
584 (L{objects.QueryFieldDefinition}, the data kind to help calling code
585 collect data and a retrieval function. The retrieval function is called
586 with two parameters, in order, the data container and the item in container
587 (see L{Query.Query}).
589 Users of this class can call L{RequestedData} before preparing the data
590 container to determine what data is needed.
592 @type fieldlist: dictionary
593 @param fieldlist: Field definitions
594 @type selected: list of strings
595 @param selected: List of selected fields
598 assert namefield is None or namefield in fieldlist
600 self._fields = _GetQueryFields(fieldlist, selected)
602 self._filter_fn = None
603 self._requested_names = None
604 self._filter_datakinds = frozenset()
606 if filter_ is not None:
607 # Collect requested names if wanted
609 hints = _FilterHints(namefield)
613 # Build filter function
614 self._filter_fn = _CompileFilter(fieldlist, hints, filter_)
616 self._requested_names = hints.RequestedNames()
617 self._filter_datakinds = hints.ReferencedData()
619 if namefield is None:
622 (_, _, _, self._name_fn) = fieldlist[namefield]
624 def RequestedNames(self):
625 """Returns all names referenced in the filter.
627 If there is no filter or operators are preventing determining the exact
628 names, C{None} is returned.
631 return self._requested_names
633 def RequestedData(self):
634 """Gets requested kinds of data.
639 return (self._filter_datakinds |
640 frozenset(datakind for (_, datakind, _, _) in self._fields
641 if datakind is not None))
644 """Returns the list of fields for this query.
646 Includes unknown fields.
648 @rtype: List of L{objects.QueryFieldDefinition}
651 return GetAllFields(self._fields)
653 def Query(self, ctx, sort_by_name=True):
656 @param ctx: Data container passed to field retrieval functions, must
657 support iteration using C{__iter__}
658 @type sort_by_name: boolean
659 @param sort_by_name: Whether to sort by name or keep the input data's
663 sort = (self._name_fn and sort_by_name)
667 for idx, item in enumerate(ctx):
668 if not (self._filter_fn is None or self._filter_fn(ctx, item)):
671 row = [_ProcessResult(fn(ctx, item)) for (_, _, _, fn) in self._fields]
675 _VerifyResultRow(self._fields, row)
678 (status, name) = _ProcessResult(self._name_fn(ctx, item))
679 assert status == constants.RS_NORMAL
680 # TODO: Are there cases where we wouldn't want to use NiceSort?
681 result.append((utils.NiceSortKey(name), idx, row))
688 # TODO: Would "heapq" be more efficient than sorting?
690 # Sorting in-place instead of using "sorted()"
693 assert not result or (len(result[0]) == 3 and len(result[-1]) == 3)
695 return map(operator.itemgetter(2), result)
697 def OldStyleQuery(self, ctx, sort_by_name=True):
698 """Query with "old" query result format.
700 See L{Query.Query} for arguments.
703 unknown = set(fdef.name for (fdef, _, _, _) in self._fields
704 if fdef.kind == QFT_UNKNOWN)
706 raise errors.OpPrereqError("Unknown output fields selected: %s" %
707 (utils.CommaJoin(unknown), ),
710 return [[value for (_, value) in row]
711 for row in self.Query(ctx, sort_by_name=sort_by_name)]
714 def _ProcessResult(value):
715 """Converts result values into externally-visible ones.
718 if value is _FS_UNKNOWN:
719 return (RS_UNKNOWN, None)
720 elif value is _FS_NODATA:
721 return (RS_NODATA, None)
722 elif value is _FS_UNAVAIL:
723 return (RS_UNAVAIL, None)
724 elif value is _FS_OFFLINE:
725 return (RS_OFFLINE, None)
727 return (RS_NORMAL, value)
730 def _VerifyResultRow(fields, row):
731 """Verifies the contents of a query result row.
734 @param fields: Field definitions for result
735 @type row: list of tuples
739 assert len(row) == len(fields)
741 for ((status, value), (fdef, _, _, _)) in zip(row, fields):
742 if status == RS_NORMAL:
743 if not _VERIFY_FN[fdef.kind](value):
744 errs.append("normal field %s fails validation (value is %s)" %
746 elif value is not None:
747 errs.append("abnormal field %s has a non-None value" % fdef.name)
748 assert not errs, ("Failed validation: %s in row %s" %
749 (utils.CommaJoin(errors), row))
752 def _PrepareFieldList(fields, aliases):
753 """Prepares field list for use by L{Query}.
755 Converts the list to a dictionary and does some verification.
757 @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data
758 kind, retrieval function)
759 @param fields: List of fields, see L{Query.__init__} for a better
761 @type aliases: list of tuples; (alias, target)
762 @param aliases: list of tuples containing aliases; for each
763 alias/target pair, a duplicate will be created in the field list
765 @return: Field dictionary for L{Query}
769 duplicates = utils.FindDuplicates(fdef.title.lower()
770 for (fdef, _, _, _) in fields)
771 assert not duplicates, "Duplicate title(s) found: %r" % duplicates
776 (fdef, _, flags, fn) = field
778 assert fdef.name and fdef.title, "Name and title are required"
779 assert FIELD_NAME_RE.match(fdef.name)
780 assert TITLE_RE.match(fdef.title)
781 assert (DOC_RE.match(fdef.doc) and len(fdef.doc.splitlines()) == 1 and
782 fdef.doc.strip() == fdef.doc), \
783 "Invalid description for field '%s'" % fdef.name
785 assert fdef.name not in result, \
786 "Duplicate field name '%s' found" % fdef.name
787 assert (flags & ~QFF_ALL) == 0, "Unknown flags for field '%s'" % fdef.name
789 result[fdef.name] = field
791 for alias, target in aliases:
792 assert alias not in result, "Alias %s overrides an existing field" % alias
793 assert target in result, "Missing target %s for alias %s" % (target, alias)
794 (fdef, k, flags, fn) = result[target]
797 result[alias] = (fdef, k, flags, fn)
799 assert len(result) == len(fields) + len(aliases)
800 assert compat.all(name == fdef.name
801 for (name, (fdef, _, _, _)) in result.items())
806 def GetQueryResponse(query, ctx, sort_by_name=True):
807 """Prepares the response for a query.
809 @type query: L{Query}
810 @param ctx: Data container, see L{Query.Query}
811 @type sort_by_name: boolean
812 @param sort_by_name: Whether to sort by name or keep the input data's
816 return objects.QueryResponse(data=query.Query(ctx, sort_by_name=sort_by_name),
817 fields=query.GetFields()).ToDict()
820 def QueryFields(fielddefs, selected):
821 """Returns list of available fields.
823 @type fielddefs: dict
824 @param fielddefs: Field definitions
825 @type selected: list of strings
826 @param selected: List of selected fields
827 @return: List of L{objects.QueryFieldDefinition}
831 # Client requests all fields, sort by name
832 fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
833 key=operator.attrgetter("name"))
835 # Keep order as requested by client
836 fdefs = Query(fielddefs, selected).GetFields()
838 return objects.QueryFieldsResponse(fields=fdefs).ToDict()
841 def _MakeField(name, title, kind, doc):
842 """Wrapper for creating L{objects.QueryFieldDefinition} instances.
844 @param name: Field name as a regular expression
845 @param title: Human-readable title
846 @param kind: Field type
847 @param doc: Human-readable description
850 return objects.QueryFieldDefinition(name=name, title=title, kind=kind,
854 def _GetNodeRole(node, master_name):
855 """Determine node role.
857 @type node: L{objects.Node}
858 @param node: Node object
859 @type master_name: string
860 @param master_name: Master node name
863 if node.name == master_name:
864 return constants.NR_MASTER
865 elif node.master_candidate:
866 return constants.NR_MCANDIDATE
868 return constants.NR_DRAINED
870 return constants.NR_OFFLINE
872 return constants.NR_REGULAR
875 def _GetItemAttr(attr):
876 """Returns a field function to return an attribute of the item.
878 @param attr: Attribute name
881 getter = operator.attrgetter(attr)
882 return lambda _, item: getter(item)
885 def _ConvWrapInner(convert, fn, ctx, item):
886 """Wrapper for converting values.
888 @param convert: Conversion function receiving value as single parameter
889 @param fn: Retrieval function
892 value = fn(ctx, item)
894 # Is the value an abnormal status?
895 if compat.any(value is fs for fs in _FS_ALL):
899 # TODO: Should conversion function also receive context, item or both?
900 return convert(value)
903 def _ConvWrap(convert, fn):
904 """Convenience wrapper for L{_ConvWrapInner}.
906 @param convert: Conversion function receiving value as single parameter
907 @param fn: Retrieval function
910 return compat.partial(_ConvWrapInner, convert, fn)
913 def _GetItemTimestamp(getter):
914 """Returns function for getting timestamp of item.
916 @type getter: callable
917 @param getter: Function to retrieve timestamp attribute
921 """Returns a timestamp of item.
924 timestamp = getter(item)
925 if timestamp is None:
926 # Old configs might not have all timestamps
934 def _GetItemTimestampFields(datatype):
935 """Returns common timestamp fields.
937 @param datatype: Field data type for use by L{Query.RequestedData}
941 (_MakeField("ctime", "CTime", QFT_TIMESTAMP, "Creation timestamp"),
942 datatype, 0, _GetItemTimestamp(operator.attrgetter("ctime"))),
943 (_MakeField("mtime", "MTime", QFT_TIMESTAMP, "Modification timestamp"),
944 datatype, 0, _GetItemTimestamp(operator.attrgetter("mtime"))),
949 """Data container for node data queries.
952 def __init__(self, nodes, live_data, master_name, node_to_primary,
953 node_to_secondary, groups, oob_support, cluster):
954 """Initializes this class.
958 self.live_data = live_data
959 self.master_name = master_name
960 self.node_to_primary = node_to_primary
961 self.node_to_secondary = node_to_secondary
963 self.oob_support = oob_support
964 self.cluster = cluster
966 # Used for individual rows
967 self.curlive_data = None
970 """Iterate over all nodes.
972 This function has side-effects and only one instance of the resulting
973 generator should be used at a time.
976 for node in self.nodes:
978 self.curlive_data = self.live_data.get(node.name, None)
980 self.curlive_data = None
984 #: Fields that are direct attributes of an L{objects.Node} object
985 _NODE_SIMPLE_FIELDS = {
986 "drained": ("Drained", QFT_BOOL, 0, "Whether node is drained"),
987 "master_candidate": ("MasterC", QFT_BOOL, 0,
988 "Whether node is a master candidate"),
989 "master_capable": ("MasterCapable", QFT_BOOL, 0,
990 "Whether node can become a master candidate"),
991 "name": ("Node", QFT_TEXT, QFF_HOSTNAME, "Node name"),
992 "offline": ("Offline", QFT_BOOL, 0, "Whether node is marked offline"),
993 "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Node"),
994 "uuid": ("UUID", QFT_TEXT, 0, "Node UUID"),
995 "vm_capable": ("VMCapable", QFT_BOOL, 0, "Whether node can host instances"),
999 #: Fields requiring talking to the node
1000 # Note that none of these are available for non-vm_capable nodes
1001 _NODE_LIVE_FIELDS = {
1002 "bootid": ("BootID", QFT_TEXT, "bootid",
1003 "Random UUID renewed for each system reboot, can be used"
1004 " for detecting reboots by tracking changes"),
1005 "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes",
1006 "Number of NUMA domains on node (if exported by hypervisor)"),
1007 "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets",
1008 "Number of physical CPU sockets (if exported by hypervisor)"),
1009 "ctotal": ("CTotal", QFT_NUMBER, "cpu_total", "Number of logical processors"),
1010 "dfree": ("DFree", QFT_UNIT, "vg_free",
1011 "Available disk space in volume group"),
1012 "dtotal": ("DTotal", QFT_UNIT, "vg_size",
1013 "Total disk space in volume group used for instance disk"
1015 "mfree": ("MFree", QFT_UNIT, "memory_free",
1016 "Memory available for instance allocations"),
1017 "mnode": ("MNode", QFT_UNIT, "memory_dom0",
1018 "Amount of memory used by node (dom0 for Xen)"),
1019 "mtotal": ("MTotal", QFT_UNIT, "memory_total",
1020 "Total amount of memory of physical machine"),
1025 """Build function for calling another function with an node group.
1027 @param cb: The callback to be called with the nodegroup
1031 """Get group data for a node.
1033 @type ctx: L{NodeQueryData}
1034 @type inst: L{objects.Node}
1035 @param inst: Node object
1038 ng = ctx.groups.get(node.group, None)
1040 # Nodes always have a group, or the configuration is corrupt
1043 return cb(ctx, node, ng)
1048 def _GetNodeGroup(ctx, node, ng): # pylint: disable-msg=W0613
1049 """Returns the name of a node's group.
1051 @type ctx: L{NodeQueryData}
1052 @type node: L{objects.Node}
1053 @param node: Node object
1054 @type ng: L{objects.NodeGroup}
1055 @param ng: The node group this node belongs to
1061 def _GetNodePower(ctx, node):
1062 """Returns the node powered state
1064 @type ctx: L{NodeQueryData}
1065 @type node: L{objects.Node}
1066 @param node: Node object
1069 if ctx.oob_support[node.name]:
1075 def _GetNdParams(ctx, node, ng):
1076 """Returns the ndparams for this node.
1078 @type ctx: L{NodeQueryData}
1079 @type node: L{objects.Node}
1080 @param node: Node object
1081 @type ng: L{objects.NodeGroup}
1082 @param ng: The node group this node belongs to
1085 return ctx.cluster.SimpleFillND(ng.FillND(node))
1088 def _GetLiveNodeField(field, kind, ctx, node):
1089 """Gets the value of a "live" field from L{NodeQueryData}.
1091 @param field: Live field name
1092 @param kind: Data kind, one of L{constants.QFT_ALL}
1093 @type ctx: L{NodeQueryData}
1094 @type node: L{objects.Node}
1095 @param node: Node object
1101 if not node.vm_capable:
1104 if not ctx.curlive_data:
1108 value = ctx.curlive_data[field]
1112 if kind == QFT_TEXT:
1115 assert kind in (QFT_NUMBER, QFT_UNIT)
1117 # Try to convert into number
1120 except (ValueError, TypeError):
1121 logging.exception("Failed to convert node field '%s' (value %r) to int",
1126 def _BuildNodeFields():
1127 """Builds list of fields for node queries.
1131 (_MakeField("pip", "PrimaryIP", QFT_TEXT, "Primary IP address"),
1132 NQ_CONFIG, 0, _GetItemAttr("primary_ip")),
1133 (_MakeField("sip", "SecondaryIP", QFT_TEXT, "Secondary IP address"),
1134 NQ_CONFIG, 0, _GetItemAttr("secondary_ip")),
1135 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0,
1136 lambda ctx, node: list(node.GetTags())),
1137 (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"),
1138 NQ_CONFIG, 0, lambda ctx, node: node.name == ctx.master_name),
1139 (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0,
1140 _GetGroup(_GetNodeGroup)),
1141 (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"),
1142 NQ_CONFIG, 0, _GetItemAttr("group")),
1143 (_MakeField("powered", "Powered", QFT_BOOL,
1144 "Whether node is thought to be powered on"),
1145 NQ_OOB, 0, _GetNodePower),
1146 (_MakeField("ndparams", "NodeParameters", QFT_OTHER,
1147 "Merged node parameters"),
1148 NQ_GROUP, 0, _GetGroup(_GetNdParams)),
1149 (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER,
1150 "Custom node parameters"),
1151 NQ_GROUP, 0, _GetItemAttr("ndparams")),
1155 role_values = (constants.NR_MASTER, constants.NR_MCANDIDATE,
1156 constants.NR_REGULAR, constants.NR_DRAINED,
1157 constants.NR_OFFLINE)
1158 role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate,"
1159 " \"%s\" for regular, \"%s\" for a drained, \"%s\" for offline" %
1161 fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
1162 lambda ctx, node: _GetNodeRole(node, ctx.master_name)))
1163 assert set(role_values) == constants.NR_ALL
1165 def _GetLength(getter):
1166 return lambda ctx, node: len(getter(ctx)[node.name])
1168 def _GetList(getter):
1169 return lambda ctx, node: list(getter(ctx)[node.name])
1171 # Add fields operating on instance lists
1172 for prefix, titleprefix, docword, getter in \
1173 [("p", "Pri", "primary", operator.attrgetter("node_to_primary")),
1174 ("s", "Sec", "secondary", operator.attrgetter("node_to_secondary"))]:
1175 # TODO: Allow filterting by hostname in list
1177 (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER,
1178 "Number of instances with this node as %s" % docword),
1179 NQ_INST, 0, _GetLength(getter)),
1180 (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
1182 "List of instances with this node as %s" % docword),
1183 NQ_INST, 0, _GetList(getter)),
1188 (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1189 for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()
1192 # Add fields requiring live data
1194 (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1195 compat.partial(_GetLiveNodeField, nfield, kind))
1196 for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()
1200 fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1202 return _PrepareFieldList(fields, [])
1205 class InstanceQueryData:
1206 """Data container for instance data queries.
1209 def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
1210 live_data, wrongnode_inst, console):
1211 """Initializes this class.
1213 @param instances: List of instance objects
1214 @param cluster: Cluster object
1215 @type disk_usage: dict; instance name as key
1216 @param disk_usage: Per-instance disk usage
1217 @type offline_nodes: list of strings
1218 @param offline_nodes: List of offline nodes
1219 @type bad_nodes: list of strings
1220 @param bad_nodes: List of faulty nodes
1221 @type live_data: dict; instance name as key
1222 @param live_data: Per-instance live data
1223 @type wrongnode_inst: set
1224 @param wrongnode_inst: Set of instances running on wrong node(s)
1225 @type console: dict; instance name as key
1226 @param console: Per-instance console information
1229 assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
1230 "Offline nodes not included in bad nodes"
1231 assert not (set(live_data.keys()) & set(bad_nodes)), \
1232 "Found live data for bad or offline nodes"
1234 self.instances = instances
1235 self.cluster = cluster
1236 self.disk_usage = disk_usage
1237 self.offline_nodes = offline_nodes
1238 self.bad_nodes = bad_nodes
1239 self.live_data = live_data
1240 self.wrongnode_inst = wrongnode_inst
1241 self.console = console
1243 # Used for individual rows
1244 self.inst_hvparams = None
1245 self.inst_beparams = None
1246 self.inst_nicparams = None
1249 """Iterate over all instances.
1251 This function has side-effects and only one instance of the resulting
1252 generator should be used at a time.
1255 for inst in self.instances:
1256 self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1257 self.inst_beparams = self.cluster.FillBE(inst)
1258 self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1259 for nic in inst.nics]
1264 def _GetInstOperState(ctx, inst):
1265 """Get instance's operational status.
1267 @type ctx: L{InstanceQueryData}
1268 @type inst: L{objects.Instance}
1269 @param inst: Instance object
1272 # Can't use RS_OFFLINE here as it would describe the instance to
1273 # be offline when we actually don't know due to missing data
1274 if inst.primary_node in ctx.bad_nodes:
1277 return bool(ctx.live_data.get(inst.name))
1280 def _GetInstLiveData(name):
1281 """Build function for retrieving live data.
1284 @param name: Live data field name
1288 """Get live data for an instance.
1290 @type ctx: L{InstanceQueryData}
1291 @type inst: L{objects.Instance}
1292 @param inst: Instance object
1295 if (inst.primary_node in ctx.bad_nodes or
1296 inst.primary_node in ctx.offline_nodes):
1297 # Can't use RS_OFFLINE here as it would describe the instance to be
1298 # offline when we actually don't know due to missing data
1301 if inst.name in ctx.live_data:
1302 data = ctx.live_data[inst.name]
1311 def _GetInstStatus(ctx, inst):
1312 """Get instance status.
1314 @type ctx: L{InstanceQueryData}
1315 @type inst: L{objects.Instance}
1316 @param inst: Instance object
1319 if inst.primary_node in ctx.offline_nodes:
1320 return constants.INSTST_NODEOFFLINE
1322 if inst.primary_node in ctx.bad_nodes:
1323 return constants.INSTST_NODEDOWN
1325 if bool(ctx.live_data.get(inst.name)):
1326 if inst.name in ctx.wrongnode_inst:
1327 return constants.INSTST_WRONGNODE
1329 return constants.INSTST_RUNNING
1331 return constants.INSTST_ERRORUP
1334 return constants.INSTST_ERRORDOWN
1336 return constants.INSTST_ADMINDOWN
1339 def _GetInstDiskSize(index):
1340 """Build function for retrieving disk size.
1343 @param index: Disk index
1347 """Get size of a disk.
1349 @type inst: L{objects.Instance}
1350 @param inst: Instance object
1354 return inst.disks[index].size
1361 def _GetInstNic(index, cb):
1362 """Build function for calling another function with an instance NIC.
1365 @param index: NIC index
1371 """Call helper function with instance NIC.
1373 @type ctx: L{InstanceQueryData}
1374 @type inst: L{objects.Instance}
1375 @param inst: Instance object
1379 nic = inst.nics[index]
1383 return cb(ctx, index, nic)
1388 def _GetInstNicIp(ctx, _, nic): # pylint: disable-msg=W0613
1389 """Get a NIC's IP address.
1391 @type ctx: L{InstanceQueryData}
1392 @type nic: L{objects.NIC}
1393 @param nic: NIC object
1402 def _GetInstNicBridge(ctx, index, _):
1403 """Get a NIC's bridge.
1405 @type ctx: L{InstanceQueryData}
1407 @param index: NIC index
1410 assert len(ctx.inst_nicparams) >= index
1412 nicparams = ctx.inst_nicparams[index]
1414 if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1415 return nicparams[constants.NIC_LINK]
1420 def _GetInstAllNicBridges(ctx, inst):
1421 """Get all network bridges for an instance.
1423 @type ctx: L{InstanceQueryData}
1424 @type inst: L{objects.Instance}
1425 @param inst: Instance object
1428 assert len(ctx.inst_nicparams) == len(inst.nics)
1432 for nicp in ctx.inst_nicparams:
1433 if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1434 result.append(nicp[constants.NIC_LINK])
1438 assert len(result) == len(inst.nics)
1443 def _GetInstNicParam(name):
1444 """Build function for retrieving a NIC parameter.
1447 @param name: Parameter name
1450 def fn(ctx, index, _):
1451 """Get a NIC's bridge.
1453 @type ctx: L{InstanceQueryData}
1454 @type inst: L{objects.Instance}
1455 @param inst: Instance object
1456 @type nic: L{objects.NIC}
1457 @param nic: NIC object
1460 assert len(ctx.inst_nicparams) >= index
1461 return ctx.inst_nicparams[index][name]
1466 def _GetInstanceNetworkFields():
1467 """Get instance fields involving network interfaces.
1469 @return: Tuple containing list of field definitions used as input for
1470 L{_PrepareFieldList} and a list of aliases
1473 nic_mac_fn = lambda ctx, _, nic: nic.mac
1474 nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1475 nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1479 (_MakeField("nic.count", "NICs", QFT_NUMBER,
1480 "Number of network interfaces"),
1481 IQ_CONFIG, 0, lambda ctx, inst: len(inst.nics)),
1482 (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER,
1483 "List containing each network interface's MAC address"),
1484 IQ_CONFIG, 0, lambda ctx, inst: [nic.mac for nic in inst.nics]),
1485 (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
1486 "List containing each network interface's IP address"),
1487 IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
1488 (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
1489 "List containing each network interface's mode"), IQ_CONFIG, 0,
1490 lambda ctx, inst: [nicp[constants.NIC_MODE]
1491 for nicp in ctx.inst_nicparams]),
1492 (_MakeField("nic.links", "NIC_links", QFT_OTHER,
1493 "List containing each network interface's link"), IQ_CONFIG, 0,
1494 lambda ctx, inst: [nicp[constants.NIC_LINK]
1495 for nicp in ctx.inst_nicparams]),
1496 (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
1497 "List containing each network interface's bridge"),
1498 IQ_CONFIG, 0, _GetInstAllNicBridges),
1502 for i in range(constants.MAX_NICS):
1503 numtext = utils.FormatOrdinal(i + 1)
1505 (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT,
1506 "IP address of %s network interface" % numtext),
1507 IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)),
1508 (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
1509 "MAC address of %s network interface" % numtext),
1510 IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
1511 (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
1512 "Mode of %s network interface" % numtext),
1513 IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
1514 (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT,
1515 "Link of %s network interface" % numtext),
1516 IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)),
1517 (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
1518 "Bridge of %s network interface" % numtext),
1519 IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
1523 # Legacy fields for first NIC
1525 ("mac", "nic.mac/0"),
1526 ("bridge", "nic.bridge/0"),
1527 ("nic_mode", "nic.mode/0"),
1528 ("nic_link", "nic.link/0"),
1531 return (fields, aliases)
1534 def _GetInstDiskUsage(ctx, inst):
1535 """Get disk usage for an instance.
1537 @type ctx: L{InstanceQueryData}
1538 @type inst: L{objects.Instance}
1539 @param inst: Instance object
1542 usage = ctx.disk_usage[inst.name]
1550 def _GetInstanceConsole(ctx, inst):
1551 """Get console information for instance.
1553 @type ctx: L{InstanceQueryData}
1554 @type inst: L{objects.Instance}
1555 @param inst: Instance object
1558 consinfo = ctx.console[inst.name]
1560 if consinfo is None:
1566 def _GetInstanceDiskFields():
1567 """Get instance fields involving disks.
1569 @return: List of field definitions used as input for L{_PrepareFieldList}
1573 (_MakeField("disk_usage", "DiskUsage", QFT_UNIT,
1574 "Total disk space used by instance on each of its nodes;"
1575 " this is not the disk size visible to the instance, but"
1576 " the usage on the node"),
1577 IQ_DISKUSAGE, 0, _GetInstDiskUsage),
1578 (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"),
1579 IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
1580 (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
1581 IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
1586 (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1587 "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
1588 IQ_CONFIG, 0, _GetInstDiskSize(i))
1589 for i in range(constants.MAX_DISKS)
1595 def _GetInstanceParameterFields():
1596 """Get instance fields involving parameters.
1598 @return: List of field definitions used as input for L{_PrepareFieldList}
1601 # TODO: Consider moving titles closer to constants
1603 constants.BE_AUTO_BALANCE: "Auto_balance",
1604 constants.BE_MEMORY: "ConfigMemory",
1605 constants.BE_VCPUS: "ConfigVCPUs",
1609 constants.HV_ACPI: "ACPI",
1610 constants.HV_BOOT_ORDER: "Boot_order",
1611 constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1612 constants.HV_DISK_TYPE: "Disk_type",
1613 constants.HV_INITRD_PATH: "Initrd_path",
1614 constants.HV_KERNEL_PATH: "Kernel_path",
1615 constants.HV_NIC_TYPE: "NIC_type",
1616 constants.HV_PAE: "PAE",
1617 constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1622 (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
1623 "Hypervisor parameters"),
1624 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
1625 (_MakeField("beparams", "BackendParameters", QFT_OTHER,
1626 "Backend parameters"),
1627 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
1629 # Unfilled parameters
1630 (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
1631 "Custom hypervisor parameters"),
1632 IQ_CONFIG, 0, _GetItemAttr("hvparams")),
1633 (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
1634 "Custom backend parameters",),
1635 IQ_CONFIG, 0, _GetItemAttr("beparams")),
1636 (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
1637 "Custom network interface parameters"),
1638 IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1642 def _GetInstHvParam(name):
1643 return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1646 (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1647 _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1648 IQ_CONFIG, 0, _GetInstHvParam(name))
1649 for name, kind in constants.HVS_PARAMETER_TYPES.items()
1650 if name not in constants.HVC_GLOBALS
1654 def _GetInstBeParam(name):
1655 return lambda ctx, _: ctx.inst_beparams.get(name, None)
1658 (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1659 _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1660 IQ_CONFIG, 0, _GetInstBeParam(name))
1661 for name, kind in constants.BES_PARAMETER_TYPES.items()
1667 _INST_SIMPLE_FIELDS = {
1668 "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"),
1669 "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"),
1670 "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"),
1671 # Depending on the hypervisor, the port can be None
1672 "network_port": ("Network_port", QFT_OTHER, 0,
1673 "Instance network port if available (e.g. for VNC console)"),
1674 "os": ("OS", QFT_TEXT, 0, "Operating system"),
1675 "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"),
1676 "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"),
1680 def _BuildInstanceFields():
1681 """Builds list of fields for instance queries.
1685 (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
1686 IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")),
1687 # TODO: Allow filtering by secondary node as hostname
1688 (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
1689 "Secondary nodes; usually this will just be one node"),
1690 IQ_CONFIG, 0, lambda ctx, inst: list(inst.secondary_nodes)),
1691 (_MakeField("admin_state", "Autostart", QFT_BOOL,
1692 "Desired state of instance (if set, the instance should be"
1694 IQ_CONFIG, 0, _GetItemAttr("admin_up")),
1695 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
1696 lambda ctx, inst: list(inst.GetTags())),
1697 (_MakeField("console", "Console", QFT_OTHER,
1698 "Instance console information"), IQ_CONSOLE, 0,
1699 _GetInstanceConsole),
1704 (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1705 for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()
1708 # Fields requiring talking to the node
1710 (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
1711 IQ_LIVE, 0, _GetInstOperState),
1712 (_MakeField("oper_ram", "Memory", QFT_UNIT,
1713 "Actual memory usage as seen by hypervisor"),
1714 IQ_LIVE, 0, _GetInstLiveData("memory")),
1715 (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
1716 "Actual number of VCPUs as seen by hypervisor"),
1717 IQ_LIVE, 0, _GetInstLiveData("vcpus")),
1721 status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
1722 constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
1723 constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
1724 constants.INSTST_NODEOFFLINE)
1725 status_doc = ("Instance status; \"%s\" if instance is set to be running"
1726 " and actually is, \"%s\" if instance is stopped and"
1727 " is not running, \"%s\" if instance running, but not on its"
1728 " designated primary node, \"%s\" if instance should be"
1729 " stopped, but is actually running, \"%s\" if instance should"
1730 " run, but doesn't, \"%s\" if instance's primary node is down,"
1731 " \"%s\" if instance's primary node is marked offline" %
1733 fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
1734 IQ_LIVE, 0, _GetInstStatus))
1735 assert set(status_values) == constants.INSTST_ALL, \
1736 "Status documentation mismatch"
1738 (network_fields, network_aliases) = _GetInstanceNetworkFields()
1740 fields.extend(network_fields)
1741 fields.extend(_GetInstanceParameterFields())
1742 fields.extend(_GetInstanceDiskFields())
1743 fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1746 ("vcpus", "be/vcpus"),
1747 ("sda_size", "disk.size/0"),
1748 ("sdb_size", "disk.size/1"),
1751 return _PrepareFieldList(fields, aliases)
1754 class LockQueryData:
1755 """Data container for lock data queries.
1758 def __init__(self, lockdata):
1759 """Initializes this class.
1762 self.lockdata = lockdata
1765 """Iterate over all locks.
1768 return iter(self.lockdata)
1771 def _GetLockOwners(_, data):
1772 """Returns a sorted list of a lock's current owners.
1775 (_, _, owners, _) = data
1778 owners = utils.NiceSort(owners)
1783 def _GetLockPending(_, data):
1784 """Returns a sorted list of a lock's pending acquires.
1787 (_, _, _, pending) = data
1790 pending = [(mode, utils.NiceSort(names))
1791 for (mode, names) in pending]
1796 def _BuildLockFields():
1797 """Builds list of fields for lock queries.
1800 return _PrepareFieldList([
1801 # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
1802 (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
1803 lambda ctx, (name, mode, owners, pending): name),
1804 (_MakeField("mode", "Mode", QFT_OTHER,
1805 "Mode in which the lock is currently acquired"
1806 " (exclusive or shared)"),
1807 LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
1808 (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
1809 LQ_OWNER, 0, _GetLockOwners),
1810 (_MakeField("pending", "Pending", QFT_OTHER,
1811 "Threads waiting for the lock"),
1812 LQ_PENDING, 0, _GetLockPending),
1816 class GroupQueryData:
1817 """Data container for node group data queries.
1820 def __init__(self, groups, group_to_nodes, group_to_instances):
1821 """Initializes this class.
1823 @param groups: List of node group objects
1824 @type group_to_nodes: dict; group UUID as key
1825 @param group_to_nodes: Per-group list of nodes
1826 @type group_to_instances: dict; group UUID as key
1827 @param group_to_instances: Per-group list of (primary) instances
1830 self.groups = groups
1831 self.group_to_nodes = group_to_nodes
1832 self.group_to_instances = group_to_instances
1835 """Iterate over all node groups.
1838 return iter(self.groups)
1841 _GROUP_SIMPLE_FIELDS = {
1842 "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
1843 "name": ("Group", QFT_TEXT, "Group name"),
1844 "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
1845 "uuid": ("UUID", QFT_TEXT, "Group UUID"),
1846 "ndparams": ("NDParams", QFT_OTHER, "Node parameters"),
1850 def _BuildGroupFields():
1851 """Builds list of fields for node group queries.
1855 fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
1857 for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
1859 def _GetLength(getter):
1860 return lambda ctx, group: len(getter(ctx)[group.uuid])
1862 def _GetSortedList(getter):
1863 return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
1865 group_to_nodes = operator.attrgetter("group_to_nodes")
1866 group_to_instances = operator.attrgetter("group_to_instances")
1868 # Add fields for nodes
1870 (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
1871 GQ_NODE, 0, _GetLength(group_to_nodes)),
1872 (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
1873 GQ_NODE, 0, _GetSortedList(group_to_nodes)),
1876 # Add fields for instances
1878 (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
1879 "Number of primary instances"),
1880 GQ_INST, 0, _GetLength(group_to_instances)),
1881 (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
1882 "List of primary instances"),
1883 GQ_INST, 0, _GetSortedList(group_to_instances)),
1886 fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1888 return _PrepareFieldList(fields, [])
1891 #: Fields available for node queries
1892 NODE_FIELDS = _BuildNodeFields()
1894 #: Fields available for instance queries
1895 INSTANCE_FIELDS = _BuildInstanceFields()
1897 #: Fields available for lock queries
1898 LOCK_FIELDS = _BuildLockFields()
1900 #: Fields available for node group queries
1901 GROUP_FIELDS = _BuildGroupFields()
1903 #: All available resources
1905 constants.QR_INSTANCE: INSTANCE_FIELDS,
1906 constants.QR_NODE: NODE_FIELDS,
1907 constants.QR_LOCK: LOCK_FIELDS,
1908 constants.QR_GROUP: GROUP_FIELDS,
1911 #: All available field lists
1912 ALL_FIELD_LISTS = ALL_FIELDS.values()