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 def _PrepareRegex(pattern):
326 """Compiles a regular expression.
330 return re.compile(pattern)
331 except re.error, err:
332 raise errors.ParameterError("Invalid regex pattern (%s)" % err)
335 class _FilterCompilerHelper:
336 """Converts a query filter to a callable usable for filtering.
339 # String statement has no effect, pylint: disable-msg=W0105
341 #: How deep filters can be nested
344 # Unique identifiers for operator groups
347 _OPTYPE_BINARY) = range(1, 4)
349 """Functions for equality checks depending on field flags.
351 List of tuples containing flags and a callable receiving the left- and
352 right-hand side of the operator. The flags are an OR-ed value of C{QFF_*}
353 (e.g. L{QFF_HOSTNAME}).
355 Order matters. The first item with flags will be used. Flags are checked
361 lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
362 case_sensitive=False),
364 (None, operator.eq, None),
369 Operator as key (C{qlang.OP_*}), value a tuple of operator group
370 (C{_OPTYPE_*}) and a group-specific value:
372 - C{_OPTYPE_LOGIC}: Callable taking any number of arguments; used by
374 - C{_OPTYPE_UNARY}: Always C{None}; details handled by L{_HandleUnaryOp}
375 - C{_OPTYPE_BINARY}: Callable taking exactly two parameters, the left- and
376 right-hand side of the operator, used by L{_HandleBinaryOp}
381 qlang.OP_OR: (_OPTYPE_LOGIC, compat.any),
382 qlang.OP_AND: (_OPTYPE_LOGIC, compat.all),
385 qlang.OP_NOT: (_OPTYPE_UNARY, None),
386 qlang.OP_TRUE: (_OPTYPE_UNARY, None),
389 qlang.OP_EQUAL: (_OPTYPE_BINARY, _EQUALITY_CHECKS),
391 (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn), valprepfn)
392 for (flags, fn, valprepfn) in _EQUALITY_CHECKS]),
393 qlang.OP_REGEXP: (_OPTYPE_BINARY, [
394 (None, lambda lhs, rhs: rhs.search(lhs), _PrepareRegex),
396 qlang.OP_CONTAINS: (_OPTYPE_BINARY, [
397 (None, operator.contains, None),
401 def __init__(self, fields):
402 """Initializes this class.
404 @param fields: Field definitions (return value of L{_PrepareFieldList})
407 self._fields = fields
409 self._op_handler = None
411 def __call__(self, hints, filter_):
412 """Converts a query filter into a callable function.
414 @type hints: L{_FilterHints} or None
415 @param hints: Callbacks doing analysis on filter
417 @param filter_: Filter structure
419 @return: Function receiving context and item as parameters, returning
420 boolean as to whether item matches filter
425 (self._HandleLogicOp, getattr(hints, "NoteLogicOp", None)),
427 (self._HandleUnaryOp, getattr(hints, "NoteUnaryOp", None)),
429 (self._HandleBinaryOp, getattr(hints, "NoteBinaryOp", None)),
433 filter_fn = self._Compile(filter_, 0)
435 self._op_handler = None
439 def _Compile(self, filter_, level):
440 """Inner function for converting filters.
442 Calls the correct handler functions for the top-level operator. This
443 function is called recursively (e.g. for logic operators).
446 if not (isinstance(filter_, (list, tuple)) and filter_):
447 raise errors.ParameterError("Invalid filter on level %s" % level)
450 if level >= self._LEVELS_MAX:
451 raise errors.ParameterError("Only up to %s levels are allowed (filter"
452 " nested too deep)" % self._LEVELS_MAX)
454 # Create copy to be modified
455 operands = filter_[:]
459 (kind, op_data) = self._OPS[op]
461 raise errors.ParameterError("Unknown operator '%s'" % op)
463 (handler, hints_cb) = self._op_handler[kind]
465 return handler(hints_cb, level, op, op_data, operands)
467 def _LookupField(self, name):
468 """Returns a field definition by name.
472 return self._fields[name]
474 raise errors.ParameterError("Unknown field '%s'" % name)
476 def _HandleLogicOp(self, hints_fn, level, op, op_fn, operands):
477 """Handles logic operators.
479 @type hints_fn: callable
480 @param hints_fn: Callback doing some analysis on the filter
482 @param level: Current depth
485 @type op_fn: callable
486 @param op_fn: Function implementing operator
488 @param operands: List of operands
494 return compat.partial(_WrapLogicOp, op_fn,
495 [self._Compile(op, level + 1) for op in operands])
497 def _HandleUnaryOp(self, hints_fn, level, op, op_fn, operands):
498 """Handles unary operators.
500 @type hints_fn: callable
501 @param hints_fn: Callback doing some analysis on the filter
503 @param level: Current depth
506 @type op_fn: callable
507 @param op_fn: Function implementing operator
509 @param operands: List of operands
517 if len(operands) != 1:
518 raise errors.ParameterError("Unary operator '%s' expects exactly one"
521 if op == qlang.OP_TRUE:
522 (_, _, _, retrieval_fn) = self._LookupField(operands[0])
524 op_fn = operator.truth
526 elif op == qlang.OP_NOT:
527 op_fn = operator.not_
528 arg = self._Compile(operands[0], level + 1)
530 raise errors.ProgrammerError("Can't handle operator '%s'" % op)
532 return compat.partial(_WrapUnaryOp, op_fn, arg)
534 def _HandleBinaryOp(self, hints_fn, level, op, op_data, operands):
535 """Handles binary operators.
537 @type hints_fn: callable
538 @param hints_fn: Callback doing some analysis on the filter
540 @param level: Current depth
543 @param op_data: Functions implementing operators
545 @param operands: List of operands
548 # Unused arguments, pylint: disable-msg=W0613
550 (name, value) = operands
551 except (ValueError, TypeError):
552 raise errors.ParameterError("Invalid binary operator, expected exactly"
555 (fdef, datakind, field_flags, retrieval_fn) = self._LookupField(name)
557 assert fdef.kind != QFT_UNKNOWN
559 # TODO: Type conversions?
561 verify_fn = _VERIFY_FN[fdef.kind]
562 if not verify_fn(value):
563 raise errors.ParameterError("Unable to compare field '%s' (type '%s')"
564 " with '%s', expected %s" %
565 (name, fdef.kind, value.__class__.__name__,
569 hints_fn(op, datakind, name, value)
571 for (fn_flags, fn, valprepfn) in op_data:
572 if fn_flags is None or fn_flags & field_flags:
573 # Prepare value if necessary (e.g. compile regular expression)
575 value = valprepfn(value)
577 return compat.partial(_WrapBinaryOp, fn, retrieval_fn, value)
579 raise errors.ProgrammerError("Unable to find operator implementation"
580 " (op '%s', flags %s)" % (op, field_flags))
583 def _CompileFilter(fields, hints, filter_):
584 """Converts a query filter into a callable function.
586 See L{_FilterCompilerHelper} for details.
591 return _FilterCompilerHelper(fields)(hints, filter_)
595 def __init__(self, fieldlist, selected, filter_=None, namefield=None):
596 """Initializes this class.
598 The field definition is a dictionary with the field's name as a key and a
599 tuple containing, in order, the field definition object
600 (L{objects.QueryFieldDefinition}, the data kind to help calling code
601 collect data and a retrieval function. The retrieval function is called
602 with two parameters, in order, the data container and the item in container
603 (see L{Query.Query}).
605 Users of this class can call L{RequestedData} before preparing the data
606 container to determine what data is needed.
608 @type fieldlist: dictionary
609 @param fieldlist: Field definitions
610 @type selected: list of strings
611 @param selected: List of selected fields
614 assert namefield is None or namefield in fieldlist
616 self._fields = _GetQueryFields(fieldlist, selected)
618 self._filter_fn = None
619 self._requested_names = None
620 self._filter_datakinds = frozenset()
622 if filter_ is not None:
623 # Collect requested names if wanted
625 hints = _FilterHints(namefield)
629 # Build filter function
630 self._filter_fn = _CompileFilter(fieldlist, hints, filter_)
632 self._requested_names = hints.RequestedNames()
633 self._filter_datakinds = hints.ReferencedData()
635 if namefield is None:
638 (_, _, _, self._name_fn) = fieldlist[namefield]
640 def RequestedNames(self):
641 """Returns all names referenced in the filter.
643 If there is no filter or operators are preventing determining the exact
644 names, C{None} is returned.
647 return self._requested_names
649 def RequestedData(self):
650 """Gets requested kinds of data.
655 return (self._filter_datakinds |
656 frozenset(datakind for (_, datakind, _, _) in self._fields
657 if datakind is not None))
660 """Returns the list of fields for this query.
662 Includes unknown fields.
664 @rtype: List of L{objects.QueryFieldDefinition}
667 return GetAllFields(self._fields)
669 def Query(self, ctx, sort_by_name=True):
672 @param ctx: Data container passed to field retrieval functions, must
673 support iteration using C{__iter__}
674 @type sort_by_name: boolean
675 @param sort_by_name: Whether to sort by name or keep the input data's
679 sort = (self._name_fn and sort_by_name)
683 for idx, item in enumerate(ctx):
684 if not (self._filter_fn is None or self._filter_fn(ctx, item)):
687 row = [_ProcessResult(fn(ctx, item)) for (_, _, _, fn) in self._fields]
691 _VerifyResultRow(self._fields, row)
694 (status, name) = _ProcessResult(self._name_fn(ctx, item))
695 assert status == constants.RS_NORMAL
696 # TODO: Are there cases where we wouldn't want to use NiceSort?
697 result.append((utils.NiceSortKey(name), idx, row))
704 # TODO: Would "heapq" be more efficient than sorting?
706 # Sorting in-place instead of using "sorted()"
709 assert not result or (len(result[0]) == 3 and len(result[-1]) == 3)
711 return map(operator.itemgetter(2), result)
713 def OldStyleQuery(self, ctx, sort_by_name=True):
714 """Query with "old" query result format.
716 See L{Query.Query} for arguments.
719 unknown = set(fdef.name for (fdef, _, _, _) in self._fields
720 if fdef.kind == QFT_UNKNOWN)
722 raise errors.OpPrereqError("Unknown output fields selected: %s" %
723 (utils.CommaJoin(unknown), ),
726 return [[value for (_, value) in row]
727 for row in self.Query(ctx, sort_by_name=sort_by_name)]
730 def _ProcessResult(value):
731 """Converts result values into externally-visible ones.
734 if value is _FS_UNKNOWN:
735 return (RS_UNKNOWN, None)
736 elif value is _FS_NODATA:
737 return (RS_NODATA, None)
738 elif value is _FS_UNAVAIL:
739 return (RS_UNAVAIL, None)
740 elif value is _FS_OFFLINE:
741 return (RS_OFFLINE, None)
743 return (RS_NORMAL, value)
746 def _VerifyResultRow(fields, row):
747 """Verifies the contents of a query result row.
750 @param fields: Field definitions for result
751 @type row: list of tuples
755 assert len(row) == len(fields)
757 for ((status, value), (fdef, _, _, _)) in zip(row, fields):
758 if status == RS_NORMAL:
759 if not _VERIFY_FN[fdef.kind](value):
760 errs.append("normal field %s fails validation (value is %s)" %
762 elif value is not None:
763 errs.append("abnormal field %s has a non-None value" % fdef.name)
764 assert not errs, ("Failed validation: %s in row %s" %
765 (utils.CommaJoin(errors), row))
768 def _PrepareFieldList(fields, aliases):
769 """Prepares field list for use by L{Query}.
771 Converts the list to a dictionary and does some verification.
773 @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data
774 kind, retrieval function)
775 @param fields: List of fields, see L{Query.__init__} for a better
777 @type aliases: list of tuples; (alias, target)
778 @param aliases: list of tuples containing aliases; for each
779 alias/target pair, a duplicate will be created in the field list
781 @return: Field dictionary for L{Query}
785 duplicates = utils.FindDuplicates(fdef.title.lower()
786 for (fdef, _, _, _) in fields)
787 assert not duplicates, "Duplicate title(s) found: %r" % duplicates
792 (fdef, _, flags, fn) = field
794 assert fdef.name and fdef.title, "Name and title are required"
795 assert FIELD_NAME_RE.match(fdef.name)
796 assert TITLE_RE.match(fdef.title)
797 assert (DOC_RE.match(fdef.doc) and len(fdef.doc.splitlines()) == 1 and
798 fdef.doc.strip() == fdef.doc), \
799 "Invalid description for field '%s'" % fdef.name
801 assert fdef.name not in result, \
802 "Duplicate field name '%s' found" % fdef.name
803 assert (flags & ~QFF_ALL) == 0, "Unknown flags for field '%s'" % fdef.name
805 result[fdef.name] = field
807 for alias, target in aliases:
808 assert alias not in result, "Alias %s overrides an existing field" % alias
809 assert target in result, "Missing target %s for alias %s" % (target, alias)
810 (fdef, k, flags, fn) = result[target]
813 result[alias] = (fdef, k, flags, fn)
815 assert len(result) == len(fields) + len(aliases)
816 assert compat.all(name == fdef.name
817 for (name, (fdef, _, _, _)) in result.items())
822 def GetQueryResponse(query, ctx, sort_by_name=True):
823 """Prepares the response for a query.
825 @type query: L{Query}
826 @param ctx: Data container, see L{Query.Query}
827 @type sort_by_name: boolean
828 @param sort_by_name: Whether to sort by name or keep the input data's
832 return objects.QueryResponse(data=query.Query(ctx, sort_by_name=sort_by_name),
833 fields=query.GetFields()).ToDict()
836 def QueryFields(fielddefs, selected):
837 """Returns list of available fields.
839 @type fielddefs: dict
840 @param fielddefs: Field definitions
841 @type selected: list of strings
842 @param selected: List of selected fields
843 @return: List of L{objects.QueryFieldDefinition}
847 # Client requests all fields, sort by name
848 fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
849 key=operator.attrgetter("name"))
851 # Keep order as requested by client
852 fdefs = Query(fielddefs, selected).GetFields()
854 return objects.QueryFieldsResponse(fields=fdefs).ToDict()
857 def _MakeField(name, title, kind, doc):
858 """Wrapper for creating L{objects.QueryFieldDefinition} instances.
860 @param name: Field name as a regular expression
861 @param title: Human-readable title
862 @param kind: Field type
863 @param doc: Human-readable description
866 return objects.QueryFieldDefinition(name=name, title=title, kind=kind,
870 def _GetNodeRole(node, master_name):
871 """Determine node role.
873 @type node: L{objects.Node}
874 @param node: Node object
875 @type master_name: string
876 @param master_name: Master node name
879 if node.name == master_name:
880 return constants.NR_MASTER
881 elif node.master_candidate:
882 return constants.NR_MCANDIDATE
884 return constants.NR_DRAINED
886 return constants.NR_OFFLINE
888 return constants.NR_REGULAR
891 def _GetItemAttr(attr):
892 """Returns a field function to return an attribute of the item.
894 @param attr: Attribute name
897 getter = operator.attrgetter(attr)
898 return lambda _, item: getter(item)
901 def _ConvWrapInner(convert, fn, ctx, item):
902 """Wrapper for converting values.
904 @param convert: Conversion function receiving value as single parameter
905 @param fn: Retrieval function
908 value = fn(ctx, item)
910 # Is the value an abnormal status?
911 if compat.any(value is fs for fs in _FS_ALL):
915 # TODO: Should conversion function also receive context, item or both?
916 return convert(value)
919 def _ConvWrap(convert, fn):
920 """Convenience wrapper for L{_ConvWrapInner}.
922 @param convert: Conversion function receiving value as single parameter
923 @param fn: Retrieval function
926 return compat.partial(_ConvWrapInner, convert, fn)
929 def _GetItemTimestamp(getter):
930 """Returns function for getting timestamp of item.
932 @type getter: callable
933 @param getter: Function to retrieve timestamp attribute
937 """Returns a timestamp of item.
940 timestamp = getter(item)
941 if timestamp is None:
942 # Old configs might not have all timestamps
950 def _GetItemTimestampFields(datatype):
951 """Returns common timestamp fields.
953 @param datatype: Field data type for use by L{Query.RequestedData}
957 (_MakeField("ctime", "CTime", QFT_TIMESTAMP, "Creation timestamp"),
958 datatype, 0, _GetItemTimestamp(operator.attrgetter("ctime"))),
959 (_MakeField("mtime", "MTime", QFT_TIMESTAMP, "Modification timestamp"),
960 datatype, 0, _GetItemTimestamp(operator.attrgetter("mtime"))),
965 """Data container for node data queries.
968 def __init__(self, nodes, live_data, master_name, node_to_primary,
969 node_to_secondary, groups, oob_support, cluster):
970 """Initializes this class.
974 self.live_data = live_data
975 self.master_name = master_name
976 self.node_to_primary = node_to_primary
977 self.node_to_secondary = node_to_secondary
979 self.oob_support = oob_support
980 self.cluster = cluster
982 # Used for individual rows
983 self.curlive_data = None
986 """Iterate over all nodes.
988 This function has side-effects and only one instance of the resulting
989 generator should be used at a time.
992 for node in self.nodes:
994 self.curlive_data = self.live_data.get(node.name, None)
996 self.curlive_data = None
1000 #: Fields that are direct attributes of an L{objects.Node} object
1001 _NODE_SIMPLE_FIELDS = {
1002 "drained": ("Drained", QFT_BOOL, 0, "Whether node is drained"),
1003 "master_candidate": ("MasterC", QFT_BOOL, 0,
1004 "Whether node is a master candidate"),
1005 "master_capable": ("MasterCapable", QFT_BOOL, 0,
1006 "Whether node can become a master candidate"),
1007 "name": ("Node", QFT_TEXT, QFF_HOSTNAME, "Node name"),
1008 "offline": ("Offline", QFT_BOOL, 0, "Whether node is marked offline"),
1009 "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Node"),
1010 "uuid": ("UUID", QFT_TEXT, 0, "Node UUID"),
1011 "vm_capable": ("VMCapable", QFT_BOOL, 0, "Whether node can host instances"),
1015 #: Fields requiring talking to the node
1016 # Note that none of these are available for non-vm_capable nodes
1017 _NODE_LIVE_FIELDS = {
1018 "bootid": ("BootID", QFT_TEXT, "bootid",
1019 "Random UUID renewed for each system reboot, can be used"
1020 " for detecting reboots by tracking changes"),
1021 "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes",
1022 "Number of NUMA domains on node (if exported by hypervisor)"),
1023 "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets",
1024 "Number of physical CPU sockets (if exported by hypervisor)"),
1025 "ctotal": ("CTotal", QFT_NUMBER, "cpu_total", "Number of logical processors"),
1026 "dfree": ("DFree", QFT_UNIT, "vg_free",
1027 "Available disk space in volume group"),
1028 "dtotal": ("DTotal", QFT_UNIT, "vg_size",
1029 "Total disk space in volume group used for instance disk"
1031 "mfree": ("MFree", QFT_UNIT, "memory_free",
1032 "Memory available for instance allocations"),
1033 "mnode": ("MNode", QFT_UNIT, "memory_dom0",
1034 "Amount of memory used by node (dom0 for Xen)"),
1035 "mtotal": ("MTotal", QFT_UNIT, "memory_total",
1036 "Total amount of memory of physical machine"),
1041 """Build function for calling another function with an node group.
1043 @param cb: The callback to be called with the nodegroup
1047 """Get group data for a node.
1049 @type ctx: L{NodeQueryData}
1050 @type inst: L{objects.Node}
1051 @param inst: Node object
1054 ng = ctx.groups.get(node.group, None)
1056 # Nodes always have a group, or the configuration is corrupt
1059 return cb(ctx, node, ng)
1064 def _GetNodeGroup(ctx, node, ng): # pylint: disable-msg=W0613
1065 """Returns the name of a node's group.
1067 @type ctx: L{NodeQueryData}
1068 @type node: L{objects.Node}
1069 @param node: Node object
1070 @type ng: L{objects.NodeGroup}
1071 @param ng: The node group this node belongs to
1077 def _GetNodePower(ctx, node):
1078 """Returns the node powered state
1080 @type ctx: L{NodeQueryData}
1081 @type node: L{objects.Node}
1082 @param node: Node object
1085 if ctx.oob_support[node.name]:
1091 def _GetNdParams(ctx, node, ng):
1092 """Returns the ndparams for this node.
1094 @type ctx: L{NodeQueryData}
1095 @type node: L{objects.Node}
1096 @param node: Node object
1097 @type ng: L{objects.NodeGroup}
1098 @param ng: The node group this node belongs to
1101 return ctx.cluster.SimpleFillND(ng.FillND(node))
1104 def _GetLiveNodeField(field, kind, ctx, node):
1105 """Gets the value of a "live" field from L{NodeQueryData}.
1107 @param field: Live field name
1108 @param kind: Data kind, one of L{constants.QFT_ALL}
1109 @type ctx: L{NodeQueryData}
1110 @type node: L{objects.Node}
1111 @param node: Node object
1117 if not node.vm_capable:
1120 if not ctx.curlive_data:
1124 value = ctx.curlive_data[field]
1128 if kind == QFT_TEXT:
1131 assert kind in (QFT_NUMBER, QFT_UNIT)
1133 # Try to convert into number
1136 except (ValueError, TypeError):
1137 logging.exception("Failed to convert node field '%s' (value %r) to int",
1142 def _BuildNodeFields():
1143 """Builds list of fields for node queries.
1147 (_MakeField("pip", "PrimaryIP", QFT_TEXT, "Primary IP address"),
1148 NQ_CONFIG, 0, _GetItemAttr("primary_ip")),
1149 (_MakeField("sip", "SecondaryIP", QFT_TEXT, "Secondary IP address"),
1150 NQ_CONFIG, 0, _GetItemAttr("secondary_ip")),
1151 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0,
1152 lambda ctx, node: list(node.GetTags())),
1153 (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"),
1154 NQ_CONFIG, 0, lambda ctx, node: node.name == ctx.master_name),
1155 (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0,
1156 _GetGroup(_GetNodeGroup)),
1157 (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"),
1158 NQ_CONFIG, 0, _GetItemAttr("group")),
1159 (_MakeField("powered", "Powered", QFT_BOOL,
1160 "Whether node is thought to be powered on"),
1161 NQ_OOB, 0, _GetNodePower),
1162 (_MakeField("ndparams", "NodeParameters", QFT_OTHER,
1163 "Merged node parameters"),
1164 NQ_GROUP, 0, _GetGroup(_GetNdParams)),
1165 (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER,
1166 "Custom node parameters"),
1167 NQ_GROUP, 0, _GetItemAttr("ndparams")),
1171 role_values = (constants.NR_MASTER, constants.NR_MCANDIDATE,
1172 constants.NR_REGULAR, constants.NR_DRAINED,
1173 constants.NR_OFFLINE)
1174 role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate,"
1175 " \"%s\" for regular, \"%s\" for a drained, \"%s\" for offline" %
1177 fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
1178 lambda ctx, node: _GetNodeRole(node, ctx.master_name)))
1179 assert set(role_values) == constants.NR_ALL
1181 def _GetLength(getter):
1182 return lambda ctx, node: len(getter(ctx)[node.name])
1184 def _GetList(getter):
1185 return lambda ctx, node: list(getter(ctx)[node.name])
1187 # Add fields operating on instance lists
1188 for prefix, titleprefix, docword, getter in \
1189 [("p", "Pri", "primary", operator.attrgetter("node_to_primary")),
1190 ("s", "Sec", "secondary", operator.attrgetter("node_to_secondary"))]:
1191 # TODO: Allow filterting by hostname in list
1193 (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER,
1194 "Number of instances with this node as %s" % docword),
1195 NQ_INST, 0, _GetLength(getter)),
1196 (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
1198 "List of instances with this node as %s" % docword),
1199 NQ_INST, 0, _GetList(getter)),
1204 (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1205 for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()
1208 # Add fields requiring live data
1210 (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1211 compat.partial(_GetLiveNodeField, nfield, kind))
1212 for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()
1216 fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1218 return _PrepareFieldList(fields, [])
1221 class InstanceQueryData:
1222 """Data container for instance data queries.
1225 def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
1226 live_data, wrongnode_inst, console):
1227 """Initializes this class.
1229 @param instances: List of instance objects
1230 @param cluster: Cluster object
1231 @type disk_usage: dict; instance name as key
1232 @param disk_usage: Per-instance disk usage
1233 @type offline_nodes: list of strings
1234 @param offline_nodes: List of offline nodes
1235 @type bad_nodes: list of strings
1236 @param bad_nodes: List of faulty nodes
1237 @type live_data: dict; instance name as key
1238 @param live_data: Per-instance live data
1239 @type wrongnode_inst: set
1240 @param wrongnode_inst: Set of instances running on wrong node(s)
1241 @type console: dict; instance name as key
1242 @param console: Per-instance console information
1245 assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
1246 "Offline nodes not included in bad nodes"
1247 assert not (set(live_data.keys()) & set(bad_nodes)), \
1248 "Found live data for bad or offline nodes"
1250 self.instances = instances
1251 self.cluster = cluster
1252 self.disk_usage = disk_usage
1253 self.offline_nodes = offline_nodes
1254 self.bad_nodes = bad_nodes
1255 self.live_data = live_data
1256 self.wrongnode_inst = wrongnode_inst
1257 self.console = console
1259 # Used for individual rows
1260 self.inst_hvparams = None
1261 self.inst_beparams = None
1262 self.inst_osparams = None
1263 self.inst_nicparams = None
1266 """Iterate over all instances.
1268 This function has side-effects and only one instance of the resulting
1269 generator should be used at a time.
1272 for inst in self.instances:
1273 self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1274 self.inst_beparams = self.cluster.FillBE(inst)
1275 self.inst_osparams = self.cluster.SimpleFillOS(inst.os, inst.osparams)
1276 self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1277 for nic in inst.nics]
1282 def _GetInstOperState(ctx, inst):
1283 """Get instance's operational status.
1285 @type ctx: L{InstanceQueryData}
1286 @type inst: L{objects.Instance}
1287 @param inst: Instance object
1290 # Can't use RS_OFFLINE here as it would describe the instance to
1291 # be offline when we actually don't know due to missing data
1292 if inst.primary_node in ctx.bad_nodes:
1295 return bool(ctx.live_data.get(inst.name))
1298 def _GetInstLiveData(name):
1299 """Build function for retrieving live data.
1302 @param name: Live data field name
1306 """Get live data for an instance.
1308 @type ctx: L{InstanceQueryData}
1309 @type inst: L{objects.Instance}
1310 @param inst: Instance object
1313 if (inst.primary_node in ctx.bad_nodes or
1314 inst.primary_node in ctx.offline_nodes):
1315 # Can't use RS_OFFLINE here as it would describe the instance to be
1316 # offline when we actually don't know due to missing data
1319 if inst.name in ctx.live_data:
1320 data = ctx.live_data[inst.name]
1329 def _GetInstStatus(ctx, inst):
1330 """Get instance status.
1332 @type ctx: L{InstanceQueryData}
1333 @type inst: L{objects.Instance}
1334 @param inst: Instance object
1337 if inst.primary_node in ctx.offline_nodes:
1338 return constants.INSTST_NODEOFFLINE
1340 if inst.primary_node in ctx.bad_nodes:
1341 return constants.INSTST_NODEDOWN
1343 if bool(ctx.live_data.get(inst.name)):
1344 if inst.name in ctx.wrongnode_inst:
1345 return constants.INSTST_WRONGNODE
1347 return constants.INSTST_RUNNING
1349 return constants.INSTST_ERRORUP
1352 return constants.INSTST_ERRORDOWN
1354 return constants.INSTST_ADMINDOWN
1357 def _GetInstDiskSize(index):
1358 """Build function for retrieving disk size.
1361 @param index: Disk index
1365 """Get size of a disk.
1367 @type inst: L{objects.Instance}
1368 @param inst: Instance object
1372 return inst.disks[index].size
1379 def _GetInstNic(index, cb):
1380 """Build function for calling another function with an instance NIC.
1383 @param index: NIC index
1389 """Call helper function with instance NIC.
1391 @type ctx: L{InstanceQueryData}
1392 @type inst: L{objects.Instance}
1393 @param inst: Instance object
1397 nic = inst.nics[index]
1401 return cb(ctx, index, nic)
1406 def _GetInstNicIp(ctx, _, nic): # pylint: disable-msg=W0613
1407 """Get a NIC's IP address.
1409 @type ctx: L{InstanceQueryData}
1410 @type nic: L{objects.NIC}
1411 @param nic: NIC object
1420 def _GetInstNicBridge(ctx, index, _):
1421 """Get a NIC's bridge.
1423 @type ctx: L{InstanceQueryData}
1425 @param index: NIC index
1428 assert len(ctx.inst_nicparams) >= index
1430 nicparams = ctx.inst_nicparams[index]
1432 if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1433 return nicparams[constants.NIC_LINK]
1438 def _GetInstAllNicBridges(ctx, inst):
1439 """Get all network bridges for an instance.
1441 @type ctx: L{InstanceQueryData}
1442 @type inst: L{objects.Instance}
1443 @param inst: Instance object
1446 assert len(ctx.inst_nicparams) == len(inst.nics)
1450 for nicp in ctx.inst_nicparams:
1451 if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1452 result.append(nicp[constants.NIC_LINK])
1456 assert len(result) == len(inst.nics)
1461 def _GetInstNicParam(name):
1462 """Build function for retrieving a NIC parameter.
1465 @param name: Parameter name
1468 def fn(ctx, index, _):
1469 """Get a NIC's bridge.
1471 @type ctx: L{InstanceQueryData}
1472 @type inst: L{objects.Instance}
1473 @param inst: Instance object
1474 @type nic: L{objects.NIC}
1475 @param nic: NIC object
1478 assert len(ctx.inst_nicparams) >= index
1479 return ctx.inst_nicparams[index][name]
1484 def _GetInstanceNetworkFields():
1485 """Get instance fields involving network interfaces.
1487 @return: Tuple containing list of field definitions used as input for
1488 L{_PrepareFieldList} and a list of aliases
1491 nic_mac_fn = lambda ctx, _, nic: nic.mac
1492 nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1493 nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1497 (_MakeField("nic.count", "NICs", QFT_NUMBER,
1498 "Number of network interfaces"),
1499 IQ_CONFIG, 0, lambda ctx, inst: len(inst.nics)),
1500 (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER,
1501 "List containing each network interface's MAC address"),
1502 IQ_CONFIG, 0, lambda ctx, inst: [nic.mac for nic in inst.nics]),
1503 (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
1504 "List containing each network interface's IP address"),
1505 IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
1506 (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
1507 "List containing each network interface's mode"), IQ_CONFIG, 0,
1508 lambda ctx, inst: [nicp[constants.NIC_MODE]
1509 for nicp in ctx.inst_nicparams]),
1510 (_MakeField("nic.links", "NIC_links", QFT_OTHER,
1511 "List containing each network interface's link"), IQ_CONFIG, 0,
1512 lambda ctx, inst: [nicp[constants.NIC_LINK]
1513 for nicp in ctx.inst_nicparams]),
1514 (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
1515 "List containing each network interface's bridge"),
1516 IQ_CONFIG, 0, _GetInstAllNicBridges),
1520 for i in range(constants.MAX_NICS):
1521 numtext = utils.FormatOrdinal(i + 1)
1523 (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT,
1524 "IP address of %s network interface" % numtext),
1525 IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)),
1526 (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
1527 "MAC address of %s network interface" % numtext),
1528 IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
1529 (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
1530 "Mode of %s network interface" % numtext),
1531 IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
1532 (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT,
1533 "Link of %s network interface" % numtext),
1534 IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)),
1535 (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
1536 "Bridge of %s network interface" % numtext),
1537 IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
1541 # Legacy fields for first NIC
1543 ("mac", "nic.mac/0"),
1544 ("bridge", "nic.bridge/0"),
1545 ("nic_mode", "nic.mode/0"),
1546 ("nic_link", "nic.link/0"),
1549 return (fields, aliases)
1552 def _GetInstDiskUsage(ctx, inst):
1553 """Get disk usage for an instance.
1555 @type ctx: L{InstanceQueryData}
1556 @type inst: L{objects.Instance}
1557 @param inst: Instance object
1560 usage = ctx.disk_usage[inst.name]
1568 def _GetInstanceConsole(ctx, inst):
1569 """Get console information for instance.
1571 @type ctx: L{InstanceQueryData}
1572 @type inst: L{objects.Instance}
1573 @param inst: Instance object
1576 consinfo = ctx.console[inst.name]
1578 if consinfo is None:
1584 def _GetInstanceDiskFields():
1585 """Get instance fields involving disks.
1587 @return: List of field definitions used as input for L{_PrepareFieldList}
1591 (_MakeField("disk_usage", "DiskUsage", QFT_UNIT,
1592 "Total disk space used by instance on each of its nodes;"
1593 " this is not the disk size visible to the instance, but"
1594 " the usage on the node"),
1595 IQ_DISKUSAGE, 0, _GetInstDiskUsage),
1596 (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"),
1597 IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
1598 (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
1599 IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
1604 (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1605 "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
1606 IQ_CONFIG, 0, _GetInstDiskSize(i))
1607 for i in range(constants.MAX_DISKS)
1613 def _GetInstanceParameterFields():
1614 """Get instance fields involving parameters.
1616 @return: List of field definitions used as input for L{_PrepareFieldList}
1619 # TODO: Consider moving titles closer to constants
1621 constants.BE_AUTO_BALANCE: "Auto_balance",
1622 constants.BE_MEMORY: "ConfigMemory",
1623 constants.BE_VCPUS: "ConfigVCPUs",
1627 constants.HV_ACPI: "ACPI",
1628 constants.HV_BOOT_ORDER: "Boot_order",
1629 constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1630 constants.HV_DISK_TYPE: "Disk_type",
1631 constants.HV_INITRD_PATH: "Initrd_path",
1632 constants.HV_KERNEL_PATH: "Kernel_path",
1633 constants.HV_NIC_TYPE: "NIC_type",
1634 constants.HV_PAE: "PAE",
1635 constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1640 (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
1641 "Hypervisor parameters (merged)"),
1642 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
1643 (_MakeField("beparams", "BackendParameters", QFT_OTHER,
1644 "Backend parameters (merged)"),
1645 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
1646 (_MakeField("osparams", "OpSysParameters", QFT_OTHER,
1647 "Operating system parameters (merged)"),
1648 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_osparams),
1650 # Unfilled parameters
1651 (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
1652 "Custom hypervisor parameters"),
1653 IQ_CONFIG, 0, _GetItemAttr("hvparams")),
1654 (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
1655 "Custom backend parameters",),
1656 IQ_CONFIG, 0, _GetItemAttr("beparams")),
1657 (_MakeField("custom_osparams", "CustomOpSysParameters", QFT_OTHER,
1658 "Custom operating system parameters",),
1659 IQ_CONFIG, 0, _GetItemAttr("osparams")),
1660 (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
1661 "Custom network interface parameters"),
1662 IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1666 def _GetInstHvParam(name):
1667 return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1670 (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1671 _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1672 IQ_CONFIG, 0, _GetInstHvParam(name))
1673 for name, kind in constants.HVS_PARAMETER_TYPES.items()
1674 if name not in constants.HVC_GLOBALS
1678 def _GetInstBeParam(name):
1679 return lambda ctx, _: ctx.inst_beparams.get(name, None)
1682 (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1683 _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1684 IQ_CONFIG, 0, _GetInstBeParam(name))
1685 for name, kind in constants.BES_PARAMETER_TYPES.items()
1691 _INST_SIMPLE_FIELDS = {
1692 "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"),
1693 "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"),
1694 "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"),
1695 # Depending on the hypervisor, the port can be None
1696 "network_port": ("Network_port", QFT_OTHER, 0,
1697 "Instance network port if available (e.g. for VNC console)"),
1698 "os": ("OS", QFT_TEXT, 0, "Operating system"),
1699 "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"),
1700 "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"),
1704 def _BuildInstanceFields():
1705 """Builds list of fields for instance queries.
1709 (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
1710 IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")),
1711 # TODO: Allow filtering by secondary node as hostname
1712 (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
1713 "Secondary nodes; usually this will just be one node"),
1714 IQ_CONFIG, 0, lambda ctx, inst: list(inst.secondary_nodes)),
1715 (_MakeField("admin_state", "Autostart", QFT_BOOL,
1716 "Desired state of instance (if set, the instance should be"
1718 IQ_CONFIG, 0, _GetItemAttr("admin_up")),
1719 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
1720 lambda ctx, inst: list(inst.GetTags())),
1721 (_MakeField("console", "Console", QFT_OTHER,
1722 "Instance console information"), IQ_CONSOLE, 0,
1723 _GetInstanceConsole),
1728 (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1729 for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()
1732 # Fields requiring talking to the node
1734 (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
1735 IQ_LIVE, 0, _GetInstOperState),
1736 (_MakeField("oper_ram", "Memory", QFT_UNIT,
1737 "Actual memory usage as seen by hypervisor"),
1738 IQ_LIVE, 0, _GetInstLiveData("memory")),
1739 (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
1740 "Actual number of VCPUs as seen by hypervisor"),
1741 IQ_LIVE, 0, _GetInstLiveData("vcpus")),
1745 status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
1746 constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
1747 constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
1748 constants.INSTST_NODEOFFLINE)
1749 status_doc = ("Instance status; \"%s\" if instance is set to be running"
1750 " and actually is, \"%s\" if instance is stopped and"
1751 " is not running, \"%s\" if instance running, but not on its"
1752 " designated primary node, \"%s\" if instance should be"
1753 " stopped, but is actually running, \"%s\" if instance should"
1754 " run, but doesn't, \"%s\" if instance's primary node is down,"
1755 " \"%s\" if instance's primary node is marked offline" %
1757 fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
1758 IQ_LIVE, 0, _GetInstStatus))
1759 assert set(status_values) == constants.INSTST_ALL, \
1760 "Status documentation mismatch"
1762 (network_fields, network_aliases) = _GetInstanceNetworkFields()
1764 fields.extend(network_fields)
1765 fields.extend(_GetInstanceParameterFields())
1766 fields.extend(_GetInstanceDiskFields())
1767 fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1770 ("vcpus", "be/vcpus"),
1771 ("sda_size", "disk.size/0"),
1772 ("sdb_size", "disk.size/1"),
1775 return _PrepareFieldList(fields, aliases)
1778 class LockQueryData:
1779 """Data container for lock data queries.
1782 def __init__(self, lockdata):
1783 """Initializes this class.
1786 self.lockdata = lockdata
1789 """Iterate over all locks.
1792 return iter(self.lockdata)
1795 def _GetLockOwners(_, data):
1796 """Returns a sorted list of a lock's current owners.
1799 (_, _, owners, _) = data
1802 owners = utils.NiceSort(owners)
1807 def _GetLockPending(_, data):
1808 """Returns a sorted list of a lock's pending acquires.
1811 (_, _, _, pending) = data
1814 pending = [(mode, utils.NiceSort(names))
1815 for (mode, names) in pending]
1820 def _BuildLockFields():
1821 """Builds list of fields for lock queries.
1824 return _PrepareFieldList([
1825 # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
1826 (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
1827 lambda ctx, (name, mode, owners, pending): name),
1828 (_MakeField("mode", "Mode", QFT_OTHER,
1829 "Mode in which the lock is currently acquired"
1830 " (exclusive or shared)"),
1831 LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
1832 (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
1833 LQ_OWNER, 0, _GetLockOwners),
1834 (_MakeField("pending", "Pending", QFT_OTHER,
1835 "Threads waiting for the lock"),
1836 LQ_PENDING, 0, _GetLockPending),
1840 class GroupQueryData:
1841 """Data container for node group data queries.
1844 def __init__(self, groups, group_to_nodes, group_to_instances):
1845 """Initializes this class.
1847 @param groups: List of node group objects
1848 @type group_to_nodes: dict; group UUID as key
1849 @param group_to_nodes: Per-group list of nodes
1850 @type group_to_instances: dict; group UUID as key
1851 @param group_to_instances: Per-group list of (primary) instances
1854 self.groups = groups
1855 self.group_to_nodes = group_to_nodes
1856 self.group_to_instances = group_to_instances
1859 """Iterate over all node groups.
1862 return iter(self.groups)
1865 _GROUP_SIMPLE_FIELDS = {
1866 "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
1867 "name": ("Group", QFT_TEXT, "Group name"),
1868 "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
1869 "uuid": ("UUID", QFT_TEXT, "Group UUID"),
1870 "ndparams": ("NDParams", QFT_OTHER, "Node parameters"),
1874 def _BuildGroupFields():
1875 """Builds list of fields for node group queries.
1879 fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
1881 for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
1883 def _GetLength(getter):
1884 return lambda ctx, group: len(getter(ctx)[group.uuid])
1886 def _GetSortedList(getter):
1887 return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
1889 group_to_nodes = operator.attrgetter("group_to_nodes")
1890 group_to_instances = operator.attrgetter("group_to_instances")
1892 # Add fields for nodes
1894 (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
1895 GQ_NODE, 0, _GetLength(group_to_nodes)),
1896 (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
1897 GQ_NODE, 0, _GetSortedList(group_to_nodes)),
1900 # Add fields for instances
1902 (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
1903 "Number of primary instances"),
1904 GQ_INST, 0, _GetLength(group_to_instances)),
1905 (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
1906 "List of primary instances"),
1907 GQ_INST, 0, _GetSortedList(group_to_instances)),
1912 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), GQ_CONFIG, 0,
1913 lambda ctx, group: list(group.GetTags())),
1916 fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1918 return _PrepareFieldList(fields, [])
1921 class OsInfo(objects.ConfigObject):
1934 def _BuildOsFields():
1935 """Builds list of fields for operating system queries.
1939 (_MakeField("name", "Name", QFT_TEXT, "Operating system name"),
1940 None, 0, _GetItemAttr("name")),
1941 (_MakeField("valid", "Valid", QFT_BOOL,
1942 "Whether operating system definition is valid"),
1943 None, 0, _GetItemAttr("valid")),
1944 (_MakeField("hidden", "Hidden", QFT_BOOL,
1945 "Whether operating system is hidden"),
1946 None, 0, _GetItemAttr("hidden")),
1947 (_MakeField("blacklisted", "Blacklisted", QFT_BOOL,
1948 "Whether operating system is blacklisted"),
1949 None, 0, _GetItemAttr("blacklisted")),
1950 (_MakeField("variants", "Variants", QFT_OTHER,
1951 "Operating system variants"),
1952 None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("variants"))),
1953 (_MakeField("api_versions", "ApiVersions", QFT_OTHER,
1954 "Operating system API versions"),
1955 None, 0, _ConvWrap(sorted, _GetItemAttr("api_versions"))),
1956 (_MakeField("parameters", "Parameters", QFT_OTHER,
1957 "Operating system parameters"),
1958 None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("parameters"))),
1959 (_MakeField("node_status", "NodeStatus", QFT_OTHER,
1960 "Status from node"),
1961 None, 0, _GetItemAttr("node_status")),
1964 return _PrepareFieldList(fields, [])
1967 #: Fields available for node queries
1968 NODE_FIELDS = _BuildNodeFields()
1970 #: Fields available for instance queries
1971 INSTANCE_FIELDS = _BuildInstanceFields()
1973 #: Fields available for lock queries
1974 LOCK_FIELDS = _BuildLockFields()
1976 #: Fields available for node group queries
1977 GROUP_FIELDS = _BuildGroupFields()
1979 #: Fields available for operating system queries
1980 OS_FIELDS = _BuildOsFields()
1982 #: All available resources
1984 constants.QR_INSTANCE: INSTANCE_FIELDS,
1985 constants.QR_NODE: NODE_FIELDS,
1986 constants.QR_LOCK: LOCK_FIELDS,
1987 constants.QR_GROUP: GROUP_FIELDS,
1988 constants.QR_OS: OS_FIELDS,
1991 #: All available field lists
1992 ALL_FIELD_LISTS = ALL_FIELDS.values()