Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ fbc263a9

History | View | Annotate | Download (54.2 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) = range(100, 104)
87

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

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

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

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

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

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

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

    
133
_SERIAL_NO_DOC = "%s object serial number, incremented on each modification"
134

    
135

    
136
def _GetUnknownField(ctx, item): # pylint: disable-msg=W0613
137
  """Gets the contents of an unknown field.
138

139
  """
140
  return _FS_UNKNOWN
141

    
142

    
143
def _GetQueryFields(fielddefs, selected):
144
  """Calculates the internal list of selected fields.
145

146
  Unknown fields are returned as L{constants.QFT_UNKNOWN}.
147

148
  @type fielddefs: dict
149
  @param fielddefs: Field definitions
150
  @type selected: list of strings
151
  @param selected: List of selected fields
152

153
  """
154
  result = []
155

    
156
  for name in selected:
157
    try:
158
      fdef = fielddefs[name]
159
    except KeyError:
160
      fdef = (_MakeField(name, name, QFT_UNKNOWN, "Unknown field '%s'" % name),
161
              None, 0, _GetUnknownField)
162

    
163
    assert len(fdef) == 4
164

    
165
    result.append(fdef)
166

    
167
  return result
168

    
169

    
170
def GetAllFields(fielddefs):
171
  """Extract L{objects.QueryFieldDefinition} from field definitions.
172

173
  @rtype: list of L{objects.QueryFieldDefinition}
174

175
  """
176
  return [fdef for (fdef, _, _, _) in fielddefs]
177

    
178

    
179
class _FilterHints:
180
  """Class for filter analytics.
181

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

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

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

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

202
  """
203
  def __init__(self, namefield):
204
    """Initializes this class.
205

206
    @type namefield: string
207
    @param namefield: Field caller is interested in
208

209
    """
210
    self._namefield = namefield
211

    
212
    #: Whether all names need to be requested (e.g. if a non-equality operator
213
    #: has been used)
214
    self._allnames = False
215

    
216
    #: Which names to request
217
    self._names = None
218

    
219
    #: Data kinds referenced by the filter (used by L{Query.RequestedData})
220
    self._datakinds = set()
221

    
222
  def RequestedNames(self):
223
    """Returns all requested values.
224

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

228
    @rtype: list
229

230
    """
231
    if self._allnames or self._names is None:
232
      return None
233

    
234
    return utils.UniqueSequence(self._names)
235

    
236
  def ReferencedData(self):
237
    """Returns all kinds of data referenced by the filter.
238

239
    """
240
    return frozenset(self._datakinds)
241

    
242
  def _NeedAllNames(self):
243
    """Changes internal state to request all names.
244

245
    """
246
    self._allnames = True
247
    self._names = None
248

    
249
  def NoteLogicOp(self, op):
250
    """Called when handling a logic operation.
251

252
    @type op: string
253
    @param op: Operator
254

255
    """
256
    if op != qlang.OP_OR:
257
      self._NeedAllNames()
258

    
259
  def NoteUnaryOp(self, op): # pylint: disable-msg=W0613
260
    """Called when handling an unary operation.
261

262
    @type op: string
263
    @param op: Operator
264

265
    """
266
    self._NeedAllNames()
267

    
268
  def NoteBinaryOp(self, op, datakind, name, value):
269
    """Called when handling a binary operation.
270

271
    @type op: string
272
    @param op: Operator
273
    @type name: string
274
    @param name: Left-hand side of operator (field name)
275
    @param value: Right-hand side of operator
276

277
    """
278
    if datakind is not None:
279
      self._datakinds.add(datakind)
280

    
281
    if self._allnames:
282
      return
283

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

    
293

    
294
def _WrapLogicOp(op_fn, sentences, ctx, item):
295
  """Wrapper for logic operator functions.
296

297
  """
298
  return op_fn(fn(ctx, item) for fn in sentences)
299

    
300

    
301
def _WrapUnaryOp(op_fn, inner, ctx, item):
302
  """Wrapper for unary operator functions.
303

304
  """
305
  return op_fn(inner(ctx, item))
306

    
307

    
308
def _WrapBinaryOp(op_fn, retrieval_fn, value, ctx, item):
309
  """Wrapper for binary operator functions.
310

311
  """
312
  return op_fn(retrieval_fn(ctx, item), value)
313

    
314

    
315
def _WrapNot(fn, lhs, rhs):
316
  """Negates the result of a wrapped function.
317

318
  """
319
  return not fn(lhs, rhs)
320

    
321

    
322
class _FilterCompilerHelper:
323
  """Converts a query filter to a callable usable for filtering.
324

325
  """
326
  # String statement has no effect, pylint: disable-msg=W0105
327

    
328
  #: How deep filters can be nested
329
  _LEVELS_MAX = 10
330

    
331
  # Unique identifiers for operator groups
332
  (_OPTYPE_LOGIC,
333
   _OPTYPE_UNARY,
334
   _OPTYPE_BINARY) = range(1, 4)
335

    
336
  """Functions for equality checks depending on field flags.
337

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

342
  Order matters. The first item with flags will be used. Flags are checked
343
  using binary AND.
344

345
  """
346
  _EQUALITY_CHECKS = [
347
    (QFF_HOSTNAME,
348
     lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
349
                                               case_sensitive=False)),
350
    (None, operator.eq),
351
    ]
352

    
353
  """Known operators
354

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

358
    - C{_OPTYPE_LOGIC}: Callable taking any number of arguments; used by
359
      L{_HandleLogicOp}
360
    - C{_OPTYPE_UNARY}: Callable taking exactly one parameter; used by
361
      L{_HandleUnaryOp}
362
    - C{_OPTYPE_BINARY}: Callable taking exactly two parameters, the left- and
363
      right-hand side of the operator, used by L{_HandleBinaryOp}
364

365
  """
366
  _OPS = {
367
    # Logic operators
368
    qlang.OP_OR: (_OPTYPE_LOGIC, compat.any),
369
    qlang.OP_AND: (_OPTYPE_LOGIC, compat.all),
370

    
371
    # Unary operators
372
    qlang.OP_NOT: (_OPTYPE_UNARY, operator.not_),
373

    
374
    # Binary operators
375
    qlang.OP_EQUAL: (_OPTYPE_BINARY, _EQUALITY_CHECKS),
376
    qlang.OP_NOT_EQUAL:
377
      (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn))
378
                        for (flags, fn) in _EQUALITY_CHECKS]),
379
    qlang.OP_GLOB: (_OPTYPE_BINARY, NotImplemented),
380
    qlang.OP_REGEXP: (_OPTYPE_BINARY, NotImplemented),
381
    qlang.OP_CONTAINS: (_OPTYPE_BINARY, [
382
      (None, operator.contains),
383
      ]),
384
    }
385

    
386
  def __init__(self, fields):
387
    """Initializes this class.
388

389
    @param fields: Field definitions (return value of L{_PrepareFieldList})
390

391
    """
392
    self._fields = fields
393
    self._hints = None
394
    self._op_handler = None
395

    
396
  def __call__(self, hints, filter_):
397
    """Converts a query filter into a callable function.
398

399
    @type hints: L{_FilterHints} or None
400
    @param hints: Callbacks doing analysis on filter
401
    @type filter_: list
402
    @param filter_: Filter structure
403
    @rtype: callable
404
    @return: Function receiving context and item as parameters, returning
405
             boolean as to whether item matches filter
406

407
    """
408
    self._op_handler = {
409
      self._OPTYPE_LOGIC:
410
        (self._HandleLogicOp, getattr(hints, "NoteLogicOp", None)),
411
      self._OPTYPE_UNARY:
412
        (self._HandleUnaryOp, getattr(hints, "NoteUnaryOp", None)),
413
      self._OPTYPE_BINARY:
414
        (self._HandleBinaryOp, getattr(hints, "NoteBinaryOp", None)),
415
      }
416

    
417
    try:
418
      filter_fn = self._Compile(filter_, 0)
419
    finally:
420
      self._op_handler = None
421

    
422
    return filter_fn
423

    
424
  def _Compile(self, filter_, level):
425
    """Inner function for converting filters.
426

427
    Calls the correct handler functions for the top-level operator. This
428
    function is called recursively (e.g. for logic operators).
429

430
    """
431
    if not (isinstance(filter_, (list, tuple)) and filter_):
432
      raise errors.ParameterError("Invalid filter on level %s" % level)
433

    
434
    # Limit recursion
435
    if level >= self._LEVELS_MAX:
436
      raise errors.ParameterError("Only up to %s levels are allowed (filter"
437
                                  " nested too deep)" % self._LEVELS_MAX)
438

    
439
    # Create copy to be modified
440
    operands = filter_[:]
441
    op = operands.pop(0)
442

    
443
    try:
444
      (kind, op_data) = self._OPS[op]
445
    except KeyError:
446
      raise errors.ParameterError("Unknown operator '%s'" % op)
447

    
448
    (handler, hints_cb) = self._op_handler[kind]
449

    
450
    return handler(hints_cb, level, op, op_data, operands)
451

    
452
  def _HandleLogicOp(self, hints_fn, level, op, op_fn, operands):
453
    """Handles logic operators.
454

455
    @type hints_fn: callable
456
    @param hints_fn: Callback doing some analysis on the filter
457
    @type level: integer
458
    @param level: Current depth
459
    @type op: string
460
    @param op: Operator
461
    @type op_fn: callable
462
    @param op_fn: Function implementing operator
463
    @type operands: list
464
    @param operands: List of operands
465

466
    """
467
    if hints_fn:
468
      hints_fn(op)
469

    
470
    return compat.partial(_WrapLogicOp, op_fn,
471
                          [self._Compile(op, level + 1) for op in operands])
472

    
473
  def _HandleUnaryOp(self, hints_fn, level, op, op_fn, operands):
474
    """Handles unary operators.
475

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

487
    """
488
    if hints_fn:
489
      hints_fn(op)
490

    
491
    if len(operands) != 1:
492
      raise errors.ParameterError("Unary operator '%s' expects exactly one"
493
                                  " operand" % op)
494

    
495
    return compat.partial(_WrapUnaryOp, op_fn,
496
                          self._Compile(operands[0], level + 1))
497

    
498
  def _HandleBinaryOp(self, hints_fn, level, op, op_data, operands):
499
    """Handles binary 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
    @param op_data: Functions implementing operators
508
    @type operands: list
509
    @param operands: List of operands
510

511
    """
512
    # Unused arguments, pylint: disable-msg=W0613
513
    try:
514
      (name, value) = operands
515
    except (ValueError, TypeError):
516
      raise errors.ParameterError("Invalid binary operator, expected exactly"
517
                                  " two operands")
518

    
519
    try:
520
      (fdef, datakind, field_flags, retrieval_fn) = self._fields[name]
521
    except KeyError:
522
      raise errors.ParameterError("Unknown field '%s'" % name)
523

    
524
    assert fdef.kind != QFT_UNKNOWN
525

    
526
    # TODO: Type conversions?
527

    
528
    verify_fn = _VERIFY_FN[fdef.kind]
529
    if not verify_fn(value):
530
      raise errors.ParameterError("Unable to compare field '%s' (type '%s')"
531
                                  " with '%s', expected %s" %
532
                                  (name, fdef.kind, value.__class__.__name__,
533
                                   verify_fn))
534

    
535
    if hints_fn:
536
      hints_fn(op, datakind, name, value)
537

    
538
    for (fn_flags, fn) in op_data:
539
      if fn_flags is None or fn_flags & field_flags:
540
        return compat.partial(_WrapBinaryOp, fn, retrieval_fn, value)
541

    
542
    raise errors.ProgrammerError("Unable to find operator implementation"
543
                                 " (op '%s', flags %s)" % (op, field_flags))
544

    
545

    
546
def _CompileFilter(fields, hints, filter_):
547
  """Converts a query filter into a callable function.
548

549
  See L{_FilterCompilerHelper} for details.
550

551
  @rtype: callable
552

553
  """
554
  return _FilterCompilerHelper(fields)(hints, filter_)
555

    
556

    
557
class Query:
558
  def __init__(self, fieldlist, selected, filter_=None, namefield=None):
559
    """Initializes this class.
560

561
    The field definition is a dictionary with the field's name as a key and a
562
    tuple containing, in order, the field definition object
563
    (L{objects.QueryFieldDefinition}, the data kind to help calling code
564
    collect data and a retrieval function. The retrieval function is called
565
    with two parameters, in order, the data container and the item in container
566
    (see L{Query.Query}).
567

568
    Users of this class can call L{RequestedData} before preparing the data
569
    container to determine what data is needed.
570

571
    @type fieldlist: dictionary
572
    @param fieldlist: Field definitions
573
    @type selected: list of strings
574
    @param selected: List of selected fields
575

576
    """
577
    assert namefield is None or namefield in fieldlist
578

    
579
    self._fields = _GetQueryFields(fieldlist, selected)
580

    
581
    self._filter_fn = None
582
    self._requested_names = None
583
    self._filter_datakinds = frozenset()
584

    
585
    if filter_ is not None:
586
      # Collect requested names if wanted
587
      if namefield:
588
        hints = _FilterHints(namefield)
589
      else:
590
        hints = None
591

    
592
      # Build filter function
593
      self._filter_fn = _CompileFilter(fieldlist, hints, filter_)
594
      if hints:
595
        self._requested_names = hints.RequestedNames()
596
        self._filter_datakinds = hints.ReferencedData()
597

    
598
    if namefield is None:
599
      self._name_fn = None
600
    else:
601
      (_, _, _, self._name_fn) = fieldlist[namefield]
602

    
603
  def RequestedNames(self):
604
    """Returns all names referenced in the filter.
605

606
    If there is no filter or operators are preventing determining the exact
607
    names, C{None} is returned.
608

609
    """
610
    return self._requested_names
611

    
612
  def RequestedData(self):
613
    """Gets requested kinds of data.
614

615
    @rtype: frozenset
616

617
    """
618
    return (self._filter_datakinds |
619
            frozenset(datakind for (_, datakind, _, _) in self._fields
620
                      if datakind is not None))
621

    
622
  def GetFields(self):
623
    """Returns the list of fields for this query.
624

625
    Includes unknown fields.
626

627
    @rtype: List of L{objects.QueryFieldDefinition}
628

629
    """
630
    return GetAllFields(self._fields)
631

    
632
  def Query(self, ctx, sort_by_name=True):
633
    """Execute a query.
634

635
    @param ctx: Data container passed to field retrieval functions, must
636
      support iteration using C{__iter__}
637
    @type sort_by_name: boolean
638
    @param sort_by_name: Whether to sort by name or keep the input data's
639
      ordering
640

641
    """
642
    sort = (self._name_fn and sort_by_name)
643

    
644
    result = []
645

    
646
    for idx, item in enumerate(ctx):
647
      if not (self._filter_fn is None or self._filter_fn(ctx, item)):
648
        continue
649

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

    
652
      # Verify result
653
      if __debug__:
654
        _VerifyResultRow(self._fields, row)
655

    
656
      if sort:
657
        (status, name) = _ProcessResult(self._name_fn(ctx, item))
658
        assert status == constants.RS_NORMAL
659
        # TODO: Are there cases where we wouldn't want to use NiceSort?
660
        result.append((utils.NiceSortKey(name), idx, row))
661
      else:
662
        result.append(row)
663

    
664
    if not sort:
665
      return result
666

    
667
    # TODO: Would "heapq" be more efficient than sorting?
668

    
669
    # Sorting in-place instead of using "sorted()"
670
    result.sort()
671

    
672
    assert not result or (len(result[0]) == 3 and len(result[-1]) == 3)
673

    
674
    return map(operator.itemgetter(2), result)
675

    
676
  def OldStyleQuery(self, ctx, sort_by_name=True):
677
    """Query with "old" query result format.
678

679
    See L{Query.Query} for arguments.
680

681
    """
682
    unknown = set(fdef.name for (fdef, _, _, _) in self._fields
683
                  if fdef.kind == QFT_UNKNOWN)
684
    if unknown:
685
      raise errors.OpPrereqError("Unknown output fields selected: %s" %
686
                                 (utils.CommaJoin(unknown), ),
687
                                 errors.ECODE_INVAL)
688

    
689
    return [[value for (_, value) in row]
690
            for row in self.Query(ctx, sort_by_name=sort_by_name)]
691

    
692

    
693
def _ProcessResult(value):
694
  """Converts result values into externally-visible ones.
695

696
  """
697
  if value is _FS_UNKNOWN:
698
    return (RS_UNKNOWN, None)
699
  elif value is _FS_NODATA:
700
    return (RS_NODATA, None)
701
  elif value is _FS_UNAVAIL:
702
    return (RS_UNAVAIL, None)
703
  elif value is _FS_OFFLINE:
704
    return (RS_OFFLINE, None)
705
  else:
706
    return (RS_NORMAL, value)
707

    
708

    
709
def _VerifyResultRow(fields, row):
710
  """Verifies the contents of a query result row.
711

712
  @type fields: list
713
  @param fields: Field definitions for result
714
  @type row: list of tuples
715
  @param row: Row data
716

717
  """
718
  assert len(row) == len(fields)
719
  errs = []
720
  for ((status, value), (fdef, _, _, _)) in zip(row, fields):
721
    if status == RS_NORMAL:
722
      if not _VERIFY_FN[fdef.kind](value):
723
        errs.append("normal field %s fails validation (value is %s)" %
724
                    (fdef.name, value))
725
    elif value is not None:
726
      errs.append("abnormal field %s has a non-None value" % fdef.name)
727
  assert not errs, ("Failed validation: %s in row %s" %
728
                    (utils.CommaJoin(errors), row))
729

    
730

    
731
def _PrepareFieldList(fields, aliases):
732
  """Prepares field list for use by L{Query}.
733

734
  Converts the list to a dictionary and does some verification.
735

736
  @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data
737
      kind, retrieval function)
738
  @param fields: List of fields, see L{Query.__init__} for a better
739
      description
740
  @type aliases: list of tuples; (alias, target)
741
  @param aliases: list of tuples containing aliases; for each
742
      alias/target pair, a duplicate will be created in the field list
743
  @rtype: dict
744
  @return: Field dictionary for L{Query}
745

746
  """
747
  if __debug__:
748
    duplicates = utils.FindDuplicates(fdef.title.lower()
749
                                      for (fdef, _, _, _) in fields)
750
    assert not duplicates, "Duplicate title(s) found: %r" % duplicates
751

    
752
  result = {}
753

    
754
  for field in fields:
755
    (fdef, _, flags, fn) = field
756

    
757
    assert fdef.name and fdef.title, "Name and title are required"
758
    assert FIELD_NAME_RE.match(fdef.name)
759
    assert TITLE_RE.match(fdef.title)
760
    assert (DOC_RE.match(fdef.doc) and len(fdef.doc.splitlines()) == 1 and
761
            fdef.doc.strip() == fdef.doc), \
762
           "Invalid description for field '%s'" % fdef.name
763
    assert callable(fn)
764
    assert fdef.name not in result, \
765
           "Duplicate field name '%s' found" % fdef.name
766
    assert (flags & ~QFF_ALL) == 0, "Unknown flags for field '%s'" % fdef.name
767

    
768
    result[fdef.name] = field
769

    
770
  for alias, target in aliases:
771
    assert alias not in result, "Alias %s overrides an existing field" % alias
772
    assert target in result, "Missing target %s for alias %s" % (target, alias)
773
    (fdef, k, flags, fn) = result[target]
774
    fdef = fdef.Copy()
775
    fdef.name = alias
776
    result[alias] = (fdef, k, flags, fn)
777

    
778
  assert len(result) == len(fields) + len(aliases)
779
  assert compat.all(name == fdef.name
780
                    for (name, (fdef, _, _, _)) in result.items())
781

    
782
  return result
783

    
784

    
785
def GetQueryResponse(query, ctx, sort_by_name=True):
786
  """Prepares the response for a query.
787

788
  @type query: L{Query}
789
  @param ctx: Data container, see L{Query.Query}
790
  @type sort_by_name: boolean
791
  @param sort_by_name: Whether to sort by name or keep the input data's
792
    ordering
793

794
  """
795
  return objects.QueryResponse(data=query.Query(ctx, sort_by_name=sort_by_name),
796
                               fields=query.GetFields()).ToDict()
797

    
798

    
799
def QueryFields(fielddefs, selected):
800
  """Returns list of available fields.
801

802
  @type fielddefs: dict
803
  @param fielddefs: Field definitions
804
  @type selected: list of strings
805
  @param selected: List of selected fields
806
  @return: List of L{objects.QueryFieldDefinition}
807

808
  """
809
  if selected is None:
810
    # Client requests all fields, sort by name
811
    fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
812
                           key=operator.attrgetter("name"))
813
  else:
814
    # Keep order as requested by client
815
    fdefs = Query(fielddefs, selected).GetFields()
816

    
817
  return objects.QueryFieldsResponse(fields=fdefs).ToDict()
818

    
819

    
820
def _MakeField(name, title, kind, doc):
821
  """Wrapper for creating L{objects.QueryFieldDefinition} instances.
822

823
  @param name: Field name as a regular expression
824
  @param title: Human-readable title
825
  @param kind: Field type
826
  @param doc: Human-readable description
827

828
  """
829
  return objects.QueryFieldDefinition(name=name, title=title, kind=kind,
830
                                      doc=doc)
831

    
832

    
833
def _GetNodeRole(node, master_name):
834
  """Determine node role.
835

836
  @type node: L{objects.Node}
837
  @param node: Node object
838
  @type master_name: string
839
  @param master_name: Master node name
840

841
  """
842
  if node.name == master_name:
843
    return constants.NR_MASTER
844
  elif node.master_candidate:
845
    return constants.NR_MCANDIDATE
846
  elif node.drained:
847
    return constants.NR_DRAINED
848
  elif node.offline:
849
    return constants.NR_OFFLINE
850
  else:
851
    return constants.NR_REGULAR
852

    
853

    
854
def _GetItemAttr(attr):
855
  """Returns a field function to return an attribute of the item.
856

857
  @param attr: Attribute name
858

859
  """
860
  getter = operator.attrgetter(attr)
861
  return lambda _, item: getter(item)
862

    
863

    
864
def _GetItemTimestamp(getter):
865
  """Returns function for getting timestamp of item.
866

867
  @type getter: callable
868
  @param getter: Function to retrieve timestamp attribute
869

870
  """
871
  def fn(_, item):
872
    """Returns a timestamp of item.
873

874
    """
875
    timestamp = getter(item)
876
    if timestamp is None:
877
      # Old configs might not have all timestamps
878
      return _FS_UNAVAIL
879
    else:
880
      return timestamp
881

    
882
  return fn
883

    
884

    
885
def _GetItemTimestampFields(datatype):
886
  """Returns common timestamp fields.
887

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

890
  """
891
  return [
892
    (_MakeField("ctime", "CTime", QFT_TIMESTAMP, "Creation timestamp"),
893
     datatype, 0, _GetItemTimestamp(operator.attrgetter("ctime"))),
894
    (_MakeField("mtime", "MTime", QFT_TIMESTAMP, "Modification timestamp"),
895
     datatype, 0, _GetItemTimestamp(operator.attrgetter("mtime"))),
896
    ]
897

    
898

    
899
class NodeQueryData:
900
  """Data container for node data queries.
901

902
  """
903
  def __init__(self, nodes, live_data, master_name, node_to_primary,
904
               node_to_secondary, groups, oob_support, cluster):
905
    """Initializes this class.
906

907
    """
908
    self.nodes = nodes
909
    self.live_data = live_data
910
    self.master_name = master_name
911
    self.node_to_primary = node_to_primary
912
    self.node_to_secondary = node_to_secondary
913
    self.groups = groups
914
    self.oob_support = oob_support
915
    self.cluster = cluster
916

    
917
    # Used for individual rows
918
    self.curlive_data = None
919

    
920
  def __iter__(self):
921
    """Iterate over all nodes.
922

923
    This function has side-effects and only one instance of the resulting
924
    generator should be used at a time.
925

926
    """
927
    for node in self.nodes:
928
      if self.live_data:
929
        self.curlive_data = self.live_data.get(node.name, None)
930
      else:
931
        self.curlive_data = None
932
      yield node
933

    
934

    
935
#: Fields that are direct attributes of an L{objects.Node} object
936
_NODE_SIMPLE_FIELDS = {
937
  "drained": ("Drained", QFT_BOOL, 0, "Whether node is drained"),
938
  "master_candidate": ("MasterC", QFT_BOOL, 0,
939
                       "Whether node is a master candidate"),
940
  "master_capable": ("MasterCapable", QFT_BOOL, 0,
941
                     "Whether node can become a master candidate"),
942
  "name": ("Node", QFT_TEXT, QFF_HOSTNAME, "Node name"),
943
  "offline": ("Offline", QFT_BOOL, 0, "Whether node is marked offline"),
944
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Node"),
945
  "uuid": ("UUID", QFT_TEXT, 0, "Node UUID"),
946
  "vm_capable": ("VMCapable", QFT_BOOL, 0, "Whether node can host instances"),
947
  }
948

    
949

    
950
#: Fields requiring talking to the node
951
# Note that none of these are available for non-vm_capable nodes
952
_NODE_LIVE_FIELDS = {
953
  "bootid": ("BootID", QFT_TEXT, "bootid",
954
             "Random UUID renewed for each system reboot, can be used"
955
             " for detecting reboots by tracking changes"),
956
  "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes",
957
             "Number of NUMA domains on node (if exported by hypervisor)"),
958
  "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets",
959
               "Number of physical CPU sockets (if exported by hypervisor)"),
960
  "ctotal": ("CTotal", QFT_NUMBER, "cpu_total", "Number of logical processors"),
961
  "dfree": ("DFree", QFT_UNIT, "vg_free",
962
            "Available disk space in volume group"),
963
  "dtotal": ("DTotal", QFT_UNIT, "vg_size",
964
             "Total disk space in volume group used for instance disk"
965
             " allocation"),
966
  "mfree": ("MFree", QFT_UNIT, "memory_free",
967
            "Memory available for instance allocations"),
968
  "mnode": ("MNode", QFT_UNIT, "memory_dom0",
969
            "Amount of memory used by node (dom0 for Xen)"),
970
  "mtotal": ("MTotal", QFT_UNIT, "memory_total",
971
             "Total amount of memory of physical machine"),
972
  }
973

    
974

    
975
def _GetGroup(cb):
976
  """Build function for calling another function with an node group.
977

978
  @param cb: The callback to be called with the nodegroup
979

980
  """
981
  def fn(ctx, node):
982
    """Get group data for a node.
983

984
    @type ctx: L{NodeQueryData}
985
    @type inst: L{objects.Node}
986
    @param inst: Node object
987

988
    """
989
    ng = ctx.groups.get(node.group, None)
990
    if ng is None:
991
      # Nodes always have a group, or the configuration is corrupt
992
      return _FS_UNAVAIL
993

    
994
    return cb(ctx, node, ng)
995

    
996
  return fn
997

    
998

    
999
def _GetNodeGroup(ctx, node, ng): # pylint: disable-msg=W0613
1000
  """Returns the name of a node's group.
1001

1002
  @type ctx: L{NodeQueryData}
1003
  @type node: L{objects.Node}
1004
  @param node: Node object
1005
  @type ng: L{objects.NodeGroup}
1006
  @param ng: The node group this node belongs to
1007

1008
  """
1009
  return ng.name
1010

    
1011

    
1012
def _GetNodePower(ctx, node):
1013
  """Returns the node powered state
1014

1015
  @type ctx: L{NodeQueryData}
1016
  @type node: L{objects.Node}
1017
  @param node: Node object
1018

1019
  """
1020
  if ctx.oob_support[node.name]:
1021
    return node.powered
1022

    
1023
  return _FS_UNAVAIL
1024

    
1025

    
1026
def _GetNdParams(ctx, node, ng):
1027
  """Returns the ndparams for this node.
1028

1029
  @type ctx: L{NodeQueryData}
1030
  @type node: L{objects.Node}
1031
  @param node: Node object
1032
  @type ng: L{objects.NodeGroup}
1033
  @param ng: The node group this node belongs to
1034

1035
  """
1036
  return ctx.cluster.SimpleFillND(ng.FillND(node))
1037

    
1038

    
1039
def _GetLiveNodeField(field, kind, ctx, node):
1040
  """Gets the value of a "live" field from L{NodeQueryData}.
1041

1042
  @param field: Live field name
1043
  @param kind: Data kind, one of L{constants.QFT_ALL}
1044
  @type ctx: L{NodeQueryData}
1045
  @type node: L{objects.Node}
1046
  @param node: Node object
1047

1048
  """
1049
  if node.offline:
1050
    return _FS_OFFLINE
1051

    
1052
  if not node.vm_capable:
1053
    return _FS_UNAVAIL
1054

    
1055
  if not ctx.curlive_data:
1056
    return _FS_NODATA
1057

    
1058
  try:
1059
    value = ctx.curlive_data[field]
1060
  except KeyError:
1061
    return _FS_UNAVAIL
1062

    
1063
  if kind == QFT_TEXT:
1064
    return value
1065

    
1066
  assert kind in (QFT_NUMBER, QFT_UNIT)
1067

    
1068
  # Try to convert into number
1069
  try:
1070
    return int(value)
1071
  except (ValueError, TypeError):
1072
    logging.exception("Failed to convert node field '%s' (value %r) to int",
1073
                      value, field)
1074
    return _FS_UNAVAIL
1075

    
1076

    
1077
def _BuildNodeFields():
1078
  """Builds list of fields for node queries.
1079

1080
  """
1081
  fields = [
1082
    (_MakeField("pip", "PrimaryIP", QFT_TEXT, "Primary IP address"),
1083
     NQ_CONFIG, 0, _GetItemAttr("primary_ip")),
1084
    (_MakeField("sip", "SecondaryIP", QFT_TEXT, "Secondary IP address"),
1085
     NQ_CONFIG, 0, _GetItemAttr("secondary_ip")),
1086
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0,
1087
     lambda ctx, node: list(node.GetTags())),
1088
    (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"),
1089
     NQ_CONFIG, 0, lambda ctx, node: node.name == ctx.master_name),
1090
    (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0,
1091
     _GetGroup(_GetNodeGroup)),
1092
    (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"),
1093
     NQ_CONFIG, 0, _GetItemAttr("group")),
1094
    (_MakeField("powered", "Powered", QFT_BOOL,
1095
                "Whether node is thought to be powered on"),
1096
     NQ_OOB, 0, _GetNodePower),
1097
    (_MakeField("ndparams", "NodeParameters", QFT_OTHER,
1098
                "Merged node parameters"),
1099
     NQ_GROUP, 0, _GetGroup(_GetNdParams)),
1100
    (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER,
1101
                "Custom node parameters"),
1102
      NQ_GROUP, 0, _GetItemAttr("ndparams")),
1103
    ]
1104

    
1105
  # Node role
1106
  role_values = (constants.NR_MASTER, constants.NR_MCANDIDATE,
1107
                 constants.NR_REGULAR, constants.NR_DRAINED,
1108
                 constants.NR_OFFLINE)
1109
  role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate,"
1110
              " \"%s\" for regular, \"%s\" for a drained, \"%s\" for offline" %
1111
              role_values)
1112
  fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
1113
                 lambda ctx, node: _GetNodeRole(node, ctx.master_name)))
1114
  assert set(role_values) == constants.NR_ALL
1115

    
1116
  def _GetLength(getter):
1117
    return lambda ctx, node: len(getter(ctx)[node.name])
1118

    
1119
  def _GetList(getter):
1120
    return lambda ctx, node: list(getter(ctx)[node.name])
1121

    
1122
  # Add fields operating on instance lists
1123
  for prefix, titleprefix, docword, getter in \
1124
      [("p", "Pri", "primary", operator.attrgetter("node_to_primary")),
1125
       ("s", "Sec", "secondary", operator.attrgetter("node_to_secondary"))]:
1126
    # TODO: Allow filterting by hostname in list
1127
    fields.extend([
1128
      (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER,
1129
                  "Number of instances with this node as %s" % docword),
1130
       NQ_INST, 0, _GetLength(getter)),
1131
      (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
1132
                  QFT_OTHER,
1133
                  "List of instances with this node as %s" % docword),
1134
       NQ_INST, 0, _GetList(getter)),
1135
      ])
1136

    
1137
  # Add simple fields
1138
  fields.extend([
1139
    (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1140
    for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()
1141
    ])
1142

    
1143
  # Add fields requiring live data
1144
  fields.extend([
1145
    (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1146
     compat.partial(_GetLiveNodeField, nfield, kind))
1147
    for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()
1148
    ])
1149

    
1150
  # Add timestamps
1151
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1152

    
1153
  return _PrepareFieldList(fields, [])
1154

    
1155

    
1156
class InstanceQueryData:
1157
  """Data container for instance data queries.
1158

1159
  """
1160
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
1161
               live_data, wrongnode_inst, console):
1162
    """Initializes this class.
1163

1164
    @param instances: List of instance objects
1165
    @param cluster: Cluster object
1166
    @type disk_usage: dict; instance name as key
1167
    @param disk_usage: Per-instance disk usage
1168
    @type offline_nodes: list of strings
1169
    @param offline_nodes: List of offline nodes
1170
    @type bad_nodes: list of strings
1171
    @param bad_nodes: List of faulty nodes
1172
    @type live_data: dict; instance name as key
1173
    @param live_data: Per-instance live data
1174
    @type wrongnode_inst: set
1175
    @param wrongnode_inst: Set of instances running on wrong node(s)
1176
    @type console: dict; instance name as key
1177
    @param console: Per-instance console information
1178

1179
    """
1180
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
1181
           "Offline nodes not included in bad nodes"
1182
    assert not (set(live_data.keys()) & set(bad_nodes)), \
1183
           "Found live data for bad or offline nodes"
1184

    
1185
    self.instances = instances
1186
    self.cluster = cluster
1187
    self.disk_usage = disk_usage
1188
    self.offline_nodes = offline_nodes
1189
    self.bad_nodes = bad_nodes
1190
    self.live_data = live_data
1191
    self.wrongnode_inst = wrongnode_inst
1192
    self.console = console
1193

    
1194
    # Used for individual rows
1195
    self.inst_hvparams = None
1196
    self.inst_beparams = None
1197
    self.inst_nicparams = None
1198

    
1199
  def __iter__(self):
1200
    """Iterate over all instances.
1201

1202
    This function has side-effects and only one instance of the resulting
1203
    generator should be used at a time.
1204

1205
    """
1206
    for inst in self.instances:
1207
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1208
      self.inst_beparams = self.cluster.FillBE(inst)
1209
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1210
                             for nic in inst.nics]
1211

    
1212
      yield inst
1213

    
1214

    
1215
def _GetInstOperState(ctx, inst):
1216
  """Get instance's operational status.
1217

1218
  @type ctx: L{InstanceQueryData}
1219
  @type inst: L{objects.Instance}
1220
  @param inst: Instance object
1221

1222
  """
1223
  # Can't use RS_OFFLINE here as it would describe the instance to
1224
  # be offline when we actually don't know due to missing data
1225
  if inst.primary_node in ctx.bad_nodes:
1226
    return _FS_NODATA
1227
  else:
1228
    return bool(ctx.live_data.get(inst.name))
1229

    
1230

    
1231
def _GetInstLiveData(name):
1232
  """Build function for retrieving live data.
1233

1234
  @type name: string
1235
  @param name: Live data field name
1236

1237
  """
1238
  def fn(ctx, inst):
1239
    """Get live data for an instance.
1240

1241
    @type ctx: L{InstanceQueryData}
1242
    @type inst: L{objects.Instance}
1243
    @param inst: Instance object
1244

1245
    """
1246
    if (inst.primary_node in ctx.bad_nodes or
1247
        inst.primary_node in ctx.offline_nodes):
1248
      # Can't use RS_OFFLINE here as it would describe the instance to be
1249
      # offline when we actually don't know due to missing data
1250
      return _FS_NODATA
1251

    
1252
    if inst.name in ctx.live_data:
1253
      data = ctx.live_data[inst.name]
1254
      if name in data:
1255
        return data[name]
1256

    
1257
    return _FS_UNAVAIL
1258

    
1259
  return fn
1260

    
1261

    
1262
def _GetInstStatus(ctx, inst):
1263
  """Get instance status.
1264

1265
  @type ctx: L{InstanceQueryData}
1266
  @type inst: L{objects.Instance}
1267
  @param inst: Instance object
1268

1269
  """
1270
  if inst.primary_node in ctx.offline_nodes:
1271
    return constants.INSTST_NODEOFFLINE
1272

    
1273
  if inst.primary_node in ctx.bad_nodes:
1274
    return constants.INSTST_NODEDOWN
1275

    
1276
  if bool(ctx.live_data.get(inst.name)):
1277
    if inst.name in ctx.wrongnode_inst:
1278
      return constants.INSTST_WRONGNODE
1279
    elif inst.admin_up:
1280
      return constants.INSTST_RUNNING
1281
    else:
1282
      return constants.INSTST_ERRORUP
1283

    
1284
  if inst.admin_up:
1285
    return constants.INSTST_ERRORDOWN
1286

    
1287
  return constants.INSTST_ADMINDOWN
1288

    
1289

    
1290
def _GetInstDiskSize(index):
1291
  """Build function for retrieving disk size.
1292

1293
  @type index: int
1294
  @param index: Disk index
1295

1296
  """
1297
  def fn(_, inst):
1298
    """Get size of a disk.
1299

1300
    @type inst: L{objects.Instance}
1301
    @param inst: Instance object
1302

1303
    """
1304
    try:
1305
      return inst.disks[index].size
1306
    except IndexError:
1307
      return _FS_UNAVAIL
1308

    
1309
  return fn
1310

    
1311

    
1312
def _GetInstNic(index, cb):
1313
  """Build function for calling another function with an instance NIC.
1314

1315
  @type index: int
1316
  @param index: NIC index
1317
  @type cb: callable
1318
  @param cb: Callback
1319

1320
  """
1321
  def fn(ctx, inst):
1322
    """Call helper function with instance NIC.
1323

1324
    @type ctx: L{InstanceQueryData}
1325
    @type inst: L{objects.Instance}
1326
    @param inst: Instance object
1327

1328
    """
1329
    try:
1330
      nic = inst.nics[index]
1331
    except IndexError:
1332
      return _FS_UNAVAIL
1333

    
1334
    return cb(ctx, index, nic)
1335

    
1336
  return fn
1337

    
1338

    
1339
def _GetInstNicIp(ctx, _, nic): # pylint: disable-msg=W0613
1340
  """Get a NIC's IP address.
1341

1342
  @type ctx: L{InstanceQueryData}
1343
  @type nic: L{objects.NIC}
1344
  @param nic: NIC object
1345

1346
  """
1347
  if nic.ip is None:
1348
    return _FS_UNAVAIL
1349
  else:
1350
    return nic.ip
1351

    
1352

    
1353
def _GetInstNicBridge(ctx, index, _):
1354
  """Get a NIC's bridge.
1355

1356
  @type ctx: L{InstanceQueryData}
1357
  @type index: int
1358
  @param index: NIC index
1359

1360
  """
1361
  assert len(ctx.inst_nicparams) >= index
1362

    
1363
  nicparams = ctx.inst_nicparams[index]
1364

    
1365
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1366
    return nicparams[constants.NIC_LINK]
1367
  else:
1368
    return _FS_UNAVAIL
1369

    
1370

    
1371
def _GetInstAllNicBridges(ctx, inst):
1372
  """Get all network bridges for an instance.
1373

1374
  @type ctx: L{InstanceQueryData}
1375
  @type inst: L{objects.Instance}
1376
  @param inst: Instance object
1377

1378
  """
1379
  assert len(ctx.inst_nicparams) == len(inst.nics)
1380

    
1381
  result = []
1382

    
1383
  for nicp in ctx.inst_nicparams:
1384
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1385
      result.append(nicp[constants.NIC_LINK])
1386
    else:
1387
      result.append(None)
1388

    
1389
  assert len(result) == len(inst.nics)
1390

    
1391
  return result
1392

    
1393

    
1394
def _GetInstNicParam(name):
1395
  """Build function for retrieving a NIC parameter.
1396

1397
  @type name: string
1398
  @param name: Parameter name
1399

1400
  """
1401
  def fn(ctx, index, _):
1402
    """Get a NIC's bridge.
1403

1404
    @type ctx: L{InstanceQueryData}
1405
    @type inst: L{objects.Instance}
1406
    @param inst: Instance object
1407
    @type nic: L{objects.NIC}
1408
    @param nic: NIC object
1409

1410
    """
1411
    assert len(ctx.inst_nicparams) >= index
1412
    return ctx.inst_nicparams[index][name]
1413

    
1414
  return fn
1415

    
1416

    
1417
def _GetInstanceNetworkFields():
1418
  """Get instance fields involving network interfaces.
1419

1420
  @return: Tuple containing list of field definitions used as input for
1421
    L{_PrepareFieldList} and a list of aliases
1422

1423
  """
1424
  nic_mac_fn = lambda ctx, _, nic: nic.mac
1425
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1426
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1427

    
1428
  fields = [
1429
    # All NICs
1430
    (_MakeField("nic.count", "NICs", QFT_NUMBER,
1431
                "Number of network interfaces"),
1432
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.nics)),
1433
    (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER,
1434
                "List containing each network interface's MAC address"),
1435
     IQ_CONFIG, 0, lambda ctx, inst: [nic.mac for nic in inst.nics]),
1436
    (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
1437
                "List containing each network interface's IP address"),
1438
     IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
1439
    (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
1440
                "List containing each network interface's mode"), IQ_CONFIG, 0,
1441
     lambda ctx, inst: [nicp[constants.NIC_MODE]
1442
                        for nicp in ctx.inst_nicparams]),
1443
    (_MakeField("nic.links", "NIC_links", QFT_OTHER,
1444
                "List containing each network interface's link"), IQ_CONFIG, 0,
1445
     lambda ctx, inst: [nicp[constants.NIC_LINK]
1446
                        for nicp in ctx.inst_nicparams]),
1447
    (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
1448
                "List containing each network interface's bridge"),
1449
     IQ_CONFIG, 0, _GetInstAllNicBridges),
1450
    ]
1451

    
1452
  # NICs by number
1453
  for i in range(constants.MAX_NICS):
1454
    numtext = utils.FormatOrdinal(i + 1)
1455
    fields.extend([
1456
      (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT,
1457
                  "IP address of %s network interface" % numtext),
1458
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)),
1459
      (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
1460
                  "MAC address of %s network interface" % numtext),
1461
       IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
1462
      (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
1463
                  "Mode of %s network interface" % numtext),
1464
       IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
1465
      (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT,
1466
                  "Link of %s network interface" % numtext),
1467
       IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)),
1468
      (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
1469
                  "Bridge of %s network interface" % numtext),
1470
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
1471
      ])
1472

    
1473
  aliases = [
1474
    # Legacy fields for first NIC
1475
    ("ip", "nic.ip/0"),
1476
    ("mac", "nic.mac/0"),
1477
    ("bridge", "nic.bridge/0"),
1478
    ("nic_mode", "nic.mode/0"),
1479
    ("nic_link", "nic.link/0"),
1480
    ]
1481

    
1482
  return (fields, aliases)
1483

    
1484

    
1485
def _GetInstDiskUsage(ctx, inst):
1486
  """Get disk usage for an instance.
1487

1488
  @type ctx: L{InstanceQueryData}
1489
  @type inst: L{objects.Instance}
1490
  @param inst: Instance object
1491

1492
  """
1493
  usage = ctx.disk_usage[inst.name]
1494

    
1495
  if usage is None:
1496
    usage = 0
1497

    
1498
  return usage
1499

    
1500

    
1501
def _GetInstanceConsole(ctx, inst):
1502
  """Get console information for instance.
1503

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

1508
  """
1509
  consinfo = ctx.console[inst.name]
1510

    
1511
  if consinfo is None:
1512
    return _FS_UNAVAIL
1513

    
1514
  return consinfo
1515

    
1516

    
1517
def _GetInstanceDiskFields():
1518
  """Get instance fields involving disks.
1519

1520
  @return: List of field definitions used as input for L{_PrepareFieldList}
1521

1522
  """
1523
  fields = [
1524
    (_MakeField("disk_usage", "DiskUsage", QFT_UNIT,
1525
                "Total disk space used by instance on each of its nodes;"
1526
                " this is not the disk size visible to the instance, but"
1527
                " the usage on the node"),
1528
     IQ_DISKUSAGE, 0, _GetInstDiskUsage),
1529
    (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"),
1530
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
1531
    (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
1532
     IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
1533
    ]
1534

    
1535
  # Disks by number
1536
  fields.extend([
1537
    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1538
                "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
1539
     IQ_CONFIG, 0, _GetInstDiskSize(i))
1540
    for i in range(constants.MAX_DISKS)
1541
    ])
1542

    
1543
  return fields
1544

    
1545

    
1546
def _GetInstanceParameterFields():
1547
  """Get instance fields involving parameters.
1548

1549
  @return: List of field definitions used as input for L{_PrepareFieldList}
1550

1551
  """
1552
  # TODO: Consider moving titles closer to constants
1553
  be_title = {
1554
    constants.BE_AUTO_BALANCE: "Auto_balance",
1555
    constants.BE_MEMORY: "ConfigMemory",
1556
    constants.BE_VCPUS: "ConfigVCPUs",
1557
    }
1558

    
1559
  hv_title = {
1560
    constants.HV_ACPI: "ACPI",
1561
    constants.HV_BOOT_ORDER: "Boot_order",
1562
    constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1563
    constants.HV_DISK_TYPE: "Disk_type",
1564
    constants.HV_INITRD_PATH: "Initrd_path",
1565
    constants.HV_KERNEL_PATH: "Kernel_path",
1566
    constants.HV_NIC_TYPE: "NIC_type",
1567
    constants.HV_PAE: "PAE",
1568
    constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1569
    }
1570

    
1571
  fields = [
1572
    # Filled parameters
1573
    (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
1574
                "Hypervisor parameters"),
1575
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
1576
    (_MakeField("beparams", "BackendParameters", QFT_OTHER,
1577
                "Backend parameters"),
1578
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
1579

    
1580
    # Unfilled parameters
1581
    (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
1582
                "Custom hypervisor parameters"),
1583
     IQ_CONFIG, 0, _GetItemAttr("hvparams")),
1584
    (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
1585
                "Custom backend parameters",),
1586
     IQ_CONFIG, 0, _GetItemAttr("beparams")),
1587
    (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
1588
                "Custom network interface parameters"),
1589
     IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1590
    ]
1591

    
1592
  # HV params
1593
  def _GetInstHvParam(name):
1594
    return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1595

    
1596
  fields.extend([
1597
    (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1598
                _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1599
     IQ_CONFIG, 0, _GetInstHvParam(name))
1600
    for name, kind in constants.HVS_PARAMETER_TYPES.items()
1601
    if name not in constants.HVC_GLOBALS
1602
    ])
1603

    
1604
  # BE params
1605
  def _GetInstBeParam(name):
1606
    return lambda ctx, _: ctx.inst_beparams.get(name, None)
1607

    
1608
  fields.extend([
1609
    (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1610
                _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1611
     IQ_CONFIG, 0, _GetInstBeParam(name))
1612
    for name, kind in constants.BES_PARAMETER_TYPES.items()
1613
    ])
1614

    
1615
  return fields
1616

    
1617

    
1618
_INST_SIMPLE_FIELDS = {
1619
  "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"),
1620
  "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"),
1621
  "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"),
1622
  # Depending on the hypervisor, the port can be None
1623
  "network_port": ("Network_port", QFT_OTHER, 0,
1624
                   "Instance network port if available (e.g. for VNC console)"),
1625
  "os": ("OS", QFT_TEXT, 0, "Operating system"),
1626
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"),
1627
  "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"),
1628
  }
1629

    
1630

    
1631
def _BuildInstanceFields():
1632
  """Builds list of fields for instance queries.
1633

1634
  """
1635
  fields = [
1636
    (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
1637
     IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")),
1638
    # TODO: Allow filtering by secondary node as hostname
1639
    (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
1640
                "Secondary nodes; usually this will just be one node"),
1641
     IQ_CONFIG, 0, lambda ctx, inst: list(inst.secondary_nodes)),
1642
    (_MakeField("admin_state", "Autostart", QFT_BOOL,
1643
                "Desired state of instance (if set, the instance should be"
1644
                " up)"),
1645
     IQ_CONFIG, 0, _GetItemAttr("admin_up")),
1646
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
1647
     lambda ctx, inst: list(inst.GetTags())),
1648
    (_MakeField("console", "Console", QFT_OTHER,
1649
                "Instance console information"), IQ_CONSOLE, 0,
1650
     _GetInstanceConsole),
1651
    ]
1652

    
1653
  # Add simple fields
1654
  fields.extend([
1655
    (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1656
    for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()
1657
    ])
1658

    
1659
  # Fields requiring talking to the node
1660
  fields.extend([
1661
    (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
1662
     IQ_LIVE, 0, _GetInstOperState),
1663
    (_MakeField("oper_ram", "Memory", QFT_UNIT,
1664
                "Actual memory usage as seen by hypervisor"),
1665
     IQ_LIVE, 0, _GetInstLiveData("memory")),
1666
    (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
1667
                "Actual number of VCPUs as seen by hypervisor"),
1668
     IQ_LIVE, 0, _GetInstLiveData("vcpus")),
1669
    ])
1670

    
1671
  # Status field
1672
  status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
1673
                   constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
1674
                   constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
1675
                   constants.INSTST_NODEOFFLINE)
1676
  status_doc = ("Instance status; \"%s\" if instance is set to be running"
1677
                " and actually is, \"%s\" if instance is stopped and"
1678
                " is not running, \"%s\" if instance running, but not on its"
1679
                " designated primary node, \"%s\" if instance should be"
1680
                " stopped, but is actually running, \"%s\" if instance should"
1681
                " run, but doesn't, \"%s\" if instance's primary node is down,"
1682
                " \"%s\" if instance's primary node is marked offline" %
1683
                status_values)
1684
  fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
1685
                 IQ_LIVE, 0, _GetInstStatus))
1686
  assert set(status_values) == constants.INSTST_ALL, \
1687
         "Status documentation mismatch"
1688

    
1689
  (network_fields, network_aliases) = _GetInstanceNetworkFields()
1690

    
1691
  fields.extend(network_fields)
1692
  fields.extend(_GetInstanceParameterFields())
1693
  fields.extend(_GetInstanceDiskFields())
1694
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1695

    
1696
  aliases = [
1697
    ("vcpus", "be/vcpus"),
1698
    ("sda_size", "disk.size/0"),
1699
    ("sdb_size", "disk.size/1"),
1700
    ] + network_aliases
1701

    
1702
  return _PrepareFieldList(fields, aliases)
1703

    
1704

    
1705
class LockQueryData:
1706
  """Data container for lock data queries.
1707

1708
  """
1709
  def __init__(self, lockdata):
1710
    """Initializes this class.
1711

1712
    """
1713
    self.lockdata = lockdata
1714

    
1715
  def __iter__(self):
1716
    """Iterate over all locks.
1717

1718
    """
1719
    return iter(self.lockdata)
1720

    
1721

    
1722
def _GetLockOwners(_, data):
1723
  """Returns a sorted list of a lock's current owners.
1724

1725
  """
1726
  (_, _, owners, _) = data
1727

    
1728
  if owners:
1729
    owners = utils.NiceSort(owners)
1730

    
1731
  return owners
1732

    
1733

    
1734
def _GetLockPending(_, data):
1735
  """Returns a sorted list of a lock's pending acquires.
1736

1737
  """
1738
  (_, _, _, pending) = data
1739

    
1740
  if pending:
1741
    pending = [(mode, utils.NiceSort(names))
1742
               for (mode, names) in pending]
1743

    
1744
  return pending
1745

    
1746

    
1747
def _BuildLockFields():
1748
  """Builds list of fields for lock queries.
1749

1750
  """
1751
  return _PrepareFieldList([
1752
    # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
1753
    (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
1754
     lambda ctx, (name, mode, owners, pending): name),
1755
    (_MakeField("mode", "Mode", QFT_OTHER,
1756
                "Mode in which the lock is currently acquired"
1757
                " (exclusive or shared)"),
1758
     LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
1759
    (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
1760
     LQ_OWNER, 0, _GetLockOwners),
1761
    (_MakeField("pending", "Pending", QFT_OTHER,
1762
                "Threads waiting for the lock"),
1763
     LQ_PENDING, 0, _GetLockPending),
1764
    ], [])
1765

    
1766

    
1767
class GroupQueryData:
1768
  """Data container for node group data queries.
1769

1770
  """
1771
  def __init__(self, groups, group_to_nodes, group_to_instances):
1772
    """Initializes this class.
1773

1774
    @param groups: List of node group objects
1775
    @type group_to_nodes: dict; group UUID as key
1776
    @param group_to_nodes: Per-group list of nodes
1777
    @type group_to_instances: dict; group UUID as key
1778
    @param group_to_instances: Per-group list of (primary) instances
1779

1780
    """
1781
    self.groups = groups
1782
    self.group_to_nodes = group_to_nodes
1783
    self.group_to_instances = group_to_instances
1784

    
1785
  def __iter__(self):
1786
    """Iterate over all node groups.
1787

1788
    """
1789
    return iter(self.groups)
1790

    
1791

    
1792
_GROUP_SIMPLE_FIELDS = {
1793
  "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
1794
  "name": ("Group", QFT_TEXT, "Group name"),
1795
  "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
1796
  "uuid": ("UUID", QFT_TEXT, "Group UUID"),
1797
  "ndparams": ("NDParams", QFT_OTHER, "Node parameters"),
1798
  }
1799

    
1800

    
1801
def _BuildGroupFields():
1802
  """Builds list of fields for node group queries.
1803

1804
  """
1805
  # Add simple fields
1806
  fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
1807
             _GetItemAttr(name))
1808
            for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
1809

    
1810
  def _GetLength(getter):
1811
    return lambda ctx, group: len(getter(ctx)[group.uuid])
1812

    
1813
  def _GetSortedList(getter):
1814
    return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
1815

    
1816
  group_to_nodes = operator.attrgetter("group_to_nodes")
1817
  group_to_instances = operator.attrgetter("group_to_instances")
1818

    
1819
  # Add fields for nodes
1820
  fields.extend([
1821
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
1822
     GQ_NODE, 0, _GetLength(group_to_nodes)),
1823
    (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
1824
     GQ_NODE, 0, _GetSortedList(group_to_nodes)),
1825
    ])
1826

    
1827
  # Add fields for instances
1828
  fields.extend([
1829
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
1830
                "Number of primary instances"),
1831
     GQ_INST, 0, _GetLength(group_to_instances)),
1832
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
1833
                "List of primary instances"),
1834
     GQ_INST, 0, _GetSortedList(group_to_instances)),
1835
    ])
1836

    
1837
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1838

    
1839
  return _PrepareFieldList(fields, [])
1840

    
1841

    
1842
#: Fields available for node queries
1843
NODE_FIELDS = _BuildNodeFields()
1844

    
1845
#: Fields available for instance queries
1846
INSTANCE_FIELDS = _BuildInstanceFields()
1847

    
1848
#: Fields available for lock queries
1849
LOCK_FIELDS = _BuildLockFields()
1850

    
1851
#: Fields available for node group queries
1852
GROUP_FIELDS = _BuildGroupFields()
1853

    
1854
#: All available resources
1855
ALL_FIELDS = {
1856
  constants.QR_INSTANCE: INSTANCE_FIELDS,
1857
  constants.QR_NODE: NODE_FIELDS,
1858
  constants.QR_LOCK: LOCK_FIELDS,
1859
  constants.QR_GROUP: GROUP_FIELDS,
1860
  }
1861

    
1862
#: All available field lists
1863
ALL_FIELD_LISTS = ALL_FIELDS.values()