Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ edd49f9b

History | View | Annotate | Download (61.8 kB)

1
#
2
#
3

    
4
# Copyright (C) 2010, 2011 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 qlang
66

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

    
72

    
73
# Constants for requesting data from the caller/data provider. Each property
74
# collected/computed separately by the data provider should have its own to
75
# only collect the requested data and not more.
76

    
77
(NQ_CONFIG,
78
 NQ_INST,
79
 NQ_LIVE,
80
 NQ_GROUP,
81
 NQ_OOB) = range(1, 6)
82

    
83
(IQ_CONFIG,
84
 IQ_LIVE,
85
 IQ_DISKUSAGE,
86
 IQ_CONSOLE,
87
 IQ_NODES) = range(100, 105)
88

    
89
(LQ_MODE,
90
 LQ_OWNER,
91
 LQ_PENDING) = range(10, 13)
92

    
93
(GQ_CONFIG,
94
 GQ_NODE,
95
 GQ_INST) = range(200, 203)
96

    
97
# Query field flags
98
QFF_HOSTNAME = 0x01
99
QFF_IP_ADDRESS = 0x02
100
# Next values: 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200
101
QFF_ALL = (QFF_HOSTNAME | QFF_IP_ADDRESS)
102

    
103
FIELD_NAME_RE = re.compile(r"^[a-z0-9/._]+$")
104
TITLE_RE = re.compile(r"^[^\s]+$")
105
DOC_RE = re.compile(r"^[A-Z].*[^.,?!]$")
106

    
107
#: Verification function for each field type
108
_VERIFY_FN = {
109
  QFT_UNKNOWN: ht.TNone,
110
  QFT_TEXT: ht.TString,
111
  QFT_BOOL: ht.TBool,
112
  QFT_NUMBER: ht.TInt,
113
  QFT_UNIT: ht.TInt,
114
  QFT_TIMESTAMP: ht.TNumber,
115
  QFT_OTHER: lambda _: True,
116
  }
117

    
118
# Unique objects for special field statuses
119
_FS_UNKNOWN = object()
120
_FS_NODATA = object()
121
_FS_UNAVAIL = object()
122
_FS_OFFLINE = object()
123

    
124
#: List of all special status
125
_FS_ALL = frozenset([_FS_UNKNOWN, _FS_NODATA, _FS_UNAVAIL, _FS_OFFLINE])
126

    
127
#: VType to QFT mapping
128
_VTToQFT = {
129
  # TODO: fix validation of empty strings
130
  constants.VTYPE_STRING: QFT_OTHER, # since VTYPE_STRINGs can be empty
131
  constants.VTYPE_MAYBE_STRING: QFT_OTHER,
132
  constants.VTYPE_BOOL: QFT_BOOL,
133
  constants.VTYPE_SIZE: QFT_UNIT,
134
  constants.VTYPE_INT: QFT_NUMBER,
135
  }
136

    
137
_SERIAL_NO_DOC = "%s object serial number, incremented on each modification"
138

    
139

    
140
def _GetUnknownField(ctx, item): # pylint: disable=W0613
141
  """Gets the contents of an unknown field.
142

143
  """
144
  return _FS_UNKNOWN
145

    
146

    
147
def _GetQueryFields(fielddefs, selected):
148
  """Calculates the internal list of selected fields.
149

150
  Unknown fields are returned as L{constants.QFT_UNKNOWN}.
151

152
  @type fielddefs: dict
153
  @param fielddefs: Field definitions
154
  @type selected: list of strings
155
  @param selected: List of selected fields
156

157
  """
158
  result = []
159

    
160
  for name in selected:
161
    try:
162
      fdef = fielddefs[name]
163
    except KeyError:
164
      fdef = (_MakeField(name, name, QFT_UNKNOWN, "Unknown field '%s'" % name),
165
              None, 0, _GetUnknownField)
166

    
167
    assert len(fdef) == 4
168

    
169
    result.append(fdef)
170

    
171
  return result
172

    
173

    
174
def GetAllFields(fielddefs):
175
  """Extract L{objects.QueryFieldDefinition} from field definitions.
176

177
  @rtype: list of L{objects.QueryFieldDefinition}
178

179
  """
180
  return [fdef for (fdef, _, _, _) in fielddefs]
181

    
182

    
183
class _FilterHints:
184
  """Class for filter analytics.
185

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

191
  There are two ways to optimize this. The first, and simpler, is to assign
192
  each field a group of data, so that the caller can determine which
193
  computations are necessary depending on the data groups requested. The list
194
  of referenced groups must also be computed for fields referenced in the
195
  filter.
196

197
  The second is restricting the items based on a primary key. The primary key
198
  is usually a unique name (e.g. a node name). This class extracts all
199
  referenced names from a filter. If it encounters any filter condition which
200
  disallows such a list to be determined (e.g. a non-equality filter), all
201
  names will be requested.
202

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

206
  """
207
  def __init__(self, namefield):
208
    """Initializes this class.
209

210
    @type namefield: string
211
    @param namefield: Field caller is interested in
212

213
    """
214
    self._namefield = namefield
215

    
216
    #: Whether all names need to be requested (e.g. if a non-equality operator
217
    #: has been used)
218
    self._allnames = False
219

    
220
    #: Which names to request
221
    self._names = None
222

    
223
    #: Data kinds referenced by the filter (used by L{Query.RequestedData})
224
    self._datakinds = set()
225

    
226
  def RequestedNames(self):
227
    """Returns all requested values.
228

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

232
    @rtype: list
233

234
    """
235
    if self._allnames or self._names is None:
236
      return None
237

    
238
    return utils.UniqueSequence(self._names)
239

    
240
  def ReferencedData(self):
241
    """Returns all kinds of data referenced by the filter.
242

243
    """
244
    return frozenset(self._datakinds)
245

    
246
  def _NeedAllNames(self):
247
    """Changes internal state to request all names.
248

249
    """
250
    self._allnames = True
251
    self._names = None
252

    
253
  def NoteLogicOp(self, op):
254
    """Called when handling a logic operation.
255

256
    @type op: string
257
    @param op: Operator
258

259
    """
260
    if op != qlang.OP_OR:
261
      self._NeedAllNames()
262

    
263
  def NoteUnaryOp(self, op): # pylint: disable=W0613
264
    """Called when handling an unary operation.
265

266
    @type op: string
267
    @param op: Operator
268

269
    """
270
    self._NeedAllNames()
271

    
272
  def NoteBinaryOp(self, op, datakind, name, value):
273
    """Called when handling a binary operation.
274

275
    @type op: string
276
    @param op: Operator
277
    @type name: string
278
    @param name: Left-hand side of operator (field name)
279
    @param value: Right-hand side of operator
280

281
    """
282
    if datakind is not None:
283
      self._datakinds.add(datakind)
284

    
285
    if self._allnames:
286
      return
287

    
288
    # If any operator other than equality was used, all names need to be
289
    # retrieved
290
    if op == qlang.OP_EQUAL and name == self._namefield:
291
      if self._names is None:
292
        self._names = []
293
      self._names.append(value)
294
    else:
295
      self._NeedAllNames()
296

    
297

    
298
def _WrapLogicOp(op_fn, sentences, ctx, item):
299
  """Wrapper for logic operator functions.
300

301
  """
302
  return op_fn(fn(ctx, item) for fn in sentences)
303

    
304

    
305
def _WrapUnaryOp(op_fn, inner, ctx, item):
306
  """Wrapper for unary operator functions.
307

308
  """
309
  return op_fn(inner(ctx, item))
310

    
311

    
312
def _WrapBinaryOp(op_fn, retrieval_fn, value, ctx, item):
313
  """Wrapper for binary operator functions.
314

315
  """
316
  return op_fn(retrieval_fn(ctx, item), value)
317

    
318

    
319
def _WrapNot(fn, lhs, rhs):
320
  """Negates the result of a wrapped function.
321

322
  """
323
  return not fn(lhs, rhs)
324

    
325

    
326
def _PrepareRegex(pattern):
327
  """Compiles a regular expression.
328

329
  """
330
  try:
331
    return re.compile(pattern)
332
  except re.error, err:
333
    raise errors.ParameterError("Invalid regex pattern (%s)" % err)
334

    
335

    
336
class _FilterCompilerHelper:
337
  """Converts a query filter to a callable usable for filtering.
338

339
  """
340
  # String statement has no effect, pylint: disable=W0105
341

    
342
  #: How deep filters can be nested
343
  _LEVELS_MAX = 10
344

    
345
  # Unique identifiers for operator groups
346
  (_OPTYPE_LOGIC,
347
   _OPTYPE_UNARY,
348
   _OPTYPE_BINARY) = range(1, 4)
349

    
350
  """Functions for equality checks depending on field flags.
351

352
  List of tuples containing flags and a callable receiving the left- and
353
  right-hand side of the operator. The flags are an OR-ed value of C{QFF_*}
354
  (e.g. L{QFF_HOSTNAME}).
355

356
  Order matters. The first item with flags will be used. Flags are checked
357
  using binary AND.
358

359
  """
360
  _EQUALITY_CHECKS = [
361
    (QFF_HOSTNAME,
362
     lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
363
                                               case_sensitive=False),
364
     None),
365
    (None, operator.eq, None),
366
    ]
367

    
368
  """Known operators
369

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

373
    - C{_OPTYPE_LOGIC}: Callable taking any number of arguments; used by
374
      L{_HandleLogicOp}
375
    - C{_OPTYPE_UNARY}: Always C{None}; details handled by L{_HandleUnaryOp}
376
    - C{_OPTYPE_BINARY}: Callable taking exactly two parameters, the left- and
377
      right-hand side of the operator, used by L{_HandleBinaryOp}
378

379
  """
380
  _OPS = {
381
    # Logic operators
382
    qlang.OP_OR: (_OPTYPE_LOGIC, compat.any),
383
    qlang.OP_AND: (_OPTYPE_LOGIC, compat.all),
384

    
385
    # Unary operators
386
    qlang.OP_NOT: (_OPTYPE_UNARY, None),
387
    qlang.OP_TRUE: (_OPTYPE_UNARY, None),
388

    
389
    # Binary operators
390
    qlang.OP_EQUAL: (_OPTYPE_BINARY, _EQUALITY_CHECKS),
391
    qlang.OP_NOT_EQUAL:
392
      (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn), valprepfn)
393
                        for (flags, fn, valprepfn) in _EQUALITY_CHECKS]),
394
    qlang.OP_REGEXP: (_OPTYPE_BINARY, [
395
      (None, lambda lhs, rhs: rhs.search(lhs), _PrepareRegex),
396
      ]),
397
    qlang.OP_CONTAINS: (_OPTYPE_BINARY, [
398
      (None, operator.contains, None),
399
      ]),
400
    }
401

    
402
  def __init__(self, fields):
403
    """Initializes this class.
404

405
    @param fields: Field definitions (return value of L{_PrepareFieldList})
406

407
    """
408
    self._fields = fields
409
    self._hints = None
410
    self._op_handler = None
411

    
412
  def __call__(self, hints, qfilter):
413
    """Converts a query filter into a callable function.
414

415
    @type hints: L{_FilterHints} or None
416
    @param hints: Callbacks doing analysis on filter
417
    @type qfilter: list
418
    @param qfilter: Filter structure
419
    @rtype: callable
420
    @return: Function receiving context and item as parameters, returning
421
             boolean as to whether item matches filter
422

423
    """
424
    self._op_handler = {
425
      self._OPTYPE_LOGIC:
426
        (self._HandleLogicOp, getattr(hints, "NoteLogicOp", None)),
427
      self._OPTYPE_UNARY:
428
        (self._HandleUnaryOp, getattr(hints, "NoteUnaryOp", None)),
429
      self._OPTYPE_BINARY:
430
        (self._HandleBinaryOp, getattr(hints, "NoteBinaryOp", None)),
431
      }
432

    
433
    try:
434
      filter_fn = self._Compile(qfilter, 0)
435
    finally:
436
      self._op_handler = None
437

    
438
    return filter_fn
439

    
440
  def _Compile(self, qfilter, level):
441
    """Inner function for converting filters.
442

443
    Calls the correct handler functions for the top-level operator. This
444
    function is called recursively (e.g. for logic operators).
445

446
    """
447
    if not (isinstance(qfilter, (list, tuple)) and qfilter):
448
      raise errors.ParameterError("Invalid filter on level %s" % level)
449

    
450
    # Limit recursion
451
    if level >= self._LEVELS_MAX:
452
      raise errors.ParameterError("Only up to %s levels are allowed (filter"
453
                                  " nested too deep)" % self._LEVELS_MAX)
454

    
455
    # Create copy to be modified
456
    operands = qfilter[:]
457
    op = operands.pop(0)
458

    
459
    try:
460
      (kind, op_data) = self._OPS[op]
461
    except KeyError:
462
      raise errors.ParameterError("Unknown operator '%s'" % op)
463

    
464
    (handler, hints_cb) = self._op_handler[kind]
465

    
466
    return handler(hints_cb, level, op, op_data, operands)
467

    
468
  def _LookupField(self, name):
469
    """Returns a field definition by name.
470

471
    """
472
    try:
473
      return self._fields[name]
474
    except KeyError:
475
      raise errors.ParameterError("Unknown field '%s'" % name)
476

    
477
  def _HandleLogicOp(self, hints_fn, level, op, op_fn, operands):
478
    """Handles logic operators.
479

480
    @type hints_fn: callable
481
    @param hints_fn: Callback doing some analysis on the filter
482
    @type level: integer
483
    @param level: Current depth
484
    @type op: string
485
    @param op: Operator
486
    @type op_fn: callable
487
    @param op_fn: Function implementing operator
488
    @type operands: list
489
    @param operands: List of operands
490

491
    """
492
    if hints_fn:
493
      hints_fn(op)
494

    
495
    return compat.partial(_WrapLogicOp, op_fn,
496
                          [self._Compile(op, level + 1) for op in operands])
497

    
498
  def _HandleUnaryOp(self, hints_fn, level, op, op_fn, operands):
499
    """Handles unary operators.
500

501
    @type hints_fn: callable
502
    @param hints_fn: Callback doing some analysis on the filter
503
    @type level: integer
504
    @param level: Current depth
505
    @type op: string
506
    @param op: Operator
507
    @type op_fn: callable
508
    @param op_fn: Function implementing operator
509
    @type operands: list
510
    @param operands: List of operands
511

512
    """
513
    assert op_fn is None
514

    
515
    if hints_fn:
516
      hints_fn(op)
517

    
518
    if len(operands) != 1:
519
      raise errors.ParameterError("Unary operator '%s' expects exactly one"
520
                                  " operand" % op)
521

    
522
    if op == qlang.OP_TRUE:
523
      (_, _, _, retrieval_fn) = self._LookupField(operands[0])
524

    
525
      op_fn = operator.truth
526
      arg = retrieval_fn
527
    elif op == qlang.OP_NOT:
528
      op_fn = operator.not_
529
      arg = self._Compile(operands[0], level + 1)
530
    else:
531
      raise errors.ProgrammerError("Can't handle operator '%s'" % op)
532

    
533
    return compat.partial(_WrapUnaryOp, op_fn, arg)
534

    
535
  def _HandleBinaryOp(self, hints_fn, level, op, op_data, operands):
536
    """Handles binary operators.
537

538
    @type hints_fn: callable
539
    @param hints_fn: Callback doing some analysis on the filter
540
    @type level: integer
541
    @param level: Current depth
542
    @type op: string
543
    @param op: Operator
544
    @param op_data: Functions implementing operators
545
    @type operands: list
546
    @param operands: List of operands
547

548
    """
549
    # Unused arguments, pylint: disable=W0613
550
    try:
551
      (name, value) = operands
552
    except (ValueError, TypeError):
553
      raise errors.ParameterError("Invalid binary operator, expected exactly"
554
                                  " two operands")
555

    
556
    (fdef, datakind, field_flags, retrieval_fn) = self._LookupField(name)
557

    
558
    assert fdef.kind != QFT_UNKNOWN
559

    
560
    # TODO: Type conversions?
561

    
562
    verify_fn = _VERIFY_FN[fdef.kind]
563
    if not verify_fn(value):
564
      raise errors.ParameterError("Unable to compare field '%s' (type '%s')"
565
                                  " with '%s', expected %s" %
566
                                  (name, fdef.kind, value.__class__.__name__,
567
                                   verify_fn))
568

    
569
    if hints_fn:
570
      hints_fn(op, datakind, name, value)
571

    
572
    for (fn_flags, fn, valprepfn) in op_data:
573
      if fn_flags is None or fn_flags & field_flags:
574
        # Prepare value if necessary (e.g. compile regular expression)
575
        if valprepfn:
576
          value = valprepfn(value)
577

    
578
        return compat.partial(_WrapBinaryOp, fn, retrieval_fn, value)
579

    
580
    raise errors.ProgrammerError("Unable to find operator implementation"
581
                                 " (op '%s', flags %s)" % (op, field_flags))
582

    
583

    
584
def _CompileFilter(fields, hints, qfilter):
585
  """Converts a query filter into a callable function.
586

587
  See L{_FilterCompilerHelper} for details.
588

589
  @rtype: callable
590

591
  """
592
  return _FilterCompilerHelper(fields)(hints, qfilter)
593

    
594

    
595
class Query:
596
  def __init__(self, fieldlist, selected, qfilter=None, namefield=None):
597
    """Initializes this class.
598

599
    The field definition is a dictionary with the field's name as a key and a
600
    tuple containing, in order, the field definition object
601
    (L{objects.QueryFieldDefinition}, the data kind to help calling code
602
    collect data and a retrieval function. The retrieval function is called
603
    with two parameters, in order, the data container and the item in container
604
    (see L{Query.Query}).
605

606
    Users of this class can call L{RequestedData} before preparing the data
607
    container to determine what data is needed.
608

609
    @type fieldlist: dictionary
610
    @param fieldlist: Field definitions
611
    @type selected: list of strings
612
    @param selected: List of selected fields
613

614
    """
615
    assert namefield is None or namefield in fieldlist
616

    
617
    self._fields = _GetQueryFields(fieldlist, selected)
618

    
619
    self._filter_fn = None
620
    self._requested_names = None
621
    self._filter_datakinds = frozenset()
622

    
623
    if qfilter is not None:
624
      # Collect requested names if wanted
625
      if namefield:
626
        hints = _FilterHints(namefield)
627
      else:
628
        hints = None
629

    
630
      # Build filter function
631
      self._filter_fn = _CompileFilter(fieldlist, hints, qfilter)
632
      if hints:
633
        self._requested_names = hints.RequestedNames()
634
        self._filter_datakinds = hints.ReferencedData()
635

    
636
    if namefield is None:
637
      self._name_fn = None
638
    else:
639
      (_, _, _, self._name_fn) = fieldlist[namefield]
640

    
641
  def RequestedNames(self):
642
    """Returns all names referenced in the filter.
643

644
    If there is no filter or operators are preventing determining the exact
645
    names, C{None} is returned.
646

647
    """
648
    return self._requested_names
649

    
650
  def RequestedData(self):
651
    """Gets requested kinds of data.
652

653
    @rtype: frozenset
654

655
    """
656
    return (self._filter_datakinds |
657
            frozenset(datakind for (_, datakind, _, _) in self._fields
658
                      if datakind is not None))
659

    
660
  def GetFields(self):
661
    """Returns the list of fields for this query.
662

663
    Includes unknown fields.
664

665
    @rtype: List of L{objects.QueryFieldDefinition}
666

667
    """
668
    return GetAllFields(self._fields)
669

    
670
  def Query(self, ctx, sort_by_name=True):
671
    """Execute a query.
672

673
    @param ctx: Data container passed to field retrieval functions, must
674
      support iteration using C{__iter__}
675
    @type sort_by_name: boolean
676
    @param sort_by_name: Whether to sort by name or keep the input data's
677
      ordering
678

679
    """
680
    sort = (self._name_fn and sort_by_name)
681

    
682
    result = []
683

    
684
    for idx, item in enumerate(ctx):
685
      if not (self._filter_fn is None or self._filter_fn(ctx, item)):
686
        continue
687

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

    
690
      # Verify result
691
      if __debug__:
692
        _VerifyResultRow(self._fields, row)
693

    
694
      if sort:
695
        (status, name) = _ProcessResult(self._name_fn(ctx, item))
696
        assert status == constants.RS_NORMAL
697
        # TODO: Are there cases where we wouldn't want to use NiceSort?
698
        result.append((utils.NiceSortKey(name), idx, row))
699
      else:
700
        result.append(row)
701

    
702
    if not sort:
703
      return result
704

    
705
    # TODO: Would "heapq" be more efficient than sorting?
706

    
707
    # Sorting in-place instead of using "sorted()"
708
    result.sort()
709

    
710
    assert not result or (len(result[0]) == 3 and len(result[-1]) == 3)
711

    
712
    return map(operator.itemgetter(2), result)
713

    
714
  def OldStyleQuery(self, ctx, sort_by_name=True):
715
    """Query with "old" query result format.
716

717
    See L{Query.Query} for arguments.
718

719
    """
720
    unknown = set(fdef.name for (fdef, _, _, _) in self._fields
721
                  if fdef.kind == QFT_UNKNOWN)
722
    if unknown:
723
      raise errors.OpPrereqError("Unknown output fields selected: %s" %
724
                                 (utils.CommaJoin(unknown), ),
725
                                 errors.ECODE_INVAL)
726

    
727
    return [[value for (_, value) in row]
728
            for row in self.Query(ctx, sort_by_name=sort_by_name)]
729

    
730

    
731
def _ProcessResult(value):
732
  """Converts result values into externally-visible ones.
733

734
  """
735
  if value is _FS_UNKNOWN:
736
    return (RS_UNKNOWN, None)
737
  elif value is _FS_NODATA:
738
    return (RS_NODATA, None)
739
  elif value is _FS_UNAVAIL:
740
    return (RS_UNAVAIL, None)
741
  elif value is _FS_OFFLINE:
742
    return (RS_OFFLINE, None)
743
  else:
744
    return (RS_NORMAL, value)
745

    
746

    
747
def _VerifyResultRow(fields, row):
748
  """Verifies the contents of a query result row.
749

750
  @type fields: list
751
  @param fields: Field definitions for result
752
  @type row: list of tuples
753
  @param row: Row data
754

755
  """
756
  assert len(row) == len(fields)
757
  errs = []
758
  for ((status, value), (fdef, _, _, _)) in zip(row, fields):
759
    if status == RS_NORMAL:
760
      if not _VERIFY_FN[fdef.kind](value):
761
        errs.append("normal field %s fails validation (value is %s)" %
762
                    (fdef.name, value))
763
    elif value is not None:
764
      errs.append("abnormal field %s has a non-None value" % fdef.name)
765
  assert not errs, ("Failed validation: %s in row %s" %
766
                    (utils.CommaJoin(errs), row))
767

    
768

    
769
def _FieldDictKey((fdef, _, flags, fn)):
770
  """Generates key for field dictionary.
771

772
  """
773
  assert fdef.name and fdef.title, "Name and title are required"
774
  assert FIELD_NAME_RE.match(fdef.name)
775
  assert TITLE_RE.match(fdef.title)
776
  assert (DOC_RE.match(fdef.doc) and len(fdef.doc.splitlines()) == 1 and
777
          fdef.doc.strip() == fdef.doc), \
778
         "Invalid description for field '%s'" % fdef.name
779
  assert callable(fn)
780
  assert (flags & ~QFF_ALL) == 0, "Unknown flags for field '%s'" % fdef.name
781

    
782
  return fdef.name
783

    
784

    
785
def _PrepareFieldList(fields, aliases):
786
  """Prepares field list for use by L{Query}.
787

788
  Converts the list to a dictionary and does some verification.
789

790
  @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data
791
      kind, retrieval function)
792
  @param fields: List of fields, see L{Query.__init__} for a better
793
      description
794
  @type aliases: list of tuples; (alias, target)
795
  @param aliases: list of tuples containing aliases; for each
796
      alias/target pair, a duplicate will be created in the field list
797
  @rtype: dict
798
  @return: Field dictionary for L{Query}
799

800
  """
801
  if __debug__:
802
    duplicates = utils.FindDuplicates(fdef.title.lower()
803
                                      for (fdef, _, _, _) in fields)
804
    assert not duplicates, "Duplicate title(s) found: %r" % duplicates
805

    
806
  result = utils.SequenceToDict(fields, key=_FieldDictKey)
807

    
808
  for alias, target in aliases:
809
    assert alias not in result, "Alias %s overrides an existing field" % alias
810
    assert target in result, "Missing target %s for alias %s" % (target, alias)
811
    (fdef, k, flags, fn) = result[target]
812
    fdef = fdef.Copy()
813
    fdef.name = alias
814
    result[alias] = (fdef, k, flags, fn)
815

    
816
  assert len(result) == len(fields) + len(aliases)
817
  assert compat.all(name == fdef.name
818
                    for (name, (fdef, _, _, _)) in result.items())
819

    
820
  return result
821

    
822

    
823
def GetQueryResponse(query, ctx, sort_by_name=True):
824
  """Prepares the response for a query.
825

826
  @type query: L{Query}
827
  @param ctx: Data container, see L{Query.Query}
828
  @type sort_by_name: boolean
829
  @param sort_by_name: Whether to sort by name or keep the input data's
830
    ordering
831

832
  """
833
  return objects.QueryResponse(data=query.Query(ctx, sort_by_name=sort_by_name),
834
                               fields=query.GetFields()).ToDict()
835

    
836

    
837
def QueryFields(fielddefs, selected):
838
  """Returns list of available fields.
839

840
  @type fielddefs: dict
841
  @param fielddefs: Field definitions
842
  @type selected: list of strings
843
  @param selected: List of selected fields
844
  @return: List of L{objects.QueryFieldDefinition}
845

846
  """
847
  if selected is None:
848
    # Client requests all fields, sort by name
849
    fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
850
                           key=operator.attrgetter("name"))
851
  else:
852
    # Keep order as requested by client
853
    fdefs = Query(fielddefs, selected).GetFields()
854

    
855
  return objects.QueryFieldsResponse(fields=fdefs).ToDict()
856

    
857

    
858
def _MakeField(name, title, kind, doc):
859
  """Wrapper for creating L{objects.QueryFieldDefinition} instances.
860

861
  @param name: Field name as a regular expression
862
  @param title: Human-readable title
863
  @param kind: Field type
864
  @param doc: Human-readable description
865

866
  """
867
  return objects.QueryFieldDefinition(name=name, title=title, kind=kind,
868
                                      doc=doc)
869

    
870

    
871
def _GetNodeRole(node, master_name):
872
  """Determine node role.
873

874
  @type node: L{objects.Node}
875
  @param node: Node object
876
  @type master_name: string
877
  @param master_name: Master node name
878

879
  """
880
  if node.name == master_name:
881
    return constants.NR_MASTER
882
  elif node.master_candidate:
883
    return constants.NR_MCANDIDATE
884
  elif node.drained:
885
    return constants.NR_DRAINED
886
  elif node.offline:
887
    return constants.NR_OFFLINE
888
  else:
889
    return constants.NR_REGULAR
890

    
891

    
892
def _GetItemAttr(attr):
893
  """Returns a field function to return an attribute of the item.
894

895
  @param attr: Attribute name
896

897
  """
898
  getter = operator.attrgetter(attr)
899
  return lambda _, item: getter(item)
900

    
901

    
902
def _ConvWrapInner(convert, fn, ctx, item):
903
  """Wrapper for converting values.
904

905
  @param convert: Conversion function receiving value as single parameter
906
  @param fn: Retrieval function
907

908
  """
909
  value = fn(ctx, item)
910

    
911
  # Is the value an abnormal status?
912
  if compat.any(value is fs for fs in _FS_ALL):
913
    # Return right away
914
    return value
915

    
916
  # TODO: Should conversion function also receive context, item or both?
917
  return convert(value)
918

    
919

    
920
def _ConvWrap(convert, fn):
921
  """Convenience wrapper for L{_ConvWrapInner}.
922

923
  @param convert: Conversion function receiving value as single parameter
924
  @param fn: Retrieval function
925

926
  """
927
  return compat.partial(_ConvWrapInner, convert, fn)
928

    
929

    
930
def _GetItemTimestamp(getter):
931
  """Returns function for getting timestamp of item.
932

933
  @type getter: callable
934
  @param getter: Function to retrieve timestamp attribute
935

936
  """
937
  def fn(_, item):
938
    """Returns a timestamp of item.
939

940
    """
941
    timestamp = getter(item)
942
    if timestamp is None:
943
      # Old configs might not have all timestamps
944
      return _FS_UNAVAIL
945
    else:
946
      return timestamp
947

    
948
  return fn
949

    
950

    
951
def _GetItemTimestampFields(datatype):
952
  """Returns common timestamp fields.
953

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

956
  """
957
  return [
958
    (_MakeField("ctime", "CTime", QFT_TIMESTAMP, "Creation timestamp"),
959
     datatype, 0, _GetItemTimestamp(operator.attrgetter("ctime"))),
960
    (_MakeField("mtime", "MTime", QFT_TIMESTAMP, "Modification timestamp"),
961
     datatype, 0, _GetItemTimestamp(operator.attrgetter("mtime"))),
962
    ]
963

    
964

    
965
class NodeQueryData:
966
  """Data container for node data queries.
967

968
  """
969
  def __init__(self, nodes, live_data, master_name, node_to_primary,
970
               node_to_secondary, groups, oob_support, cluster):
971
    """Initializes this class.
972

973
    """
974
    self.nodes = nodes
975
    self.live_data = live_data
976
    self.master_name = master_name
977
    self.node_to_primary = node_to_primary
978
    self.node_to_secondary = node_to_secondary
979
    self.groups = groups
980
    self.oob_support = oob_support
981
    self.cluster = cluster
982

    
983
    # Used for individual rows
984
    self.curlive_data = None
985

    
986
  def __iter__(self):
987
    """Iterate over all nodes.
988

989
    This function has side-effects and only one instance of the resulting
990
    generator should be used at a time.
991

992
    """
993
    for node in self.nodes:
994
      if self.live_data:
995
        self.curlive_data = self.live_data.get(node.name, None)
996
      else:
997
        self.curlive_data = None
998
      yield node
999

    
1000

    
1001
#: Fields that are direct attributes of an L{objects.Node} object
1002
_NODE_SIMPLE_FIELDS = {
1003
  "drained": ("Drained", QFT_BOOL, 0, "Whether node is drained"),
1004
  "master_candidate": ("MasterC", QFT_BOOL, 0,
1005
                       "Whether node is a master candidate"),
1006
  "master_capable": ("MasterCapable", QFT_BOOL, 0,
1007
                     "Whether node can become a master candidate"),
1008
  "name": ("Node", QFT_TEXT, QFF_HOSTNAME, "Node name"),
1009
  "offline": ("Offline", QFT_BOOL, 0, "Whether node is marked offline"),
1010
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Node"),
1011
  "uuid": ("UUID", QFT_TEXT, 0, "Node UUID"),
1012
  "vm_capable": ("VMCapable", QFT_BOOL, 0, "Whether node can host instances"),
1013
  }
1014

    
1015

    
1016
#: Fields requiring talking to the node
1017
# Note that none of these are available for non-vm_capable nodes
1018
_NODE_LIVE_FIELDS = {
1019
  "bootid": ("BootID", QFT_TEXT, "bootid",
1020
             "Random UUID renewed for each system reboot, can be used"
1021
             " for detecting reboots by tracking changes"),
1022
  "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes",
1023
             "Number of NUMA domains on node (if exported by hypervisor)"),
1024
  "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets",
1025
               "Number of physical CPU sockets (if exported by hypervisor)"),
1026
  "ctotal": ("CTotal", QFT_NUMBER, "cpu_total", "Number of logical processors"),
1027
  "dfree": ("DFree", QFT_UNIT, "vg_free",
1028
            "Available disk space in volume group"),
1029
  "dtotal": ("DTotal", QFT_UNIT, "vg_size",
1030
             "Total disk space in volume group used for instance disk"
1031
             " allocation"),
1032
  "mfree": ("MFree", QFT_UNIT, "memory_free",
1033
            "Memory available for instance allocations"),
1034
  "mnode": ("MNode", QFT_UNIT, "memory_dom0",
1035
            "Amount of memory used by node (dom0 for Xen)"),
1036
  "mtotal": ("MTotal", QFT_UNIT, "memory_total",
1037
             "Total amount of memory of physical machine"),
1038
  }
1039

    
1040

    
1041
def _GetGroup(cb):
1042
  """Build function for calling another function with an node group.
1043

1044
  @param cb: The callback to be called with the nodegroup
1045

1046
  """
1047
  def fn(ctx, node):
1048
    """Get group data for a node.
1049

1050
    @type ctx: L{NodeQueryData}
1051
    @type inst: L{objects.Node}
1052
    @param inst: Node object
1053

1054
    """
1055
    ng = ctx.groups.get(node.group, None)
1056
    if ng is None:
1057
      # Nodes always have a group, or the configuration is corrupt
1058
      return _FS_UNAVAIL
1059

    
1060
    return cb(ctx, node, ng)
1061

    
1062
  return fn
1063

    
1064

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

1068
  @type ctx: L{NodeQueryData}
1069
  @type node: L{objects.Node}
1070
  @param node: Node object
1071
  @type ng: L{objects.NodeGroup}
1072
  @param ng: The node group this node belongs to
1073

1074
  """
1075
  return ng.name
1076

    
1077

    
1078
def _GetNodePower(ctx, node):
1079
  """Returns the node powered state
1080

1081
  @type ctx: L{NodeQueryData}
1082
  @type node: L{objects.Node}
1083
  @param node: Node object
1084

1085
  """
1086
  if ctx.oob_support[node.name]:
1087
    return node.powered
1088

    
1089
  return _FS_UNAVAIL
1090

    
1091

    
1092
def _GetNdParams(ctx, node, ng):
1093
  """Returns the ndparams for this node.
1094

1095
  @type ctx: L{NodeQueryData}
1096
  @type node: L{objects.Node}
1097
  @param node: Node object
1098
  @type ng: L{objects.NodeGroup}
1099
  @param ng: The node group this node belongs to
1100

1101
  """
1102
  return ctx.cluster.SimpleFillND(ng.FillND(node))
1103

    
1104

    
1105
def _GetLiveNodeField(field, kind, ctx, node):
1106
  """Gets the value of a "live" field from L{NodeQueryData}.
1107

1108
  @param field: Live field name
1109
  @param kind: Data kind, one of L{constants.QFT_ALL}
1110
  @type ctx: L{NodeQueryData}
1111
  @type node: L{objects.Node}
1112
  @param node: Node object
1113

1114
  """
1115
  if node.offline:
1116
    return _FS_OFFLINE
1117

    
1118
  if not node.vm_capable:
1119
    return _FS_UNAVAIL
1120

    
1121
  if not ctx.curlive_data:
1122
    return _FS_NODATA
1123

    
1124
  try:
1125
    value = ctx.curlive_data[field]
1126
  except KeyError:
1127
    return _FS_UNAVAIL
1128

    
1129
  if kind == QFT_TEXT:
1130
    return value
1131

    
1132
  assert kind in (QFT_NUMBER, QFT_UNIT)
1133

    
1134
  # Try to convert into number
1135
  try:
1136
    return int(value)
1137
  except (ValueError, TypeError):
1138
    logging.exception("Failed to convert node field '%s' (value %r) to int",
1139
                      value, field)
1140
    return _FS_UNAVAIL
1141

    
1142

    
1143
def _GetNodeHvState(_, node):
1144
  """Converts node's hypervisor state for query result.
1145

1146
  """
1147
  hv_state = node.hv_state
1148

    
1149
  if hv_state is None:
1150
    return _FS_UNAVAIL
1151

    
1152
  return dict((name, value.ToDict()) for (name, value) in hv_state.items())
1153

    
1154

    
1155
def _GetNodeDiskState(_, node):
1156
  """Converts node's disk state for query result.
1157

1158
  """
1159
  disk_state = node.disk_state
1160

    
1161
  if disk_state is None:
1162
    return _FS_UNAVAIL
1163

    
1164
  return dict((disk_kind, dict((name, value.ToDict())
1165
                               for (name, value) in kind_state.items()))
1166
              for (disk_kind, kind_state) in disk_state.items())
1167

    
1168

    
1169
def _BuildNodeFields():
1170
  """Builds list of fields for node queries.
1171

1172
  """
1173
  fields = [
1174
    (_MakeField("pip", "PrimaryIP", QFT_TEXT, "Primary IP address"),
1175
     NQ_CONFIG, 0, _GetItemAttr("primary_ip")),
1176
    (_MakeField("sip", "SecondaryIP", QFT_TEXT, "Secondary IP address"),
1177
     NQ_CONFIG, 0, _GetItemAttr("secondary_ip")),
1178
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0,
1179
     lambda ctx, node: list(node.GetTags())),
1180
    (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"),
1181
     NQ_CONFIG, 0, lambda ctx, node: node.name == ctx.master_name),
1182
    (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0,
1183
     _GetGroup(_GetNodeGroup)),
1184
    (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"),
1185
     NQ_CONFIG, 0, _GetItemAttr("group")),
1186
    (_MakeField("powered", "Powered", QFT_BOOL,
1187
                "Whether node is thought to be powered on"),
1188
     NQ_OOB, 0, _GetNodePower),
1189
    (_MakeField("ndparams", "NodeParameters", QFT_OTHER,
1190
                "Merged node parameters"),
1191
     NQ_GROUP, 0, _GetGroup(_GetNdParams)),
1192
    (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER,
1193
                "Custom node parameters"),
1194
      NQ_GROUP, 0, _GetItemAttr("ndparams")),
1195
    (_MakeField("hv_state", "HypervisorState", QFT_OTHER, "Hypervisor state"),
1196
     NQ_CONFIG, 0, _GetNodeHvState),
1197
    (_MakeField("disk_state", "DiskState", QFT_OTHER, "Disk state"),
1198
     NQ_CONFIG, 0, _GetNodeDiskState),
1199
    ]
1200

    
1201
  # Node role
1202
  role_values = (constants.NR_MASTER, constants.NR_MCANDIDATE,
1203
                 constants.NR_REGULAR, constants.NR_DRAINED,
1204
                 constants.NR_OFFLINE)
1205
  role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate,"
1206
              " \"%s\" for regular, \"%s\" for a drained, \"%s\" for offline" %
1207
              role_values)
1208
  fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
1209
                 lambda ctx, node: _GetNodeRole(node, ctx.master_name)))
1210
  assert set(role_values) == constants.NR_ALL
1211

    
1212
  def _GetLength(getter):
1213
    return lambda ctx, node: len(getter(ctx)[node.name])
1214

    
1215
  def _GetList(getter):
1216
    return lambda ctx, node: list(getter(ctx)[node.name])
1217

    
1218
  # Add fields operating on instance lists
1219
  for prefix, titleprefix, docword, getter in \
1220
      [("p", "Pri", "primary", operator.attrgetter("node_to_primary")),
1221
       ("s", "Sec", "secondary", operator.attrgetter("node_to_secondary"))]:
1222
    # TODO: Allow filterting by hostname in list
1223
    fields.extend([
1224
      (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER,
1225
                  "Number of instances with this node as %s" % docword),
1226
       NQ_INST, 0, _GetLength(getter)),
1227
      (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
1228
                  QFT_OTHER,
1229
                  "List of instances with this node as %s" % docword),
1230
       NQ_INST, 0, _GetList(getter)),
1231
      ])
1232

    
1233
  # Add simple fields
1234
  fields.extend([
1235
    (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1236
    for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()
1237
    ])
1238

    
1239
  # Add fields requiring live data
1240
  fields.extend([
1241
    (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1242
     compat.partial(_GetLiveNodeField, nfield, kind))
1243
    for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()
1244
    ])
1245

    
1246
  # Add timestamps
1247
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1248

    
1249
  return _PrepareFieldList(fields, [])
1250

    
1251

    
1252
class InstanceQueryData:
1253
  """Data container for instance data queries.
1254

1255
  """
1256
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
1257
               live_data, wrongnode_inst, console, nodes, groups):
1258
    """Initializes this class.
1259

1260
    @param instances: List of instance objects
1261
    @param cluster: Cluster object
1262
    @type disk_usage: dict; instance name as key
1263
    @param disk_usage: Per-instance disk usage
1264
    @type offline_nodes: list of strings
1265
    @param offline_nodes: List of offline nodes
1266
    @type bad_nodes: list of strings
1267
    @param bad_nodes: List of faulty nodes
1268
    @type live_data: dict; instance name as key
1269
    @param live_data: Per-instance live data
1270
    @type wrongnode_inst: set
1271
    @param wrongnode_inst: Set of instances running on wrong node(s)
1272
    @type console: dict; instance name as key
1273
    @param console: Per-instance console information
1274
    @type nodes: dict; node name as key
1275
    @param nodes: Node objects
1276

1277
    """
1278
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
1279
           "Offline nodes not included in bad nodes"
1280
    assert not (set(live_data.keys()) & set(bad_nodes)), \
1281
           "Found live data for bad or offline nodes"
1282

    
1283
    self.instances = instances
1284
    self.cluster = cluster
1285
    self.disk_usage = disk_usage
1286
    self.offline_nodes = offline_nodes
1287
    self.bad_nodes = bad_nodes
1288
    self.live_data = live_data
1289
    self.wrongnode_inst = wrongnode_inst
1290
    self.console = console
1291
    self.nodes = nodes
1292
    self.groups = groups
1293

    
1294
    # Used for individual rows
1295
    self.inst_hvparams = None
1296
    self.inst_beparams = None
1297
    self.inst_osparams = None
1298
    self.inst_nicparams = None
1299

    
1300
  def __iter__(self):
1301
    """Iterate over all instances.
1302

1303
    This function has side-effects and only one instance of the resulting
1304
    generator should be used at a time.
1305

1306
    """
1307
    for inst in self.instances:
1308
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1309
      self.inst_beparams = self.cluster.FillBE(inst)
1310
      self.inst_osparams = self.cluster.SimpleFillOS(inst.os, inst.osparams)
1311
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1312
                             for nic in inst.nics]
1313

    
1314
      yield inst
1315

    
1316

    
1317
def _GetInstOperState(ctx, inst):
1318
  """Get instance's operational status.
1319

1320
  @type ctx: L{InstanceQueryData}
1321
  @type inst: L{objects.Instance}
1322
  @param inst: Instance object
1323

1324
  """
1325
  # Can't use RS_OFFLINE here as it would describe the instance to
1326
  # be offline when we actually don't know due to missing data
1327
  if inst.primary_node in ctx.bad_nodes:
1328
    return _FS_NODATA
1329
  else:
1330
    return bool(ctx.live_data.get(inst.name))
1331

    
1332

    
1333
def _GetInstLiveData(name):
1334
  """Build function for retrieving live data.
1335

1336
  @type name: string
1337
  @param name: Live data field name
1338

1339
  """
1340
  def fn(ctx, inst):
1341
    """Get live data for an instance.
1342

1343
    @type ctx: L{InstanceQueryData}
1344
    @type inst: L{objects.Instance}
1345
    @param inst: Instance object
1346

1347
    """
1348
    if (inst.primary_node in ctx.bad_nodes or
1349
        inst.primary_node in ctx.offline_nodes):
1350
      # Can't use RS_OFFLINE here as it would describe the instance to be
1351
      # offline when we actually don't know due to missing data
1352
      return _FS_NODATA
1353

    
1354
    if inst.name in ctx.live_data:
1355
      data = ctx.live_data[inst.name]
1356
      if name in data:
1357
        return data[name]
1358

    
1359
    return _FS_UNAVAIL
1360

    
1361
  return fn
1362

    
1363

    
1364
def _GetInstStatus(ctx, inst):
1365
  """Get instance status.
1366

1367
  @type ctx: L{InstanceQueryData}
1368
  @type inst: L{objects.Instance}
1369
  @param inst: Instance object
1370

1371
  """
1372
  if inst.primary_node in ctx.offline_nodes:
1373
    return constants.INSTST_NODEOFFLINE
1374

    
1375
  if inst.primary_node in ctx.bad_nodes:
1376
    return constants.INSTST_NODEDOWN
1377

    
1378
  if bool(ctx.live_data.get(inst.name)):
1379
    if inst.name in ctx.wrongnode_inst:
1380
      return constants.INSTST_WRONGNODE
1381
    elif inst.admin_state == constants.ADMINST_UP:
1382
      return constants.INSTST_RUNNING
1383
    else:
1384
      return constants.INSTST_ERRORUP
1385

    
1386
  if inst.admin_state == constants.ADMINST_UP:
1387
    return constants.INSTST_ERRORDOWN
1388
  elif inst.admin_state == constants.ADMINST_DOWN:
1389
    return constants.INSTST_ADMINDOWN
1390

    
1391
  return constants.INSTST_ADMINOFFLINE
1392

    
1393

    
1394
def _GetInstDiskSize(index):
1395
  """Build function for retrieving disk size.
1396

1397
  @type index: int
1398
  @param index: Disk index
1399

1400
  """
1401
  def fn(_, inst):
1402
    """Get size of a disk.
1403

1404
    @type inst: L{objects.Instance}
1405
    @param inst: Instance object
1406

1407
    """
1408
    try:
1409
      return inst.disks[index].size
1410
    except IndexError:
1411
      return _FS_UNAVAIL
1412

    
1413
  return fn
1414

    
1415

    
1416
def _GetInstNic(index, cb):
1417
  """Build function for calling another function with an instance NIC.
1418

1419
  @type index: int
1420
  @param index: NIC index
1421
  @type cb: callable
1422
  @param cb: Callback
1423

1424
  """
1425
  def fn(ctx, inst):
1426
    """Call helper function with instance NIC.
1427

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

1432
    """
1433
    try:
1434
      nic = inst.nics[index]
1435
    except IndexError:
1436
      return _FS_UNAVAIL
1437

    
1438
    return cb(ctx, index, nic)
1439

    
1440
  return fn
1441

    
1442

    
1443
def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613
1444
  """Get a NIC's IP address.
1445

1446
  @type ctx: L{InstanceQueryData}
1447
  @type nic: L{objects.NIC}
1448
  @param nic: NIC object
1449

1450
  """
1451
  if nic.ip is None:
1452
    return _FS_UNAVAIL
1453
  else:
1454
    return nic.ip
1455

    
1456

    
1457
def _GetInstNicBridge(ctx, index, _):
1458
  """Get a NIC's bridge.
1459

1460
  @type ctx: L{InstanceQueryData}
1461
  @type index: int
1462
  @param index: NIC index
1463

1464
  """
1465
  assert len(ctx.inst_nicparams) >= index
1466

    
1467
  nicparams = ctx.inst_nicparams[index]
1468

    
1469
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1470
    return nicparams[constants.NIC_LINK]
1471
  else:
1472
    return _FS_UNAVAIL
1473

    
1474

    
1475
def _GetInstAllNicBridges(ctx, inst):
1476
  """Get all network bridges for an instance.
1477

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

1482
  """
1483
  assert len(ctx.inst_nicparams) == len(inst.nics)
1484

    
1485
  result = []
1486

    
1487
  for nicp in ctx.inst_nicparams:
1488
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1489
      result.append(nicp[constants.NIC_LINK])
1490
    else:
1491
      result.append(None)
1492

    
1493
  assert len(result) == len(inst.nics)
1494

    
1495
  return result
1496

    
1497

    
1498
def _GetInstNicParam(name):
1499
  """Build function for retrieving a NIC parameter.
1500

1501
  @type name: string
1502
  @param name: Parameter name
1503

1504
  """
1505
  def fn(ctx, index, _):
1506
    """Get a NIC's bridge.
1507

1508
    @type ctx: L{InstanceQueryData}
1509
    @type inst: L{objects.Instance}
1510
    @param inst: Instance object
1511
    @type nic: L{objects.NIC}
1512
    @param nic: NIC object
1513

1514
    """
1515
    assert len(ctx.inst_nicparams) >= index
1516
    return ctx.inst_nicparams[index][name]
1517

    
1518
  return fn
1519

    
1520

    
1521
def _GetInstanceNetworkFields():
1522
  """Get instance fields involving network interfaces.
1523

1524
  @return: Tuple containing list of field definitions used as input for
1525
    L{_PrepareFieldList} and a list of aliases
1526

1527
  """
1528
  nic_mac_fn = lambda ctx, _, nic: nic.mac
1529
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1530
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1531

    
1532
  fields = [
1533
    # All NICs
1534
    (_MakeField("nic.count", "NICs", QFT_NUMBER,
1535
                "Number of network interfaces"),
1536
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.nics)),
1537
    (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER,
1538
                "List containing each network interface's MAC address"),
1539
     IQ_CONFIG, 0, lambda ctx, inst: [nic.mac for nic in inst.nics]),
1540
    (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
1541
                "List containing each network interface's IP address"),
1542
     IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
1543
    (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
1544
                "List containing each network interface's mode"), IQ_CONFIG, 0,
1545
     lambda ctx, inst: [nicp[constants.NIC_MODE]
1546
                        for nicp in ctx.inst_nicparams]),
1547
    (_MakeField("nic.links", "NIC_links", QFT_OTHER,
1548
                "List containing each network interface's link"), IQ_CONFIG, 0,
1549
     lambda ctx, inst: [nicp[constants.NIC_LINK]
1550
                        for nicp in ctx.inst_nicparams]),
1551
    (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
1552
                "List containing each network interface's bridge"),
1553
     IQ_CONFIG, 0, _GetInstAllNicBridges),
1554
    ]
1555

    
1556
  # NICs by number
1557
  for i in range(constants.MAX_NICS):
1558
    numtext = utils.FormatOrdinal(i + 1)
1559
    fields.extend([
1560
      (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT,
1561
                  "IP address of %s network interface" % numtext),
1562
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)),
1563
      (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
1564
                  "MAC address of %s network interface" % numtext),
1565
       IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
1566
      (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
1567
                  "Mode of %s network interface" % numtext),
1568
       IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
1569
      (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT,
1570
                  "Link of %s network interface" % numtext),
1571
       IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)),
1572
      (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
1573
                  "Bridge of %s network interface" % numtext),
1574
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
1575
      ])
1576

    
1577
  aliases = [
1578
    # Legacy fields for first NIC
1579
    ("ip", "nic.ip/0"),
1580
    ("mac", "nic.mac/0"),
1581
    ("bridge", "nic.bridge/0"),
1582
    ("nic_mode", "nic.mode/0"),
1583
    ("nic_link", "nic.link/0"),
1584
    ]
1585

    
1586
  return (fields, aliases)
1587

    
1588

    
1589
def _GetInstDiskUsage(ctx, inst):
1590
  """Get disk usage for an instance.
1591

1592
  @type ctx: L{InstanceQueryData}
1593
  @type inst: L{objects.Instance}
1594
  @param inst: Instance object
1595

1596
  """
1597
  usage = ctx.disk_usage[inst.name]
1598

    
1599
  if usage is None:
1600
    usage = 0
1601

    
1602
  return usage
1603

    
1604

    
1605
def _GetInstanceConsole(ctx, inst):
1606
  """Get console information for instance.
1607

1608
  @type ctx: L{InstanceQueryData}
1609
  @type inst: L{objects.Instance}
1610
  @param inst: Instance object
1611

1612
  """
1613
  consinfo = ctx.console[inst.name]
1614

    
1615
  if consinfo is None:
1616
    return _FS_UNAVAIL
1617

    
1618
  return consinfo
1619

    
1620

    
1621
def _GetInstanceDiskFields():
1622
  """Get instance fields involving disks.
1623

1624
  @return: List of field definitions used as input for L{_PrepareFieldList}
1625

1626
  """
1627
  fields = [
1628
    (_MakeField("disk_usage", "DiskUsage", QFT_UNIT,
1629
                "Total disk space used by instance on each of its nodes;"
1630
                " this is not the disk size visible to the instance, but"
1631
                " the usage on the node"),
1632
     IQ_DISKUSAGE, 0, _GetInstDiskUsage),
1633
    (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"),
1634
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
1635
    (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
1636
     IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
1637
    ]
1638

    
1639
  # Disks by number
1640
  fields.extend([
1641
    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1642
                "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
1643
     IQ_CONFIG, 0, _GetInstDiskSize(i))
1644
    for i in range(constants.MAX_DISKS)
1645
    ])
1646

    
1647
  return fields
1648

    
1649

    
1650
def _GetInstanceParameterFields():
1651
  """Get instance fields involving parameters.
1652

1653
  @return: List of field definitions used as input for L{_PrepareFieldList}
1654

1655
  """
1656
  # TODO: Consider moving titles closer to constants
1657
  be_title = {
1658
    constants.BE_AUTO_BALANCE: "Auto_balance",
1659
    constants.BE_MAXMEM: "ConfigMaxMem",
1660
    constants.BE_MINMEM: "ConfigMinMem",
1661
    constants.BE_VCPUS: "ConfigVCPUs",
1662
    }
1663

    
1664
  hv_title = {
1665
    constants.HV_ACPI: "ACPI",
1666
    constants.HV_BOOT_ORDER: "Boot_order",
1667
    constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1668
    constants.HV_DISK_TYPE: "Disk_type",
1669
    constants.HV_INITRD_PATH: "Initrd_path",
1670
    constants.HV_KERNEL_PATH: "Kernel_path",
1671
    constants.HV_NIC_TYPE: "NIC_type",
1672
    constants.HV_PAE: "PAE",
1673
    constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1674
    }
1675

    
1676
  fields = [
1677
    # Filled parameters
1678
    (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
1679
                "Hypervisor parameters (merged)"),
1680
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
1681
    (_MakeField("beparams", "BackendParameters", QFT_OTHER,
1682
                "Backend parameters (merged)"),
1683
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
1684
    (_MakeField("osparams", "OpSysParameters", QFT_OTHER,
1685
                "Operating system parameters (merged)"),
1686
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_osparams),
1687

    
1688
    # Unfilled parameters
1689
    (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
1690
                "Custom hypervisor parameters"),
1691
     IQ_CONFIG, 0, _GetItemAttr("hvparams")),
1692
    (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
1693
                "Custom backend parameters",),
1694
     IQ_CONFIG, 0, _GetItemAttr("beparams")),
1695
    (_MakeField("custom_osparams", "CustomOpSysParameters", QFT_OTHER,
1696
                "Custom operating system parameters",),
1697
     IQ_CONFIG, 0, _GetItemAttr("osparams")),
1698
    (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
1699
                "Custom network interface parameters"),
1700
     IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1701
    ]
1702

    
1703
  # HV params
1704
  def _GetInstHvParam(name):
1705
    return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1706

    
1707
  fields.extend([
1708
    (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1709
                _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1710
     IQ_CONFIG, 0, _GetInstHvParam(name))
1711
    for name, kind in constants.HVS_PARAMETER_TYPES.items()
1712
    if name not in constants.HVC_GLOBALS
1713
    ])
1714

    
1715
  # BE params
1716
  def _GetInstBeParam(name):
1717
    return lambda ctx, _: ctx.inst_beparams.get(name, None)
1718

    
1719
  fields.extend([
1720
    (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1721
                _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1722
     IQ_CONFIG, 0, _GetInstBeParam(name))
1723
    for name, kind in constants.BES_PARAMETER_TYPES.items()
1724
    ])
1725

    
1726
  return fields
1727

    
1728

    
1729
_INST_SIMPLE_FIELDS = {
1730
  "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"),
1731
  "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"),
1732
  "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"),
1733
  # Depending on the hypervisor, the port can be None
1734
  "network_port": ("Network_port", QFT_OTHER, 0,
1735
                   "Instance network port if available (e.g. for VNC console)"),
1736
  "os": ("OS", QFT_TEXT, 0, "Operating system"),
1737
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"),
1738
  "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"),
1739
  }
1740

    
1741

    
1742
def _GetInstNodeGroup(ctx, default, node_name):
1743
  """Gets group UUID of an instance node.
1744

1745
  @type ctx: L{InstanceQueryData}
1746
  @param default: Default value
1747
  @type node_name: string
1748
  @param node_name: Node name
1749

1750
  """
1751
  try:
1752
    node = ctx.nodes[node_name]
1753
  except KeyError:
1754
    return default
1755
  else:
1756
    return node.group
1757

    
1758

    
1759
def _GetInstNodeGroupName(ctx, default, node_name):
1760
  """Gets group name of an instance node.
1761

1762
  @type ctx: L{InstanceQueryData}
1763
  @param default: Default value
1764
  @type node_name: string
1765
  @param node_name: Node name
1766

1767
  """
1768
  try:
1769
    node = ctx.nodes[node_name]
1770
  except KeyError:
1771
    return default
1772

    
1773
  try:
1774
    group = ctx.groups[node.group]
1775
  except KeyError:
1776
    return default
1777

    
1778
  return group.name
1779

    
1780

    
1781
def _BuildInstanceFields():
1782
  """Builds list of fields for instance queries.
1783

1784
  """
1785
  fields = [
1786
    (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
1787
     IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")),
1788
    (_MakeField("pnode.group", "PrimaryNodeGroup", QFT_TEXT,
1789
                "Primary node's group"),
1790
     IQ_NODES, 0,
1791
     lambda ctx, inst: _GetInstNodeGroupName(ctx, _FS_UNAVAIL,
1792
                                             inst.primary_node)),
1793
    (_MakeField("pnode.group.uuid", "PrimaryNodeGroupUUID", QFT_TEXT,
1794
                "Primary node's group UUID"),
1795
     IQ_NODES, 0,
1796
     lambda ctx, inst: _GetInstNodeGroup(ctx, _FS_UNAVAIL, inst.primary_node)),
1797
    # TODO: Allow filtering by secondary node as hostname
1798
    (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
1799
                "Secondary nodes; usually this will just be one node"),
1800
     IQ_CONFIG, 0, lambda ctx, inst: list(inst.secondary_nodes)),
1801
    (_MakeField("snodes.group", "SecondaryNodesGroups", QFT_OTHER,
1802
                "Node groups of secondary nodes"),
1803
     IQ_NODES, 0,
1804
     lambda ctx, inst: map(compat.partial(_GetInstNodeGroupName, ctx, None),
1805
                           inst.secondary_nodes)),
1806
    (_MakeField("snodes.group.uuid", "SecondaryNodesGroupsUUID", QFT_OTHER,
1807
                "Node group UUIDs of secondary nodes"),
1808
     IQ_NODES, 0,
1809
     lambda ctx, inst: map(compat.partial(_GetInstNodeGroup, ctx, None),
1810
                           inst.secondary_nodes)),
1811
    (_MakeField("admin_state", "InstanceState", QFT_TEXT,
1812
                "Desired state of instance"),
1813
     IQ_CONFIG, 0, _GetItemAttr("admin_state")),
1814
    (_MakeField("admin_up", "Autostart", QFT_BOOL,
1815
                "Desired state of instance"),
1816
     IQ_CONFIG, 0, lambda ctx, inst: inst.admin_state == constants.ADMINST_UP),
1817
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
1818
     lambda ctx, inst: list(inst.GetTags())),
1819
    (_MakeField("console", "Console", QFT_OTHER,
1820
                "Instance console information"), IQ_CONSOLE, 0,
1821
     _GetInstanceConsole),
1822
    ]
1823

    
1824
  # Add simple fields
1825
  fields.extend([
1826
    (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1827
    for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()
1828
    ])
1829

    
1830
  # Fields requiring talking to the node
1831
  fields.extend([
1832
    (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
1833
     IQ_LIVE, 0, _GetInstOperState),
1834
    (_MakeField("oper_ram", "Memory", QFT_UNIT,
1835
                "Actual memory usage as seen by hypervisor"),
1836
     IQ_LIVE, 0, _GetInstLiveData("memory")),
1837
    (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
1838
                "Actual number of VCPUs as seen by hypervisor"),
1839
     IQ_LIVE, 0, _GetInstLiveData("vcpus")),
1840
    ])
1841

    
1842
  # Status field
1843
  status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
1844
                   constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
1845
                   constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
1846
                   constants.INSTST_NODEOFFLINE, constants.INSTST_ADMINOFFLINE)
1847
  status_doc = ("Instance status; \"%s\" if instance is set to be running"
1848
                " and actually is, \"%s\" if instance is stopped and"
1849
                " is not running, \"%s\" if instance running, but not on its"
1850
                " designated primary node, \"%s\" if instance should be"
1851
                " stopped, but is actually running, \"%s\" if instance should"
1852
                " run, but doesn't, \"%s\" if instance's primary node is down,"
1853
                " \"%s\" if instance's primary node is marked offline,"
1854
                " \"%s\" if instance is offline and does not use dynamic"
1855
                " resources" % status_values)
1856
  fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
1857
                 IQ_LIVE, 0, _GetInstStatus))
1858
  assert set(status_values) == constants.INSTST_ALL, \
1859
         "Status documentation mismatch"
1860

    
1861
  (network_fields, network_aliases) = _GetInstanceNetworkFields()
1862

    
1863
  fields.extend(network_fields)
1864
  fields.extend(_GetInstanceParameterFields())
1865
  fields.extend(_GetInstanceDiskFields())
1866
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1867

    
1868
  aliases = [
1869
    ("vcpus", "be/vcpus"),
1870
    ("be/memory", "be/maxmem"),
1871
    ("sda_size", "disk.size/0"),
1872
    ("sdb_size", "disk.size/1"),
1873
    ] + network_aliases
1874

    
1875
  return _PrepareFieldList(fields, aliases)
1876

    
1877

    
1878
class LockQueryData:
1879
  """Data container for lock data queries.
1880

1881
  """
1882
  def __init__(self, lockdata):
1883
    """Initializes this class.
1884

1885
    """
1886
    self.lockdata = lockdata
1887

    
1888
  def __iter__(self):
1889
    """Iterate over all locks.
1890

1891
    """
1892
    return iter(self.lockdata)
1893

    
1894

    
1895
def _GetLockOwners(_, data):
1896
  """Returns a sorted list of a lock's current owners.
1897

1898
  """
1899
  (_, _, owners, _) = data
1900

    
1901
  if owners:
1902
    owners = utils.NiceSort(owners)
1903

    
1904
  return owners
1905

    
1906

    
1907
def _GetLockPending(_, data):
1908
  """Returns a sorted list of a lock's pending acquires.
1909

1910
  """
1911
  (_, _, _, pending) = data
1912

    
1913
  if pending:
1914
    pending = [(mode, utils.NiceSort(names))
1915
               for (mode, names) in pending]
1916

    
1917
  return pending
1918

    
1919

    
1920
def _BuildLockFields():
1921
  """Builds list of fields for lock queries.
1922

1923
  """
1924
  return _PrepareFieldList([
1925
    # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
1926
    (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
1927
     lambda ctx, (name, mode, owners, pending): name),
1928
    (_MakeField("mode", "Mode", QFT_OTHER,
1929
                "Mode in which the lock is currently acquired"
1930
                " (exclusive or shared)"),
1931
     LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
1932
    (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
1933
     LQ_OWNER, 0, _GetLockOwners),
1934
    (_MakeField("pending", "Pending", QFT_OTHER,
1935
                "Threads waiting for the lock"),
1936
     LQ_PENDING, 0, _GetLockPending),
1937
    ], [])
1938

    
1939

    
1940
class GroupQueryData:
1941
  """Data container for node group data queries.
1942

1943
  """
1944
  def __init__(self, cluster, groups, group_to_nodes, group_to_instances):
1945
    """Initializes this class.
1946

1947
    @param cluster: Cluster object
1948
    @param groups: List of node group objects
1949
    @type group_to_nodes: dict; group UUID as key
1950
    @param group_to_nodes: Per-group list of nodes
1951
    @type group_to_instances: dict; group UUID as key
1952
    @param group_to_instances: Per-group list of (primary) instances
1953

1954
    """
1955
    self.groups = groups
1956
    self.group_to_nodes = group_to_nodes
1957
    self.group_to_instances = group_to_instances
1958
    self.cluster = cluster
1959

    
1960
    # Used for individual rows
1961
    self.group_ipolicy = None
1962

    
1963
  def __iter__(self):
1964
    """Iterate over all node groups.
1965

1966
    This function has side-effects and only one instance of the resulting
1967
    generator should be used at a time.
1968

1969
    """
1970
    for group in self.groups:
1971
      self.group_ipolicy = self.cluster.SimpleFillIPolicy(group.ipolicy)
1972
      yield group
1973

    
1974

    
1975
_GROUP_SIMPLE_FIELDS = {
1976
  "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
1977
  "name": ("Group", QFT_TEXT, "Group name"),
1978
  "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
1979
  "uuid": ("UUID", QFT_TEXT, "Group UUID"),
1980
  "ndparams": ("NDParams", QFT_OTHER, "Node parameters"),
1981
  }
1982

    
1983

    
1984
def _BuildGroupFields():
1985
  """Builds list of fields for node group queries.
1986

1987
  """
1988
  # Add simple fields
1989
  fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
1990
             _GetItemAttr(name))
1991
            for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
1992

    
1993
  def _GetLength(getter):
1994
    return lambda ctx, group: len(getter(ctx)[group.uuid])
1995

    
1996
  def _GetSortedList(getter):
1997
    return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
1998

    
1999
  group_to_nodes = operator.attrgetter("group_to_nodes")
2000
  group_to_instances = operator.attrgetter("group_to_instances")
2001

    
2002
  # Add fields for nodes
2003
  fields.extend([
2004
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
2005
     GQ_NODE, 0, _GetLength(group_to_nodes)),
2006
    (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
2007
     GQ_NODE, 0, _GetSortedList(group_to_nodes)),
2008
    ])
2009

    
2010
  # Add fields for instances
2011
  fields.extend([
2012
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
2013
                "Number of primary instances"),
2014
     GQ_INST, 0, _GetLength(group_to_instances)),
2015
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
2016
                "List of primary instances"),
2017
     GQ_INST, 0, _GetSortedList(group_to_instances)),
2018
    ])
2019

    
2020
  # Other fields
2021
  fields.extend([
2022
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), GQ_CONFIG, 0,
2023
     lambda ctx, group: list(group.GetTags())),
2024
    (_MakeField("ipolicy", "InstancePolicy", QFT_OTHER,
2025
                "Instance policy limitations (merged)"),
2026
     GQ_CONFIG, 0, lambda ctx, _: ctx.group_ipolicy),
2027
    (_MakeField("custom_ipolicy", "CustomInstancePolicy", QFT_OTHER,
2028
                "Custom instance policy limitations"),
2029
     GQ_CONFIG, 0, _GetItemAttr("ipolicy")),
2030
    ])
2031

    
2032
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
2033

    
2034
  return _PrepareFieldList(fields, [])
2035

    
2036

    
2037
class OsInfo(objects.ConfigObject):
2038
  __slots__ = [
2039
    "name",
2040
    "valid",
2041
    "hidden",
2042
    "blacklisted",
2043
    "variants",
2044
    "api_versions",
2045
    "parameters",
2046
    "node_status",
2047
    ]
2048

    
2049

    
2050
def _BuildOsFields():
2051
  """Builds list of fields for operating system queries.
2052

2053
  """
2054
  fields = [
2055
    (_MakeField("name", "Name", QFT_TEXT, "Operating system name"),
2056
     None, 0, _GetItemAttr("name")),
2057
    (_MakeField("valid", "Valid", QFT_BOOL,
2058
                "Whether operating system definition is valid"),
2059
     None, 0, _GetItemAttr("valid")),
2060
    (_MakeField("hidden", "Hidden", QFT_BOOL,
2061
                "Whether operating system is hidden"),
2062
     None, 0, _GetItemAttr("hidden")),
2063
    (_MakeField("blacklisted", "Blacklisted", QFT_BOOL,
2064
                "Whether operating system is blacklisted"),
2065
     None, 0, _GetItemAttr("blacklisted")),
2066
    (_MakeField("variants", "Variants", QFT_OTHER,
2067
                "Operating system variants"),
2068
     None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("variants"))),
2069
    (_MakeField("api_versions", "ApiVersions", QFT_OTHER,
2070
                "Operating system API versions"),
2071
     None, 0, _ConvWrap(sorted, _GetItemAttr("api_versions"))),
2072
    (_MakeField("parameters", "Parameters", QFT_OTHER,
2073
                "Operating system parameters"),
2074
     None, 0, _ConvWrap(compat.partial(utils.NiceSort, key=compat.fst),
2075
                        _GetItemAttr("parameters"))),
2076
    (_MakeField("node_status", "NodeStatus", QFT_OTHER,
2077
                "Status from node"),
2078
     None, 0, _GetItemAttr("node_status")),
2079
    ]
2080

    
2081
  return _PrepareFieldList(fields, [])
2082

    
2083

    
2084
#: Fields available for node queries
2085
NODE_FIELDS = _BuildNodeFields()
2086

    
2087
#: Fields available for instance queries
2088
INSTANCE_FIELDS = _BuildInstanceFields()
2089

    
2090
#: Fields available for lock queries
2091
LOCK_FIELDS = _BuildLockFields()
2092

    
2093
#: Fields available for node group queries
2094
GROUP_FIELDS = _BuildGroupFields()
2095

    
2096
#: Fields available for operating system queries
2097
OS_FIELDS = _BuildOsFields()
2098

    
2099
#: All available resources
2100
ALL_FIELDS = {
2101
  constants.QR_INSTANCE: INSTANCE_FIELDS,
2102
  constants.QR_NODE: NODE_FIELDS,
2103
  constants.QR_LOCK: LOCK_FIELDS,
2104
  constants.QR_GROUP: GROUP_FIELDS,
2105
  constants.QR_OS: OS_FIELDS,
2106
  }
2107

    
2108
#: All available field lists
2109
ALL_FIELD_LISTS = ALL_FIELDS.values()