Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ 93f1e606

History | View | Annotate | Download (86.3 kB)

1
#
2
#
3

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

    
21

    
22
"""Module for query operations
23

24
How it works:
25

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

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

53
"""
54

    
55
import logging
56
import operator
57
import re
58

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
161

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

165
  """
166
  return _FS_UNKNOWN
167

    
168

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

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

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

179
  """
180
  result = []
181

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

    
189
    assert len(fdef) == 4
190

    
191
    result.append(fdef)
192

    
193
  return result
194

    
195

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

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

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

    
204

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

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

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

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

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

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

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

235
    """
236
    self._namefield = namefield
237

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

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

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

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

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

254
    @rtype: list
255

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

    
260
    return utils.UniqueSequence(self._names)
261

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

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

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

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

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

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

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

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

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

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

    
295
    self._NeedAllNames()
296

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

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

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

    
310
    if self._allnames:
311
      return
312

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

    
322

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

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

    
329

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

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

    
336

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

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

    
343

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

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

    
350

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

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

    
360

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

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

    
370

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

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

    
377

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

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

    
390

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

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

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

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

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

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

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

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

    
425
  """Known operators
426

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

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

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

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

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

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

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

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

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

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

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

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

    
499
    return filter_fn
500

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

573
    """
574
    assert op_fn is None
575

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

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

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

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

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

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

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

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

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

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

    
622
    assert fdef.kind != QFT_UNKNOWN
623

    
624
    # TODO: Type conversions?
625

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

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

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

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

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

    
647

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

651
  See L{_FilterCompilerHelper} for details.
652

653
  @rtype: callable
654

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

    
658

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

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

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

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

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

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

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

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

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

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

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

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

711
    """
712
    return self._requested_names
713

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

717
    @rtype: frozenset
718

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

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

727
    Includes unknown fields.
728

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

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

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

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

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

    
746
    result = []
747

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

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

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

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

    
767
    if not sort:
768
      return result
769

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

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

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

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

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

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

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

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

    
795

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

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

    
811

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

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

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

    
833

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

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

    
847
  return fdef.name
848

    
849

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

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

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

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

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

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

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

    
885
  return result
886

    
887

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

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

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

    
901

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

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

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

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

    
922

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

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

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

    
935

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

939
  """
940
  return value
941

    
942

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

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

    
949

    
950
def _GetNodeRole(node, master_uuid):
951
  """Determine node role.
952

953
  @type node: L{objects.Node}
954
  @param node: Node object
955
  @type master_uuid: string
956
  @param master_uuid: Master node UUID
957

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

    
970

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

974
  @param attr: Attribute name
975

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

    
980

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

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

986
  @param attr: Attribute name
987

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

    
997

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

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

    
1009

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

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

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

    
1027

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

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

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

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

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

    
1045

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

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

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

    
1055

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

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

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

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

    
1074
  return fn
1075

    
1076

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

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

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

    
1090

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

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

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

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

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

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

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

    
1134

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

    
1149

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

    
1180

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

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

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

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

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

    
1200
    return cb(ctx, node, ng)
1201

    
1202
  return fn
1203

    
1204

    
1205
def _GetNodeGroup(ctx, node, ng): # pylint: disable=W0613
1206
  """Returns the name of a node's group.
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 ng.name
1216

    
1217

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

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

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

    
1229
  return _FS_UNAVAIL
1230

    
1231

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

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

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

    
1244

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

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

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

    
1258
  if not node.vm_capable:
1259
    return _FS_UNAVAIL
1260

    
1261
  if not ctx.curlive_data:
1262
    return _FS_NODATA
1263

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

    
1266

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

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

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

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

    
1285
  if kind == QFT_TEXT:
1286
    return value
1287

    
1288
  assert kind in (QFT_NUMBER, QFT_UNIT)
1289

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

    
1298

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

1302
  """
1303
  hv_state = node.hv_state
1304

    
1305
  if hv_state is None:
1306
    return _FS_UNAVAIL
1307

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

    
1310

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

1314
  """
1315
  disk_state = node.disk_state
1316

    
1317
  if disk_state is None:
1318
    return _FS_UNAVAIL
1319

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

    
1324

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

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

    
1357
  fields.extend(_BuildNDFields(False))
1358

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

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

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

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

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

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

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

    
1407
  return _PrepareFieldList(fields, [])
1408

    
1409

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

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

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

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

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

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

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

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

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

    
1477
      yield inst
1478

    
1479

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

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

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

    
1495

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

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

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

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

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

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

    
1522
    return _FS_UNAVAIL
1523

    
1524
  return fn
1525

    
1526

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

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

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

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

    
1541
  instance_live_data = ctx.live_data.get(inst.uuid)
1542

    
1543
  if bool(instance_live_data):
1544
    instance_state = instance_live_data["state"]
1545

    
1546
    if inst.uuid in ctx.wrongnode_inst:
1547
      return constants.INSTST_WRONGNODE
1548
    else:
1549
      if hv_base.HvInstanceState.IsShutdown(instance_state):
1550
        if inst.admin_state == constants.ADMINST_UP:
1551
          return constants.INSTST_USERDOWN
1552
        else:
1553
          return constants.INSTST_ADMINDOWN
1554
      else:
1555
        if inst.admin_state == constants.ADMINST_UP:
1556
          return constants.INSTST_RUNNING
1557
        else:
1558
          return constants.INSTST_ERRORUP
1559

    
1560
  if inst.admin_state == constants.ADMINST_UP:
1561
    return constants.INSTST_ERRORDOWN
1562
  elif inst.admin_state == constants.ADMINST_DOWN:
1563
    return constants.INSTST_ADMINDOWN
1564

    
1565
  return constants.INSTST_ADMINOFFLINE
1566

    
1567

    
1568
def _GetInstDisk(index, cb):
1569
  """Build function for calling another function with an instance Disk.
1570

1571
  @type index: int
1572
  @param index: Disk index
1573
  @type cb: callable
1574
  @param cb: Callback
1575

1576
  """
1577
  def fn(ctx, inst):
1578
    """Call helper function with instance Disk.
1579

1580
    @type ctx: L{InstanceQueryData}
1581
    @type inst: L{objects.Instance}
1582
    @param inst: Instance object
1583

1584
    """
1585
    try:
1586
      nic = inst.disks[index]
1587
    except IndexError:
1588
      return _FS_UNAVAIL
1589

    
1590
    return cb(ctx, index, nic)
1591

    
1592
  return fn
1593

    
1594

    
1595
def _GetInstDiskSize(ctx, _, disk): # pylint: disable=W0613
1596
  """Get a Disk's size.
1597

1598
  @type ctx: L{InstanceQueryData}
1599
  @type disk: L{objects.Disk}
1600
  @param disk: The Disk object
1601

1602
  """
1603
  if disk.size is None:
1604
    return _FS_UNAVAIL
1605
  else:
1606
    return disk.size
1607

    
1608

    
1609
def _GetInstDiskSpindles(ctx, _, disk): # pylint: disable=W0613
1610
  """Get a Disk's spindles.
1611

1612
  @type disk: L{objects.Disk}
1613
  @param disk: The Disk object
1614

1615
  """
1616
  if disk.spindles is None:
1617
    return _FS_UNAVAIL
1618
  else:
1619
    return disk.spindles
1620

    
1621

    
1622
def _GetInstDeviceName(ctx, _, device): # pylint: disable=W0613
1623
  """Get a Device's Name.
1624

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

1629
  """
1630
  if device.name is None:
1631
    return _FS_UNAVAIL
1632
  else:
1633
    return device.name
1634

    
1635

    
1636
def _GetInstDeviceUUID(ctx, _, device): # pylint: disable=W0613
1637
  """Get a Device's UUID.
1638

1639
  @type ctx: L{InstanceQueryData}
1640
  @type device: L{objects.NIC} or L{objects.Disk}
1641
  @param device: The NIC or Disk object
1642

1643
  """
1644
  if device.uuid is None:
1645
    return _FS_UNAVAIL
1646
  else:
1647
    return device.uuid
1648

    
1649

    
1650
def _GetInstNic(index, cb):
1651
  """Build function for calling another function with an instance NIC.
1652

1653
  @type index: int
1654
  @param index: NIC index
1655
  @type cb: callable
1656
  @param cb: Callback
1657

1658
  """
1659
  def fn(ctx, inst):
1660
    """Call helper function with instance NIC.
1661

1662
    @type ctx: L{InstanceQueryData}
1663
    @type inst: L{objects.Instance}
1664
    @param inst: Instance object
1665

1666
    """
1667
    try:
1668
      nic = inst.nics[index]
1669
    except IndexError:
1670
      return _FS_UNAVAIL
1671

    
1672
    return cb(ctx, index, nic)
1673

    
1674
  return fn
1675

    
1676

    
1677
def _GetInstNicNetworkName(ctx, _, nic): # pylint: disable=W0613
1678
  """Get a NIC's Network.
1679

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

1684
  """
1685
  if nic.network is None:
1686
    return _FS_UNAVAIL
1687
  else:
1688
    return ctx.networks[nic.network].name
1689

    
1690

    
1691
def _GetInstNicNetwork(ctx, _, nic): # pylint: disable=W0613
1692
  """Get a NIC's Network.
1693

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

1698
  """
1699
  if nic.network is None:
1700
    return _FS_UNAVAIL
1701
  else:
1702
    return nic.network
1703

    
1704

    
1705
def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613
1706
  """Get a NIC's IP address.
1707

1708
  @type ctx: L{InstanceQueryData}
1709
  @type nic: L{objects.NIC}
1710
  @param nic: NIC object
1711

1712
  """
1713
  if nic.ip is None:
1714
    return _FS_UNAVAIL
1715
  else:
1716
    return nic.ip
1717

    
1718

    
1719
def _GetInstNicBridge(ctx, index, _):
1720
  """Get a NIC's bridge.
1721

1722
  @type ctx: L{InstanceQueryData}
1723
  @type index: int
1724
  @param index: NIC index
1725

1726
  """
1727
  assert len(ctx.inst_nicparams) >= index
1728

    
1729
  nicparams = ctx.inst_nicparams[index]
1730

    
1731
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1732
    return nicparams[constants.NIC_LINK]
1733
  else:
1734
    return _FS_UNAVAIL
1735

    
1736

    
1737
def _GetInstNicVLan(ctx, index, _):
1738
  """Get a NIC's VLAN.
1739

1740
  @type ctx: L{InstanceQueryData}
1741
  @type index: int
1742
  @param index: NIC index
1743

1744
  """
1745
  assert len(ctx.inst_nicparams) >= index
1746

    
1747
  nicparams = ctx.inst_nicparams[index]
1748

    
1749
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_OVS:
1750
    return nicparams[constants.NIC_VLAN]
1751
  else:
1752
    return _FS_UNAVAIL
1753

    
1754

    
1755
def _GetInstAllNicNetworkNames(ctx, inst):
1756
  """Get all network names for an instance.
1757

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

1762
  """
1763
  result = []
1764

    
1765
  for nic in inst.nics:
1766
    name = None
1767
    if nic.network:
1768
      name = ctx.networks[nic.network].name
1769
    result.append(name)
1770

    
1771
  assert len(result) == len(inst.nics)
1772

    
1773
  return result
1774

    
1775

    
1776
def _GetInstAllNicVlans(ctx, inst):
1777
  """Get all network VLANs for an instance.
1778

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

1783
  """
1784
  assert len(ctx.inst_nicparams) == len(inst.nics)
1785

    
1786
  result = []
1787

    
1788
  for nicp in ctx.inst_nicparams:
1789
    if nicp[constants.NIC_MODE] in \
1790
          [constants.NIC_MODE_BRIDGED, constants.NIC_MODE_OVS]:
1791
      result.append(nicp[constants.NIC_VLAN])
1792
    else:
1793
      result.append(None)
1794

    
1795
  assert len(result) == len(inst.nics)
1796

    
1797
  return result
1798

    
1799

    
1800
def _GetInstAllNicBridges(ctx, inst):
1801
  """Get all network bridges for an instance.
1802

1803
  @type ctx: L{InstanceQueryData}
1804
  @type inst: L{objects.Instance}
1805
  @param inst: Instance object
1806

1807
  """
1808
  assert len(ctx.inst_nicparams) == len(inst.nics)
1809

    
1810
  result = []
1811

    
1812
  for nicp in ctx.inst_nicparams:
1813
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1814
      result.append(nicp[constants.NIC_LINK])
1815
    else:
1816
      result.append(None)
1817

    
1818
  assert len(result) == len(inst.nics)
1819

    
1820
  return result
1821

    
1822

    
1823
def _GetInstNicParam(name):
1824
  """Build function for retrieving a NIC parameter.
1825

1826
  @type name: string
1827
  @param name: Parameter name
1828

1829
  """
1830
  def fn(ctx, index, _):
1831
    """Get a NIC's bridge.
1832

1833
    @type ctx: L{InstanceQueryData}
1834
    @type inst: L{objects.Instance}
1835
    @param inst: Instance object
1836
    @type nic: L{objects.NIC}
1837
    @param nic: NIC object
1838

1839
    """
1840
    assert len(ctx.inst_nicparams) >= index
1841
    return ctx.inst_nicparams[index][name]
1842

    
1843
  return fn
1844

    
1845

    
1846
def _GetInstanceNetworkFields():
1847
  """Get instance fields involving network interfaces.
1848

1849
  @return: Tuple containing list of field definitions used as input for
1850
    L{_PrepareFieldList} and a list of aliases
1851

1852
  """
1853
  nic_mac_fn = lambda ctx, _, nic: nic.mac
1854
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1855
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1856

    
1857
  fields = [
1858
    # All NICs
1859
    (_MakeField("nic.count", "NICs", QFT_NUMBER,
1860
                "Number of network interfaces"),
1861
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.nics)),
1862
    (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER,
1863
                "List containing each network interface's MAC address"),
1864
     IQ_CONFIG, 0, lambda ctx, inst: [nic.mac for nic in inst.nics]),
1865
    (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
1866
                "List containing each network interface's IP address"),
1867
     IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
1868
    (_MakeField("nic.names", "NIC_Names", QFT_OTHER,
1869
                "List containing each network interface's name"),
1870
     IQ_CONFIG, 0, lambda ctx, inst: [nic.name for nic in inst.nics]),
1871
    (_MakeField("nic.uuids", "NIC_UUIDs", QFT_OTHER,
1872
                "List containing each network interface's UUID"),
1873
     IQ_CONFIG, 0, lambda ctx, inst: [nic.uuid for nic in inst.nics]),
1874
    (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
1875
                "List containing each network interface's mode"), IQ_CONFIG, 0,
1876
     lambda ctx, inst: [nicp[constants.NIC_MODE]
1877
                        for nicp in ctx.inst_nicparams]),
1878
    (_MakeField("nic.links", "NIC_links", QFT_OTHER,
1879
                "List containing each network interface's link"), IQ_CONFIG, 0,
1880
     lambda ctx, inst: [nicp[constants.NIC_LINK]
1881
                        for nicp in ctx.inst_nicparams]),
1882
    (_MakeField("nic.vlans", "NIC_VLANs", QFT_OTHER,
1883
                "List containing each network interface's VLAN"),
1884
     IQ_CONFIG, 0, _GetInstAllNicVlans),
1885
    (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
1886
                "List containing each network interface's bridge"),
1887
     IQ_CONFIG, 0, _GetInstAllNicBridges),
1888
    (_MakeField("nic.networks", "NIC_networks", QFT_OTHER,
1889
                "List containing each interface's network"), IQ_CONFIG, 0,
1890
     lambda ctx, inst: [nic.network for nic in inst.nics]),
1891
    (_MakeField("nic.networks.names", "NIC_networks_names", QFT_OTHER,
1892
                "List containing each interface's network"),
1893
     IQ_NETWORKS, 0, _GetInstAllNicNetworkNames)
1894
    ]
1895

    
1896
  # NICs by number
1897
  for i in range(constants.MAX_NICS):
1898
    numtext = utils.FormatOrdinal(i + 1)
1899
    fields.extend([
1900
      (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT,
1901
                  "IP address of %s network interface" % numtext),
1902
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)),
1903
      (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
1904
                  "MAC address of %s network interface" % numtext),
1905
       IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
1906
      (_MakeField("nic.name/%s" % i, "NicName/%s" % i, QFT_TEXT,
1907
                  "Name address of %s network interface" % numtext),
1908
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstDeviceName)),
1909
      (_MakeField("nic.uuid/%s" % i, "NicUUID/%s" % i, QFT_TEXT,
1910
                  "UUID address of %s network interface" % numtext),
1911
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstDeviceUUID)),
1912
      (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
1913
                  "Mode of %s network interface" % numtext),
1914
       IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
1915
      (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT,
1916
                  "Link of %s network interface" % numtext),
1917
       IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)),
1918
      (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
1919
                  "Bridge of %s network interface" % numtext),
1920
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
1921
      (_MakeField("nic.vlan/%s" % i, "NicVLAN/%s" % i, QFT_TEXT,
1922
                  "VLAN of %s network interface" % numtext),
1923
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicVLan)),
1924
      (_MakeField("nic.network/%s" % i, "NicNetwork/%s" % i, QFT_TEXT,
1925
                  "Network of %s network interface" % numtext),
1926
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicNetwork)),
1927
      (_MakeField("nic.network.name/%s" % i, "NicNetworkName/%s" % i, QFT_TEXT,
1928
                  "Network name of %s network interface" % numtext),
1929
       IQ_NETWORKS, 0, _GetInstNic(i, _GetInstNicNetworkName)),
1930
      ])
1931

    
1932
  aliases = [
1933
    # Legacy fields for first NIC
1934
    ("ip", "nic.ip/0"),
1935
    ("mac", "nic.mac/0"),
1936
    ("bridge", "nic.bridge/0"),
1937
    ("nic_mode", "nic.mode/0"),
1938
    ("nic_link", "nic.link/0"),
1939
    ("nic_network", "nic.network/0"),
1940
    ]
1941

    
1942
  return (fields, aliases)
1943

    
1944

    
1945
def _GetInstDiskUsage(ctx, inst):
1946
  """Get disk usage for an instance.
1947

1948
  @type ctx: L{InstanceQueryData}
1949
  @type inst: L{objects.Instance}
1950
  @param inst: Instance object
1951

1952
  """
1953
  usage = ctx.disk_usage[inst.uuid]
1954

    
1955
  if usage is None:
1956
    usage = 0
1957

    
1958
  return usage
1959

    
1960

    
1961
def _GetInstanceConsole(ctx, inst):
1962
  """Get console information for instance.
1963

1964
  @type ctx: L{InstanceQueryData}
1965
  @type inst: L{objects.Instance}
1966
  @param inst: Instance object
1967

1968
  """
1969
  consinfo = ctx.console[inst.uuid]
1970

    
1971
  if consinfo is None:
1972
    return _FS_UNAVAIL
1973

    
1974
  return consinfo
1975

    
1976

    
1977
def _GetInstanceDiskFields():
1978
  """Get instance fields involving disks.
1979

1980
  @return: List of field definitions used as input for L{_PrepareFieldList}
1981

1982
  """
1983
  fields = [
1984
    (_MakeField("disk_usage", "DiskUsage", QFT_UNIT,
1985
                "Total disk space used by instance on each of its nodes;"
1986
                " this is not the disk size visible to the instance, but"
1987
                " the usage on the node"),
1988
     IQ_DISKUSAGE, 0, _GetInstDiskUsage),
1989
    (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"),
1990
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
1991
    (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
1992
     IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
1993
    (_MakeField("disk.spindles", "Disk_spindles", QFT_OTHER,
1994
                "List of disk spindles"),
1995
     IQ_CONFIG, 0, lambda ctx, inst: [disk.spindles for disk in inst.disks]),
1996
    (_MakeField("disk.names", "Disk_names", QFT_OTHER, "List of disk names"),
1997
     IQ_CONFIG, 0, lambda ctx, inst: [disk.name for disk in inst.disks]),
1998
    (_MakeField("disk.uuids", "Disk_UUIDs", QFT_OTHER, "List of disk UUIDs"),
1999
     IQ_CONFIG, 0, lambda ctx, inst: [disk.uuid for disk in inst.disks]),
2000
    ]
2001

    
2002
  # Disks by number
2003
  for i in range(constants.MAX_DISKS):
2004
    numtext = utils.FormatOrdinal(i + 1)
2005
    fields.extend([
2006
        (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
2007
                    "Disk size of %s disk" % numtext),
2008
         IQ_CONFIG, 0, _GetInstDisk(i, _GetInstDiskSize)),
2009
        (_MakeField("disk.spindles/%s" % i, "DiskSpindles/%s" % i, QFT_NUMBER,
2010
                    "Spindles of %s disk" % numtext),
2011
         IQ_CONFIG, 0, _GetInstDisk(i, _GetInstDiskSpindles)),
2012
        (_MakeField("disk.name/%s" % i, "DiskName/%s" % i, QFT_TEXT,
2013
                    "Name of %s disk" % numtext),
2014
         IQ_CONFIG, 0, _GetInstDisk(i, _GetInstDeviceName)),
2015
        (_MakeField("disk.uuid/%s" % i, "DiskUUID/%s" % i, QFT_TEXT,
2016
                    "UUID of %s disk" % numtext),
2017
         IQ_CONFIG, 0, _GetInstDisk(i, _GetInstDeviceUUID))])
2018

    
2019
  return fields
2020

    
2021

    
2022
def _GetInstanceParameterFields():
2023
  """Get instance fields involving parameters.
2024

2025
  @return: List of field definitions used as input for L{_PrepareFieldList}
2026

2027
  """
2028
  fields = [
2029
    # Filled parameters
2030
    (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
2031
                "Hypervisor parameters (merged)"),
2032
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
2033
    (_MakeField("beparams", "BackendParameters", QFT_OTHER,
2034
                "Backend parameters (merged)"),
2035
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
2036
    (_MakeField("osparams", "OpSysParameters", QFT_OTHER,
2037
                "Operating system parameters (merged)"),
2038
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_osparams),
2039

    
2040
    # Unfilled parameters
2041
    (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
2042
                "Custom hypervisor parameters"),
2043
     IQ_CONFIG, 0, _GetItemAttr("hvparams")),
2044
    (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
2045
                "Custom backend parameters",),
2046
     IQ_CONFIG, 0, _GetItemAttr("beparams")),
2047
    (_MakeField("custom_osparams", "CustomOpSysParameters", QFT_OTHER,
2048
                "Custom operating system parameters",),
2049
     IQ_CONFIG, 0, _GetItemAttr("osparams")),
2050
    (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
2051
                "Custom network interface parameters"),
2052
     IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
2053
    ]
2054

    
2055
  # HV params
2056
  def _GetInstHvParam(name):
2057
    return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
2058

    
2059
  fields.extend([
2060
    (_MakeField("hv/%s" % name,
2061
                constants.HVS_PARAMETER_TITLES.get(name, "hv/%s" % name),
2062
                _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
2063
     IQ_CONFIG, 0, _GetInstHvParam(name))
2064
    for name, kind in constants.HVS_PARAMETER_TYPES.items()
2065
    if name not in constants.HVC_GLOBALS])
2066

    
2067
  # BE params
2068
  def _GetInstBeParam(name):
2069
    return lambda ctx, _: ctx.inst_beparams.get(name, None)
2070

    
2071
  fields.extend([
2072
    (_MakeField("be/%s" % name,
2073
                constants.BES_PARAMETER_TITLES.get(name, "be/%s" % name),
2074
                _VTToQFT[kind], "The \"%s\" backend parameter" % name),
2075
     IQ_CONFIG, 0, _GetInstBeParam(name))
2076
    for name, kind in constants.BES_PARAMETER_TYPES.items()])
2077

    
2078
  return fields
2079

    
2080

    
2081
_INST_SIMPLE_FIELDS = {
2082
  "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"),
2083
  "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"),
2084
  "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"),
2085
  # Depending on the hypervisor, the port can be None
2086
  "network_port": ("Network_port", QFT_OTHER, 0,
2087
                   "Instance network port if available (e.g. for VNC console)"),
2088
  "os": ("OS", QFT_TEXT, 0, "Operating system"),
2089
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"),
2090
  "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"),
2091
  }
2092

    
2093

    
2094
def _GetNodeName(ctx, default, node_uuid):
2095
  """Gets node name of a node.
2096

2097
  @type ctx: L{InstanceQueryData}
2098
  @param default: Default value
2099
  @type node_uuid: string
2100
  @param node_uuid: Node UUID
2101

2102
  """
2103
  try:
2104
    node = ctx.nodes[node_uuid]
2105
  except KeyError:
2106
    return default
2107
  else:
2108
    return node.name
2109

    
2110

    
2111
def _GetInstNodeGroup(ctx, default, node_uuid):
2112
  """Gets group UUID of an instance node.
2113

2114
  @type ctx: L{InstanceQueryData}
2115
  @param default: Default value
2116
  @type node_uuid: string
2117
  @param node_uuid: Node UUID
2118

2119
  """
2120
  try:
2121
    node = ctx.nodes[node_uuid]
2122
  except KeyError:
2123
    return default
2124
  else:
2125
    return node.group
2126

    
2127

    
2128
def _GetInstNodeGroupName(ctx, default, node_uuid):
2129
  """Gets group name of an instance node.
2130

2131
  @type ctx: L{InstanceQueryData}
2132
  @param default: Default value
2133
  @type node_uuid: string
2134
  @param node_uuid: Node UUID
2135

2136
  """
2137
  try:
2138
    node = ctx.nodes[node_uuid]
2139
  except KeyError:
2140
    return default
2141

    
2142
  try:
2143
    group = ctx.groups[node.group]
2144
  except KeyError:
2145
    return default
2146

    
2147
  return group.name
2148

    
2149

    
2150
def _BuildInstanceFields():
2151
  """Builds list of fields for instance queries.
2152

2153
  """
2154
  fields = [
2155
    (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
2156
     IQ_NODES, QFF_HOSTNAME,
2157
     lambda ctx, inst: _GetNodeName(ctx, None, inst.primary_node)),
2158
    (_MakeField("pnode.group", "PrimaryNodeGroup", QFT_TEXT,
2159
                "Primary node's group"),
2160
     IQ_NODES, 0,
2161
     lambda ctx, inst: _GetInstNodeGroupName(ctx, _FS_UNAVAIL,
2162
                                             inst.primary_node)),
2163
    (_MakeField("pnode.group.uuid", "PrimaryNodeGroupUUID", QFT_TEXT,
2164
                "Primary node's group UUID"),
2165
     IQ_NODES, 0,
2166
     lambda ctx, inst: _GetInstNodeGroup(ctx, _FS_UNAVAIL, inst.primary_node)),
2167
    # TODO: Allow filtering by secondary node as hostname
2168
    (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
2169
                "Secondary nodes; usually this will just be one node"),
2170
     IQ_NODES, 0,
2171
     lambda ctx, inst: map(compat.partial(_GetNodeName, ctx, None),
2172
                           inst.secondary_nodes)),
2173
    (_MakeField("snodes.group", "SecondaryNodesGroups", QFT_OTHER,
2174
                "Node groups of secondary nodes"),
2175
     IQ_NODES, 0,
2176
     lambda ctx, inst: map(compat.partial(_GetInstNodeGroupName, ctx, None),
2177
                           inst.secondary_nodes)),
2178
    (_MakeField("snodes.group.uuid", "SecondaryNodesGroupsUUID", QFT_OTHER,
2179
                "Node group UUIDs of secondary nodes"),
2180
     IQ_NODES, 0,
2181
     lambda ctx, inst: map(compat.partial(_GetInstNodeGroup, ctx, None),
2182
                           inst.secondary_nodes)),
2183
    (_MakeField("admin_state", "InstanceState", QFT_TEXT,
2184
                "Desired state of instance"),
2185
     IQ_CONFIG, 0, _GetItemAttr("admin_state")),
2186
    (_MakeField("admin_up", "Autostart", QFT_BOOL,
2187
                "Desired state of instance"),
2188
     IQ_CONFIG, 0, lambda ctx, inst: inst.admin_state == constants.ADMINST_UP),
2189
    (_MakeField("disks_active", "DisksActive", QFT_BOOL,
2190
                "Desired state of instance disks"),
2191
     IQ_CONFIG, 0, _GetItemAttr("disks_active")),
2192
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
2193
     lambda ctx, inst: list(inst.GetTags())),
2194
    (_MakeField("console", "Console", QFT_OTHER,
2195
                "Instance console information"), IQ_CONSOLE, 0,
2196
     _GetInstanceConsole),
2197
    ]
2198

    
2199
  # Add simple fields
2200
  fields.extend([
2201
    (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
2202
    for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()])
2203

    
2204
  # Fields requiring talking to the node
2205
  fields.extend([
2206
    (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
2207
     IQ_LIVE, 0, _GetInstOperState),
2208
    (_MakeField("oper_ram", "Memory", QFT_UNIT,
2209
                "Actual memory usage as seen by hypervisor"),
2210
     IQ_LIVE, 0, _GetInstLiveData("memory")),
2211
    (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
2212
                "Actual number of VCPUs as seen by hypervisor"),
2213
     IQ_LIVE, 0, _GetInstLiveData("vcpus")),
2214
    ])
2215

    
2216
  # Status field
2217
  status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
2218
                   constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
2219
                   constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
2220
                   constants.INSTST_NODEOFFLINE, constants.INSTST_ADMINOFFLINE,
2221
                   constants.INSTST_USERDOWN)
2222
  status_doc = ("Instance status; \"%s\" if instance is set to be running"
2223
                " and actually is, \"%s\" if instance is stopped and"
2224
                " is not running, \"%s\" if instance running, but not on its"
2225
                " designated primary node, \"%s\" if instance should be"
2226
                " stopped, but is actually running, \"%s\" if instance should"
2227
                " run, but doesn't, \"%s\" if instance's primary node is down,"
2228
                " \"%s\" if instance's primary node is marked offline,"
2229
                " \"%s\" if instance is offline and does not use dynamic,"
2230
                " \"%s\" if the user shutdown the instance"
2231
                " resources" % status_values)
2232
  fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
2233
                 IQ_LIVE, 0, _GetInstStatus))
2234

    
2235
  assert set(status_values) == constants.INSTST_ALL, \
2236
         "Status documentation mismatch"
2237

    
2238
  (network_fields, network_aliases) = _GetInstanceNetworkFields()
2239

    
2240
  fields.extend(network_fields)
2241
  fields.extend(_GetInstanceParameterFields())
2242
  fields.extend(_GetInstanceDiskFields())
2243
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
2244

    
2245
  aliases = [
2246
    ("vcpus", "be/vcpus"),
2247
    ("be/memory", "be/maxmem"),
2248
    ("sda_size", "disk.size/0"),
2249
    ("sdb_size", "disk.size/1"),
2250
    ] + network_aliases
2251

    
2252
  return _PrepareFieldList(fields, aliases)
2253

    
2254

    
2255
class LockQueryData:
2256
  """Data container for lock data queries.
2257

2258
  """
2259
  def __init__(self, lockdata):
2260
    """Initializes this class.
2261

2262
    """
2263
    self.lockdata = lockdata
2264

    
2265
  def __iter__(self):
2266
    """Iterate over all locks.
2267

2268
    """
2269
    return iter(self.lockdata)
2270

    
2271

    
2272
def _GetLockOwners(_, data):
2273
  """Returns a sorted list of a lock's current owners.
2274

2275
  """
2276
  (_, _, owners, _) = data
2277

    
2278
  if owners:
2279
    owners = utils.NiceSort(owners)
2280

    
2281
  return owners
2282

    
2283

    
2284
def _GetLockPending(_, data):
2285
  """Returns a sorted list of a lock's pending acquires.
2286

2287
  """
2288
  (_, _, _, pending) = data
2289

    
2290
  if pending:
2291
    pending = [(mode, utils.NiceSort(names))
2292
               for (mode, names) in pending]
2293

    
2294
  return pending
2295

    
2296

    
2297
def _BuildLockFields():
2298
  """Builds list of fields for lock queries.
2299

2300
  """
2301
  return _PrepareFieldList([
2302
    # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
2303
    (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
2304
     lambda ctx, (name, mode, owners, pending): name),
2305
    (_MakeField("mode", "Mode", QFT_OTHER,
2306
                "Mode in which the lock is currently acquired"
2307
                " (exclusive or shared)"),
2308
     LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
2309
    (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
2310
     LQ_OWNER, 0, _GetLockOwners),
2311
    (_MakeField("pending", "Pending", QFT_OTHER,
2312
                "Threads waiting for the lock"),
2313
     LQ_PENDING, 0, _GetLockPending),
2314
    ], [])
2315

    
2316

    
2317
class GroupQueryData:
2318
  """Data container for node group data queries.
2319

2320
  """
2321
  def __init__(self, cluster, groups, group_to_nodes, group_to_instances,
2322
               want_diskparams):
2323
    """Initializes this class.
2324

2325
    @param cluster: Cluster object
2326
    @param groups: List of node group objects
2327
    @type group_to_nodes: dict; group UUID as key
2328
    @param group_to_nodes: Per-group list of nodes
2329
    @type group_to_instances: dict; group UUID as key
2330
    @param group_to_instances: Per-group list of (primary) instances
2331
    @type want_diskparams: bool
2332
    @param want_diskparams: Whether diskparamters should be calculated
2333

2334
    """
2335
    self.groups = groups
2336
    self.group_to_nodes = group_to_nodes
2337
    self.group_to_instances = group_to_instances
2338
    self.cluster = cluster
2339
    self.want_diskparams = want_diskparams
2340

    
2341
    # Used for individual rows
2342
    self.group_ipolicy = None
2343
    self.ndparams = None
2344
    self.group_dp = None
2345

    
2346
  def __iter__(self):
2347
    """Iterate over all node groups.
2348

2349
    This function has side-effects and only one instance of the resulting
2350
    generator should be used at a time.
2351

2352
    """
2353
    for group in self.groups:
2354
      self.group_ipolicy = self.cluster.SimpleFillIPolicy(group.ipolicy)
2355
      self.ndparams = self.cluster.SimpleFillND(group.ndparams)
2356
      if self.want_diskparams:
2357
        self.group_dp = self.cluster.SimpleFillDP(group.diskparams)
2358
      else:
2359
        self.group_dp = None
2360
      yield group
2361

    
2362

    
2363
_GROUP_SIMPLE_FIELDS = {
2364
  "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
2365
  "name": ("Group", QFT_TEXT, "Group name"),
2366
  "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
2367
  "uuid": ("UUID", QFT_TEXT, "Group UUID"),
2368
  }
2369

    
2370

    
2371
def _BuildGroupFields():
2372
  """Builds list of fields for node group queries.
2373

2374
  """
2375
  # Add simple fields
2376
  fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
2377
             _GetItemAttr(name))
2378
            for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
2379

    
2380
  def _GetLength(getter):
2381
    return lambda ctx, group: len(getter(ctx)[group.uuid])
2382

    
2383
  def _GetSortedList(getter):
2384
    return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
2385

    
2386
  group_to_nodes = operator.attrgetter("group_to_nodes")
2387
  group_to_instances = operator.attrgetter("group_to_instances")
2388

    
2389
  # Add fields for nodes
2390
  fields.extend([
2391
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
2392
     GQ_NODE, 0, _GetLength(group_to_nodes)),
2393
    (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
2394
     GQ_NODE, 0, _GetSortedList(group_to_nodes)),
2395
    ])
2396

    
2397
  # Add fields for instances
2398
  fields.extend([
2399
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
2400
                "Number of primary instances"),
2401
     GQ_INST, 0, _GetLength(group_to_instances)),
2402
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
2403
                "List of primary instances"),
2404
     GQ_INST, 0, _GetSortedList(group_to_instances)),
2405
    ])
2406

    
2407
  # Other fields
2408
  fields.extend([
2409
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), GQ_CONFIG, 0,
2410
     lambda ctx, group: list(group.GetTags())),
2411
    (_MakeField("ipolicy", "InstancePolicy", QFT_OTHER,
2412
                "Instance policy limitations (merged)"),
2413
     GQ_CONFIG, 0, lambda ctx, _: ctx.group_ipolicy),
2414
    (_MakeField("custom_ipolicy", "CustomInstancePolicy", QFT_OTHER,
2415
                "Custom instance policy limitations"),
2416
     GQ_CONFIG, 0, _GetItemAttr("ipolicy")),
2417
    (_MakeField("custom_ndparams", "CustomNDParams", QFT_OTHER,
2418
                "Custom node parameters"),
2419
     GQ_CONFIG, 0, _GetItemAttr("ndparams")),
2420
    (_MakeField("ndparams", "NDParams", QFT_OTHER,
2421
                "Node parameters"),
2422
     GQ_CONFIG, 0, lambda ctx, _: ctx.ndparams),
2423
    (_MakeField("diskparams", "DiskParameters", QFT_OTHER,
2424
                "Disk parameters (merged)"),
2425
     GQ_DISKPARAMS, 0, lambda ctx, _: ctx.group_dp),
2426
    (_MakeField("custom_diskparams", "CustomDiskParameters", QFT_OTHER,
2427
                "Custom disk parameters"),
2428
     GQ_CONFIG, 0, _GetItemAttr("diskparams")),
2429
    ])
2430

    
2431
  # ND parameters
2432
  fields.extend(_BuildNDFields(True))
2433

    
2434
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
2435

    
2436
  return _PrepareFieldList(fields, [])
2437

    
2438

    
2439
class OsInfo(objects.ConfigObject):
2440
  __slots__ = [
2441
    "name",
2442
    "valid",
2443
    "hidden",
2444
    "blacklisted",
2445
    "variants",
2446
    "api_versions",
2447
    "parameters",
2448
    "node_status",
2449
    ]
2450

    
2451

    
2452
def _BuildOsFields():
2453
  """Builds list of fields for operating system queries.
2454

2455
  """
2456
  fields = [
2457
    (_MakeField("name", "Name", QFT_TEXT, "Operating system name"),
2458
     None, 0, _GetItemAttr("name")),
2459
    (_MakeField("valid", "Valid", QFT_BOOL,
2460
                "Whether operating system definition is valid"),
2461
     None, 0, _GetItemAttr("valid")),
2462
    (_MakeField("hidden", "Hidden", QFT_BOOL,
2463
                "Whether operating system is hidden"),
2464
     None, 0, _GetItemAttr("hidden")),
2465
    (_MakeField("blacklisted", "Blacklisted", QFT_BOOL,
2466
                "Whether operating system is blacklisted"),
2467
     None, 0, _GetItemAttr("blacklisted")),
2468
    (_MakeField("variants", "Variants", QFT_OTHER,
2469
                "Operating system variants"),
2470
     None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("variants"))),
2471
    (_MakeField("api_versions", "ApiVersions", QFT_OTHER,
2472
                "Operating system API versions"),
2473
     None, 0, _ConvWrap(sorted, _GetItemAttr("api_versions"))),
2474
    (_MakeField("parameters", "Parameters", QFT_OTHER,
2475
                "Operating system parameters"),
2476
     None, 0, _ConvWrap(compat.partial(utils.NiceSort, key=compat.fst),
2477
                        _GetItemAttr("parameters"))),
2478
    (_MakeField("node_status", "NodeStatus", QFT_OTHER,
2479
                "Status from node"),
2480
     None, 0, _GetItemAttr("node_status")),
2481
    ]
2482

    
2483
  return _PrepareFieldList(fields, [])
2484

    
2485

    
2486
class ExtStorageInfo(objects.ConfigObject):
2487
  __slots__ = [
2488
    "name",
2489
    "node_status",
2490
    "nodegroup_status",
2491
    "parameters",
2492
    ]
2493

    
2494

    
2495
def _BuildExtStorageFields():
2496
  """Builds list of fields for extstorage provider queries.
2497

2498
  """
2499
  fields = [
2500
    (_MakeField("name", "Name", QFT_TEXT, "ExtStorage provider name"),
2501
     None, 0, _GetItemAttr("name")),
2502
    (_MakeField("node_status", "NodeStatus", QFT_OTHER,
2503
                "Status from node"),
2504
     None, 0, _GetItemAttr("node_status")),
2505
    (_MakeField("nodegroup_status", "NodegroupStatus", QFT_OTHER,
2506
                "Overall Nodegroup status"),
2507
     None, 0, _GetItemAttr("nodegroup_status")),
2508
    (_MakeField("parameters", "Parameters", QFT_OTHER,
2509
                "ExtStorage provider parameters"),
2510
     None, 0, _GetItemAttr("parameters")),
2511
    ]
2512

    
2513
  return _PrepareFieldList(fields, [])
2514

    
2515

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

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

2522
  """
2523
  if job is None:
2524
    return _FS_UNAVAIL
2525
  else:
2526
    return fn(job)
2527

    
2528

    
2529
def _JobUnavail(inner):
2530
  """Wrapper for L{_JobUnavailInner}.
2531

2532
  """
2533
  return compat.partial(_JobUnavailInner, inner)
2534

    
2535

    
2536
def _PerJobOpInner(fn, job):
2537
  """Executes a function per opcode in a job.
2538

2539
  """
2540
  return map(fn, job.ops)
2541

    
2542

    
2543
def _PerJobOp(fn):
2544
  """Wrapper for L{_PerJobOpInner}.
2545

2546
  """
2547
  return _JobUnavail(compat.partial(_PerJobOpInner, fn))
2548

    
2549

    
2550
def _JobTimestampInner(fn, job):
2551
  """Converts unavailable timestamp to L{_FS_UNAVAIL}.
2552

2553
  """
2554
  timestamp = fn(job)
2555

    
2556
  if timestamp is None:
2557
    return _FS_UNAVAIL
2558
  else:
2559
    return timestamp
2560

    
2561

    
2562
def _JobTimestamp(fn):
2563
  """Wrapper for L{_JobTimestampInner}.
2564

2565
  """
2566
  return _JobUnavail(compat.partial(_JobTimestampInner, fn))
2567

    
2568

    
2569
def _BuildJobFields():
2570
  """Builds list of fields for job queries.
2571

2572
  """
2573
  fields = [
2574
    (_MakeField("id", "ID", QFT_NUMBER, "Job ID"),
2575
     None, QFF_JOB_ID, lambda _, (job_id, job): job_id),
2576
    (_MakeField("status", "Status", QFT_TEXT, "Job status"),
2577
     None, 0, _JobUnavail(lambda job: job.CalcStatus())),
2578
    (_MakeField("priority", "Priority", QFT_NUMBER,
2579
                ("Current job priority (%s to %s)" %
2580
                 (constants.OP_PRIO_LOWEST, constants.OP_PRIO_HIGHEST))),
2581
     None, 0, _JobUnavail(lambda job: job.CalcPriority())),
2582
    (_MakeField("archived", "Archived", QFT_BOOL, "Whether job is archived"),
2583
     JQ_ARCHIVED, 0, lambda _, (job_id, job): job.archived),
2584
    (_MakeField("ops", "OpCodes", QFT_OTHER, "List of all opcodes"),
2585
     None, 0, _PerJobOp(lambda op: op.input.__getstate__())),
2586
    (_MakeField("opresult", "OpCode_result", QFT_OTHER,
2587
                "List of opcodes results"),
2588
     None, 0, _PerJobOp(operator.attrgetter("result"))),
2589
    (_MakeField("opstatus", "OpCode_status", QFT_OTHER,
2590
                "List of opcodes status"),
2591
     None, 0, _PerJobOp(operator.attrgetter("status"))),
2592
    (_MakeField("oplog", "OpCode_log", QFT_OTHER,
2593
                "List of opcode output logs"),
2594
     None, 0, _PerJobOp(operator.attrgetter("log"))),
2595
    (_MakeField("opstart", "OpCode_start", QFT_OTHER,
2596
                "List of opcode start timestamps (before acquiring locks)"),
2597
     None, 0, _PerJobOp(operator.attrgetter("start_timestamp"))),
2598
    (_MakeField("opexec", "OpCode_exec", QFT_OTHER,
2599
                "List of opcode execution start timestamps (after acquiring"
2600
                " locks)"),
2601
     None, 0, _PerJobOp(operator.attrgetter("exec_timestamp"))),
2602
    (_MakeField("opend", "OpCode_end", QFT_OTHER,
2603
                "List of opcode execution end timestamps"),
2604
     None, 0, _PerJobOp(operator.attrgetter("end_timestamp"))),
2605
    (_MakeField("oppriority", "OpCode_prio", QFT_OTHER,
2606
                "List of opcode priorities"),
2607
     None, 0, _PerJobOp(operator.attrgetter("priority"))),
2608
    (_MakeField("summary", "Summary", QFT_OTHER,
2609
                "List of per-opcode summaries"),
2610
     None, 0, _PerJobOp(lambda op: op.input.Summary())),
2611
    ]
2612

    
2613
  # Timestamp fields
2614
  for (name, attr, title, desc) in [
2615
    ("received_ts", "received_timestamp", "Received",
2616
     "Timestamp of when job was received"),
2617
    ("start_ts", "start_timestamp", "Start", "Timestamp of job start"),
2618
    ("end_ts", "end_timestamp", "End", "Timestamp of job end"),
2619
    ]:
2620
    getter = operator.attrgetter(attr)
2621
    fields.extend([
2622
      (_MakeField(name, title, QFT_OTHER,
2623
                  "%s (tuple containing seconds and microseconds)" % desc),
2624
       None, QFF_SPLIT_TIMESTAMP, _JobTimestamp(getter)),
2625
      ])
2626

    
2627
  return _PrepareFieldList(fields, [])
2628

    
2629

    
2630
def _GetExportName(_, (node_name, expname)): # pylint: disable=W0613
2631
  """Returns an export name if available.
2632

2633
  """
2634
  if expname is None:
2635
    return _FS_NODATA
2636
  else:
2637
    return expname
2638

    
2639

    
2640
def _BuildExportFields():
2641
  """Builds list of fields for exports.
2642

2643
  """
2644
  fields = [
2645
    (_MakeField("node", "Node", QFT_TEXT, "Node name"),
2646
     None, QFF_HOSTNAME, lambda _, (node_name, expname): node_name),
2647
    (_MakeField("export", "Export", QFT_TEXT, "Export name"),
2648
     None, 0, _GetExportName),
2649
    ]
2650

    
2651
  return _PrepareFieldList(fields, [])
2652

    
2653

    
2654
_CLUSTER_VERSION_FIELDS = {
2655
  "software_version": ("SoftwareVersion", QFT_TEXT, constants.RELEASE_VERSION,
2656
                       "Software version"),
2657
  "protocol_version": ("ProtocolVersion", QFT_NUMBER,
2658
                       constants.PROTOCOL_VERSION,
2659
                       "RPC protocol version"),
2660
  "config_version": ("ConfigVersion", QFT_NUMBER, constants.CONFIG_VERSION,
2661
                     "Configuration format version"),
2662
  "os_api_version": ("OsApiVersion", QFT_NUMBER, max(constants.OS_API_VERSIONS),
2663
                     "API version for OS template scripts"),
2664
  "export_version": ("ExportVersion", QFT_NUMBER, constants.EXPORT_VERSION,
2665
                     "Import/export file format version"),
2666
  "vcs_version": ("VCSVersion", QFT_TEXT, constants.VCS_VERSION,
2667
                     "VCS version"),
2668
  }
2669

    
2670

    
2671
_CLUSTER_SIMPLE_FIELDS = {
2672
  "cluster_name": ("Name", QFT_TEXT, QFF_HOSTNAME, "Cluster name"),
2673
  "volume_group_name": ("VgName", QFT_TEXT, 0, "LVM volume group name"),
2674
  }
2675

    
2676

    
2677
class ClusterQueryData:
2678
  def __init__(self, cluster, nodes, drain_flag, watcher_pause):
2679
    """Initializes this class.
2680

2681
    @type cluster: L{objects.Cluster}
2682
    @param cluster: Instance of cluster object
2683
    @type nodes: dict; node UUID as key
2684
    @param nodes: Node objects
2685
    @type drain_flag: bool
2686
    @param drain_flag: Whether job queue is drained
2687
    @type watcher_pause: number
2688
    @param watcher_pause: Until when watcher is paused (Unix timestamp)
2689

2690
    """
2691
    self._cluster = cluster
2692
    self.nodes = nodes
2693
    self.drain_flag = drain_flag
2694
    self.watcher_pause = watcher_pause
2695

    
2696
  def __iter__(self):
2697
    return iter([self._cluster])
2698

    
2699

    
2700
def _ClusterWatcherPause(ctx, _):
2701
  """Returns until when watcher is paused (if available).
2702

2703
  """
2704
  if ctx.watcher_pause is None:
2705
    return _FS_UNAVAIL
2706
  else:
2707
    return ctx.watcher_pause
2708

    
2709

    
2710
def _BuildClusterFields():
2711
  """Builds list of fields for cluster information.
2712

2713
  """
2714
  fields = [
2715
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), CQ_CONFIG, 0,
2716
     lambda ctx, cluster: list(cluster.GetTags())),
2717
    (_MakeField("architecture", "ArchInfo", QFT_OTHER,
2718
                "Architecture information"), None, 0,
2719
     lambda ctx, _: runtime.GetArchInfo()),
2720
    (_MakeField("drain_flag", "QueueDrained", QFT_BOOL,
2721
                "Flag whether job queue is drained"), CQ_QUEUE_DRAINED, 0,
2722
     lambda ctx, _: ctx.drain_flag),
2723
    (_MakeField("watcher_pause", "WatcherPause", QFT_TIMESTAMP,
2724
                "Until when watcher is paused"), CQ_WATCHER_PAUSE, 0,
2725
     _ClusterWatcherPause),
2726
    (_MakeField("master_node", "Master", QFT_TEXT, "Master node name"),
2727
     CQ_CONFIG, QFF_HOSTNAME,
2728
     lambda ctx, cluster: _GetNodeName(ctx, None, cluster.master_node)),
2729
    ]
2730

    
2731
  # Simple fields
2732
  fields.extend([
2733
    (_MakeField(name, title, kind, doc), CQ_CONFIG, flags, _GetItemAttr(name))
2734
    for (name, (title, kind, flags, doc)) in _CLUSTER_SIMPLE_FIELDS.items()
2735
    ],)
2736

    
2737
  # Version fields
2738
  fields.extend([
2739
    (_MakeField(name, title, kind, doc), None, 0, _StaticValue(value))
2740
    for (name, (title, kind, value, doc)) in _CLUSTER_VERSION_FIELDS.items()])
2741

    
2742
  # Add timestamps
2743
  fields.extend(_GetItemTimestampFields(CQ_CONFIG))
2744

    
2745
  return _PrepareFieldList(fields, [
2746
    ("name", "cluster_name")])
2747

    
2748

    
2749
class NetworkQueryData:
2750
  """Data container for network data queries.
2751

2752
  """
2753
  def __init__(self, networks, network_to_groups,
2754
               network_to_instances, stats):
2755
    """Initializes this class.
2756

2757
    @param networks: List of network objects
2758
    @type network_to_groups: dict; network UUID as key
2759
    @param network_to_groups: Per-network list of groups
2760
    @type network_to_instances: dict; network UUID as key
2761
    @param network_to_instances: Per-network list of instances
2762
    @type stats: dict; network UUID as key
2763
    @param stats: Per-network usage statistics
2764

2765
    """
2766
    self.networks = networks
2767
    self.network_to_groups = network_to_groups
2768
    self.network_to_instances = network_to_instances
2769
    self.stats = stats
2770

    
2771
  def __iter__(self):
2772
    """Iterate over all networks.
2773

2774
    """
2775
    for net in self.networks:
2776
      if self.stats:
2777
        self.curstats = self.stats.get(net.uuid, None)
2778
      else:
2779
        self.curstats = None
2780
      yield net
2781

    
2782

    
2783
_NETWORK_SIMPLE_FIELDS = {
2784
  "name": ("Network", QFT_TEXT, 0, "Name"),
2785
  "network": ("Subnet", QFT_TEXT, 0, "IPv4 subnet"),
2786
  "gateway": ("Gateway", QFT_OTHER, 0, "IPv4 gateway"),
2787
  "network6": ("IPv6Subnet", QFT_OTHER, 0, "IPv6 subnet"),
2788
  "gateway6": ("IPv6Gateway", QFT_OTHER, 0, "IPv6 gateway"),
2789
  "mac_prefix": ("MacPrefix", QFT_OTHER, 0, "MAC address prefix"),
2790
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Network"),
2791
  "uuid": ("UUID", QFT_TEXT, 0, "Network UUID"),
2792
  }
2793

    
2794

    
2795
_NETWORK_STATS_FIELDS = {
2796
  "free_count": ("FreeCount", QFT_NUMBER, 0, "Number of available addresses"),
2797
  "reserved_count":
2798
    ("ReservedCount", QFT_NUMBER, 0, "Number of reserved addresses"),
2799
  "map": ("Map", QFT_TEXT, 0, "Actual mapping"),
2800
  "external_reservations":
2801
    ("ExternalReservations", QFT_TEXT, 0, "External reservations"),
2802
  }
2803

    
2804

    
2805
def _GetNetworkStatsField(field, kind, ctx, _):
2806
  """Gets the value of a "stats" field from L{NetworkQueryData}.
2807

2808
  @param field: Field name
2809
  @param kind: Data kind, one of L{constants.QFT_ALL}
2810
  @type ctx: L{NetworkQueryData}
2811

2812
  """
2813
  return _GetStatsField(field, kind, ctx.curstats)
2814

    
2815

    
2816
def _BuildNetworkFields():
2817
  """Builds list of fields for network queries.
2818

2819
  """
2820
  fields = [
2821
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
2822
     lambda ctx, inst: list(inst.GetTags())),
2823
    ]
2824

    
2825
  # Add simple fields
2826
  fields.extend([
2827
    (_MakeField(name, title, kind, doc),
2828
     NETQ_CONFIG, 0, _GetItemMaybeAttr(name))
2829
     for (name, (title, kind, _, doc)) in _NETWORK_SIMPLE_FIELDS.items()])
2830

    
2831
  def _GetLength(getter):
2832
    return lambda ctx, network: len(getter(ctx)[network.uuid])
2833

    
2834
  def _GetSortedList(getter):
2835
    return lambda ctx, network: utils.NiceSort(getter(ctx)[network.uuid])
2836

    
2837
  network_to_groups = operator.attrgetter("network_to_groups")
2838
  network_to_instances = operator.attrgetter("network_to_instances")
2839

    
2840
  # Add fields for node groups
2841
  fields.extend([
2842
    (_MakeField("group_cnt", "NodeGroups", QFT_NUMBER, "Number of nodegroups"),
2843
     NETQ_GROUP, 0, _GetLength(network_to_groups)),
2844
    (_MakeField("group_list", "GroupList", QFT_OTHER,
2845
     "List of nodegroups (group name, NIC mode, NIC link)"),
2846
     NETQ_GROUP, 0, lambda ctx, network: network_to_groups(ctx)[network.uuid]),
2847
    ])
2848

    
2849
  # Add fields for instances
2850
  fields.extend([
2851
    (_MakeField("inst_cnt", "Instances", QFT_NUMBER, "Number of instances"),
2852
     NETQ_INST, 0, _GetLength(network_to_instances)),
2853
    (_MakeField("inst_list", "InstanceList", QFT_OTHER, "List of instances"),
2854
     NETQ_INST, 0, _GetSortedList(network_to_instances)),
2855
    ])
2856

    
2857
  # Add fields for usage statistics
2858
  fields.extend([
2859
    (_MakeField(name, title, kind, doc), NETQ_STATS, 0,
2860
     compat.partial(_GetNetworkStatsField, name, kind))
2861
    for (name, (title, kind, _, doc)) in _NETWORK_STATS_FIELDS.items()])
2862

    
2863
  # Add timestamps
2864
  fields.extend(_GetItemTimestampFields(IQ_NETWORKS))
2865

    
2866
  return _PrepareFieldList(fields, [])
2867

    
2868
#: Fields for cluster information
2869
CLUSTER_FIELDS = _BuildClusterFields()
2870

    
2871
#: Fields available for node queries
2872
NODE_FIELDS = _BuildNodeFields()
2873

    
2874
#: Fields available for instance queries
2875
INSTANCE_FIELDS = _BuildInstanceFields()
2876

    
2877
#: Fields available for lock queries
2878
LOCK_FIELDS = _BuildLockFields()
2879

    
2880
#: Fields available for node group queries
2881
GROUP_FIELDS = _BuildGroupFields()
2882

    
2883
#: Fields available for operating system queries
2884
OS_FIELDS = _BuildOsFields()
2885

    
2886
#: Fields available for extstorage provider queries
2887
EXTSTORAGE_FIELDS = _BuildExtStorageFields()
2888

    
2889
#: Fields available for job queries
2890
JOB_FIELDS = _BuildJobFields()
2891

    
2892
#: Fields available for exports
2893
EXPORT_FIELDS = _BuildExportFields()
2894

    
2895
#: Fields available for network queries
2896
NETWORK_FIELDS = _BuildNetworkFields()
2897

    
2898
#: All available resources
2899
ALL_FIELDS = {
2900
  constants.QR_CLUSTER: CLUSTER_FIELDS,
2901
  constants.QR_INSTANCE: INSTANCE_FIELDS,
2902
  constants.QR_NODE: NODE_FIELDS,
2903
  constants.QR_LOCK: LOCK_FIELDS,
2904
  constants.QR_GROUP: GROUP_FIELDS,
2905
  constants.QR_OS: OS_FIELDS,
2906
  constants.QR_EXTSTORAGE: EXTSTORAGE_FIELDS,
2907
  constants.QR_JOB: JOB_FIELDS,
2908
  constants.QR_EXPORT: EXPORT_FIELDS,
2909
  constants.QR_NETWORK: NETWORK_FIELDS,
2910
  }
2911

    
2912
#: All available field lists
2913
ALL_FIELD_LISTS = ALL_FIELDS.values()