Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ 1ae17369

History | View | Annotate | Download (35.4 kB)

1
#
2
#
3

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

    
21

    
22
"""Module for query operations
23

24
How it works:
25

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

47
@attention: Retrieval functions must be idempotent. They can be called multiple
48
  times, in any order and any number of times. This is important to keep in
49
  mind for implementing filters in the future.
50

51
"""
52

    
53
import logging
54
import operator
55
import re
56

    
57
from ganeti import constants
58
from ganeti import errors
59
from ganeti import utils
60
from ganeti import compat
61
from ganeti import objects
62
from ganeti import ht
63

    
64
from ganeti.constants import (QFT_UNKNOWN, QFT_TEXT, QFT_BOOL, QFT_NUMBER,
65
                              QFT_UNIT, QFT_TIMESTAMP, QFT_OTHER,
66
                              RS_NORMAL, RS_UNKNOWN, RS_NODATA,
67
                              RS_UNAVAIL, RS_OFFLINE)
68

    
69

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

    
74
(NQ_CONFIG,
75
 NQ_INST,
76
 NQ_LIVE,
77
 NQ_GROUP,
78
 NQ_OOB) = range(1, 6)
79

    
80
(IQ_CONFIG,
81
 IQ_LIVE,
82
 IQ_DISKUSAGE,
83
 IQ_CONSOLE) = range(100, 104)
84

    
85
(LQ_MODE,
86
 LQ_OWNER,
87
 LQ_PENDING) = range(10, 13)
88

    
89
(GQ_CONFIG,
90
 GQ_NODE,
91
 GQ_INST) = range(200, 203)
92

    
93

    
94
FIELD_NAME_RE = re.compile(r"^[a-z0-9/._]+$")
95
TITLE_RE = re.compile(r"^[^\s]+$")
96
DOC_RE = re.compile(r"^[A-Z].*[^.,?!]$")
97

    
98
#: Verification function for each field type
99
_VERIFY_FN = {
100
  QFT_UNKNOWN: ht.TNone,
101
  QFT_TEXT: ht.TString,
102
  QFT_BOOL: ht.TBool,
103
  QFT_NUMBER: ht.TInt,
104
  QFT_UNIT: ht.TInt,
105
  QFT_TIMESTAMP: ht.TOr(ht.TInt, ht.TFloat),
106
  QFT_OTHER: lambda _: True,
107
  }
108

    
109
# Unique objects for special field statuses
110
_FS_UNKNOWN = object()
111
_FS_NODATA = object()
112
_FS_UNAVAIL = object()
113
_FS_OFFLINE = object()
114

    
115
#: VType to QFT mapping
116
_VTToQFT = {
117
  # TODO: fix validation of empty strings
118
  constants.VTYPE_STRING: QFT_OTHER, # since VTYPE_STRINGs can be empty
119
  constants.VTYPE_MAYBE_STRING: QFT_OTHER,
120
  constants.VTYPE_BOOL: QFT_BOOL,
121
  constants.VTYPE_SIZE: QFT_UNIT,
122
  constants.VTYPE_INT: QFT_NUMBER,
123
  }
124

    
125

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

129
  """
130
  return _FS_UNKNOWN
131

    
132

    
133
def _GetQueryFields(fielddefs, selected):
134
  """Calculates the internal list of selected fields.
135

136
  Unknown fields are returned as L{constants.QFT_UNKNOWN}.
137

138
  @type fielddefs: dict
139
  @param fielddefs: Field definitions
140
  @type selected: list of strings
141
  @param selected: List of selected fields
142

143
  """
144
  result = []
145

    
146
  for name in selected:
147
    try:
148
      fdef = fielddefs[name]
149
    except KeyError:
150
      fdef = (_MakeField(name, name, QFT_UNKNOWN), None, _GetUnknownField)
151

    
152
    assert len(fdef) == 3
153

    
154
    result.append(fdef)
155

    
156
  return result
157

    
158

    
159
def GetAllFields(fielddefs):
160
  """Extract L{objects.QueryFieldDefinition} from field definitions.
161

162
  @rtype: list of L{objects.QueryFieldDefinition}
163

164
  """
165
  return [fdef for (fdef, _, _) in fielddefs]
166

    
167

    
168
class Query:
169
  def __init__(self, fieldlist, selected):
170
    """Initializes this class.
171

172
    The field definition is a dictionary with the field's name as a key and a
173
    tuple containing, in order, the field definition object
174
    (L{objects.QueryFieldDefinition}, the data kind to help calling code
175
    collect data and a retrieval function. The retrieval function is called
176
    with two parameters, in order, the data container and the item in container
177
    (see L{Query.Query}).
178

179
    Users of this class can call L{RequestedData} before preparing the data
180
    container to determine what data is needed.
181

182
    @type fieldlist: dictionary
183
    @param fieldlist: Field definitions
184
    @type selected: list of strings
185
    @param selected: List of selected fields
186

187
    """
188
    self._fields = _GetQueryFields(fieldlist, selected)
189

    
190
  def RequestedData(self):
191
    """Gets requested kinds of data.
192

193
    @rtype: frozenset
194

195
    """
196
    return frozenset(datakind
197
                     for (_, datakind, _) in self._fields
198
                     if datakind is not None)
199

    
200
  def GetFields(self):
201
    """Returns the list of fields for this query.
202

203
    Includes unknown fields.
204

205
    @rtype: List of L{objects.QueryFieldDefinition}
206

207
    """
208
    return GetAllFields(self._fields)
209

    
210
  def Query(self, ctx):
211
    """Execute a query.
212

213
    @param ctx: Data container passed to field retrieval functions, must
214
      support iteration using C{__iter__}
215

216
    """
217
    result = [[_ProcessResult(fn(ctx, item)) for (_, _, fn) in self._fields]
218
              for item in ctx]
219

    
220
    # Verify result
221
    if __debug__:
222
      for row in result:
223
        _VerifyResultRow(self._fields, row)
224

    
225
    return result
226

    
227
  def OldStyleQuery(self, ctx):
228
    """Query with "old" query result format.
229

230
    See L{Query.Query} for arguments.
231

232
    """
233
    unknown = set(fdef.name
234
                  for (fdef, _, _) in self._fields if fdef.kind == QFT_UNKNOWN)
235
    if unknown:
236
      raise errors.OpPrereqError("Unknown output fields selected: %s" %
237
                                 (utils.CommaJoin(unknown), ),
238
                                 errors.ECODE_INVAL)
239

    
240
    return [[value for (_, value) in row]
241
            for row in self.Query(ctx)]
242

    
243

    
244
def _ProcessResult(value):
245
  """Converts result values into externally-visible ones.
246

247
  """
248
  if value is _FS_UNKNOWN:
249
    return (RS_UNKNOWN, None)
250
  elif value is _FS_NODATA:
251
    return (RS_NODATA, None)
252
  elif value is _FS_UNAVAIL:
253
    return (RS_UNAVAIL, None)
254
  elif value is _FS_OFFLINE:
255
    return (RS_OFFLINE, None)
256
  else:
257
    return (RS_NORMAL, value)
258

    
259

    
260
def _VerifyResultRow(fields, row):
261
  """Verifies the contents of a query result row.
262

263
  @type fields: list
264
  @param fields: Field definitions for result
265
  @type row: list of tuples
266
  @param row: Row data
267

268
  """
269
  assert len(row) == len(fields)
270
  errs = []
271
  for ((status, value), (fdef, _, _)) in zip(row, fields):
272
    if status == RS_NORMAL:
273
      if not _VERIFY_FN[fdef.kind](value):
274
        errs.append("normal field %s fails validation (value is %s)" %
275
                    (fdef.name, value))
276
    elif value is not None:
277
      errs.append("abnormal field %s has a non-None value" % fdef.name)
278
  assert not errs, ("Failed validation: %s in row %s" %
279
                    (utils.CommaJoin(errors), row))
280

    
281

    
282
def _PrepareFieldList(fields, aliases):
283
  """Prepares field list for use by L{Query}.
284

285
  Converts the list to a dictionary and does some verification.
286

287
  @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data
288
      kind, retrieval function)
289
  @param fields: List of fields, see L{Query.__init__} for a better
290
      description
291
  @type aliases: list of tuples; (alias, target)
292
  @param aliases: list of tuples containing aliases; for each
293
      alias/target pair, a duplicate will be created in the field list
294
  @rtype: dict
295
  @return: Field dictionary for L{Query}
296

297
  """
298
  if __debug__:
299
    duplicates = utils.FindDuplicates(fdef.title.lower()
300
                                      for (fdef, _, _) in fields)
301
    assert not duplicates, "Duplicate title(s) found: %r" % duplicates
302

    
303
  result = {}
304

    
305
  for field in fields:
306
    (fdef, _, fn) = field
307

    
308
    assert fdef.name and fdef.title, "Name and title are required"
309
    assert FIELD_NAME_RE.match(fdef.name)
310
    assert TITLE_RE.match(fdef.title)
311
    assert (fdef.doc is None or
312
            (DOC_RE.match(fdef.doc) and len(fdef.doc.splitlines()) == 1 and
313
             fdef.doc.strip() == fdef.doc))
314
    assert callable(fn)
315
    assert fdef.name not in result, \
316
           "Duplicate field name '%s' found" % fdef.name
317

    
318
    result[fdef.name] = field
319

    
320
  for alias, target in aliases:
321
    assert alias not in result, "Alias %s overrides an existing field" % alias
322
    assert target in result, "Missing target %s for alias %s" % (target, alias)
323
    (fdef, k, fn) = result[target]
324
    fdef = fdef.Copy()
325
    fdef.name = alias
326
    result[alias] = (fdef, k, fn)
327

    
328
  assert len(result) == len(fields) + len(aliases)
329
  assert compat.all(name == fdef.name
330
                    for (name, (fdef, _, _)) in result.items())
331

    
332
  return result
333

    
334

    
335
def GetQueryResponse(query, ctx):
336
  """Prepares the response for a query.
337

338
  @type query: L{Query}
339
  @param ctx: Data container, see L{Query.Query}
340

341
  """
342
  return objects.QueryResponse(data=query.Query(ctx),
343
                               fields=query.GetFields()).ToDict()
344

    
345

    
346
def QueryFields(fielddefs, selected):
347
  """Returns list of available fields.
348

349
  @type fielddefs: dict
350
  @param fielddefs: Field definitions
351
  @type selected: list of strings
352
  @param selected: List of selected fields
353
  @return: List of L{objects.QueryFieldDefinition}
354

355
  """
356
  if selected is None:
357
    # Client requests all fields, sort by name
358
    fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
359
                           key=operator.attrgetter("name"))
360
  else:
361
    # Keep order as requested by client
362
    fdefs = Query(fielddefs, selected).GetFields()
363

    
364
  return objects.QueryFieldsResponse(fields=fdefs).ToDict()
365

    
366

    
367
def _MakeField(name, title, kind, doc=None):
368
  """Wrapper for creating L{objects.QueryFieldDefinition} instances.
369

370
  @param name: Field name as a regular expression
371
  @param title: Human-readable title
372
  @param kind: Field type
373
  @param doc: Human-readable description
374

375
  """
376
  return objects.QueryFieldDefinition(name=name, title=title, kind=kind,
377
                                      doc=doc)
378

    
379

    
380
def _GetNodeRole(node, master_name):
381
  """Determine node role.
382

383
  @type node: L{objects.Node}
384
  @param node: Node object
385
  @type master_name: string
386
  @param master_name: Master node name
387

388
  """
389
  if node.name == master_name:
390
    return constants.NR_MASTER
391
  elif node.master_candidate:
392
    return constants.NR_MCANDIDATE
393
  elif node.drained:
394
    return constants.NR_DRAINED
395
  elif node.offline:
396
    return constants.NR_OFFLINE
397
  else:
398
    return constants.NR_REGULAR
399

    
400

    
401
def _GetItemAttr(attr):
402
  """Returns a field function to return an attribute of the item.
403

404
  @param attr: Attribute name
405

406
  """
407
  getter = operator.attrgetter(attr)
408
  return lambda _, item: getter(item)
409

    
410

    
411
def _GetItemTimestamp(getter):
412
  """Returns function for getting timestamp of item.
413

414
  @type getter: callable
415
  @param getter: Function to retrieve timestamp attribute
416

417
  """
418
  def fn(_, item):
419
    """Returns a timestamp of item.
420

421
    """
422
    timestamp = getter(item)
423
    if timestamp is None:
424
      # Old configs might not have all timestamps
425
      return _FS_UNAVAIL
426
    else:
427
      return timestamp
428

    
429
  return fn
430

    
431

    
432
def _GetItemTimestampFields(datatype):
433
  """Returns common timestamp fields.
434

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

437
  """
438
  return [
439
    (_MakeField("ctime", "CTime", QFT_TIMESTAMP), datatype,
440
     _GetItemTimestamp(operator.attrgetter("ctime"))),
441
    (_MakeField("mtime", "MTime", QFT_TIMESTAMP), datatype,
442
     _GetItemTimestamp(operator.attrgetter("mtime"))),
443
    ]
444

    
445

    
446
class NodeQueryData:
447
  """Data container for node data queries.
448

449
  """
450
  def __init__(self, nodes, live_data, master_name, node_to_primary,
451
               node_to_secondary, groups, oob_support, cluster):
452
    """Initializes this class.
453

454
    """
455
    self.nodes = nodes
456
    self.live_data = live_data
457
    self.master_name = master_name
458
    self.node_to_primary = node_to_primary
459
    self.node_to_secondary = node_to_secondary
460
    self.groups = groups
461
    self.oob_support = oob_support
462
    self.cluster = cluster
463

    
464
    # Used for individual rows
465
    self.curlive_data = None
466

    
467
  def __iter__(self):
468
    """Iterate over all nodes.
469

470
    This function has side-effects and only one instance of the resulting
471
    generator should be used at a time.
472

473
    """
474
    for node in self.nodes:
475
      if self.live_data:
476
        self.curlive_data = self.live_data.get(node.name, None)
477
      else:
478
        self.curlive_data = None
479
      yield node
480

    
481

    
482
#: Fields that are direct attributes of an L{objects.Node} object
483
_NODE_SIMPLE_FIELDS = {
484
  "drained": ("Drained", QFT_BOOL),
485
  "master_candidate": ("MasterC", QFT_BOOL),
486
  "master_capable": ("MasterCapable", QFT_BOOL),
487
  "name": ("Node", QFT_TEXT),
488
  "offline": ("Offline", QFT_BOOL),
489
  "serial_no": ("SerialNo", QFT_NUMBER),
490
  "uuid": ("UUID", QFT_TEXT),
491
  "vm_capable": ("VMCapable", QFT_BOOL),
492
  }
493

    
494

    
495
#: Fields requiring talking to the node
496
# Note that none of these are available for non-vm_capable nodes
497
_NODE_LIVE_FIELDS = {
498
  "bootid": ("BootID", QFT_TEXT, "bootid"),
499
  "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes"),
500
  "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets"),
501
  "ctotal": ("CTotal", QFT_NUMBER, "cpu_total"),
502
  "dfree": ("DFree", QFT_UNIT, "vg_free"),
503
  "dtotal": ("DTotal", QFT_UNIT, "vg_size"),
504
  "mfree": ("MFree", QFT_UNIT, "memory_free"),
505
  "mnode": ("MNode", QFT_UNIT, "memory_dom0"),
506
  "mtotal": ("MTotal", QFT_UNIT, "memory_total"),
507
  }
508

    
509

    
510
def _GetGroup(cb):
511
  """Build function for calling another function with an node group.
512

513
  @param cb: The callback to be called with the nodegroup
514

515
  """
516
  def fn(ctx, node):
517
    """Get group data for a node.
518

519
    @type ctx: L{NodeQueryData}
520
    @type inst: L{objects.Node}
521
    @param inst: Node object
522

523
    """
524
    ng = ctx.groups.get(node.group, None)
525
    if ng is None:
526
      # Nodes always have a group, or the configuration is corrupt
527
      return _FS_UNAVAIL
528

    
529
    return cb(ctx, node, ng)
530

    
531
  return fn
532

    
533

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

537
  @type ctx: L{NodeQueryData}
538
  @type node: L{objects.Node}
539
  @param node: Node object
540
  @type ng: L{objects.NodeGroup}
541
  @param ng: The node group this node belongs to
542

543
  """
544
  return ng.name
545

    
546

    
547
def _GetNodePower(ctx, node):
548
  """Returns the node powered state
549

550
  @type ctx: L{NodeQueryData}
551
  @type node: L{objects.Node}
552
  @param node: Node object
553

554
  """
555
  if ctx.oob_support[node.name]:
556
    return node.powered
557

    
558
  return _FS_UNAVAIL
559

    
560

    
561
def _GetNdParams(ctx, node, ng):
562
  """Returns the ndparams for this node.
563

564
  @type ctx: L{NodeQueryData}
565
  @type node: L{objects.Node}
566
  @param node: Node object
567
  @type ng: L{objects.NodeGroup}
568
  @param ng: The node group this node belongs to
569

570
  """
571
  return ctx.cluster.SimpleFillND(ng.FillND(node))
572

    
573

    
574
def _GetLiveNodeField(field, kind, ctx, node):
575
  """Gets the value of a "live" field from L{NodeQueryData}.
576

577
  @param field: Live field name
578
  @param kind: Data kind, one of L{constants.QFT_ALL}
579
  @type ctx: L{NodeQueryData}
580
  @type node: L{objects.Node}
581
  @param node: Node object
582

583
  """
584
  if node.offline:
585
    return _FS_OFFLINE
586

    
587
  if not node.vm_capable:
588
    return _FS_UNAVAIL
589

    
590
  if not ctx.curlive_data:
591
    return _FS_NODATA
592

    
593
  try:
594
    value = ctx.curlive_data[field]
595
  except KeyError:
596
    return _FS_UNAVAIL
597

    
598
  if kind == QFT_TEXT:
599
    return value
600

    
601
  assert kind in (QFT_NUMBER, QFT_UNIT)
602

    
603
  # Try to convert into number
604
  try:
605
    return int(value)
606
  except (ValueError, TypeError):
607
    logging.exception("Failed to convert node field '%s' (value %r) to int",
608
                      value, field)
609
    return _FS_UNAVAIL
610

    
611

    
612
def _BuildNodeFields():
613
  """Builds list of fields for node queries.
614

615
  """
616
  fields = [
617
    (_MakeField("pip", "PrimaryIP", QFT_TEXT), NQ_CONFIG,
618
     _GetItemAttr("primary_ip")),
619
    (_MakeField("sip", "SecondaryIP", QFT_TEXT), NQ_CONFIG,
620
     _GetItemAttr("secondary_ip")),
621
    (_MakeField("tags", "Tags", QFT_OTHER), NQ_CONFIG,
622
     lambda ctx, node: list(node.GetTags())),
623
    (_MakeField("master", "IsMaster", QFT_BOOL), NQ_CONFIG,
624
     lambda ctx, node: node.name == ctx.master_name),
625
    (_MakeField("role", "Role", QFT_TEXT), NQ_CONFIG,
626
     lambda ctx, node: _GetNodeRole(node, ctx.master_name)),
627
    (_MakeField("group", "Group", QFT_TEXT), NQ_GROUP,
628
     _GetGroup(_GetNodeGroup)),
629
    (_MakeField("group.uuid", "GroupUUID", QFT_TEXT),
630
     NQ_CONFIG, _GetItemAttr("group")),
631
    (_MakeField("powered", "Powered", QFT_BOOL), NQ_OOB, _GetNodePower),
632
    (_MakeField("ndparams", "NodeParameters", QFT_OTHER), NQ_GROUP,
633
      _GetGroup(_GetNdParams)),
634
    (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER),
635
      NQ_GROUP, _GetItemAttr("ndparams")),
636
    ]
637

    
638
  def _GetLength(getter):
639
    return lambda ctx, node: len(getter(ctx)[node.name])
640

    
641
  def _GetList(getter):
642
    return lambda ctx, node: list(getter(ctx)[node.name])
643

    
644
  # Add fields operating on instance lists
645
  for prefix, titleprefix, getter in \
646
      [("p", "Pri", operator.attrgetter("node_to_primary")),
647
       ("s", "Sec", operator.attrgetter("node_to_secondary"))]:
648
    fields.extend([
649
      (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER),
650
       NQ_INST, _GetLength(getter)),
651
      (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
652
                  QFT_OTHER),
653
       NQ_INST, _GetList(getter)),
654
      ])
655

    
656
  # Add simple fields
657
  fields.extend([(_MakeField(name, title, kind), NQ_CONFIG, _GetItemAttr(name))
658
                 for (name, (title, kind)) in _NODE_SIMPLE_FIELDS.items()])
659

    
660
  # Add fields requiring live data
661
  fields.extend([
662
    (_MakeField(name, title, kind), NQ_LIVE,
663
     compat.partial(_GetLiveNodeField, nfield, kind))
664
    for (name, (title, kind, nfield)) in _NODE_LIVE_FIELDS.items()
665
    ])
666

    
667
  # Add timestamps
668
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
669

    
670
  return _PrepareFieldList(fields, [])
671

    
672

    
673
class InstanceQueryData:
674
  """Data container for instance data queries.
675

676
  """
677
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
678
               live_data, wrongnode_inst, console):
679
    """Initializes this class.
680

681
    @param instances: List of instance objects
682
    @param cluster: Cluster object
683
    @type disk_usage: dict; instance name as key
684
    @param disk_usage: Per-instance disk usage
685
    @type offline_nodes: list of strings
686
    @param offline_nodes: List of offline nodes
687
    @type bad_nodes: list of strings
688
    @param bad_nodes: List of faulty nodes
689
    @type live_data: dict; instance name as key
690
    @param live_data: Per-instance live data
691
    @type wrongnode_inst: set
692
    @param wrongnode_inst: Set of instances running on wrong node(s)
693
    @type console: dict; instance name as key
694
    @param console: Per-instance console information
695

696
    """
697
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
698
           "Offline nodes not included in bad nodes"
699
    assert not (set(live_data.keys()) & set(bad_nodes)), \
700
           "Found live data for bad or offline nodes"
701

    
702
    self.instances = instances
703
    self.cluster = cluster
704
    self.disk_usage = disk_usage
705
    self.offline_nodes = offline_nodes
706
    self.bad_nodes = bad_nodes
707
    self.live_data = live_data
708
    self.wrongnode_inst = wrongnode_inst
709
    self.console = console
710

    
711
    # Used for individual rows
712
    self.inst_hvparams = None
713
    self.inst_beparams = None
714
    self.inst_nicparams = None
715

    
716
  def __iter__(self):
717
    """Iterate over all instances.
718

719
    This function has side-effects and only one instance of the resulting
720
    generator should be used at a time.
721

722
    """
723
    for inst in self.instances:
724
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
725
      self.inst_beparams = self.cluster.FillBE(inst)
726
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
727
                             for nic in inst.nics]
728

    
729
      yield inst
730

    
731

    
732
def _GetInstOperState(ctx, inst):
733
  """Get instance's operational status.
734

735
  @type ctx: L{InstanceQueryData}
736
  @type inst: L{objects.Instance}
737
  @param inst: Instance object
738

739
  """
740
  # Can't use RS_OFFLINE here as it would describe the instance to
741
  # be offline when we actually don't know due to missing data
742
  if inst.primary_node in ctx.bad_nodes:
743
    return _FS_NODATA
744
  else:
745
    return bool(ctx.live_data.get(inst.name))
746

    
747

    
748
def _GetInstLiveData(name):
749
  """Build function for retrieving live data.
750

751
  @type name: string
752
  @param name: Live data field name
753

754
  """
755
  def fn(ctx, inst):
756
    """Get live data for an instance.
757

758
    @type ctx: L{InstanceQueryData}
759
    @type inst: L{objects.Instance}
760
    @param inst: Instance object
761

762
    """
763
    if (inst.primary_node in ctx.bad_nodes or
764
        inst.primary_node in ctx.offline_nodes):
765
      # Can't use RS_OFFLINE here as it would describe the instance to be
766
      # offline when we actually don't know due to missing data
767
      return _FS_NODATA
768

    
769
    if inst.name in ctx.live_data:
770
      data = ctx.live_data[inst.name]
771
      if name in data:
772
        return data[name]
773

    
774
    return _FS_UNAVAIL
775

    
776
  return fn
777

    
778

    
779
def _GetInstStatus(ctx, inst):
780
  """Get instance status.
781

782
  @type ctx: L{InstanceQueryData}
783
  @type inst: L{objects.Instance}
784
  @param inst: Instance object
785

786
  """
787
  if inst.primary_node in ctx.offline_nodes:
788
    return constants.INSTST_NODEOFFLINE
789

    
790
  if inst.primary_node in ctx.bad_nodes:
791
    return constants.INSTST_NODEDOWN
792

    
793
  if bool(ctx.live_data.get(inst.name)):
794
    if inst.name in ctx.wrongnode_inst:
795
      return constants.INSTST_WRONGNODE
796
    elif inst.admin_up:
797
      return constants.INSTST_RUNNING
798
    else:
799
      return constants.INSTST_ERRORUP
800

    
801
  if inst.admin_up:
802
    return constants.INSTST_ERRORDOWN
803

    
804
  return constants.INSTST_ADMINDOWN
805

    
806

    
807
def _GetInstDiskSize(index):
808
  """Build function for retrieving disk size.
809

810
  @type index: int
811
  @param index: Disk index
812

813
  """
814
  def fn(_, inst):
815
    """Get size of a disk.
816

817
    @type inst: L{objects.Instance}
818
    @param inst: Instance object
819

820
    """
821
    try:
822
      return inst.disks[index].size
823
    except IndexError:
824
      return _FS_UNAVAIL
825

    
826
  return fn
827

    
828

    
829
def _GetInstNic(index, cb):
830
  """Build function for calling another function with an instance NIC.
831

832
  @type index: int
833
  @param index: NIC index
834
  @type cb: callable
835
  @param cb: Callback
836

837
  """
838
  def fn(ctx, inst):
839
    """Call helper function with instance NIC.
840

841
    @type ctx: L{InstanceQueryData}
842
    @type inst: L{objects.Instance}
843
    @param inst: Instance object
844

845
    """
846
    try:
847
      nic = inst.nics[index]
848
    except IndexError:
849
      return _FS_UNAVAIL
850

    
851
    return cb(ctx, index, nic)
852

    
853
  return fn
854

    
855

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

859
  @type ctx: L{InstanceQueryData}
860
  @type nic: L{objects.NIC}
861
  @param nic: NIC object
862

863
  """
864
  if nic.ip is None:
865
    return _FS_UNAVAIL
866
  else:
867
    return nic.ip
868

    
869

    
870
def _GetInstNicBridge(ctx, index, _):
871
  """Get a NIC's bridge.
872

873
  @type ctx: L{InstanceQueryData}
874
  @type index: int
875
  @param index: NIC index
876

877
  """
878
  assert len(ctx.inst_nicparams) >= index
879

    
880
  nicparams = ctx.inst_nicparams[index]
881

    
882
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
883
    return nicparams[constants.NIC_LINK]
884
  else:
885
    return _FS_UNAVAIL
886

    
887

    
888
def _GetInstAllNicBridges(ctx, inst):
889
  """Get all network bridges for an instance.
890

891
  @type ctx: L{InstanceQueryData}
892
  @type inst: L{objects.Instance}
893
  @param inst: Instance object
894

895
  """
896
  assert len(ctx.inst_nicparams) == len(inst.nics)
897

    
898
  result = []
899

    
900
  for nicp in ctx.inst_nicparams:
901
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
902
      result.append(nicp[constants.NIC_LINK])
903
    else:
904
      result.append(None)
905

    
906
  assert len(result) == len(inst.nics)
907

    
908
  return result
909

    
910

    
911
def _GetInstNicParam(name):
912
  """Build function for retrieving a NIC parameter.
913

914
  @type name: string
915
  @param name: Parameter name
916

917
  """
918
  def fn(ctx, index, _):
919
    """Get a NIC's bridge.
920

921
    @type ctx: L{InstanceQueryData}
922
    @type inst: L{objects.Instance}
923
    @param inst: Instance object
924
    @type nic: L{objects.NIC}
925
    @param nic: NIC object
926

927
    """
928
    assert len(ctx.inst_nicparams) >= index
929
    return ctx.inst_nicparams[index][name]
930

    
931
  return fn
932

    
933

    
934
def _GetInstanceNetworkFields():
935
  """Get instance fields involving network interfaces.
936

937
  @return: Tuple containing list of field definitions used as input for
938
    L{_PrepareFieldList} and a list of aliases
939

940
  """
941
  nic_mac_fn = lambda ctx, _, nic: nic.mac
942
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
943
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
944

    
945
  fields = [
946
    # All NICs
947
    (_MakeField("nic.count", "NICs", QFT_NUMBER), IQ_CONFIG,
948
     lambda ctx, inst: len(inst.nics)),
949
    (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER), IQ_CONFIG,
950
     lambda ctx, inst: [nic.mac for nic in inst.nics]),
951
    (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER), IQ_CONFIG,
952
     lambda ctx, inst: [nic.ip for nic in inst.nics]),
953
    (_MakeField("nic.modes", "NIC_modes", QFT_OTHER), IQ_CONFIG,
954
     lambda ctx, inst: [nicp[constants.NIC_MODE]
955
                        for nicp in ctx.inst_nicparams]),
956
    (_MakeField("nic.links", "NIC_links", QFT_OTHER), IQ_CONFIG,
957
     lambda ctx, inst: [nicp[constants.NIC_LINK]
958
                        for nicp in ctx.inst_nicparams]),
959
    (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER), IQ_CONFIG,
960
     _GetInstAllNicBridges),
961
    ]
962

    
963
  # NICs by number
964
  for i in range(constants.MAX_NICS):
965
    fields.extend([
966
      (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT),
967
       IQ_CONFIG, _GetInstNic(i, _GetInstNicIp)),
968
      (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT),
969
       IQ_CONFIG, _GetInstNic(i, nic_mac_fn)),
970
      (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT),
971
       IQ_CONFIG, _GetInstNic(i, nic_mode_fn)),
972
      (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT),
973
       IQ_CONFIG, _GetInstNic(i, nic_link_fn)),
974
      (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT),
975
       IQ_CONFIG, _GetInstNic(i, _GetInstNicBridge)),
976
      ])
977

    
978
  aliases = [
979
    # Legacy fields for first NIC
980
    ("ip", "nic.ip/0"),
981
    ("mac", "nic.mac/0"),
982
    ("bridge", "nic.bridge/0"),
983
    ("nic_mode", "nic.mode/0"),
984
    ("nic_link", "nic.link/0"),
985
    ]
986

    
987
  return (fields, aliases)
988

    
989

    
990
def _GetInstDiskUsage(ctx, inst):
991
  """Get disk usage for an instance.
992

993
  @type ctx: L{InstanceQueryData}
994
  @type inst: L{objects.Instance}
995
  @param inst: Instance object
996

997
  """
998
  usage = ctx.disk_usage[inst.name]
999

    
1000
  if usage is None:
1001
    usage = 0
1002

    
1003
  return usage
1004

    
1005

    
1006
def _GetInstanceConsole(ctx, inst):
1007
  """Get console information for instance.
1008

1009
  @type ctx: L{InstanceQueryData}
1010
  @type inst: L{objects.Instance}
1011
  @param inst: Instance object
1012

1013
  """
1014
  consinfo = ctx.console[inst.name]
1015

    
1016
  if consinfo is None:
1017
    return _FS_UNAVAIL
1018

    
1019
  return consinfo
1020

    
1021

    
1022
def _GetInstanceDiskFields():
1023
  """Get instance fields involving disks.
1024

1025
  @return: List of field definitions used as input for L{_PrepareFieldList}
1026

1027
  """
1028
  fields = [
1029
    (_MakeField("disk_usage", "DiskUsage", QFT_UNIT), IQ_DISKUSAGE,
1030
     _GetInstDiskUsage),
1031
    (_MakeField("disk.count", "Disks", QFT_NUMBER), IQ_CONFIG,
1032
     lambda ctx, inst: len(inst.disks)),
1033
    (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER), IQ_CONFIG,
1034
     lambda ctx, inst: [disk.size for disk in inst.disks]),
1035
    ]
1036

    
1037
  # Disks by number
1038
  fields.extend([
1039
    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT),
1040
     IQ_CONFIG, _GetInstDiskSize(i))
1041
    for i in range(constants.MAX_DISKS)
1042
    ])
1043

    
1044
  return fields
1045

    
1046

    
1047
def _GetInstanceParameterFields():
1048
  """Get instance fields involving parameters.
1049

1050
  @return: List of field definitions used as input for L{_PrepareFieldList}
1051

1052
  """
1053
  # TODO: Consider moving titles closer to constants
1054
  be_title = {
1055
    constants.BE_AUTO_BALANCE: "Auto_balance",
1056
    constants.BE_MEMORY: "ConfigMemory",
1057
    constants.BE_VCPUS: "ConfigVCPUs",
1058
    }
1059

    
1060
  hv_title = {
1061
    constants.HV_ACPI: "ACPI",
1062
    constants.HV_BOOT_ORDER: "Boot_order",
1063
    constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1064
    constants.HV_DISK_TYPE: "Disk_type",
1065
    constants.HV_INITRD_PATH: "Initrd_path",
1066
    constants.HV_KERNEL_PATH: "Kernel_path",
1067
    constants.HV_NIC_TYPE: "NIC_type",
1068
    constants.HV_PAE: "PAE",
1069
    constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1070
    }
1071

    
1072
  fields = [
1073
    # Filled parameters
1074
    (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER),
1075
     IQ_CONFIG, lambda ctx, _: ctx.inst_hvparams),
1076
    (_MakeField("beparams", "BackendParameters", QFT_OTHER),
1077
     IQ_CONFIG, lambda ctx, _: ctx.inst_beparams),
1078

    
1079
    # Unfilled parameters
1080
    (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER),
1081
     IQ_CONFIG, _GetItemAttr("hvparams")),
1082
    (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER),
1083
     IQ_CONFIG, _GetItemAttr("beparams")),
1084
    (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER),
1085
     IQ_CONFIG, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1086
    ]
1087

    
1088
  # HV params
1089
  def _GetInstHvParam(name):
1090
    return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1091

    
1092
  fields.extend([
1093
    (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1094
                _VTToQFT[kind]),
1095
     IQ_CONFIG, _GetInstHvParam(name))
1096
    for name, kind in constants.HVS_PARAMETER_TYPES.items()
1097
    if name not in constants.HVC_GLOBALS
1098
    ])
1099

    
1100
  # BE params
1101
  def _GetInstBeParam(name):
1102
    return lambda ctx, _: ctx.inst_beparams.get(name, None)
1103

    
1104
  fields.extend([
1105
    (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1106
                _VTToQFT[kind]), IQ_CONFIG,
1107
     _GetInstBeParam(name))
1108
    for name, kind in constants.BES_PARAMETER_TYPES.items()
1109
    ])
1110

    
1111
  return fields
1112

    
1113

    
1114
_INST_SIMPLE_FIELDS = {
1115
  "disk_template": ("Disk_template", QFT_TEXT),
1116
  "hypervisor": ("Hypervisor", QFT_TEXT),
1117
  "name": ("Instance", QFT_TEXT),
1118
  # Depending on the hypervisor, the port can be None
1119
  "network_port": ("Network_port", QFT_OTHER),
1120
  "os": ("OS", QFT_TEXT),
1121
  "serial_no": ("SerialNo", QFT_NUMBER),
1122
  "uuid": ("UUID", QFT_TEXT),
1123
  }
1124

    
1125

    
1126
def _BuildInstanceFields():
1127
  """Builds list of fields for instance queries.
1128

1129
  """
1130
  fields = [
1131
    (_MakeField("pnode", "Primary_node", QFT_TEXT), IQ_CONFIG,
1132
     _GetItemAttr("primary_node")),
1133
    (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER), IQ_CONFIG,
1134
     lambda ctx, inst: list(inst.secondary_nodes)),
1135
    (_MakeField("admin_state", "Autostart", QFT_BOOL), IQ_CONFIG,
1136
     _GetItemAttr("admin_up")),
1137
    (_MakeField("tags", "Tags", QFT_OTHER), IQ_CONFIG,
1138
     lambda ctx, inst: list(inst.GetTags())),
1139
    (_MakeField("console", "Console", QFT_OTHER), IQ_CONSOLE,
1140
     _GetInstanceConsole),
1141
    ]
1142

    
1143
  # Add simple fields
1144
  fields.extend([(_MakeField(name, title, kind), IQ_CONFIG, _GetItemAttr(name))
1145
                 for (name, (title, kind)) in _INST_SIMPLE_FIELDS.items()])
1146

    
1147
  # Fields requiring talking to the node
1148
  fields.extend([
1149
    (_MakeField("oper_state", "Running", QFT_BOOL), IQ_LIVE,
1150
     _GetInstOperState),
1151
    (_MakeField("oper_ram", "Memory", QFT_UNIT), IQ_LIVE,
1152
     _GetInstLiveData("memory")),
1153
    (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER), IQ_LIVE,
1154
     _GetInstLiveData("vcpus")),
1155
    (_MakeField("status", "Status", QFT_TEXT), IQ_LIVE, _GetInstStatus),
1156
    ])
1157

    
1158
  (network_fields, network_aliases) = _GetInstanceNetworkFields()
1159

    
1160
  fields.extend(network_fields)
1161
  fields.extend(_GetInstanceParameterFields())
1162
  fields.extend(_GetInstanceDiskFields())
1163
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1164

    
1165
  aliases = [
1166
    ("vcpus", "be/vcpus"),
1167
    ("sda_size", "disk.size/0"),
1168
    ("sdb_size", "disk.size/1"),
1169
    ] + network_aliases
1170

    
1171
  return _PrepareFieldList(fields, aliases)
1172

    
1173

    
1174
class LockQueryData:
1175
  """Data container for lock data queries.
1176

1177
  """
1178
  def __init__(self, lockdata):
1179
    """Initializes this class.
1180

1181
    """
1182
    self.lockdata = lockdata
1183

    
1184
  def __iter__(self):
1185
    """Iterate over all locks.
1186

1187
    """
1188
    return iter(self.lockdata)
1189

    
1190

    
1191
def _GetLockOwners(_, data):
1192
  """Returns a sorted list of a lock's current owners.
1193

1194
  """
1195
  (_, _, owners, _) = data
1196

    
1197
  if owners:
1198
    owners = utils.NiceSort(owners)
1199

    
1200
  return owners
1201

    
1202

    
1203
def _GetLockPending(_, data):
1204
  """Returns a sorted list of a lock's pending acquires.
1205

1206
  """
1207
  (_, _, _, pending) = data
1208

    
1209
  if pending:
1210
    pending = [(mode, utils.NiceSort(names))
1211
               for (mode, names) in pending]
1212

    
1213
  return pending
1214

    
1215

    
1216
def _BuildLockFields():
1217
  """Builds list of fields for lock queries.
1218

1219
  """
1220
  return _PrepareFieldList([
1221
    (_MakeField("name", "Name", QFT_TEXT), None,
1222
     lambda ctx, (name, mode, owners, pending): name),
1223
    (_MakeField("mode", "Mode", QFT_OTHER), LQ_MODE,
1224
     lambda ctx, (name, mode, owners, pending): mode),
1225
    (_MakeField("owner", "Owner", QFT_OTHER), LQ_OWNER, _GetLockOwners),
1226
    (_MakeField("pending", "Pending", QFT_OTHER), LQ_PENDING, _GetLockPending),
1227
    ], [])
1228

    
1229

    
1230
class GroupQueryData:
1231
  """Data container for node group data queries.
1232

1233
  """
1234
  def __init__(self, groups, group_to_nodes, group_to_instances):
1235
    """Initializes this class.
1236

1237
    @param groups: List of node group objects
1238
    @type group_to_nodes: dict; group UUID as key
1239
    @param group_to_nodes: Per-group list of nodes
1240
    @type group_to_instances: dict; group UUID as key
1241
    @param group_to_instances: Per-group list of (primary) instances
1242

1243
    """
1244
    self.groups = groups
1245
    self.group_to_nodes = group_to_nodes
1246
    self.group_to_instances = group_to_instances
1247

    
1248
  def __iter__(self):
1249
    """Iterate over all node groups.
1250

1251
    """
1252
    return iter(self.groups)
1253

    
1254

    
1255
_GROUP_SIMPLE_FIELDS = {
1256
  "alloc_policy": ("AllocPolicy", QFT_TEXT),
1257
  "name": ("Group", QFT_TEXT),
1258
  "serial_no": ("SerialNo", QFT_NUMBER),
1259
  "uuid": ("UUID", QFT_TEXT),
1260
  "ndparams": ("NDParams", QFT_OTHER),
1261
  }
1262

    
1263

    
1264
def _BuildGroupFields():
1265
  """Builds list of fields for node group queries.
1266

1267
  """
1268
  # Add simple fields
1269
  fields = [(_MakeField(name, title, kind), GQ_CONFIG, _GetItemAttr(name))
1270
            for (name, (title, kind)) in _GROUP_SIMPLE_FIELDS.items()]
1271

    
1272
  def _GetLength(getter):
1273
    return lambda ctx, group: len(getter(ctx)[group.uuid])
1274

    
1275
  def _GetSortedList(getter):
1276
    return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
1277

    
1278
  group_to_nodes = operator.attrgetter("group_to_nodes")
1279
  group_to_instances = operator.attrgetter("group_to_instances")
1280

    
1281
  # Add fields for nodes
1282
  fields.extend([
1283
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER),
1284
     GQ_NODE, _GetLength(group_to_nodes)),
1285
    (_MakeField("node_list", "NodeList", QFT_OTHER),
1286
     GQ_NODE, _GetSortedList(group_to_nodes)),
1287
    ])
1288

    
1289
  # Add fields for instances
1290
  fields.extend([
1291
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER),
1292
     GQ_INST, _GetLength(group_to_instances)),
1293
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER),
1294
     GQ_INST, _GetSortedList(group_to_instances)),
1295
    ])
1296

    
1297
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1298

    
1299
  return _PrepareFieldList(fields, [])
1300

    
1301

    
1302
#: Fields available for node queries
1303
NODE_FIELDS = _BuildNodeFields()
1304

    
1305
#: Fields available for instance queries
1306
INSTANCE_FIELDS = _BuildInstanceFields()
1307

    
1308
#: Fields available for lock queries
1309
LOCK_FIELDS = _BuildLockFields()
1310

    
1311
#: Fields available for node group queries
1312
GROUP_FIELDS = _BuildGroupFields()
1313

    
1314
#: All available field lists
1315
ALL_FIELD_LISTS = [NODE_FIELDS, INSTANCE_FIELDS, LOCK_FIELDS, GROUP_FIELDS]