Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ 87f0aa48

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
        result.append((utils.NiceSortKey(name), idx, row))
750
      else:
751
        result.append(row)
752

    
753
    if not sort:
754
      return result
755

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

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

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

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

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

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

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

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

    
781

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

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

    
797

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

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

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

    
819

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

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

    
833
  return fdef.name
834

    
835

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

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

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

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

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

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

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

    
871
  return result
872

    
873

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

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

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

    
887

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

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

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

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

    
908

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

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

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

    
921

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

925
  """
926
  return value
927

    
928

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

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

    
935

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

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

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

    
956

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

960
  @param attr: Attribute name
961

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

    
966

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

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

    
978

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

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

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

    
994

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

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

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

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

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

    
1012

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

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

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

    
1022

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

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

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

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

    
1041
  return fn
1042

    
1043

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

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

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

    
1057

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

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

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

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

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

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

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

    
1099

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

    
1114

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

    
1139

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

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

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

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

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

    
1159
    return cb(ctx, node, ng)
1160

    
1161
  return fn
1162

    
1163

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

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

1173
  """
1174
  return ng.name
1175

    
1176

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

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

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

    
1188
  return _FS_UNAVAIL
1189

    
1190

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

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

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

    
1203

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

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

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

    
1217
  if not node.vm_capable:
1218
    return _FS_UNAVAIL
1219

    
1220
  if not ctx.curlive_data:
1221
    return _FS_NODATA
1222

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

    
1228
  if kind == QFT_TEXT:
1229
    return value
1230

    
1231
  assert kind in (QFT_NUMBER, QFT_UNIT)
1232

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

    
1241

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

1245
  """
1246
  hv_state = node.hv_state
1247

    
1248
  if hv_state is None:
1249
    return _FS_UNAVAIL
1250

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

    
1253

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

1257
  """
1258
  disk_state = node.disk_state
1259

    
1260
  if disk_state is None:
1261
    return _FS_UNAVAIL
1262

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

    
1267

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

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

    
1300
  fields.extend(_BuildNDFields(False))
1301

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

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

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

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

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

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

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

    
1350
  return _PrepareFieldList(fields, [])
1351

    
1352

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

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

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

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

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

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

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

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

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

    
1415
      yield inst
1416

    
1417

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

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

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

    
1433

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

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

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

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

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

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

    
1460
    return _FS_UNAVAIL
1461

    
1462
  return fn
1463

    
1464

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

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

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

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

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

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

    
1492
  return constants.INSTST_ADMINOFFLINE
1493

    
1494

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

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

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

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

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

    
1514
  return fn
1515

    
1516

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

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

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

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

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

    
1539
    return cb(ctx, index, nic)
1540

    
1541
  return fn
1542

    
1543

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

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

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

    
1557

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

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

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

    
1568
  nicparams = ctx.inst_nicparams[index]
1569

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

    
1575

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

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

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

    
1586
  result = []
1587

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

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

    
1596
  return result
1597

    
1598

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

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

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

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

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

    
1619
  return fn
1620

    
1621

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

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

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

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

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

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

    
1687
  return (fields, aliases)
1688

    
1689

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

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

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

    
1700
  if usage is None:
1701
    usage = 0
1702

    
1703
  return usage
1704

    
1705

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

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

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

    
1716
  if consinfo is None:
1717
    return _FS_UNAVAIL
1718

    
1719
  return consinfo
1720

    
1721

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

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

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

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

    
1748
  return fields
1749

    
1750

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

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

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

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

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

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

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

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

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

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

    
1828
  return fields
1829

    
1830

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

    
1843

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

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

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

    
1860

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

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

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

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

    
1880
  return group.name
1881

    
1882

    
1883
def _BuildInstanceFields():
1884
  """Builds list of fields for instance queries.
1885

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

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

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

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

    
1963
  (network_fields, network_aliases) = _GetInstanceNetworkFields()
1964

    
1965
  fields.extend(network_fields)
1966
  fields.extend(_GetInstanceParameterFields())
1967
  fields.extend(_GetInstanceDiskFields())
1968
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1969

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

    
1977
  return _PrepareFieldList(fields, aliases)
1978

    
1979

    
1980
class LockQueryData:
1981
  """Data container for lock data queries.
1982

1983
  """
1984
  def __init__(self, lockdata):
1985
    """Initializes this class.
1986

1987
    """
1988
    self.lockdata = lockdata
1989

    
1990
  def __iter__(self):
1991
    """Iterate over all locks.
1992

1993
    """
1994
    return iter(self.lockdata)
1995

    
1996

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

2000
  """
2001
  (_, _, owners, _) = data
2002

    
2003
  if owners:
2004
    owners = utils.NiceSort(owners)
2005

    
2006
  return owners
2007

    
2008

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

2012
  """
2013
  (_, _, _, pending) = data
2014

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

    
2019
  return pending
2020

    
2021

    
2022
def _BuildLockFields():
2023
  """Builds list of fields for lock queries.
2024

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

    
2041

    
2042
class GroupQueryData:
2043
  """Data container for node group data queries.
2044

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

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

2059
    """
2060
    self.groups = groups
2061
    self.group_to_nodes = group_to_nodes
2062
    self.group_to_instances = group_to_instances
2063
    self.cluster = cluster
2064
    self.want_diskparams = want_diskparams
2065

    
2066
    # Used for individual rows
2067
    self.group_ipolicy = None
2068
    self.ndparams = None
2069
    self.group_dp = None
2070

    
2071
  def __iter__(self):
2072
    """Iterate over all node groups.
2073

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

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

    
2087

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

    
2095

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

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

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

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

    
2111
  group_to_nodes = operator.attrgetter("group_to_nodes")
2112
  group_to_instances = operator.attrgetter("group_to_instances")
2113

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

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

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

    
2156
  # ND parameters
2157
  fields.extend(_BuildNDFields(True))
2158

    
2159
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
2160

    
2161
  return _PrepareFieldList(fields, [])
2162

    
2163

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

    
2176

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

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

    
2208
  return _PrepareFieldList(fields, [])
2209

    
2210

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

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

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

    
2223

    
2224
def _JobUnavail(inner):
2225
  """Wrapper for L{_JobUnavailInner}.
2226

2227
  """
2228
  return compat.partial(_JobUnavailInner, inner)
2229

    
2230

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

2234
  """
2235
  return map(fn, job.ops)
2236

    
2237

    
2238
def _PerJobOp(fn):
2239
  """Wrapper for L{_PerJobOpInner}.
2240

2241
  """
2242
  return _JobUnavail(compat.partial(_PerJobOpInner, fn))
2243

    
2244

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

2248
  """
2249
  timestamp = fn(job)
2250

    
2251
  if timestamp is None:
2252
    return _FS_UNAVAIL
2253
  else:
2254
    return timestamp
2255

    
2256

    
2257
def _JobTimestamp(fn):
2258
  """Wrapper for L{_JobTimestampInner}.
2259

2260
  """
2261
  return _JobUnavail(compat.partial(_JobTimestampInner, fn))
2262

    
2263

    
2264
def _BuildJobFields():
2265
  """Builds list of fields for job queries.
2266

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

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

    
2320
  return _PrepareFieldList(fields, [])
2321

    
2322

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

2326
  """
2327
  if expname is None:
2328
    return _FS_UNAVAIL
2329
  else:
2330
    return expname
2331

    
2332

    
2333
def _BuildExportFields():
2334
  """Builds list of fields for exports.
2335

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

    
2344
  return _PrepareFieldList(fields, [])
2345

    
2346

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

    
2361

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

    
2368

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

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

2380
    """
2381
    self._cluster = cluster
2382
    self.drain_flag = drain_flag
2383
    self.watcher_pause = watcher_pause
2384

    
2385
  def __iter__(self):
2386
    return iter([self._cluster])
2387

    
2388

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

2392
  """
2393
  if ctx.watcher_pause is None:
2394
    return _FS_UNAVAIL
2395
  else:
2396
    return ctx.watcher_pause
2397

    
2398

    
2399
def _BuildClusterFields():
2400
  """Builds list of fields for cluster information.
2401

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

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

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

    
2429
  # Add timestamps
2430
  fields.extend(_GetItemTimestampFields(CQ_CONFIG))
2431

    
2432
  return _PrepareFieldList(fields, [
2433
    ("name", "cluster_name"),
2434
    ])
2435

    
2436

    
2437
#: Fields for cluster information
2438
CLUSTER_FIELDS = _BuildClusterFields()
2439

    
2440
#: Fields available for node queries
2441
NODE_FIELDS = _BuildNodeFields()
2442

    
2443
#: Fields available for instance queries
2444
INSTANCE_FIELDS = _BuildInstanceFields()
2445

    
2446
#: Fields available for lock queries
2447
LOCK_FIELDS = _BuildLockFields()
2448

    
2449
#: Fields available for node group queries
2450
GROUP_FIELDS = _BuildGroupFields()
2451

    
2452
#: Fields available for operating system queries
2453
OS_FIELDS = _BuildOsFields()
2454

    
2455
#: Fields available for job queries
2456
JOB_FIELDS = _BuildJobFields()
2457

    
2458
#: Fields available for exports
2459
EXPORT_FIELDS = _BuildExportFields()
2460

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

    
2473
#: All available field lists
2474
ALL_FIELD_LISTS = ALL_FIELDS.values()