Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ 6ae1fade

History | View | Annotate | Download (58.1 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.TNumber,
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
def _PrepareRegex(pattern):
326
  """Compiles a regular expression.
327

328
  """
329
  try:
330
    return re.compile(pattern)
331
  except re.error, err:
332
    raise errors.ParameterError("Invalid regex pattern (%s)" % err)
333

    
334

    
335
class _FilterCompilerHelper:
336
  """Converts a query filter to a callable usable for filtering.
337

338
  """
339
  # String statement has no effect, pylint: disable-msg=W0105
340

    
341
  #: How deep filters can be nested
342
  _LEVELS_MAX = 10
343

    
344
  # Unique identifiers for operator groups
345
  (_OPTYPE_LOGIC,
346
   _OPTYPE_UNARY,
347
   _OPTYPE_BINARY) = range(1, 4)
348

    
349
  """Functions for equality checks depending on field flags.
350

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

355
  Order matters. The first item with flags will be used. Flags are checked
356
  using binary AND.
357

358
  """
359
  _EQUALITY_CHECKS = [
360
    (QFF_HOSTNAME,
361
     lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
362
                                               case_sensitive=False),
363
     None),
364
    (None, operator.eq, None),
365
    ]
366

    
367
  """Known operators
368

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

372
    - C{_OPTYPE_LOGIC}: Callable taking any number of arguments; used by
373
      L{_HandleLogicOp}
374
    - C{_OPTYPE_UNARY}: Always C{None}; details handled by L{_HandleUnaryOp}
375
    - C{_OPTYPE_BINARY}: Callable taking exactly two parameters, the left- and
376
      right-hand side of the operator, used by L{_HandleBinaryOp}
377

378
  """
379
  _OPS = {
380
    # Logic operators
381
    qlang.OP_OR: (_OPTYPE_LOGIC, compat.any),
382
    qlang.OP_AND: (_OPTYPE_LOGIC, compat.all),
383

    
384
    # Unary operators
385
    qlang.OP_NOT: (_OPTYPE_UNARY, None),
386
    qlang.OP_TRUE: (_OPTYPE_UNARY, None),
387

    
388
    # Binary operators
389
    qlang.OP_EQUAL: (_OPTYPE_BINARY, _EQUALITY_CHECKS),
390
    qlang.OP_NOT_EQUAL:
391
      (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn), valprepfn)
392
                        for (flags, fn, valprepfn) in _EQUALITY_CHECKS]),
393
    qlang.OP_REGEXP: (_OPTYPE_BINARY, [
394
      (None, lambda lhs, rhs: rhs.search(lhs), _PrepareRegex),
395
      ]),
396
    qlang.OP_CONTAINS: (_OPTYPE_BINARY, [
397
      (None, operator.contains, None),
398
      ]),
399
    }
400

    
401
  def __init__(self, fields):
402
    """Initializes this class.
403

404
    @param fields: Field definitions (return value of L{_PrepareFieldList})
405

406
    """
407
    self._fields = fields
408
    self._hints = None
409
    self._op_handler = None
410

    
411
  def __call__(self, hints, filter_):
412
    """Converts a query filter into a callable function.
413

414
    @type hints: L{_FilterHints} or None
415
    @param hints: Callbacks doing analysis on filter
416
    @type filter_: list
417
    @param filter_: Filter structure
418
    @rtype: callable
419
    @return: Function receiving context and item as parameters, returning
420
             boolean as to whether item matches filter
421

422
    """
423
    self._op_handler = {
424
      self._OPTYPE_LOGIC:
425
        (self._HandleLogicOp, getattr(hints, "NoteLogicOp", None)),
426
      self._OPTYPE_UNARY:
427
        (self._HandleUnaryOp, getattr(hints, "NoteUnaryOp", None)),
428
      self._OPTYPE_BINARY:
429
        (self._HandleBinaryOp, getattr(hints, "NoteBinaryOp", None)),
430
      }
431

    
432
    try:
433
      filter_fn = self._Compile(filter_, 0)
434
    finally:
435
      self._op_handler = None
436

    
437
    return filter_fn
438

    
439
  def _Compile(self, filter_, level):
440
    """Inner function for converting filters.
441

442
    Calls the correct handler functions for the top-level operator. This
443
    function is called recursively (e.g. for logic operators).
444

445
    """
446
    if not (isinstance(filter_, (list, tuple)) and filter_):
447
      raise errors.ParameterError("Invalid filter on level %s" % level)
448

    
449
    # Limit recursion
450
    if level >= self._LEVELS_MAX:
451
      raise errors.ParameterError("Only up to %s levels are allowed (filter"
452
                                  " nested too deep)" % self._LEVELS_MAX)
453

    
454
    # Create copy to be modified
455
    operands = filter_[:]
456
    op = operands.pop(0)
457

    
458
    try:
459
      (kind, op_data) = self._OPS[op]
460
    except KeyError:
461
      raise errors.ParameterError("Unknown operator '%s'" % op)
462

    
463
    (handler, hints_cb) = self._op_handler[kind]
464

    
465
    return handler(hints_cb, level, op, op_data, operands)
466

    
467
  def _LookupField(self, name):
468
    """Returns a field definition by name.
469

470
    """
471
    try:
472
      return self._fields[name]
473
    except KeyError:
474
      raise errors.ParameterError("Unknown field '%s'" % name)
475

    
476
  def _HandleLogicOp(self, hints_fn, level, op, op_fn, operands):
477
    """Handles logic operators.
478

479
    @type hints_fn: callable
480
    @param hints_fn: Callback doing some analysis on the filter
481
    @type level: integer
482
    @param level: Current depth
483
    @type op: string
484
    @param op: Operator
485
    @type op_fn: callable
486
    @param op_fn: Function implementing operator
487
    @type operands: list
488
    @param operands: List of operands
489

490
    """
491
    if hints_fn:
492
      hints_fn(op)
493

    
494
    return compat.partial(_WrapLogicOp, op_fn,
495
                          [self._Compile(op, level + 1) for op in operands])
496

    
497
  def _HandleUnaryOp(self, hints_fn, level, op, op_fn, operands):
498
    """Handles unary operators.
499

500
    @type hints_fn: callable
501
    @param hints_fn: Callback doing some analysis on the filter
502
    @type level: integer
503
    @param level: Current depth
504
    @type op: string
505
    @param op: Operator
506
    @type op_fn: callable
507
    @param op_fn: Function implementing operator
508
    @type operands: list
509
    @param operands: List of operands
510

511
    """
512
    assert op_fn is None
513

    
514
    if hints_fn:
515
      hints_fn(op)
516

    
517
    if len(operands) != 1:
518
      raise errors.ParameterError("Unary operator '%s' expects exactly one"
519
                                  " operand" % op)
520

    
521
    if op == qlang.OP_TRUE:
522
      (_, _, _, retrieval_fn) = self._LookupField(operands[0])
523

    
524
      op_fn = operator.truth
525
      arg = retrieval_fn
526
    elif op == qlang.OP_NOT:
527
      op_fn = operator.not_
528
      arg = self._Compile(operands[0], level + 1)
529
    else:
530
      raise errors.ProgrammerError("Can't handle operator '%s'" % op)
531

    
532
    return compat.partial(_WrapUnaryOp, op_fn, arg)
533

    
534
  def _HandleBinaryOp(self, hints_fn, level, op, op_data, operands):
535
    """Handles binary operators.
536

537
    @type hints_fn: callable
538
    @param hints_fn: Callback doing some analysis on the filter
539
    @type level: integer
540
    @param level: Current depth
541
    @type op: string
542
    @param op: Operator
543
    @param op_data: Functions implementing operators
544
    @type operands: list
545
    @param operands: List of operands
546

547
    """
548
    # Unused arguments, pylint: disable-msg=W0613
549
    try:
550
      (name, value) = operands
551
    except (ValueError, TypeError):
552
      raise errors.ParameterError("Invalid binary operator, expected exactly"
553
                                  " two operands")
554

    
555
    (fdef, datakind, field_flags, retrieval_fn) = self._LookupField(name)
556

    
557
    assert fdef.kind != QFT_UNKNOWN
558

    
559
    # TODO: Type conversions?
560

    
561
    verify_fn = _VERIFY_FN[fdef.kind]
562
    if not verify_fn(value):
563
      raise errors.ParameterError("Unable to compare field '%s' (type '%s')"
564
                                  " with '%s', expected %s" %
565
                                  (name, fdef.kind, value.__class__.__name__,
566
                                   verify_fn))
567

    
568
    if hints_fn:
569
      hints_fn(op, datakind, name, value)
570

    
571
    for (fn_flags, fn, valprepfn) in op_data:
572
      if fn_flags is None or fn_flags & field_flags:
573
        # Prepare value if necessary (e.g. compile regular expression)
574
        if valprepfn:
575
          value = valprepfn(value)
576

    
577
        return compat.partial(_WrapBinaryOp, fn, retrieval_fn, value)
578

    
579
    raise errors.ProgrammerError("Unable to find operator implementation"
580
                                 " (op '%s', flags %s)" % (op, field_flags))
581

    
582

    
583
def _CompileFilter(fields, hints, filter_):
584
  """Converts a query filter into a callable function.
585

586
  See L{_FilterCompilerHelper} for details.
587

588
  @rtype: callable
589

590
  """
591
  return _FilterCompilerHelper(fields)(hints, filter_)
592

    
593

    
594
class Query:
595
  def __init__(self, fieldlist, selected, filter_=None, namefield=None):
596
    """Initializes this class.
597

598
    The field definition is a dictionary with the field's name as a key and a
599
    tuple containing, in order, the field definition object
600
    (L{objects.QueryFieldDefinition}, the data kind to help calling code
601
    collect data and a retrieval function. The retrieval function is called
602
    with two parameters, in order, the data container and the item in container
603
    (see L{Query.Query}).
604

605
    Users of this class can call L{RequestedData} before preparing the data
606
    container to determine what data is needed.
607

608
    @type fieldlist: dictionary
609
    @param fieldlist: Field definitions
610
    @type selected: list of strings
611
    @param selected: List of selected fields
612

613
    """
614
    assert namefield is None or namefield in fieldlist
615

    
616
    self._fields = _GetQueryFields(fieldlist, selected)
617

    
618
    self._filter_fn = None
619
    self._requested_names = None
620
    self._filter_datakinds = frozenset()
621

    
622
    if filter_ is not None:
623
      # Collect requested names if wanted
624
      if namefield:
625
        hints = _FilterHints(namefield)
626
      else:
627
        hints = None
628

    
629
      # Build filter function
630
      self._filter_fn = _CompileFilter(fieldlist, hints, filter_)
631
      if hints:
632
        self._requested_names = hints.RequestedNames()
633
        self._filter_datakinds = hints.ReferencedData()
634

    
635
    if namefield is None:
636
      self._name_fn = None
637
    else:
638
      (_, _, _, self._name_fn) = fieldlist[namefield]
639

    
640
  def RequestedNames(self):
641
    """Returns all names referenced in the filter.
642

643
    If there is no filter or operators are preventing determining the exact
644
    names, C{None} is returned.
645

646
    """
647
    return self._requested_names
648

    
649
  def RequestedData(self):
650
    """Gets requested kinds of data.
651

652
    @rtype: frozenset
653

654
    """
655
    return (self._filter_datakinds |
656
            frozenset(datakind for (_, datakind, _, _) in self._fields
657
                      if datakind is not None))
658

    
659
  def GetFields(self):
660
    """Returns the list of fields for this query.
661

662
    Includes unknown fields.
663

664
    @rtype: List of L{objects.QueryFieldDefinition}
665

666
    """
667
    return GetAllFields(self._fields)
668

    
669
  def Query(self, ctx, sort_by_name=True):
670
    """Execute a query.
671

672
    @param ctx: Data container passed to field retrieval functions, must
673
      support iteration using C{__iter__}
674
    @type sort_by_name: boolean
675
    @param sort_by_name: Whether to sort by name or keep the input data's
676
      ordering
677

678
    """
679
    sort = (self._name_fn and sort_by_name)
680

    
681
    result = []
682

    
683
    for idx, item in enumerate(ctx):
684
      if not (self._filter_fn is None or self._filter_fn(ctx, item)):
685
        continue
686

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

    
689
      # Verify result
690
      if __debug__:
691
        _VerifyResultRow(self._fields, row)
692

    
693
      if sort:
694
        (status, name) = _ProcessResult(self._name_fn(ctx, item))
695
        assert status == constants.RS_NORMAL
696
        # TODO: Are there cases where we wouldn't want to use NiceSort?
697
        result.append((utils.NiceSortKey(name), idx, row))
698
      else:
699
        result.append(row)
700

    
701
    if not sort:
702
      return result
703

    
704
    # TODO: Would "heapq" be more efficient than sorting?
705

    
706
    # Sorting in-place instead of using "sorted()"
707
    result.sort()
708

    
709
    assert not result or (len(result[0]) == 3 and len(result[-1]) == 3)
710

    
711
    return map(operator.itemgetter(2), result)
712

    
713
  def OldStyleQuery(self, ctx, sort_by_name=True):
714
    """Query with "old" query result format.
715

716
    See L{Query.Query} for arguments.
717

718
    """
719
    unknown = set(fdef.name for (fdef, _, _, _) in self._fields
720
                  if fdef.kind == QFT_UNKNOWN)
721
    if unknown:
722
      raise errors.OpPrereqError("Unknown output fields selected: %s" %
723
                                 (utils.CommaJoin(unknown), ),
724
                                 errors.ECODE_INVAL)
725

    
726
    return [[value for (_, value) in row]
727
            for row in self.Query(ctx, sort_by_name=sort_by_name)]
728

    
729

    
730
def _ProcessResult(value):
731
  """Converts result values into externally-visible ones.
732

733
  """
734
  if value is _FS_UNKNOWN:
735
    return (RS_UNKNOWN, None)
736
  elif value is _FS_NODATA:
737
    return (RS_NODATA, None)
738
  elif value is _FS_UNAVAIL:
739
    return (RS_UNAVAIL, None)
740
  elif value is _FS_OFFLINE:
741
    return (RS_OFFLINE, None)
742
  else:
743
    return (RS_NORMAL, value)
744

    
745

    
746
def _VerifyResultRow(fields, row):
747
  """Verifies the contents of a query result row.
748

749
  @type fields: list
750
  @param fields: Field definitions for result
751
  @type row: list of tuples
752
  @param row: Row data
753

754
  """
755
  assert len(row) == len(fields)
756
  errs = []
757
  for ((status, value), (fdef, _, _, _)) in zip(row, fields):
758
    if status == RS_NORMAL:
759
      if not _VERIFY_FN[fdef.kind](value):
760
        errs.append("normal field %s fails validation (value is %s)" %
761
                    (fdef.name, value))
762
    elif value is not None:
763
      errs.append("abnormal field %s has a non-None value" % fdef.name)
764
  assert not errs, ("Failed validation: %s in row %s" %
765
                    (utils.CommaJoin(errors), row))
766

    
767

    
768
def _PrepareFieldList(fields, aliases):
769
  """Prepares field list for use by L{Query}.
770

771
  Converts the list to a dictionary and does some verification.
772

773
  @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data
774
      kind, retrieval function)
775
  @param fields: List of fields, see L{Query.__init__} for a better
776
      description
777
  @type aliases: list of tuples; (alias, target)
778
  @param aliases: list of tuples containing aliases; for each
779
      alias/target pair, a duplicate will be created in the field list
780
  @rtype: dict
781
  @return: Field dictionary for L{Query}
782

783
  """
784
  if __debug__:
785
    duplicates = utils.FindDuplicates(fdef.title.lower()
786
                                      for (fdef, _, _, _) in fields)
787
    assert not duplicates, "Duplicate title(s) found: %r" % duplicates
788

    
789
  result = {}
790

    
791
  for field in fields:
792
    (fdef, _, flags, fn) = field
793

    
794
    assert fdef.name and fdef.title, "Name and title are required"
795
    assert FIELD_NAME_RE.match(fdef.name)
796
    assert TITLE_RE.match(fdef.title)
797
    assert (DOC_RE.match(fdef.doc) and len(fdef.doc.splitlines()) == 1 and
798
            fdef.doc.strip() == fdef.doc), \
799
           "Invalid description for field '%s'" % fdef.name
800
    assert callable(fn)
801
    assert fdef.name not in result, \
802
           "Duplicate field name '%s' found" % fdef.name
803
    assert (flags & ~QFF_ALL) == 0, "Unknown flags for field '%s'" % fdef.name
804

    
805
    result[fdef.name] = field
806

    
807
  for alias, target in aliases:
808
    assert alias not in result, "Alias %s overrides an existing field" % alias
809
    assert target in result, "Missing target %s for alias %s" % (target, alias)
810
    (fdef, k, flags, fn) = result[target]
811
    fdef = fdef.Copy()
812
    fdef.name = alias
813
    result[alias] = (fdef, k, flags, fn)
814

    
815
  assert len(result) == len(fields) + len(aliases)
816
  assert compat.all(name == fdef.name
817
                    for (name, (fdef, _, _, _)) in result.items())
818

    
819
  return result
820

    
821

    
822
def GetQueryResponse(query, ctx, sort_by_name=True):
823
  """Prepares the response for a query.
824

825
  @type query: L{Query}
826
  @param ctx: Data container, see L{Query.Query}
827
  @type sort_by_name: boolean
828
  @param sort_by_name: Whether to sort by name or keep the input data's
829
    ordering
830

831
  """
832
  return objects.QueryResponse(data=query.Query(ctx, sort_by_name=sort_by_name),
833
                               fields=query.GetFields()).ToDict()
834

    
835

    
836
def QueryFields(fielddefs, selected):
837
  """Returns list of available fields.
838

839
  @type fielddefs: dict
840
  @param fielddefs: Field definitions
841
  @type selected: list of strings
842
  @param selected: List of selected fields
843
  @return: List of L{objects.QueryFieldDefinition}
844

845
  """
846
  if selected is None:
847
    # Client requests all fields, sort by name
848
    fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
849
                           key=operator.attrgetter("name"))
850
  else:
851
    # Keep order as requested by client
852
    fdefs = Query(fielddefs, selected).GetFields()
853

    
854
  return objects.QueryFieldsResponse(fields=fdefs).ToDict()
855

    
856

    
857
def _MakeField(name, title, kind, doc):
858
  """Wrapper for creating L{objects.QueryFieldDefinition} instances.
859

860
  @param name: Field name as a regular expression
861
  @param title: Human-readable title
862
  @param kind: Field type
863
  @param doc: Human-readable description
864

865
  """
866
  return objects.QueryFieldDefinition(name=name, title=title, kind=kind,
867
                                      doc=doc)
868

    
869

    
870
def _GetNodeRole(node, master_name):
871
  """Determine node role.
872

873
  @type node: L{objects.Node}
874
  @param node: Node object
875
  @type master_name: string
876
  @param master_name: Master node name
877

878
  """
879
  if node.name == master_name:
880
    return constants.NR_MASTER
881
  elif node.master_candidate:
882
    return constants.NR_MCANDIDATE
883
  elif node.drained:
884
    return constants.NR_DRAINED
885
  elif node.offline:
886
    return constants.NR_OFFLINE
887
  else:
888
    return constants.NR_REGULAR
889

    
890

    
891
def _GetItemAttr(attr):
892
  """Returns a field function to return an attribute of the item.
893

894
  @param attr: Attribute name
895

896
  """
897
  getter = operator.attrgetter(attr)
898
  return lambda _, item: getter(item)
899

    
900

    
901
def _ConvWrapInner(convert, fn, ctx, item):
902
  """Wrapper for converting values.
903

904
  @param convert: Conversion function receiving value as single parameter
905
  @param fn: Retrieval function
906

907
  """
908
  value = fn(ctx, item)
909

    
910
  # Is the value an abnormal status?
911
  if compat.any(value is fs for fs in _FS_ALL):
912
    # Return right away
913
    return value
914

    
915
  # TODO: Should conversion function also receive context, item or both?
916
  return convert(value)
917

    
918

    
919
def _ConvWrap(convert, fn):
920
  """Convenience wrapper for L{_ConvWrapInner}.
921

922
  @param convert: Conversion function receiving value as single parameter
923
  @param fn: Retrieval function
924

925
  """
926
  return compat.partial(_ConvWrapInner, convert, fn)
927

    
928

    
929
def _GetItemTimestamp(getter):
930
  """Returns function for getting timestamp of item.
931

932
  @type getter: callable
933
  @param getter: Function to retrieve timestamp attribute
934

935
  """
936
  def fn(_, item):
937
    """Returns a timestamp of item.
938

939
    """
940
    timestamp = getter(item)
941
    if timestamp is None:
942
      # Old configs might not have all timestamps
943
      return _FS_UNAVAIL
944
    else:
945
      return timestamp
946

    
947
  return fn
948

    
949

    
950
def _GetItemTimestampFields(datatype):
951
  """Returns common timestamp fields.
952

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

955
  """
956
  return [
957
    (_MakeField("ctime", "CTime", QFT_TIMESTAMP, "Creation timestamp"),
958
     datatype, 0, _GetItemTimestamp(operator.attrgetter("ctime"))),
959
    (_MakeField("mtime", "MTime", QFT_TIMESTAMP, "Modification timestamp"),
960
     datatype, 0, _GetItemTimestamp(operator.attrgetter("mtime"))),
961
    ]
962

    
963

    
964
class NodeQueryData:
965
  """Data container for node data queries.
966

967
  """
968
  def __init__(self, nodes, live_data, master_name, node_to_primary,
969
               node_to_secondary, groups, oob_support, cluster):
970
    """Initializes this class.
971

972
    """
973
    self.nodes = nodes
974
    self.live_data = live_data
975
    self.master_name = master_name
976
    self.node_to_primary = node_to_primary
977
    self.node_to_secondary = node_to_secondary
978
    self.groups = groups
979
    self.oob_support = oob_support
980
    self.cluster = cluster
981

    
982
    # Used for individual rows
983
    self.curlive_data = None
984

    
985
  def __iter__(self):
986
    """Iterate over all nodes.
987

988
    This function has side-effects and only one instance of the resulting
989
    generator should be used at a time.
990

991
    """
992
    for node in self.nodes:
993
      if self.live_data:
994
        self.curlive_data = self.live_data.get(node.name, None)
995
      else:
996
        self.curlive_data = None
997
      yield node
998

    
999

    
1000
#: Fields that are direct attributes of an L{objects.Node} object
1001
_NODE_SIMPLE_FIELDS = {
1002
  "drained": ("Drained", QFT_BOOL, 0, "Whether node is drained"),
1003
  "master_candidate": ("MasterC", QFT_BOOL, 0,
1004
                       "Whether node is a master candidate"),
1005
  "master_capable": ("MasterCapable", QFT_BOOL, 0,
1006
                     "Whether node can become a master candidate"),
1007
  "name": ("Node", QFT_TEXT, QFF_HOSTNAME, "Node name"),
1008
  "offline": ("Offline", QFT_BOOL, 0, "Whether node is marked offline"),
1009
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Node"),
1010
  "uuid": ("UUID", QFT_TEXT, 0, "Node UUID"),
1011
  "vm_capable": ("VMCapable", QFT_BOOL, 0, "Whether node can host instances"),
1012
  }
1013

    
1014

    
1015
#: Fields requiring talking to the node
1016
# Note that none of these are available for non-vm_capable nodes
1017
_NODE_LIVE_FIELDS = {
1018
  "bootid": ("BootID", QFT_TEXT, "bootid",
1019
             "Random UUID renewed for each system reboot, can be used"
1020
             " for detecting reboots by tracking changes"),
1021
  "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes",
1022
             "Number of NUMA domains on node (if exported by hypervisor)"),
1023
  "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets",
1024
               "Number of physical CPU sockets (if exported by hypervisor)"),
1025
  "ctotal": ("CTotal", QFT_NUMBER, "cpu_total", "Number of logical processors"),
1026
  "dfree": ("DFree", QFT_UNIT, "vg_free",
1027
            "Available disk space in volume group"),
1028
  "dtotal": ("DTotal", QFT_UNIT, "vg_size",
1029
             "Total disk space in volume group used for instance disk"
1030
             " allocation"),
1031
  "mfree": ("MFree", QFT_UNIT, "memory_free",
1032
            "Memory available for instance allocations"),
1033
  "mnode": ("MNode", QFT_UNIT, "memory_dom0",
1034
            "Amount of memory used by node (dom0 for Xen)"),
1035
  "mtotal": ("MTotal", QFT_UNIT, "memory_total",
1036
             "Total amount of memory of physical machine"),
1037
  }
1038

    
1039

    
1040
def _GetGroup(cb):
1041
  """Build function for calling another function with an node group.
1042

1043
  @param cb: The callback to be called with the nodegroup
1044

1045
  """
1046
  def fn(ctx, node):
1047
    """Get group data for a node.
1048

1049
    @type ctx: L{NodeQueryData}
1050
    @type inst: L{objects.Node}
1051
    @param inst: Node object
1052

1053
    """
1054
    ng = ctx.groups.get(node.group, None)
1055
    if ng is None:
1056
      # Nodes always have a group, or the configuration is corrupt
1057
      return _FS_UNAVAIL
1058

    
1059
    return cb(ctx, node, ng)
1060

    
1061
  return fn
1062

    
1063

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

1067
  @type ctx: L{NodeQueryData}
1068
  @type node: L{objects.Node}
1069
  @param node: Node object
1070
  @type ng: L{objects.NodeGroup}
1071
  @param ng: The node group this node belongs to
1072

1073
  """
1074
  return ng.name
1075

    
1076

    
1077
def _GetNodePower(ctx, node):
1078
  """Returns the node powered state
1079

1080
  @type ctx: L{NodeQueryData}
1081
  @type node: L{objects.Node}
1082
  @param node: Node object
1083

1084
  """
1085
  if ctx.oob_support[node.name]:
1086
    return node.powered
1087

    
1088
  return _FS_UNAVAIL
1089

    
1090

    
1091
def _GetNdParams(ctx, node, ng):
1092
  """Returns the ndparams for this node.
1093

1094
  @type ctx: L{NodeQueryData}
1095
  @type node: L{objects.Node}
1096
  @param node: Node object
1097
  @type ng: L{objects.NodeGroup}
1098
  @param ng: The node group this node belongs to
1099

1100
  """
1101
  return ctx.cluster.SimpleFillND(ng.FillND(node))
1102

    
1103

    
1104
def _GetLiveNodeField(field, kind, ctx, node):
1105
  """Gets the value of a "live" field from L{NodeQueryData}.
1106

1107
  @param field: Live field name
1108
  @param kind: Data kind, one of L{constants.QFT_ALL}
1109
  @type ctx: L{NodeQueryData}
1110
  @type node: L{objects.Node}
1111
  @param node: Node object
1112

1113
  """
1114
  if node.offline:
1115
    return _FS_OFFLINE
1116

    
1117
  if not node.vm_capable:
1118
    return _FS_UNAVAIL
1119

    
1120
  if not ctx.curlive_data:
1121
    return _FS_NODATA
1122

    
1123
  try:
1124
    value = ctx.curlive_data[field]
1125
  except KeyError:
1126
    return _FS_UNAVAIL
1127

    
1128
  if kind == QFT_TEXT:
1129
    return value
1130

    
1131
  assert kind in (QFT_NUMBER, QFT_UNIT)
1132

    
1133
  # Try to convert into number
1134
  try:
1135
    return int(value)
1136
  except (ValueError, TypeError):
1137
    logging.exception("Failed to convert node field '%s' (value %r) to int",
1138
                      value, field)
1139
    return _FS_UNAVAIL
1140

    
1141

    
1142
def _BuildNodeFields():
1143
  """Builds list of fields for node queries.
1144

1145
  """
1146
  fields = [
1147
    (_MakeField("pip", "PrimaryIP", QFT_TEXT, "Primary IP address"),
1148
     NQ_CONFIG, 0, _GetItemAttr("primary_ip")),
1149
    (_MakeField("sip", "SecondaryIP", QFT_TEXT, "Secondary IP address"),
1150
     NQ_CONFIG, 0, _GetItemAttr("secondary_ip")),
1151
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0,
1152
     lambda ctx, node: list(node.GetTags())),
1153
    (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"),
1154
     NQ_CONFIG, 0, lambda ctx, node: node.name == ctx.master_name),
1155
    (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0,
1156
     _GetGroup(_GetNodeGroup)),
1157
    (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"),
1158
     NQ_CONFIG, 0, _GetItemAttr("group")),
1159
    (_MakeField("powered", "Powered", QFT_BOOL,
1160
                "Whether node is thought to be powered on"),
1161
     NQ_OOB, 0, _GetNodePower),
1162
    (_MakeField("ndparams", "NodeParameters", QFT_OTHER,
1163
                "Merged node parameters"),
1164
     NQ_GROUP, 0, _GetGroup(_GetNdParams)),
1165
    (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER,
1166
                "Custom node parameters"),
1167
      NQ_GROUP, 0, _GetItemAttr("ndparams")),
1168
    ]
1169

    
1170
  # Node role
1171
  role_values = (constants.NR_MASTER, constants.NR_MCANDIDATE,
1172
                 constants.NR_REGULAR, constants.NR_DRAINED,
1173
                 constants.NR_OFFLINE)
1174
  role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate,"
1175
              " \"%s\" for regular, \"%s\" for a drained, \"%s\" for offline" %
1176
              role_values)
1177
  fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
1178
                 lambda ctx, node: _GetNodeRole(node, ctx.master_name)))
1179
  assert set(role_values) == constants.NR_ALL
1180

    
1181
  def _GetLength(getter):
1182
    return lambda ctx, node: len(getter(ctx)[node.name])
1183

    
1184
  def _GetList(getter):
1185
    return lambda ctx, node: list(getter(ctx)[node.name])
1186

    
1187
  # Add fields operating on instance lists
1188
  for prefix, titleprefix, docword, getter in \
1189
      [("p", "Pri", "primary", operator.attrgetter("node_to_primary")),
1190
       ("s", "Sec", "secondary", operator.attrgetter("node_to_secondary"))]:
1191
    # TODO: Allow filterting by hostname in list
1192
    fields.extend([
1193
      (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER,
1194
                  "Number of instances with this node as %s" % docword),
1195
       NQ_INST, 0, _GetLength(getter)),
1196
      (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
1197
                  QFT_OTHER,
1198
                  "List of instances with this node as %s" % docword),
1199
       NQ_INST, 0, _GetList(getter)),
1200
      ])
1201

    
1202
  # Add simple fields
1203
  fields.extend([
1204
    (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1205
    for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()
1206
    ])
1207

    
1208
  # Add fields requiring live data
1209
  fields.extend([
1210
    (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1211
     compat.partial(_GetLiveNodeField, nfield, kind))
1212
    for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()
1213
    ])
1214

    
1215
  # Add timestamps
1216
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1217

    
1218
  return _PrepareFieldList(fields, [])
1219

    
1220

    
1221
class InstanceQueryData:
1222
  """Data container for instance data queries.
1223

1224
  """
1225
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
1226
               live_data, wrongnode_inst, console):
1227
    """Initializes this class.
1228

1229
    @param instances: List of instance objects
1230
    @param cluster: Cluster object
1231
    @type disk_usage: dict; instance name as key
1232
    @param disk_usage: Per-instance disk usage
1233
    @type offline_nodes: list of strings
1234
    @param offline_nodes: List of offline nodes
1235
    @type bad_nodes: list of strings
1236
    @param bad_nodes: List of faulty nodes
1237
    @type live_data: dict; instance name as key
1238
    @param live_data: Per-instance live data
1239
    @type wrongnode_inst: set
1240
    @param wrongnode_inst: Set of instances running on wrong node(s)
1241
    @type console: dict; instance name as key
1242
    @param console: Per-instance console information
1243

1244
    """
1245
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
1246
           "Offline nodes not included in bad nodes"
1247
    assert not (set(live_data.keys()) & set(bad_nodes)), \
1248
           "Found live data for bad or offline nodes"
1249

    
1250
    self.instances = instances
1251
    self.cluster = cluster
1252
    self.disk_usage = disk_usage
1253
    self.offline_nodes = offline_nodes
1254
    self.bad_nodes = bad_nodes
1255
    self.live_data = live_data
1256
    self.wrongnode_inst = wrongnode_inst
1257
    self.console = console
1258

    
1259
    # Used for individual rows
1260
    self.inst_hvparams = None
1261
    self.inst_beparams = None
1262
    self.inst_osparams = None
1263
    self.inst_nicparams = None
1264

    
1265
  def __iter__(self):
1266
    """Iterate over all instances.
1267

1268
    This function has side-effects and only one instance of the resulting
1269
    generator should be used at a time.
1270

1271
    """
1272
    for inst in self.instances:
1273
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1274
      self.inst_beparams = self.cluster.FillBE(inst)
1275
      self.inst_osparams = self.cluster.SimpleFillOS(inst.os, inst.osparams)
1276
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1277
                             for nic in inst.nics]
1278

    
1279
      yield inst
1280

    
1281

    
1282
def _GetInstOperState(ctx, inst):
1283
  """Get instance's operational status.
1284

1285
  @type ctx: L{InstanceQueryData}
1286
  @type inst: L{objects.Instance}
1287
  @param inst: Instance object
1288

1289
  """
1290
  # Can't use RS_OFFLINE here as it would describe the instance to
1291
  # be offline when we actually don't know due to missing data
1292
  if inst.primary_node in ctx.bad_nodes:
1293
    return _FS_NODATA
1294
  else:
1295
    return bool(ctx.live_data.get(inst.name))
1296

    
1297

    
1298
def _GetInstLiveData(name):
1299
  """Build function for retrieving live data.
1300

1301
  @type name: string
1302
  @param name: Live data field name
1303

1304
  """
1305
  def fn(ctx, inst):
1306
    """Get live data for an instance.
1307

1308
    @type ctx: L{InstanceQueryData}
1309
    @type inst: L{objects.Instance}
1310
    @param inst: Instance object
1311

1312
    """
1313
    if (inst.primary_node in ctx.bad_nodes or
1314
        inst.primary_node in ctx.offline_nodes):
1315
      # Can't use RS_OFFLINE here as it would describe the instance to be
1316
      # offline when we actually don't know due to missing data
1317
      return _FS_NODATA
1318

    
1319
    if inst.name in ctx.live_data:
1320
      data = ctx.live_data[inst.name]
1321
      if name in data:
1322
        return data[name]
1323

    
1324
    return _FS_UNAVAIL
1325

    
1326
  return fn
1327

    
1328

    
1329
def _GetInstStatus(ctx, inst):
1330
  """Get instance status.
1331

1332
  @type ctx: L{InstanceQueryData}
1333
  @type inst: L{objects.Instance}
1334
  @param inst: Instance object
1335

1336
  """
1337
  if inst.primary_node in ctx.offline_nodes:
1338
    return constants.INSTST_NODEOFFLINE
1339

    
1340
  if inst.primary_node in ctx.bad_nodes:
1341
    return constants.INSTST_NODEDOWN
1342

    
1343
  if bool(ctx.live_data.get(inst.name)):
1344
    if inst.name in ctx.wrongnode_inst:
1345
      return constants.INSTST_WRONGNODE
1346
    elif inst.admin_up:
1347
      return constants.INSTST_RUNNING
1348
    else:
1349
      return constants.INSTST_ERRORUP
1350

    
1351
  if inst.admin_up:
1352
    return constants.INSTST_ERRORDOWN
1353

    
1354
  return constants.INSTST_ADMINDOWN
1355

    
1356

    
1357
def _GetInstDiskSize(index):
1358
  """Build function for retrieving disk size.
1359

1360
  @type index: int
1361
  @param index: Disk index
1362

1363
  """
1364
  def fn(_, inst):
1365
    """Get size of a disk.
1366

1367
    @type inst: L{objects.Instance}
1368
    @param inst: Instance object
1369

1370
    """
1371
    try:
1372
      return inst.disks[index].size
1373
    except IndexError:
1374
      return _FS_UNAVAIL
1375

    
1376
  return fn
1377

    
1378

    
1379
def _GetInstNic(index, cb):
1380
  """Build function for calling another function with an instance NIC.
1381

1382
  @type index: int
1383
  @param index: NIC index
1384
  @type cb: callable
1385
  @param cb: Callback
1386

1387
  """
1388
  def fn(ctx, inst):
1389
    """Call helper function with instance NIC.
1390

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

1395
    """
1396
    try:
1397
      nic = inst.nics[index]
1398
    except IndexError:
1399
      return _FS_UNAVAIL
1400

    
1401
    return cb(ctx, index, nic)
1402

    
1403
  return fn
1404

    
1405

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

1409
  @type ctx: L{InstanceQueryData}
1410
  @type nic: L{objects.NIC}
1411
  @param nic: NIC object
1412

1413
  """
1414
  if nic.ip is None:
1415
    return _FS_UNAVAIL
1416
  else:
1417
    return nic.ip
1418

    
1419

    
1420
def _GetInstNicBridge(ctx, index, _):
1421
  """Get a NIC's bridge.
1422

1423
  @type ctx: L{InstanceQueryData}
1424
  @type index: int
1425
  @param index: NIC index
1426

1427
  """
1428
  assert len(ctx.inst_nicparams) >= index
1429

    
1430
  nicparams = ctx.inst_nicparams[index]
1431

    
1432
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1433
    return nicparams[constants.NIC_LINK]
1434
  else:
1435
    return _FS_UNAVAIL
1436

    
1437

    
1438
def _GetInstAllNicBridges(ctx, inst):
1439
  """Get all network bridges for an instance.
1440

1441
  @type ctx: L{InstanceQueryData}
1442
  @type inst: L{objects.Instance}
1443
  @param inst: Instance object
1444

1445
  """
1446
  assert len(ctx.inst_nicparams) == len(inst.nics)
1447

    
1448
  result = []
1449

    
1450
  for nicp in ctx.inst_nicparams:
1451
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1452
      result.append(nicp[constants.NIC_LINK])
1453
    else:
1454
      result.append(None)
1455

    
1456
  assert len(result) == len(inst.nics)
1457

    
1458
  return result
1459

    
1460

    
1461
def _GetInstNicParam(name):
1462
  """Build function for retrieving a NIC parameter.
1463

1464
  @type name: string
1465
  @param name: Parameter name
1466

1467
  """
1468
  def fn(ctx, index, _):
1469
    """Get a NIC's bridge.
1470

1471
    @type ctx: L{InstanceQueryData}
1472
    @type inst: L{objects.Instance}
1473
    @param inst: Instance object
1474
    @type nic: L{objects.NIC}
1475
    @param nic: NIC object
1476

1477
    """
1478
    assert len(ctx.inst_nicparams) >= index
1479
    return ctx.inst_nicparams[index][name]
1480

    
1481
  return fn
1482

    
1483

    
1484
def _GetInstanceNetworkFields():
1485
  """Get instance fields involving network interfaces.
1486

1487
  @return: Tuple containing list of field definitions used as input for
1488
    L{_PrepareFieldList} and a list of aliases
1489

1490
  """
1491
  nic_mac_fn = lambda ctx, _, nic: nic.mac
1492
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1493
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1494

    
1495
  fields = [
1496
    # All NICs
1497
    (_MakeField("nic.count", "NICs", QFT_NUMBER,
1498
                "Number of network interfaces"),
1499
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.nics)),
1500
    (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER,
1501
                "List containing each network interface's MAC address"),
1502
     IQ_CONFIG, 0, lambda ctx, inst: [nic.mac for nic in inst.nics]),
1503
    (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
1504
                "List containing each network interface's IP address"),
1505
     IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
1506
    (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
1507
                "List containing each network interface's mode"), IQ_CONFIG, 0,
1508
     lambda ctx, inst: [nicp[constants.NIC_MODE]
1509
                        for nicp in ctx.inst_nicparams]),
1510
    (_MakeField("nic.links", "NIC_links", QFT_OTHER,
1511
                "List containing each network interface's link"), IQ_CONFIG, 0,
1512
     lambda ctx, inst: [nicp[constants.NIC_LINK]
1513
                        for nicp in ctx.inst_nicparams]),
1514
    (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
1515
                "List containing each network interface's bridge"),
1516
     IQ_CONFIG, 0, _GetInstAllNicBridges),
1517
    ]
1518

    
1519
  # NICs by number
1520
  for i in range(constants.MAX_NICS):
1521
    numtext = utils.FormatOrdinal(i + 1)
1522
    fields.extend([
1523
      (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT,
1524
                  "IP address of %s network interface" % numtext),
1525
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)),
1526
      (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
1527
                  "MAC address of %s network interface" % numtext),
1528
       IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
1529
      (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
1530
                  "Mode of %s network interface" % numtext),
1531
       IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
1532
      (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT,
1533
                  "Link of %s network interface" % numtext),
1534
       IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)),
1535
      (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
1536
                  "Bridge of %s network interface" % numtext),
1537
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
1538
      ])
1539

    
1540
  aliases = [
1541
    # Legacy fields for first NIC
1542
    ("ip", "nic.ip/0"),
1543
    ("mac", "nic.mac/0"),
1544
    ("bridge", "nic.bridge/0"),
1545
    ("nic_mode", "nic.mode/0"),
1546
    ("nic_link", "nic.link/0"),
1547
    ]
1548

    
1549
  return (fields, aliases)
1550

    
1551

    
1552
def _GetInstDiskUsage(ctx, inst):
1553
  """Get disk usage for an instance.
1554

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

1559
  """
1560
  usage = ctx.disk_usage[inst.name]
1561

    
1562
  if usage is None:
1563
    usage = 0
1564

    
1565
  return usage
1566

    
1567

    
1568
def _GetInstanceConsole(ctx, inst):
1569
  """Get console information for instance.
1570

1571
  @type ctx: L{InstanceQueryData}
1572
  @type inst: L{objects.Instance}
1573
  @param inst: Instance object
1574

1575
  """
1576
  consinfo = ctx.console[inst.name]
1577

    
1578
  if consinfo is None:
1579
    return _FS_UNAVAIL
1580

    
1581
  return consinfo
1582

    
1583

    
1584
def _GetInstanceDiskFields():
1585
  """Get instance fields involving disks.
1586

1587
  @return: List of field definitions used as input for L{_PrepareFieldList}
1588

1589
  """
1590
  fields = [
1591
    (_MakeField("disk_usage", "DiskUsage", QFT_UNIT,
1592
                "Total disk space used by instance on each of its nodes;"
1593
                " this is not the disk size visible to the instance, but"
1594
                " the usage on the node"),
1595
     IQ_DISKUSAGE, 0, _GetInstDiskUsage),
1596
    (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"),
1597
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
1598
    (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
1599
     IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
1600
    ]
1601

    
1602
  # Disks by number
1603
  fields.extend([
1604
    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1605
                "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
1606
     IQ_CONFIG, 0, _GetInstDiskSize(i))
1607
    for i in range(constants.MAX_DISKS)
1608
    ])
1609

    
1610
  return fields
1611

    
1612

    
1613
def _GetInstanceParameterFields():
1614
  """Get instance fields involving parameters.
1615

1616
  @return: List of field definitions used as input for L{_PrepareFieldList}
1617

1618
  """
1619
  # TODO: Consider moving titles closer to constants
1620
  be_title = {
1621
    constants.BE_AUTO_BALANCE: "Auto_balance",
1622
    constants.BE_MEMORY: "ConfigMemory",
1623
    constants.BE_VCPUS: "ConfigVCPUs",
1624
    }
1625

    
1626
  hv_title = {
1627
    constants.HV_ACPI: "ACPI",
1628
    constants.HV_BOOT_ORDER: "Boot_order",
1629
    constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1630
    constants.HV_DISK_TYPE: "Disk_type",
1631
    constants.HV_INITRD_PATH: "Initrd_path",
1632
    constants.HV_KERNEL_PATH: "Kernel_path",
1633
    constants.HV_NIC_TYPE: "NIC_type",
1634
    constants.HV_PAE: "PAE",
1635
    constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1636
    }
1637

    
1638
  fields = [
1639
    # Filled parameters
1640
    (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
1641
                "Hypervisor parameters (merged)"),
1642
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
1643
    (_MakeField("beparams", "BackendParameters", QFT_OTHER,
1644
                "Backend parameters (merged)"),
1645
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
1646
    (_MakeField("osparams", "OpSysParameters", QFT_OTHER,
1647
                "Operating system parameters (merged)"),
1648
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_osparams),
1649

    
1650
    # Unfilled parameters
1651
    (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
1652
                "Custom hypervisor parameters"),
1653
     IQ_CONFIG, 0, _GetItemAttr("hvparams")),
1654
    (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
1655
                "Custom backend parameters",),
1656
     IQ_CONFIG, 0, _GetItemAttr("beparams")),
1657
    (_MakeField("custom_osparams", "CustomOpSysParameters", QFT_OTHER,
1658
                "Custom operating system parameters",),
1659
     IQ_CONFIG, 0, _GetItemAttr("osparams")),
1660
    (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
1661
                "Custom network interface parameters"),
1662
     IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1663
    ]
1664

    
1665
  # HV params
1666
  def _GetInstHvParam(name):
1667
    return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1668

    
1669
  fields.extend([
1670
    (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1671
                _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1672
     IQ_CONFIG, 0, _GetInstHvParam(name))
1673
    for name, kind in constants.HVS_PARAMETER_TYPES.items()
1674
    if name not in constants.HVC_GLOBALS
1675
    ])
1676

    
1677
  # BE params
1678
  def _GetInstBeParam(name):
1679
    return lambda ctx, _: ctx.inst_beparams.get(name, None)
1680

    
1681
  fields.extend([
1682
    (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1683
                _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1684
     IQ_CONFIG, 0, _GetInstBeParam(name))
1685
    for name, kind in constants.BES_PARAMETER_TYPES.items()
1686
    ])
1687

    
1688
  return fields
1689

    
1690

    
1691
_INST_SIMPLE_FIELDS = {
1692
  "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"),
1693
  "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"),
1694
  "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"),
1695
  # Depending on the hypervisor, the port can be None
1696
  "network_port": ("Network_port", QFT_OTHER, 0,
1697
                   "Instance network port if available (e.g. for VNC console)"),
1698
  "os": ("OS", QFT_TEXT, 0, "Operating system"),
1699
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"),
1700
  "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"),
1701
  }
1702

    
1703

    
1704
def _BuildInstanceFields():
1705
  """Builds list of fields for instance queries.
1706

1707
  """
1708
  fields = [
1709
    (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
1710
     IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")),
1711
    # TODO: Allow filtering by secondary node as hostname
1712
    (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
1713
                "Secondary nodes; usually this will just be one node"),
1714
     IQ_CONFIG, 0, lambda ctx, inst: list(inst.secondary_nodes)),
1715
    (_MakeField("admin_state", "Autostart", QFT_BOOL,
1716
                "Desired state of instance (if set, the instance should be"
1717
                " up)"),
1718
     IQ_CONFIG, 0, _GetItemAttr("admin_up")),
1719
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
1720
     lambda ctx, inst: list(inst.GetTags())),
1721
    (_MakeField("console", "Console", QFT_OTHER,
1722
                "Instance console information"), IQ_CONSOLE, 0,
1723
     _GetInstanceConsole),
1724
    ]
1725

    
1726
  # Add simple fields
1727
  fields.extend([
1728
    (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1729
    for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()
1730
    ])
1731

    
1732
  # Fields requiring talking to the node
1733
  fields.extend([
1734
    (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
1735
     IQ_LIVE, 0, _GetInstOperState),
1736
    (_MakeField("oper_ram", "Memory", QFT_UNIT,
1737
                "Actual memory usage as seen by hypervisor"),
1738
     IQ_LIVE, 0, _GetInstLiveData("memory")),
1739
    (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
1740
                "Actual number of VCPUs as seen by hypervisor"),
1741
     IQ_LIVE, 0, _GetInstLiveData("vcpus")),
1742
    ])
1743

    
1744
  # Status field
1745
  status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
1746
                   constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
1747
                   constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
1748
                   constants.INSTST_NODEOFFLINE)
1749
  status_doc = ("Instance status; \"%s\" if instance is set to be running"
1750
                " and actually is, \"%s\" if instance is stopped and"
1751
                " is not running, \"%s\" if instance running, but not on its"
1752
                " designated primary node, \"%s\" if instance should be"
1753
                " stopped, but is actually running, \"%s\" if instance should"
1754
                " run, but doesn't, \"%s\" if instance's primary node is down,"
1755
                " \"%s\" if instance's primary node is marked offline" %
1756
                status_values)
1757
  fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
1758
                 IQ_LIVE, 0, _GetInstStatus))
1759
  assert set(status_values) == constants.INSTST_ALL, \
1760
         "Status documentation mismatch"
1761

    
1762
  (network_fields, network_aliases) = _GetInstanceNetworkFields()
1763

    
1764
  fields.extend(network_fields)
1765
  fields.extend(_GetInstanceParameterFields())
1766
  fields.extend(_GetInstanceDiskFields())
1767
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1768

    
1769
  aliases = [
1770
    ("vcpus", "be/vcpus"),
1771
    ("sda_size", "disk.size/0"),
1772
    ("sdb_size", "disk.size/1"),
1773
    ] + network_aliases
1774

    
1775
  return _PrepareFieldList(fields, aliases)
1776

    
1777

    
1778
class LockQueryData:
1779
  """Data container for lock data queries.
1780

1781
  """
1782
  def __init__(self, lockdata):
1783
    """Initializes this class.
1784

1785
    """
1786
    self.lockdata = lockdata
1787

    
1788
  def __iter__(self):
1789
    """Iterate over all locks.
1790

1791
    """
1792
    return iter(self.lockdata)
1793

    
1794

    
1795
def _GetLockOwners(_, data):
1796
  """Returns a sorted list of a lock's current owners.
1797

1798
  """
1799
  (_, _, owners, _) = data
1800

    
1801
  if owners:
1802
    owners = utils.NiceSort(owners)
1803

    
1804
  return owners
1805

    
1806

    
1807
def _GetLockPending(_, data):
1808
  """Returns a sorted list of a lock's pending acquires.
1809

1810
  """
1811
  (_, _, _, pending) = data
1812

    
1813
  if pending:
1814
    pending = [(mode, utils.NiceSort(names))
1815
               for (mode, names) in pending]
1816

    
1817
  return pending
1818

    
1819

    
1820
def _BuildLockFields():
1821
  """Builds list of fields for lock queries.
1822

1823
  """
1824
  return _PrepareFieldList([
1825
    # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
1826
    (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
1827
     lambda ctx, (name, mode, owners, pending): name),
1828
    (_MakeField("mode", "Mode", QFT_OTHER,
1829
                "Mode in which the lock is currently acquired"
1830
                " (exclusive or shared)"),
1831
     LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
1832
    (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
1833
     LQ_OWNER, 0, _GetLockOwners),
1834
    (_MakeField("pending", "Pending", QFT_OTHER,
1835
                "Threads waiting for the lock"),
1836
     LQ_PENDING, 0, _GetLockPending),
1837
    ], [])
1838

    
1839

    
1840
class GroupQueryData:
1841
  """Data container for node group data queries.
1842

1843
  """
1844
  def __init__(self, groups, group_to_nodes, group_to_instances):
1845
    """Initializes this class.
1846

1847
    @param groups: List of node group objects
1848
    @type group_to_nodes: dict; group UUID as key
1849
    @param group_to_nodes: Per-group list of nodes
1850
    @type group_to_instances: dict; group UUID as key
1851
    @param group_to_instances: Per-group list of (primary) instances
1852

1853
    """
1854
    self.groups = groups
1855
    self.group_to_nodes = group_to_nodes
1856
    self.group_to_instances = group_to_instances
1857

    
1858
  def __iter__(self):
1859
    """Iterate over all node groups.
1860

1861
    """
1862
    return iter(self.groups)
1863

    
1864

    
1865
_GROUP_SIMPLE_FIELDS = {
1866
  "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
1867
  "name": ("Group", QFT_TEXT, "Group name"),
1868
  "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
1869
  "uuid": ("UUID", QFT_TEXT, "Group UUID"),
1870
  "ndparams": ("NDParams", QFT_OTHER, "Node parameters"),
1871
  }
1872

    
1873

    
1874
def _BuildGroupFields():
1875
  """Builds list of fields for node group queries.
1876

1877
  """
1878
  # Add simple fields
1879
  fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
1880
             _GetItemAttr(name))
1881
            for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
1882

    
1883
  def _GetLength(getter):
1884
    return lambda ctx, group: len(getter(ctx)[group.uuid])
1885

    
1886
  def _GetSortedList(getter):
1887
    return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
1888

    
1889
  group_to_nodes = operator.attrgetter("group_to_nodes")
1890
  group_to_instances = operator.attrgetter("group_to_instances")
1891

    
1892
  # Add fields for nodes
1893
  fields.extend([
1894
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
1895
     GQ_NODE, 0, _GetLength(group_to_nodes)),
1896
    (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
1897
     GQ_NODE, 0, _GetSortedList(group_to_nodes)),
1898
    ])
1899

    
1900
  # Add fields for instances
1901
  fields.extend([
1902
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
1903
                "Number of primary instances"),
1904
     GQ_INST, 0, _GetLength(group_to_instances)),
1905
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
1906
                "List of primary instances"),
1907
     GQ_INST, 0, _GetSortedList(group_to_instances)),
1908
    ])
1909

    
1910
  # Other fields
1911
  fields.extend([
1912
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), GQ_CONFIG, 0,
1913
     lambda ctx, group: list(group.GetTags())),
1914
    ])
1915

    
1916
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1917

    
1918
  return _PrepareFieldList(fields, [])
1919

    
1920

    
1921
class OsInfo(objects.ConfigObject):
1922
  __slots__ = [
1923
    "name",
1924
    "valid",
1925
    "hidden",
1926
    "blacklisted",
1927
    "variants",
1928
    "api_versions",
1929
    "parameters",
1930
    "node_status",
1931
    ]
1932

    
1933

    
1934
def _BuildOsFields():
1935
  """Builds list of fields for operating system queries.
1936

1937
  """
1938
  fields = [
1939
    (_MakeField("name", "Name", QFT_TEXT, "Operating system name"),
1940
     None, 0, _GetItemAttr("name")),
1941
    (_MakeField("valid", "Valid", QFT_BOOL,
1942
                "Whether operating system definition is valid"),
1943
     None, 0, _GetItemAttr("valid")),
1944
    (_MakeField("hidden", "Hidden", QFT_BOOL,
1945
                "Whether operating system is hidden"),
1946
     None, 0, _GetItemAttr("hidden")),
1947
    (_MakeField("blacklisted", "Blacklisted", QFT_BOOL,
1948
                "Whether operating system is blacklisted"),
1949
     None, 0, _GetItemAttr("blacklisted")),
1950
    (_MakeField("variants", "Variants", QFT_OTHER,
1951
                "Operating system variants"),
1952
     None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("variants"))),
1953
    (_MakeField("api_versions", "ApiVersions", QFT_OTHER,
1954
                "Operating system API versions"),
1955
     None, 0, _ConvWrap(sorted, _GetItemAttr("api_versions"))),
1956
    (_MakeField("parameters", "Parameters", QFT_OTHER,
1957
                "Operating system parameters"),
1958
     None, 0, _ConvWrap(compat.partial(utils.NiceSort,
1959
                                       key=operator.itemgetter(0)),
1960
                        _GetItemAttr("parameters"))),
1961
    (_MakeField("node_status", "NodeStatus", QFT_OTHER,
1962
                "Status from node"),
1963
     None, 0, _GetItemAttr("node_status")),
1964
    ]
1965

    
1966
  return _PrepareFieldList(fields, [])
1967

    
1968

    
1969
#: Fields available for node queries
1970
NODE_FIELDS = _BuildNodeFields()
1971

    
1972
#: Fields available for instance queries
1973
INSTANCE_FIELDS = _BuildInstanceFields()
1974

    
1975
#: Fields available for lock queries
1976
LOCK_FIELDS = _BuildLockFields()
1977

    
1978
#: Fields available for node group queries
1979
GROUP_FIELDS = _BuildGroupFields()
1980

    
1981
#: Fields available for operating system queries
1982
OS_FIELDS = _BuildOsFields()
1983

    
1984
#: All available resources
1985
ALL_FIELDS = {
1986
  constants.QR_INSTANCE: INSTANCE_FIELDS,
1987
  constants.QR_NODE: NODE_FIELDS,
1988
  constants.QR_LOCK: LOCK_FIELDS,
1989
  constants.QR_GROUP: GROUP_FIELDS,
1990
  constants.QR_OS: OS_FIELDS,
1991
  }
1992

    
1993
#: All available field lists
1994
ALL_FIELD_LISTS = ALL_FIELDS.values()