Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ 5e12acfe

History | View | Annotate | Download (72.5 kB)

1
#
2
#
3

    
4
# Copyright (C) 2010, 2011, 2012 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 runtime
66
from ganeti import qlang
67
from ganeti import jstore
68

    
69
from ganeti.constants import (QFT_UNKNOWN, QFT_TEXT, QFT_BOOL, QFT_NUMBER,
70
                              QFT_UNIT, QFT_TIMESTAMP, QFT_OTHER,
71
                              RS_NORMAL, RS_UNKNOWN, RS_NODATA,
72
                              RS_UNAVAIL, RS_OFFLINE)
73

    
74

    
75
# Constants for requesting data from the caller/data provider. Each property
76
# collected/computed separately by the data provider should have its own to
77
# only collect the requested data and not more.
78

    
79
(NQ_CONFIG,
80
 NQ_INST,
81
 NQ_LIVE,
82
 NQ_GROUP,
83
 NQ_OOB) = range(1, 6)
84

    
85
(IQ_CONFIG,
86
 IQ_LIVE,
87
 IQ_DISKUSAGE,
88
 IQ_CONSOLE,
89
 IQ_NODES) = range(100, 105)
90

    
91
(LQ_MODE,
92
 LQ_OWNER,
93
 LQ_PENDING) = range(10, 13)
94

    
95
(GQ_CONFIG,
96
 GQ_NODE,
97
 GQ_INST,
98
 GQ_DISKPARAMS) = range(200, 204)
99

    
100
(CQ_CONFIG,
101
 CQ_QUEUE_DRAINED,
102
 CQ_WATCHER_PAUSE) = range(300, 303)
103

    
104
(JQ_ARCHIVED, ) = range(400, 401)
105

    
106
# Query field flags
107
QFF_HOSTNAME = 0x01
108
QFF_IP_ADDRESS = 0x02
109
QFF_JOB_ID = 0x04
110
QFF_SPLIT_TIMESTAMP = 0x08
111
# Next values: 0x10, 0x20, 0x40, 0x80, 0x100, 0x200
112
QFF_ALL = (QFF_HOSTNAME | QFF_IP_ADDRESS | QFF_JOB_ID | QFF_SPLIT_TIMESTAMP)
113

    
114
FIELD_NAME_RE = re.compile(r"^[a-z0-9/._]+$")
115
TITLE_RE = re.compile(r"^[^\s]+$")
116
DOC_RE = re.compile(r"^[A-Z].*[^.,?!]$")
117

    
118
#: Verification function for each field type
119
_VERIFY_FN = {
120
  QFT_UNKNOWN: ht.TNone,
121
  QFT_TEXT: ht.TString,
122
  QFT_BOOL: ht.TBool,
123
  QFT_NUMBER: ht.TInt,
124
  QFT_UNIT: ht.TInt,
125
  QFT_TIMESTAMP: ht.TNumber,
126
  QFT_OTHER: lambda _: True,
127
  }
128

    
129
# Unique objects for special field statuses
130
_FS_UNKNOWN = object()
131
_FS_NODATA = object()
132
_FS_UNAVAIL = object()
133
_FS_OFFLINE = object()
134

    
135
#: List of all special status
136
_FS_ALL = frozenset([_FS_UNKNOWN, _FS_NODATA, _FS_UNAVAIL, _FS_OFFLINE])
137

    
138
#: VType to QFT mapping
139
_VTToQFT = {
140
  # TODO: fix validation of empty strings
141
  constants.VTYPE_STRING: QFT_OTHER, # since VTYPE_STRINGs can be empty
142
  constants.VTYPE_MAYBE_STRING: QFT_OTHER,
143
  constants.VTYPE_BOOL: QFT_BOOL,
144
  constants.VTYPE_SIZE: QFT_UNIT,
145
  constants.VTYPE_INT: QFT_NUMBER,
146
  }
147

    
148
_SERIAL_NO_DOC = "%s object serial number, incremented on each modification"
149

    
150

    
151
def _GetUnknownField(ctx, item): # pylint: disable=W0613
152
  """Gets the contents of an unknown field.
153

154
  """
155
  return _FS_UNKNOWN
156

    
157

    
158
def _GetQueryFields(fielddefs, selected):
159
  """Calculates the internal list of selected fields.
160

161
  Unknown fields are returned as L{constants.QFT_UNKNOWN}.
162

163
  @type fielddefs: dict
164
  @param fielddefs: Field definitions
165
  @type selected: list of strings
166
  @param selected: List of selected fields
167

168
  """
169
  result = []
170

    
171
  for name in selected:
172
    try:
173
      fdef = fielddefs[name]
174
    except KeyError:
175
      fdef = (_MakeField(name, name, QFT_UNKNOWN, "Unknown field '%s'" % name),
176
              None, 0, _GetUnknownField)
177

    
178
    assert len(fdef) == 4
179

    
180
    result.append(fdef)
181

    
182
  return result
183

    
184

    
185
def GetAllFields(fielddefs):
186
  """Extract L{objects.QueryFieldDefinition} from field definitions.
187

188
  @rtype: list of L{objects.QueryFieldDefinition}
189

190
  """
191
  return [fdef for (fdef, _, _, _) in fielddefs]
192

    
193

    
194
class _FilterHints:
195
  """Class for filter analytics.
196

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

202
  There are two ways to optimize this. The first, and simpler, is to assign
203
  each field a group of data, so that the caller can determine which
204
  computations are necessary depending on the data groups requested. The list
205
  of referenced groups must also be computed for fields referenced in the
206
  filter.
207

208
  The second is restricting the items based on a primary key. The primary key
209
  is usually a unique name (e.g. a node name). This class extracts all
210
  referenced names from a filter. If it encounters any filter condition which
211
  disallows such a list to be determined (e.g. a non-equality filter), all
212
  names will be requested.
213

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

217
  """
218
  def __init__(self, namefield):
219
    """Initializes this class.
220

221
    @type namefield: string
222
    @param namefield: Field caller is interested in
223

224
    """
225
    self._namefield = namefield
226

    
227
    #: Whether all names need to be requested (e.g. if a non-equality operator
228
    #: has been used)
229
    self._allnames = False
230

    
231
    #: Which names to request
232
    self._names = None
233

    
234
    #: Data kinds referenced by the filter (used by L{Query.RequestedData})
235
    self._datakinds = set()
236

    
237
  def RequestedNames(self):
238
    """Returns all requested values.
239

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

243
    @rtype: list
244

245
    """
246
    if self._allnames or self._names is None:
247
      return None
248

    
249
    return utils.UniqueSequence(self._names)
250

    
251
  def ReferencedData(self):
252
    """Returns all kinds of data referenced by the filter.
253

254
    """
255
    return frozenset(self._datakinds)
256

    
257
  def _NeedAllNames(self):
258
    """Changes internal state to request all names.
259

260
    """
261
    self._allnames = True
262
    self._names = None
263

    
264
  def NoteLogicOp(self, op):
265
    """Called when handling a logic operation.
266

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

270
    """
271
    if op != qlang.OP_OR:
272
      self._NeedAllNames()
273

    
274
  def NoteUnaryOp(self, op, datakind): # pylint: disable=W0613
275
    """Called when handling an unary operation.
276

277
    @type op: string
278
    @param op: Operator
279

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

    
284
    self._NeedAllNames()
285

    
286
  def NoteBinaryOp(self, op, datakind, name, value):
287
    """Called when handling a binary operation.
288

289
    @type op: string
290
    @param op: Operator
291
    @type name: string
292
    @param name: Left-hand side of operator (field name)
293
    @param value: Right-hand side of operator
294

295
    """
296
    if datakind is not None:
297
      self._datakinds.add(datakind)
298

    
299
    if self._allnames:
300
      return
301

    
302
    # If any operator other than equality was used, all names need to be
303
    # retrieved
304
    if op == qlang.OP_EQUAL and name == self._namefield:
305
      if self._names is None:
306
        self._names = []
307
      self._names.append(value)
308
    else:
309
      self._NeedAllNames()
310

    
311

    
312
def _WrapLogicOp(op_fn, sentences, ctx, item):
313
  """Wrapper for logic operator functions.
314

315
  """
316
  return op_fn(fn(ctx, item) for fn in sentences)
317

    
318

    
319
def _WrapUnaryOp(op_fn, inner, ctx, item):
320
  """Wrapper for unary operator functions.
321

322
  """
323
  return op_fn(inner(ctx, item))
324

    
325

    
326
def _WrapBinaryOp(op_fn, retrieval_fn, value, ctx, item):
327
  """Wrapper for binary operator functions.
328

329
  """
330
  return op_fn(retrieval_fn(ctx, item), value)
331

    
332

    
333
def _WrapNot(fn, lhs, rhs):
334
  """Negates the result of a wrapped function.
335

336
  """
337
  return not fn(lhs, rhs)
338

    
339

    
340
def _PrepareRegex(pattern):
341
  """Compiles a regular expression.
342

343
  """
344
  try:
345
    return re.compile(pattern)
346
  except re.error, err:
347
    raise errors.ParameterError("Invalid regex pattern (%s)" % err)
348

    
349

    
350
def _PrepareSplitTimestamp(value):
351
  """Prepares a value for comparison by L{_MakeSplitTimestampComparison}.
352

353
  """
354
  if ht.TNumber(value):
355
    return value
356
  else:
357
    return utils.MergeTime(value)
358

    
359

    
360
def _MakeSplitTimestampComparison(fn):
361
  """Compares split timestamp values after converting to float.
362

363
  """
364
  return lambda lhs, rhs: fn(utils.MergeTime(lhs), rhs)
365

    
366

    
367
def _MakeComparisonChecks(fn):
368
  """Prepares flag-specific comparisons using a comparison function.
369

370
  """
371
  return [
372
    (QFF_SPLIT_TIMESTAMP, _MakeSplitTimestampComparison(fn),
373
     _PrepareSplitTimestamp),
374
    (QFF_JOB_ID, lambda lhs, rhs: fn(jstore.ParseJobId(lhs), rhs),
375
     jstore.ParseJobId),
376
    (None, fn, None),
377
    ]
378

    
379

    
380
class _FilterCompilerHelper:
381
  """Converts a query filter to a callable usable for filtering.
382

383
  """
384
  # String statement has no effect, pylint: disable=W0105
385

    
386
  #: How deep filters can be nested
387
  _LEVELS_MAX = 10
388

    
389
  # Unique identifiers for operator groups
390
  (_OPTYPE_LOGIC,
391
   _OPTYPE_UNARY,
392
   _OPTYPE_BINARY) = range(1, 4)
393

    
394
  """Functions for equality checks depending on field flags.
395

396
  List of tuples containing flags and a callable receiving the left- and
397
  right-hand side of the operator. The flags are an OR-ed value of C{QFF_*}
398
  (e.g. L{QFF_HOSTNAME} or L{QFF_SPLIT_TIMESTAMP}).
399

400
  Order matters. The first item with flags will be used. Flags are checked
401
  using binary AND.
402

403
  """
404
  _EQUALITY_CHECKS = [
405
    (QFF_HOSTNAME,
406
     lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
407
                                               case_sensitive=False),
408
     None),
409
    (QFF_SPLIT_TIMESTAMP, _MakeSplitTimestampComparison(operator.eq),
410
     _PrepareSplitTimestamp),
411
    (None, operator.eq, None),
412
    ]
413

    
414
  """Known operators
415

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

419
    - C{_OPTYPE_LOGIC}: Callable taking any number of arguments; used by
420
      L{_HandleLogicOp}
421
    - C{_OPTYPE_UNARY}: Always C{None}; details handled by L{_HandleUnaryOp}
422
    - C{_OPTYPE_BINARY}: Callable taking exactly two parameters, the left- and
423
      right-hand side of the operator, used by L{_HandleBinaryOp}
424

425
  """
426
  _OPS = {
427
    # Logic operators
428
    qlang.OP_OR: (_OPTYPE_LOGIC, compat.any),
429
    qlang.OP_AND: (_OPTYPE_LOGIC, compat.all),
430

    
431
    # Unary operators
432
    qlang.OP_NOT: (_OPTYPE_UNARY, None),
433
    qlang.OP_TRUE: (_OPTYPE_UNARY, None),
434

    
435
    # Binary operators
436
    qlang.OP_EQUAL: (_OPTYPE_BINARY, _EQUALITY_CHECKS),
437
    qlang.OP_NOT_EQUAL:
438
      (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn), valprepfn)
439
                        for (flags, fn, valprepfn) in _EQUALITY_CHECKS]),
440
    qlang.OP_LT: (_OPTYPE_BINARY, _MakeComparisonChecks(operator.lt)),
441
    qlang.OP_LE: (_OPTYPE_BINARY, _MakeComparisonChecks(operator.le)),
442
    qlang.OP_GT: (_OPTYPE_BINARY, _MakeComparisonChecks(operator.gt)),
443
    qlang.OP_GE: (_OPTYPE_BINARY, _MakeComparisonChecks(operator.ge)),
444
    qlang.OP_REGEXP: (_OPTYPE_BINARY, [
445
      (None, lambda lhs, rhs: rhs.search(lhs), _PrepareRegex),
446
      ]),
447
    qlang.OP_CONTAINS: (_OPTYPE_BINARY, [
448
      (None, operator.contains, None),
449
      ]),
450
    }
451

    
452
  def __init__(self, fields):
453
    """Initializes this class.
454

455
    @param fields: Field definitions (return value of L{_PrepareFieldList})
456

457
    """
458
    self._fields = fields
459
    self._hints = None
460
    self._op_handler = None
461

    
462
  def __call__(self, hints, qfilter):
463
    """Converts a query filter into a callable function.
464

465
    @type hints: L{_FilterHints} or None
466
    @param hints: Callbacks doing analysis on filter
467
    @type qfilter: list
468
    @param qfilter: Filter structure
469
    @rtype: callable
470
    @return: Function receiving context and item as parameters, returning
471
             boolean as to whether item matches filter
472

473
    """
474
    self._op_handler = {
475
      self._OPTYPE_LOGIC:
476
        (self._HandleLogicOp, getattr(hints, "NoteLogicOp", None)),
477
      self._OPTYPE_UNARY:
478
        (self._HandleUnaryOp, getattr(hints, "NoteUnaryOp", None)),
479
      self._OPTYPE_BINARY:
480
        (self._HandleBinaryOp, getattr(hints, "NoteBinaryOp", None)),
481
      }
482

    
483
    try:
484
      filter_fn = self._Compile(qfilter, 0)
485
    finally:
486
      self._op_handler = None
487

    
488
    return filter_fn
489

    
490
  def _Compile(self, qfilter, level):
491
    """Inner function for converting filters.
492

493
    Calls the correct handler functions for the top-level operator. This
494
    function is called recursively (e.g. for logic operators).
495

496
    """
497
    if not (isinstance(qfilter, (list, tuple)) and qfilter):
498
      raise errors.ParameterError("Invalid filter on level %s" % level)
499

    
500
    # Limit recursion
501
    if level >= self._LEVELS_MAX:
502
      raise errors.ParameterError("Only up to %s levels are allowed (filter"
503
                                  " nested too deep)" % self._LEVELS_MAX)
504

    
505
    # Create copy to be modified
506
    operands = qfilter[:]
507
    op = operands.pop(0)
508

    
509
    try:
510
      (kind, op_data) = self._OPS[op]
511
    except KeyError:
512
      raise errors.ParameterError("Unknown operator '%s'" % op)
513

    
514
    (handler, hints_cb) = self._op_handler[kind]
515

    
516
    return handler(hints_cb, level, op, op_data, operands)
517

    
518
  def _LookupField(self, name):
519
    """Returns a field definition by name.
520

521
    """
522
    try:
523
      return self._fields[name]
524
    except KeyError:
525
      raise errors.ParameterError("Unknown field '%s'" % name)
526

    
527
  def _HandleLogicOp(self, hints_fn, level, op, op_fn, operands):
528
    """Handles logic operators.
529

530
    @type hints_fn: callable
531
    @param hints_fn: Callback doing some analysis on the filter
532
    @type level: integer
533
    @param level: Current depth
534
    @type op: string
535
    @param op: Operator
536
    @type op_fn: callable
537
    @param op_fn: Function implementing operator
538
    @type operands: list
539
    @param operands: List of operands
540

541
    """
542
    if hints_fn:
543
      hints_fn(op)
544

    
545
    return compat.partial(_WrapLogicOp, op_fn,
546
                          [self._Compile(op, level + 1) for op in operands])
547

    
548
  def _HandleUnaryOp(self, hints_fn, level, op, op_fn, operands):
549
    """Handles unary operators.
550

551
    @type hints_fn: callable
552
    @param hints_fn: Callback doing some analysis on the filter
553
    @type level: integer
554
    @param level: Current depth
555
    @type op: string
556
    @param op: Operator
557
    @type op_fn: callable
558
    @param op_fn: Function implementing operator
559
    @type operands: list
560
    @param operands: List of operands
561

562
    """
563
    assert op_fn is None
564

    
565
    if len(operands) != 1:
566
      raise errors.ParameterError("Unary operator '%s' expects exactly one"
567
                                  " operand" % op)
568

    
569
    if op == qlang.OP_TRUE:
570
      (_, datakind, _, retrieval_fn) = self._LookupField(operands[0])
571

    
572
      if hints_fn:
573
        hints_fn(op, datakind)
574

    
575
      op_fn = operator.truth
576
      arg = retrieval_fn
577
    elif op == qlang.OP_NOT:
578
      if hints_fn:
579
        hints_fn(op, None)
580

    
581
      op_fn = operator.not_
582
      arg = self._Compile(operands[0], level + 1)
583
    else:
584
      raise errors.ProgrammerError("Can't handle operator '%s'" % op)
585

    
586
    return compat.partial(_WrapUnaryOp, op_fn, arg)
587

    
588
  def _HandleBinaryOp(self, hints_fn, level, op, op_data, operands):
589
    """Handles binary operators.
590

591
    @type hints_fn: callable
592
    @param hints_fn: Callback doing some analysis on the filter
593
    @type level: integer
594
    @param level: Current depth
595
    @type op: string
596
    @param op: Operator
597
    @param op_data: Functions implementing operators
598
    @type operands: list
599
    @param operands: List of operands
600

601
    """
602
    # Unused arguments, pylint: disable=W0613
603
    try:
604
      (name, value) = operands
605
    except (ValueError, TypeError):
606
      raise errors.ParameterError("Invalid binary operator, expected exactly"
607
                                  " two operands")
608

    
609
    (fdef, datakind, field_flags, retrieval_fn) = self._LookupField(name)
610

    
611
    assert fdef.kind != QFT_UNKNOWN
612

    
613
    # TODO: Type conversions?
614

    
615
    verify_fn = _VERIFY_FN[fdef.kind]
616
    if not verify_fn(value):
617
      raise errors.ParameterError("Unable to compare field '%s' (type '%s')"
618
                                  " with '%s', expected %s" %
619
                                  (name, fdef.kind, value.__class__.__name__,
620
                                   verify_fn))
621

    
622
    if hints_fn:
623
      hints_fn(op, datakind, name, value)
624

    
625
    for (fn_flags, fn, valprepfn) in op_data:
626
      if fn_flags is None or fn_flags & field_flags:
627
        # Prepare value if necessary (e.g. compile regular expression)
628
        if valprepfn:
629
          value = valprepfn(value)
630

    
631
        return compat.partial(_WrapBinaryOp, fn, retrieval_fn, value)
632

    
633
    raise errors.ProgrammerError("Unable to find operator implementation"
634
                                 " (op '%s', flags %s)" % (op, field_flags))
635

    
636

    
637
def _CompileFilter(fields, hints, qfilter):
638
  """Converts a query filter into a callable function.
639

640
  See L{_FilterCompilerHelper} for details.
641

642
  @rtype: callable
643

644
  """
645
  return _FilterCompilerHelper(fields)(hints, qfilter)
646

    
647

    
648
class Query:
649
  def __init__(self, fieldlist, selected, qfilter=None, namefield=None):
650
    """Initializes this class.
651

652
    The field definition is a dictionary with the field's name as a key and a
653
    tuple containing, in order, the field definition object
654
    (L{objects.QueryFieldDefinition}, the data kind to help calling code
655
    collect data and a retrieval function. The retrieval function is called
656
    with two parameters, in order, the data container and the item in container
657
    (see L{Query.Query}).
658

659
    Users of this class can call L{RequestedData} before preparing the data
660
    container to determine what data is needed.
661

662
    @type fieldlist: dictionary
663
    @param fieldlist: Field definitions
664
    @type selected: list of strings
665
    @param selected: List of selected fields
666

667
    """
668
    assert namefield is None or namefield in fieldlist
669

    
670
    self._fields = _GetQueryFields(fieldlist, selected)
671

    
672
    self._filter_fn = None
673
    self._requested_names = None
674
    self._filter_datakinds = frozenset()
675

    
676
    if qfilter is not None:
677
      # Collect requested names if wanted
678
      if namefield:
679
        hints = _FilterHints(namefield)
680
      else:
681
        hints = None
682

    
683
      # Build filter function
684
      self._filter_fn = _CompileFilter(fieldlist, hints, qfilter)
685
      if hints:
686
        self._requested_names = hints.RequestedNames()
687
        self._filter_datakinds = hints.ReferencedData()
688

    
689
    if namefield is None:
690
      self._name_fn = None
691
    else:
692
      (_, _, _, self._name_fn) = fieldlist[namefield]
693

    
694
  def RequestedNames(self):
695
    """Returns all names referenced in the filter.
696

697
    If there is no filter or operators are preventing determining the exact
698
    names, C{None} is returned.
699

700
    """
701
    return self._requested_names
702

    
703
  def RequestedData(self):
704
    """Gets requested kinds of data.
705

706
    @rtype: frozenset
707

708
    """
709
    return (self._filter_datakinds |
710
            frozenset(datakind for (_, datakind, _, _) in self._fields
711
                      if datakind is not None))
712

    
713
  def GetFields(self):
714
    """Returns the list of fields for this query.
715

716
    Includes unknown fields.
717

718
    @rtype: List of L{objects.QueryFieldDefinition}
719

720
    """
721
    return GetAllFields(self._fields)
722

    
723
  def Query(self, ctx, sort_by_name=True):
724
    """Execute a query.
725

726
    @param ctx: Data container passed to field retrieval functions, must
727
      support iteration using C{__iter__}
728
    @type sort_by_name: boolean
729
    @param sort_by_name: Whether to sort by name or keep the input data's
730
      ordering
731

732
    """
733
    sort = (self._name_fn and sort_by_name)
734

    
735
    result = []
736

    
737
    for idx, item in enumerate(ctx):
738
      if not (self._filter_fn is None or self._filter_fn(ctx, item)):
739
        continue
740

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

    
743
      # Verify result
744
      if __debug__:
745
        _VerifyResultRow(self._fields, row)
746

    
747
      if sort:
748
        (status, name) = _ProcessResult(self._name_fn(ctx, item))
749
        assert status == constants.RS_NORMAL
750
        # TODO: Are there cases where we wouldn't want to use NiceSort?
751
        # Answer: if the name field is non-string...
752
        result.append((utils.NiceSortKey(name), idx, row))
753
      else:
754
        result.append(row)
755

    
756
    if not sort:
757
      return result
758

    
759
    # TODO: Would "heapq" be more efficient than sorting?
760

    
761
    # Sorting in-place instead of using "sorted()"
762
    result.sort()
763

    
764
    assert not result or (len(result[0]) == 3 and len(result[-1]) == 3)
765

    
766
    return map(operator.itemgetter(2), result)
767

    
768
  def OldStyleQuery(self, ctx, sort_by_name=True):
769
    """Query with "old" query result format.
770

771
    See L{Query.Query} for arguments.
772

773
    """
774
    unknown = set(fdef.name for (fdef, _, _, _) in self._fields
775
                  if fdef.kind == QFT_UNKNOWN)
776
    if unknown:
777
      raise errors.OpPrereqError("Unknown output fields selected: %s" %
778
                                 (utils.CommaJoin(unknown), ),
779
                                 errors.ECODE_INVAL)
780

    
781
    return [[value for (_, value) in row]
782
            for row in self.Query(ctx, sort_by_name=sort_by_name)]
783

    
784

    
785
def _ProcessResult(value):
786
  """Converts result values into externally-visible ones.
787

788
  """
789
  if value is _FS_UNKNOWN:
790
    return (RS_UNKNOWN, None)
791
  elif value is _FS_NODATA:
792
    return (RS_NODATA, None)
793
  elif value is _FS_UNAVAIL:
794
    return (RS_UNAVAIL, None)
795
  elif value is _FS_OFFLINE:
796
    return (RS_OFFLINE, None)
797
  else:
798
    return (RS_NORMAL, value)
799

    
800

    
801
def _VerifyResultRow(fields, row):
802
  """Verifies the contents of a query result row.
803

804
  @type fields: list
805
  @param fields: Field definitions for result
806
  @type row: list of tuples
807
  @param row: Row data
808

809
  """
810
  assert len(row) == len(fields)
811
  errs = []
812
  for ((status, value), (fdef, _, _, _)) in zip(row, fields):
813
    if status == RS_NORMAL:
814
      if not _VERIFY_FN[fdef.kind](value):
815
        errs.append("normal field %s fails validation (value is %s)" %
816
                    (fdef.name, value))
817
    elif value is not None:
818
      errs.append("abnormal field %s has a non-None value" % fdef.name)
819
  assert not errs, ("Failed validation: %s in row %s" %
820
                    (utils.CommaJoin(errs), row))
821

    
822

    
823
def _FieldDictKey((fdef, _, flags, fn)):
824
  """Generates key for field dictionary.
825

826
  """
827
  assert fdef.name and fdef.title, "Name and title are required"
828
  assert FIELD_NAME_RE.match(fdef.name)
829
  assert TITLE_RE.match(fdef.title)
830
  assert (DOC_RE.match(fdef.doc) and len(fdef.doc.splitlines()) == 1 and
831
          fdef.doc.strip() == fdef.doc), \
832
         "Invalid description for field '%s'" % fdef.name
833
  assert callable(fn)
834
  assert (flags & ~QFF_ALL) == 0, "Unknown flags for field '%s'" % fdef.name
835

    
836
  return fdef.name
837

    
838

    
839
def _PrepareFieldList(fields, aliases):
840
  """Prepares field list for use by L{Query}.
841

842
  Converts the list to a dictionary and does some verification.
843

844
  @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data
845
      kind, retrieval function)
846
  @param fields: List of fields, see L{Query.__init__} for a better
847
      description
848
  @type aliases: list of tuples; (alias, target)
849
  @param aliases: list of tuples containing aliases; for each
850
      alias/target pair, a duplicate will be created in the field list
851
  @rtype: dict
852
  @return: Field dictionary for L{Query}
853

854
  """
855
  if __debug__:
856
    duplicates = utils.FindDuplicates(fdef.title.lower()
857
                                      for (fdef, _, _, _) in fields)
858
    assert not duplicates, "Duplicate title(s) found: %r" % duplicates
859

    
860
  result = utils.SequenceToDict(fields, key=_FieldDictKey)
861

    
862
  for alias, target in aliases:
863
    assert alias not in result, "Alias %s overrides an existing field" % alias
864
    assert target in result, "Missing target %s for alias %s" % (target, alias)
865
    (fdef, k, flags, fn) = result[target]
866
    fdef = fdef.Copy()
867
    fdef.name = alias
868
    result[alias] = (fdef, k, flags, fn)
869

    
870
  assert len(result) == len(fields) + len(aliases)
871
  assert compat.all(name == fdef.name
872
                    for (name, (fdef, _, _, _)) in result.items())
873

    
874
  return result
875

    
876

    
877
def GetQueryResponse(query, ctx, sort_by_name=True):
878
  """Prepares the response for a query.
879

880
  @type query: L{Query}
881
  @param ctx: Data container, see L{Query.Query}
882
  @type sort_by_name: boolean
883
  @param sort_by_name: Whether to sort by name or keep the input data's
884
    ordering
885

886
  """
887
  return objects.QueryResponse(data=query.Query(ctx, sort_by_name=sort_by_name),
888
                               fields=query.GetFields()).ToDict()
889

    
890

    
891
def QueryFields(fielddefs, selected):
892
  """Returns list of available fields.
893

894
  @type fielddefs: dict
895
  @param fielddefs: Field definitions
896
  @type selected: list of strings
897
  @param selected: List of selected fields
898
  @return: List of L{objects.QueryFieldDefinition}
899

900
  """
901
  if selected is None:
902
    # Client requests all fields, sort by name
903
    fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
904
                           key=operator.attrgetter("name"))
905
  else:
906
    # Keep order as requested by client
907
    fdefs = Query(fielddefs, selected).GetFields()
908

    
909
  return objects.QueryFieldsResponse(fields=fdefs).ToDict()
910

    
911

    
912
def _MakeField(name, title, kind, doc):
913
  """Wrapper for creating L{objects.QueryFieldDefinition} instances.
914

915
  @param name: Field name as a regular expression
916
  @param title: Human-readable title
917
  @param kind: Field type
918
  @param doc: Human-readable description
919

920
  """
921
  return objects.QueryFieldDefinition(name=name, title=title, kind=kind,
922
                                      doc=doc)
923

    
924

    
925
def _StaticValueInner(value, ctx, _): # pylint: disable=W0613
926
  """Returns a static value.
927

928
  """
929
  return value
930

    
931

    
932
def _StaticValue(value):
933
  """Prepares a function to return a static value.
934

935
  """
936
  return compat.partial(_StaticValueInner, value)
937

    
938

    
939
def _GetNodeRole(node, master_name):
940
  """Determine node role.
941

942
  @type node: L{objects.Node}
943
  @param node: Node object
944
  @type master_name: string
945
  @param master_name: Master node name
946

947
  """
948
  if node.name == master_name:
949
    return constants.NR_MASTER
950
  elif node.master_candidate:
951
    return constants.NR_MCANDIDATE
952
  elif node.drained:
953
    return constants.NR_DRAINED
954
  elif node.offline:
955
    return constants.NR_OFFLINE
956
  else:
957
    return constants.NR_REGULAR
958

    
959

    
960
def _GetItemAttr(attr):
961
  """Returns a field function to return an attribute of the item.
962

963
  @param attr: Attribute name
964

965
  """
966
  getter = operator.attrgetter(attr)
967
  return lambda _, item: getter(item)
968

    
969

    
970
def _GetNDParam(name):
971
  """Return a field function to return an ND parameter out of the context.
972

973
  """
974
  def _helper(ctx, _):
975
    if ctx.ndparams is None:
976
      return _FS_UNAVAIL
977
    else:
978
      return ctx.ndparams.get(name, None)
979
  return _helper
980

    
981

    
982
def _BuildNDFields(is_group):
983
  """Builds all the ndparam fields.
984

985
  @param is_group: whether this is called at group or node level
986

987
  """
988
  if is_group:
989
    field_kind = GQ_CONFIG
990
  else:
991
    field_kind = NQ_GROUP
992
  return [(_MakeField("ndp/%s" % name,
993
                      constants.NDS_PARAMETER_TITLES.get(name,
994
                                                         "ndp/%s" % name),
995
                      _VTToQFT[kind], "The \"%s\" node parameter" % name),
996
           field_kind, 0, _GetNDParam(name))
997
          for name, kind in constants.NDS_PARAMETER_TYPES.items()]
998

    
999

    
1000
def _ConvWrapInner(convert, fn, ctx, item):
1001
  """Wrapper for converting values.
1002

1003
  @param convert: Conversion function receiving value as single parameter
1004
  @param fn: Retrieval function
1005

1006
  """
1007
  value = fn(ctx, item)
1008

    
1009
  # Is the value an abnormal status?
1010
  if compat.any(value is fs for fs in _FS_ALL):
1011
    # Return right away
1012
    return value
1013

    
1014
  # TODO: Should conversion function also receive context, item or both?
1015
  return convert(value)
1016

    
1017

    
1018
def _ConvWrap(convert, fn):
1019
  """Convenience wrapper for L{_ConvWrapInner}.
1020

1021
  @param convert: Conversion function receiving value as single parameter
1022
  @param fn: Retrieval function
1023

1024
  """
1025
  return compat.partial(_ConvWrapInner, convert, fn)
1026

    
1027

    
1028
def _GetItemTimestamp(getter):
1029
  """Returns function for getting timestamp of item.
1030

1031
  @type getter: callable
1032
  @param getter: Function to retrieve timestamp attribute
1033

1034
  """
1035
  def fn(_, item):
1036
    """Returns a timestamp of item.
1037

1038
    """
1039
    timestamp = getter(item)
1040
    if timestamp is None:
1041
      # Old configs might not have all timestamps
1042
      return _FS_UNAVAIL
1043
    else:
1044
      return timestamp
1045

    
1046
  return fn
1047

    
1048

    
1049
def _GetItemTimestampFields(datatype):
1050
  """Returns common timestamp fields.
1051

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

1054
  """
1055
  return [
1056
    (_MakeField("ctime", "CTime", QFT_TIMESTAMP, "Creation timestamp"),
1057
     datatype, 0, _GetItemTimestamp(operator.attrgetter("ctime"))),
1058
    (_MakeField("mtime", "MTime", QFT_TIMESTAMP, "Modification timestamp"),
1059
     datatype, 0, _GetItemTimestamp(operator.attrgetter("mtime"))),
1060
    ]
1061

    
1062

    
1063
class NodeQueryData:
1064
  """Data container for node data queries.
1065

1066
  """
1067
  def __init__(self, nodes, live_data, master_name, node_to_primary,
1068
               node_to_secondary, groups, oob_support, cluster):
1069
    """Initializes this class.
1070

1071
    """
1072
    self.nodes = nodes
1073
    self.live_data = live_data
1074
    self.master_name = master_name
1075
    self.node_to_primary = node_to_primary
1076
    self.node_to_secondary = node_to_secondary
1077
    self.groups = groups
1078
    self.oob_support = oob_support
1079
    self.cluster = cluster
1080

    
1081
    # Used for individual rows
1082
    self.curlive_data = None
1083
    self.ndparams = None
1084

    
1085
  def __iter__(self):
1086
    """Iterate over all nodes.
1087

1088
    This function has side-effects and only one instance of the resulting
1089
    generator should be used at a time.
1090

1091
    """
1092
    for node in self.nodes:
1093
      group = self.groups.get(node.group, None)
1094
      if group is None:
1095
        self.ndparams = None
1096
      else:
1097
        self.ndparams = self.cluster.FillND(node, group)
1098
      if self.live_data:
1099
        self.curlive_data = self.live_data.get(node.name, None)
1100
      else:
1101
        self.curlive_data = None
1102
      yield node
1103

    
1104

    
1105
#: Fields that are direct attributes of an L{objects.Node} object
1106
_NODE_SIMPLE_FIELDS = {
1107
  "drained": ("Drained", QFT_BOOL, 0, "Whether node is drained"),
1108
  "master_candidate": ("MasterC", QFT_BOOL, 0,
1109
                       "Whether node is a master candidate"),
1110
  "master_capable": ("MasterCapable", QFT_BOOL, 0,
1111
                     "Whether node can become a master candidate"),
1112
  "name": ("Node", QFT_TEXT, QFF_HOSTNAME, "Node name"),
1113
  "offline": ("Offline", QFT_BOOL, 0, "Whether node is marked offline"),
1114
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Node"),
1115
  "uuid": ("UUID", QFT_TEXT, 0, "Node UUID"),
1116
  "vm_capable": ("VMCapable", QFT_BOOL, 0, "Whether node can host instances"),
1117
  }
1118

    
1119

    
1120
#: Fields requiring talking to the node
1121
# Note that none of these are available for non-vm_capable nodes
1122
_NODE_LIVE_FIELDS = {
1123
  "bootid": ("BootID", QFT_TEXT, "bootid",
1124
             "Random UUID renewed for each system reboot, can be used"
1125
             " for detecting reboots by tracking changes"),
1126
  "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes",
1127
             "Number of NUMA domains on node (if exported by hypervisor)"),
1128
  "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets",
1129
               "Number of physical CPU sockets (if exported by hypervisor)"),
1130
  "ctotal": ("CTotal", QFT_NUMBER, "cpu_total", "Number of logical processors"),
1131
  "dfree": ("DFree", QFT_UNIT, "vg_free",
1132
            "Available disk space in volume group"),
1133
  "dtotal": ("DTotal", QFT_UNIT, "vg_size",
1134
             "Total disk space in volume group used for instance disk"
1135
             " allocation"),
1136
  "mfree": ("MFree", QFT_UNIT, "memory_free",
1137
            "Memory available for instance allocations"),
1138
  "mnode": ("MNode", QFT_UNIT, "memory_dom0",
1139
            "Amount of memory used by node (dom0 for Xen)"),
1140
  "mtotal": ("MTotal", QFT_UNIT, "memory_total",
1141
             "Total amount of memory of physical machine"),
1142
  }
1143

    
1144

    
1145
def _GetGroup(cb):
1146
  """Build function for calling another function with an node group.
1147

1148
  @param cb: The callback to be called with the nodegroup
1149

1150
  """
1151
  def fn(ctx, node):
1152
    """Get group data for a node.
1153

1154
    @type ctx: L{NodeQueryData}
1155
    @type inst: L{objects.Node}
1156
    @param inst: Node object
1157

1158
    """
1159
    ng = ctx.groups.get(node.group, None)
1160
    if ng is None:
1161
      # Nodes always have a group, or the configuration is corrupt
1162
      return _FS_UNAVAIL
1163

    
1164
    return cb(ctx, node, ng)
1165

    
1166
  return fn
1167

    
1168

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

1172
  @type ctx: L{NodeQueryData}
1173
  @type node: L{objects.Node}
1174
  @param node: Node object
1175
  @type ng: L{objects.NodeGroup}
1176
  @param ng: The node group this node belongs to
1177

1178
  """
1179
  return ng.name
1180

    
1181

    
1182
def _GetNodePower(ctx, node):
1183
  """Returns the node powered state
1184

1185
  @type ctx: L{NodeQueryData}
1186
  @type node: L{objects.Node}
1187
  @param node: Node object
1188

1189
  """
1190
  if ctx.oob_support[node.name]:
1191
    return node.powered
1192

    
1193
  return _FS_UNAVAIL
1194

    
1195

    
1196
def _GetNdParams(ctx, node, ng):
1197
  """Returns the ndparams for this node.
1198

1199
  @type ctx: L{NodeQueryData}
1200
  @type node: L{objects.Node}
1201
  @param node: Node object
1202
  @type ng: L{objects.NodeGroup}
1203
  @param ng: The node group this node belongs to
1204

1205
  """
1206
  return ctx.cluster.SimpleFillND(ng.FillND(node))
1207

    
1208

    
1209
def _GetLiveNodeField(field, kind, ctx, node):
1210
  """Gets the value of a "live" field from L{NodeQueryData}.
1211

1212
  @param field: Live field name
1213
  @param kind: Data kind, one of L{constants.QFT_ALL}
1214
  @type ctx: L{NodeQueryData}
1215
  @type node: L{objects.Node}
1216
  @param node: Node object
1217

1218
  """
1219
  if node.offline:
1220
    return _FS_OFFLINE
1221

    
1222
  if not node.vm_capable:
1223
    return _FS_UNAVAIL
1224

    
1225
  if not ctx.curlive_data:
1226
    return _FS_NODATA
1227

    
1228
  try:
1229
    value = ctx.curlive_data[field]
1230
  except KeyError:
1231
    return _FS_UNAVAIL
1232

    
1233
  if kind == QFT_TEXT:
1234
    return value
1235

    
1236
  assert kind in (QFT_NUMBER, QFT_UNIT)
1237

    
1238
  # Try to convert into number
1239
  try:
1240
    return int(value)
1241
  except (ValueError, TypeError):
1242
    logging.exception("Failed to convert node field '%s' (value %r) to int",
1243
                      value, field)
1244
    return _FS_UNAVAIL
1245

    
1246

    
1247
def _GetNodeHvState(_, node):
1248
  """Converts node's hypervisor state for query result.
1249

1250
  """
1251
  hv_state = node.hv_state
1252

    
1253
  if hv_state is None:
1254
    return _FS_UNAVAIL
1255

    
1256
  return dict((name, value.ToDict()) for (name, value) in hv_state.items())
1257

    
1258

    
1259
def _GetNodeDiskState(_, node):
1260
  """Converts node's disk state for query result.
1261

1262
  """
1263
  disk_state = node.disk_state
1264

    
1265
  if disk_state is None:
1266
    return _FS_UNAVAIL
1267

    
1268
  return dict((disk_kind, dict((name, value.ToDict())
1269
                               for (name, value) in kind_state.items()))
1270
              for (disk_kind, kind_state) in disk_state.items())
1271

    
1272

    
1273
def _BuildNodeFields():
1274
  """Builds list of fields for node queries.
1275

1276
  """
1277
  fields = [
1278
    (_MakeField("pip", "PrimaryIP", QFT_TEXT, "Primary IP address"),
1279
     NQ_CONFIG, 0, _GetItemAttr("primary_ip")),
1280
    (_MakeField("sip", "SecondaryIP", QFT_TEXT, "Secondary IP address"),
1281
     NQ_CONFIG, 0, _GetItemAttr("secondary_ip")),
1282
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0,
1283
     lambda ctx, node: list(node.GetTags())),
1284
    (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"),
1285
     NQ_CONFIG, 0, lambda ctx, node: node.name == ctx.master_name),
1286
    (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0,
1287
     _GetGroup(_GetNodeGroup)),
1288
    (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"),
1289
     NQ_CONFIG, 0, _GetItemAttr("group")),
1290
    (_MakeField("powered", "Powered", QFT_BOOL,
1291
                "Whether node is thought to be powered on"),
1292
     NQ_OOB, 0, _GetNodePower),
1293
    (_MakeField("ndparams", "NodeParameters", QFT_OTHER,
1294
                "Merged node parameters"),
1295
     NQ_GROUP, 0, _GetGroup(_GetNdParams)),
1296
    (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER,
1297
                "Custom node parameters"),
1298
      NQ_GROUP, 0, _GetItemAttr("ndparams")),
1299
    (_MakeField("hv_state", "HypervisorState", QFT_OTHER, "Hypervisor state"),
1300
     NQ_CONFIG, 0, _GetNodeHvState),
1301
    (_MakeField("disk_state", "DiskState", QFT_OTHER, "Disk state"),
1302
     NQ_CONFIG, 0, _GetNodeDiskState),
1303
    ]
1304

    
1305
  fields.extend(_BuildNDFields(False))
1306

    
1307
  # Node role
1308
  role_values = (constants.NR_MASTER, constants.NR_MCANDIDATE,
1309
                 constants.NR_REGULAR, constants.NR_DRAINED,
1310
                 constants.NR_OFFLINE)
1311
  role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate,"
1312
              " \"%s\" for regular, \"%s\" for drained, \"%s\" for offline" %
1313
              role_values)
1314
  fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
1315
                 lambda ctx, node: _GetNodeRole(node, ctx.master_name)))
1316
  assert set(role_values) == constants.NR_ALL
1317

    
1318
  def _GetLength(getter):
1319
    return lambda ctx, node: len(getter(ctx)[node.name])
1320

    
1321
  def _GetList(getter):
1322
    return lambda ctx, node: list(getter(ctx)[node.name])
1323

    
1324
  # Add fields operating on instance lists
1325
  for prefix, titleprefix, docword, getter in \
1326
      [("p", "Pri", "primary", operator.attrgetter("node_to_primary")),
1327
       ("s", "Sec", "secondary", operator.attrgetter("node_to_secondary"))]:
1328
    # TODO: Allow filterting by hostname in list
1329
    fields.extend([
1330
      (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER,
1331
                  "Number of instances with this node as %s" % docword),
1332
       NQ_INST, 0, _GetLength(getter)),
1333
      (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
1334
                  QFT_OTHER,
1335
                  "List of instances with this node as %s" % docword),
1336
       NQ_INST, 0, _GetList(getter)),
1337
      ])
1338

    
1339
  # Add simple fields
1340
  fields.extend([
1341
    (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1342
    for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()
1343
    ])
1344

    
1345
  # Add fields requiring live data
1346
  fields.extend([
1347
    (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1348
     compat.partial(_GetLiveNodeField, nfield, kind))
1349
    for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()
1350
    ])
1351

    
1352
  # Add timestamps
1353
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1354

    
1355
  return _PrepareFieldList(fields, [])
1356

    
1357

    
1358
class InstanceQueryData:
1359
  """Data container for instance data queries.
1360

1361
  """
1362
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
1363
               live_data, wrongnode_inst, console, nodes, groups):
1364
    """Initializes this class.
1365

1366
    @param instances: List of instance objects
1367
    @param cluster: Cluster object
1368
    @type disk_usage: dict; instance name as key
1369
    @param disk_usage: Per-instance disk usage
1370
    @type offline_nodes: list of strings
1371
    @param offline_nodes: List of offline nodes
1372
    @type bad_nodes: list of strings
1373
    @param bad_nodes: List of faulty nodes
1374
    @type live_data: dict; instance name as key
1375
    @param live_data: Per-instance live data
1376
    @type wrongnode_inst: set
1377
    @param wrongnode_inst: Set of instances running on wrong node(s)
1378
    @type console: dict; instance name as key
1379
    @param console: Per-instance console information
1380
    @type nodes: dict; node name as key
1381
    @param nodes: Node objects
1382

1383
    """
1384
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
1385
           "Offline nodes not included in bad nodes"
1386
    assert not (set(live_data.keys()) & set(bad_nodes)), \
1387
           "Found live data for bad or offline nodes"
1388

    
1389
    self.instances = instances
1390
    self.cluster = cluster
1391
    self.disk_usage = disk_usage
1392
    self.offline_nodes = offline_nodes
1393
    self.bad_nodes = bad_nodes
1394
    self.live_data = live_data
1395
    self.wrongnode_inst = wrongnode_inst
1396
    self.console = console
1397
    self.nodes = nodes
1398
    self.groups = groups
1399

    
1400
    # Used for individual rows
1401
    self.inst_hvparams = None
1402
    self.inst_beparams = None
1403
    self.inst_osparams = None
1404
    self.inst_nicparams = None
1405

    
1406
  def __iter__(self):
1407
    """Iterate over all instances.
1408

1409
    This function has side-effects and only one instance of the resulting
1410
    generator should be used at a time.
1411

1412
    """
1413
    for inst in self.instances:
1414
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1415
      self.inst_beparams = self.cluster.FillBE(inst)
1416
      self.inst_osparams = self.cluster.SimpleFillOS(inst.os, inst.osparams)
1417
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1418
                             for nic in inst.nics]
1419

    
1420
      yield inst
1421

    
1422

    
1423
def _GetInstOperState(ctx, inst):
1424
  """Get instance's operational status.
1425

1426
  @type ctx: L{InstanceQueryData}
1427
  @type inst: L{objects.Instance}
1428
  @param inst: Instance object
1429

1430
  """
1431
  # Can't use RS_OFFLINE here as it would describe the instance to
1432
  # be offline when we actually don't know due to missing data
1433
  if inst.primary_node in ctx.bad_nodes:
1434
    return _FS_NODATA
1435
  else:
1436
    return bool(ctx.live_data.get(inst.name))
1437

    
1438

    
1439
def _GetInstLiveData(name):
1440
  """Build function for retrieving live data.
1441

1442
  @type name: string
1443
  @param name: Live data field name
1444

1445
  """
1446
  def fn(ctx, inst):
1447
    """Get live data for an instance.
1448

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

1453
    """
1454
    if (inst.primary_node in ctx.bad_nodes or
1455
        inst.primary_node in ctx.offline_nodes):
1456
      # Can't use RS_OFFLINE here as it would describe the instance to be
1457
      # offline when we actually don't know due to missing data
1458
      return _FS_NODATA
1459

    
1460
    if inst.name in ctx.live_data:
1461
      data = ctx.live_data[inst.name]
1462
      if name in data:
1463
        return data[name]
1464

    
1465
    return _FS_UNAVAIL
1466

    
1467
  return fn
1468

    
1469

    
1470
def _GetInstStatus(ctx, inst):
1471
  """Get instance status.
1472

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

1477
  """
1478
  if inst.primary_node in ctx.offline_nodes:
1479
    return constants.INSTST_NODEOFFLINE
1480

    
1481
  if inst.primary_node in ctx.bad_nodes:
1482
    return constants.INSTST_NODEDOWN
1483

    
1484
  if bool(ctx.live_data.get(inst.name)):
1485
    if inst.name in ctx.wrongnode_inst:
1486
      return constants.INSTST_WRONGNODE
1487
    elif inst.admin_state == constants.ADMINST_UP:
1488
      return constants.INSTST_RUNNING
1489
    else:
1490
      return constants.INSTST_ERRORUP
1491

    
1492
  if inst.admin_state == constants.ADMINST_UP:
1493
    return constants.INSTST_ERRORDOWN
1494
  elif inst.admin_state == constants.ADMINST_DOWN:
1495
    return constants.INSTST_ADMINDOWN
1496

    
1497
  return constants.INSTST_ADMINOFFLINE
1498

    
1499

    
1500
def _GetInstDiskSize(index):
1501
  """Build function for retrieving disk size.
1502

1503
  @type index: int
1504
  @param index: Disk index
1505

1506
  """
1507
  def fn(_, inst):
1508
    """Get size of a disk.
1509

1510
    @type inst: L{objects.Instance}
1511
    @param inst: Instance object
1512

1513
    """
1514
    try:
1515
      return inst.disks[index].size
1516
    except IndexError:
1517
      return _FS_UNAVAIL
1518

    
1519
  return fn
1520

    
1521

    
1522
def _GetInstNic(index, cb):
1523
  """Build function for calling another function with an instance NIC.
1524

1525
  @type index: int
1526
  @param index: NIC index
1527
  @type cb: callable
1528
  @param cb: Callback
1529

1530
  """
1531
  def fn(ctx, inst):
1532
    """Call helper function with instance NIC.
1533

1534
    @type ctx: L{InstanceQueryData}
1535
    @type inst: L{objects.Instance}
1536
    @param inst: Instance object
1537

1538
    """
1539
    try:
1540
      nic = inst.nics[index]
1541
    except IndexError:
1542
      return _FS_UNAVAIL
1543

    
1544
    return cb(ctx, index, nic)
1545

    
1546
  return fn
1547

    
1548

    
1549
def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613
1550
  """Get a NIC's IP address.
1551

1552
  @type ctx: L{InstanceQueryData}
1553
  @type nic: L{objects.NIC}
1554
  @param nic: NIC object
1555

1556
  """
1557
  if nic.ip is None:
1558
    return _FS_UNAVAIL
1559
  else:
1560
    return nic.ip
1561

    
1562

    
1563
def _GetInstNicBridge(ctx, index, _):
1564
  """Get a NIC's bridge.
1565

1566
  @type ctx: L{InstanceQueryData}
1567
  @type index: int
1568
  @param index: NIC index
1569

1570
  """
1571
  assert len(ctx.inst_nicparams) >= index
1572

    
1573
  nicparams = ctx.inst_nicparams[index]
1574

    
1575
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1576
    return nicparams[constants.NIC_LINK]
1577
  else:
1578
    return _FS_UNAVAIL
1579

    
1580

    
1581
def _GetInstAllNicBridges(ctx, inst):
1582
  """Get all network bridges for an instance.
1583

1584
  @type ctx: L{InstanceQueryData}
1585
  @type inst: L{objects.Instance}
1586
  @param inst: Instance object
1587

1588
  """
1589
  assert len(ctx.inst_nicparams) == len(inst.nics)
1590

    
1591
  result = []
1592

    
1593
  for nicp in ctx.inst_nicparams:
1594
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1595
      result.append(nicp[constants.NIC_LINK])
1596
    else:
1597
      result.append(None)
1598

    
1599
  assert len(result) == len(inst.nics)
1600

    
1601
  return result
1602

    
1603

    
1604
def _GetInstNicParam(name):
1605
  """Build function for retrieving a NIC parameter.
1606

1607
  @type name: string
1608
  @param name: Parameter name
1609

1610
  """
1611
  def fn(ctx, index, _):
1612
    """Get a NIC's bridge.
1613

1614
    @type ctx: L{InstanceQueryData}
1615
    @type inst: L{objects.Instance}
1616
    @param inst: Instance object
1617
    @type nic: L{objects.NIC}
1618
    @param nic: NIC object
1619

1620
    """
1621
    assert len(ctx.inst_nicparams) >= index
1622
    return ctx.inst_nicparams[index][name]
1623

    
1624
  return fn
1625

    
1626

    
1627
def _GetInstanceNetworkFields():
1628
  """Get instance fields involving network interfaces.
1629

1630
  @return: Tuple containing list of field definitions used as input for
1631
    L{_PrepareFieldList} and a list of aliases
1632

1633
  """
1634
  nic_mac_fn = lambda ctx, _, nic: nic.mac
1635
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1636
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1637

    
1638
  fields = [
1639
    # All NICs
1640
    (_MakeField("nic.count", "NICs", QFT_NUMBER,
1641
                "Number of network interfaces"),
1642
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.nics)),
1643
    (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER,
1644
                "List containing each network interface's MAC address"),
1645
     IQ_CONFIG, 0, lambda ctx, inst: [nic.mac for nic in inst.nics]),
1646
    (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
1647
                "List containing each network interface's IP address"),
1648
     IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
1649
    (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
1650
                "List containing each network interface's mode"), IQ_CONFIG, 0,
1651
     lambda ctx, inst: [nicp[constants.NIC_MODE]
1652
                        for nicp in ctx.inst_nicparams]),
1653
    (_MakeField("nic.links", "NIC_links", QFT_OTHER,
1654
                "List containing each network interface's link"), IQ_CONFIG, 0,
1655
     lambda ctx, inst: [nicp[constants.NIC_LINK]
1656
                        for nicp in ctx.inst_nicparams]),
1657
    (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
1658
                "List containing each network interface's bridge"),
1659
     IQ_CONFIG, 0, _GetInstAllNicBridges),
1660
    ]
1661

    
1662
  # NICs by number
1663
  for i in range(constants.MAX_NICS):
1664
    numtext = utils.FormatOrdinal(i + 1)
1665
    fields.extend([
1666
      (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT,
1667
                  "IP address of %s network interface" % numtext),
1668
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)),
1669
      (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
1670
                  "MAC address of %s network interface" % numtext),
1671
       IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
1672
      (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
1673
                  "Mode of %s network interface" % numtext),
1674
       IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
1675
      (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT,
1676
                  "Link of %s network interface" % numtext),
1677
       IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)),
1678
      (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
1679
                  "Bridge of %s network interface" % numtext),
1680
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
1681
      ])
1682

    
1683
  aliases = [
1684
    # Legacy fields for first NIC
1685
    ("ip", "nic.ip/0"),
1686
    ("mac", "nic.mac/0"),
1687
    ("bridge", "nic.bridge/0"),
1688
    ("nic_mode", "nic.mode/0"),
1689
    ("nic_link", "nic.link/0"),
1690
    ]
1691

    
1692
  return (fields, aliases)
1693

    
1694

    
1695
def _GetInstDiskUsage(ctx, inst):
1696
  """Get disk usage for an instance.
1697

1698
  @type ctx: L{InstanceQueryData}
1699
  @type inst: L{objects.Instance}
1700
  @param inst: Instance object
1701

1702
  """
1703
  usage = ctx.disk_usage[inst.name]
1704

    
1705
  if usage is None:
1706
    usage = 0
1707

    
1708
  return usage
1709

    
1710

    
1711
def _GetInstanceConsole(ctx, inst):
1712
  """Get console information for instance.
1713

1714
  @type ctx: L{InstanceQueryData}
1715
  @type inst: L{objects.Instance}
1716
  @param inst: Instance object
1717

1718
  """
1719
  consinfo = ctx.console[inst.name]
1720

    
1721
  if consinfo is None:
1722
    return _FS_UNAVAIL
1723

    
1724
  return consinfo
1725

    
1726

    
1727
def _GetInstanceDiskFields():
1728
  """Get instance fields involving disks.
1729

1730
  @return: List of field definitions used as input for L{_PrepareFieldList}
1731

1732
  """
1733
  fields = [
1734
    (_MakeField("disk_usage", "DiskUsage", QFT_UNIT,
1735
                "Total disk space used by instance on each of its nodes;"
1736
                " this is not the disk size visible to the instance, but"
1737
                " the usage on the node"),
1738
     IQ_DISKUSAGE, 0, _GetInstDiskUsage),
1739
    (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"),
1740
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
1741
    (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
1742
     IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
1743
    ]
1744

    
1745
  # Disks by number
1746
  fields.extend([
1747
    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1748
                "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
1749
     IQ_CONFIG, 0, _GetInstDiskSize(i))
1750
    for i in range(constants.MAX_DISKS)
1751
    ])
1752

    
1753
  return fields
1754

    
1755

    
1756
def _GetInstanceParameterFields():
1757
  """Get instance fields involving parameters.
1758

1759
  @return: List of field definitions used as input for L{_PrepareFieldList}
1760

1761
  """
1762
  fields = [
1763
    # Filled parameters
1764
    (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
1765
                "Hypervisor parameters (merged)"),
1766
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
1767
    (_MakeField("beparams", "BackendParameters", QFT_OTHER,
1768
                "Backend parameters (merged)"),
1769
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
1770
    (_MakeField("osparams", "OpSysParameters", QFT_OTHER,
1771
                "Operating system parameters (merged)"),
1772
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_osparams),
1773

    
1774
    # Unfilled parameters
1775
    (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
1776
                "Custom hypervisor parameters"),
1777
     IQ_CONFIG, 0, _GetItemAttr("hvparams")),
1778
    (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
1779
                "Custom backend parameters",),
1780
     IQ_CONFIG, 0, _GetItemAttr("beparams")),
1781
    (_MakeField("custom_osparams", "CustomOpSysParameters", QFT_OTHER,
1782
                "Custom operating system parameters",),
1783
     IQ_CONFIG, 0, _GetItemAttr("osparams")),
1784
    (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
1785
                "Custom network interface parameters"),
1786
     IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1787
    ]
1788

    
1789
  # HV params
1790
  def _GetInstHvParam(name):
1791
    return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1792

    
1793
  fields.extend([
1794
    (_MakeField("hv/%s" % name,
1795
                constants.HVS_PARAMETER_TITLES.get(name, "hv/%s" % name),
1796
                _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1797
     IQ_CONFIG, 0, _GetInstHvParam(name))
1798
    for name, kind in constants.HVS_PARAMETER_TYPES.items()
1799
    if name not in constants.HVC_GLOBALS
1800
    ])
1801

    
1802
  # BE params
1803
  def _GetInstBeParam(name):
1804
    return lambda ctx, _: ctx.inst_beparams.get(name, None)
1805

    
1806
  fields.extend([
1807
    (_MakeField("be/%s" % name,
1808
                constants.BES_PARAMETER_TITLES.get(name, "be/%s" % name),
1809
                _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1810
     IQ_CONFIG, 0, _GetInstBeParam(name))
1811
    for name, kind in constants.BES_PARAMETER_TYPES.items()
1812
    ])
1813

    
1814
  return fields
1815

    
1816

    
1817
_INST_SIMPLE_FIELDS = {
1818
  "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"),
1819
  "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"),
1820
  "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"),
1821
  # Depending on the hypervisor, the port can be None
1822
  "network_port": ("Network_port", QFT_OTHER, 0,
1823
                   "Instance network port if available (e.g. for VNC console)"),
1824
  "os": ("OS", QFT_TEXT, 0, "Operating system"),
1825
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"),
1826
  "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"),
1827
  }
1828

    
1829

    
1830
def _GetInstNodeGroup(ctx, default, node_name):
1831
  """Gets group UUID of an instance node.
1832

1833
  @type ctx: L{InstanceQueryData}
1834
  @param default: Default value
1835
  @type node_name: string
1836
  @param node_name: Node name
1837

1838
  """
1839
  try:
1840
    node = ctx.nodes[node_name]
1841
  except KeyError:
1842
    return default
1843
  else:
1844
    return node.group
1845

    
1846

    
1847
def _GetInstNodeGroupName(ctx, default, node_name):
1848
  """Gets group name of an instance node.
1849

1850
  @type ctx: L{InstanceQueryData}
1851
  @param default: Default value
1852
  @type node_name: string
1853
  @param node_name: Node name
1854

1855
  """
1856
  try:
1857
    node = ctx.nodes[node_name]
1858
  except KeyError:
1859
    return default
1860

    
1861
  try:
1862
    group = ctx.groups[node.group]
1863
  except KeyError:
1864
    return default
1865

    
1866
  return group.name
1867

    
1868

    
1869
def _BuildInstanceFields():
1870
  """Builds list of fields for instance queries.
1871

1872
  """
1873
  fields = [
1874
    (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
1875
     IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")),
1876
    (_MakeField("pnode.group", "PrimaryNodeGroup", QFT_TEXT,
1877
                "Primary node's group"),
1878
     IQ_NODES, 0,
1879
     lambda ctx, inst: _GetInstNodeGroupName(ctx, _FS_UNAVAIL,
1880
                                             inst.primary_node)),
1881
    (_MakeField("pnode.group.uuid", "PrimaryNodeGroupUUID", QFT_TEXT,
1882
                "Primary node's group UUID"),
1883
     IQ_NODES, 0,
1884
     lambda ctx, inst: _GetInstNodeGroup(ctx, _FS_UNAVAIL, inst.primary_node)),
1885
    # TODO: Allow filtering by secondary node as hostname
1886
    (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
1887
                "Secondary nodes; usually this will just be one node"),
1888
     IQ_CONFIG, 0, lambda ctx, inst: list(inst.secondary_nodes)),
1889
    (_MakeField("snodes.group", "SecondaryNodesGroups", QFT_OTHER,
1890
                "Node groups of secondary nodes"),
1891
     IQ_NODES, 0,
1892
     lambda ctx, inst: map(compat.partial(_GetInstNodeGroupName, ctx, None),
1893
                           inst.secondary_nodes)),
1894
    (_MakeField("snodes.group.uuid", "SecondaryNodesGroupsUUID", QFT_OTHER,
1895
                "Node group UUIDs of secondary nodes"),
1896
     IQ_NODES, 0,
1897
     lambda ctx, inst: map(compat.partial(_GetInstNodeGroup, ctx, None),
1898
                           inst.secondary_nodes)),
1899
    (_MakeField("admin_state", "InstanceState", QFT_TEXT,
1900
                "Desired state of instance"),
1901
     IQ_CONFIG, 0, _GetItemAttr("admin_state")),
1902
    (_MakeField("admin_up", "Autostart", QFT_BOOL,
1903
                "Desired state of instance"),
1904
     IQ_CONFIG, 0, lambda ctx, inst: inst.admin_state == constants.ADMINST_UP),
1905
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
1906
     lambda ctx, inst: list(inst.GetTags())),
1907
    (_MakeField("console", "Console", QFT_OTHER,
1908
                "Instance console information"), IQ_CONSOLE, 0,
1909
     _GetInstanceConsole),
1910
    ]
1911

    
1912
  # Add simple fields
1913
  fields.extend([
1914
    (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1915
    for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()
1916
    ])
1917

    
1918
  # Fields requiring talking to the node
1919
  fields.extend([
1920
    (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
1921
     IQ_LIVE, 0, _GetInstOperState),
1922
    (_MakeField("oper_ram", "Memory", QFT_UNIT,
1923
                "Actual memory usage as seen by hypervisor"),
1924
     IQ_LIVE, 0, _GetInstLiveData("memory")),
1925
    (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
1926
                "Actual number of VCPUs as seen by hypervisor"),
1927
     IQ_LIVE, 0, _GetInstLiveData("vcpus")),
1928
    ])
1929

    
1930
  # Status field
1931
  status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
1932
                   constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
1933
                   constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
1934
                   constants.INSTST_NODEOFFLINE, constants.INSTST_ADMINOFFLINE)
1935
  status_doc = ("Instance status; \"%s\" if instance is set to be running"
1936
                " and actually is, \"%s\" if instance is stopped and"
1937
                " is not running, \"%s\" if instance running, but not on its"
1938
                " designated primary node, \"%s\" if instance should be"
1939
                " stopped, but is actually running, \"%s\" if instance should"
1940
                " run, but doesn't, \"%s\" if instance's primary node is down,"
1941
                " \"%s\" if instance's primary node is marked offline,"
1942
                " \"%s\" if instance is offline and does not use dynamic"
1943
                " resources" % status_values)
1944
  fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
1945
                 IQ_LIVE, 0, _GetInstStatus))
1946
  assert set(status_values) == constants.INSTST_ALL, \
1947
         "Status documentation mismatch"
1948

    
1949
  (network_fields, network_aliases) = _GetInstanceNetworkFields()
1950

    
1951
  fields.extend(network_fields)
1952
  fields.extend(_GetInstanceParameterFields())
1953
  fields.extend(_GetInstanceDiskFields())
1954
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1955

    
1956
  aliases = [
1957
    ("vcpus", "be/vcpus"),
1958
    ("be/memory", "be/maxmem"),
1959
    ("sda_size", "disk.size/0"),
1960
    ("sdb_size", "disk.size/1"),
1961
    ] + network_aliases
1962

    
1963
  return _PrepareFieldList(fields, aliases)
1964

    
1965

    
1966
class LockQueryData:
1967
  """Data container for lock data queries.
1968

1969
  """
1970
  def __init__(self, lockdata):
1971
    """Initializes this class.
1972

1973
    """
1974
    self.lockdata = lockdata
1975

    
1976
  def __iter__(self):
1977
    """Iterate over all locks.
1978

1979
    """
1980
    return iter(self.lockdata)
1981

    
1982

    
1983
def _GetLockOwners(_, data):
1984
  """Returns a sorted list of a lock's current owners.
1985

1986
  """
1987
  (_, _, owners, _) = data
1988

    
1989
  if owners:
1990
    owners = utils.NiceSort(owners)
1991

    
1992
  return owners
1993

    
1994

    
1995
def _GetLockPending(_, data):
1996
  """Returns a sorted list of a lock's pending acquires.
1997

1998
  """
1999
  (_, _, _, pending) = data
2000

    
2001
  if pending:
2002
    pending = [(mode, utils.NiceSort(names))
2003
               for (mode, names) in pending]
2004

    
2005
  return pending
2006

    
2007

    
2008
def _BuildLockFields():
2009
  """Builds list of fields for lock queries.
2010

2011
  """
2012
  return _PrepareFieldList([
2013
    # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
2014
    (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
2015
     lambda ctx, (name, mode, owners, pending): name),
2016
    (_MakeField("mode", "Mode", QFT_OTHER,
2017
                "Mode in which the lock is currently acquired"
2018
                " (exclusive or shared)"),
2019
     LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
2020
    (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
2021
     LQ_OWNER, 0, _GetLockOwners),
2022
    (_MakeField("pending", "Pending", QFT_OTHER,
2023
                "Threads waiting for the lock"),
2024
     LQ_PENDING, 0, _GetLockPending),
2025
    ], [])
2026

    
2027

    
2028
class GroupQueryData:
2029
  """Data container for node group data queries.
2030

2031
  """
2032
  def __init__(self, cluster, groups, group_to_nodes, group_to_instances,
2033
               want_diskparams):
2034
    """Initializes this class.
2035

2036
    @param cluster: Cluster object
2037
    @param groups: List of node group objects
2038
    @type group_to_nodes: dict; group UUID as key
2039
    @param group_to_nodes: Per-group list of nodes
2040
    @type group_to_instances: dict; group UUID as key
2041
    @param group_to_instances: Per-group list of (primary) instances
2042
    @type want_diskparams: bool
2043
    @param want_diskparams: Whether diskparamters should be calculated
2044

2045
    """
2046
    self.groups = groups
2047
    self.group_to_nodes = group_to_nodes
2048
    self.group_to_instances = group_to_instances
2049
    self.cluster = cluster
2050
    self.want_diskparams = want_diskparams
2051

    
2052
    # Used for individual rows
2053
    self.group_ipolicy = None
2054
    self.ndparams = None
2055
    self.group_dp = None
2056

    
2057
  def __iter__(self):
2058
    """Iterate over all node groups.
2059

2060
    This function has side-effects and only one instance of the resulting
2061
    generator should be used at a time.
2062

2063
    """
2064
    for group in self.groups:
2065
      self.group_ipolicy = self.cluster.SimpleFillIPolicy(group.ipolicy)
2066
      self.ndparams = self.cluster.SimpleFillND(group.ndparams)
2067
      if self.want_diskparams:
2068
        self.group_dp = self.cluster.SimpleFillDP(group.diskparams)
2069
      else:
2070
        self.group_dp = None
2071
      yield group
2072

    
2073

    
2074
_GROUP_SIMPLE_FIELDS = {
2075
  "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
2076
  "name": ("Group", QFT_TEXT, "Group name"),
2077
  "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
2078
  "uuid": ("UUID", QFT_TEXT, "Group UUID"),
2079
  }
2080

    
2081

    
2082
def _BuildGroupFields():
2083
  """Builds list of fields for node group queries.
2084

2085
  """
2086
  # Add simple fields
2087
  fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
2088
             _GetItemAttr(name))
2089
            for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
2090

    
2091
  def _GetLength(getter):
2092
    return lambda ctx, group: len(getter(ctx)[group.uuid])
2093

    
2094
  def _GetSortedList(getter):
2095
    return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
2096

    
2097
  group_to_nodes = operator.attrgetter("group_to_nodes")
2098
  group_to_instances = operator.attrgetter("group_to_instances")
2099

    
2100
  # Add fields for nodes
2101
  fields.extend([
2102
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
2103
     GQ_NODE, 0, _GetLength(group_to_nodes)),
2104
    (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
2105
     GQ_NODE, 0, _GetSortedList(group_to_nodes)),
2106
    ])
2107

    
2108
  # Add fields for instances
2109
  fields.extend([
2110
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
2111
                "Number of primary instances"),
2112
     GQ_INST, 0, _GetLength(group_to_instances)),
2113
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
2114
                "List of primary instances"),
2115
     GQ_INST, 0, _GetSortedList(group_to_instances)),
2116
    ])
2117

    
2118
  # Other fields
2119
  fields.extend([
2120
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), GQ_CONFIG, 0,
2121
     lambda ctx, group: list(group.GetTags())),
2122
    (_MakeField("ipolicy", "InstancePolicy", QFT_OTHER,
2123
                "Instance policy limitations (merged)"),
2124
     GQ_CONFIG, 0, lambda ctx, _: ctx.group_ipolicy),
2125
    (_MakeField("custom_ipolicy", "CustomInstancePolicy", QFT_OTHER,
2126
                "Custom instance policy limitations"),
2127
     GQ_CONFIG, 0, _GetItemAttr("ipolicy")),
2128
    (_MakeField("custom_ndparams", "CustomNDParams", QFT_OTHER,
2129
                "Custom node parameters"),
2130
     GQ_CONFIG, 0, _GetItemAttr("ndparams")),
2131
    (_MakeField("ndparams", "NDParams", QFT_OTHER,
2132
                "Node parameters"),
2133
     GQ_CONFIG, 0, lambda ctx, _: ctx.ndparams),
2134
    (_MakeField("diskparams", "DiskParameters", QFT_OTHER,
2135
                "Disk parameters (merged)"),
2136
     GQ_DISKPARAMS, 0, lambda ctx, _: ctx.group_dp),
2137
    (_MakeField("custom_diskparams", "CustomDiskParameters", QFT_OTHER,
2138
                "Custom disk parameters"),
2139
     GQ_CONFIG, 0, _GetItemAttr("diskparams")),
2140
    ])
2141

    
2142
  # ND parameters
2143
  fields.extend(_BuildNDFields(True))
2144

    
2145
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
2146

    
2147
  return _PrepareFieldList(fields, [])
2148

    
2149

    
2150
class OsInfo(objects.ConfigObject):
2151
  __slots__ = [
2152
    "name",
2153
    "valid",
2154
    "hidden",
2155
    "blacklisted",
2156
    "variants",
2157
    "api_versions",
2158
    "parameters",
2159
    "node_status",
2160
    ]
2161

    
2162

    
2163
def _BuildOsFields():
2164
  """Builds list of fields for operating system queries.
2165

2166
  """
2167
  fields = [
2168
    (_MakeField("name", "Name", QFT_TEXT, "Operating system name"),
2169
     None, 0, _GetItemAttr("name")),
2170
    (_MakeField("valid", "Valid", QFT_BOOL,
2171
                "Whether operating system definition is valid"),
2172
     None, 0, _GetItemAttr("valid")),
2173
    (_MakeField("hidden", "Hidden", QFT_BOOL,
2174
                "Whether operating system is hidden"),
2175
     None, 0, _GetItemAttr("hidden")),
2176
    (_MakeField("blacklisted", "Blacklisted", QFT_BOOL,
2177
                "Whether operating system is blacklisted"),
2178
     None, 0, _GetItemAttr("blacklisted")),
2179
    (_MakeField("variants", "Variants", QFT_OTHER,
2180
                "Operating system variants"),
2181
     None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("variants"))),
2182
    (_MakeField("api_versions", "ApiVersions", QFT_OTHER,
2183
                "Operating system API versions"),
2184
     None, 0, _ConvWrap(sorted, _GetItemAttr("api_versions"))),
2185
    (_MakeField("parameters", "Parameters", QFT_OTHER,
2186
                "Operating system parameters"),
2187
     None, 0, _ConvWrap(compat.partial(utils.NiceSort, key=compat.fst),
2188
                        _GetItemAttr("parameters"))),
2189
    (_MakeField("node_status", "NodeStatus", QFT_OTHER,
2190
                "Status from node"),
2191
     None, 0, _GetItemAttr("node_status")),
2192
    ]
2193

    
2194
  return _PrepareFieldList(fields, [])
2195

    
2196

    
2197
def _JobUnavailInner(fn, ctx, (job_id, job)): # pylint: disable=W0613
2198
  """Return L{_FS_UNAVAIL} if job is None.
2199

2200
  When listing specifc jobs (e.g. "gnt-job list 1 2 3"), a job may not be
2201
  found, in which case this function converts it to L{_FS_UNAVAIL}.
2202

2203
  """
2204
  if job is None:
2205
    return _FS_UNAVAIL
2206
  else:
2207
    return fn(job)
2208

    
2209

    
2210
def _JobUnavail(inner):
2211
  """Wrapper for L{_JobUnavailInner}.
2212

2213
  """
2214
  return compat.partial(_JobUnavailInner, inner)
2215

    
2216

    
2217
def _PerJobOpInner(fn, job):
2218
  """Executes a function per opcode in a job.
2219

2220
  """
2221
  return map(fn, job.ops)
2222

    
2223

    
2224
def _PerJobOp(fn):
2225
  """Wrapper for L{_PerJobOpInner}.
2226

2227
  """
2228
  return _JobUnavail(compat.partial(_PerJobOpInner, fn))
2229

    
2230

    
2231
def _JobTimestampInner(fn, job):
2232
  """Converts unavailable timestamp to L{_FS_UNAVAIL}.
2233

2234
  """
2235
  timestamp = fn(job)
2236

    
2237
  if timestamp is None:
2238
    return _FS_UNAVAIL
2239
  else:
2240
    return timestamp
2241

    
2242

    
2243
def _JobTimestamp(fn):
2244
  """Wrapper for L{_JobTimestampInner}.
2245

2246
  """
2247
  return _JobUnavail(compat.partial(_JobTimestampInner, fn))
2248

    
2249

    
2250
def _BuildJobFields():
2251
  """Builds list of fields for job queries.
2252

2253
  """
2254
  fields = [
2255
    (_MakeField("id", "ID", QFT_NUMBER, "Job ID"),
2256
     None, QFF_JOB_ID, lambda _, (job_id, job): job_id),
2257
    (_MakeField("status", "Status", QFT_TEXT, "Job status"),
2258
     None, 0, _JobUnavail(lambda job: job.CalcStatus())),
2259
    (_MakeField("priority", "Priority", QFT_NUMBER,
2260
                ("Current job priority (%s to %s)" %
2261
                 (constants.OP_PRIO_LOWEST, constants.OP_PRIO_HIGHEST))),
2262
     None, 0, _JobUnavail(lambda job: job.CalcPriority())),
2263
    (_MakeField("archived", "Archived", QFT_BOOL, "Whether job is archived"),
2264
     JQ_ARCHIVED, 0, lambda _, (job_id, job): job.archived),
2265
    (_MakeField("ops", "OpCodes", QFT_OTHER, "List of all opcodes"),
2266
     None, 0, _PerJobOp(lambda op: op.input.__getstate__())),
2267
    (_MakeField("opresult", "OpCode_result", QFT_OTHER,
2268
                "List of opcodes results"),
2269
     None, 0, _PerJobOp(operator.attrgetter("result"))),
2270
    (_MakeField("opstatus", "OpCode_status", QFT_OTHER,
2271
                "List of opcodes status"),
2272
     None, 0, _PerJobOp(operator.attrgetter("status"))),
2273
    (_MakeField("oplog", "OpCode_log", QFT_OTHER,
2274
                "List of opcode output logs"),
2275
     None, 0, _PerJobOp(operator.attrgetter("log"))),
2276
    (_MakeField("opstart", "OpCode_start", QFT_OTHER,
2277
                "List of opcode start timestamps (before acquiring locks)"),
2278
     None, 0, _PerJobOp(operator.attrgetter("start_timestamp"))),
2279
    (_MakeField("opexec", "OpCode_exec", QFT_OTHER,
2280
                "List of opcode execution start timestamps (after acquiring"
2281
                " locks)"),
2282
     None, 0, _PerJobOp(operator.attrgetter("exec_timestamp"))),
2283
    (_MakeField("opend", "OpCode_end", QFT_OTHER,
2284
                "List of opcode execution end timestamps"),
2285
     None, 0, _PerJobOp(operator.attrgetter("end_timestamp"))),
2286
    (_MakeField("oppriority", "OpCode_prio", QFT_OTHER,
2287
                "List of opcode priorities"),
2288
     None, 0, _PerJobOp(operator.attrgetter("priority"))),
2289
    (_MakeField("summary", "Summary", QFT_OTHER,
2290
                "List of per-opcode summaries"),
2291
     None, 0, _PerJobOp(lambda op: op.input.Summary())),
2292
    ]
2293

    
2294
  # Timestamp fields
2295
  for (name, attr, title, desc) in [
2296
    ("received_ts", "received_timestamp", "Received",
2297
     "Timestamp of when job was received"),
2298
    ("start_ts", "start_timestamp", "Start", "Timestamp of job start"),
2299
    ("end_ts", "end_timestamp", "End", "Timestamp of job end"),
2300
    ]:
2301
    getter = operator.attrgetter(attr)
2302
    fields.extend([
2303
      (_MakeField(name, title, QFT_OTHER,
2304
                  "%s (tuple containing seconds and microseconds)" % desc),
2305
       None, QFF_SPLIT_TIMESTAMP, _JobTimestamp(getter)),
2306
      ])
2307

    
2308
  return _PrepareFieldList(fields, [])
2309

    
2310

    
2311
def _GetExportName(_, (node_name, expname)): # pylint: disable=W0613
2312
  """Returns an export name if available.
2313

2314
  """
2315
  if expname is None:
2316
    return _FS_UNAVAIL
2317
  else:
2318
    return expname
2319

    
2320

    
2321
def _BuildExportFields():
2322
  """Builds list of fields for exports.
2323

2324
  """
2325
  fields = [
2326
    (_MakeField("node", "Node", QFT_TEXT, "Node name"),
2327
     None, QFF_HOSTNAME, lambda _, (node_name, expname): node_name),
2328
    (_MakeField("export", "Export", QFT_TEXT, "Export name"),
2329
     None, 0, _GetExportName),
2330
    ]
2331

    
2332
  return _PrepareFieldList(fields, [])
2333

    
2334

    
2335
_CLUSTER_VERSION_FIELDS = {
2336
  "software_version": ("SoftwareVersion", QFT_TEXT, constants.RELEASE_VERSION,
2337
                       "Software version"),
2338
  "protocol_version": ("ProtocolVersion", QFT_NUMBER,
2339
                       constants.PROTOCOL_VERSION,
2340
                       "RPC protocol version"),
2341
  "config_version": ("ConfigVersion", QFT_NUMBER, constants.CONFIG_VERSION,
2342
                     "Configuration format version"),
2343
  "os_api_version": ("OsApiVersion", QFT_NUMBER, max(constants.OS_API_VERSIONS),
2344
                     "API version for OS template scripts"),
2345
  "export_version": ("ExportVersion", QFT_NUMBER, constants.EXPORT_VERSION,
2346
                     "Import/export file format version"),
2347
  }
2348

    
2349

    
2350
_CLUSTER_SIMPLE_FIELDS = {
2351
  "cluster_name": ("Name", QFT_TEXT, QFF_HOSTNAME, "Cluster name"),
2352
  "master_node": ("Master", QFT_TEXT, QFF_HOSTNAME, "Master node name"),
2353
  "volume_group_name": ("VgName", QFT_TEXT, 0, "LVM volume group name"),
2354
  }
2355

    
2356

    
2357
class ClusterQueryData:
2358
  def __init__(self, cluster, drain_flag, watcher_pause):
2359
    """Initializes this class.
2360

2361
    @type cluster: L{objects.Cluster}
2362
    @param cluster: Instance of cluster object
2363
    @type drain_flag: bool
2364
    @param drain_flag: Whether job queue is drained
2365
    @type watcher_pause: number
2366
    @param watcher_pause: Until when watcher is paused (Unix timestamp)
2367

2368
    """
2369
    self._cluster = cluster
2370
    self.drain_flag = drain_flag
2371
    self.watcher_pause = watcher_pause
2372

    
2373
  def __iter__(self):
2374
    return iter([self._cluster])
2375

    
2376

    
2377
def _ClusterWatcherPause(ctx, _):
2378
  """Returns until when watcher is paused (if available).
2379

2380
  """
2381
  if ctx.watcher_pause is None:
2382
    return _FS_UNAVAIL
2383
  else:
2384
    return ctx.watcher_pause
2385

    
2386

    
2387
def _BuildClusterFields():
2388
  """Builds list of fields for cluster information.
2389

2390
  """
2391
  fields = [
2392
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), CQ_CONFIG, 0,
2393
     lambda ctx, cluster: list(cluster.GetTags())),
2394
    (_MakeField("architecture", "ArchInfo", QFT_OTHER,
2395
                "Architecture information"), None, 0,
2396
     lambda ctx, _: runtime.GetArchInfo()),
2397
    (_MakeField("drain_flag", "QueueDrained", QFT_BOOL,
2398
                "Flag whether job queue is drained"), CQ_QUEUE_DRAINED, 0,
2399
     lambda ctx, _: ctx.drain_flag),
2400
    (_MakeField("watcher_pause", "WatcherPause", QFT_TIMESTAMP,
2401
                "Until when watcher is paused"), CQ_WATCHER_PAUSE, 0,
2402
     _ClusterWatcherPause),
2403
    ]
2404

    
2405
  # Simple fields
2406
  fields.extend([
2407
    (_MakeField(name, title, kind, doc), CQ_CONFIG, flags, _GetItemAttr(name))
2408
    for (name, (title, kind, flags, doc)) in _CLUSTER_SIMPLE_FIELDS.items()
2409
    ])
2410

    
2411
  # Version fields
2412
  fields.extend([
2413
    (_MakeField(name, title, kind, doc), None, 0, _StaticValue(value))
2414
    for (name, (title, kind, value, doc)) in _CLUSTER_VERSION_FIELDS.items()
2415
    ])
2416

    
2417
  # Add timestamps
2418
  fields.extend(_GetItemTimestampFields(CQ_CONFIG))
2419

    
2420
  return _PrepareFieldList(fields, [
2421
    ("name", "cluster_name"),
2422
    ])
2423

    
2424

    
2425
#: Fields for cluster information
2426
CLUSTER_FIELDS = _BuildClusterFields()
2427

    
2428
#: Fields available for node queries
2429
NODE_FIELDS = _BuildNodeFields()
2430

    
2431
#: Fields available for instance queries
2432
INSTANCE_FIELDS = _BuildInstanceFields()
2433

    
2434
#: Fields available for lock queries
2435
LOCK_FIELDS = _BuildLockFields()
2436

    
2437
#: Fields available for node group queries
2438
GROUP_FIELDS = _BuildGroupFields()
2439

    
2440
#: Fields available for operating system queries
2441
OS_FIELDS = _BuildOsFields()
2442

    
2443
#: Fields available for job queries
2444
JOB_FIELDS = _BuildJobFields()
2445

    
2446
#: Fields available for exports
2447
EXPORT_FIELDS = _BuildExportFields()
2448

    
2449
#: All available resources
2450
ALL_FIELDS = {
2451
  constants.QR_CLUSTER: CLUSTER_FIELDS,
2452
  constants.QR_INSTANCE: INSTANCE_FIELDS,
2453
  constants.QR_NODE: NODE_FIELDS,
2454
  constants.QR_LOCK: LOCK_FIELDS,
2455
  constants.QR_GROUP: GROUP_FIELDS,
2456
  constants.QR_OS: OS_FIELDS,
2457
  constants.QR_JOB: JOB_FIELDS,
2458
  constants.QR_EXPORT: EXPORT_FIELDS,
2459
  }
2460

    
2461
#: All available field lists
2462
ALL_FIELD_LISTS = ALL_FIELDS.values()