Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ a8740a25

History | View | Annotate | Download (76.6 kB)

1
#
2
#
3

    
4
# Copyright (C) 2010, 2011, 2012 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Module for query operations
23

24
How it works:
25

26
  - Add field definitions
27
    - See how L{NODE_FIELDS} is built
28
    - Each field gets:
29
      - Query field definition (L{objects.QueryFieldDefinition}, use
30
        L{_MakeField} for creating), containing:
31
          - Name, must be lowercase and match L{FIELD_NAME_RE}
32
          - Title for tables, must not contain whitespace and match
33
            L{TITLE_RE}
34
          - Value data type, e.g. L{constants.QFT_NUMBER}
35
          - Human-readable description, must not end with punctuation or
36
            contain newlines
37
      - Data request type, see e.g. C{NQ_*}
38
      - OR-ed flags, see C{QFF_*}
39
      - A retrieval function, see L{Query.__init__} for description
40
    - Pass list of fields through L{_PrepareFieldList} for preparation and
41
      checks
42
  - Instantiate L{Query} with prepared field list definition and selected fields
43
  - Call L{Query.RequestedData} to determine what data to collect/compute
44
  - Call L{Query.Query} or L{Query.OldStyleQuery} with collected data and use
45
    result
46
      - Data container must support iteration using C{__iter__}
47
      - Items are passed to retrieval functions and can have any format
48
  - Call L{Query.GetFields} to get list of definitions for selected fields
49

50
@attention: Retrieval functions must be idempotent. They can be called multiple
51
  times, in any order and any number of times.
52

53
"""
54

    
55
import logging
56
import operator
57
import re
58

    
59
from ganeti import constants
60
from ganeti import errors
61
from ganeti import utils
62
from ganeti import compat
63
from ganeti import objects
64
from ganeti import ht
65
from ganeti import runtime
66
from ganeti import qlang
67

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

    
73
(NETQ_CONFIG,
74
 NETQ_GROUP,
75
 NETQ_STATS,
76
 NETQ_INST) = range(300, 304)
77

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

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

    
88
(IQ_CONFIG,
89
 IQ_LIVE,
90
 IQ_DISKUSAGE,
91
 IQ_CONSOLE,
92
 IQ_NODES) = range(100, 105)
93

    
94
(LQ_MODE,
95
 LQ_OWNER,
96
 LQ_PENDING) = range(10, 13)
97

    
98
(GQ_CONFIG,
99
 GQ_NODE,
100
 GQ_INST,
101
 GQ_DISKPARAMS) = range(200, 204)
102

    
103
(CQ_CONFIG,
104
 CQ_QUEUE_DRAINED,
105
 CQ_WATCHER_PAUSE) = range(300, 303)
106

    
107
# Query field flags
108
QFF_HOSTNAME = 0x01
109
QFF_IP_ADDRESS = 0x02
110
# Next values: 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200
111
QFF_ALL = (QFF_HOSTNAME | QFF_IP_ADDRESS)
112

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

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

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

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

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

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

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

    
155

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

159
  """
160
  return _FS_UNKNOWN
161

    
162

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

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

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

173
  """
174
  result = []
175

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

    
183
    assert len(fdef) == 4
184

    
185
    result.append(fdef)
186

    
187
  return result
188

    
189

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

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

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

    
198

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

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

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

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

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

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

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

229
    """
230
    self._namefield = namefield
231

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

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

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

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

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

248
    @rtype: list
249

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

    
254
    return utils.UniqueSequence(self._names)
255

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

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

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

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

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

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

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

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

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

285
    """
286
    self._NeedAllNames()
287

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

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

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

    
301
    if self._allnames:
302
      return
303

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

    
313

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

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

    
320

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

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

    
327

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

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

    
334

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

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

    
341

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

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

    
351

    
352
class _FilterCompilerHelper:
353
  """Converts a query filter to a callable usable for filtering.
354

355
  """
356
  # String statement has no effect, pylint: disable=W0105
357

    
358
  #: How deep filters can be nested
359
  _LEVELS_MAX = 10
360

    
361
  # Unique identifiers for operator groups
362
  (_OPTYPE_LOGIC,
363
   _OPTYPE_UNARY,
364
   _OPTYPE_BINARY) = range(1, 4)
365

    
366
  """Functions for equality checks depending on field flags.
367

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

372
  Order matters. The first item with flags will be used. Flags are checked
373
  using binary AND.
374

375
  """
376
  _EQUALITY_CHECKS = [
377
    (QFF_HOSTNAME,
378
     lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
379
                                               case_sensitive=False),
380
     None),
381
    (None, operator.eq, None),
382
    ]
383

    
384
  """Known operators
385

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

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

395
  """
396
  _OPS = {
397
    # Logic operators
398
    qlang.OP_OR: (_OPTYPE_LOGIC, compat.any),
399
    qlang.OP_AND: (_OPTYPE_LOGIC, compat.all),
400

    
401
    # Unary operators
402
    qlang.OP_NOT: (_OPTYPE_UNARY, None),
403
    qlang.OP_TRUE: (_OPTYPE_UNARY, None),
404

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

    
430
  def __init__(self, fields):
431
    """Initializes this class.
432

433
    @param fields: Field definitions (return value of L{_PrepareFieldList})
434

435
    """
436
    self._fields = fields
437
    self._hints = None
438
    self._op_handler = None
439

    
440
  def __call__(self, hints, qfilter):
441
    """Converts a query filter into a callable function.
442

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

451
    """
452
    self._op_handler = {
453
      self._OPTYPE_LOGIC:
454
        (self._HandleLogicOp, getattr(hints, "NoteLogicOp", None)),
455
      self._OPTYPE_UNARY:
456
        (self._HandleUnaryOp, getattr(hints, "NoteUnaryOp", None)),
457
      self._OPTYPE_BINARY:
458
        (self._HandleBinaryOp, getattr(hints, "NoteBinaryOp", None)),
459
      }
460

    
461
    try:
462
      filter_fn = self._Compile(qfilter, 0)
463
    finally:
464
      self._op_handler = None
465

    
466
    return filter_fn
467

    
468
  def _Compile(self, qfilter, level):
469
    """Inner function for converting filters.
470

471
    Calls the correct handler functions for the top-level operator. This
472
    function is called recursively (e.g. for logic operators).
473

474
    """
475
    if not (isinstance(qfilter, (list, tuple)) and qfilter):
476
      raise errors.ParameterError("Invalid filter on level %s" % level)
477

    
478
    # Limit recursion
479
    if level >= self._LEVELS_MAX:
480
      raise errors.ParameterError("Only up to %s levels are allowed (filter"
481
                                  " nested too deep)" % self._LEVELS_MAX)
482

    
483
    # Create copy to be modified
484
    operands = qfilter[:]
485
    op = operands.pop(0)
486

    
487
    try:
488
      (kind, op_data) = self._OPS[op]
489
    except KeyError:
490
      raise errors.ParameterError("Unknown operator '%s'" % op)
491

    
492
    (handler, hints_cb) = self._op_handler[kind]
493

    
494
    return handler(hints_cb, level, op, op_data, operands)
495

    
496
  def _LookupField(self, name):
497
    """Returns a field definition by name.
498

499
    """
500
    try:
501
      return self._fields[name]
502
    except KeyError:
503
      raise errors.ParameterError("Unknown field '%s'" % name)
504

    
505
  def _HandleLogicOp(self, hints_fn, level, op, op_fn, operands):
506
    """Handles logic operators.
507

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

519
    """
520
    if hints_fn:
521
      hints_fn(op)
522

    
523
    return compat.partial(_WrapLogicOp, op_fn,
524
                          [self._Compile(op, level + 1) for op in operands])
525

    
526
  def _HandleUnaryOp(self, hints_fn, level, op, op_fn, operands):
527
    """Handles unary operators.
528

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

540
    """
541
    assert op_fn is None
542

    
543
    if hints_fn:
544
      hints_fn(op)
545

    
546
    if len(operands) != 1:
547
      raise errors.ParameterError("Unary operator '%s' expects exactly one"
548
                                  " operand" % op)
549

    
550
    if op == qlang.OP_TRUE:
551
      (_, _, _, retrieval_fn) = self._LookupField(operands[0])
552

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

    
561
    return compat.partial(_WrapUnaryOp, op_fn, arg)
562

    
563
  def _HandleBinaryOp(self, hints_fn, level, op, op_data, operands):
564
    """Handles binary operators.
565

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

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

    
584
    (fdef, datakind, field_flags, retrieval_fn) = self._LookupField(name)
585

    
586
    assert fdef.kind != QFT_UNKNOWN
587

    
588
    # TODO: Type conversions?
589

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

    
597
    if hints_fn:
598
      hints_fn(op, datakind, name, value)
599

    
600
    for (fn_flags, fn, valprepfn) in op_data:
601
      if fn_flags is None or fn_flags & field_flags:
602
        # Prepare value if necessary (e.g. compile regular expression)
603
        if valprepfn:
604
          value = valprepfn(value)
605

    
606
        return compat.partial(_WrapBinaryOp, fn, retrieval_fn, value)
607

    
608
    raise errors.ProgrammerError("Unable to find operator implementation"
609
                                 " (op '%s', flags %s)" % (op, field_flags))
610

    
611

    
612
def _CompileFilter(fields, hints, qfilter):
613
  """Converts a query filter into a callable function.
614

615
  See L{_FilterCompilerHelper} for details.
616

617
  @rtype: callable
618

619
  """
620
  return _FilterCompilerHelper(fields)(hints, qfilter)
621

    
622

    
623
class Query:
624
  def __init__(self, fieldlist, selected, qfilter=None, namefield=None):
625
    """Initializes this class.
626

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

634
    Users of this class can call L{RequestedData} before preparing the data
635
    container to determine what data is needed.
636

637
    @type fieldlist: dictionary
638
    @param fieldlist: Field definitions
639
    @type selected: list of strings
640
    @param selected: List of selected fields
641

642
    """
643
    assert namefield is None or namefield in fieldlist
644

    
645
    self._fields = _GetQueryFields(fieldlist, selected)
646

    
647
    self._filter_fn = None
648
    self._requested_names = None
649
    self._filter_datakinds = frozenset()
650

    
651
    if qfilter is not None:
652
      # Collect requested names if wanted
653
      if namefield:
654
        hints = _FilterHints(namefield)
655
      else:
656
        hints = None
657

    
658
      # Build filter function
659
      self._filter_fn = _CompileFilter(fieldlist, hints, qfilter)
660
      if hints:
661
        self._requested_names = hints.RequestedNames()
662
        self._filter_datakinds = hints.ReferencedData()
663

    
664
    if namefield is None:
665
      self._name_fn = None
666
    else:
667
      (_, _, _, self._name_fn) = fieldlist[namefield]
668

    
669
  def RequestedNames(self):
670
    """Returns all names referenced in the filter.
671

672
    If there is no filter or operators are preventing determining the exact
673
    names, C{None} is returned.
674

675
    """
676
    return self._requested_names
677

    
678
  def RequestedData(self):
679
    """Gets requested kinds of data.
680

681
    @rtype: frozenset
682

683
    """
684
    return (self._filter_datakinds |
685
            frozenset(datakind for (_, datakind, _, _) in self._fields
686
                      if datakind is not None))
687

    
688
  def GetFields(self):
689
    """Returns the list of fields for this query.
690

691
    Includes unknown fields.
692

693
    @rtype: List of L{objects.QueryFieldDefinition}
694

695
    """
696
    return GetAllFields(self._fields)
697

    
698
  def Query(self, ctx, sort_by_name=True):
699
    """Execute a query.
700

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

707
    """
708
    sort = (self._name_fn and sort_by_name)
709

    
710
    result = []
711

    
712
    for idx, item in enumerate(ctx):
713
      if not (self._filter_fn is None or self._filter_fn(ctx, item)):
714
        continue
715

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

    
718
      # Verify result
719
      if __debug__:
720
        _VerifyResultRow(self._fields, row)
721

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

    
730
    if not sort:
731
      return result
732

    
733
    # TODO: Would "heapq" be more efficient than sorting?
734

    
735
    # Sorting in-place instead of using "sorted()"
736
    result.sort()
737

    
738
    assert not result or (len(result[0]) == 3 and len(result[-1]) == 3)
739

    
740
    return map(operator.itemgetter(2), result)
741

    
742
  def OldStyleQuery(self, ctx, sort_by_name=True):
743
    """Query with "old" query result format.
744

745
    See L{Query.Query} for arguments.
746

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

    
755
    return [[value for (_, value) in row]
756
            for row in self.Query(ctx, sort_by_name=sort_by_name)]
757

    
758

    
759
def _ProcessResult(value):
760
  """Converts result values into externally-visible ones.
761

762
  """
763
  if value is _FS_UNKNOWN:
764
    return (RS_UNKNOWN, None)
765
  elif value is _FS_NODATA:
766
    return (RS_NODATA, None)
767
  elif value is _FS_UNAVAIL:
768
    return (RS_UNAVAIL, None)
769
  elif value is _FS_OFFLINE:
770
    return (RS_OFFLINE, None)
771
  else:
772
    return (RS_NORMAL, value)
773

    
774

    
775
def _VerifyResultRow(fields, row):
776
  """Verifies the contents of a query result row.
777

778
  @type fields: list
779
  @param fields: Field definitions for result
780
  @type row: list of tuples
781
  @param row: Row data
782

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

    
796

    
797
def _FieldDictKey((fdef, _, flags, fn)):
798
  """Generates key for field dictionary.
799

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

    
810
  return fdef.name
811

    
812

    
813
def _PrepareFieldList(fields, aliases):
814
  """Prepares field list for use by L{Query}.
815

816
  Converts the list to a dictionary and does some verification.
817

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

828
  """
829
  if __debug__:
830
    duplicates = utils.FindDuplicates(fdef.title.lower()
831
                                      for (fdef, _, _, _) in fields)
832
    assert not duplicates, "Duplicate title(s) found: %r" % duplicates
833

    
834
  result = utils.SequenceToDict(fields, key=_FieldDictKey)
835

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

    
844
  assert len(result) == len(fields) + len(aliases)
845
  assert compat.all(name == fdef.name
846
                    for (name, (fdef, _, _, _)) in result.items())
847

    
848
  return result
849

    
850

    
851
def GetQueryResponse(query, ctx, sort_by_name=True):
852
  """Prepares the response for a query.
853

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

860
  """
861
  return objects.QueryResponse(data=query.Query(ctx, sort_by_name=sort_by_name),
862
                               fields=query.GetFields()).ToDict()
863

    
864

    
865
def QueryFields(fielddefs, selected):
866
  """Returns list of available fields.
867

868
  @type fielddefs: dict
869
  @param fielddefs: Field definitions
870
  @type selected: list of strings
871
  @param selected: List of selected fields
872
  @return: List of L{objects.QueryFieldDefinition}
873

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

    
883
  return objects.QueryFieldsResponse(fields=fdefs).ToDict()
884

    
885

    
886
def _MakeField(name, title, kind, doc):
887
  """Wrapper for creating L{objects.QueryFieldDefinition} instances.
888

889
  @param name: Field name as a regular expression
890
  @param title: Human-readable title
891
  @param kind: Field type
892
  @param doc: Human-readable description
893

894
  """
895
  return objects.QueryFieldDefinition(name=name, title=title, kind=kind,
896
                                      doc=doc)
897

    
898

    
899
def _StaticValueInner(value, ctx, _): # pylint: disable=W0613
900
  """Returns a static value.
901

902
  """
903
  return value
904

    
905

    
906
def _StaticValue(value):
907
  """Prepares a function to return a static value.
908

909
  """
910
  return compat.partial(_StaticValueInner, value)
911

    
912

    
913
def _GetNodeRole(node, master_name):
914
  """Determine node role.
915

916
  @type node: L{objects.Node}
917
  @param node: Node object
918
  @type master_name: string
919
  @param master_name: Master node name
920

921
  """
922
  if node.name == master_name:
923
    return constants.NR_MASTER
924
  elif node.master_candidate:
925
    return constants.NR_MCANDIDATE
926
  elif node.drained:
927
    return constants.NR_DRAINED
928
  elif node.offline:
929
    return constants.NR_OFFLINE
930
  else:
931
    return constants.NR_REGULAR
932

    
933

    
934
def _GetItemAttr(attr):
935
  """Returns a field function to return an attribute of the item.
936

937
  @param attr: Attribute name
938

939
  """
940
  getter = operator.attrgetter(attr)
941
  return lambda _, item: getter(item)
942

    
943

    
944
def _GetNDParam(name):
945
  """Return a field function to return an ND parameter out of the context.
946

947
  """
948
  def _helper(ctx, _):
949
    if ctx.ndparams is None:
950
      return _FS_UNAVAIL
951
    else:
952
      return ctx.ndparams.get(name, None)
953
  return _helper
954

    
955

    
956
def _BuildNDFields(is_group):
957
  """Builds all the ndparam fields.
958

959
  @param is_group: whether this is called at group or node level
960

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

    
971

    
972
def _ConvWrapInner(convert, fn, ctx, item):
973
  """Wrapper for converting values.
974

975
  @param convert: Conversion function receiving value as single parameter
976
  @param fn: Retrieval function
977

978
  """
979
  value = fn(ctx, item)
980

    
981
  # Is the value an abnormal status?
982
  if compat.any(value is fs for fs in _FS_ALL):
983
    # Return right away
984
    return value
985

    
986
  # TODO: Should conversion function also receive context, item or both?
987
  return convert(value)
988

    
989

    
990
def _ConvWrap(convert, fn):
991
  """Convenience wrapper for L{_ConvWrapInner}.
992

993
  @param convert: Conversion function receiving value as single parameter
994
  @param fn: Retrieval function
995

996
  """
997
  return compat.partial(_ConvWrapInner, convert, fn)
998

    
999

    
1000
def _GetItemTimestamp(getter):
1001
  """Returns function for getting timestamp of item.
1002

1003
  @type getter: callable
1004
  @param getter: Function to retrieve timestamp attribute
1005

1006
  """
1007
  def fn(_, item):
1008
    """Returns a timestamp of item.
1009

1010
    """
1011
    timestamp = getter(item)
1012
    if timestamp is None:
1013
      # Old configs might not have all timestamps
1014
      return _FS_UNAVAIL
1015
    else:
1016
      return timestamp
1017

    
1018
  return fn
1019

    
1020

    
1021
def _GetItemTimestampFields(datatype):
1022
  """Returns common timestamp fields.
1023

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

1026
  """
1027
  return [
1028
    (_MakeField("ctime", "CTime", QFT_TIMESTAMP, "Creation timestamp"),
1029
     datatype, 0, _GetItemTimestamp(operator.attrgetter("ctime"))),
1030
    (_MakeField("mtime", "MTime", QFT_TIMESTAMP, "Modification timestamp"),
1031
     datatype, 0, _GetItemTimestamp(operator.attrgetter("mtime"))),
1032
    ]
1033

    
1034

    
1035
class NodeQueryData:
1036
  """Data container for node data queries.
1037

1038
  """
1039
  def __init__(self, nodes, live_data, master_name, node_to_primary,
1040
               node_to_secondary, groups, oob_support, cluster):
1041
    """Initializes this class.
1042

1043
    """
1044
    self.nodes = nodes
1045
    self.live_data = live_data
1046
    self.master_name = master_name
1047
    self.node_to_primary = node_to_primary
1048
    self.node_to_secondary = node_to_secondary
1049
    self.groups = groups
1050
    self.oob_support = oob_support
1051
    self.cluster = cluster
1052

    
1053
    # Used for individual rows
1054
    self.curlive_data = None
1055
    self.ndparams = None
1056

    
1057
  def __iter__(self):
1058
    """Iterate over all nodes.
1059

1060
    This function has side-effects and only one instance of the resulting
1061
    generator should be used at a time.
1062

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

    
1076

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

    
1091

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

    
1116

    
1117
def _GetGroup(cb):
1118
  """Build function for calling another function with an node group.
1119

1120
  @param cb: The callback to be called with the nodegroup
1121

1122
  """
1123
  def fn(ctx, node):
1124
    """Get group data for a node.
1125

1126
    @type ctx: L{NodeQueryData}
1127
    @type inst: L{objects.Node}
1128
    @param inst: Node object
1129

1130
    """
1131
    ng = ctx.groups.get(node.group, None)
1132
    if ng is None:
1133
      # Nodes always have a group, or the configuration is corrupt
1134
      return _FS_UNAVAIL
1135

    
1136
    return cb(ctx, node, ng)
1137

    
1138
  return fn
1139

    
1140

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

1144
  @type ctx: L{NodeQueryData}
1145
  @type node: L{objects.Node}
1146
  @param node: Node object
1147
  @type ng: L{objects.NodeGroup}
1148
  @param ng: The node group this node belongs to
1149

1150
  """
1151
  return ng.name
1152

    
1153

    
1154
def _GetNodePower(ctx, node):
1155
  """Returns the node powered state
1156

1157
  @type ctx: L{NodeQueryData}
1158
  @type node: L{objects.Node}
1159
  @param node: Node object
1160

1161
  """
1162
  if ctx.oob_support[node.name]:
1163
    return node.powered
1164

    
1165
  return _FS_UNAVAIL
1166

    
1167

    
1168
def _GetNdParams(ctx, node, ng):
1169
  """Returns the ndparams for this node.
1170

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

1177
  """
1178
  return ctx.cluster.SimpleFillND(ng.FillND(node))
1179

    
1180

    
1181
def _GetLiveNodeField(field, kind, ctx, node):
1182
  """Gets the value of a "live" field from L{NodeQueryData}.
1183

1184
  @param field: Live field name
1185
  @param kind: Data kind, one of L{constants.QFT_ALL}
1186
  @type ctx: L{NodeQueryData}
1187
  @type node: L{objects.Node}
1188
  @param node: Node object
1189

1190
  """
1191
  if node.offline:
1192
    return _FS_OFFLINE
1193

    
1194
  if not node.vm_capable:
1195
    return _FS_UNAVAIL
1196

    
1197
  if not ctx.curlive_data:
1198
    return _FS_NODATA
1199

    
1200
  try:
1201
    value = ctx.curlive_data[field]
1202
  except KeyError:
1203
    return _FS_UNAVAIL
1204

    
1205
  if kind == QFT_TEXT:
1206
    return value
1207

    
1208
  assert kind in (QFT_NUMBER, QFT_UNIT)
1209

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

    
1218

    
1219
def _GetNodeHvState(_, node):
1220
  """Converts node's hypervisor state for query result.
1221

1222
  """
1223
  hv_state = node.hv_state
1224

    
1225
  if hv_state is None:
1226
    return _FS_UNAVAIL
1227

    
1228
  return dict((name, value.ToDict()) for (name, value) in hv_state.items())
1229

    
1230

    
1231
def _GetNodeDiskState(_, node):
1232
  """Converts node's disk state for query result.
1233

1234
  """
1235
  disk_state = node.disk_state
1236

    
1237
  if disk_state is None:
1238
    return _FS_UNAVAIL
1239

    
1240
  return dict((disk_kind, dict((name, value.ToDict())
1241
                               for (name, value) in kind_state.items()))
1242
              for (disk_kind, kind_state) in disk_state.items())
1243

    
1244

    
1245
def _BuildNodeFields():
1246
  """Builds list of fields for node queries.
1247

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

    
1277
  fields.extend(_BuildNDFields(False))
1278

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

    
1290
  def _GetLength(getter):
1291
    return lambda ctx, node: len(getter(ctx)[node.name])
1292

    
1293
  def _GetList(getter):
1294
    return lambda ctx, node: list(getter(ctx)[node.name])
1295

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

    
1311
  # Add simple fields
1312
  fields.extend([
1313
    (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1314
    for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()
1315
    ])
1316

    
1317
  # Add fields requiring live data
1318
  fields.extend([
1319
    (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1320
     compat.partial(_GetLiveNodeField, nfield, kind))
1321
    for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()
1322
    ])
1323

    
1324
  # Add timestamps
1325
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1326

    
1327
  return _PrepareFieldList(fields, [])
1328

    
1329

    
1330
class InstanceQueryData:
1331
  """Data container for instance data queries.
1332

1333
  """
1334
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
1335
               live_data, wrongnode_inst, console, nodes, groups):
1336
    """Initializes this class.
1337

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

1355
    """
1356
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
1357
           "Offline nodes not included in bad nodes"
1358
    assert not (set(live_data.keys()) & set(bad_nodes)), \
1359
           "Found live data for bad or offline nodes"
1360

    
1361
    self.instances = instances
1362
    self.cluster = cluster
1363
    self.disk_usage = disk_usage
1364
    self.offline_nodes = offline_nodes
1365
    self.bad_nodes = bad_nodes
1366
    self.live_data = live_data
1367
    self.wrongnode_inst = wrongnode_inst
1368
    self.console = console
1369
    self.nodes = nodes
1370
    self.groups = groups
1371

    
1372
    # Used for individual rows
1373
    self.inst_hvparams = None
1374
    self.inst_beparams = None
1375
    self.inst_osparams = None
1376
    self.inst_nicparams = None
1377

    
1378
  def __iter__(self):
1379
    """Iterate over all instances.
1380

1381
    This function has side-effects and only one instance of the resulting
1382
    generator should be used at a time.
1383

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

    
1392
      yield inst
1393

    
1394

    
1395
def _GetInstOperState(ctx, inst):
1396
  """Get instance's operational status.
1397

1398
  @type ctx: L{InstanceQueryData}
1399
  @type inst: L{objects.Instance}
1400
  @param inst: Instance object
1401

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

    
1410

    
1411
def _GetInstLiveData(name):
1412
  """Build function for retrieving live data.
1413

1414
  @type name: string
1415
  @param name: Live data field name
1416

1417
  """
1418
  def fn(ctx, inst):
1419
    """Get live data for an instance.
1420

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

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

    
1432
    if inst.name in ctx.live_data:
1433
      data = ctx.live_data[inst.name]
1434
      if name in data:
1435
        return data[name]
1436

    
1437
    return _FS_UNAVAIL
1438

    
1439
  return fn
1440

    
1441

    
1442
def _GetInstStatus(ctx, inst):
1443
  """Get instance status.
1444

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

1449
  """
1450
  if inst.primary_node in ctx.offline_nodes:
1451
    return constants.INSTST_NODEOFFLINE
1452

    
1453
  if inst.primary_node in ctx.bad_nodes:
1454
    return constants.INSTST_NODEDOWN
1455

    
1456
  if bool(ctx.live_data.get(inst.name)):
1457
    if inst.name in ctx.wrongnode_inst:
1458
      return constants.INSTST_WRONGNODE
1459
    elif inst.admin_state == constants.ADMINST_UP:
1460
      return constants.INSTST_RUNNING
1461
    else:
1462
      return constants.INSTST_ERRORUP
1463

    
1464
  if inst.admin_state == constants.ADMINST_UP:
1465
    return constants.INSTST_ERRORDOWN
1466
  elif inst.admin_state == constants.ADMINST_DOWN:
1467
    return constants.INSTST_ADMINDOWN
1468

    
1469
  return constants.INSTST_ADMINOFFLINE
1470

    
1471

    
1472
def _GetInstDiskSize(index):
1473
  """Build function for retrieving disk size.
1474

1475
  @type index: int
1476
  @param index: Disk index
1477

1478
  """
1479
  def fn(_, inst):
1480
    """Get size of a disk.
1481

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

1485
    """
1486
    try:
1487
      return inst.disks[index].size
1488
    except IndexError:
1489
      return _FS_UNAVAIL
1490

    
1491
  return fn
1492

    
1493

    
1494
def _GetInstNic(index, cb):
1495
  """Build function for calling another function with an instance NIC.
1496

1497
  @type index: int
1498
  @param index: NIC index
1499
  @type cb: callable
1500
  @param cb: Callback
1501

1502
  """
1503
  def fn(ctx, inst):
1504
    """Call helper function with instance NIC.
1505

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

1510
    """
1511
    try:
1512
      nic = inst.nics[index]
1513
    except IndexError:
1514
      return _FS_UNAVAIL
1515

    
1516
    return cb(ctx, index, nic)
1517

    
1518
  return fn
1519

    
1520

    
1521
def _GetInstNicNetwork(ctx, _, nic): # pylint: disable=W0613
1522
  """Get a NIC's Network.
1523

1524
  @type ctx: L{InstanceQueryData}
1525
  @type nic: L{objects.NIC}
1526
  @param nic: NIC object
1527

1528
  """
1529
  if nic.network is None:
1530
    return _FS_UNAVAIL
1531
  else:
1532
    return nic.network
1533

    
1534

    
1535
def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613
1536
  """Get a NIC's IP address.
1537

1538
  @type ctx: L{InstanceQueryData}
1539
  @type nic: L{objects.NIC}
1540
  @param nic: NIC object
1541

1542
  """
1543
  if nic.ip is None:
1544
    return _FS_UNAVAIL
1545
  else:
1546
    return nic.ip
1547

    
1548

    
1549
def _GetInstNicBridge(ctx, index, _):
1550
  """Get a NIC's bridge.
1551

1552
  @type ctx: L{InstanceQueryData}
1553
  @type index: int
1554
  @param index: NIC index
1555

1556
  """
1557
  assert len(ctx.inst_nicparams) >= index
1558

    
1559
  nicparams = ctx.inst_nicparams[index]
1560

    
1561
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1562
    return nicparams[constants.NIC_LINK]
1563
  else:
1564
    return _FS_UNAVAIL
1565

    
1566

    
1567
def _GetInstAllNicBridges(ctx, inst):
1568
  """Get all network bridges for an instance.
1569

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

1574
  """
1575
  assert len(ctx.inst_nicparams) == len(inst.nics)
1576

    
1577
  result = []
1578

    
1579
  for nicp in ctx.inst_nicparams:
1580
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1581
      result.append(nicp[constants.NIC_LINK])
1582
    else:
1583
      result.append(None)
1584

    
1585
  assert len(result) == len(inst.nics)
1586

    
1587
  return result
1588

    
1589

    
1590
def _GetInstNicParam(name):
1591
  """Build function for retrieving a NIC parameter.
1592

1593
  @type name: string
1594
  @param name: Parameter name
1595

1596
  """
1597
  def fn(ctx, index, _):
1598
    """Get a NIC's bridge.
1599

1600
    @type ctx: L{InstanceQueryData}
1601
    @type inst: L{objects.Instance}
1602
    @param inst: Instance object
1603
    @type nic: L{objects.NIC}
1604
    @param nic: NIC object
1605

1606
    """
1607
    assert len(ctx.inst_nicparams) >= index
1608
    return ctx.inst_nicparams[index][name]
1609

    
1610
  return fn
1611

    
1612

    
1613
def _GetInstanceNetworkFields():
1614
  """Get instance fields involving network interfaces.
1615

1616
  @return: Tuple containing list of field definitions used as input for
1617
    L{_PrepareFieldList} and a list of aliases
1618

1619
  """
1620
  nic_mac_fn = lambda ctx, _, nic: nic.mac
1621
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1622
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1623

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

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

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

    
1685
  return (fields, aliases)
1686

    
1687

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

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

1695
  """
1696
  usage = ctx.disk_usage[inst.name]
1697

    
1698
  if usage is None:
1699
    usage = 0
1700

    
1701
  return usage
1702

    
1703

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

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

1711
  """
1712
  consinfo = ctx.console[inst.name]
1713

    
1714
  if consinfo is None:
1715
    return _FS_UNAVAIL
1716

    
1717
  return consinfo
1718

    
1719

    
1720
def _GetInstanceDiskFields():
1721
  """Get instance fields involving disks.
1722

1723
  @return: List of field definitions used as input for L{_PrepareFieldList}
1724

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

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

    
1746
  return fields
1747

    
1748

    
1749
def _GetInstanceParameterFields():
1750
  """Get instance fields involving parameters.
1751

1752
  @return: List of field definitions used as input for L{_PrepareFieldList}
1753

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

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

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

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

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

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

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

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

    
1825
  return fields
1826

    
1827

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

    
1840

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

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

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

    
1857

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

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

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

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

    
1877
  return group.name
1878

    
1879

    
1880
def _BuildInstanceFields():
1881
  """Builds list of fields for instance queries.
1882

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

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

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

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

    
1960
  (network_fields, network_aliases) = _GetInstanceNetworkFields()
1961

    
1962
  fields.extend(network_fields)
1963
  fields.extend(_GetInstanceParameterFields())
1964
  fields.extend(_GetInstanceDiskFields())
1965
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1966

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

    
1974
  return _PrepareFieldList(fields, aliases)
1975

    
1976

    
1977
class LockQueryData:
1978
  """Data container for lock data queries.
1979

1980
  """
1981
  def __init__(self, lockdata):
1982
    """Initializes this class.
1983

1984
    """
1985
    self.lockdata = lockdata
1986

    
1987
  def __iter__(self):
1988
    """Iterate over all locks.
1989

1990
    """
1991
    return iter(self.lockdata)
1992

    
1993

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

1997
  """
1998
  (_, _, owners, _) = data
1999

    
2000
  if owners:
2001
    owners = utils.NiceSort(owners)
2002

    
2003
  return owners
2004

    
2005

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

2009
  """
2010
  (_, _, _, pending) = data
2011

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

    
2016
  return pending
2017

    
2018

    
2019
def _BuildLockFields():
2020
  """Builds list of fields for lock queries.
2021

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

    
2038

    
2039
class GroupQueryData:
2040
  """Data container for node group data queries.
2041

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

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

2056
    """
2057
    self.groups = groups
2058
    self.group_to_nodes = group_to_nodes
2059
    self.group_to_instances = group_to_instances
2060
    self.cluster = cluster
2061
    self.want_diskparams = want_diskparams
2062

    
2063
    # Used for individual rows
2064
    self.group_ipolicy = None
2065
    self.ndparams = None
2066
    self.group_dp = None
2067

    
2068
  def __iter__(self):
2069
    """Iterate over all node groups.
2070

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

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

    
2084

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

    
2092

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

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

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

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

    
2108
  group_to_nodes = operator.attrgetter("group_to_nodes")
2109
  group_to_instances = operator.attrgetter("group_to_instances")
2110

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

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

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

    
2153
  # ND parameters
2154
  fields.extend(_BuildNDFields(True))
2155

    
2156
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
2157

    
2158
  return _PrepareFieldList(fields, [])
2159

    
2160

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

    
2173

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

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

    
2205
  return _PrepareFieldList(fields, [])
2206

    
2207

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

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

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

    
2220

    
2221
def _JobUnavail(inner):
2222
  """Wrapper for L{_JobUnavailInner}.
2223

2224
  """
2225
  return compat.partial(_JobUnavailInner, inner)
2226

    
2227

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

2231
  """
2232
  return map(fn, job.ops)
2233

    
2234

    
2235
def _PerJobOp(fn):
2236
  """Wrapper for L{_PerJobOpInner}.
2237

2238
  """
2239
  return _JobUnavail(compat.partial(_PerJobOpInner, fn))
2240

    
2241

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

2245
  """
2246
  timestamp = fn(job)
2247

    
2248
  if timestamp is None:
2249
    return _FS_UNAVAIL
2250
  else:
2251
    return timestamp
2252

    
2253

    
2254
def _JobTimestamp(fn):
2255
  """Wrapper for L{_JobTimestampInner}.
2256

2257
  """
2258
  return _JobUnavail(compat.partial(_JobTimestampInner, fn))
2259

    
2260

    
2261
def _BuildJobFields():
2262
  """Builds list of fields for job queries.
2263

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

    
2312
  return _PrepareFieldList(fields, [])
2313

    
2314

    
2315
def _GetExportName(_, (node_name, expname)): # pylint: disable=W0613
2316
  """Returns an export name if available.
2317

2318
  """
2319
  if expname is None:
2320
    return _FS_UNAVAIL
2321
  else:
2322
    return expname
2323

    
2324

    
2325
def _BuildExportFields():
2326
  """Builds list of fields for exports.
2327

2328
  """
2329
  fields = [
2330
    (_MakeField("node", "Node", QFT_TEXT, "Node name"),
2331
     None, QFF_HOSTNAME, lambda _, (node_name, expname): node_name),
2332
    (_MakeField("export", "Export", QFT_TEXT, "Export name"),
2333
     None, 0, _GetExportName),
2334
    ]
2335

    
2336
  return _PrepareFieldList(fields, [])
2337

    
2338

    
2339
_CLUSTER_VERSION_FIELDS = {
2340
  "software_version": ("SoftwareVersion", QFT_TEXT, constants.RELEASE_VERSION,
2341
                       "Software version"),
2342
  "protocol_version": ("ProtocolVersion", QFT_NUMBER,
2343
                       constants.PROTOCOL_VERSION,
2344
                       "RPC protocol version"),
2345
  "config_version": ("ConfigVersion", QFT_NUMBER, constants.CONFIG_VERSION,
2346
                     "Configuration format version"),
2347
  "os_api_version": ("OsApiVersion", QFT_NUMBER, max(constants.OS_API_VERSIONS),
2348
                     "API version for OS template scripts"),
2349
  "export_version": ("ExportVersion", QFT_NUMBER, constants.EXPORT_VERSION,
2350
                     "Import/export file format version"),
2351
  }
2352

    
2353

    
2354
_CLUSTER_SIMPLE_FIELDS = {
2355
  "cluster_name": ("Name", QFT_TEXT, QFF_HOSTNAME, "Cluster name"),
2356
  "master_node": ("Master", QFT_TEXT, QFF_HOSTNAME, "Master node name"),
2357
  "volume_group_name": ("VgName", QFT_TEXT, 0, "LVM volume group name"),
2358
  }
2359

    
2360

    
2361
class ClusterQueryData:
2362
  def __init__(self, cluster, drain_flag, watcher_pause):
2363
    """Initializes this class.
2364

2365
    @type cluster: L{objects.Cluster}
2366
    @param cluster: Instance of cluster object
2367
    @type drain_flag: bool
2368
    @param drain_flag: Whether job queue is drained
2369
    @type watcher_pause: number
2370
    @param watcher_pause: Until when watcher is paused (Unix timestamp)
2371

2372
    """
2373
    self._cluster = cluster
2374
    self.drain_flag = drain_flag
2375
    self.watcher_pause = watcher_pause
2376

    
2377
  def __iter__(self):
2378
    return iter([self._cluster])
2379

    
2380

    
2381
def _ClusterWatcherPause(ctx, _):
2382
  """Returns until when watcher is paused (if available).
2383

2384
  """
2385
  if ctx.watcher_pause is None:
2386
    return _FS_UNAVAIL
2387
  else:
2388
    return ctx.watcher_pause
2389

    
2390

    
2391
def _BuildClusterFields():
2392
  """Builds list of fields for cluster information.
2393

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

    
2409
  # Simple fields
2410
  fields.extend([
2411
    (_MakeField(name, title, kind, doc), CQ_CONFIG, flags, _GetItemAttr(name))
2412
    for (name, (title, kind, flags, doc)) in _CLUSTER_SIMPLE_FIELDS.items()
2413
    ])
2414

    
2415
  # Version fields
2416
  fields.extend([
2417
    (_MakeField(name, title, kind, doc), None, 0, _StaticValue(value))
2418
    for (name, (title, kind, value, doc)) in _CLUSTER_VERSION_FIELDS.items()
2419
    ])
2420

    
2421
  # Add timestamps
2422
  fields.extend(_GetItemTimestampFields(CQ_CONFIG))
2423

    
2424
  return _PrepareFieldList(fields, [
2425
    ("name", "cluster_name"),
2426
    ])
2427

    
2428

    
2429
class NetworkQueryData:
2430
  """Data container for network data queries.
2431

2432
  """
2433
  def __init__(self, networks, network_to_groups,
2434
               network_to_instances, stats):
2435
    """Initializes this class.
2436

2437
    @param networks: List of network objects
2438
    @type network_to_groups: dict; network UUID as key
2439
    @param network_to_groups: Per-network list of groups
2440
    @type network_to_instances: dict; network UUID as key
2441
    @param network_to_instances: Per-network list of instances
2442
    @type stats: dict; network UUID as key
2443
    @param stats: Per-network usage statistics
2444

2445
    """
2446
    self.networks = networks
2447
    self.network_to_groups = network_to_groups
2448
    self.network_to_instances = network_to_instances
2449
    self.stats = stats
2450

    
2451
  def __iter__(self):
2452
    """Iterate over all networks.
2453

2454
    """
2455
    for net in self.networks:
2456
      if self.stats:
2457
        self.curstats = self.stats.get(net.uuid, None)
2458
      else:
2459
        self.curstats = None
2460
      yield net
2461

    
2462

    
2463
_NETWORK_SIMPLE_FIELDS = {
2464
  "name": ("Network", QFT_TEXT, 0, "The network"),
2465
  "network": ("Subnet", QFT_TEXT, 0, "The subnet"),
2466
  "gateway": ("Gateway", QFT_OTHER, 0, "The gateway"),
2467
  "network6": ("IPv6Subnet", QFT_OTHER, 0, "The ipv6 subnet"),
2468
  "gateway6": ("IPv6Gateway", QFT_OTHER, 0, "The ipv6 gateway"),
2469
  "mac_prefix": ("MacPrefix", QFT_OTHER, 0, "The mac prefix"),
2470
  "network_type": ("NetworkType", QFT_OTHER, 0, "The network type"),
2471
  }
2472

    
2473

    
2474
_NETWORK_STATS_FIELDS = {
2475
  "free_count": ("FreeCount", QFT_NUMBER, 0, "How many addresses are free"),
2476
  "reserved_count": ("ReservedCount", QFT_NUMBER, 0, "How many addresses are reserved"),
2477
  "map": ("Map", QFT_TEXT, 0, "The actual mapping"),
2478
  "external_reservations": ("ExternalReservations", QFT_TEXT, 0, "The external reservations"),
2479
  }
2480

    
2481

    
2482
def _GetNetworkStatsField(field, kind, ctx, net):
2483
  """Gets the value of a "stats" field from L{NetworkQueryData}.
2484

2485
  @param field: Field name
2486
  @param kind: Data kind, one of L{constants.QFT_ALL}
2487
  @type ctx: L{NetworkQueryData}
2488

2489
  """
2490

    
2491
  try:
2492
    value = ctx.curstats[field]
2493
  except KeyError:
2494
    return _FS_UNAVAIL
2495

    
2496
  if kind == QFT_TEXT:
2497
    return value
2498

    
2499
  assert kind in (QFT_NUMBER, QFT_UNIT)
2500

    
2501
  # Try to convert into number
2502
  try:
2503
    return int(value)
2504
  except (ValueError, TypeError):
2505
    logging.exception("Failed to convert network field '%s' (value %r) to int",
2506
                      value, field)
2507
    return _FS_UNAVAIL
2508

    
2509

    
2510
def _BuildNetworkFields():
2511
  """Builds list of fields for network queries.
2512

2513
  """
2514
  fields = [
2515
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
2516
     lambda ctx, inst: list(inst.GetTags())),
2517
    ]
2518

    
2519
  # Add simple fields
2520
  fields.extend([
2521
    (_MakeField(name, title, kind, doc),
2522
     NETQ_CONFIG, 0, _GetItemAttr(name))
2523
     for (name, (title, kind, flags, doc)) in _NETWORK_SIMPLE_FIELDS.items()
2524
    ])
2525

    
2526
  def _GetLength(getter):
2527
    return lambda ctx, network: len(getter(ctx)[network.uuid])
2528

    
2529
  def _GetSortedList(getter):
2530
    return lambda ctx, network: utils.NiceSort(getter(ctx)[network.uuid])
2531

    
2532
  network_to_groups = operator.attrgetter("network_to_groups")
2533
  network_to_instances = operator.attrgetter("network_to_instances")
2534

    
2535
  # Add fields for node groups
2536
  fields.extend([
2537
    (_MakeField("group_cnt", "NodeGroups", QFT_NUMBER, "Number of nodegroups"),
2538
     NETQ_GROUP, 0, _GetLength(network_to_groups)),
2539
        (_MakeField("group_list", "GroupList", QFT_OTHER, "List of nodegroups"),
2540
     NETQ_GROUP, 0, _GetSortedList(network_to_groups)),
2541
    ])
2542

    
2543
  # Add fields for instances
2544
  fields.extend([
2545
    (_MakeField("inst_cnt", "Instances", QFT_NUMBER, "Number of instances"),
2546
     NETQ_INST, 0, _GetLength(network_to_instances)),
2547
    (_MakeField("inst_list", "InstanceList", QFT_OTHER, "List of instances"),
2548
     NETQ_INST, 0, _GetSortedList(network_to_instances)),
2549
    ])
2550

    
2551
  # Add fields for usage statistics
2552
  fields.extend([
2553
    (_MakeField(name, title, kind, doc), NETQ_STATS, 0,
2554
    compat.partial(_GetNetworkStatsField, name, kind))
2555
    for (name, (title, kind, flags, doc)) in _NETWORK_STATS_FIELDS.items()
2556
    ])
2557

    
2558
  return _PrepareFieldList(fields, [])
2559

    
2560
#: Fields for cluster information
2561
CLUSTER_FIELDS = _BuildClusterFields()
2562

    
2563
#: Fields available for node queries
2564
NODE_FIELDS = _BuildNodeFields()
2565

    
2566
#: Fields available for instance queries
2567
INSTANCE_FIELDS = _BuildInstanceFields()
2568

    
2569
#: Fields available for lock queries
2570
LOCK_FIELDS = _BuildLockFields()
2571

    
2572
#: Fields available for node group queries
2573
GROUP_FIELDS = _BuildGroupFields()
2574

    
2575
#: Fields available for operating system queries
2576
OS_FIELDS = _BuildOsFields()
2577

    
2578
#: Fields available for job queries
2579
JOB_FIELDS = _BuildJobFields()
2580

    
2581
#: Fields available for exports
2582
EXPORT_FIELDS = _BuildExportFields()
2583

    
2584
#: Fields available for network queries
2585
NETWORK_FIELDS = _BuildNetworkFields()
2586

    
2587
#: All available resources
2588
ALL_FIELDS = {
2589
  constants.QR_CLUSTER: CLUSTER_FIELDS,
2590
  constants.QR_INSTANCE: INSTANCE_FIELDS,
2591
  constants.QR_NODE: NODE_FIELDS,
2592
  constants.QR_LOCK: LOCK_FIELDS,
2593
  constants.QR_GROUP: GROUP_FIELDS,
2594
  constants.QR_OS: OS_FIELDS,
2595
  constants.QR_JOB: JOB_FIELDS,
2596
  constants.QR_EXPORT: EXPORT_FIELDS,
2597
  constants.QR_NETWORK: NETWORK_FIELDS,
2598
  }
2599

    
2600
#: All available field lists
2601
ALL_FIELD_LISTS = ALL_FIELDS.values()