Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ f43c898d

History | View | Annotate | Download (84.5 kB)

1
#
2
#
3

    
4
# Copyright (C) 2010, 2011, 2012, 2013 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
(NETQ_CONFIG,
75
 NETQ_GROUP,
76
 NETQ_STATS,
77
 NETQ_INST) = range(300, 304)
78

    
79
# Constants for requesting data from the caller/data provider. Each property
80
# collected/computed separately by the data provider should have its own to
81
# only collect the requested data and not more.
82

    
83
(NQ_CONFIG,
84
 NQ_INST,
85
 NQ_LIVE,
86
 NQ_GROUP,
87
 NQ_OOB) = range(1, 6)
88

    
89
(IQ_CONFIG,
90
 IQ_LIVE,
91
 IQ_DISKUSAGE,
92
 IQ_CONSOLE,
93
 IQ_NODES,
94
 IQ_NETWORKS) = range(100, 106)
95

    
96
(LQ_MODE,
97
 LQ_OWNER,
98
 LQ_PENDING) = range(10, 13)
99

    
100
(GQ_CONFIG,
101
 GQ_NODE,
102
 GQ_INST,
103
 GQ_DISKPARAMS) = range(200, 204)
104

    
105
(CQ_CONFIG,
106
 CQ_QUEUE_DRAINED,
107
 CQ_WATCHER_PAUSE) = range(300, 303)
108

    
109
(JQ_ARCHIVED, ) = range(400, 401)
110

    
111
# Query field flags
112
QFF_HOSTNAME = 0x01
113
QFF_IP_ADDRESS = 0x02
114
QFF_JOB_ID = 0x04
115
QFF_SPLIT_TIMESTAMP = 0x08
116
# Next values: 0x10, 0x20, 0x40, 0x80, 0x100, 0x200
117
QFF_ALL = (QFF_HOSTNAME | QFF_IP_ADDRESS | QFF_JOB_ID | QFF_SPLIT_TIMESTAMP)
118

    
119
FIELD_NAME_RE = re.compile(r"^[a-z0-9/._]+$")
120
TITLE_RE = re.compile(r"^[^\s]+$")
121
DOC_RE = re.compile(r"^[A-Z].*[^.,?!]$")
122

    
123
#: Verification function for each field type
124
_VERIFY_FN = {
125
  QFT_UNKNOWN: ht.TNone,
126
  QFT_TEXT: ht.TString,
127
  QFT_BOOL: ht.TBool,
128
  QFT_NUMBER: ht.TInt,
129
  QFT_UNIT: ht.TInt,
130
  QFT_TIMESTAMP: ht.TNumber,
131
  QFT_OTHER: lambda _: True,
132
  }
133

    
134
# Unique objects for special field statuses
135
_FS_UNKNOWN = object()
136
_FS_NODATA = object()
137
_FS_UNAVAIL = object()
138
_FS_OFFLINE = object()
139

    
140
#: List of all special status
141
_FS_ALL = compat.UniqueFrozenset([
142
  _FS_UNKNOWN,
143
  _FS_NODATA,
144
  _FS_UNAVAIL,
145
  _FS_OFFLINE,
146
  ])
147

    
148
#: VType to QFT mapping
149
_VTToQFT = {
150
  # TODO: fix validation of empty strings
151
  constants.VTYPE_STRING: QFT_OTHER, # since VTYPE_STRINGs can be empty
152
  constants.VTYPE_MAYBE_STRING: QFT_OTHER,
153
  constants.VTYPE_BOOL: QFT_BOOL,
154
  constants.VTYPE_SIZE: QFT_UNIT,
155
  constants.VTYPE_INT: QFT_NUMBER,
156
  }
157

    
158
_SERIAL_NO_DOC = "%s object serial number, incremented on each modification"
159

    
160

    
161
def _GetUnknownField(ctx, item): # pylint: disable=W0613
162
  """Gets the contents of an unknown field.
163

164
  """
165
  return _FS_UNKNOWN
166

    
167

    
168
def _GetQueryFields(fielddefs, selected):
169
  """Calculates the internal list of selected fields.
170

171
  Unknown fields are returned as L{constants.QFT_UNKNOWN}.
172

173
  @type fielddefs: dict
174
  @param fielddefs: Field definitions
175
  @type selected: list of strings
176
  @param selected: List of selected fields
177

178
  """
179
  result = []
180

    
181
  for name in selected:
182
    try:
183
      fdef = fielddefs[name]
184
    except KeyError:
185
      fdef = (_MakeField(name, name, QFT_UNKNOWN, "Unknown field '%s'" % name),
186
              None, 0, _GetUnknownField)
187

    
188
    assert len(fdef) == 4
189

    
190
    result.append(fdef)
191

    
192
  return result
193

    
194

    
195
def GetAllFields(fielddefs):
196
  """Extract L{objects.QueryFieldDefinition} from field definitions.
197

198
  @rtype: list of L{objects.QueryFieldDefinition}
199

200
  """
201
  return [fdef for (fdef, _, _, _) in fielddefs]
202

    
203

    
204
class _FilterHints:
205
  """Class for filter analytics.
206

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

212
  There are two ways to optimize this. The first, and simpler, is to assign
213
  each field a group of data, so that the caller can determine which
214
  computations are necessary depending on the data groups requested. The list
215
  of referenced groups must also be computed for fields referenced in the
216
  filter.
217

218
  The second is restricting the items based on a primary key. The primary key
219
  is usually a unique name (e.g. a node name). This class extracts all
220
  referenced names from a filter. If it encounters any filter condition which
221
  disallows such a list to be determined (e.g. a non-equality filter), all
222
  names will be requested.
223

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

227
  """
228
  def __init__(self, namefield):
229
    """Initializes this class.
230

231
    @type namefield: string
232
    @param namefield: Field caller is interested in
233

234
    """
235
    self._namefield = namefield
236

    
237
    #: Whether all names need to be requested (e.g. if a non-equality operator
238
    #: has been used)
239
    self._allnames = False
240

    
241
    #: Which names to request
242
    self._names = None
243

    
244
    #: Data kinds referenced by the filter (used by L{Query.RequestedData})
245
    self._datakinds = set()
246

    
247
  def RequestedNames(self):
248
    """Returns all requested values.
249

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

253
    @rtype: list
254

255
    """
256
    if self._allnames or self._names is None:
257
      return None
258

    
259
    return utils.UniqueSequence(self._names)
260

    
261
  def ReferencedData(self):
262
    """Returns all kinds of data referenced by the filter.
263

264
    """
265
    return frozenset(self._datakinds)
266

    
267
  def _NeedAllNames(self):
268
    """Changes internal state to request all names.
269

270
    """
271
    self._allnames = True
272
    self._names = None
273

    
274
  def NoteLogicOp(self, op):
275
    """Called when handling a logic operation.
276

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

280
    """
281
    if op != qlang.OP_OR:
282
      self._NeedAllNames()
283

    
284
  def NoteUnaryOp(self, op, datakind): # pylint: disable=W0613
285
    """Called when handling an unary operation.
286

287
    @type op: string
288
    @param op: Operator
289

290
    """
291
    if datakind is not None:
292
      self._datakinds.add(datakind)
293

    
294
    self._NeedAllNames()
295

    
296
  def NoteBinaryOp(self, op, datakind, name, value):
297
    """Called when handling a binary operation.
298

299
    @type op: string
300
    @param op: Operator
301
    @type name: string
302
    @param name: Left-hand side of operator (field name)
303
    @param value: Right-hand side of operator
304

305
    """
306
    if datakind is not None:
307
      self._datakinds.add(datakind)
308

    
309
    if self._allnames:
310
      return
311

    
312
    # If any operator other than equality was used, all names need to be
313
    # retrieved
314
    if op == qlang.OP_EQUAL and name == self._namefield:
315
      if self._names is None:
316
        self._names = []
317
      self._names.append(value)
318
    else:
319
      self._NeedAllNames()
320

    
321

    
322
def _WrapLogicOp(op_fn, sentences, ctx, item):
323
  """Wrapper for logic operator functions.
324

325
  """
326
  return op_fn(fn(ctx, item) for fn in sentences)
327

    
328

    
329
def _WrapUnaryOp(op_fn, inner, ctx, item):
330
  """Wrapper for unary operator functions.
331

332
  """
333
  return op_fn(inner(ctx, item))
334

    
335

    
336
def _WrapBinaryOp(op_fn, retrieval_fn, value, ctx, item):
337
  """Wrapper for binary operator functions.
338

339
  """
340
  return op_fn(retrieval_fn(ctx, item), value)
341

    
342

    
343
def _WrapNot(fn, lhs, rhs):
344
  """Negates the result of a wrapped function.
345

346
  """
347
  return not fn(lhs, rhs)
348

    
349

    
350
def _PrepareRegex(pattern):
351
  """Compiles a regular expression.
352

353
  """
354
  try:
355
    return re.compile(pattern)
356
  except re.error, err:
357
    raise errors.ParameterError("Invalid regex pattern (%s)" % err)
358

    
359

    
360
def _PrepareSplitTimestamp(value):
361
  """Prepares a value for comparison by L{_MakeSplitTimestampComparison}.
362

363
  """
364
  if ht.TNumber(value):
365
    return value
366
  else:
367
    return utils.MergeTime(value)
368

    
369

    
370
def _MakeSplitTimestampComparison(fn):
371
  """Compares split timestamp values after converting to float.
372

373
  """
374
  return lambda lhs, rhs: fn(utils.MergeTime(lhs), rhs)
375

    
376

    
377
def _MakeComparisonChecks(fn):
378
  """Prepares flag-specific comparisons using a comparison function.
379

380
  """
381
  return [
382
    (QFF_SPLIT_TIMESTAMP, _MakeSplitTimestampComparison(fn),
383
     _PrepareSplitTimestamp),
384
    (QFF_JOB_ID, lambda lhs, rhs: fn(jstore.ParseJobId(lhs), rhs),
385
     jstore.ParseJobId),
386
    (None, fn, None),
387
    ]
388

    
389

    
390
class _FilterCompilerHelper:
391
  """Converts a query filter to a callable usable for filtering.
392

393
  """
394
  # String statement has no effect, pylint: disable=W0105
395

    
396
  #: How deep filters can be nested
397
  _LEVELS_MAX = 10
398

    
399
  # Unique identifiers for operator groups
400
  (_OPTYPE_LOGIC,
401
   _OPTYPE_UNARY,
402
   _OPTYPE_BINARY) = range(1, 4)
403

    
404
  """Functions for equality checks depending on field flags.
405

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

410
  Order matters. The first item with flags will be used. Flags are checked
411
  using binary AND.
412

413
  """
414
  _EQUALITY_CHECKS = [
415
    (QFF_HOSTNAME,
416
     lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
417
                                               case_sensitive=False),
418
     None),
419
    (QFF_SPLIT_TIMESTAMP, _MakeSplitTimestampComparison(operator.eq),
420
     _PrepareSplitTimestamp),
421
    (None, operator.eq, None),
422
    ]
423

    
424
  """Known operators
425

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

429
    - C{_OPTYPE_LOGIC}: Callable taking any number of arguments; used by
430
      L{_HandleLogicOp}
431
    - C{_OPTYPE_UNARY}: Always C{None}; details handled by L{_HandleUnaryOp}
432
    - C{_OPTYPE_BINARY}: Callable taking exactly two parameters, the left- and
433
      right-hand side of the operator, used by L{_HandleBinaryOp}
434

435
  """
436
  _OPS = {
437
    # Logic operators
438
    qlang.OP_OR: (_OPTYPE_LOGIC, compat.any),
439
    qlang.OP_AND: (_OPTYPE_LOGIC, compat.all),
440

    
441
    # Unary operators
442
    qlang.OP_NOT: (_OPTYPE_UNARY, None),
443
    qlang.OP_TRUE: (_OPTYPE_UNARY, None),
444

    
445
    # Binary operators
446
    qlang.OP_EQUAL: (_OPTYPE_BINARY, _EQUALITY_CHECKS),
447
    qlang.OP_NOT_EQUAL:
448
      (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn), valprepfn)
449
                        for (flags, fn, valprepfn) in _EQUALITY_CHECKS]),
450
    qlang.OP_LT: (_OPTYPE_BINARY, _MakeComparisonChecks(operator.lt)),
451
    qlang.OP_LE: (_OPTYPE_BINARY, _MakeComparisonChecks(operator.le)),
452
    qlang.OP_GT: (_OPTYPE_BINARY, _MakeComparisonChecks(operator.gt)),
453
    qlang.OP_GE: (_OPTYPE_BINARY, _MakeComparisonChecks(operator.ge)),
454
    qlang.OP_REGEXP: (_OPTYPE_BINARY, [
455
      (None, lambda lhs, rhs: rhs.search(lhs), _PrepareRegex),
456
      ]),
457
    qlang.OP_CONTAINS: (_OPTYPE_BINARY, [
458
      (None, operator.contains, None),
459
      ]),
460
    }
461

    
462
  def __init__(self, fields):
463
    """Initializes this class.
464

465
    @param fields: Field definitions (return value of L{_PrepareFieldList})
466

467
    """
468
    self._fields = fields
469
    self._hints = None
470
    self._op_handler = None
471

    
472
  def __call__(self, hints, qfilter):
473
    """Converts a query filter into a callable function.
474

475
    @type hints: L{_FilterHints} or None
476
    @param hints: Callbacks doing analysis on filter
477
    @type qfilter: list
478
    @param qfilter: Filter structure
479
    @rtype: callable
480
    @return: Function receiving context and item as parameters, returning
481
             boolean as to whether item matches filter
482

483
    """
484
    self._op_handler = {
485
      self._OPTYPE_LOGIC:
486
        (self._HandleLogicOp, getattr(hints, "NoteLogicOp", None)),
487
      self._OPTYPE_UNARY:
488
        (self._HandleUnaryOp, getattr(hints, "NoteUnaryOp", None)),
489
      self._OPTYPE_BINARY:
490
        (self._HandleBinaryOp, getattr(hints, "NoteBinaryOp", None)),
491
      }
492

    
493
    try:
494
      filter_fn = self._Compile(qfilter, 0)
495
    finally:
496
      self._op_handler = None
497

    
498
    return filter_fn
499

    
500
  def _Compile(self, qfilter, level):
501
    """Inner function for converting filters.
502

503
    Calls the correct handler functions for the top-level operator. This
504
    function is called recursively (e.g. for logic operators).
505

506
    """
507
    if not (isinstance(qfilter, (list, tuple)) and qfilter):
508
      raise errors.ParameterError("Invalid filter on level %s" % level)
509

    
510
    # Limit recursion
511
    if level >= self._LEVELS_MAX:
512
      raise errors.ParameterError("Only up to %s levels are allowed (filter"
513
                                  " nested too deep)" % self._LEVELS_MAX)
514

    
515
    # Create copy to be modified
516
    operands = qfilter[:]
517
    op = operands.pop(0)
518

    
519
    try:
520
      (kind, op_data) = self._OPS[op]
521
    except KeyError:
522
      raise errors.ParameterError("Unknown operator '%s'" % op)
523

    
524
    (handler, hints_cb) = self._op_handler[kind]
525

    
526
    return handler(hints_cb, level, op, op_data, operands)
527

    
528
  def _LookupField(self, name):
529
    """Returns a field definition by name.
530

531
    """
532
    try:
533
      return self._fields[name]
534
    except KeyError:
535
      raise errors.ParameterError("Unknown field '%s'" % name)
536

    
537
  def _HandleLogicOp(self, hints_fn, level, op, op_fn, operands):
538
    """Handles logic operators.
539

540
    @type hints_fn: callable
541
    @param hints_fn: Callback doing some analysis on the filter
542
    @type level: integer
543
    @param level: Current depth
544
    @type op: string
545
    @param op: Operator
546
    @type op_fn: callable
547
    @param op_fn: Function implementing operator
548
    @type operands: list
549
    @param operands: List of operands
550

551
    """
552
    if hints_fn:
553
      hints_fn(op)
554

    
555
    return compat.partial(_WrapLogicOp, op_fn,
556
                          [self._Compile(op, level + 1) for op in operands])
557

    
558
  def _HandleUnaryOp(self, hints_fn, level, op, op_fn, operands):
559
    """Handles unary operators.
560

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

572
    """
573
    assert op_fn is None
574

    
575
    if len(operands) != 1:
576
      raise errors.ParameterError("Unary operator '%s' expects exactly one"
577
                                  " operand" % op)
578

    
579
    if op == qlang.OP_TRUE:
580
      (_, datakind, _, retrieval_fn) = self._LookupField(operands[0])
581

    
582
      if hints_fn:
583
        hints_fn(op, datakind)
584

    
585
      op_fn = operator.truth
586
      arg = retrieval_fn
587
    elif op == qlang.OP_NOT:
588
      if hints_fn:
589
        hints_fn(op, None)
590

    
591
      op_fn = operator.not_
592
      arg = self._Compile(operands[0], level + 1)
593
    else:
594
      raise errors.ProgrammerError("Can't handle operator '%s'" % op)
595

    
596
    return compat.partial(_WrapUnaryOp, op_fn, arg)
597

    
598
  def _HandleBinaryOp(self, hints_fn, level, op, op_data, operands):
599
    """Handles binary operators.
600

601
    @type hints_fn: callable
602
    @param hints_fn: Callback doing some analysis on the filter
603
    @type level: integer
604
    @param level: Current depth
605
    @type op: string
606
    @param op: Operator
607
    @param op_data: Functions implementing operators
608
    @type operands: list
609
    @param operands: List of operands
610

611
    """
612
    # Unused arguments, pylint: disable=W0613
613
    try:
614
      (name, value) = operands
615
    except (ValueError, TypeError):
616
      raise errors.ParameterError("Invalid binary operator, expected exactly"
617
                                  " two operands")
618

    
619
    (fdef, datakind, field_flags, retrieval_fn) = self._LookupField(name)
620

    
621
    assert fdef.kind != QFT_UNKNOWN
622

    
623
    # TODO: Type conversions?
624

    
625
    verify_fn = _VERIFY_FN[fdef.kind]
626
    if not verify_fn(value):
627
      raise errors.ParameterError("Unable to compare field '%s' (type '%s')"
628
                                  " with '%s', expected %s" %
629
                                  (name, fdef.kind, value.__class__.__name__,
630
                                   verify_fn))
631

    
632
    if hints_fn:
633
      hints_fn(op, datakind, name, value)
634

    
635
    for (fn_flags, fn, valprepfn) in op_data:
636
      if fn_flags is None or fn_flags & field_flags:
637
        # Prepare value if necessary (e.g. compile regular expression)
638
        if valprepfn:
639
          value = valprepfn(value)
640

    
641
        return compat.partial(_WrapBinaryOp, fn, retrieval_fn, value)
642

    
643
    raise errors.ProgrammerError("Unable to find operator implementation"
644
                                 " (op '%s', flags %s)" % (op, field_flags))
645

    
646

    
647
def _CompileFilter(fields, hints, qfilter):
648
  """Converts a query filter into a callable function.
649

650
  See L{_FilterCompilerHelper} for details.
651

652
  @rtype: callable
653

654
  """
655
  return _FilterCompilerHelper(fields)(hints, qfilter)
656

    
657

    
658
class Query:
659
  def __init__(self, fieldlist, selected, qfilter=None, namefield=None):
660
    """Initializes this class.
661

662
    The field definition is a dictionary with the field's name as a key and a
663
    tuple containing, in order, the field definition object
664
    (L{objects.QueryFieldDefinition}, the data kind to help calling code
665
    collect data and a retrieval function. The retrieval function is called
666
    with two parameters, in order, the data container and the item in container
667
    (see L{Query.Query}).
668

669
    Users of this class can call L{RequestedData} before preparing the data
670
    container to determine what data is needed.
671

672
    @type fieldlist: dictionary
673
    @param fieldlist: Field definitions
674
    @type selected: list of strings
675
    @param selected: List of selected fields
676

677
    """
678
    assert namefield is None or namefield in fieldlist
679

    
680
    self._fields = _GetQueryFields(fieldlist, selected)
681

    
682
    self._filter_fn = None
683
    self._requested_names = None
684
    self._filter_datakinds = frozenset()
685

    
686
    if qfilter is not None:
687
      # Collect requested names if wanted
688
      if namefield:
689
        hints = _FilterHints(namefield)
690
      else:
691
        hints = None
692

    
693
      # Build filter function
694
      self._filter_fn = _CompileFilter(fieldlist, hints, qfilter)
695
      if hints:
696
        self._requested_names = hints.RequestedNames()
697
        self._filter_datakinds = hints.ReferencedData()
698

    
699
    if namefield is None:
700
      self._name_fn = None
701
    else:
702
      (_, _, _, self._name_fn) = fieldlist[namefield]
703

    
704
  def RequestedNames(self):
705
    """Returns all names referenced in the filter.
706

707
    If there is no filter or operators are preventing determining the exact
708
    names, C{None} is returned.
709

710
    """
711
    return self._requested_names
712

    
713
  def RequestedData(self):
714
    """Gets requested kinds of data.
715

716
    @rtype: frozenset
717

718
    """
719
    return (self._filter_datakinds |
720
            frozenset(datakind for (_, datakind, _, _) in self._fields
721
                      if datakind is not None))
722

    
723
  def GetFields(self):
724
    """Returns the list of fields for this query.
725

726
    Includes unknown fields.
727

728
    @rtype: List of L{objects.QueryFieldDefinition}
729

730
    """
731
    return GetAllFields(self._fields)
732

    
733
  def Query(self, ctx, sort_by_name=True):
734
    """Execute a query.
735

736
    @param ctx: Data container passed to field retrieval functions, must
737
      support iteration using C{__iter__}
738
    @type sort_by_name: boolean
739
    @param sort_by_name: Whether to sort by name or keep the input data's
740
      ordering
741

742
    """
743
    sort = (self._name_fn and sort_by_name)
744

    
745
    result = []
746

    
747
    for idx, item in enumerate(ctx):
748
      if not (self._filter_fn is None or self._filter_fn(ctx, item)):
749
        continue
750

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

    
753
      # Verify result
754
      if __debug__:
755
        _VerifyResultRow(self._fields, row)
756

    
757
      if sort:
758
        (status, name) = _ProcessResult(self._name_fn(ctx, item))
759
        assert status == constants.RS_NORMAL
760
        # TODO: Are there cases where we wouldn't want to use NiceSort?
761
        # Answer: if the name field is non-string...
762
        result.append((utils.NiceSortKey(name), idx, row))
763
      else:
764
        result.append(row)
765

    
766
    if not sort:
767
      return result
768

    
769
    # TODO: Would "heapq" be more efficient than sorting?
770

    
771
    # Sorting in-place instead of using "sorted()"
772
    result.sort()
773

    
774
    assert not result or (len(result[0]) == 3 and len(result[-1]) == 3)
775

    
776
    return map(operator.itemgetter(2), result)
777

    
778
  def OldStyleQuery(self, ctx, sort_by_name=True):
779
    """Query with "old" query result format.
780

781
    See L{Query.Query} for arguments.
782

783
    """
784
    unknown = set(fdef.name for (fdef, _, _, _) in self._fields
785
                  if fdef.kind == QFT_UNKNOWN)
786
    if unknown:
787
      raise errors.OpPrereqError("Unknown output fields selected: %s" %
788
                                 (utils.CommaJoin(unknown), ),
789
                                 errors.ECODE_INVAL)
790

    
791
    return [[value for (_, value) in row]
792
            for row in self.Query(ctx, sort_by_name=sort_by_name)]
793

    
794

    
795
def _ProcessResult(value):
796
  """Converts result values into externally-visible ones.
797

798
  """
799
  if value is _FS_UNKNOWN:
800
    return (RS_UNKNOWN, None)
801
  elif value is _FS_NODATA:
802
    return (RS_NODATA, None)
803
  elif value is _FS_UNAVAIL:
804
    return (RS_UNAVAIL, None)
805
  elif value is _FS_OFFLINE:
806
    return (RS_OFFLINE, None)
807
  else:
808
    return (RS_NORMAL, value)
809

    
810

    
811
def _VerifyResultRow(fields, row):
812
  """Verifies the contents of a query result row.
813

814
  @type fields: list
815
  @param fields: Field definitions for result
816
  @type row: list of tuples
817
  @param row: Row data
818

819
  """
820
  assert len(row) == len(fields)
821
  errs = []
822
  for ((status, value), (fdef, _, _, _)) in zip(row, fields):
823
    if status == RS_NORMAL:
824
      if not _VERIFY_FN[fdef.kind](value):
825
        errs.append("normal field %s fails validation (value is %s)" %
826
                    (fdef.name, value))
827
    elif value is not None:
828
      errs.append("abnormal field %s has a non-None value" % fdef.name)
829
  assert not errs, ("Failed validation: %s in row %s" %
830
                    (utils.CommaJoin(errs), row))
831

    
832

    
833
def _FieldDictKey((fdef, _, flags, fn)):
834
  """Generates key for field dictionary.
835

836
  """
837
  assert fdef.name and fdef.title, "Name and title are required"
838
  assert FIELD_NAME_RE.match(fdef.name)
839
  assert TITLE_RE.match(fdef.title)
840
  assert (DOC_RE.match(fdef.doc) and len(fdef.doc.splitlines()) == 1 and
841
          fdef.doc.strip() == fdef.doc), \
842
         "Invalid description for field '%s'" % fdef.name
843
  assert callable(fn)
844
  assert (flags & ~QFF_ALL) == 0, "Unknown flags for field '%s'" % fdef.name
845

    
846
  return fdef.name
847

    
848

    
849
def _PrepareFieldList(fields, aliases):
850
  """Prepares field list for use by L{Query}.
851

852
  Converts the list to a dictionary and does some verification.
853

854
  @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data
855
      kind, retrieval function)
856
  @param fields: List of fields, see L{Query.__init__} for a better
857
      description
858
  @type aliases: list of tuples; (alias, target)
859
  @param aliases: list of tuples containing aliases; for each
860
      alias/target pair, a duplicate will be created in the field list
861
  @rtype: dict
862
  @return: Field dictionary for L{Query}
863

864
  """
865
  if __debug__:
866
    duplicates = utils.FindDuplicates(fdef.title.lower()
867
                                      for (fdef, _, _, _) in fields)
868
    assert not duplicates, "Duplicate title(s) found: %r" % duplicates
869

    
870
  result = utils.SequenceToDict(fields, key=_FieldDictKey)
871

    
872
  for alias, target in aliases:
873
    assert alias not in result, "Alias %s overrides an existing field" % alias
874
    assert target in result, "Missing target %s for alias %s" % (target, alias)
875
    (fdef, k, flags, fn) = result[target]
876
    fdef = fdef.Copy()
877
    fdef.name = alias
878
    result[alias] = (fdef, k, flags, fn)
879

    
880
  assert len(result) == len(fields) + len(aliases)
881
  assert compat.all(name == fdef.name
882
                    for (name, (fdef, _, _, _)) in result.items())
883

    
884
  return result
885

    
886

    
887
def GetQueryResponse(query, ctx, sort_by_name=True):
888
  """Prepares the response for a query.
889

890
  @type query: L{Query}
891
  @param ctx: Data container, see L{Query.Query}
892
  @type sort_by_name: boolean
893
  @param sort_by_name: Whether to sort by name or keep the input data's
894
    ordering
895

896
  """
897
  return objects.QueryResponse(data=query.Query(ctx, sort_by_name=sort_by_name),
898
                               fields=query.GetFields()).ToDict()
899

    
900

    
901
def QueryFields(fielddefs, selected):
902
  """Returns list of available fields.
903

904
  @type fielddefs: dict
905
  @param fielddefs: Field definitions
906
  @type selected: list of strings
907
  @param selected: List of selected fields
908
  @return: List of L{objects.QueryFieldDefinition}
909

910
  """
911
  if selected is None:
912
    # Client requests all fields, sort by name
913
    fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
914
                           key=operator.attrgetter("name"))
915
  else:
916
    # Keep order as requested by client
917
    fdefs = Query(fielddefs, selected).GetFields()
918

    
919
  return objects.QueryFieldsResponse(fields=fdefs).ToDict()
920

    
921

    
922
def _MakeField(name, title, kind, doc):
923
  """Wrapper for creating L{objects.QueryFieldDefinition} instances.
924

925
  @param name: Field name as a regular expression
926
  @param title: Human-readable title
927
  @param kind: Field type
928
  @param doc: Human-readable description
929

930
  """
931
  return objects.QueryFieldDefinition(name=name, title=title, kind=kind,
932
                                      doc=doc)
933

    
934

    
935
def _StaticValueInner(value, ctx, _): # pylint: disable=W0613
936
  """Returns a static value.
937

938
  """
939
  return value
940

    
941

    
942
def _StaticValue(value):
943
  """Prepares a function to return a static value.
944

945
  """
946
  return compat.partial(_StaticValueInner, value)
947

    
948

    
949
def _GetNodeRole(node, master_name):
950
  """Determine node role.
951

952
  @type node: L{objects.Node}
953
  @param node: Node object
954
  @type master_name: string
955
  @param master_name: Master node name
956

957
  """
958
  if node.name == master_name:
959
    return constants.NR_MASTER
960
  elif node.master_candidate:
961
    return constants.NR_MCANDIDATE
962
  elif node.drained:
963
    return constants.NR_DRAINED
964
  elif node.offline:
965
    return constants.NR_OFFLINE
966
  else:
967
    return constants.NR_REGULAR
968

    
969

    
970
def _GetItemAttr(attr):
971
  """Returns a field function to return an attribute of the item.
972

973
  @param attr: Attribute name
974

975
  """
976
  getter = operator.attrgetter(attr)
977
  return lambda _, item: getter(item)
978

    
979

    
980
def _GetItemMaybeAttr(attr):
981
  """Returns a field function to return a not-None attribute of the item.
982

983
  If the value is None, then C{_FS_UNAVAIL} will be returned instead.
984

985
  @param attr: Attribute name
986

987
  """
988
  def _helper(_, obj):
989
    val = getattr(obj, attr)
990
    if val is None:
991
      return _FS_UNAVAIL
992
    else:
993
      return val
994
  return _helper
995

    
996

    
997
def _GetNDParam(name):
998
  """Return a field function to return an ND parameter out of the context.
999

1000
  """
1001
  def _helper(ctx, _):
1002
    if ctx.ndparams is None:
1003
      return _FS_UNAVAIL
1004
    else:
1005
      return ctx.ndparams.get(name, None)
1006
  return _helper
1007

    
1008

    
1009
def _BuildNDFields(is_group):
1010
  """Builds all the ndparam fields.
1011

1012
  @param is_group: whether this is called at group or node level
1013

1014
  """
1015
  if is_group:
1016
    field_kind = GQ_CONFIG
1017
  else:
1018
    field_kind = NQ_GROUP
1019
  return [(_MakeField("ndp/%s" % name,
1020
                      constants.NDS_PARAMETER_TITLES.get(name,
1021
                                                         "ndp/%s" % name),
1022
                      _VTToQFT[kind], "The \"%s\" node parameter" % name),
1023
           field_kind, 0, _GetNDParam(name))
1024
          for name, kind in constants.NDS_PARAMETER_TYPES.items()]
1025

    
1026

    
1027
def _ConvWrapInner(convert, fn, ctx, item):
1028
  """Wrapper for converting values.
1029

1030
  @param convert: Conversion function receiving value as single parameter
1031
  @param fn: Retrieval function
1032

1033
  """
1034
  value = fn(ctx, item)
1035

    
1036
  # Is the value an abnormal status?
1037
  if compat.any(value is fs for fs in _FS_ALL):
1038
    # Return right away
1039
    return value
1040

    
1041
  # TODO: Should conversion function also receive context, item or both?
1042
  return convert(value)
1043

    
1044

    
1045
def _ConvWrap(convert, fn):
1046
  """Convenience wrapper for L{_ConvWrapInner}.
1047

1048
  @param convert: Conversion function receiving value as single parameter
1049
  @param fn: Retrieval function
1050

1051
  """
1052
  return compat.partial(_ConvWrapInner, convert, fn)
1053

    
1054

    
1055
def _GetItemTimestamp(getter):
1056
  """Returns function for getting timestamp of item.
1057

1058
  @type getter: callable
1059
  @param getter: Function to retrieve timestamp attribute
1060

1061
  """
1062
  def fn(_, item):
1063
    """Returns a timestamp of item.
1064

1065
    """
1066
    timestamp = getter(item)
1067
    if timestamp is None:
1068
      # Old configs might not have all timestamps
1069
      return _FS_UNAVAIL
1070
    else:
1071
      return timestamp
1072

    
1073
  return fn
1074

    
1075

    
1076
def _GetItemTimestampFields(datatype):
1077
  """Returns common timestamp fields.
1078

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

1081
  """
1082
  return [
1083
    (_MakeField("ctime", "CTime", QFT_TIMESTAMP, "Creation timestamp"),
1084
     datatype, 0, _GetItemTimestamp(operator.attrgetter("ctime"))),
1085
    (_MakeField("mtime", "MTime", QFT_TIMESTAMP, "Modification timestamp"),
1086
     datatype, 0, _GetItemTimestamp(operator.attrgetter("mtime"))),
1087
    ]
1088

    
1089

    
1090
class NodeQueryData:
1091
  """Data container for node data queries.
1092

1093
  """
1094
  def __init__(self, nodes, live_data, master_uuid, node_to_primary,
1095
               node_to_secondary, inst_uuid_to_inst_name, groups, oob_support,
1096
               cluster):
1097
    """Initializes this class.
1098

1099
    """
1100
    self.nodes = nodes
1101
    self.live_data = live_data
1102
    self.master_uuid = master_uuid
1103
    self.node_to_primary = node_to_primary
1104
    self.node_to_secondary = node_to_secondary
1105
    self.inst_uuid_to_inst_name = inst_uuid_to_inst_name
1106
    self.groups = groups
1107
    self.oob_support = oob_support
1108
    self.cluster = cluster
1109

    
1110
    # Used for individual rows
1111
    self.curlive_data = None
1112
    self.ndparams = None
1113

    
1114
  def __iter__(self):
1115
    """Iterate over all nodes.
1116

1117
    This function has side-effects and only one instance of the resulting
1118
    generator should be used at a time.
1119

1120
    """
1121
    for node in self.nodes:
1122
      group = self.groups.get(node.group, None)
1123
      if group is None:
1124
        self.ndparams = None
1125
      else:
1126
        self.ndparams = self.cluster.FillND(node, group)
1127
      if self.live_data:
1128
        self.curlive_data = self.live_data.get(node.uuid, None)
1129
      else:
1130
        self.curlive_data = None
1131
      yield node
1132

    
1133

    
1134
#: Fields that are direct attributes of an L{objects.Node} object
1135
_NODE_SIMPLE_FIELDS = {
1136
  "drained": ("Drained", QFT_BOOL, 0, "Whether node is drained"),
1137
  "master_candidate": ("MasterC", QFT_BOOL, 0,
1138
                       "Whether node is a master candidate"),
1139
  "master_capable": ("MasterCapable", QFT_BOOL, 0,
1140
                     "Whether node can become a master candidate"),
1141
  "name": ("Node", QFT_TEXT, QFF_HOSTNAME, "Node name"),
1142
  "offline": ("Offline", QFT_BOOL, 0, "Whether node is marked offline"),
1143
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Node"),
1144
  "uuid": ("UUID", QFT_TEXT, 0, "Node UUID"),
1145
  "vm_capable": ("VMCapable", QFT_BOOL, 0, "Whether node can host instances"),
1146
  }
1147

    
1148

    
1149
#: Fields requiring talking to the node
1150
# Note that none of these are available for non-vm_capable nodes
1151
_NODE_LIVE_FIELDS = {
1152
  "bootid": ("BootID", QFT_TEXT, "bootid",
1153
             "Random UUID renewed for each system reboot, can be used"
1154
             " for detecting reboots by tracking changes"),
1155
  "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes",
1156
             "Number of NUMA domains on node (if exported by hypervisor)"),
1157
  "cnos": ("CNOs", QFT_NUMBER, "cpu_dom0",
1158
            "Number of logical processors used by the node OS (dom0 for Xen)"),
1159
  "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets",
1160
               "Number of physical CPU sockets (if exported by hypervisor)"),
1161
  "ctotal": ("CTotal", QFT_NUMBER, "cpu_total", "Number of logical processors"),
1162
  "dfree": ("DFree", QFT_UNIT, "storage_free",
1163
            "Available storage space in storage unit"),
1164
  "dtotal": ("DTotal", QFT_UNIT, "storage_size",
1165
             "Total storage space in storage unit used for instance disk"
1166
             " allocation"),
1167
  "spfree": ("SpFree", QFT_NUMBER, "spindles_free",
1168
             "Available spindles in volume group (exclusive storage only)"),
1169
  "sptotal": ("SpTotal", QFT_NUMBER, "spindles_total",
1170
              "Total spindles in volume group (exclusive storage only)"),
1171
  "mfree": ("MFree", QFT_UNIT, "memory_free",
1172
            "Memory available for instance allocations"),
1173
  "mnode": ("MNode", QFT_UNIT, "memory_dom0",
1174
            "Amount of memory used by node (dom0 for Xen)"),
1175
  "mtotal": ("MTotal", QFT_UNIT, "memory_total",
1176
             "Total amount of memory of physical machine"),
1177
  }
1178

    
1179

    
1180
def _GetGroup(cb):
1181
  """Build function for calling another function with an node group.
1182

1183
  @param cb: The callback to be called with the nodegroup
1184

1185
  """
1186
  def fn(ctx, node):
1187
    """Get group data for a node.
1188

1189
    @type ctx: L{NodeQueryData}
1190
    @type inst: L{objects.Node}
1191
    @param inst: Node object
1192

1193
    """
1194
    ng = ctx.groups.get(node.group, None)
1195
    if ng is None:
1196
      # Nodes always have a group, or the configuration is corrupt
1197
      return _FS_UNAVAIL
1198

    
1199
    return cb(ctx, node, ng)
1200

    
1201
  return fn
1202

    
1203

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

1207
  @type ctx: L{NodeQueryData}
1208
  @type node: L{objects.Node}
1209
  @param node: Node object
1210
  @type ng: L{objects.NodeGroup}
1211
  @param ng: The node group this node belongs to
1212

1213
  """
1214
  return ng.name
1215

    
1216

    
1217
def _GetNodePower(ctx, node):
1218
  """Returns the node powered state
1219

1220
  @type ctx: L{NodeQueryData}
1221
  @type node: L{objects.Node}
1222
  @param node: Node object
1223

1224
  """
1225
  if ctx.oob_support[node.uuid]:
1226
    return node.powered
1227

    
1228
  return _FS_UNAVAIL
1229

    
1230

    
1231
def _GetNdParams(ctx, node, ng):
1232
  """Returns the ndparams for this node.
1233

1234
  @type ctx: L{NodeQueryData}
1235
  @type node: L{objects.Node}
1236
  @param node: Node object
1237
  @type ng: L{objects.NodeGroup}
1238
  @param ng: The node group this node belongs to
1239

1240
  """
1241
  return ctx.cluster.SimpleFillND(ng.FillND(node))
1242

    
1243

    
1244
def _GetLiveNodeField(field, kind, ctx, node):
1245
  """Gets the value of a "live" field from L{NodeQueryData}.
1246

1247
  @param field: Live field name
1248
  @param kind: Data kind, one of L{constants.QFT_ALL}
1249
  @type ctx: L{NodeQueryData}
1250
  @type node: L{objects.Node}
1251
  @param node: Node object
1252

1253
  """
1254
  if node.offline:
1255
    return _FS_OFFLINE
1256

    
1257
  if not node.vm_capable:
1258
    return _FS_UNAVAIL
1259

    
1260
  if not ctx.curlive_data:
1261
    return _FS_NODATA
1262

    
1263
  return _GetStatsField(field, kind, ctx.curlive_data)
1264

    
1265

    
1266
def _GetStatsField(field, kind, data):
1267
  """Gets a value from live statistics.
1268

1269
  If the value is not found, L{_FS_UNAVAIL} is returned. If the field kind is
1270
  numeric a conversion to integer is attempted. If that fails, L{_FS_UNAVAIL}
1271
  is returned.
1272

1273
  @param field: Live field name
1274
  @param kind: Data kind, one of L{constants.QFT_ALL}
1275
  @type data: dict
1276
  @param data: Statistics
1277

1278
  """
1279
  try:
1280
    value = data[field]
1281
  except KeyError:
1282
    return _FS_UNAVAIL
1283

    
1284
  if kind == QFT_TEXT:
1285
    return value
1286

    
1287
  assert kind in (QFT_NUMBER, QFT_UNIT)
1288

    
1289
  # Try to convert into number
1290
  try:
1291
    return int(value)
1292
  except (ValueError, TypeError):
1293
    logging.exception("Failed to convert node field '%s' (value %r) to int",
1294
                      field, value)
1295
    return _FS_UNAVAIL
1296

    
1297

    
1298
def _GetNodeHvState(_, node):
1299
  """Converts node's hypervisor state for query result.
1300

1301
  """
1302
  hv_state = node.hv_state
1303

    
1304
  if hv_state is None:
1305
    return _FS_UNAVAIL
1306

    
1307
  return dict((name, value.ToDict()) for (name, value) in hv_state.items())
1308

    
1309

    
1310
def _GetNodeDiskState(_, node):
1311
  """Converts node's disk state for query result.
1312

1313
  """
1314
  disk_state = node.disk_state
1315

    
1316
  if disk_state is None:
1317
    return _FS_UNAVAIL
1318

    
1319
  return dict((disk_kind, dict((name, value.ToDict())
1320
                               for (name, value) in kind_state.items()))
1321
              for (disk_kind, kind_state) in disk_state.items())
1322

    
1323

    
1324
def _BuildNodeFields():
1325
  """Builds list of fields for node queries.
1326

1327
  """
1328
  fields = [
1329
    (_MakeField("pip", "PrimaryIP", QFT_TEXT, "Primary IP address"),
1330
     NQ_CONFIG, 0, _GetItemAttr("primary_ip")),
1331
    (_MakeField("sip", "SecondaryIP", QFT_TEXT, "Secondary IP address"),
1332
     NQ_CONFIG, 0, _GetItemAttr("secondary_ip")),
1333
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0,
1334
     lambda ctx, node: list(node.GetTags())),
1335
    (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"),
1336
     NQ_CONFIG, 0, lambda ctx, node: node.uuid == ctx.master_uuid),
1337
    (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0,
1338
     _GetGroup(_GetNodeGroup)),
1339
    (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"),
1340
     NQ_CONFIG, 0, _GetItemAttr("group")),
1341
    (_MakeField("powered", "Powered", QFT_BOOL,
1342
                "Whether node is thought to be powered on"),
1343
     NQ_OOB, 0, _GetNodePower),
1344
    (_MakeField("ndparams", "NodeParameters", QFT_OTHER,
1345
                "Merged node parameters"),
1346
     NQ_GROUP, 0, _GetGroup(_GetNdParams)),
1347
    (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER,
1348
                "Custom node parameters"),
1349
      NQ_GROUP, 0, _GetItemAttr("ndparams")),
1350
    (_MakeField("hv_state", "HypervisorState", QFT_OTHER, "Hypervisor state"),
1351
     NQ_CONFIG, 0, _GetNodeHvState),
1352
    (_MakeField("disk_state", "DiskState", QFT_OTHER, "Disk state"),
1353
     NQ_CONFIG, 0, _GetNodeDiskState),
1354
    ]
1355

    
1356
  fields.extend(_BuildNDFields(False))
1357

    
1358
  # Node role
1359
  role_values = (constants.NR_MASTER, constants.NR_MCANDIDATE,
1360
                 constants.NR_REGULAR, constants.NR_DRAINED,
1361
                 constants.NR_OFFLINE)
1362
  role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate,"
1363
              " \"%s\" for regular, \"%s\" for drained, \"%s\" for offline" %
1364
              role_values)
1365
  fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
1366
                 lambda ctx, node: _GetNodeRole(node, ctx.master_uuid)))
1367
  assert set(role_values) == constants.NR_ALL
1368

    
1369
  def _GetLength(getter):
1370
    return lambda ctx, node: len(getter(ctx)[node.uuid])
1371

    
1372
  def _GetList(getter):
1373
    return lambda ctx, node: utils.NiceSort(
1374
                               [ctx.inst_uuid_to_inst_name[uuid]
1375
                                for uuid in getter(ctx)[node.uuid]])
1376

    
1377
  # Add fields operating on instance lists
1378
  for prefix, titleprefix, docword, getter in \
1379
      [("p", "Pri", "primary", operator.attrgetter("node_to_primary")),
1380
       ("s", "Sec", "secondary", operator.attrgetter("node_to_secondary"))]:
1381
    # TODO: Allow filterting by hostname in list
1382
    fields.extend([
1383
      (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER,
1384
                  "Number of instances with this node as %s" % docword),
1385
       NQ_INST, 0, _GetLength(getter)),
1386
      (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
1387
                  QFT_OTHER,
1388
                  "List of instances with this node as %s" % docword),
1389
       NQ_INST, 0, _GetList(getter)),
1390
      ])
1391

    
1392
  # Add simple fields
1393
  fields.extend([
1394
    (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1395
    for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()])
1396

    
1397
  # Add fields requiring live data
1398
  fields.extend([
1399
    (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1400
     compat.partial(_GetLiveNodeField, nfield, kind))
1401
    for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()])
1402

    
1403
  # Add timestamps
1404
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1405

    
1406
  return _PrepareFieldList(fields, [])
1407

    
1408

    
1409
class InstanceQueryData:
1410
  """Data container for instance data queries.
1411

1412
  """
1413
  def __init__(self, instances, cluster, disk_usage, offline_node_uuids,
1414
               bad_node_uuids, live_data, wrongnode_inst, console, nodes,
1415
               groups, networks):
1416
    """Initializes this class.
1417

1418
    @param instances: List of instance objects
1419
    @param cluster: Cluster object
1420
    @type disk_usage: dict; instance UUID as key
1421
    @param disk_usage: Per-instance disk usage
1422
    @type offline_node_uuids: list of strings
1423
    @param offline_node_uuids: List of offline nodes
1424
    @type bad_node_uuids: list of strings
1425
    @param bad_node_uuids: List of faulty nodes
1426
    @type live_data: dict; instance UUID as key
1427
    @param live_data: Per-instance live data
1428
    @type wrongnode_inst: set
1429
    @param wrongnode_inst: Set of instances running on wrong node(s)
1430
    @type console: dict; instance UUID as key
1431
    @param console: Per-instance console information
1432
    @type nodes: dict; node UUID as key
1433
    @param nodes: Node objects
1434
    @type networks: dict; net_uuid as key
1435
    @param networks: Network objects
1436

1437
    """
1438
    assert len(set(bad_node_uuids) & set(offline_node_uuids)) == \
1439
           len(offline_node_uuids), \
1440
           "Offline nodes not included in bad nodes"
1441
    assert not (set(live_data.keys()) & set(bad_node_uuids)), \
1442
           "Found live data for bad or offline nodes"
1443

    
1444
    self.instances = instances
1445
    self.cluster = cluster
1446
    self.disk_usage = disk_usage
1447
    self.offline_nodes = offline_node_uuids
1448
    self.bad_nodes = bad_node_uuids
1449
    self.live_data = live_data
1450
    self.wrongnode_inst = wrongnode_inst
1451
    self.console = console
1452
    self.nodes = nodes
1453
    self.groups = groups
1454
    self.networks = networks
1455

    
1456
    # Used for individual rows
1457
    self.inst_hvparams = None
1458
    self.inst_beparams = None
1459
    self.inst_osparams = None
1460
    self.inst_nicparams = None
1461

    
1462
  def __iter__(self):
1463
    """Iterate over all instances.
1464

1465
    This function has side-effects and only one instance of the resulting
1466
    generator should be used at a time.
1467

1468
    """
1469
    for inst in self.instances:
1470
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1471
      self.inst_beparams = self.cluster.FillBE(inst)
1472
      self.inst_osparams = self.cluster.SimpleFillOS(inst.os, inst.osparams)
1473
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1474
                             for nic in inst.nics]
1475

    
1476
      yield inst
1477

    
1478

    
1479
def _GetInstOperState(ctx, inst):
1480
  """Get instance's operational status.
1481

1482
  @type ctx: L{InstanceQueryData}
1483
  @type inst: L{objects.Instance}
1484
  @param inst: Instance object
1485

1486
  """
1487
  # Can't use RS_OFFLINE here as it would describe the instance to
1488
  # be offline when we actually don't know due to missing data
1489
  if inst.primary_node in ctx.bad_nodes:
1490
    return _FS_NODATA
1491
  else:
1492
    return bool(ctx.live_data.get(inst.uuid))
1493

    
1494

    
1495
def _GetInstLiveData(name):
1496
  """Build function for retrieving live data.
1497

1498
  @type name: string
1499
  @param name: Live data field name
1500

1501
  """
1502
  def fn(ctx, inst):
1503
    """Get live data for an instance.
1504

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

1509
    """
1510
    if (inst.primary_node in ctx.bad_nodes or
1511
        inst.primary_node in ctx.offline_nodes):
1512
      # Can't use RS_OFFLINE here as it would describe the instance to be
1513
      # offline when we actually don't know due to missing data
1514
      return _FS_NODATA
1515

    
1516
    if inst.uuid in ctx.live_data:
1517
      data = ctx.live_data[inst.uuid]
1518
      if name in data:
1519
        return data[name]
1520

    
1521
    return _FS_UNAVAIL
1522

    
1523
  return fn
1524

    
1525

    
1526
def _GetInstStatus(ctx, inst):
1527
  """Get instance status.
1528

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

1533
  """
1534
  if inst.primary_node in ctx.offline_nodes:
1535
    return constants.INSTST_NODEOFFLINE
1536

    
1537
  if inst.primary_node in ctx.bad_nodes:
1538
    return constants.INSTST_NODEDOWN
1539

    
1540
  if bool(ctx.live_data.get(inst.uuid)):
1541
    if inst.uuid in ctx.wrongnode_inst:
1542
      return constants.INSTST_WRONGNODE
1543
    elif inst.admin_state == constants.ADMINST_UP:
1544
      return constants.INSTST_RUNNING
1545
    else:
1546
      return constants.INSTST_ERRORUP
1547

    
1548
  if inst.admin_state == constants.ADMINST_UP:
1549
    return constants.INSTST_ERRORDOWN
1550
  elif inst.admin_state == constants.ADMINST_DOWN:
1551
    return constants.INSTST_ADMINDOWN
1552

    
1553
  return constants.INSTST_ADMINOFFLINE
1554

    
1555

    
1556
def _GetInstDisk(index, cb):
1557
  """Build function for calling another function with an instance Disk.
1558

1559
  @type index: int
1560
  @param index: Disk index
1561
  @type cb: callable
1562
  @param cb: Callback
1563

1564
  """
1565
  def fn(ctx, inst):
1566
    """Call helper function with instance Disk.
1567

1568
    @type ctx: L{InstanceQueryData}
1569
    @type inst: L{objects.Instance}
1570
    @param inst: Instance object
1571

1572
    """
1573
    try:
1574
      nic = inst.disks[index]
1575
    except IndexError:
1576
      return _FS_UNAVAIL
1577

    
1578
    return cb(ctx, index, nic)
1579

    
1580
  return fn
1581

    
1582

    
1583
def _GetInstDiskSize(ctx, _, disk): # pylint: disable=W0613
1584
  """Get a Disk's size.
1585

1586
  @type ctx: L{InstanceQueryData}
1587
  @type disk: L{objects.Disk}
1588
  @param disk: The Disk object
1589

1590
  """
1591
  if disk.size is None:
1592
    return _FS_UNAVAIL
1593
  else:
1594
    return disk.size
1595

    
1596

    
1597
def _GetInstDiskSpindles(ctx, _, disk): # pylint: disable=W0613
1598
  """Get a Disk's spindles.
1599

1600
  @type disk: L{objects.Disk}
1601
  @param disk: The Disk object
1602

1603
  """
1604
  if disk.spindles is None:
1605
    return _FS_UNAVAIL
1606
  else:
1607
    return disk.spindles
1608

    
1609

    
1610
def _GetInstDeviceName(ctx, _, device): # pylint: disable=W0613
1611
  """Get a Device's Name.
1612

1613
  @type ctx: L{InstanceQueryData}
1614
  @type device: L{objects.NIC} or L{objects.Disk}
1615
  @param device: The NIC or Disk object
1616

1617
  """
1618
  if device.name is None:
1619
    return _FS_UNAVAIL
1620
  else:
1621
    return device.name
1622

    
1623

    
1624
def _GetInstDeviceUUID(ctx, _, device): # pylint: disable=W0613
1625
  """Get a Device's UUID.
1626

1627
  @type ctx: L{InstanceQueryData}
1628
  @type device: L{objects.NIC} or L{objects.Disk}
1629
  @param device: The NIC or Disk object
1630

1631
  """
1632
  if device.uuid is None:
1633
    return _FS_UNAVAIL
1634
  else:
1635
    return device.uuid
1636

    
1637

    
1638
def _GetInstNic(index, cb):
1639
  """Build function for calling another function with an instance NIC.
1640

1641
  @type index: int
1642
  @param index: NIC index
1643
  @type cb: callable
1644
  @param cb: Callback
1645

1646
  """
1647
  def fn(ctx, inst):
1648
    """Call helper function with instance NIC.
1649

1650
    @type ctx: L{InstanceQueryData}
1651
    @type inst: L{objects.Instance}
1652
    @param inst: Instance object
1653

1654
    """
1655
    try:
1656
      nic = inst.nics[index]
1657
    except IndexError:
1658
      return _FS_UNAVAIL
1659

    
1660
    return cb(ctx, index, nic)
1661

    
1662
  return fn
1663

    
1664

    
1665
def _GetInstNicNetworkName(ctx, _, nic): # pylint: disable=W0613
1666
  """Get a NIC's Network.
1667

1668
  @type ctx: L{InstanceQueryData}
1669
  @type nic: L{objects.NIC}
1670
  @param nic: NIC object
1671

1672
  """
1673
  if nic.network is None:
1674
    return _FS_UNAVAIL
1675
  else:
1676
    return ctx.networks[nic.network].name
1677

    
1678

    
1679
def _GetInstNicNetwork(ctx, _, nic): # pylint: disable=W0613
1680
  """Get a NIC's Network.
1681

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

1686
  """
1687
  if nic.network is None:
1688
    return _FS_UNAVAIL
1689
  else:
1690
    return nic.network
1691

    
1692

    
1693
def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613
1694
  """Get a NIC's IP address.
1695

1696
  @type ctx: L{InstanceQueryData}
1697
  @type nic: L{objects.NIC}
1698
  @param nic: NIC object
1699

1700
  """
1701
  if nic.ip is None:
1702
    return _FS_UNAVAIL
1703
  else:
1704
    return nic.ip
1705

    
1706

    
1707
def _GetInstNicBridge(ctx, index, _):
1708
  """Get a NIC's bridge.
1709

1710
  @type ctx: L{InstanceQueryData}
1711
  @type index: int
1712
  @param index: NIC index
1713

1714
  """
1715
  assert len(ctx.inst_nicparams) >= index
1716

    
1717
  nicparams = ctx.inst_nicparams[index]
1718

    
1719
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1720
    return nicparams[constants.NIC_LINK]
1721
  else:
1722
    return _FS_UNAVAIL
1723

    
1724

    
1725
def _GetInstAllNicNetworkNames(ctx, inst):
1726
  """Get all network names for an instance.
1727

1728
  @type ctx: L{InstanceQueryData}
1729
  @type inst: L{objects.Instance}
1730
  @param inst: Instance object
1731

1732
  """
1733
  result = []
1734

    
1735
  for nic in inst.nics:
1736
    name = None
1737
    if nic.network:
1738
      name = ctx.networks[nic.network].name
1739
    result.append(name)
1740

    
1741
  assert len(result) == len(inst.nics)
1742

    
1743
  return result
1744

    
1745

    
1746
def _GetInstAllNicBridges(ctx, inst):
1747
  """Get all network bridges for an instance.
1748

1749
  @type ctx: L{InstanceQueryData}
1750
  @type inst: L{objects.Instance}
1751
  @param inst: Instance object
1752

1753
  """
1754
  assert len(ctx.inst_nicparams) == len(inst.nics)
1755

    
1756
  result = []
1757

    
1758
  for nicp in ctx.inst_nicparams:
1759
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1760
      result.append(nicp[constants.NIC_LINK])
1761
    else:
1762
      result.append(None)
1763

    
1764
  assert len(result) == len(inst.nics)
1765

    
1766
  return result
1767

    
1768

    
1769
def _GetInstNicParam(name):
1770
  """Build function for retrieving a NIC parameter.
1771

1772
  @type name: string
1773
  @param name: Parameter name
1774

1775
  """
1776
  def fn(ctx, index, _):
1777
    """Get a NIC's bridge.
1778

1779
    @type ctx: L{InstanceQueryData}
1780
    @type inst: L{objects.Instance}
1781
    @param inst: Instance object
1782
    @type nic: L{objects.NIC}
1783
    @param nic: NIC object
1784

1785
    """
1786
    assert len(ctx.inst_nicparams) >= index
1787
    return ctx.inst_nicparams[index][name]
1788

    
1789
  return fn
1790

    
1791

    
1792
def _GetInstanceNetworkFields():
1793
  """Get instance fields involving network interfaces.
1794

1795
  @return: Tuple containing list of field definitions used as input for
1796
    L{_PrepareFieldList} and a list of aliases
1797

1798
  """
1799
  nic_mac_fn = lambda ctx, _, nic: nic.mac
1800
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1801
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1802

    
1803
  fields = [
1804
    # All NICs
1805
    (_MakeField("nic.count", "NICs", QFT_NUMBER,
1806
                "Number of network interfaces"),
1807
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.nics)),
1808
    (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER,
1809
                "List containing each network interface's MAC address"),
1810
     IQ_CONFIG, 0, lambda ctx, inst: [nic.mac for nic in inst.nics]),
1811
    (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
1812
                "List containing each network interface's IP address"),
1813
     IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
1814
    (_MakeField("nic.names", "NIC_Names", QFT_OTHER,
1815
                "List containing each network interface's name"),
1816
     IQ_CONFIG, 0, lambda ctx, inst: [nic.name for nic in inst.nics]),
1817
    (_MakeField("nic.uuids", "NIC_UUIDs", QFT_OTHER,
1818
                "List containing each network interface's UUID"),
1819
     IQ_CONFIG, 0, lambda ctx, inst: [nic.uuid for nic in inst.nics]),
1820
    (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
1821
                "List containing each network interface's mode"), IQ_CONFIG, 0,
1822
     lambda ctx, inst: [nicp[constants.NIC_MODE]
1823
                        for nicp in ctx.inst_nicparams]),
1824
    (_MakeField("nic.links", "NIC_links", QFT_OTHER,
1825
                "List containing each network interface's link"), IQ_CONFIG, 0,
1826
     lambda ctx, inst: [nicp[constants.NIC_LINK]
1827
                        for nicp in ctx.inst_nicparams]),
1828
    (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
1829
                "List containing each network interface's bridge"),
1830
     IQ_CONFIG, 0, _GetInstAllNicBridges),
1831
    (_MakeField("nic.networks", "NIC_networks", QFT_OTHER,
1832
                "List containing each interface's network"), IQ_CONFIG, 0,
1833
     lambda ctx, inst: [nic.network for nic in inst.nics]),
1834
    (_MakeField("nic.networks.names", "NIC_networks_names", QFT_OTHER,
1835
                "List containing each interface's network"),
1836
     IQ_NETWORKS, 0, _GetInstAllNicNetworkNames)
1837
    ]
1838

    
1839
  # NICs by number
1840
  for i in range(constants.MAX_NICS):
1841
    numtext = utils.FormatOrdinal(i + 1)
1842
    fields.extend([
1843
      (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT,
1844
                  "IP address of %s network interface" % numtext),
1845
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)),
1846
      (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
1847
                  "MAC address of %s network interface" % numtext),
1848
       IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
1849
      (_MakeField("nic.name/%s" % i, "NicName/%s" % i, QFT_TEXT,
1850
                  "Name address of %s network interface" % numtext),
1851
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstDeviceName)),
1852
      (_MakeField("nic.uuid/%s" % i, "NicUUID/%s" % i, QFT_TEXT,
1853
                  "UUID address of %s network interface" % numtext),
1854
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstDeviceUUID)),
1855
      (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
1856
                  "Mode of %s network interface" % numtext),
1857
       IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
1858
      (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT,
1859
                  "Link of %s network interface" % numtext),
1860
       IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)),
1861
      (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
1862
                  "Bridge of %s network interface" % numtext),
1863
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
1864
      (_MakeField("nic.network/%s" % i, "NicNetwork/%s" % i, QFT_TEXT,
1865
                  "Network of %s network interface" % numtext),
1866
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicNetwork)),
1867
      (_MakeField("nic.network.name/%s" % i, "NicNetworkName/%s" % i, QFT_TEXT,
1868
                  "Network name of %s network interface" % numtext),
1869
       IQ_NETWORKS, 0, _GetInstNic(i, _GetInstNicNetworkName)),
1870
      ])
1871

    
1872
  aliases = [
1873
    # Legacy fields for first NIC
1874
    ("ip", "nic.ip/0"),
1875
    ("mac", "nic.mac/0"),
1876
    ("bridge", "nic.bridge/0"),
1877
    ("nic_mode", "nic.mode/0"),
1878
    ("nic_link", "nic.link/0"),
1879
    ("nic_network", "nic.network/0"),
1880
    ]
1881

    
1882
  return (fields, aliases)
1883

    
1884

    
1885
def _GetInstDiskUsage(ctx, inst):
1886
  """Get disk usage for an instance.
1887

1888
  @type ctx: L{InstanceQueryData}
1889
  @type inst: L{objects.Instance}
1890
  @param inst: Instance object
1891

1892
  """
1893
  usage = ctx.disk_usage[inst.uuid]
1894

    
1895
  if usage is None:
1896
    usage = 0
1897

    
1898
  return usage
1899

    
1900

    
1901
def _GetInstanceConsole(ctx, inst):
1902
  """Get console information for instance.
1903

1904
  @type ctx: L{InstanceQueryData}
1905
  @type inst: L{objects.Instance}
1906
  @param inst: Instance object
1907

1908
  """
1909
  consinfo = ctx.console[inst.uuid]
1910

    
1911
  if consinfo is None:
1912
    return _FS_UNAVAIL
1913

    
1914
  return consinfo
1915

    
1916

    
1917
def _GetInstanceDiskFields():
1918
  """Get instance fields involving disks.
1919

1920
  @return: List of field definitions used as input for L{_PrepareFieldList}
1921

1922
  """
1923
  fields = [
1924
    (_MakeField("disk_usage", "DiskUsage", QFT_UNIT,
1925
                "Total disk space used by instance on each of its nodes;"
1926
                " this is not the disk size visible to the instance, but"
1927
                " the usage on the node"),
1928
     IQ_DISKUSAGE, 0, _GetInstDiskUsage),
1929
    (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"),
1930
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
1931
    (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
1932
     IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
1933
    (_MakeField("disk.spindles", "Disk_spindles", QFT_OTHER,
1934
                "List of disk spindles"),
1935
     IQ_CONFIG, 0, lambda ctx, inst: [disk.spindles for disk in inst.disks]),
1936
    (_MakeField("disk.names", "Disk_names", QFT_OTHER, "List of disk names"),
1937
     IQ_CONFIG, 0, lambda ctx, inst: [disk.name for disk in inst.disks]),
1938
    (_MakeField("disk.uuids", "Disk_UUIDs", QFT_OTHER, "List of disk UUIDs"),
1939
     IQ_CONFIG, 0, lambda ctx, inst: [disk.uuid for disk in inst.disks]),
1940
    ]
1941

    
1942
  # Disks by number
1943
  for i in range(constants.MAX_DISKS):
1944
    numtext = utils.FormatOrdinal(i + 1)
1945
    fields.extend([
1946
        (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1947
                    "Disk size of %s disk" % numtext),
1948
        IQ_CONFIG, 0, _GetInstDisk(i, _GetInstDiskSize)),
1949
        (_MakeField("disk.spindles/%s" % i, "DiskSpindles/%s" % i, QFT_NUMBER,
1950
                    "Spindles of %s disk" % numtext),
1951
         IQ_CONFIG, 0, _GetInstDisk(i, _GetInstDiskSpindles)),
1952
        (_MakeField("disk.name/%s" % i, "DiskName/%s" % i, QFT_TEXT,
1953
                    "Name of %s disk" % numtext),
1954
        IQ_CONFIG, 0, _GetInstDisk(i, _GetInstDeviceName)),
1955
        (_MakeField("disk.uuid/%s" % i, "DiskUUID/%s" % i, QFT_TEXT,
1956
                    "UUID of %s disk" % numtext),
1957
        IQ_CONFIG, 0, _GetInstDisk(i, _GetInstDeviceUUID))])
1958

    
1959
  return fields
1960

    
1961

    
1962
def _GetInstanceParameterFields():
1963
  """Get instance fields involving parameters.
1964

1965
  @return: List of field definitions used as input for L{_PrepareFieldList}
1966

1967
  """
1968
  fields = [
1969
    # Filled parameters
1970
    (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
1971
                "Hypervisor parameters (merged)"),
1972
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
1973
    (_MakeField("beparams", "BackendParameters", QFT_OTHER,
1974
                "Backend parameters (merged)"),
1975
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
1976
    (_MakeField("osparams", "OpSysParameters", QFT_OTHER,
1977
                "Operating system parameters (merged)"),
1978
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_osparams),
1979

    
1980
    # Unfilled parameters
1981
    (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
1982
                "Custom hypervisor parameters"),
1983
     IQ_CONFIG, 0, _GetItemAttr("hvparams")),
1984
    (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
1985
                "Custom backend parameters",),
1986
     IQ_CONFIG, 0, _GetItemAttr("beparams")),
1987
    (_MakeField("custom_osparams", "CustomOpSysParameters", QFT_OTHER,
1988
                "Custom operating system parameters",),
1989
     IQ_CONFIG, 0, _GetItemAttr("osparams")),
1990
    (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
1991
                "Custom network interface parameters"),
1992
     IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1993
    ]
1994

    
1995
  # HV params
1996
  def _GetInstHvParam(name):
1997
    return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1998

    
1999
  fields.extend([
2000
    (_MakeField("hv/%s" % name,
2001
                constants.HVS_PARAMETER_TITLES.get(name, "hv/%s" % name),
2002
                _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
2003
     IQ_CONFIG, 0, _GetInstHvParam(name))
2004
    for name, kind in constants.HVS_PARAMETER_TYPES.items()
2005
    if name not in constants.HVC_GLOBALS])
2006

    
2007
  # BE params
2008
  def _GetInstBeParam(name):
2009
    return lambda ctx, _: ctx.inst_beparams.get(name, None)
2010

    
2011
  fields.extend([
2012
    (_MakeField("be/%s" % name,
2013
                constants.BES_PARAMETER_TITLES.get(name, "be/%s" % name),
2014
                _VTToQFT[kind], "The \"%s\" backend parameter" % name),
2015
     IQ_CONFIG, 0, _GetInstBeParam(name))
2016
    for name, kind in constants.BES_PARAMETER_TYPES.items()])
2017

    
2018
  return fields
2019

    
2020

    
2021
_INST_SIMPLE_FIELDS = {
2022
  "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"),
2023
  "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"),
2024
  "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"),
2025
  # Depending on the hypervisor, the port can be None
2026
  "network_port": ("Network_port", QFT_OTHER, 0,
2027
                   "Instance network port if available (e.g. for VNC console)"),
2028
  "os": ("OS", QFT_TEXT, 0, "Operating system"),
2029
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"),
2030
  "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"),
2031
  }
2032

    
2033

    
2034
def _GetNodeName(ctx, default, node_uuid):
2035
  """Gets node name of a node.
2036

2037
  @type ctx: L{InstanceQueryData}
2038
  @param default: Default value
2039
  @type node_uuid: string
2040
  @param node_uuid: Node UUID
2041

2042
  """
2043
  try:
2044
    node = ctx.nodes[node_uuid]
2045
  except KeyError:
2046
    return default
2047
  else:
2048
    return node.name
2049

    
2050

    
2051
def _GetInstNodeGroup(ctx, default, node_uuid):
2052
  """Gets group UUID of an instance node.
2053

2054
  @type ctx: L{InstanceQueryData}
2055
  @param default: Default value
2056
  @type node_uuid: string
2057
  @param node_uuid: Node UUID
2058

2059
  """
2060
  try:
2061
    node = ctx.nodes[node_uuid]
2062
  except KeyError:
2063
    return default
2064
  else:
2065
    return node.group
2066

    
2067

    
2068
def _GetInstNodeGroupName(ctx, default, node_uuid):
2069
  """Gets group name of an instance node.
2070

2071
  @type ctx: L{InstanceQueryData}
2072
  @param default: Default value
2073
  @type node_uuid: string
2074
  @param node_uuid: Node UUID
2075

2076
  """
2077
  try:
2078
    node = ctx.nodes[node_uuid]
2079
  except KeyError:
2080
    return default
2081

    
2082
  try:
2083
    group = ctx.groups[node.group]
2084
  except KeyError:
2085
    return default
2086

    
2087
  return group.name
2088

    
2089

    
2090
def _BuildInstanceFields():
2091
  """Builds list of fields for instance queries.
2092

2093
  """
2094
  fields = [
2095
    (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
2096
     IQ_NODES, QFF_HOSTNAME,
2097
     lambda ctx, inst: _GetNodeName(ctx, None, inst.primary_node)),
2098
    (_MakeField("pnode.group", "PrimaryNodeGroup", QFT_TEXT,
2099
                "Primary node's group"),
2100
     IQ_NODES, 0,
2101
     lambda ctx, inst: _GetInstNodeGroupName(ctx, _FS_UNAVAIL,
2102
                                             inst.primary_node)),
2103
    (_MakeField("pnode.group.uuid", "PrimaryNodeGroupUUID", QFT_TEXT,
2104
                "Primary node's group UUID"),
2105
     IQ_NODES, 0,
2106
     lambda ctx, inst: _GetInstNodeGroup(ctx, _FS_UNAVAIL, inst.primary_node)),
2107
    # TODO: Allow filtering by secondary node as hostname
2108
    (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
2109
                "Secondary nodes; usually this will just be one node"),
2110
     IQ_NODES, 0,
2111
     lambda ctx, inst: map(compat.partial(_GetNodeName, ctx, None),
2112
                           inst.secondary_nodes)),
2113
    (_MakeField("snodes.group", "SecondaryNodesGroups", QFT_OTHER,
2114
                "Node groups of secondary nodes"),
2115
     IQ_NODES, 0,
2116
     lambda ctx, inst: map(compat.partial(_GetInstNodeGroupName, ctx, None),
2117
                           inst.secondary_nodes)),
2118
    (_MakeField("snodes.group.uuid", "SecondaryNodesGroupsUUID", QFT_OTHER,
2119
                "Node group UUIDs of secondary nodes"),
2120
     IQ_NODES, 0,
2121
     lambda ctx, inst: map(compat.partial(_GetInstNodeGroup, ctx, None),
2122
                           inst.secondary_nodes)),
2123
    (_MakeField("admin_state", "InstanceState", QFT_TEXT,
2124
                "Desired state of instance"),
2125
     IQ_CONFIG, 0, _GetItemAttr("admin_state")),
2126
    (_MakeField("admin_up", "Autostart", QFT_BOOL,
2127
                "Desired state of instance"),
2128
     IQ_CONFIG, 0, lambda ctx, inst: inst.admin_state == constants.ADMINST_UP),
2129
    (_MakeField("disks_active", "DisksActive", QFT_BOOL,
2130
                "Desired state of instance disks"),
2131
     IQ_CONFIG, 0, _GetItemAttr("disks_active")),
2132
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
2133
     lambda ctx, inst: list(inst.GetTags())),
2134
    (_MakeField("console", "Console", QFT_OTHER,
2135
                "Instance console information"), IQ_CONSOLE, 0,
2136
     _GetInstanceConsole),
2137
    ]
2138

    
2139
  # Add simple fields
2140
  fields.extend([
2141
    (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
2142
    for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()])
2143

    
2144
  # Fields requiring talking to the node
2145
  fields.extend([
2146
    (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
2147
     IQ_LIVE, 0, _GetInstOperState),
2148
    (_MakeField("oper_ram", "Memory", QFT_UNIT,
2149
                "Actual memory usage as seen by hypervisor"),
2150
     IQ_LIVE, 0, _GetInstLiveData("memory")),
2151
    (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
2152
                "Actual number of VCPUs as seen by hypervisor"),
2153
     IQ_LIVE, 0, _GetInstLiveData("vcpus")),
2154
    ])
2155

    
2156
  # Status field
2157
  status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
2158
                   constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
2159
                   constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
2160
                   constants.INSTST_NODEOFFLINE, constants.INSTST_ADMINOFFLINE)
2161
  status_doc = ("Instance status; \"%s\" if instance is set to be running"
2162
                " and actually is, \"%s\" if instance is stopped and"
2163
                " is not running, \"%s\" if instance running, but not on its"
2164
                " designated primary node, \"%s\" if instance should be"
2165
                " stopped, but is actually running, \"%s\" if instance should"
2166
                " run, but doesn't, \"%s\" if instance's primary node is down,"
2167
                " \"%s\" if instance's primary node is marked offline,"
2168
                " \"%s\" if instance is offline and does not use dynamic"
2169
                " resources" % status_values)
2170
  fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
2171
                 IQ_LIVE, 0, _GetInstStatus))
2172
  assert set(status_values) == constants.INSTST_ALL, \
2173
         "Status documentation mismatch"
2174

    
2175
  (network_fields, network_aliases) = _GetInstanceNetworkFields()
2176

    
2177
  fields.extend(network_fields)
2178
  fields.extend(_GetInstanceParameterFields())
2179
  fields.extend(_GetInstanceDiskFields())
2180
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
2181

    
2182
  aliases = [
2183
    ("vcpus", "be/vcpus"),
2184
    ("be/memory", "be/maxmem"),
2185
    ("sda_size", "disk.size/0"),
2186
    ("sdb_size", "disk.size/1"),
2187
    ] + network_aliases
2188

    
2189
  return _PrepareFieldList(fields, aliases)
2190

    
2191

    
2192
class LockQueryData:
2193
  """Data container for lock data queries.
2194

2195
  """
2196
  def __init__(self, lockdata):
2197
    """Initializes this class.
2198

2199
    """
2200
    self.lockdata = lockdata
2201

    
2202
  def __iter__(self):
2203
    """Iterate over all locks.
2204

2205
    """
2206
    return iter(self.lockdata)
2207

    
2208

    
2209
def _GetLockOwners(_, data):
2210
  """Returns a sorted list of a lock's current owners.
2211

2212
  """
2213
  (_, _, owners, _) = data
2214

    
2215
  if owners:
2216
    owners = utils.NiceSort(owners)
2217

    
2218
  return owners
2219

    
2220

    
2221
def _GetLockPending(_, data):
2222
  """Returns a sorted list of a lock's pending acquires.
2223

2224
  """
2225
  (_, _, _, pending) = data
2226

    
2227
  if pending:
2228
    pending = [(mode, utils.NiceSort(names))
2229
               for (mode, names) in pending]
2230

    
2231
  return pending
2232

    
2233

    
2234
def _BuildLockFields():
2235
  """Builds list of fields for lock queries.
2236

2237
  """
2238
  return _PrepareFieldList([
2239
    # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
2240
    (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
2241
     lambda ctx, (name, mode, owners, pending): name),
2242
    (_MakeField("mode", "Mode", QFT_OTHER,
2243
                "Mode in which the lock is currently acquired"
2244
                " (exclusive or shared)"),
2245
     LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
2246
    (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
2247
     LQ_OWNER, 0, _GetLockOwners),
2248
    (_MakeField("pending", "Pending", QFT_OTHER,
2249
                "Threads waiting for the lock"),
2250
     LQ_PENDING, 0, _GetLockPending),
2251
    ], [])
2252

    
2253

    
2254
class GroupQueryData:
2255
  """Data container for node group data queries.
2256

2257
  """
2258
  def __init__(self, cluster, groups, group_to_nodes, group_to_instances,
2259
               want_diskparams):
2260
    """Initializes this class.
2261

2262
    @param cluster: Cluster object
2263
    @param groups: List of node group objects
2264
    @type group_to_nodes: dict; group UUID as key
2265
    @param group_to_nodes: Per-group list of nodes
2266
    @type group_to_instances: dict; group UUID as key
2267
    @param group_to_instances: Per-group list of (primary) instances
2268
    @type want_diskparams: bool
2269
    @param want_diskparams: Whether diskparamters should be calculated
2270

2271
    """
2272
    self.groups = groups
2273
    self.group_to_nodes = group_to_nodes
2274
    self.group_to_instances = group_to_instances
2275
    self.cluster = cluster
2276
    self.want_diskparams = want_diskparams
2277

    
2278
    # Used for individual rows
2279
    self.group_ipolicy = None
2280
    self.ndparams = None
2281
    self.group_dp = None
2282

    
2283
  def __iter__(self):
2284
    """Iterate over all node groups.
2285

2286
    This function has side-effects and only one instance of the resulting
2287
    generator should be used at a time.
2288

2289
    """
2290
    for group in self.groups:
2291
      self.group_ipolicy = self.cluster.SimpleFillIPolicy(group.ipolicy)
2292
      self.ndparams = self.cluster.SimpleFillND(group.ndparams)
2293
      if self.want_diskparams:
2294
        self.group_dp = self.cluster.SimpleFillDP(group.diskparams)
2295
      else:
2296
        self.group_dp = None
2297
      yield group
2298

    
2299

    
2300
_GROUP_SIMPLE_FIELDS = {
2301
  "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
2302
  "name": ("Group", QFT_TEXT, "Group name"),
2303
  "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
2304
  "uuid": ("UUID", QFT_TEXT, "Group UUID"),
2305
  }
2306

    
2307

    
2308
def _BuildGroupFields():
2309
  """Builds list of fields for node group queries.
2310

2311
  """
2312
  # Add simple fields
2313
  fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
2314
             _GetItemAttr(name))
2315
            for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
2316

    
2317
  def _GetLength(getter):
2318
    return lambda ctx, group: len(getter(ctx)[group.uuid])
2319

    
2320
  def _GetSortedList(getter):
2321
    return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
2322

    
2323
  group_to_nodes = operator.attrgetter("group_to_nodes")
2324
  group_to_instances = operator.attrgetter("group_to_instances")
2325

    
2326
  # Add fields for nodes
2327
  fields.extend([
2328
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
2329
     GQ_NODE, 0, _GetLength(group_to_nodes)),
2330
    (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
2331
     GQ_NODE, 0, _GetSortedList(group_to_nodes)),
2332
    ])
2333

    
2334
  # Add fields for instances
2335
  fields.extend([
2336
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
2337
                "Number of primary instances"),
2338
     GQ_INST, 0, _GetLength(group_to_instances)),
2339
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
2340
                "List of primary instances"),
2341
     GQ_INST, 0, _GetSortedList(group_to_instances)),
2342
    ])
2343

    
2344
  # Other fields
2345
  fields.extend([
2346
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), GQ_CONFIG, 0,
2347
     lambda ctx, group: list(group.GetTags())),
2348
    (_MakeField("ipolicy", "InstancePolicy", QFT_OTHER,
2349
                "Instance policy limitations (merged)"),
2350
     GQ_CONFIG, 0, lambda ctx, _: ctx.group_ipolicy),
2351
    (_MakeField("custom_ipolicy", "CustomInstancePolicy", QFT_OTHER,
2352
                "Custom instance policy limitations"),
2353
     GQ_CONFIG, 0, _GetItemAttr("ipolicy")),
2354
    (_MakeField("custom_ndparams", "CustomNDParams", QFT_OTHER,
2355
                "Custom node parameters"),
2356
     GQ_CONFIG, 0, _GetItemAttr("ndparams")),
2357
    (_MakeField("ndparams", "NDParams", QFT_OTHER,
2358
                "Node parameters"),
2359
     GQ_CONFIG, 0, lambda ctx, _: ctx.ndparams),
2360
    (_MakeField("diskparams", "DiskParameters", QFT_OTHER,
2361
                "Disk parameters (merged)"),
2362
     GQ_DISKPARAMS, 0, lambda ctx, _: ctx.group_dp),
2363
    (_MakeField("custom_diskparams", "CustomDiskParameters", QFT_OTHER,
2364
                "Custom disk parameters"),
2365
     GQ_CONFIG, 0, _GetItemAttr("diskparams")),
2366
    ])
2367

    
2368
  # ND parameters
2369
  fields.extend(_BuildNDFields(True))
2370

    
2371
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
2372

    
2373
  return _PrepareFieldList(fields, [])
2374

    
2375

    
2376
class OsInfo(objects.ConfigObject):
2377
  __slots__ = [
2378
    "name",
2379
    "valid",
2380
    "hidden",
2381
    "blacklisted",
2382
    "variants",
2383
    "api_versions",
2384
    "parameters",
2385
    "node_status",
2386
    ]
2387

    
2388

    
2389
def _BuildOsFields():
2390
  """Builds list of fields for operating system queries.
2391

2392
  """
2393
  fields = [
2394
    (_MakeField("name", "Name", QFT_TEXT, "Operating system name"),
2395
     None, 0, _GetItemAttr("name")),
2396
    (_MakeField("valid", "Valid", QFT_BOOL,
2397
                "Whether operating system definition is valid"),
2398
     None, 0, _GetItemAttr("valid")),
2399
    (_MakeField("hidden", "Hidden", QFT_BOOL,
2400
                "Whether operating system is hidden"),
2401
     None, 0, _GetItemAttr("hidden")),
2402
    (_MakeField("blacklisted", "Blacklisted", QFT_BOOL,
2403
                "Whether operating system is blacklisted"),
2404
     None, 0, _GetItemAttr("blacklisted")),
2405
    (_MakeField("variants", "Variants", QFT_OTHER,
2406
                "Operating system variants"),
2407
     None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("variants"))),
2408
    (_MakeField("api_versions", "ApiVersions", QFT_OTHER,
2409
                "Operating system API versions"),
2410
     None, 0, _ConvWrap(sorted, _GetItemAttr("api_versions"))),
2411
    (_MakeField("parameters", "Parameters", QFT_OTHER,
2412
                "Operating system parameters"),
2413
     None, 0, _ConvWrap(compat.partial(utils.NiceSort, key=compat.fst),
2414
                        _GetItemAttr("parameters"))),
2415
    (_MakeField("node_status", "NodeStatus", QFT_OTHER,
2416
                "Status from node"),
2417
     None, 0, _GetItemAttr("node_status")),
2418
    ]
2419

    
2420
  return _PrepareFieldList(fields, [])
2421

    
2422

    
2423
class ExtStorageInfo(objects.ConfigObject):
2424
  __slots__ = [
2425
    "name",
2426
    "node_status",
2427
    "nodegroup_status",
2428
    "parameters",
2429
    ]
2430

    
2431

    
2432
def _BuildExtStorageFields():
2433
  """Builds list of fields for extstorage provider queries.
2434

2435
  """
2436
  fields = [
2437
    (_MakeField("name", "Name", QFT_TEXT, "ExtStorage provider name"),
2438
     None, 0, _GetItemAttr("name")),
2439
    (_MakeField("node_status", "NodeStatus", QFT_OTHER,
2440
                "Status from node"),
2441
     None, 0, _GetItemAttr("node_status")),
2442
    (_MakeField("nodegroup_status", "NodegroupStatus", QFT_OTHER,
2443
                "Overall Nodegroup status"),
2444
     None, 0, _GetItemAttr("nodegroup_status")),
2445
    (_MakeField("parameters", "Parameters", QFT_OTHER,
2446
                "ExtStorage provider parameters"),
2447
     None, 0, _GetItemAttr("parameters")),
2448
    ]
2449

    
2450
  return _PrepareFieldList(fields, [])
2451

    
2452

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

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

2459
  """
2460
  if job is None:
2461
    return _FS_UNAVAIL
2462
  else:
2463
    return fn(job)
2464

    
2465

    
2466
def _JobUnavail(inner):
2467
  """Wrapper for L{_JobUnavailInner}.
2468

2469
  """
2470
  return compat.partial(_JobUnavailInner, inner)
2471

    
2472

    
2473
def _PerJobOpInner(fn, job):
2474
  """Executes a function per opcode in a job.
2475

2476
  """
2477
  return map(fn, job.ops)
2478

    
2479

    
2480
def _PerJobOp(fn):
2481
  """Wrapper for L{_PerJobOpInner}.
2482

2483
  """
2484
  return _JobUnavail(compat.partial(_PerJobOpInner, fn))
2485

    
2486

    
2487
def _JobTimestampInner(fn, job):
2488
  """Converts unavailable timestamp to L{_FS_UNAVAIL}.
2489

2490
  """
2491
  timestamp = fn(job)
2492

    
2493
  if timestamp is None:
2494
    return _FS_UNAVAIL
2495
  else:
2496
    return timestamp
2497

    
2498

    
2499
def _JobTimestamp(fn):
2500
  """Wrapper for L{_JobTimestampInner}.
2501

2502
  """
2503
  return _JobUnavail(compat.partial(_JobTimestampInner, fn))
2504

    
2505

    
2506
def _BuildJobFields():
2507
  """Builds list of fields for job queries.
2508

2509
  """
2510
  fields = [
2511
    (_MakeField("id", "ID", QFT_NUMBER, "Job ID"),
2512
     None, QFF_JOB_ID, lambda _, (job_id, job): job_id),
2513
    (_MakeField("status", "Status", QFT_TEXT, "Job status"),
2514
     None, 0, _JobUnavail(lambda job: job.CalcStatus())),
2515
    (_MakeField("priority", "Priority", QFT_NUMBER,
2516
                ("Current job priority (%s to %s)" %
2517
                 (constants.OP_PRIO_LOWEST, constants.OP_PRIO_HIGHEST))),
2518
     None, 0, _JobUnavail(lambda job: job.CalcPriority())),
2519
    (_MakeField("archived", "Archived", QFT_BOOL, "Whether job is archived"),
2520
     JQ_ARCHIVED, 0, lambda _, (job_id, job): job.archived),
2521
    (_MakeField("ops", "OpCodes", QFT_OTHER, "List of all opcodes"),
2522
     None, 0, _PerJobOp(lambda op: op.input.__getstate__())),
2523
    (_MakeField("opresult", "OpCode_result", QFT_OTHER,
2524
                "List of opcodes results"),
2525
     None, 0, _PerJobOp(operator.attrgetter("result"))),
2526
    (_MakeField("opstatus", "OpCode_status", QFT_OTHER,
2527
                "List of opcodes status"),
2528
     None, 0, _PerJobOp(operator.attrgetter("status"))),
2529
    (_MakeField("oplog", "OpCode_log", QFT_OTHER,
2530
                "List of opcode output logs"),
2531
     None, 0, _PerJobOp(operator.attrgetter("log"))),
2532
    (_MakeField("opstart", "OpCode_start", QFT_OTHER,
2533
                "List of opcode start timestamps (before acquiring locks)"),
2534
     None, 0, _PerJobOp(operator.attrgetter("start_timestamp"))),
2535
    (_MakeField("opexec", "OpCode_exec", QFT_OTHER,
2536
                "List of opcode execution start timestamps (after acquiring"
2537
                " locks)"),
2538
     None, 0, _PerJobOp(operator.attrgetter("exec_timestamp"))),
2539
    (_MakeField("opend", "OpCode_end", QFT_OTHER,
2540
                "List of opcode execution end timestamps"),
2541
     None, 0, _PerJobOp(operator.attrgetter("end_timestamp"))),
2542
    (_MakeField("oppriority", "OpCode_prio", QFT_OTHER,
2543
                "List of opcode priorities"),
2544
     None, 0, _PerJobOp(operator.attrgetter("priority"))),
2545
    (_MakeField("summary", "Summary", QFT_OTHER,
2546
                "List of per-opcode summaries"),
2547
     None, 0, _PerJobOp(lambda op: op.input.Summary())),
2548
    ]
2549

    
2550
  # Timestamp fields
2551
  for (name, attr, title, desc) in [
2552
    ("received_ts", "received_timestamp", "Received",
2553
     "Timestamp of when job was received"),
2554
    ("start_ts", "start_timestamp", "Start", "Timestamp of job start"),
2555
    ("end_ts", "end_timestamp", "End", "Timestamp of job end"),
2556
    ]:
2557
    getter = operator.attrgetter(attr)
2558
    fields.extend([
2559
      (_MakeField(name, title, QFT_OTHER,
2560
                  "%s (tuple containing seconds and microseconds)" % desc),
2561
       None, QFF_SPLIT_TIMESTAMP, _JobTimestamp(getter)),
2562
      ])
2563

    
2564
  return _PrepareFieldList(fields, [])
2565

    
2566

    
2567
def _GetExportName(_, (node_name, expname)): # pylint: disable=W0613
2568
  """Returns an export name if available.
2569

2570
  """
2571
  if expname is None:
2572
    return _FS_NODATA
2573
  else:
2574
    return expname
2575

    
2576

    
2577
def _BuildExportFields():
2578
  """Builds list of fields for exports.
2579

2580
  """
2581
  fields = [
2582
    (_MakeField("node", "Node", QFT_TEXT, "Node name"),
2583
     None, QFF_HOSTNAME, lambda _, (node_name, expname): node_name),
2584
    (_MakeField("export", "Export", QFT_TEXT, "Export name"),
2585
     None, 0, _GetExportName),
2586
    ]
2587

    
2588
  return _PrepareFieldList(fields, [])
2589

    
2590

    
2591
_CLUSTER_VERSION_FIELDS = {
2592
  "software_version": ("SoftwareVersion", QFT_TEXT, constants.RELEASE_VERSION,
2593
                       "Software version"),
2594
  "protocol_version": ("ProtocolVersion", QFT_NUMBER,
2595
                       constants.PROTOCOL_VERSION,
2596
                       "RPC protocol version"),
2597
  "config_version": ("ConfigVersion", QFT_NUMBER, constants.CONFIG_VERSION,
2598
                     "Configuration format version"),
2599
  "os_api_version": ("OsApiVersion", QFT_NUMBER, max(constants.OS_API_VERSIONS),
2600
                     "API version for OS template scripts"),
2601
  "export_version": ("ExportVersion", QFT_NUMBER, constants.EXPORT_VERSION,
2602
                     "Import/export file format version"),
2603
  }
2604

    
2605

    
2606
_CLUSTER_SIMPLE_FIELDS = {
2607
  "cluster_name": ("Name", QFT_TEXT, QFF_HOSTNAME, "Cluster name"),
2608
  "volume_group_name": ("VgName", QFT_TEXT, 0, "LVM volume group name"),
2609
  }
2610

    
2611

    
2612
class ClusterQueryData:
2613
  def __init__(self, cluster, nodes, drain_flag, watcher_pause):
2614
    """Initializes this class.
2615

2616
    @type cluster: L{objects.Cluster}
2617
    @param cluster: Instance of cluster object
2618
    @type nodes: dict; node UUID as key
2619
    @param nodes: Node objects
2620
    @type drain_flag: bool
2621
    @param drain_flag: Whether job queue is drained
2622
    @type watcher_pause: number
2623
    @param watcher_pause: Until when watcher is paused (Unix timestamp)
2624

2625
    """
2626
    self._cluster = cluster
2627
    self.nodes = nodes
2628
    self.drain_flag = drain_flag
2629
    self.watcher_pause = watcher_pause
2630

    
2631
  def __iter__(self):
2632
    return iter([self._cluster])
2633

    
2634

    
2635
def _ClusterWatcherPause(ctx, _):
2636
  """Returns until when watcher is paused (if available).
2637

2638
  """
2639
  if ctx.watcher_pause is None:
2640
    return _FS_UNAVAIL
2641
  else:
2642
    return ctx.watcher_pause
2643

    
2644

    
2645
def _BuildClusterFields():
2646
  """Builds list of fields for cluster information.
2647

2648
  """
2649
  fields = [
2650
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), CQ_CONFIG, 0,
2651
     lambda ctx, cluster: list(cluster.GetTags())),
2652
    (_MakeField("architecture", "ArchInfo", QFT_OTHER,
2653
                "Architecture information"), None, 0,
2654
     lambda ctx, _: runtime.GetArchInfo()),
2655
    (_MakeField("drain_flag", "QueueDrained", QFT_BOOL,
2656
                "Flag whether job queue is drained"), CQ_QUEUE_DRAINED, 0,
2657
     lambda ctx, _: ctx.drain_flag),
2658
    (_MakeField("watcher_pause", "WatcherPause", QFT_TIMESTAMP,
2659
                "Until when watcher is paused"), CQ_WATCHER_PAUSE, 0,
2660
     _ClusterWatcherPause),
2661
    (_MakeField("master_node", "Master", QFT_TEXT, "Master node name"),
2662
     CQ_CONFIG, QFF_HOSTNAME,
2663
     lambda ctx, cluster: _GetNodeName(ctx, None, cluster.master_node)),
2664
    ]
2665

    
2666
  # Simple fields
2667
  fields.extend([
2668
    (_MakeField(name, title, kind, doc), CQ_CONFIG, flags, _GetItemAttr(name))
2669
    for (name, (title, kind, flags, doc)) in _CLUSTER_SIMPLE_FIELDS.items()
2670
    ],)
2671

    
2672
  # Version fields
2673
  fields.extend([
2674
    (_MakeField(name, title, kind, doc), None, 0, _StaticValue(value))
2675
    for (name, (title, kind, value, doc)) in _CLUSTER_VERSION_FIELDS.items()])
2676

    
2677
  # Add timestamps
2678
  fields.extend(_GetItemTimestampFields(CQ_CONFIG))
2679

    
2680
  return _PrepareFieldList(fields, [
2681
    ("name", "cluster_name")])
2682

    
2683

    
2684
class NetworkQueryData:
2685
  """Data container for network data queries.
2686

2687
  """
2688
  def __init__(self, networks, network_to_groups,
2689
               network_to_instances, stats):
2690
    """Initializes this class.
2691

2692
    @param networks: List of network objects
2693
    @type network_to_groups: dict; network UUID as key
2694
    @param network_to_groups: Per-network list of groups
2695
    @type network_to_instances: dict; network UUID as key
2696
    @param network_to_instances: Per-network list of instances
2697
    @type stats: dict; network UUID as key
2698
    @param stats: Per-network usage statistics
2699

2700
    """
2701
    self.networks = networks
2702
    self.network_to_groups = network_to_groups
2703
    self.network_to_instances = network_to_instances
2704
    self.stats = stats
2705

    
2706
  def __iter__(self):
2707
    """Iterate over all networks.
2708

2709
    """
2710
    for net in self.networks:
2711
      if self.stats:
2712
        self.curstats = self.stats.get(net.uuid, None)
2713
      else:
2714
        self.curstats = None
2715
      yield net
2716

    
2717

    
2718
_NETWORK_SIMPLE_FIELDS = {
2719
  "name": ("Network", QFT_TEXT, 0, "Name"),
2720
  "network": ("Subnet", QFT_TEXT, 0, "IPv4 subnet"),
2721
  "gateway": ("Gateway", QFT_OTHER, 0, "IPv4 gateway"),
2722
  "network6": ("IPv6Subnet", QFT_OTHER, 0, "IPv6 subnet"),
2723
  "gateway6": ("IPv6Gateway", QFT_OTHER, 0, "IPv6 gateway"),
2724
  "mac_prefix": ("MacPrefix", QFT_OTHER, 0, "MAC address prefix"),
2725
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Network"),
2726
  "uuid": ("UUID", QFT_TEXT, 0, "Network UUID"),
2727
  }
2728

    
2729

    
2730
_NETWORK_STATS_FIELDS = {
2731
  "free_count": ("FreeCount", QFT_NUMBER, 0, "Number of available addresses"),
2732
  "reserved_count":
2733
    ("ReservedCount", QFT_NUMBER, 0, "Number of reserved addresses"),
2734
  "map": ("Map", QFT_TEXT, 0, "Actual mapping"),
2735
  "external_reservations":
2736
    ("ExternalReservations", QFT_TEXT, 0, "External reservations"),
2737
  }
2738

    
2739

    
2740
def _GetNetworkStatsField(field, kind, ctx, _):
2741
  """Gets the value of a "stats" field from L{NetworkQueryData}.
2742

2743
  @param field: Field name
2744
  @param kind: Data kind, one of L{constants.QFT_ALL}
2745
  @type ctx: L{NetworkQueryData}
2746

2747
  """
2748
  return _GetStatsField(field, kind, ctx.curstats)
2749

    
2750

    
2751
def _BuildNetworkFields():
2752
  """Builds list of fields for network queries.
2753

2754
  """
2755
  fields = [
2756
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
2757
     lambda ctx, inst: list(inst.GetTags())),
2758
    ]
2759

    
2760
  # Add simple fields
2761
  fields.extend([
2762
    (_MakeField(name, title, kind, doc),
2763
     NETQ_CONFIG, 0, _GetItemMaybeAttr(name))
2764
     for (name, (title, kind, _, doc)) in _NETWORK_SIMPLE_FIELDS.items()])
2765

    
2766
  def _GetLength(getter):
2767
    return lambda ctx, network: len(getter(ctx)[network.uuid])
2768

    
2769
  def _GetSortedList(getter):
2770
    return lambda ctx, network: utils.NiceSort(getter(ctx)[network.uuid])
2771

    
2772
  network_to_groups = operator.attrgetter("network_to_groups")
2773
  network_to_instances = operator.attrgetter("network_to_instances")
2774

    
2775
  # Add fields for node groups
2776
  fields.extend([
2777
    (_MakeField("group_cnt", "NodeGroups", QFT_NUMBER, "Number of nodegroups"),
2778
     NETQ_GROUP, 0, _GetLength(network_to_groups)),
2779
    (_MakeField("group_list", "GroupList", QFT_OTHER,
2780
     "List of nodegroups (group name, NIC mode, NIC link)"),
2781
     NETQ_GROUP, 0, lambda ctx, network: network_to_groups(ctx)[network.uuid]),
2782
    ])
2783

    
2784
  # Add fields for instances
2785
  fields.extend([
2786
    (_MakeField("inst_cnt", "Instances", QFT_NUMBER, "Number of instances"),
2787
     NETQ_INST, 0, _GetLength(network_to_instances)),
2788
    (_MakeField("inst_list", "InstanceList", QFT_OTHER, "List of instances"),
2789
     NETQ_INST, 0, _GetSortedList(network_to_instances)),
2790
    ])
2791

    
2792
  # Add fields for usage statistics
2793
  fields.extend([
2794
    (_MakeField(name, title, kind, doc), NETQ_STATS, 0,
2795
    compat.partial(_GetNetworkStatsField, name, kind))
2796
    for (name, (title, kind, _, doc)) in _NETWORK_STATS_FIELDS.items()])
2797

    
2798
  return _PrepareFieldList(fields, [])
2799

    
2800
#: Fields for cluster information
2801
CLUSTER_FIELDS = _BuildClusterFields()
2802

    
2803
#: Fields available for node queries
2804
NODE_FIELDS = _BuildNodeFields()
2805

    
2806
#: Fields available for instance queries
2807
INSTANCE_FIELDS = _BuildInstanceFields()
2808

    
2809
#: Fields available for lock queries
2810
LOCK_FIELDS = _BuildLockFields()
2811

    
2812
#: Fields available for node group queries
2813
GROUP_FIELDS = _BuildGroupFields()
2814

    
2815
#: Fields available for operating system queries
2816
OS_FIELDS = _BuildOsFields()
2817

    
2818
#: Fields available for extstorage provider queries
2819
EXTSTORAGE_FIELDS = _BuildExtStorageFields()
2820

    
2821
#: Fields available for job queries
2822
JOB_FIELDS = _BuildJobFields()
2823

    
2824
#: Fields available for exports
2825
EXPORT_FIELDS = _BuildExportFields()
2826

    
2827
#: Fields available for network queries
2828
NETWORK_FIELDS = _BuildNetworkFields()
2829

    
2830
#: All available resources
2831
ALL_FIELDS = {
2832
  constants.QR_CLUSTER: CLUSTER_FIELDS,
2833
  constants.QR_INSTANCE: INSTANCE_FIELDS,
2834
  constants.QR_NODE: NODE_FIELDS,
2835
  constants.QR_LOCK: LOCK_FIELDS,
2836
  constants.QR_GROUP: GROUP_FIELDS,
2837
  constants.QR_OS: OS_FIELDS,
2838
  constants.QR_EXTSTORAGE: EXTSTORAGE_FIELDS,
2839
  constants.QR_JOB: JOB_FIELDS,
2840
  constants.QR_EXPORT: EXPORT_FIELDS,
2841
  constants.QR_NETWORK: NETWORK_FIELDS,
2842
  }
2843

    
2844
#: All available field lists
2845
ALL_FIELD_LISTS = ALL_FIELDS.values()