Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ 7925d409

History | View | Annotate | Download (53.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) = 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):
633
    """Execute a query.
634

635
    @param ctx: Data container passed to field retrieval functions, must
636
      support iteration using C{__iter__}
637

638
    """
639
    result = []
640

    
641
    for idx, item in enumerate(ctx):
642
      if not (self._filter_fn is None or self._filter_fn(ctx, item)):
643
        continue
644

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

    
647
      # Verify result
648
      if __debug__:
649
        _VerifyResultRow(self._fields, row)
650

    
651
      if self._name_fn:
652
        (status, name) = _ProcessResult(self._name_fn(ctx, item))
653
        assert status == constants.RS_NORMAL
654
        # TODO: Are there cases where we wouldn't want to use NiceSort?
655
        sortname = utils.NiceSortKey(name)
656
      else:
657
        sortname = None
658

    
659
      result.append((sortname, idx, row))
660

    
661
    # TODO: Would "heapq" be more efficient than sorting?
662

    
663
    # Sorting in-place instead of using "sorted()"
664
    result.sort()
665

    
666
    assert not result or (len(result[0]) == 3 and len(result[-1]) == 3)
667

    
668
    return map(operator.itemgetter(2), result)
669

    
670
  def OldStyleQuery(self, ctx):
671
    """Query with "old" query result format.
672

673
    See L{Query.Query} for arguments.
674

675
    """
676
    unknown = set(fdef.name for (fdef, _, _, _) in self._fields
677
                  if fdef.kind == QFT_UNKNOWN)
678
    if unknown:
679
      raise errors.OpPrereqError("Unknown output fields selected: %s" %
680
                                 (utils.CommaJoin(unknown), ),
681
                                 errors.ECODE_INVAL)
682

    
683
    return [[value for (_, value) in row]
684
            for row in self.Query(ctx)]
685

    
686

    
687
def _ProcessResult(value):
688
  """Converts result values into externally-visible ones.
689

690
  """
691
  if value is _FS_UNKNOWN:
692
    return (RS_UNKNOWN, None)
693
  elif value is _FS_NODATA:
694
    return (RS_NODATA, None)
695
  elif value is _FS_UNAVAIL:
696
    return (RS_UNAVAIL, None)
697
  elif value is _FS_OFFLINE:
698
    return (RS_OFFLINE, None)
699
  else:
700
    return (RS_NORMAL, value)
701

    
702

    
703
def _VerifyResultRow(fields, row):
704
  """Verifies the contents of a query result row.
705

706
  @type fields: list
707
  @param fields: Field definitions for result
708
  @type row: list of tuples
709
  @param row: Row data
710

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

    
724

    
725
def _PrepareFieldList(fields, aliases):
726
  """Prepares field list for use by L{Query}.
727

728
  Converts the list to a dictionary and does some verification.
729

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

740
  """
741
  if __debug__:
742
    duplicates = utils.FindDuplicates(fdef.title.lower()
743
                                      for (fdef, _, _, _) in fields)
744
    assert not duplicates, "Duplicate title(s) found: %r" % duplicates
745

    
746
  result = {}
747

    
748
  for field in fields:
749
    (fdef, _, flags, fn) = field
750

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

    
762
    result[fdef.name] = field
763

    
764
  for alias, target in aliases:
765
    assert alias not in result, "Alias %s overrides an existing field" % alias
766
    assert target in result, "Missing target %s for alias %s" % (target, alias)
767
    (fdef, k, flags, fn) = result[target]
768
    fdef = fdef.Copy()
769
    fdef.name = alias
770
    result[alias] = (fdef, k, flags, fn)
771

    
772
  assert len(result) == len(fields) + len(aliases)
773
  assert compat.all(name == fdef.name
774
                    for (name, (fdef, _, _, _)) in result.items())
775

    
776
  return result
777

    
778

    
779
def GetQueryResponse(query, ctx):
780
  """Prepares the response for a query.
781

782
  @type query: L{Query}
783
  @param ctx: Data container, see L{Query.Query}
784

785
  """
786
  return objects.QueryResponse(data=query.Query(ctx),
787
                               fields=query.GetFields()).ToDict()
788

    
789

    
790
def QueryFields(fielddefs, selected):
791
  """Returns list of available fields.
792

793
  @type fielddefs: dict
794
  @param fielddefs: Field definitions
795
  @type selected: list of strings
796
  @param selected: List of selected fields
797
  @return: List of L{objects.QueryFieldDefinition}
798

799
  """
800
  if selected is None:
801
    # Client requests all fields, sort by name
802
    fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
803
                           key=operator.attrgetter("name"))
804
  else:
805
    # Keep order as requested by client
806
    fdefs = Query(fielddefs, selected).GetFields()
807

    
808
  return objects.QueryFieldsResponse(fields=fdefs).ToDict()
809

    
810

    
811
def _MakeField(name, title, kind, doc):
812
  """Wrapper for creating L{objects.QueryFieldDefinition} instances.
813

814
  @param name: Field name as a regular expression
815
  @param title: Human-readable title
816
  @param kind: Field type
817
  @param doc: Human-readable description
818

819
  """
820
  return objects.QueryFieldDefinition(name=name, title=title, kind=kind,
821
                                      doc=doc)
822

    
823

    
824
def _GetNodeRole(node, master_name):
825
  """Determine node role.
826

827
  @type node: L{objects.Node}
828
  @param node: Node object
829
  @type master_name: string
830
  @param master_name: Master node name
831

832
  """
833
  if node.name == master_name:
834
    return constants.NR_MASTER
835
  elif node.master_candidate:
836
    return constants.NR_MCANDIDATE
837
  elif node.drained:
838
    return constants.NR_DRAINED
839
  elif node.offline:
840
    return constants.NR_OFFLINE
841
  else:
842
    return constants.NR_REGULAR
843

    
844

    
845
def _GetItemAttr(attr):
846
  """Returns a field function to return an attribute of the item.
847

848
  @param attr: Attribute name
849

850
  """
851
  getter = operator.attrgetter(attr)
852
  return lambda _, item: getter(item)
853

    
854

    
855
def _GetItemTimestamp(getter):
856
  """Returns function for getting timestamp of item.
857

858
  @type getter: callable
859
  @param getter: Function to retrieve timestamp attribute
860

861
  """
862
  def fn(_, item):
863
    """Returns a timestamp of item.
864

865
    """
866
    timestamp = getter(item)
867
    if timestamp is None:
868
      # Old configs might not have all timestamps
869
      return _FS_UNAVAIL
870
    else:
871
      return timestamp
872

    
873
  return fn
874

    
875

    
876
def _GetItemTimestampFields(datatype):
877
  """Returns common timestamp fields.
878

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

881
  """
882
  return [
883
    (_MakeField("ctime", "CTime", QFT_TIMESTAMP, "Creation timestamp"),
884
     datatype, 0, _GetItemTimestamp(operator.attrgetter("ctime"))),
885
    (_MakeField("mtime", "MTime", QFT_TIMESTAMP, "Modification timestamp"),
886
     datatype, 0, _GetItemTimestamp(operator.attrgetter("mtime"))),
887
    ]
888

    
889

    
890
class NodeQueryData:
891
  """Data container for node data queries.
892

893
  """
894
  def __init__(self, nodes, live_data, master_name, node_to_primary,
895
               node_to_secondary, groups, oob_support, cluster):
896
    """Initializes this class.
897

898
    """
899
    self.nodes = nodes
900
    self.live_data = live_data
901
    self.master_name = master_name
902
    self.node_to_primary = node_to_primary
903
    self.node_to_secondary = node_to_secondary
904
    self.groups = groups
905
    self.oob_support = oob_support
906
    self.cluster = cluster
907

    
908
    # Used for individual rows
909
    self.curlive_data = None
910

    
911
  def __iter__(self):
912
    """Iterate over all nodes.
913

914
    This function has side-effects and only one instance of the resulting
915
    generator should be used at a time.
916

917
    """
918
    for node in self.nodes:
919
      if self.live_data:
920
        self.curlive_data = self.live_data.get(node.name, None)
921
      else:
922
        self.curlive_data = None
923
      yield node
924

    
925

    
926
#: Fields that are direct attributes of an L{objects.Node} object
927
_NODE_SIMPLE_FIELDS = {
928
  "drained": ("Drained", QFT_BOOL, 0, "Whether node is drained"),
929
  "master_candidate": ("MasterC", QFT_BOOL, 0,
930
                       "Whether node is a master candidate"),
931
  "master_capable": ("MasterCapable", QFT_BOOL, 0,
932
                     "Whether node can become a master candidate"),
933
  "name": ("Node", QFT_TEXT, QFF_HOSTNAME, "Node name"),
934
  "offline": ("Offline", QFT_BOOL, 0, "Whether node is marked offline"),
935
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Node"),
936
  "uuid": ("UUID", QFT_TEXT, 0, "Node UUID"),
937
  "vm_capable": ("VMCapable", QFT_BOOL, 0, "Whether node can host instances"),
938
  }
939

    
940

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

    
965

    
966
def _GetGroup(cb):
967
  """Build function for calling another function with an node group.
968

969
  @param cb: The callback to be called with the nodegroup
970

971
  """
972
  def fn(ctx, node):
973
    """Get group data for a node.
974

975
    @type ctx: L{NodeQueryData}
976
    @type inst: L{objects.Node}
977
    @param inst: Node object
978

979
    """
980
    ng = ctx.groups.get(node.group, None)
981
    if ng is None:
982
      # Nodes always have a group, or the configuration is corrupt
983
      return _FS_UNAVAIL
984

    
985
    return cb(ctx, node, ng)
986

    
987
  return fn
988

    
989

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

993
  @type ctx: L{NodeQueryData}
994
  @type node: L{objects.Node}
995
  @param node: Node object
996
  @type ng: L{objects.NodeGroup}
997
  @param ng: The node group this node belongs to
998

999
  """
1000
  return ng.name
1001

    
1002

    
1003
def _GetNodePower(ctx, node):
1004
  """Returns the node powered state
1005

1006
  @type ctx: L{NodeQueryData}
1007
  @type node: L{objects.Node}
1008
  @param node: Node object
1009

1010
  """
1011
  if ctx.oob_support[node.name]:
1012
    return node.powered
1013

    
1014
  return _FS_UNAVAIL
1015

    
1016

    
1017
def _GetNdParams(ctx, node, ng):
1018
  """Returns the ndparams for this node.
1019

1020
  @type ctx: L{NodeQueryData}
1021
  @type node: L{objects.Node}
1022
  @param node: Node object
1023
  @type ng: L{objects.NodeGroup}
1024
  @param ng: The node group this node belongs to
1025

1026
  """
1027
  return ctx.cluster.SimpleFillND(ng.FillND(node))
1028

    
1029

    
1030
def _GetLiveNodeField(field, kind, ctx, node):
1031
  """Gets the value of a "live" field from L{NodeQueryData}.
1032

1033
  @param field: Live field name
1034
  @param kind: Data kind, one of L{constants.QFT_ALL}
1035
  @type ctx: L{NodeQueryData}
1036
  @type node: L{objects.Node}
1037
  @param node: Node object
1038

1039
  """
1040
  if node.offline:
1041
    return _FS_OFFLINE
1042

    
1043
  if not node.vm_capable:
1044
    return _FS_UNAVAIL
1045

    
1046
  if not ctx.curlive_data:
1047
    return _FS_NODATA
1048

    
1049
  try:
1050
    value = ctx.curlive_data[field]
1051
  except KeyError:
1052
    return _FS_UNAVAIL
1053

    
1054
  if kind == QFT_TEXT:
1055
    return value
1056

    
1057
  assert kind in (QFT_NUMBER, QFT_UNIT)
1058

    
1059
  # Try to convert into number
1060
  try:
1061
    return int(value)
1062
  except (ValueError, TypeError):
1063
    logging.exception("Failed to convert node field '%s' (value %r) to int",
1064
                      value, field)
1065
    return _FS_UNAVAIL
1066

    
1067

    
1068
def _BuildNodeFields():
1069
  """Builds list of fields for node queries.
1070

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

    
1096
  # Node role
1097
  role_values = (constants.NR_MASTER, constants.NR_MCANDIDATE,
1098
                 constants.NR_REGULAR, constants.NR_DRAINED,
1099
                 constants.NR_OFFLINE)
1100
  role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate,"
1101
              " \"%s\" for regular, \"%s\" for a drained, \"%s\" for offline" %
1102
              role_values)
1103
  fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
1104
                 lambda ctx, node: _GetNodeRole(node, ctx.master_name)))
1105
  assert set(role_values) == constants.NR_ALL
1106

    
1107
  def _GetLength(getter):
1108
    return lambda ctx, node: len(getter(ctx)[node.name])
1109

    
1110
  def _GetList(getter):
1111
    return lambda ctx, node: list(getter(ctx)[node.name])
1112

    
1113
  # Add fields operating on instance lists
1114
  for prefix, titleprefix, docword, getter in \
1115
      [("p", "Pri", "primary", operator.attrgetter("node_to_primary")),
1116
       ("s", "Sec", "secondary", operator.attrgetter("node_to_secondary"))]:
1117
    # TODO: Allow filterting by hostname in list
1118
    fields.extend([
1119
      (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER,
1120
                  "Number of instances with this node as %s" % docword),
1121
       NQ_INST, 0, _GetLength(getter)),
1122
      (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
1123
                  QFT_OTHER,
1124
                  "List of instances with this node as %s" % docword),
1125
       NQ_INST, 0, _GetList(getter)),
1126
      ])
1127

    
1128
  # Add simple fields
1129
  fields.extend([
1130
    (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1131
    for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()
1132
    ])
1133

    
1134
  # Add fields requiring live data
1135
  fields.extend([
1136
    (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1137
     compat.partial(_GetLiveNodeField, nfield, kind))
1138
    for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()
1139
    ])
1140

    
1141
  # Add timestamps
1142
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1143

    
1144
  return _PrepareFieldList(fields, [])
1145

    
1146

    
1147
class InstanceQueryData:
1148
  """Data container for instance data queries.
1149

1150
  """
1151
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
1152
               live_data, wrongnode_inst, console):
1153
    """Initializes this class.
1154

1155
    @param instances: List of instance objects
1156
    @param cluster: Cluster object
1157
    @type disk_usage: dict; instance name as key
1158
    @param disk_usage: Per-instance disk usage
1159
    @type offline_nodes: list of strings
1160
    @param offline_nodes: List of offline nodes
1161
    @type bad_nodes: list of strings
1162
    @param bad_nodes: List of faulty nodes
1163
    @type live_data: dict; instance name as key
1164
    @param live_data: Per-instance live data
1165
    @type wrongnode_inst: set
1166
    @param wrongnode_inst: Set of instances running on wrong node(s)
1167
    @type console: dict; instance name as key
1168
    @param console: Per-instance console information
1169

1170
    """
1171
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
1172
           "Offline nodes not included in bad nodes"
1173
    assert not (set(live_data.keys()) & set(bad_nodes)), \
1174
           "Found live data for bad or offline nodes"
1175

    
1176
    self.instances = instances
1177
    self.cluster = cluster
1178
    self.disk_usage = disk_usage
1179
    self.offline_nodes = offline_nodes
1180
    self.bad_nodes = bad_nodes
1181
    self.live_data = live_data
1182
    self.wrongnode_inst = wrongnode_inst
1183
    self.console = console
1184

    
1185
    # Used for individual rows
1186
    self.inst_hvparams = None
1187
    self.inst_beparams = None
1188
    self.inst_nicparams = None
1189

    
1190
  def __iter__(self):
1191
    """Iterate over all instances.
1192

1193
    This function has side-effects and only one instance of the resulting
1194
    generator should be used at a time.
1195

1196
    """
1197
    for inst in self.instances:
1198
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1199
      self.inst_beparams = self.cluster.FillBE(inst)
1200
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1201
                             for nic in inst.nics]
1202

    
1203
      yield inst
1204

    
1205

    
1206
def _GetInstOperState(ctx, inst):
1207
  """Get instance's operational status.
1208

1209
  @type ctx: L{InstanceQueryData}
1210
  @type inst: L{objects.Instance}
1211
  @param inst: Instance object
1212

1213
  """
1214
  # Can't use RS_OFFLINE here as it would describe the instance to
1215
  # be offline when we actually don't know due to missing data
1216
  if inst.primary_node in ctx.bad_nodes:
1217
    return _FS_NODATA
1218
  else:
1219
    return bool(ctx.live_data.get(inst.name))
1220

    
1221

    
1222
def _GetInstLiveData(name):
1223
  """Build function for retrieving live data.
1224

1225
  @type name: string
1226
  @param name: Live data field name
1227

1228
  """
1229
  def fn(ctx, inst):
1230
    """Get live data for an instance.
1231

1232
    @type ctx: L{InstanceQueryData}
1233
    @type inst: L{objects.Instance}
1234
    @param inst: Instance object
1235

1236
    """
1237
    if (inst.primary_node in ctx.bad_nodes or
1238
        inst.primary_node in ctx.offline_nodes):
1239
      # Can't use RS_OFFLINE here as it would describe the instance to be
1240
      # offline when we actually don't know due to missing data
1241
      return _FS_NODATA
1242

    
1243
    if inst.name in ctx.live_data:
1244
      data = ctx.live_data[inst.name]
1245
      if name in data:
1246
        return data[name]
1247

    
1248
    return _FS_UNAVAIL
1249

    
1250
  return fn
1251

    
1252

    
1253
def _GetInstStatus(ctx, inst):
1254
  """Get instance status.
1255

1256
  @type ctx: L{InstanceQueryData}
1257
  @type inst: L{objects.Instance}
1258
  @param inst: Instance object
1259

1260
  """
1261
  if inst.primary_node in ctx.offline_nodes:
1262
    return constants.INSTST_NODEOFFLINE
1263

    
1264
  if inst.primary_node in ctx.bad_nodes:
1265
    return constants.INSTST_NODEDOWN
1266

    
1267
  if bool(ctx.live_data.get(inst.name)):
1268
    if inst.name in ctx.wrongnode_inst:
1269
      return constants.INSTST_WRONGNODE
1270
    elif inst.admin_up:
1271
      return constants.INSTST_RUNNING
1272
    else:
1273
      return constants.INSTST_ERRORUP
1274

    
1275
  if inst.admin_up:
1276
    return constants.INSTST_ERRORDOWN
1277

    
1278
  return constants.INSTST_ADMINDOWN
1279

    
1280

    
1281
def _GetInstDiskSize(index):
1282
  """Build function for retrieving disk size.
1283

1284
  @type index: int
1285
  @param index: Disk index
1286

1287
  """
1288
  def fn(_, inst):
1289
    """Get size of a disk.
1290

1291
    @type inst: L{objects.Instance}
1292
    @param inst: Instance object
1293

1294
    """
1295
    try:
1296
      return inst.disks[index].size
1297
    except IndexError:
1298
      return _FS_UNAVAIL
1299

    
1300
  return fn
1301

    
1302

    
1303
def _GetInstNic(index, cb):
1304
  """Build function for calling another function with an instance NIC.
1305

1306
  @type index: int
1307
  @param index: NIC index
1308
  @type cb: callable
1309
  @param cb: Callback
1310

1311
  """
1312
  def fn(ctx, inst):
1313
    """Call helper function with instance NIC.
1314

1315
    @type ctx: L{InstanceQueryData}
1316
    @type inst: L{objects.Instance}
1317
    @param inst: Instance object
1318

1319
    """
1320
    try:
1321
      nic = inst.nics[index]
1322
    except IndexError:
1323
      return _FS_UNAVAIL
1324

    
1325
    return cb(ctx, index, nic)
1326

    
1327
  return fn
1328

    
1329

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

1333
  @type ctx: L{InstanceQueryData}
1334
  @type nic: L{objects.NIC}
1335
  @param nic: NIC object
1336

1337
  """
1338
  if nic.ip is None:
1339
    return _FS_UNAVAIL
1340
  else:
1341
    return nic.ip
1342

    
1343

    
1344
def _GetInstNicBridge(ctx, index, _):
1345
  """Get a NIC's bridge.
1346

1347
  @type ctx: L{InstanceQueryData}
1348
  @type index: int
1349
  @param index: NIC index
1350

1351
  """
1352
  assert len(ctx.inst_nicparams) >= index
1353

    
1354
  nicparams = ctx.inst_nicparams[index]
1355

    
1356
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1357
    return nicparams[constants.NIC_LINK]
1358
  else:
1359
    return _FS_UNAVAIL
1360

    
1361

    
1362
def _GetInstAllNicBridges(ctx, inst):
1363
  """Get all network bridges for an instance.
1364

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

1369
  """
1370
  assert len(ctx.inst_nicparams) == len(inst.nics)
1371

    
1372
  result = []
1373

    
1374
  for nicp in ctx.inst_nicparams:
1375
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1376
      result.append(nicp[constants.NIC_LINK])
1377
    else:
1378
      result.append(None)
1379

    
1380
  assert len(result) == len(inst.nics)
1381

    
1382
  return result
1383

    
1384

    
1385
def _GetInstNicParam(name):
1386
  """Build function for retrieving a NIC parameter.
1387

1388
  @type name: string
1389
  @param name: Parameter name
1390

1391
  """
1392
  def fn(ctx, index, _):
1393
    """Get a NIC's bridge.
1394

1395
    @type ctx: L{InstanceQueryData}
1396
    @type inst: L{objects.Instance}
1397
    @param inst: Instance object
1398
    @type nic: L{objects.NIC}
1399
    @param nic: NIC object
1400

1401
    """
1402
    assert len(ctx.inst_nicparams) >= index
1403
    return ctx.inst_nicparams[index][name]
1404

    
1405
  return fn
1406

    
1407

    
1408
def _GetInstanceNetworkFields():
1409
  """Get instance fields involving network interfaces.
1410

1411
  @return: Tuple containing list of field definitions used as input for
1412
    L{_PrepareFieldList} and a list of aliases
1413

1414
  """
1415
  nic_mac_fn = lambda ctx, _, nic: nic.mac
1416
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1417
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1418

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

    
1443
  # NICs by number
1444
  for i in range(constants.MAX_NICS):
1445
    numtext = utils.FormatOrdinal(i + 1)
1446
    fields.extend([
1447
      (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT,
1448
                  "IP address of %s network interface" % numtext),
1449
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)),
1450
      (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
1451
                  "MAC address of %s network interface" % numtext),
1452
       IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
1453
      (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
1454
                  "Mode of %s network interface" % numtext),
1455
       IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
1456
      (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT,
1457
                  "Link of %s network interface" % numtext),
1458
       IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)),
1459
      (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
1460
                  "Bridge of %s network interface" % numtext),
1461
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
1462
      ])
1463

    
1464
  aliases = [
1465
    # Legacy fields for first NIC
1466
    ("ip", "nic.ip/0"),
1467
    ("mac", "nic.mac/0"),
1468
    ("bridge", "nic.bridge/0"),
1469
    ("nic_mode", "nic.mode/0"),
1470
    ("nic_link", "nic.link/0"),
1471
    ]
1472

    
1473
  return (fields, aliases)
1474

    
1475

    
1476
def _GetInstDiskUsage(ctx, inst):
1477
  """Get disk usage for an instance.
1478

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

1483
  """
1484
  usage = ctx.disk_usage[inst.name]
1485

    
1486
  if usage is None:
1487
    usage = 0
1488

    
1489
  return usage
1490

    
1491

    
1492
def _GetInstanceConsole(ctx, inst):
1493
  """Get console information for instance.
1494

1495
  @type ctx: L{InstanceQueryData}
1496
  @type inst: L{objects.Instance}
1497
  @param inst: Instance object
1498

1499
  """
1500
  consinfo = ctx.console[inst.name]
1501

    
1502
  if consinfo is None:
1503
    return _FS_UNAVAIL
1504

    
1505
  return consinfo
1506

    
1507

    
1508
def _GetInstanceDiskFields():
1509
  """Get instance fields involving disks.
1510

1511
  @return: List of field definitions used as input for L{_PrepareFieldList}
1512

1513
  """
1514
  fields = [
1515
    (_MakeField("disk_usage", "DiskUsage", QFT_UNIT,
1516
                "Total disk space used by instance on each of its nodes;"
1517
                " this is not the disk size visible to the instance, but"
1518
                " the usage on the node"),
1519
     IQ_DISKUSAGE, 0, _GetInstDiskUsage),
1520
    (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"),
1521
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
1522
    (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
1523
     IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
1524
    ]
1525

    
1526
  # Disks by number
1527
  fields.extend([
1528
    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1529
                "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
1530
     IQ_CONFIG, 0, _GetInstDiskSize(i))
1531
    for i in range(constants.MAX_DISKS)
1532
    ])
1533

    
1534
  return fields
1535

    
1536

    
1537
def _GetInstanceParameterFields():
1538
  """Get instance fields involving parameters.
1539

1540
  @return: List of field definitions used as input for L{_PrepareFieldList}
1541

1542
  """
1543
  # TODO: Consider moving titles closer to constants
1544
  be_title = {
1545
    constants.BE_AUTO_BALANCE: "Auto_balance",
1546
    constants.BE_MEMORY: "ConfigMemory",
1547
    constants.BE_VCPUS: "ConfigVCPUs",
1548
    }
1549

    
1550
  hv_title = {
1551
    constants.HV_ACPI: "ACPI",
1552
    constants.HV_BOOT_ORDER: "Boot_order",
1553
    constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1554
    constants.HV_DISK_TYPE: "Disk_type",
1555
    constants.HV_INITRD_PATH: "Initrd_path",
1556
    constants.HV_KERNEL_PATH: "Kernel_path",
1557
    constants.HV_NIC_TYPE: "NIC_type",
1558
    constants.HV_PAE: "PAE",
1559
    constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1560
    }
1561

    
1562
  fields = [
1563
    # Filled parameters
1564
    (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
1565
                "Hypervisor parameters"),
1566
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
1567
    (_MakeField("beparams", "BackendParameters", QFT_OTHER,
1568
                "Backend parameters"),
1569
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
1570

    
1571
    # Unfilled parameters
1572
    (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
1573
                "Custom hypervisor parameters"),
1574
     IQ_CONFIG, 0, _GetItemAttr("hvparams")),
1575
    (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
1576
                "Custom backend parameters",),
1577
     IQ_CONFIG, 0, _GetItemAttr("beparams")),
1578
    (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
1579
                "Custom network interface parameters"),
1580
     IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1581
    ]
1582

    
1583
  # HV params
1584
  def _GetInstHvParam(name):
1585
    return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1586

    
1587
  fields.extend([
1588
    (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1589
                _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1590
     IQ_CONFIG, 0, _GetInstHvParam(name))
1591
    for name, kind in constants.HVS_PARAMETER_TYPES.items()
1592
    if name not in constants.HVC_GLOBALS
1593
    ])
1594

    
1595
  # BE params
1596
  def _GetInstBeParam(name):
1597
    return lambda ctx, _: ctx.inst_beparams.get(name, None)
1598

    
1599
  fields.extend([
1600
    (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1601
                _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1602
     IQ_CONFIG, 0, _GetInstBeParam(name))
1603
    for name, kind in constants.BES_PARAMETER_TYPES.items()
1604
    ])
1605

    
1606
  return fields
1607

    
1608

    
1609
_INST_SIMPLE_FIELDS = {
1610
  "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"),
1611
  "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"),
1612
  "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"),
1613
  # Depending on the hypervisor, the port can be None
1614
  "network_port": ("Network_port", QFT_OTHER, 0,
1615
                   "Instance network port if available (e.g. for VNC console)"),
1616
  "os": ("OS", QFT_TEXT, 0, "Operating system"),
1617
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"),
1618
  "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"),
1619
  }
1620

    
1621

    
1622
def _BuildInstanceFields():
1623
  """Builds list of fields for instance queries.
1624

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

    
1644
  # Add simple fields
1645
  fields.extend([
1646
    (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1647
    for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()
1648
    ])
1649

    
1650
  # Fields requiring talking to the node
1651
  fields.extend([
1652
    (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
1653
     IQ_LIVE, 0, _GetInstOperState),
1654
    (_MakeField("oper_ram", "Memory", QFT_UNIT,
1655
                "Actual memory usage as seen by hypervisor"),
1656
     IQ_LIVE, 0, _GetInstLiveData("memory")),
1657
    (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
1658
                "Actual number of VCPUs as seen by hypervisor"),
1659
     IQ_LIVE, 0, _GetInstLiveData("vcpus")),
1660
    ])
1661

    
1662
  # Status field
1663
  status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
1664
                   constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
1665
                   constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
1666
                   constants.INSTST_NODEOFFLINE)
1667
  status_doc = ("Instance status; \"%s\" if instance is set to be running"
1668
                " and actually is, \"%s\" if instance is stopped and"
1669
                " is not running, \"%s\" if instance running, but not on its"
1670
                " designated primary node, \"%s\" if instance should be"
1671
                " stopped, but is actually running, \"%s\" if instance should"
1672
                " run, but doesn't, \"%s\" if instance's primary node is down,"
1673
                " \"%s\" if instance's primary node is marked offline" %
1674
                status_values)
1675
  fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
1676
                 IQ_LIVE, 0, _GetInstStatus))
1677
  assert set(status_values) == constants.INSTST_ALL, \
1678
         "Status documentation mismatch"
1679

    
1680
  (network_fields, network_aliases) = _GetInstanceNetworkFields()
1681

    
1682
  fields.extend(network_fields)
1683
  fields.extend(_GetInstanceParameterFields())
1684
  fields.extend(_GetInstanceDiskFields())
1685
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1686

    
1687
  aliases = [
1688
    ("vcpus", "be/vcpus"),
1689
    ("sda_size", "disk.size/0"),
1690
    ("sdb_size", "disk.size/1"),
1691
    ] + network_aliases
1692

    
1693
  return _PrepareFieldList(fields, aliases)
1694

    
1695

    
1696
class LockQueryData:
1697
  """Data container for lock data queries.
1698

1699
  """
1700
  def __init__(self, lockdata):
1701
    """Initializes this class.
1702

1703
    """
1704
    self.lockdata = lockdata
1705

    
1706
  def __iter__(self):
1707
    """Iterate over all locks.
1708

1709
    """
1710
    return iter(self.lockdata)
1711

    
1712

    
1713
def _GetLockOwners(_, data):
1714
  """Returns a sorted list of a lock's current owners.
1715

1716
  """
1717
  (_, _, owners, _) = data
1718

    
1719
  if owners:
1720
    owners = utils.NiceSort(owners)
1721

    
1722
  return owners
1723

    
1724

    
1725
def _GetLockPending(_, data):
1726
  """Returns a sorted list of a lock's pending acquires.
1727

1728
  """
1729
  (_, _, _, pending) = data
1730

    
1731
  if pending:
1732
    pending = [(mode, utils.NiceSort(names))
1733
               for (mode, names) in pending]
1734

    
1735
  return pending
1736

    
1737

    
1738
def _BuildLockFields():
1739
  """Builds list of fields for lock queries.
1740

1741
  """
1742
  return _PrepareFieldList([
1743
    # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
1744
    (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
1745
     lambda ctx, (name, mode, owners, pending): name),
1746
    (_MakeField("mode", "Mode", QFT_OTHER,
1747
                "Mode in which the lock is currently acquired"
1748
                " (exclusive or shared)"),
1749
     LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
1750
    (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
1751
     LQ_OWNER, 0, _GetLockOwners),
1752
    (_MakeField("pending", "Pending", QFT_OTHER,
1753
                "Threads waiting for the lock"),
1754
     LQ_PENDING, 0, _GetLockPending),
1755
    ], [])
1756

    
1757

    
1758
class GroupQueryData:
1759
  """Data container for node group data queries.
1760

1761
  """
1762
  def __init__(self, groups, group_to_nodes, group_to_instances):
1763
    """Initializes this class.
1764

1765
    @param groups: List of node group objects
1766
    @type group_to_nodes: dict; group UUID as key
1767
    @param group_to_nodes: Per-group list of nodes
1768
    @type group_to_instances: dict; group UUID as key
1769
    @param group_to_instances: Per-group list of (primary) instances
1770

1771
    """
1772
    self.groups = groups
1773
    self.group_to_nodes = group_to_nodes
1774
    self.group_to_instances = group_to_instances
1775

    
1776
  def __iter__(self):
1777
    """Iterate over all node groups.
1778

1779
    """
1780
    return iter(self.groups)
1781

    
1782

    
1783
_GROUP_SIMPLE_FIELDS = {
1784
  "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
1785
  "name": ("Group", QFT_TEXT, "Group name"),
1786
  "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
1787
  "uuid": ("UUID", QFT_TEXT, "Group UUID"),
1788
  "ndparams": ("NDParams", QFT_OTHER, "Node parameters"),
1789
  }
1790

    
1791

    
1792
def _BuildGroupFields():
1793
  """Builds list of fields for node group queries.
1794

1795
  """
1796
  # Add simple fields
1797
  fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
1798
             _GetItemAttr(name))
1799
            for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
1800

    
1801
  def _GetLength(getter):
1802
    return lambda ctx, group: len(getter(ctx)[group.uuid])
1803

    
1804
  def _GetSortedList(getter):
1805
    return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
1806

    
1807
  group_to_nodes = operator.attrgetter("group_to_nodes")
1808
  group_to_instances = operator.attrgetter("group_to_instances")
1809

    
1810
  # Add fields for nodes
1811
  fields.extend([
1812
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
1813
     GQ_NODE, 0, _GetLength(group_to_nodes)),
1814
    (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
1815
     GQ_NODE, 0, _GetSortedList(group_to_nodes)),
1816
    ])
1817

    
1818
  # Add fields for instances
1819
  fields.extend([
1820
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
1821
                "Number of primary instances"),
1822
     GQ_INST, 0, _GetLength(group_to_instances)),
1823
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
1824
                "List of primary instances"),
1825
     GQ_INST, 0, _GetSortedList(group_to_instances)),
1826
    ])
1827

    
1828
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1829

    
1830
  return _PrepareFieldList(fields, [])
1831

    
1832

    
1833
#: Fields available for node queries
1834
NODE_FIELDS = _BuildNodeFields()
1835

    
1836
#: Fields available for instance queries
1837
INSTANCE_FIELDS = _BuildInstanceFields()
1838

    
1839
#: Fields available for lock queries
1840
LOCK_FIELDS = _BuildLockFields()
1841

    
1842
#: Fields available for node group queries
1843
GROUP_FIELDS = _BuildGroupFields()
1844

    
1845
#: All available resources
1846
ALL_FIELDS = {
1847
  constants.QR_INSTANCE: INSTANCE_FIELDS,
1848
  constants.QR_NODE: NODE_FIELDS,
1849
  constants.QR_LOCK: LOCK_FIELDS,
1850
  constants.QR_GROUP: GROUP_FIELDS,
1851
  }
1852

    
1853
#: All available field lists
1854
ALL_FIELD_LISTS = ALL_FIELDS.values()