Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ 5cfa6c37

History | View | Annotate | Download (78.4 kB)

1
#
2
#
3

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

    
21

    
22
"""Module for query operations
23

24
How it works:
25

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

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

53
"""
54

    
55
import logging
56
import operator
57
import re
58

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
159

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

163
  """
164
  return _FS_UNKNOWN
165

    
166

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

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

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

177
  """
178
  result = []
179

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

    
187
    assert len(fdef) == 4
188

    
189
    result.append(fdef)
190

    
191
  return result
192

    
193

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

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

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

    
202

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

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

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

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

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

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

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

233
    """
234
    self._namefield = namefield
235

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

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

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

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

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

252
    @rtype: list
253

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

    
258
    return utils.UniqueSequence(self._names)
259

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

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

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

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

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

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

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

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

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

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

    
293
    self._NeedAllNames()
294

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

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

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

    
308
    if self._allnames:
309
      return
310

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

    
320

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

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

    
327

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

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

    
334

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

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

    
341

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

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

    
348

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

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

    
358

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

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

    
368

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

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

    
375

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

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

    
388

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

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

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

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

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

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

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

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

    
423
  """Known operators
424

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

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

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

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

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

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

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

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

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

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

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

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

    
497
    return filter_fn
498

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

571
    """
572
    assert op_fn is None
573

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

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

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

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

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

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

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

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

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

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

    
620
    assert fdef.kind != QFT_UNKNOWN
621

    
622
    # TODO: Type conversions?
623

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

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

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

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

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

    
645

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

649
  See L{_FilterCompilerHelper} for details.
650

651
  @rtype: callable
652

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

    
656

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

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

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

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

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

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

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

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

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

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

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

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

709
    """
710
    return self._requested_names
711

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

715
    @rtype: frozenset
716

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

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

725
    Includes unknown fields.
726

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

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

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

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

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

    
744
    result = []
745

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

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

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

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

    
765
    if not sort:
766
      return result
767

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

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

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

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

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

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

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

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

    
793

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

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

    
809

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

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

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

    
831

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

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

    
845
  return fdef.name
846

    
847

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

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

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

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

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

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

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

    
883
  return result
884

    
885

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

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

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

    
899

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

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

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

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

    
920

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

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

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

    
933

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

937
  """
938
  return value
939

    
940

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

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

    
947

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

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

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

    
968

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

972
  @param attr: Attribute name
973

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

    
978

    
979
def _GetNDParam(name):
980
  """Return a field function to return an ND parameter out of the context.
981

982
  """
983
  def _helper(ctx, _):
984
    if ctx.ndparams is None:
985
      return _FS_UNAVAIL
986
    else:
987
      return ctx.ndparams.get(name, None)
988
  return _helper
989

    
990

    
991
def _BuildNDFields(is_group):
992
  """Builds all the ndparam fields.
993

994
  @param is_group: whether this is called at group or node level
995

996
  """
997
  if is_group:
998
    field_kind = GQ_CONFIG
999
  else:
1000
    field_kind = NQ_GROUP
1001
  return [(_MakeField("ndp/%s" % name,
1002
                      constants.NDS_PARAMETER_TITLES.get(name,
1003
                                                         "ndp/%s" % name),
1004
                      _VTToQFT[kind], "The \"%s\" node parameter" % name),
1005
           field_kind, 0, _GetNDParam(name))
1006
          for name, kind in constants.NDS_PARAMETER_TYPES.items()]
1007

    
1008

    
1009
def _ConvWrapInner(convert, fn, ctx, item):
1010
  """Wrapper for converting values.
1011

1012
  @param convert: Conversion function receiving value as single parameter
1013
  @param fn: Retrieval function
1014

1015
  """
1016
  value = fn(ctx, item)
1017

    
1018
  # Is the value an abnormal status?
1019
  if compat.any(value is fs for fs in _FS_ALL):
1020
    # Return right away
1021
    return value
1022

    
1023
  # TODO: Should conversion function also receive context, item or both?
1024
  return convert(value)
1025

    
1026

    
1027
def _ConvWrap(convert, fn):
1028
  """Convenience wrapper for L{_ConvWrapInner}.
1029

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

1033
  """
1034
  return compat.partial(_ConvWrapInner, convert, fn)
1035

    
1036

    
1037
def _GetItemTimestamp(getter):
1038
  """Returns function for getting timestamp of item.
1039

1040
  @type getter: callable
1041
  @param getter: Function to retrieve timestamp attribute
1042

1043
  """
1044
  def fn(_, item):
1045
    """Returns a timestamp of item.
1046

1047
    """
1048
    timestamp = getter(item)
1049
    if timestamp is None:
1050
      # Old configs might not have all timestamps
1051
      return _FS_UNAVAIL
1052
    else:
1053
      return timestamp
1054

    
1055
  return fn
1056

    
1057

    
1058
def _GetItemTimestampFields(datatype):
1059
  """Returns common timestamp fields.
1060

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

1063
  """
1064
  return [
1065
    (_MakeField("ctime", "CTime", QFT_TIMESTAMP, "Creation timestamp"),
1066
     datatype, 0, _GetItemTimestamp(operator.attrgetter("ctime"))),
1067
    (_MakeField("mtime", "MTime", QFT_TIMESTAMP, "Modification timestamp"),
1068
     datatype, 0, _GetItemTimestamp(operator.attrgetter("mtime"))),
1069
    ]
1070

    
1071

    
1072
class NodeQueryData:
1073
  """Data container for node data queries.
1074

1075
  """
1076
  def __init__(self, nodes, live_data, master_name, node_to_primary,
1077
               node_to_secondary, groups, oob_support, cluster):
1078
    """Initializes this class.
1079

1080
    """
1081
    self.nodes = nodes
1082
    self.live_data = live_data
1083
    self.master_name = master_name
1084
    self.node_to_primary = node_to_primary
1085
    self.node_to_secondary = node_to_secondary
1086
    self.groups = groups
1087
    self.oob_support = oob_support
1088
    self.cluster = cluster
1089

    
1090
    # Used for individual rows
1091
    self.curlive_data = None
1092
    self.ndparams = None
1093

    
1094
  def __iter__(self):
1095
    """Iterate over all nodes.
1096

1097
    This function has side-effects and only one instance of the resulting
1098
    generator should be used at a time.
1099

1100
    """
1101
    for node in self.nodes:
1102
      group = self.groups.get(node.group, None)
1103
      if group is None:
1104
        self.ndparams = None
1105
      else:
1106
        self.ndparams = self.cluster.FillND(node, group)
1107
      if self.live_data:
1108
        self.curlive_data = self.live_data.get(node.name, None)
1109
      else:
1110
        self.curlive_data = None
1111
      yield node
1112

    
1113

    
1114
#: Fields that are direct attributes of an L{objects.Node} object
1115
_NODE_SIMPLE_FIELDS = {
1116
  "drained": ("Drained", QFT_BOOL, 0, "Whether node is drained"),
1117
  "master_candidate": ("MasterC", QFT_BOOL, 0,
1118
                       "Whether node is a master candidate"),
1119
  "master_capable": ("MasterCapable", QFT_BOOL, 0,
1120
                     "Whether node can become a master candidate"),
1121
  "name": ("Node", QFT_TEXT, QFF_HOSTNAME, "Node name"),
1122
  "offline": ("Offline", QFT_BOOL, 0, "Whether node is marked offline"),
1123
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Node"),
1124
  "uuid": ("UUID", QFT_TEXT, 0, "Node UUID"),
1125
  "vm_capable": ("VMCapable", QFT_BOOL, 0, "Whether node can host instances"),
1126
  }
1127

    
1128

    
1129
#: Fields requiring talking to the node
1130
# Note that none of these are available for non-vm_capable nodes
1131
_NODE_LIVE_FIELDS = {
1132
  "bootid": ("BootID", QFT_TEXT, "bootid",
1133
             "Random UUID renewed for each system reboot, can be used"
1134
             " for detecting reboots by tracking changes"),
1135
  "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes",
1136
             "Number of NUMA domains on node (if exported by hypervisor)"),
1137
  "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets",
1138
               "Number of physical CPU sockets (if exported by hypervisor)"),
1139
  "ctotal": ("CTotal", QFT_NUMBER, "cpu_total", "Number of logical processors"),
1140
  "dfree": ("DFree", QFT_UNIT, "vg_free",
1141
            "Available disk space in volume group"),
1142
  "dtotal": ("DTotal", QFT_UNIT, "vg_size",
1143
             "Total disk space in volume group used for instance disk"
1144
             " allocation"),
1145
  "mfree": ("MFree", QFT_UNIT, "memory_free",
1146
            "Memory available for instance allocations"),
1147
  "mnode": ("MNode", QFT_UNIT, "memory_dom0",
1148
            "Amount of memory used by node (dom0 for Xen)"),
1149
  "mtotal": ("MTotal", QFT_UNIT, "memory_total",
1150
             "Total amount of memory of physical machine"),
1151
  }
1152

    
1153

    
1154
def _GetGroup(cb):
1155
  """Build function for calling another function with an node group.
1156

1157
  @param cb: The callback to be called with the nodegroup
1158

1159
  """
1160
  def fn(ctx, node):
1161
    """Get group data for a node.
1162

1163
    @type ctx: L{NodeQueryData}
1164
    @type inst: L{objects.Node}
1165
    @param inst: Node object
1166

1167
    """
1168
    ng = ctx.groups.get(node.group, None)
1169
    if ng is None:
1170
      # Nodes always have a group, or the configuration is corrupt
1171
      return _FS_UNAVAIL
1172

    
1173
    return cb(ctx, node, ng)
1174

    
1175
  return fn
1176

    
1177

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

1181
  @type ctx: L{NodeQueryData}
1182
  @type node: L{objects.Node}
1183
  @param node: Node object
1184
  @type ng: L{objects.NodeGroup}
1185
  @param ng: The node group this node belongs to
1186

1187
  """
1188
  return ng.name
1189

    
1190

    
1191
def _GetNodePower(ctx, node):
1192
  """Returns the node powered state
1193

1194
  @type ctx: L{NodeQueryData}
1195
  @type node: L{objects.Node}
1196
  @param node: Node object
1197

1198
  """
1199
  if ctx.oob_support[node.name]:
1200
    return node.powered
1201

    
1202
  return _FS_UNAVAIL
1203

    
1204

    
1205
def _GetNdParams(ctx, node, ng):
1206
  """Returns the ndparams for this node.
1207

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

1214
  """
1215
  return ctx.cluster.SimpleFillND(ng.FillND(node))
1216

    
1217

    
1218
def _GetLiveNodeField(field, kind, ctx, node):
1219
  """Gets the value of a "live" field from L{NodeQueryData}.
1220

1221
  @param field: Live field name
1222
  @param kind: Data kind, one of L{constants.QFT_ALL}
1223
  @type ctx: L{NodeQueryData}
1224
  @type node: L{objects.Node}
1225
  @param node: Node object
1226

1227
  """
1228
  if node.offline:
1229
    return _FS_OFFLINE
1230

    
1231
  if not node.vm_capable:
1232
    return _FS_UNAVAIL
1233

    
1234
  if not ctx.curlive_data:
1235
    return _FS_NODATA
1236

    
1237
  return _GetStatsField(field, kind, ctx.curlive_data)
1238

    
1239

    
1240
def _GetStatsField(field, kind, data):
1241
  """Gets a value from live statistics.
1242

1243
  If the value is not found, L{_FS_UNAVAIL} is returned. If the field kind is
1244
  numeric a conversion to integer is attempted. If that fails, L{_FS_UNAVAIL}
1245
  is returned.
1246

1247
  @param field: Live field name
1248
  @param kind: Data kind, one of L{constants.QFT_ALL}
1249
  @type data: dict
1250
  @param data: Statistics
1251

1252
  """
1253
  try:
1254
    value = data[field]
1255
  except KeyError:
1256
    return _FS_UNAVAIL
1257

    
1258
  if kind == QFT_TEXT:
1259
    return value
1260

    
1261
  assert kind in (QFT_NUMBER, QFT_UNIT)
1262

    
1263
  # Try to convert into number
1264
  try:
1265
    return int(value)
1266
  except (ValueError, TypeError):
1267
    logging.exception("Failed to convert node field '%s' (value %r) to int",
1268
                      field, value)
1269
    return _FS_UNAVAIL
1270

    
1271

    
1272
def _GetNodeHvState(_, node):
1273
  """Converts node's hypervisor state for query result.
1274

1275
  """
1276
  hv_state = node.hv_state
1277

    
1278
  if hv_state is None:
1279
    return _FS_UNAVAIL
1280

    
1281
  return dict((name, value.ToDict()) for (name, value) in hv_state.items())
1282

    
1283

    
1284
def _GetNodeDiskState(_, node):
1285
  """Converts node's disk state for query result.
1286

1287
  """
1288
  disk_state = node.disk_state
1289

    
1290
  if disk_state is None:
1291
    return _FS_UNAVAIL
1292

    
1293
  return dict((disk_kind, dict((name, value.ToDict())
1294
                               for (name, value) in kind_state.items()))
1295
              for (disk_kind, kind_state) in disk_state.items())
1296

    
1297

    
1298
def _BuildNodeFields():
1299
  """Builds list of fields for node queries.
1300

1301
  """
1302
  fields = [
1303
    (_MakeField("pip", "PrimaryIP", QFT_TEXT, "Primary IP address"),
1304
     NQ_CONFIG, 0, _GetItemAttr("primary_ip")),
1305
    (_MakeField("sip", "SecondaryIP", QFT_TEXT, "Secondary IP address"),
1306
     NQ_CONFIG, 0, _GetItemAttr("secondary_ip")),
1307
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0,
1308
     lambda ctx, node: list(node.GetTags())),
1309
    (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"),
1310
     NQ_CONFIG, 0, lambda ctx, node: node.name == ctx.master_name),
1311
    (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0,
1312
     _GetGroup(_GetNodeGroup)),
1313
    (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"),
1314
     NQ_CONFIG, 0, _GetItemAttr("group")),
1315
    (_MakeField("powered", "Powered", QFT_BOOL,
1316
                "Whether node is thought to be powered on"),
1317
     NQ_OOB, 0, _GetNodePower),
1318
    (_MakeField("ndparams", "NodeParameters", QFT_OTHER,
1319
                "Merged node parameters"),
1320
     NQ_GROUP, 0, _GetGroup(_GetNdParams)),
1321
    (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER,
1322
                "Custom node parameters"),
1323
      NQ_GROUP, 0, _GetItemAttr("ndparams")),
1324
    (_MakeField("hv_state", "HypervisorState", QFT_OTHER, "Hypervisor state"),
1325
     NQ_CONFIG, 0, _GetNodeHvState),
1326
    (_MakeField("disk_state", "DiskState", QFT_OTHER, "Disk state"),
1327
     NQ_CONFIG, 0, _GetNodeDiskState),
1328
    ]
1329

    
1330
  fields.extend(_BuildNDFields(False))
1331

    
1332
  # Node role
1333
  role_values = (constants.NR_MASTER, constants.NR_MCANDIDATE,
1334
                 constants.NR_REGULAR, constants.NR_DRAINED,
1335
                 constants.NR_OFFLINE)
1336
  role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate,"
1337
              " \"%s\" for regular, \"%s\" for drained, \"%s\" for offline" %
1338
              role_values)
1339
  fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
1340
                 lambda ctx, node: _GetNodeRole(node, ctx.master_name)))
1341
  assert set(role_values) == constants.NR_ALL
1342

    
1343
  def _GetLength(getter):
1344
    return lambda ctx, node: len(getter(ctx)[node.name])
1345

    
1346
  def _GetList(getter):
1347
    return lambda ctx, node: list(getter(ctx)[node.name])
1348

    
1349
  # Add fields operating on instance lists
1350
  for prefix, titleprefix, docword, getter in \
1351
      [("p", "Pri", "primary", operator.attrgetter("node_to_primary")),
1352
       ("s", "Sec", "secondary", operator.attrgetter("node_to_secondary"))]:
1353
    # TODO: Allow filterting by hostname in list
1354
    fields.extend([
1355
      (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER,
1356
                  "Number of instances with this node as %s" % docword),
1357
       NQ_INST, 0, _GetLength(getter)),
1358
      (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
1359
                  QFT_OTHER,
1360
                  "List of instances with this node as %s" % docword),
1361
       NQ_INST, 0, _GetList(getter)),
1362
      ])
1363

    
1364
  # Add simple fields
1365
  fields.extend([
1366
    (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1367
    for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()])
1368

    
1369
  # Add fields requiring live data
1370
  fields.extend([
1371
    (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1372
     compat.partial(_GetLiveNodeField, nfield, kind))
1373
    for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()])
1374

    
1375
  # Add timestamps
1376
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1377

    
1378
  return _PrepareFieldList(fields, [])
1379

    
1380

    
1381
class InstanceQueryData:
1382
  """Data container for instance data queries.
1383

1384
  """
1385
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
1386
               live_data, wrongnode_inst, console, nodes, groups):
1387
    """Initializes this class.
1388

1389
    @param instances: List of instance objects
1390
    @param cluster: Cluster object
1391
    @type disk_usage: dict; instance name as key
1392
    @param disk_usage: Per-instance disk usage
1393
    @type offline_nodes: list of strings
1394
    @param offline_nodes: List of offline nodes
1395
    @type bad_nodes: list of strings
1396
    @param bad_nodes: List of faulty nodes
1397
    @type live_data: dict; instance name as key
1398
    @param live_data: Per-instance live data
1399
    @type wrongnode_inst: set
1400
    @param wrongnode_inst: Set of instances running on wrong node(s)
1401
    @type console: dict; instance name as key
1402
    @param console: Per-instance console information
1403
    @type nodes: dict; node name as key
1404
    @param nodes: Node objects
1405

1406
    """
1407
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
1408
           "Offline nodes not included in bad nodes"
1409
    assert not (set(live_data.keys()) & set(bad_nodes)), \
1410
           "Found live data for bad or offline nodes"
1411

    
1412
    self.instances = instances
1413
    self.cluster = cluster
1414
    self.disk_usage = disk_usage
1415
    self.offline_nodes = offline_nodes
1416
    self.bad_nodes = bad_nodes
1417
    self.live_data = live_data
1418
    self.wrongnode_inst = wrongnode_inst
1419
    self.console = console
1420
    self.nodes = nodes
1421
    self.groups = groups
1422

    
1423
    # Used for individual rows
1424
    self.inst_hvparams = None
1425
    self.inst_beparams = None
1426
    self.inst_osparams = None
1427
    self.inst_nicparams = None
1428

    
1429
  def __iter__(self):
1430
    """Iterate over all instances.
1431

1432
    This function has side-effects and only one instance of the resulting
1433
    generator should be used at a time.
1434

1435
    """
1436
    for inst in self.instances:
1437
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1438
      self.inst_beparams = self.cluster.FillBE(inst)
1439
      self.inst_osparams = self.cluster.SimpleFillOS(inst.os, inst.osparams)
1440
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1441
                             for nic in inst.nics]
1442

    
1443
      yield inst
1444

    
1445

    
1446
def _GetInstOperState(ctx, inst):
1447
  """Get instance's operational status.
1448

1449
  @type ctx: L{InstanceQueryData}
1450
  @type inst: L{objects.Instance}
1451
  @param inst: Instance object
1452

1453
  """
1454
  # Can't use RS_OFFLINE here as it would describe the instance to
1455
  # be offline when we actually don't know due to missing data
1456
  if inst.primary_node in ctx.bad_nodes:
1457
    return _FS_NODATA
1458
  else:
1459
    return bool(ctx.live_data.get(inst.name))
1460

    
1461

    
1462
def _GetInstLiveData(name):
1463
  """Build function for retrieving live data.
1464

1465
  @type name: string
1466
  @param name: Live data field name
1467

1468
  """
1469
  def fn(ctx, inst):
1470
    """Get live data for an instance.
1471

1472
    @type ctx: L{InstanceQueryData}
1473
    @type inst: L{objects.Instance}
1474
    @param inst: Instance object
1475

1476
    """
1477
    if (inst.primary_node in ctx.bad_nodes or
1478
        inst.primary_node in ctx.offline_nodes):
1479
      # Can't use RS_OFFLINE here as it would describe the instance to be
1480
      # offline when we actually don't know due to missing data
1481
      return _FS_NODATA
1482

    
1483
    if inst.name in ctx.live_data:
1484
      data = ctx.live_data[inst.name]
1485
      if name in data:
1486
        return data[name]
1487

    
1488
    return _FS_UNAVAIL
1489

    
1490
  return fn
1491

    
1492

    
1493
def _GetInstStatus(ctx, inst):
1494
  """Get instance status.
1495

1496
  @type ctx: L{InstanceQueryData}
1497
  @type inst: L{objects.Instance}
1498
  @param inst: Instance object
1499

1500
  """
1501
  if inst.primary_node in ctx.offline_nodes:
1502
    return constants.INSTST_NODEOFFLINE
1503

    
1504
  if inst.primary_node in ctx.bad_nodes:
1505
    return constants.INSTST_NODEDOWN
1506

    
1507
  if bool(ctx.live_data.get(inst.name)):
1508
    if inst.name in ctx.wrongnode_inst:
1509
      return constants.INSTST_WRONGNODE
1510
    elif inst.admin_state == constants.ADMINST_UP:
1511
      return constants.INSTST_RUNNING
1512
    else:
1513
      return constants.INSTST_ERRORUP
1514

    
1515
  if inst.admin_state == constants.ADMINST_UP:
1516
    return constants.INSTST_ERRORDOWN
1517
  elif inst.admin_state == constants.ADMINST_DOWN:
1518
    return constants.INSTST_ADMINDOWN
1519

    
1520
  return constants.INSTST_ADMINOFFLINE
1521

    
1522

    
1523
def _GetInstDiskSize(index):
1524
  """Build function for retrieving disk size.
1525

1526
  @type index: int
1527
  @param index: Disk index
1528

1529
  """
1530
  def fn(_, inst):
1531
    """Get size of a disk.
1532

1533
    @type inst: L{objects.Instance}
1534
    @param inst: Instance object
1535

1536
    """
1537
    try:
1538
      return inst.disks[index].size
1539
    except IndexError:
1540
      return _FS_UNAVAIL
1541

    
1542
  return fn
1543

    
1544

    
1545
def _GetInstNic(index, cb):
1546
  """Build function for calling another function with an instance NIC.
1547

1548
  @type index: int
1549
  @param index: NIC index
1550
  @type cb: callable
1551
  @param cb: Callback
1552

1553
  """
1554
  def fn(ctx, inst):
1555
    """Call helper function with instance NIC.
1556

1557
    @type ctx: L{InstanceQueryData}
1558
    @type inst: L{objects.Instance}
1559
    @param inst: Instance object
1560

1561
    """
1562
    try:
1563
      nic = inst.nics[index]
1564
    except IndexError:
1565
      return _FS_UNAVAIL
1566

    
1567
    return cb(ctx, index, nic)
1568

    
1569
  return fn
1570

    
1571

    
1572
def _GetInstNicNetwork(ctx, _, nic): # pylint: disable=W0613
1573
  """Get a NIC's Network.
1574

1575
  @type ctx: L{InstanceQueryData}
1576
  @type nic: L{objects.NIC}
1577
  @param nic: NIC object
1578

1579
  """
1580
  if nic.network is None:
1581
    return _FS_UNAVAIL
1582
  else:
1583
    return nic.network
1584

    
1585

    
1586
def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613
1587
  """Get a NIC's IP address.
1588

1589
  @type ctx: L{InstanceQueryData}
1590
  @type nic: L{objects.NIC}
1591
  @param nic: NIC object
1592

1593
  """
1594
  if nic.ip is None:
1595
    return _FS_UNAVAIL
1596
  else:
1597
    return nic.ip
1598

    
1599

    
1600
def _GetInstNicBridge(ctx, index, _):
1601
  """Get a NIC's bridge.
1602

1603
  @type ctx: L{InstanceQueryData}
1604
  @type index: int
1605
  @param index: NIC index
1606

1607
  """
1608
  assert len(ctx.inst_nicparams) >= index
1609

    
1610
  nicparams = ctx.inst_nicparams[index]
1611

    
1612
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1613
    return nicparams[constants.NIC_LINK]
1614
  else:
1615
    return _FS_UNAVAIL
1616

    
1617

    
1618
def _GetInstAllNicBridges(ctx, inst):
1619
  """Get all network bridges for an instance.
1620

1621
  @type ctx: L{InstanceQueryData}
1622
  @type inst: L{objects.Instance}
1623
  @param inst: Instance object
1624

1625
  """
1626
  assert len(ctx.inst_nicparams) == len(inst.nics)
1627

    
1628
  result = []
1629

    
1630
  for nicp in ctx.inst_nicparams:
1631
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1632
      result.append(nicp[constants.NIC_LINK])
1633
    else:
1634
      result.append(None)
1635

    
1636
  assert len(result) == len(inst.nics)
1637

    
1638
  return result
1639

    
1640

    
1641
def _GetInstNicParam(name):
1642
  """Build function for retrieving a NIC parameter.
1643

1644
  @type name: string
1645
  @param name: Parameter name
1646

1647
  """
1648
  def fn(ctx, index, _):
1649
    """Get a NIC's bridge.
1650

1651
    @type ctx: L{InstanceQueryData}
1652
    @type inst: L{objects.Instance}
1653
    @param inst: Instance object
1654
    @type nic: L{objects.NIC}
1655
    @param nic: NIC object
1656

1657
    """
1658
    assert len(ctx.inst_nicparams) >= index
1659
    return ctx.inst_nicparams[index][name]
1660

    
1661
  return fn
1662

    
1663

    
1664
def _GetInstanceNetworkFields():
1665
  """Get instance fields involving network interfaces.
1666

1667
  @return: Tuple containing list of field definitions used as input for
1668
    L{_PrepareFieldList} and a list of aliases
1669

1670
  """
1671
  nic_mac_fn = lambda ctx, _, nic: nic.mac
1672
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1673
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1674

    
1675
  fields = [
1676
    # All NICs
1677
    (_MakeField("nic.count", "NICs", QFT_NUMBER,
1678
                "Number of network interfaces"),
1679
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.nics)),
1680
    (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER,
1681
                "List containing each network interface's MAC address"),
1682
     IQ_CONFIG, 0, lambda ctx, inst: [nic.mac for nic in inst.nics]),
1683
    (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
1684
                "List containing each network interface's IP address"),
1685
     IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
1686
    (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
1687
                "List containing each network interface's mode"), IQ_CONFIG, 0,
1688
     lambda ctx, inst: [nicp[constants.NIC_MODE]
1689
                        for nicp in ctx.inst_nicparams]),
1690
    (_MakeField("nic.links", "NIC_links", QFT_OTHER,
1691
                "List containing each network interface's link"), IQ_CONFIG, 0,
1692
     lambda ctx, inst: [nicp[constants.NIC_LINK]
1693
                        for nicp in ctx.inst_nicparams]),
1694
    (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
1695
                "List containing each network interface's bridge"),
1696
     IQ_CONFIG, 0, _GetInstAllNicBridges),
1697
    (_MakeField("nic.networks", "NIC_networks", QFT_OTHER,
1698
                "List containing each interface's network"), IQ_CONFIG, 0,
1699
     lambda ctx, inst: [nic.network for nic in inst.nics]),
1700
    ]
1701

    
1702
  # NICs by number
1703
  for i in range(constants.MAX_NICS):
1704
    numtext = utils.FormatOrdinal(i + 1)
1705
    fields.extend([
1706
      (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT,
1707
                  "IP address of %s network interface" % numtext),
1708
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)),
1709
      (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
1710
                  "MAC address of %s network interface" % numtext),
1711
       IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
1712
      (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
1713
                  "Mode of %s network interface" % numtext),
1714
       IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
1715
      (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT,
1716
                  "Link of %s network interface" % numtext),
1717
       IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)),
1718
      (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
1719
                  "Bridge of %s network interface" % numtext),
1720
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
1721
      (_MakeField("nic.network/%s" % i, "NicNetwork/%s" % i, QFT_TEXT,
1722
                  "Network of %s network interface" % numtext),
1723
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicNetwork)),
1724
      ])
1725

    
1726
  aliases = [
1727
    # Legacy fields for first NIC
1728
    ("ip", "nic.ip/0"),
1729
    ("mac", "nic.mac/0"),
1730
    ("bridge", "nic.bridge/0"),
1731
    ("nic_mode", "nic.mode/0"),
1732
    ("nic_link", "nic.link/0"),
1733
    ("nic_network", "nic.network/0"),
1734
    ]
1735

    
1736
  return (fields, aliases)
1737

    
1738

    
1739
def _GetInstDiskUsage(ctx, inst):
1740
  """Get disk usage for an instance.
1741

1742
  @type ctx: L{InstanceQueryData}
1743
  @type inst: L{objects.Instance}
1744
  @param inst: Instance object
1745

1746
  """
1747
  usage = ctx.disk_usage[inst.name]
1748

    
1749
  if usage is None:
1750
    usage = 0
1751

    
1752
  return usage
1753

    
1754

    
1755
def _GetInstanceConsole(ctx, inst):
1756
  """Get console information for instance.
1757

1758
  @type ctx: L{InstanceQueryData}
1759
  @type inst: L{objects.Instance}
1760
  @param inst: Instance object
1761

1762
  """
1763
  consinfo = ctx.console[inst.name]
1764

    
1765
  if consinfo is None:
1766
    return _FS_UNAVAIL
1767

    
1768
  return consinfo
1769

    
1770

    
1771
def _GetInstanceDiskFields():
1772
  """Get instance fields involving disks.
1773

1774
  @return: List of field definitions used as input for L{_PrepareFieldList}
1775

1776
  """
1777
  fields = [
1778
    (_MakeField("disk_usage", "DiskUsage", QFT_UNIT,
1779
                "Total disk space used by instance on each of its nodes;"
1780
                " this is not the disk size visible to the instance, but"
1781
                " the usage on the node"),
1782
     IQ_DISKUSAGE, 0, _GetInstDiskUsage),
1783
    (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"),
1784
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
1785
    (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
1786
     IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
1787
    ]
1788

    
1789
  # Disks by number
1790
  fields.extend([
1791
    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1792
                "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
1793
     IQ_CONFIG, 0, _GetInstDiskSize(i))
1794
    for i in range(constants.MAX_DISKS)])
1795

    
1796
  return fields
1797

    
1798

    
1799
def _GetInstanceParameterFields():
1800
  """Get instance fields involving parameters.
1801

1802
  @return: List of field definitions used as input for L{_PrepareFieldList}
1803

1804
  """
1805
  fields = [
1806
    # Filled parameters
1807
    (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
1808
                "Hypervisor parameters (merged)"),
1809
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
1810
    (_MakeField("beparams", "BackendParameters", QFT_OTHER,
1811
                "Backend parameters (merged)"),
1812
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
1813
    (_MakeField("osparams", "OpSysParameters", QFT_OTHER,
1814
                "Operating system parameters (merged)"),
1815
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_osparams),
1816

    
1817
    # Unfilled parameters
1818
    (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
1819
                "Custom hypervisor parameters"),
1820
     IQ_CONFIG, 0, _GetItemAttr("hvparams")),
1821
    (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
1822
                "Custom backend parameters",),
1823
     IQ_CONFIG, 0, _GetItemAttr("beparams")),
1824
    (_MakeField("custom_osparams", "CustomOpSysParameters", QFT_OTHER,
1825
                "Custom operating system parameters",),
1826
     IQ_CONFIG, 0, _GetItemAttr("osparams")),
1827
    (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
1828
                "Custom network interface parameters"),
1829
     IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1830
    ]
1831

    
1832
  # HV params
1833
  def _GetInstHvParam(name):
1834
    return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1835

    
1836
  fields.extend([
1837
    (_MakeField("hv/%s" % name,
1838
                constants.HVS_PARAMETER_TITLES.get(name, "hv/%s" % name),
1839
                _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1840
     IQ_CONFIG, 0, _GetInstHvParam(name))
1841
    for name, kind in constants.HVS_PARAMETER_TYPES.items()
1842
    if name not in constants.HVC_GLOBALS])
1843

    
1844
  # BE params
1845
  def _GetInstBeParam(name):
1846
    return lambda ctx, _: ctx.inst_beparams.get(name, None)
1847

    
1848
  fields.extend([
1849
    (_MakeField("be/%s" % name,
1850
                constants.BES_PARAMETER_TITLES.get(name, "be/%s" % name),
1851
                _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1852
     IQ_CONFIG, 0, _GetInstBeParam(name))
1853
    for name, kind in constants.BES_PARAMETER_TYPES.items()])
1854

    
1855
  return fields
1856

    
1857

    
1858
_INST_SIMPLE_FIELDS = {
1859
  "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"),
1860
  "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"),
1861
  "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"),
1862
  # Depending on the hypervisor, the port can be None
1863
  "network_port": ("Network_port", QFT_OTHER, 0,
1864
                   "Instance network port if available (e.g. for VNC console)"),
1865
  "os": ("OS", QFT_TEXT, 0, "Operating system"),
1866
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"),
1867
  "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"),
1868
  }
1869

    
1870

    
1871
def _GetInstNodeGroup(ctx, default, node_name):
1872
  """Gets group UUID of an instance node.
1873

1874
  @type ctx: L{InstanceQueryData}
1875
  @param default: Default value
1876
  @type node_name: string
1877
  @param node_name: Node name
1878

1879
  """
1880
  try:
1881
    node = ctx.nodes[node_name]
1882
  except KeyError:
1883
    return default
1884
  else:
1885
    return node.group
1886

    
1887

    
1888
def _GetInstNodeGroupName(ctx, default, node_name):
1889
  """Gets group name of an instance node.
1890

1891
  @type ctx: L{InstanceQueryData}
1892
  @param default: Default value
1893
  @type node_name: string
1894
  @param node_name: Node name
1895

1896
  """
1897
  try:
1898
    node = ctx.nodes[node_name]
1899
  except KeyError:
1900
    return default
1901

    
1902
  try:
1903
    group = ctx.groups[node.group]
1904
  except KeyError:
1905
    return default
1906

    
1907
  return group.name
1908

    
1909

    
1910
def _BuildInstanceFields():
1911
  """Builds list of fields for instance queries.
1912

1913
  """
1914
  fields = [
1915
    (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
1916
     IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")),
1917
    (_MakeField("pnode.group", "PrimaryNodeGroup", QFT_TEXT,
1918
                "Primary node's group"),
1919
     IQ_NODES, 0,
1920
     lambda ctx, inst: _GetInstNodeGroupName(ctx, _FS_UNAVAIL,
1921
                                             inst.primary_node)),
1922
    (_MakeField("pnode.group.uuid", "PrimaryNodeGroupUUID", QFT_TEXT,
1923
                "Primary node's group UUID"),
1924
     IQ_NODES, 0,
1925
     lambda ctx, inst: _GetInstNodeGroup(ctx, _FS_UNAVAIL, inst.primary_node)),
1926
    # TODO: Allow filtering by secondary node as hostname
1927
    (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
1928
                "Secondary nodes; usually this will just be one node"),
1929
     IQ_CONFIG, 0, lambda ctx, inst: list(inst.secondary_nodes)),
1930
    (_MakeField("snodes.group", "SecondaryNodesGroups", QFT_OTHER,
1931
                "Node groups of secondary nodes"),
1932
     IQ_NODES, 0,
1933
     lambda ctx, inst: map(compat.partial(_GetInstNodeGroupName, ctx, None),
1934
                           inst.secondary_nodes)),
1935
    (_MakeField("snodes.group.uuid", "SecondaryNodesGroupsUUID", QFT_OTHER,
1936
                "Node group UUIDs of secondary nodes"),
1937
     IQ_NODES, 0,
1938
     lambda ctx, inst: map(compat.partial(_GetInstNodeGroup, ctx, None),
1939
                           inst.secondary_nodes)),
1940
    (_MakeField("admin_state", "InstanceState", QFT_TEXT,
1941
                "Desired state of instance"),
1942
     IQ_CONFIG, 0, _GetItemAttr("admin_state")),
1943
    (_MakeField("admin_up", "Autostart", QFT_BOOL,
1944
                "Desired state of instance"),
1945
     IQ_CONFIG, 0, lambda ctx, inst: inst.admin_state == constants.ADMINST_UP),
1946
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
1947
     lambda ctx, inst: list(inst.GetTags())),
1948
    (_MakeField("console", "Console", QFT_OTHER,
1949
                "Instance console information"), IQ_CONSOLE, 0,
1950
     _GetInstanceConsole),
1951
    ]
1952

    
1953
  # Add simple fields
1954
  fields.extend([
1955
    (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1956
    for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()])
1957

    
1958
  # Fields requiring talking to the node
1959
  fields.extend([
1960
    (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
1961
     IQ_LIVE, 0, _GetInstOperState),
1962
    (_MakeField("oper_ram", "Memory", QFT_UNIT,
1963
                "Actual memory usage as seen by hypervisor"),
1964
     IQ_LIVE, 0, _GetInstLiveData("memory")),
1965
    (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
1966
                "Actual number of VCPUs as seen by hypervisor"),
1967
     IQ_LIVE, 0, _GetInstLiveData("vcpus")),
1968
    ])
1969

    
1970
  # Status field
1971
  status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
1972
                   constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
1973
                   constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
1974
                   constants.INSTST_NODEOFFLINE, constants.INSTST_ADMINOFFLINE)
1975
  status_doc = ("Instance status; \"%s\" if instance is set to be running"
1976
                " and actually is, \"%s\" if instance is stopped and"
1977
                " is not running, \"%s\" if instance running, but not on its"
1978
                " designated primary node, \"%s\" if instance should be"
1979
                " stopped, but is actually running, \"%s\" if instance should"
1980
                " run, but doesn't, \"%s\" if instance's primary node is down,"
1981
                " \"%s\" if instance's primary node is marked offline,"
1982
                " \"%s\" if instance is offline and does not use dynamic"
1983
                " resources" % status_values)
1984
  fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
1985
                 IQ_LIVE, 0, _GetInstStatus))
1986
  assert set(status_values) == constants.INSTST_ALL, \
1987
         "Status documentation mismatch"
1988

    
1989
  (network_fields, network_aliases) = _GetInstanceNetworkFields()
1990

    
1991
  fields.extend(network_fields)
1992
  fields.extend(_GetInstanceParameterFields())
1993
  fields.extend(_GetInstanceDiskFields())
1994
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1995

    
1996
  aliases = [
1997
    ("vcpus", "be/vcpus"),
1998
    ("be/memory", "be/maxmem"),
1999
    ("sda_size", "disk.size/0"),
2000
    ("sdb_size", "disk.size/1"),
2001
    ] + network_aliases
2002

    
2003
  return _PrepareFieldList(fields, aliases)
2004

    
2005

    
2006
class LockQueryData:
2007
  """Data container for lock data queries.
2008

2009
  """
2010
  def __init__(self, lockdata):
2011
    """Initializes this class.
2012

2013
    """
2014
    self.lockdata = lockdata
2015

    
2016
  def __iter__(self):
2017
    """Iterate over all locks.
2018

2019
    """
2020
    return iter(self.lockdata)
2021

    
2022

    
2023
def _GetLockOwners(_, data):
2024
  """Returns a sorted list of a lock's current owners.
2025

2026
  """
2027
  (_, _, owners, _) = data
2028

    
2029
  if owners:
2030
    owners = utils.NiceSort(owners)
2031

    
2032
  return owners
2033

    
2034

    
2035
def _GetLockPending(_, data):
2036
  """Returns a sorted list of a lock's pending acquires.
2037

2038
  """
2039
  (_, _, _, pending) = data
2040

    
2041
  if pending:
2042
    pending = [(mode, utils.NiceSort(names))
2043
               for (mode, names) in pending]
2044

    
2045
  return pending
2046

    
2047

    
2048
def _BuildLockFields():
2049
  """Builds list of fields for lock queries.
2050

2051
  """
2052
  return _PrepareFieldList([
2053
    # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
2054
    (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
2055
     lambda ctx, (name, mode, owners, pending): name),
2056
    (_MakeField("mode", "Mode", QFT_OTHER,
2057
                "Mode in which the lock is currently acquired"
2058
                " (exclusive or shared)"),
2059
     LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
2060
    (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
2061
     LQ_OWNER, 0, _GetLockOwners),
2062
    (_MakeField("pending", "Pending", QFT_OTHER,
2063
                "Threads waiting for the lock"),
2064
     LQ_PENDING, 0, _GetLockPending),
2065
    ], [])
2066

    
2067

    
2068
class GroupQueryData:
2069
  """Data container for node group data queries.
2070

2071
  """
2072
  def __init__(self, cluster, groups, group_to_nodes, group_to_instances,
2073
               want_diskparams):
2074
    """Initializes this class.
2075

2076
    @param cluster: Cluster object
2077
    @param groups: List of node group objects
2078
    @type group_to_nodes: dict; group UUID as key
2079
    @param group_to_nodes: Per-group list of nodes
2080
    @type group_to_instances: dict; group UUID as key
2081
    @param group_to_instances: Per-group list of (primary) instances
2082
    @type want_diskparams: bool
2083
    @param want_diskparams: Whether diskparamters should be calculated
2084

2085
    """
2086
    self.groups = groups
2087
    self.group_to_nodes = group_to_nodes
2088
    self.group_to_instances = group_to_instances
2089
    self.cluster = cluster
2090
    self.want_diskparams = want_diskparams
2091

    
2092
    # Used for individual rows
2093
    self.group_ipolicy = None
2094
    self.ndparams = None
2095
    self.group_dp = None
2096

    
2097
  def __iter__(self):
2098
    """Iterate over all node groups.
2099

2100
    This function has side-effects and only one instance of the resulting
2101
    generator should be used at a time.
2102

2103
    """
2104
    for group in self.groups:
2105
      self.group_ipolicy = self.cluster.SimpleFillIPolicy(group.ipolicy)
2106
      self.ndparams = self.cluster.SimpleFillND(group.ndparams)
2107
      if self.want_diskparams:
2108
        self.group_dp = self.cluster.SimpleFillDP(group.diskparams)
2109
      else:
2110
        self.group_dp = None
2111
      yield group
2112

    
2113

    
2114
_GROUP_SIMPLE_FIELDS = {
2115
  "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
2116
  "name": ("Group", QFT_TEXT, "Group name"),
2117
  "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
2118
  "uuid": ("UUID", QFT_TEXT, "Group UUID"),
2119
  }
2120

    
2121

    
2122
def _BuildGroupFields():
2123
  """Builds list of fields for node group queries.
2124

2125
  """
2126
  # Add simple fields
2127
  fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
2128
             _GetItemAttr(name))
2129
            for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
2130

    
2131
  def _GetLength(getter):
2132
    return lambda ctx, group: len(getter(ctx)[group.uuid])
2133

    
2134
  def _GetSortedList(getter):
2135
    return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
2136

    
2137
  group_to_nodes = operator.attrgetter("group_to_nodes")
2138
  group_to_instances = operator.attrgetter("group_to_instances")
2139

    
2140
  # Add fields for nodes
2141
  fields.extend([
2142
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
2143
     GQ_NODE, 0, _GetLength(group_to_nodes)),
2144
    (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
2145
     GQ_NODE, 0, _GetSortedList(group_to_nodes)),
2146
    ])
2147

    
2148
  # Add fields for instances
2149
  fields.extend([
2150
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
2151
                "Number of primary instances"),
2152
     GQ_INST, 0, _GetLength(group_to_instances)),
2153
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
2154
                "List of primary instances"),
2155
     GQ_INST, 0, _GetSortedList(group_to_instances)),
2156
    ])
2157

    
2158
  # Other fields
2159
  fields.extend([
2160
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), GQ_CONFIG, 0,
2161
     lambda ctx, group: list(group.GetTags())),
2162
    (_MakeField("ipolicy", "InstancePolicy", QFT_OTHER,
2163
                "Instance policy limitations (merged)"),
2164
     GQ_CONFIG, 0, lambda ctx, _: ctx.group_ipolicy),
2165
    (_MakeField("custom_ipolicy", "CustomInstancePolicy", QFT_OTHER,
2166
                "Custom instance policy limitations"),
2167
     GQ_CONFIG, 0, _GetItemAttr("ipolicy")),
2168
    (_MakeField("custom_ndparams", "CustomNDParams", QFT_OTHER,
2169
                "Custom node parameters"),
2170
     GQ_CONFIG, 0, _GetItemAttr("ndparams")),
2171
    (_MakeField("ndparams", "NDParams", QFT_OTHER,
2172
                "Node parameters"),
2173
     GQ_CONFIG, 0, lambda ctx, _: ctx.ndparams),
2174
    (_MakeField("diskparams", "DiskParameters", QFT_OTHER,
2175
                "Disk parameters (merged)"),
2176
     GQ_DISKPARAMS, 0, lambda ctx, _: ctx.group_dp),
2177
    (_MakeField("custom_diskparams", "CustomDiskParameters", QFT_OTHER,
2178
                "Custom disk parameters"),
2179
     GQ_CONFIG, 0, _GetItemAttr("diskparams")),
2180
    ])
2181

    
2182
  # ND parameters
2183
  fields.extend(_BuildNDFields(True))
2184

    
2185
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
2186

    
2187
  return _PrepareFieldList(fields, [])
2188

    
2189

    
2190
class OsInfo(objects.ConfigObject):
2191
  __slots__ = [
2192
    "name",
2193
    "valid",
2194
    "hidden",
2195
    "blacklisted",
2196
    "variants",
2197
    "api_versions",
2198
    "parameters",
2199
    "node_status",
2200
    ]
2201

    
2202

    
2203
def _BuildOsFields():
2204
  """Builds list of fields for operating system queries.
2205

2206
  """
2207
  fields = [
2208
    (_MakeField("name", "Name", QFT_TEXT, "Operating system name"),
2209
     None, 0, _GetItemAttr("name")),
2210
    (_MakeField("valid", "Valid", QFT_BOOL,
2211
                "Whether operating system definition is valid"),
2212
     None, 0, _GetItemAttr("valid")),
2213
    (_MakeField("hidden", "Hidden", QFT_BOOL,
2214
                "Whether operating system is hidden"),
2215
     None, 0, _GetItemAttr("hidden")),
2216
    (_MakeField("blacklisted", "Blacklisted", QFT_BOOL,
2217
                "Whether operating system is blacklisted"),
2218
     None, 0, _GetItemAttr("blacklisted")),
2219
    (_MakeField("variants", "Variants", QFT_OTHER,
2220
                "Operating system variants"),
2221
     None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("variants"))),
2222
    (_MakeField("api_versions", "ApiVersions", QFT_OTHER,
2223
                "Operating system API versions"),
2224
     None, 0, _ConvWrap(sorted, _GetItemAttr("api_versions"))),
2225
    (_MakeField("parameters", "Parameters", QFT_OTHER,
2226
                "Operating system parameters"),
2227
     None, 0, _ConvWrap(compat.partial(utils.NiceSort, key=compat.fst),
2228
                        _GetItemAttr("parameters"))),
2229
    (_MakeField("node_status", "NodeStatus", QFT_OTHER,
2230
                "Status from node"),
2231
     None, 0, _GetItemAttr("node_status")),
2232
    ]
2233

    
2234
  return _PrepareFieldList(fields, [])
2235

    
2236

    
2237
class ExtStorageInfo(objects.ConfigObject):
2238
  __slots__ = [
2239
    "name",
2240
    "node_status",
2241
    "nodegroup_status",
2242
    "parameters",
2243
    ]
2244

    
2245

    
2246
def _BuildExtStorageFields():
2247
  """Builds list of fields for extstorage provider queries.
2248

2249
  """
2250
  fields = [
2251
    (_MakeField("name", "Name", QFT_TEXT, "ExtStorage provider name"),
2252
     None, 0, _GetItemAttr("name")),
2253
    (_MakeField("node_status", "NodeStatus", QFT_OTHER,
2254
                "Status from node"),
2255
     None, 0, _GetItemAttr("node_status")),
2256
    (_MakeField("nodegroup_status", "NodegroupStatus", QFT_OTHER,
2257
                "Overall Nodegroup status"),
2258
     None, 0, _GetItemAttr("nodegroup_status")),
2259
    (_MakeField("parameters", "Parameters", QFT_OTHER,
2260
                "ExtStorage provider parameters"),
2261
     None, 0, _GetItemAttr("parameters")),
2262
    ]
2263

    
2264
  return _PrepareFieldList(fields, [])
2265

    
2266

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

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

2273
  """
2274
  if job is None:
2275
    return _FS_UNAVAIL
2276
  else:
2277
    return fn(job)
2278

    
2279

    
2280
def _JobUnavail(inner):
2281
  """Wrapper for L{_JobUnavailInner}.
2282

2283
  """
2284
  return compat.partial(_JobUnavailInner, inner)
2285

    
2286

    
2287
def _PerJobOpInner(fn, job):
2288
  """Executes a function per opcode in a job.
2289

2290
  """
2291
  return map(fn, job.ops)
2292

    
2293

    
2294
def _PerJobOp(fn):
2295
  """Wrapper for L{_PerJobOpInner}.
2296

2297
  """
2298
  return _JobUnavail(compat.partial(_PerJobOpInner, fn))
2299

    
2300

    
2301
def _JobTimestampInner(fn, job):
2302
  """Converts unavailable timestamp to L{_FS_UNAVAIL}.
2303

2304
  """
2305
  timestamp = fn(job)
2306

    
2307
  if timestamp is None:
2308
    return _FS_UNAVAIL
2309
  else:
2310
    return timestamp
2311

    
2312

    
2313
def _JobTimestamp(fn):
2314
  """Wrapper for L{_JobTimestampInner}.
2315

2316
  """
2317
  return _JobUnavail(compat.partial(_JobTimestampInner, fn))
2318

    
2319

    
2320
def _BuildJobFields():
2321
  """Builds list of fields for job queries.
2322

2323
  """
2324
  fields = [
2325
    (_MakeField("id", "ID", QFT_NUMBER, "Job ID"),
2326
     None, QFF_JOB_ID, lambda _, (job_id, job): job_id),
2327
    (_MakeField("status", "Status", QFT_TEXT, "Job status"),
2328
     None, 0, _JobUnavail(lambda job: job.CalcStatus())),
2329
    (_MakeField("priority", "Priority", QFT_NUMBER,
2330
                ("Current job priority (%s to %s)" %
2331
                 (constants.OP_PRIO_LOWEST, constants.OP_PRIO_HIGHEST))),
2332
     None, 0, _JobUnavail(lambda job: job.CalcPriority())),
2333
    (_MakeField("archived", "Archived", QFT_BOOL, "Whether job is archived"),
2334
     JQ_ARCHIVED, 0, lambda _, (job_id, job): job.archived),
2335
    (_MakeField("ops", "OpCodes", QFT_OTHER, "List of all opcodes"),
2336
     None, 0, _PerJobOp(lambda op: op.input.__getstate__())),
2337
    (_MakeField("opresult", "OpCode_result", QFT_OTHER,
2338
                "List of opcodes results"),
2339
     None, 0, _PerJobOp(operator.attrgetter("result"))),
2340
    (_MakeField("opstatus", "OpCode_status", QFT_OTHER,
2341
                "List of opcodes status"),
2342
     None, 0, _PerJobOp(operator.attrgetter("status"))),
2343
    (_MakeField("oplog", "OpCode_log", QFT_OTHER,
2344
                "List of opcode output logs"),
2345
     None, 0, _PerJobOp(operator.attrgetter("log"))),
2346
    (_MakeField("opstart", "OpCode_start", QFT_OTHER,
2347
                "List of opcode start timestamps (before acquiring locks)"),
2348
     None, 0, _PerJobOp(operator.attrgetter("start_timestamp"))),
2349
    (_MakeField("opexec", "OpCode_exec", QFT_OTHER,
2350
                "List of opcode execution start timestamps (after acquiring"
2351
                " locks)"),
2352
     None, 0, _PerJobOp(operator.attrgetter("exec_timestamp"))),
2353
    (_MakeField("opend", "OpCode_end", QFT_OTHER,
2354
                "List of opcode execution end timestamps"),
2355
     None, 0, _PerJobOp(operator.attrgetter("end_timestamp"))),
2356
    (_MakeField("oppriority", "OpCode_prio", QFT_OTHER,
2357
                "List of opcode priorities"),
2358
     None, 0, _PerJobOp(operator.attrgetter("priority"))),
2359
    (_MakeField("summary", "Summary", QFT_OTHER,
2360
                "List of per-opcode summaries"),
2361
     None, 0, _PerJobOp(lambda op: op.input.Summary())),
2362
    ]
2363

    
2364
  # Timestamp fields
2365
  for (name, attr, title, desc) in [
2366
    ("received_ts", "received_timestamp", "Received",
2367
     "Timestamp of when job was received"),
2368
    ("start_ts", "start_timestamp", "Start", "Timestamp of job start"),
2369
    ("end_ts", "end_timestamp", "End", "Timestamp of job end"),
2370
    ]:
2371
    getter = operator.attrgetter(attr)
2372
    fields.extend([
2373
      (_MakeField(name, title, QFT_OTHER,
2374
                  "%s (tuple containing seconds and microseconds)" % desc),
2375
       None, QFF_SPLIT_TIMESTAMP, _JobTimestamp(getter)),
2376
      ])
2377

    
2378
  return _PrepareFieldList(fields, [])
2379

    
2380

    
2381
def _GetExportName(_, (node_name, expname)): # pylint: disable=W0613
2382
  """Returns an export name if available.
2383

2384
  """
2385
  if expname is None:
2386
    return _FS_UNAVAIL
2387
  else:
2388
    return expname
2389

    
2390

    
2391
def _BuildExportFields():
2392
  """Builds list of fields for exports.
2393

2394
  """
2395
  fields = [
2396
    (_MakeField("node", "Node", QFT_TEXT, "Node name"),
2397
     None, QFF_HOSTNAME, lambda _, (node_name, expname): node_name),
2398
    (_MakeField("export", "Export", QFT_TEXT, "Export name"),
2399
     None, 0, _GetExportName),
2400
    ]
2401

    
2402
  return _PrepareFieldList(fields, [])
2403

    
2404

    
2405
_CLUSTER_VERSION_FIELDS = {
2406
  "software_version": ("SoftwareVersion", QFT_TEXT, constants.RELEASE_VERSION,
2407
                       "Software version"),
2408
  "protocol_version": ("ProtocolVersion", QFT_NUMBER,
2409
                       constants.PROTOCOL_VERSION,
2410
                       "RPC protocol version"),
2411
  "config_version": ("ConfigVersion", QFT_NUMBER, constants.CONFIG_VERSION,
2412
                     "Configuration format version"),
2413
  "os_api_version": ("OsApiVersion", QFT_NUMBER, max(constants.OS_API_VERSIONS),
2414
                     "API version for OS template scripts"),
2415
  "export_version": ("ExportVersion", QFT_NUMBER, constants.EXPORT_VERSION,
2416
                     "Import/export file format version"),
2417
  }
2418

    
2419

    
2420
_CLUSTER_SIMPLE_FIELDS = {
2421
  "cluster_name": ("Name", QFT_TEXT, QFF_HOSTNAME, "Cluster name"),
2422
  "master_node": ("Master", QFT_TEXT, QFF_HOSTNAME, "Master node name"),
2423
  "volume_group_name": ("VgName", QFT_TEXT, 0, "LVM volume group name"),
2424
  }
2425

    
2426

    
2427
class ClusterQueryData:
2428
  def __init__(self, cluster, drain_flag, watcher_pause):
2429
    """Initializes this class.
2430

2431
    @type cluster: L{objects.Cluster}
2432
    @param cluster: Instance of cluster object
2433
    @type drain_flag: bool
2434
    @param drain_flag: Whether job queue is drained
2435
    @type watcher_pause: number
2436
    @param watcher_pause: Until when watcher is paused (Unix timestamp)
2437

2438
    """
2439
    self._cluster = cluster
2440
    self.drain_flag = drain_flag
2441
    self.watcher_pause = watcher_pause
2442

    
2443
  def __iter__(self):
2444
    return iter([self._cluster])
2445

    
2446

    
2447
def _ClusterWatcherPause(ctx, _):
2448
  """Returns until when watcher is paused (if available).
2449

2450
  """
2451
  if ctx.watcher_pause is None:
2452
    return _FS_UNAVAIL
2453
  else:
2454
    return ctx.watcher_pause
2455

    
2456

    
2457
def _BuildClusterFields():
2458
  """Builds list of fields for cluster information.
2459

2460
  """
2461
  fields = [
2462
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), CQ_CONFIG, 0,
2463
     lambda ctx, cluster: list(cluster.GetTags())),
2464
    (_MakeField("architecture", "ArchInfo", QFT_OTHER,
2465
                "Architecture information"), None, 0,
2466
     lambda ctx, _: runtime.GetArchInfo()),
2467
    (_MakeField("drain_flag", "QueueDrained", QFT_BOOL,
2468
                "Flag whether job queue is drained"), CQ_QUEUE_DRAINED, 0,
2469
     lambda ctx, _: ctx.drain_flag),
2470
    (_MakeField("watcher_pause", "WatcherPause", QFT_TIMESTAMP,
2471
                "Until when watcher is paused"), CQ_WATCHER_PAUSE, 0,
2472
     _ClusterWatcherPause),
2473
    ]
2474

    
2475
  # Simple fields
2476
  fields.extend([
2477
    (_MakeField(name, title, kind, doc), CQ_CONFIG, flags, _GetItemAttr(name))
2478
    for (name, (title, kind, flags, doc)) in _CLUSTER_SIMPLE_FIELDS.items()
2479
    ],)
2480

    
2481
  # Version fields
2482
  fields.extend([
2483
    (_MakeField(name, title, kind, doc), None, 0, _StaticValue(value))
2484
    for (name, (title, kind, value, doc)) in _CLUSTER_VERSION_FIELDS.items()])
2485

    
2486
  # Add timestamps
2487
  fields.extend(_GetItemTimestampFields(CQ_CONFIG))
2488

    
2489
  return _PrepareFieldList(fields, [
2490
    ("name", "cluster_name")])
2491

    
2492

    
2493
class NetworkQueryData:
2494
  """Data container for network data queries.
2495

2496
  """
2497
  def __init__(self, networks, network_to_groups,
2498
               network_to_instances, stats):
2499
    """Initializes this class.
2500

2501
    @param networks: List of network objects
2502
    @type network_to_groups: dict; network UUID as key
2503
    @param network_to_groups: Per-network list of groups
2504
    @type network_to_instances: dict; network UUID as key
2505
    @param network_to_instances: Per-network list of instances
2506
    @type stats: dict; network UUID as key
2507
    @param stats: Per-network usage statistics
2508

2509
    """
2510
    self.networks = networks
2511
    self.network_to_groups = network_to_groups
2512
    self.network_to_instances = network_to_instances
2513
    self.stats = stats
2514

    
2515
  def __iter__(self):
2516
    """Iterate over all networks.
2517

2518
    """
2519
    for net in self.networks:
2520
      if self.stats:
2521
        self.curstats = self.stats.get(net.uuid, None)
2522
      else:
2523
        self.curstats = None
2524
      yield net
2525

    
2526

    
2527
_NETWORK_SIMPLE_FIELDS = {
2528
  "name": ("Network", QFT_TEXT, 0, "Name"),
2529
  "network": ("Subnet", QFT_TEXT, 0, "IPv4 subnet"),
2530
  "gateway": ("Gateway", QFT_OTHER, 0, "IPv4 gateway"),
2531
  "network6": ("IPv6Subnet", QFT_OTHER, 0, "IPv6 subnet"),
2532
  "gateway6": ("IPv6Gateway", QFT_OTHER, 0, "IPv6 gateway"),
2533
  "mac_prefix": ("MacPrefix", QFT_OTHER, 0, "MAC address prefix"),
2534
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Network"),
2535
  "uuid": ("UUID", QFT_TEXT, 0, "Network UUID"),
2536
  }
2537

    
2538

    
2539
_NETWORK_STATS_FIELDS = {
2540
  "free_count": ("FreeCount", QFT_NUMBER, 0, "Number of available addresses"),
2541
  "reserved_count":
2542
    ("ReservedCount", QFT_NUMBER, 0, "Number of reserved addresses"),
2543
  "map": ("Map", QFT_TEXT, 0, "Actual mapping"),
2544
  "external_reservations":
2545
    ("ExternalReservations", QFT_TEXT, 0, "External reservations"),
2546
  }
2547

    
2548

    
2549
def _GetNetworkStatsField(field, kind, ctx, _):
2550
  """Gets the value of a "stats" field from L{NetworkQueryData}.
2551

2552
  @param field: Field name
2553
  @param kind: Data kind, one of L{constants.QFT_ALL}
2554
  @type ctx: L{NetworkQueryData}
2555

2556
  """
2557
  return _GetStatsField(field, kind, ctx.curstats)
2558

    
2559

    
2560
def _BuildNetworkFields():
2561
  """Builds list of fields for network queries.
2562

2563
  """
2564
  fields = [
2565
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
2566
     lambda ctx, inst: list(inst.GetTags())),
2567
    ]
2568

    
2569
  # Add simple fields
2570
  fields.extend([
2571
    (_MakeField(name, title, kind, doc),
2572
     NETQ_CONFIG, 0, _GetItemAttr(name))
2573
     for (name, (title, kind, _, doc)) in _NETWORK_SIMPLE_FIELDS.items()])
2574

    
2575
  def _GetLength(getter):
2576
    return lambda ctx, network: len(getter(ctx)[network.uuid])
2577

    
2578
  def _GetSortedList(getter):
2579
    return lambda ctx, network: utils.NiceSort(getter(ctx)[network.uuid])
2580

    
2581
  network_to_groups = operator.attrgetter("network_to_groups")
2582
  network_to_instances = operator.attrgetter("network_to_instances")
2583

    
2584
  # Add fields for node groups
2585
  fields.extend([
2586
    (_MakeField("group_cnt", "NodeGroups", QFT_NUMBER, "Number of nodegroups"),
2587
     NETQ_GROUP, 0, _GetLength(network_to_groups)),
2588
    (_MakeField("group_list", "GroupList", QFT_OTHER,
2589
     "List of nodegroups (group name, NIC mode, NIC link)"),
2590
     NETQ_GROUP, 0, lambda ctx, network: network_to_groups(ctx)[network.uuid]),
2591
    ])
2592

    
2593
  # Add fields for instances
2594
  fields.extend([
2595
    (_MakeField("inst_cnt", "Instances", QFT_NUMBER, "Number of instances"),
2596
     NETQ_INST, 0, _GetLength(network_to_instances)),
2597
    (_MakeField("inst_list", "InstanceList", QFT_OTHER, "List of instances"),
2598
     NETQ_INST, 0, _GetSortedList(network_to_instances)),
2599
    ])
2600

    
2601
  # Add fields for usage statistics
2602
  fields.extend([
2603
    (_MakeField(name, title, kind, doc), NETQ_STATS, 0,
2604
    compat.partial(_GetNetworkStatsField, name, kind))
2605
    for (name, (title, kind, _, doc)) in _NETWORK_STATS_FIELDS.items()])
2606

    
2607
  return _PrepareFieldList(fields, [])
2608

    
2609
#: Fields for cluster information
2610
CLUSTER_FIELDS = _BuildClusterFields()
2611

    
2612
#: Fields available for node queries
2613
NODE_FIELDS = _BuildNodeFields()
2614

    
2615
#: Fields available for instance queries
2616
INSTANCE_FIELDS = _BuildInstanceFields()
2617

    
2618
#: Fields available for lock queries
2619
LOCK_FIELDS = _BuildLockFields()
2620

    
2621
#: Fields available for node group queries
2622
GROUP_FIELDS = _BuildGroupFields()
2623

    
2624
#: Fields available for operating system queries
2625
OS_FIELDS = _BuildOsFields()
2626

    
2627
#: Fields available for extstorage provider queries
2628
EXTSTORAGE_FIELDS = _BuildExtStorageFields()
2629

    
2630
#: Fields available for job queries
2631
JOB_FIELDS = _BuildJobFields()
2632

    
2633
#: Fields available for exports
2634
EXPORT_FIELDS = _BuildExportFields()
2635

    
2636
#: Fields available for network queries
2637
NETWORK_FIELDS = _BuildNetworkFields()
2638

    
2639
#: All available resources
2640
ALL_FIELDS = {
2641
  constants.QR_CLUSTER: CLUSTER_FIELDS,
2642
  constants.QR_INSTANCE: INSTANCE_FIELDS,
2643
  constants.QR_NODE: NODE_FIELDS,
2644
  constants.QR_LOCK: LOCK_FIELDS,
2645
  constants.QR_GROUP: GROUP_FIELDS,
2646
  constants.QR_OS: OS_FIELDS,
2647
  constants.QR_EXTSTORAGE: EXTSTORAGE_FIELDS,
2648
  constants.QR_JOB: JOB_FIELDS,
2649
  constants.QR_EXPORT: EXPORT_FIELDS,
2650
  constants.QR_NETWORK: NETWORK_FIELDS,
2651
  }
2652

    
2653
#: All available field lists
2654
ALL_FIELD_LISTS = ALL_FIELDS.values()