Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ 9665bb3a

History | View | Annotate | Download (66.9 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 qlang
66

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

    
72

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

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

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

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

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

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

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

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

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

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

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

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

    
139
# TODO: Consider moving titles closer to constants
140
NDP_TITLE = {
141
  constants.ND_OOB_PROGRAM: "OutOfBandProgram",
142
  constants.ND_SPINDLE_COUNT: "SpindleCount",
143
  }
144

    
145

    
146
def _GetUnknownField(ctx, item): # pylint: disable=W0613
147
  """Gets the contents of an unknown field.
148

149
  """
150
  return _FS_UNKNOWN
151

    
152

    
153
def _GetQueryFields(fielddefs, selected):
154
  """Calculates the internal list of selected fields.
155

156
  Unknown fields are returned as L{constants.QFT_UNKNOWN}.
157

158
  @type fielddefs: dict
159
  @param fielddefs: Field definitions
160
  @type selected: list of strings
161
  @param selected: List of selected fields
162

163
  """
164
  result = []
165

    
166
  for name in selected:
167
    try:
168
      fdef = fielddefs[name]
169
    except KeyError:
170
      fdef = (_MakeField(name, name, QFT_UNKNOWN, "Unknown field '%s'" % name),
171
              None, 0, _GetUnknownField)
172

    
173
    assert len(fdef) == 4
174

    
175
    result.append(fdef)
176

    
177
  return result
178

    
179

    
180
def GetAllFields(fielddefs):
181
  """Extract L{objects.QueryFieldDefinition} from field definitions.
182

183
  @rtype: list of L{objects.QueryFieldDefinition}
184

185
  """
186
  return [fdef for (fdef, _, _, _) in fielddefs]
187

    
188

    
189
class _FilterHints:
190
  """Class for filter analytics.
191

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

197
  There are two ways to optimize this. The first, and simpler, is to assign
198
  each field a group of data, so that the caller can determine which
199
  computations are necessary depending on the data groups requested. The list
200
  of referenced groups must also be computed for fields referenced in the
201
  filter.
202

203
  The second is restricting the items based on a primary key. The primary key
204
  is usually a unique name (e.g. a node name). This class extracts all
205
  referenced names from a filter. If it encounters any filter condition which
206
  disallows such a list to be determined (e.g. a non-equality filter), all
207
  names will be requested.
208

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

212
  """
213
  def __init__(self, namefield):
214
    """Initializes this class.
215

216
    @type namefield: string
217
    @param namefield: Field caller is interested in
218

219
    """
220
    self._namefield = namefield
221

    
222
    #: Whether all names need to be requested (e.g. if a non-equality operator
223
    #: has been used)
224
    self._allnames = False
225

    
226
    #: Which names to request
227
    self._names = None
228

    
229
    #: Data kinds referenced by the filter (used by L{Query.RequestedData})
230
    self._datakinds = set()
231

    
232
  def RequestedNames(self):
233
    """Returns all requested values.
234

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

238
    @rtype: list
239

240
    """
241
    if self._allnames or self._names is None:
242
      return None
243

    
244
    return utils.UniqueSequence(self._names)
245

    
246
  def ReferencedData(self):
247
    """Returns all kinds of data referenced by the filter.
248

249
    """
250
    return frozenset(self._datakinds)
251

    
252
  def _NeedAllNames(self):
253
    """Changes internal state to request all names.
254

255
    """
256
    self._allnames = True
257
    self._names = None
258

    
259
  def NoteLogicOp(self, op):
260
    """Called when handling a logic operation.
261

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

265
    """
266
    if op != qlang.OP_OR:
267
      self._NeedAllNames()
268

    
269
  def NoteUnaryOp(self, op): # pylint: disable=W0613
270
    """Called when handling an unary operation.
271

272
    @type op: string
273
    @param op: Operator
274

275
    """
276
    self._NeedAllNames()
277

    
278
  def NoteBinaryOp(self, op, datakind, name, value):
279
    """Called when handling a binary operation.
280

281
    @type op: string
282
    @param op: Operator
283
    @type name: string
284
    @param name: Left-hand side of operator (field name)
285
    @param value: Right-hand side of operator
286

287
    """
288
    if datakind is not None:
289
      self._datakinds.add(datakind)
290

    
291
    if self._allnames:
292
      return
293

    
294
    # If any operator other than equality was used, all names need to be
295
    # retrieved
296
    if op == qlang.OP_EQUAL and name == self._namefield:
297
      if self._names is None:
298
        self._names = []
299
      self._names.append(value)
300
    else:
301
      self._NeedAllNames()
302

    
303

    
304
def _WrapLogicOp(op_fn, sentences, ctx, item):
305
  """Wrapper for logic operator functions.
306

307
  """
308
  return op_fn(fn(ctx, item) for fn in sentences)
309

    
310

    
311
def _WrapUnaryOp(op_fn, inner, ctx, item):
312
  """Wrapper for unary operator functions.
313

314
  """
315
  return op_fn(inner(ctx, item))
316

    
317

    
318
def _WrapBinaryOp(op_fn, retrieval_fn, value, ctx, item):
319
  """Wrapper for binary operator functions.
320

321
  """
322
  return op_fn(retrieval_fn(ctx, item), value)
323

    
324

    
325
def _WrapNot(fn, lhs, rhs):
326
  """Negates the result of a wrapped function.
327

328
  """
329
  return not fn(lhs, rhs)
330

    
331

    
332
def _PrepareRegex(pattern):
333
  """Compiles a regular expression.
334

335
  """
336
  try:
337
    return re.compile(pattern)
338
  except re.error, err:
339
    raise errors.ParameterError("Invalid regex pattern (%s)" % err)
340

    
341

    
342
class _FilterCompilerHelper:
343
  """Converts a query filter to a callable usable for filtering.
344

345
  """
346
  # String statement has no effect, pylint: disable=W0105
347

    
348
  #: How deep filters can be nested
349
  _LEVELS_MAX = 10
350

    
351
  # Unique identifiers for operator groups
352
  (_OPTYPE_LOGIC,
353
   _OPTYPE_UNARY,
354
   _OPTYPE_BINARY) = range(1, 4)
355

    
356
  """Functions for equality checks depending on field flags.
357

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

362
  Order matters. The first item with flags will be used. Flags are checked
363
  using binary AND.
364

365
  """
366
  _EQUALITY_CHECKS = [
367
    (QFF_HOSTNAME,
368
     lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
369
                                               case_sensitive=False),
370
     None),
371
    (None, operator.eq, None),
372
    ]
373

    
374
  """Known operators
375

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

379
    - C{_OPTYPE_LOGIC}: Callable taking any number of arguments; used by
380
      L{_HandleLogicOp}
381
    - C{_OPTYPE_UNARY}: Always C{None}; details handled by L{_HandleUnaryOp}
382
    - C{_OPTYPE_BINARY}: Callable taking exactly two parameters, the left- and
383
      right-hand side of the operator, used by L{_HandleBinaryOp}
384

385
  """
386
  _OPS = {
387
    # Logic operators
388
    qlang.OP_OR: (_OPTYPE_LOGIC, compat.any),
389
    qlang.OP_AND: (_OPTYPE_LOGIC, compat.all),
390

    
391
    # Unary operators
392
    qlang.OP_NOT: (_OPTYPE_UNARY, None),
393
    qlang.OP_TRUE: (_OPTYPE_UNARY, None),
394

    
395
    # Binary operators
396
    qlang.OP_EQUAL: (_OPTYPE_BINARY, _EQUALITY_CHECKS),
397
    qlang.OP_NOT_EQUAL:
398
      (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn), valprepfn)
399
                        for (flags, fn, valprepfn) in _EQUALITY_CHECKS]),
400
    qlang.OP_REGEXP: (_OPTYPE_BINARY, [
401
      (None, lambda lhs, rhs: rhs.search(lhs), _PrepareRegex),
402
      ]),
403
    qlang.OP_CONTAINS: (_OPTYPE_BINARY, [
404
      (None, operator.contains, None),
405
      ]),
406
    }
407

    
408
  def __init__(self, fields):
409
    """Initializes this class.
410

411
    @param fields: Field definitions (return value of L{_PrepareFieldList})
412

413
    """
414
    self._fields = fields
415
    self._hints = None
416
    self._op_handler = None
417

    
418
  def __call__(self, hints, qfilter):
419
    """Converts a query filter into a callable function.
420

421
    @type hints: L{_FilterHints} or None
422
    @param hints: Callbacks doing analysis on filter
423
    @type qfilter: list
424
    @param qfilter: Filter structure
425
    @rtype: callable
426
    @return: Function receiving context and item as parameters, returning
427
             boolean as to whether item matches filter
428

429
    """
430
    self._op_handler = {
431
      self._OPTYPE_LOGIC:
432
        (self._HandleLogicOp, getattr(hints, "NoteLogicOp", None)),
433
      self._OPTYPE_UNARY:
434
        (self._HandleUnaryOp, getattr(hints, "NoteUnaryOp", None)),
435
      self._OPTYPE_BINARY:
436
        (self._HandleBinaryOp, getattr(hints, "NoteBinaryOp", None)),
437
      }
438

    
439
    try:
440
      filter_fn = self._Compile(qfilter, 0)
441
    finally:
442
      self._op_handler = None
443

    
444
    return filter_fn
445

    
446
  def _Compile(self, qfilter, level):
447
    """Inner function for converting filters.
448

449
    Calls the correct handler functions for the top-level operator. This
450
    function is called recursively (e.g. for logic operators).
451

452
    """
453
    if not (isinstance(qfilter, (list, tuple)) and qfilter):
454
      raise errors.ParameterError("Invalid filter on level %s" % level)
455

    
456
    # Limit recursion
457
    if level >= self._LEVELS_MAX:
458
      raise errors.ParameterError("Only up to %s levels are allowed (filter"
459
                                  " nested too deep)" % self._LEVELS_MAX)
460

    
461
    # Create copy to be modified
462
    operands = qfilter[:]
463
    op = operands.pop(0)
464

    
465
    try:
466
      (kind, op_data) = self._OPS[op]
467
    except KeyError:
468
      raise errors.ParameterError("Unknown operator '%s'" % op)
469

    
470
    (handler, hints_cb) = self._op_handler[kind]
471

    
472
    return handler(hints_cb, level, op, op_data, operands)
473

    
474
  def _LookupField(self, name):
475
    """Returns a field definition by name.
476

477
    """
478
    try:
479
      return self._fields[name]
480
    except KeyError:
481
      raise errors.ParameterError("Unknown field '%s'" % name)
482

    
483
  def _HandleLogicOp(self, hints_fn, level, op, op_fn, operands):
484
    """Handles logic operators.
485

486
    @type hints_fn: callable
487
    @param hints_fn: Callback doing some analysis on the filter
488
    @type level: integer
489
    @param level: Current depth
490
    @type op: string
491
    @param op: Operator
492
    @type op_fn: callable
493
    @param op_fn: Function implementing operator
494
    @type operands: list
495
    @param operands: List of operands
496

497
    """
498
    if hints_fn:
499
      hints_fn(op)
500

    
501
    return compat.partial(_WrapLogicOp, op_fn,
502
                          [self._Compile(op, level + 1) for op in operands])
503

    
504
  def _HandleUnaryOp(self, hints_fn, level, op, op_fn, operands):
505
    """Handles unary operators.
506

507
    @type hints_fn: callable
508
    @param hints_fn: Callback doing some analysis on the filter
509
    @type level: integer
510
    @param level: Current depth
511
    @type op: string
512
    @param op: Operator
513
    @type op_fn: callable
514
    @param op_fn: Function implementing operator
515
    @type operands: list
516
    @param operands: List of operands
517

518
    """
519
    assert op_fn is None
520

    
521
    if hints_fn:
522
      hints_fn(op)
523

    
524
    if len(operands) != 1:
525
      raise errors.ParameterError("Unary operator '%s' expects exactly one"
526
                                  " operand" % op)
527

    
528
    if op == qlang.OP_TRUE:
529
      (_, _, _, retrieval_fn) = self._LookupField(operands[0])
530

    
531
      op_fn = operator.truth
532
      arg = retrieval_fn
533
    elif op == qlang.OP_NOT:
534
      op_fn = operator.not_
535
      arg = self._Compile(operands[0], level + 1)
536
    else:
537
      raise errors.ProgrammerError("Can't handle operator '%s'" % op)
538

    
539
    return compat.partial(_WrapUnaryOp, op_fn, arg)
540

    
541
  def _HandleBinaryOp(self, hints_fn, level, op, op_data, operands):
542
    """Handles binary operators.
543

544
    @type hints_fn: callable
545
    @param hints_fn: Callback doing some analysis on the filter
546
    @type level: integer
547
    @param level: Current depth
548
    @type op: string
549
    @param op: Operator
550
    @param op_data: Functions implementing operators
551
    @type operands: list
552
    @param operands: List of operands
553

554
    """
555
    # Unused arguments, pylint: disable=W0613
556
    try:
557
      (name, value) = operands
558
    except (ValueError, TypeError):
559
      raise errors.ParameterError("Invalid binary operator, expected exactly"
560
                                  " two operands")
561

    
562
    (fdef, datakind, field_flags, retrieval_fn) = self._LookupField(name)
563

    
564
    assert fdef.kind != QFT_UNKNOWN
565

    
566
    # TODO: Type conversions?
567

    
568
    verify_fn = _VERIFY_FN[fdef.kind]
569
    if not verify_fn(value):
570
      raise errors.ParameterError("Unable to compare field '%s' (type '%s')"
571
                                  " with '%s', expected %s" %
572
                                  (name, fdef.kind, value.__class__.__name__,
573
                                   verify_fn))
574

    
575
    if hints_fn:
576
      hints_fn(op, datakind, name, value)
577

    
578
    for (fn_flags, fn, valprepfn) in op_data:
579
      if fn_flags is None or fn_flags & field_flags:
580
        # Prepare value if necessary (e.g. compile regular expression)
581
        if valprepfn:
582
          value = valprepfn(value)
583

    
584
        return compat.partial(_WrapBinaryOp, fn, retrieval_fn, value)
585

    
586
    raise errors.ProgrammerError("Unable to find operator implementation"
587
                                 " (op '%s', flags %s)" % (op, field_flags))
588

    
589

    
590
def _CompileFilter(fields, hints, qfilter):
591
  """Converts a query filter into a callable function.
592

593
  See L{_FilterCompilerHelper} for details.
594

595
  @rtype: callable
596

597
  """
598
  return _FilterCompilerHelper(fields)(hints, qfilter)
599

    
600

    
601
class Query:
602
  def __init__(self, fieldlist, selected, qfilter=None, namefield=None):
603
    """Initializes this class.
604

605
    The field definition is a dictionary with the field's name as a key and a
606
    tuple containing, in order, the field definition object
607
    (L{objects.QueryFieldDefinition}, the data kind to help calling code
608
    collect data and a retrieval function. The retrieval function is called
609
    with two parameters, in order, the data container and the item in container
610
    (see L{Query.Query}).
611

612
    Users of this class can call L{RequestedData} before preparing the data
613
    container to determine what data is needed.
614

615
    @type fieldlist: dictionary
616
    @param fieldlist: Field definitions
617
    @type selected: list of strings
618
    @param selected: List of selected fields
619

620
    """
621
    assert namefield is None or namefield in fieldlist
622

    
623
    self._fields = _GetQueryFields(fieldlist, selected)
624

    
625
    self._filter_fn = None
626
    self._requested_names = None
627
    self._filter_datakinds = frozenset()
628

    
629
    if qfilter is not None:
630
      # Collect requested names if wanted
631
      if namefield:
632
        hints = _FilterHints(namefield)
633
      else:
634
        hints = None
635

    
636
      # Build filter function
637
      self._filter_fn = _CompileFilter(fieldlist, hints, qfilter)
638
      if hints:
639
        self._requested_names = hints.RequestedNames()
640
        self._filter_datakinds = hints.ReferencedData()
641

    
642
    if namefield is None:
643
      self._name_fn = None
644
    else:
645
      (_, _, _, self._name_fn) = fieldlist[namefield]
646

    
647
  def RequestedNames(self):
648
    """Returns all names referenced in the filter.
649

650
    If there is no filter or operators are preventing determining the exact
651
    names, C{None} is returned.
652

653
    """
654
    return self._requested_names
655

    
656
  def RequestedData(self):
657
    """Gets requested kinds of data.
658

659
    @rtype: frozenset
660

661
    """
662
    return (self._filter_datakinds |
663
            frozenset(datakind for (_, datakind, _, _) in self._fields
664
                      if datakind is not None))
665

    
666
  def GetFields(self):
667
    """Returns the list of fields for this query.
668

669
    Includes unknown fields.
670

671
    @rtype: List of L{objects.QueryFieldDefinition}
672

673
    """
674
    return GetAllFields(self._fields)
675

    
676
  def Query(self, ctx, sort_by_name=True):
677
    """Execute a query.
678

679
    @param ctx: Data container passed to field retrieval functions, must
680
      support iteration using C{__iter__}
681
    @type sort_by_name: boolean
682
    @param sort_by_name: Whether to sort by name or keep the input data's
683
      ordering
684

685
    """
686
    sort = (self._name_fn and sort_by_name)
687

    
688
    result = []
689

    
690
    for idx, item in enumerate(ctx):
691
      if not (self._filter_fn is None or self._filter_fn(ctx, item)):
692
        continue
693

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

    
696
      # Verify result
697
      if __debug__:
698
        _VerifyResultRow(self._fields, row)
699

    
700
      if sort:
701
        (status, name) = _ProcessResult(self._name_fn(ctx, item))
702
        assert status == constants.RS_NORMAL
703
        # TODO: Are there cases where we wouldn't want to use NiceSort?
704
        result.append((utils.NiceSortKey(name), idx, row))
705
      else:
706
        result.append(row)
707

    
708
    if not sort:
709
      return result
710

    
711
    # TODO: Would "heapq" be more efficient than sorting?
712

    
713
    # Sorting in-place instead of using "sorted()"
714
    result.sort()
715

    
716
    assert not result or (len(result[0]) == 3 and len(result[-1]) == 3)
717

    
718
    return map(operator.itemgetter(2), result)
719

    
720
  def OldStyleQuery(self, ctx, sort_by_name=True):
721
    """Query with "old" query result format.
722

723
    See L{Query.Query} for arguments.
724

725
    """
726
    unknown = set(fdef.name for (fdef, _, _, _) in self._fields
727
                  if fdef.kind == QFT_UNKNOWN)
728
    if unknown:
729
      raise errors.OpPrereqError("Unknown output fields selected: %s" %
730
                                 (utils.CommaJoin(unknown), ),
731
                                 errors.ECODE_INVAL)
732

    
733
    return [[value for (_, value) in row]
734
            for row in self.Query(ctx, sort_by_name=sort_by_name)]
735

    
736

    
737
def _ProcessResult(value):
738
  """Converts result values into externally-visible ones.
739

740
  """
741
  if value is _FS_UNKNOWN:
742
    return (RS_UNKNOWN, None)
743
  elif value is _FS_NODATA:
744
    return (RS_NODATA, None)
745
  elif value is _FS_UNAVAIL:
746
    return (RS_UNAVAIL, None)
747
  elif value is _FS_OFFLINE:
748
    return (RS_OFFLINE, None)
749
  else:
750
    return (RS_NORMAL, value)
751

    
752

    
753
def _VerifyResultRow(fields, row):
754
  """Verifies the contents of a query result row.
755

756
  @type fields: list
757
  @param fields: Field definitions for result
758
  @type row: list of tuples
759
  @param row: Row data
760

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

    
774

    
775
def _FieldDictKey((fdef, _, flags, fn)):
776
  """Generates key for field dictionary.
777

778
  """
779
  assert fdef.name and fdef.title, "Name and title are required"
780
  assert FIELD_NAME_RE.match(fdef.name)
781
  assert TITLE_RE.match(fdef.title)
782
  assert (DOC_RE.match(fdef.doc) and len(fdef.doc.splitlines()) == 1 and
783
          fdef.doc.strip() == fdef.doc), \
784
         "Invalid description for field '%s'" % fdef.name
785
  assert callable(fn)
786
  assert (flags & ~QFF_ALL) == 0, "Unknown flags for field '%s'" % fdef.name
787

    
788
  return fdef.name
789

    
790

    
791
def _PrepareFieldList(fields, aliases):
792
  """Prepares field list for use by L{Query}.
793

794
  Converts the list to a dictionary and does some verification.
795

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

806
  """
807
  if __debug__:
808
    duplicates = utils.FindDuplicates(fdef.title.lower()
809
                                      for (fdef, _, _, _) in fields)
810
    assert not duplicates, "Duplicate title(s) found: %r" % duplicates
811

    
812
  result = utils.SequenceToDict(fields, key=_FieldDictKey)
813

    
814
  for alias, target in aliases:
815
    assert alias not in result, "Alias %s overrides an existing field" % alias
816
    assert target in result, "Missing target %s for alias %s" % (target, alias)
817
    (fdef, k, flags, fn) = result[target]
818
    fdef = fdef.Copy()
819
    fdef.name = alias
820
    result[alias] = (fdef, k, flags, fn)
821

    
822
  assert len(result) == len(fields) + len(aliases)
823
  assert compat.all(name == fdef.name
824
                    for (name, (fdef, _, _, _)) in result.items())
825

    
826
  return result
827

    
828

    
829
def GetQueryResponse(query, ctx, sort_by_name=True):
830
  """Prepares the response for a query.
831

832
  @type query: L{Query}
833
  @param ctx: Data container, see L{Query.Query}
834
  @type sort_by_name: boolean
835
  @param sort_by_name: Whether to sort by name or keep the input data's
836
    ordering
837

838
  """
839
  return objects.QueryResponse(data=query.Query(ctx, sort_by_name=sort_by_name),
840
                               fields=query.GetFields()).ToDict()
841

    
842

    
843
def QueryFields(fielddefs, selected):
844
  """Returns list of available fields.
845

846
  @type fielddefs: dict
847
  @param fielddefs: Field definitions
848
  @type selected: list of strings
849
  @param selected: List of selected fields
850
  @return: List of L{objects.QueryFieldDefinition}
851

852
  """
853
  if selected is None:
854
    # Client requests all fields, sort by name
855
    fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
856
                           key=operator.attrgetter("name"))
857
  else:
858
    # Keep order as requested by client
859
    fdefs = Query(fielddefs, selected).GetFields()
860

    
861
  return objects.QueryFieldsResponse(fields=fdefs).ToDict()
862

    
863

    
864
def _MakeField(name, title, kind, doc):
865
  """Wrapper for creating L{objects.QueryFieldDefinition} instances.
866

867
  @param name: Field name as a regular expression
868
  @param title: Human-readable title
869
  @param kind: Field type
870
  @param doc: Human-readable description
871

872
  """
873
  return objects.QueryFieldDefinition(name=name, title=title, kind=kind,
874
                                      doc=doc)
875

    
876

    
877
def _GetNodeRole(node, master_name):
878
  """Determine node role.
879

880
  @type node: L{objects.Node}
881
  @param node: Node object
882
  @type master_name: string
883
  @param master_name: Master node name
884

885
  """
886
  if node.name == master_name:
887
    return constants.NR_MASTER
888
  elif node.master_candidate:
889
    return constants.NR_MCANDIDATE
890
  elif node.drained:
891
    return constants.NR_DRAINED
892
  elif node.offline:
893
    return constants.NR_OFFLINE
894
  else:
895
    return constants.NR_REGULAR
896

    
897

    
898
def _GetItemAttr(attr):
899
  """Returns a field function to return an attribute of the item.
900

901
  @param attr: Attribute name
902

903
  """
904
  getter = operator.attrgetter(attr)
905
  return lambda _, item: getter(item)
906

    
907

    
908
def _GetNDParam(name):
909
  """Return a field function to return an ND parameter out of the context.
910

911
  """
912
  def _helper(ctx, _):
913
    if ctx.ndparams is None:
914
      return _FS_UNAVAIL
915
    else:
916
      return ctx.ndparams.get(name, None)
917
  return _helper
918

    
919

    
920
def _BuildNDFields(is_group):
921
  """Builds all the ndparam fields.
922

923
  @param is_group: whether this is called at group or node level
924

925
  """
926
  if is_group:
927
    field_kind = GQ_CONFIG
928
  else:
929
    field_kind = NQ_GROUP
930
  return [(_MakeField("ndp/%s" % name, NDP_TITLE.get(name, "ndp/%s" % name),
931
                      _VTToQFT[kind], "The \"%s\" node parameter" % name),
932
           field_kind, 0, _GetNDParam(name))
933
          for name, kind in constants.NDS_PARAMETER_TYPES.items()]
934

    
935

    
936
def _ConvWrapInner(convert, fn, ctx, item):
937
  """Wrapper for converting values.
938

939
  @param convert: Conversion function receiving value as single parameter
940
  @param fn: Retrieval function
941

942
  """
943
  value = fn(ctx, item)
944

    
945
  # Is the value an abnormal status?
946
  if compat.any(value is fs for fs in _FS_ALL):
947
    # Return right away
948
    return value
949

    
950
  # TODO: Should conversion function also receive context, item or both?
951
  return convert(value)
952

    
953

    
954
def _ConvWrap(convert, fn):
955
  """Convenience wrapper for L{_ConvWrapInner}.
956

957
  @param convert: Conversion function receiving value as single parameter
958
  @param fn: Retrieval function
959

960
  """
961
  return compat.partial(_ConvWrapInner, convert, fn)
962

    
963

    
964
def _GetItemTimestamp(getter):
965
  """Returns function for getting timestamp of item.
966

967
  @type getter: callable
968
  @param getter: Function to retrieve timestamp attribute
969

970
  """
971
  def fn(_, item):
972
    """Returns a timestamp of item.
973

974
    """
975
    timestamp = getter(item)
976
    if timestamp is None:
977
      # Old configs might not have all timestamps
978
      return _FS_UNAVAIL
979
    else:
980
      return timestamp
981

    
982
  return fn
983

    
984

    
985
def _GetItemTimestampFields(datatype):
986
  """Returns common timestamp fields.
987

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

990
  """
991
  return [
992
    (_MakeField("ctime", "CTime", QFT_TIMESTAMP, "Creation timestamp"),
993
     datatype, 0, _GetItemTimestamp(operator.attrgetter("ctime"))),
994
    (_MakeField("mtime", "MTime", QFT_TIMESTAMP, "Modification timestamp"),
995
     datatype, 0, _GetItemTimestamp(operator.attrgetter("mtime"))),
996
    ]
997

    
998

    
999
class NodeQueryData:
1000
  """Data container for node data queries.
1001

1002
  """
1003
  def __init__(self, nodes, live_data, master_name, node_to_primary,
1004
               node_to_secondary, groups, oob_support, cluster):
1005
    """Initializes this class.
1006

1007
    """
1008
    self.nodes = nodes
1009
    self.live_data = live_data
1010
    self.master_name = master_name
1011
    self.node_to_primary = node_to_primary
1012
    self.node_to_secondary = node_to_secondary
1013
    self.groups = groups
1014
    self.oob_support = oob_support
1015
    self.cluster = cluster
1016

    
1017
    # Used for individual rows
1018
    self.curlive_data = None
1019
    self.ndparams = None
1020

    
1021
  def __iter__(self):
1022
    """Iterate over all nodes.
1023

1024
    This function has side-effects and only one instance of the resulting
1025
    generator should be used at a time.
1026

1027
    """
1028
    for node in self.nodes:
1029
      group = self.groups.get(node.group, None)
1030
      if group is None:
1031
        self.ndparams = None
1032
      else:
1033
        self.ndparams = self.cluster.FillND(node, group)
1034
      if self.live_data:
1035
        self.curlive_data = self.live_data.get(node.name, None)
1036
      else:
1037
        self.curlive_data = None
1038
      yield node
1039

    
1040

    
1041
#: Fields that are direct attributes of an L{objects.Node} object
1042
_NODE_SIMPLE_FIELDS = {
1043
  "drained": ("Drained", QFT_BOOL, 0, "Whether node is drained"),
1044
  "master_candidate": ("MasterC", QFT_BOOL, 0,
1045
                       "Whether node is a master candidate"),
1046
  "master_capable": ("MasterCapable", QFT_BOOL, 0,
1047
                     "Whether node can become a master candidate"),
1048
  "name": ("Node", QFT_TEXT, QFF_HOSTNAME, "Node name"),
1049
  "offline": ("Offline", QFT_BOOL, 0, "Whether node is marked offline"),
1050
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Node"),
1051
  "uuid": ("UUID", QFT_TEXT, 0, "Node UUID"),
1052
  "vm_capable": ("VMCapable", QFT_BOOL, 0, "Whether node can host instances"),
1053
  }
1054

    
1055

    
1056
#: Fields requiring talking to the node
1057
# Note that none of these are available for non-vm_capable nodes
1058
_NODE_LIVE_FIELDS = {
1059
  "bootid": ("BootID", QFT_TEXT, "bootid",
1060
             "Random UUID renewed for each system reboot, can be used"
1061
             " for detecting reboots by tracking changes"),
1062
  "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes",
1063
             "Number of NUMA domains on node (if exported by hypervisor)"),
1064
  "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets",
1065
               "Number of physical CPU sockets (if exported by hypervisor)"),
1066
  "ctotal": ("CTotal", QFT_NUMBER, "cpu_total", "Number of logical processors"),
1067
  "dfree": ("DFree", QFT_UNIT, "vg_free",
1068
            "Available disk space in volume group"),
1069
  "dtotal": ("DTotal", QFT_UNIT, "vg_size",
1070
             "Total disk space in volume group used for instance disk"
1071
             " allocation"),
1072
  "mfree": ("MFree", QFT_UNIT, "memory_free",
1073
            "Memory available for instance allocations"),
1074
  "mnode": ("MNode", QFT_UNIT, "memory_dom0",
1075
            "Amount of memory used by node (dom0 for Xen)"),
1076
  "mtotal": ("MTotal", QFT_UNIT, "memory_total",
1077
             "Total amount of memory of physical machine"),
1078
  }
1079

    
1080

    
1081
def _GetGroup(cb):
1082
  """Build function for calling another function with an node group.
1083

1084
  @param cb: The callback to be called with the nodegroup
1085

1086
  """
1087
  def fn(ctx, node):
1088
    """Get group data for a node.
1089

1090
    @type ctx: L{NodeQueryData}
1091
    @type inst: L{objects.Node}
1092
    @param inst: Node object
1093

1094
    """
1095
    ng = ctx.groups.get(node.group, None)
1096
    if ng is None:
1097
      # Nodes always have a group, or the configuration is corrupt
1098
      return _FS_UNAVAIL
1099

    
1100
    return cb(ctx, node, ng)
1101

    
1102
  return fn
1103

    
1104

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

1108
  @type ctx: L{NodeQueryData}
1109
  @type node: L{objects.Node}
1110
  @param node: Node object
1111
  @type ng: L{objects.NodeGroup}
1112
  @param ng: The node group this node belongs to
1113

1114
  """
1115
  return ng.name
1116

    
1117

    
1118
def _GetNodePower(ctx, node):
1119
  """Returns the node powered state
1120

1121
  @type ctx: L{NodeQueryData}
1122
  @type node: L{objects.Node}
1123
  @param node: Node object
1124

1125
  """
1126
  if ctx.oob_support[node.name]:
1127
    return node.powered
1128

    
1129
  return _FS_UNAVAIL
1130

    
1131

    
1132
def _GetNdParams(ctx, node, ng):
1133
  """Returns the ndparams for this node.
1134

1135
  @type ctx: L{NodeQueryData}
1136
  @type node: L{objects.Node}
1137
  @param node: Node object
1138
  @type ng: L{objects.NodeGroup}
1139
  @param ng: The node group this node belongs to
1140

1141
  """
1142
  return ctx.cluster.SimpleFillND(ng.FillND(node))
1143

    
1144

    
1145
def _GetLiveNodeField(field, kind, ctx, node):
1146
  """Gets the value of a "live" field from L{NodeQueryData}.
1147

1148
  @param field: Live field name
1149
  @param kind: Data kind, one of L{constants.QFT_ALL}
1150
  @type ctx: L{NodeQueryData}
1151
  @type node: L{objects.Node}
1152
  @param node: Node object
1153

1154
  """
1155
  if node.offline:
1156
    return _FS_OFFLINE
1157

    
1158
  if not node.vm_capable:
1159
    return _FS_UNAVAIL
1160

    
1161
  if not ctx.curlive_data:
1162
    return _FS_NODATA
1163

    
1164
  try:
1165
    value = ctx.curlive_data[field]
1166
  except KeyError:
1167
    return _FS_UNAVAIL
1168

    
1169
  if kind == QFT_TEXT:
1170
    return value
1171

    
1172
  assert kind in (QFT_NUMBER, QFT_UNIT)
1173

    
1174
  # Try to convert into number
1175
  try:
1176
    return int(value)
1177
  except (ValueError, TypeError):
1178
    logging.exception("Failed to convert node field '%s' (value %r) to int",
1179
                      value, field)
1180
    return _FS_UNAVAIL
1181

    
1182

    
1183
def _GetNodeHvState(_, node):
1184
  """Converts node's hypervisor state for query result.
1185

1186
  """
1187
  hv_state = node.hv_state
1188

    
1189
  if hv_state is None:
1190
    return _FS_UNAVAIL
1191

    
1192
  return dict((name, value.ToDict()) for (name, value) in hv_state.items())
1193

    
1194

    
1195
def _GetNodeDiskState(_, node):
1196
  """Converts node's disk state for query result.
1197

1198
  """
1199
  disk_state = node.disk_state
1200

    
1201
  if disk_state is None:
1202
    return _FS_UNAVAIL
1203

    
1204
  return dict((disk_kind, dict((name, value.ToDict())
1205
                               for (name, value) in kind_state.items()))
1206
              for (disk_kind, kind_state) in disk_state.items())
1207

    
1208

    
1209
def _BuildNodeFields():
1210
  """Builds list of fields for node queries.
1211

1212
  """
1213
  fields = [
1214
    (_MakeField("pip", "PrimaryIP", QFT_TEXT, "Primary IP address"),
1215
     NQ_CONFIG, 0, _GetItemAttr("primary_ip")),
1216
    (_MakeField("sip", "SecondaryIP", QFT_TEXT, "Secondary IP address"),
1217
     NQ_CONFIG, 0, _GetItemAttr("secondary_ip")),
1218
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0,
1219
     lambda ctx, node: list(node.GetTags())),
1220
    (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"),
1221
     NQ_CONFIG, 0, lambda ctx, node: node.name == ctx.master_name),
1222
    (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0,
1223
     _GetGroup(_GetNodeGroup)),
1224
    (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"),
1225
     NQ_CONFIG, 0, _GetItemAttr("group")),
1226
    (_MakeField("powered", "Powered", QFT_BOOL,
1227
                "Whether node is thought to be powered on"),
1228
     NQ_OOB, 0, _GetNodePower),
1229
    (_MakeField("ndparams", "NodeParameters", QFT_OTHER,
1230
                "Merged node parameters"),
1231
     NQ_GROUP, 0, _GetGroup(_GetNdParams)),
1232
    (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER,
1233
                "Custom node parameters"),
1234
      NQ_GROUP, 0, _GetItemAttr("ndparams")),
1235
    (_MakeField("hv_state", "HypervisorState", QFT_OTHER, "Hypervisor state"),
1236
     NQ_CONFIG, 0, _GetNodeHvState),
1237
    (_MakeField("disk_state", "DiskState", QFT_OTHER, "Disk state"),
1238
     NQ_CONFIG, 0, _GetNodeDiskState),
1239
    ]
1240

    
1241
  fields.extend(_BuildNDFields(False))
1242

    
1243
  # Node role
1244
  role_values = (constants.NR_MASTER, constants.NR_MCANDIDATE,
1245
                 constants.NR_REGULAR, constants.NR_DRAINED,
1246
                 constants.NR_OFFLINE)
1247
  role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate,"
1248
              " \"%s\" for regular, \"%s\" for a drained, \"%s\" for offline" %
1249
              role_values)
1250
  fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
1251
                 lambda ctx, node: _GetNodeRole(node, ctx.master_name)))
1252
  assert set(role_values) == constants.NR_ALL
1253

    
1254
  def _GetLength(getter):
1255
    return lambda ctx, node: len(getter(ctx)[node.name])
1256

    
1257
  def _GetList(getter):
1258
    return lambda ctx, node: list(getter(ctx)[node.name])
1259

    
1260
  # Add fields operating on instance lists
1261
  for prefix, titleprefix, docword, getter in \
1262
      [("p", "Pri", "primary", operator.attrgetter("node_to_primary")),
1263
       ("s", "Sec", "secondary", operator.attrgetter("node_to_secondary"))]:
1264
    # TODO: Allow filterting by hostname in list
1265
    fields.extend([
1266
      (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER,
1267
                  "Number of instances with this node as %s" % docword),
1268
       NQ_INST, 0, _GetLength(getter)),
1269
      (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
1270
                  QFT_OTHER,
1271
                  "List of instances with this node as %s" % docword),
1272
       NQ_INST, 0, _GetList(getter)),
1273
      ])
1274

    
1275
  # Add simple fields
1276
  fields.extend([
1277
    (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1278
    for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()
1279
    ])
1280

    
1281
  # Add fields requiring live data
1282
  fields.extend([
1283
    (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1284
     compat.partial(_GetLiveNodeField, nfield, kind))
1285
    for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()
1286
    ])
1287

    
1288
  # Add timestamps
1289
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1290

    
1291
  return _PrepareFieldList(fields, [])
1292

    
1293

    
1294
class InstanceQueryData:
1295
  """Data container for instance data queries.
1296

1297
  """
1298
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
1299
               live_data, wrongnode_inst, console, nodes, groups):
1300
    """Initializes this class.
1301

1302
    @param instances: List of instance objects
1303
    @param cluster: Cluster object
1304
    @type disk_usage: dict; instance name as key
1305
    @param disk_usage: Per-instance disk usage
1306
    @type offline_nodes: list of strings
1307
    @param offline_nodes: List of offline nodes
1308
    @type bad_nodes: list of strings
1309
    @param bad_nodes: List of faulty nodes
1310
    @type live_data: dict; instance name as key
1311
    @param live_data: Per-instance live data
1312
    @type wrongnode_inst: set
1313
    @param wrongnode_inst: Set of instances running on wrong node(s)
1314
    @type console: dict; instance name as key
1315
    @param console: Per-instance console information
1316
    @type nodes: dict; node name as key
1317
    @param nodes: Node objects
1318

1319
    """
1320
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
1321
           "Offline nodes not included in bad nodes"
1322
    assert not (set(live_data.keys()) & set(bad_nodes)), \
1323
           "Found live data for bad or offline nodes"
1324

    
1325
    self.instances = instances
1326
    self.cluster = cluster
1327
    self.disk_usage = disk_usage
1328
    self.offline_nodes = offline_nodes
1329
    self.bad_nodes = bad_nodes
1330
    self.live_data = live_data
1331
    self.wrongnode_inst = wrongnode_inst
1332
    self.console = console
1333
    self.nodes = nodes
1334
    self.groups = groups
1335

    
1336
    # Used for individual rows
1337
    self.inst_hvparams = None
1338
    self.inst_beparams = None
1339
    self.inst_osparams = None
1340
    self.inst_nicparams = None
1341

    
1342
  def __iter__(self):
1343
    """Iterate over all instances.
1344

1345
    This function has side-effects and only one instance of the resulting
1346
    generator should be used at a time.
1347

1348
    """
1349
    for inst in self.instances:
1350
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1351
      self.inst_beparams = self.cluster.FillBE(inst)
1352
      self.inst_osparams = self.cluster.SimpleFillOS(inst.os, inst.osparams)
1353
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1354
                             for nic in inst.nics]
1355

    
1356
      yield inst
1357

    
1358

    
1359
def _GetInstOperState(ctx, inst):
1360
  """Get instance's operational status.
1361

1362
  @type ctx: L{InstanceQueryData}
1363
  @type inst: L{objects.Instance}
1364
  @param inst: Instance object
1365

1366
  """
1367
  # Can't use RS_OFFLINE here as it would describe the instance to
1368
  # be offline when we actually don't know due to missing data
1369
  if inst.primary_node in ctx.bad_nodes:
1370
    return _FS_NODATA
1371
  else:
1372
    return bool(ctx.live_data.get(inst.name))
1373

    
1374

    
1375
def _GetInstLiveData(name):
1376
  """Build function for retrieving live data.
1377

1378
  @type name: string
1379
  @param name: Live data field name
1380

1381
  """
1382
  def fn(ctx, inst):
1383
    """Get live data for an instance.
1384

1385
    @type ctx: L{InstanceQueryData}
1386
    @type inst: L{objects.Instance}
1387
    @param inst: Instance object
1388

1389
    """
1390
    if (inst.primary_node in ctx.bad_nodes or
1391
        inst.primary_node in ctx.offline_nodes):
1392
      # Can't use RS_OFFLINE here as it would describe the instance to be
1393
      # offline when we actually don't know due to missing data
1394
      return _FS_NODATA
1395

    
1396
    if inst.name in ctx.live_data:
1397
      data = ctx.live_data[inst.name]
1398
      if name in data:
1399
        return data[name]
1400

    
1401
    return _FS_UNAVAIL
1402

    
1403
  return fn
1404

    
1405

    
1406
def _GetInstStatus(ctx, inst):
1407
  """Get instance status.
1408

1409
  @type ctx: L{InstanceQueryData}
1410
  @type inst: L{objects.Instance}
1411
  @param inst: Instance object
1412

1413
  """
1414
  if inst.primary_node in ctx.offline_nodes:
1415
    return constants.INSTST_NODEOFFLINE
1416

    
1417
  if inst.primary_node in ctx.bad_nodes:
1418
    return constants.INSTST_NODEDOWN
1419

    
1420
  if bool(ctx.live_data.get(inst.name)):
1421
    if inst.name in ctx.wrongnode_inst:
1422
      return constants.INSTST_WRONGNODE
1423
    elif inst.admin_state == constants.ADMINST_UP:
1424
      return constants.INSTST_RUNNING
1425
    else:
1426
      return constants.INSTST_ERRORUP
1427

    
1428
  if inst.admin_state == constants.ADMINST_UP:
1429
    return constants.INSTST_ERRORDOWN
1430
  elif inst.admin_state == constants.ADMINST_DOWN:
1431
    return constants.INSTST_ADMINDOWN
1432

    
1433
  return constants.INSTST_ADMINOFFLINE
1434

    
1435

    
1436
def _GetInstDiskSize(index):
1437
  """Build function for retrieving disk size.
1438

1439
  @type index: int
1440
  @param index: Disk index
1441

1442
  """
1443
  def fn(_, inst):
1444
    """Get size of a disk.
1445

1446
    @type inst: L{objects.Instance}
1447
    @param inst: Instance object
1448

1449
    """
1450
    try:
1451
      return inst.disks[index].size
1452
    except IndexError:
1453
      return _FS_UNAVAIL
1454

    
1455
  return fn
1456

    
1457

    
1458
def _GetInstNic(index, cb):
1459
  """Build function for calling another function with an instance NIC.
1460

1461
  @type index: int
1462
  @param index: NIC index
1463
  @type cb: callable
1464
  @param cb: Callback
1465

1466
  """
1467
  def fn(ctx, inst):
1468
    """Call helper function with instance NIC.
1469

1470
    @type ctx: L{InstanceQueryData}
1471
    @type inst: L{objects.Instance}
1472
    @param inst: Instance object
1473

1474
    """
1475
    try:
1476
      nic = inst.nics[index]
1477
    except IndexError:
1478
      return _FS_UNAVAIL
1479

    
1480
    return cb(ctx, index, nic)
1481

    
1482
  return fn
1483

    
1484

    
1485
def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613
1486
  """Get a NIC's IP address.
1487

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

1492
  """
1493
  if nic.ip is None:
1494
    return _FS_UNAVAIL
1495
  else:
1496
    return nic.ip
1497

    
1498

    
1499
def _GetInstNicBridge(ctx, index, _):
1500
  """Get a NIC's bridge.
1501

1502
  @type ctx: L{InstanceQueryData}
1503
  @type index: int
1504
  @param index: NIC index
1505

1506
  """
1507
  assert len(ctx.inst_nicparams) >= index
1508

    
1509
  nicparams = ctx.inst_nicparams[index]
1510

    
1511
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1512
    return nicparams[constants.NIC_LINK]
1513
  else:
1514
    return _FS_UNAVAIL
1515

    
1516

    
1517
def _GetInstAllNicBridges(ctx, inst):
1518
  """Get all network bridges for an instance.
1519

1520
  @type ctx: L{InstanceQueryData}
1521
  @type inst: L{objects.Instance}
1522
  @param inst: Instance object
1523

1524
  """
1525
  assert len(ctx.inst_nicparams) == len(inst.nics)
1526

    
1527
  result = []
1528

    
1529
  for nicp in ctx.inst_nicparams:
1530
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1531
      result.append(nicp[constants.NIC_LINK])
1532
    else:
1533
      result.append(None)
1534

    
1535
  assert len(result) == len(inst.nics)
1536

    
1537
  return result
1538

    
1539

    
1540
def _GetInstNicParam(name):
1541
  """Build function for retrieving a NIC parameter.
1542

1543
  @type name: string
1544
  @param name: Parameter name
1545

1546
  """
1547
  def fn(ctx, index, _):
1548
    """Get a NIC's bridge.
1549

1550
    @type ctx: L{InstanceQueryData}
1551
    @type inst: L{objects.Instance}
1552
    @param inst: Instance object
1553
    @type nic: L{objects.NIC}
1554
    @param nic: NIC object
1555

1556
    """
1557
    assert len(ctx.inst_nicparams) >= index
1558
    return ctx.inst_nicparams[index][name]
1559

    
1560
  return fn
1561

    
1562

    
1563
def _GetInstanceNetworkFields():
1564
  """Get instance fields involving network interfaces.
1565

1566
  @return: Tuple containing list of field definitions used as input for
1567
    L{_PrepareFieldList} and a list of aliases
1568

1569
  """
1570
  nic_mac_fn = lambda ctx, _, nic: nic.mac
1571
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1572
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1573

    
1574
  fields = [
1575
    # All NICs
1576
    (_MakeField("nic.count", "NICs", QFT_NUMBER,
1577
                "Number of network interfaces"),
1578
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.nics)),
1579
    (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER,
1580
                "List containing each network interface's MAC address"),
1581
     IQ_CONFIG, 0, lambda ctx, inst: [nic.mac for nic in inst.nics]),
1582
    (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
1583
                "List containing each network interface's IP address"),
1584
     IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
1585
    (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
1586
                "List containing each network interface's mode"), IQ_CONFIG, 0,
1587
     lambda ctx, inst: [nicp[constants.NIC_MODE]
1588
                        for nicp in ctx.inst_nicparams]),
1589
    (_MakeField("nic.links", "NIC_links", QFT_OTHER,
1590
                "List containing each network interface's link"), IQ_CONFIG, 0,
1591
     lambda ctx, inst: [nicp[constants.NIC_LINK]
1592
                        for nicp in ctx.inst_nicparams]),
1593
    (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
1594
                "List containing each network interface's bridge"),
1595
     IQ_CONFIG, 0, _GetInstAllNicBridges),
1596
    ]
1597

    
1598
  # NICs by number
1599
  for i in range(constants.MAX_NICS):
1600
    numtext = utils.FormatOrdinal(i + 1)
1601
    fields.extend([
1602
      (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT,
1603
                  "IP address of %s network interface" % numtext),
1604
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)),
1605
      (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
1606
                  "MAC address of %s network interface" % numtext),
1607
       IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
1608
      (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
1609
                  "Mode of %s network interface" % numtext),
1610
       IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
1611
      (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT,
1612
                  "Link of %s network interface" % numtext),
1613
       IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)),
1614
      (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
1615
                  "Bridge of %s network interface" % numtext),
1616
       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
1617
      ])
1618

    
1619
  aliases = [
1620
    # Legacy fields for first NIC
1621
    ("ip", "nic.ip/0"),
1622
    ("mac", "nic.mac/0"),
1623
    ("bridge", "nic.bridge/0"),
1624
    ("nic_mode", "nic.mode/0"),
1625
    ("nic_link", "nic.link/0"),
1626
    ]
1627

    
1628
  return (fields, aliases)
1629

    
1630

    
1631
def _GetInstDiskUsage(ctx, inst):
1632
  """Get disk usage for an instance.
1633

1634
  @type ctx: L{InstanceQueryData}
1635
  @type inst: L{objects.Instance}
1636
  @param inst: Instance object
1637

1638
  """
1639
  usage = ctx.disk_usage[inst.name]
1640

    
1641
  if usage is None:
1642
    usage = 0
1643

    
1644
  return usage
1645

    
1646

    
1647
def _GetInstanceConsole(ctx, inst):
1648
  """Get console information for instance.
1649

1650
  @type ctx: L{InstanceQueryData}
1651
  @type inst: L{objects.Instance}
1652
  @param inst: Instance object
1653

1654
  """
1655
  consinfo = ctx.console[inst.name]
1656

    
1657
  if consinfo is None:
1658
    return _FS_UNAVAIL
1659

    
1660
  return consinfo
1661

    
1662

    
1663
def _GetInstanceDiskFields():
1664
  """Get instance fields involving disks.
1665

1666
  @return: List of field definitions used as input for L{_PrepareFieldList}
1667

1668
  """
1669
  fields = [
1670
    (_MakeField("disk_usage", "DiskUsage", QFT_UNIT,
1671
                "Total disk space used by instance on each of its nodes;"
1672
                " this is not the disk size visible to the instance, but"
1673
                " the usage on the node"),
1674
     IQ_DISKUSAGE, 0, _GetInstDiskUsage),
1675
    (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"),
1676
     IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
1677
    (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
1678
     IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
1679
    ]
1680

    
1681
  # Disks by number
1682
  fields.extend([
1683
    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1684
                "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
1685
     IQ_CONFIG, 0, _GetInstDiskSize(i))
1686
    for i in range(constants.MAX_DISKS)
1687
    ])
1688

    
1689
  return fields
1690

    
1691

    
1692
def _GetInstanceParameterFields():
1693
  """Get instance fields involving parameters.
1694

1695
  @return: List of field definitions used as input for L{_PrepareFieldList}
1696

1697
  """
1698
  # TODO: Consider moving titles closer to constants
1699
  be_title = {
1700
    constants.BE_AUTO_BALANCE: "Auto_balance",
1701
    constants.BE_MAXMEM: "ConfigMaxMem",
1702
    constants.BE_MINMEM: "ConfigMinMem",
1703
    constants.BE_VCPUS: "ConfigVCPUs",
1704
    }
1705

    
1706
  hv_title = {
1707
    constants.HV_ACPI: "ACPI",
1708
    constants.HV_BOOT_ORDER: "Boot_order",
1709
    constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1710
    constants.HV_DISK_TYPE: "Disk_type",
1711
    constants.HV_INITRD_PATH: "Initrd_path",
1712
    constants.HV_KERNEL_PATH: "Kernel_path",
1713
    constants.HV_NIC_TYPE: "NIC_type",
1714
    constants.HV_PAE: "PAE",
1715
    constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1716
    }
1717

    
1718
  fields = [
1719
    # Filled parameters
1720
    (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
1721
                "Hypervisor parameters (merged)"),
1722
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
1723
    (_MakeField("beparams", "BackendParameters", QFT_OTHER,
1724
                "Backend parameters (merged)"),
1725
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
1726
    (_MakeField("osparams", "OpSysParameters", QFT_OTHER,
1727
                "Operating system parameters (merged)"),
1728
     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_osparams),
1729

    
1730
    # Unfilled parameters
1731
    (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
1732
                "Custom hypervisor parameters"),
1733
     IQ_CONFIG, 0, _GetItemAttr("hvparams")),
1734
    (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
1735
                "Custom backend parameters",),
1736
     IQ_CONFIG, 0, _GetItemAttr("beparams")),
1737
    (_MakeField("custom_osparams", "CustomOpSysParameters", QFT_OTHER,
1738
                "Custom operating system parameters",),
1739
     IQ_CONFIG, 0, _GetItemAttr("osparams")),
1740
    (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
1741
                "Custom network interface parameters"),
1742
     IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1743
    ]
1744

    
1745
  # HV params
1746
  def _GetInstHvParam(name):
1747
    return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1748

    
1749
  fields.extend([
1750
    (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1751
                _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1752
     IQ_CONFIG, 0, _GetInstHvParam(name))
1753
    for name, kind in constants.HVS_PARAMETER_TYPES.items()
1754
    if name not in constants.HVC_GLOBALS
1755
    ])
1756

    
1757
  # BE params
1758
  def _GetInstBeParam(name):
1759
    return lambda ctx, _: ctx.inst_beparams.get(name, None)
1760

    
1761
  fields.extend([
1762
    (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1763
                _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1764
     IQ_CONFIG, 0, _GetInstBeParam(name))
1765
    for name, kind in constants.BES_PARAMETER_TYPES.items()
1766
    ])
1767

    
1768
  return fields
1769

    
1770

    
1771
_INST_SIMPLE_FIELDS = {
1772
  "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"),
1773
  "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"),
1774
  "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"),
1775
  # Depending on the hypervisor, the port can be None
1776
  "network_port": ("Network_port", QFT_OTHER, 0,
1777
                   "Instance network port if available (e.g. for VNC console)"),
1778
  "os": ("OS", QFT_TEXT, 0, "Operating system"),
1779
  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"),
1780
  "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"),
1781
  }
1782

    
1783

    
1784
def _GetInstNodeGroup(ctx, default, node_name):
1785
  """Gets group UUID of an instance node.
1786

1787
  @type ctx: L{InstanceQueryData}
1788
  @param default: Default value
1789
  @type node_name: string
1790
  @param node_name: Node name
1791

1792
  """
1793
  try:
1794
    node = ctx.nodes[node_name]
1795
  except KeyError:
1796
    return default
1797
  else:
1798
    return node.group
1799

    
1800

    
1801
def _GetInstNodeGroupName(ctx, default, node_name):
1802
  """Gets group name of an instance node.
1803

1804
  @type ctx: L{InstanceQueryData}
1805
  @param default: Default value
1806
  @type node_name: string
1807
  @param node_name: Node name
1808

1809
  """
1810
  try:
1811
    node = ctx.nodes[node_name]
1812
  except KeyError:
1813
    return default
1814

    
1815
  try:
1816
    group = ctx.groups[node.group]
1817
  except KeyError:
1818
    return default
1819

    
1820
  return group.name
1821

    
1822

    
1823
def _BuildInstanceFields():
1824
  """Builds list of fields for instance queries.
1825

1826
  """
1827
  fields = [
1828
    (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
1829
     IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")),
1830
    (_MakeField("pnode.group", "PrimaryNodeGroup", QFT_TEXT,
1831
                "Primary node's group"),
1832
     IQ_NODES, 0,
1833
     lambda ctx, inst: _GetInstNodeGroupName(ctx, _FS_UNAVAIL,
1834
                                             inst.primary_node)),
1835
    (_MakeField("pnode.group.uuid", "PrimaryNodeGroupUUID", QFT_TEXT,
1836
                "Primary node's group UUID"),
1837
     IQ_NODES, 0,
1838
     lambda ctx, inst: _GetInstNodeGroup(ctx, _FS_UNAVAIL, inst.primary_node)),
1839
    # TODO: Allow filtering by secondary node as hostname
1840
    (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
1841
                "Secondary nodes; usually this will just be one node"),
1842
     IQ_CONFIG, 0, lambda ctx, inst: list(inst.secondary_nodes)),
1843
    (_MakeField("snodes.group", "SecondaryNodesGroups", QFT_OTHER,
1844
                "Node groups of secondary nodes"),
1845
     IQ_NODES, 0,
1846
     lambda ctx, inst: map(compat.partial(_GetInstNodeGroupName, ctx, None),
1847
                           inst.secondary_nodes)),
1848
    (_MakeField("snodes.group.uuid", "SecondaryNodesGroupsUUID", QFT_OTHER,
1849
                "Node group UUIDs of secondary nodes"),
1850
     IQ_NODES, 0,
1851
     lambda ctx, inst: map(compat.partial(_GetInstNodeGroup, ctx, None),
1852
                           inst.secondary_nodes)),
1853
    (_MakeField("admin_state", "InstanceState", QFT_TEXT,
1854
                "Desired state of instance"),
1855
     IQ_CONFIG, 0, _GetItemAttr("admin_state")),
1856
    (_MakeField("admin_up", "Autostart", QFT_BOOL,
1857
                "Desired state of instance"),
1858
     IQ_CONFIG, 0, lambda ctx, inst: inst.admin_state == constants.ADMINST_UP),
1859
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
1860
     lambda ctx, inst: list(inst.GetTags())),
1861
    (_MakeField("console", "Console", QFT_OTHER,
1862
                "Instance console information"), IQ_CONSOLE, 0,
1863
     _GetInstanceConsole),
1864
    ]
1865

    
1866
  # Add simple fields
1867
  fields.extend([
1868
    (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1869
    for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()
1870
    ])
1871

    
1872
  # Fields requiring talking to the node
1873
  fields.extend([
1874
    (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
1875
     IQ_LIVE, 0, _GetInstOperState),
1876
    (_MakeField("oper_ram", "Memory", QFT_UNIT,
1877
                "Actual memory usage as seen by hypervisor"),
1878
     IQ_LIVE, 0, _GetInstLiveData("memory")),
1879
    (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
1880
                "Actual number of VCPUs as seen by hypervisor"),
1881
     IQ_LIVE, 0, _GetInstLiveData("vcpus")),
1882
    ])
1883

    
1884
  # Status field
1885
  status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
1886
                   constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
1887
                   constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
1888
                   constants.INSTST_NODEOFFLINE, constants.INSTST_ADMINOFFLINE)
1889
  status_doc = ("Instance status; \"%s\" if instance is set to be running"
1890
                " and actually is, \"%s\" if instance is stopped and"
1891
                " is not running, \"%s\" if instance running, but not on its"
1892
                " designated primary node, \"%s\" if instance should be"
1893
                " stopped, but is actually running, \"%s\" if instance should"
1894
                " run, but doesn't, \"%s\" if instance's primary node is down,"
1895
                " \"%s\" if instance's primary node is marked offline,"
1896
                " \"%s\" if instance is offline and does not use dynamic"
1897
                " resources" % status_values)
1898
  fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
1899
                 IQ_LIVE, 0, _GetInstStatus))
1900
  assert set(status_values) == constants.INSTST_ALL, \
1901
         "Status documentation mismatch"
1902

    
1903
  (network_fields, network_aliases) = _GetInstanceNetworkFields()
1904

    
1905
  fields.extend(network_fields)
1906
  fields.extend(_GetInstanceParameterFields())
1907
  fields.extend(_GetInstanceDiskFields())
1908
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1909

    
1910
  aliases = [
1911
    ("vcpus", "be/vcpus"),
1912
    ("be/memory", "be/maxmem"),
1913
    ("sda_size", "disk.size/0"),
1914
    ("sdb_size", "disk.size/1"),
1915
    ] + network_aliases
1916

    
1917
  return _PrepareFieldList(fields, aliases)
1918

    
1919

    
1920
class LockQueryData:
1921
  """Data container for lock data queries.
1922

1923
  """
1924
  def __init__(self, lockdata):
1925
    """Initializes this class.
1926

1927
    """
1928
    self.lockdata = lockdata
1929

    
1930
  def __iter__(self):
1931
    """Iterate over all locks.
1932

1933
    """
1934
    return iter(self.lockdata)
1935

    
1936

    
1937
def _GetLockOwners(_, data):
1938
  """Returns a sorted list of a lock's current owners.
1939

1940
  """
1941
  (_, _, owners, _) = data
1942

    
1943
  if owners:
1944
    owners = utils.NiceSort(owners)
1945

    
1946
  return owners
1947

    
1948

    
1949
def _GetLockPending(_, data):
1950
  """Returns a sorted list of a lock's pending acquires.
1951

1952
  """
1953
  (_, _, _, pending) = data
1954

    
1955
  if pending:
1956
    pending = [(mode, utils.NiceSort(names))
1957
               for (mode, names) in pending]
1958

    
1959
  return pending
1960

    
1961

    
1962
def _BuildLockFields():
1963
  """Builds list of fields for lock queries.
1964

1965
  """
1966
  return _PrepareFieldList([
1967
    # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
1968
    (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
1969
     lambda ctx, (name, mode, owners, pending): name),
1970
    (_MakeField("mode", "Mode", QFT_OTHER,
1971
                "Mode in which the lock is currently acquired"
1972
                " (exclusive or shared)"),
1973
     LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
1974
    (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
1975
     LQ_OWNER, 0, _GetLockOwners),
1976
    (_MakeField("pending", "Pending", QFT_OTHER,
1977
                "Threads waiting for the lock"),
1978
     LQ_PENDING, 0, _GetLockPending),
1979
    ], [])
1980

    
1981

    
1982
class GroupQueryData:
1983
  """Data container for node group data queries.
1984

1985
  """
1986
  def __init__(self, cluster, groups, group_to_nodes, group_to_instances):
1987
    """Initializes this class.
1988

1989
    @param cluster: Cluster object
1990
    @param groups: List of node group objects
1991
    @type group_to_nodes: dict; group UUID as key
1992
    @param group_to_nodes: Per-group list of nodes
1993
    @type group_to_instances: dict; group UUID as key
1994
    @param group_to_instances: Per-group list of (primary) instances
1995

1996
    """
1997
    self.groups = groups
1998
    self.group_to_nodes = group_to_nodes
1999
    self.group_to_instances = group_to_instances
2000
    self.cluster = cluster
2001

    
2002
    # Used for individual rows
2003
    self.group_ipolicy = None
2004
    self.ndparams = None
2005

    
2006
  def __iter__(self):
2007
    """Iterate over all node groups.
2008

2009
    This function has side-effects and only one instance of the resulting
2010
    generator should be used at a time.
2011

2012
    """
2013
    for group in self.groups:
2014
      self.group_ipolicy = self.cluster.SimpleFillIPolicy(group.ipolicy)
2015
      self.ndparams = self.cluster.SimpleFillND(group.ndparams)
2016
      yield group
2017

    
2018

    
2019
_GROUP_SIMPLE_FIELDS = {
2020
  "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
2021
  "name": ("Group", QFT_TEXT, "Group name"),
2022
  "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
2023
  "uuid": ("UUID", QFT_TEXT, "Group UUID"),
2024
  }
2025

    
2026

    
2027
def _BuildGroupFields():
2028
  """Builds list of fields for node group queries.
2029

2030
  """
2031
  # Add simple fields
2032
  fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
2033
             _GetItemAttr(name))
2034
            for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
2035

    
2036
  def _GetLength(getter):
2037
    return lambda ctx, group: len(getter(ctx)[group.uuid])
2038

    
2039
  def _GetSortedList(getter):
2040
    return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
2041

    
2042
  group_to_nodes = operator.attrgetter("group_to_nodes")
2043
  group_to_instances = operator.attrgetter("group_to_instances")
2044

    
2045
  # Add fields for nodes
2046
  fields.extend([
2047
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
2048
     GQ_NODE, 0, _GetLength(group_to_nodes)),
2049
    (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
2050
     GQ_NODE, 0, _GetSortedList(group_to_nodes)),
2051
    ])
2052

    
2053
  # Add fields for instances
2054
  fields.extend([
2055
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
2056
                "Number of primary instances"),
2057
     GQ_INST, 0, _GetLength(group_to_instances)),
2058
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
2059
                "List of primary instances"),
2060
     GQ_INST, 0, _GetSortedList(group_to_instances)),
2061
    ])
2062

    
2063
  # Other fields
2064
  fields.extend([
2065
    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), GQ_CONFIG, 0,
2066
     lambda ctx, group: list(group.GetTags())),
2067
    (_MakeField("ipolicy", "InstancePolicy", QFT_OTHER,
2068
                "Instance policy limitations (merged)"),
2069
     GQ_CONFIG, 0, lambda ctx, _: ctx.group_ipolicy),
2070
    (_MakeField("custom_ipolicy", "CustomInstancePolicy", QFT_OTHER,
2071
                "Custom instance policy limitations"),
2072
     GQ_CONFIG, 0, _GetItemAttr("ipolicy")),
2073
    (_MakeField("custom_ndparams", "CustomNDParams", QFT_OTHER,
2074
                "Custom node parameters"),
2075
     GQ_CONFIG, 0, _GetItemAttr("ndparams")),
2076
    (_MakeField("ndparams", "NDParams", QFT_OTHER,
2077
                "Node parameters"),
2078
     GQ_CONFIG, 0, lambda ctx, _: ctx.ndparams),
2079
    ])
2080

    
2081
  # ND parameters
2082
  fields.extend(_BuildNDFields(True))
2083

    
2084
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
2085

    
2086
  return _PrepareFieldList(fields, [])
2087

    
2088

    
2089
class OsInfo(objects.ConfigObject):
2090
  __slots__ = [
2091
    "name",
2092
    "valid",
2093
    "hidden",
2094
    "blacklisted",
2095
    "variants",
2096
    "api_versions",
2097
    "parameters",
2098
    "node_status",
2099
    ]
2100

    
2101

    
2102
def _BuildOsFields():
2103
  """Builds list of fields for operating system queries.
2104

2105
  """
2106
  fields = [
2107
    (_MakeField("name", "Name", QFT_TEXT, "Operating system name"),
2108
     None, 0, _GetItemAttr("name")),
2109
    (_MakeField("valid", "Valid", QFT_BOOL,
2110
                "Whether operating system definition is valid"),
2111
     None, 0, _GetItemAttr("valid")),
2112
    (_MakeField("hidden", "Hidden", QFT_BOOL,
2113
                "Whether operating system is hidden"),
2114
     None, 0, _GetItemAttr("hidden")),
2115
    (_MakeField("blacklisted", "Blacklisted", QFT_BOOL,
2116
                "Whether operating system is blacklisted"),
2117
     None, 0, _GetItemAttr("blacklisted")),
2118
    (_MakeField("variants", "Variants", QFT_OTHER,
2119
                "Operating system variants"),
2120
     None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("variants"))),
2121
    (_MakeField("api_versions", "ApiVersions", QFT_OTHER,
2122
                "Operating system API versions"),
2123
     None, 0, _ConvWrap(sorted, _GetItemAttr("api_versions"))),
2124
    (_MakeField("parameters", "Parameters", QFT_OTHER,
2125
                "Operating system parameters"),
2126
     None, 0, _ConvWrap(compat.partial(utils.NiceSort, key=compat.fst),
2127
                        _GetItemAttr("parameters"))),
2128
    (_MakeField("node_status", "NodeStatus", QFT_OTHER,
2129
                "Status from node"),
2130
     None, 0, _GetItemAttr("node_status")),
2131
    ]
2132

    
2133
  return _PrepareFieldList(fields, [])
2134

    
2135

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

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

2142
  """
2143
  if job is None:
2144
    return _FS_UNAVAIL
2145
  else:
2146
    return fn(job)
2147

    
2148

    
2149
def _JobUnavail(inner):
2150
  """Wrapper for L{_JobUnavailInner}.
2151

2152
  """
2153
  return compat.partial(_JobUnavailInner, inner)
2154

    
2155

    
2156
def _PerJobOpInner(fn, job):
2157
  """Executes a function per opcode in a job.
2158

2159
  """
2160
  return map(fn, job.ops)
2161

    
2162

    
2163
def _PerJobOp(fn):
2164
  """Wrapper for L{_PerJobOpInner}.
2165

2166
  """
2167
  return _JobUnavail(compat.partial(_PerJobOpInner, fn))
2168

    
2169

    
2170
def _JobTimestampInner(fn, job):
2171
  """Converts unavailable timestamp to L{_FS_UNAVAIL}.
2172

2173
  """
2174
  timestamp = fn(job)
2175

    
2176
  if timestamp is None:
2177
    return _FS_UNAVAIL
2178
  else:
2179
    return timestamp
2180

    
2181

    
2182
def _JobTimestamp(fn):
2183
  """Wrapper for L{_JobTimestampInner}.
2184

2185
  """
2186
  return _JobUnavail(compat.partial(_JobTimestampInner, fn))
2187

    
2188

    
2189
def _BuildJobFields():
2190
  """Builds list of fields for job queries.
2191

2192
  """
2193
  fields = [
2194
    (_MakeField("id", "ID", QFT_TEXT, "Job ID"),
2195
     None, 0, lambda _, (job_id, job): job_id),
2196
    (_MakeField("status", "Status", QFT_TEXT, "Job status"),
2197
     None, 0, _JobUnavail(lambda job: job.CalcStatus())),
2198
    (_MakeField("priority", "Priority", QFT_NUMBER,
2199
                ("Current job priority (%s to %s)" %
2200
                 (constants.OP_PRIO_LOWEST, constants.OP_PRIO_HIGHEST))),
2201
     None, 0, _JobUnavail(lambda job: job.CalcPriority())),
2202
    (_MakeField("ops", "OpCodes", QFT_OTHER, "List of all opcodes"),
2203
     None, 0, _PerJobOp(lambda op: op.input.__getstate__())),
2204
    (_MakeField("opresult", "OpCode_result", QFT_OTHER,
2205
                "List of opcodes results"),
2206
     None, 0, _PerJobOp(operator.attrgetter("result"))),
2207
    (_MakeField("opstatus", "OpCode_status", QFT_OTHER,
2208
                "List of opcodes status"),
2209
     None, 0, _PerJobOp(operator.attrgetter("status"))),
2210
    (_MakeField("oplog", "OpCode_log", QFT_OTHER,
2211
                "List of opcode output logs"),
2212
     None, 0, _PerJobOp(operator.attrgetter("log"))),
2213
    (_MakeField("opstart", "OpCode_start", QFT_OTHER,
2214
                "List of opcode start timestamps (before acquiring locks)"),
2215
     None, 0, _PerJobOp(operator.attrgetter("start_timestamp"))),
2216
    (_MakeField("opexec", "OpCode_exec", QFT_OTHER,
2217
                "List of opcode execution start timestamps (after acquiring"
2218
                " locks)"),
2219
     None, 0, _PerJobOp(operator.attrgetter("exec_timestamp"))),
2220
    (_MakeField("opend", "OpCode_end", QFT_OTHER,
2221
                "List of opcode execution end timestamps"),
2222
     None, 0, _PerJobOp(operator.attrgetter("end_timestamp"))),
2223
    (_MakeField("oppriority", "OpCode_prio", QFT_OTHER,
2224
                "List of opcode priorities"),
2225
     None, 0, _PerJobOp(operator.attrgetter("priority"))),
2226
    (_MakeField("received_ts", "Received", QFT_OTHER,
2227
                "Timestamp of when job was received"),
2228
     None, 0, _JobTimestamp(operator.attrgetter("received_timestamp"))),
2229
    (_MakeField("start_ts", "Start", QFT_OTHER,
2230
                "Timestamp of job start"),
2231
     None, 0, _JobTimestamp(operator.attrgetter("start_timestamp"))),
2232
    (_MakeField("end_ts", "End", QFT_OTHER,
2233
                "Timestamp of job end"),
2234
     None, 0, _JobTimestamp(operator.attrgetter("end_timestamp"))),
2235
    (_MakeField("summary", "Summary", QFT_OTHER,
2236
                "List of per-opcode summaries"),
2237
     None, 0, _PerJobOp(lambda op: op.input.Summary())),
2238
    ]
2239

    
2240
  return _PrepareFieldList(fields, [])
2241

    
2242

    
2243
#: Fields available for node queries
2244
NODE_FIELDS = _BuildNodeFields()
2245

    
2246
#: Fields available for instance queries
2247
INSTANCE_FIELDS = _BuildInstanceFields()
2248

    
2249
#: Fields available for lock queries
2250
LOCK_FIELDS = _BuildLockFields()
2251

    
2252
#: Fields available for node group queries
2253
GROUP_FIELDS = _BuildGroupFields()
2254

    
2255
#: Fields available for operating system queries
2256
OS_FIELDS = _BuildOsFields()
2257

    
2258
#: Fields available for job queries
2259
JOB_FIELDS = _BuildJobFields()
2260

    
2261
#: All available resources
2262
ALL_FIELDS = {
2263
  constants.QR_INSTANCE: INSTANCE_FIELDS,
2264
  constants.QR_NODE: NODE_FIELDS,
2265
  constants.QR_LOCK: LOCK_FIELDS,
2266
  constants.QR_GROUP: GROUP_FIELDS,
2267
  constants.QR_OS: OS_FIELDS,
2268
  constants.QR_JOB: JOB_FIELDS,
2269
  }
2270

    
2271
#: All available field lists
2272
ALL_FIELD_LISTS = ALL_FIELDS.values()