Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ 9ca8a7c5

History | View | Annotate | Download (59.8 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,
87
 IQ_NODES) = range(100, 105)
88

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

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

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

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

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

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

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

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

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

    
139

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

143
  """
144
  return _FS_UNKNOWN
145

    
146

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

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

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

157
  """
158
  result = []
159

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

    
167
    assert len(fdef) == 4
168

    
169
    result.append(fdef)
170

    
171
  return result
172

    
173

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

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

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

    
182

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

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

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

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

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

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

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

213
    """
214
    self._namefield = namefield
215

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

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

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

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

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

232
    @rtype: list
233

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

    
238
    return utils.UniqueSequence(self._names)
239

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

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

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

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

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

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

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

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

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

269
    """
270
    self._NeedAllNames()
271

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

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

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

    
285
    if self._allnames:
286
      return
287

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

    
297

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

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

    
304

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

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

    
311

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

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

    
318

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

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

    
325

    
326
def _PrepareRegex(pattern):
327
  """Compiles a regular expression.
328

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

    
335

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

339
  """
340
  # String statement has no effect, pylint: disable=W0105
341

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

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

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

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

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

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

    
368
  """Known operators
369

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

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

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

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

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

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

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

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

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

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

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

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

    
438
    return filter_fn
439

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

512
    """
513
    assert op_fn is None
514

    
515
    if hints_fn:
516
      hints_fn(op)
517

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

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

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

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

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

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

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

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

    
558
    assert fdef.kind != QFT_UNKNOWN
559

    
560
    # TODO: Type conversions?
561

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

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

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

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

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

    
583

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

587
  See L{_FilterCompilerHelper} for details.
588

589
  @rtype: callable
590

591
  """
592
  return _FilterCompilerHelper(fields)(hints, qfilter)
593

    
594

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

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

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

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

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

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

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

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

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

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

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

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

647
    """
648
    return self._requested_names
649

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

653
    @rtype: frozenset
654

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

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

663
    Includes unknown fields.
664

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

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

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

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

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

    
682
    result = []
683

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

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

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

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

    
702
    if not sort:
703
      return result
704

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

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

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

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

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

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

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

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

    
730

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

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

    
746

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

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

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

    
768

    
769
def _FieldDictKey((fdef, _, flags, fn)):
770
  """Generates key for field dictionary.
771

772
  """
773
  assert fdef.name and fdef.title, "Name and title are required"
774
  assert FIELD_NAME_RE.match(fdef.name)
775
  assert TITLE_RE.match(fdef.title)
776
  assert (DOC_RE.match(fdef.doc) and len(fdef.doc.splitlines()) == 1 and
777
          fdef.doc.strip() == fdef.doc), \
778
         "Invalid description for field '%s'" % fdef.name
779
  assert callable(fn)
780
  assert (flags & ~QFF_ALL) == 0, "Unknown flags for field '%s'" % fdef.name
781

    
782
  return fdef.name
783

    
784

    
785
def _PrepareFieldList(fields, aliases):
786
  """Prepares field list for use by L{Query}.
787

788
  Converts the list to a dictionary and does some verification.
789

790
  @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data
791
      kind, retrieval function)
792
  @param fields: List of fields, see L{Query.__init__} for a better
793
      description
794
  @type aliases: list of tuples; (alias, target)
795
  @param aliases: list of tuples containing aliases; for each
796
      alias/target pair, a duplicate will be created in the field list
797
  @rtype: dict
798
  @return: Field dictionary for L{Query}
799

800
  """
801
  if __debug__:
802
    duplicates = utils.FindDuplicates(fdef.title.lower()
803
                                      for (fdef, _, _, _) in fields)
804
    assert not duplicates, "Duplicate title(s) found: %r" % duplicates
805

    
806
  result = utils.SequenceToDict(fields, key=_FieldDictKey)
807

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

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

    
820
  return result
821

    
822

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

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

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

    
836

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

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

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

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

    
857

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

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

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

    
870

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

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

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

    
891

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

895
  @param attr: Attribute name
896

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

    
901

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

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

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

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

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

    
919

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

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

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

    
929

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

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

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

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

    
948
  return fn
949

    
950

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

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

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

    
964

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

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

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

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

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

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

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

    
1000

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

    
1015

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

    
1040

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

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

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

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

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

    
1060
    return cb(ctx, node, ng)
1061

    
1062
  return fn
1063

    
1064

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

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

1074
  """
1075
  return ng.name
1076

    
1077

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

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

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

    
1089
  return _FS_UNAVAIL
1090

    
1091

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

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

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

    
1104

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

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

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

    
1118
  if not node.vm_capable:
1119
    return _FS_UNAVAIL
1120

    
1121
  if not ctx.curlive_data:
1122
    return _FS_NODATA
1123

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

    
1129
  if kind == QFT_TEXT:
1130
    return value
1131

    
1132
  assert kind in (QFT_NUMBER, QFT_UNIT)
1133

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

    
1142

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

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

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

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

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

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

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

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

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

    
1219
  return _PrepareFieldList(fields, [])
1220

    
1221

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

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

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

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

    
1253
    self.instances = instances
1254
    self.cluster = cluster
1255
    self.disk_usage = disk_usage
1256
    self.offline_nodes = offline_nodes
1257
    self.bad_nodes = bad_nodes
1258
    self.live_data = live_data
1259
    self.wrongnode_inst = wrongnode_inst
1260
    self.console = console
1261
    self.nodes = nodes
1262
    self.groups = groups
1263

    
1264
    # Used for individual rows
1265
    self.inst_hvparams = None
1266
    self.inst_beparams = None
1267
    self.inst_osparams = None
1268
    self.inst_nicparams = None
1269

    
1270
  def __iter__(self):
1271
    """Iterate over all instances.
1272

1273
    This function has side-effects and only one instance of the resulting
1274
    generator should be used at a time.
1275

1276
    """
1277
    for inst in self.instances:
1278
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1279
      self.inst_beparams = self.cluster.FillBE(inst)
1280
      self.inst_osparams = self.cluster.SimpleFillOS(inst.os, inst.osparams)
1281
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1282
                             for nic in inst.nics]
1283

    
1284
      yield inst
1285

    
1286

    
1287
def _GetInstOperState(ctx, inst):
1288
  """Get instance's operational status.
1289

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

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

    
1302

    
1303
def _GetInstLiveData(name):
1304
  """Build function for retrieving live data.
1305

1306
  @type name: string
1307
  @param name: Live data field name
1308

1309
  """
1310
  def fn(ctx, inst):
1311
    """Get live data for an instance.
1312

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

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

    
1324
    if inst.name in ctx.live_data:
1325
      data = ctx.live_data[inst.name]
1326
      if name in data:
1327
        return data[name]
1328

    
1329
    return _FS_UNAVAIL
1330

    
1331
  return fn
1332

    
1333

    
1334
def _GetInstStatus(ctx, inst):
1335
  """Get instance status.
1336

1337
  @type ctx: L{InstanceQueryData}
1338
  @type inst: L{objects.Instance}
1339
  @param inst: Instance object
1340

1341
  """
1342
  if inst.primary_node in ctx.offline_nodes:
1343
    return constants.INSTST_NODEOFFLINE
1344

    
1345
  if inst.primary_node in ctx.bad_nodes:
1346
    return constants.INSTST_NODEDOWN
1347

    
1348
  if bool(ctx.live_data.get(inst.name)):
1349
    if inst.name in ctx.wrongnode_inst:
1350
      return constants.INSTST_WRONGNODE
1351
    elif inst.admin_state:
1352
      return constants.INSTST_RUNNING
1353
    else:
1354
      return constants.INSTST_ERRORUP
1355

    
1356
  if inst.admin_state:
1357
    return constants.INSTST_ERRORDOWN
1358

    
1359
  return constants.INSTST_ADMINDOWN
1360

    
1361

    
1362
def _GetInstDiskSize(index):
1363
  """Build function for retrieving disk size.
1364

1365
  @type index: int
1366
  @param index: Disk index
1367

1368
  """
1369
  def fn(_, inst):
1370
    """Get size of a disk.
1371

1372
    @type inst: L{objects.Instance}
1373
    @param inst: Instance object
1374

1375
    """
1376
    try:
1377
      return inst.disks[index].size
1378
    except IndexError:
1379
      return _FS_UNAVAIL
1380

    
1381
  return fn
1382

    
1383

    
1384
def _GetInstNic(index, cb):
1385
  """Build function for calling another function with an instance NIC.
1386

1387
  @type index: int
1388
  @param index: NIC index
1389
  @type cb: callable
1390
  @param cb: Callback
1391

1392
  """
1393
  def fn(ctx, inst):
1394
    """Call helper function with instance NIC.
1395

1396
    @type ctx: L{InstanceQueryData}
1397
    @type inst: L{objects.Instance}
1398
    @param inst: Instance object
1399

1400
    """
1401
    try:
1402
      nic = inst.nics[index]
1403
    except IndexError:
1404
      return _FS_UNAVAIL
1405

    
1406
    return cb(ctx, index, nic)
1407

    
1408
  return fn
1409

    
1410

    
1411
def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613
1412
  """Get a NIC's IP address.
1413

1414
  @type ctx: L{InstanceQueryData}
1415
  @type nic: L{objects.NIC}
1416
  @param nic: NIC object
1417

1418
  """
1419
  if nic.ip is None:
1420
    return _FS_UNAVAIL
1421
  else:
1422
    return nic.ip
1423

    
1424

    
1425
def _GetInstNicBridge(ctx, index, _):
1426
  """Get a NIC's bridge.
1427

1428
  @type ctx: L{InstanceQueryData}
1429
  @type index: int
1430
  @param index: NIC index
1431

1432
  """
1433
  assert len(ctx.inst_nicparams) >= index
1434

    
1435
  nicparams = ctx.inst_nicparams[index]
1436

    
1437
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1438
    return nicparams[constants.NIC_LINK]
1439
  else:
1440
    return _FS_UNAVAIL
1441

    
1442

    
1443
def _GetInstAllNicBridges(ctx, inst):
1444
  """Get all network bridges for an instance.
1445

1446
  @type ctx: L{InstanceQueryData}
1447
  @type inst: L{objects.Instance}
1448
  @param inst: Instance object
1449

1450
  """
1451
  assert len(ctx.inst_nicparams) == len(inst.nics)
1452

    
1453
  result = []
1454

    
1455
  for nicp in ctx.inst_nicparams:
1456
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1457
      result.append(nicp[constants.NIC_LINK])
1458
    else:
1459
      result.append(None)
1460

    
1461
  assert len(result) == len(inst.nics)
1462

    
1463
  return result
1464

    
1465

    
1466
def _GetInstNicParam(name):
1467
  """Build function for retrieving a NIC parameter.
1468

1469
  @type name: string
1470
  @param name: Parameter name
1471

1472
  """
1473
  def fn(ctx, index, _):
1474
    """Get a NIC's bridge.
1475

1476
    @type ctx: L{InstanceQueryData}
1477
    @type inst: L{objects.Instance}
1478
    @param inst: Instance object
1479
    @type nic: L{objects.NIC}
1480
    @param nic: NIC object
1481

1482
    """
1483
    assert len(ctx.inst_nicparams) >= index
1484
    return ctx.inst_nicparams[index][name]
1485

    
1486
  return fn
1487

    
1488

    
1489
def _GetInstanceNetworkFields():
1490
  """Get instance fields involving network interfaces.
1491

1492
  @return: Tuple containing list of field definitions used as input for
1493
    L{_PrepareFieldList} and a list of aliases
1494

1495
  """
1496
  nic_mac_fn = lambda ctx, _, nic: nic.mac
1497
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1498
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1499

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

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

    
1545
  aliases = [
1546
    # Legacy fields for first NIC
1547
    ("ip", "nic.ip/0"),
1548
    ("mac", "nic.mac/0"),
1549
    ("bridge", "nic.bridge/0"),
1550
    ("nic_mode", "nic.mode/0"),
1551
    ("nic_link", "nic.link/0"),
1552
    ]
1553

    
1554
  return (fields, aliases)
1555

    
1556

    
1557
def _GetInstDiskUsage(ctx, inst):
1558
  """Get disk usage for an instance.
1559

1560
  @type ctx: L{InstanceQueryData}
1561
  @type inst: L{objects.Instance}
1562
  @param inst: Instance object
1563

1564
  """
1565
  usage = ctx.disk_usage[inst.name]
1566

    
1567
  if usage is None:
1568
    usage = 0
1569

    
1570
  return usage
1571

    
1572

    
1573
def _GetInstanceConsole(ctx, inst):
1574
  """Get console information for instance.
1575

1576
  @type ctx: L{InstanceQueryData}
1577
  @type inst: L{objects.Instance}
1578
  @param inst: Instance object
1579

1580
  """
1581
  consinfo = ctx.console[inst.name]
1582

    
1583
  if consinfo is None:
1584
    return _FS_UNAVAIL
1585

    
1586
  return consinfo
1587

    
1588

    
1589
def _GetInstanceDiskFields():
1590
  """Get instance fields involving disks.
1591

1592
  @return: List of field definitions used as input for L{_PrepareFieldList}
1593

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

    
1607
  # Disks by number
1608
  fields.extend([
1609
    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1610
                "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
1611
     IQ_CONFIG, 0, _GetInstDiskSize(i))
1612
    for i in range(constants.MAX_DISKS)
1613
    ])
1614

    
1615
  return fields
1616

    
1617

    
1618
def _GetInstanceParameterFields():
1619
  """Get instance fields involving parameters.
1620

1621
  @return: List of field definitions used as input for L{_PrepareFieldList}
1622

1623
  """
1624
  # TODO: Consider moving titles closer to constants
1625
  be_title = {
1626
    constants.BE_AUTO_BALANCE: "Auto_balance",
1627
    constants.BE_MEMORY: "ConfigMemory",
1628
    constants.BE_VCPUS: "ConfigVCPUs",
1629
    }
1630

    
1631
  hv_title = {
1632
    constants.HV_ACPI: "ACPI",
1633
    constants.HV_BOOT_ORDER: "Boot_order",
1634
    constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1635
    constants.HV_DISK_TYPE: "Disk_type",
1636
    constants.HV_INITRD_PATH: "Initrd_path",
1637
    constants.HV_KERNEL_PATH: "Kernel_path",
1638
    constants.HV_NIC_TYPE: "NIC_type",
1639
    constants.HV_PAE: "PAE",
1640
    constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1641
    }
1642

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

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

    
1670
  # HV params
1671
  def _GetInstHvParam(name):
1672
    return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1673

    
1674
  fields.extend([
1675
    (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1676
                _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1677
     IQ_CONFIG, 0, _GetInstHvParam(name))
1678
    for name, kind in constants.HVS_PARAMETER_TYPES.items()
1679
    if name not in constants.HVC_GLOBALS
1680
    ])
1681

    
1682
  # BE params
1683
  def _GetInstBeParam(name):
1684
    return lambda ctx, _: ctx.inst_beparams.get(name, None)
1685

    
1686
  fields.extend([
1687
    (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1688
                _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1689
     IQ_CONFIG, 0, _GetInstBeParam(name))
1690
    for name, kind in constants.BES_PARAMETER_TYPES.items()
1691
    ])
1692

    
1693
  return fields
1694

    
1695

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

    
1708

    
1709
def _GetInstNodeGroup(ctx, default, node_name):
1710
  """Gets group UUID of an instance node.
1711

1712
  @type ctx: L{InstanceQueryData}
1713
  @param default: Default value
1714
  @type node_name: string
1715
  @param node_name: Node name
1716

1717
  """
1718
  try:
1719
    node = ctx.nodes[node_name]
1720
  except KeyError:
1721
    return default
1722
  else:
1723
    return node.group
1724

    
1725

    
1726
def _GetInstNodeGroupName(ctx, default, node_name):
1727
  """Gets group name of an instance node.
1728

1729
  @type ctx: L{InstanceQueryData}
1730
  @param default: Default value
1731
  @type node_name: string
1732
  @param node_name: Node name
1733

1734
  """
1735
  try:
1736
    node = ctx.nodes[node_name]
1737
  except KeyError:
1738
    return default
1739

    
1740
  try:
1741
    group = ctx.groups[node.group]
1742
  except KeyError:
1743
    return default
1744

    
1745
  return group.name
1746

    
1747

    
1748
def _BuildInstanceFields():
1749
  """Builds list of fields for instance queries.
1750

1751
  """
1752
  fields = [
1753
    (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
1754
     IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")),
1755
    (_MakeField("pnode.group", "PrimaryNodeGroup", QFT_TEXT,
1756
                "Primary node's group"),
1757
     IQ_NODES, 0,
1758
     lambda ctx, inst: _GetInstNodeGroupName(ctx, _FS_UNAVAIL,
1759
                                             inst.primary_node)),
1760
    (_MakeField("pnode.group.uuid", "PrimaryNodeGroupUUID", QFT_TEXT,
1761
                "Primary node's group UUID"),
1762
     IQ_NODES, 0,
1763
     lambda ctx, inst: _GetInstNodeGroup(ctx, _FS_UNAVAIL, inst.primary_node)),
1764
    # TODO: Allow filtering by secondary node as hostname
1765
    (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
1766
                "Secondary nodes; usually this will just be one node"),
1767
     IQ_CONFIG, 0, lambda ctx, inst: list(inst.secondary_nodes)),
1768
    (_MakeField("snodes.group", "SecondaryNodesGroups", QFT_OTHER,
1769
                "Node groups of secondary nodes"),
1770
     IQ_NODES, 0,
1771
     lambda ctx, inst: map(compat.partial(_GetInstNodeGroupName, ctx, None),
1772
                           inst.secondary_nodes)),
1773
    (_MakeField("snodes.group.uuid", "SecondaryNodesGroupsUUID", QFT_OTHER,
1774
                "Node group UUIDs of secondary nodes"),
1775
     IQ_NODES, 0,
1776
     lambda ctx, inst: map(compat.partial(_GetInstNodeGroup, ctx, None),
1777
                           inst.secondary_nodes)),
1778
    (_MakeField("admin_state", "Autostart", QFT_BOOL,
1779
                "Desired state of instance (if set, the instance should be"
1780
                " up)"),
1781
     IQ_CONFIG, 0, _GetItemAttr("admin_state")),
1782
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
1783
     lambda ctx, inst: list(inst.GetTags())),
1784
    (_MakeField("console", "Console", QFT_OTHER,
1785
                "Instance console information"), IQ_CONSOLE, 0,
1786
     _GetInstanceConsole),
1787
    ]
1788

    
1789
  # Add simple fields
1790
  fields.extend([
1791
    (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1792
    for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()
1793
    ])
1794

    
1795
  # Fields requiring talking to the node
1796
  fields.extend([
1797
    (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
1798
     IQ_LIVE, 0, _GetInstOperState),
1799
    (_MakeField("oper_ram", "Memory", QFT_UNIT,
1800
                "Actual memory usage as seen by hypervisor"),
1801
     IQ_LIVE, 0, _GetInstLiveData("memory")),
1802
    (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
1803
                "Actual number of VCPUs as seen by hypervisor"),
1804
     IQ_LIVE, 0, _GetInstLiveData("vcpus")),
1805
    ])
1806

    
1807
  # Status field
1808
  status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
1809
                   constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
1810
                   constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
1811
                   constants.INSTST_NODEOFFLINE)
1812
  status_doc = ("Instance status; \"%s\" if instance is set to be running"
1813
                " and actually is, \"%s\" if instance is stopped and"
1814
                " is not running, \"%s\" if instance running, but not on its"
1815
                " designated primary node, \"%s\" if instance should be"
1816
                " stopped, but is actually running, \"%s\" if instance should"
1817
                " run, but doesn't, \"%s\" if instance's primary node is down,"
1818
                " \"%s\" if instance's primary node is marked offline" %
1819
                status_values)
1820
  fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
1821
                 IQ_LIVE, 0, _GetInstStatus))
1822
  assert set(status_values) == constants.INSTST_ALL, \
1823
         "Status documentation mismatch"
1824

    
1825
  (network_fields, network_aliases) = _GetInstanceNetworkFields()
1826

    
1827
  fields.extend(network_fields)
1828
  fields.extend(_GetInstanceParameterFields())
1829
  fields.extend(_GetInstanceDiskFields())
1830
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1831

    
1832
  aliases = [
1833
    ("vcpus", "be/vcpus"),
1834
    ("sda_size", "disk.size/0"),
1835
    ("sdb_size", "disk.size/1"),
1836
    ] + network_aliases
1837

    
1838
  return _PrepareFieldList(fields, aliases)
1839

    
1840

    
1841
class LockQueryData:
1842
  """Data container for lock data queries.
1843

1844
  """
1845
  def __init__(self, lockdata):
1846
    """Initializes this class.
1847

1848
    """
1849
    self.lockdata = lockdata
1850

    
1851
  def __iter__(self):
1852
    """Iterate over all locks.
1853

1854
    """
1855
    return iter(self.lockdata)
1856

    
1857

    
1858
def _GetLockOwners(_, data):
1859
  """Returns a sorted list of a lock's current owners.
1860

1861
  """
1862
  (_, _, owners, _) = data
1863

    
1864
  if owners:
1865
    owners = utils.NiceSort(owners)
1866

    
1867
  return owners
1868

    
1869

    
1870
def _GetLockPending(_, data):
1871
  """Returns a sorted list of a lock's pending acquires.
1872

1873
  """
1874
  (_, _, _, pending) = data
1875

    
1876
  if pending:
1877
    pending = [(mode, utils.NiceSort(names))
1878
               for (mode, names) in pending]
1879

    
1880
  return pending
1881

    
1882

    
1883
def _BuildLockFields():
1884
  """Builds list of fields for lock queries.
1885

1886
  """
1887
  return _PrepareFieldList([
1888
    # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
1889
    (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
1890
     lambda ctx, (name, mode, owners, pending): name),
1891
    (_MakeField("mode", "Mode", QFT_OTHER,
1892
                "Mode in which the lock is currently acquired"
1893
                " (exclusive or shared)"),
1894
     LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
1895
    (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
1896
     LQ_OWNER, 0, _GetLockOwners),
1897
    (_MakeField("pending", "Pending", QFT_OTHER,
1898
                "Threads waiting for the lock"),
1899
     LQ_PENDING, 0, _GetLockPending),
1900
    ], [])
1901

    
1902

    
1903
class GroupQueryData:
1904
  """Data container for node group data queries.
1905

1906
  """
1907
  def __init__(self, groups, group_to_nodes, group_to_instances):
1908
    """Initializes this class.
1909

1910
    @param groups: List of node group objects
1911
    @type group_to_nodes: dict; group UUID as key
1912
    @param group_to_nodes: Per-group list of nodes
1913
    @type group_to_instances: dict; group UUID as key
1914
    @param group_to_instances: Per-group list of (primary) instances
1915

1916
    """
1917
    self.groups = groups
1918
    self.group_to_nodes = group_to_nodes
1919
    self.group_to_instances = group_to_instances
1920

    
1921
  def __iter__(self):
1922
    """Iterate over all node groups.
1923

1924
    """
1925
    return iter(self.groups)
1926

    
1927

    
1928
_GROUP_SIMPLE_FIELDS = {
1929
  "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
1930
  "name": ("Group", QFT_TEXT, "Group name"),
1931
  "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
1932
  "uuid": ("UUID", QFT_TEXT, "Group UUID"),
1933
  "ndparams": ("NDParams", QFT_OTHER, "Node parameters"),
1934
  }
1935

    
1936

    
1937
def _BuildGroupFields():
1938
  """Builds list of fields for node group queries.
1939

1940
  """
1941
  # Add simple fields
1942
  fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
1943
             _GetItemAttr(name))
1944
            for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
1945

    
1946
  def _GetLength(getter):
1947
    return lambda ctx, group: len(getter(ctx)[group.uuid])
1948

    
1949
  def _GetSortedList(getter):
1950
    return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
1951

    
1952
  group_to_nodes = operator.attrgetter("group_to_nodes")
1953
  group_to_instances = operator.attrgetter("group_to_instances")
1954

    
1955
  # Add fields for nodes
1956
  fields.extend([
1957
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
1958
     GQ_NODE, 0, _GetLength(group_to_nodes)),
1959
    (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
1960
     GQ_NODE, 0, _GetSortedList(group_to_nodes)),
1961
    ])
1962

    
1963
  # Add fields for instances
1964
  fields.extend([
1965
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
1966
                "Number of primary instances"),
1967
     GQ_INST, 0, _GetLength(group_to_instances)),
1968
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
1969
                "List of primary instances"),
1970
     GQ_INST, 0, _GetSortedList(group_to_instances)),
1971
    ])
1972

    
1973
  # Other fields
1974
  fields.extend([
1975
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), GQ_CONFIG, 0,
1976
     lambda ctx, group: list(group.GetTags())),
1977
    ])
1978

    
1979
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1980

    
1981
  return _PrepareFieldList(fields, [])
1982

    
1983

    
1984
class OsInfo(objects.ConfigObject):
1985
  __slots__ = [
1986
    "name",
1987
    "valid",
1988
    "hidden",
1989
    "blacklisted",
1990
    "variants",
1991
    "api_versions",
1992
    "parameters",
1993
    "node_status",
1994
    ]
1995

    
1996

    
1997
def _BuildOsFields():
1998
  """Builds list of fields for operating system queries.
1999

2000
  """
2001
  fields = [
2002
    (_MakeField("name", "Name", QFT_TEXT, "Operating system name"),
2003
     None, 0, _GetItemAttr("name")),
2004
    (_MakeField("valid", "Valid", QFT_BOOL,
2005
                "Whether operating system definition is valid"),
2006
     None, 0, _GetItemAttr("valid")),
2007
    (_MakeField("hidden", "Hidden", QFT_BOOL,
2008
                "Whether operating system is hidden"),
2009
     None, 0, _GetItemAttr("hidden")),
2010
    (_MakeField("blacklisted", "Blacklisted", QFT_BOOL,
2011
                "Whether operating system is blacklisted"),
2012
     None, 0, _GetItemAttr("blacklisted")),
2013
    (_MakeField("variants", "Variants", QFT_OTHER,
2014
                "Operating system variants"),
2015
     None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("variants"))),
2016
    (_MakeField("api_versions", "ApiVersions", QFT_OTHER,
2017
                "Operating system API versions"),
2018
     None, 0, _ConvWrap(sorted, _GetItemAttr("api_versions"))),
2019
    (_MakeField("parameters", "Parameters", QFT_OTHER,
2020
                "Operating system parameters"),
2021
     None, 0, _ConvWrap(compat.partial(utils.NiceSort, key=compat.fst),
2022
                        _GetItemAttr("parameters"))),
2023
    (_MakeField("node_status", "NodeStatus", QFT_OTHER,
2024
                "Status from node"),
2025
     None, 0, _GetItemAttr("node_status")),
2026
    ]
2027

    
2028
  return _PrepareFieldList(fields, [])
2029

    
2030

    
2031
#: Fields available for node queries
2032
NODE_FIELDS = _BuildNodeFields()
2033

    
2034
#: Fields available for instance queries
2035
INSTANCE_FIELDS = _BuildInstanceFields()
2036

    
2037
#: Fields available for lock queries
2038
LOCK_FIELDS = _BuildLockFields()
2039

    
2040
#: Fields available for node group queries
2041
GROUP_FIELDS = _BuildGroupFields()
2042

    
2043
#: Fields available for operating system queries
2044
OS_FIELDS = _BuildOsFields()
2045

    
2046
#: All available resources
2047
ALL_FIELDS = {
2048
  constants.QR_INSTANCE: INSTANCE_FIELDS,
2049
  constants.QR_NODE: NODE_FIELDS,
2050
  constants.QR_LOCK: LOCK_FIELDS,
2051
  constants.QR_GROUP: GROUP_FIELDS,
2052
  constants.QR_OS: OS_FIELDS,
2053
  }
2054

    
2055
#: All available field lists
2056
ALL_FIELD_LISTS = ALL_FIELDS.values()