Package ganeti :: Module query
[hide private]
[frames] | no frames]

Source Code for Module ganeti.query

   1  # 
   2  # 
   3   
   4  # Copyright (C) 2010, 2011 Google Inc. 
   5  # 
   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. 
  10  # 
  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. 
  15  # 
  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 
  19  # 02110-1301, USA. 
  20   
  21   
  22  """Module for query operations 
  23   
  24  How it works: 
  25   
  26    - Add field definitions 
  27      - See how L{NODE_FIELDS} is built 
  28      - Each field gets: 
  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 
  33              L{TITLE_RE} 
  34            - Value data type, e.g. L{constants.QFT_NUMBER} 
  35            - Human-readable description, must not end with punctuation or 
  36              contain newlines 
  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 
  41        checks 
  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 
  45      result 
  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 
  49   
  50  @attention: Retrieval functions must be idempotent. They can be called multiple 
  51    times, in any order and any number of times. 
  52   
  53  """ 
  54   
  55  import logging 
  56  import operator 
  57  import re 
  58   
  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 
  64  from ganeti import ht 
  65  from ganeti import qlang 
  66   
  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) 
  71   
  72   
  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. 
  76   
  77  (NQ_CONFIG, 
  78   NQ_INST, 
  79   NQ_LIVE, 
  80   NQ_GROUP, 
  81   NQ_OOB) = range(1, 6) 
  82   
  83  (IQ_CONFIG, 
  84   IQ_LIVE, 
  85   IQ_DISKUSAGE, 
  86   IQ_CONSOLE, 
  87   IQ_NODES) = range(100, 105) 
  88   
  89  (LQ_MODE, 
  90   LQ_OWNER, 
  91   LQ_PENDING) = range(10, 13) 
  92   
  93  (GQ_CONFIG, 
  94   GQ_NODE, 
  95   GQ_INST) = range(200, 203) 
  96   
  97  # Query field flags 
  98  QFF_HOSTNAME = 0x01 
  99  QFF_IP_ADDRESS = 0x02 
 100  # Next values: 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200 
 101  QFF_ALL = (QFF_HOSTNAME | QFF_IP_ADDRESS) 
 102   
 103  FIELD_NAME_RE = re.compile(r"^[a-z0-9/._]+$") 
 104  TITLE_RE = re.compile(r"^[^\s]+$") 
 105  DOC_RE = re.compile(r"^[A-Z].*[^.,?!]$") 
 106   
 107  #: Verification function for each field type 
 108  _VERIFY_FN = { 
 109    QFT_UNKNOWN: ht.TNone, 
 110    QFT_TEXT: ht.TString, 
 111    QFT_BOOL: ht.TBool, 
 112    QFT_NUMBER: ht.TInt, 
 113    QFT_UNIT: ht.TInt, 
 114    QFT_TIMESTAMP: ht.TNumber, 
 115    QFT_OTHER: lambda _: True, 
 116    } 
 117   
 118  # Unique objects for special field statuses 
 119  _FS_UNKNOWN = object() 
 120  _FS_NODATA = object() 
 121  _FS_UNAVAIL = object() 
 122  _FS_OFFLINE = object() 
 123   
 124  #: List of all special status 
 125  _FS_ALL = frozenset([_FS_UNKNOWN, _FS_NODATA, _FS_UNAVAIL, _FS_OFFLINE]) 
 126   
 127  #: VType to QFT mapping 
 128  _VTToQFT = { 
 129    # TODO: fix validation of empty strings 
 130    constants.VTYPE_STRING: QFT_OTHER, # since VTYPE_STRINGs can be empty 
 131    constants.VTYPE_MAYBE_STRING: QFT_OTHER, 
 132    constants.VTYPE_BOOL: QFT_BOOL, 
 133    constants.VTYPE_SIZE: QFT_UNIT, 
 134    constants.VTYPE_INT: QFT_NUMBER, 
 135    } 
 136   
 137  _SERIAL_NO_DOC = "%s object serial number, incremented on each modification" 
 138   
 139   
140 -def _GetUnknownField(ctx, item): # pylint: disable=W0613
141 """Gets the contents of an unknown field. 142 143 """ 144 return _FS_UNKNOWN 145 146
147 -def _GetQueryFields(fielddefs, selected):
148 """Calculates the internal list of selected fields. 149 150 Unknown fields are returned as L{constants.QFT_UNKNOWN}. 151 152 @type fielddefs: dict 153 @param fielddefs: Field definitions 154 @type selected: list of strings 155 @param selected: List of selected fields 156 157 """ 158 result = [] 159 160 for name in selected: 161 try: 162 fdef = fielddefs[name] 163 except KeyError: 164 fdef = (_MakeField(name, name, QFT_UNKNOWN, "Unknown field '%s'" % name), 165 None, 0, _GetUnknownField) 166 167 assert len(fdef) == 4 168 169 result.append(fdef) 170 171 return result
172 173
174 -def GetAllFields(fielddefs):
175 """Extract L{objects.QueryFieldDefinition} from field definitions. 176 177 @rtype: list of L{objects.QueryFieldDefinition} 178 179 """ 180 return [fdef for (fdef, _, _, _) in fielddefs]
181 182
183 -class _FilterHints:
184 """Class for filter analytics. 185 186 When filters are used, the user of the L{Query} class usually doesn't know 187 exactly which items will be necessary for building the result. It therefore 188 has to prepare and compute the input data for potentially returning 189 everything. 190 191 There are two ways to optimize this. The first, and simpler, is to assign 192 each field a group of data, so that the caller can determine which 193 computations are necessary depending on the data groups requested. The list 194 of referenced groups must also be computed for fields referenced in the 195 filter. 196 197 The second is restricting the items based on a primary key. The primary key 198 is usually a unique name (e.g. a node name). This class extracts all 199 referenced names from a filter. If it encounters any filter condition which 200 disallows such a list to be determined (e.g. a non-equality filter), all 201 names will be requested. 202 203 The end-effect is that any operation other than L{qlang.OP_OR} and 204 L{qlang.OP_EQUAL} will make the query more expensive. 205 206 """
207 - def __init__(self, namefield):
208 """Initializes this class. 209 210 @type namefield: string 211 @param namefield: Field caller is interested in 212 213 """ 214 self._namefield = namefield 215 216 #: Whether all names need to be requested (e.g. if a non-equality operator 217 #: has been used) 218 self._allnames = False 219 220 #: Which names to request 221 self._names = None 222 223 #: Data kinds referenced by the filter (used by L{Query.RequestedData}) 224 self._datakinds = set()
225
226 - def RequestedNames(self):
227 """Returns all requested values. 228 229 Returns C{None} if list of values can't be determined (e.g. encountered 230 non-equality operators). 231 232 @rtype: list 233 234 """ 235 if self._allnames or self._names is None: 236 return None 237 238 return utils.UniqueSequence(self._names)
239
240 - def ReferencedData(self):
241 """Returns all kinds of data referenced by the filter. 242 243 """ 244 return frozenset(self._datakinds)
245
246 - def _NeedAllNames(self):
247 """Changes internal state to request all names. 248 249 """ 250 self._allnames = True 251 self._names = None
252
253 - def NoteLogicOp(self, op):
254 """Called when handling a logic operation. 255 256 @type op: string 257 @param op: Operator 258 259 """ 260 if op != qlang.OP_OR: 261 self._NeedAllNames()
262
263 - def NoteUnaryOp(self, op): # pylint: disable=W0613
264 """Called when handling an unary operation. 265 266 @type op: string 267 @param op: Operator 268 269 """ 270 self._NeedAllNames()
271
272 - def NoteBinaryOp(self, op, datakind, name, value):
273 """Called when handling a binary operation. 274 275 @type op: string 276 @param op: Operator 277 @type name: string 278 @param name: Left-hand side of operator (field name) 279 @param value: Right-hand side of operator 280 281 """ 282 if datakind is not None: 283 self._datakinds.add(datakind) 284 285 if self._allnames: 286 return 287 288 # If any operator other than equality was used, all names need to be 289 # retrieved 290 if op == qlang.OP_EQUAL and name == self._namefield: 291 if self._names is None: 292 self._names = [] 293 self._names.append(value) 294 else: 295 self._NeedAllNames()
296 297
298 -def _WrapLogicOp(op_fn, sentences, ctx, item):
299 """Wrapper for logic operator functions. 300 301 """ 302 return op_fn(fn(ctx, item) for fn in sentences)
303 304
305 -def _WrapUnaryOp(op_fn, inner, ctx, item):
306 """Wrapper for unary operator functions. 307 308 """ 309 return op_fn(inner(ctx, item))
310 311
312 -def _WrapBinaryOp(op_fn, retrieval_fn, value, ctx, item):
313 """Wrapper for binary operator functions. 314 315 """ 316 return op_fn(retrieval_fn(ctx, item), value)
317 318
319 -def _WrapNot(fn, lhs, rhs):
320 """Negates the result of a wrapped function. 321 322 """ 323 return not fn(lhs, rhs)
324 325
326 -def _PrepareRegex(pattern):
327 """Compiles a regular expression. 328 329 """ 330 try: 331 return re.compile(pattern) 332 except re.error, err: 333 raise errors.ParameterError("Invalid regex pattern (%s)" % err)
334 335
336 -class _FilterCompilerHelper:
337 """Converts a query filter to a callable usable for filtering. 338 339 """ 340 # String statement has no effect, pylint: disable=W0105 341 342 #: How deep filters can be nested 343 _LEVELS_MAX = 10 344 345 # Unique identifiers for operator groups 346 (_OPTYPE_LOGIC, 347 _OPTYPE_UNARY, 348 _OPTYPE_BINARY) = range(1, 4) 349 350 """Functions for equality checks depending on field flags. 351 352 List of tuples containing flags and a callable receiving the left- and 353 right-hand side of the operator. The flags are an OR-ed value of C{QFF_*} 354 (e.g. L{QFF_HOSTNAME}). 355 356 Order matters. The first item with flags will be used. Flags are checked 357 using binary AND. 358 359 """ 360 _EQUALITY_CHECKS = [ 361 (QFF_HOSTNAME, 362 lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs], 363 case_sensitive=False), 364 None), 365 (None, operator.eq, None), 366 ] 367 368 """Known operators 369 370 Operator as key (C{qlang.OP_*}), value a tuple of operator group 371 (C{_OPTYPE_*}) and a group-specific value: 372 373 - C{_OPTYPE_LOGIC}: Callable taking any number of arguments; used by 374 L{_HandleLogicOp} 375 - C{_OPTYPE_UNARY}: Always C{None}; details handled by L{_HandleUnaryOp} 376 - C{_OPTYPE_BINARY}: Callable taking exactly two parameters, the left- and 377 right-hand side of the operator, used by L{_HandleBinaryOp} 378 379 """ 380 _OPS = { 381 # Logic operators 382 qlang.OP_OR: (_OPTYPE_LOGIC, compat.any), 383 qlang.OP_AND: (_OPTYPE_LOGIC, compat.all), 384 385 # Unary operators 386 qlang.OP_NOT: (_OPTYPE_UNARY, None), 387 qlang.OP_TRUE: (_OPTYPE_UNARY, None), 388 389 # Binary operators 390 qlang.OP_EQUAL: (_OPTYPE_BINARY, _EQUALITY_CHECKS), 391 qlang.OP_NOT_EQUAL: 392 (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn), valprepfn) 393 for (flags, fn, valprepfn) in _EQUALITY_CHECKS]), 394 qlang.OP_REGEXP: (_OPTYPE_BINARY, [ 395 (None, lambda lhs, rhs: rhs.search(lhs), _PrepareRegex), 396 ]), 397 qlang.OP_CONTAINS: (_OPTYPE_BINARY, [ 398 (None, operator.contains, None), 399 ]), 400 } 401
402 - def __init__(self, fields):
403 """Initializes this class. 404 405 @param fields: Field definitions (return value of L{_PrepareFieldList}) 406 407 """ 408 self._fields = fields 409 self._hints = None 410 self._op_handler = None
411
412 - def __call__(self, hints, filter_):
413 """Converts a query filter into a callable function. 414 415 @type hints: L{_FilterHints} or None 416 @param hints: Callbacks doing analysis on filter 417 @type filter_: list 418 @param filter_: Filter structure 419 @rtype: callable 420 @return: Function receiving context and item as parameters, returning 421 boolean as to whether item matches filter 422 423 """ 424 self._op_handler = { 425 self._OPTYPE_LOGIC: 426 (self._HandleLogicOp, getattr(hints, "NoteLogicOp", None)), 427 self._OPTYPE_UNARY: 428 (self._HandleUnaryOp, getattr(hints, "NoteUnaryOp", None)), 429 self._OPTYPE_BINARY: 430 (self._HandleBinaryOp, getattr(hints, "NoteBinaryOp", None)), 431 } 432 433 try: 434 filter_fn = self._Compile(filter_, 0) 435 finally: 436 self._op_handler = None 437 438 return filter_fn
439
440 - def _Compile(self, filter_, level):
441 """Inner function for converting filters. 442 443 Calls the correct handler functions for the top-level operator. This 444 function is called recursively (e.g. for logic operators). 445 446 """ 447 if not (isinstance(filter_, (list, tuple)) and filter_): 448 raise errors.ParameterError("Invalid filter on level %s" % level) 449 450 # Limit recursion 451 if level >= self._LEVELS_MAX: 452 raise errors.ParameterError("Only up to %s levels are allowed (filter" 453 " nested too deep)" % self._LEVELS_MAX) 454 455 # Create copy to be modified 456 operands = filter_[:] 457 op = operands.pop(0) 458 459 try: 460 (kind, op_data) = self._OPS[op] 461 except KeyError: 462 raise errors.ParameterError("Unknown operator '%s'" % op) 463 464 (handler, hints_cb) = self._op_handler[kind] 465 466 return handler(hints_cb, level, op, op_data, operands)
467
468 - def _LookupField(self, name):
469 """Returns a field definition by name. 470 471 """ 472 try: 473 return self._fields[name] 474 except KeyError: 475 raise errors.ParameterError("Unknown field '%s'" % name)
476
477 - def _HandleLogicOp(self, hints_fn, level, op, op_fn, operands):
478 """Handles logic operators. 479 480 @type hints_fn: callable 481 @param hints_fn: Callback doing some analysis on the filter 482 @type level: integer 483 @param level: Current depth 484 @type op: string 485 @param op: Operator 486 @type op_fn: callable 487 @param op_fn: Function implementing operator 488 @type operands: list 489 @param operands: List of operands 490 491 """ 492 if hints_fn: 493 hints_fn(op) 494 495 return compat.partial(_WrapLogicOp, op_fn, 496 [self._Compile(op, level + 1) for op in operands])
497
498 - def _HandleUnaryOp(self, hints_fn, level, op, op_fn, operands):
499 """Handles unary operators. 500 501 @type hints_fn: callable 502 @param hints_fn: Callback doing some analysis on the filter 503 @type level: integer 504 @param level: Current depth 505 @type op: string 506 @param op: Operator 507 @type op_fn: callable 508 @param op_fn: Function implementing operator 509 @type operands: list 510 @param operands: List of operands 511 512 """ 513 assert op_fn is None 514 515 if hints_fn: 516 hints_fn(op) 517 518 if len(operands) != 1: 519 raise errors.ParameterError("Unary operator '%s' expects exactly one" 520 " operand" % op) 521 522 if op == qlang.OP_TRUE: 523 (_, _, _, retrieval_fn) = self._LookupField(operands[0]) 524 525 op_fn = operator.truth 526 arg = retrieval_fn 527 elif op == qlang.OP_NOT: 528 op_fn = operator.not_ 529 arg = self._Compile(operands[0], level + 1) 530 else: 531 raise errors.ProgrammerError("Can't handle operator '%s'" % op) 532 533 return compat.partial(_WrapUnaryOp, op_fn, arg)
534
535 - def _HandleBinaryOp(self, hints_fn, level, op, op_data, operands):
536 """Handles binary operators. 537 538 @type hints_fn: callable 539 @param hints_fn: Callback doing some analysis on the filter 540 @type level: integer 541 @param level: Current depth 542 @type op: string 543 @param op: Operator 544 @param op_data: Functions implementing operators 545 @type operands: list 546 @param operands: List of operands 547 548 """ 549 # Unused arguments, pylint: disable=W0613 550 try: 551 (name, value) = operands 552 except (ValueError, TypeError): 553 raise errors.ParameterError("Invalid binary operator, expected exactly" 554 " two operands") 555 556 (fdef, datakind, field_flags, retrieval_fn) = self._LookupField(name) 557 558 assert fdef.kind != QFT_UNKNOWN 559 560 # TODO: Type conversions? 561 562 verify_fn = _VERIFY_FN[fdef.kind] 563 if not verify_fn(value): 564 raise errors.ParameterError("Unable to compare field '%s' (type '%s')" 565 " with '%s', expected %s" % 566 (name, fdef.kind, value.__class__.__name__, 567 verify_fn)) 568 569 if hints_fn: 570 hints_fn(op, datakind, name, value) 571 572 for (fn_flags, fn, valprepfn) in op_data: 573 if fn_flags is None or fn_flags & field_flags: 574 # Prepare value if necessary (e.g. compile regular expression) 575 if valprepfn: 576 value = valprepfn(value) 577 578 return compat.partial(_WrapBinaryOp, fn, retrieval_fn, value) 579 580 raise errors.ProgrammerError("Unable to find operator implementation" 581 " (op '%s', flags %s)" % (op, field_flags))
582 583
584 -def _CompileFilter(fields, hints, filter_):
585 """Converts a query filter into a callable function. 586 587 See L{_FilterCompilerHelper} for details. 588 589 @rtype: callable 590 591 """ 592 return _FilterCompilerHelper(fields)(hints, filter_)
593 594
595 -class Query:
596 - def __init__(self, fieldlist, selected, filter_=None, namefield=None):
597 """Initializes this class. 598 599 The field definition is a dictionary with the field's name as a key and a 600 tuple containing, in order, the field definition object 601 (L{objects.QueryFieldDefinition}, the data kind to help calling code 602 collect data and a retrieval function. The retrieval function is called 603 with two parameters, in order, the data container and the item in container 604 (see L{Query.Query}). 605 606 Users of this class can call L{RequestedData} before preparing the data 607 container to determine what data is needed. 608 609 @type fieldlist: dictionary 610 @param fieldlist: Field definitions 611 @type selected: list of strings 612 @param selected: List of selected fields 613 614 """ 615 assert namefield is None or namefield in fieldlist 616 617 self._fields = _GetQueryFields(fieldlist, selected) 618 619 self._filter_fn = None 620 self._requested_names = None 621 self._filter_datakinds = frozenset() 622 623 if filter_ is not None: 624 # Collect requested names if wanted 625 if namefield: 626 hints = _FilterHints(namefield) 627 else: 628 hints = None 629 630 # Build filter function 631 self._filter_fn = _CompileFilter(fieldlist, hints, filter_) 632 if hints: 633 self._requested_names = hints.RequestedNames() 634 self._filter_datakinds = hints.ReferencedData() 635 636 if namefield is None: 637 self._name_fn = None 638 else: 639 (_, _, _, self._name_fn) = fieldlist[namefield]
640
641 - def RequestedNames(self):
642 """Returns all names referenced in the filter. 643 644 If there is no filter or operators are preventing determining the exact 645 names, C{None} is returned. 646 647 """ 648 return self._requested_names
649
650 - def RequestedData(self):
651 """Gets requested kinds of data. 652 653 @rtype: frozenset 654 655 """ 656 return (self._filter_datakinds | 657 frozenset(datakind for (_, datakind, _, _) in self._fields 658 if datakind is not None))
659
660 - def GetFields(self):
661 """Returns the list of fields for this query. 662 663 Includes unknown fields. 664 665 @rtype: List of L{objects.QueryFieldDefinition} 666 667 """ 668 return GetAllFields(self._fields)
669
670 - def Query(self, ctx, sort_by_name=True):
671 """Execute a query. 672 673 @param ctx: Data container passed to field retrieval functions, must 674 support iteration using C{__iter__} 675 @type sort_by_name: boolean 676 @param sort_by_name: Whether to sort by name or keep the input data's 677 ordering 678 679 """ 680 sort = (self._name_fn and sort_by_name) 681 682 result = [] 683 684 for idx, item in enumerate(ctx): 685 if not (self._filter_fn is None or self._filter_fn(ctx, item)): 686 continue 687 688 row = [_ProcessResult(fn(ctx, item)) for (_, _, _, fn) in self._fields] 689 690 # Verify result 691 if __debug__: 692 _VerifyResultRow(self._fields, row) 693 694 if sort: 695 (status, name) = _ProcessResult(self._name_fn(ctx, item)) 696 assert status == constants.RS_NORMAL 697 # TODO: Are there cases where we wouldn't want to use NiceSort? 698 result.append((utils.NiceSortKey(name), idx, row)) 699 else: 700 result.append(row) 701 702 if not sort: 703 return result 704 705 # TODO: Would "heapq" be more efficient than sorting? 706 707 # Sorting in-place instead of using "sorted()" 708 result.sort() 709 710 assert not result or (len(result[0]) == 3 and len(result[-1]) == 3) 711 712 return map(operator.itemgetter(2), result)
713
714 - def OldStyleQuery(self, ctx, sort_by_name=True):
715 """Query with "old" query result format. 716 717 See L{Query.Query} for arguments. 718 719 """ 720 unknown = set(fdef.name for (fdef, _, _, _) in self._fields 721 if fdef.kind == QFT_UNKNOWN) 722 if unknown: 723 raise errors.OpPrereqError("Unknown output fields selected: %s" % 724 (utils.CommaJoin(unknown), ), 725 errors.ECODE_INVAL) 726 727 return [[value for (_, value) in row] 728 for row in self.Query(ctx, sort_by_name=sort_by_name)]
729 730
731 -def _ProcessResult(value):
732 """Converts result values into externally-visible ones. 733 734 """ 735 if value is _FS_UNKNOWN: 736 return (RS_UNKNOWN, None) 737 elif value is _FS_NODATA: 738 return (RS_NODATA, None) 739 elif value is _FS_UNAVAIL: 740 return (RS_UNAVAIL, None) 741 elif value is _FS_OFFLINE: 742 return (RS_OFFLINE, None) 743 else: 744 return (RS_NORMAL, value)
745 746
747 -def _VerifyResultRow(fields, row):
748 """Verifies the contents of a query result row. 749 750 @type fields: list 751 @param fields: Field definitions for result 752 @type row: list of tuples 753 @param row: Row data 754 755 """ 756 assert len(row) == len(fields) 757 errs = [] 758 for ((status, value), (fdef, _, _, _)) in zip(row, fields): 759 if status == RS_NORMAL: 760 if not _VERIFY_FN[fdef.kind](value): 761 errs.append("normal field %s fails validation (value is %s)" % 762 (fdef.name, value)) 763 elif value is not None: 764 errs.append("abnormal field %s has a non-None value" % fdef.name) 765 assert not errs, ("Failed validation: %s in row %s" % 766 (utils.CommaJoin(errors), row))
767 768
769 -def _PrepareFieldList(fields, aliases):
770 """Prepares field list for use by L{Query}. 771 772 Converts the list to a dictionary and does some verification. 773 774 @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data 775 kind, retrieval function) 776 @param fields: List of fields, see L{Query.__init__} for a better 777 description 778 @type aliases: list of tuples; (alias, target) 779 @param aliases: list of tuples containing aliases; for each 780 alias/target pair, a duplicate will be created in the field list 781 @rtype: dict 782 @return: Field dictionary for L{Query} 783 784 """ 785 if __debug__: 786 duplicates = utils.FindDuplicates(fdef.title.lower() 787 for (fdef, _, _, _) in fields) 788 assert not duplicates, "Duplicate title(s) found: %r" % duplicates 789 790 result = {} 791 792 for field in fields: 793 (fdef, _, flags, fn) = field 794 795 assert fdef.name and fdef.title, "Name and title are required" 796 assert FIELD_NAME_RE.match(fdef.name) 797 assert TITLE_RE.match(fdef.title) 798 assert (DOC_RE.match(fdef.doc) and len(fdef.doc.splitlines()) == 1 and 799 fdef.doc.strip() == fdef.doc), \ 800 "Invalid description for field '%s'" % fdef.name 801 assert callable(fn) 802 assert fdef.name not in result, \ 803 "Duplicate field name '%s' found" % fdef.name 804 assert (flags & ~QFF_ALL) == 0, "Unknown flags for field '%s'" % fdef.name 805 806 result[fdef.name] = field 807 808 for alias, target in aliases: 809 assert alias not in result, "Alias %s overrides an existing field" % alias 810 assert target in result, "Missing target %s for alias %s" % (target, alias) 811 (fdef, k, flags, fn) = result[target] 812 fdef = fdef.Copy() 813 fdef.name = alias 814 result[alias] = (fdef, k, flags, fn) 815 816 assert len(result) == len(fields) + len(aliases) 817 assert compat.all(name == fdef.name 818 for (name, (fdef, _, _, _)) in result.items()) 819 820 return result
821 822
823 -def GetQueryResponse(query, ctx, sort_by_name=True):
824 """Prepares the response for a query. 825 826 @type query: L{Query} 827 @param ctx: Data container, see L{Query.Query} 828 @type sort_by_name: boolean 829 @param sort_by_name: Whether to sort by name or keep the input data's 830 ordering 831 832 """ 833 return objects.QueryResponse(data=query.Query(ctx, sort_by_name=sort_by_name), 834 fields=query.GetFields()).ToDict()
835 836
837 -def QueryFields(fielddefs, selected):
838 """Returns list of available fields. 839 840 @type fielddefs: dict 841 @param fielddefs: Field definitions 842 @type selected: list of strings 843 @param selected: List of selected fields 844 @return: List of L{objects.QueryFieldDefinition} 845 846 """ 847 if selected is None: 848 # Client requests all fields, sort by name 849 fdefs = utils.NiceSort(GetAllFields(fielddefs.values()), 850 key=operator.attrgetter("name")) 851 else: 852 # Keep order as requested by client 853 fdefs = Query(fielddefs, selected).GetFields() 854 855 return objects.QueryFieldsResponse(fields=fdefs).ToDict()
856 857
858 -def _MakeField(name, title, kind, doc):
859 """Wrapper for creating L{objects.QueryFieldDefinition} instances. 860 861 @param name: Field name as a regular expression 862 @param title: Human-readable title 863 @param kind: Field type 864 @param doc: Human-readable description 865 866 """ 867 return objects.QueryFieldDefinition(name=name, title=title, kind=kind, 868 doc=doc)
869 870
871 -def _GetNodeRole(node, master_name):
872 """Determine node role. 873 874 @type node: L{objects.Node} 875 @param node: Node object 876 @type master_name: string 877 @param master_name: Master node name 878 879 """ 880 if node.name == master_name: 881 return constants.NR_MASTER 882 elif node.master_candidate: 883 return constants.NR_MCANDIDATE 884 elif node.drained: 885 return constants.NR_DRAINED 886 elif node.offline: 887 return constants.NR_OFFLINE 888 else: 889 return constants.NR_REGULAR
890 891
892 -def _GetItemAttr(attr):
893 """Returns a field function to return an attribute of the item. 894 895 @param attr: Attribute name 896 897 """ 898 getter = operator.attrgetter(attr) 899 return lambda _, item: getter(item)
900 901
902 -def _ConvWrapInner(convert, fn, ctx, item):
903 """Wrapper for converting values. 904 905 @param convert: Conversion function receiving value as single parameter 906 @param fn: Retrieval function 907 908 """ 909 value = fn(ctx, item) 910 911 # Is the value an abnormal status? 912 if compat.any(value is fs for fs in _FS_ALL): 913 # Return right away 914 return value 915 916 # TODO: Should conversion function also receive context, item or both? 917 return convert(value)
918 919
920 -def _ConvWrap(convert, fn):
921 """Convenience wrapper for L{_ConvWrapInner}. 922 923 @param convert: Conversion function receiving value as single parameter 924 @param fn: Retrieval function 925 926 """ 927 return compat.partial(_ConvWrapInner, convert, fn)
928 929
930 -def _GetItemTimestamp(getter):
931 """Returns function for getting timestamp of item. 932 933 @type getter: callable 934 @param getter: Function to retrieve timestamp attribute 935 936 """ 937 def fn(_, item): 938 """Returns a timestamp of item. 939 940 """ 941 timestamp = getter(item) 942 if timestamp is None: 943 # Old configs might not have all timestamps 944 return _FS_UNAVAIL 945 else: 946 return timestamp
947 948 return fn 949 950
951 -def _GetItemTimestampFields(datatype):
952 """Returns common timestamp fields. 953 954 @param datatype: Field data type for use by L{Query.RequestedData} 955 956 """ 957 return [ 958 (_MakeField("ctime", "CTime", QFT_TIMESTAMP, "Creation timestamp"), 959 datatype, 0, _GetItemTimestamp(operator.attrgetter("ctime"))), 960 (_MakeField("mtime", "MTime", QFT_TIMESTAMP, "Modification timestamp"), 961 datatype, 0, _GetItemTimestamp(operator.attrgetter("mtime"))), 962 ]
963 964
965 -class NodeQueryData:
966 """Data container for node data queries. 967 968 """
969 - def __init__(self, nodes, live_data, master_name, node_to_primary, 970 node_to_secondary, groups, oob_support, cluster):
971 """Initializes this class. 972 973 """ 974 self.nodes = nodes 975 self.live_data = live_data 976 self.master_name = master_name 977 self.node_to_primary = node_to_primary 978 self.node_to_secondary = node_to_secondary 979 self.groups = groups 980 self.oob_support = oob_support 981 self.cluster = cluster 982 983 # Used for individual rows 984 self.curlive_data = None
985
986 - def __iter__(self):
987 """Iterate over all nodes. 988 989 This function has side-effects and only one instance of the resulting 990 generator should be used at a time. 991 992 """ 993 for node in self.nodes: 994 if self.live_data: 995 self.curlive_data = self.live_data.get(node.name, None) 996 else: 997 self.curlive_data = None 998 yield node
999 1000 1001 #: Fields that are direct attributes of an L{objects.Node} object 1002 _NODE_SIMPLE_FIELDS = { 1003 "drained": ("Drained", QFT_BOOL, 0, "Whether node is drained"), 1004 "master_candidate": ("MasterC", QFT_BOOL, 0, 1005 "Whether node is a master candidate"), 1006 "master_capable": ("MasterCapable", QFT_BOOL, 0, 1007 "Whether node can become a master candidate"), 1008 "name": ("Node", QFT_TEXT, QFF_HOSTNAME, "Node name"), 1009 "offline": ("Offline", QFT_BOOL, 0, "Whether node is marked offline"), 1010 "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Node"), 1011 "uuid": ("UUID", QFT_TEXT, 0, "Node UUID"), 1012 "vm_capable": ("VMCapable", QFT_BOOL, 0, "Whether node can host instances"), 1013 } 1014 1015 1016 #: Fields requiring talking to the node 1017 # Note that none of these are available for non-vm_capable nodes 1018 _NODE_LIVE_FIELDS = { 1019 "bootid": ("BootID", QFT_TEXT, "bootid", 1020 "Random UUID renewed for each system reboot, can be used" 1021 " for detecting reboots by tracking changes"), 1022 "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes", 1023 "Number of NUMA domains on node (if exported by hypervisor)"), 1024 "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets", 1025 "Number of physical CPU sockets (if exported by hypervisor)"), 1026 "ctotal": ("CTotal", QFT_NUMBER, "cpu_total", "Number of logical processors"), 1027 "dfree": ("DFree", QFT_UNIT, "vg_free", 1028 "Available disk space in volume group"), 1029 "dtotal": ("DTotal", QFT_UNIT, "vg_size", 1030 "Total disk space in volume group used for instance disk" 1031 " allocation"), 1032 "mfree": ("MFree", QFT_UNIT, "memory_free", 1033 "Memory available for instance allocations"), 1034 "mnode": ("MNode", QFT_UNIT, "memory_dom0", 1035 "Amount of memory used by node (dom0 for Xen)"), 1036 "mtotal": ("MTotal", QFT_UNIT, "memory_total", 1037 "Total amount of memory of physical machine"), 1038 } 1039 1040
1041 -def _GetGroup(cb):
1042 """Build function for calling another function with an node group. 1043 1044 @param cb: The callback to be called with the nodegroup 1045 1046 """ 1047 def fn(ctx, node): 1048 """Get group data for a node. 1049 1050 @type ctx: L{NodeQueryData} 1051 @type inst: L{objects.Node} 1052 @param inst: Node object 1053 1054 """ 1055 ng = ctx.groups.get(node.group, None) 1056 if ng is None: 1057 # Nodes always have a group, or the configuration is corrupt 1058 return _FS_UNAVAIL 1059 1060 return cb(ctx, node, ng)
1061 1062 return fn 1063 1064
1065 -def _GetNodeGroup(ctx, node, ng): # pylint: disable=W0613
1066 """Returns the name of a node's group. 1067 1068 @type ctx: L{NodeQueryData} 1069 @type node: L{objects.Node} 1070 @param node: Node object 1071 @type ng: L{objects.NodeGroup} 1072 @param ng: The node group this node belongs to 1073 1074 """ 1075 return ng.name 1076 1077
1078 -def _GetNodePower(ctx, node):
1079 """Returns the node powered state 1080 1081 @type ctx: L{NodeQueryData} 1082 @type node: L{objects.Node} 1083 @param node: Node object 1084 1085 """ 1086 if ctx.oob_support[node.name]: 1087 return node.powered 1088 1089 return _FS_UNAVAIL
1090 1091
1092 -def _GetNdParams(ctx, node, ng):
1093 """Returns the ndparams for this node. 1094 1095 @type ctx: L{NodeQueryData} 1096 @type node: L{objects.Node} 1097 @param node: Node object 1098 @type ng: L{objects.NodeGroup} 1099 @param ng: The node group this node belongs to 1100 1101 """ 1102 return ctx.cluster.SimpleFillND(ng.FillND(node))
1103 1104
1105 -def _GetLiveNodeField(field, kind, ctx, node):
1106 """Gets the value of a "live" field from L{NodeQueryData}. 1107 1108 @param field: Live field name 1109 @param kind: Data kind, one of L{constants.QFT_ALL} 1110 @type ctx: L{NodeQueryData} 1111 @type node: L{objects.Node} 1112 @param node: Node object 1113 1114 """ 1115 if node.offline: 1116 return _FS_OFFLINE 1117 1118 if not node.vm_capable: 1119 return _FS_UNAVAIL 1120 1121 if not ctx.curlive_data: 1122 return _FS_NODATA 1123 1124 try: 1125 value = ctx.curlive_data[field] 1126 except KeyError: 1127 return _FS_UNAVAIL 1128 1129 if kind == QFT_TEXT: 1130 return value 1131 1132 assert kind in (QFT_NUMBER, QFT_UNIT) 1133 1134 # Try to convert into number 1135 try: 1136 return int(value) 1137 except (ValueError, TypeError): 1138 logging.exception("Failed to convert node field '%s' (value %r) to int", 1139 value, field) 1140 return _FS_UNAVAIL
1141 1142
1143 -def _BuildNodeFields():
1144 """Builds list of fields for node queries. 1145 1146 """ 1147 fields = [ 1148 (_MakeField("pip", "PrimaryIP", QFT_TEXT, "Primary IP address"), 1149 NQ_CONFIG, 0, _GetItemAttr("primary_ip")), 1150 (_MakeField("sip", "SecondaryIP", QFT_TEXT, "Secondary IP address"), 1151 NQ_CONFIG, 0, _GetItemAttr("secondary_ip")), 1152 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0, 1153 lambda ctx, node: list(node.GetTags())), 1154 (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"), 1155 NQ_CONFIG, 0, lambda ctx, node: node.name == ctx.master_name), 1156 (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0, 1157 _GetGroup(_GetNodeGroup)), 1158 (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"), 1159 NQ_CONFIG, 0, _GetItemAttr("group")), 1160 (_MakeField("powered", "Powered", QFT_BOOL, 1161 "Whether node is thought to be powered on"), 1162 NQ_OOB, 0, _GetNodePower), 1163 (_MakeField("ndparams", "NodeParameters", QFT_OTHER, 1164 "Merged node parameters"), 1165 NQ_GROUP, 0, _GetGroup(_GetNdParams)), 1166 (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER, 1167 "Custom node parameters"), 1168 NQ_GROUP, 0, _GetItemAttr("ndparams")), 1169 ] 1170 1171 # Node role 1172 role_values = (constants.NR_MASTER, constants.NR_MCANDIDATE, 1173 constants.NR_REGULAR, constants.NR_DRAINED, 1174 constants.NR_OFFLINE) 1175 role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate," 1176 " \"%s\" for regular, \"%s\" for a drained, \"%s\" for offline" % 1177 role_values) 1178 fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0, 1179 lambda ctx, node: _GetNodeRole(node, ctx.master_name))) 1180 assert set(role_values) == constants.NR_ALL 1181 1182 def _GetLength(getter): 1183 return lambda ctx, node: len(getter(ctx)[node.name])
1184 1185 def _GetList(getter): 1186 return lambda ctx, node: list(getter(ctx)[node.name]) 1187 1188 # Add fields operating on instance lists 1189 for prefix, titleprefix, docword, getter in \ 1190 [("p", "Pri", "primary", operator.attrgetter("node_to_primary")), 1191 ("s", "Sec", "secondary", operator.attrgetter("node_to_secondary"))]: 1192 # TODO: Allow filterting by hostname in list 1193 fields.extend([ 1194 (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER, 1195 "Number of instances with this node as %s" % docword), 1196 NQ_INST, 0, _GetLength(getter)), 1197 (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix, 1198 QFT_OTHER, 1199 "List of instances with this node as %s" % docword), 1200 NQ_INST, 0, _GetList(getter)), 1201 ]) 1202 1203 # Add simple fields 1204 fields.extend([ 1205 (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name)) 1206 for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items() 1207 ]) 1208 1209 # Add fields requiring live data 1210 fields.extend([ 1211 (_MakeField(name, title, kind, doc), NQ_LIVE, 0, 1212 compat.partial(_GetLiveNodeField, nfield, kind)) 1213 for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items() 1214 ]) 1215 1216 # Add timestamps 1217 fields.extend(_GetItemTimestampFields(NQ_CONFIG)) 1218 1219 return _PrepareFieldList(fields, []) 1220 1221
1222 -class InstanceQueryData:
1223 """Data container for instance data queries. 1224 1225 """
1226 - def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes, 1227 live_data, wrongnode_inst, console, nodes, groups):
1228 """Initializes this class. 1229 1230 @param instances: List of instance objects 1231 @param cluster: Cluster object 1232 @type disk_usage: dict; instance name as key 1233 @param disk_usage: Per-instance disk usage 1234 @type offline_nodes: list of strings 1235 @param offline_nodes: List of offline nodes 1236 @type bad_nodes: list of strings 1237 @param bad_nodes: List of faulty nodes 1238 @type live_data: dict; instance name as key 1239 @param live_data: Per-instance live data 1240 @type wrongnode_inst: set 1241 @param wrongnode_inst: Set of instances running on wrong node(s) 1242 @type console: dict; instance name as key 1243 @param console: Per-instance console information 1244 @type nodes: dict; node name as key 1245 @param nodes: Node objects 1246 1247 """ 1248 assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \ 1249 "Offline nodes not included in bad nodes" 1250 assert not (set(live_data.keys()) & set(bad_nodes)), \ 1251 "Found live data for bad or offline nodes" 1252 1253 self.instances = instances 1254 self.cluster = cluster 1255 self.disk_usage = disk_usage 1256 self.offline_nodes = offline_nodes 1257 self.bad_nodes = bad_nodes 1258 self.live_data = live_data 1259 self.wrongnode_inst = wrongnode_inst 1260 self.console = console 1261 self.nodes = nodes 1262 self.groups = groups 1263 1264 # Used for individual rows 1265 self.inst_hvparams = None 1266 self.inst_beparams = None 1267 self.inst_osparams = None 1268 self.inst_nicparams = None
1269
1270 - def __iter__(self):
1271 """Iterate over all instances. 1272 1273 This function has side-effects and only one instance of the resulting 1274 generator should be used at a time. 1275 1276 """ 1277 for inst in self.instances: 1278 self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True) 1279 self.inst_beparams = self.cluster.FillBE(inst) 1280 self.inst_osparams = self.cluster.SimpleFillOS(inst.os, inst.osparams) 1281 self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams) 1282 for nic in inst.nics] 1283 1284 yield inst
1285 1286
1287 -def _GetInstOperState(ctx, inst):
1288 """Get instance's operational status. 1289 1290 @type ctx: L{InstanceQueryData} 1291 @type inst: L{objects.Instance} 1292 @param inst: Instance object 1293 1294 """ 1295 # Can't use RS_OFFLINE here as it would describe the instance to 1296 # be offline when we actually don't know due to missing data 1297 if inst.primary_node in ctx.bad_nodes: 1298 return _FS_NODATA 1299 else: 1300 return bool(ctx.live_data.get(inst.name))
1301 1302
1303 -def _GetInstLiveData(name):
1304 """Build function for retrieving live data. 1305 1306 @type name: string 1307 @param name: Live data field name 1308 1309 """ 1310 def fn(ctx, inst): 1311 """Get live data for an instance. 1312 1313 @type ctx: L{InstanceQueryData} 1314 @type inst: L{objects.Instance} 1315 @param inst: Instance object 1316 1317 """ 1318 if (inst.primary_node in ctx.bad_nodes or 1319 inst.primary_node in ctx.offline_nodes): 1320 # Can't use RS_OFFLINE here as it would describe the instance to be 1321 # offline when we actually don't know due to missing data 1322 return _FS_NODATA 1323 1324 if inst.name in ctx.live_data: 1325 data = ctx.live_data[inst.name] 1326 if name in data: 1327 return data[name] 1328 1329 return _FS_UNAVAIL
1330 1331 return fn 1332 1333
1334 -def _GetInstStatus(ctx, inst):
1335 """Get instance status. 1336 1337 @type ctx: L{InstanceQueryData} 1338 @type inst: L{objects.Instance} 1339 @param inst: Instance object 1340 1341 """ 1342 if inst.primary_node in ctx.offline_nodes: 1343 return constants.INSTST_NODEOFFLINE 1344 1345 if inst.primary_node in ctx.bad_nodes: 1346 return constants.INSTST_NODEDOWN 1347 1348 if bool(ctx.live_data.get(inst.name)): 1349 if inst.name in ctx.wrongnode_inst: 1350 return constants.INSTST_WRONGNODE 1351 elif inst.admin_up: 1352 return constants.INSTST_RUNNING 1353 else: 1354 return constants.INSTST_ERRORUP 1355 1356 if inst.admin_up: 1357 return constants.INSTST_ERRORDOWN 1358 1359 return constants.INSTST_ADMINDOWN
1360 1361
1362 -def _GetInstDiskSize(index):
1363 """Build function for retrieving disk size. 1364 1365 @type index: int 1366 @param index: Disk index 1367 1368 """ 1369 def fn(_, inst): 1370 """Get size of a disk. 1371 1372 @type inst: L{objects.Instance} 1373 @param inst: Instance object 1374 1375 """ 1376 try: 1377 return inst.disks[index].size 1378 except IndexError: 1379 return _FS_UNAVAIL
1380 1381 return fn 1382 1383
1384 -def _GetInstNic(index, cb):
1385 """Build function for calling another function with an instance NIC. 1386 1387 @type index: int 1388 @param index: NIC index 1389 @type cb: callable 1390 @param cb: Callback 1391 1392 """ 1393 def fn(ctx, inst): 1394 """Call helper function with instance NIC. 1395 1396 @type ctx: L{InstanceQueryData} 1397 @type inst: L{objects.Instance} 1398 @param inst: Instance object 1399 1400 """ 1401 try: 1402 nic = inst.nics[index] 1403 except IndexError: 1404 return _FS_UNAVAIL 1405 1406 return cb(ctx, index, nic)
1407 1408 return fn 1409 1410
1411 -def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613
1412 """Get a NIC's IP address. 1413 1414 @type ctx: L{InstanceQueryData} 1415 @type nic: L{objects.NIC} 1416 @param nic: NIC object 1417 1418 """ 1419 if nic.ip is None: 1420 return _FS_UNAVAIL 1421 else: 1422 return nic.ip 1423 1424
1425 -def _GetInstNicBridge(ctx, index, _):
1426 """Get a NIC's bridge. 1427 1428 @type ctx: L{InstanceQueryData} 1429 @type index: int 1430 @param index: NIC index 1431 1432 """ 1433 assert len(ctx.inst_nicparams) >= index 1434 1435 nicparams = ctx.inst_nicparams[index] 1436 1437 if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED: 1438 return nicparams[constants.NIC_LINK] 1439 else: 1440 return _FS_UNAVAIL
1441 1442
1443 -def _GetInstAllNicBridges(ctx, inst):
1444 """Get all network bridges for an instance. 1445 1446 @type ctx: L{InstanceQueryData} 1447 @type inst: L{objects.Instance} 1448 @param inst: Instance object 1449 1450 """ 1451 assert len(ctx.inst_nicparams) == len(inst.nics) 1452 1453 result = [] 1454 1455 for nicp in ctx.inst_nicparams: 1456 if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED: 1457 result.append(nicp[constants.NIC_LINK]) 1458 else: 1459 result.append(None) 1460 1461 assert len(result) == len(inst.nics) 1462 1463 return result
1464 1465
1466 -def _GetInstNicParam(name):
1467 """Build function for retrieving a NIC parameter. 1468 1469 @type name: string 1470 @param name: Parameter name 1471 1472 """ 1473 def fn(ctx, index, _): 1474 """Get a NIC's bridge. 1475 1476 @type ctx: L{InstanceQueryData} 1477 @type inst: L{objects.Instance} 1478 @param inst: Instance object 1479 @type nic: L{objects.NIC} 1480 @param nic: NIC object 1481 1482 """ 1483 assert len(ctx.inst_nicparams) >= index 1484 return ctx.inst_nicparams[index][name]
1485 1486 return fn 1487 1488
1489 -def _GetInstanceNetworkFields():
1490 """Get instance fields involving network interfaces. 1491 1492 @return: Tuple containing list of field definitions used as input for 1493 L{_PrepareFieldList} and a list of aliases 1494 1495 """ 1496 nic_mac_fn = lambda ctx, _, nic: nic.mac 1497 nic_mode_fn = _GetInstNicParam(constants.NIC_MODE) 1498 nic_link_fn = _GetInstNicParam(constants.NIC_LINK) 1499 1500 fields = [ 1501 # All NICs 1502 (_MakeField("nic.count", "NICs", QFT_NUMBER, 1503 "Number of network interfaces"), 1504 IQ_CONFIG, 0, lambda ctx, inst: len(inst.nics)), 1505 (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER, 1506 "List containing each network interface's MAC address"), 1507 IQ_CONFIG, 0, lambda ctx, inst: [nic.mac for nic in inst.nics]), 1508 (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER, 1509 "List containing each network interface's IP address"), 1510 IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]), 1511 (_MakeField("nic.modes", "NIC_modes", QFT_OTHER, 1512 "List containing each network interface's mode"), IQ_CONFIG, 0, 1513 lambda ctx, inst: [nicp[constants.NIC_MODE] 1514 for nicp in ctx.inst_nicparams]), 1515 (_MakeField("nic.links", "NIC_links", QFT_OTHER, 1516 "List containing each network interface's link"), IQ_CONFIG, 0, 1517 lambda ctx, inst: [nicp[constants.NIC_LINK] 1518 for nicp in ctx.inst_nicparams]), 1519 (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER, 1520 "List containing each network interface's bridge"), 1521 IQ_CONFIG, 0, _GetInstAllNicBridges), 1522 ] 1523 1524 # NICs by number 1525 for i in range(constants.MAX_NICS): 1526 numtext = utils.FormatOrdinal(i + 1) 1527 fields.extend([ 1528 (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT, 1529 "IP address of %s network interface" % numtext), 1530 IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)), 1531 (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT, 1532 "MAC address of %s network interface" % numtext), 1533 IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)), 1534 (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT, 1535 "Mode of %s network interface" % numtext), 1536 IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)), 1537 (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT, 1538 "Link of %s network interface" % numtext), 1539 IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)), 1540 (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT, 1541 "Bridge of %s network interface" % numtext), 1542 IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)), 1543 ]) 1544 1545 aliases = [ 1546 # Legacy fields for first NIC 1547 ("ip", "nic.ip/0"), 1548 ("mac", "nic.mac/0"), 1549 ("bridge", "nic.bridge/0"), 1550 ("nic_mode", "nic.mode/0"), 1551 ("nic_link", "nic.link/0"), 1552 ] 1553 1554 return (fields, aliases)
1555 1556
1557 -def _GetInstDiskUsage(ctx, inst):
1558 """Get disk usage for an instance. 1559 1560 @type ctx: L{InstanceQueryData} 1561 @type inst: L{objects.Instance} 1562 @param inst: Instance object 1563 1564 """ 1565 usage = ctx.disk_usage[inst.name] 1566 1567 if usage is None: 1568 usage = 0 1569 1570 return usage
1571 1572
1573 -def _GetInstanceConsole(ctx, inst):
1574 """Get console information for instance. 1575 1576 @type ctx: L{InstanceQueryData} 1577 @type inst: L{objects.Instance} 1578 @param inst: Instance object 1579 1580 """ 1581 consinfo = ctx.console[inst.name] 1582 1583 if consinfo is None: 1584 return _FS_UNAVAIL 1585 1586 return consinfo
1587 1588
1589 -def _GetInstanceDiskFields():
1590 """Get instance fields involving disks. 1591 1592 @return: List of field definitions used as input for L{_PrepareFieldList} 1593 1594 """ 1595 fields = [ 1596 (_MakeField("disk_usage", "DiskUsage", QFT_UNIT, 1597 "Total disk space used by instance on each of its nodes;" 1598 " this is not the disk size visible to the instance, but" 1599 " the usage on the node"), 1600 IQ_DISKUSAGE, 0, _GetInstDiskUsage), 1601 (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"), 1602 IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)), 1603 (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"), 1604 IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]), 1605 ] 1606 1607 # Disks by number 1608 fields.extend([ 1609 (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT, 1610 "Disk size of %s disk" % utils.FormatOrdinal(i + 1)), 1611 IQ_CONFIG, 0, _GetInstDiskSize(i)) 1612 for i in range(constants.MAX_DISKS) 1613 ]) 1614 1615 return fields
1616 1617
1618 -def _GetInstanceParameterFields():
1619 """Get instance fields involving parameters. 1620 1621 @return: List of field definitions used as input for L{_PrepareFieldList} 1622 1623 """ 1624 # TODO: Consider moving titles closer to constants 1625 be_title = { 1626 constants.BE_AUTO_BALANCE: "Auto_balance", 1627 constants.BE_MEMORY: "ConfigMemory", 1628 constants.BE_VCPUS: "ConfigVCPUs", 1629 } 1630 1631 hv_title = { 1632 constants.HV_ACPI: "ACPI", 1633 constants.HV_BOOT_ORDER: "Boot_order", 1634 constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path", 1635 constants.HV_DISK_TYPE: "Disk_type", 1636 constants.HV_INITRD_PATH: "Initrd_path", 1637 constants.HV_KERNEL_PATH: "Kernel_path", 1638 constants.HV_NIC_TYPE: "NIC_type", 1639 constants.HV_PAE: "PAE", 1640 constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address", 1641 } 1642 1643 fields = [ 1644 # Filled parameters 1645 (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER, 1646 "Hypervisor parameters (merged)"), 1647 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams), 1648 (_MakeField("beparams", "BackendParameters", QFT_OTHER, 1649 "Backend parameters (merged)"), 1650 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams), 1651 (_MakeField("osparams", "OpSysParameters", QFT_OTHER, 1652 "Operating system parameters (merged)"), 1653 IQ_CONFIG, 0, lambda ctx, _: ctx.inst_osparams), 1654 1655 # Unfilled parameters 1656 (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER, 1657 "Custom hypervisor parameters"), 1658 IQ_CONFIG, 0, _GetItemAttr("hvparams")), 1659 (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER, 1660 "Custom backend parameters",), 1661 IQ_CONFIG, 0, _GetItemAttr("beparams")), 1662 (_MakeField("custom_osparams", "CustomOpSysParameters", QFT_OTHER, 1663 "Custom operating system parameters",), 1664 IQ_CONFIG, 0, _GetItemAttr("osparams")), 1665 (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER, 1666 "Custom network interface parameters"), 1667 IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]), 1668 ] 1669 1670 # HV params 1671 def _GetInstHvParam(name): 1672 return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1673 1674 fields.extend([ 1675 (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name), 1676 _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name), 1677 IQ_CONFIG, 0, _GetInstHvParam(name)) 1678 for name, kind in constants.HVS_PARAMETER_TYPES.items() 1679 if name not in constants.HVC_GLOBALS 1680 ]) 1681 1682 # BE params 1683 def _GetInstBeParam(name): 1684 return lambda ctx, _: ctx.inst_beparams.get(name, None) 1685 1686 fields.extend([ 1687 (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name), 1688 _VTToQFT[kind], "The \"%s\" backend parameter" % name), 1689 IQ_CONFIG, 0, _GetInstBeParam(name)) 1690 for name, kind in constants.BES_PARAMETER_TYPES.items() 1691 ]) 1692 1693 return fields 1694 1695 1696 _INST_SIMPLE_FIELDS = { 1697 "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"), 1698 "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"), 1699 "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"), 1700 # Depending on the hypervisor, the port can be None 1701 "network_port": ("Network_port", QFT_OTHER, 0, 1702 "Instance network port if available (e.g. for VNC console)"), 1703 "os": ("OS", QFT_TEXT, 0, "Operating system"), 1704 "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"), 1705 "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"), 1706 } 1707 1708
1709 -def _GetInstNodeGroup(ctx, default, node_name):
1710 """Gets group UUID of an instance node. 1711 1712 @type ctx: L{InstanceQueryData} 1713 @param default: Default value 1714 @type node_name: string 1715 @param node_name: Node name 1716 1717 """ 1718 try: 1719 node = ctx.nodes[node_name] 1720 except KeyError: 1721 return default 1722 else: 1723 return node.group
1724 1725
1726 -def _GetInstNodeGroupName(ctx, default, node_name):
1727 """Gets group name of an instance node. 1728 1729 @type ctx: L{InstanceQueryData} 1730 @param default: Default value 1731 @type node_name: string 1732 @param node_name: Node name 1733 1734 """ 1735 try: 1736 node = ctx.nodes[node_name] 1737 except KeyError: 1738 return default 1739 1740 try: 1741 group = ctx.groups[node.group] 1742 except KeyError: 1743 return default 1744 1745 return group.name
1746 1747
1748 -def _BuildInstanceFields():
1749 """Builds list of fields for instance queries. 1750 1751 """ 1752 fields = [ 1753 (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"), 1754 IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")), 1755 (_MakeField("pnode.group", "PrimaryNodeGroup", QFT_TEXT, 1756 "Primary node's group"), 1757 IQ_NODES, 0, 1758 lambda ctx, inst: _GetInstNodeGroupName(ctx, _FS_UNAVAIL, 1759 inst.primary_node)), 1760 (_MakeField("pnode.group.uuid", "PrimaryNodeGroupUUID", QFT_TEXT, 1761 "Primary node's group UUID"), 1762 IQ_NODES, 0, 1763 lambda ctx, inst: _GetInstNodeGroup(ctx, _FS_UNAVAIL, inst.primary_node)), 1764 # TODO: Allow filtering by secondary node as hostname 1765 (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER, 1766 "Secondary nodes; usually this will just be one node"), 1767 IQ_CONFIG, 0, lambda ctx, inst: list(inst.secondary_nodes)), 1768 (_MakeField("snodes.group", "SecondaryNodesGroups", QFT_OTHER, 1769 "Node groups of secondary nodes"), 1770 IQ_NODES, 0, 1771 lambda ctx, inst: map(compat.partial(_GetInstNodeGroupName, ctx, None), 1772 inst.secondary_nodes)), 1773 (_MakeField("snodes.group.uuid", "SecondaryNodesGroupsUUID", QFT_OTHER, 1774 "Node group UUIDs of secondary nodes"), 1775 IQ_NODES, 0, 1776 lambda ctx, inst: map(compat.partial(_GetInstNodeGroup, ctx, None), 1777 inst.secondary_nodes)), 1778 (_MakeField("admin_state", "Autostart", QFT_BOOL, 1779 "Desired state of instance (if set, the instance should be" 1780 " up)"), 1781 IQ_CONFIG, 0, _GetItemAttr("admin_up")), 1782 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0, 1783 lambda ctx, inst: list(inst.GetTags())), 1784 (_MakeField("console", "Console", QFT_OTHER, 1785 "Instance console information"), IQ_CONSOLE, 0, 1786 _GetInstanceConsole), 1787 ] 1788 1789 # Add simple fields 1790 fields.extend([ 1791 (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name)) 1792 for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items() 1793 ]) 1794 1795 # Fields requiring talking to the node 1796 fields.extend([ 1797 (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"), 1798 IQ_LIVE, 0, _GetInstOperState), 1799 (_MakeField("oper_ram", "Memory", QFT_UNIT, 1800 "Actual memory usage as seen by hypervisor"), 1801 IQ_LIVE, 0, _GetInstLiveData("memory")), 1802 (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER, 1803 "Actual number of VCPUs as seen by hypervisor"), 1804 IQ_LIVE, 0, _GetInstLiveData("vcpus")), 1805 ]) 1806 1807 # Status field 1808 status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN, 1809 constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP, 1810 constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN, 1811 constants.INSTST_NODEOFFLINE) 1812 status_doc = ("Instance status; \"%s\" if instance is set to be running" 1813 " and actually is, \"%s\" if instance is stopped and" 1814 " is not running, \"%s\" if instance running, but not on its" 1815 " designated primary node, \"%s\" if instance should be" 1816 " stopped, but is actually running, \"%s\" if instance should" 1817 " run, but doesn't, \"%s\" if instance's primary node is down," 1818 " \"%s\" if instance's primary node is marked offline" % 1819 status_values) 1820 fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc), 1821 IQ_LIVE, 0, _GetInstStatus)) 1822 assert set(status_values) == constants.INSTST_ALL, \ 1823 "Status documentation mismatch" 1824 1825 (network_fields, network_aliases) = _GetInstanceNetworkFields() 1826 1827 fields.extend(network_fields) 1828 fields.extend(_GetInstanceParameterFields()) 1829 fields.extend(_GetInstanceDiskFields()) 1830 fields.extend(_GetItemTimestampFields(IQ_CONFIG)) 1831 1832 aliases = [ 1833 ("vcpus", "be/vcpus"), 1834 ("sda_size", "disk.size/0"), 1835 ("sdb_size", "disk.size/1"), 1836 ] + network_aliases 1837 1838 return _PrepareFieldList(fields, aliases)
1839 1840
1841 -class LockQueryData:
1842 """Data container for lock data queries. 1843 1844 """
1845 - def __init__(self, lockdata):
1846 """Initializes this class. 1847 1848 """ 1849 self.lockdata = lockdata
1850
1851 - def __iter__(self):
1852 """Iterate over all locks. 1853 1854 """ 1855 return iter(self.lockdata)
1856 1857
1858 -def _GetLockOwners(_, data):
1859 """Returns a sorted list of a lock's current owners. 1860 1861 """ 1862 (_, _, owners, _) = data 1863 1864 if owners: 1865 owners = utils.NiceSort(owners) 1866 1867 return owners
1868 1869
1870 -def _GetLockPending(_, data):
1871 """Returns a sorted list of a lock's pending acquires. 1872 1873 """ 1874 (_, _, _, pending) = data 1875 1876 if pending: 1877 pending = [(mode, utils.NiceSort(names)) 1878 for (mode, names) in pending] 1879 1880 return pending
1881 1882
1883 -def _BuildLockFields():
1884 """Builds list of fields for lock queries. 1885 1886 """ 1887 return _PrepareFieldList([ 1888 # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used? 1889 (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0, 1890 lambda ctx, (name, mode, owners, pending): name), 1891 (_MakeField("mode", "Mode", QFT_OTHER, 1892 "Mode in which the lock is currently acquired" 1893 " (exclusive or shared)"), 1894 LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode), 1895 (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"), 1896 LQ_OWNER, 0, _GetLockOwners), 1897 (_MakeField("pending", "Pending", QFT_OTHER, 1898 "Threads waiting for the lock"), 1899 LQ_PENDING, 0, _GetLockPending), 1900 ], [])
1901 1902
1903 -class GroupQueryData:
1904 """Data container for node group data queries. 1905 1906 """
1907 - def __init__(self, groups, group_to_nodes, group_to_instances):
1908 """Initializes this class. 1909 1910 @param groups: List of node group objects 1911 @type group_to_nodes: dict; group UUID as key 1912 @param group_to_nodes: Per-group list of nodes 1913 @type group_to_instances: dict; group UUID as key 1914 @param group_to_instances: Per-group list of (primary) instances 1915 1916 """ 1917 self.groups = groups 1918 self.group_to_nodes = group_to_nodes 1919 self.group_to_instances = group_to_instances
1920
1921 - def __iter__(self):
1922 """Iterate over all node groups. 1923 1924 """ 1925 return iter(self.groups)
1926 1927 1928 _GROUP_SIMPLE_FIELDS = { 1929 "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"), 1930 "name": ("Group", QFT_TEXT, "Group name"), 1931 "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"), 1932 "uuid": ("UUID", QFT_TEXT, "Group UUID"), 1933 "ndparams": ("NDParams", QFT_OTHER, "Node parameters"), 1934 } 1935 1936
1937 -def _BuildGroupFields():
1938 """Builds list of fields for node group queries. 1939 1940 """ 1941 # Add simple fields 1942 fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0, 1943 _GetItemAttr(name)) 1944 for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()] 1945 1946 def _GetLength(getter): 1947 return lambda ctx, group: len(getter(ctx)[group.uuid])
1948 1949 def _GetSortedList(getter): 1950 return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid]) 1951 1952 group_to_nodes = operator.attrgetter("group_to_nodes") 1953 group_to_instances = operator.attrgetter("group_to_instances") 1954 1955 # Add fields for nodes 1956 fields.extend([ 1957 (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"), 1958 GQ_NODE, 0, _GetLength(group_to_nodes)), 1959 (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"), 1960 GQ_NODE, 0, _GetSortedList(group_to_nodes)), 1961 ]) 1962 1963 # Add fields for instances 1964 fields.extend([ 1965 (_MakeField("pinst_cnt", "Instances", QFT_NUMBER, 1966 "Number of primary instances"), 1967 GQ_INST, 0, _GetLength(group_to_instances)), 1968 (_MakeField("pinst_list", "InstanceList", QFT_OTHER, 1969 "List of primary instances"), 1970 GQ_INST, 0, _GetSortedList(group_to_instances)), 1971 ]) 1972 1973 # Other fields 1974 fields.extend([ 1975 (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), GQ_CONFIG, 0, 1976 lambda ctx, group: list(group.GetTags())), 1977 ]) 1978 1979 fields.extend(_GetItemTimestampFields(GQ_CONFIG)) 1980 1981 return _PrepareFieldList(fields, []) 1982 1983
1984 -class OsInfo(objects.ConfigObject):
1985 __slots__ = [ 1986 "name", 1987 "valid", 1988 "hidden", 1989 "blacklisted", 1990 "variants", 1991 "api_versions", 1992 "parameters", 1993 "node_status", 1994 ]
1995 1996
1997 -def _BuildOsFields():
1998 """Builds list of fields for operating system queries. 1999 2000 """ 2001 fields = [ 2002 (_MakeField("name", "Name", QFT_TEXT, "Operating system name"), 2003 None, 0, _GetItemAttr("name")), 2004 (_MakeField("valid", "Valid", QFT_BOOL, 2005 "Whether operating system definition is valid"), 2006 None, 0, _GetItemAttr("valid")), 2007 (_MakeField("hidden", "Hidden", QFT_BOOL, 2008 "Whether operating system is hidden"), 2009 None, 0, _GetItemAttr("hidden")), 2010 (_MakeField("blacklisted", "Blacklisted", QFT_BOOL, 2011 "Whether operating system is blacklisted"), 2012 None, 0, _GetItemAttr("blacklisted")), 2013 (_MakeField("variants", "Variants", QFT_OTHER, 2014 "Operating system variants"), 2015 None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("variants"))), 2016 (_MakeField("api_versions", "ApiVersions", QFT_OTHER, 2017 "Operating system API versions"), 2018 None, 0, _ConvWrap(sorted, _GetItemAttr("api_versions"))), 2019 (_MakeField("parameters", "Parameters", QFT_OTHER, 2020 "Operating system parameters"), 2021 None, 0, _ConvWrap(compat.partial(utils.NiceSort, key=compat.fst), 2022 _GetItemAttr("parameters"))), 2023 (_MakeField("node_status", "NodeStatus", QFT_OTHER, 2024 "Status from node"), 2025 None, 0, _GetItemAttr("node_status")), 2026 ] 2027 2028 return _PrepareFieldList(fields, [])
2029 2030 2031 #: Fields available for node queries 2032 NODE_FIELDS = _BuildNodeFields() 2033 2034 #: Fields available for instance queries 2035 INSTANCE_FIELDS = _BuildInstanceFields() 2036 2037 #: Fields available for lock queries 2038 LOCK_FIELDS = _BuildLockFields() 2039 2040 #: Fields available for node group queries 2041 GROUP_FIELDS = _BuildGroupFields() 2042 2043 #: Fields available for operating system queries 2044 OS_FIELDS = _BuildOsFields() 2045 2046 #: All available resources 2047 ALL_FIELDS = { 2048 constants.QR_INSTANCE: INSTANCE_FIELDS, 2049 constants.QR_NODE: NODE_FIELDS, 2050 constants.QR_LOCK: LOCK_FIELDS, 2051 constants.QR_GROUP: GROUP_FIELDS, 2052 constants.QR_OS: OS_FIELDS, 2053 } 2054 2055 #: All available field lists 2056 ALL_FIELD_LISTS = ALL_FIELDS.values() 2057