Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ 0b3f2215

History | View | Annotate | Download (77.4 kB)

1
#
2
#
3

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

    
21

    
22
"""Module for query operations
23

24
How it works:
25

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

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

53
"""
54

    
55
import logging
56
import operator
57
import re
58

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
159

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

163
  """
164
  return _FS_UNKNOWN
165

    
166

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

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

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

177
  """
178
  result = []
179

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

    
187
    assert len(fdef) == 4
188

    
189
    result.append(fdef)
190

    
191
  return result
192

    
193

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

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

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

    
202

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

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

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

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

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

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

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

233
    """
234
    self._namefield = namefield
235

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

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

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

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

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

252
    @rtype: list
253

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

    
258
    return utils.UniqueSequence(self._names)
259

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

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

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

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

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

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

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

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

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

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

    
293
    self._NeedAllNames()
294

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

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

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

    
308
    if self._allnames:
309
      return
310

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

    
320

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

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

    
327

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

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

    
334

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

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

    
341

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

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

    
348

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

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

    
358

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

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

    
368

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

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

    
375

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

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

    
388

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

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

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

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

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

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

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

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

    
423
  """Known operators
424

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

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

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

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

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

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

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

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

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

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

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

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

    
497
    return filter_fn
498

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

571
    """
572
    assert op_fn is None
573

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

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

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

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

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

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

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

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

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

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

    
620
    assert fdef.kind != QFT_UNKNOWN
621

    
622
    # TODO: Type conversions?
623

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

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

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

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

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

    
645

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

649
  See L{_FilterCompilerHelper} for details.
650

651
  @rtype: callable
652

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

    
656

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

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

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

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

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

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

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

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

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

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

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

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

709
    """
710
    return self._requested_names
711

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

715
    @rtype: frozenset
716

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

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

725
    Includes unknown fields.
726

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

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

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

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

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

    
744
    result = []
745

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

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

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

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

    
765
    if not sort:
766
      return result
767

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

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

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

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

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

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

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

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

    
793

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

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

    
809

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

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

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

    
831

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

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

    
845
  return fdef.name
846

    
847

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

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

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

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

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

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

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

    
883
  return result
884

    
885

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

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

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

    
899

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

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

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

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

    
920

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

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

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

    
933

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

937
  """
938
  return value
939

    
940

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

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

    
947

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

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

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

    
968

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

972
  @param attr: Attribute name
973

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

    
978

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

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

    
990

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

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

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

    
1008

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

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

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

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

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

    
1026

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

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

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

    
1036

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

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

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

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

    
1055
  return fn
1056

    
1057

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

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

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

    
1071

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

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

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

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

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

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

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

    
1113

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

    
1128

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

    
1153

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

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

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

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

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

    
1173
    return cb(ctx, node, ng)
1174

    
1175
  return fn
1176

    
1177

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

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

1187
  """
1188
  return ng.name
1189

    
1190

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

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

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

    
1202
  return _FS_UNAVAIL
1203

    
1204

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

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

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

    
1217

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

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

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

    
1231
  if not node.vm_capable:
1232
    return _FS_UNAVAIL
1233

    
1234
  if not ctx.curlive_data:
1235
    return _FS_NODATA
1236

    
1237
  try:
1238
    value = ctx.curlive_data[field]
1239
  except KeyError:
1240
    return _FS_UNAVAIL
1241

    
1242
  if kind == QFT_TEXT:
1243
    return value
1244

    
1245
  assert kind in (QFT_NUMBER, QFT_UNIT)
1246

    
1247
  # Try to convert into number
1248
  try:
1249
    return int(value)
1250
  except (ValueError, TypeError):
1251
    logging.exception("Failed to convert node field '%s' (value %r) to int",
1252
                      value, field)
1253
    return _FS_UNAVAIL
1254

    
1255

    
1256
def _GetNodeHvState(_, node):
1257
  """Converts node's hypervisor state for query result.
1258

1259
  """
1260
  hv_state = node.hv_state
1261

    
1262
  if hv_state is None:
1263
    return _FS_UNAVAIL
1264

    
1265
  return dict((name, value.ToDict()) for (name, value) in hv_state.items())
1266

    
1267

    
1268
def _GetNodeDiskState(_, node):
1269
  """Converts node's disk state for query result.
1270

1271
  """
1272
  disk_state = node.disk_state
1273

    
1274
  if disk_state is None:
1275
    return _FS_UNAVAIL
1276

    
1277
  return dict((disk_kind, dict((name, value.ToDict())
1278
                               for (name, value) in kind_state.items()))
1279
              for (disk_kind, kind_state) in disk_state.items())
1280

    
1281

    
1282
def _BuildNodeFields():
1283
  """Builds list of fields for node queries.
1284

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

    
1314
  fields.extend(_BuildNDFields(False))
1315

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

    
1327
  def _GetLength(getter):
1328
    return lambda ctx, node: len(getter(ctx)[node.name])
1329

    
1330
  def _GetList(getter):
1331
    return lambda ctx, node: list(getter(ctx)[node.name])
1332

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

    
1348
  # Add simple fields
1349
  fields.extend([
1350
    (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1351
    for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()])
1352

    
1353
  # Add fields requiring live data
1354
  fields.extend([
1355
    (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1356
     compat.partial(_GetLiveNodeField, nfield, kind))
1357
    for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()])
1358

    
1359
  # Add timestamps
1360
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1361

    
1362
  return _PrepareFieldList(fields, [])
1363

    
1364

    
1365
class InstanceQueryData:
1366
  """Data container for instance data queries.
1367

1368
  """
1369
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
1370
               live_data, wrongnode_inst, console, nodes, groups):
1371
    """Initializes this class.
1372

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

1390
    """
1391
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
1392
           "Offline nodes not included in bad nodes"
1393
    assert not (set(live_data.keys()) & set(bad_nodes)), \
1394
           "Found live data for bad or offline nodes"
1395

    
1396
    self.instances = instances
1397
    self.cluster = cluster
1398
    self.disk_usage = disk_usage
1399
    self.offline_nodes = offline_nodes
1400
    self.bad_nodes = bad_nodes
1401
    self.live_data = live_data
1402
    self.wrongnode_inst = wrongnode_inst
1403
    self.console = console
1404
    self.nodes = nodes
1405
    self.groups = groups
1406

    
1407
    # Used for individual rows
1408
    self.inst_hvparams = None
1409
    self.inst_beparams = None
1410
    self.inst_osparams = None
1411
    self.inst_nicparams = None
1412

    
1413
  def __iter__(self):
1414
    """Iterate over all instances.
1415

1416
    This function has side-effects and only one instance of the resulting
1417
    generator should be used at a time.
1418

1419
    """
1420
    for inst in self.instances:
1421
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1422
      self.inst_beparams = self.cluster.FillBE(inst)
1423
      self.inst_osparams = self.cluster.SimpleFillOS(inst.os, inst.osparams)
1424
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1425
                             for nic in inst.nics]
1426

    
1427
      yield inst
1428

    
1429

    
1430
def _GetInstOperState(ctx, inst):
1431
  """Get instance's operational status.
1432

1433
  @type ctx: L{InstanceQueryData}
1434
  @type inst: L{objects.Instance}
1435
  @param inst: Instance object
1436

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

    
1445

    
1446
def _GetInstLiveData(name):
1447
  """Build function for retrieving live data.
1448

1449
  @type name: string
1450
  @param name: Live data field name
1451

1452
  """
1453
  def fn(ctx, inst):
1454
    """Get live data for an instance.
1455

1456
    @type ctx: L{InstanceQueryData}
1457
    @type inst: L{objects.Instance}
1458
    @param inst: Instance object
1459

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

    
1467
    if inst.name in ctx.live_data:
1468
      data = ctx.live_data[inst.name]
1469
      if name in data:
1470
        return data[name]
1471

    
1472
    return _FS_UNAVAIL
1473

    
1474
  return fn
1475

    
1476

    
1477
def _GetInstStatus(ctx, inst):
1478
  """Get instance status.
1479

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

1484
  """
1485
  if inst.primary_node in ctx.offline_nodes:
1486
    return constants.INSTST_NODEOFFLINE
1487

    
1488
  if inst.primary_node in ctx.bad_nodes:
1489
    return constants.INSTST_NODEDOWN
1490

    
1491
  if bool(ctx.live_data.get(inst.name)):
1492
    if inst.name in ctx.wrongnode_inst:
1493
      return constants.INSTST_WRONGNODE
1494
    elif inst.admin_state == constants.ADMINST_UP:
1495
      return constants.INSTST_RUNNING
1496
    else:
1497
      return constants.INSTST_ERRORUP
1498

    
1499
  if inst.admin_state == constants.ADMINST_UP:
1500
    return constants.INSTST_ERRORDOWN
1501
  elif inst.admin_state == constants.ADMINST_DOWN:
1502
    return constants.INSTST_ADMINDOWN
1503

    
1504
  return constants.INSTST_ADMINOFFLINE
1505

    
1506

    
1507
def _GetInstDiskSize(index):
1508
  """Build function for retrieving disk size.
1509

1510
  @type index: int
1511
  @param index: Disk index
1512

1513
  """
1514
  def fn(_, inst):
1515
    """Get size of a disk.
1516

1517
    @type inst: L{objects.Instance}
1518
    @param inst: Instance object
1519

1520
    """
1521
    try:
1522
      return inst.disks[index].size
1523
    except IndexError:
1524
      return _FS_UNAVAIL
1525

    
1526
  return fn
1527

    
1528

    
1529
def _GetInstNic(index, cb):
1530
  """Build function for calling another function with an instance NIC.
1531

1532
  @type index: int
1533
  @param index: NIC index
1534
  @type cb: callable
1535
  @param cb: Callback
1536

1537
  """
1538
  def fn(ctx, inst):
1539
    """Call helper function with instance NIC.
1540

1541
    @type ctx: L{InstanceQueryData}
1542
    @type inst: L{objects.Instance}
1543
    @param inst: Instance object
1544

1545
    """
1546
    try:
1547
      nic = inst.nics[index]
1548
    except IndexError:
1549
      return _FS_UNAVAIL
1550

    
1551
    return cb(ctx, index, nic)
1552

    
1553
  return fn
1554

    
1555

    
1556
def _GetInstNicNetwork(ctx, _, nic): # pylint: disable=W0613
1557
  """Get a NIC's Network.
1558

1559
  @type ctx: L{InstanceQueryData}
1560
  @type nic: L{objects.NIC}
1561
  @param nic: NIC object
1562

1563
  """
1564
  if nic.network is None:
1565
    return _FS_UNAVAIL
1566
  else:
1567
    return nic.network
1568

    
1569

    
1570
def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613
1571
  """Get a NIC's IP address.
1572

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

1577
  """
1578
  if nic.ip is None:
1579
    return _FS_UNAVAIL
1580
  else:
1581
    return nic.ip
1582

    
1583

    
1584
def _GetInstNicBridge(ctx, index, _):
1585
  """Get a NIC's bridge.
1586

1587
  @type ctx: L{InstanceQueryData}
1588
  @type index: int
1589
  @param index: NIC index
1590

1591
  """
1592
  assert len(ctx.inst_nicparams) >= index
1593

    
1594
  nicparams = ctx.inst_nicparams[index]
1595

    
1596
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1597
    return nicparams[constants.NIC_LINK]
1598
  else:
1599
    return _FS_UNAVAIL
1600

    
1601

    
1602
def _GetInstAllNicBridges(ctx, inst):
1603
  """Get all network bridges for an instance.
1604

1605
  @type ctx: L{InstanceQueryData}
1606
  @type inst: L{objects.Instance}
1607
  @param inst: Instance object
1608

1609
  """
1610
  assert len(ctx.inst_nicparams) == len(inst.nics)
1611

    
1612
  result = []
1613

    
1614
  for nicp in ctx.inst_nicparams:
1615
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1616
      result.append(nicp[constants.NIC_LINK])
1617
    else:
1618
      result.append(None)
1619

    
1620
  assert len(result) == len(inst.nics)
1621

    
1622
  return result
1623

    
1624

    
1625
def _GetInstNicParam(name):
1626
  """Build function for retrieving a NIC parameter.
1627

1628
  @type name: string
1629
  @param name: Parameter name
1630

1631
  """
1632
  def fn(ctx, index, _):
1633
    """Get a NIC's bridge.
1634

1635
    @type ctx: L{InstanceQueryData}
1636
    @type inst: L{objects.Instance}
1637
    @param inst: Instance object
1638
    @type nic: L{objects.NIC}
1639
    @param nic: NIC object
1640

1641
    """
1642
    assert len(ctx.inst_nicparams) >= index
1643
    return ctx.inst_nicparams[index][name]
1644

    
1645
  return fn
1646

    
1647

    
1648
def _GetInstanceNetworkFields():
1649
  """Get instance fields involving network interfaces.
1650

1651
  @return: Tuple containing list of field definitions used as input for
1652
    L{_PrepareFieldList} and a list of aliases
1653

1654
  """
1655
  nic_mac_fn = lambda ctx, _, nic: nic.mac
1656
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1657
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1658

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

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

    
1710
  aliases = [
1711
    # Legacy fields for first NIC
1712
    ("ip", "nic.ip/0"),
1713
    ("mac", "nic.mac/0"),
1714
    ("bridge", "nic.bridge/0"),
1715
    ("nic_mode", "nic.mode/0"),
1716
    ("nic_link", "nic.link/0"),
1717
    ("nic_network", "nic.network/0"),
1718
    ]
1719

    
1720
  return (fields, aliases)
1721

    
1722

    
1723
def _GetInstDiskUsage(ctx, inst):
1724
  """Get disk usage for an instance.
1725

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

1730
  """
1731
  usage = ctx.disk_usage[inst.name]
1732

    
1733
  if usage is None:
1734
    usage = 0
1735

    
1736
  return usage
1737

    
1738

    
1739
def _GetInstanceConsole(ctx, inst):
1740
  """Get console information for instance.
1741

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

1746
  """
1747
  consinfo = ctx.console[inst.name]
1748

    
1749
  if consinfo is None:
1750
    return _FS_UNAVAIL
1751

    
1752
  return consinfo
1753

    
1754

    
1755
def _GetInstanceDiskFields():
1756
  """Get instance fields involving disks.
1757

1758
  @return: List of field definitions used as input for L{_PrepareFieldList}
1759

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

    
1773
  # Disks by number
1774
  fields.extend([
1775
    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1776
                "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
1777
     IQ_CONFIG, 0, _GetInstDiskSize(i))
1778
    for i in range(constants.MAX_DISKS)])
1779

    
1780
  return fields
1781

    
1782

    
1783
def _GetInstanceParameterFields():
1784
  """Get instance fields involving parameters.
1785

1786
  @return: List of field definitions used as input for L{_PrepareFieldList}
1787

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

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

    
1816
  # HV params
1817
  def _GetInstHvParam(name):
1818
    return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1819

    
1820
  fields.extend([
1821
    (_MakeField("hv/%s" % name,
1822
                constants.HVS_PARAMETER_TITLES.get(name, "hv/%s" % name),
1823
                _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1824
     IQ_CONFIG, 0, _GetInstHvParam(name))
1825
    for name, kind in constants.HVS_PARAMETER_TYPES.items()
1826
    if name not in constants.HVC_GLOBALS])
1827

    
1828
  # BE params
1829
  def _GetInstBeParam(name):
1830
    return lambda ctx, _: ctx.inst_beparams.get(name, None)
1831

    
1832
  fields.extend([
1833
    (_MakeField("be/%s" % name,
1834
                constants.BES_PARAMETER_TITLES.get(name, "be/%s" % name),
1835
                _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1836
     IQ_CONFIG, 0, _GetInstBeParam(name))
1837
    for name, kind in constants.BES_PARAMETER_TYPES.items()])
1838

    
1839
  return fields
1840

    
1841

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

    
1854

    
1855
def _GetInstNodeGroup(ctx, default, node_name):
1856
  """Gets group UUID of an instance node.
1857

1858
  @type ctx: L{InstanceQueryData}
1859
  @param default: Default value
1860
  @type node_name: string
1861
  @param node_name: Node name
1862

1863
  """
1864
  try:
1865
    node = ctx.nodes[node_name]
1866
  except KeyError:
1867
    return default
1868
  else:
1869
    return node.group
1870

    
1871

    
1872
def _GetInstNodeGroupName(ctx, default, node_name):
1873
  """Gets group name of an instance node.
1874

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

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

    
1886
  try:
1887
    group = ctx.groups[node.group]
1888
  except KeyError:
1889
    return default
1890

    
1891
  return group.name
1892

    
1893

    
1894
def _BuildInstanceFields():
1895
  """Builds list of fields for instance queries.
1896

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

    
1937
  # Add simple fields
1938
  fields.extend([
1939
    (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1940
    for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()])
1941

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

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

    
1973
  (network_fields, network_aliases) = _GetInstanceNetworkFields()
1974

    
1975
  fields.extend(network_fields)
1976
  fields.extend(_GetInstanceParameterFields())
1977
  fields.extend(_GetInstanceDiskFields())
1978
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1979

    
1980
  aliases = [
1981
    ("vcpus", "be/vcpus"),
1982
    ("be/memory", "be/maxmem"),
1983
    ("sda_size", "disk.size/0"),
1984
    ("sdb_size", "disk.size/1"),
1985
    ] + network_aliases
1986

    
1987
  return _PrepareFieldList(fields, aliases)
1988

    
1989

    
1990
class LockQueryData:
1991
  """Data container for lock data queries.
1992

1993
  """
1994
  def __init__(self, lockdata):
1995
    """Initializes this class.
1996

1997
    """
1998
    self.lockdata = lockdata
1999

    
2000
  def __iter__(self):
2001
    """Iterate over all locks.
2002

2003
    """
2004
    return iter(self.lockdata)
2005

    
2006

    
2007
def _GetLockOwners(_, data):
2008
  """Returns a sorted list of a lock's current owners.
2009

2010
  """
2011
  (_, _, owners, _) = data
2012

    
2013
  if owners:
2014
    owners = utils.NiceSort(owners)
2015

    
2016
  return owners
2017

    
2018

    
2019
def _GetLockPending(_, data):
2020
  """Returns a sorted list of a lock's pending acquires.
2021

2022
  """
2023
  (_, _, _, pending) = data
2024

    
2025
  if pending:
2026
    pending = [(mode, utils.NiceSort(names))
2027
               for (mode, names) in pending]
2028

    
2029
  return pending
2030

    
2031

    
2032
def _BuildLockFields():
2033
  """Builds list of fields for lock queries.
2034

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

    
2051

    
2052
class GroupQueryData:
2053
  """Data container for node group data queries.
2054

2055
  """
2056
  def __init__(self, cluster, groups, group_to_nodes, group_to_instances,
2057
               want_diskparams):
2058
    """Initializes this class.
2059

2060
    @param cluster: Cluster object
2061
    @param groups: List of node group objects
2062
    @type group_to_nodes: dict; group UUID as key
2063
    @param group_to_nodes: Per-group list of nodes
2064
    @type group_to_instances: dict; group UUID as key
2065
    @param group_to_instances: Per-group list of (primary) instances
2066
    @type want_diskparams: bool
2067
    @param want_diskparams: Whether diskparamters should be calculated
2068

2069
    """
2070
    self.groups = groups
2071
    self.group_to_nodes = group_to_nodes
2072
    self.group_to_instances = group_to_instances
2073
    self.cluster = cluster
2074
    self.want_diskparams = want_diskparams
2075

    
2076
    # Used for individual rows
2077
    self.group_ipolicy = None
2078
    self.ndparams = None
2079
    self.group_dp = None
2080

    
2081
  def __iter__(self):
2082
    """Iterate over all node groups.
2083

2084
    This function has side-effects and only one instance of the resulting
2085
    generator should be used at a time.
2086

2087
    """
2088
    for group in self.groups:
2089
      self.group_ipolicy = self.cluster.SimpleFillIPolicy(group.ipolicy)
2090
      self.ndparams = self.cluster.SimpleFillND(group.ndparams)
2091
      if self.want_diskparams:
2092
        self.group_dp = self.cluster.SimpleFillDP(group.diskparams)
2093
      else:
2094
        self.group_dp = None
2095
      yield group
2096

    
2097

    
2098
_GROUP_SIMPLE_FIELDS = {
2099
  "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
2100
  "name": ("Group", QFT_TEXT, "Group name"),
2101
  "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
2102
  "uuid": ("UUID", QFT_TEXT, "Group UUID"),
2103
  }
2104

    
2105

    
2106
def _BuildGroupFields():
2107
  """Builds list of fields for node group queries.
2108

2109
  """
2110
  # Add simple fields
2111
  fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
2112
             _GetItemAttr(name))
2113
            for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
2114

    
2115
  def _GetLength(getter):
2116
    return lambda ctx, group: len(getter(ctx)[group.uuid])
2117

    
2118
  def _GetSortedList(getter):
2119
    return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
2120

    
2121
  group_to_nodes = operator.attrgetter("group_to_nodes")
2122
  group_to_instances = operator.attrgetter("group_to_instances")
2123

    
2124
  # Add fields for nodes
2125
  fields.extend([
2126
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
2127
     GQ_NODE, 0, _GetLength(group_to_nodes)),
2128
    (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
2129
     GQ_NODE, 0, _GetSortedList(group_to_nodes)),
2130
    ])
2131

    
2132
  # Add fields for instances
2133
  fields.extend([
2134
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
2135
                "Number of primary instances"),
2136
     GQ_INST, 0, _GetLength(group_to_instances)),
2137
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
2138
                "List of primary instances"),
2139
     GQ_INST, 0, _GetSortedList(group_to_instances)),
2140
    ])
2141

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

    
2166
  # ND parameters
2167
  fields.extend(_BuildNDFields(True))
2168

    
2169
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
2170

    
2171
  return _PrepareFieldList(fields, [])
2172

    
2173

    
2174
class OsInfo(objects.ConfigObject):
2175
  __slots__ = [
2176
    "name",
2177
    "valid",
2178
    "hidden",
2179
    "blacklisted",
2180
    "variants",
2181
    "api_versions",
2182
    "parameters",
2183
    "node_status",
2184
    ]
2185

    
2186

    
2187
def _BuildOsFields():
2188
  """Builds list of fields for operating system queries.
2189

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

    
2218
  return _PrepareFieldList(fields, [])
2219

    
2220

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

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

2227
  """
2228
  if job is None:
2229
    return _FS_UNAVAIL
2230
  else:
2231
    return fn(job)
2232

    
2233

    
2234
def _JobUnavail(inner):
2235
  """Wrapper for L{_JobUnavailInner}.
2236

2237
  """
2238
  return compat.partial(_JobUnavailInner, inner)
2239

    
2240

    
2241
def _PerJobOpInner(fn, job):
2242
  """Executes a function per opcode in a job.
2243

2244
  """
2245
  return map(fn, job.ops)
2246

    
2247

    
2248
def _PerJobOp(fn):
2249
  """Wrapper for L{_PerJobOpInner}.
2250

2251
  """
2252
  return _JobUnavail(compat.partial(_PerJobOpInner, fn))
2253

    
2254

    
2255
def _JobTimestampInner(fn, job):
2256
  """Converts unavailable timestamp to L{_FS_UNAVAIL}.
2257

2258
  """
2259
  timestamp = fn(job)
2260

    
2261
  if timestamp is None:
2262
    return _FS_UNAVAIL
2263
  else:
2264
    return timestamp
2265

    
2266

    
2267
def _JobTimestamp(fn):
2268
  """Wrapper for L{_JobTimestampInner}.
2269

2270
  """
2271
  return _JobUnavail(compat.partial(_JobTimestampInner, fn))
2272

    
2273

    
2274
def _BuildJobFields():
2275
  """Builds list of fields for job queries.
2276

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

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

    
2332
  return _PrepareFieldList(fields, [])
2333

    
2334

    
2335
def _GetExportName(_, (node_name, expname)): # pylint: disable=W0613
2336
  """Returns an export name if available.
2337

2338
  """
2339
  if expname is None:
2340
    return _FS_UNAVAIL
2341
  else:
2342
    return expname
2343

    
2344

    
2345
def _BuildExportFields():
2346
  """Builds list of fields for exports.
2347

2348
  """
2349
  fields = [
2350
    (_MakeField("node", "Node", QFT_TEXT, "Node name"),
2351
     None, QFF_HOSTNAME, lambda _, (node_name, expname): node_name),
2352
    (_MakeField("export", "Export", QFT_TEXT, "Export name"),
2353
     None, 0, _GetExportName),
2354
    ]
2355

    
2356
  return _PrepareFieldList(fields, [])
2357

    
2358

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

    
2373

    
2374
_CLUSTER_SIMPLE_FIELDS = {
2375
  "cluster_name": ("Name", QFT_TEXT, QFF_HOSTNAME, "Cluster name"),
2376
  "master_node": ("Master", QFT_TEXT, QFF_HOSTNAME, "Master node name"),
2377
  "volume_group_name": ("VgName", QFT_TEXT, 0, "LVM volume group name"),
2378
  }
2379

    
2380

    
2381
class ClusterQueryData:
2382
  def __init__(self, cluster, drain_flag, watcher_pause):
2383
    """Initializes this class.
2384

2385
    @type cluster: L{objects.Cluster}
2386
    @param cluster: Instance of cluster object
2387
    @type drain_flag: bool
2388
    @param drain_flag: Whether job queue is drained
2389
    @type watcher_pause: number
2390
    @param watcher_pause: Until when watcher is paused (Unix timestamp)
2391

2392
    """
2393
    self._cluster = cluster
2394
    self.drain_flag = drain_flag
2395
    self.watcher_pause = watcher_pause
2396

    
2397
  def __iter__(self):
2398
    return iter([self._cluster])
2399

    
2400

    
2401
def _ClusterWatcherPause(ctx, _):
2402
  """Returns until when watcher is paused (if available).
2403

2404
  """
2405
  if ctx.watcher_pause is None:
2406
    return _FS_UNAVAIL
2407
  else:
2408
    return ctx.watcher_pause
2409

    
2410

    
2411
def _BuildClusterFields():
2412
  """Builds list of fields for cluster information.
2413

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

    
2429
  # Simple fields
2430
  fields.extend([
2431
    (_MakeField(name, title, kind, doc), CQ_CONFIG, flags, _GetItemAttr(name))
2432
    for (name, (title, kind, flags, doc)) in _CLUSTER_SIMPLE_FIELDS.items()
2433
    ],)
2434

    
2435
  # Version fields
2436
  fields.extend([
2437
    (_MakeField(name, title, kind, doc), None, 0, _StaticValue(value))
2438
    for (name, (title, kind, value, doc)) in _CLUSTER_VERSION_FIELDS.items()])
2439

    
2440
  # Add timestamps
2441
  fields.extend(_GetItemTimestampFields(CQ_CONFIG))
2442

    
2443
  return _PrepareFieldList(fields, [
2444
    ("name", "cluster_name")])
2445

    
2446

    
2447
class NetworkQueryData:
2448
  """Data container for network data queries.
2449

2450
  """
2451
  def __init__(self, networks, network_to_groups,
2452
               network_to_instances, stats):
2453
    """Initializes this class.
2454

2455
    @param networks: List of network objects
2456
    @type network_to_groups: dict; network UUID as key
2457
    @param network_to_groups: Per-network list of groups
2458
    @type network_to_instances: dict; network UUID as key
2459
    @param network_to_instances: Per-network list of instances
2460
    @type stats: dict; network UUID as key
2461
    @param stats: Per-network usage statistics
2462

2463
    """
2464
    self.networks = networks
2465
    self.network_to_groups = network_to_groups
2466
    self.network_to_instances = network_to_instances
2467
    self.stats = stats
2468

    
2469
  def __iter__(self):
2470
    """Iterate over all networks.
2471

2472
    """
2473
    for net in self.networks:
2474
      if self.stats:
2475
        self.curstats = self.stats.get(net.uuid, None)
2476
      else:
2477
        self.curstats = None
2478
      yield net
2479

    
2480

    
2481
_NETWORK_SIMPLE_FIELDS = {
2482
  "name": ("Network", QFT_TEXT, 0, "Name"),
2483
  "network": ("Subnet", QFT_TEXT, 0, "IPv4 subnet"),
2484
  "gateway": ("Gateway", QFT_OTHER, 0, "IPv4 gateway"),
2485
  "network6": ("IPv6Subnet", QFT_OTHER, 0, "IPv6 subnet"),
2486
  "gateway6": ("IPv6Gateway", QFT_OTHER, 0, "IPv6 gateway"),
2487
  "mac_prefix": ("MacPrefix", QFT_OTHER, 0, "MAC address prefix"),
2488
  "network_type": ("NetworkType", QFT_OTHER, 0, "Network type"),
2489
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Network"),
2490
  "uuid": ("UUID", QFT_TEXT, 0, "Network UUID"),
2491
  }
2492

    
2493

    
2494
_NETWORK_STATS_FIELDS = {
2495
  "free_count": ("FreeCount", QFT_NUMBER, 0, "Number of available addresses"),
2496
  "reserved_count":
2497
    ("ReservedCount", QFT_NUMBER, 0, "Number of reserved addresses"),
2498
  "map": ("Map", QFT_TEXT, 0, "Actual mapping"),
2499
  "external_reservations":
2500
    ("ExternalReservations", QFT_TEXT, 0, "External reservations"),
2501
  }
2502

    
2503

    
2504
def _GetNetworkStatsField(field, kind, ctx, _):
2505
  """Gets the value of a "stats" field from L{NetworkQueryData}.
2506

2507
  @param field: Field name
2508
  @param kind: Data kind, one of L{constants.QFT_ALL}
2509
  @type ctx: L{NetworkQueryData}
2510

2511
  """
2512

    
2513
  try:
2514
    value = ctx.curstats[field]
2515
  except KeyError:
2516
    return _FS_UNAVAIL
2517

    
2518
  if kind == QFT_TEXT:
2519
    return value
2520

    
2521
  assert kind in (QFT_NUMBER, QFT_UNIT)
2522

    
2523
  # Try to convert into number
2524
  try:
2525
    return int(value)
2526
  except (ValueError, TypeError):
2527
    logging.exception("Failed to convert network field '%s' (value %r) to int",
2528
                      field, value)
2529
    return _FS_UNAVAIL
2530

    
2531

    
2532
def _BuildNetworkFields():
2533
  """Builds list of fields for network queries.
2534

2535
  """
2536
  fields = [
2537
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
2538
     lambda ctx, inst: list(inst.GetTags())),
2539
    ]
2540

    
2541
  # Add simple fields
2542
  fields.extend([
2543
    (_MakeField(name, title, kind, doc),
2544
     NETQ_CONFIG, 0, _GetItemAttr(name))
2545
     for (name, (title, kind, _, doc)) in _NETWORK_SIMPLE_FIELDS.items()])
2546

    
2547
  def _GetLength(getter):
2548
    return lambda ctx, network: len(getter(ctx)[network.uuid])
2549

    
2550
  def _GetSortedList(getter):
2551
    return lambda ctx, network: utils.NiceSort(getter(ctx)[network.uuid])
2552

    
2553
  network_to_groups = operator.attrgetter("network_to_groups")
2554
  network_to_instances = operator.attrgetter("network_to_instances")
2555

    
2556
  # Add fields for node groups
2557
  fields.extend([
2558
    (_MakeField("group_cnt", "NodeGroups", QFT_NUMBER, "Number of nodegroups"),
2559
     NETQ_GROUP, 0, _GetLength(network_to_groups)),
2560
    (_MakeField("group_list", "GroupList", QFT_OTHER, "List of nodegroups"),
2561
     NETQ_GROUP, 0, _GetSortedList(network_to_groups)),
2562
    ])
2563

    
2564
  # Add fields for instances
2565
  fields.extend([
2566
    (_MakeField("inst_cnt", "Instances", QFT_NUMBER, "Number of instances"),
2567
     NETQ_INST, 0, _GetLength(network_to_instances)),
2568
    (_MakeField("inst_list", "InstanceList", QFT_OTHER, "List of instances"),
2569
     NETQ_INST, 0, _GetSortedList(network_to_instances)),
2570
    ])
2571

    
2572
  # Add fields for usage statistics
2573
  fields.extend([
2574
    (_MakeField(name, title, kind, doc), NETQ_STATS, 0,
2575
    compat.partial(_GetNetworkStatsField, name, kind))
2576
    for (name, (title, kind, _, doc)) in _NETWORK_STATS_FIELDS.items()])
2577

    
2578
  return _PrepareFieldList(fields, [])
2579

    
2580
#: Fields for cluster information
2581
CLUSTER_FIELDS = _BuildClusterFields()
2582

    
2583
#: Fields available for node queries
2584
NODE_FIELDS = _BuildNodeFields()
2585

    
2586
#: Fields available for instance queries
2587
INSTANCE_FIELDS = _BuildInstanceFields()
2588

    
2589
#: Fields available for lock queries
2590
LOCK_FIELDS = _BuildLockFields()
2591

    
2592
#: Fields available for node group queries
2593
GROUP_FIELDS = _BuildGroupFields()
2594

    
2595
#: Fields available for operating system queries
2596
OS_FIELDS = _BuildOsFields()
2597

    
2598
#: Fields available for job queries
2599
JOB_FIELDS = _BuildJobFields()
2600

    
2601
#: Fields available for exports
2602
EXPORT_FIELDS = _BuildExportFields()
2603

    
2604
#: Fields available for network queries
2605
NETWORK_FIELDS = _BuildNetworkFields()
2606

    
2607
#: All available resources
2608
ALL_FIELDS = {
2609
  constants.QR_CLUSTER: CLUSTER_FIELDS,
2610
  constants.QR_INSTANCE: INSTANCE_FIELDS,
2611
  constants.QR_NODE: NODE_FIELDS,
2612
  constants.QR_LOCK: LOCK_FIELDS,
2613
  constants.QR_GROUP: GROUP_FIELDS,
2614
  constants.QR_OS: OS_FIELDS,
2615
  constants.QR_JOB: JOB_FIELDS,
2616
  constants.QR_EXPORT: EXPORT_FIELDS,
2617
  constants.QR_NETWORK: NETWORK_FIELDS,
2618
  }
2619

    
2620
#: All available field lists
2621
ALL_FIELD_LISTS = ALL_FIELDS.values()