Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ ea0f78c8

History | View | Annotate | Download (71.5 kB)

1
#
2
#
3

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

    
21

    
22
"""Module for query operations
23

24
How it works:
25

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

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

53
"""
54

    
55
import logging
56
import operator
57
import re
58

    
59
from ganeti import constants
60
from ganeti import errors
61
from ganeti import utils
62
from ganeti import compat
63
from ganeti import objects
64
from ganeti import ht
65
from ganeti import runtime
66
from ganeti import qlang
67

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

    
73

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

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

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

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

    
94
(GQ_CONFIG,
95
 GQ_NODE,
96
 GQ_INST,
97
 GQ_DISKPARAMS) = range(200, 204)
98

    
99
(CQ_CONFIG,
100
 CQ_QUEUE_DRAINED,
101
 CQ_WATCHER_PAUSE) = range(300, 303)
102

    
103
# Query field flags
104
QFF_HOSTNAME = 0x01
105
QFF_IP_ADDRESS = 0x02
106
# Next values: 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200
107
QFF_ALL = (QFF_HOSTNAME | QFF_IP_ADDRESS)
108

    
109
FIELD_NAME_RE = re.compile(r"^[a-z0-9/._]+$")
110
TITLE_RE = re.compile(r"^[^\s]+$")
111
DOC_RE = re.compile(r"^[A-Z].*[^.,?!]$")
112

    
113
#: Verification function for each field type
114
_VERIFY_FN = {
115
  QFT_UNKNOWN: ht.TNone,
116
  QFT_TEXT: ht.TString,
117
  QFT_BOOL: ht.TBool,
118
  QFT_NUMBER: ht.TInt,
119
  QFT_UNIT: ht.TInt,
120
  QFT_TIMESTAMP: ht.TNumber,
121
  QFT_OTHER: lambda _: True,
122
  }
123

    
124
# Unique objects for special field statuses
125
_FS_UNKNOWN = object()
126
_FS_NODATA = object()
127
_FS_UNAVAIL = object()
128
_FS_OFFLINE = object()
129

    
130
#: List of all special status
131
_FS_ALL = frozenset([_FS_UNKNOWN, _FS_NODATA, _FS_UNAVAIL, _FS_OFFLINE])
132

    
133
#: VType to QFT mapping
134
_VTToQFT = {
135
  # TODO: fix validation of empty strings
136
  constants.VTYPE_STRING: QFT_OTHER, # since VTYPE_STRINGs can be empty
137
  constants.VTYPE_MAYBE_STRING: QFT_OTHER,
138
  constants.VTYPE_BOOL: QFT_BOOL,
139
  constants.VTYPE_SIZE: QFT_UNIT,
140
  constants.VTYPE_INT: QFT_NUMBER,
141
  }
142

    
143
_SERIAL_NO_DOC = "%s object serial number, incremented on each modification"
144

    
145
# TODO: Consider moving titles closer to constants
146
NDP_TITLE = {
147
  constants.ND_OOB_PROGRAM: "OutOfBandProgram",
148
  constants.ND_SPINDLE_COUNT: "SpindleCount",
149
  }
150

    
151

    
152
def _GetUnknownField(ctx, item): # pylint: disable=W0613
153
  """Gets the contents of an unknown field.
154

155
  """
156
  return _FS_UNKNOWN
157

    
158

    
159
def _GetQueryFields(fielddefs, selected):
160
  """Calculates the internal list of selected fields.
161

162
  Unknown fields are returned as L{constants.QFT_UNKNOWN}.
163

164
  @type fielddefs: dict
165
  @param fielddefs: Field definitions
166
  @type selected: list of strings
167
  @param selected: List of selected fields
168

169
  """
170
  result = []
171

    
172
  for name in selected:
173
    try:
174
      fdef = fielddefs[name]
175
    except KeyError:
176
      fdef = (_MakeField(name, name, QFT_UNKNOWN, "Unknown field '%s'" % name),
177
              None, 0, _GetUnknownField)
178

    
179
    assert len(fdef) == 4
180

    
181
    result.append(fdef)
182

    
183
  return result
184

    
185

    
186
def GetAllFields(fielddefs):
187
  """Extract L{objects.QueryFieldDefinition} from field definitions.
188

189
  @rtype: list of L{objects.QueryFieldDefinition}
190

191
  """
192
  return [fdef for (fdef, _, _, _) in fielddefs]
193

    
194

    
195
class _FilterHints:
196
  """Class for filter analytics.
197

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

203
  There are two ways to optimize this. The first, and simpler, is to assign
204
  each field a group of data, so that the caller can determine which
205
  computations are necessary depending on the data groups requested. The list
206
  of referenced groups must also be computed for fields referenced in the
207
  filter.
208

209
  The second is restricting the items based on a primary key. The primary key
210
  is usually a unique name (e.g. a node name). This class extracts all
211
  referenced names from a filter. If it encounters any filter condition which
212
  disallows such a list to be determined (e.g. a non-equality filter), all
213
  names will be requested.
214

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

218
  """
219
  def __init__(self, namefield):
220
    """Initializes this class.
221

222
    @type namefield: string
223
    @param namefield: Field caller is interested in
224

225
    """
226
    self._namefield = namefield
227

    
228
    #: Whether all names need to be requested (e.g. if a non-equality operator
229
    #: has been used)
230
    self._allnames = False
231

    
232
    #: Which names to request
233
    self._names = None
234

    
235
    #: Data kinds referenced by the filter (used by L{Query.RequestedData})
236
    self._datakinds = set()
237

    
238
  def RequestedNames(self):
239
    """Returns all requested values.
240

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

244
    @rtype: list
245

246
    """
247
    if self._allnames or self._names is None:
248
      return None
249

    
250
    return utils.UniqueSequence(self._names)
251

    
252
  def ReferencedData(self):
253
    """Returns all kinds of data referenced by the filter.
254

255
    """
256
    return frozenset(self._datakinds)
257

    
258
  def _NeedAllNames(self):
259
    """Changes internal state to request all names.
260

261
    """
262
    self._allnames = True
263
    self._names = None
264

    
265
  def NoteLogicOp(self, op):
266
    """Called when handling a logic operation.
267

268
    @type op: string
269
    @param op: Operator
270

271
    """
272
    if op != qlang.OP_OR:
273
      self._NeedAllNames()
274

    
275
  def NoteUnaryOp(self, op): # pylint: disable=W0613
276
    """Called when handling an unary operation.
277

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

281
    """
282
    self._NeedAllNames()
283

    
284
  def NoteBinaryOp(self, op, datakind, name, value):
285
    """Called when handling a binary operation.
286

287
    @type op: string
288
    @param op: Operator
289
    @type name: string
290
    @param name: Left-hand side of operator (field name)
291
    @param value: Right-hand side of operator
292

293
    """
294
    if datakind is not None:
295
      self._datakinds.add(datakind)
296

    
297
    if self._allnames:
298
      return
299

    
300
    # If any operator other than equality was used, all names need to be
301
    # retrieved
302
    if op == qlang.OP_EQUAL and name == self._namefield:
303
      if self._names is None:
304
        self._names = []
305
      self._names.append(value)
306
    else:
307
      self._NeedAllNames()
308

    
309

    
310
def _WrapLogicOp(op_fn, sentences, ctx, item):
311
  """Wrapper for logic operator functions.
312

313
  """
314
  return op_fn(fn(ctx, item) for fn in sentences)
315

    
316

    
317
def _WrapUnaryOp(op_fn, inner, ctx, item):
318
  """Wrapper for unary operator functions.
319

320
  """
321
  return op_fn(inner(ctx, item))
322

    
323

    
324
def _WrapBinaryOp(op_fn, retrieval_fn, value, ctx, item):
325
  """Wrapper for binary operator functions.
326

327
  """
328
  return op_fn(retrieval_fn(ctx, item), value)
329

    
330

    
331
def _WrapNot(fn, lhs, rhs):
332
  """Negates the result of a wrapped function.
333

334
  """
335
  return not fn(lhs, rhs)
336

    
337

    
338
def _PrepareRegex(pattern):
339
  """Compiles a regular expression.
340

341
  """
342
  try:
343
    return re.compile(pattern)
344
  except re.error, err:
345
    raise errors.ParameterError("Invalid regex pattern (%s)" % err)
346

    
347

    
348
class _FilterCompilerHelper:
349
  """Converts a query filter to a callable usable for filtering.
350

351
  """
352
  # String statement has no effect, pylint: disable=W0105
353

    
354
  #: How deep filters can be nested
355
  _LEVELS_MAX = 10
356

    
357
  # Unique identifiers for operator groups
358
  (_OPTYPE_LOGIC,
359
   _OPTYPE_UNARY,
360
   _OPTYPE_BINARY) = range(1, 4)
361

    
362
  """Functions for equality checks depending on field flags.
363

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

368
  Order matters. The first item with flags will be used. Flags are checked
369
  using binary AND.
370

371
  """
372
  _EQUALITY_CHECKS = [
373
    (QFF_HOSTNAME,
374
     lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
375
                                               case_sensitive=False),
376
     None),
377
    (None, operator.eq, None),
378
    ]
379

    
380
  """Known operators
381

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

385
    - C{_OPTYPE_LOGIC}: Callable taking any number of arguments; used by
386
      L{_HandleLogicOp}
387
    - C{_OPTYPE_UNARY}: Always C{None}; details handled by L{_HandleUnaryOp}
388
    - C{_OPTYPE_BINARY}: Callable taking exactly two parameters, the left- and
389
      right-hand side of the operator, used by L{_HandleBinaryOp}
390

391
  """
392
  _OPS = {
393
    # Logic operators
394
    qlang.OP_OR: (_OPTYPE_LOGIC, compat.any),
395
    qlang.OP_AND: (_OPTYPE_LOGIC, compat.all),
396

    
397
    # Unary operators
398
    qlang.OP_NOT: (_OPTYPE_UNARY, None),
399
    qlang.OP_TRUE: (_OPTYPE_UNARY, None),
400

    
401
    # Binary operators
402
    qlang.OP_EQUAL: (_OPTYPE_BINARY, _EQUALITY_CHECKS),
403
    qlang.OP_NOT_EQUAL:
404
      (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn), valprepfn)
405
                        for (flags, fn, valprepfn) in _EQUALITY_CHECKS]),
406
    qlang.OP_REGEXP: (_OPTYPE_BINARY, [
407
      (None, lambda lhs, rhs: rhs.search(lhs), _PrepareRegex),
408
      ]),
409
    qlang.OP_CONTAINS: (_OPTYPE_BINARY, [
410
      (None, operator.contains, None),
411
      ]),
412
    }
413

    
414
  def __init__(self, fields):
415
    """Initializes this class.
416

417
    @param fields: Field definitions (return value of L{_PrepareFieldList})
418

419
    """
420
    self._fields = fields
421
    self._hints = None
422
    self._op_handler = None
423

    
424
  def __call__(self, hints, qfilter):
425
    """Converts a query filter into a callable function.
426

427
    @type hints: L{_FilterHints} or None
428
    @param hints: Callbacks doing analysis on filter
429
    @type qfilter: list
430
    @param qfilter: Filter structure
431
    @rtype: callable
432
    @return: Function receiving context and item as parameters, returning
433
             boolean as to whether item matches filter
434

435
    """
436
    self._op_handler = {
437
      self._OPTYPE_LOGIC:
438
        (self._HandleLogicOp, getattr(hints, "NoteLogicOp", None)),
439
      self._OPTYPE_UNARY:
440
        (self._HandleUnaryOp, getattr(hints, "NoteUnaryOp", None)),
441
      self._OPTYPE_BINARY:
442
        (self._HandleBinaryOp, getattr(hints, "NoteBinaryOp", None)),
443
      }
444

    
445
    try:
446
      filter_fn = self._Compile(qfilter, 0)
447
    finally:
448
      self._op_handler = None
449

    
450
    return filter_fn
451

    
452
  def _Compile(self, qfilter, level):
453
    """Inner function for converting filters.
454

455
    Calls the correct handler functions for the top-level operator. This
456
    function is called recursively (e.g. for logic operators).
457

458
    """
459
    if not (isinstance(qfilter, (list, tuple)) and qfilter):
460
      raise errors.ParameterError("Invalid filter on level %s" % level)
461

    
462
    # Limit recursion
463
    if level >= self._LEVELS_MAX:
464
      raise errors.ParameterError("Only up to %s levels are allowed (filter"
465
                                  " nested too deep)" % self._LEVELS_MAX)
466

    
467
    # Create copy to be modified
468
    operands = qfilter[:]
469
    op = operands.pop(0)
470

    
471
    try:
472
      (kind, op_data) = self._OPS[op]
473
    except KeyError:
474
      raise errors.ParameterError("Unknown operator '%s'" % op)
475

    
476
    (handler, hints_cb) = self._op_handler[kind]
477

    
478
    return handler(hints_cb, level, op, op_data, operands)
479

    
480
  def _LookupField(self, name):
481
    """Returns a field definition by name.
482

483
    """
484
    try:
485
      return self._fields[name]
486
    except KeyError:
487
      raise errors.ParameterError("Unknown field '%s'" % name)
488

    
489
  def _HandleLogicOp(self, hints_fn, level, op, op_fn, operands):
490
    """Handles logic operators.
491

492
    @type hints_fn: callable
493
    @param hints_fn: Callback doing some analysis on the filter
494
    @type level: integer
495
    @param level: Current depth
496
    @type op: string
497
    @param op: Operator
498
    @type op_fn: callable
499
    @param op_fn: Function implementing operator
500
    @type operands: list
501
    @param operands: List of operands
502

503
    """
504
    if hints_fn:
505
      hints_fn(op)
506

    
507
    return compat.partial(_WrapLogicOp, op_fn,
508
                          [self._Compile(op, level + 1) for op in operands])
509

    
510
  def _HandleUnaryOp(self, hints_fn, level, op, op_fn, operands):
511
    """Handles unary operators.
512

513
    @type hints_fn: callable
514
    @param hints_fn: Callback doing some analysis on the filter
515
    @type level: integer
516
    @param level: Current depth
517
    @type op: string
518
    @param op: Operator
519
    @type op_fn: callable
520
    @param op_fn: Function implementing operator
521
    @type operands: list
522
    @param operands: List of operands
523

524
    """
525
    assert op_fn is None
526

    
527
    if hints_fn:
528
      hints_fn(op)
529

    
530
    if len(operands) != 1:
531
      raise errors.ParameterError("Unary operator '%s' expects exactly one"
532
                                  " operand" % op)
533

    
534
    if op == qlang.OP_TRUE:
535
      (_, _, _, retrieval_fn) = self._LookupField(operands[0])
536

    
537
      op_fn = operator.truth
538
      arg = retrieval_fn
539
    elif op == qlang.OP_NOT:
540
      op_fn = operator.not_
541
      arg = self._Compile(operands[0], level + 1)
542
    else:
543
      raise errors.ProgrammerError("Can't handle operator '%s'" % op)
544

    
545
    return compat.partial(_WrapUnaryOp, op_fn, arg)
546

    
547
  def _HandleBinaryOp(self, hints_fn, level, op, op_data, operands):
548
    """Handles binary operators.
549

550
    @type hints_fn: callable
551
    @param hints_fn: Callback doing some analysis on the filter
552
    @type level: integer
553
    @param level: Current depth
554
    @type op: string
555
    @param op: Operator
556
    @param op_data: Functions implementing operators
557
    @type operands: list
558
    @param operands: List of operands
559

560
    """
561
    # Unused arguments, pylint: disable=W0613
562
    try:
563
      (name, value) = operands
564
    except (ValueError, TypeError):
565
      raise errors.ParameterError("Invalid binary operator, expected exactly"
566
                                  " two operands")
567

    
568
    (fdef, datakind, field_flags, retrieval_fn) = self._LookupField(name)
569

    
570
    assert fdef.kind != QFT_UNKNOWN
571

    
572
    # TODO: Type conversions?
573

    
574
    verify_fn = _VERIFY_FN[fdef.kind]
575
    if not verify_fn(value):
576
      raise errors.ParameterError("Unable to compare field '%s' (type '%s')"
577
                                  " with '%s', expected %s" %
578
                                  (name, fdef.kind, value.__class__.__name__,
579
                                   verify_fn))
580

    
581
    if hints_fn:
582
      hints_fn(op, datakind, name, value)
583

    
584
    for (fn_flags, fn, valprepfn) in op_data:
585
      if fn_flags is None or fn_flags & field_flags:
586
        # Prepare value if necessary (e.g. compile regular expression)
587
        if valprepfn:
588
          value = valprepfn(value)
589

    
590
        return compat.partial(_WrapBinaryOp, fn, retrieval_fn, value)
591

    
592
    raise errors.ProgrammerError("Unable to find operator implementation"
593
                                 " (op '%s', flags %s)" % (op, field_flags))
594

    
595

    
596
def _CompileFilter(fields, hints, qfilter):
597
  """Converts a query filter into a callable function.
598

599
  See L{_FilterCompilerHelper} for details.
600

601
  @rtype: callable
602

603
  """
604
  return _FilterCompilerHelper(fields)(hints, qfilter)
605

    
606

    
607
class Query:
608
  def __init__(self, fieldlist, selected, qfilter=None, namefield=None):
609
    """Initializes this class.
610

611
    The field definition is a dictionary with the field's name as a key and a
612
    tuple containing, in order, the field definition object
613
    (L{objects.QueryFieldDefinition}, the data kind to help calling code
614
    collect data and a retrieval function. The retrieval function is called
615
    with two parameters, in order, the data container and the item in container
616
    (see L{Query.Query}).
617

618
    Users of this class can call L{RequestedData} before preparing the data
619
    container to determine what data is needed.
620

621
    @type fieldlist: dictionary
622
    @param fieldlist: Field definitions
623
    @type selected: list of strings
624
    @param selected: List of selected fields
625

626
    """
627
    assert namefield is None or namefield in fieldlist
628

    
629
    self._fields = _GetQueryFields(fieldlist, selected)
630

    
631
    self._filter_fn = None
632
    self._requested_names = None
633
    self._filter_datakinds = frozenset()
634

    
635
    if qfilter is not None:
636
      # Collect requested names if wanted
637
      if namefield:
638
        hints = _FilterHints(namefield)
639
      else:
640
        hints = None
641

    
642
      # Build filter function
643
      self._filter_fn = _CompileFilter(fieldlist, hints, qfilter)
644
      if hints:
645
        self._requested_names = hints.RequestedNames()
646
        self._filter_datakinds = hints.ReferencedData()
647

    
648
    if namefield is None:
649
      self._name_fn = None
650
    else:
651
      (_, _, _, self._name_fn) = fieldlist[namefield]
652

    
653
  def RequestedNames(self):
654
    """Returns all names referenced in the filter.
655

656
    If there is no filter or operators are preventing determining the exact
657
    names, C{None} is returned.
658

659
    """
660
    return self._requested_names
661

    
662
  def RequestedData(self):
663
    """Gets requested kinds of data.
664

665
    @rtype: frozenset
666

667
    """
668
    return (self._filter_datakinds |
669
            frozenset(datakind for (_, datakind, _, _) in self._fields
670
                      if datakind is not None))
671

    
672
  def GetFields(self):
673
    """Returns the list of fields for this query.
674

675
    Includes unknown fields.
676

677
    @rtype: List of L{objects.QueryFieldDefinition}
678

679
    """
680
    return GetAllFields(self._fields)
681

    
682
  def Query(self, ctx, sort_by_name=True):
683
    """Execute a query.
684

685
    @param ctx: Data container passed to field retrieval functions, must
686
      support iteration using C{__iter__}
687
    @type sort_by_name: boolean
688
    @param sort_by_name: Whether to sort by name or keep the input data's
689
      ordering
690

691
    """
692
    sort = (self._name_fn and sort_by_name)
693

    
694
    result = []
695

    
696
    for idx, item in enumerate(ctx):
697
      if not (self._filter_fn is None or self._filter_fn(ctx, item)):
698
        continue
699

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

    
702
      # Verify result
703
      if __debug__:
704
        _VerifyResultRow(self._fields, row)
705

    
706
      if sort:
707
        (status, name) = _ProcessResult(self._name_fn(ctx, item))
708
        assert status == constants.RS_NORMAL
709
        # TODO: Are there cases where we wouldn't want to use NiceSort?
710
        result.append((utils.NiceSortKey(name), idx, row))
711
      else:
712
        result.append(row)
713

    
714
    if not sort:
715
      return result
716

    
717
    # TODO: Would "heapq" be more efficient than sorting?
718

    
719
    # Sorting in-place instead of using "sorted()"
720
    result.sort()
721

    
722
    assert not result or (len(result[0]) == 3 and len(result[-1]) == 3)
723

    
724
    return map(operator.itemgetter(2), result)
725

    
726
  def OldStyleQuery(self, ctx, sort_by_name=True):
727
    """Query with "old" query result format.
728

729
    See L{Query.Query} for arguments.
730

731
    """
732
    unknown = set(fdef.name for (fdef, _, _, _) in self._fields
733
                  if fdef.kind == QFT_UNKNOWN)
734
    if unknown:
735
      raise errors.OpPrereqError("Unknown output fields selected: %s" %
736
                                 (utils.CommaJoin(unknown), ),
737
                                 errors.ECODE_INVAL)
738

    
739
    return [[value for (_, value) in row]
740
            for row in self.Query(ctx, sort_by_name=sort_by_name)]
741

    
742

    
743
def _ProcessResult(value):
744
  """Converts result values into externally-visible ones.
745

746
  """
747
  if value is _FS_UNKNOWN:
748
    return (RS_UNKNOWN, None)
749
  elif value is _FS_NODATA:
750
    return (RS_NODATA, None)
751
  elif value is _FS_UNAVAIL:
752
    return (RS_UNAVAIL, None)
753
  elif value is _FS_OFFLINE:
754
    return (RS_OFFLINE, None)
755
  else:
756
    return (RS_NORMAL, value)
757

    
758

    
759
def _VerifyResultRow(fields, row):
760
  """Verifies the contents of a query result row.
761

762
  @type fields: list
763
  @param fields: Field definitions for result
764
  @type row: list of tuples
765
  @param row: Row data
766

767
  """
768
  assert len(row) == len(fields)
769
  errs = []
770
  for ((status, value), (fdef, _, _, _)) in zip(row, fields):
771
    if status == RS_NORMAL:
772
      if not _VERIFY_FN[fdef.kind](value):
773
        errs.append("normal field %s fails validation (value is %s)" %
774
                    (fdef.name, value))
775
    elif value is not None:
776
      errs.append("abnormal field %s has a non-None value" % fdef.name)
777
  assert not errs, ("Failed validation: %s in row %s" %
778
                    (utils.CommaJoin(errs), row))
779

    
780

    
781
def _FieldDictKey((fdef, _, flags, fn)):
782
  """Generates key for field dictionary.
783

784
  """
785
  assert fdef.name and fdef.title, "Name and title are required"
786
  assert FIELD_NAME_RE.match(fdef.name)
787
  assert TITLE_RE.match(fdef.title)
788
  assert (DOC_RE.match(fdef.doc) and len(fdef.doc.splitlines()) == 1 and
789
          fdef.doc.strip() == fdef.doc), \
790
         "Invalid description for field '%s'" % fdef.name
791
  assert callable(fn)
792
  assert (flags & ~QFF_ALL) == 0, "Unknown flags for field '%s'" % fdef.name
793

    
794
  return fdef.name
795

    
796

    
797
def _PrepareFieldList(fields, aliases):
798
  """Prepares field list for use by L{Query}.
799

800
  Converts the list to a dictionary and does some verification.
801

802
  @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data
803
      kind, retrieval function)
804
  @param fields: List of fields, see L{Query.__init__} for a better
805
      description
806
  @type aliases: list of tuples; (alias, target)
807
  @param aliases: list of tuples containing aliases; for each
808
      alias/target pair, a duplicate will be created in the field list
809
  @rtype: dict
810
  @return: Field dictionary for L{Query}
811

812
  """
813
  if __debug__:
814
    duplicates = utils.FindDuplicates(fdef.title.lower()
815
                                      for (fdef, _, _, _) in fields)
816
    assert not duplicates, "Duplicate title(s) found: %r" % duplicates
817

    
818
  result = utils.SequenceToDict(fields, key=_FieldDictKey)
819

    
820
  for alias, target in aliases:
821
    assert alias not in result, "Alias %s overrides an existing field" % alias
822
    assert target in result, "Missing target %s for alias %s" % (target, alias)
823
    (fdef, k, flags, fn) = result[target]
824
    fdef = fdef.Copy()
825
    fdef.name = alias
826
    result[alias] = (fdef, k, flags, fn)
827

    
828
  assert len(result) == len(fields) + len(aliases)
829
  assert compat.all(name == fdef.name
830
                    for (name, (fdef, _, _, _)) in result.items())
831

    
832
  return result
833

    
834

    
835
def GetQueryResponse(query, ctx, sort_by_name=True):
836
  """Prepares the response for a query.
837

838
  @type query: L{Query}
839
  @param ctx: Data container, see L{Query.Query}
840
  @type sort_by_name: boolean
841
  @param sort_by_name: Whether to sort by name or keep the input data's
842
    ordering
843

844
  """
845
  return objects.QueryResponse(data=query.Query(ctx, sort_by_name=sort_by_name),
846
                               fields=query.GetFields()).ToDict()
847

    
848

    
849
def QueryFields(fielddefs, selected):
850
  """Returns list of available fields.
851

852
  @type fielddefs: dict
853
  @param fielddefs: Field definitions
854
  @type selected: list of strings
855
  @param selected: List of selected fields
856
  @return: List of L{objects.QueryFieldDefinition}
857

858
  """
859
  if selected is None:
860
    # Client requests all fields, sort by name
861
    fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
862
                           key=operator.attrgetter("name"))
863
  else:
864
    # Keep order as requested by client
865
    fdefs = Query(fielddefs, selected).GetFields()
866

    
867
  return objects.QueryFieldsResponse(fields=fdefs).ToDict()
868

    
869

    
870
def _MakeField(name, title, kind, doc):
871
  """Wrapper for creating L{objects.QueryFieldDefinition} instances.
872

873
  @param name: Field name as a regular expression
874
  @param title: Human-readable title
875
  @param kind: Field type
876
  @param doc: Human-readable description
877

878
  """
879
  return objects.QueryFieldDefinition(name=name, title=title, kind=kind,
880
                                      doc=doc)
881

    
882

    
883
def _StaticValueInner(value, ctx, _): # pylint: disable=W0613
884
  """Returns a static value.
885

886
  """
887
  return value
888

    
889

    
890
def _StaticValue(value):
891
  """Prepares a function to return a static value.
892

893
  """
894
  return compat.partial(_StaticValueInner, value)
895

    
896

    
897
def _GetNodeRole(node, master_name):
898
  """Determine node role.
899

900
  @type node: L{objects.Node}
901
  @param node: Node object
902
  @type master_name: string
903
  @param master_name: Master node name
904

905
  """
906
  if node.name == master_name:
907
    return constants.NR_MASTER
908
  elif node.master_candidate:
909
    return constants.NR_MCANDIDATE
910
  elif node.drained:
911
    return constants.NR_DRAINED
912
  elif node.offline:
913
    return constants.NR_OFFLINE
914
  else:
915
    return constants.NR_REGULAR
916

    
917

    
918
def _GetItemAttr(attr):
919
  """Returns a field function to return an attribute of the item.
920

921
  @param attr: Attribute name
922

923
  """
924
  getter = operator.attrgetter(attr)
925
  return lambda _, item: getter(item)
926

    
927

    
928
def _GetNDParam(name):
929
  """Return a field function to return an ND parameter out of the context.
930

931
  """
932
  def _helper(ctx, _):
933
    if ctx.ndparams is None:
934
      return _FS_UNAVAIL
935
    else:
936
      return ctx.ndparams.get(name, None)
937
  return _helper
938

    
939

    
940
def _BuildNDFields(is_group):
941
  """Builds all the ndparam fields.
942

943
  @param is_group: whether this is called at group or node level
944

945
  """
946
  if is_group:
947
    field_kind = GQ_CONFIG
948
  else:
949
    field_kind = NQ_GROUP
950
  return [(_MakeField("ndp/%s" % name, NDP_TITLE.get(name, "ndp/%s" % name),
951
                      _VTToQFT[kind], "The \"%s\" node parameter" % name),
952
           field_kind, 0, _GetNDParam(name))
953
          for name, kind in constants.NDS_PARAMETER_TYPES.items()]
954

    
955

    
956
def _ConvWrapInner(convert, fn, ctx, item):
957
  """Wrapper for converting values.
958

959
  @param convert: Conversion function receiving value as single parameter
960
  @param fn: Retrieval function
961

962
  """
963
  value = fn(ctx, item)
964

    
965
  # Is the value an abnormal status?
966
  if compat.any(value is fs for fs in _FS_ALL):
967
    # Return right away
968
    return value
969

    
970
  # TODO: Should conversion function also receive context, item or both?
971
  return convert(value)
972

    
973

    
974
def _ConvWrap(convert, fn):
975
  """Convenience wrapper for L{_ConvWrapInner}.
976

977
  @param convert: Conversion function receiving value as single parameter
978
  @param fn: Retrieval function
979

980
  """
981
  return compat.partial(_ConvWrapInner, convert, fn)
982

    
983

    
984
def _GetItemTimestamp(getter):
985
  """Returns function for getting timestamp of item.
986

987
  @type getter: callable
988
  @param getter: Function to retrieve timestamp attribute
989

990
  """
991
  def fn(_, item):
992
    """Returns a timestamp of item.
993

994
    """
995
    timestamp = getter(item)
996
    if timestamp is None:
997
      # Old configs might not have all timestamps
998
      return _FS_UNAVAIL
999
    else:
1000
      return timestamp
1001

    
1002
  return fn
1003

    
1004

    
1005
def _GetItemTimestampFields(datatype):
1006
  """Returns common timestamp fields.
1007

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

1010
  """
1011
  return [
1012
    (_MakeField("ctime", "CTime", QFT_TIMESTAMP, "Creation timestamp"),
1013
     datatype, 0, _GetItemTimestamp(operator.attrgetter("ctime"))),
1014
    (_MakeField("mtime", "MTime", QFT_TIMESTAMP, "Modification timestamp"),
1015
     datatype, 0, _GetItemTimestamp(operator.attrgetter("mtime"))),
1016
    ]
1017

    
1018

    
1019
class NodeQueryData:
1020
  """Data container for node data queries.
1021

1022
  """
1023
  def __init__(self, nodes, live_data, master_name, node_to_primary,
1024
               node_to_secondary, groups, oob_support, cluster):
1025
    """Initializes this class.
1026

1027
    """
1028
    self.nodes = nodes
1029
    self.live_data = live_data
1030
    self.master_name = master_name
1031
    self.node_to_primary = node_to_primary
1032
    self.node_to_secondary = node_to_secondary
1033
    self.groups = groups
1034
    self.oob_support = oob_support
1035
    self.cluster = cluster
1036

    
1037
    # Used for individual rows
1038
    self.curlive_data = None
1039
    self.ndparams = None
1040

    
1041
  def __iter__(self):
1042
    """Iterate over all nodes.
1043

1044
    This function has side-effects and only one instance of the resulting
1045
    generator should be used at a time.
1046

1047
    """
1048
    for node in self.nodes:
1049
      group = self.groups.get(node.group, None)
1050
      if group is None:
1051
        self.ndparams = None
1052
      else:
1053
        self.ndparams = self.cluster.FillND(node, group)
1054
      if self.live_data:
1055
        self.curlive_data = self.live_data.get(node.name, None)
1056
      else:
1057
        self.curlive_data = None
1058
      yield node
1059

    
1060

    
1061
#: Fields that are direct attributes of an L{objects.Node} object
1062
_NODE_SIMPLE_FIELDS = {
1063
  "drained": ("Drained", QFT_BOOL, 0, "Whether node is drained"),
1064
  "master_candidate": ("MasterC", QFT_BOOL, 0,
1065
                       "Whether node is a master candidate"),
1066
  "master_capable": ("MasterCapable", QFT_BOOL, 0,
1067
                     "Whether node can become a master candidate"),
1068
  "name": ("Node", QFT_TEXT, QFF_HOSTNAME, "Node name"),
1069
  "offline": ("Offline", QFT_BOOL, 0, "Whether node is marked offline"),
1070
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Node"),
1071
  "uuid": ("UUID", QFT_TEXT, 0, "Node UUID"),
1072
  "vm_capable": ("VMCapable", QFT_BOOL, 0, "Whether node can host instances"),
1073
  }
1074

    
1075

    
1076
#: Fields requiring talking to the node
1077
# Note that none of these are available for non-vm_capable nodes
1078
_NODE_LIVE_FIELDS = {
1079
  "bootid": ("BootID", QFT_TEXT, "bootid",
1080
             "Random UUID renewed for each system reboot, can be used"
1081
             " for detecting reboots by tracking changes"),
1082
  "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes",
1083
             "Number of NUMA domains on node (if exported by hypervisor)"),
1084
  "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets",
1085
               "Number of physical CPU sockets (if exported by hypervisor)"),
1086
  "ctotal": ("CTotal", QFT_NUMBER, "cpu_total", "Number of logical processors"),
1087
  "dfree": ("DFree", QFT_UNIT, "vg_free",
1088
            "Available disk space in volume group"),
1089
  "dtotal": ("DTotal", QFT_UNIT, "vg_size",
1090
             "Total disk space in volume group used for instance disk"
1091
             " allocation"),
1092
  "mfree": ("MFree", QFT_UNIT, "memory_free",
1093
            "Memory available for instance allocations"),
1094
  "mnode": ("MNode", QFT_UNIT, "memory_dom0",
1095
            "Amount of memory used by node (dom0 for Xen)"),
1096
  "mtotal": ("MTotal", QFT_UNIT, "memory_total",
1097
             "Total amount of memory of physical machine"),
1098
  }
1099

    
1100

    
1101
def _GetGroup(cb):
1102
  """Build function for calling another function with an node group.
1103

1104
  @param cb: The callback to be called with the nodegroup
1105

1106
  """
1107
  def fn(ctx, node):
1108
    """Get group data for a node.
1109

1110
    @type ctx: L{NodeQueryData}
1111
    @type inst: L{objects.Node}
1112
    @param inst: Node object
1113

1114
    """
1115
    ng = ctx.groups.get(node.group, None)
1116
    if ng is None:
1117
      # Nodes always have a group, or the configuration is corrupt
1118
      return _FS_UNAVAIL
1119

    
1120
    return cb(ctx, node, ng)
1121

    
1122
  return fn
1123

    
1124

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

1128
  @type ctx: L{NodeQueryData}
1129
  @type node: L{objects.Node}
1130
  @param node: Node object
1131
  @type ng: L{objects.NodeGroup}
1132
  @param ng: The node group this node belongs to
1133

1134
  """
1135
  return ng.name
1136

    
1137

    
1138
def _GetNodePower(ctx, node):
1139
  """Returns the node powered state
1140

1141
  @type ctx: L{NodeQueryData}
1142
  @type node: L{objects.Node}
1143
  @param node: Node object
1144

1145
  """
1146
  if ctx.oob_support[node.name]:
1147
    return node.powered
1148

    
1149
  return _FS_UNAVAIL
1150

    
1151

    
1152
def _GetNdParams(ctx, node, ng):
1153
  """Returns the ndparams for this node.
1154

1155
  @type ctx: L{NodeQueryData}
1156
  @type node: L{objects.Node}
1157
  @param node: Node object
1158
  @type ng: L{objects.NodeGroup}
1159
  @param ng: The node group this node belongs to
1160

1161
  """
1162
  return ctx.cluster.SimpleFillND(ng.FillND(node))
1163

    
1164

    
1165
def _GetLiveNodeField(field, kind, ctx, node):
1166
  """Gets the value of a "live" field from L{NodeQueryData}.
1167

1168
  @param field: Live field name
1169
  @param kind: Data kind, one of L{constants.QFT_ALL}
1170
  @type ctx: L{NodeQueryData}
1171
  @type node: L{objects.Node}
1172
  @param node: Node object
1173

1174
  """
1175
  if node.offline:
1176
    return _FS_OFFLINE
1177

    
1178
  if not node.vm_capable:
1179
    return _FS_UNAVAIL
1180

    
1181
  if not ctx.curlive_data:
1182
    return _FS_NODATA
1183

    
1184
  try:
1185
    value = ctx.curlive_data[field]
1186
  except KeyError:
1187
    return _FS_UNAVAIL
1188

    
1189
  if kind == QFT_TEXT:
1190
    return value
1191

    
1192
  assert kind in (QFT_NUMBER, QFT_UNIT)
1193

    
1194
  # Try to convert into number
1195
  try:
1196
    return int(value)
1197
  except (ValueError, TypeError):
1198
    logging.exception("Failed to convert node field '%s' (value %r) to int",
1199
                      value, field)
1200
    return _FS_UNAVAIL
1201

    
1202

    
1203
def _GetNodeHvState(_, node):
1204
  """Converts node's hypervisor state for query result.
1205

1206
  """
1207
  hv_state = node.hv_state
1208

    
1209
  if hv_state is None:
1210
    return _FS_UNAVAIL
1211

    
1212
  return dict((name, value.ToDict()) for (name, value) in hv_state.items())
1213

    
1214

    
1215
def _GetNodeDiskState(_, node):
1216
  """Converts node's disk state for query result.
1217

1218
  """
1219
  disk_state = node.disk_state
1220

    
1221
  if disk_state is None:
1222
    return _FS_UNAVAIL
1223

    
1224
  return dict((disk_kind, dict((name, value.ToDict())
1225
                               for (name, value) in kind_state.items()))
1226
              for (disk_kind, kind_state) in disk_state.items())
1227

    
1228

    
1229
def _BuildNodeFields():
1230
  """Builds list of fields for node queries.
1231

1232
  """
1233
  fields = [
1234
    (_MakeField("pip", "PrimaryIP", QFT_TEXT, "Primary IP address"),
1235
     NQ_CONFIG, 0, _GetItemAttr("primary_ip")),
1236
    (_MakeField("sip", "SecondaryIP", QFT_TEXT, "Secondary IP address"),
1237
     NQ_CONFIG, 0, _GetItemAttr("secondary_ip")),
1238
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0,
1239
     lambda ctx, node: list(node.GetTags())),
1240
    (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"),
1241
     NQ_CONFIG, 0, lambda ctx, node: node.name == ctx.master_name),
1242
    (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0,
1243
     _GetGroup(_GetNodeGroup)),
1244
    (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"),
1245
     NQ_CONFIG, 0, _GetItemAttr("group")),
1246
    (_MakeField("powered", "Powered", QFT_BOOL,
1247
                "Whether node is thought to be powered on"),
1248
     NQ_OOB, 0, _GetNodePower),
1249
    (_MakeField("ndparams", "NodeParameters", QFT_OTHER,
1250
                "Merged node parameters"),
1251
     NQ_GROUP, 0, _GetGroup(_GetNdParams)),
1252
    (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER,
1253
                "Custom node parameters"),
1254
      NQ_GROUP, 0, _GetItemAttr("ndparams")),
1255
    (_MakeField("hv_state", "HypervisorState", QFT_OTHER, "Hypervisor state"),
1256
     NQ_CONFIG, 0, _GetNodeHvState),
1257
    (_MakeField("disk_state", "DiskState", QFT_OTHER, "Disk state"),
1258
     NQ_CONFIG, 0, _GetNodeDiskState),
1259
    ]
1260

    
1261
  fields.extend(_BuildNDFields(False))
1262

    
1263
  # Node role
1264
  role_values = (constants.NR_MASTER, constants.NR_MCANDIDATE,
1265
                 constants.NR_REGULAR, constants.NR_DRAINED,
1266
                 constants.NR_OFFLINE)
1267
  role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate,"
1268
              " \"%s\" for regular, \"%s\" for a drained, \"%s\" for offline" %
1269
              role_values)
1270
  fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
1271
                 lambda ctx, node: _GetNodeRole(node, ctx.master_name)))
1272
  assert set(role_values) == constants.NR_ALL
1273

    
1274
  def _GetLength(getter):
1275
    return lambda ctx, node: len(getter(ctx)[node.name])
1276

    
1277
  def _GetList(getter):
1278
    return lambda ctx, node: list(getter(ctx)[node.name])
1279

    
1280
  # Add fields operating on instance lists
1281
  for prefix, titleprefix, docword, getter in \
1282
      [("p", "Pri", "primary", operator.attrgetter("node_to_primary")),
1283
       ("s", "Sec", "secondary", operator.attrgetter("node_to_secondary"))]:
1284
    # TODO: Allow filterting by hostname in list
1285
    fields.extend([
1286
      (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER,
1287
                  "Number of instances with this node as %s" % docword),
1288
       NQ_INST, 0, _GetLength(getter)),
1289
      (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
1290
                  QFT_OTHER,
1291
                  "List of instances with this node as %s" % docword),
1292
       NQ_INST, 0, _GetList(getter)),
1293
      ])
1294

    
1295
  # Add simple fields
1296
  fields.extend([
1297
    (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1298
    for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()
1299
    ])
1300

    
1301
  # Add fields requiring live data
1302
  fields.extend([
1303
    (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1304
     compat.partial(_GetLiveNodeField, nfield, kind))
1305
    for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()
1306
    ])
1307

    
1308
  # Add timestamps
1309
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1310

    
1311
  return _PrepareFieldList(fields, [])
1312

    
1313

    
1314
class InstanceQueryData:
1315
  """Data container for instance data queries.
1316

1317
  """
1318
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
1319
               live_data, wrongnode_inst, console, nodes, groups):
1320
    """Initializes this class.
1321

1322
    @param instances: List of instance objects
1323
    @param cluster: Cluster object
1324
    @type disk_usage: dict; instance name as key
1325
    @param disk_usage: Per-instance disk usage
1326
    @type offline_nodes: list of strings
1327
    @param offline_nodes: List of offline nodes
1328
    @type bad_nodes: list of strings
1329
    @param bad_nodes: List of faulty nodes
1330
    @type live_data: dict; instance name as key
1331
    @param live_data: Per-instance live data
1332
    @type wrongnode_inst: set
1333
    @param wrongnode_inst: Set of instances running on wrong node(s)
1334
    @type console: dict; instance name as key
1335
    @param console: Per-instance console information
1336
    @type nodes: dict; node name as key
1337
    @param nodes: Node objects
1338

1339
    """
1340
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
1341
           "Offline nodes not included in bad nodes"
1342
    assert not (set(live_data.keys()) & set(bad_nodes)), \
1343
           "Found live data for bad or offline nodes"
1344

    
1345
    self.instances = instances
1346
    self.cluster = cluster
1347
    self.disk_usage = disk_usage
1348
    self.offline_nodes = offline_nodes
1349
    self.bad_nodes = bad_nodes
1350
    self.live_data = live_data
1351
    self.wrongnode_inst = wrongnode_inst
1352
    self.console = console
1353
    self.nodes = nodes
1354
    self.groups = groups
1355

    
1356
    # Used for individual rows
1357
    self.inst_hvparams = None
1358
    self.inst_beparams = None
1359
    self.inst_osparams = None
1360
    self.inst_nicparams = None
1361

    
1362
  def __iter__(self):
1363
    """Iterate over all instances.
1364

1365
    This function has side-effects and only one instance of the resulting
1366
    generator should be used at a time.
1367

1368
    """
1369
    for inst in self.instances:
1370
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1371
      self.inst_beparams = self.cluster.FillBE(inst)
1372
      self.inst_osparams = self.cluster.SimpleFillOS(inst.os, inst.osparams)
1373
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1374
                             for nic in inst.nics]
1375

    
1376
      yield inst
1377

    
1378

    
1379
def _GetInstOperState(ctx, inst):
1380
  """Get instance's operational status.
1381

1382
  @type ctx: L{InstanceQueryData}
1383
  @type inst: L{objects.Instance}
1384
  @param inst: Instance object
1385

1386
  """
1387
  # Can't use RS_OFFLINE here as it would describe the instance to
1388
  # be offline when we actually don't know due to missing data
1389
  if inst.primary_node in ctx.bad_nodes:
1390
    return _FS_NODATA
1391
  else:
1392
    return bool(ctx.live_data.get(inst.name))
1393

    
1394

    
1395
def _GetInstLiveData(name):
1396
  """Build function for retrieving live data.
1397

1398
  @type name: string
1399
  @param name: Live data field name
1400

1401
  """
1402
  def fn(ctx, inst):
1403
    """Get live data for an instance.
1404

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

1409
    """
1410
    if (inst.primary_node in ctx.bad_nodes or
1411
        inst.primary_node in ctx.offline_nodes):
1412
      # Can't use RS_OFFLINE here as it would describe the instance to be
1413
      # offline when we actually don't know due to missing data
1414
      return _FS_NODATA
1415

    
1416
    if inst.name in ctx.live_data:
1417
      data = ctx.live_data[inst.name]
1418
      if name in data:
1419
        return data[name]
1420

    
1421
    return _FS_UNAVAIL
1422

    
1423
  return fn
1424

    
1425

    
1426
def _GetInstStatus(ctx, inst):
1427
  """Get instance status.
1428

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

1433
  """
1434
  if inst.primary_node in ctx.offline_nodes:
1435
    return constants.INSTST_NODEOFFLINE
1436

    
1437
  if inst.primary_node in ctx.bad_nodes:
1438
    return constants.INSTST_NODEDOWN
1439

    
1440
  if bool(ctx.live_data.get(inst.name)):
1441
    if inst.name in ctx.wrongnode_inst:
1442
      return constants.INSTST_WRONGNODE
1443
    elif inst.admin_state == constants.ADMINST_UP:
1444
      return constants.INSTST_RUNNING
1445
    else:
1446
      return constants.INSTST_ERRORUP
1447

    
1448
  if inst.admin_state == constants.ADMINST_UP:
1449
    return constants.INSTST_ERRORDOWN
1450
  elif inst.admin_state == constants.ADMINST_DOWN:
1451
    return constants.INSTST_ADMINDOWN
1452

    
1453
  return constants.INSTST_ADMINOFFLINE
1454

    
1455

    
1456
def _GetInstDiskSize(index):
1457
  """Build function for retrieving disk size.
1458

1459
  @type index: int
1460
  @param index: Disk index
1461

1462
  """
1463
  def fn(_, inst):
1464
    """Get size of a disk.
1465

1466
    @type inst: L{objects.Instance}
1467
    @param inst: Instance object
1468

1469
    """
1470
    try:
1471
      return inst.disks[index].size
1472
    except IndexError:
1473
      return _FS_UNAVAIL
1474

    
1475
  return fn
1476

    
1477

    
1478
def _GetInstNic(index, cb):
1479
  """Build function for calling another function with an instance NIC.
1480

1481
  @type index: int
1482
  @param index: NIC index
1483
  @type cb: callable
1484
  @param cb: Callback
1485

1486
  """
1487
  def fn(ctx, inst):
1488
    """Call helper function with instance NIC.
1489

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

1494
    """
1495
    try:
1496
      nic = inst.nics[index]
1497
    except IndexError:
1498
      return _FS_UNAVAIL
1499

    
1500
    return cb(ctx, index, nic)
1501

    
1502
  return fn
1503

    
1504

    
1505
def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613
1506
  """Get a NIC's IP address.
1507

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

1512
  """
1513
  if nic.ip is None:
1514
    return _FS_UNAVAIL
1515
  else:
1516
    return nic.ip
1517

    
1518

    
1519
def _GetInstNicBridge(ctx, index, _):
1520
  """Get a NIC's bridge.
1521

1522
  @type ctx: L{InstanceQueryData}
1523
  @type index: int
1524
  @param index: NIC index
1525

1526
  """
1527
  assert len(ctx.inst_nicparams) >= index
1528

    
1529
  nicparams = ctx.inst_nicparams[index]
1530

    
1531
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1532
    return nicparams[constants.NIC_LINK]
1533
  else:
1534
    return _FS_UNAVAIL
1535

    
1536

    
1537
def _GetInstAllNicBridges(ctx, inst):
1538
  """Get all network bridges for an instance.
1539

1540
  @type ctx: L{InstanceQueryData}
1541
  @type inst: L{objects.Instance}
1542
  @param inst: Instance object
1543

1544
  """
1545
  assert len(ctx.inst_nicparams) == len(inst.nics)
1546

    
1547
  result = []
1548

    
1549
  for nicp in ctx.inst_nicparams:
1550
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1551
      result.append(nicp[constants.NIC_LINK])
1552
    else:
1553
      result.append(None)
1554

    
1555
  assert len(result) == len(inst.nics)
1556

    
1557
  return result
1558

    
1559

    
1560
def _GetInstNicParam(name):
1561
  """Build function for retrieving a NIC parameter.
1562

1563
  @type name: string
1564
  @param name: Parameter name
1565

1566
  """
1567
  def fn(ctx, index, _):
1568
    """Get a NIC's bridge.
1569

1570
    @type ctx: L{InstanceQueryData}
1571
    @type inst: L{objects.Instance}
1572
    @param inst: Instance object
1573
    @type nic: L{objects.NIC}
1574
    @param nic: NIC object
1575

1576
    """
1577
    assert len(ctx.inst_nicparams) >= index
1578
    return ctx.inst_nicparams[index][name]
1579

    
1580
  return fn
1581

    
1582

    
1583
def _GetInstanceNetworkFields():
1584
  """Get instance fields involving network interfaces.
1585

1586
  @return: Tuple containing list of field definitions used as input for
1587
    L{_PrepareFieldList} and a list of aliases
1588

1589
  """
1590
  nic_mac_fn = lambda ctx, _, nic: nic.mac
1591
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1592
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1593

    
1594
  fields = [
1595
    # All NICs
1596
    (_MakeField("nic.count", "NICs", QFT_NUMBER,
1597
                "Number of network interfaces"),
1598
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.nics)),
1599
    (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER,
1600
                "List containing each network interface's MAC address"),
1601
     IQ_CONFIG, 0, lambda ctx, inst: [nic.mac for nic in inst.nics]),
1602
    (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
1603
                "List containing each network interface's IP address"),
1604
     IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
1605
    (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
1606
                "List containing each network interface's mode"), IQ_CONFIG, 0,
1607
     lambda ctx, inst: [nicp[constants.NIC_MODE]
1608
                        for nicp in ctx.inst_nicparams]),
1609
    (_MakeField("nic.links", "NIC_links", QFT_OTHER,
1610
                "List containing each network interface's link"), IQ_CONFIG, 0,
1611
     lambda ctx, inst: [nicp[constants.NIC_LINK]
1612
                        for nicp in ctx.inst_nicparams]),
1613
    (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
1614
                "List containing each network interface's bridge"),
1615
     IQ_CONFIG, 0, _GetInstAllNicBridges),
1616
    ]
1617

    
1618
  # NICs by number
1619
  for i in range(constants.MAX_NICS):
1620
    numtext = utils.FormatOrdinal(i + 1)
1621
    fields.extend([
1622
      (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT,
1623
                  "IP address of %s network interface" % numtext),
1624
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)),
1625
      (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
1626
                  "MAC address of %s network interface" % numtext),
1627
       IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
1628
      (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
1629
                  "Mode of %s network interface" % numtext),
1630
       IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
1631
      (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT,
1632
                  "Link of %s network interface" % numtext),
1633
       IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)),
1634
      (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
1635
                  "Bridge of %s network interface" % numtext),
1636
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
1637
      ])
1638

    
1639
  aliases = [
1640
    # Legacy fields for first NIC
1641
    ("ip", "nic.ip/0"),
1642
    ("mac", "nic.mac/0"),
1643
    ("bridge", "nic.bridge/0"),
1644
    ("nic_mode", "nic.mode/0"),
1645
    ("nic_link", "nic.link/0"),
1646
    ]
1647

    
1648
  return (fields, aliases)
1649

    
1650

    
1651
def _GetInstDiskUsage(ctx, inst):
1652
  """Get disk usage for an instance.
1653

1654
  @type ctx: L{InstanceQueryData}
1655
  @type inst: L{objects.Instance}
1656
  @param inst: Instance object
1657

1658
  """
1659
  usage = ctx.disk_usage[inst.name]
1660

    
1661
  if usage is None:
1662
    usage = 0
1663

    
1664
  return usage
1665

    
1666

    
1667
def _GetInstanceConsole(ctx, inst):
1668
  """Get console information for instance.
1669

1670
  @type ctx: L{InstanceQueryData}
1671
  @type inst: L{objects.Instance}
1672
  @param inst: Instance object
1673

1674
  """
1675
  consinfo = ctx.console[inst.name]
1676

    
1677
  if consinfo is None:
1678
    return _FS_UNAVAIL
1679

    
1680
  return consinfo
1681

    
1682

    
1683
def _GetInstanceDiskFields():
1684
  """Get instance fields involving disks.
1685

1686
  @return: List of field definitions used as input for L{_PrepareFieldList}
1687

1688
  """
1689
  fields = [
1690
    (_MakeField("disk_usage", "DiskUsage", QFT_UNIT,
1691
                "Total disk space used by instance on each of its nodes;"
1692
                " this is not the disk size visible to the instance, but"
1693
                " the usage on the node"),
1694
     IQ_DISKUSAGE, 0, _GetInstDiskUsage),
1695
    (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"),
1696
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
1697
    (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
1698
     IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
1699
    ]
1700

    
1701
  # Disks by number
1702
  fields.extend([
1703
    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1704
                "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
1705
     IQ_CONFIG, 0, _GetInstDiskSize(i))
1706
    for i in range(constants.MAX_DISKS)
1707
    ])
1708

    
1709
  return fields
1710

    
1711

    
1712
def _GetInstanceParameterFields():
1713
  """Get instance fields involving parameters.
1714

1715
  @return: List of field definitions used as input for L{_PrepareFieldList}
1716

1717
  """
1718
  # TODO: Consider moving titles closer to constants
1719
  be_title = {
1720
    constants.BE_AUTO_BALANCE: "Auto_balance",
1721
    constants.BE_MAXMEM: "ConfigMaxMem",
1722
    constants.BE_MINMEM: "ConfigMinMem",
1723
    constants.BE_VCPUS: "ConfigVCPUs",
1724
    }
1725

    
1726
  hv_title = {
1727
    constants.HV_ACPI: "ACPI",
1728
    constants.HV_BOOT_ORDER: "Boot_order",
1729
    constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1730
    constants.HV_DISK_TYPE: "Disk_type",
1731
    constants.HV_INITRD_PATH: "Initrd_path",
1732
    constants.HV_KERNEL_PATH: "Kernel_path",
1733
    constants.HV_NIC_TYPE: "NIC_type",
1734
    constants.HV_PAE: "PAE",
1735
    constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1736
    }
1737

    
1738
  fields = [
1739
    # Filled parameters
1740
    (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
1741
                "Hypervisor parameters (merged)"),
1742
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
1743
    (_MakeField("beparams", "BackendParameters", QFT_OTHER,
1744
                "Backend parameters (merged)"),
1745
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
1746
    (_MakeField("osparams", "OpSysParameters", QFT_OTHER,
1747
                "Operating system parameters (merged)"),
1748
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_osparams),
1749

    
1750
    # Unfilled parameters
1751
    (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
1752
                "Custom hypervisor parameters"),
1753
     IQ_CONFIG, 0, _GetItemAttr("hvparams")),
1754
    (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
1755
                "Custom backend parameters",),
1756
     IQ_CONFIG, 0, _GetItemAttr("beparams")),
1757
    (_MakeField("custom_osparams", "CustomOpSysParameters", QFT_OTHER,
1758
                "Custom operating system parameters",),
1759
     IQ_CONFIG, 0, _GetItemAttr("osparams")),
1760
    (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
1761
                "Custom network interface parameters"),
1762
     IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1763
    ]
1764

    
1765
  # HV params
1766
  def _GetInstHvParam(name):
1767
    return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1768

    
1769
  fields.extend([
1770
    (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1771
                _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1772
     IQ_CONFIG, 0, _GetInstHvParam(name))
1773
    for name, kind in constants.HVS_PARAMETER_TYPES.items()
1774
    if name not in constants.HVC_GLOBALS
1775
    ])
1776

    
1777
  # BE params
1778
  def _GetInstBeParam(name):
1779
    return lambda ctx, _: ctx.inst_beparams.get(name, None)
1780

    
1781
  fields.extend([
1782
    (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1783
                _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1784
     IQ_CONFIG, 0, _GetInstBeParam(name))
1785
    for name, kind in constants.BES_PARAMETER_TYPES.items()
1786
    ])
1787

    
1788
  return fields
1789

    
1790

    
1791
_INST_SIMPLE_FIELDS = {
1792
  "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"),
1793
  "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"),
1794
  "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"),
1795
  # Depending on the hypervisor, the port can be None
1796
  "network_port": ("Network_port", QFT_OTHER, 0,
1797
                   "Instance network port if available (e.g. for VNC console)"),
1798
  "os": ("OS", QFT_TEXT, 0, "Operating system"),
1799
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"),
1800
  "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"),
1801
  }
1802

    
1803

    
1804
def _GetInstNodeGroup(ctx, default, node_name):
1805
  """Gets group UUID of an instance node.
1806

1807
  @type ctx: L{InstanceQueryData}
1808
  @param default: Default value
1809
  @type node_name: string
1810
  @param node_name: Node name
1811

1812
  """
1813
  try:
1814
    node = ctx.nodes[node_name]
1815
  except KeyError:
1816
    return default
1817
  else:
1818
    return node.group
1819

    
1820

    
1821
def _GetInstNodeGroupName(ctx, default, node_name):
1822
  """Gets group name of an instance node.
1823

1824
  @type ctx: L{InstanceQueryData}
1825
  @param default: Default value
1826
  @type node_name: string
1827
  @param node_name: Node name
1828

1829
  """
1830
  try:
1831
    node = ctx.nodes[node_name]
1832
  except KeyError:
1833
    return default
1834

    
1835
  try:
1836
    group = ctx.groups[node.group]
1837
  except KeyError:
1838
    return default
1839

    
1840
  return group.name
1841

    
1842

    
1843
def _BuildInstanceFields():
1844
  """Builds list of fields for instance queries.
1845

1846
  """
1847
  fields = [
1848
    (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
1849
     IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")),
1850
    (_MakeField("pnode.group", "PrimaryNodeGroup", QFT_TEXT,
1851
                "Primary node's group"),
1852
     IQ_NODES, 0,
1853
     lambda ctx, inst: _GetInstNodeGroupName(ctx, _FS_UNAVAIL,
1854
                                             inst.primary_node)),
1855
    (_MakeField("pnode.group.uuid", "PrimaryNodeGroupUUID", QFT_TEXT,
1856
                "Primary node's group UUID"),
1857
     IQ_NODES, 0,
1858
     lambda ctx, inst: _GetInstNodeGroup(ctx, _FS_UNAVAIL, inst.primary_node)),
1859
    # TODO: Allow filtering by secondary node as hostname
1860
    (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
1861
                "Secondary nodes; usually this will just be one node"),
1862
     IQ_CONFIG, 0, lambda ctx, inst: list(inst.secondary_nodes)),
1863
    (_MakeField("snodes.group", "SecondaryNodesGroups", QFT_OTHER,
1864
                "Node groups of secondary nodes"),
1865
     IQ_NODES, 0,
1866
     lambda ctx, inst: map(compat.partial(_GetInstNodeGroupName, ctx, None),
1867
                           inst.secondary_nodes)),
1868
    (_MakeField("snodes.group.uuid", "SecondaryNodesGroupsUUID", QFT_OTHER,
1869
                "Node group UUIDs of secondary nodes"),
1870
     IQ_NODES, 0,
1871
     lambda ctx, inst: map(compat.partial(_GetInstNodeGroup, ctx, None),
1872
                           inst.secondary_nodes)),
1873
    (_MakeField("admin_state", "InstanceState", QFT_TEXT,
1874
                "Desired state of instance"),
1875
     IQ_CONFIG, 0, _GetItemAttr("admin_state")),
1876
    (_MakeField("admin_up", "Autostart", QFT_BOOL,
1877
                "Desired state of instance"),
1878
     IQ_CONFIG, 0, lambda ctx, inst: inst.admin_state == constants.ADMINST_UP),
1879
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
1880
     lambda ctx, inst: list(inst.GetTags())),
1881
    (_MakeField("console", "Console", QFT_OTHER,
1882
                "Instance console information"), IQ_CONSOLE, 0,
1883
     _GetInstanceConsole),
1884
    ]
1885

    
1886
  # Add simple fields
1887
  fields.extend([
1888
    (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1889
    for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()
1890
    ])
1891

    
1892
  # Fields requiring talking to the node
1893
  fields.extend([
1894
    (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
1895
     IQ_LIVE, 0, _GetInstOperState),
1896
    (_MakeField("oper_ram", "Memory", QFT_UNIT,
1897
                "Actual memory usage as seen by hypervisor"),
1898
     IQ_LIVE, 0, _GetInstLiveData("memory")),
1899
    (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
1900
                "Actual number of VCPUs as seen by hypervisor"),
1901
     IQ_LIVE, 0, _GetInstLiveData("vcpus")),
1902
    ])
1903

    
1904
  # Status field
1905
  status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
1906
                   constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
1907
                   constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
1908
                   constants.INSTST_NODEOFFLINE, constants.INSTST_ADMINOFFLINE)
1909
  status_doc = ("Instance status; \"%s\" if instance is set to be running"
1910
                " and actually is, \"%s\" if instance is stopped and"
1911
                " is not running, \"%s\" if instance running, but not on its"
1912
                " designated primary node, \"%s\" if instance should be"
1913
                " stopped, but is actually running, \"%s\" if instance should"
1914
                " run, but doesn't, \"%s\" if instance's primary node is down,"
1915
                " \"%s\" if instance's primary node is marked offline,"
1916
                " \"%s\" if instance is offline and does not use dynamic"
1917
                " resources" % status_values)
1918
  fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
1919
                 IQ_LIVE, 0, _GetInstStatus))
1920
  assert set(status_values) == constants.INSTST_ALL, \
1921
         "Status documentation mismatch"
1922

    
1923
  (network_fields, network_aliases) = _GetInstanceNetworkFields()
1924

    
1925
  fields.extend(network_fields)
1926
  fields.extend(_GetInstanceParameterFields())
1927
  fields.extend(_GetInstanceDiskFields())
1928
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1929

    
1930
  aliases = [
1931
    ("vcpus", "be/vcpus"),
1932
    ("be/memory", "be/maxmem"),
1933
    ("sda_size", "disk.size/0"),
1934
    ("sdb_size", "disk.size/1"),
1935
    ] + network_aliases
1936

    
1937
  return _PrepareFieldList(fields, aliases)
1938

    
1939

    
1940
class LockQueryData:
1941
  """Data container for lock data queries.
1942

1943
  """
1944
  def __init__(self, lockdata):
1945
    """Initializes this class.
1946

1947
    """
1948
    self.lockdata = lockdata
1949

    
1950
  def __iter__(self):
1951
    """Iterate over all locks.
1952

1953
    """
1954
    return iter(self.lockdata)
1955

    
1956

    
1957
def _GetLockOwners(_, data):
1958
  """Returns a sorted list of a lock's current owners.
1959

1960
  """
1961
  (_, _, owners, _) = data
1962

    
1963
  if owners:
1964
    owners = utils.NiceSort(owners)
1965

    
1966
  return owners
1967

    
1968

    
1969
def _GetLockPending(_, data):
1970
  """Returns a sorted list of a lock's pending acquires.
1971

1972
  """
1973
  (_, _, _, pending) = data
1974

    
1975
  if pending:
1976
    pending = [(mode, utils.NiceSort(names))
1977
               for (mode, names) in pending]
1978

    
1979
  return pending
1980

    
1981

    
1982
def _BuildLockFields():
1983
  """Builds list of fields for lock queries.
1984

1985
  """
1986
  return _PrepareFieldList([
1987
    # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
1988
    (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
1989
     lambda ctx, (name, mode, owners, pending): name),
1990
    (_MakeField("mode", "Mode", QFT_OTHER,
1991
                "Mode in which the lock is currently acquired"
1992
                " (exclusive or shared)"),
1993
     LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
1994
    (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
1995
     LQ_OWNER, 0, _GetLockOwners),
1996
    (_MakeField("pending", "Pending", QFT_OTHER,
1997
                "Threads waiting for the lock"),
1998
     LQ_PENDING, 0, _GetLockPending),
1999
    ], [])
2000

    
2001

    
2002
class GroupQueryData:
2003
  """Data container for node group data queries.
2004

2005
  """
2006
  def __init__(self, cluster, groups, group_to_nodes, group_to_instances,
2007
               want_diskparams):
2008
    """Initializes this class.
2009

2010
    @param cluster: Cluster object
2011
    @param groups: List of node group objects
2012
    @type group_to_nodes: dict; group UUID as key
2013
    @param group_to_nodes: Per-group list of nodes
2014
    @type group_to_instances: dict; group UUID as key
2015
    @param group_to_instances: Per-group list of (primary) instances
2016
    @type want_diskparams: bool
2017
    @param want_diskparams: Whether diskparamters should be calculated
2018

2019
    """
2020
    self.groups = groups
2021
    self.group_to_nodes = group_to_nodes
2022
    self.group_to_instances = group_to_instances
2023
    self.cluster = cluster
2024
    self.want_diskparams = want_diskparams
2025

    
2026
    # Used for individual rows
2027
    self.group_ipolicy = None
2028
    self.ndparams = None
2029
    self.group_dp = None
2030

    
2031
  def __iter__(self):
2032
    """Iterate over all node groups.
2033

2034
    This function has side-effects and only one instance of the resulting
2035
    generator should be used at a time.
2036

2037
    """
2038
    for group in self.groups:
2039
      self.group_ipolicy = self.cluster.SimpleFillIPolicy(group.ipolicy)
2040
      self.ndparams = self.cluster.SimpleFillND(group.ndparams)
2041
      if self.want_diskparams:
2042
        self.group_dp = self.cluster.SimpleFillDP(group.diskparams)
2043
      else:
2044
        self.group_dp = None
2045
      yield group
2046

    
2047

    
2048
_GROUP_SIMPLE_FIELDS = {
2049
  "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
2050
  "name": ("Group", QFT_TEXT, "Group name"),
2051
  "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
2052
  "uuid": ("UUID", QFT_TEXT, "Group UUID"),
2053
  }
2054

    
2055

    
2056
def _BuildGroupFields():
2057
  """Builds list of fields for node group queries.
2058

2059
  """
2060
  # Add simple fields
2061
  fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
2062
             _GetItemAttr(name))
2063
            for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
2064

    
2065
  def _GetLength(getter):
2066
    return lambda ctx, group: len(getter(ctx)[group.uuid])
2067

    
2068
  def _GetSortedList(getter):
2069
    return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
2070

    
2071
  group_to_nodes = operator.attrgetter("group_to_nodes")
2072
  group_to_instances = operator.attrgetter("group_to_instances")
2073

    
2074
  # Add fields for nodes
2075
  fields.extend([
2076
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
2077
     GQ_NODE, 0, _GetLength(group_to_nodes)),
2078
    (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
2079
     GQ_NODE, 0, _GetSortedList(group_to_nodes)),
2080
    ])
2081

    
2082
  # Add fields for instances
2083
  fields.extend([
2084
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
2085
                "Number of primary instances"),
2086
     GQ_INST, 0, _GetLength(group_to_instances)),
2087
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
2088
                "List of primary instances"),
2089
     GQ_INST, 0, _GetSortedList(group_to_instances)),
2090
    ])
2091

    
2092
  # Other fields
2093
  fields.extend([
2094
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), GQ_CONFIG, 0,
2095
     lambda ctx, group: list(group.GetTags())),
2096
    (_MakeField("ipolicy", "InstancePolicy", QFT_OTHER,
2097
                "Instance policy limitations (merged)"),
2098
     GQ_CONFIG, 0, lambda ctx, _: ctx.group_ipolicy),
2099
    (_MakeField("custom_ipolicy", "CustomInstancePolicy", QFT_OTHER,
2100
                "Custom instance policy limitations"),
2101
     GQ_CONFIG, 0, _GetItemAttr("ipolicy")),
2102
    (_MakeField("custom_ndparams", "CustomNDParams", QFT_OTHER,
2103
                "Custom node parameters"),
2104
     GQ_CONFIG, 0, _GetItemAttr("ndparams")),
2105
    (_MakeField("ndparams", "NDParams", QFT_OTHER,
2106
                "Node parameters"),
2107
     GQ_CONFIG, 0, lambda ctx, _: ctx.ndparams),
2108
    (_MakeField("diskparams", "DiskParameters", QFT_OTHER,
2109
                "Disk parameters (merged)"),
2110
     GQ_DISKPARAMS, 0, lambda ctx, _: ctx.group_dp),
2111
    (_MakeField("custom_diskparams", "CustomDiskParameters", QFT_OTHER,
2112
                "Custom disk parameters"),
2113
     GQ_CONFIG, 0, _GetItemAttr("diskparams")),
2114
    ])
2115

    
2116
  # ND parameters
2117
  fields.extend(_BuildNDFields(True))
2118

    
2119
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
2120

    
2121
  return _PrepareFieldList(fields, [])
2122

    
2123

    
2124
class OsInfo(objects.ConfigObject):
2125
  __slots__ = [
2126
    "name",
2127
    "valid",
2128
    "hidden",
2129
    "blacklisted",
2130
    "variants",
2131
    "api_versions",
2132
    "parameters",
2133
    "node_status",
2134
    ]
2135

    
2136

    
2137
def _BuildOsFields():
2138
  """Builds list of fields for operating system queries.
2139

2140
  """
2141
  fields = [
2142
    (_MakeField("name", "Name", QFT_TEXT, "Operating system name"),
2143
     None, 0, _GetItemAttr("name")),
2144
    (_MakeField("valid", "Valid", QFT_BOOL,
2145
                "Whether operating system definition is valid"),
2146
     None, 0, _GetItemAttr("valid")),
2147
    (_MakeField("hidden", "Hidden", QFT_BOOL,
2148
                "Whether operating system is hidden"),
2149
     None, 0, _GetItemAttr("hidden")),
2150
    (_MakeField("blacklisted", "Blacklisted", QFT_BOOL,
2151
                "Whether operating system is blacklisted"),
2152
     None, 0, _GetItemAttr("blacklisted")),
2153
    (_MakeField("variants", "Variants", QFT_OTHER,
2154
                "Operating system variants"),
2155
     None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("variants"))),
2156
    (_MakeField("api_versions", "ApiVersions", QFT_OTHER,
2157
                "Operating system API versions"),
2158
     None, 0, _ConvWrap(sorted, _GetItemAttr("api_versions"))),
2159
    (_MakeField("parameters", "Parameters", QFT_OTHER,
2160
                "Operating system parameters"),
2161
     None, 0, _ConvWrap(compat.partial(utils.NiceSort, key=compat.fst),
2162
                        _GetItemAttr("parameters"))),
2163
    (_MakeField("node_status", "NodeStatus", QFT_OTHER,
2164
                "Status from node"),
2165
     None, 0, _GetItemAttr("node_status")),
2166
    ]
2167

    
2168
  return _PrepareFieldList(fields, [])
2169

    
2170

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

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

2177
  """
2178
  if job is None:
2179
    return _FS_UNAVAIL
2180
  else:
2181
    return fn(job)
2182

    
2183

    
2184
def _JobUnavail(inner):
2185
  """Wrapper for L{_JobUnavailInner}.
2186

2187
  """
2188
  return compat.partial(_JobUnavailInner, inner)
2189

    
2190

    
2191
def _PerJobOpInner(fn, job):
2192
  """Executes a function per opcode in a job.
2193

2194
  """
2195
  return map(fn, job.ops)
2196

    
2197

    
2198
def _PerJobOp(fn):
2199
  """Wrapper for L{_PerJobOpInner}.
2200

2201
  """
2202
  return _JobUnavail(compat.partial(_PerJobOpInner, fn))
2203

    
2204

    
2205
def _JobTimestampInner(fn, job):
2206
  """Converts unavailable timestamp to L{_FS_UNAVAIL}.
2207

2208
  """
2209
  timestamp = fn(job)
2210

    
2211
  if timestamp is None:
2212
    return _FS_UNAVAIL
2213
  else:
2214
    return timestamp
2215

    
2216

    
2217
def _JobTimestamp(fn):
2218
  """Wrapper for L{_JobTimestampInner}.
2219

2220
  """
2221
  return _JobUnavail(compat.partial(_JobTimestampInner, fn))
2222

    
2223

    
2224
def _BuildJobFields():
2225
  """Builds list of fields for job queries.
2226

2227
  """
2228
  fields = [
2229
    (_MakeField("id", "ID", QFT_TEXT, "Job ID"),
2230
     None, 0, lambda _, (job_id, job): job_id),
2231
    (_MakeField("status", "Status", QFT_TEXT, "Job status"),
2232
     None, 0, _JobUnavail(lambda job: job.CalcStatus())),
2233
    (_MakeField("priority", "Priority", QFT_NUMBER,
2234
                ("Current job priority (%s to %s)" %
2235
                 (constants.OP_PRIO_LOWEST, constants.OP_PRIO_HIGHEST))),
2236
     None, 0, _JobUnavail(lambda job: job.CalcPriority())),
2237
    (_MakeField("ops", "OpCodes", QFT_OTHER, "List of all opcodes"),
2238
     None, 0, _PerJobOp(lambda op: op.input.__getstate__())),
2239
    (_MakeField("opresult", "OpCode_result", QFT_OTHER,
2240
                "List of opcodes results"),
2241
     None, 0, _PerJobOp(operator.attrgetter("result"))),
2242
    (_MakeField("opstatus", "OpCode_status", QFT_OTHER,
2243
                "List of opcodes status"),
2244
     None, 0, _PerJobOp(operator.attrgetter("status"))),
2245
    (_MakeField("oplog", "OpCode_log", QFT_OTHER,
2246
                "List of opcode output logs"),
2247
     None, 0, _PerJobOp(operator.attrgetter("log"))),
2248
    (_MakeField("opstart", "OpCode_start", QFT_OTHER,
2249
                "List of opcode start timestamps (before acquiring locks)"),
2250
     None, 0, _PerJobOp(operator.attrgetter("start_timestamp"))),
2251
    (_MakeField("opexec", "OpCode_exec", QFT_OTHER,
2252
                "List of opcode execution start timestamps (after acquiring"
2253
                " locks)"),
2254
     None, 0, _PerJobOp(operator.attrgetter("exec_timestamp"))),
2255
    (_MakeField("opend", "OpCode_end", QFT_OTHER,
2256
                "List of opcode execution end timestamps"),
2257
     None, 0, _PerJobOp(operator.attrgetter("end_timestamp"))),
2258
    (_MakeField("oppriority", "OpCode_prio", QFT_OTHER,
2259
                "List of opcode priorities"),
2260
     None, 0, _PerJobOp(operator.attrgetter("priority"))),
2261
    (_MakeField("received_ts", "Received", QFT_OTHER,
2262
                "Timestamp of when job was received"),
2263
     None, 0, _JobTimestamp(operator.attrgetter("received_timestamp"))),
2264
    (_MakeField("start_ts", "Start", QFT_OTHER,
2265
                "Timestamp of job start"),
2266
     None, 0, _JobTimestamp(operator.attrgetter("start_timestamp"))),
2267
    (_MakeField("end_ts", "End", QFT_OTHER,
2268
                "Timestamp of job end"),
2269
     None, 0, _JobTimestamp(operator.attrgetter("end_timestamp"))),
2270
    (_MakeField("summary", "Summary", QFT_OTHER,
2271
                "List of per-opcode summaries"),
2272
     None, 0, _PerJobOp(lambda op: op.input.Summary())),
2273
    ]
2274

    
2275
  return _PrepareFieldList(fields, [])
2276

    
2277

    
2278
def _GetExportName(_, (node_name, expname)): # pylint: disable=W0613
2279
  """Returns an export name if available.
2280

2281
  """
2282
  if expname is None:
2283
    return _FS_UNAVAIL
2284
  else:
2285
    return expname
2286

    
2287

    
2288
def _BuildExportFields():
2289
  """Builds list of fields for exports.
2290

2291
  """
2292
  fields = [
2293
    (_MakeField("node", "Node", QFT_TEXT, "Node name"),
2294
     None, QFF_HOSTNAME, lambda _, (node_name, expname): node_name),
2295
    (_MakeField("export", "Export", QFT_TEXT, "Export name"),
2296
     None, 0, _GetExportName),
2297
    ]
2298

    
2299
  return _PrepareFieldList(fields, [])
2300

    
2301

    
2302
_CLUSTER_VERSION_FIELDS = {
2303
  "software_version": ("SoftwareVersion", QFT_TEXT, constants.RELEASE_VERSION,
2304
                       "Software version"),
2305
  "protocol_version": ("ProtocolVersion", QFT_NUMBER,
2306
                       constants.PROTOCOL_VERSION,
2307
                       "RPC protocol version"),
2308
  "config_version": ("ConfigVersion", QFT_NUMBER, constants.CONFIG_VERSION,
2309
                     "Configuration format version"),
2310
  "os_api_version": ("OsApiVersion", QFT_NUMBER, max(constants.OS_API_VERSIONS),
2311
                     "API version for OS template scripts"),
2312
  "export_version": ("ExportVersion", QFT_NUMBER, constants.EXPORT_VERSION,
2313
                     "Import/export file format version"),
2314
  }
2315

    
2316

    
2317
_CLUSTER_SIMPLE_FIELDS = {
2318
  "cluster_name": ("Name", QFT_TEXT, QFF_HOSTNAME, "Cluster name"),
2319
  "master_node": ("Master", QFT_TEXT, QFF_HOSTNAME, "Master node name"),
2320
  "volume_group_name": ("VgName", QFT_TEXT, 0, "LVM volume group name"),
2321
  }
2322

    
2323

    
2324
class ClusterQueryData:
2325
  def __init__(self, cluster, drain_flag, watcher_pause):
2326
    """Initializes this class.
2327

2328
    @type cluster: L{objects.Cluster}
2329
    @param cluster: Instance of cluster object
2330
    @type drain_flag: bool
2331
    @param drain_flag: Whether job queue is drained
2332
    @type watcher_pause: number
2333
    @param watcher_pause: Until when watcher is paused (Unix timestamp)
2334

2335
    """
2336
    self._cluster = cluster
2337
    self.drain_flag = drain_flag
2338
    self.watcher_pause = watcher_pause
2339

    
2340
  def __iter__(self):
2341
    return iter([self._cluster])
2342

    
2343

    
2344
def _ClusterWatcherPause(ctx, _):
2345
  """Returns until when watcher is paused (if available).
2346

2347
  """
2348
  if ctx.watcher_pause is None:
2349
    return _FS_UNAVAIL
2350
  else:
2351
    return ctx.watcher_pause
2352

    
2353

    
2354
def _BuildClusterFields():
2355
  """Builds list of fields for cluster information.
2356

2357
  """
2358
  fields = [
2359
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), CQ_CONFIG, 0,
2360
     lambda ctx, cluster: list(cluster.GetTags())),
2361
    (_MakeField("architecture", "ArchInfo", QFT_OTHER,
2362
                "Architecture information"), None, 0,
2363
     lambda ctx, _: runtime.GetArchInfo()),
2364
    (_MakeField("drain_flag", "QueueDrained", QFT_BOOL,
2365
                "Flag whether job queue is drained"), CQ_QUEUE_DRAINED, 0,
2366
     lambda ctx, _: ctx.drain_flag),
2367
    (_MakeField("watcher_pause", "WatcherPause", QFT_TIMESTAMP,
2368
                "Until when watcher is paused"), CQ_WATCHER_PAUSE, 0,
2369
     _ClusterWatcherPause),
2370
    ]
2371

    
2372
  # Simple fields
2373
  fields.extend([
2374
    (_MakeField(name, title, kind, doc), CQ_CONFIG, flags, _GetItemAttr(name))
2375
    for (name, (title, kind, flags, doc)) in _CLUSTER_SIMPLE_FIELDS.items()
2376
    ])
2377

    
2378
  # Version fields
2379
  fields.extend([
2380
    (_MakeField(name, title, kind, doc), None, 0, _StaticValue(value))
2381
    for (name, (title, kind, value, doc)) in _CLUSTER_VERSION_FIELDS.items()
2382
    ])
2383

    
2384
  # Add timestamps
2385
  fields.extend(_GetItemTimestampFields(CQ_CONFIG))
2386

    
2387
  return _PrepareFieldList(fields, [
2388
    ("name", "cluster_name"),
2389
    ])
2390

    
2391

    
2392
#: Fields for cluster information
2393
CLUSTER_FIELDS = _BuildClusterFields()
2394

    
2395
#: Fields available for node queries
2396
NODE_FIELDS = _BuildNodeFields()
2397

    
2398
#: Fields available for instance queries
2399
INSTANCE_FIELDS = _BuildInstanceFields()
2400

    
2401
#: Fields available for lock queries
2402
LOCK_FIELDS = _BuildLockFields()
2403

    
2404
#: Fields available for node group queries
2405
GROUP_FIELDS = _BuildGroupFields()
2406

    
2407
#: Fields available for operating system queries
2408
OS_FIELDS = _BuildOsFields()
2409

    
2410
#: Fields available for job queries
2411
JOB_FIELDS = _BuildJobFields()
2412

    
2413
#: Fields available for exports
2414
EXPORT_FIELDS = _BuildExportFields()
2415

    
2416
#: All available resources
2417
ALL_FIELDS = {
2418
  constants.QR_CLUSTER: CLUSTER_FIELDS,
2419
  constants.QR_INSTANCE: INSTANCE_FIELDS,
2420
  constants.QR_NODE: NODE_FIELDS,
2421
  constants.QR_LOCK: LOCK_FIELDS,
2422
  constants.QR_GROUP: GROUP_FIELDS,
2423
  constants.QR_OS: OS_FIELDS,
2424
  constants.QR_JOB: JOB_FIELDS,
2425
  constants.QR_EXPORT: EXPORT_FIELDS,
2426
  }
2427

    
2428
#: All available field lists
2429
ALL_FIELD_LISTS = ALL_FIELDS.values()