Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ b8291e00

History | View | Annotate | Download (71.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

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

    
73

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

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

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

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

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

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

    
103
# Query field flags
104
QFF_HOSTNAME = 0x01
105
QFF_IP_ADDRESS = 0x02
106
# Next values: 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200
107
QFF_ALL = (QFF_HOSTNAME | QFF_IP_ADDRESS)
108

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

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

    
124
# Unique objects for special field statuses
125
_FS_UNKNOWN = object()
126
_FS_NODATA = object()
127
_FS_UNAVAIL = object()
128
_FS_OFFLINE = object()
129

    
130
#: List of all special status
131
_FS_ALL = frozenset([_FS_UNKNOWN, _FS_NODATA, _FS_UNAVAIL, _FS_OFFLINE])
132

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

    
143
_SERIAL_NO_DOC = "%s object serial number, incremented on each modification"
144

    
145
# TODO: Consider moving titles closer to constants
146
NDP_TITLE = {
147
  constants.ND_OOB_PROGRAM: "OutOfBandProgram",
148
  constants.ND_SPINDLE_COUNT: "SpindleCount",
149
  }
150

    
151

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

155
  """
156
  return _FS_UNKNOWN
157

    
158

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

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

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

169
  """
170
  result = []
171

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

    
179
    assert len(fdef) == 4
180

    
181
    result.append(fdef)
182

    
183
  return result
184

    
185

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

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

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

    
194

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

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

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

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

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

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

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

225
    """
226
    self._namefield = namefield
227

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

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

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

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

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

244
    @rtype: list
245

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

    
250
    return utils.UniqueSequence(self._names)
251

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

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

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

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

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

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

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

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

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

281
    """
282
    self._NeedAllNames()
283

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

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

293
    """
294
    if datakind is not None:
295
      self._datakinds.add(datakind)
296

    
297
    if self._allnames:
298
      return
299

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

    
309

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

313
  """
314
  return op_fn(fn(ctx, item) for fn in sentences)
315

    
316

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

320
  """
321
  return op_fn(inner(ctx, item))
322

    
323

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

327
  """
328
  return op_fn(retrieval_fn(ctx, item), value)
329

    
330

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

334
  """
335
  return not fn(lhs, rhs)
336

    
337

    
338
def _PrepareRegex(pattern):
339
  """Compiles a regular expression.
340

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

    
347

    
348
class _FilterCompilerHelper:
349
  """Converts a query filter to a callable usable for filtering.
350

351
  """
352
  # String statement has no effect, pylint: disable=W0105
353

    
354
  #: How deep filters can be nested
355
  _LEVELS_MAX = 10
356

    
357
  # Unique identifiers for operator groups
358
  (_OPTYPE_LOGIC,
359
   _OPTYPE_UNARY,
360
   _OPTYPE_BINARY) = range(1, 4)
361

    
362
  """Functions for equality checks depending on field flags.
363

364
  List of tuples containing flags and a callable receiving the left- and
365
  right-hand side of the operator. The flags are an OR-ed value of C{QFF_*}
366
  (e.g. L{QFF_HOSTNAME}).
367

368
  Order matters. The first item with flags will be used. Flags are checked
369
  using binary AND.
370

371
  """
372
  _EQUALITY_CHECKS = [
373
    (QFF_HOSTNAME,
374
     lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
375
                                               case_sensitive=False),
376
     None),
377
    (None, operator.eq, None),
378
    ]
379

    
380
  """Known operators
381

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

385
    - C{_OPTYPE_LOGIC}: Callable taking any number of arguments; used by
386
      L{_HandleLogicOp}
387
    - C{_OPTYPE_UNARY}: Always C{None}; details handled by L{_HandleUnaryOp}
388
    - C{_OPTYPE_BINARY}: Callable taking exactly two parameters, the left- and
389
      right-hand side of the operator, used by L{_HandleBinaryOp}
390

391
  """
392
  _OPS = {
393
    # Logic operators
394
    qlang.OP_OR: (_OPTYPE_LOGIC, compat.any),
395
    qlang.OP_AND: (_OPTYPE_LOGIC, compat.all),
396

    
397
    # Unary operators
398
    qlang.OP_NOT: (_OPTYPE_UNARY, None),
399
    qlang.OP_TRUE: (_OPTYPE_UNARY, None),
400

    
401
    # Binary operators
402
    qlang.OP_EQUAL: (_OPTYPE_BINARY, _EQUALITY_CHECKS),
403
    qlang.OP_NOT_EQUAL:
404
      (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn), valprepfn)
405
                        for (flags, fn, valprepfn) in _EQUALITY_CHECKS]),
406
    qlang.OP_LT: (_OPTYPE_BINARY, [
407
      (None, operator.lt, None),
408
      ]),
409
    qlang.OP_GT: (_OPTYPE_BINARY, [
410
      (None, operator.gt, None),
411
      ]),
412
    qlang.OP_LE: (_OPTYPE_BINARY, [
413
      (None, operator.le, None),
414
      ]),
415
    qlang.OP_GE: (_OPTYPE_BINARY, [
416
      (None, operator.ge, None),
417
      ]),
418
    qlang.OP_REGEXP: (_OPTYPE_BINARY, [
419
      (None, lambda lhs, rhs: rhs.search(lhs), _PrepareRegex),
420
      ]),
421
    qlang.OP_CONTAINS: (_OPTYPE_BINARY, [
422
      (None, operator.contains, None),
423
      ]),
424
    }
425

    
426
  def __init__(self, fields):
427
    """Initializes this class.
428

429
    @param fields: Field definitions (return value of L{_PrepareFieldList})
430

431
    """
432
    self._fields = fields
433
    self._hints = None
434
    self._op_handler = None
435

    
436
  def __call__(self, hints, qfilter):
437
    """Converts a query filter into a callable function.
438

439
    @type hints: L{_FilterHints} or None
440
    @param hints: Callbacks doing analysis on filter
441
    @type qfilter: list
442
    @param qfilter: Filter structure
443
    @rtype: callable
444
    @return: Function receiving context and item as parameters, returning
445
             boolean as to whether item matches filter
446

447
    """
448
    self._op_handler = {
449
      self._OPTYPE_LOGIC:
450
        (self._HandleLogicOp, getattr(hints, "NoteLogicOp", None)),
451
      self._OPTYPE_UNARY:
452
        (self._HandleUnaryOp, getattr(hints, "NoteUnaryOp", None)),
453
      self._OPTYPE_BINARY:
454
        (self._HandleBinaryOp, getattr(hints, "NoteBinaryOp", None)),
455
      }
456

    
457
    try:
458
      filter_fn = self._Compile(qfilter, 0)
459
    finally:
460
      self._op_handler = None
461

    
462
    return filter_fn
463

    
464
  def _Compile(self, qfilter, level):
465
    """Inner function for converting filters.
466

467
    Calls the correct handler functions for the top-level operator. This
468
    function is called recursively (e.g. for logic operators).
469

470
    """
471
    if not (isinstance(qfilter, (list, tuple)) and qfilter):
472
      raise errors.ParameterError("Invalid filter on level %s" % level)
473

    
474
    # Limit recursion
475
    if level >= self._LEVELS_MAX:
476
      raise errors.ParameterError("Only up to %s levels are allowed (filter"
477
                                  " nested too deep)" % self._LEVELS_MAX)
478

    
479
    # Create copy to be modified
480
    operands = qfilter[:]
481
    op = operands.pop(0)
482

    
483
    try:
484
      (kind, op_data) = self._OPS[op]
485
    except KeyError:
486
      raise errors.ParameterError("Unknown operator '%s'" % op)
487

    
488
    (handler, hints_cb) = self._op_handler[kind]
489

    
490
    return handler(hints_cb, level, op, op_data, operands)
491

    
492
  def _LookupField(self, name):
493
    """Returns a field definition by name.
494

495
    """
496
    try:
497
      return self._fields[name]
498
    except KeyError:
499
      raise errors.ParameterError("Unknown field '%s'" % name)
500

    
501
  def _HandleLogicOp(self, hints_fn, level, op, op_fn, operands):
502
    """Handles logic operators.
503

504
    @type hints_fn: callable
505
    @param hints_fn: Callback doing some analysis on the filter
506
    @type level: integer
507
    @param level: Current depth
508
    @type op: string
509
    @param op: Operator
510
    @type op_fn: callable
511
    @param op_fn: Function implementing operator
512
    @type operands: list
513
    @param operands: List of operands
514

515
    """
516
    if hints_fn:
517
      hints_fn(op)
518

    
519
    return compat.partial(_WrapLogicOp, op_fn,
520
                          [self._Compile(op, level + 1) for op in operands])
521

    
522
  def _HandleUnaryOp(self, hints_fn, level, op, op_fn, operands):
523
    """Handles unary operators.
524

525
    @type hints_fn: callable
526
    @param hints_fn: Callback doing some analysis on the filter
527
    @type level: integer
528
    @param level: Current depth
529
    @type op: string
530
    @param op: Operator
531
    @type op_fn: callable
532
    @param op_fn: Function implementing operator
533
    @type operands: list
534
    @param operands: List of operands
535

536
    """
537
    assert op_fn is None
538

    
539
    if hints_fn:
540
      hints_fn(op)
541

    
542
    if len(operands) != 1:
543
      raise errors.ParameterError("Unary operator '%s' expects exactly one"
544
                                  " operand" % op)
545

    
546
    if op == qlang.OP_TRUE:
547
      (_, _, _, retrieval_fn) = self._LookupField(operands[0])
548

    
549
      op_fn = operator.truth
550
      arg = retrieval_fn
551
    elif op == qlang.OP_NOT:
552
      op_fn = operator.not_
553
      arg = self._Compile(operands[0], level + 1)
554
    else:
555
      raise errors.ProgrammerError("Can't handle operator '%s'" % op)
556

    
557
    return compat.partial(_WrapUnaryOp, op_fn, arg)
558

    
559
  def _HandleBinaryOp(self, hints_fn, level, op, op_data, operands):
560
    """Handles binary operators.
561

562
    @type hints_fn: callable
563
    @param hints_fn: Callback doing some analysis on the filter
564
    @type level: integer
565
    @param level: Current depth
566
    @type op: string
567
    @param op: Operator
568
    @param op_data: Functions implementing operators
569
    @type operands: list
570
    @param operands: List of operands
571

572
    """
573
    # Unused arguments, pylint: disable=W0613
574
    try:
575
      (name, value) = operands
576
    except (ValueError, TypeError):
577
      raise errors.ParameterError("Invalid binary operator, expected exactly"
578
                                  " two operands")
579

    
580
    (fdef, datakind, field_flags, retrieval_fn) = self._LookupField(name)
581

    
582
    assert fdef.kind != QFT_UNKNOWN
583

    
584
    # TODO: Type conversions?
585

    
586
    verify_fn = _VERIFY_FN[fdef.kind]
587
    if not verify_fn(value):
588
      raise errors.ParameterError("Unable to compare field '%s' (type '%s')"
589
                                  " with '%s', expected %s" %
590
                                  (name, fdef.kind, value.__class__.__name__,
591
                                   verify_fn))
592

    
593
    if hints_fn:
594
      hints_fn(op, datakind, name, value)
595

    
596
    for (fn_flags, fn, valprepfn) in op_data:
597
      if fn_flags is None or fn_flags & field_flags:
598
        # Prepare value if necessary (e.g. compile regular expression)
599
        if valprepfn:
600
          value = valprepfn(value)
601

    
602
        return compat.partial(_WrapBinaryOp, fn, retrieval_fn, value)
603

    
604
    raise errors.ProgrammerError("Unable to find operator implementation"
605
                                 " (op '%s', flags %s)" % (op, field_flags))
606

    
607

    
608
def _CompileFilter(fields, hints, qfilter):
609
  """Converts a query filter into a callable function.
610

611
  See L{_FilterCompilerHelper} for details.
612

613
  @rtype: callable
614

615
  """
616
  return _FilterCompilerHelper(fields)(hints, qfilter)
617

    
618

    
619
class Query:
620
  def __init__(self, fieldlist, selected, qfilter=None, namefield=None):
621
    """Initializes this class.
622

623
    The field definition is a dictionary with the field's name as a key and a
624
    tuple containing, in order, the field definition object
625
    (L{objects.QueryFieldDefinition}, the data kind to help calling code
626
    collect data and a retrieval function. The retrieval function is called
627
    with two parameters, in order, the data container and the item in container
628
    (see L{Query.Query}).
629

630
    Users of this class can call L{RequestedData} before preparing the data
631
    container to determine what data is needed.
632

633
    @type fieldlist: dictionary
634
    @param fieldlist: Field definitions
635
    @type selected: list of strings
636
    @param selected: List of selected fields
637

638
    """
639
    assert namefield is None or namefield in fieldlist
640

    
641
    self._fields = _GetQueryFields(fieldlist, selected)
642

    
643
    self._filter_fn = None
644
    self._requested_names = None
645
    self._filter_datakinds = frozenset()
646

    
647
    if qfilter is not None:
648
      # Collect requested names if wanted
649
      if namefield:
650
        hints = _FilterHints(namefield)
651
      else:
652
        hints = None
653

    
654
      # Build filter function
655
      self._filter_fn = _CompileFilter(fieldlist, hints, qfilter)
656
      if hints:
657
        self._requested_names = hints.RequestedNames()
658
        self._filter_datakinds = hints.ReferencedData()
659

    
660
    if namefield is None:
661
      self._name_fn = None
662
    else:
663
      (_, _, _, self._name_fn) = fieldlist[namefield]
664

    
665
  def RequestedNames(self):
666
    """Returns all names referenced in the filter.
667

668
    If there is no filter or operators are preventing determining the exact
669
    names, C{None} is returned.
670

671
    """
672
    return self._requested_names
673

    
674
  def RequestedData(self):
675
    """Gets requested kinds of data.
676

677
    @rtype: frozenset
678

679
    """
680
    return (self._filter_datakinds |
681
            frozenset(datakind for (_, datakind, _, _) in self._fields
682
                      if datakind is not None))
683

    
684
  def GetFields(self):
685
    """Returns the list of fields for this query.
686

687
    Includes unknown fields.
688

689
    @rtype: List of L{objects.QueryFieldDefinition}
690

691
    """
692
    return GetAllFields(self._fields)
693

    
694
  def Query(self, ctx, sort_by_name=True):
695
    """Execute a query.
696

697
    @param ctx: Data container passed to field retrieval functions, must
698
      support iteration using C{__iter__}
699
    @type sort_by_name: boolean
700
    @param sort_by_name: Whether to sort by name or keep the input data's
701
      ordering
702

703
    """
704
    sort = (self._name_fn and sort_by_name)
705

    
706
    result = []
707

    
708
    for idx, item in enumerate(ctx):
709
      if not (self._filter_fn is None or self._filter_fn(ctx, item)):
710
        continue
711

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

    
714
      # Verify result
715
      if __debug__:
716
        _VerifyResultRow(self._fields, row)
717

    
718
      if sort:
719
        (status, name) = _ProcessResult(self._name_fn(ctx, item))
720
        assert status == constants.RS_NORMAL
721
        # TODO: Are there cases where we wouldn't want to use NiceSort?
722
        result.append((utils.NiceSortKey(name), idx, row))
723
      else:
724
        result.append(row)
725

    
726
    if not sort:
727
      return result
728

    
729
    # TODO: Would "heapq" be more efficient than sorting?
730

    
731
    # Sorting in-place instead of using "sorted()"
732
    result.sort()
733

    
734
    assert not result or (len(result[0]) == 3 and len(result[-1]) == 3)
735

    
736
    return map(operator.itemgetter(2), result)
737

    
738
  def OldStyleQuery(self, ctx, sort_by_name=True):
739
    """Query with "old" query result format.
740

741
    See L{Query.Query} for arguments.
742

743
    """
744
    unknown = set(fdef.name for (fdef, _, _, _) in self._fields
745
                  if fdef.kind == QFT_UNKNOWN)
746
    if unknown:
747
      raise errors.OpPrereqError("Unknown output fields selected: %s" %
748
                                 (utils.CommaJoin(unknown), ),
749
                                 errors.ECODE_INVAL)
750

    
751
    return [[value for (_, value) in row]
752
            for row in self.Query(ctx, sort_by_name=sort_by_name)]
753

    
754

    
755
def _ProcessResult(value):
756
  """Converts result values into externally-visible ones.
757

758
  """
759
  if value is _FS_UNKNOWN:
760
    return (RS_UNKNOWN, None)
761
  elif value is _FS_NODATA:
762
    return (RS_NODATA, None)
763
  elif value is _FS_UNAVAIL:
764
    return (RS_UNAVAIL, None)
765
  elif value is _FS_OFFLINE:
766
    return (RS_OFFLINE, None)
767
  else:
768
    return (RS_NORMAL, value)
769

    
770

    
771
def _VerifyResultRow(fields, row):
772
  """Verifies the contents of a query result row.
773

774
  @type fields: list
775
  @param fields: Field definitions for result
776
  @type row: list of tuples
777
  @param row: Row data
778

779
  """
780
  assert len(row) == len(fields)
781
  errs = []
782
  for ((status, value), (fdef, _, _, _)) in zip(row, fields):
783
    if status == RS_NORMAL:
784
      if not _VERIFY_FN[fdef.kind](value):
785
        errs.append("normal field %s fails validation (value is %s)" %
786
                    (fdef.name, value))
787
    elif value is not None:
788
      errs.append("abnormal field %s has a non-None value" % fdef.name)
789
  assert not errs, ("Failed validation: %s in row %s" %
790
                    (utils.CommaJoin(errs), row))
791

    
792

    
793
def _FieldDictKey((fdef, _, flags, fn)):
794
  """Generates key for field dictionary.
795

796
  """
797
  assert fdef.name and fdef.title, "Name and title are required"
798
  assert FIELD_NAME_RE.match(fdef.name)
799
  assert TITLE_RE.match(fdef.title)
800
  assert (DOC_RE.match(fdef.doc) and len(fdef.doc.splitlines()) == 1 and
801
          fdef.doc.strip() == fdef.doc), \
802
         "Invalid description for field '%s'" % fdef.name
803
  assert callable(fn)
804
  assert (flags & ~QFF_ALL) == 0, "Unknown flags for field '%s'" % fdef.name
805

    
806
  return fdef.name
807

    
808

    
809
def _PrepareFieldList(fields, aliases):
810
  """Prepares field list for use by L{Query}.
811

812
  Converts the list to a dictionary and does some verification.
813

814
  @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data
815
      kind, retrieval function)
816
  @param fields: List of fields, see L{Query.__init__} for a better
817
      description
818
  @type aliases: list of tuples; (alias, target)
819
  @param aliases: list of tuples containing aliases; for each
820
      alias/target pair, a duplicate will be created in the field list
821
  @rtype: dict
822
  @return: Field dictionary for L{Query}
823

824
  """
825
  if __debug__:
826
    duplicates = utils.FindDuplicates(fdef.title.lower()
827
                                      for (fdef, _, _, _) in fields)
828
    assert not duplicates, "Duplicate title(s) found: %r" % duplicates
829

    
830
  result = utils.SequenceToDict(fields, key=_FieldDictKey)
831

    
832
  for alias, target in aliases:
833
    assert alias not in result, "Alias %s overrides an existing field" % alias
834
    assert target in result, "Missing target %s for alias %s" % (target, alias)
835
    (fdef, k, flags, fn) = result[target]
836
    fdef = fdef.Copy()
837
    fdef.name = alias
838
    result[alias] = (fdef, k, flags, fn)
839

    
840
  assert len(result) == len(fields) + len(aliases)
841
  assert compat.all(name == fdef.name
842
                    for (name, (fdef, _, _, _)) in result.items())
843

    
844
  return result
845

    
846

    
847
def GetQueryResponse(query, ctx, sort_by_name=True):
848
  """Prepares the response for a query.
849

850
  @type query: L{Query}
851
  @param ctx: Data container, see L{Query.Query}
852
  @type sort_by_name: boolean
853
  @param sort_by_name: Whether to sort by name or keep the input data's
854
    ordering
855

856
  """
857
  return objects.QueryResponse(data=query.Query(ctx, sort_by_name=sort_by_name),
858
                               fields=query.GetFields()).ToDict()
859

    
860

    
861
def QueryFields(fielddefs, selected):
862
  """Returns list of available fields.
863

864
  @type fielddefs: dict
865
  @param fielddefs: Field definitions
866
  @type selected: list of strings
867
  @param selected: List of selected fields
868
  @return: List of L{objects.QueryFieldDefinition}
869

870
  """
871
  if selected is None:
872
    # Client requests all fields, sort by name
873
    fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
874
                           key=operator.attrgetter("name"))
875
  else:
876
    # Keep order as requested by client
877
    fdefs = Query(fielddefs, selected).GetFields()
878

    
879
  return objects.QueryFieldsResponse(fields=fdefs).ToDict()
880

    
881

    
882
def _MakeField(name, title, kind, doc):
883
  """Wrapper for creating L{objects.QueryFieldDefinition} instances.
884

885
  @param name: Field name as a regular expression
886
  @param title: Human-readable title
887
  @param kind: Field type
888
  @param doc: Human-readable description
889

890
  """
891
  return objects.QueryFieldDefinition(name=name, title=title, kind=kind,
892
                                      doc=doc)
893

    
894

    
895
def _StaticValueInner(value, ctx, _): # pylint: disable=W0613
896
  """Returns a static value.
897

898
  """
899
  return value
900

    
901

    
902
def _StaticValue(value):
903
  """Prepares a function to return a static value.
904

905
  """
906
  return compat.partial(_StaticValueInner, value)
907

    
908

    
909
def _GetNodeRole(node, master_name):
910
  """Determine node role.
911

912
  @type node: L{objects.Node}
913
  @param node: Node object
914
  @type master_name: string
915
  @param master_name: Master node name
916

917
  """
918
  if node.name == master_name:
919
    return constants.NR_MASTER
920
  elif node.master_candidate:
921
    return constants.NR_MCANDIDATE
922
  elif node.drained:
923
    return constants.NR_DRAINED
924
  elif node.offline:
925
    return constants.NR_OFFLINE
926
  else:
927
    return constants.NR_REGULAR
928

    
929

    
930
def _GetItemAttr(attr):
931
  """Returns a field function to return an attribute of the item.
932

933
  @param attr: Attribute name
934

935
  """
936
  getter = operator.attrgetter(attr)
937
  return lambda _, item: getter(item)
938

    
939

    
940
def _GetNDParam(name):
941
  """Return a field function to return an ND parameter out of the context.
942

943
  """
944
  def _helper(ctx, _):
945
    if ctx.ndparams is None:
946
      return _FS_UNAVAIL
947
    else:
948
      return ctx.ndparams.get(name, None)
949
  return _helper
950

    
951

    
952
def _BuildNDFields(is_group):
953
  """Builds all the ndparam fields.
954

955
  @param is_group: whether this is called at group or node level
956

957
  """
958
  if is_group:
959
    field_kind = GQ_CONFIG
960
  else:
961
    field_kind = NQ_GROUP
962
  return [(_MakeField("ndp/%s" % name, NDP_TITLE.get(name, "ndp/%s" % name),
963
                      _VTToQFT[kind], "The \"%s\" node parameter" % name),
964
           field_kind, 0, _GetNDParam(name))
965
          for name, kind in constants.NDS_PARAMETER_TYPES.items()]
966

    
967

    
968
def _ConvWrapInner(convert, fn, ctx, item):
969
  """Wrapper for converting values.
970

971
  @param convert: Conversion function receiving value as single parameter
972
  @param fn: Retrieval function
973

974
  """
975
  value = fn(ctx, item)
976

    
977
  # Is the value an abnormal status?
978
  if compat.any(value is fs for fs in _FS_ALL):
979
    # Return right away
980
    return value
981

    
982
  # TODO: Should conversion function also receive context, item or both?
983
  return convert(value)
984

    
985

    
986
def _ConvWrap(convert, fn):
987
  """Convenience wrapper for L{_ConvWrapInner}.
988

989
  @param convert: Conversion function receiving value as single parameter
990
  @param fn: Retrieval function
991

992
  """
993
  return compat.partial(_ConvWrapInner, convert, fn)
994

    
995

    
996
def _GetItemTimestamp(getter):
997
  """Returns function for getting timestamp of item.
998

999
  @type getter: callable
1000
  @param getter: Function to retrieve timestamp attribute
1001

1002
  """
1003
  def fn(_, item):
1004
    """Returns a timestamp of item.
1005

1006
    """
1007
    timestamp = getter(item)
1008
    if timestamp is None:
1009
      # Old configs might not have all timestamps
1010
      return _FS_UNAVAIL
1011
    else:
1012
      return timestamp
1013

    
1014
  return fn
1015

    
1016

    
1017
def _GetItemTimestampFields(datatype):
1018
  """Returns common timestamp fields.
1019

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

1022
  """
1023
  return [
1024
    (_MakeField("ctime", "CTime", QFT_TIMESTAMP, "Creation timestamp"),
1025
     datatype, 0, _GetItemTimestamp(operator.attrgetter("ctime"))),
1026
    (_MakeField("mtime", "MTime", QFT_TIMESTAMP, "Modification timestamp"),
1027
     datatype, 0, _GetItemTimestamp(operator.attrgetter("mtime"))),
1028
    ]
1029

    
1030

    
1031
class NodeQueryData:
1032
  """Data container for node data queries.
1033

1034
  """
1035
  def __init__(self, nodes, live_data, master_name, node_to_primary,
1036
               node_to_secondary, groups, oob_support, cluster):
1037
    """Initializes this class.
1038

1039
    """
1040
    self.nodes = nodes
1041
    self.live_data = live_data
1042
    self.master_name = master_name
1043
    self.node_to_primary = node_to_primary
1044
    self.node_to_secondary = node_to_secondary
1045
    self.groups = groups
1046
    self.oob_support = oob_support
1047
    self.cluster = cluster
1048

    
1049
    # Used for individual rows
1050
    self.curlive_data = None
1051
    self.ndparams = None
1052

    
1053
  def __iter__(self):
1054
    """Iterate over all nodes.
1055

1056
    This function has side-effects and only one instance of the resulting
1057
    generator should be used at a time.
1058

1059
    """
1060
    for node in self.nodes:
1061
      group = self.groups.get(node.group, None)
1062
      if group is None:
1063
        self.ndparams = None
1064
      else:
1065
        self.ndparams = self.cluster.FillND(node, group)
1066
      if self.live_data:
1067
        self.curlive_data = self.live_data.get(node.name, None)
1068
      else:
1069
        self.curlive_data = None
1070
      yield node
1071

    
1072

    
1073
#: Fields that are direct attributes of an L{objects.Node} object
1074
_NODE_SIMPLE_FIELDS = {
1075
  "drained": ("Drained", QFT_BOOL, 0, "Whether node is drained"),
1076
  "master_candidate": ("MasterC", QFT_BOOL, 0,
1077
                       "Whether node is a master candidate"),
1078
  "master_capable": ("MasterCapable", QFT_BOOL, 0,
1079
                     "Whether node can become a master candidate"),
1080
  "name": ("Node", QFT_TEXT, QFF_HOSTNAME, "Node name"),
1081
  "offline": ("Offline", QFT_BOOL, 0, "Whether node is marked offline"),
1082
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Node"),
1083
  "uuid": ("UUID", QFT_TEXT, 0, "Node UUID"),
1084
  "vm_capable": ("VMCapable", QFT_BOOL, 0, "Whether node can host instances"),
1085
  }
1086

    
1087

    
1088
#: Fields requiring talking to the node
1089
# Note that none of these are available for non-vm_capable nodes
1090
_NODE_LIVE_FIELDS = {
1091
  "bootid": ("BootID", QFT_TEXT, "bootid",
1092
             "Random UUID renewed for each system reboot, can be used"
1093
             " for detecting reboots by tracking changes"),
1094
  "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes",
1095
             "Number of NUMA domains on node (if exported by hypervisor)"),
1096
  "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets",
1097
               "Number of physical CPU sockets (if exported by hypervisor)"),
1098
  "ctotal": ("CTotal", QFT_NUMBER, "cpu_total", "Number of logical processors"),
1099
  "dfree": ("DFree", QFT_UNIT, "vg_free",
1100
            "Available disk space in volume group"),
1101
  "dtotal": ("DTotal", QFT_UNIT, "vg_size",
1102
             "Total disk space in volume group used for instance disk"
1103
             " allocation"),
1104
  "mfree": ("MFree", QFT_UNIT, "memory_free",
1105
            "Memory available for instance allocations"),
1106
  "mnode": ("MNode", QFT_UNIT, "memory_dom0",
1107
            "Amount of memory used by node (dom0 for Xen)"),
1108
  "mtotal": ("MTotal", QFT_UNIT, "memory_total",
1109
             "Total amount of memory of physical machine"),
1110
  }
1111

    
1112

    
1113
def _GetGroup(cb):
1114
  """Build function for calling another function with an node group.
1115

1116
  @param cb: The callback to be called with the nodegroup
1117

1118
  """
1119
  def fn(ctx, node):
1120
    """Get group data for a node.
1121

1122
    @type ctx: L{NodeQueryData}
1123
    @type inst: L{objects.Node}
1124
    @param inst: Node object
1125

1126
    """
1127
    ng = ctx.groups.get(node.group, None)
1128
    if ng is None:
1129
      # Nodes always have a group, or the configuration is corrupt
1130
      return _FS_UNAVAIL
1131

    
1132
    return cb(ctx, node, ng)
1133

    
1134
  return fn
1135

    
1136

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

1140
  @type ctx: L{NodeQueryData}
1141
  @type node: L{objects.Node}
1142
  @param node: Node object
1143
  @type ng: L{objects.NodeGroup}
1144
  @param ng: The node group this node belongs to
1145

1146
  """
1147
  return ng.name
1148

    
1149

    
1150
def _GetNodePower(ctx, node):
1151
  """Returns the node powered state
1152

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

1157
  """
1158
  if ctx.oob_support[node.name]:
1159
    return node.powered
1160

    
1161
  return _FS_UNAVAIL
1162

    
1163

    
1164
def _GetNdParams(ctx, node, ng):
1165
  """Returns the ndparams for this node.
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 ctx.cluster.SimpleFillND(ng.FillND(node))
1175

    
1176

    
1177
def _GetLiveNodeField(field, kind, ctx, node):
1178
  """Gets the value of a "live" field from L{NodeQueryData}.
1179

1180
  @param field: Live field name
1181
  @param kind: Data kind, one of L{constants.QFT_ALL}
1182
  @type ctx: L{NodeQueryData}
1183
  @type node: L{objects.Node}
1184
  @param node: Node object
1185

1186
  """
1187
  if node.offline:
1188
    return _FS_OFFLINE
1189

    
1190
  if not node.vm_capable:
1191
    return _FS_UNAVAIL
1192

    
1193
  if not ctx.curlive_data:
1194
    return _FS_NODATA
1195

    
1196
  try:
1197
    value = ctx.curlive_data[field]
1198
  except KeyError:
1199
    return _FS_UNAVAIL
1200

    
1201
  if kind == QFT_TEXT:
1202
    return value
1203

    
1204
  assert kind in (QFT_NUMBER, QFT_UNIT)
1205

    
1206
  # Try to convert into number
1207
  try:
1208
    return int(value)
1209
  except (ValueError, TypeError):
1210
    logging.exception("Failed to convert node field '%s' (value %r) to int",
1211
                      value, field)
1212
    return _FS_UNAVAIL
1213

    
1214

    
1215
def _GetNodeHvState(_, node):
1216
  """Converts node's hypervisor state for query result.
1217

1218
  """
1219
  hv_state = node.hv_state
1220

    
1221
  if hv_state is None:
1222
    return _FS_UNAVAIL
1223

    
1224
  return dict((name, value.ToDict()) for (name, value) in hv_state.items())
1225

    
1226

    
1227
def _GetNodeDiskState(_, node):
1228
  """Converts node's disk state for query result.
1229

1230
  """
1231
  disk_state = node.disk_state
1232

    
1233
  if disk_state is None:
1234
    return _FS_UNAVAIL
1235

    
1236
  return dict((disk_kind, dict((name, value.ToDict())
1237
                               for (name, value) in kind_state.items()))
1238
              for (disk_kind, kind_state) in disk_state.items())
1239

    
1240

    
1241
def _BuildNodeFields():
1242
  """Builds list of fields for node queries.
1243

1244
  """
1245
  fields = [
1246
    (_MakeField("pip", "PrimaryIP", QFT_TEXT, "Primary IP address"),
1247
     NQ_CONFIG, 0, _GetItemAttr("primary_ip")),
1248
    (_MakeField("sip", "SecondaryIP", QFT_TEXT, "Secondary IP address"),
1249
     NQ_CONFIG, 0, _GetItemAttr("secondary_ip")),
1250
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0,
1251
     lambda ctx, node: list(node.GetTags())),
1252
    (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"),
1253
     NQ_CONFIG, 0, lambda ctx, node: node.name == ctx.master_name),
1254
    (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0,
1255
     _GetGroup(_GetNodeGroup)),
1256
    (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"),
1257
     NQ_CONFIG, 0, _GetItemAttr("group")),
1258
    (_MakeField("powered", "Powered", QFT_BOOL,
1259
                "Whether node is thought to be powered on"),
1260
     NQ_OOB, 0, _GetNodePower),
1261
    (_MakeField("ndparams", "NodeParameters", QFT_OTHER,
1262
                "Merged node parameters"),
1263
     NQ_GROUP, 0, _GetGroup(_GetNdParams)),
1264
    (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER,
1265
                "Custom node parameters"),
1266
      NQ_GROUP, 0, _GetItemAttr("ndparams")),
1267
    (_MakeField("hv_state", "HypervisorState", QFT_OTHER, "Hypervisor state"),
1268
     NQ_CONFIG, 0, _GetNodeHvState),
1269
    (_MakeField("disk_state", "DiskState", QFT_OTHER, "Disk state"),
1270
     NQ_CONFIG, 0, _GetNodeDiskState),
1271
    ]
1272

    
1273
  fields.extend(_BuildNDFields(False))
1274

    
1275
  # Node role
1276
  role_values = (constants.NR_MASTER, constants.NR_MCANDIDATE,
1277
                 constants.NR_REGULAR, constants.NR_DRAINED,
1278
                 constants.NR_OFFLINE)
1279
  role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate,"
1280
              " \"%s\" for regular, \"%s\" for a drained, \"%s\" for offline" %
1281
              role_values)
1282
  fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
1283
                 lambda ctx, node: _GetNodeRole(node, ctx.master_name)))
1284
  assert set(role_values) == constants.NR_ALL
1285

    
1286
  def _GetLength(getter):
1287
    return lambda ctx, node: len(getter(ctx)[node.name])
1288

    
1289
  def _GetList(getter):
1290
    return lambda ctx, node: list(getter(ctx)[node.name])
1291

    
1292
  # Add fields operating on instance lists
1293
  for prefix, titleprefix, docword, getter in \
1294
      [("p", "Pri", "primary", operator.attrgetter("node_to_primary")),
1295
       ("s", "Sec", "secondary", operator.attrgetter("node_to_secondary"))]:
1296
    # TODO: Allow filterting by hostname in list
1297
    fields.extend([
1298
      (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER,
1299
                  "Number of instances with this node as %s" % docword),
1300
       NQ_INST, 0, _GetLength(getter)),
1301
      (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
1302
                  QFT_OTHER,
1303
                  "List of instances with this node as %s" % docword),
1304
       NQ_INST, 0, _GetList(getter)),
1305
      ])
1306

    
1307
  # Add simple fields
1308
  fields.extend([
1309
    (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1310
    for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()
1311
    ])
1312

    
1313
  # Add fields requiring live data
1314
  fields.extend([
1315
    (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1316
     compat.partial(_GetLiveNodeField, nfield, kind))
1317
    for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()
1318
    ])
1319

    
1320
  # Add timestamps
1321
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1322

    
1323
  return _PrepareFieldList(fields, [])
1324

    
1325

    
1326
class InstanceQueryData:
1327
  """Data container for instance data queries.
1328

1329
  """
1330
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
1331
               live_data, wrongnode_inst, console, nodes, groups):
1332
    """Initializes this class.
1333

1334
    @param instances: List of instance objects
1335
    @param cluster: Cluster object
1336
    @type disk_usage: dict; instance name as key
1337
    @param disk_usage: Per-instance disk usage
1338
    @type offline_nodes: list of strings
1339
    @param offline_nodes: List of offline nodes
1340
    @type bad_nodes: list of strings
1341
    @param bad_nodes: List of faulty nodes
1342
    @type live_data: dict; instance name as key
1343
    @param live_data: Per-instance live data
1344
    @type wrongnode_inst: set
1345
    @param wrongnode_inst: Set of instances running on wrong node(s)
1346
    @type console: dict; instance name as key
1347
    @param console: Per-instance console information
1348
    @type nodes: dict; node name as key
1349
    @param nodes: Node objects
1350

1351
    """
1352
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
1353
           "Offline nodes not included in bad nodes"
1354
    assert not (set(live_data.keys()) & set(bad_nodes)), \
1355
           "Found live data for bad or offline nodes"
1356

    
1357
    self.instances = instances
1358
    self.cluster = cluster
1359
    self.disk_usage = disk_usage
1360
    self.offline_nodes = offline_nodes
1361
    self.bad_nodes = bad_nodes
1362
    self.live_data = live_data
1363
    self.wrongnode_inst = wrongnode_inst
1364
    self.console = console
1365
    self.nodes = nodes
1366
    self.groups = groups
1367

    
1368
    # Used for individual rows
1369
    self.inst_hvparams = None
1370
    self.inst_beparams = None
1371
    self.inst_osparams = None
1372
    self.inst_nicparams = None
1373

    
1374
  def __iter__(self):
1375
    """Iterate over all instances.
1376

1377
    This function has side-effects and only one instance of the resulting
1378
    generator should be used at a time.
1379

1380
    """
1381
    for inst in self.instances:
1382
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1383
      self.inst_beparams = self.cluster.FillBE(inst)
1384
      self.inst_osparams = self.cluster.SimpleFillOS(inst.os, inst.osparams)
1385
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1386
                             for nic in inst.nics]
1387

    
1388
      yield inst
1389

    
1390

    
1391
def _GetInstOperState(ctx, inst):
1392
  """Get instance's operational status.
1393

1394
  @type ctx: L{InstanceQueryData}
1395
  @type inst: L{objects.Instance}
1396
  @param inst: Instance object
1397

1398
  """
1399
  # Can't use RS_OFFLINE here as it would describe the instance to
1400
  # be offline when we actually don't know due to missing data
1401
  if inst.primary_node in ctx.bad_nodes:
1402
    return _FS_NODATA
1403
  else:
1404
    return bool(ctx.live_data.get(inst.name))
1405

    
1406

    
1407
def _GetInstLiveData(name):
1408
  """Build function for retrieving live data.
1409

1410
  @type name: string
1411
  @param name: Live data field name
1412

1413
  """
1414
  def fn(ctx, inst):
1415
    """Get live data for an instance.
1416

1417
    @type ctx: L{InstanceQueryData}
1418
    @type inst: L{objects.Instance}
1419
    @param inst: Instance object
1420

1421
    """
1422
    if (inst.primary_node in ctx.bad_nodes or
1423
        inst.primary_node in ctx.offline_nodes):
1424
      # Can't use RS_OFFLINE here as it would describe the instance to be
1425
      # offline when we actually don't know due to missing data
1426
      return _FS_NODATA
1427

    
1428
    if inst.name in ctx.live_data:
1429
      data = ctx.live_data[inst.name]
1430
      if name in data:
1431
        return data[name]
1432

    
1433
    return _FS_UNAVAIL
1434

    
1435
  return fn
1436

    
1437

    
1438
def _GetInstStatus(ctx, inst):
1439
  """Get instance status.
1440

1441
  @type ctx: L{InstanceQueryData}
1442
  @type inst: L{objects.Instance}
1443
  @param inst: Instance object
1444

1445
  """
1446
  if inst.primary_node in ctx.offline_nodes:
1447
    return constants.INSTST_NODEOFFLINE
1448

    
1449
  if inst.primary_node in ctx.bad_nodes:
1450
    return constants.INSTST_NODEDOWN
1451

    
1452
  if bool(ctx.live_data.get(inst.name)):
1453
    if inst.name in ctx.wrongnode_inst:
1454
      return constants.INSTST_WRONGNODE
1455
    elif inst.admin_state == constants.ADMINST_UP:
1456
      return constants.INSTST_RUNNING
1457
    else:
1458
      return constants.INSTST_ERRORUP
1459

    
1460
  if inst.admin_state == constants.ADMINST_UP:
1461
    return constants.INSTST_ERRORDOWN
1462
  elif inst.admin_state == constants.ADMINST_DOWN:
1463
    return constants.INSTST_ADMINDOWN
1464

    
1465
  return constants.INSTST_ADMINOFFLINE
1466

    
1467

    
1468
def _GetInstDiskSize(index):
1469
  """Build function for retrieving disk size.
1470

1471
  @type index: int
1472
  @param index: Disk index
1473

1474
  """
1475
  def fn(_, inst):
1476
    """Get size of a disk.
1477

1478
    @type inst: L{objects.Instance}
1479
    @param inst: Instance object
1480

1481
    """
1482
    try:
1483
      return inst.disks[index].size
1484
    except IndexError:
1485
      return _FS_UNAVAIL
1486

    
1487
  return fn
1488

    
1489

    
1490
def _GetInstNic(index, cb):
1491
  """Build function for calling another function with an instance NIC.
1492

1493
  @type index: int
1494
  @param index: NIC index
1495
  @type cb: callable
1496
  @param cb: Callback
1497

1498
  """
1499
  def fn(ctx, inst):
1500
    """Call helper function with instance NIC.
1501

1502
    @type ctx: L{InstanceQueryData}
1503
    @type inst: L{objects.Instance}
1504
    @param inst: Instance object
1505

1506
    """
1507
    try:
1508
      nic = inst.nics[index]
1509
    except IndexError:
1510
      return _FS_UNAVAIL
1511

    
1512
    return cb(ctx, index, nic)
1513

    
1514
  return fn
1515

    
1516

    
1517
def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613
1518
  """Get a NIC's IP address.
1519

1520
  @type ctx: L{InstanceQueryData}
1521
  @type nic: L{objects.NIC}
1522
  @param nic: NIC object
1523

1524
  """
1525
  if nic.ip is None:
1526
    return _FS_UNAVAIL
1527
  else:
1528
    return nic.ip
1529

    
1530

    
1531
def _GetInstNicBridge(ctx, index, _):
1532
  """Get a NIC's bridge.
1533

1534
  @type ctx: L{InstanceQueryData}
1535
  @type index: int
1536
  @param index: NIC index
1537

1538
  """
1539
  assert len(ctx.inst_nicparams) >= index
1540

    
1541
  nicparams = ctx.inst_nicparams[index]
1542

    
1543
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1544
    return nicparams[constants.NIC_LINK]
1545
  else:
1546
    return _FS_UNAVAIL
1547

    
1548

    
1549
def _GetInstAllNicBridges(ctx, inst):
1550
  """Get all network bridges for an instance.
1551

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

1556
  """
1557
  assert len(ctx.inst_nicparams) == len(inst.nics)
1558

    
1559
  result = []
1560

    
1561
  for nicp in ctx.inst_nicparams:
1562
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1563
      result.append(nicp[constants.NIC_LINK])
1564
    else:
1565
      result.append(None)
1566

    
1567
  assert len(result) == len(inst.nics)
1568

    
1569
  return result
1570

    
1571

    
1572
def _GetInstNicParam(name):
1573
  """Build function for retrieving a NIC parameter.
1574

1575
  @type name: string
1576
  @param name: Parameter name
1577

1578
  """
1579
  def fn(ctx, index, _):
1580
    """Get a NIC's bridge.
1581

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

1588
    """
1589
    assert len(ctx.inst_nicparams) >= index
1590
    return ctx.inst_nicparams[index][name]
1591

    
1592
  return fn
1593

    
1594

    
1595
def _GetInstanceNetworkFields():
1596
  """Get instance fields involving network interfaces.
1597

1598
  @return: Tuple containing list of field definitions used as input for
1599
    L{_PrepareFieldList} and a list of aliases
1600

1601
  """
1602
  nic_mac_fn = lambda ctx, _, nic: nic.mac
1603
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1604
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1605

    
1606
  fields = [
1607
    # All NICs
1608
    (_MakeField("nic.count", "NICs", QFT_NUMBER,
1609
                "Number of network interfaces"),
1610
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.nics)),
1611
    (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER,
1612
                "List containing each network interface's MAC address"),
1613
     IQ_CONFIG, 0, lambda ctx, inst: [nic.mac for nic in inst.nics]),
1614
    (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
1615
                "List containing each network interface's IP address"),
1616
     IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
1617
    (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
1618
                "List containing each network interface's mode"), IQ_CONFIG, 0,
1619
     lambda ctx, inst: [nicp[constants.NIC_MODE]
1620
                        for nicp in ctx.inst_nicparams]),
1621
    (_MakeField("nic.links", "NIC_links", QFT_OTHER,
1622
                "List containing each network interface's link"), IQ_CONFIG, 0,
1623
     lambda ctx, inst: [nicp[constants.NIC_LINK]
1624
                        for nicp in ctx.inst_nicparams]),
1625
    (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
1626
                "List containing each network interface's bridge"),
1627
     IQ_CONFIG, 0, _GetInstAllNicBridges),
1628
    ]
1629

    
1630
  # NICs by number
1631
  for i in range(constants.MAX_NICS):
1632
    numtext = utils.FormatOrdinal(i + 1)
1633
    fields.extend([
1634
      (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT,
1635
                  "IP address of %s network interface" % numtext),
1636
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)),
1637
      (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
1638
                  "MAC address of %s network interface" % numtext),
1639
       IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
1640
      (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
1641
                  "Mode of %s network interface" % numtext),
1642
       IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
1643
      (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT,
1644
                  "Link of %s network interface" % numtext),
1645
       IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)),
1646
      (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
1647
                  "Bridge of %s network interface" % numtext),
1648
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
1649
      ])
1650

    
1651
  aliases = [
1652
    # Legacy fields for first NIC
1653
    ("ip", "nic.ip/0"),
1654
    ("mac", "nic.mac/0"),
1655
    ("bridge", "nic.bridge/0"),
1656
    ("nic_mode", "nic.mode/0"),
1657
    ("nic_link", "nic.link/0"),
1658
    ]
1659

    
1660
  return (fields, aliases)
1661

    
1662

    
1663
def _GetInstDiskUsage(ctx, inst):
1664
  """Get disk usage for an instance.
1665

1666
  @type ctx: L{InstanceQueryData}
1667
  @type inst: L{objects.Instance}
1668
  @param inst: Instance object
1669

1670
  """
1671
  usage = ctx.disk_usage[inst.name]
1672

    
1673
  if usage is None:
1674
    usage = 0
1675

    
1676
  return usage
1677

    
1678

    
1679
def _GetInstanceConsole(ctx, inst):
1680
  """Get console information for instance.
1681

1682
  @type ctx: L{InstanceQueryData}
1683
  @type inst: L{objects.Instance}
1684
  @param inst: Instance object
1685

1686
  """
1687
  consinfo = ctx.console[inst.name]
1688

    
1689
  if consinfo is None:
1690
    return _FS_UNAVAIL
1691

    
1692
  return consinfo
1693

    
1694

    
1695
def _GetInstanceDiskFields():
1696
  """Get instance fields involving disks.
1697

1698
  @return: List of field definitions used as input for L{_PrepareFieldList}
1699

1700
  """
1701
  fields = [
1702
    (_MakeField("disk_usage", "DiskUsage", QFT_UNIT,
1703
                "Total disk space used by instance on each of its nodes;"
1704
                " this is not the disk size visible to the instance, but"
1705
                " the usage on the node"),
1706
     IQ_DISKUSAGE, 0, _GetInstDiskUsage),
1707
    (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"),
1708
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
1709
    (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
1710
     IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
1711
    ]
1712

    
1713
  # Disks by number
1714
  fields.extend([
1715
    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1716
                "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
1717
     IQ_CONFIG, 0, _GetInstDiskSize(i))
1718
    for i in range(constants.MAX_DISKS)
1719
    ])
1720

    
1721
  return fields
1722

    
1723

    
1724
def _GetInstanceParameterFields():
1725
  """Get instance fields involving parameters.
1726

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

1729
  """
1730
  # TODO: Consider moving titles closer to constants
1731
  be_title = {
1732
    constants.BE_AUTO_BALANCE: "Auto_balance",
1733
    constants.BE_MAXMEM: "ConfigMaxMem",
1734
    constants.BE_MINMEM: "ConfigMinMem",
1735
    constants.BE_VCPUS: "ConfigVCPUs",
1736
    }
1737

    
1738
  hv_title = {
1739
    constants.HV_ACPI: "ACPI",
1740
    constants.HV_BOOT_ORDER: "Boot_order",
1741
    constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1742
    constants.HV_DISK_TYPE: "Disk_type",
1743
    constants.HV_INITRD_PATH: "Initrd_path",
1744
    constants.HV_KERNEL_PATH: "Kernel_path",
1745
    constants.HV_NIC_TYPE: "NIC_type",
1746
    constants.HV_PAE: "PAE",
1747
    constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1748
    }
1749

    
1750
  fields = [
1751
    # Filled parameters
1752
    (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
1753
                "Hypervisor parameters (merged)"),
1754
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
1755
    (_MakeField("beparams", "BackendParameters", QFT_OTHER,
1756
                "Backend parameters (merged)"),
1757
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
1758
    (_MakeField("osparams", "OpSysParameters", QFT_OTHER,
1759
                "Operating system parameters (merged)"),
1760
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_osparams),
1761

    
1762
    # Unfilled parameters
1763
    (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
1764
                "Custom hypervisor parameters"),
1765
     IQ_CONFIG, 0, _GetItemAttr("hvparams")),
1766
    (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
1767
                "Custom backend parameters",),
1768
     IQ_CONFIG, 0, _GetItemAttr("beparams")),
1769
    (_MakeField("custom_osparams", "CustomOpSysParameters", QFT_OTHER,
1770
                "Custom operating system parameters",),
1771
     IQ_CONFIG, 0, _GetItemAttr("osparams")),
1772
    (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
1773
                "Custom network interface parameters"),
1774
     IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1775
    ]
1776

    
1777
  # HV params
1778
  def _GetInstHvParam(name):
1779
    return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1780

    
1781
  fields.extend([
1782
    (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1783
                _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1784
     IQ_CONFIG, 0, _GetInstHvParam(name))
1785
    for name, kind in constants.HVS_PARAMETER_TYPES.items()
1786
    if name not in constants.HVC_GLOBALS
1787
    ])
1788

    
1789
  # BE params
1790
  def _GetInstBeParam(name):
1791
    return lambda ctx, _: ctx.inst_beparams.get(name, None)
1792

    
1793
  fields.extend([
1794
    (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1795
                _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1796
     IQ_CONFIG, 0, _GetInstBeParam(name))
1797
    for name, kind in constants.BES_PARAMETER_TYPES.items()
1798
    ])
1799

    
1800
  return fields
1801

    
1802

    
1803
_INST_SIMPLE_FIELDS = {
1804
  "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"),
1805
  "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"),
1806
  "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"),
1807
  # Depending on the hypervisor, the port can be None
1808
  "network_port": ("Network_port", QFT_OTHER, 0,
1809
                   "Instance network port if available (e.g. for VNC console)"),
1810
  "os": ("OS", QFT_TEXT, 0, "Operating system"),
1811
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"),
1812
  "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"),
1813
  }
1814

    
1815

    
1816
def _GetInstNodeGroup(ctx, default, node_name):
1817
  """Gets group UUID of an instance node.
1818

1819
  @type ctx: L{InstanceQueryData}
1820
  @param default: Default value
1821
  @type node_name: string
1822
  @param node_name: Node name
1823

1824
  """
1825
  try:
1826
    node = ctx.nodes[node_name]
1827
  except KeyError:
1828
    return default
1829
  else:
1830
    return node.group
1831

    
1832

    
1833
def _GetInstNodeGroupName(ctx, default, node_name):
1834
  """Gets group name of an instance node.
1835

1836
  @type ctx: L{InstanceQueryData}
1837
  @param default: Default value
1838
  @type node_name: string
1839
  @param node_name: Node name
1840

1841
  """
1842
  try:
1843
    node = ctx.nodes[node_name]
1844
  except KeyError:
1845
    return default
1846

    
1847
  try:
1848
    group = ctx.groups[node.group]
1849
  except KeyError:
1850
    return default
1851

    
1852
  return group.name
1853

    
1854

    
1855
def _BuildInstanceFields():
1856
  """Builds list of fields for instance queries.
1857

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

    
1898
  # Add simple fields
1899
  fields.extend([
1900
    (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1901
    for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()
1902
    ])
1903

    
1904
  # Fields requiring talking to the node
1905
  fields.extend([
1906
    (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
1907
     IQ_LIVE, 0, _GetInstOperState),
1908
    (_MakeField("oper_ram", "Memory", QFT_UNIT,
1909
                "Actual memory usage as seen by hypervisor"),
1910
     IQ_LIVE, 0, _GetInstLiveData("memory")),
1911
    (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
1912
                "Actual number of VCPUs as seen by hypervisor"),
1913
     IQ_LIVE, 0, _GetInstLiveData("vcpus")),
1914
    ])
1915

    
1916
  # Status field
1917
  status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
1918
                   constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
1919
                   constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
1920
                   constants.INSTST_NODEOFFLINE, constants.INSTST_ADMINOFFLINE)
1921
  status_doc = ("Instance status; \"%s\" if instance is set to be running"
1922
                " and actually is, \"%s\" if instance is stopped and"
1923
                " is not running, \"%s\" if instance running, but not on its"
1924
                " designated primary node, \"%s\" if instance should be"
1925
                " stopped, but is actually running, \"%s\" if instance should"
1926
                " run, but doesn't, \"%s\" if instance's primary node is down,"
1927
                " \"%s\" if instance's primary node is marked offline,"
1928
                " \"%s\" if instance is offline and does not use dynamic"
1929
                " resources" % status_values)
1930
  fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
1931
                 IQ_LIVE, 0, _GetInstStatus))
1932
  assert set(status_values) == constants.INSTST_ALL, \
1933
         "Status documentation mismatch"
1934

    
1935
  (network_fields, network_aliases) = _GetInstanceNetworkFields()
1936

    
1937
  fields.extend(network_fields)
1938
  fields.extend(_GetInstanceParameterFields())
1939
  fields.extend(_GetInstanceDiskFields())
1940
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1941

    
1942
  aliases = [
1943
    ("vcpus", "be/vcpus"),
1944
    ("be/memory", "be/maxmem"),
1945
    ("sda_size", "disk.size/0"),
1946
    ("sdb_size", "disk.size/1"),
1947
    ] + network_aliases
1948

    
1949
  return _PrepareFieldList(fields, aliases)
1950

    
1951

    
1952
class LockQueryData:
1953
  """Data container for lock data queries.
1954

1955
  """
1956
  def __init__(self, lockdata):
1957
    """Initializes this class.
1958

1959
    """
1960
    self.lockdata = lockdata
1961

    
1962
  def __iter__(self):
1963
    """Iterate over all locks.
1964

1965
    """
1966
    return iter(self.lockdata)
1967

    
1968

    
1969
def _GetLockOwners(_, data):
1970
  """Returns a sorted list of a lock's current owners.
1971

1972
  """
1973
  (_, _, owners, _) = data
1974

    
1975
  if owners:
1976
    owners = utils.NiceSort(owners)
1977

    
1978
  return owners
1979

    
1980

    
1981
def _GetLockPending(_, data):
1982
  """Returns a sorted list of a lock's pending acquires.
1983

1984
  """
1985
  (_, _, _, pending) = data
1986

    
1987
  if pending:
1988
    pending = [(mode, utils.NiceSort(names))
1989
               for (mode, names) in pending]
1990

    
1991
  return pending
1992

    
1993

    
1994
def _BuildLockFields():
1995
  """Builds list of fields for lock queries.
1996

1997
  """
1998
  return _PrepareFieldList([
1999
    # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
2000
    (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
2001
     lambda ctx, (name, mode, owners, pending): name),
2002
    (_MakeField("mode", "Mode", QFT_OTHER,
2003
                "Mode in which the lock is currently acquired"
2004
                " (exclusive or shared)"),
2005
     LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
2006
    (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
2007
     LQ_OWNER, 0, _GetLockOwners),
2008
    (_MakeField("pending", "Pending", QFT_OTHER,
2009
                "Threads waiting for the lock"),
2010
     LQ_PENDING, 0, _GetLockPending),
2011
    ], [])
2012

    
2013

    
2014
class GroupQueryData:
2015
  """Data container for node group data queries.
2016

2017
  """
2018
  def __init__(self, cluster, groups, group_to_nodes, group_to_instances,
2019
               want_diskparams):
2020
    """Initializes this class.
2021

2022
    @param cluster: Cluster object
2023
    @param groups: List of node group objects
2024
    @type group_to_nodes: dict; group UUID as key
2025
    @param group_to_nodes: Per-group list of nodes
2026
    @type group_to_instances: dict; group UUID as key
2027
    @param group_to_instances: Per-group list of (primary) instances
2028
    @type want_diskparams: bool
2029
    @param want_diskparams: Whether diskparamters should be calculated
2030

2031
    """
2032
    self.groups = groups
2033
    self.group_to_nodes = group_to_nodes
2034
    self.group_to_instances = group_to_instances
2035
    self.cluster = cluster
2036
    self.want_diskparams = want_diskparams
2037

    
2038
    # Used for individual rows
2039
    self.group_ipolicy = None
2040
    self.ndparams = None
2041
    self.group_dp = None
2042

    
2043
  def __iter__(self):
2044
    """Iterate over all node groups.
2045

2046
    This function has side-effects and only one instance of the resulting
2047
    generator should be used at a time.
2048

2049
    """
2050
    for group in self.groups:
2051
      self.group_ipolicy = self.cluster.SimpleFillIPolicy(group.ipolicy)
2052
      self.ndparams = self.cluster.SimpleFillND(group.ndparams)
2053
      if self.want_diskparams:
2054
        self.group_dp = self.cluster.SimpleFillDP(group.diskparams)
2055
      else:
2056
        self.group_dp = None
2057
      yield group
2058

    
2059

    
2060
_GROUP_SIMPLE_FIELDS = {
2061
  "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
2062
  "name": ("Group", QFT_TEXT, "Group name"),
2063
  "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
2064
  "uuid": ("UUID", QFT_TEXT, "Group UUID"),
2065
  }
2066

    
2067

    
2068
def _BuildGroupFields():
2069
  """Builds list of fields for node group queries.
2070

2071
  """
2072
  # Add simple fields
2073
  fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
2074
             _GetItemAttr(name))
2075
            for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
2076

    
2077
  def _GetLength(getter):
2078
    return lambda ctx, group: len(getter(ctx)[group.uuid])
2079

    
2080
  def _GetSortedList(getter):
2081
    return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
2082

    
2083
  group_to_nodes = operator.attrgetter("group_to_nodes")
2084
  group_to_instances = operator.attrgetter("group_to_instances")
2085

    
2086
  # Add fields for nodes
2087
  fields.extend([
2088
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
2089
     GQ_NODE, 0, _GetLength(group_to_nodes)),
2090
    (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
2091
     GQ_NODE, 0, _GetSortedList(group_to_nodes)),
2092
    ])
2093

    
2094
  # Add fields for instances
2095
  fields.extend([
2096
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
2097
                "Number of primary instances"),
2098
     GQ_INST, 0, _GetLength(group_to_instances)),
2099
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
2100
                "List of primary instances"),
2101
     GQ_INST, 0, _GetSortedList(group_to_instances)),
2102
    ])
2103

    
2104
  # Other fields
2105
  fields.extend([
2106
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), GQ_CONFIG, 0,
2107
     lambda ctx, group: list(group.GetTags())),
2108
    (_MakeField("ipolicy", "InstancePolicy", QFT_OTHER,
2109
                "Instance policy limitations (merged)"),
2110
     GQ_CONFIG, 0, lambda ctx, _: ctx.group_ipolicy),
2111
    (_MakeField("custom_ipolicy", "CustomInstancePolicy", QFT_OTHER,
2112
                "Custom instance policy limitations"),
2113
     GQ_CONFIG, 0, _GetItemAttr("ipolicy")),
2114
    (_MakeField("custom_ndparams", "CustomNDParams", QFT_OTHER,
2115
                "Custom node parameters"),
2116
     GQ_CONFIG, 0, _GetItemAttr("ndparams")),
2117
    (_MakeField("ndparams", "NDParams", QFT_OTHER,
2118
                "Node parameters"),
2119
     GQ_CONFIG, 0, lambda ctx, _: ctx.ndparams),
2120
    (_MakeField("diskparams", "DiskParameters", QFT_OTHER,
2121
                "Disk parameters (merged)"),
2122
     GQ_DISKPARAMS, 0, lambda ctx, _: ctx.group_dp),
2123
    (_MakeField("custom_diskparams", "CustomDiskParameters", QFT_OTHER,
2124
                "Custom disk parameters"),
2125
     GQ_CONFIG, 0, _GetItemAttr("diskparams")),
2126
    ])
2127

    
2128
  # ND parameters
2129
  fields.extend(_BuildNDFields(True))
2130

    
2131
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
2132

    
2133
  return _PrepareFieldList(fields, [])
2134

    
2135

    
2136
class OsInfo(objects.ConfigObject):
2137
  __slots__ = [
2138
    "name",
2139
    "valid",
2140
    "hidden",
2141
    "blacklisted",
2142
    "variants",
2143
    "api_versions",
2144
    "parameters",
2145
    "node_status",
2146
    ]
2147

    
2148

    
2149
def _BuildOsFields():
2150
  """Builds list of fields for operating system queries.
2151

2152
  """
2153
  fields = [
2154
    (_MakeField("name", "Name", QFT_TEXT, "Operating system name"),
2155
     None, 0, _GetItemAttr("name")),
2156
    (_MakeField("valid", "Valid", QFT_BOOL,
2157
                "Whether operating system definition is valid"),
2158
     None, 0, _GetItemAttr("valid")),
2159
    (_MakeField("hidden", "Hidden", QFT_BOOL,
2160
                "Whether operating system is hidden"),
2161
     None, 0, _GetItemAttr("hidden")),
2162
    (_MakeField("blacklisted", "Blacklisted", QFT_BOOL,
2163
                "Whether operating system is blacklisted"),
2164
     None, 0, _GetItemAttr("blacklisted")),
2165
    (_MakeField("variants", "Variants", QFT_OTHER,
2166
                "Operating system variants"),
2167
     None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("variants"))),
2168
    (_MakeField("api_versions", "ApiVersions", QFT_OTHER,
2169
                "Operating system API versions"),
2170
     None, 0, _ConvWrap(sorted, _GetItemAttr("api_versions"))),
2171
    (_MakeField("parameters", "Parameters", QFT_OTHER,
2172
                "Operating system parameters"),
2173
     None, 0, _ConvWrap(compat.partial(utils.NiceSort, key=compat.fst),
2174
                        _GetItemAttr("parameters"))),
2175
    (_MakeField("node_status", "NodeStatus", QFT_OTHER,
2176
                "Status from node"),
2177
     None, 0, _GetItemAttr("node_status")),
2178
    ]
2179

    
2180
  return _PrepareFieldList(fields, [])
2181

    
2182

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

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

2189
  """
2190
  if job is None:
2191
    return _FS_UNAVAIL
2192
  else:
2193
    return fn(job)
2194

    
2195

    
2196
def _JobUnavail(inner):
2197
  """Wrapper for L{_JobUnavailInner}.
2198

2199
  """
2200
  return compat.partial(_JobUnavailInner, inner)
2201

    
2202

    
2203
def _PerJobOpInner(fn, job):
2204
  """Executes a function per opcode in a job.
2205

2206
  """
2207
  return map(fn, job.ops)
2208

    
2209

    
2210
def _PerJobOp(fn):
2211
  """Wrapper for L{_PerJobOpInner}.
2212

2213
  """
2214
  return _JobUnavail(compat.partial(_PerJobOpInner, fn))
2215

    
2216

    
2217
def _JobTimestampInner(fn, job):
2218
  """Converts unavailable timestamp to L{_FS_UNAVAIL}.
2219

2220
  """
2221
  timestamp = fn(job)
2222

    
2223
  if timestamp is None:
2224
    return _FS_UNAVAIL
2225
  else:
2226
    return timestamp
2227

    
2228

    
2229
def _JobTimestamp(fn):
2230
  """Wrapper for L{_JobTimestampInner}.
2231

2232
  """
2233
  return _JobUnavail(compat.partial(_JobTimestampInner, fn))
2234

    
2235

    
2236
def _BuildJobFields():
2237
  """Builds list of fields for job queries.
2238

2239
  """
2240
  fields = [
2241
    (_MakeField("id", "ID", QFT_TEXT, "Job ID"),
2242
     None, 0, lambda _, (job_id, job): job_id),
2243
    (_MakeField("status", "Status", QFT_TEXT, "Job status"),
2244
     None, 0, _JobUnavail(lambda job: job.CalcStatus())),
2245
    (_MakeField("priority", "Priority", QFT_NUMBER,
2246
                ("Current job priority (%s to %s)" %
2247
                 (constants.OP_PRIO_LOWEST, constants.OP_PRIO_HIGHEST))),
2248
     None, 0, _JobUnavail(lambda job: job.CalcPriority())),
2249
    (_MakeField("ops", "OpCodes", QFT_OTHER, "List of all opcodes"),
2250
     None, 0, _PerJobOp(lambda op: op.input.__getstate__())),
2251
    (_MakeField("opresult", "OpCode_result", QFT_OTHER,
2252
                "List of opcodes results"),
2253
     None, 0, _PerJobOp(operator.attrgetter("result"))),
2254
    (_MakeField("opstatus", "OpCode_status", QFT_OTHER,
2255
                "List of opcodes status"),
2256
     None, 0, _PerJobOp(operator.attrgetter("status"))),
2257
    (_MakeField("oplog", "OpCode_log", QFT_OTHER,
2258
                "List of opcode output logs"),
2259
     None, 0, _PerJobOp(operator.attrgetter("log"))),
2260
    (_MakeField("opstart", "OpCode_start", QFT_OTHER,
2261
                "List of opcode start timestamps (before acquiring locks)"),
2262
     None, 0, _PerJobOp(operator.attrgetter("start_timestamp"))),
2263
    (_MakeField("opexec", "OpCode_exec", QFT_OTHER,
2264
                "List of opcode execution start timestamps (after acquiring"
2265
                " locks)"),
2266
     None, 0, _PerJobOp(operator.attrgetter("exec_timestamp"))),
2267
    (_MakeField("opend", "OpCode_end", QFT_OTHER,
2268
                "List of opcode execution end timestamps"),
2269
     None, 0, _PerJobOp(operator.attrgetter("end_timestamp"))),
2270
    (_MakeField("oppriority", "OpCode_prio", QFT_OTHER,
2271
                "List of opcode priorities"),
2272
     None, 0, _PerJobOp(operator.attrgetter("priority"))),
2273
    (_MakeField("received_ts", "Received", QFT_OTHER,
2274
                "Timestamp of when job was received"),
2275
     None, 0, _JobTimestamp(operator.attrgetter("received_timestamp"))),
2276
    (_MakeField("start_ts", "Start", QFT_OTHER,
2277
                "Timestamp of job start"),
2278
     None, 0, _JobTimestamp(operator.attrgetter("start_timestamp"))),
2279
    (_MakeField("end_ts", "End", QFT_OTHER,
2280
                "Timestamp of job end"),
2281
     None, 0, _JobTimestamp(operator.attrgetter("end_timestamp"))),
2282
    (_MakeField("summary", "Summary", QFT_OTHER,
2283
                "List of per-opcode summaries"),
2284
     None, 0, _PerJobOp(lambda op: op.input.Summary())),
2285
    ]
2286

    
2287
  return _PrepareFieldList(fields, [])
2288

    
2289

    
2290
def _GetExportName(_, (node_name, expname)): # pylint: disable=W0613
2291
  """Returns an export name if available.
2292

2293
  """
2294
  if expname is None:
2295
    return _FS_UNAVAIL
2296
  else:
2297
    return expname
2298

    
2299

    
2300
def _BuildExportFields():
2301
  """Builds list of fields for exports.
2302

2303
  """
2304
  fields = [
2305
    (_MakeField("node", "Node", QFT_TEXT, "Node name"),
2306
     None, QFF_HOSTNAME, lambda _, (node_name, expname): node_name),
2307
    (_MakeField("export", "Export", QFT_TEXT, "Export name"),
2308
     None, 0, _GetExportName),
2309
    ]
2310

    
2311
  return _PrepareFieldList(fields, [])
2312

    
2313

    
2314
_CLUSTER_VERSION_FIELDS = {
2315
  "software_version": ("SoftwareVersion", QFT_TEXT, constants.RELEASE_VERSION,
2316
                       "Software version"),
2317
  "protocol_version": ("ProtocolVersion", QFT_NUMBER,
2318
                       constants.PROTOCOL_VERSION,
2319
                       "RPC protocol version"),
2320
  "config_version": ("ConfigVersion", QFT_NUMBER, constants.CONFIG_VERSION,
2321
                     "Configuration format version"),
2322
  "os_api_version": ("OsApiVersion", QFT_NUMBER, max(constants.OS_API_VERSIONS),
2323
                     "API version for OS template scripts"),
2324
  "export_version": ("ExportVersion", QFT_NUMBER, constants.EXPORT_VERSION,
2325
                     "Import/export file format version"),
2326
  }
2327

    
2328

    
2329
_CLUSTER_SIMPLE_FIELDS = {
2330
  "cluster_name": ("Name", QFT_TEXT, QFF_HOSTNAME, "Cluster name"),
2331
  "master_node": ("Master", QFT_TEXT, QFF_HOSTNAME, "Master node name"),
2332
  "volume_group_name": ("VgName", QFT_TEXT, 0, "LVM volume group name"),
2333
  }
2334

    
2335

    
2336
class ClusterQueryData:
2337
  def __init__(self, cluster, drain_flag, watcher_pause):
2338
    """Initializes this class.
2339

2340
    @type cluster: L{objects.Cluster}
2341
    @param cluster: Instance of cluster object
2342
    @type drain_flag: bool
2343
    @param drain_flag: Whether job queue is drained
2344
    @type watcher_pause: number
2345
    @param watcher_pause: Until when watcher is paused (Unix timestamp)
2346

2347
    """
2348
    self._cluster = cluster
2349
    self.drain_flag = drain_flag
2350
    self.watcher_pause = watcher_pause
2351

    
2352
  def __iter__(self):
2353
    return iter([self._cluster])
2354

    
2355

    
2356
def _ClusterWatcherPause(ctx, _):
2357
  """Returns until when watcher is paused (if available).
2358

2359
  """
2360
  if ctx.watcher_pause is None:
2361
    return _FS_UNAVAIL
2362
  else:
2363
    return ctx.watcher_pause
2364

    
2365

    
2366
def _BuildClusterFields():
2367
  """Builds list of fields for cluster information.
2368

2369
  """
2370
  fields = [
2371
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), CQ_CONFIG, 0,
2372
     lambda ctx, cluster: list(cluster.GetTags())),
2373
    (_MakeField("architecture", "ArchInfo", QFT_OTHER,
2374
                "Architecture information"), None, 0,
2375
     lambda ctx, _: runtime.GetArchInfo()),
2376
    (_MakeField("drain_flag", "QueueDrained", QFT_BOOL,
2377
                "Flag whether job queue is drained"), CQ_QUEUE_DRAINED, 0,
2378
     lambda ctx, _: ctx.drain_flag),
2379
    (_MakeField("watcher_pause", "WatcherPause", QFT_TIMESTAMP,
2380
                "Until when watcher is paused"), CQ_WATCHER_PAUSE, 0,
2381
     _ClusterWatcherPause),
2382
    ]
2383

    
2384
  # Simple fields
2385
  fields.extend([
2386
    (_MakeField(name, title, kind, doc), CQ_CONFIG, flags, _GetItemAttr(name))
2387
    for (name, (title, kind, flags, doc)) in _CLUSTER_SIMPLE_FIELDS.items()
2388
    ])
2389

    
2390
  # Version fields
2391
  fields.extend([
2392
    (_MakeField(name, title, kind, doc), None, 0, _StaticValue(value))
2393
    for (name, (title, kind, value, doc)) in _CLUSTER_VERSION_FIELDS.items()
2394
    ])
2395

    
2396
  # Add timestamps
2397
  fields.extend(_GetItemTimestampFields(CQ_CONFIG))
2398

    
2399
  return _PrepareFieldList(fields, [
2400
    ("name", "cluster_name"),
2401
    ])
2402

    
2403

    
2404
#: Fields for cluster information
2405
CLUSTER_FIELDS = _BuildClusterFields()
2406

    
2407
#: Fields available for node queries
2408
NODE_FIELDS = _BuildNodeFields()
2409

    
2410
#: Fields available for instance queries
2411
INSTANCE_FIELDS = _BuildInstanceFields()
2412

    
2413
#: Fields available for lock queries
2414
LOCK_FIELDS = _BuildLockFields()
2415

    
2416
#: Fields available for node group queries
2417
GROUP_FIELDS = _BuildGroupFields()
2418

    
2419
#: Fields available for operating system queries
2420
OS_FIELDS = _BuildOsFields()
2421

    
2422
#: Fields available for job queries
2423
JOB_FIELDS = _BuildJobFields()
2424

    
2425
#: Fields available for exports
2426
EXPORT_FIELDS = _BuildExportFields()
2427

    
2428
#: All available resources
2429
ALL_FIELDS = {
2430
  constants.QR_CLUSTER: CLUSTER_FIELDS,
2431
  constants.QR_INSTANCE: INSTANCE_FIELDS,
2432
  constants.QR_NODE: NODE_FIELDS,
2433
  constants.QR_LOCK: LOCK_FIELDS,
2434
  constants.QR_GROUP: GROUP_FIELDS,
2435
  constants.QR_OS: OS_FIELDS,
2436
  constants.QR_JOB: JOB_FIELDS,
2437
  constants.QR_EXPORT: EXPORT_FIELDS,
2438
  }
2439

    
2440
#: All available field lists
2441
ALL_FIELD_LISTS = ALL_FIELDS.values()