Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ 76b62028

History | View | Annotate | Download (72.8 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
# Query field flags
105
QFF_HOSTNAME = 0x01
106
QFF_IP_ADDRESS = 0x02
107
QFF_JOB_ID = 0x04
108
QFF_SPLIT_TIMESTAMP = 0x08
109
# Next values: 0x10, 0x20, 0x40, 0x80, 0x100, 0x200
110
QFF_ALL = (QFF_HOSTNAME | QFF_IP_ADDRESS | QFF_JOB_ID | QFF_SPLIT_TIMESTAMP)
111

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

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

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

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

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

    
146
_SERIAL_NO_DOC = "%s object serial number, incremented on each modification"
147

    
148
# TODO: Consider moving titles closer to constants
149
NDP_TITLE = {
150
  constants.ND_OOB_PROGRAM: "OutOfBandProgram",
151
  constants.ND_SPINDLE_COUNT: "SpindleCount",
152
  }
153

    
154

    
155
def _GetUnknownField(ctx, item): # pylint: disable=W0613
156
  """Gets the contents of an unknown field.
157

158
  """
159
  return _FS_UNKNOWN
160

    
161

    
162
def _GetQueryFields(fielddefs, selected):
163
  """Calculates the internal list of selected fields.
164

165
  Unknown fields are returned as L{constants.QFT_UNKNOWN}.
166

167
  @type fielddefs: dict
168
  @param fielddefs: Field definitions
169
  @type selected: list of strings
170
  @param selected: List of selected fields
171

172
  """
173
  result = []
174

    
175
  for name in selected:
176
    try:
177
      fdef = fielddefs[name]
178
    except KeyError:
179
      fdef = (_MakeField(name, name, QFT_UNKNOWN, "Unknown field '%s'" % name),
180
              None, 0, _GetUnknownField)
181

    
182
    assert len(fdef) == 4
183

    
184
    result.append(fdef)
185

    
186
  return result
187

    
188

    
189
def GetAllFields(fielddefs):
190
  """Extract L{objects.QueryFieldDefinition} from field definitions.
191

192
  @rtype: list of L{objects.QueryFieldDefinition}
193

194
  """
195
  return [fdef for (fdef, _, _, _) in fielddefs]
196

    
197

    
198
class _FilterHints:
199
  """Class for filter analytics.
200

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

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

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

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

221
  """
222
  def __init__(self, namefield):
223
    """Initializes this class.
224

225
    @type namefield: string
226
    @param namefield: Field caller is interested in
227

228
    """
229
    self._namefield = namefield
230

    
231
    #: Whether all names need to be requested (e.g. if a non-equality operator
232
    #: has been used)
233
    self._allnames = False
234

    
235
    #: Which names to request
236
    self._names = None
237

    
238
    #: Data kinds referenced by the filter (used by L{Query.RequestedData})
239
    self._datakinds = set()
240

    
241
  def RequestedNames(self):
242
    """Returns all requested values.
243

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

247
    @rtype: list
248

249
    """
250
    if self._allnames or self._names is None:
251
      return None
252

    
253
    return utils.UniqueSequence(self._names)
254

    
255
  def ReferencedData(self):
256
    """Returns all kinds of data referenced by the filter.
257

258
    """
259
    return frozenset(self._datakinds)
260

    
261
  def _NeedAllNames(self):
262
    """Changes internal state to request all names.
263

264
    """
265
    self._allnames = True
266
    self._names = None
267

    
268
  def NoteLogicOp(self, op):
269
    """Called when handling a logic operation.
270

271
    @type op: string
272
    @param op: Operator
273

274
    """
275
    if op != qlang.OP_OR:
276
      self._NeedAllNames()
277

    
278
  def NoteUnaryOp(self, op): # pylint: disable=W0613
279
    """Called when handling an unary operation.
280

281
    @type op: string
282
    @param op: Operator
283

284
    """
285
    self._NeedAllNames()
286

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

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

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

    
300
    if self._allnames:
301
      return
302

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

    
312

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

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

    
319

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

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

    
326

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

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

    
333

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

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

    
340

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

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

    
350

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

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

    
360

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

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

    
367

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

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

    
380

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

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

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

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

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

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

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

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

    
415
  """Known operators
416

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

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

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

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

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

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

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

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

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

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

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

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

    
489
    return filter_fn
490

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

563
    """
564
    assert op_fn is None
565

    
566
    if hints_fn:
567
      hints_fn(op)
568

    
569
    if len(operands) != 1:
570
      raise errors.ParameterError("Unary operator '%s' expects exactly one"
571
                                  " operand" % op)
572

    
573
    if op == qlang.OP_TRUE:
574
      (_, _, _, retrieval_fn) = self._LookupField(operands[0])
575

    
576
      op_fn = operator.truth
577
      arg = retrieval_fn
578
    elif op == qlang.OP_NOT:
579
      op_fn = operator.not_
580
      arg = self._Compile(operands[0], level + 1)
581
    else:
582
      raise errors.ProgrammerError("Can't handle operator '%s'" % op)
583

    
584
    return compat.partial(_WrapUnaryOp, op_fn, arg)
585

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

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

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

    
607
    (fdef, datakind, field_flags, retrieval_fn) = self._LookupField(name)
608

    
609
    assert fdef.kind != QFT_UNKNOWN
610

    
611
    # TODO: Type conversions?
612

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

    
620
    if hints_fn:
621
      hints_fn(op, datakind, name, value)
622

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

    
629
        return compat.partial(_WrapBinaryOp, fn, retrieval_fn, value)
630

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

    
634

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

638
  See L{_FilterCompilerHelper} for details.
639

640
  @rtype: callable
641

642
  """
643
  return _FilterCompilerHelper(fields)(hints, qfilter)
644

    
645

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

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

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

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

665
    """
666
    assert namefield is None or namefield in fieldlist
667

    
668
    self._fields = _GetQueryFields(fieldlist, selected)
669

    
670
    self._filter_fn = None
671
    self._requested_names = None
672
    self._filter_datakinds = frozenset()
673

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

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

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

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

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

698
    """
699
    return self._requested_names
700

    
701
  def RequestedData(self):
702
    """Gets requested kinds of data.
703

704
    @rtype: frozenset
705

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

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

714
    Includes unknown fields.
715

716
    @rtype: List of L{objects.QueryFieldDefinition}
717

718
    """
719
    return GetAllFields(self._fields)
720

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

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

730
    """
731
    sort = (self._name_fn and sort_by_name)
732

    
733
    result = []
734

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

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

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

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

    
754
    if not sort:
755
      return result
756

    
757
    # TODO: Would "heapq" be more efficient than sorting?
758

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

    
762
    assert not result or (len(result[0]) == 3 and len(result[-1]) == 3)
763

    
764
    return map(operator.itemgetter(2), result)
765

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

769
    See L{Query.Query} for arguments.
770

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

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

    
782

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

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

    
798

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

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

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

    
820

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

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

    
834
  return fdef.name
835

    
836

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

840
  Converts the list to a dictionary and does some verification.
841

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

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

    
858
  result = utils.SequenceToDict(fields, key=_FieldDictKey)
859

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

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

    
872
  return result
873

    
874

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

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

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

    
888

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

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

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

    
907
  return objects.QueryFieldsResponse(fields=fdefs).ToDict()
908

    
909

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

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

918
  """
919
  return objects.QueryFieldDefinition(name=name, title=title, kind=kind,
920
                                      doc=doc)
921

    
922

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

926
  """
927
  return value
928

    
929

    
930
def _StaticValue(value):
931
  """Prepares a function to return a static value.
932

933
  """
934
  return compat.partial(_StaticValueInner, value)
935

    
936

    
937
def _GetNodeRole(node, master_name):
938
  """Determine node role.
939

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

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

    
957

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

961
  @param attr: Attribute name
962

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

    
967

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

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

    
979

    
980
def _BuildNDFields(is_group):
981
  """Builds all the ndparam fields.
982

983
  @param is_group: whether this is called at group or node level
984

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

    
995

    
996
def _ConvWrapInner(convert, fn, ctx, item):
997
  """Wrapper for converting values.
998

999
  @param convert: Conversion function receiving value as single parameter
1000
  @param fn: Retrieval function
1001

1002
  """
1003
  value = fn(ctx, item)
1004

    
1005
  # Is the value an abnormal status?
1006
  if compat.any(value is fs for fs in _FS_ALL):
1007
    # Return right away
1008
    return value
1009

    
1010
  # TODO: Should conversion function also receive context, item or both?
1011
  return convert(value)
1012

    
1013

    
1014
def _ConvWrap(convert, fn):
1015
  """Convenience wrapper for L{_ConvWrapInner}.
1016

1017
  @param convert: Conversion function receiving value as single parameter
1018
  @param fn: Retrieval function
1019

1020
  """
1021
  return compat.partial(_ConvWrapInner, convert, fn)
1022

    
1023

    
1024
def _GetItemTimestamp(getter):
1025
  """Returns function for getting timestamp of item.
1026

1027
  @type getter: callable
1028
  @param getter: Function to retrieve timestamp attribute
1029

1030
  """
1031
  def fn(_, item):
1032
    """Returns a timestamp of item.
1033

1034
    """
1035
    timestamp = getter(item)
1036
    if timestamp is None:
1037
      # Old configs might not have all timestamps
1038
      return _FS_UNAVAIL
1039
    else:
1040
      return timestamp
1041

    
1042
  return fn
1043

    
1044

    
1045
def _GetItemTimestampFields(datatype):
1046
  """Returns common timestamp fields.
1047

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

1050
  """
1051
  return [
1052
    (_MakeField("ctime", "CTime", QFT_TIMESTAMP, "Creation timestamp"),
1053
     datatype, 0, _GetItemTimestamp(operator.attrgetter("ctime"))),
1054
    (_MakeField("mtime", "MTime", QFT_TIMESTAMP, "Modification timestamp"),
1055
     datatype, 0, _GetItemTimestamp(operator.attrgetter("mtime"))),
1056
    ]
1057

    
1058

    
1059
class NodeQueryData:
1060
  """Data container for node data queries.
1061

1062
  """
1063
  def __init__(self, nodes, live_data, master_name, node_to_primary,
1064
               node_to_secondary, groups, oob_support, cluster):
1065
    """Initializes this class.
1066

1067
    """
1068
    self.nodes = nodes
1069
    self.live_data = live_data
1070
    self.master_name = master_name
1071
    self.node_to_primary = node_to_primary
1072
    self.node_to_secondary = node_to_secondary
1073
    self.groups = groups
1074
    self.oob_support = oob_support
1075
    self.cluster = cluster
1076

    
1077
    # Used for individual rows
1078
    self.curlive_data = None
1079
    self.ndparams = None
1080

    
1081
  def __iter__(self):
1082
    """Iterate over all nodes.
1083

1084
    This function has side-effects and only one instance of the resulting
1085
    generator should be used at a time.
1086

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

    
1100

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

    
1115

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

    
1140

    
1141
def _GetGroup(cb):
1142
  """Build function for calling another function with an node group.
1143

1144
  @param cb: The callback to be called with the nodegroup
1145

1146
  """
1147
  def fn(ctx, node):
1148
    """Get group data for a node.
1149

1150
    @type ctx: L{NodeQueryData}
1151
    @type inst: L{objects.Node}
1152
    @param inst: Node object
1153

1154
    """
1155
    ng = ctx.groups.get(node.group, None)
1156
    if ng is None:
1157
      # Nodes always have a group, or the configuration is corrupt
1158
      return _FS_UNAVAIL
1159

    
1160
    return cb(ctx, node, ng)
1161

    
1162
  return fn
1163

    
1164

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

1168
  @type ctx: L{NodeQueryData}
1169
  @type node: L{objects.Node}
1170
  @param node: Node object
1171
  @type ng: L{objects.NodeGroup}
1172
  @param ng: The node group this node belongs to
1173

1174
  """
1175
  return ng.name
1176

    
1177

    
1178
def _GetNodePower(ctx, node):
1179
  """Returns the node powered state
1180

1181
  @type ctx: L{NodeQueryData}
1182
  @type node: L{objects.Node}
1183
  @param node: Node object
1184

1185
  """
1186
  if ctx.oob_support[node.name]:
1187
    return node.powered
1188

    
1189
  return _FS_UNAVAIL
1190

    
1191

    
1192
def _GetNdParams(ctx, node, ng):
1193
  """Returns the ndparams for this node.
1194

1195
  @type ctx: L{NodeQueryData}
1196
  @type node: L{objects.Node}
1197
  @param node: Node object
1198
  @type ng: L{objects.NodeGroup}
1199
  @param ng: The node group this node belongs to
1200

1201
  """
1202
  return ctx.cluster.SimpleFillND(ng.FillND(node))
1203

    
1204

    
1205
def _GetLiveNodeField(field, kind, ctx, node):
1206
  """Gets the value of a "live" field from L{NodeQueryData}.
1207

1208
  @param field: Live field name
1209
  @param kind: Data kind, one of L{constants.QFT_ALL}
1210
  @type ctx: L{NodeQueryData}
1211
  @type node: L{objects.Node}
1212
  @param node: Node object
1213

1214
  """
1215
  if node.offline:
1216
    return _FS_OFFLINE
1217

    
1218
  if not node.vm_capable:
1219
    return _FS_UNAVAIL
1220

    
1221
  if not ctx.curlive_data:
1222
    return _FS_NODATA
1223

    
1224
  try:
1225
    value = ctx.curlive_data[field]
1226
  except KeyError:
1227
    return _FS_UNAVAIL
1228

    
1229
  if kind == QFT_TEXT:
1230
    return value
1231

    
1232
  assert kind in (QFT_NUMBER, QFT_UNIT)
1233

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

    
1242

    
1243
def _GetNodeHvState(_, node):
1244
  """Converts node's hypervisor state for query result.
1245

1246
  """
1247
  hv_state = node.hv_state
1248

    
1249
  if hv_state is None:
1250
    return _FS_UNAVAIL
1251

    
1252
  return dict((name, value.ToDict()) for (name, value) in hv_state.items())
1253

    
1254

    
1255
def _GetNodeDiskState(_, node):
1256
  """Converts node's disk state for query result.
1257

1258
  """
1259
  disk_state = node.disk_state
1260

    
1261
  if disk_state is None:
1262
    return _FS_UNAVAIL
1263

    
1264
  return dict((disk_kind, dict((name, value.ToDict())
1265
                               for (name, value) in kind_state.items()))
1266
              for (disk_kind, kind_state) in disk_state.items())
1267

    
1268

    
1269
def _BuildNodeFields():
1270
  """Builds list of fields for node queries.
1271

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

    
1301
  fields.extend(_BuildNDFields(False))
1302

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

    
1314
  def _GetLength(getter):
1315
    return lambda ctx, node: len(getter(ctx)[node.name])
1316

    
1317
  def _GetList(getter):
1318
    return lambda ctx, node: list(getter(ctx)[node.name])
1319

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

    
1335
  # Add simple fields
1336
  fields.extend([
1337
    (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1338
    for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()
1339
    ])
1340

    
1341
  # Add fields requiring live data
1342
  fields.extend([
1343
    (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1344
     compat.partial(_GetLiveNodeField, nfield, kind))
1345
    for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()
1346
    ])
1347

    
1348
  # Add timestamps
1349
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1350

    
1351
  return _PrepareFieldList(fields, [])
1352

    
1353

    
1354
class InstanceQueryData:
1355
  """Data container for instance data queries.
1356

1357
  """
1358
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
1359
               live_data, wrongnode_inst, console, nodes, groups):
1360
    """Initializes this class.
1361

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

1379
    """
1380
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
1381
           "Offline nodes not included in bad nodes"
1382
    assert not (set(live_data.keys()) & set(bad_nodes)), \
1383
           "Found live data for bad or offline nodes"
1384

    
1385
    self.instances = instances
1386
    self.cluster = cluster
1387
    self.disk_usage = disk_usage
1388
    self.offline_nodes = offline_nodes
1389
    self.bad_nodes = bad_nodes
1390
    self.live_data = live_data
1391
    self.wrongnode_inst = wrongnode_inst
1392
    self.console = console
1393
    self.nodes = nodes
1394
    self.groups = groups
1395

    
1396
    # Used for individual rows
1397
    self.inst_hvparams = None
1398
    self.inst_beparams = None
1399
    self.inst_osparams = None
1400
    self.inst_nicparams = None
1401

    
1402
  def __iter__(self):
1403
    """Iterate over all instances.
1404

1405
    This function has side-effects and only one instance of the resulting
1406
    generator should be used at a time.
1407

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

    
1416
      yield inst
1417

    
1418

    
1419
def _GetInstOperState(ctx, inst):
1420
  """Get instance's operational status.
1421

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

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

    
1434

    
1435
def _GetInstLiveData(name):
1436
  """Build function for retrieving live data.
1437

1438
  @type name: string
1439
  @param name: Live data field name
1440

1441
  """
1442
  def fn(ctx, inst):
1443
    """Get live data for an instance.
1444

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

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

    
1456
    if inst.name in ctx.live_data:
1457
      data = ctx.live_data[inst.name]
1458
      if name in data:
1459
        return data[name]
1460

    
1461
    return _FS_UNAVAIL
1462

    
1463
  return fn
1464

    
1465

    
1466
def _GetInstStatus(ctx, inst):
1467
  """Get instance status.
1468

1469
  @type ctx: L{InstanceQueryData}
1470
  @type inst: L{objects.Instance}
1471
  @param inst: Instance object
1472

1473
  """
1474
  if inst.primary_node in ctx.offline_nodes:
1475
    return constants.INSTST_NODEOFFLINE
1476

    
1477
  if inst.primary_node in ctx.bad_nodes:
1478
    return constants.INSTST_NODEDOWN
1479

    
1480
  if bool(ctx.live_data.get(inst.name)):
1481
    if inst.name in ctx.wrongnode_inst:
1482
      return constants.INSTST_WRONGNODE
1483
    elif inst.admin_state == constants.ADMINST_UP:
1484
      return constants.INSTST_RUNNING
1485
    else:
1486
      return constants.INSTST_ERRORUP
1487

    
1488
  if inst.admin_state == constants.ADMINST_UP:
1489
    return constants.INSTST_ERRORDOWN
1490
  elif inst.admin_state == constants.ADMINST_DOWN:
1491
    return constants.INSTST_ADMINDOWN
1492

    
1493
  return constants.INSTST_ADMINOFFLINE
1494

    
1495

    
1496
def _GetInstDiskSize(index):
1497
  """Build function for retrieving disk size.
1498

1499
  @type index: int
1500
  @param index: Disk index
1501

1502
  """
1503
  def fn(_, inst):
1504
    """Get size of a disk.
1505

1506
    @type inst: L{objects.Instance}
1507
    @param inst: Instance object
1508

1509
    """
1510
    try:
1511
      return inst.disks[index].size
1512
    except IndexError:
1513
      return _FS_UNAVAIL
1514

    
1515
  return fn
1516

    
1517

    
1518
def _GetInstNic(index, cb):
1519
  """Build function for calling another function with an instance NIC.
1520

1521
  @type index: int
1522
  @param index: NIC index
1523
  @type cb: callable
1524
  @param cb: Callback
1525

1526
  """
1527
  def fn(ctx, inst):
1528
    """Call helper function with instance NIC.
1529

1530
    @type ctx: L{InstanceQueryData}
1531
    @type inst: L{objects.Instance}
1532
    @param inst: Instance object
1533

1534
    """
1535
    try:
1536
      nic = inst.nics[index]
1537
    except IndexError:
1538
      return _FS_UNAVAIL
1539

    
1540
    return cb(ctx, index, nic)
1541

    
1542
  return fn
1543

    
1544

    
1545
def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613
1546
  """Get a NIC's IP address.
1547

1548
  @type ctx: L{InstanceQueryData}
1549
  @type nic: L{objects.NIC}
1550
  @param nic: NIC object
1551

1552
  """
1553
  if nic.ip is None:
1554
    return _FS_UNAVAIL
1555
  else:
1556
    return nic.ip
1557

    
1558

    
1559
def _GetInstNicBridge(ctx, index, _):
1560
  """Get a NIC's bridge.
1561

1562
  @type ctx: L{InstanceQueryData}
1563
  @type index: int
1564
  @param index: NIC index
1565

1566
  """
1567
  assert len(ctx.inst_nicparams) >= index
1568

    
1569
  nicparams = ctx.inst_nicparams[index]
1570

    
1571
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1572
    return nicparams[constants.NIC_LINK]
1573
  else:
1574
    return _FS_UNAVAIL
1575

    
1576

    
1577
def _GetInstAllNicBridges(ctx, inst):
1578
  """Get all network bridges for an instance.
1579

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

1584
  """
1585
  assert len(ctx.inst_nicparams) == len(inst.nics)
1586

    
1587
  result = []
1588

    
1589
  for nicp in ctx.inst_nicparams:
1590
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1591
      result.append(nicp[constants.NIC_LINK])
1592
    else:
1593
      result.append(None)
1594

    
1595
  assert len(result) == len(inst.nics)
1596

    
1597
  return result
1598

    
1599

    
1600
def _GetInstNicParam(name):
1601
  """Build function for retrieving a NIC parameter.
1602

1603
  @type name: string
1604
  @param name: Parameter name
1605

1606
  """
1607
  def fn(ctx, index, _):
1608
    """Get a NIC's bridge.
1609

1610
    @type ctx: L{InstanceQueryData}
1611
    @type inst: L{objects.Instance}
1612
    @param inst: Instance object
1613
    @type nic: L{objects.NIC}
1614
    @param nic: NIC object
1615

1616
    """
1617
    assert len(ctx.inst_nicparams) >= index
1618
    return ctx.inst_nicparams[index][name]
1619

    
1620
  return fn
1621

    
1622

    
1623
def _GetInstanceNetworkFields():
1624
  """Get instance fields involving network interfaces.
1625

1626
  @return: Tuple containing list of field definitions used as input for
1627
    L{_PrepareFieldList} and a list of aliases
1628

1629
  """
1630
  nic_mac_fn = lambda ctx, _, nic: nic.mac
1631
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1632
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1633

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

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

    
1679
  aliases = [
1680
    # Legacy fields for first NIC
1681
    ("ip", "nic.ip/0"),
1682
    ("mac", "nic.mac/0"),
1683
    ("bridge", "nic.bridge/0"),
1684
    ("nic_mode", "nic.mode/0"),
1685
    ("nic_link", "nic.link/0"),
1686
    ]
1687

    
1688
  return (fields, aliases)
1689

    
1690

    
1691
def _GetInstDiskUsage(ctx, inst):
1692
  """Get disk usage for an instance.
1693

1694
  @type ctx: L{InstanceQueryData}
1695
  @type inst: L{objects.Instance}
1696
  @param inst: Instance object
1697

1698
  """
1699
  usage = ctx.disk_usage[inst.name]
1700

    
1701
  if usage is None:
1702
    usage = 0
1703

    
1704
  return usage
1705

    
1706

    
1707
def _GetInstanceConsole(ctx, inst):
1708
  """Get console information for instance.
1709

1710
  @type ctx: L{InstanceQueryData}
1711
  @type inst: L{objects.Instance}
1712
  @param inst: Instance object
1713

1714
  """
1715
  consinfo = ctx.console[inst.name]
1716

    
1717
  if consinfo is None:
1718
    return _FS_UNAVAIL
1719

    
1720
  return consinfo
1721

    
1722

    
1723
def _GetInstanceDiskFields():
1724
  """Get instance fields involving disks.
1725

1726
  @return: List of field definitions used as input for L{_PrepareFieldList}
1727

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

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

    
1749
  return fields
1750

    
1751

    
1752
def _GetInstanceParameterFields():
1753
  """Get instance fields involving parameters.
1754

1755
  @return: List of field definitions used as input for L{_PrepareFieldList}
1756

1757
  """
1758
  # TODO: Consider moving titles closer to constants
1759
  be_title = {
1760
    constants.BE_AUTO_BALANCE: "Auto_balance",
1761
    constants.BE_MAXMEM: "ConfigMaxMem",
1762
    constants.BE_MINMEM: "ConfigMinMem",
1763
    constants.BE_VCPUS: "ConfigVCPUs",
1764
    }
1765

    
1766
  hv_title = {
1767
    constants.HV_ACPI: "ACPI",
1768
    constants.HV_BOOT_ORDER: "Boot_order",
1769
    constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1770
    constants.HV_DISK_TYPE: "Disk_type",
1771
    constants.HV_INITRD_PATH: "Initrd_path",
1772
    constants.HV_KERNEL_PATH: "Kernel_path",
1773
    constants.HV_NIC_TYPE: "NIC_type",
1774
    constants.HV_PAE: "PAE",
1775
    constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1776
    constants.HV_PASSTHROUGH: "pci_pass",
1777
    constants.HV_CPU_TYPE: "cpu_type",
1778
    }
1779

    
1780
  fields = [
1781
    # Filled parameters
1782
    (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
1783
                "Hypervisor parameters (merged)"),
1784
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
1785
    (_MakeField("beparams", "BackendParameters", QFT_OTHER,
1786
                "Backend parameters (merged)"),
1787
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
1788
    (_MakeField("osparams", "OpSysParameters", QFT_OTHER,
1789
                "Operating system parameters (merged)"),
1790
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_osparams),
1791

    
1792
    # Unfilled parameters
1793
    (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
1794
                "Custom hypervisor parameters"),
1795
     IQ_CONFIG, 0, _GetItemAttr("hvparams")),
1796
    (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
1797
                "Custom backend parameters",),
1798
     IQ_CONFIG, 0, _GetItemAttr("beparams")),
1799
    (_MakeField("custom_osparams", "CustomOpSysParameters", QFT_OTHER,
1800
                "Custom operating system parameters",),
1801
     IQ_CONFIG, 0, _GetItemAttr("osparams")),
1802
    (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
1803
                "Custom network interface parameters"),
1804
     IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1805
    ]
1806

    
1807
  # HV params
1808
  def _GetInstHvParam(name):
1809
    return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1810

    
1811
  fields.extend([
1812
    (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1813
                _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1814
     IQ_CONFIG, 0, _GetInstHvParam(name))
1815
    for name, kind in constants.HVS_PARAMETER_TYPES.items()
1816
    if name not in constants.HVC_GLOBALS
1817
    ])
1818

    
1819
  # BE params
1820
  def _GetInstBeParam(name):
1821
    return lambda ctx, _: ctx.inst_beparams.get(name, None)
1822

    
1823
  fields.extend([
1824
    (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1825
                _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1826
     IQ_CONFIG, 0, _GetInstBeParam(name))
1827
    for name, kind in constants.BES_PARAMETER_TYPES.items()
1828
    ])
1829

    
1830
  return fields
1831

    
1832

    
1833
_INST_SIMPLE_FIELDS = {
1834
  "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"),
1835
  "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"),
1836
  "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"),
1837
  # Depending on the hypervisor, the port can be None
1838
  "network_port": ("Network_port", QFT_OTHER, 0,
1839
                   "Instance network port if available (e.g. for VNC console)"),
1840
  "os": ("OS", QFT_TEXT, 0, "Operating system"),
1841
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"),
1842
  "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"),
1843
  }
1844

    
1845

    
1846
def _GetInstNodeGroup(ctx, default, node_name):
1847
  """Gets group UUID of an instance node.
1848

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

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

    
1862

    
1863
def _GetInstNodeGroupName(ctx, default, node_name):
1864
  """Gets group name of an instance node.
1865

1866
  @type ctx: L{InstanceQueryData}
1867
  @param default: Default value
1868
  @type node_name: string
1869
  @param node_name: Node name
1870

1871
  """
1872
  try:
1873
    node = ctx.nodes[node_name]
1874
  except KeyError:
1875
    return default
1876

    
1877
  try:
1878
    group = ctx.groups[node.group]
1879
  except KeyError:
1880
    return default
1881

    
1882
  return group.name
1883

    
1884

    
1885
def _BuildInstanceFields():
1886
  """Builds list of fields for instance queries.
1887

1888
  """
1889
  fields = [
1890
    (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
1891
     IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")),
1892
    (_MakeField("pnode.group", "PrimaryNodeGroup", QFT_TEXT,
1893
                "Primary node's group"),
1894
     IQ_NODES, 0,
1895
     lambda ctx, inst: _GetInstNodeGroupName(ctx, _FS_UNAVAIL,
1896
                                             inst.primary_node)),
1897
    (_MakeField("pnode.group.uuid", "PrimaryNodeGroupUUID", QFT_TEXT,
1898
                "Primary node's group UUID"),
1899
     IQ_NODES, 0,
1900
     lambda ctx, inst: _GetInstNodeGroup(ctx, _FS_UNAVAIL, inst.primary_node)),
1901
    # TODO: Allow filtering by secondary node as hostname
1902
    (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
1903
                "Secondary nodes; usually this will just be one node"),
1904
     IQ_CONFIG, 0, lambda ctx, inst: list(inst.secondary_nodes)),
1905
    (_MakeField("snodes.group", "SecondaryNodesGroups", QFT_OTHER,
1906
                "Node groups of secondary nodes"),
1907
     IQ_NODES, 0,
1908
     lambda ctx, inst: map(compat.partial(_GetInstNodeGroupName, ctx, None),
1909
                           inst.secondary_nodes)),
1910
    (_MakeField("snodes.group.uuid", "SecondaryNodesGroupsUUID", QFT_OTHER,
1911
                "Node group UUIDs of secondary nodes"),
1912
     IQ_NODES, 0,
1913
     lambda ctx, inst: map(compat.partial(_GetInstNodeGroup, ctx, None),
1914
                           inst.secondary_nodes)),
1915
    (_MakeField("admin_state", "InstanceState", QFT_TEXT,
1916
                "Desired state of instance"),
1917
     IQ_CONFIG, 0, _GetItemAttr("admin_state")),
1918
    (_MakeField("admin_up", "Autostart", QFT_BOOL,
1919
                "Desired state of instance"),
1920
     IQ_CONFIG, 0, lambda ctx, inst: inst.admin_state == constants.ADMINST_UP),
1921
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
1922
     lambda ctx, inst: list(inst.GetTags())),
1923
    (_MakeField("console", "Console", QFT_OTHER,
1924
                "Instance console information"), IQ_CONSOLE, 0,
1925
     _GetInstanceConsole),
1926
    ]
1927

    
1928
  # Add simple fields
1929
  fields.extend([
1930
    (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1931
    for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()
1932
    ])
1933

    
1934
  # Fields requiring talking to the node
1935
  fields.extend([
1936
    (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
1937
     IQ_LIVE, 0, _GetInstOperState),
1938
    (_MakeField("oper_ram", "Memory", QFT_UNIT,
1939
                "Actual memory usage as seen by hypervisor"),
1940
     IQ_LIVE, 0, _GetInstLiveData("memory")),
1941
    (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
1942
                "Actual number of VCPUs as seen by hypervisor"),
1943
     IQ_LIVE, 0, _GetInstLiveData("vcpus")),
1944
    ])
1945

    
1946
  # Status field
1947
  status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
1948
                   constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
1949
                   constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
1950
                   constants.INSTST_NODEOFFLINE, constants.INSTST_ADMINOFFLINE)
1951
  status_doc = ("Instance status; \"%s\" if instance is set to be running"
1952
                " and actually is, \"%s\" if instance is stopped and"
1953
                " is not running, \"%s\" if instance running, but not on its"
1954
                " designated primary node, \"%s\" if instance should be"
1955
                " stopped, but is actually running, \"%s\" if instance should"
1956
                " run, but doesn't, \"%s\" if instance's primary node is down,"
1957
                " \"%s\" if instance's primary node is marked offline,"
1958
                " \"%s\" if instance is offline and does not use dynamic"
1959
                " resources" % status_values)
1960
  fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
1961
                 IQ_LIVE, 0, _GetInstStatus))
1962
  assert set(status_values) == constants.INSTST_ALL, \
1963
         "Status documentation mismatch"
1964

    
1965
  (network_fields, network_aliases) = _GetInstanceNetworkFields()
1966

    
1967
  fields.extend(network_fields)
1968
  fields.extend(_GetInstanceParameterFields())
1969
  fields.extend(_GetInstanceDiskFields())
1970
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1971

    
1972
  aliases = [
1973
    ("vcpus", "be/vcpus"),
1974
    ("be/memory", "be/maxmem"),
1975
    ("sda_size", "disk.size/0"),
1976
    ("sdb_size", "disk.size/1"),
1977
    ] + network_aliases
1978

    
1979
  return _PrepareFieldList(fields, aliases)
1980

    
1981

    
1982
class LockQueryData:
1983
  """Data container for lock data queries.
1984

1985
  """
1986
  def __init__(self, lockdata):
1987
    """Initializes this class.
1988

1989
    """
1990
    self.lockdata = lockdata
1991

    
1992
  def __iter__(self):
1993
    """Iterate over all locks.
1994

1995
    """
1996
    return iter(self.lockdata)
1997

    
1998

    
1999
def _GetLockOwners(_, data):
2000
  """Returns a sorted list of a lock's current owners.
2001

2002
  """
2003
  (_, _, owners, _) = data
2004

    
2005
  if owners:
2006
    owners = utils.NiceSort(owners)
2007

    
2008
  return owners
2009

    
2010

    
2011
def _GetLockPending(_, data):
2012
  """Returns a sorted list of a lock's pending acquires.
2013

2014
  """
2015
  (_, _, _, pending) = data
2016

    
2017
  if pending:
2018
    pending = [(mode, utils.NiceSort(names))
2019
               for (mode, names) in pending]
2020

    
2021
  return pending
2022

    
2023

    
2024
def _BuildLockFields():
2025
  """Builds list of fields for lock queries.
2026

2027
  """
2028
  return _PrepareFieldList([
2029
    # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
2030
    (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
2031
     lambda ctx, (name, mode, owners, pending): name),
2032
    (_MakeField("mode", "Mode", QFT_OTHER,
2033
                "Mode in which the lock is currently acquired"
2034
                " (exclusive or shared)"),
2035
     LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
2036
    (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
2037
     LQ_OWNER, 0, _GetLockOwners),
2038
    (_MakeField("pending", "Pending", QFT_OTHER,
2039
                "Threads waiting for the lock"),
2040
     LQ_PENDING, 0, _GetLockPending),
2041
    ], [])
2042

    
2043

    
2044
class GroupQueryData:
2045
  """Data container for node group data queries.
2046

2047
  """
2048
  def __init__(self, cluster, groups, group_to_nodes, group_to_instances,
2049
               want_diskparams):
2050
    """Initializes this class.
2051

2052
    @param cluster: Cluster object
2053
    @param groups: List of node group objects
2054
    @type group_to_nodes: dict; group UUID as key
2055
    @param group_to_nodes: Per-group list of nodes
2056
    @type group_to_instances: dict; group UUID as key
2057
    @param group_to_instances: Per-group list of (primary) instances
2058
    @type want_diskparams: bool
2059
    @param want_diskparams: Whether diskparamters should be calculated
2060

2061
    """
2062
    self.groups = groups
2063
    self.group_to_nodes = group_to_nodes
2064
    self.group_to_instances = group_to_instances
2065
    self.cluster = cluster
2066
    self.want_diskparams = want_diskparams
2067

    
2068
    # Used for individual rows
2069
    self.group_ipolicy = None
2070
    self.ndparams = None
2071
    self.group_dp = None
2072

    
2073
  def __iter__(self):
2074
    """Iterate over all node groups.
2075

2076
    This function has side-effects and only one instance of the resulting
2077
    generator should be used at a time.
2078

2079
    """
2080
    for group in self.groups:
2081
      self.group_ipolicy = self.cluster.SimpleFillIPolicy(group.ipolicy)
2082
      self.ndparams = self.cluster.SimpleFillND(group.ndparams)
2083
      if self.want_diskparams:
2084
        self.group_dp = self.cluster.SimpleFillDP(group.diskparams)
2085
      else:
2086
        self.group_dp = None
2087
      yield group
2088

    
2089

    
2090
_GROUP_SIMPLE_FIELDS = {
2091
  "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
2092
  "name": ("Group", QFT_TEXT, "Group name"),
2093
  "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
2094
  "uuid": ("UUID", QFT_TEXT, "Group UUID"),
2095
  }
2096

    
2097

    
2098
def _BuildGroupFields():
2099
  """Builds list of fields for node group queries.
2100

2101
  """
2102
  # Add simple fields
2103
  fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
2104
             _GetItemAttr(name))
2105
            for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
2106

    
2107
  def _GetLength(getter):
2108
    return lambda ctx, group: len(getter(ctx)[group.uuid])
2109

    
2110
  def _GetSortedList(getter):
2111
    return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
2112

    
2113
  group_to_nodes = operator.attrgetter("group_to_nodes")
2114
  group_to_instances = operator.attrgetter("group_to_instances")
2115

    
2116
  # Add fields for nodes
2117
  fields.extend([
2118
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
2119
     GQ_NODE, 0, _GetLength(group_to_nodes)),
2120
    (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
2121
     GQ_NODE, 0, _GetSortedList(group_to_nodes)),
2122
    ])
2123

    
2124
  # Add fields for instances
2125
  fields.extend([
2126
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
2127
                "Number of primary instances"),
2128
     GQ_INST, 0, _GetLength(group_to_instances)),
2129
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
2130
                "List of primary instances"),
2131
     GQ_INST, 0, _GetSortedList(group_to_instances)),
2132
    ])
2133

    
2134
  # Other fields
2135
  fields.extend([
2136
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), GQ_CONFIG, 0,
2137
     lambda ctx, group: list(group.GetTags())),
2138
    (_MakeField("ipolicy", "InstancePolicy", QFT_OTHER,
2139
                "Instance policy limitations (merged)"),
2140
     GQ_CONFIG, 0, lambda ctx, _: ctx.group_ipolicy),
2141
    (_MakeField("custom_ipolicy", "CustomInstancePolicy", QFT_OTHER,
2142
                "Custom instance policy limitations"),
2143
     GQ_CONFIG, 0, _GetItemAttr("ipolicy")),
2144
    (_MakeField("custom_ndparams", "CustomNDParams", QFT_OTHER,
2145
                "Custom node parameters"),
2146
     GQ_CONFIG, 0, _GetItemAttr("ndparams")),
2147
    (_MakeField("ndparams", "NDParams", QFT_OTHER,
2148
                "Node parameters"),
2149
     GQ_CONFIG, 0, lambda ctx, _: ctx.ndparams),
2150
    (_MakeField("diskparams", "DiskParameters", QFT_OTHER,
2151
                "Disk parameters (merged)"),
2152
     GQ_DISKPARAMS, 0, lambda ctx, _: ctx.group_dp),
2153
    (_MakeField("custom_diskparams", "CustomDiskParameters", QFT_OTHER,
2154
                "Custom disk parameters"),
2155
     GQ_CONFIG, 0, _GetItemAttr("diskparams")),
2156
    ])
2157

    
2158
  # ND parameters
2159
  fields.extend(_BuildNDFields(True))
2160

    
2161
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
2162

    
2163
  return _PrepareFieldList(fields, [])
2164

    
2165

    
2166
class OsInfo(objects.ConfigObject):
2167
  __slots__ = [
2168
    "name",
2169
    "valid",
2170
    "hidden",
2171
    "blacklisted",
2172
    "variants",
2173
    "api_versions",
2174
    "parameters",
2175
    "node_status",
2176
    ]
2177

    
2178

    
2179
def _BuildOsFields():
2180
  """Builds list of fields for operating system queries.
2181

2182
  """
2183
  fields = [
2184
    (_MakeField("name", "Name", QFT_TEXT, "Operating system name"),
2185
     None, 0, _GetItemAttr("name")),
2186
    (_MakeField("valid", "Valid", QFT_BOOL,
2187
                "Whether operating system definition is valid"),
2188
     None, 0, _GetItemAttr("valid")),
2189
    (_MakeField("hidden", "Hidden", QFT_BOOL,
2190
                "Whether operating system is hidden"),
2191
     None, 0, _GetItemAttr("hidden")),
2192
    (_MakeField("blacklisted", "Blacklisted", QFT_BOOL,
2193
                "Whether operating system is blacklisted"),
2194
     None, 0, _GetItemAttr("blacklisted")),
2195
    (_MakeField("variants", "Variants", QFT_OTHER,
2196
                "Operating system variants"),
2197
     None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("variants"))),
2198
    (_MakeField("api_versions", "ApiVersions", QFT_OTHER,
2199
                "Operating system API versions"),
2200
     None, 0, _ConvWrap(sorted, _GetItemAttr("api_versions"))),
2201
    (_MakeField("parameters", "Parameters", QFT_OTHER,
2202
                "Operating system parameters"),
2203
     None, 0, _ConvWrap(compat.partial(utils.NiceSort, key=compat.fst),
2204
                        _GetItemAttr("parameters"))),
2205
    (_MakeField("node_status", "NodeStatus", QFT_OTHER,
2206
                "Status from node"),
2207
     None, 0, _GetItemAttr("node_status")),
2208
    ]
2209

    
2210
  return _PrepareFieldList(fields, [])
2211

    
2212

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

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

2219
  """
2220
  if job is None:
2221
    return _FS_UNAVAIL
2222
  else:
2223
    return fn(job)
2224

    
2225

    
2226
def _JobUnavail(inner):
2227
  """Wrapper for L{_JobUnavailInner}.
2228

2229
  """
2230
  return compat.partial(_JobUnavailInner, inner)
2231

    
2232

    
2233
def _PerJobOpInner(fn, job):
2234
  """Executes a function per opcode in a job.
2235

2236
  """
2237
  return map(fn, job.ops)
2238

    
2239

    
2240
def _PerJobOp(fn):
2241
  """Wrapper for L{_PerJobOpInner}.
2242

2243
  """
2244
  return _JobUnavail(compat.partial(_PerJobOpInner, fn))
2245

    
2246

    
2247
def _JobTimestampInner(fn, job):
2248
  """Converts unavailable timestamp to L{_FS_UNAVAIL}.
2249

2250
  """
2251
  timestamp = fn(job)
2252

    
2253
  if timestamp is None:
2254
    return _FS_UNAVAIL
2255
  else:
2256
    return timestamp
2257

    
2258

    
2259
def _JobTimestamp(fn):
2260
  """Wrapper for L{_JobTimestampInner}.
2261

2262
  """
2263
  return _JobUnavail(compat.partial(_JobTimestampInner, fn))
2264

    
2265

    
2266
def _BuildJobFields():
2267
  """Builds list of fields for job queries.
2268

2269
  """
2270
  fields = [
2271
    (_MakeField("id", "ID", QFT_NUMBER, "Job ID"),
2272
     None, QFF_JOB_ID, lambda _, (job_id, job): job_id),
2273
    (_MakeField("status", "Status", QFT_TEXT, "Job status"),
2274
     None, 0, _JobUnavail(lambda job: job.CalcStatus())),
2275
    (_MakeField("priority", "Priority", QFT_NUMBER,
2276
                ("Current job priority (%s to %s)" %
2277
                 (constants.OP_PRIO_LOWEST, constants.OP_PRIO_HIGHEST))),
2278
     None, 0, _JobUnavail(lambda job: job.CalcPriority())),
2279
    (_MakeField("ops", "OpCodes", QFT_OTHER, "List of all opcodes"),
2280
     None, 0, _PerJobOp(lambda op: op.input.__getstate__())),
2281
    (_MakeField("opresult", "OpCode_result", QFT_OTHER,
2282
                "List of opcodes results"),
2283
     None, 0, _PerJobOp(operator.attrgetter("result"))),
2284
    (_MakeField("opstatus", "OpCode_status", QFT_OTHER,
2285
                "List of opcodes status"),
2286
     None, 0, _PerJobOp(operator.attrgetter("status"))),
2287
    (_MakeField("oplog", "OpCode_log", QFT_OTHER,
2288
                "List of opcode output logs"),
2289
     None, 0, _PerJobOp(operator.attrgetter("log"))),
2290
    (_MakeField("opstart", "OpCode_start", QFT_OTHER,
2291
                "List of opcode start timestamps (before acquiring locks)"),
2292
     None, 0, _PerJobOp(operator.attrgetter("start_timestamp"))),
2293
    (_MakeField("opexec", "OpCode_exec", QFT_OTHER,
2294
                "List of opcode execution start timestamps (after acquiring"
2295
                " locks)"),
2296
     None, 0, _PerJobOp(operator.attrgetter("exec_timestamp"))),
2297
    (_MakeField("opend", "OpCode_end", QFT_OTHER,
2298
                "List of opcode execution end timestamps"),
2299
     None, 0, _PerJobOp(operator.attrgetter("end_timestamp"))),
2300
    (_MakeField("oppriority", "OpCode_prio", QFT_OTHER,
2301
                "List of opcode priorities"),
2302
     None, 0, _PerJobOp(operator.attrgetter("priority"))),
2303
    (_MakeField("summary", "Summary", QFT_OTHER,
2304
                "List of per-opcode summaries"),
2305
     None, 0, _PerJobOp(lambda op: op.input.Summary())),
2306
    ]
2307

    
2308
  # Timestamp fields
2309
  for (name, attr, title, desc) in [
2310
    ("received_ts", "received_timestamp", "Received",
2311
     "Timestamp of when job was received"),
2312
    ("start_ts", "start_timestamp", "Start", "Timestamp of job start"),
2313
    ("end_ts", "end_timestamp", "End", "Timestamp of job end"),
2314
    ]:
2315
    getter = operator.attrgetter(attr)
2316
    fields.extend([
2317
      (_MakeField(name, title, QFT_OTHER,
2318
                  "%s (tuple containing seconds and microseconds)" % desc),
2319
       None, QFF_SPLIT_TIMESTAMP, _JobTimestamp(getter)),
2320
      ])
2321

    
2322
  return _PrepareFieldList(fields, [])
2323

    
2324

    
2325
def _GetExportName(_, (node_name, expname)): # pylint: disable=W0613
2326
  """Returns an export name if available.
2327

2328
  """
2329
  if expname is None:
2330
    return _FS_UNAVAIL
2331
  else:
2332
    return expname
2333

    
2334

    
2335
def _BuildExportFields():
2336
  """Builds list of fields for exports.
2337

2338
  """
2339
  fields = [
2340
    (_MakeField("node", "Node", QFT_TEXT, "Node name"),
2341
     None, QFF_HOSTNAME, lambda _, (node_name, expname): node_name),
2342
    (_MakeField("export", "Export", QFT_TEXT, "Export name"),
2343
     None, 0, _GetExportName),
2344
    ]
2345

    
2346
  return _PrepareFieldList(fields, [])
2347

    
2348

    
2349
_CLUSTER_VERSION_FIELDS = {
2350
  "software_version": ("SoftwareVersion", QFT_TEXT, constants.RELEASE_VERSION,
2351
                       "Software version"),
2352
  "protocol_version": ("ProtocolVersion", QFT_NUMBER,
2353
                       constants.PROTOCOL_VERSION,
2354
                       "RPC protocol version"),
2355
  "config_version": ("ConfigVersion", QFT_NUMBER, constants.CONFIG_VERSION,
2356
                     "Configuration format version"),
2357
  "os_api_version": ("OsApiVersion", QFT_NUMBER, max(constants.OS_API_VERSIONS),
2358
                     "API version for OS template scripts"),
2359
  "export_version": ("ExportVersion", QFT_NUMBER, constants.EXPORT_VERSION,
2360
                     "Import/export file format version"),
2361
  }
2362

    
2363

    
2364
_CLUSTER_SIMPLE_FIELDS = {
2365
  "cluster_name": ("Name", QFT_TEXT, QFF_HOSTNAME, "Cluster name"),
2366
  "master_node": ("Master", QFT_TEXT, QFF_HOSTNAME, "Master node name"),
2367
  "volume_group_name": ("VgName", QFT_TEXT, 0, "LVM volume group name"),
2368
  }
2369

    
2370

    
2371
class ClusterQueryData:
2372
  def __init__(self, cluster, drain_flag, watcher_pause):
2373
    """Initializes this class.
2374

2375
    @type cluster: L{objects.Cluster}
2376
    @param cluster: Instance of cluster object
2377
    @type drain_flag: bool
2378
    @param drain_flag: Whether job queue is drained
2379
    @type watcher_pause: number
2380
    @param watcher_pause: Until when watcher is paused (Unix timestamp)
2381

2382
    """
2383
    self._cluster = cluster
2384
    self.drain_flag = drain_flag
2385
    self.watcher_pause = watcher_pause
2386

    
2387
  def __iter__(self):
2388
    return iter([self._cluster])
2389

    
2390

    
2391
def _ClusterWatcherPause(ctx, _):
2392
  """Returns until when watcher is paused (if available).
2393

2394
  """
2395
  if ctx.watcher_pause is None:
2396
    return _FS_UNAVAIL
2397
  else:
2398
    return ctx.watcher_pause
2399

    
2400

    
2401
def _BuildClusterFields():
2402
  """Builds list of fields for cluster information.
2403

2404
  """
2405
  fields = [
2406
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), CQ_CONFIG, 0,
2407
     lambda ctx, cluster: list(cluster.GetTags())),
2408
    (_MakeField("architecture", "ArchInfo", QFT_OTHER,
2409
                "Architecture information"), None, 0,
2410
     lambda ctx, _: runtime.GetArchInfo()),
2411
    (_MakeField("drain_flag", "QueueDrained", QFT_BOOL,
2412
                "Flag whether job queue is drained"), CQ_QUEUE_DRAINED, 0,
2413
     lambda ctx, _: ctx.drain_flag),
2414
    (_MakeField("watcher_pause", "WatcherPause", QFT_TIMESTAMP,
2415
                "Until when watcher is paused"), CQ_WATCHER_PAUSE, 0,
2416
     _ClusterWatcherPause),
2417
    ]
2418

    
2419
  # Simple fields
2420
  fields.extend([
2421
    (_MakeField(name, title, kind, doc), CQ_CONFIG, flags, _GetItemAttr(name))
2422
    for (name, (title, kind, flags, doc)) in _CLUSTER_SIMPLE_FIELDS.items()
2423
    ])
2424

    
2425
  # Version fields
2426
  fields.extend([
2427
    (_MakeField(name, title, kind, doc), None, 0, _StaticValue(value))
2428
    for (name, (title, kind, value, doc)) in _CLUSTER_VERSION_FIELDS.items()
2429
    ])
2430

    
2431
  # Add timestamps
2432
  fields.extend(_GetItemTimestampFields(CQ_CONFIG))
2433

    
2434
  return _PrepareFieldList(fields, [
2435
    ("name", "cluster_name"),
2436
    ])
2437

    
2438

    
2439
#: Fields for cluster information
2440
CLUSTER_FIELDS = _BuildClusterFields()
2441

    
2442
#: Fields available for node queries
2443
NODE_FIELDS = _BuildNodeFields()
2444

    
2445
#: Fields available for instance queries
2446
INSTANCE_FIELDS = _BuildInstanceFields()
2447

    
2448
#: Fields available for lock queries
2449
LOCK_FIELDS = _BuildLockFields()
2450

    
2451
#: Fields available for node group queries
2452
GROUP_FIELDS = _BuildGroupFields()
2453

    
2454
#: Fields available for operating system queries
2455
OS_FIELDS = _BuildOsFields()
2456

    
2457
#: Fields available for job queries
2458
JOB_FIELDS = _BuildJobFields()
2459

    
2460
#: Fields available for exports
2461
EXPORT_FIELDS = _BuildExportFields()
2462

    
2463
#: All available resources
2464
ALL_FIELDS = {
2465
  constants.QR_CLUSTER: CLUSTER_FIELDS,
2466
  constants.QR_INSTANCE: INSTANCE_FIELDS,
2467
  constants.QR_NODE: NODE_FIELDS,
2468
  constants.QR_LOCK: LOCK_FIELDS,
2469
  constants.QR_GROUP: GROUP_FIELDS,
2470
  constants.QR_OS: OS_FIELDS,
2471
  constants.QR_JOB: JOB_FIELDS,
2472
  constants.QR_EXPORT: EXPORT_FIELDS,
2473
  }
2474

    
2475
#: All available field lists
2476
ALL_FIELD_LISTS = ALL_FIELDS.values()