Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ 3c286190

History | View | Annotate | Download (77.3 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 = frozenset([_FS_UNKNOWN, _FS_NODATA, _FS_UNAVAIL, _FS_OFFLINE])
141

    
142
#: VType to QFT mapping
143
_VTToQFT = {
144
  # TODO: fix validation of empty strings
145
  constants.VTYPE_STRING: QFT_OTHER, # since VTYPE_STRINGs can be empty
146
  constants.VTYPE_MAYBE_STRING: QFT_OTHER,
147
  constants.VTYPE_BOOL: QFT_BOOL,
148
  constants.VTYPE_SIZE: QFT_UNIT,
149
  constants.VTYPE_INT: QFT_NUMBER,
150
  }
151

    
152
_SERIAL_NO_DOC = "%s object serial number, incremented on each modification"
153

    
154

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

158
  """
159
  return _FS_UNKNOWN
160

    
161

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

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

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

172
  """
173
  result = []
174

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

    
182
    assert len(fdef) == 4
183

    
184
    result.append(fdef)
185

    
186
  return result
187

    
188

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

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

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

    
197

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

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

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

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

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

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

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

228
    """
229
    self._namefield = namefield
230

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

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

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

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

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

247
    @rtype: list
248

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

    
253
    return utils.UniqueSequence(self._names)
254

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

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

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

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

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

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

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

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

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

284
    """
285
    if datakind is not None:
286
      self._datakinds.add(datakind)
287

    
288
    self._NeedAllNames()
289

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

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

299
    """
300
    if datakind is not None:
301
      self._datakinds.add(datakind)
302

    
303
    if self._allnames:
304
      return
305

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

    
315

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

319
  """
320
  return op_fn(fn(ctx, item) for fn in sentences)
321

    
322

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

326
  """
327
  return op_fn(inner(ctx, item))
328

    
329

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

333
  """
334
  return op_fn(retrieval_fn(ctx, item), value)
335

    
336

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

340
  """
341
  return not fn(lhs, rhs)
342

    
343

    
344
def _PrepareRegex(pattern):
345
  """Compiles a regular expression.
346

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

    
353

    
354
def _PrepareSplitTimestamp(value):
355
  """Prepares a value for comparison by L{_MakeSplitTimestampComparison}.
356

357
  """
358
  if ht.TNumber(value):
359
    return value
360
  else:
361
    return utils.MergeTime(value)
362

    
363

    
364
def _MakeSplitTimestampComparison(fn):
365
  """Compares split timestamp values after converting to float.
366

367
  """
368
  return lambda lhs, rhs: fn(utils.MergeTime(lhs), rhs)
369

    
370

    
371
def _MakeComparisonChecks(fn):
372
  """Prepares flag-specific comparisons using a comparison function.
373

374
  """
375
  return [
376
    (QFF_SPLIT_TIMESTAMP, _MakeSplitTimestampComparison(fn),
377
     _PrepareSplitTimestamp),
378
    (QFF_JOB_ID, lambda lhs, rhs: fn(jstore.ParseJobId(lhs), rhs),
379
     jstore.ParseJobId),
380
    (None, fn, None),
381
    ]
382

    
383

    
384
class _FilterCompilerHelper:
385
  """Converts a query filter to a callable usable for filtering.
386

387
  """
388
  # String statement has no effect, pylint: disable=W0105
389

    
390
  #: How deep filters can be nested
391
  _LEVELS_MAX = 10
392

    
393
  # Unique identifiers for operator groups
394
  (_OPTYPE_LOGIC,
395
   _OPTYPE_UNARY,
396
   _OPTYPE_BINARY) = range(1, 4)
397

    
398
  """Functions for equality checks depending on field flags.
399

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

404
  Order matters. The first item with flags will be used. Flags are checked
405
  using binary AND.
406

407
  """
408
  _EQUALITY_CHECKS = [
409
    (QFF_HOSTNAME,
410
     lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
411
                                               case_sensitive=False),
412
     None),
413
    (QFF_SPLIT_TIMESTAMP, _MakeSplitTimestampComparison(operator.eq),
414
     _PrepareSplitTimestamp),
415
    (None, operator.eq, None),
416
    ]
417

    
418
  """Known operators
419

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

423
    - C{_OPTYPE_LOGIC}: Callable taking any number of arguments; used by
424
      L{_HandleLogicOp}
425
    - C{_OPTYPE_UNARY}: Always C{None}; details handled by L{_HandleUnaryOp}
426
    - C{_OPTYPE_BINARY}: Callable taking exactly two parameters, the left- and
427
      right-hand side of the operator, used by L{_HandleBinaryOp}
428

429
  """
430
  _OPS = {
431
    # Logic operators
432
    qlang.OP_OR: (_OPTYPE_LOGIC, compat.any),
433
    qlang.OP_AND: (_OPTYPE_LOGIC, compat.all),
434

    
435
    # Unary operators
436
    qlang.OP_NOT: (_OPTYPE_UNARY, None),
437
    qlang.OP_TRUE: (_OPTYPE_UNARY, None),
438

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

    
456
  def __init__(self, fields):
457
    """Initializes this class.
458

459
    @param fields: Field definitions (return value of L{_PrepareFieldList})
460

461
    """
462
    self._fields = fields
463
    self._hints = None
464
    self._op_handler = None
465

    
466
  def __call__(self, hints, qfilter):
467
    """Converts a query filter into a callable function.
468

469
    @type hints: L{_FilterHints} or None
470
    @param hints: Callbacks doing analysis on filter
471
    @type qfilter: list
472
    @param qfilter: Filter structure
473
    @rtype: callable
474
    @return: Function receiving context and item as parameters, returning
475
             boolean as to whether item matches filter
476

477
    """
478
    self._op_handler = {
479
      self._OPTYPE_LOGIC:
480
        (self._HandleLogicOp, getattr(hints, "NoteLogicOp", None)),
481
      self._OPTYPE_UNARY:
482
        (self._HandleUnaryOp, getattr(hints, "NoteUnaryOp", None)),
483
      self._OPTYPE_BINARY:
484
        (self._HandleBinaryOp, getattr(hints, "NoteBinaryOp", None)),
485
      }
486

    
487
    try:
488
      filter_fn = self._Compile(qfilter, 0)
489
    finally:
490
      self._op_handler = None
491

    
492
    return filter_fn
493

    
494
  def _Compile(self, qfilter, level):
495
    """Inner function for converting filters.
496

497
    Calls the correct handler functions for the top-level operator. This
498
    function is called recursively (e.g. for logic operators).
499

500
    """
501
    if not (isinstance(qfilter, (list, tuple)) and qfilter):
502
      raise errors.ParameterError("Invalid filter on level %s" % level)
503

    
504
    # Limit recursion
505
    if level >= self._LEVELS_MAX:
506
      raise errors.ParameterError("Only up to %s levels are allowed (filter"
507
                                  " nested too deep)" % self._LEVELS_MAX)
508

    
509
    # Create copy to be modified
510
    operands = qfilter[:]
511
    op = operands.pop(0)
512

    
513
    try:
514
      (kind, op_data) = self._OPS[op]
515
    except KeyError:
516
      raise errors.ParameterError("Unknown operator '%s'" % op)
517

    
518
    (handler, hints_cb) = self._op_handler[kind]
519

    
520
    return handler(hints_cb, level, op, op_data, operands)
521

    
522
  def _LookupField(self, name):
523
    """Returns a field definition by name.
524

525
    """
526
    try:
527
      return self._fields[name]
528
    except KeyError:
529
      raise errors.ParameterError("Unknown field '%s'" % name)
530

    
531
  def _HandleLogicOp(self, hints_fn, level, op, op_fn, operands):
532
    """Handles logic operators.
533

534
    @type hints_fn: callable
535
    @param hints_fn: Callback doing some analysis on the filter
536
    @type level: integer
537
    @param level: Current depth
538
    @type op: string
539
    @param op: Operator
540
    @type op_fn: callable
541
    @param op_fn: Function implementing operator
542
    @type operands: list
543
    @param operands: List of operands
544

545
    """
546
    if hints_fn:
547
      hints_fn(op)
548

    
549
    return compat.partial(_WrapLogicOp, op_fn,
550
                          [self._Compile(op, level + 1) for op in operands])
551

    
552
  def _HandleUnaryOp(self, hints_fn, level, op, op_fn, operands):
553
    """Handles unary operators.
554

555
    @type hints_fn: callable
556
    @param hints_fn: Callback doing some analysis on the filter
557
    @type level: integer
558
    @param level: Current depth
559
    @type op: string
560
    @param op: Operator
561
    @type op_fn: callable
562
    @param op_fn: Function implementing operator
563
    @type operands: list
564
    @param operands: List of operands
565

566
    """
567
    assert op_fn is None
568

    
569
    if len(operands) != 1:
570
      raise errors.ParameterError("Unary operator '%s' expects exactly one"
571
                                  " operand" % op)
572

    
573
    if op == qlang.OP_TRUE:
574
      (_, datakind, _, retrieval_fn) = self._LookupField(operands[0])
575

    
576
      if hints_fn:
577
        hints_fn(op, datakind)
578

    
579
      op_fn = operator.truth
580
      arg = retrieval_fn
581
    elif op == qlang.OP_NOT:
582
      if hints_fn:
583
        hints_fn(op, None)
584

    
585
      op_fn = operator.not_
586
      arg = self._Compile(operands[0], level + 1)
587
    else:
588
      raise errors.ProgrammerError("Can't handle operator '%s'" % op)
589

    
590
    return compat.partial(_WrapUnaryOp, op_fn, arg)
591

    
592
  def _HandleBinaryOp(self, hints_fn, level, op, op_data, operands):
593
    """Handles binary operators.
594

595
    @type hints_fn: callable
596
    @param hints_fn: Callback doing some analysis on the filter
597
    @type level: integer
598
    @param level: Current depth
599
    @type op: string
600
    @param op: Operator
601
    @param op_data: Functions implementing operators
602
    @type operands: list
603
    @param operands: List of operands
604

605
    """
606
    # Unused arguments, pylint: disable=W0613
607
    try:
608
      (name, value) = operands
609
    except (ValueError, TypeError):
610
      raise errors.ParameterError("Invalid binary operator, expected exactly"
611
                                  " two operands")
612

    
613
    (fdef, datakind, field_flags, retrieval_fn) = self._LookupField(name)
614

    
615
    assert fdef.kind != QFT_UNKNOWN
616

    
617
    # TODO: Type conversions?
618

    
619
    verify_fn = _VERIFY_FN[fdef.kind]
620
    if not verify_fn(value):
621
      raise errors.ParameterError("Unable to compare field '%s' (type '%s')"
622
                                  " with '%s', expected %s" %
623
                                  (name, fdef.kind, value.__class__.__name__,
624
                                   verify_fn))
625

    
626
    if hints_fn:
627
      hints_fn(op, datakind, name, value)
628

    
629
    for (fn_flags, fn, valprepfn) in op_data:
630
      if fn_flags is None or fn_flags & field_flags:
631
        # Prepare value if necessary (e.g. compile regular expression)
632
        if valprepfn:
633
          value = valprepfn(value)
634

    
635
        return compat.partial(_WrapBinaryOp, fn, retrieval_fn, value)
636

    
637
    raise errors.ProgrammerError("Unable to find operator implementation"
638
                                 " (op '%s', flags %s)" % (op, field_flags))
639

    
640

    
641
def _CompileFilter(fields, hints, qfilter):
642
  """Converts a query filter into a callable function.
643

644
  See L{_FilterCompilerHelper} for details.
645

646
  @rtype: callable
647

648
  """
649
  return _FilterCompilerHelper(fields)(hints, qfilter)
650

    
651

    
652
class Query:
653
  def __init__(self, fieldlist, selected, qfilter=None, namefield=None):
654
    """Initializes this class.
655

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

663
    Users of this class can call L{RequestedData} before preparing the data
664
    container to determine what data is needed.
665

666
    @type fieldlist: dictionary
667
    @param fieldlist: Field definitions
668
    @type selected: list of strings
669
    @param selected: List of selected fields
670

671
    """
672
    assert namefield is None or namefield in fieldlist
673

    
674
    self._fields = _GetQueryFields(fieldlist, selected)
675

    
676
    self._filter_fn = None
677
    self._requested_names = None
678
    self._filter_datakinds = frozenset()
679

    
680
    if qfilter is not None:
681
      # Collect requested names if wanted
682
      if namefield:
683
        hints = _FilterHints(namefield)
684
      else:
685
        hints = None
686

    
687
      # Build filter function
688
      self._filter_fn = _CompileFilter(fieldlist, hints, qfilter)
689
      if hints:
690
        self._requested_names = hints.RequestedNames()
691
        self._filter_datakinds = hints.ReferencedData()
692

    
693
    if namefield is None:
694
      self._name_fn = None
695
    else:
696
      (_, _, _, self._name_fn) = fieldlist[namefield]
697

    
698
  def RequestedNames(self):
699
    """Returns all names referenced in the filter.
700

701
    If there is no filter or operators are preventing determining the exact
702
    names, C{None} is returned.
703

704
    """
705
    return self._requested_names
706

    
707
  def RequestedData(self):
708
    """Gets requested kinds of data.
709

710
    @rtype: frozenset
711

712
    """
713
    return (self._filter_datakinds |
714
            frozenset(datakind for (_, datakind, _, _) in self._fields
715
                      if datakind is not None))
716

    
717
  def GetFields(self):
718
    """Returns the list of fields for this query.
719

720
    Includes unknown fields.
721

722
    @rtype: List of L{objects.QueryFieldDefinition}
723

724
    """
725
    return GetAllFields(self._fields)
726

    
727
  def Query(self, ctx, sort_by_name=True):
728
    """Execute a query.
729

730
    @param ctx: Data container passed to field retrieval functions, must
731
      support iteration using C{__iter__}
732
    @type sort_by_name: boolean
733
    @param sort_by_name: Whether to sort by name or keep the input data's
734
      ordering
735

736
    """
737
    sort = (self._name_fn and sort_by_name)
738

    
739
    result = []
740

    
741
    for idx, item in enumerate(ctx):
742
      if not (self._filter_fn is None or self._filter_fn(ctx, item)):
743
        continue
744

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

    
747
      # Verify result
748
      if __debug__:
749
        _VerifyResultRow(self._fields, row)
750

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

    
760
    if not sort:
761
      return result
762

    
763
    # TODO: Would "heapq" be more efficient than sorting?
764

    
765
    # Sorting in-place instead of using "sorted()"
766
    result.sort()
767

    
768
    assert not result or (len(result[0]) == 3 and len(result[-1]) == 3)
769

    
770
    return map(operator.itemgetter(2), result)
771

    
772
  def OldStyleQuery(self, ctx, sort_by_name=True):
773
    """Query with "old" query result format.
774

775
    See L{Query.Query} for arguments.
776

777
    """
778
    unknown = set(fdef.name for (fdef, _, _, _) in self._fields
779
                  if fdef.kind == QFT_UNKNOWN)
780
    if unknown:
781
      raise errors.OpPrereqError("Unknown output fields selected: %s" %
782
                                 (utils.CommaJoin(unknown), ),
783
                                 errors.ECODE_INVAL)
784

    
785
    return [[value for (_, value) in row]
786
            for row in self.Query(ctx, sort_by_name=sort_by_name)]
787

    
788

    
789
def _ProcessResult(value):
790
  """Converts result values into externally-visible ones.
791

792
  """
793
  if value is _FS_UNKNOWN:
794
    return (RS_UNKNOWN, None)
795
  elif value is _FS_NODATA:
796
    return (RS_NODATA, None)
797
  elif value is _FS_UNAVAIL:
798
    return (RS_UNAVAIL, None)
799
  elif value is _FS_OFFLINE:
800
    return (RS_OFFLINE, None)
801
  else:
802
    return (RS_NORMAL, value)
803

    
804

    
805
def _VerifyResultRow(fields, row):
806
  """Verifies the contents of a query result row.
807

808
  @type fields: list
809
  @param fields: Field definitions for result
810
  @type row: list of tuples
811
  @param row: Row data
812

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

    
826

    
827
def _FieldDictKey((fdef, _, flags, fn)):
828
  """Generates key for field dictionary.
829

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

    
840
  return fdef.name
841

    
842

    
843
def _PrepareFieldList(fields, aliases):
844
  """Prepares field list for use by L{Query}.
845

846
  Converts the list to a dictionary and does some verification.
847

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

858
  """
859
  if __debug__:
860
    duplicates = utils.FindDuplicates(fdef.title.lower()
861
                                      for (fdef, _, _, _) in fields)
862
    assert not duplicates, "Duplicate title(s) found: %r" % duplicates
863

    
864
  result = utils.SequenceToDict(fields, key=_FieldDictKey)
865

    
866
  for alias, target in aliases:
867
    assert alias not in result, "Alias %s overrides an existing field" % alias
868
    assert target in result, "Missing target %s for alias %s" % (target, alias)
869
    (fdef, k, flags, fn) = result[target]
870
    fdef = fdef.Copy()
871
    fdef.name = alias
872
    result[alias] = (fdef, k, flags, fn)
873

    
874
  assert len(result) == len(fields) + len(aliases)
875
  assert compat.all(name == fdef.name
876
                    for (name, (fdef, _, _, _)) in result.items())
877

    
878
  return result
879

    
880

    
881
def GetQueryResponse(query, ctx, sort_by_name=True):
882
  """Prepares the response for a query.
883

884
  @type query: L{Query}
885
  @param ctx: Data container, see L{Query.Query}
886
  @type sort_by_name: boolean
887
  @param sort_by_name: Whether to sort by name or keep the input data's
888
    ordering
889

890
  """
891
  return objects.QueryResponse(data=query.Query(ctx, sort_by_name=sort_by_name),
892
                               fields=query.GetFields()).ToDict()
893

    
894

    
895
def QueryFields(fielddefs, selected):
896
  """Returns list of available fields.
897

898
  @type fielddefs: dict
899
  @param fielddefs: Field definitions
900
  @type selected: list of strings
901
  @param selected: List of selected fields
902
  @return: List of L{objects.QueryFieldDefinition}
903

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

    
913
  return objects.QueryFieldsResponse(fields=fdefs).ToDict()
914

    
915

    
916
def _MakeField(name, title, kind, doc):
917
  """Wrapper for creating L{objects.QueryFieldDefinition} instances.
918

919
  @param name: Field name as a regular expression
920
  @param title: Human-readable title
921
  @param kind: Field type
922
  @param doc: Human-readable description
923

924
  """
925
  return objects.QueryFieldDefinition(name=name, title=title, kind=kind,
926
                                      doc=doc)
927

    
928

    
929
def _StaticValueInner(value, ctx, _): # pylint: disable=W0613
930
  """Returns a static value.
931

932
  """
933
  return value
934

    
935

    
936
def _StaticValue(value):
937
  """Prepares a function to return a static value.
938

939
  """
940
  return compat.partial(_StaticValueInner, value)
941

    
942

    
943
def _GetNodeRole(node, master_name):
944
  """Determine node role.
945

946
  @type node: L{objects.Node}
947
  @param node: Node object
948
  @type master_name: string
949
  @param master_name: Master node name
950

951
  """
952
  if node.name == master_name:
953
    return constants.NR_MASTER
954
  elif node.master_candidate:
955
    return constants.NR_MCANDIDATE
956
  elif node.drained:
957
    return constants.NR_DRAINED
958
  elif node.offline:
959
    return constants.NR_OFFLINE
960
  else:
961
    return constants.NR_REGULAR
962

    
963

    
964
def _GetItemAttr(attr):
965
  """Returns a field function to return an attribute of the item.
966

967
  @param attr: Attribute name
968

969
  """
970
  getter = operator.attrgetter(attr)
971
  return lambda _, item: getter(item)
972

    
973

    
974
def _GetNDParam(name):
975
  """Return a field function to return an ND parameter out of the context.
976

977
  """
978
  def _helper(ctx, _):
979
    if ctx.ndparams is None:
980
      return _FS_UNAVAIL
981
    else:
982
      return ctx.ndparams.get(name, None)
983
  return _helper
984

    
985

    
986
def _BuildNDFields(is_group):
987
  """Builds all the ndparam fields.
988

989
  @param is_group: whether this is called at group or node level
990

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

    
1003

    
1004
def _ConvWrapInner(convert, fn, ctx, item):
1005
  """Wrapper for converting values.
1006

1007
  @param convert: Conversion function receiving value as single parameter
1008
  @param fn: Retrieval function
1009

1010
  """
1011
  value = fn(ctx, item)
1012

    
1013
  # Is the value an abnormal status?
1014
  if compat.any(value is fs for fs in _FS_ALL):
1015
    # Return right away
1016
    return value
1017

    
1018
  # TODO: Should conversion function also receive context, item or both?
1019
  return convert(value)
1020

    
1021

    
1022
def _ConvWrap(convert, fn):
1023
  """Convenience wrapper for L{_ConvWrapInner}.
1024

1025
  @param convert: Conversion function receiving value as single parameter
1026
  @param fn: Retrieval function
1027

1028
  """
1029
  return compat.partial(_ConvWrapInner, convert, fn)
1030

    
1031

    
1032
def _GetItemTimestamp(getter):
1033
  """Returns function for getting timestamp of item.
1034

1035
  @type getter: callable
1036
  @param getter: Function to retrieve timestamp attribute
1037

1038
  """
1039
  def fn(_, item):
1040
    """Returns a timestamp of item.
1041

1042
    """
1043
    timestamp = getter(item)
1044
    if timestamp is None:
1045
      # Old configs might not have all timestamps
1046
      return _FS_UNAVAIL
1047
    else:
1048
      return timestamp
1049

    
1050
  return fn
1051

    
1052

    
1053
def _GetItemTimestampFields(datatype):
1054
  """Returns common timestamp fields.
1055

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

1058
  """
1059
  return [
1060
    (_MakeField("ctime", "CTime", QFT_TIMESTAMP, "Creation timestamp"),
1061
     datatype, 0, _GetItemTimestamp(operator.attrgetter("ctime"))),
1062
    (_MakeField("mtime", "MTime", QFT_TIMESTAMP, "Modification timestamp"),
1063
     datatype, 0, _GetItemTimestamp(operator.attrgetter("mtime"))),
1064
    ]
1065

    
1066

    
1067
class NodeQueryData:
1068
  """Data container for node data queries.
1069

1070
  """
1071
  def __init__(self, nodes, live_data, master_name, node_to_primary,
1072
               node_to_secondary, groups, oob_support, cluster):
1073
    """Initializes this class.
1074

1075
    """
1076
    self.nodes = nodes
1077
    self.live_data = live_data
1078
    self.master_name = master_name
1079
    self.node_to_primary = node_to_primary
1080
    self.node_to_secondary = node_to_secondary
1081
    self.groups = groups
1082
    self.oob_support = oob_support
1083
    self.cluster = cluster
1084

    
1085
    # Used for individual rows
1086
    self.curlive_data = None
1087
    self.ndparams = None
1088

    
1089
  def __iter__(self):
1090
    """Iterate over all nodes.
1091

1092
    This function has side-effects and only one instance of the resulting
1093
    generator should be used at a time.
1094

1095
    """
1096
    for node in self.nodes:
1097
      group = self.groups.get(node.group, None)
1098
      if group is None:
1099
        self.ndparams = None
1100
      else:
1101
        self.ndparams = self.cluster.FillND(node, group)
1102
      if self.live_data:
1103
        self.curlive_data = self.live_data.get(node.name, None)
1104
      else:
1105
        self.curlive_data = None
1106
      yield node
1107

    
1108

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

    
1123

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

    
1148

    
1149
def _GetGroup(cb):
1150
  """Build function for calling another function with an node group.
1151

1152
  @param cb: The callback to be called with the nodegroup
1153

1154
  """
1155
  def fn(ctx, node):
1156
    """Get group data for a node.
1157

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

1162
    """
1163
    ng = ctx.groups.get(node.group, None)
1164
    if ng is None:
1165
      # Nodes always have a group, or the configuration is corrupt
1166
      return _FS_UNAVAIL
1167

    
1168
    return cb(ctx, node, ng)
1169

    
1170
  return fn
1171

    
1172

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

1176
  @type ctx: L{NodeQueryData}
1177
  @type node: L{objects.Node}
1178
  @param node: Node object
1179
  @type ng: L{objects.NodeGroup}
1180
  @param ng: The node group this node belongs to
1181

1182
  """
1183
  return ng.name
1184

    
1185

    
1186
def _GetNodePower(ctx, node):
1187
  """Returns the node powered state
1188

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

1193
  """
1194
  if ctx.oob_support[node.name]:
1195
    return node.powered
1196

    
1197
  return _FS_UNAVAIL
1198

    
1199

    
1200
def _GetNdParams(ctx, node, ng):
1201
  """Returns the ndparams for this node.
1202

1203
  @type ctx: L{NodeQueryData}
1204
  @type node: L{objects.Node}
1205
  @param node: Node object
1206
  @type ng: L{objects.NodeGroup}
1207
  @param ng: The node group this node belongs to
1208

1209
  """
1210
  return ctx.cluster.SimpleFillND(ng.FillND(node))
1211

    
1212

    
1213
def _GetLiveNodeField(field, kind, ctx, node):
1214
  """Gets the value of a "live" field from L{NodeQueryData}.
1215

1216
  @param field: Live field name
1217
  @param kind: Data kind, one of L{constants.QFT_ALL}
1218
  @type ctx: L{NodeQueryData}
1219
  @type node: L{objects.Node}
1220
  @param node: Node object
1221

1222
  """
1223
  if node.offline:
1224
    return _FS_OFFLINE
1225

    
1226
  if not node.vm_capable:
1227
    return _FS_UNAVAIL
1228

    
1229
  if not ctx.curlive_data:
1230
    return _FS_NODATA
1231

    
1232
  try:
1233
    value = ctx.curlive_data[field]
1234
  except KeyError:
1235
    return _FS_UNAVAIL
1236

    
1237
  if kind == QFT_TEXT:
1238
    return value
1239

    
1240
  assert kind in (QFT_NUMBER, QFT_UNIT)
1241

    
1242
  # Try to convert into number
1243
  try:
1244
    return int(value)
1245
  except (ValueError, TypeError):
1246
    logging.exception("Failed to convert node field '%s' (value %r) to int",
1247
                      value, field)
1248
    return _FS_UNAVAIL
1249

    
1250

    
1251
def _GetNodeHvState(_, node):
1252
  """Converts node's hypervisor state for query result.
1253

1254
  """
1255
  hv_state = node.hv_state
1256

    
1257
  if hv_state is None:
1258
    return _FS_UNAVAIL
1259

    
1260
  return dict((name, value.ToDict()) for (name, value) in hv_state.items())
1261

    
1262

    
1263
def _GetNodeDiskState(_, node):
1264
  """Converts node's disk state for query result.
1265

1266
  """
1267
  disk_state = node.disk_state
1268

    
1269
  if disk_state is None:
1270
    return _FS_UNAVAIL
1271

    
1272
  return dict((disk_kind, dict((name, value.ToDict())
1273
                               for (name, value) in kind_state.items()))
1274
              for (disk_kind, kind_state) in disk_state.items())
1275

    
1276

    
1277
def _BuildNodeFields():
1278
  """Builds list of fields for node queries.
1279

1280
  """
1281
  fields = [
1282
    (_MakeField("pip", "PrimaryIP", QFT_TEXT, "Primary IP address"),
1283
     NQ_CONFIG, 0, _GetItemAttr("primary_ip")),
1284
    (_MakeField("sip", "SecondaryIP", QFT_TEXT, "Secondary IP address"),
1285
     NQ_CONFIG, 0, _GetItemAttr("secondary_ip")),
1286
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0,
1287
     lambda ctx, node: list(node.GetTags())),
1288
    (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"),
1289
     NQ_CONFIG, 0, lambda ctx, node: node.name == ctx.master_name),
1290
    (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0,
1291
     _GetGroup(_GetNodeGroup)),
1292
    (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"),
1293
     NQ_CONFIG, 0, _GetItemAttr("group")),
1294
    (_MakeField("powered", "Powered", QFT_BOOL,
1295
                "Whether node is thought to be powered on"),
1296
     NQ_OOB, 0, _GetNodePower),
1297
    (_MakeField("ndparams", "NodeParameters", QFT_OTHER,
1298
                "Merged node parameters"),
1299
     NQ_GROUP, 0, _GetGroup(_GetNdParams)),
1300
    (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER,
1301
                "Custom node parameters"),
1302
      NQ_GROUP, 0, _GetItemAttr("ndparams")),
1303
    (_MakeField("hv_state", "HypervisorState", QFT_OTHER, "Hypervisor state"),
1304
     NQ_CONFIG, 0, _GetNodeHvState),
1305
    (_MakeField("disk_state", "DiskState", QFT_OTHER, "Disk state"),
1306
     NQ_CONFIG, 0, _GetNodeDiskState),
1307
    ]
1308

    
1309
  fields.extend(_BuildNDFields(False))
1310

    
1311
  # Node role
1312
  role_values = (constants.NR_MASTER, constants.NR_MCANDIDATE,
1313
                 constants.NR_REGULAR, constants.NR_DRAINED,
1314
                 constants.NR_OFFLINE)
1315
  role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate,"
1316
              " \"%s\" for regular, \"%s\" for drained, \"%s\" for offline" %
1317
              role_values)
1318
  fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
1319
                 lambda ctx, node: _GetNodeRole(node, ctx.master_name)))
1320
  assert set(role_values) == constants.NR_ALL
1321

    
1322
  def _GetLength(getter):
1323
    return lambda ctx, node: len(getter(ctx)[node.name])
1324

    
1325
  def _GetList(getter):
1326
    return lambda ctx, node: list(getter(ctx)[node.name])
1327

    
1328
  # Add fields operating on instance lists
1329
  for prefix, titleprefix, docword, getter in \
1330
      [("p", "Pri", "primary", operator.attrgetter("node_to_primary")),
1331
       ("s", "Sec", "secondary", operator.attrgetter("node_to_secondary"))]:
1332
    # TODO: Allow filterting by hostname in list
1333
    fields.extend([
1334
      (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER,
1335
                  "Number of instances with this node as %s" % docword),
1336
       NQ_INST, 0, _GetLength(getter)),
1337
      (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
1338
                  QFT_OTHER,
1339
                  "List of instances with this node as %s" % docword),
1340
       NQ_INST, 0, _GetList(getter)),
1341
      ])
1342

    
1343
  # Add simple fields
1344
  fields.extend([
1345
    (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1346
    for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()])
1347

    
1348
  # Add fields requiring live data
1349
  fields.extend([
1350
    (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1351
     compat.partial(_GetLiveNodeField, nfield, kind))
1352
    for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()])
1353

    
1354
  # Add timestamps
1355
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1356

    
1357
  return _PrepareFieldList(fields, [])
1358

    
1359

    
1360
class InstanceQueryData:
1361
  """Data container for instance data queries.
1362

1363
  """
1364
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
1365
               live_data, wrongnode_inst, console, nodes, groups):
1366
    """Initializes this class.
1367

1368
    @param instances: List of instance objects
1369
    @param cluster: Cluster object
1370
    @type disk_usage: dict; instance name as key
1371
    @param disk_usage: Per-instance disk usage
1372
    @type offline_nodes: list of strings
1373
    @param offline_nodes: List of offline nodes
1374
    @type bad_nodes: list of strings
1375
    @param bad_nodes: List of faulty nodes
1376
    @type live_data: dict; instance name as key
1377
    @param live_data: Per-instance live data
1378
    @type wrongnode_inst: set
1379
    @param wrongnode_inst: Set of instances running on wrong node(s)
1380
    @type console: dict; instance name as key
1381
    @param console: Per-instance console information
1382
    @type nodes: dict; node name as key
1383
    @param nodes: Node objects
1384

1385
    """
1386
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
1387
           "Offline nodes not included in bad nodes"
1388
    assert not (set(live_data.keys()) & set(bad_nodes)), \
1389
           "Found live data for bad or offline nodes"
1390

    
1391
    self.instances = instances
1392
    self.cluster = cluster
1393
    self.disk_usage = disk_usage
1394
    self.offline_nodes = offline_nodes
1395
    self.bad_nodes = bad_nodes
1396
    self.live_data = live_data
1397
    self.wrongnode_inst = wrongnode_inst
1398
    self.console = console
1399
    self.nodes = nodes
1400
    self.groups = groups
1401

    
1402
    # Used for individual rows
1403
    self.inst_hvparams = None
1404
    self.inst_beparams = None
1405
    self.inst_osparams = None
1406
    self.inst_nicparams = None
1407

    
1408
  def __iter__(self):
1409
    """Iterate over all instances.
1410

1411
    This function has side-effects and only one instance of the resulting
1412
    generator should be used at a time.
1413

1414
    """
1415
    for inst in self.instances:
1416
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1417
      self.inst_beparams = self.cluster.FillBE(inst)
1418
      self.inst_osparams = self.cluster.SimpleFillOS(inst.os, inst.osparams)
1419
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1420
                             for nic in inst.nics]
1421

    
1422
      yield inst
1423

    
1424

    
1425
def _GetInstOperState(ctx, inst):
1426
  """Get instance's operational status.
1427

1428
  @type ctx: L{InstanceQueryData}
1429
  @type inst: L{objects.Instance}
1430
  @param inst: Instance object
1431

1432
  """
1433
  # Can't use RS_OFFLINE here as it would describe the instance to
1434
  # be offline when we actually don't know due to missing data
1435
  if inst.primary_node in ctx.bad_nodes:
1436
    return _FS_NODATA
1437
  else:
1438
    return bool(ctx.live_data.get(inst.name))
1439

    
1440

    
1441
def _GetInstLiveData(name):
1442
  """Build function for retrieving live data.
1443

1444
  @type name: string
1445
  @param name: Live data field name
1446

1447
  """
1448
  def fn(ctx, inst):
1449
    """Get live data for an instance.
1450

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

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

    
1462
    if inst.name in ctx.live_data:
1463
      data = ctx.live_data[inst.name]
1464
      if name in data:
1465
        return data[name]
1466

    
1467
    return _FS_UNAVAIL
1468

    
1469
  return fn
1470

    
1471

    
1472
def _GetInstStatus(ctx, inst):
1473
  """Get instance status.
1474

1475
  @type ctx: L{InstanceQueryData}
1476
  @type inst: L{objects.Instance}
1477
  @param inst: Instance object
1478

1479
  """
1480
  if inst.primary_node in ctx.offline_nodes:
1481
    return constants.INSTST_NODEOFFLINE
1482

    
1483
  if inst.primary_node in ctx.bad_nodes:
1484
    return constants.INSTST_NODEDOWN
1485

    
1486
  if bool(ctx.live_data.get(inst.name)):
1487
    if inst.name in ctx.wrongnode_inst:
1488
      return constants.INSTST_WRONGNODE
1489
    elif inst.admin_state == constants.ADMINST_UP:
1490
      return constants.INSTST_RUNNING
1491
    else:
1492
      return constants.INSTST_ERRORUP
1493

    
1494
  if inst.admin_state == constants.ADMINST_UP:
1495
    return constants.INSTST_ERRORDOWN
1496
  elif inst.admin_state == constants.ADMINST_DOWN:
1497
    return constants.INSTST_ADMINDOWN
1498

    
1499
  return constants.INSTST_ADMINOFFLINE
1500

    
1501

    
1502
def _GetInstDiskSize(index):
1503
  """Build function for retrieving disk size.
1504

1505
  @type index: int
1506
  @param index: Disk index
1507

1508
  """
1509
  def fn(_, inst):
1510
    """Get size of a disk.
1511

1512
    @type inst: L{objects.Instance}
1513
    @param inst: Instance object
1514

1515
    """
1516
    try:
1517
      return inst.disks[index].size
1518
    except IndexError:
1519
      return _FS_UNAVAIL
1520

    
1521
  return fn
1522

    
1523

    
1524
def _GetInstNic(index, cb):
1525
  """Build function for calling another function with an instance NIC.
1526

1527
  @type index: int
1528
  @param index: NIC index
1529
  @type cb: callable
1530
  @param cb: Callback
1531

1532
  """
1533
  def fn(ctx, inst):
1534
    """Call helper function with instance NIC.
1535

1536
    @type ctx: L{InstanceQueryData}
1537
    @type inst: L{objects.Instance}
1538
    @param inst: Instance object
1539

1540
    """
1541
    try:
1542
      nic = inst.nics[index]
1543
    except IndexError:
1544
      return _FS_UNAVAIL
1545

    
1546
    return cb(ctx, index, nic)
1547

    
1548
  return fn
1549

    
1550

    
1551
def _GetInstNicNetwork(ctx, _, nic): # pylint: disable=W0613
1552
  """Get a NIC's Network.
1553

1554
  @type ctx: L{InstanceQueryData}
1555
  @type nic: L{objects.NIC}
1556
  @param nic: NIC object
1557

1558
  """
1559
  if nic.network is None:
1560
    return _FS_UNAVAIL
1561
  else:
1562
    return nic.network
1563

    
1564

    
1565
def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613
1566
  """Get a NIC's IP address.
1567

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

1572
  """
1573
  if nic.ip is None:
1574
    return _FS_UNAVAIL
1575
  else:
1576
    return nic.ip
1577

    
1578

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

1582
  @type ctx: L{InstanceQueryData}
1583
  @type index: int
1584
  @param index: NIC index
1585

1586
  """
1587
  assert len(ctx.inst_nicparams) >= index
1588

    
1589
  nicparams = ctx.inst_nicparams[index]
1590

    
1591
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1592
    return nicparams[constants.NIC_LINK]
1593
  else:
1594
    return _FS_UNAVAIL
1595

    
1596

    
1597
def _GetInstAllNicBridges(ctx, inst):
1598
  """Get all network bridges for an instance.
1599

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

1604
  """
1605
  assert len(ctx.inst_nicparams) == len(inst.nics)
1606

    
1607
  result = []
1608

    
1609
  for nicp in ctx.inst_nicparams:
1610
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1611
      result.append(nicp[constants.NIC_LINK])
1612
    else:
1613
      result.append(None)
1614

    
1615
  assert len(result) == len(inst.nics)
1616

    
1617
  return result
1618

    
1619

    
1620
def _GetInstNicParam(name):
1621
  """Build function for retrieving a NIC parameter.
1622

1623
  @type name: string
1624
  @param name: Parameter name
1625

1626
  """
1627
  def fn(ctx, index, _):
1628
    """Get a NIC's bridge.
1629

1630
    @type ctx: L{InstanceQueryData}
1631
    @type inst: L{objects.Instance}
1632
    @param inst: Instance object
1633
    @type nic: L{objects.NIC}
1634
    @param nic: NIC object
1635

1636
    """
1637
    assert len(ctx.inst_nicparams) >= index
1638
    return ctx.inst_nicparams[index][name]
1639

    
1640
  return fn
1641

    
1642

    
1643
def _GetInstanceNetworkFields():
1644
  """Get instance fields involving network interfaces.
1645

1646
  @return: Tuple containing list of field definitions used as input for
1647
    L{_PrepareFieldList} and a list of aliases
1648

1649
  """
1650
  nic_mac_fn = lambda ctx, _, nic: nic.mac
1651
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1652
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1653

    
1654
  fields = [
1655
    # All NICs
1656
    (_MakeField("nic.count", "NICs", QFT_NUMBER,
1657
                "Number of network interfaces"),
1658
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.nics)),
1659
    (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER,
1660
                "List containing each network interface's MAC address"),
1661
     IQ_CONFIG, 0, lambda ctx, inst: [nic.mac for nic in inst.nics]),
1662
    (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
1663
                "List containing each network interface's IP address"),
1664
     IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
1665
    (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
1666
                "List containing each network interface's mode"), IQ_CONFIG, 0,
1667
     lambda ctx, inst: [nicp[constants.NIC_MODE]
1668
                        for nicp in ctx.inst_nicparams]),
1669
    (_MakeField("nic.links", "NIC_links", QFT_OTHER,
1670
                "List containing each network interface's link"), IQ_CONFIG, 0,
1671
     lambda ctx, inst: [nicp[constants.NIC_LINK]
1672
                        for nicp in ctx.inst_nicparams]),
1673
    (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
1674
                "List containing each network interface's bridge"),
1675
     IQ_CONFIG, 0, _GetInstAllNicBridges),
1676
    (_MakeField("nic.networks", "NIC_networks", QFT_OTHER,
1677
                "List containing each interface's network"), IQ_CONFIG, 0,
1678
     lambda ctx, inst: [nic.network for nic in inst.nics]),
1679
    ]
1680

    
1681
  # NICs by number
1682
  for i in range(constants.MAX_NICS):
1683
    numtext = utils.FormatOrdinal(i + 1)
1684
    fields.extend([
1685
      (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT,
1686
                  "IP address of %s network interface" % numtext),
1687
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)),
1688
      (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
1689
                  "MAC address of %s network interface" % numtext),
1690
       IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
1691
      (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
1692
                  "Mode of %s network interface" % numtext),
1693
       IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
1694
      (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT,
1695
                  "Link of %s network interface" % numtext),
1696
       IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)),
1697
      (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
1698
                  "Bridge of %s network interface" % numtext),
1699
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
1700
      (_MakeField("nic.network/%s" % i, "NicNetwork/%s" % i, QFT_TEXT,
1701
                  "Network of %s network interface" % numtext),
1702
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicNetwork)),
1703
      ])
1704

    
1705
  aliases = [
1706
    # Legacy fields for first NIC
1707
    ("ip", "nic.ip/0"),
1708
    ("mac", "nic.mac/0"),
1709
    ("bridge", "nic.bridge/0"),
1710
    ("nic_mode", "nic.mode/0"),
1711
    ("nic_link", "nic.link/0"),
1712
    ("nic_network", "nic.network/0"),
1713
    ]
1714

    
1715
  return (fields, aliases)
1716

    
1717

    
1718
def _GetInstDiskUsage(ctx, inst):
1719
  """Get disk usage for an instance.
1720

1721
  @type ctx: L{InstanceQueryData}
1722
  @type inst: L{objects.Instance}
1723
  @param inst: Instance object
1724

1725
  """
1726
  usage = ctx.disk_usage[inst.name]
1727

    
1728
  if usage is None:
1729
    usage = 0
1730

    
1731
  return usage
1732

    
1733

    
1734
def _GetInstanceConsole(ctx, inst):
1735
  """Get console information for instance.
1736

1737
  @type ctx: L{InstanceQueryData}
1738
  @type inst: L{objects.Instance}
1739
  @param inst: Instance object
1740

1741
  """
1742
  consinfo = ctx.console[inst.name]
1743

    
1744
  if consinfo is None:
1745
    return _FS_UNAVAIL
1746

    
1747
  return consinfo
1748

    
1749

    
1750
def _GetInstanceDiskFields():
1751
  """Get instance fields involving disks.
1752

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

1755
  """
1756
  fields = [
1757
    (_MakeField("disk_usage", "DiskUsage", QFT_UNIT,
1758
                "Total disk space used by instance on each of its nodes;"
1759
                " this is not the disk size visible to the instance, but"
1760
                " the usage on the node"),
1761
     IQ_DISKUSAGE, 0, _GetInstDiskUsage),
1762
    (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"),
1763
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
1764
    (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
1765
     IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
1766
    ]
1767

    
1768
  # Disks by number
1769
  fields.extend([
1770
    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1771
                "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
1772
     IQ_CONFIG, 0, _GetInstDiskSize(i))
1773
    for i in range(constants.MAX_DISKS)])
1774

    
1775
  return fields
1776

    
1777

    
1778
def _GetInstanceParameterFields():
1779
  """Get instance fields involving parameters.
1780

1781
  @return: List of field definitions used as input for L{_PrepareFieldList}
1782

1783
  """
1784
  fields = [
1785
    # Filled parameters
1786
    (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
1787
                "Hypervisor parameters (merged)"),
1788
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
1789
    (_MakeField("beparams", "BackendParameters", QFT_OTHER,
1790
                "Backend parameters (merged)"),
1791
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
1792
    (_MakeField("osparams", "OpSysParameters", QFT_OTHER,
1793
                "Operating system parameters (merged)"),
1794
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_osparams),
1795

    
1796
    # Unfilled parameters
1797
    (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
1798
                "Custom hypervisor parameters"),
1799
     IQ_CONFIG, 0, _GetItemAttr("hvparams")),
1800
    (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
1801
                "Custom backend parameters",),
1802
     IQ_CONFIG, 0, _GetItemAttr("beparams")),
1803
    (_MakeField("custom_osparams", "CustomOpSysParameters", QFT_OTHER,
1804
                "Custom operating system parameters",),
1805
     IQ_CONFIG, 0, _GetItemAttr("osparams")),
1806
    (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
1807
                "Custom network interface parameters"),
1808
     IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1809
    ]
1810

    
1811
  # HV params
1812
  def _GetInstHvParam(name):
1813
    return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1814

    
1815
  fields.extend([
1816
    (_MakeField("hv/%s" % name,
1817
                constants.HVS_PARAMETER_TITLES.get(name, "hv/%s" % name),
1818
                _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1819
     IQ_CONFIG, 0, _GetInstHvParam(name))
1820
    for name, kind in constants.HVS_PARAMETER_TYPES.items()
1821
    if name not in constants.HVC_GLOBALS])
1822

    
1823
  # BE params
1824
  def _GetInstBeParam(name):
1825
    return lambda ctx, _: ctx.inst_beparams.get(name, None)
1826

    
1827
  fields.extend([
1828
    (_MakeField("be/%s" % name,
1829
                constants.BES_PARAMETER_TITLES.get(name, "be/%s" % name),
1830
                _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1831
     IQ_CONFIG, 0, _GetInstBeParam(name))
1832
    for name, kind in constants.BES_PARAMETER_TYPES.items()])
1833

    
1834
  return fields
1835

    
1836

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

    
1849

    
1850
def _GetInstNodeGroup(ctx, default, node_name):
1851
  """Gets group UUID of an instance node.
1852

1853
  @type ctx: L{InstanceQueryData}
1854
  @param default: Default value
1855
  @type node_name: string
1856
  @param node_name: Node name
1857

1858
  """
1859
  try:
1860
    node = ctx.nodes[node_name]
1861
  except KeyError:
1862
    return default
1863
  else:
1864
    return node.group
1865

    
1866

    
1867
def _GetInstNodeGroupName(ctx, default, node_name):
1868
  """Gets group name of an instance node.
1869

1870
  @type ctx: L{InstanceQueryData}
1871
  @param default: Default value
1872
  @type node_name: string
1873
  @param node_name: Node name
1874

1875
  """
1876
  try:
1877
    node = ctx.nodes[node_name]
1878
  except KeyError:
1879
    return default
1880

    
1881
  try:
1882
    group = ctx.groups[node.group]
1883
  except KeyError:
1884
    return default
1885

    
1886
  return group.name
1887

    
1888

    
1889
def _BuildInstanceFields():
1890
  """Builds list of fields for instance queries.
1891

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

    
1932
  # Add simple fields
1933
  fields.extend([
1934
    (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1935
    for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()])
1936

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

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

    
1968
  (network_fields, network_aliases) = _GetInstanceNetworkFields()
1969

    
1970
  fields.extend(network_fields)
1971
  fields.extend(_GetInstanceParameterFields())
1972
  fields.extend(_GetInstanceDiskFields())
1973
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1974

    
1975
  aliases = [
1976
    ("vcpus", "be/vcpus"),
1977
    ("be/memory", "be/maxmem"),
1978
    ("sda_size", "disk.size/0"),
1979
    ("sdb_size", "disk.size/1"),
1980
    ] + network_aliases
1981

    
1982
  return _PrepareFieldList(fields, aliases)
1983

    
1984

    
1985
class LockQueryData:
1986
  """Data container for lock data queries.
1987

1988
  """
1989
  def __init__(self, lockdata):
1990
    """Initializes this class.
1991

1992
    """
1993
    self.lockdata = lockdata
1994

    
1995
  def __iter__(self):
1996
    """Iterate over all locks.
1997

1998
    """
1999
    return iter(self.lockdata)
2000

    
2001

    
2002
def _GetLockOwners(_, data):
2003
  """Returns a sorted list of a lock's current owners.
2004

2005
  """
2006
  (_, _, owners, _) = data
2007

    
2008
  if owners:
2009
    owners = utils.NiceSort(owners)
2010

    
2011
  return owners
2012

    
2013

    
2014
def _GetLockPending(_, data):
2015
  """Returns a sorted list of a lock's pending acquires.
2016

2017
  """
2018
  (_, _, _, pending) = data
2019

    
2020
  if pending:
2021
    pending = [(mode, utils.NiceSort(names))
2022
               for (mode, names) in pending]
2023

    
2024
  return pending
2025

    
2026

    
2027
def _BuildLockFields():
2028
  """Builds list of fields for lock queries.
2029

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

    
2046

    
2047
class GroupQueryData:
2048
  """Data container for node group data queries.
2049

2050
  """
2051
  def __init__(self, cluster, groups, group_to_nodes, group_to_instances,
2052
               want_diskparams):
2053
    """Initializes this class.
2054

2055
    @param cluster: Cluster object
2056
    @param groups: List of node group objects
2057
    @type group_to_nodes: dict; group UUID as key
2058
    @param group_to_nodes: Per-group list of nodes
2059
    @type group_to_instances: dict; group UUID as key
2060
    @param group_to_instances: Per-group list of (primary) instances
2061
    @type want_diskparams: bool
2062
    @param want_diskparams: Whether diskparamters should be calculated
2063

2064
    """
2065
    self.groups = groups
2066
    self.group_to_nodes = group_to_nodes
2067
    self.group_to_instances = group_to_instances
2068
    self.cluster = cluster
2069
    self.want_diskparams = want_diskparams
2070

    
2071
    # Used for individual rows
2072
    self.group_ipolicy = None
2073
    self.ndparams = None
2074
    self.group_dp = None
2075

    
2076
  def __iter__(self):
2077
    """Iterate over all node groups.
2078

2079
    This function has side-effects and only one instance of the resulting
2080
    generator should be used at a time.
2081

2082
    """
2083
    for group in self.groups:
2084
      self.group_ipolicy = self.cluster.SimpleFillIPolicy(group.ipolicy)
2085
      self.ndparams = self.cluster.SimpleFillND(group.ndparams)
2086
      if self.want_diskparams:
2087
        self.group_dp = self.cluster.SimpleFillDP(group.diskparams)
2088
      else:
2089
        self.group_dp = None
2090
      yield group
2091

    
2092

    
2093
_GROUP_SIMPLE_FIELDS = {
2094
  "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
2095
  "name": ("Group", QFT_TEXT, "Group name"),
2096
  "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
2097
  "uuid": ("UUID", QFT_TEXT, "Group UUID"),
2098
  }
2099

    
2100

    
2101
def _BuildGroupFields():
2102
  """Builds list of fields for node group queries.
2103

2104
  """
2105
  # Add simple fields
2106
  fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
2107
             _GetItemAttr(name))
2108
            for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
2109

    
2110
  def _GetLength(getter):
2111
    return lambda ctx, group: len(getter(ctx)[group.uuid])
2112

    
2113
  def _GetSortedList(getter):
2114
    return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
2115

    
2116
  group_to_nodes = operator.attrgetter("group_to_nodes")
2117
  group_to_instances = operator.attrgetter("group_to_instances")
2118

    
2119
  # Add fields for nodes
2120
  fields.extend([
2121
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
2122
     GQ_NODE, 0, _GetLength(group_to_nodes)),
2123
    (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
2124
     GQ_NODE, 0, _GetSortedList(group_to_nodes)),
2125
    ])
2126

    
2127
  # Add fields for instances
2128
  fields.extend([
2129
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
2130
                "Number of primary instances"),
2131
     GQ_INST, 0, _GetLength(group_to_instances)),
2132
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
2133
                "List of primary instances"),
2134
     GQ_INST, 0, _GetSortedList(group_to_instances)),
2135
    ])
2136

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

    
2161
  # ND parameters
2162
  fields.extend(_BuildNDFields(True))
2163

    
2164
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
2165

    
2166
  return _PrepareFieldList(fields, [])
2167

    
2168

    
2169
class OsInfo(objects.ConfigObject):
2170
  __slots__ = [
2171
    "name",
2172
    "valid",
2173
    "hidden",
2174
    "blacklisted",
2175
    "variants",
2176
    "api_versions",
2177
    "parameters",
2178
    "node_status",
2179
    ]
2180

    
2181

    
2182
def _BuildOsFields():
2183
  """Builds list of fields for operating system queries.
2184

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

    
2213
  return _PrepareFieldList(fields, [])
2214

    
2215

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

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

2222
  """
2223
  if job is None:
2224
    return _FS_UNAVAIL
2225
  else:
2226
    return fn(job)
2227

    
2228

    
2229
def _JobUnavail(inner):
2230
  """Wrapper for L{_JobUnavailInner}.
2231

2232
  """
2233
  return compat.partial(_JobUnavailInner, inner)
2234

    
2235

    
2236
def _PerJobOpInner(fn, job):
2237
  """Executes a function per opcode in a job.
2238

2239
  """
2240
  return map(fn, job.ops)
2241

    
2242

    
2243
def _PerJobOp(fn):
2244
  """Wrapper for L{_PerJobOpInner}.
2245

2246
  """
2247
  return _JobUnavail(compat.partial(_PerJobOpInner, fn))
2248

    
2249

    
2250
def _JobTimestampInner(fn, job):
2251
  """Converts unavailable timestamp to L{_FS_UNAVAIL}.
2252

2253
  """
2254
  timestamp = fn(job)
2255

    
2256
  if timestamp is None:
2257
    return _FS_UNAVAIL
2258
  else:
2259
    return timestamp
2260

    
2261

    
2262
def _JobTimestamp(fn):
2263
  """Wrapper for L{_JobTimestampInner}.
2264

2265
  """
2266
  return _JobUnavail(compat.partial(_JobTimestampInner, fn))
2267

    
2268

    
2269
def _BuildJobFields():
2270
  """Builds list of fields for job queries.
2271

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

    
2313
  # Timestamp fields
2314
  for (name, attr, title, desc) in [
2315
    ("received_ts", "received_timestamp", "Received",
2316
     "Timestamp of when job was received"),
2317
    ("start_ts", "start_timestamp", "Start", "Timestamp of job start"),
2318
    ("end_ts", "end_timestamp", "End", "Timestamp of job end"),
2319
    ]:
2320
    getter = operator.attrgetter(attr)
2321
    fields.extend([
2322
      (_MakeField(name, title, QFT_OTHER,
2323
                  "%s (tuple containing seconds and microseconds)" % desc),
2324
       None, QFF_SPLIT_TIMESTAMP, _JobTimestamp(getter)),
2325
      ])
2326

    
2327
  return _PrepareFieldList(fields, [])
2328

    
2329

    
2330
def _GetExportName(_, (node_name, expname)): # pylint: disable=W0613
2331
  """Returns an export name if available.
2332

2333
  """
2334
  if expname is None:
2335
    return _FS_UNAVAIL
2336
  else:
2337
    return expname
2338

    
2339

    
2340
def _BuildExportFields():
2341
  """Builds list of fields for exports.
2342

2343
  """
2344
  fields = [
2345
    (_MakeField("node", "Node", QFT_TEXT, "Node name"),
2346
     None, QFF_HOSTNAME, lambda _, (node_name, expname): node_name),
2347
    (_MakeField("export", "Export", QFT_TEXT, "Export name"),
2348
     None, 0, _GetExportName),
2349
    ]
2350

    
2351
  return _PrepareFieldList(fields, [])
2352

    
2353

    
2354
_CLUSTER_VERSION_FIELDS = {
2355
  "software_version": ("SoftwareVersion", QFT_TEXT, constants.RELEASE_VERSION,
2356
                       "Software version"),
2357
  "protocol_version": ("ProtocolVersion", QFT_NUMBER,
2358
                       constants.PROTOCOL_VERSION,
2359
                       "RPC protocol version"),
2360
  "config_version": ("ConfigVersion", QFT_NUMBER, constants.CONFIG_VERSION,
2361
                     "Configuration format version"),
2362
  "os_api_version": ("OsApiVersion", QFT_NUMBER, max(constants.OS_API_VERSIONS),
2363
                     "API version for OS template scripts"),
2364
  "export_version": ("ExportVersion", QFT_NUMBER, constants.EXPORT_VERSION,
2365
                     "Import/export file format version"),
2366
  }
2367

    
2368

    
2369
_CLUSTER_SIMPLE_FIELDS = {
2370
  "cluster_name": ("Name", QFT_TEXT, QFF_HOSTNAME, "Cluster name"),
2371
  "master_node": ("Master", QFT_TEXT, QFF_HOSTNAME, "Master node name"),
2372
  "volume_group_name": ("VgName", QFT_TEXT, 0, "LVM volume group name"),
2373
  }
2374

    
2375

    
2376
class ClusterQueryData:
2377
  def __init__(self, cluster, drain_flag, watcher_pause):
2378
    """Initializes this class.
2379

2380
    @type cluster: L{objects.Cluster}
2381
    @param cluster: Instance of cluster object
2382
    @type drain_flag: bool
2383
    @param drain_flag: Whether job queue is drained
2384
    @type watcher_pause: number
2385
    @param watcher_pause: Until when watcher is paused (Unix timestamp)
2386

2387
    """
2388
    self._cluster = cluster
2389
    self.drain_flag = drain_flag
2390
    self.watcher_pause = watcher_pause
2391

    
2392
  def __iter__(self):
2393
    return iter([self._cluster])
2394

    
2395

    
2396
def _ClusterWatcherPause(ctx, _):
2397
  """Returns until when watcher is paused (if available).
2398

2399
  """
2400
  if ctx.watcher_pause is None:
2401
    return _FS_UNAVAIL
2402
  else:
2403
    return ctx.watcher_pause
2404

    
2405

    
2406
def _BuildClusterFields():
2407
  """Builds list of fields for cluster information.
2408

2409
  """
2410
  fields = [
2411
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), CQ_CONFIG, 0,
2412
     lambda ctx, cluster: list(cluster.GetTags())),
2413
    (_MakeField("architecture", "ArchInfo", QFT_OTHER,
2414
                "Architecture information"), None, 0,
2415
     lambda ctx, _: runtime.GetArchInfo()),
2416
    (_MakeField("drain_flag", "QueueDrained", QFT_BOOL,
2417
                "Flag whether job queue is drained"), CQ_QUEUE_DRAINED, 0,
2418
     lambda ctx, _: ctx.drain_flag),
2419
    (_MakeField("watcher_pause", "WatcherPause", QFT_TIMESTAMP,
2420
                "Until when watcher is paused"), CQ_WATCHER_PAUSE, 0,
2421
     _ClusterWatcherPause),
2422
    ]
2423

    
2424
  # Simple fields
2425
  fields.extend([
2426
    (_MakeField(name, title, kind, doc), CQ_CONFIG, flags, _GetItemAttr(name))
2427
    for (name, (title, kind, flags, doc)) in _CLUSTER_SIMPLE_FIELDS.items()
2428
    ],)
2429

    
2430
  # Version fields
2431
  fields.extend([
2432
    (_MakeField(name, title, kind, doc), None, 0, _StaticValue(value))
2433
    for (name, (title, kind, value, doc)) in _CLUSTER_VERSION_FIELDS.items()])
2434

    
2435
  # Add timestamps
2436
  fields.extend(_GetItemTimestampFields(CQ_CONFIG))
2437

    
2438
  return _PrepareFieldList(fields, [
2439
    ("name", "cluster_name")])
2440

    
2441

    
2442
class NetworkQueryData:
2443
  """Data container for network data queries.
2444

2445
  """
2446
  def __init__(self, networks, network_to_groups,
2447
               network_to_instances, stats):
2448
    """Initializes this class.
2449

2450
    @param networks: List of network objects
2451
    @type network_to_groups: dict; network UUID as key
2452
    @param network_to_groups: Per-network list of groups
2453
    @type network_to_instances: dict; network UUID as key
2454
    @param network_to_instances: Per-network list of instances
2455
    @type stats: dict; network UUID as key
2456
    @param stats: Per-network usage statistics
2457

2458
    """
2459
    self.networks = networks
2460
    self.network_to_groups = network_to_groups
2461
    self.network_to_instances = network_to_instances
2462
    self.stats = stats
2463

    
2464
  def __iter__(self):
2465
    """Iterate over all networks.
2466

2467
    """
2468
    for net in self.networks:
2469
      if self.stats:
2470
        self.curstats = self.stats.get(net.uuid, None)
2471
      else:
2472
        self.curstats = None
2473
      yield net
2474

    
2475

    
2476
_NETWORK_SIMPLE_FIELDS = {
2477
  "name": ("Network", QFT_TEXT, 0, "The network"),
2478
  "network": ("Subnet", QFT_TEXT, 0, "The subnet"),
2479
  "gateway": ("Gateway", QFT_OTHER, 0, "The gateway"),
2480
  "network6": ("IPv6Subnet", QFT_OTHER, 0, "The ipv6 subnet"),
2481
  "gateway6": ("IPv6Gateway", QFT_OTHER, 0, "The ipv6 gateway"),
2482
  "mac_prefix": ("MacPrefix", QFT_OTHER, 0, "The mac prefix"),
2483
  "network_type": ("NetworkType", QFT_OTHER, 0, "The network type"),
2484
  }
2485

    
2486

    
2487
_NETWORK_STATS_FIELDS = {
2488
  "free_count": ("FreeCount", QFT_NUMBER, 0, "How many addresses are free"),
2489
  "reserved_count": ("ReservedCount", QFT_NUMBER, 0,
2490
                     "How many addresses are reserved"),
2491
  "map": ("Map", QFT_TEXT, 0, "The actual mapping"),
2492
  "external_reservations": ("ExternalReservations", QFT_TEXT, 0,
2493
                            "The external reservations"),
2494
  }
2495

    
2496

    
2497
def _GetNetworkStatsField(field, kind, ctx):
2498
  """Gets the value of a "stats" field from L{NetworkQueryData}.
2499

2500
  @param field: Field name
2501
  @param kind: Data kind, one of L{constants.QFT_ALL}
2502
  @type ctx: L{NetworkQueryData}
2503

2504
  """
2505

    
2506
  try:
2507
    value = ctx.curstats[field]
2508
  except KeyError:
2509
    return _FS_UNAVAIL
2510

    
2511
  if kind == QFT_TEXT:
2512
    return value
2513

    
2514
  assert kind in (QFT_NUMBER, QFT_UNIT)
2515

    
2516
  # Try to convert into number
2517
  try:
2518
    return int(value)
2519
  except (ValueError, TypeError):
2520
    logging.exception("Failed to convert network field '%s' (value %r) to int",
2521
                      value, field)
2522
    return _FS_UNAVAIL
2523

    
2524

    
2525
def _BuildNetworkFields():
2526
  """Builds list of fields for network queries.
2527

2528
  """
2529
  fields = [
2530
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
2531
     lambda ctx, inst: list(inst.GetTags())),
2532
    ]
2533

    
2534
  # Add simple fields
2535
  fields.extend([
2536
    (_MakeField(name, title, kind, doc),
2537
     NETQ_CONFIG, 0, _GetItemAttr(name))
2538
     for (name, (title, kind, _, doc)) in _NETWORK_SIMPLE_FIELDS.items()])
2539

    
2540
  def _GetLength(getter):
2541
    return lambda ctx, network: len(getter(ctx)[network.uuid])
2542

    
2543
  def _GetSortedList(getter):
2544
    return lambda ctx, network: utils.NiceSort(getter(ctx)[network.uuid])
2545

    
2546
  network_to_groups = operator.attrgetter("network_to_groups")
2547
  network_to_instances = operator.attrgetter("network_to_instances")
2548

    
2549
  # Add fields for node groups
2550
  fields.extend([
2551
    (_MakeField("group_cnt", "NodeGroups", QFT_NUMBER, "Number of nodegroups"),
2552
     NETQ_GROUP, 0, _GetLength(network_to_groups)),
2553
    (_MakeField("group_list", "GroupList", QFT_OTHER, "List of nodegroups"),
2554
     NETQ_GROUP, 0, _GetSortedList(network_to_groups)),
2555
    ])
2556

    
2557
  # Add fields for instances
2558
  fields.extend([
2559
    (_MakeField("inst_cnt", "Instances", QFT_NUMBER, "Number of instances"),
2560
     NETQ_INST, 0, _GetLength(network_to_instances)),
2561
    (_MakeField("inst_list", "InstanceList", QFT_OTHER, "List of instances"),
2562
     NETQ_INST, 0, _GetSortedList(network_to_instances)),
2563
    ])
2564

    
2565
  # Add fields for usage statistics
2566
  fields.extend([
2567
    (_MakeField(name, title, kind, doc), NETQ_STATS, 0,
2568
    compat.partial(_GetNetworkStatsField, name, kind))
2569
    for (name, (title, kind, _, doc)) in _NETWORK_STATS_FIELDS.items()])
2570

    
2571
  return _PrepareFieldList(fields, [])
2572

    
2573
#: Fields for cluster information
2574
CLUSTER_FIELDS = _BuildClusterFields()
2575

    
2576
#: Fields available for node queries
2577
NODE_FIELDS = _BuildNodeFields()
2578

    
2579
#: Fields available for instance queries
2580
INSTANCE_FIELDS = _BuildInstanceFields()
2581

    
2582
#: Fields available for lock queries
2583
LOCK_FIELDS = _BuildLockFields()
2584

    
2585
#: Fields available for node group queries
2586
GROUP_FIELDS = _BuildGroupFields()
2587

    
2588
#: Fields available for operating system queries
2589
OS_FIELDS = _BuildOsFields()
2590

    
2591
#: Fields available for job queries
2592
JOB_FIELDS = _BuildJobFields()
2593

    
2594
#: Fields available for exports
2595
EXPORT_FIELDS = _BuildExportFields()
2596

    
2597
#: Fields available for network queries
2598
NETWORK_FIELDS = _BuildNetworkFields()
2599

    
2600
#: All available resources
2601
ALL_FIELDS = {
2602
  constants.QR_CLUSTER: CLUSTER_FIELDS,
2603
  constants.QR_INSTANCE: INSTANCE_FIELDS,
2604
  constants.QR_NODE: NODE_FIELDS,
2605
  constants.QR_LOCK: LOCK_FIELDS,
2606
  constants.QR_GROUP: GROUP_FIELDS,
2607
  constants.QR_OS: OS_FIELDS,
2608
  constants.QR_JOB: JOB_FIELDS,
2609
  constants.QR_EXPORT: EXPORT_FIELDS,
2610
  constants.QR_NETWORK: NETWORK_FIELDS,
2611
  }
2612

    
2613
#: All available field lists
2614
ALL_FIELD_LISTS = ALL_FIELDS.values()