Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ 526f866b

History | View | Annotate | Download (72.7 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
    }
1776

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

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

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

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

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

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

    
1827
  return fields
1828

    
1829

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

    
1842

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

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

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

    
1859

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

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

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

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

    
1879
  return group.name
1880

    
1881

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

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

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

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

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

    
1962
  (network_fields, network_aliases) = _GetInstanceNetworkFields()
1963

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

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

    
1976
  return _PrepareFieldList(fields, aliases)
1977

    
1978

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

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

1986
    """
1987
    self.lockdata = lockdata
1988

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

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

    
1995

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

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

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

    
2005
  return owners
2006

    
2007

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

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

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

    
2018
  return pending
2019

    
2020

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

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

    
2040

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

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

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

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

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

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

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

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

    
2086

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

    
2094

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

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

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

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

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

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

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

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

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

    
2158
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
2159

    
2160
  return _PrepareFieldList(fields, [])
2161

    
2162

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

    
2175

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

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

    
2207
  return _PrepareFieldList(fields, [])
2208

    
2209

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

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

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

    
2222

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

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

    
2229

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

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

    
2236

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

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

    
2243

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

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

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

    
2255

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

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

    
2262

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

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

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

    
2319
  return _PrepareFieldList(fields, [])
2320

    
2321

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

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

    
2331

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

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

    
2343
  return _PrepareFieldList(fields, [])
2344

    
2345

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

    
2360

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

    
2367

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

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

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

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

    
2387

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

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

    
2397

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

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

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

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

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

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

    
2435

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

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

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

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

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

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

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

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

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

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