Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ 61a980a9

History | View | Annotate | Download (35.1 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

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

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

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

    
124

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

128
  """
129
  return _FS_UNKNOWN
130

    
131

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

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

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

142
  """
143
  result = []
144

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

    
151
    assert len(fdef) == 3
152

    
153
    result.append(fdef)
154

    
155
  return result
156

    
157

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

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

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

    
166

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

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

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

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

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

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

192
    @rtype: frozenset
193

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

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

202
    Includes unknown fields.
203

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

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

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

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

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

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

    
224
    return result
225

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

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

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

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

    
242

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

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

    
258

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

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

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

    
280

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

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

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

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

    
302
  result = {}
303

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

    
307
    assert fdef.name and fdef.title, "Name and title are required"
308
    assert FIELD_NAME_RE.match(fdef.name)
309
    assert TITLE_RE.match(fdef.title)
310
    assert callable(fn)
311
    assert fdef.name not in result, \
312
           "Duplicate field name '%s' found" % fdef.name
313

    
314
    result[fdef.name] = field
315

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

    
324
  assert len(result) == len(fields) + len(aliases)
325
  assert compat.all(name == fdef.name
326
                    for (name, (fdef, _, _)) in result.items())
327

    
328
  return result
329

    
330

    
331
def GetQueryResponse(query, ctx):
332
  """Prepares the response for a query.
333

334
  @type query: L{Query}
335
  @param ctx: Data container, see L{Query.Query}
336

337
  """
338
  return objects.QueryResponse(data=query.Query(ctx),
339
                               fields=query.GetFields()).ToDict()
340

    
341

    
342
def QueryFields(fielddefs, selected):
343
  """Returns list of available fields.
344

345
  @type fielddefs: dict
346
  @param fielddefs: Field definitions
347
  @type selected: list of strings
348
  @param selected: List of selected fields
349
  @return: List of L{objects.QueryFieldDefinition}
350

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

    
360
  return objects.QueryFieldsResponse(fields=fdefs).ToDict()
361

    
362

    
363
def _MakeField(name, title, kind):
364
  """Wrapper for creating L{objects.QueryFieldDefinition} instances.
365

366
  @param name: Field name as a regular expression
367
  @param title: Human-readable title
368
  @param kind: Field type
369

370
  """
371
  return objects.QueryFieldDefinition(name=name, title=title, kind=kind)
372

    
373

    
374
def _GetNodeRole(node, master_name):
375
  """Determine node role.
376

377
  @type node: L{objects.Node}
378
  @param node: Node object
379
  @type master_name: string
380
  @param master_name: Master node name
381

382
  """
383
  if node.name == master_name:
384
    return "M"
385
  elif node.master_candidate:
386
    return "C"
387
  elif node.drained:
388
    return "D"
389
  elif node.offline:
390
    return "O"
391
  else:
392
    return "R"
393

    
394

    
395
def _GetItemAttr(attr):
396
  """Returns a field function to return an attribute of the item.
397

398
  @param attr: Attribute name
399

400
  """
401
  getter = operator.attrgetter(attr)
402
  return lambda _, item: getter(item)
403

    
404

    
405
def _GetItemTimestamp(getter):
406
  """Returns function for getting timestamp of item.
407

408
  @type getter: callable
409
  @param getter: Function to retrieve timestamp attribute
410

411
  """
412
  def fn(_, item):
413
    """Returns a timestamp of item.
414

415
    """
416
    timestamp = getter(item)
417
    if timestamp is None:
418
      # Old configs might not have all timestamps
419
      return _FS_UNAVAIL
420
    else:
421
      return timestamp
422

    
423
  return fn
424

    
425

    
426
def _GetItemTimestampFields(datatype):
427
  """Returns common timestamp fields.
428

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

431
  """
432
  return [
433
    (_MakeField("ctime", "CTime", QFT_TIMESTAMP), datatype,
434
     _GetItemTimestamp(operator.attrgetter("ctime"))),
435
    (_MakeField("mtime", "MTime", QFT_TIMESTAMP), datatype,
436
     _GetItemTimestamp(operator.attrgetter("mtime"))),
437
    ]
438

    
439

    
440
class NodeQueryData:
441
  """Data container for node data queries.
442

443
  """
444
  def __init__(self, nodes, live_data, master_name, node_to_primary,
445
               node_to_secondary, groups, oob_support, cluster):
446
    """Initializes this class.
447

448
    """
449
    self.nodes = nodes
450
    self.live_data = live_data
451
    self.master_name = master_name
452
    self.node_to_primary = node_to_primary
453
    self.node_to_secondary = node_to_secondary
454
    self.groups = groups
455
    self.oob_support = oob_support
456
    self.cluster = cluster
457

    
458
    # Used for individual rows
459
    self.curlive_data = None
460

    
461
  def __iter__(self):
462
    """Iterate over all nodes.
463

464
    This function has side-effects and only one instance of the resulting
465
    generator should be used at a time.
466

467
    """
468
    for node in self.nodes:
469
      if self.live_data:
470
        self.curlive_data = self.live_data.get(node.name, None)
471
      else:
472
        self.curlive_data = None
473
      yield node
474

    
475

    
476
#: Fields that are direct attributes of an L{objects.Node} object
477
_NODE_SIMPLE_FIELDS = {
478
  "drained": ("Drained", QFT_BOOL),
479
  "master_candidate": ("MasterC", QFT_BOOL),
480
  "master_capable": ("MasterCapable", QFT_BOOL),
481
  "name": ("Node", QFT_TEXT),
482
  "offline": ("Offline", QFT_BOOL),
483
  "serial_no": ("SerialNo", QFT_NUMBER),
484
  "uuid": ("UUID", QFT_TEXT),
485
  "vm_capable": ("VMCapable", QFT_BOOL),
486
  }
487

    
488

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

    
503

    
504
def _GetGroup(cb):
505
  """Build function for calling another function with an node group.
506

507
  @param cb: The callback to be called with the nodegroup
508

509
  """
510
  def fn(ctx, node):
511
    """Get group data for a node.
512

513
    @type ctx: L{NodeQueryData}
514
    @type inst: L{objects.Node}
515
    @param inst: Node object
516

517
    """
518
    ng = ctx.groups.get(node.group, None)
519
    if ng is None:
520
      # Nodes always have a group, or the configuration is corrupt
521
      return _FS_UNAVAIL
522

    
523
    return cb(ctx, node, ng)
524

    
525
  return fn
526

    
527

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

531
  @type ctx: L{NodeQueryData}
532
  @type node: L{objects.Node}
533
  @param node: Node object
534
  @type ng: L{objects.NodeGroup}
535
  @param ng: The node group this node belongs to
536

537
  """
538
  return ng.name
539

    
540

    
541
def _GetNodePower(ctx, node):
542
  """Returns the node powered state
543

544
  @type ctx: L{NodeQueryData}
545
  @type node: L{objects.Node}
546
  @param node: Node object
547

548
  """
549
  if ctx.oob_support[node.name]:
550
    return node.powered
551

    
552
  return _FS_UNAVAIL
553

    
554

    
555
def _GetNdParams(ctx, node, ng):
556
  """Returns the ndparams for this node.
557

558
  @type ctx: L{NodeQueryData}
559
  @type node: L{objects.Node}
560
  @param node: Node object
561
  @type ng: L{objects.NodeGroup}
562
  @param ng: The node group this node belongs to
563

564
  """
565
  return ctx.cluster.SimpleFillND(ng.FillND(node))
566

    
567

    
568
def _GetLiveNodeField(field, kind, ctx, node):
569
  """Gets the value of a "live" field from L{NodeQueryData}.
570

571
  @param field: Live field name
572
  @param kind: Data kind, one of L{constants.QFT_ALL}
573
  @type ctx: L{NodeQueryData}
574
  @type node: L{objects.Node}
575
  @param node: Node object
576

577
  """
578
  if node.offline:
579
    return _FS_OFFLINE
580

    
581
  if not node.vm_capable:
582
    return _FS_UNAVAIL
583

    
584
  if not ctx.curlive_data:
585
    return _FS_NODATA
586

    
587
  try:
588
    value = ctx.curlive_data[field]
589
  except KeyError:
590
    return _FS_UNAVAIL
591

    
592
  if kind == QFT_TEXT:
593
    return value
594

    
595
  assert kind in (QFT_NUMBER, QFT_UNIT)
596

    
597
  # Try to convert into number
598
  try:
599
    return int(value)
600
  except (ValueError, TypeError):
601
    logging.exception("Failed to convert node field '%s' (value %r) to int",
602
                      value, field)
603
    return _FS_UNAVAIL
604

    
605

    
606
def _BuildNodeFields():
607
  """Builds list of fields for node queries.
608

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

    
632
  def _GetLength(getter):
633
    return lambda ctx, node: len(getter(ctx)[node.name])
634

    
635
  def _GetList(getter):
636
    return lambda ctx, node: list(getter(ctx)[node.name])
637

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

    
650
  # Add simple fields
651
  fields.extend([(_MakeField(name, title, kind), NQ_CONFIG, _GetItemAttr(name))
652
                 for (name, (title, kind)) in _NODE_SIMPLE_FIELDS.items()])
653

    
654
  # Add fields requiring live data
655
  fields.extend([
656
    (_MakeField(name, title, kind), NQ_LIVE,
657
     compat.partial(_GetLiveNodeField, nfield, kind))
658
    for (name, (title, kind, nfield)) in _NODE_LIVE_FIELDS.items()
659
    ])
660

    
661
  # Add timestamps
662
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
663

    
664
  return _PrepareFieldList(fields, [])
665

    
666

    
667
class InstanceQueryData:
668
  """Data container for instance data queries.
669

670
  """
671
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
672
               live_data, wrongnode_inst, console):
673
    """Initializes this class.
674

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

690
    """
691
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
692
           "Offline nodes not included in bad nodes"
693
    assert not (set(live_data.keys()) & set(bad_nodes)), \
694
           "Found live data for bad or offline nodes"
695

    
696
    self.instances = instances
697
    self.cluster = cluster
698
    self.disk_usage = disk_usage
699
    self.offline_nodes = offline_nodes
700
    self.bad_nodes = bad_nodes
701
    self.live_data = live_data
702
    self.wrongnode_inst = wrongnode_inst
703
    self.console = console
704

    
705
    # Used for individual rows
706
    self.inst_hvparams = None
707
    self.inst_beparams = None
708
    self.inst_nicparams = None
709

    
710
  def __iter__(self):
711
    """Iterate over all instances.
712

713
    This function has side-effects and only one instance of the resulting
714
    generator should be used at a time.
715

716
    """
717
    for inst in self.instances:
718
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
719
      self.inst_beparams = self.cluster.FillBE(inst)
720
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
721
                             for nic in inst.nics]
722

    
723
      yield inst
724

    
725

    
726
def _GetInstOperState(ctx, inst):
727
  """Get instance's operational status.
728

729
  @type ctx: L{InstanceQueryData}
730
  @type inst: L{objects.Instance}
731
  @param inst: Instance object
732

733
  """
734
  # Can't use RS_OFFLINE here as it would describe the instance to
735
  # be offline when we actually don't know due to missing data
736
  if inst.primary_node in ctx.bad_nodes:
737
    return _FS_NODATA
738
  else:
739
    return bool(ctx.live_data.get(inst.name))
740

    
741

    
742
def _GetInstLiveData(name):
743
  """Build function for retrieving live data.
744

745
  @type name: string
746
  @param name: Live data field name
747

748
  """
749
  def fn(ctx, inst):
750
    """Get live data for an instance.
751

752
    @type ctx: L{InstanceQueryData}
753
    @type inst: L{objects.Instance}
754
    @param inst: Instance object
755

756
    """
757
    if (inst.primary_node in ctx.bad_nodes or
758
        inst.primary_node in ctx.offline_nodes):
759
      # Can't use RS_OFFLINE here as it would describe the instance to be
760
      # offline when we actually don't know due to missing data
761
      return _FS_NODATA
762

    
763
    if inst.name in ctx.live_data:
764
      data = ctx.live_data[inst.name]
765
      if name in data:
766
        return data[name]
767

    
768
    return _FS_UNAVAIL
769

    
770
  return fn
771

    
772

    
773
def _GetInstStatus(ctx, inst):
774
  """Get instance status.
775

776
  @type ctx: L{InstanceQueryData}
777
  @type inst: L{objects.Instance}
778
  @param inst: Instance object
779

780
  """
781
  if inst.primary_node in ctx.offline_nodes:
782
    return constants.INSTST_NODEOFFLINE
783

    
784
  if inst.primary_node in ctx.bad_nodes:
785
    return constants.INSTST_NODEDOWN
786

    
787
  if bool(ctx.live_data.get(inst.name)):
788
    if inst.name in ctx.wrongnode_inst:
789
      return constants.INSTST_WRONGNODE
790
    elif inst.admin_up:
791
      return constants.INSTST_RUNNING
792
    else:
793
      return constants.INSTST_ERRORUP
794

    
795
  if inst.admin_up:
796
    return constants.INSTST_ERRORDOWN
797

    
798
  return constants.INSTST_ADMINDOWN
799

    
800

    
801
def _GetInstDiskSize(index):
802
  """Build function for retrieving disk size.
803

804
  @type index: int
805
  @param index: Disk index
806

807
  """
808
  def fn(_, inst):
809
    """Get size of a disk.
810

811
    @type inst: L{objects.Instance}
812
    @param inst: Instance object
813

814
    """
815
    try:
816
      return inst.disks[index].size
817
    except IndexError:
818
      return _FS_UNAVAIL
819

    
820
  return fn
821

    
822

    
823
def _GetInstNic(index, cb):
824
  """Build function for calling another function with an instance NIC.
825

826
  @type index: int
827
  @param index: NIC index
828
  @type cb: callable
829
  @param cb: Callback
830

831
  """
832
  def fn(ctx, inst):
833
    """Call helper function with instance NIC.
834

835
    @type ctx: L{InstanceQueryData}
836
    @type inst: L{objects.Instance}
837
    @param inst: Instance object
838

839
    """
840
    try:
841
      nic = inst.nics[index]
842
    except IndexError:
843
      return _FS_UNAVAIL
844

    
845
    return cb(ctx, index, nic)
846

    
847
  return fn
848

    
849

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

853
  @type ctx: L{InstanceQueryData}
854
  @type nic: L{objects.NIC}
855
  @param nic: NIC object
856

857
  """
858
  if nic.ip is None:
859
    return _FS_UNAVAIL
860
  else:
861
    return nic.ip
862

    
863

    
864
def _GetInstNicBridge(ctx, index, _):
865
  """Get a NIC's bridge.
866

867
  @type ctx: L{InstanceQueryData}
868
  @type index: int
869
  @param index: NIC index
870

871
  """
872
  assert len(ctx.inst_nicparams) >= index
873

    
874
  nicparams = ctx.inst_nicparams[index]
875

    
876
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
877
    return nicparams[constants.NIC_LINK]
878
  else:
879
    return _FS_UNAVAIL
880

    
881

    
882
def _GetInstAllNicBridges(ctx, inst):
883
  """Get all network bridges for an instance.
884

885
  @type ctx: L{InstanceQueryData}
886
  @type inst: L{objects.Instance}
887
  @param inst: Instance object
888

889
  """
890
  assert len(ctx.inst_nicparams) == len(inst.nics)
891

    
892
  result = []
893

    
894
  for nicp in ctx.inst_nicparams:
895
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
896
      result.append(nicp[constants.NIC_LINK])
897
    else:
898
      result.append(None)
899

    
900
  assert len(result) == len(inst.nics)
901

    
902
  return result
903

    
904

    
905
def _GetInstNicParam(name):
906
  """Build function for retrieving a NIC parameter.
907

908
  @type name: string
909
  @param name: Parameter name
910

911
  """
912
  def fn(ctx, index, _):
913
    """Get a NIC's bridge.
914

915
    @type ctx: L{InstanceQueryData}
916
    @type inst: L{objects.Instance}
917
    @param inst: Instance object
918
    @type nic: L{objects.NIC}
919
    @param nic: NIC object
920

921
    """
922
    assert len(ctx.inst_nicparams) >= index
923
    return ctx.inst_nicparams[index][name]
924

    
925
  return fn
926

    
927

    
928
def _GetInstanceNetworkFields():
929
  """Get instance fields involving network interfaces.
930

931
  @return: Tuple containing list of field definitions used as input for
932
    L{_PrepareFieldList} and a list of aliases
933

934
  """
935
  nic_mac_fn = lambda ctx, _, nic: nic.mac
936
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
937
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
938

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

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

    
972
  aliases = [
973
    # Legacy fields for first NIC
974
    ("ip", "nic.ip/0"),
975
    ("mac", "nic.mac/0"),
976
    ("bridge", "nic.bridge/0"),
977
    ("nic_mode", "nic.mode/0"),
978
    ("nic_link", "nic.link/0"),
979
    ]
980

    
981
  return (fields, aliases)
982

    
983

    
984
def _GetInstDiskUsage(ctx, inst):
985
  """Get disk usage for an instance.
986

987
  @type ctx: L{InstanceQueryData}
988
  @type inst: L{objects.Instance}
989
  @param inst: Instance object
990

991
  """
992
  usage = ctx.disk_usage[inst.name]
993

    
994
  if usage is None:
995
    usage = 0
996

    
997
  return usage
998

    
999

    
1000
def _GetInstanceConsole(ctx, inst):
1001
  """Get console information for instance.
1002

1003
  @type ctx: L{InstanceQueryData}
1004
  @type inst: L{objects.Instance}
1005
  @param inst: Instance object
1006

1007
  """
1008
  consinfo = ctx.console[inst.name]
1009

    
1010
  if consinfo is None:
1011
    return _FS_UNAVAIL
1012

    
1013
  return consinfo
1014

    
1015

    
1016
def _GetInstanceDiskFields():
1017
  """Get instance fields involving disks.
1018

1019
  @return: List of field definitions used as input for L{_PrepareFieldList}
1020

1021
  """
1022
  fields = [
1023
    (_MakeField("disk_usage", "DiskUsage", QFT_UNIT), IQ_DISKUSAGE,
1024
     _GetInstDiskUsage),
1025
    (_MakeField("disk.count", "Disks", QFT_NUMBER), IQ_CONFIG,
1026
     lambda ctx, inst: len(inst.disks)),
1027
    (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER), IQ_CONFIG,
1028
     lambda ctx, inst: [disk.size for disk in inst.disks]),
1029
    ]
1030

    
1031
  # Disks by number
1032
  fields.extend([
1033
    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT),
1034
     IQ_CONFIG, _GetInstDiskSize(i))
1035
    for i in range(constants.MAX_DISKS)
1036
    ])
1037

    
1038
  return fields
1039

    
1040

    
1041
def _GetInstanceParameterFields():
1042
  """Get instance fields involving parameters.
1043

1044
  @return: List of field definitions used as input for L{_PrepareFieldList}
1045

1046
  """
1047
  # TODO: Consider moving titles closer to constants
1048
  be_title = {
1049
    constants.BE_AUTO_BALANCE: "Auto_balance",
1050
    constants.BE_MEMORY: "ConfigMemory",
1051
    constants.BE_VCPUS: "ConfigVCPUs",
1052
    }
1053

    
1054
  hv_title = {
1055
    constants.HV_ACPI: "ACPI",
1056
    constants.HV_BOOT_ORDER: "Boot_order",
1057
    constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1058
    constants.HV_DISK_TYPE: "Disk_type",
1059
    constants.HV_INITRD_PATH: "Initrd_path",
1060
    constants.HV_KERNEL_PATH: "Kernel_path",
1061
    constants.HV_NIC_TYPE: "NIC_type",
1062
    constants.HV_PAE: "PAE",
1063
    constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1064
    }
1065

    
1066
  fields = [
1067
    # Filled parameters
1068
    (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER),
1069
     IQ_CONFIG, lambda ctx, _: ctx.inst_hvparams),
1070
    (_MakeField("beparams", "BackendParameters", QFT_OTHER),
1071
     IQ_CONFIG, lambda ctx, _: ctx.inst_beparams),
1072

    
1073
    # Unfilled parameters
1074
    (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER),
1075
     IQ_CONFIG, _GetItemAttr("hvparams")),
1076
    (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER),
1077
     IQ_CONFIG, _GetItemAttr("beparams")),
1078
    (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER),
1079
     IQ_CONFIG, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1080
    ]
1081

    
1082
  # HV params
1083
  def _GetInstHvParam(name):
1084
    return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1085

    
1086
  fields.extend([
1087
    (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1088
                _VTToQFT[kind]),
1089
     IQ_CONFIG, _GetInstHvParam(name))
1090
    for name, kind in constants.HVS_PARAMETER_TYPES.items()
1091
    if name not in constants.HVC_GLOBALS
1092
    ])
1093

    
1094
  # BE params
1095
  def _GetInstBeParam(name):
1096
    return lambda ctx, _: ctx.inst_beparams.get(name, None)
1097

    
1098
  fields.extend([
1099
    (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1100
                _VTToQFT[kind]), IQ_CONFIG,
1101
     _GetInstBeParam(name))
1102
    for name, kind in constants.BES_PARAMETER_TYPES.items()
1103
    ])
1104

    
1105
  return fields
1106

    
1107

    
1108
_INST_SIMPLE_FIELDS = {
1109
  "disk_template": ("Disk_template", QFT_TEXT),
1110
  "hypervisor": ("Hypervisor", QFT_TEXT),
1111
  "name": ("Instance", QFT_TEXT),
1112
  # Depending on the hypervisor, the port can be None
1113
  "network_port": ("Network_port", QFT_OTHER),
1114
  "os": ("OS", QFT_TEXT),
1115
  "serial_no": ("SerialNo", QFT_NUMBER),
1116
  "uuid": ("UUID", QFT_TEXT),
1117
  }
1118

    
1119

    
1120
def _BuildInstanceFields():
1121
  """Builds list of fields for instance queries.
1122

1123
  """
1124
  fields = [
1125
    (_MakeField("pnode", "Primary_node", QFT_TEXT), IQ_CONFIG,
1126
     _GetItemAttr("primary_node")),
1127
    (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER), IQ_CONFIG,
1128
     lambda ctx, inst: list(inst.secondary_nodes)),
1129
    (_MakeField("admin_state", "Autostart", QFT_BOOL), IQ_CONFIG,
1130
     _GetItemAttr("admin_up")),
1131
    (_MakeField("tags", "Tags", QFT_OTHER), IQ_CONFIG,
1132
     lambda ctx, inst: list(inst.GetTags())),
1133
    (_MakeField("console", "Console", QFT_OTHER), IQ_CONSOLE,
1134
     _GetInstanceConsole),
1135
    ]
1136

    
1137
  # Add simple fields
1138
  fields.extend([(_MakeField(name, title, kind), IQ_CONFIG, _GetItemAttr(name))
1139
                 for (name, (title, kind)) in _INST_SIMPLE_FIELDS.items()])
1140

    
1141
  # Fields requiring talking to the node
1142
  fields.extend([
1143
    (_MakeField("oper_state", "Running", QFT_BOOL), IQ_LIVE,
1144
     _GetInstOperState),
1145
    (_MakeField("oper_ram", "Memory", QFT_UNIT), IQ_LIVE,
1146
     _GetInstLiveData("memory")),
1147
    (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER), IQ_LIVE,
1148
     _GetInstLiveData("vcpus")),
1149
    (_MakeField("status", "Status", QFT_TEXT), IQ_LIVE, _GetInstStatus),
1150
    ])
1151

    
1152
  (network_fields, network_aliases) = _GetInstanceNetworkFields()
1153

    
1154
  fields.extend(network_fields)
1155
  fields.extend(_GetInstanceParameterFields())
1156
  fields.extend(_GetInstanceDiskFields())
1157
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1158

    
1159
  aliases = [
1160
    ("vcpus", "be/vcpus"),
1161
    ("sda_size", "disk.size/0"),
1162
    ("sdb_size", "disk.size/1"),
1163
    ] + network_aliases
1164

    
1165
  return _PrepareFieldList(fields, aliases)
1166

    
1167

    
1168
class LockQueryData:
1169
  """Data container for lock data queries.
1170

1171
  """
1172
  def __init__(self, lockdata):
1173
    """Initializes this class.
1174

1175
    """
1176
    self.lockdata = lockdata
1177

    
1178
  def __iter__(self):
1179
    """Iterate over all locks.
1180

1181
    """
1182
    return iter(self.lockdata)
1183

    
1184

    
1185
def _GetLockOwners(_, data):
1186
  """Returns a sorted list of a lock's current owners.
1187

1188
  """
1189
  (_, _, owners, _) = data
1190

    
1191
  if owners:
1192
    owners = utils.NiceSort(owners)
1193

    
1194
  return owners
1195

    
1196

    
1197
def _GetLockPending(_, data):
1198
  """Returns a sorted list of a lock's pending acquires.
1199

1200
  """
1201
  (_, _, _, pending) = data
1202

    
1203
  if pending:
1204
    pending = [(mode, utils.NiceSort(names))
1205
               for (mode, names) in pending]
1206

    
1207
  return pending
1208

    
1209

    
1210
def _BuildLockFields():
1211
  """Builds list of fields for lock queries.
1212

1213
  """
1214
  return _PrepareFieldList([
1215
    (_MakeField("name", "Name", QFT_TEXT), None,
1216
     lambda ctx, (name, mode, owners, pending): name),
1217
    (_MakeField("mode", "Mode", QFT_OTHER), LQ_MODE,
1218
     lambda ctx, (name, mode, owners, pending): mode),
1219
    (_MakeField("owner", "Owner", QFT_OTHER), LQ_OWNER, _GetLockOwners),
1220
    (_MakeField("pending", "Pending", QFT_OTHER), LQ_PENDING, _GetLockPending),
1221
    ], [])
1222

    
1223

    
1224
class GroupQueryData:
1225
  """Data container for node group data queries.
1226

1227
  """
1228
  def __init__(self, groups, group_to_nodes, group_to_instances):
1229
    """Initializes this class.
1230

1231
    @param groups: List of node group objects
1232
    @type group_to_nodes: dict; group UUID as key
1233
    @param group_to_nodes: Per-group list of nodes
1234
    @type group_to_instances: dict; group UUID as key
1235
    @param group_to_instances: Per-group list of (primary) instances
1236

1237
    """
1238
    self.groups = groups
1239
    self.group_to_nodes = group_to_nodes
1240
    self.group_to_instances = group_to_instances
1241

    
1242
  def __iter__(self):
1243
    """Iterate over all node groups.
1244

1245
    """
1246
    return iter(self.groups)
1247

    
1248

    
1249
_GROUP_SIMPLE_FIELDS = {
1250
  "alloc_policy": ("AllocPolicy", QFT_TEXT),
1251
  "name": ("Group", QFT_TEXT),
1252
  "serial_no": ("SerialNo", QFT_NUMBER),
1253
  "uuid": ("UUID", QFT_TEXT),
1254
  "ndparams": ("NDParams", QFT_OTHER),
1255
  }
1256

    
1257

    
1258
def _BuildGroupFields():
1259
  """Builds list of fields for node group queries.
1260

1261
  """
1262
  # Add simple fields
1263
  fields = [(_MakeField(name, title, kind), GQ_CONFIG, _GetItemAttr(name))
1264
            for (name, (title, kind)) in _GROUP_SIMPLE_FIELDS.items()]
1265

    
1266
  def _GetLength(getter):
1267
    return lambda ctx, group: len(getter(ctx)[group.uuid])
1268

    
1269
  def _GetSortedList(getter):
1270
    return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
1271

    
1272
  group_to_nodes = operator.attrgetter("group_to_nodes")
1273
  group_to_instances = operator.attrgetter("group_to_instances")
1274

    
1275
  # Add fields for nodes
1276
  fields.extend([
1277
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER),
1278
     GQ_NODE, _GetLength(group_to_nodes)),
1279
    (_MakeField("node_list", "NodeList", QFT_OTHER),
1280
     GQ_NODE, _GetSortedList(group_to_nodes)),
1281
    ])
1282

    
1283
  # Add fields for instances
1284
  fields.extend([
1285
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER),
1286
     GQ_INST, _GetLength(group_to_instances)),
1287
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER),
1288
     GQ_INST, _GetSortedList(group_to_instances)),
1289
    ])
1290

    
1291
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1292

    
1293
  return _PrepareFieldList(fields, [])
1294

    
1295

    
1296
#: Fields available for node queries
1297
NODE_FIELDS = _BuildNodeFields()
1298

    
1299
#: Fields available for instance queries
1300
INSTANCE_FIELDS = _BuildInstanceFields()
1301

    
1302
#: Fields available for lock queries
1303
LOCK_FIELDS = _BuildLockFields()
1304

    
1305
#: Fields available for node group queries
1306
GROUP_FIELDS = _BuildGroupFields()
1307

    
1308
#: All available field lists
1309
ALL_FIELD_LISTS = [NODE_FIELDS, INSTANCE_FIELDS, LOCK_FIELDS, GROUP_FIELDS]