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 _GetNodeHvState(_, node):
1144 """Converts node's hypervisor state for query result.
1147 hv_state = node.hv_state
1149 if hv_state is None:
1152 return dict((name, value.ToDict()) for (name, value) in hv_state.items())
1155 def _GetNodeDiskState(_, node):
1156 """Converts node's disk state for query result.
1159 disk_state = node.disk_state
1161 if disk_state is None:
1164 return dict((disk_kind, dict((name, value.ToDict())
1165 for (name, value) in kind_state.items()))
1166 for (disk_kind, kind_state) in disk_state.items())
1169 def _BuildNodeFields():
1170 """Builds list of fields for node queries.
1174 (_MakeField("pip", "PrimaryIP", QFT_TEXT, "Primary IP address"),
1175 NQ_CONFIG, 0, _GetItemAttr("primary_ip")),
1176 (_MakeField("sip", "SecondaryIP", QFT_TEXT, "Secondary IP address"),
1177 NQ_CONFIG, 0, _GetItemAttr("secondary_ip")),
1178 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0,
1179 lambda ctx, node: list(node.GetTags())),
1180 (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"),
1181 NQ_CONFIG, 0, lambda ctx, node: node.name == ctx.master_name),
1182 (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0,
1183 _GetGroup(_GetNodeGroup)),
1184 (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"),
1185 NQ_CONFIG, 0, _GetItemAttr("group")),
1186 (_MakeField("powered", "Powered", QFT_BOOL,
1187 "Whether node is thought to be powered on"),
1188 NQ_OOB, 0, _GetNodePower),
1189 (_MakeField("ndparams", "NodeParameters", QFT_OTHER,
1190 "Merged node parameters"),
1191 NQ_GROUP, 0, _GetGroup(_GetNdParams)),
1192 (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER,
1193 "Custom node parameters"),
1194 NQ_GROUP, 0, _GetItemAttr("ndparams")),
1195 (_MakeField("hv_state", "HypervisorState", QFT_OTHER, "Hypervisor state"),
1196 NQ_CONFIG, 0, _GetNodeHvState),
1197 (_MakeField("disk_state", "DiskState", QFT_OTHER, "Disk state"),
1198 NQ_CONFIG, 0, _GetNodeDiskState),
1202 role_values = (constants.NR_MASTER, constants.NR_MCANDIDATE,
1203 constants.NR_REGULAR, constants.NR_DRAINED,
1204 constants.NR_OFFLINE)
1205 role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate,"
1206 " \"%s\" for regular, \"%s\" for a drained, \"%s\" for offline" %
1208 fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
1209 lambda ctx, node: _GetNodeRole(node, ctx.master_name)))
1210 assert set(role_values) == constants.NR_ALL
1212 def _GetLength(getter):
1213 return lambda ctx, node: len(getter(ctx)[node.name])
1215 def _GetList(getter):
1216 return lambda ctx, node: list(getter(ctx)[node.name])
1218 # Add fields operating on instance lists
1219 for prefix, titleprefix, docword, getter in \
1220 [("p", "Pri", "primary", operator.attrgetter("node_to_primary")),
1221 ("s", "Sec", "secondary", operator.attrgetter("node_to_secondary"))]:
1222 # TODO: Allow filterting by hostname in list
1224 (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER,
1225 "Number of instances with this node as %s" % docword),
1226 NQ_INST, 0, _GetLength(getter)),
1227 (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
1229 "List of instances with this node as %s" % docword),
1230 NQ_INST, 0, _GetList(getter)),
1235 (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1236 for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()
1239 # Add fields requiring live data
1241 (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1242 compat.partial(_GetLiveNodeField, nfield, kind))
1243 for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()
1247 fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1249 return _PrepareFieldList(fields, [])
1252 class InstanceQueryData:
1253 """Data container for instance data queries.
1256 def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
1257 live_data, wrongnode_inst, console, nodes, groups):
1258 """Initializes this class.
1260 @param instances: List of instance objects
1261 @param cluster: Cluster object
1262 @type disk_usage: dict; instance name as key
1263 @param disk_usage: Per-instance disk usage
1264 @type offline_nodes: list of strings
1265 @param offline_nodes: List of offline nodes
1266 @type bad_nodes: list of strings
1267 @param bad_nodes: List of faulty nodes
1268 @type live_data: dict; instance name as key
1269 @param live_data: Per-instance live data
1270 @type wrongnode_inst: set
1271 @param wrongnode_inst: Set of instances running on wrong node(s)
1272 @type console: dict; instance name as key
1273 @param console: Per-instance console information
1274 @type nodes: dict; node name as key
1275 @param nodes: Node objects
1278 assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
1279 "Offline nodes not included in bad nodes"
1280 assert not (set(live_data.keys()) & set(bad_nodes)), \
1281 "Found live data for bad or offline nodes"
1283 self.instances = instances
1284 self.cluster = cluster
1285 self.disk_usage = disk_usage
1286 self.offline_nodes = offline_nodes
1287 self.bad_nodes = bad_nodes
1288 self.live_data = live_data
1289 self.wrongnode_inst = wrongnode_inst
1290 self.console = console
1292 self.groups = groups
1294 # Used for individual rows
1295 self.inst_hvparams = None
1296 self.inst_beparams = None
1297 self.inst_osparams = None
1298 self.inst_nicparams = None
1301 """Iterate over all instances.
1303 This function has side-effects and only one instance of the resulting
1304 generator should be used at a time.
1307 for inst in self.instances:
1308 self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1309 self.inst_beparams = self.cluster.FillBE(inst)
1310 self.inst_osparams = self.cluster.SimpleFillOS(inst.os, inst.osparams)
1311 self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1312 for nic in inst.nics]
1317 def _GetInstOperState(ctx, inst):
1318 """Get instance's operational status.
1320 @type ctx: L{InstanceQueryData}
1321 @type inst: L{objects.Instance}
1322 @param inst: Instance object
1325 # Can't use RS_OFFLINE here as it would describe the instance to
1326 # be offline when we actually don't know due to missing data
1327 if inst.primary_node in ctx.bad_nodes:
1330 return bool(ctx.live_data.get(inst.name))
1333 def _GetInstLiveData(name):
1334 """Build function for retrieving live data.
1337 @param name: Live data field name
1341 """Get live data for an instance.
1343 @type ctx: L{InstanceQueryData}
1344 @type inst: L{objects.Instance}
1345 @param inst: Instance object
1348 if (inst.primary_node in ctx.bad_nodes or
1349 inst.primary_node in ctx.offline_nodes):
1350 # Can't use RS_OFFLINE here as it would describe the instance to be
1351 # offline when we actually don't know due to missing data
1354 if inst.name in ctx.live_data:
1355 data = ctx.live_data[inst.name]
1364 def _GetInstStatus(ctx, inst):
1365 """Get instance status.
1367 @type ctx: L{InstanceQueryData}
1368 @type inst: L{objects.Instance}
1369 @param inst: Instance object
1372 if inst.primary_node in ctx.offline_nodes:
1373 return constants.INSTST_NODEOFFLINE
1375 if inst.primary_node in ctx.bad_nodes:
1376 return constants.INSTST_NODEDOWN
1378 if bool(ctx.live_data.get(inst.name)):
1379 if inst.name in ctx.wrongnode_inst:
1380 return constants.INSTST_WRONGNODE
1381 elif inst.admin_state == constants.ADMINST_UP:
1382 return constants.INSTST_RUNNING
1384 return constants.INSTST_ERRORUP
1386 if inst.admin_state == constants.ADMINST_UP:
1387 return constants.INSTST_ERRORDOWN
1388 elif inst.admin_state == constants.ADMINST_DOWN:
1389 return constants.INSTST_ADMINDOWN
1391 return constants.INSTST_ADMINOFFLINE
1394 def _GetInstDiskSize(index):
1395 """Build function for retrieving disk size.
1398 @param index: Disk index
1402 """Get size of a disk.
1404 @type inst: L{objects.Instance}
1405 @param inst: Instance object
1409 return inst.disks[index].size
1416 def _GetInstNic(index, cb):
1417 """Build function for calling another function with an instance NIC.
1420 @param index: NIC index
1426 """Call helper function with instance NIC.
1428 @type ctx: L{InstanceQueryData}
1429 @type inst: L{objects.Instance}
1430 @param inst: Instance object
1434 nic = inst.nics[index]
1438 return cb(ctx, index, nic)
1443 def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613
1444 """Get a NIC's IP address.
1446 @type ctx: L{InstanceQueryData}
1447 @type nic: L{objects.NIC}
1448 @param nic: NIC object
1457 def _GetInstNicBridge(ctx, index, _):
1458 """Get a NIC's bridge.
1460 @type ctx: L{InstanceQueryData}
1462 @param index: NIC index
1465 assert len(ctx.inst_nicparams) >= index
1467 nicparams = ctx.inst_nicparams[index]
1469 if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1470 return nicparams[constants.NIC_LINK]
1475 def _GetInstAllNicBridges(ctx, inst):
1476 """Get all network bridges for an instance.
1478 @type ctx: L{InstanceQueryData}
1479 @type inst: L{objects.Instance}
1480 @param inst: Instance object
1483 assert len(ctx.inst_nicparams) == len(inst.nics)
1487 for nicp in ctx.inst_nicparams:
1488 if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1489 result.append(nicp[constants.NIC_LINK])
1493 assert len(result) == len(inst.nics)
1498 def _GetInstNicParam(name):
1499 """Build function for retrieving a NIC parameter.
1502 @param name: Parameter name
1505 def fn(ctx, index, _):
1506 """Get a NIC's bridge.
1508 @type ctx: L{InstanceQueryData}
1509 @type inst: L{objects.Instance}
1510 @param inst: Instance object
1511 @type nic: L{objects.NIC}
1512 @param nic: NIC object
1515 assert len(ctx.inst_nicparams) >= index
1516 return ctx.inst_nicparams[index][name]
1521 def _GetInstanceNetworkFields():
1522 """Get instance fields involving network interfaces.
1524 @return: Tuple containing list of field definitions used as input for
1525 L{_PrepareFieldList} and a list of aliases
1528 nic_mac_fn = lambda ctx, _, nic: nic.mac
1529 nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1530 nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1534 (_MakeField("nic.count", "NICs", QFT_NUMBER,
1535 "Number of network interfaces"),
1536 IQ_CONFIG, 0, lambda ctx, inst: len(inst.nics)),
1537 (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER,
1538 "List containing each network interface's MAC address"),
1539 IQ_CONFIG, 0, lambda ctx, inst: [nic.mac for nic in inst.nics]),
1540 (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
1541 "List containing each network interface's IP address"),
1542 IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
1543 (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
1544 "List containing each network interface's mode"), IQ_CONFIG, 0,
1545 lambda ctx, inst: [nicp[constants.NIC_MODE]
1546 for nicp in ctx.inst_nicparams]),
1547 (_MakeField("nic.links", "NIC_links", QFT_OTHER,
1548 "List containing each network interface's link"), IQ_CONFIG, 0,
1549 lambda ctx, inst: [nicp[constants.NIC_LINK]
1550 for nicp in ctx.inst_nicparams]),
1551 (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
1552 "List containing each network interface's bridge"),
1553 IQ_CONFIG, 0, _GetInstAllNicBridges),
1557 for i in range(constants.MAX_NICS):
1558 numtext = utils.FormatOrdinal(i + 1)
1560 (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT,
1561 "IP address of %s network interface" % numtext),
1562 IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)),
1563 (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
1564 "MAC address of %s network interface" % numtext),
1565 IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
1566 (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
1567 "Mode of %s network interface" % numtext),
1568 IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
1569 (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT,
1570 "Link of %s network interface" % numtext),
1571 IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)),
1572 (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
1573 "Bridge of %s network interface" % numtext),
1574 IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
1578 # Legacy fields for first NIC
1580 ("mac", "nic.mac/0"),
1581 ("bridge", "nic.bridge/0"),
1582 ("nic_mode", "nic.mode/0"),
1583 ("nic_link", "nic.link/0"),
1586 return (fields, aliases)
1589 def _GetInstDiskUsage(ctx, inst):
1590 """Get disk usage for an instance.
1592 @type ctx: L{InstanceQueryData}
1593 @type inst: L{objects.Instance}
1594 @param inst: Instance object
1597 usage = ctx.disk_usage[inst.name]
1605 def _GetInstanceConsole(ctx, inst):
1606 """Get console information for instance.
1608 @type ctx: L{InstanceQueryData}
1609 @type inst: L{objects.Instance}
1610 @param inst: Instance object
1613 consinfo = ctx.console[inst.name]
1615 if consinfo is None:
1621 def _GetInstanceDiskFields():
1622 """Get instance fields involving disks.
1624 @return: List of field definitions used as input for L{_PrepareFieldList}
1628 (_MakeField("disk_usage", "DiskUsage", QFT_UNIT,
1629 "Total disk space used by instance on each of its nodes;"
1630 " this is not the disk size visible to the instance, but"
1631 " the usage on the node"),
1632 IQ_DISKUSAGE, 0, _GetInstDiskUsage),
1633 (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"),
1634 IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
1635 (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
1636 IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
1641 (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1642 "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
1643 IQ_CONFIG, 0, _GetInstDiskSize(i))
1644 for i in range(constants.MAX_DISKS)
1650 def _GetInstanceParameterFields():
1651 """Get instance fields involving parameters.
1653 @return: List of field definitions used as input for L{_PrepareFieldList}
1656 # TODO: Consider moving titles closer to constants
1658 constants.BE_AUTO_BALANCE: "Auto_balance",
1659 constants.BE_MAXMEM: "ConfigMaxMem",
1660 constants.BE_MINMEM: "ConfigMinMem",
1661 constants.BE_VCPUS: "ConfigVCPUs",
1665 constants.HV_ACPI: "ACPI",
1666 constants.HV_BOOT_ORDER: "Boot_order",
1667 constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1668 constants.HV_DISK_TYPE: "Disk_type",
1669 constants.HV_INITRD_PATH: "Initrd_path",
1670 constants.HV_KERNEL_PATH: "Kernel_path",
1671 constants.HV_NIC_TYPE: "NIC_type",
1672 constants.HV_PAE: "PAE",
1673 constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1678 (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
1679 "Hypervisor parameters (merged)"),
1680 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
1681 (_MakeField("beparams", "BackendParameters", QFT_OTHER,
1682 "Backend parameters (merged)"),
1683 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
1684 (_MakeField("osparams", "OpSysParameters", QFT_OTHER,
1685 "Operating system parameters (merged)"),
1686 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_osparams),
1688 # Unfilled parameters
1689 (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
1690 "Custom hypervisor parameters"),
1691 IQ_CONFIG, 0, _GetItemAttr("hvparams")),
1692 (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
1693 "Custom backend parameters",),
1694 IQ_CONFIG, 0, _GetItemAttr("beparams")),
1695 (_MakeField("custom_osparams", "CustomOpSysParameters", QFT_OTHER,
1696 "Custom operating system parameters",),
1697 IQ_CONFIG, 0, _GetItemAttr("osparams")),
1698 (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
1699 "Custom network interface parameters"),
1700 IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1704 def _GetInstHvParam(name):
1705 return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1708 (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1709 _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1710 IQ_CONFIG, 0, _GetInstHvParam(name))
1711 for name, kind in constants.HVS_PARAMETER_TYPES.items()
1712 if name not in constants.HVC_GLOBALS
1716 def _GetInstBeParam(name):
1717 return lambda ctx, _: ctx.inst_beparams.get(name, None)
1720 (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1721 _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1722 IQ_CONFIG, 0, _GetInstBeParam(name))
1723 for name, kind in constants.BES_PARAMETER_TYPES.items()
1729 _INST_SIMPLE_FIELDS = {
1730 "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"),
1731 "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"),
1732 "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"),
1733 # Depending on the hypervisor, the port can be None
1734 "network_port": ("Network_port", QFT_OTHER, 0,
1735 "Instance network port if available (e.g. for VNC console)"),
1736 "os": ("OS", QFT_TEXT, 0, "Operating system"),
1737 "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"),
1738 "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"),
1742 def _GetInstNodeGroup(ctx, default, node_name):
1743 """Gets group UUID of an instance node.
1745 @type ctx: L{InstanceQueryData}
1746 @param default: Default value
1747 @type node_name: string
1748 @param node_name: Node name
1752 node = ctx.nodes[node_name]
1759 def _GetInstNodeGroupName(ctx, default, node_name):
1760 """Gets group name of an instance node.
1762 @type ctx: L{InstanceQueryData}
1763 @param default: Default value
1764 @type node_name: string
1765 @param node_name: Node name
1769 node = ctx.nodes[node_name]
1774 group = ctx.groups[node.group]
1781 def _BuildInstanceFields():
1782 """Builds list of fields for instance queries.
1786 (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
1787 IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")),
1788 (_MakeField("pnode.group", "PrimaryNodeGroup", QFT_TEXT,
1789 "Primary node's group"),
1791 lambda ctx, inst: _GetInstNodeGroupName(ctx, _FS_UNAVAIL,
1792 inst.primary_node)),
1793 (_MakeField("pnode.group.uuid", "PrimaryNodeGroupUUID", QFT_TEXT,
1794 "Primary node's group UUID"),
1796 lambda ctx, inst: _GetInstNodeGroup(ctx, _FS_UNAVAIL, inst.primary_node)),
1797 # TODO: Allow filtering by secondary node as hostname
1798 (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
1799 "Secondary nodes; usually this will just be one node"),
1800 IQ_CONFIG, 0, lambda ctx, inst: list(inst.secondary_nodes)),
1801 (_MakeField("snodes.group", "SecondaryNodesGroups", QFT_OTHER,
1802 "Node groups of secondary nodes"),
1804 lambda ctx, inst: map(compat.partial(_GetInstNodeGroupName, ctx, None),
1805 inst.secondary_nodes)),
1806 (_MakeField("snodes.group.uuid", "SecondaryNodesGroupsUUID", QFT_OTHER,
1807 "Node group UUIDs of secondary nodes"),
1809 lambda ctx, inst: map(compat.partial(_GetInstNodeGroup, ctx, None),
1810 inst.secondary_nodes)),
1811 (_MakeField("admin_state", "InstanceState", QFT_TEXT,
1812 "Desired state of instance"),
1813 IQ_CONFIG, 0, _GetItemAttr("admin_state")),
1814 (_MakeField("admin_up", "Autostart", QFT_BOOL,
1815 "Desired state of instance"),
1816 IQ_CONFIG, 0, lambda ctx, inst: inst.admin_state == constants.ADMINST_UP),
1817 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
1818 lambda ctx, inst: list(inst.GetTags())),
1819 (_MakeField("console", "Console", QFT_OTHER,
1820 "Instance console information"), IQ_CONSOLE, 0,
1821 _GetInstanceConsole),
1826 (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1827 for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()
1830 # Fields requiring talking to the node
1832 (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
1833 IQ_LIVE, 0, _GetInstOperState),
1834 (_MakeField("oper_ram", "Memory", QFT_UNIT,
1835 "Actual memory usage as seen by hypervisor"),
1836 IQ_LIVE, 0, _GetInstLiveData("memory")),
1837 (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
1838 "Actual number of VCPUs as seen by hypervisor"),
1839 IQ_LIVE, 0, _GetInstLiveData("vcpus")),
1843 status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
1844 constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
1845 constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
1846 constants.INSTST_NODEOFFLINE, constants.INSTST_ADMINOFFLINE)
1847 status_doc = ("Instance status; \"%s\" if instance is set to be running"
1848 " and actually is, \"%s\" if instance is stopped and"
1849 " is not running, \"%s\" if instance running, but not on its"
1850 " designated primary node, \"%s\" if instance should be"
1851 " stopped, but is actually running, \"%s\" if instance should"
1852 " run, but doesn't, \"%s\" if instance's primary node is down,"
1853 " \"%s\" if instance's primary node is marked offline,"
1854 " \"%s\" if instance is offline and does not use dynamic"
1855 " resources" % status_values)
1856 fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
1857 IQ_LIVE, 0, _GetInstStatus))
1858 assert set(status_values) == constants.INSTST_ALL, \
1859 "Status documentation mismatch"
1861 (network_fields, network_aliases) = _GetInstanceNetworkFields()
1863 fields.extend(network_fields)
1864 fields.extend(_GetInstanceParameterFields())
1865 fields.extend(_GetInstanceDiskFields())
1866 fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1869 ("vcpus", "be/vcpus"),
1870 ("be/memory", "be/maxmem"),
1871 ("sda_size", "disk.size/0"),
1872 ("sdb_size", "disk.size/1"),
1875 return _PrepareFieldList(fields, aliases)
1878 class LockQueryData:
1879 """Data container for lock data queries.
1882 def __init__(self, lockdata):
1883 """Initializes this class.
1886 self.lockdata = lockdata
1889 """Iterate over all locks.
1892 return iter(self.lockdata)
1895 def _GetLockOwners(_, data):
1896 """Returns a sorted list of a lock's current owners.
1899 (_, _, owners, _) = data
1902 owners = utils.NiceSort(owners)
1907 def _GetLockPending(_, data):
1908 """Returns a sorted list of a lock's pending acquires.
1911 (_, _, _, pending) = data
1914 pending = [(mode, utils.NiceSort(names))
1915 for (mode, names) in pending]
1920 def _BuildLockFields():
1921 """Builds list of fields for lock queries.
1924 return _PrepareFieldList([
1925 # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
1926 (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
1927 lambda ctx, (name, mode, owners, pending): name),
1928 (_MakeField("mode", "Mode", QFT_OTHER,
1929 "Mode in which the lock is currently acquired"
1930 " (exclusive or shared)"),
1931 LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
1932 (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
1933 LQ_OWNER, 0, _GetLockOwners),
1934 (_MakeField("pending", "Pending", QFT_OTHER,
1935 "Threads waiting for the lock"),
1936 LQ_PENDING, 0, _GetLockPending),
1940 class GroupQueryData:
1941 """Data container for node group data queries.
1944 def __init__(self, cluster, groups, group_to_nodes, group_to_instances):
1945 """Initializes this class.
1947 @param cluster: Cluster object
1948 @param groups: List of node group objects
1949 @type group_to_nodes: dict; group UUID as key
1950 @param group_to_nodes: Per-group list of nodes
1951 @type group_to_instances: dict; group UUID as key
1952 @param group_to_instances: Per-group list of (primary) instances
1955 self.groups = groups
1956 self.group_to_nodes = group_to_nodes
1957 self.group_to_instances = group_to_instances
1958 self.cluster = cluster
1960 # Used for individual rows
1961 self.group_ipolicy = None
1964 """Iterate over all node groups.
1966 This function has side-effects and only one instance of the resulting
1967 generator should be used at a time.
1970 for group in self.groups:
1971 self.group_ipolicy = self.cluster.SimpleFillIPolicy(group.ipolicy)
1975 _GROUP_SIMPLE_FIELDS = {
1976 "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
1977 "name": ("Group", QFT_TEXT, "Group name"),
1978 "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
1979 "uuid": ("UUID", QFT_TEXT, "Group UUID"),
1980 "ndparams": ("NDParams", QFT_OTHER, "Node parameters"),
1984 def _BuildGroupFields():
1985 """Builds list of fields for node group queries.
1989 fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
1991 for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
1993 def _GetLength(getter):
1994 return lambda ctx, group: len(getter(ctx)[group.uuid])
1996 def _GetSortedList(getter):
1997 return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
1999 group_to_nodes = operator.attrgetter("group_to_nodes")
2000 group_to_instances = operator.attrgetter("group_to_instances")
2002 # Add fields for nodes
2004 (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
2005 GQ_NODE, 0, _GetLength(group_to_nodes)),
2006 (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
2007 GQ_NODE, 0, _GetSortedList(group_to_nodes)),
2010 # Add fields for instances
2012 (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
2013 "Number of primary instances"),
2014 GQ_INST, 0, _GetLength(group_to_instances)),
2015 (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
2016 "List of primary instances"),
2017 GQ_INST, 0, _GetSortedList(group_to_instances)),
2022 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), GQ_CONFIG, 0,
2023 lambda ctx, group: list(group.GetTags())),
2024 (_MakeField("ipolicy", "InstancePolicy", QFT_OTHER,
2025 "Instance policy limitations (merged)"),
2026 GQ_CONFIG, 0, lambda ctx, _: ctx.group_ipolicy),
2027 (_MakeField("custom_ipolicy", "CustomInstancePolicy", QFT_OTHER,
2028 "Custom instance policy limitations"),
2029 GQ_CONFIG, 0, _GetItemAttr("ipolicy")),
2032 fields.extend(_GetItemTimestampFields(GQ_CONFIG))
2034 return _PrepareFieldList(fields, [])
2037 class OsInfo(objects.ConfigObject):
2050 def _BuildOsFields():
2051 """Builds list of fields for operating system queries.
2055 (_MakeField("name", "Name", QFT_TEXT, "Operating system name"),
2056 None, 0, _GetItemAttr("name")),
2057 (_MakeField("valid", "Valid", QFT_BOOL,
2058 "Whether operating system definition is valid"),
2059 None, 0, _GetItemAttr("valid")),
2060 (_MakeField("hidden", "Hidden", QFT_BOOL,
2061 "Whether operating system is hidden"),
2062 None, 0, _GetItemAttr("hidden")),
2063 (_MakeField("blacklisted", "Blacklisted", QFT_BOOL,
2064 "Whether operating system is blacklisted"),
2065 None, 0, _GetItemAttr("blacklisted")),
2066 (_MakeField("variants", "Variants", QFT_OTHER,
2067 "Operating system variants"),
2068 None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("variants"))),
2069 (_MakeField("api_versions", "ApiVersions", QFT_OTHER,
2070 "Operating system API versions"),
2071 None, 0, _ConvWrap(sorted, _GetItemAttr("api_versions"))),
2072 (_MakeField("parameters", "Parameters", QFT_OTHER,
2073 "Operating system parameters"),
2074 None, 0, _ConvWrap(compat.partial(utils.NiceSort, key=compat.fst),
2075 _GetItemAttr("parameters"))),
2076 (_MakeField("node_status", "NodeStatus", QFT_OTHER,
2077 "Status from node"),
2078 None, 0, _GetItemAttr("node_status")),
2081 return _PrepareFieldList(fields, [])
2084 #: Fields available for node queries
2085 NODE_FIELDS = _BuildNodeFields()
2087 #: Fields available for instance queries
2088 INSTANCE_FIELDS = _BuildInstanceFields()
2090 #: Fields available for lock queries
2091 LOCK_FIELDS = _BuildLockFields()
2093 #: Fields available for node group queries
2094 GROUP_FIELDS = _BuildGroupFields()
2096 #: Fields available for operating system queries
2097 OS_FIELDS = _BuildOsFields()
2099 #: All available resources
2101 constants.QR_INSTANCE: INSTANCE_FIELDS,
2102 constants.QR_NODE: NODE_FIELDS,
2103 constants.QR_LOCK: LOCK_FIELDS,
2104 constants.QR_GROUP: GROUP_FIELDS,
2105 constants.QR_OS: OS_FIELDS,
2108 #: All available field lists
2109 ALL_FIELD_LISTS = ALL_FIELDS.values()