Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ d24bc000

History | View | Annotate | Download (35 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
_NODE_LIVE_FIELDS = {
491
  "bootid": ("BootID", QFT_TEXT, "bootid"),
492
  "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes"),
493
  "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets"),
494
  "ctotal": ("CTotal", QFT_NUMBER, "cpu_total"),
495
  "dfree": ("DFree", QFT_UNIT, "vg_free"),
496
  "dtotal": ("DTotal", QFT_UNIT, "vg_size"),
497
  "mfree": ("MFree", QFT_UNIT, "memory_free"),
498
  "mnode": ("MNode", QFT_UNIT, "memory_dom0"),
499
  "mtotal": ("MTotal", QFT_UNIT, "memory_total"),
500
  }
501

    
502

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

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

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

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

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

    
522
    return cb(ctx, node, ng)
523

    
524
  return fn
525

    
526

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

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

536
  """
537
  return ng.name
538

    
539

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

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

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

    
551
  return _FS_UNAVAIL
552

    
553

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

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

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

    
566

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

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

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

    
580
  if not ctx.curlive_data:
581
    return _FS_NODATA
582

    
583
  try:
584
    value = ctx.curlive_data[field]
585
  except KeyError:
586
    return _FS_UNAVAIL
587

    
588
  if kind == QFT_TEXT:
589
    return value
590

    
591
  assert kind in (QFT_NUMBER, QFT_UNIT)
592

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

    
601

    
602
def _BuildNodeFields():
603
  """Builds list of fields for node queries.
604

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

    
628
  def _GetLength(getter):
629
    return lambda ctx, node: len(getter(ctx)[node.name])
630

    
631
  def _GetList(getter):
632
    return lambda ctx, node: list(getter(ctx)[node.name])
633

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

    
646
  # Add simple fields
647
  fields.extend([(_MakeField(name, title, kind), NQ_CONFIG, _GetItemAttr(name))
648
                 for (name, (title, kind)) in _NODE_SIMPLE_FIELDS.items()])
649

    
650
  # Add fields requiring live data
651
  fields.extend([
652
    (_MakeField(name, title, kind), NQ_LIVE,
653
     compat.partial(_GetLiveNodeField, nfield, kind))
654
    for (name, (title, kind, nfield)) in _NODE_LIVE_FIELDS.items()
655
    ])
656

    
657
  # Add timestamps
658
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
659

    
660
  return _PrepareFieldList(fields, [])
661

    
662

    
663
class InstanceQueryData:
664
  """Data container for instance data queries.
665

666
  """
667
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
668
               live_data, wrongnode_inst, console):
669
    """Initializes this class.
670

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

686
    """
687
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
688
           "Offline nodes not included in bad nodes"
689
    assert not (set(live_data.keys()) & set(bad_nodes)), \
690
           "Found live data for bad or offline nodes"
691

    
692
    self.instances = instances
693
    self.cluster = cluster
694
    self.disk_usage = disk_usage
695
    self.offline_nodes = offline_nodes
696
    self.bad_nodes = bad_nodes
697
    self.live_data = live_data
698
    self.wrongnode_inst = wrongnode_inst
699
    self.console = console
700

    
701
    # Used for individual rows
702
    self.inst_hvparams = None
703
    self.inst_beparams = None
704
    self.inst_nicparams = None
705

    
706
  def __iter__(self):
707
    """Iterate over all instances.
708

709
    This function has side-effects and only one instance of the resulting
710
    generator should be used at a time.
711

712
    """
713
    for inst in self.instances:
714
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
715
      self.inst_beparams = self.cluster.FillBE(inst)
716
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
717
                             for nic in inst.nics]
718

    
719
      yield inst
720

    
721

    
722
def _GetInstOperState(ctx, inst):
723
  """Get instance's operational status.
724

725
  @type ctx: L{InstanceQueryData}
726
  @type inst: L{objects.Instance}
727
  @param inst: Instance object
728

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

    
737

    
738
def _GetInstLiveData(name):
739
  """Build function for retrieving live data.
740

741
  @type name: string
742
  @param name: Live data field name
743

744
  """
745
  def fn(ctx, inst):
746
    """Get live data for an instance.
747

748
    @type ctx: L{InstanceQueryData}
749
    @type inst: L{objects.Instance}
750
    @param inst: Instance object
751

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

    
759
    if inst.name in ctx.live_data:
760
      data = ctx.live_data[inst.name]
761
      if name in data:
762
        return data[name]
763

    
764
    return _FS_UNAVAIL
765

    
766
  return fn
767

    
768

    
769
def _GetInstStatus(ctx, inst):
770
  """Get instance status.
771

772
  @type ctx: L{InstanceQueryData}
773
  @type inst: L{objects.Instance}
774
  @param inst: Instance object
775

776
  """
777
  if inst.primary_node in ctx.offline_nodes:
778
    return "ERROR_nodeoffline"
779

    
780
  if inst.primary_node in ctx.bad_nodes:
781
    return "ERROR_nodedown"
782

    
783
  if bool(ctx.live_data.get(inst.name)):
784
    if inst.name in ctx.wrongnode_inst:
785
      return "ERROR_wrongnode"
786
    elif inst.admin_up:
787
      return "running"
788
    else:
789
      return "ERROR_up"
790

    
791
  if inst.admin_up:
792
    return "ERROR_down"
793

    
794
  return "ADMIN_down"
795

    
796

    
797
def _GetInstDiskSize(index):
798
  """Build function for retrieving disk size.
799

800
  @type index: int
801
  @param index: Disk index
802

803
  """
804
  def fn(_, inst):
805
    """Get size of a disk.
806

807
    @type inst: L{objects.Instance}
808
    @param inst: Instance object
809

810
    """
811
    try:
812
      return inst.disks[index].size
813
    except IndexError:
814
      return _FS_UNAVAIL
815

    
816
  return fn
817

    
818

    
819
def _GetInstNic(index, cb):
820
  """Build function for calling another function with an instance NIC.
821

822
  @type index: int
823
  @param index: NIC index
824
  @type cb: callable
825
  @param cb: Callback
826

827
  """
828
  def fn(ctx, inst):
829
    """Call helper function with instance NIC.
830

831
    @type ctx: L{InstanceQueryData}
832
    @type inst: L{objects.Instance}
833
    @param inst: Instance object
834

835
    """
836
    try:
837
      nic = inst.nics[index]
838
    except IndexError:
839
      return _FS_UNAVAIL
840

    
841
    return cb(ctx, index, nic)
842

    
843
  return fn
844

    
845

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

849
  @type ctx: L{InstanceQueryData}
850
  @type nic: L{objects.NIC}
851
  @param nic: NIC object
852

853
  """
854
  if nic.ip is None:
855
    return _FS_UNAVAIL
856
  else:
857
    return nic.ip
858

    
859

    
860
def _GetInstNicBridge(ctx, index, _):
861
  """Get a NIC's bridge.
862

863
  @type ctx: L{InstanceQueryData}
864
  @type index: int
865
  @param index: NIC index
866

867
  """
868
  assert len(ctx.inst_nicparams) >= index
869

    
870
  nicparams = ctx.inst_nicparams[index]
871

    
872
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
873
    return nicparams[constants.NIC_LINK]
874
  else:
875
    return _FS_UNAVAIL
876

    
877

    
878
def _GetInstAllNicBridges(ctx, inst):
879
  """Get all network bridges for an instance.
880

881
  @type ctx: L{InstanceQueryData}
882
  @type inst: L{objects.Instance}
883
  @param inst: Instance object
884

885
  """
886
  assert len(ctx.inst_nicparams) == len(inst.nics)
887

    
888
  result = []
889

    
890
  for nicp in ctx.inst_nicparams:
891
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
892
      result.append(nicp[constants.NIC_LINK])
893
    else:
894
      result.append(None)
895

    
896
  assert len(result) == len(inst.nics)
897

    
898
  return result
899

    
900

    
901
def _GetInstNicParam(name):
902
  """Build function for retrieving a NIC parameter.
903

904
  @type name: string
905
  @param name: Parameter name
906

907
  """
908
  def fn(ctx, index, _):
909
    """Get a NIC's bridge.
910

911
    @type ctx: L{InstanceQueryData}
912
    @type inst: L{objects.Instance}
913
    @param inst: Instance object
914
    @type nic: L{objects.NIC}
915
    @param nic: NIC object
916

917
    """
918
    assert len(ctx.inst_nicparams) >= index
919
    return ctx.inst_nicparams[index][name]
920

    
921
  return fn
922

    
923

    
924
def _GetInstanceNetworkFields():
925
  """Get instance fields involving network interfaces.
926

927
  @return: List of field definitions used as input for L{_PrepareFieldList}
928

929
  """
930
  nic_mac_fn = lambda ctx, _, nic: nic.mac
931
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
932
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
933

    
934
  fields = [
935
    # First NIC (legacy)
936
    (_MakeField("ip", "IP_address", QFT_TEXT), IQ_CONFIG,
937
     _GetInstNic(0, _GetInstNicIp)),
938
    (_MakeField("mac", "MAC_address", QFT_TEXT), IQ_CONFIG,
939
     _GetInstNic(0, nic_mac_fn)),
940
    (_MakeField("bridge", "Bridge", QFT_TEXT), IQ_CONFIG,
941
     _GetInstNic(0, _GetInstNicBridge)),
942
    (_MakeField("nic_mode", "NIC_Mode", QFT_TEXT), IQ_CONFIG,
943
     _GetInstNic(0, nic_mode_fn)),
944
    (_MakeField("nic_link", "NIC_Link", QFT_TEXT), IQ_CONFIG,
945
     _GetInstNic(0, nic_link_fn)),
946

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

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

    
979
  return fields
980

    
981

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

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

989
  """
990
  usage = ctx.disk_usage[inst.name]
991

    
992
  if usage is None:
993
    usage = 0
994

    
995
  return usage
996

    
997

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

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

1005
  """
1006
  consinfo = ctx.console[inst.name]
1007

    
1008
  if consinfo is None:
1009
    return _FS_UNAVAIL
1010

    
1011
  return consinfo
1012

    
1013

    
1014
def _GetInstanceDiskFields():
1015
  """Get instance fields involving disks.
1016

1017
  @return: List of field definitions used as input for L{_PrepareFieldList}
1018

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

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

    
1036
  return fields
1037

    
1038

    
1039
def _GetInstanceParameterFields():
1040
  """Get instance fields involving parameters.
1041

1042
  @return: List of field definitions used as input for L{_PrepareFieldList}
1043

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

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

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

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

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

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

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

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

    
1103
  return fields
1104

    
1105

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

    
1117

    
1118
def _BuildInstanceFields():
1119
  """Builds list of fields for instance queries.
1120

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

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

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

    
1150
  fields.extend(_GetInstanceParameterFields())
1151
  fields.extend(_GetInstanceDiskFields())
1152
  fields.extend(_GetInstanceNetworkFields())
1153
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1154

    
1155
  aliases = [
1156
    ("vcpus", "be/vcpus"),
1157
    ("sda_size", "disk.size/0"),
1158
    ("sdb_size", "disk.size/1"),
1159
    ]
1160

    
1161
  return _PrepareFieldList(fields, aliases)
1162

    
1163

    
1164
class LockQueryData:
1165
  """Data container for lock data queries.
1166

1167
  """
1168
  def __init__(self, lockdata):
1169
    """Initializes this class.
1170

1171
    """
1172
    self.lockdata = lockdata
1173

    
1174
  def __iter__(self):
1175
    """Iterate over all locks.
1176

1177
    """
1178
    return iter(self.lockdata)
1179

    
1180

    
1181
def _GetLockOwners(_, data):
1182
  """Returns a sorted list of a lock's current owners.
1183

1184
  """
1185
  (_, _, owners, _) = data
1186

    
1187
  if owners:
1188
    owners = utils.NiceSort(owners)
1189

    
1190
  return owners
1191

    
1192

    
1193
def _GetLockPending(_, data):
1194
  """Returns a sorted list of a lock's pending acquires.
1195

1196
  """
1197
  (_, _, _, pending) = data
1198

    
1199
  if pending:
1200
    pending = [(mode, utils.NiceSort(names))
1201
               for (mode, names) in pending]
1202

    
1203
  return pending
1204

    
1205

    
1206
def _BuildLockFields():
1207
  """Builds list of fields for lock queries.
1208

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

    
1219

    
1220
class GroupQueryData:
1221
  """Data container for node group data queries.
1222

1223
  """
1224
  def __init__(self, groups, group_to_nodes, group_to_instances):
1225
    """Initializes this class.
1226

1227
    @param groups: List of node group objects
1228
    @type group_to_nodes: dict; group UUID as key
1229
    @param group_to_nodes: Per-group list of nodes
1230
    @type group_to_instances: dict; group UUID as key
1231
    @param group_to_instances: Per-group list of (primary) instances
1232

1233
    """
1234
    self.groups = groups
1235
    self.group_to_nodes = group_to_nodes
1236
    self.group_to_instances = group_to_instances
1237

    
1238
  def __iter__(self):
1239
    """Iterate over all node groups.
1240

1241
    """
1242
    return iter(self.groups)
1243

    
1244

    
1245
_GROUP_SIMPLE_FIELDS = {
1246
  "alloc_policy": ("AllocPolicy", QFT_TEXT),
1247
  "name": ("Group", QFT_TEXT),
1248
  "serial_no": ("SerialNo", QFT_NUMBER),
1249
  "uuid": ("UUID", QFT_TEXT),
1250
  "ndparams": ("NDParams", QFT_OTHER),
1251
  }
1252

    
1253

    
1254
def _BuildGroupFields():
1255
  """Builds list of fields for node group queries.
1256

1257
  """
1258
  # Add simple fields
1259
  fields = [(_MakeField(name, title, kind), GQ_CONFIG, _GetItemAttr(name))
1260
            for (name, (title, kind)) in _GROUP_SIMPLE_FIELDS.items()]
1261

    
1262
  def _GetLength(getter):
1263
    return lambda ctx, group: len(getter(ctx)[group.uuid])
1264

    
1265
  def _GetSortedList(getter):
1266
    return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
1267

    
1268
  group_to_nodes = operator.attrgetter("group_to_nodes")
1269
  group_to_instances = operator.attrgetter("group_to_instances")
1270

    
1271
  # Add fields for nodes
1272
  fields.extend([
1273
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER),
1274
     GQ_NODE, _GetLength(group_to_nodes)),
1275
    (_MakeField("node_list", "NodeList", QFT_OTHER),
1276
     GQ_NODE, _GetSortedList(group_to_nodes)),
1277
    ])
1278

    
1279
  # Add fields for instances
1280
  fields.extend([
1281
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER),
1282
     GQ_INST, _GetLength(group_to_instances)),
1283
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER),
1284
     GQ_INST, _GetSortedList(group_to_instances)),
1285
    ])
1286

    
1287
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1288

    
1289
  return _PrepareFieldList(fields, [])
1290

    
1291

    
1292
#: Fields available for node queries
1293
NODE_FIELDS = _BuildNodeFields()
1294

    
1295
#: Fields available for instance queries
1296
INSTANCE_FIELDS = _BuildInstanceFields()
1297

    
1298
#: Fields available for lock queries
1299
LOCK_FIELDS = _BuildLockFields()
1300

    
1301
#: Fields available for node group queries
1302
GROUP_FIELDS = _BuildGroupFields()
1303

    
1304
#: All available field lists
1305
ALL_FIELD_LISTS = [NODE_FIELDS, INSTANCE_FIELDS, LOCK_FIELDS, GROUP_FIELDS]