4 # Copyright (C) 2010, 2011, 2012 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Module for query operations
26 - Add field definitions
27 - See how L{NODE_FIELDS} is built
29 - Query field definition (L{objects.QueryFieldDefinition}, use
30 L{_MakeField} for creating), containing:
31 - Name, must be lowercase and match L{FIELD_NAME_RE}
32 - Title for tables, must not contain whitespace and match
34 - Value data type, e.g. L{constants.QFT_NUMBER}
35 - Human-readable description, must not end with punctuation or
37 - Data request type, see e.g. C{NQ_*}
38 - OR-ed flags, see C{QFF_*}
39 - A retrieval function, see L{Query.__init__} for description
40 - Pass list of fields through L{_PrepareFieldList} for preparation and
42 - Instantiate L{Query} with prepared field list definition and selected fields
43 - Call L{Query.RequestedData} to determine what data to collect/compute
44 - Call L{Query.Query} or L{Query.OldStyleQuery} with collected data and use
46 - Data container must support iteration using C{__iter__}
47 - Items are passed to retrieval functions and can have any format
48 - Call L{Query.GetFields} to get list of definitions for selected fields
50 @attention: Retrieval functions must be idempotent. They can be called multiple
51 times, in any order and any number of times.
59 from ganeti import constants
60 from ganeti import errors
61 from ganeti import utils
62 from ganeti import compat
63 from ganeti import objects
65 from ganeti import runtime
66 from ganeti import qlang
68 from ganeti.constants import (QFT_UNKNOWN, QFT_TEXT, QFT_BOOL, QFT_NUMBER,
69 QFT_UNIT, QFT_TIMESTAMP, QFT_OTHER,
70 RS_NORMAL, RS_UNKNOWN, RS_NODATA,
71 RS_UNAVAIL, RS_OFFLINE)
74 # Constants for requesting data from the caller/data provider. Each property
75 # collected/computed separately by the data provider should have its own to
76 # only collect the requested data and not more.
88 IQ_NODES) = range(100, 105)
92 LQ_PENDING) = range(10, 13)
96 GQ_INST) = range(200, 203)
100 CQ_WATCHER_PAUSE) = range(300, 303)
104 QFF_IP_ADDRESS = 0x02
105 # Next values: 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200
106 QFF_ALL = (QFF_HOSTNAME | QFF_IP_ADDRESS)
108 FIELD_NAME_RE = re.compile(r"^[a-z0-9/._]+$")
109 TITLE_RE = re.compile(r"^[^\s]+$")
110 DOC_RE = re.compile(r"^[A-Z].*[^.,?!]$")
112 #: Verification function for each field type
114 QFT_UNKNOWN: ht.TNone,
115 QFT_TEXT: ht.TString,
119 QFT_TIMESTAMP: ht.TNumber,
120 QFT_OTHER: lambda _: True,
123 # Unique objects for special field statuses
124 _FS_UNKNOWN = object()
125 _FS_NODATA = object()
126 _FS_UNAVAIL = object()
127 _FS_OFFLINE = object()
129 #: List of all special status
130 _FS_ALL = frozenset([_FS_UNKNOWN, _FS_NODATA, _FS_UNAVAIL, _FS_OFFLINE])
132 #: VType to QFT mapping
134 # TODO: fix validation of empty strings
135 constants.VTYPE_STRING: QFT_OTHER, # since VTYPE_STRINGs can be empty
136 constants.VTYPE_MAYBE_STRING: QFT_OTHER,
137 constants.VTYPE_BOOL: QFT_BOOL,
138 constants.VTYPE_SIZE: QFT_UNIT,
139 constants.VTYPE_INT: QFT_NUMBER,
142 _SERIAL_NO_DOC = "%s object serial number, incremented on each modification"
144 # TODO: Consider moving titles closer to constants
146 constants.ND_OOB_PROGRAM: "OutOfBandProgram",
147 constants.ND_SPINDLE_COUNT: "SpindleCount",
151 def _GetUnknownField(ctx, item): # pylint: disable=W0613
152 """Gets the contents of an unknown field.
158 def _GetQueryFields(fielddefs, selected):
159 """Calculates the internal list of selected fields.
161 Unknown fields are returned as L{constants.QFT_UNKNOWN}.
163 @type fielddefs: dict
164 @param fielddefs: Field definitions
165 @type selected: list of strings
166 @param selected: List of selected fields
171 for name in selected:
173 fdef = fielddefs[name]
175 fdef = (_MakeField(name, name, QFT_UNKNOWN, "Unknown field '%s'" % name),
176 None, 0, _GetUnknownField)
178 assert len(fdef) == 4
185 def GetAllFields(fielddefs):
186 """Extract L{objects.QueryFieldDefinition} from field definitions.
188 @rtype: list of L{objects.QueryFieldDefinition}
191 return [fdef for (fdef, _, _, _) in fielddefs]
195 """Class for filter analytics.
197 When filters are used, the user of the L{Query} class usually doesn't know
198 exactly which items will be necessary for building the result. It therefore
199 has to prepare and compute the input data for potentially returning
202 There are two ways to optimize this. The first, and simpler, is to assign
203 each field a group of data, so that the caller can determine which
204 computations are necessary depending on the data groups requested. The list
205 of referenced groups must also be computed for fields referenced in the
208 The second is restricting the items based on a primary key. The primary key
209 is usually a unique name (e.g. a node name). This class extracts all
210 referenced names from a filter. If it encounters any filter condition which
211 disallows such a list to be determined (e.g. a non-equality filter), all
212 names will be requested.
214 The end-effect is that any operation other than L{qlang.OP_OR} and
215 L{qlang.OP_EQUAL} will make the query more expensive.
218 def __init__(self, namefield):
219 """Initializes this class.
221 @type namefield: string
222 @param namefield: Field caller is interested in
225 self._namefield = namefield
227 #: Whether all names need to be requested (e.g. if a non-equality operator
229 self._allnames = False
231 #: Which names to request
234 #: Data kinds referenced by the filter (used by L{Query.RequestedData})
235 self._datakinds = set()
237 def RequestedNames(self):
238 """Returns all requested values.
240 Returns C{None} if list of values can't be determined (e.g. encountered
241 non-equality operators).
246 if self._allnames or self._names is None:
249 return utils.UniqueSequence(self._names)
251 def ReferencedData(self):
252 """Returns all kinds of data referenced by the filter.
255 return frozenset(self._datakinds)
257 def _NeedAllNames(self):
258 """Changes internal state to request all names.
261 self._allnames = True
264 def NoteLogicOp(self, op):
265 """Called when handling a logic operation.
271 if op != qlang.OP_OR:
274 def NoteUnaryOp(self, op): # pylint: disable=W0613
275 """Called when handling an unary operation.
283 def NoteBinaryOp(self, op, datakind, name, value):
284 """Called when handling a binary operation.
289 @param name: Left-hand side of operator (field name)
290 @param value: Right-hand side of operator
293 if datakind is not None:
294 self._datakinds.add(datakind)
299 # If any operator other than equality was used, all names need to be
301 if op == qlang.OP_EQUAL and name == self._namefield:
302 if self._names is None:
304 self._names.append(value)
309 def _WrapLogicOp(op_fn, sentences, ctx, item):
310 """Wrapper for logic operator functions.
313 return op_fn(fn(ctx, item) for fn in sentences)
316 def _WrapUnaryOp(op_fn, inner, ctx, item):
317 """Wrapper for unary operator functions.
320 return op_fn(inner(ctx, item))
323 def _WrapBinaryOp(op_fn, retrieval_fn, value, ctx, item):
324 """Wrapper for binary operator functions.
327 return op_fn(retrieval_fn(ctx, item), value)
330 def _WrapNot(fn, lhs, rhs):
331 """Negates the result of a wrapped function.
334 return not fn(lhs, rhs)
337 def _PrepareRegex(pattern):
338 """Compiles a regular expression.
342 return re.compile(pattern)
343 except re.error, err:
344 raise errors.ParameterError("Invalid regex pattern (%s)" % err)
347 class _FilterCompilerHelper:
348 """Converts a query filter to a callable usable for filtering.
351 # String statement has no effect, pylint: disable=W0105
353 #: How deep filters can be nested
356 # Unique identifiers for operator groups
359 _OPTYPE_BINARY) = range(1, 4)
361 """Functions for equality checks depending on field flags.
363 List of tuples containing flags and a callable receiving the left- and
364 right-hand side of the operator. The flags are an OR-ed value of C{QFF_*}
365 (e.g. L{QFF_HOSTNAME}).
367 Order matters. The first item with flags will be used. Flags are checked
373 lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
374 case_sensitive=False),
376 (None, operator.eq, None),
381 Operator as key (C{qlang.OP_*}), value a tuple of operator group
382 (C{_OPTYPE_*}) and a group-specific value:
384 - C{_OPTYPE_LOGIC}: Callable taking any number of arguments; used by
386 - C{_OPTYPE_UNARY}: Always C{None}; details handled by L{_HandleUnaryOp}
387 - C{_OPTYPE_BINARY}: Callable taking exactly two parameters, the left- and
388 right-hand side of the operator, used by L{_HandleBinaryOp}
393 qlang.OP_OR: (_OPTYPE_LOGIC, compat.any),
394 qlang.OP_AND: (_OPTYPE_LOGIC, compat.all),
397 qlang.OP_NOT: (_OPTYPE_UNARY, None),
398 qlang.OP_TRUE: (_OPTYPE_UNARY, None),
401 qlang.OP_EQUAL: (_OPTYPE_BINARY, _EQUALITY_CHECKS),
403 (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn), valprepfn)
404 for (flags, fn, valprepfn) in _EQUALITY_CHECKS]),
405 qlang.OP_REGEXP: (_OPTYPE_BINARY, [
406 (None, lambda lhs, rhs: rhs.search(lhs), _PrepareRegex),
408 qlang.OP_CONTAINS: (_OPTYPE_BINARY, [
409 (None, operator.contains, None),
413 def __init__(self, fields):
414 """Initializes this class.
416 @param fields: Field definitions (return value of L{_PrepareFieldList})
419 self._fields = fields
421 self._op_handler = None
423 def __call__(self, hints, qfilter):
424 """Converts a query filter into a callable function.
426 @type hints: L{_FilterHints} or None
427 @param hints: Callbacks doing analysis on filter
429 @param qfilter: Filter structure
431 @return: Function receiving context and item as parameters, returning
432 boolean as to whether item matches filter
437 (self._HandleLogicOp, getattr(hints, "NoteLogicOp", None)),
439 (self._HandleUnaryOp, getattr(hints, "NoteUnaryOp", None)),
441 (self._HandleBinaryOp, getattr(hints, "NoteBinaryOp", None)),
445 filter_fn = self._Compile(qfilter, 0)
447 self._op_handler = None
451 def _Compile(self, qfilter, level):
452 """Inner function for converting filters.
454 Calls the correct handler functions for the top-level operator. This
455 function is called recursively (e.g. for logic operators).
458 if not (isinstance(qfilter, (list, tuple)) and qfilter):
459 raise errors.ParameterError("Invalid filter on level %s" % level)
462 if level >= self._LEVELS_MAX:
463 raise errors.ParameterError("Only up to %s levels are allowed (filter"
464 " nested too deep)" % self._LEVELS_MAX)
466 # Create copy to be modified
467 operands = qfilter[:]
471 (kind, op_data) = self._OPS[op]
473 raise errors.ParameterError("Unknown operator '%s'" % op)
475 (handler, hints_cb) = self._op_handler[kind]
477 return handler(hints_cb, level, op, op_data, operands)
479 def _LookupField(self, name):
480 """Returns a field definition by name.
484 return self._fields[name]
486 raise errors.ParameterError("Unknown field '%s'" % name)
488 def _HandleLogicOp(self, hints_fn, level, op, op_fn, operands):
489 """Handles logic operators.
491 @type hints_fn: callable
492 @param hints_fn: Callback doing some analysis on the filter
494 @param level: Current depth
497 @type op_fn: callable
498 @param op_fn: Function implementing operator
500 @param operands: List of operands
506 return compat.partial(_WrapLogicOp, op_fn,
507 [self._Compile(op, level + 1) for op in operands])
509 def _HandleUnaryOp(self, hints_fn, level, op, op_fn, operands):
510 """Handles unary operators.
512 @type hints_fn: callable
513 @param hints_fn: Callback doing some analysis on the filter
515 @param level: Current depth
518 @type op_fn: callable
519 @param op_fn: Function implementing operator
521 @param operands: List of operands
529 if len(operands) != 1:
530 raise errors.ParameterError("Unary operator '%s' expects exactly one"
533 if op == qlang.OP_TRUE:
534 (_, _, _, retrieval_fn) = self._LookupField(operands[0])
536 op_fn = operator.truth
538 elif op == qlang.OP_NOT:
539 op_fn = operator.not_
540 arg = self._Compile(operands[0], level + 1)
542 raise errors.ProgrammerError("Can't handle operator '%s'" % op)
544 return compat.partial(_WrapUnaryOp, op_fn, arg)
546 def _HandleBinaryOp(self, hints_fn, level, op, op_data, operands):
547 """Handles binary operators.
549 @type hints_fn: callable
550 @param hints_fn: Callback doing some analysis on the filter
552 @param level: Current depth
555 @param op_data: Functions implementing operators
557 @param operands: List of operands
560 # Unused arguments, pylint: disable=W0613
562 (name, value) = operands
563 except (ValueError, TypeError):
564 raise errors.ParameterError("Invalid binary operator, expected exactly"
567 (fdef, datakind, field_flags, retrieval_fn) = self._LookupField(name)
569 assert fdef.kind != QFT_UNKNOWN
571 # TODO: Type conversions?
573 verify_fn = _VERIFY_FN[fdef.kind]
574 if not verify_fn(value):
575 raise errors.ParameterError("Unable to compare field '%s' (type '%s')"
576 " with '%s', expected %s" %
577 (name, fdef.kind, value.__class__.__name__,
581 hints_fn(op, datakind, name, value)
583 for (fn_flags, fn, valprepfn) in op_data:
584 if fn_flags is None or fn_flags & field_flags:
585 # Prepare value if necessary (e.g. compile regular expression)
587 value = valprepfn(value)
589 return compat.partial(_WrapBinaryOp, fn, retrieval_fn, value)
591 raise errors.ProgrammerError("Unable to find operator implementation"
592 " (op '%s', flags %s)" % (op, field_flags))
595 def _CompileFilter(fields, hints, qfilter):
596 """Converts a query filter into a callable function.
598 See L{_FilterCompilerHelper} for details.
603 return _FilterCompilerHelper(fields)(hints, qfilter)
607 def __init__(self, fieldlist, selected, qfilter=None, namefield=None):
608 """Initializes this class.
610 The field definition is a dictionary with the field's name as a key and a
611 tuple containing, in order, the field definition object
612 (L{objects.QueryFieldDefinition}, the data kind to help calling code
613 collect data and a retrieval function. The retrieval function is called
614 with two parameters, in order, the data container and the item in container
615 (see L{Query.Query}).
617 Users of this class can call L{RequestedData} before preparing the data
618 container to determine what data is needed.
620 @type fieldlist: dictionary
621 @param fieldlist: Field definitions
622 @type selected: list of strings
623 @param selected: List of selected fields
626 assert namefield is None or namefield in fieldlist
628 self._fields = _GetQueryFields(fieldlist, selected)
630 self._filter_fn = None
631 self._requested_names = None
632 self._filter_datakinds = frozenset()
634 if qfilter is not None:
635 # Collect requested names if wanted
637 hints = _FilterHints(namefield)
641 # Build filter function
642 self._filter_fn = _CompileFilter(fieldlist, hints, qfilter)
644 self._requested_names = hints.RequestedNames()
645 self._filter_datakinds = hints.ReferencedData()
647 if namefield is None:
650 (_, _, _, self._name_fn) = fieldlist[namefield]
652 def RequestedNames(self):
653 """Returns all names referenced in the filter.
655 If there is no filter or operators are preventing determining the exact
656 names, C{None} is returned.
659 return self._requested_names
661 def RequestedData(self):
662 """Gets requested kinds of data.
667 return (self._filter_datakinds |
668 frozenset(datakind for (_, datakind, _, _) in self._fields
669 if datakind is not None))
672 """Returns the list of fields for this query.
674 Includes unknown fields.
676 @rtype: List of L{objects.QueryFieldDefinition}
679 return GetAllFields(self._fields)
681 def Query(self, ctx, sort_by_name=True):
684 @param ctx: Data container passed to field retrieval functions, must
685 support iteration using C{__iter__}
686 @type sort_by_name: boolean
687 @param sort_by_name: Whether to sort by name or keep the input data's
691 sort = (self._name_fn and sort_by_name)
695 for idx, item in enumerate(ctx):
696 if not (self._filter_fn is None or self._filter_fn(ctx, item)):
699 row = [_ProcessResult(fn(ctx, item)) for (_, _, _, fn) in self._fields]
703 _VerifyResultRow(self._fields, row)
706 (status, name) = _ProcessResult(self._name_fn(ctx, item))
707 assert status == constants.RS_NORMAL
708 # TODO: Are there cases where we wouldn't want to use NiceSort?
709 result.append((utils.NiceSortKey(name), idx, row))
716 # TODO: Would "heapq" be more efficient than sorting?
718 # Sorting in-place instead of using "sorted()"
721 assert not result or (len(result[0]) == 3 and len(result[-1]) == 3)
723 return map(operator.itemgetter(2), result)
725 def OldStyleQuery(self, ctx, sort_by_name=True):
726 """Query with "old" query result format.
728 See L{Query.Query} for arguments.
731 unknown = set(fdef.name for (fdef, _, _, _) in self._fields
732 if fdef.kind == QFT_UNKNOWN)
734 raise errors.OpPrereqError("Unknown output fields selected: %s" %
735 (utils.CommaJoin(unknown), ),
738 return [[value for (_, value) in row]
739 for row in self.Query(ctx, sort_by_name=sort_by_name)]
742 def _ProcessResult(value):
743 """Converts result values into externally-visible ones.
746 if value is _FS_UNKNOWN:
747 return (RS_UNKNOWN, None)
748 elif value is _FS_NODATA:
749 return (RS_NODATA, None)
750 elif value is _FS_UNAVAIL:
751 return (RS_UNAVAIL, None)
752 elif value is _FS_OFFLINE:
753 return (RS_OFFLINE, None)
755 return (RS_NORMAL, value)
758 def _VerifyResultRow(fields, row):
759 """Verifies the contents of a query result row.
762 @param fields: Field definitions for result
763 @type row: list of tuples
767 assert len(row) == len(fields)
769 for ((status, value), (fdef, _, _, _)) in zip(row, fields):
770 if status == RS_NORMAL:
771 if not _VERIFY_FN[fdef.kind](value):
772 errs.append("normal field %s fails validation (value is %s)" %
774 elif value is not None:
775 errs.append("abnormal field %s has a non-None value" % fdef.name)
776 assert not errs, ("Failed validation: %s in row %s" %
777 (utils.CommaJoin(errs), row))
780 def _FieldDictKey((fdef, _, flags, fn)):
781 """Generates key for field dictionary.
784 assert fdef.name and fdef.title, "Name and title are required"
785 assert FIELD_NAME_RE.match(fdef.name)
786 assert TITLE_RE.match(fdef.title)
787 assert (DOC_RE.match(fdef.doc) and len(fdef.doc.splitlines()) == 1 and
788 fdef.doc.strip() == fdef.doc), \
789 "Invalid description for field '%s'" % fdef.name
791 assert (flags & ~QFF_ALL) == 0, "Unknown flags for field '%s'" % fdef.name
796 def _PrepareFieldList(fields, aliases):
797 """Prepares field list for use by L{Query}.
799 Converts the list to a dictionary and does some verification.
801 @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data
802 kind, retrieval function)
803 @param fields: List of fields, see L{Query.__init__} for a better
805 @type aliases: list of tuples; (alias, target)
806 @param aliases: list of tuples containing aliases; for each
807 alias/target pair, a duplicate will be created in the field list
809 @return: Field dictionary for L{Query}
813 duplicates = utils.FindDuplicates(fdef.title.lower()
814 for (fdef, _, _, _) in fields)
815 assert not duplicates, "Duplicate title(s) found: %r" % duplicates
817 result = utils.SequenceToDict(fields, key=_FieldDictKey)
819 for alias, target in aliases:
820 assert alias not in result, "Alias %s overrides an existing field" % alias
821 assert target in result, "Missing target %s for alias %s" % (target, alias)
822 (fdef, k, flags, fn) = result[target]
825 result[alias] = (fdef, k, flags, fn)
827 assert len(result) == len(fields) + len(aliases)
828 assert compat.all(name == fdef.name
829 for (name, (fdef, _, _, _)) in result.items())
834 def GetQueryResponse(query, ctx, sort_by_name=True):
835 """Prepares the response for a query.
837 @type query: L{Query}
838 @param ctx: Data container, see L{Query.Query}
839 @type sort_by_name: boolean
840 @param sort_by_name: Whether to sort by name or keep the input data's
844 return objects.QueryResponse(data=query.Query(ctx, sort_by_name=sort_by_name),
845 fields=query.GetFields()).ToDict()
848 def QueryFields(fielddefs, selected):
849 """Returns list of available fields.
851 @type fielddefs: dict
852 @param fielddefs: Field definitions
853 @type selected: list of strings
854 @param selected: List of selected fields
855 @return: List of L{objects.QueryFieldDefinition}
859 # Client requests all fields, sort by name
860 fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
861 key=operator.attrgetter("name"))
863 # Keep order as requested by client
864 fdefs = Query(fielddefs, selected).GetFields()
866 return objects.QueryFieldsResponse(fields=fdefs).ToDict()
869 def _MakeField(name, title, kind, doc):
870 """Wrapper for creating L{objects.QueryFieldDefinition} instances.
872 @param name: Field name as a regular expression
873 @param title: Human-readable title
874 @param kind: Field type
875 @param doc: Human-readable description
878 return objects.QueryFieldDefinition(name=name, title=title, kind=kind,
882 def _StaticValueInner(value, ctx, _): # pylint: disable=W0613
883 """Returns a static value.
889 def _StaticValue(value):
890 """Prepares a function to return a static value.
893 return compat.partial(_StaticValueInner, value)
896 def _GetNodeRole(node, master_name):
897 """Determine node role.
899 @type node: L{objects.Node}
900 @param node: Node object
901 @type master_name: string
902 @param master_name: Master node name
905 if node.name == master_name:
906 return constants.NR_MASTER
907 elif node.master_candidate:
908 return constants.NR_MCANDIDATE
910 return constants.NR_DRAINED
912 return constants.NR_OFFLINE
914 return constants.NR_REGULAR
917 def _GetItemAttr(attr):
918 """Returns a field function to return an attribute of the item.
920 @param attr: Attribute name
923 getter = operator.attrgetter(attr)
924 return lambda _, item: getter(item)
927 def _GetNDParam(name):
928 """Return a field function to return an ND parameter out of the context.
932 if ctx.ndparams is None:
935 return ctx.ndparams.get(name, None)
939 def _BuildNDFields(is_group):
940 """Builds all the ndparam fields.
942 @param is_group: whether this is called at group or node level
946 field_kind = GQ_CONFIG
948 field_kind = NQ_GROUP
949 return [(_MakeField("ndp/%s" % name, NDP_TITLE.get(name, "ndp/%s" % name),
950 _VTToQFT[kind], "The \"%s\" node parameter" % name),
951 field_kind, 0, _GetNDParam(name))
952 for name, kind in constants.NDS_PARAMETER_TYPES.items()]
955 def _ConvWrapInner(convert, fn, ctx, item):
956 """Wrapper for converting values.
958 @param convert: Conversion function receiving value as single parameter
959 @param fn: Retrieval function
962 value = fn(ctx, item)
964 # Is the value an abnormal status?
965 if compat.any(value is fs for fs in _FS_ALL):
969 # TODO: Should conversion function also receive context, item or both?
970 return convert(value)
973 def _ConvWrap(convert, fn):
974 """Convenience wrapper for L{_ConvWrapInner}.
976 @param convert: Conversion function receiving value as single parameter
977 @param fn: Retrieval function
980 return compat.partial(_ConvWrapInner, convert, fn)
983 def _GetItemTimestamp(getter):
984 """Returns function for getting timestamp of item.
986 @type getter: callable
987 @param getter: Function to retrieve timestamp attribute
991 """Returns a timestamp of item.
994 timestamp = getter(item)
995 if timestamp is None:
996 # Old configs might not have all timestamps
1004 def _GetItemTimestampFields(datatype):
1005 """Returns common timestamp fields.
1007 @param datatype: Field data type for use by L{Query.RequestedData}
1011 (_MakeField("ctime", "CTime", QFT_TIMESTAMP, "Creation timestamp"),
1012 datatype, 0, _GetItemTimestamp(operator.attrgetter("ctime"))),
1013 (_MakeField("mtime", "MTime", QFT_TIMESTAMP, "Modification timestamp"),
1014 datatype, 0, _GetItemTimestamp(operator.attrgetter("mtime"))),
1018 class NodeQueryData:
1019 """Data container for node data queries.
1022 def __init__(self, nodes, live_data, master_name, node_to_primary,
1023 node_to_secondary, groups, oob_support, cluster):
1024 """Initializes this class.
1028 self.live_data = live_data
1029 self.master_name = master_name
1030 self.node_to_primary = node_to_primary
1031 self.node_to_secondary = node_to_secondary
1032 self.groups = groups
1033 self.oob_support = oob_support
1034 self.cluster = cluster
1036 # Used for individual rows
1037 self.curlive_data = None
1038 self.ndparams = None
1041 """Iterate over all nodes.
1043 This function has side-effects and only one instance of the resulting
1044 generator should be used at a time.
1047 for node in self.nodes:
1048 group = self.groups.get(node.group, None)
1050 self.ndparams = None
1052 self.ndparams = self.cluster.FillND(node, group)
1054 self.curlive_data = self.live_data.get(node.name, None)
1056 self.curlive_data = None
1060 #: Fields that are direct attributes of an L{objects.Node} object
1061 _NODE_SIMPLE_FIELDS = {
1062 "drained": ("Drained", QFT_BOOL, 0, "Whether node is drained"),
1063 "master_candidate": ("MasterC", QFT_BOOL, 0,
1064 "Whether node is a master candidate"),
1065 "master_capable": ("MasterCapable", QFT_BOOL, 0,
1066 "Whether node can become a master candidate"),
1067 "name": ("Node", QFT_TEXT, QFF_HOSTNAME, "Node name"),
1068 "offline": ("Offline", QFT_BOOL, 0, "Whether node is marked offline"),
1069 "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Node"),
1070 "uuid": ("UUID", QFT_TEXT, 0, "Node UUID"),
1071 "vm_capable": ("VMCapable", QFT_BOOL, 0, "Whether node can host instances"),
1075 #: Fields requiring talking to the node
1076 # Note that none of these are available for non-vm_capable nodes
1077 _NODE_LIVE_FIELDS = {
1078 "bootid": ("BootID", QFT_TEXT, "bootid",
1079 "Random UUID renewed for each system reboot, can be used"
1080 " for detecting reboots by tracking changes"),
1081 "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes",
1082 "Number of NUMA domains on node (if exported by hypervisor)"),
1083 "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets",
1084 "Number of physical CPU sockets (if exported by hypervisor)"),
1085 "ctotal": ("CTotal", QFT_NUMBER, "cpu_total", "Number of logical processors"),
1086 "dfree": ("DFree", QFT_UNIT, "vg_free",
1087 "Available disk space in volume group"),
1088 "dtotal": ("DTotal", QFT_UNIT, "vg_size",
1089 "Total disk space in volume group used for instance disk"
1091 "mfree": ("MFree", QFT_UNIT, "memory_free",
1092 "Memory available for instance allocations"),
1093 "mnode": ("MNode", QFT_UNIT, "memory_dom0",
1094 "Amount of memory used by node (dom0 for Xen)"),
1095 "mtotal": ("MTotal", QFT_UNIT, "memory_total",
1096 "Total amount of memory of physical machine"),
1101 """Build function for calling another function with an node group.
1103 @param cb: The callback to be called with the nodegroup
1107 """Get group data for a node.
1109 @type ctx: L{NodeQueryData}
1110 @type inst: L{objects.Node}
1111 @param inst: Node object
1114 ng = ctx.groups.get(node.group, None)
1116 # Nodes always have a group, or the configuration is corrupt
1119 return cb(ctx, node, ng)
1124 def _GetNodeGroup(ctx, node, ng): # pylint: disable=W0613
1125 """Returns the name of a node's group.
1127 @type ctx: L{NodeQueryData}
1128 @type node: L{objects.Node}
1129 @param node: Node object
1130 @type ng: L{objects.NodeGroup}
1131 @param ng: The node group this node belongs to
1137 def _GetNodePower(ctx, node):
1138 """Returns the node powered state
1140 @type ctx: L{NodeQueryData}
1141 @type node: L{objects.Node}
1142 @param node: Node object
1145 if ctx.oob_support[node.name]:
1151 def _GetNdParams(ctx, node, ng):
1152 """Returns the ndparams for this node.
1154 @type ctx: L{NodeQueryData}
1155 @type node: L{objects.Node}
1156 @param node: Node object
1157 @type ng: L{objects.NodeGroup}
1158 @param ng: The node group this node belongs to
1161 return ctx.cluster.SimpleFillND(ng.FillND(node))
1164 def _GetLiveNodeField(field, kind, ctx, node):
1165 """Gets the value of a "live" field from L{NodeQueryData}.
1167 @param field: Live field name
1168 @param kind: Data kind, one of L{constants.QFT_ALL}
1169 @type ctx: L{NodeQueryData}
1170 @type node: L{objects.Node}
1171 @param node: Node object
1177 if not node.vm_capable:
1180 if not ctx.curlive_data:
1184 value = ctx.curlive_data[field]
1188 if kind == QFT_TEXT:
1191 assert kind in (QFT_NUMBER, QFT_UNIT)
1193 # Try to convert into number
1196 except (ValueError, TypeError):
1197 logging.exception("Failed to convert node field '%s' (value %r) to int",
1202 def _GetNodeHvState(_, node):
1203 """Converts node's hypervisor state for query result.
1206 hv_state = node.hv_state
1208 if hv_state is None:
1211 return dict((name, value.ToDict()) for (name, value) in hv_state.items())
1214 def _GetNodeDiskState(_, node):
1215 """Converts node's disk state for query result.
1218 disk_state = node.disk_state
1220 if disk_state is None:
1223 return dict((disk_kind, dict((name, value.ToDict())
1224 for (name, value) in kind_state.items()))
1225 for (disk_kind, kind_state) in disk_state.items())
1228 def _BuildNodeFields():
1229 """Builds list of fields for node queries.
1233 (_MakeField("pip", "PrimaryIP", QFT_TEXT, "Primary IP address"),
1234 NQ_CONFIG, 0, _GetItemAttr("primary_ip")),
1235 (_MakeField("sip", "SecondaryIP", QFT_TEXT, "Secondary IP address"),
1236 NQ_CONFIG, 0, _GetItemAttr("secondary_ip")),
1237 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0,
1238 lambda ctx, node: list(node.GetTags())),
1239 (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"),
1240 NQ_CONFIG, 0, lambda ctx, node: node.name == ctx.master_name),
1241 (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0,
1242 _GetGroup(_GetNodeGroup)),
1243 (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"),
1244 NQ_CONFIG, 0, _GetItemAttr("group")),
1245 (_MakeField("powered", "Powered", QFT_BOOL,
1246 "Whether node is thought to be powered on"),
1247 NQ_OOB, 0, _GetNodePower),
1248 (_MakeField("ndparams", "NodeParameters", QFT_OTHER,
1249 "Merged node parameters"),
1250 NQ_GROUP, 0, _GetGroup(_GetNdParams)),
1251 (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER,
1252 "Custom node parameters"),
1253 NQ_GROUP, 0, _GetItemAttr("ndparams")),
1254 (_MakeField("hv_state", "HypervisorState", QFT_OTHER, "Hypervisor state"),
1255 NQ_CONFIG, 0, _GetNodeHvState),
1256 (_MakeField("disk_state", "DiskState", QFT_OTHER, "Disk state"),
1257 NQ_CONFIG, 0, _GetNodeDiskState),
1260 fields.extend(_BuildNDFields(False))
1263 role_values = (constants.NR_MASTER, constants.NR_MCANDIDATE,
1264 constants.NR_REGULAR, constants.NR_DRAINED,
1265 constants.NR_OFFLINE)
1266 role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate,"
1267 " \"%s\" for regular, \"%s\" for a drained, \"%s\" for offline" %
1269 fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
1270 lambda ctx, node: _GetNodeRole(node, ctx.master_name)))
1271 assert set(role_values) == constants.NR_ALL
1273 def _GetLength(getter):
1274 return lambda ctx, node: len(getter(ctx)[node.name])
1276 def _GetList(getter):
1277 return lambda ctx, node: list(getter(ctx)[node.name])
1279 # Add fields operating on instance lists
1280 for prefix, titleprefix, docword, getter in \
1281 [("p", "Pri", "primary", operator.attrgetter("node_to_primary")),
1282 ("s", "Sec", "secondary", operator.attrgetter("node_to_secondary"))]:
1283 # TODO: Allow filterting by hostname in list
1285 (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER,
1286 "Number of instances with this node as %s" % docword),
1287 NQ_INST, 0, _GetLength(getter)),
1288 (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
1290 "List of instances with this node as %s" % docword),
1291 NQ_INST, 0, _GetList(getter)),
1296 (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1297 for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()
1300 # Add fields requiring live data
1302 (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1303 compat.partial(_GetLiveNodeField, nfield, kind))
1304 for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()
1308 fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1310 return _PrepareFieldList(fields, [])
1313 class InstanceQueryData:
1314 """Data container for instance data queries.
1317 def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
1318 live_data, wrongnode_inst, console, nodes, groups):
1319 """Initializes this class.
1321 @param instances: List of instance objects
1322 @param cluster: Cluster object
1323 @type disk_usage: dict; instance name as key
1324 @param disk_usage: Per-instance disk usage
1325 @type offline_nodes: list of strings
1326 @param offline_nodes: List of offline nodes
1327 @type bad_nodes: list of strings
1328 @param bad_nodes: List of faulty nodes
1329 @type live_data: dict; instance name as key
1330 @param live_data: Per-instance live data
1331 @type wrongnode_inst: set
1332 @param wrongnode_inst: Set of instances running on wrong node(s)
1333 @type console: dict; instance name as key
1334 @param console: Per-instance console information
1335 @type nodes: dict; node name as key
1336 @param nodes: Node objects
1339 assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
1340 "Offline nodes not included in bad nodes"
1341 assert not (set(live_data.keys()) & set(bad_nodes)), \
1342 "Found live data for bad or offline nodes"
1344 self.instances = instances
1345 self.cluster = cluster
1346 self.disk_usage = disk_usage
1347 self.offline_nodes = offline_nodes
1348 self.bad_nodes = bad_nodes
1349 self.live_data = live_data
1350 self.wrongnode_inst = wrongnode_inst
1351 self.console = console
1353 self.groups = groups
1355 # Used for individual rows
1356 self.inst_hvparams = None
1357 self.inst_beparams = None
1358 self.inst_osparams = None
1359 self.inst_nicparams = None
1362 """Iterate over all instances.
1364 This function has side-effects and only one instance of the resulting
1365 generator should be used at a time.
1368 for inst in self.instances:
1369 self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1370 self.inst_beparams = self.cluster.FillBE(inst)
1371 self.inst_osparams = self.cluster.SimpleFillOS(inst.os, inst.osparams)
1372 self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1373 for nic in inst.nics]
1378 def _GetInstOperState(ctx, inst):
1379 """Get instance's operational status.
1381 @type ctx: L{InstanceQueryData}
1382 @type inst: L{objects.Instance}
1383 @param inst: Instance object
1386 # Can't use RS_OFFLINE here as it would describe the instance to
1387 # be offline when we actually don't know due to missing data
1388 if inst.primary_node in ctx.bad_nodes:
1391 return bool(ctx.live_data.get(inst.name))
1394 def _GetInstLiveData(name):
1395 """Build function for retrieving live data.
1398 @param name: Live data field name
1402 """Get live data for an instance.
1404 @type ctx: L{InstanceQueryData}
1405 @type inst: L{objects.Instance}
1406 @param inst: Instance object
1409 if (inst.primary_node in ctx.bad_nodes or
1410 inst.primary_node in ctx.offline_nodes):
1411 # Can't use RS_OFFLINE here as it would describe the instance to be
1412 # offline when we actually don't know due to missing data
1415 if inst.name in ctx.live_data:
1416 data = ctx.live_data[inst.name]
1425 def _GetInstStatus(ctx, inst):
1426 """Get instance status.
1428 @type ctx: L{InstanceQueryData}
1429 @type inst: L{objects.Instance}
1430 @param inst: Instance object
1433 if inst.primary_node in ctx.offline_nodes:
1434 return constants.INSTST_NODEOFFLINE
1436 if inst.primary_node in ctx.bad_nodes:
1437 return constants.INSTST_NODEDOWN
1439 if bool(ctx.live_data.get(inst.name)):
1440 if inst.name in ctx.wrongnode_inst:
1441 return constants.INSTST_WRONGNODE
1442 elif inst.admin_state == constants.ADMINST_UP:
1443 return constants.INSTST_RUNNING
1445 return constants.INSTST_ERRORUP
1447 if inst.admin_state == constants.ADMINST_UP:
1448 return constants.INSTST_ERRORDOWN
1449 elif inst.admin_state == constants.ADMINST_DOWN:
1450 return constants.INSTST_ADMINDOWN
1452 return constants.INSTST_ADMINOFFLINE
1455 def _GetInstDiskSize(index):
1456 """Build function for retrieving disk size.
1459 @param index: Disk index
1463 """Get size of a disk.
1465 @type inst: L{objects.Instance}
1466 @param inst: Instance object
1470 return inst.disks[index].size
1477 def _GetInstNic(index, cb):
1478 """Build function for calling another function with an instance NIC.
1481 @param index: NIC index
1487 """Call helper function with instance NIC.
1489 @type ctx: L{InstanceQueryData}
1490 @type inst: L{objects.Instance}
1491 @param inst: Instance object
1495 nic = inst.nics[index]
1499 return cb(ctx, index, nic)
1504 def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613
1505 """Get a NIC's IP address.
1507 @type ctx: L{InstanceQueryData}
1508 @type nic: L{objects.NIC}
1509 @param nic: NIC object
1518 def _GetInstNicBridge(ctx, index, _):
1519 """Get a NIC's bridge.
1521 @type ctx: L{InstanceQueryData}
1523 @param index: NIC index
1526 assert len(ctx.inst_nicparams) >= index
1528 nicparams = ctx.inst_nicparams[index]
1530 if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1531 return nicparams[constants.NIC_LINK]
1536 def _GetInstAllNicBridges(ctx, inst):
1537 """Get all network bridges for an instance.
1539 @type ctx: L{InstanceQueryData}
1540 @type inst: L{objects.Instance}
1541 @param inst: Instance object
1544 assert len(ctx.inst_nicparams) == len(inst.nics)
1548 for nicp in ctx.inst_nicparams:
1549 if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1550 result.append(nicp[constants.NIC_LINK])
1554 assert len(result) == len(inst.nics)
1559 def _GetInstNicParam(name):
1560 """Build function for retrieving a NIC parameter.
1563 @param name: Parameter name
1566 def fn(ctx, index, _):
1567 """Get a NIC's bridge.
1569 @type ctx: L{InstanceQueryData}
1570 @type inst: L{objects.Instance}
1571 @param inst: Instance object
1572 @type nic: L{objects.NIC}
1573 @param nic: NIC object
1576 assert len(ctx.inst_nicparams) >= index
1577 return ctx.inst_nicparams[index][name]
1582 def _GetInstanceNetworkFields():
1583 """Get instance fields involving network interfaces.
1585 @return: Tuple containing list of field definitions used as input for
1586 L{_PrepareFieldList} and a list of aliases
1589 nic_mac_fn = lambda ctx, _, nic: nic.mac
1590 nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1591 nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1595 (_MakeField("nic.count", "NICs", QFT_NUMBER,
1596 "Number of network interfaces"),
1597 IQ_CONFIG, 0, lambda ctx, inst: len(inst.nics)),
1598 (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER,
1599 "List containing each network interface's MAC address"),
1600 IQ_CONFIG, 0, lambda ctx, inst: [nic.mac for nic in inst.nics]),
1601 (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
1602 "List containing each network interface's IP address"),
1603 IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
1604 (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
1605 "List containing each network interface's mode"), IQ_CONFIG, 0,
1606 lambda ctx, inst: [nicp[constants.NIC_MODE]
1607 for nicp in ctx.inst_nicparams]),
1608 (_MakeField("nic.links", "NIC_links", QFT_OTHER,
1609 "List containing each network interface's link"), IQ_CONFIG, 0,
1610 lambda ctx, inst: [nicp[constants.NIC_LINK]
1611 for nicp in ctx.inst_nicparams]),
1612 (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
1613 "List containing each network interface's bridge"),
1614 IQ_CONFIG, 0, _GetInstAllNicBridges),
1618 for i in range(constants.MAX_NICS):
1619 numtext = utils.FormatOrdinal(i + 1)
1621 (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT,
1622 "IP address of %s network interface" % numtext),
1623 IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)),
1624 (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
1625 "MAC address of %s network interface" % numtext),
1626 IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
1627 (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
1628 "Mode of %s network interface" % numtext),
1629 IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
1630 (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT,
1631 "Link of %s network interface" % numtext),
1632 IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)),
1633 (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
1634 "Bridge of %s network interface" % numtext),
1635 IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
1639 # Legacy fields for first NIC
1641 ("mac", "nic.mac/0"),
1642 ("bridge", "nic.bridge/0"),
1643 ("nic_mode", "nic.mode/0"),
1644 ("nic_link", "nic.link/0"),
1647 return (fields, aliases)
1650 def _GetInstDiskUsage(ctx, inst):
1651 """Get disk usage for an instance.
1653 @type ctx: L{InstanceQueryData}
1654 @type inst: L{objects.Instance}
1655 @param inst: Instance object
1658 usage = ctx.disk_usage[inst.name]
1666 def _GetInstanceConsole(ctx, inst):
1667 """Get console information for instance.
1669 @type ctx: L{InstanceQueryData}
1670 @type inst: L{objects.Instance}
1671 @param inst: Instance object
1674 consinfo = ctx.console[inst.name]
1676 if consinfo is None:
1682 def _GetInstanceDiskFields():
1683 """Get instance fields involving disks.
1685 @return: List of field definitions used as input for L{_PrepareFieldList}
1689 (_MakeField("disk_usage", "DiskUsage", QFT_UNIT,
1690 "Total disk space used by instance on each of its nodes;"
1691 " this is not the disk size visible to the instance, but"
1692 " the usage on the node"),
1693 IQ_DISKUSAGE, 0, _GetInstDiskUsage),
1694 (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"),
1695 IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
1696 (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
1697 IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
1702 (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1703 "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
1704 IQ_CONFIG, 0, _GetInstDiskSize(i))
1705 for i in range(constants.MAX_DISKS)
1711 def _GetInstanceParameterFields():
1712 """Get instance fields involving parameters.
1714 @return: List of field definitions used as input for L{_PrepareFieldList}
1717 # TODO: Consider moving titles closer to constants
1719 constants.BE_AUTO_BALANCE: "Auto_balance",
1720 constants.BE_MAXMEM: "ConfigMaxMem",
1721 constants.BE_MINMEM: "ConfigMinMem",
1722 constants.BE_VCPUS: "ConfigVCPUs",
1726 constants.HV_ACPI: "ACPI",
1727 constants.HV_BOOT_ORDER: "Boot_order",
1728 constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1729 constants.HV_DISK_TYPE: "Disk_type",
1730 constants.HV_INITRD_PATH: "Initrd_path",
1731 constants.HV_KERNEL_PATH: "Kernel_path",
1732 constants.HV_NIC_TYPE: "NIC_type",
1733 constants.HV_PAE: "PAE",
1734 constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1739 (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
1740 "Hypervisor parameters (merged)"),
1741 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
1742 (_MakeField("beparams", "BackendParameters", QFT_OTHER,
1743 "Backend parameters (merged)"),
1744 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
1745 (_MakeField("osparams", "OpSysParameters", QFT_OTHER,
1746 "Operating system parameters (merged)"),
1747 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_osparams),
1749 # Unfilled parameters
1750 (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
1751 "Custom hypervisor parameters"),
1752 IQ_CONFIG, 0, _GetItemAttr("hvparams")),
1753 (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
1754 "Custom backend parameters",),
1755 IQ_CONFIG, 0, _GetItemAttr("beparams")),
1756 (_MakeField("custom_osparams", "CustomOpSysParameters", QFT_OTHER,
1757 "Custom operating system parameters",),
1758 IQ_CONFIG, 0, _GetItemAttr("osparams")),
1759 (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
1760 "Custom network interface parameters"),
1761 IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1765 def _GetInstHvParam(name):
1766 return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1769 (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1770 _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1771 IQ_CONFIG, 0, _GetInstHvParam(name))
1772 for name, kind in constants.HVS_PARAMETER_TYPES.items()
1773 if name not in constants.HVC_GLOBALS
1777 def _GetInstBeParam(name):
1778 return lambda ctx, _: ctx.inst_beparams.get(name, None)
1781 (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1782 _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1783 IQ_CONFIG, 0, _GetInstBeParam(name))
1784 for name, kind in constants.BES_PARAMETER_TYPES.items()
1790 _INST_SIMPLE_FIELDS = {
1791 "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"),
1792 "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"),
1793 "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"),
1794 # Depending on the hypervisor, the port can be None
1795 "network_port": ("Network_port", QFT_OTHER, 0,
1796 "Instance network port if available (e.g. for VNC console)"),
1797 "os": ("OS", QFT_TEXT, 0, "Operating system"),
1798 "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"),
1799 "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"),
1803 def _GetInstNodeGroup(ctx, default, node_name):
1804 """Gets group UUID of an instance node.
1806 @type ctx: L{InstanceQueryData}
1807 @param default: Default value
1808 @type node_name: string
1809 @param node_name: Node name
1813 node = ctx.nodes[node_name]
1820 def _GetInstNodeGroupName(ctx, default, node_name):
1821 """Gets group name of an instance node.
1823 @type ctx: L{InstanceQueryData}
1824 @param default: Default value
1825 @type node_name: string
1826 @param node_name: Node name
1830 node = ctx.nodes[node_name]
1835 group = ctx.groups[node.group]
1842 def _BuildInstanceFields():
1843 """Builds list of fields for instance queries.
1847 (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
1848 IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")),
1849 (_MakeField("pnode.group", "PrimaryNodeGroup", QFT_TEXT,
1850 "Primary node's group"),
1852 lambda ctx, inst: _GetInstNodeGroupName(ctx, _FS_UNAVAIL,
1853 inst.primary_node)),
1854 (_MakeField("pnode.group.uuid", "PrimaryNodeGroupUUID", QFT_TEXT,
1855 "Primary node's group UUID"),
1857 lambda ctx, inst: _GetInstNodeGroup(ctx, _FS_UNAVAIL, inst.primary_node)),
1858 # TODO: Allow filtering by secondary node as hostname
1859 (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
1860 "Secondary nodes; usually this will just be one node"),
1861 IQ_CONFIG, 0, lambda ctx, inst: list(inst.secondary_nodes)),
1862 (_MakeField("snodes.group", "SecondaryNodesGroups", QFT_OTHER,
1863 "Node groups of secondary nodes"),
1865 lambda ctx, inst: map(compat.partial(_GetInstNodeGroupName, ctx, None),
1866 inst.secondary_nodes)),
1867 (_MakeField("snodes.group.uuid", "SecondaryNodesGroupsUUID", QFT_OTHER,
1868 "Node group UUIDs of secondary nodes"),
1870 lambda ctx, inst: map(compat.partial(_GetInstNodeGroup, ctx, None),
1871 inst.secondary_nodes)),
1872 (_MakeField("admin_state", "InstanceState", QFT_TEXT,
1873 "Desired state of instance"),
1874 IQ_CONFIG, 0, _GetItemAttr("admin_state")),
1875 (_MakeField("admin_up", "Autostart", QFT_BOOL,
1876 "Desired state of instance"),
1877 IQ_CONFIG, 0, lambda ctx, inst: inst.admin_state == constants.ADMINST_UP),
1878 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
1879 lambda ctx, inst: list(inst.GetTags())),
1880 (_MakeField("console", "Console", QFT_OTHER,
1881 "Instance console information"), IQ_CONSOLE, 0,
1882 _GetInstanceConsole),
1887 (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1888 for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()
1891 # Fields requiring talking to the node
1893 (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
1894 IQ_LIVE, 0, _GetInstOperState),
1895 (_MakeField("oper_ram", "Memory", QFT_UNIT,
1896 "Actual memory usage as seen by hypervisor"),
1897 IQ_LIVE, 0, _GetInstLiveData("memory")),
1898 (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
1899 "Actual number of VCPUs as seen by hypervisor"),
1900 IQ_LIVE, 0, _GetInstLiveData("vcpus")),
1904 status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
1905 constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
1906 constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
1907 constants.INSTST_NODEOFFLINE, constants.INSTST_ADMINOFFLINE)
1908 status_doc = ("Instance status; \"%s\" if instance is set to be running"
1909 " and actually is, \"%s\" if instance is stopped and"
1910 " is not running, \"%s\" if instance running, but not on its"
1911 " designated primary node, \"%s\" if instance should be"
1912 " stopped, but is actually running, \"%s\" if instance should"
1913 " run, but doesn't, \"%s\" if instance's primary node is down,"
1914 " \"%s\" if instance's primary node is marked offline,"
1915 " \"%s\" if instance is offline and does not use dynamic"
1916 " resources" % status_values)
1917 fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
1918 IQ_LIVE, 0, _GetInstStatus))
1919 assert set(status_values) == constants.INSTST_ALL, \
1920 "Status documentation mismatch"
1922 (network_fields, network_aliases) = _GetInstanceNetworkFields()
1924 fields.extend(network_fields)
1925 fields.extend(_GetInstanceParameterFields())
1926 fields.extend(_GetInstanceDiskFields())
1927 fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1930 ("vcpus", "be/vcpus"),
1931 ("be/memory", "be/maxmem"),
1932 ("sda_size", "disk.size/0"),
1933 ("sdb_size", "disk.size/1"),
1936 return _PrepareFieldList(fields, aliases)
1939 class LockQueryData:
1940 """Data container for lock data queries.
1943 def __init__(self, lockdata):
1944 """Initializes this class.
1947 self.lockdata = lockdata
1950 """Iterate over all locks.
1953 return iter(self.lockdata)
1956 def _GetLockOwners(_, data):
1957 """Returns a sorted list of a lock's current owners.
1960 (_, _, owners, _) = data
1963 owners = utils.NiceSort(owners)
1968 def _GetLockPending(_, data):
1969 """Returns a sorted list of a lock's pending acquires.
1972 (_, _, _, pending) = data
1975 pending = [(mode, utils.NiceSort(names))
1976 for (mode, names) in pending]
1981 def _BuildLockFields():
1982 """Builds list of fields for lock queries.
1985 return _PrepareFieldList([
1986 # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
1987 (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
1988 lambda ctx, (name, mode, owners, pending): name),
1989 (_MakeField("mode", "Mode", QFT_OTHER,
1990 "Mode in which the lock is currently acquired"
1991 " (exclusive or shared)"),
1992 LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
1993 (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
1994 LQ_OWNER, 0, _GetLockOwners),
1995 (_MakeField("pending", "Pending", QFT_OTHER,
1996 "Threads waiting for the lock"),
1997 LQ_PENDING, 0, _GetLockPending),
2001 class GroupQueryData:
2002 """Data container for node group data queries.
2005 def __init__(self, cluster, groups, group_to_nodes, group_to_instances):
2006 """Initializes this class.
2008 @param cluster: Cluster object
2009 @param groups: List of node group objects
2010 @type group_to_nodes: dict; group UUID as key
2011 @param group_to_nodes: Per-group list of nodes
2012 @type group_to_instances: dict; group UUID as key
2013 @param group_to_instances: Per-group list of (primary) instances
2016 self.groups = groups
2017 self.group_to_nodes = group_to_nodes
2018 self.group_to_instances = group_to_instances
2019 self.cluster = cluster
2021 # Used for individual rows
2022 self.group_ipolicy = None
2023 self.ndparams = None
2026 """Iterate over all node groups.
2028 This function has side-effects and only one instance of the resulting
2029 generator should be used at a time.
2032 for group in self.groups:
2033 self.group_ipolicy = self.cluster.SimpleFillIPolicy(group.ipolicy)
2034 self.ndparams = self.cluster.SimpleFillND(group.ndparams)
2038 _GROUP_SIMPLE_FIELDS = {
2039 "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
2040 "name": ("Group", QFT_TEXT, "Group name"),
2041 "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
2042 "uuid": ("UUID", QFT_TEXT, "Group UUID"),
2046 def _BuildGroupFields():
2047 """Builds list of fields for node group queries.
2051 fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
2053 for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
2055 def _GetLength(getter):
2056 return lambda ctx, group: len(getter(ctx)[group.uuid])
2058 def _GetSortedList(getter):
2059 return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
2061 group_to_nodes = operator.attrgetter("group_to_nodes")
2062 group_to_instances = operator.attrgetter("group_to_instances")
2064 # Add fields for nodes
2066 (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
2067 GQ_NODE, 0, _GetLength(group_to_nodes)),
2068 (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
2069 GQ_NODE, 0, _GetSortedList(group_to_nodes)),
2072 # Add fields for instances
2074 (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
2075 "Number of primary instances"),
2076 GQ_INST, 0, _GetLength(group_to_instances)),
2077 (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
2078 "List of primary instances"),
2079 GQ_INST, 0, _GetSortedList(group_to_instances)),
2084 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), GQ_CONFIG, 0,
2085 lambda ctx, group: list(group.GetTags())),
2086 (_MakeField("ipolicy", "InstancePolicy", QFT_OTHER,
2087 "Instance policy limitations (merged)"),
2088 GQ_CONFIG, 0, lambda ctx, _: ctx.group_ipolicy),
2089 (_MakeField("custom_ipolicy", "CustomInstancePolicy", QFT_OTHER,
2090 "Custom instance policy limitations"),
2091 GQ_CONFIG, 0, _GetItemAttr("ipolicy")),
2092 (_MakeField("custom_ndparams", "CustomNDParams", QFT_OTHER,
2093 "Custom node parameters"),
2094 GQ_CONFIG, 0, _GetItemAttr("ndparams")),
2095 (_MakeField("ndparams", "NDParams", QFT_OTHER,
2097 GQ_CONFIG, 0, lambda ctx, _: ctx.ndparams),
2101 fields.extend(_BuildNDFields(True))
2103 fields.extend(_GetItemTimestampFields(GQ_CONFIG))
2105 return _PrepareFieldList(fields, [])
2108 class OsInfo(objects.ConfigObject):
2121 def _BuildOsFields():
2122 """Builds list of fields for operating system queries.
2126 (_MakeField("name", "Name", QFT_TEXT, "Operating system name"),
2127 None, 0, _GetItemAttr("name")),
2128 (_MakeField("valid", "Valid", QFT_BOOL,
2129 "Whether operating system definition is valid"),
2130 None, 0, _GetItemAttr("valid")),
2131 (_MakeField("hidden", "Hidden", QFT_BOOL,
2132 "Whether operating system is hidden"),
2133 None, 0, _GetItemAttr("hidden")),
2134 (_MakeField("blacklisted", "Blacklisted", QFT_BOOL,
2135 "Whether operating system is blacklisted"),
2136 None, 0, _GetItemAttr("blacklisted")),
2137 (_MakeField("variants", "Variants", QFT_OTHER,
2138 "Operating system variants"),
2139 None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("variants"))),
2140 (_MakeField("api_versions", "ApiVersions", QFT_OTHER,
2141 "Operating system API versions"),
2142 None, 0, _ConvWrap(sorted, _GetItemAttr("api_versions"))),
2143 (_MakeField("parameters", "Parameters", QFT_OTHER,
2144 "Operating system parameters"),
2145 None, 0, _ConvWrap(compat.partial(utils.NiceSort, key=compat.fst),
2146 _GetItemAttr("parameters"))),
2147 (_MakeField("node_status", "NodeStatus", QFT_OTHER,
2148 "Status from node"),
2149 None, 0, _GetItemAttr("node_status")),
2152 return _PrepareFieldList(fields, [])
2155 def _JobUnavailInner(fn, ctx, (job_id, job)): # pylint: disable=W0613
2156 """Return L{_FS_UNAVAIL} if job is None.
2158 When listing specifc jobs (e.g. "gnt-job list 1 2 3"), a job may not be
2159 found, in which case this function converts it to L{_FS_UNAVAIL}.
2168 def _JobUnavail(inner):
2169 """Wrapper for L{_JobUnavailInner}.
2172 return compat.partial(_JobUnavailInner, inner)
2175 def _PerJobOpInner(fn, job):
2176 """Executes a function per opcode in a job.
2179 return map(fn, job.ops)
2183 """Wrapper for L{_PerJobOpInner}.
2186 return _JobUnavail(compat.partial(_PerJobOpInner, fn))
2189 def _JobTimestampInner(fn, job):
2190 """Converts unavailable timestamp to L{_FS_UNAVAIL}.
2195 if timestamp is None:
2201 def _JobTimestamp(fn):
2202 """Wrapper for L{_JobTimestampInner}.
2205 return _JobUnavail(compat.partial(_JobTimestampInner, fn))
2208 def _BuildJobFields():
2209 """Builds list of fields for job queries.
2213 (_MakeField("id", "ID", QFT_TEXT, "Job ID"),
2214 None, 0, lambda _, (job_id, job): job_id),
2215 (_MakeField("status", "Status", QFT_TEXT, "Job status"),
2216 None, 0, _JobUnavail(lambda job: job.CalcStatus())),
2217 (_MakeField("priority", "Priority", QFT_NUMBER,
2218 ("Current job priority (%s to %s)" %
2219 (constants.OP_PRIO_LOWEST, constants.OP_PRIO_HIGHEST))),
2220 None, 0, _JobUnavail(lambda job: job.CalcPriority())),
2221 (_MakeField("ops", "OpCodes", QFT_OTHER, "List of all opcodes"),
2222 None, 0, _PerJobOp(lambda op: op.input.__getstate__())),
2223 (_MakeField("opresult", "OpCode_result", QFT_OTHER,
2224 "List of opcodes results"),
2225 None, 0, _PerJobOp(operator.attrgetter("result"))),
2226 (_MakeField("opstatus", "OpCode_status", QFT_OTHER,
2227 "List of opcodes status"),
2228 None, 0, _PerJobOp(operator.attrgetter("status"))),
2229 (_MakeField("oplog", "OpCode_log", QFT_OTHER,
2230 "List of opcode output logs"),
2231 None, 0, _PerJobOp(operator.attrgetter("log"))),
2232 (_MakeField("opstart", "OpCode_start", QFT_OTHER,
2233 "List of opcode start timestamps (before acquiring locks)"),
2234 None, 0, _PerJobOp(operator.attrgetter("start_timestamp"))),
2235 (_MakeField("opexec", "OpCode_exec", QFT_OTHER,
2236 "List of opcode execution start timestamps (after acquiring"
2238 None, 0, _PerJobOp(operator.attrgetter("exec_timestamp"))),
2239 (_MakeField("opend", "OpCode_end", QFT_OTHER,
2240 "List of opcode execution end timestamps"),
2241 None, 0, _PerJobOp(operator.attrgetter("end_timestamp"))),
2242 (_MakeField("oppriority", "OpCode_prio", QFT_OTHER,
2243 "List of opcode priorities"),
2244 None, 0, _PerJobOp(operator.attrgetter("priority"))),
2245 (_MakeField("received_ts", "Received", QFT_OTHER,
2246 "Timestamp of when job was received"),
2247 None, 0, _JobTimestamp(operator.attrgetter("received_timestamp"))),
2248 (_MakeField("start_ts", "Start", QFT_OTHER,
2249 "Timestamp of job start"),
2250 None, 0, _JobTimestamp(operator.attrgetter("start_timestamp"))),
2251 (_MakeField("end_ts", "End", QFT_OTHER,
2252 "Timestamp of job end"),
2253 None, 0, _JobTimestamp(operator.attrgetter("end_timestamp"))),
2254 (_MakeField("summary", "Summary", QFT_OTHER,
2255 "List of per-opcode summaries"),
2256 None, 0, _PerJobOp(lambda op: op.input.Summary())),
2259 return _PrepareFieldList(fields, [])
2262 def _GetExportName(_, (node_name, expname)): # pylint: disable=W0613
2263 """Returns an export name if available.
2272 def _BuildExportFields():
2273 """Builds list of fields for exports.
2277 (_MakeField("node", "Node", QFT_TEXT, "Node name"),
2278 None, QFF_HOSTNAME, lambda _, (node_name, expname): node_name),
2279 (_MakeField("export", "Export", QFT_TEXT, "Export name"),
2280 None, 0, _GetExportName),
2283 return _PrepareFieldList(fields, [])
2286 _CLUSTER_VERSION_FIELDS = {
2287 "software_version": ("SoftwareVersion", QFT_TEXT, constants.RELEASE_VERSION,
2288 "Software version"),
2289 "protocol_version": ("ProtocolVersion", QFT_NUMBER,
2290 constants.PROTOCOL_VERSION,
2291 "RPC protocol version"),
2292 "config_version": ("ConfigVersion", QFT_NUMBER, constants.CONFIG_VERSION,
2293 "Configuration format version"),
2294 "os_api_version": ("OsApiVersion", QFT_NUMBER, max(constants.OS_API_VERSIONS),
2295 "API version for OS template scripts"),
2296 "export_version": ("ExportVersion", QFT_NUMBER, constants.EXPORT_VERSION,
2297 "Import/export file format version"),
2301 _CLUSTER_SIMPLE_FIELDS = {
2302 "cluster_name": ("Name", QFT_TEXT, QFF_HOSTNAME, "Cluster name"),
2303 "master_node": ("Master", QFT_TEXT, QFF_HOSTNAME, "Master node name"),
2304 "volume_group_name": ("VgName", QFT_TEXT, 0, "LVM volume group name"),
2308 class ClusterQueryData:
2309 def __init__(self, cluster, drain_flag, watcher_pause):
2310 """Initializes this class.
2312 @type cluster: L{objects.Cluster}
2313 @param cluster: Instance of cluster object
2314 @type drain_flag: bool
2315 @param drain_flag: Whether job queue is drained
2316 @type watcher_pause: number
2317 @param watcher_pause: Until when watcher is paused (Unix timestamp)
2320 self._cluster = cluster
2321 self.drain_flag = drain_flag
2322 self.watcher_pause = watcher_pause
2325 return iter([self._cluster])
2328 def _ClusterWatcherPause(ctx, _):
2329 """Returns until when watcher is paused (if available).
2332 if ctx.watcher_pause is None:
2335 return ctx.watcher_pause
2338 def _BuildClusterFields():
2339 """Builds list of fields for cluster information.
2343 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), CQ_CONFIG, 0,
2344 lambda ctx, cluster: list(cluster.GetTags())),
2345 (_MakeField("architecture", "ArchInfo", QFT_OTHER,
2346 "Architecture information"), None, 0,
2347 lambda ctx, _: runtime.GetArchInfo()),
2348 (_MakeField("drain_flag", "QueueDrained", QFT_BOOL,
2349 "Flag whether job queue is drained"), CQ_QUEUE_DRAINED, 0,
2350 lambda ctx, _: ctx.drain_flag),
2351 (_MakeField("watcher_pause", "WatcherPause", QFT_TIMESTAMP,
2352 "Until when watcher is paused"), CQ_WATCHER_PAUSE, 0,
2353 _ClusterWatcherPause),
2358 (_MakeField(name, title, kind, doc), CQ_CONFIG, flags, _GetItemAttr(name))
2359 for (name, (title, kind, flags, doc)) in _CLUSTER_SIMPLE_FIELDS.items()
2364 (_MakeField(name, title, kind, doc), None, 0, _StaticValue(value))
2365 for (name, (title, kind, value, doc)) in _CLUSTER_VERSION_FIELDS.items()
2369 fields.extend(_GetItemTimestampFields(CQ_CONFIG))
2371 return _PrepareFieldList(fields, [
2372 ("name", "cluster_name"),
2376 #: Fields for cluster information
2377 CLUSTER_FIELDS = _BuildClusterFields()
2379 #: Fields available for node queries
2380 NODE_FIELDS = _BuildNodeFields()
2382 #: Fields available for instance queries
2383 INSTANCE_FIELDS = _BuildInstanceFields()
2385 #: Fields available for lock queries
2386 LOCK_FIELDS = _BuildLockFields()
2388 #: Fields available for node group queries
2389 GROUP_FIELDS = _BuildGroupFields()
2391 #: Fields available for operating system queries
2392 OS_FIELDS = _BuildOsFields()
2394 #: Fields available for job queries
2395 JOB_FIELDS = _BuildJobFields()
2397 #: Fields available for exports
2398 EXPORT_FIELDS = _BuildExportFields()
2400 #: All available resources
2402 constants.QR_CLUSTER: CLUSTER_FIELDS,
2403 constants.QR_INSTANCE: INSTANCE_FIELDS,
2404 constants.QR_NODE: NODE_FIELDS,
2405 constants.QR_LOCK: LOCK_FIELDS,
2406 constants.QR_GROUP: GROUP_FIELDS,
2407 constants.QR_OS: OS_FIELDS,
2408 constants.QR_JOB: JOB_FIELDS,
2409 constants.QR_EXPORT: EXPORT_FIELDS,
2412 #: All available field lists
2413 ALL_FIELD_LISTS = ALL_FIELDS.values()