Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ e5395072

History | View | Annotate | Download (57 kB)

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) = range(100, 104)
87

    
88
(LQ_MODE,
89
 LQ_OWNER,
90
 LQ_PENDING) = range(10, 13)
91

    
92
(GQ_CONFIG,
93
 GQ_NODE,
94
 GQ_INST) = range(200, 203)
95

    
96
# Query field flags
97
QFF_HOSTNAME = 0x01
98
QFF_IP_ADDRESS = 0x02
99
# Next values: 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200
100
QFF_ALL = (QFF_HOSTNAME | QFF_IP_ADDRESS)
101

    
102
FIELD_NAME_RE = re.compile(r"^[a-z0-9/._]+$")
103
TITLE_RE = re.compile(r"^[^\s]+$")
104
DOC_RE = re.compile(r"^[A-Z].*[^.,?!]$")
105

    
106
#: Verification function for each field type
107
_VERIFY_FN = {
108
  QFT_UNKNOWN: ht.TNone,
109
  QFT_TEXT: ht.TString,
110
  QFT_BOOL: ht.TBool,
111
  QFT_NUMBER: ht.TInt,
112
  QFT_UNIT: ht.TInt,
113
  QFT_TIMESTAMP: ht.TOr(ht.TInt, ht.TFloat),
114
  QFT_OTHER: lambda _: True,
115
  }
116

    
117
# Unique objects for special field statuses
118
_FS_UNKNOWN = object()
119
_FS_NODATA = object()
120
_FS_UNAVAIL = object()
121
_FS_OFFLINE = object()
122

    
123
#: List of all special status
124
_FS_ALL = frozenset([_FS_UNKNOWN, _FS_NODATA, _FS_UNAVAIL, _FS_OFFLINE])
125

    
126
#: VType to QFT mapping
127
_VTToQFT = {
128
  # TODO: fix validation of empty strings
129
  constants.VTYPE_STRING: QFT_OTHER, # since VTYPE_STRINGs can be empty
130
  constants.VTYPE_MAYBE_STRING: QFT_OTHER,
131
  constants.VTYPE_BOOL: QFT_BOOL,
132
  constants.VTYPE_SIZE: QFT_UNIT,
133
  constants.VTYPE_INT: QFT_NUMBER,
134
  }
135

    
136
_SERIAL_NO_DOC = "%s object serial number, incremented on each modification"
137

    
138

    
139
def _GetUnknownField(ctx, item): # pylint: disable-msg=W0613
140
  """Gets the contents of an unknown field.
141

142
  """
143
  return _FS_UNKNOWN
144

    
145

    
146
def _GetQueryFields(fielddefs, selected):
147
  """Calculates the internal list of selected fields.
148

149
  Unknown fields are returned as L{constants.QFT_UNKNOWN}.
150

151
  @type fielddefs: dict
152
  @param fielddefs: Field definitions
153
  @type selected: list of strings
154
  @param selected: List of selected fields
155

156
  """
157
  result = []
158

    
159
  for name in selected:
160
    try:
161
      fdef = fielddefs[name]
162
    except KeyError:
163
      fdef = (_MakeField(name, name, QFT_UNKNOWN, "Unknown field '%s'" % name),
164
              None, 0, _GetUnknownField)
165

    
166
    assert len(fdef) == 4
167

    
168
    result.append(fdef)
169

    
170
  return result
171

    
172

    
173
def GetAllFields(fielddefs):
174
  """Extract L{objects.QueryFieldDefinition} from field definitions.
175

176
  @rtype: list of L{objects.QueryFieldDefinition}
177

178
  """
179
  return [fdef for (fdef, _, _, _) in fielddefs]
180

    
181

    
182
class _FilterHints:
183
  """Class for filter analytics.
184

185
  When filters are used, the user of the L{Query} class usually doesn't know
186
  exactly which items will be necessary for building the result. It therefore
187
  has to prepare and compute the input data for potentially returning
188
  everything.
189

190
  There are two ways to optimize this. The first, and simpler, is to assign
191
  each field a group of data, so that the caller can determine which
192
  computations are necessary depending on the data groups requested. The list
193
  of referenced groups must also be computed for fields referenced in the
194
  filter.
195

196
  The second is restricting the items based on a primary key. The primary key
197
  is usually a unique name (e.g. a node name). This class extracts all
198
  referenced names from a filter. If it encounters any filter condition which
199
  disallows such a list to be determined (e.g. a non-equality filter), all
200
  names will be requested.
201

202
  The end-effect is that any operation other than L{qlang.OP_OR} and
203
  L{qlang.OP_EQUAL} will make the query more expensive.
204

205
  """
206
  def __init__(self, namefield):
207
    """Initializes this class.
208

209
    @type namefield: string
210
    @param namefield: Field caller is interested in
211

212
    """
213
    self._namefield = namefield
214

    
215
    #: Whether all names need to be requested (e.g. if a non-equality operator
216
    #: has been used)
217
    self._allnames = False
218

    
219
    #: Which names to request
220
    self._names = None
221

    
222
    #: Data kinds referenced by the filter (used by L{Query.RequestedData})
223
    self._datakinds = set()
224

    
225
  def RequestedNames(self):
226
    """Returns all requested values.
227

228
    Returns C{None} if list of values can't be determined (e.g. encountered
229
    non-equality operators).
230

231
    @rtype: list
232

233
    """
234
    if self._allnames or self._names is None:
235
      return None
236

    
237
    return utils.UniqueSequence(self._names)
238

    
239
  def ReferencedData(self):
240
    """Returns all kinds of data referenced by the filter.
241

242
    """
243
    return frozenset(self._datakinds)
244

    
245
  def _NeedAllNames(self):
246
    """Changes internal state to request all names.
247

248
    """
249
    self._allnames = True
250
    self._names = None
251

    
252
  def NoteLogicOp(self, op):
253
    """Called when handling a logic operation.
254

255
    @type op: string
256
    @param op: Operator
257

258
    """
259
    if op != qlang.OP_OR:
260
      self._NeedAllNames()
261

    
262
  def NoteUnaryOp(self, op): # pylint: disable-msg=W0613
263
    """Called when handling an unary operation.
264

265
    @type op: string
266
    @param op: Operator
267

268
    """
269
    self._NeedAllNames()
270

    
271
  def NoteBinaryOp(self, op, datakind, name, value):
272
    """Called when handling a binary operation.
273

274
    @type op: string
275
    @param op: Operator
276
    @type name: string
277
    @param name: Left-hand side of operator (field name)
278
    @param value: Right-hand side of operator
279

280
    """
281
    if datakind is not None:
282
      self._datakinds.add(datakind)
283

    
284
    if self._allnames:
285
      return
286

    
287
    # If any operator other than equality was used, all names need to be
288
    # retrieved
289
    if op == qlang.OP_EQUAL and name == self._namefield:
290
      if self._names is None:
291
        self._names = []
292
      self._names.append(value)
293
    else:
294
      self._NeedAllNames()
295

    
296

    
297
def _WrapLogicOp(op_fn, sentences, ctx, item):
298
  """Wrapper for logic operator functions.
299

300
  """
301
  return op_fn(fn(ctx, item) for fn in sentences)
302

    
303

    
304
def _WrapUnaryOp(op_fn, inner, ctx, item):
305
  """Wrapper for unary operator functions.
306

307
  """
308
  return op_fn(inner(ctx, item))
309

    
310

    
311
def _WrapBinaryOp(op_fn, retrieval_fn, value, ctx, item):
312
  """Wrapper for binary operator functions.
313

314
  """
315
  return op_fn(retrieval_fn(ctx, item), value)
316

    
317

    
318
def _WrapNot(fn, lhs, rhs):
319
  """Negates the result of a wrapped function.
320

321
  """
322
  return not fn(lhs, rhs)
323

    
324

    
325
class _FilterCompilerHelper:
326
  """Converts a query filter to a callable usable for filtering.
327

328
  """
329
  # String statement has no effect, pylint: disable-msg=W0105
330

    
331
  #: How deep filters can be nested
332
  _LEVELS_MAX = 10
333

    
334
  # Unique identifiers for operator groups
335
  (_OPTYPE_LOGIC,
336
   _OPTYPE_UNARY,
337
   _OPTYPE_BINARY) = range(1, 4)
338

    
339
  """Functions for equality checks depending on field flags.
340

341
  List of tuples containing flags and a callable receiving the left- and
342
  right-hand side of the operator. The flags are an OR-ed value of C{QFF_*}
343
  (e.g. L{QFF_HOSTNAME}).
344

345
  Order matters. The first item with flags will be used. Flags are checked
346
  using binary AND.
347

348
  """
349
  _EQUALITY_CHECKS = [
350
    (QFF_HOSTNAME,
351
     lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
352
                                               case_sensitive=False)),
353
    (None, operator.eq),
354
    ]
355

    
356
  """Known operators
357

358
  Operator as key (C{qlang.OP_*}), value a tuple of operator group
359
  (C{_OPTYPE_*}) and a group-specific value:
360

361
    - C{_OPTYPE_LOGIC}: Callable taking any number of arguments; used by
362
      L{_HandleLogicOp}
363
    - C{_OPTYPE_UNARY}: Always C{None}; details handled by L{_HandleUnaryOp}
364
    - C{_OPTYPE_BINARY}: Callable taking exactly two parameters, the left- and
365
      right-hand side of the operator, used by L{_HandleBinaryOp}
366

367
  """
368
  _OPS = {
369
    # Logic operators
370
    qlang.OP_OR: (_OPTYPE_LOGIC, compat.any),
371
    qlang.OP_AND: (_OPTYPE_LOGIC, compat.all),
372

    
373
    # Unary operators
374
    qlang.OP_NOT: (_OPTYPE_UNARY, None),
375
    qlang.OP_TRUE: (_OPTYPE_UNARY, None),
376

    
377
    # Binary operators
378
    qlang.OP_EQUAL: (_OPTYPE_BINARY, _EQUALITY_CHECKS),
379
    qlang.OP_NOT_EQUAL:
380
      (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn))
381
                        for (flags, fn) in _EQUALITY_CHECKS]),
382
    qlang.OP_GLOB: (_OPTYPE_BINARY, NotImplemented),
383
    qlang.OP_REGEXP: (_OPTYPE_BINARY, NotImplemented),
384
    qlang.OP_CONTAINS: (_OPTYPE_BINARY, [
385
      (None, operator.contains),
386
      ]),
387
    }
388

    
389
  def __init__(self, fields):
390
    """Initializes this class.
391

392
    @param fields: Field definitions (return value of L{_PrepareFieldList})
393

394
    """
395
    self._fields = fields
396
    self._hints = None
397
    self._op_handler = None
398

    
399
  def __call__(self, hints, filter_):
400
    """Converts a query filter into a callable function.
401

402
    @type hints: L{_FilterHints} or None
403
    @param hints: Callbacks doing analysis on filter
404
    @type filter_: list
405
    @param filter_: Filter structure
406
    @rtype: callable
407
    @return: Function receiving context and item as parameters, returning
408
             boolean as to whether item matches filter
409

410
    """
411
    self._op_handler = {
412
      self._OPTYPE_LOGIC:
413
        (self._HandleLogicOp, getattr(hints, "NoteLogicOp", None)),
414
      self._OPTYPE_UNARY:
415
        (self._HandleUnaryOp, getattr(hints, "NoteUnaryOp", None)),
416
      self._OPTYPE_BINARY:
417
        (self._HandleBinaryOp, getattr(hints, "NoteBinaryOp", None)),
418
      }
419

    
420
    try:
421
      filter_fn = self._Compile(filter_, 0)
422
    finally:
423
      self._op_handler = None
424

    
425
    return filter_fn
426

    
427
  def _Compile(self, filter_, level):
428
    """Inner function for converting filters.
429

430
    Calls the correct handler functions for the top-level operator. This
431
    function is called recursively (e.g. for logic operators).
432

433
    """
434
    if not (isinstance(filter_, (list, tuple)) and filter_):
435
      raise errors.ParameterError("Invalid filter on level %s" % level)
436

    
437
    # Limit recursion
438
    if level >= self._LEVELS_MAX:
439
      raise errors.ParameterError("Only up to %s levels are allowed (filter"
440
                                  " nested too deep)" % self._LEVELS_MAX)
441

    
442
    # Create copy to be modified
443
    operands = filter_[:]
444
    op = operands.pop(0)
445

    
446
    try:
447
      (kind, op_data) = self._OPS[op]
448
    except KeyError:
449
      raise errors.ParameterError("Unknown operator '%s'" % op)
450

    
451
    (handler, hints_cb) = self._op_handler[kind]
452

    
453
    return handler(hints_cb, level, op, op_data, operands)
454

    
455
  def _LookupField(self, name):
456
    """Returns a field definition by name.
457

458
    """
459
    try:
460
      return self._fields[name]
461
    except KeyError:
462
      raise errors.ParameterError("Unknown field '%s'" % name)
463

    
464
  def _HandleLogicOp(self, hints_fn, level, op, op_fn, operands):
465
    """Handles logic operators.
466

467
    @type hints_fn: callable
468
    @param hints_fn: Callback doing some analysis on the filter
469
    @type level: integer
470
    @param level: Current depth
471
    @type op: string
472
    @param op: Operator
473
    @type op_fn: callable
474
    @param op_fn: Function implementing operator
475
    @type operands: list
476
    @param operands: List of operands
477

478
    """
479
    if hints_fn:
480
      hints_fn(op)
481

    
482
    return compat.partial(_WrapLogicOp, op_fn,
483
                          [self._Compile(op, level + 1) for op in operands])
484

    
485
  def _HandleUnaryOp(self, hints_fn, level, op, op_fn, operands):
486
    """Handles unary operators.
487

488
    @type hints_fn: callable
489
    @param hints_fn: Callback doing some analysis on the filter
490
    @type level: integer
491
    @param level: Current depth
492
    @type op: string
493
    @param op: Operator
494
    @type op_fn: callable
495
    @param op_fn: Function implementing operator
496
    @type operands: list
497
    @param operands: List of operands
498

499
    """
500
    assert op_fn is None
501

    
502
    if hints_fn:
503
      hints_fn(op)
504

    
505
    if len(operands) != 1:
506
      raise errors.ParameterError("Unary operator '%s' expects exactly one"
507
                                  " operand" % op)
508

    
509
    if op == qlang.OP_TRUE:
510
      (_, _, _, retrieval_fn) = self._LookupField(operands[0])
511

    
512
      op_fn = operator.truth
513
      arg = retrieval_fn
514
    elif op == qlang.OP_NOT:
515
      op_fn = operator.not_
516
      arg = self._Compile(operands[0], level + 1)
517
    else:
518
      raise errors.ProgrammerError("Can't handle operator '%s'" % op)
519

    
520
    return compat.partial(_WrapUnaryOp, op_fn, arg)
521

    
522
  def _HandleBinaryOp(self, hints_fn, level, op, op_data, operands):
523
    """Handles binary operators.
524

525
    @type hints_fn: callable
526
    @param hints_fn: Callback doing some analysis on the filter
527
    @type level: integer
528
    @param level: Current depth
529
    @type op: string
530
    @param op: Operator
531
    @param op_data: Functions implementing operators
532
    @type operands: list
533
    @param operands: List of operands
534

535
    """
536
    # Unused arguments, pylint: disable-msg=W0613
537
    try:
538
      (name, value) = operands
539
    except (ValueError, TypeError):
540
      raise errors.ParameterError("Invalid binary operator, expected exactly"
541
                                  " two operands")
542

    
543
    (fdef, datakind, field_flags, retrieval_fn) = self._LookupField(name)
544

    
545
    assert fdef.kind != QFT_UNKNOWN
546

    
547
    # TODO: Type conversions?
548

    
549
    verify_fn = _VERIFY_FN[fdef.kind]
550
    if not verify_fn(value):
551
      raise errors.ParameterError("Unable to compare field '%s' (type '%s')"
552
                                  " with '%s', expected %s" %
553
                                  (name, fdef.kind, value.__class__.__name__,
554
                                   verify_fn))
555

    
556
    if hints_fn:
557
      hints_fn(op, datakind, name, value)
558

    
559
    for (fn_flags, fn) in op_data:
560
      if fn_flags is None or fn_flags & field_flags:
561
        return compat.partial(_WrapBinaryOp, fn, retrieval_fn, value)
562

    
563
    raise errors.ProgrammerError("Unable to find operator implementation"
564
                                 " (op '%s', flags %s)" % (op, field_flags))
565

    
566

    
567
def _CompileFilter(fields, hints, filter_):
568
  """Converts a query filter into a callable function.
569

570
  See L{_FilterCompilerHelper} for details.
571

572
  @rtype: callable
573

574
  """
575
  return _FilterCompilerHelper(fields)(hints, filter_)
576

    
577

    
578
class Query:
579
  def __init__(self, fieldlist, selected, filter_=None, namefield=None):
580
    """Initializes this class.
581

582
    The field definition is a dictionary with the field's name as a key and a
583
    tuple containing, in order, the field definition object
584
    (L{objects.QueryFieldDefinition}, the data kind to help calling code
585
    collect data and a retrieval function. The retrieval function is called
586
    with two parameters, in order, the data container and the item in container
587
    (see L{Query.Query}).
588

589
    Users of this class can call L{RequestedData} before preparing the data
590
    container to determine what data is needed.
591

592
    @type fieldlist: dictionary
593
    @param fieldlist: Field definitions
594
    @type selected: list of strings
595
    @param selected: List of selected fields
596

597
    """
598
    assert namefield is None or namefield in fieldlist
599

    
600
    self._fields = _GetQueryFields(fieldlist, selected)
601

    
602
    self._filter_fn = None
603
    self._requested_names = None
604
    self._filter_datakinds = frozenset()
605

    
606
    if filter_ is not None:
607
      # Collect requested names if wanted
608
      if namefield:
609
        hints = _FilterHints(namefield)
610
      else:
611
        hints = None
612

    
613
      # Build filter function
614
      self._filter_fn = _CompileFilter(fieldlist, hints, filter_)
615
      if hints:
616
        self._requested_names = hints.RequestedNames()
617
        self._filter_datakinds = hints.ReferencedData()
618

    
619
    if namefield is None:
620
      self._name_fn = None
621
    else:
622
      (_, _, _, self._name_fn) = fieldlist[namefield]
623

    
624
  def RequestedNames(self):
625
    """Returns all names referenced in the filter.
626

627
    If there is no filter or operators are preventing determining the exact
628
    names, C{None} is returned.
629

630
    """
631
    return self._requested_names
632

    
633
  def RequestedData(self):
634
    """Gets requested kinds of data.
635

636
    @rtype: frozenset
637

638
    """
639
    return (self._filter_datakinds |
640
            frozenset(datakind for (_, datakind, _, _) in self._fields
641
                      if datakind is not None))
642

    
643
  def GetFields(self):
644
    """Returns the list of fields for this query.
645

646
    Includes unknown fields.
647

648
    @rtype: List of L{objects.QueryFieldDefinition}
649

650
    """
651
    return GetAllFields(self._fields)
652

    
653
  def Query(self, ctx, sort_by_name=True):
654
    """Execute a query.
655

656
    @param ctx: Data container passed to field retrieval functions, must
657
      support iteration using C{__iter__}
658
    @type sort_by_name: boolean
659
    @param sort_by_name: Whether to sort by name or keep the input data's
660
      ordering
661

662
    """
663
    sort = (self._name_fn and sort_by_name)
664

    
665
    result = []
666

    
667
    for idx, item in enumerate(ctx):
668
      if not (self._filter_fn is None or self._filter_fn(ctx, item)):
669
        continue
670

    
671
      row = [_ProcessResult(fn(ctx, item)) for (_, _, _, fn) in self._fields]
672

    
673
      # Verify result
674
      if __debug__:
675
        _VerifyResultRow(self._fields, row)
676

    
677
      if sort:
678
        (status, name) = _ProcessResult(self._name_fn(ctx, item))
679
        assert status == constants.RS_NORMAL
680
        # TODO: Are there cases where we wouldn't want to use NiceSort?
681
        result.append((utils.NiceSortKey(name), idx, row))
682
      else:
683
        result.append(row)
684

    
685
    if not sort:
686
      return result
687

    
688
    # TODO: Would "heapq" be more efficient than sorting?
689

    
690
    # Sorting in-place instead of using "sorted()"
691
    result.sort()
692

    
693
    assert not result or (len(result[0]) == 3 and len(result[-1]) == 3)
694

    
695
    return map(operator.itemgetter(2), result)
696

    
697
  def OldStyleQuery(self, ctx, sort_by_name=True):
698
    """Query with "old" query result format.
699

700
    See L{Query.Query} for arguments.
701

702
    """
703
    unknown = set(fdef.name for (fdef, _, _, _) in self._fields
704
                  if fdef.kind == QFT_UNKNOWN)
705
    if unknown:
706
      raise errors.OpPrereqError("Unknown output fields selected: %s" %
707
                                 (utils.CommaJoin(unknown), ),
708
                                 errors.ECODE_INVAL)
709

    
710
    return [[value for (_, value) in row]
711
            for row in self.Query(ctx, sort_by_name=sort_by_name)]
712

    
713

    
714
def _ProcessResult(value):
715
  """Converts result values into externally-visible ones.
716

717
  """
718
  if value is _FS_UNKNOWN:
719
    return (RS_UNKNOWN, None)
720
  elif value is _FS_NODATA:
721
    return (RS_NODATA, None)
722
  elif value is _FS_UNAVAIL:
723
    return (RS_UNAVAIL, None)
724
  elif value is _FS_OFFLINE:
725
    return (RS_OFFLINE, None)
726
  else:
727
    return (RS_NORMAL, value)
728

    
729

    
730
def _VerifyResultRow(fields, row):
731
  """Verifies the contents of a query result row.
732

733
  @type fields: list
734
  @param fields: Field definitions for result
735
  @type row: list of tuples
736
  @param row: Row data
737

738
  """
739
  assert len(row) == len(fields)
740
  errs = []
741
  for ((status, value), (fdef, _, _, _)) in zip(row, fields):
742
    if status == RS_NORMAL:
743
      if not _VERIFY_FN[fdef.kind](value):
744
        errs.append("normal field %s fails validation (value is %s)" %
745
                    (fdef.name, value))
746
    elif value is not None:
747
      errs.append("abnormal field %s has a non-None value" % fdef.name)
748
  assert not errs, ("Failed validation: %s in row %s" %
749
                    (utils.CommaJoin(errors), row))
750

    
751

    
752
def _PrepareFieldList(fields, aliases):
753
  """Prepares field list for use by L{Query}.
754

755
  Converts the list to a dictionary and does some verification.
756

757
  @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data
758
      kind, retrieval function)
759
  @param fields: List of fields, see L{Query.__init__} for a better
760
      description
761
  @type aliases: list of tuples; (alias, target)
762
  @param aliases: list of tuples containing aliases; for each
763
      alias/target pair, a duplicate will be created in the field list
764
  @rtype: dict
765
  @return: Field dictionary for L{Query}
766

767
  """
768
  if __debug__:
769
    duplicates = utils.FindDuplicates(fdef.title.lower()
770
                                      for (fdef, _, _, _) in fields)
771
    assert not duplicates, "Duplicate title(s) found: %r" % duplicates
772

    
773
  result = {}
774

    
775
  for field in fields:
776
    (fdef, _, flags, fn) = field
777

    
778
    assert fdef.name and fdef.title, "Name and title are required"
779
    assert FIELD_NAME_RE.match(fdef.name)
780
    assert TITLE_RE.match(fdef.title)
781
    assert (DOC_RE.match(fdef.doc) and len(fdef.doc.splitlines()) == 1 and
782
            fdef.doc.strip() == fdef.doc), \
783
           "Invalid description for field '%s'" % fdef.name
784
    assert callable(fn)
785
    assert fdef.name not in result, \
786
           "Duplicate field name '%s' found" % fdef.name
787
    assert (flags & ~QFF_ALL) == 0, "Unknown flags for field '%s'" % fdef.name
788

    
789
    result[fdef.name] = field
790

    
791
  for alias, target in aliases:
792
    assert alias not in result, "Alias %s overrides an existing field" % alias
793
    assert target in result, "Missing target %s for alias %s" % (target, alias)
794
    (fdef, k, flags, fn) = result[target]
795
    fdef = fdef.Copy()
796
    fdef.name = alias
797
    result[alias] = (fdef, k, flags, fn)
798

    
799
  assert len(result) == len(fields) + len(aliases)
800
  assert compat.all(name == fdef.name
801
                    for (name, (fdef, _, _, _)) in result.items())
802

    
803
  return result
804

    
805

    
806
def GetQueryResponse(query, ctx, sort_by_name=True):
807
  """Prepares the response for a query.
808

809
  @type query: L{Query}
810
  @param ctx: Data container, see L{Query.Query}
811
  @type sort_by_name: boolean
812
  @param sort_by_name: Whether to sort by name or keep the input data's
813
    ordering
814

815
  """
816
  return objects.QueryResponse(data=query.Query(ctx, sort_by_name=sort_by_name),
817
                               fields=query.GetFields()).ToDict()
818

    
819

    
820
def QueryFields(fielddefs, selected):
821
  """Returns list of available fields.
822

823
  @type fielddefs: dict
824
  @param fielddefs: Field definitions
825
  @type selected: list of strings
826
  @param selected: List of selected fields
827
  @return: List of L{objects.QueryFieldDefinition}
828

829
  """
830
  if selected is None:
831
    # Client requests all fields, sort by name
832
    fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
833
                           key=operator.attrgetter("name"))
834
  else:
835
    # Keep order as requested by client
836
    fdefs = Query(fielddefs, selected).GetFields()
837

    
838
  return objects.QueryFieldsResponse(fields=fdefs).ToDict()
839

    
840

    
841
def _MakeField(name, title, kind, doc):
842
  """Wrapper for creating L{objects.QueryFieldDefinition} instances.
843

844
  @param name: Field name as a regular expression
845
  @param title: Human-readable title
846
  @param kind: Field type
847
  @param doc: Human-readable description
848

849
  """
850
  return objects.QueryFieldDefinition(name=name, title=title, kind=kind,
851
                                      doc=doc)
852

    
853

    
854
def _GetNodeRole(node, master_name):
855
  """Determine node role.
856

857
  @type node: L{objects.Node}
858
  @param node: Node object
859
  @type master_name: string
860
  @param master_name: Master node name
861

862
  """
863
  if node.name == master_name:
864
    return constants.NR_MASTER
865
  elif node.master_candidate:
866
    return constants.NR_MCANDIDATE
867
  elif node.drained:
868
    return constants.NR_DRAINED
869
  elif node.offline:
870
    return constants.NR_OFFLINE
871
  else:
872
    return constants.NR_REGULAR
873

    
874

    
875
def _GetItemAttr(attr):
876
  """Returns a field function to return an attribute of the item.
877

878
  @param attr: Attribute name
879

880
  """
881
  getter = operator.attrgetter(attr)
882
  return lambda _, item: getter(item)
883

    
884

    
885
def _ConvWrapInner(convert, fn, ctx, item):
886
  """Wrapper for converting values.
887

888
  @param convert: Conversion function receiving value as single parameter
889
  @param fn: Retrieval function
890

891
  """
892
  value = fn(ctx, item)
893

    
894
  # Is the value an abnormal status?
895
  if compat.any(value is fs for fs in _FS_ALL):
896
    # Return right away
897
    return value
898

    
899
  # TODO: Should conversion function also receive context, item or both?
900
  return convert(value)
901

    
902

    
903
def _ConvWrap(convert, fn):
904
  """Convenience wrapper for L{_ConvWrapInner}.
905

906
  @param convert: Conversion function receiving value as single parameter
907
  @param fn: Retrieval function
908

909
  """
910
  return compat.partial(_ConvWrapInner, convert, fn)
911

    
912

    
913
def _GetItemTimestamp(getter):
914
  """Returns function for getting timestamp of item.
915

916
  @type getter: callable
917
  @param getter: Function to retrieve timestamp attribute
918

919
  """
920
  def fn(_, item):
921
    """Returns a timestamp of item.
922

923
    """
924
    timestamp = getter(item)
925
    if timestamp is None:
926
      # Old configs might not have all timestamps
927
      return _FS_UNAVAIL
928
    else:
929
      return timestamp
930

    
931
  return fn
932

    
933

    
934
def _GetItemTimestampFields(datatype):
935
  """Returns common timestamp fields.
936

937
  @param datatype: Field data type for use by L{Query.RequestedData}
938

939
  """
940
  return [
941
    (_MakeField("ctime", "CTime", QFT_TIMESTAMP, "Creation timestamp"),
942
     datatype, 0, _GetItemTimestamp(operator.attrgetter("ctime"))),
943
    (_MakeField("mtime", "MTime", QFT_TIMESTAMP, "Modification timestamp"),
944
     datatype, 0, _GetItemTimestamp(operator.attrgetter("mtime"))),
945
    ]
946

    
947

    
948
class NodeQueryData:
949
  """Data container for node data queries.
950

951
  """
952
  def __init__(self, nodes, live_data, master_name, node_to_primary,
953
               node_to_secondary, groups, oob_support, cluster):
954
    """Initializes this class.
955

956
    """
957
    self.nodes = nodes
958
    self.live_data = live_data
959
    self.master_name = master_name
960
    self.node_to_primary = node_to_primary
961
    self.node_to_secondary = node_to_secondary
962
    self.groups = groups
963
    self.oob_support = oob_support
964
    self.cluster = cluster
965

    
966
    # Used for individual rows
967
    self.curlive_data = None
968

    
969
  def __iter__(self):
970
    """Iterate over all nodes.
971

972
    This function has side-effects and only one instance of the resulting
973
    generator should be used at a time.
974

975
    """
976
    for node in self.nodes:
977
      if self.live_data:
978
        self.curlive_data = self.live_data.get(node.name, None)
979
      else:
980
        self.curlive_data = None
981
      yield node
982

    
983

    
984
#: Fields that are direct attributes of an L{objects.Node} object
985
_NODE_SIMPLE_FIELDS = {
986
  "drained": ("Drained", QFT_BOOL, 0, "Whether node is drained"),
987
  "master_candidate": ("MasterC", QFT_BOOL, 0,
988
                       "Whether node is a master candidate"),
989
  "master_capable": ("MasterCapable", QFT_BOOL, 0,
990
                     "Whether node can become a master candidate"),
991
  "name": ("Node", QFT_TEXT, QFF_HOSTNAME, "Node name"),
992
  "offline": ("Offline", QFT_BOOL, 0, "Whether node is marked offline"),
993
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Node"),
994
  "uuid": ("UUID", QFT_TEXT, 0, "Node UUID"),
995
  "vm_capable": ("VMCapable", QFT_BOOL, 0, "Whether node can host instances"),
996
  }
997

    
998

    
999
#: Fields requiring talking to the node
1000
# Note that none of these are available for non-vm_capable nodes
1001
_NODE_LIVE_FIELDS = {
1002
  "bootid": ("BootID", QFT_TEXT, "bootid",
1003
             "Random UUID renewed for each system reboot, can be used"
1004
             " for detecting reboots by tracking changes"),
1005
  "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes",
1006
             "Number of NUMA domains on node (if exported by hypervisor)"),
1007
  "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets",
1008
               "Number of physical CPU sockets (if exported by hypervisor)"),
1009
  "ctotal": ("CTotal", QFT_NUMBER, "cpu_total", "Number of logical processors"),
1010
  "dfree": ("DFree", QFT_UNIT, "vg_free",
1011
            "Available disk space in volume group"),
1012
  "dtotal": ("DTotal", QFT_UNIT, "vg_size",
1013
             "Total disk space in volume group used for instance disk"
1014
             " allocation"),
1015
  "mfree": ("MFree", QFT_UNIT, "memory_free",
1016
            "Memory available for instance allocations"),
1017
  "mnode": ("MNode", QFT_UNIT, "memory_dom0",
1018
            "Amount of memory used by node (dom0 for Xen)"),
1019
  "mtotal": ("MTotal", QFT_UNIT, "memory_total",
1020
             "Total amount of memory of physical machine"),
1021
  }
1022

    
1023

    
1024
def _GetGroup(cb):
1025
  """Build function for calling another function with an node group.
1026

1027
  @param cb: The callback to be called with the nodegroup
1028

1029
  """
1030
  def fn(ctx, node):
1031
    """Get group data for a node.
1032

1033
    @type ctx: L{NodeQueryData}
1034
    @type inst: L{objects.Node}
1035
    @param inst: Node object
1036

1037
    """
1038
    ng = ctx.groups.get(node.group, None)
1039
    if ng is None:
1040
      # Nodes always have a group, or the configuration is corrupt
1041
      return _FS_UNAVAIL
1042

    
1043
    return cb(ctx, node, ng)
1044

    
1045
  return fn
1046

    
1047

    
1048
def _GetNodeGroup(ctx, node, ng): # pylint: disable-msg=W0613
1049
  """Returns the name of a node's group.
1050

1051
  @type ctx: L{NodeQueryData}
1052
  @type node: L{objects.Node}
1053
  @param node: Node object
1054
  @type ng: L{objects.NodeGroup}
1055
  @param ng: The node group this node belongs to
1056

1057
  """
1058
  return ng.name
1059

    
1060

    
1061
def _GetNodePower(ctx, node):
1062
  """Returns the node powered state
1063

1064
  @type ctx: L{NodeQueryData}
1065
  @type node: L{objects.Node}
1066
  @param node: Node object
1067

1068
  """
1069
  if ctx.oob_support[node.name]:
1070
    return node.powered
1071

    
1072
  return _FS_UNAVAIL
1073

    
1074

    
1075
def _GetNdParams(ctx, node, ng):
1076
  """Returns the ndparams for this node.
1077

1078
  @type ctx: L{NodeQueryData}
1079
  @type node: L{objects.Node}
1080
  @param node: Node object
1081
  @type ng: L{objects.NodeGroup}
1082
  @param ng: The node group this node belongs to
1083

1084
  """
1085
  return ctx.cluster.SimpleFillND(ng.FillND(node))
1086

    
1087

    
1088
def _GetLiveNodeField(field, kind, ctx, node):
1089
  """Gets the value of a "live" field from L{NodeQueryData}.
1090

1091
  @param field: Live field name
1092
  @param kind: Data kind, one of L{constants.QFT_ALL}
1093
  @type ctx: L{NodeQueryData}
1094
  @type node: L{objects.Node}
1095
  @param node: Node object
1096

1097
  """
1098
  if node.offline:
1099
    return _FS_OFFLINE
1100

    
1101
  if not node.vm_capable:
1102
    return _FS_UNAVAIL
1103

    
1104
  if not ctx.curlive_data:
1105
    return _FS_NODATA
1106

    
1107
  try:
1108
    value = ctx.curlive_data[field]
1109
  except KeyError:
1110
    return _FS_UNAVAIL
1111

    
1112
  if kind == QFT_TEXT:
1113
    return value
1114

    
1115
  assert kind in (QFT_NUMBER, QFT_UNIT)
1116

    
1117
  # Try to convert into number
1118
  try:
1119
    return int(value)
1120
  except (ValueError, TypeError):
1121
    logging.exception("Failed to convert node field '%s' (value %r) to int",
1122
                      value, field)
1123
    return _FS_UNAVAIL
1124

    
1125

    
1126
def _BuildNodeFields():
1127
  """Builds list of fields for node queries.
1128

1129
  """
1130
  fields = [
1131
    (_MakeField("pip", "PrimaryIP", QFT_TEXT, "Primary IP address"),
1132
     NQ_CONFIG, 0, _GetItemAttr("primary_ip")),
1133
    (_MakeField("sip", "SecondaryIP", QFT_TEXT, "Secondary IP address"),
1134
     NQ_CONFIG, 0, _GetItemAttr("secondary_ip")),
1135
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0,
1136
     lambda ctx, node: list(node.GetTags())),
1137
    (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"),
1138
     NQ_CONFIG, 0, lambda ctx, node: node.name == ctx.master_name),
1139
    (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0,
1140
     _GetGroup(_GetNodeGroup)),
1141
    (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"),
1142
     NQ_CONFIG, 0, _GetItemAttr("group")),
1143
    (_MakeField("powered", "Powered", QFT_BOOL,
1144
                "Whether node is thought to be powered on"),
1145
     NQ_OOB, 0, _GetNodePower),
1146
    (_MakeField("ndparams", "NodeParameters", QFT_OTHER,
1147
                "Merged node parameters"),
1148
     NQ_GROUP, 0, _GetGroup(_GetNdParams)),
1149
    (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER,
1150
                "Custom node parameters"),
1151
      NQ_GROUP, 0, _GetItemAttr("ndparams")),
1152
    ]
1153

    
1154
  # Node role
1155
  role_values = (constants.NR_MASTER, constants.NR_MCANDIDATE,
1156
                 constants.NR_REGULAR, constants.NR_DRAINED,
1157
                 constants.NR_OFFLINE)
1158
  role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate,"
1159
              " \"%s\" for regular, \"%s\" for a drained, \"%s\" for offline" %
1160
              role_values)
1161
  fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
1162
                 lambda ctx, node: _GetNodeRole(node, ctx.master_name)))
1163
  assert set(role_values) == constants.NR_ALL
1164

    
1165
  def _GetLength(getter):
1166
    return lambda ctx, node: len(getter(ctx)[node.name])
1167

    
1168
  def _GetList(getter):
1169
    return lambda ctx, node: list(getter(ctx)[node.name])
1170

    
1171
  # Add fields operating on instance lists
1172
  for prefix, titleprefix, docword, getter in \
1173
      [("p", "Pri", "primary", operator.attrgetter("node_to_primary")),
1174
       ("s", "Sec", "secondary", operator.attrgetter("node_to_secondary"))]:
1175
    # TODO: Allow filterting by hostname in list
1176
    fields.extend([
1177
      (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER,
1178
                  "Number of instances with this node as %s" % docword),
1179
       NQ_INST, 0, _GetLength(getter)),
1180
      (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
1181
                  QFT_OTHER,
1182
                  "List of instances with this node as %s" % docword),
1183
       NQ_INST, 0, _GetList(getter)),
1184
      ])
1185

    
1186
  # Add simple fields
1187
  fields.extend([
1188
    (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1189
    for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()
1190
    ])
1191

    
1192
  # Add fields requiring live data
1193
  fields.extend([
1194
    (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1195
     compat.partial(_GetLiveNodeField, nfield, kind))
1196
    for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()
1197
    ])
1198

    
1199
  # Add timestamps
1200
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1201

    
1202
  return _PrepareFieldList(fields, [])
1203

    
1204

    
1205
class InstanceQueryData:
1206
  """Data container for instance data queries.
1207

1208
  """
1209
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
1210
               live_data, wrongnode_inst, console):
1211
    """Initializes this class.
1212

1213
    @param instances: List of instance objects
1214
    @param cluster: Cluster object
1215
    @type disk_usage: dict; instance name as key
1216
    @param disk_usage: Per-instance disk usage
1217
    @type offline_nodes: list of strings
1218
    @param offline_nodes: List of offline nodes
1219
    @type bad_nodes: list of strings
1220
    @param bad_nodes: List of faulty nodes
1221
    @type live_data: dict; instance name as key
1222
    @param live_data: Per-instance live data
1223
    @type wrongnode_inst: set
1224
    @param wrongnode_inst: Set of instances running on wrong node(s)
1225
    @type console: dict; instance name as key
1226
    @param console: Per-instance console information
1227

1228
    """
1229
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
1230
           "Offline nodes not included in bad nodes"
1231
    assert not (set(live_data.keys()) & set(bad_nodes)), \
1232
           "Found live data for bad or offline nodes"
1233

    
1234
    self.instances = instances
1235
    self.cluster = cluster
1236
    self.disk_usage = disk_usage
1237
    self.offline_nodes = offline_nodes
1238
    self.bad_nodes = bad_nodes
1239
    self.live_data = live_data
1240
    self.wrongnode_inst = wrongnode_inst
1241
    self.console = console
1242

    
1243
    # Used for individual rows
1244
    self.inst_hvparams = None
1245
    self.inst_beparams = None
1246
    self.inst_nicparams = None
1247

    
1248
  def __iter__(self):
1249
    """Iterate over all instances.
1250

1251
    This function has side-effects and only one instance of the resulting
1252
    generator should be used at a time.
1253

1254
    """
1255
    for inst in self.instances:
1256
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1257
      self.inst_beparams = self.cluster.FillBE(inst)
1258
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1259
                             for nic in inst.nics]
1260

    
1261
      yield inst
1262

    
1263

    
1264
def _GetInstOperState(ctx, inst):
1265
  """Get instance's operational status.
1266

1267
  @type ctx: L{InstanceQueryData}
1268
  @type inst: L{objects.Instance}
1269
  @param inst: Instance object
1270

1271
  """
1272
  # Can't use RS_OFFLINE here as it would describe the instance to
1273
  # be offline when we actually don't know due to missing data
1274
  if inst.primary_node in ctx.bad_nodes:
1275
    return _FS_NODATA
1276
  else:
1277
    return bool(ctx.live_data.get(inst.name))
1278

    
1279

    
1280
def _GetInstLiveData(name):
1281
  """Build function for retrieving live data.
1282

1283
  @type name: string
1284
  @param name: Live data field name
1285

1286
  """
1287
  def fn(ctx, inst):
1288
    """Get live data for an instance.
1289

1290
    @type ctx: L{InstanceQueryData}
1291
    @type inst: L{objects.Instance}
1292
    @param inst: Instance object
1293

1294
    """
1295
    if (inst.primary_node in ctx.bad_nodes or
1296
        inst.primary_node in ctx.offline_nodes):
1297
      # Can't use RS_OFFLINE here as it would describe the instance to be
1298
      # offline when we actually don't know due to missing data
1299
      return _FS_NODATA
1300

    
1301
    if inst.name in ctx.live_data:
1302
      data = ctx.live_data[inst.name]
1303
      if name in data:
1304
        return data[name]
1305

    
1306
    return _FS_UNAVAIL
1307

    
1308
  return fn
1309

    
1310

    
1311
def _GetInstStatus(ctx, inst):
1312
  """Get instance status.
1313

1314
  @type ctx: L{InstanceQueryData}
1315
  @type inst: L{objects.Instance}
1316
  @param inst: Instance object
1317

1318
  """
1319
  if inst.primary_node in ctx.offline_nodes:
1320
    return constants.INSTST_NODEOFFLINE
1321

    
1322
  if inst.primary_node in ctx.bad_nodes:
1323
    return constants.INSTST_NODEDOWN
1324

    
1325
  if bool(ctx.live_data.get(inst.name)):
1326
    if inst.name in ctx.wrongnode_inst:
1327
      return constants.INSTST_WRONGNODE
1328
    elif inst.admin_up:
1329
      return constants.INSTST_RUNNING
1330
    else:
1331
      return constants.INSTST_ERRORUP
1332

    
1333
  if inst.admin_up:
1334
    return constants.INSTST_ERRORDOWN
1335

    
1336
  return constants.INSTST_ADMINDOWN
1337

    
1338

    
1339
def _GetInstDiskSize(index):
1340
  """Build function for retrieving disk size.
1341

1342
  @type index: int
1343
  @param index: Disk index
1344

1345
  """
1346
  def fn(_, inst):
1347
    """Get size of a disk.
1348

1349
    @type inst: L{objects.Instance}
1350
    @param inst: Instance object
1351

1352
    """
1353
    try:
1354
      return inst.disks[index].size
1355
    except IndexError:
1356
      return _FS_UNAVAIL
1357

    
1358
  return fn
1359

    
1360

    
1361
def _GetInstNic(index, cb):
1362
  """Build function for calling another function with an instance NIC.
1363

1364
  @type index: int
1365
  @param index: NIC index
1366
  @type cb: callable
1367
  @param cb: Callback
1368

1369
  """
1370
  def fn(ctx, inst):
1371
    """Call helper function with instance NIC.
1372

1373
    @type ctx: L{InstanceQueryData}
1374
    @type inst: L{objects.Instance}
1375
    @param inst: Instance object
1376

1377
    """
1378
    try:
1379
      nic = inst.nics[index]
1380
    except IndexError:
1381
      return _FS_UNAVAIL
1382

    
1383
    return cb(ctx, index, nic)
1384

    
1385
  return fn
1386

    
1387

    
1388
def _GetInstNicIp(ctx, _, nic): # pylint: disable-msg=W0613
1389
  """Get a NIC's IP address.
1390

1391
  @type ctx: L{InstanceQueryData}
1392
  @type nic: L{objects.NIC}
1393
  @param nic: NIC object
1394

1395
  """
1396
  if nic.ip is None:
1397
    return _FS_UNAVAIL
1398
  else:
1399
    return nic.ip
1400

    
1401

    
1402
def _GetInstNicBridge(ctx, index, _):
1403
  """Get a NIC's bridge.
1404

1405
  @type ctx: L{InstanceQueryData}
1406
  @type index: int
1407
  @param index: NIC index
1408

1409
  """
1410
  assert len(ctx.inst_nicparams) >= index
1411

    
1412
  nicparams = ctx.inst_nicparams[index]
1413

    
1414
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1415
    return nicparams[constants.NIC_LINK]
1416
  else:
1417
    return _FS_UNAVAIL
1418

    
1419

    
1420
def _GetInstAllNicBridges(ctx, inst):
1421
  """Get all network bridges for an instance.
1422

1423
  @type ctx: L{InstanceQueryData}
1424
  @type inst: L{objects.Instance}
1425
  @param inst: Instance object
1426

1427
  """
1428
  assert len(ctx.inst_nicparams) == len(inst.nics)
1429

    
1430
  result = []
1431

    
1432
  for nicp in ctx.inst_nicparams:
1433
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1434
      result.append(nicp[constants.NIC_LINK])
1435
    else:
1436
      result.append(None)
1437

    
1438
  assert len(result) == len(inst.nics)
1439

    
1440
  return result
1441

    
1442

    
1443
def _GetInstNicParam(name):
1444
  """Build function for retrieving a NIC parameter.
1445

1446
  @type name: string
1447
  @param name: Parameter name
1448

1449
  """
1450
  def fn(ctx, index, _):
1451
    """Get a NIC's bridge.
1452

1453
    @type ctx: L{InstanceQueryData}
1454
    @type inst: L{objects.Instance}
1455
    @param inst: Instance object
1456
    @type nic: L{objects.NIC}
1457
    @param nic: NIC object
1458

1459
    """
1460
    assert len(ctx.inst_nicparams) >= index
1461
    return ctx.inst_nicparams[index][name]
1462

    
1463
  return fn
1464

    
1465

    
1466
def _GetInstanceNetworkFields():
1467
  """Get instance fields involving network interfaces.
1468

1469
  @return: Tuple containing list of field definitions used as input for
1470
    L{_PrepareFieldList} and a list of aliases
1471

1472
  """
1473
  nic_mac_fn = lambda ctx, _, nic: nic.mac
1474
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1475
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1476

    
1477
  fields = [
1478
    # All NICs
1479
    (_MakeField("nic.count", "NICs", QFT_NUMBER,
1480
                "Number of network interfaces"),
1481
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.nics)),
1482
    (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER,
1483
                "List containing each network interface's MAC address"),
1484
     IQ_CONFIG, 0, lambda ctx, inst: [nic.mac for nic in inst.nics]),
1485
    (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
1486
                "List containing each network interface's IP address"),
1487
     IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
1488
    (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
1489
                "List containing each network interface's mode"), IQ_CONFIG, 0,
1490
     lambda ctx, inst: [nicp[constants.NIC_MODE]
1491
                        for nicp in ctx.inst_nicparams]),
1492
    (_MakeField("nic.links", "NIC_links", QFT_OTHER,
1493
                "List containing each network interface's link"), IQ_CONFIG, 0,
1494
     lambda ctx, inst: [nicp[constants.NIC_LINK]
1495
                        for nicp in ctx.inst_nicparams]),
1496
    (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
1497
                "List containing each network interface's bridge"),
1498
     IQ_CONFIG, 0, _GetInstAllNicBridges),
1499
    ]
1500

    
1501
  # NICs by number
1502
  for i in range(constants.MAX_NICS):
1503
    numtext = utils.FormatOrdinal(i + 1)
1504
    fields.extend([
1505
      (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT,
1506
                  "IP address of %s network interface" % numtext),
1507
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)),
1508
      (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
1509
                  "MAC address of %s network interface" % numtext),
1510
       IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
1511
      (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
1512
                  "Mode of %s network interface" % numtext),
1513
       IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
1514
      (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT,
1515
                  "Link of %s network interface" % numtext),
1516
       IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)),
1517
      (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
1518
                  "Bridge of %s network interface" % numtext),
1519
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
1520
      ])
1521

    
1522
  aliases = [
1523
    # Legacy fields for first NIC
1524
    ("ip", "nic.ip/0"),
1525
    ("mac", "nic.mac/0"),
1526
    ("bridge", "nic.bridge/0"),
1527
    ("nic_mode", "nic.mode/0"),
1528
    ("nic_link", "nic.link/0"),
1529
    ]
1530

    
1531
  return (fields, aliases)
1532

    
1533

    
1534
def _GetInstDiskUsage(ctx, inst):
1535
  """Get disk usage for an instance.
1536

1537
  @type ctx: L{InstanceQueryData}
1538
  @type inst: L{objects.Instance}
1539
  @param inst: Instance object
1540

1541
  """
1542
  usage = ctx.disk_usage[inst.name]
1543

    
1544
  if usage is None:
1545
    usage = 0
1546

    
1547
  return usage
1548

    
1549

    
1550
def _GetInstanceConsole(ctx, inst):
1551
  """Get console information for instance.
1552

1553
  @type ctx: L{InstanceQueryData}
1554
  @type inst: L{objects.Instance}
1555
  @param inst: Instance object
1556

1557
  """
1558
  consinfo = ctx.console[inst.name]
1559

    
1560
  if consinfo is None:
1561
    return _FS_UNAVAIL
1562

    
1563
  return consinfo
1564

    
1565

    
1566
def _GetInstanceDiskFields():
1567
  """Get instance fields involving disks.
1568

1569
  @return: List of field definitions used as input for L{_PrepareFieldList}
1570

1571
  """
1572
  fields = [
1573
    (_MakeField("disk_usage", "DiskUsage", QFT_UNIT,
1574
                "Total disk space used by instance on each of its nodes;"
1575
                " this is not the disk size visible to the instance, but"
1576
                " the usage on the node"),
1577
     IQ_DISKUSAGE, 0, _GetInstDiskUsage),
1578
    (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"),
1579
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
1580
    (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
1581
     IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
1582
    ]
1583

    
1584
  # Disks by number
1585
  fields.extend([
1586
    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1587
                "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
1588
     IQ_CONFIG, 0, _GetInstDiskSize(i))
1589
    for i in range(constants.MAX_DISKS)
1590
    ])
1591

    
1592
  return fields
1593

    
1594

    
1595
def _GetInstanceParameterFields():
1596
  """Get instance fields involving parameters.
1597

1598
  @return: List of field definitions used as input for L{_PrepareFieldList}
1599

1600
  """
1601
  # TODO: Consider moving titles closer to constants
1602
  be_title = {
1603
    constants.BE_AUTO_BALANCE: "Auto_balance",
1604
    constants.BE_MEMORY: "ConfigMemory",
1605
    constants.BE_VCPUS: "ConfigVCPUs",
1606
    }
1607

    
1608
  hv_title = {
1609
    constants.HV_ACPI: "ACPI",
1610
    constants.HV_BOOT_ORDER: "Boot_order",
1611
    constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1612
    constants.HV_DISK_TYPE: "Disk_type",
1613
    constants.HV_INITRD_PATH: "Initrd_path",
1614
    constants.HV_KERNEL_PATH: "Kernel_path",
1615
    constants.HV_NIC_TYPE: "NIC_type",
1616
    constants.HV_PAE: "PAE",
1617
    constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1618
    }
1619

    
1620
  fields = [
1621
    # Filled parameters
1622
    (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
1623
                "Hypervisor parameters"),
1624
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
1625
    (_MakeField("beparams", "BackendParameters", QFT_OTHER,
1626
                "Backend parameters"),
1627
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
1628

    
1629
    # Unfilled parameters
1630
    (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
1631
                "Custom hypervisor parameters"),
1632
     IQ_CONFIG, 0, _GetItemAttr("hvparams")),
1633
    (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
1634
                "Custom backend parameters",),
1635
     IQ_CONFIG, 0, _GetItemAttr("beparams")),
1636
    (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
1637
                "Custom network interface parameters"),
1638
     IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1639
    ]
1640

    
1641
  # HV params
1642
  def _GetInstHvParam(name):
1643
    return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1644

    
1645
  fields.extend([
1646
    (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1647
                _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1648
     IQ_CONFIG, 0, _GetInstHvParam(name))
1649
    for name, kind in constants.HVS_PARAMETER_TYPES.items()
1650
    if name not in constants.HVC_GLOBALS
1651
    ])
1652

    
1653
  # BE params
1654
  def _GetInstBeParam(name):
1655
    return lambda ctx, _: ctx.inst_beparams.get(name, None)
1656

    
1657
  fields.extend([
1658
    (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1659
                _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1660
     IQ_CONFIG, 0, _GetInstBeParam(name))
1661
    for name, kind in constants.BES_PARAMETER_TYPES.items()
1662
    ])
1663

    
1664
  return fields
1665

    
1666

    
1667
_INST_SIMPLE_FIELDS = {
1668
  "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"),
1669
  "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"),
1670
  "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"),
1671
  # Depending on the hypervisor, the port can be None
1672
  "network_port": ("Network_port", QFT_OTHER, 0,
1673
                   "Instance network port if available (e.g. for VNC console)"),
1674
  "os": ("OS", QFT_TEXT, 0, "Operating system"),
1675
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"),
1676
  "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"),
1677
  }
1678

    
1679

    
1680
def _BuildInstanceFields():
1681
  """Builds list of fields for instance queries.
1682

1683
  """
1684
  fields = [
1685
    (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
1686
     IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")),
1687
    # TODO: Allow filtering by secondary node as hostname
1688
    (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
1689
                "Secondary nodes; usually this will just be one node"),
1690
     IQ_CONFIG, 0, lambda ctx, inst: list(inst.secondary_nodes)),
1691
    (_MakeField("admin_state", "Autostart", QFT_BOOL,
1692
                "Desired state of instance (if set, the instance should be"
1693
                " up)"),
1694
     IQ_CONFIG, 0, _GetItemAttr("admin_up")),
1695
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
1696
     lambda ctx, inst: list(inst.GetTags())),
1697
    (_MakeField("console", "Console", QFT_OTHER,
1698
                "Instance console information"), IQ_CONSOLE, 0,
1699
     _GetInstanceConsole),
1700
    ]
1701

    
1702
  # Add simple fields
1703
  fields.extend([
1704
    (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1705
    for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()
1706
    ])
1707

    
1708
  # Fields requiring talking to the node
1709
  fields.extend([
1710
    (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
1711
     IQ_LIVE, 0, _GetInstOperState),
1712
    (_MakeField("oper_ram", "Memory", QFT_UNIT,
1713
                "Actual memory usage as seen by hypervisor"),
1714
     IQ_LIVE, 0, _GetInstLiveData("memory")),
1715
    (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
1716
                "Actual number of VCPUs as seen by hypervisor"),
1717
     IQ_LIVE, 0, _GetInstLiveData("vcpus")),
1718
    ])
1719

    
1720
  # Status field
1721
  status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
1722
                   constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
1723
                   constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
1724
                   constants.INSTST_NODEOFFLINE)
1725
  status_doc = ("Instance status; \"%s\" if instance is set to be running"
1726
                " and actually is, \"%s\" if instance is stopped and"
1727
                " is not running, \"%s\" if instance running, but not on its"
1728
                " designated primary node, \"%s\" if instance should be"
1729
                " stopped, but is actually running, \"%s\" if instance should"
1730
                " run, but doesn't, \"%s\" if instance's primary node is down,"
1731
                " \"%s\" if instance's primary node is marked offline" %
1732
                status_values)
1733
  fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
1734
                 IQ_LIVE, 0, _GetInstStatus))
1735
  assert set(status_values) == constants.INSTST_ALL, \
1736
         "Status documentation mismatch"
1737

    
1738
  (network_fields, network_aliases) = _GetInstanceNetworkFields()
1739

    
1740
  fields.extend(network_fields)
1741
  fields.extend(_GetInstanceParameterFields())
1742
  fields.extend(_GetInstanceDiskFields())
1743
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1744

    
1745
  aliases = [
1746
    ("vcpus", "be/vcpus"),
1747
    ("sda_size", "disk.size/0"),
1748
    ("sdb_size", "disk.size/1"),
1749
    ] + network_aliases
1750

    
1751
  return _PrepareFieldList(fields, aliases)
1752

    
1753

    
1754
class LockQueryData:
1755
  """Data container for lock data queries.
1756

1757
  """
1758
  def __init__(self, lockdata):
1759
    """Initializes this class.
1760

1761
    """
1762
    self.lockdata = lockdata
1763

    
1764
  def __iter__(self):
1765
    """Iterate over all locks.
1766

1767
    """
1768
    return iter(self.lockdata)
1769

    
1770

    
1771
def _GetLockOwners(_, data):
1772
  """Returns a sorted list of a lock's current owners.
1773

1774
  """
1775
  (_, _, owners, _) = data
1776

    
1777
  if owners:
1778
    owners = utils.NiceSort(owners)
1779

    
1780
  return owners
1781

    
1782

    
1783
def _GetLockPending(_, data):
1784
  """Returns a sorted list of a lock's pending acquires.
1785

1786
  """
1787
  (_, _, _, pending) = data
1788

    
1789
  if pending:
1790
    pending = [(mode, utils.NiceSort(names))
1791
               for (mode, names) in pending]
1792

    
1793
  return pending
1794

    
1795

    
1796
def _BuildLockFields():
1797
  """Builds list of fields for lock queries.
1798

1799
  """
1800
  return _PrepareFieldList([
1801
    # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
1802
    (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
1803
     lambda ctx, (name, mode, owners, pending): name),
1804
    (_MakeField("mode", "Mode", QFT_OTHER,
1805
                "Mode in which the lock is currently acquired"
1806
                " (exclusive or shared)"),
1807
     LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
1808
    (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
1809
     LQ_OWNER, 0, _GetLockOwners),
1810
    (_MakeField("pending", "Pending", QFT_OTHER,
1811
                "Threads waiting for the lock"),
1812
     LQ_PENDING, 0, _GetLockPending),
1813
    ], [])
1814

    
1815

    
1816
class GroupQueryData:
1817
  """Data container for node group data queries.
1818

1819
  """
1820
  def __init__(self, groups, group_to_nodes, group_to_instances):
1821
    """Initializes this class.
1822

1823
    @param groups: List of node group objects
1824
    @type group_to_nodes: dict; group UUID as key
1825
    @param group_to_nodes: Per-group list of nodes
1826
    @type group_to_instances: dict; group UUID as key
1827
    @param group_to_instances: Per-group list of (primary) instances
1828

1829
    """
1830
    self.groups = groups
1831
    self.group_to_nodes = group_to_nodes
1832
    self.group_to_instances = group_to_instances
1833

    
1834
  def __iter__(self):
1835
    """Iterate over all node groups.
1836

1837
    """
1838
    return iter(self.groups)
1839

    
1840

    
1841
_GROUP_SIMPLE_FIELDS = {
1842
  "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
1843
  "name": ("Group", QFT_TEXT, "Group name"),
1844
  "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
1845
  "uuid": ("UUID", QFT_TEXT, "Group UUID"),
1846
  "ndparams": ("NDParams", QFT_OTHER, "Node parameters"),
1847
  }
1848

    
1849

    
1850
def _BuildGroupFields():
1851
  """Builds list of fields for node group queries.
1852

1853
  """
1854
  # Add simple fields
1855
  fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
1856
             _GetItemAttr(name))
1857
            for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
1858

    
1859
  def _GetLength(getter):
1860
    return lambda ctx, group: len(getter(ctx)[group.uuid])
1861

    
1862
  def _GetSortedList(getter):
1863
    return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
1864

    
1865
  group_to_nodes = operator.attrgetter("group_to_nodes")
1866
  group_to_instances = operator.attrgetter("group_to_instances")
1867

    
1868
  # Add fields for nodes
1869
  fields.extend([
1870
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
1871
     GQ_NODE, 0, _GetLength(group_to_nodes)),
1872
    (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
1873
     GQ_NODE, 0, _GetSortedList(group_to_nodes)),
1874
    ])
1875

    
1876
  # Add fields for instances
1877
  fields.extend([
1878
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
1879
                "Number of primary instances"),
1880
     GQ_INST, 0, _GetLength(group_to_instances)),
1881
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
1882
                "List of primary instances"),
1883
     GQ_INST, 0, _GetSortedList(group_to_instances)),
1884
    ])
1885

    
1886
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1887

    
1888
  return _PrepareFieldList(fields, [])
1889

    
1890

    
1891
class OsInfo(objects.ConfigObject):
1892
  __slots__ = [
1893
    "name",
1894
    "valid",
1895
    "hidden",
1896
    "blacklisted",
1897
    "variants",
1898
    "api_versions",
1899
    "parameters",
1900
    "node_status",
1901
    ]
1902

    
1903

    
1904
def _BuildOsFields():
1905
  """Builds list of fields for operating system queries.
1906

1907
  """
1908
  fields = [
1909
    (_MakeField("name", "Name", QFT_TEXT, "Operating system name"),
1910
     None, 0, _GetItemAttr("name")),
1911
    (_MakeField("valid", "Valid", QFT_BOOL,
1912
                "Whether operating system definition is valid"),
1913
     None, 0, _GetItemAttr("valid")),
1914
    (_MakeField("hidden", "Hidden", QFT_BOOL,
1915
                "Whether operating system is hidden"),
1916
     None, 0, _GetItemAttr("hidden")),
1917
    (_MakeField("blacklisted", "Blacklisted", QFT_BOOL,
1918
                "Whether operating system is blacklisted"),
1919
     None, 0, _GetItemAttr("blacklisted")),
1920
    (_MakeField("variants", "Variants", QFT_OTHER,
1921
                "Operating system variants"),
1922
     None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("variants"))),
1923
    (_MakeField("api_versions", "ApiVersions", QFT_OTHER,
1924
                "Operating system API versions"),
1925
     None, 0, _ConvWrap(sorted, _GetItemAttr("api_versions"))),
1926
    (_MakeField("parameters", "Parameters", QFT_OTHER,
1927
                "Operating system parameters"),
1928
     None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("parameters"))),
1929
    (_MakeField("node_status", "NodeStatus", QFT_OTHER,
1930
                "Status from node"),
1931
     None, 0, _GetItemAttr("node_status")),
1932
    ]
1933

    
1934
  return _PrepareFieldList(fields, [])
1935

    
1936

    
1937
#: Fields available for node queries
1938
NODE_FIELDS = _BuildNodeFields()
1939

    
1940
#: Fields available for instance queries
1941
INSTANCE_FIELDS = _BuildInstanceFields()
1942

    
1943
#: Fields available for lock queries
1944
LOCK_FIELDS = _BuildLockFields()
1945

    
1946
#: Fields available for node group queries
1947
GROUP_FIELDS = _BuildGroupFields()
1948

    
1949
#: Fields available for operating system queries
1950
OS_FIELDS = _BuildOsFields()
1951

    
1952
#: All available resources
1953
ALL_FIELDS = {
1954
  constants.QR_INSTANCE: INSTANCE_FIELDS,
1955
  constants.QR_NODE: NODE_FIELDS,
1956
  constants.QR_LOCK: LOCK_FIELDS,
1957
  constants.QR_GROUP: GROUP_FIELDS,
1958
  constants.QR_OS: OS_FIELDS,
1959
  }
1960

    
1961
#: All available field lists
1962
ALL_FIELD_LISTS = ALL_FIELDS.values()