Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ 602db636

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_name):
951
  """Determine node role.
952

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

958
  """
959
  if node.name == master_name:
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] == constants.NIC_MODE_BRIDGED:
1790
      result.append(nicp[constants.NIC_VLAN])
1791
    else:
1792
      result.append(None)
1793

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

    
1796
  return result
1797

    
1798

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

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

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

    
1809
  result = []
1810

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

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

    
1819
  return result
1820

    
1821

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

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

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

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

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

    
1842
  return fn
1843

    
1844

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

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

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

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

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

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

    
1941
  return (fields, aliases)
1942

    
1943

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

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

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

    
1954
  if usage is None:
1955
    usage = 0
1956

    
1957
  return usage
1958

    
1959

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

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

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

    
1970
  if consinfo is None:
1971
    return _FS_UNAVAIL
1972

    
1973
  return consinfo
1974

    
1975

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

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

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

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

    
2018
  return fields
2019

    
2020

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

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

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

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

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

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

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

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

    
2077
  return fields
2078

    
2079

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

    
2092

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

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

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

    
2109

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

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

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

    
2126

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

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

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

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

    
2146
  return group.name
2147

    
2148

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

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

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

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

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

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

    
2237
  (network_fields, network_aliases) = _GetInstanceNetworkFields()
2238

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

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

    
2251
  return _PrepareFieldList(fields, aliases)
2252

    
2253

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

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

2261
    """
2262
    self.lockdata = lockdata
2263

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

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

    
2270

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

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

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

    
2280
  return owners
2281

    
2282

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

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

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

    
2293
  return pending
2294

    
2295

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

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

    
2315

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

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

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

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

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

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

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

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

    
2361

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

    
2369

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

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

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

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

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

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

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

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

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

    
2433
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
2434

    
2435
  return _PrepareFieldList(fields, [])
2436

    
2437

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

    
2450

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

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

    
2482
  return _PrepareFieldList(fields, [])
2483

    
2484

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

    
2493

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

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

    
2512
  return _PrepareFieldList(fields, [])
2513

    
2514

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

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

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

    
2527

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

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

    
2534

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

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

    
2541

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

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

    
2548

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

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

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

    
2560

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

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

    
2567

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

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

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

    
2626
  return _PrepareFieldList(fields, [])
2627

    
2628

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

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

    
2638

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

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

    
2650
  return _PrepareFieldList(fields, [])
2651

    
2652

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

    
2669

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

    
2675

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

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

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

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

    
2698

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

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

    
2708

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

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

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

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

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

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

    
2747

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

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

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

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

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

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

    
2781

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

    
2793

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

    
2803

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

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

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

    
2814

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

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

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

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

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

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

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

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

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

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

    
2865
  return _PrepareFieldList(fields, [])
2866

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

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

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

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

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

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

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

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

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

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

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

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