Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ 69f0340a

History | View | Annotate | Download (60 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 == constants.ADMINST_UP:
1352
      return constants.INSTST_RUNNING
1353
    else:
1354
      return constants.INSTST_ERRORUP
1355

    
1356
  if inst.admin_state == constants.ADMINST_UP:
1357
    return constants.INSTST_ERRORDOWN
1358
  elif inst.admin_state == constants.ADMINST_DOWN:
1359
    return constants.INSTST_ADMINDOWN
1360

    
1361
  return constants.INSTST_ADMINOFFLINE
1362

    
1363

    
1364
def _GetInstDiskSize(index):
1365
  """Build function for retrieving disk size.
1366

1367
  @type index: int
1368
  @param index: Disk index
1369

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

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

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

    
1383
  return fn
1384

    
1385

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

1389
  @type index: int
1390
  @param index: NIC index
1391
  @type cb: callable
1392
  @param cb: Callback
1393

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

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

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

    
1408
    return cb(ctx, index, nic)
1409

    
1410
  return fn
1411

    
1412

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

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

1420
  """
1421
  if nic.ip is None:
1422
    return _FS_UNAVAIL
1423
  else:
1424
    return nic.ip
1425

    
1426

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

1430
  @type ctx: L{InstanceQueryData}
1431
  @type index: int
1432
  @param index: NIC index
1433

1434
  """
1435
  assert len(ctx.inst_nicparams) >= index
1436

    
1437
  nicparams = ctx.inst_nicparams[index]
1438

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

    
1444

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

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

1452
  """
1453
  assert len(ctx.inst_nicparams) == len(inst.nics)
1454

    
1455
  result = []
1456

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

    
1463
  assert len(result) == len(inst.nics)
1464

    
1465
  return result
1466

    
1467

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

1471
  @type name: string
1472
  @param name: Parameter name
1473

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

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

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

    
1488
  return fn
1489

    
1490

    
1491
def _GetInstanceNetworkFields():
1492
  """Get instance fields involving network interfaces.
1493

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

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

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

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

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

    
1556
  return (fields, aliases)
1557

    
1558

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

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

1566
  """
1567
  usage = ctx.disk_usage[inst.name]
1568

    
1569
  if usage is None:
1570
    usage = 0
1571

    
1572
  return usage
1573

    
1574

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

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

1582
  """
1583
  consinfo = ctx.console[inst.name]
1584

    
1585
  if consinfo is None:
1586
    return _FS_UNAVAIL
1587

    
1588
  return consinfo
1589

    
1590

    
1591
def _GetInstanceDiskFields():
1592
  """Get instance fields involving disks.
1593

1594
  @return: List of field definitions used as input for L{_PrepareFieldList}
1595

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

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

    
1617
  return fields
1618

    
1619

    
1620
def _GetInstanceParameterFields():
1621
  """Get instance fields involving parameters.
1622

1623
  @return: List of field definitions used as input for L{_PrepareFieldList}
1624

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

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

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

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

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

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

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

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

    
1695
  return fields
1696

    
1697

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

    
1710

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

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

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

    
1727

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

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

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

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

    
1747
  return group.name
1748

    
1749

    
1750
def _BuildInstanceFields():
1751
  """Builds list of fields for instance queries.
1752

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

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

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

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

    
1827
  (network_fields, network_aliases) = _GetInstanceNetworkFields()
1828

    
1829
  fields.extend(network_fields)
1830
  fields.extend(_GetInstanceParameterFields())
1831
  fields.extend(_GetInstanceDiskFields())
1832
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1833

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

    
1840
  return _PrepareFieldList(fields, aliases)
1841

    
1842

    
1843
class LockQueryData:
1844
  """Data container for lock data queries.
1845

1846
  """
1847
  def __init__(self, lockdata):
1848
    """Initializes this class.
1849

1850
    """
1851
    self.lockdata = lockdata
1852

    
1853
  def __iter__(self):
1854
    """Iterate over all locks.
1855

1856
    """
1857
    return iter(self.lockdata)
1858

    
1859

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

1863
  """
1864
  (_, _, owners, _) = data
1865

    
1866
  if owners:
1867
    owners = utils.NiceSort(owners)
1868

    
1869
  return owners
1870

    
1871

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

1875
  """
1876
  (_, _, _, pending) = data
1877

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

    
1882
  return pending
1883

    
1884

    
1885
def _BuildLockFields():
1886
  """Builds list of fields for lock queries.
1887

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

    
1904

    
1905
class GroupQueryData:
1906
  """Data container for node group data queries.
1907

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

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

1918
    """
1919
    self.groups = groups
1920
    self.group_to_nodes = group_to_nodes
1921
    self.group_to_instances = group_to_instances
1922

    
1923
  def __iter__(self):
1924
    """Iterate over all node groups.
1925

1926
    """
1927
    return iter(self.groups)
1928

    
1929

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

    
1938

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

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

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

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

    
1954
  group_to_nodes = operator.attrgetter("group_to_nodes")
1955
  group_to_instances = operator.attrgetter("group_to_instances")
1956

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

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

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

    
1981
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1982

    
1983
  return _PrepareFieldList(fields, [])
1984

    
1985

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

    
1998

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

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

    
2030
  return _PrepareFieldList(fields, [])
2031

    
2032

    
2033
#: Fields available for node queries
2034
NODE_FIELDS = _BuildNodeFields()
2035

    
2036
#: Fields available for instance queries
2037
INSTANCE_FIELDS = _BuildInstanceFields()
2038

    
2039
#: Fields available for lock queries
2040
LOCK_FIELDS = _BuildLockFields()
2041

    
2042
#: Fields available for node group queries
2043
GROUP_FIELDS = _BuildGroupFields()
2044

    
2045
#: Fields available for operating system queries
2046
OS_FIELDS = _BuildOsFields()
2047

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

    
2057
#: All available field lists
2058
ALL_FIELD_LISTS = ALL_FIELDS.values()