Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ e431074f

History | View | Annotate | Download (34.5 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) = range(100, 103)
83

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

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

    
92

    
93
FIELD_NAME_RE = re.compile(r"^[a-z0-9/._]+$")
94
TITLE_RE = re.compile(r"^[^\s]+$")
95

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

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

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

    
123

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

127
  """
128
  return _FS_UNKNOWN
129

    
130

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

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

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

141
  """
142
  result = []
143

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

    
150
    assert len(fdef) == 3
151

    
152
    result.append(fdef)
153

    
154
  return result
155

    
156

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

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

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

    
165

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

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

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

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

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

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

191
    @rtype: frozenset
192

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

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

201
    Includes unknown fields.
202

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

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

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

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

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

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

    
223
    return result
224

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

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

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

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

    
241

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

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

    
257

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

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

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

    
279

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

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

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

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

    
301
  result = {}
302

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

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

    
313
    result[fdef.name] = field
314

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

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

    
327
  return result
328

    
329

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

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

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

    
340

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

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

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

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

    
361

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

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

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

    
372

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

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

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

    
393

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

397
  @param attr: Attribute name
398

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

    
403

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

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

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

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

    
422
  return fn
423

    
424

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

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

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

    
438

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

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

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

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

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

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

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

    
474

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

    
487

    
488
#: Fields requiring talking to the node
489
_NODE_LIVE_FIELDS = {
490
  "bootid": ("BootID", QFT_TEXT, "bootid"),
491
  "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes"),
492
  "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets"),
493
  "ctotal": ("CTotal", QFT_NUMBER, "cpu_total"),
494
  "dfree": ("DFree", QFT_UNIT, "vg_free"),
495
  "dtotal": ("DTotal", QFT_UNIT, "vg_size"),
496
  "mfree": ("MFree", QFT_UNIT, "memory_free"),
497
  "mnode": ("MNode", QFT_UNIT, "memory_dom0"),
498
  "mtotal": ("MTotal", QFT_UNIT, "memory_total"),
499
  }
500

    
501

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

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

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

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

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

    
521
    return cb(ctx, node, ng)
522

    
523
  return fn
524

    
525

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

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

535
  """
536
  return ng.name
537

    
538

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

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

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

    
550
  return _FS_UNAVAIL
551

    
552

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

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

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

    
565

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

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

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

    
579
  if not ctx.curlive_data:
580
    return _FS_NODATA
581

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

    
587
  if kind == QFT_TEXT:
588
    return value
589

    
590
  assert kind in (QFT_NUMBER, QFT_UNIT)
591

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

    
600

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

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

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

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

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

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

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

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

    
659
  return _PrepareFieldList(fields, [])
660

    
661

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

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

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

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

    
689
    self.instances = instances
690
    self.cluster = cluster
691
    self.disk_usage = disk_usage
692
    self.offline_nodes = offline_nodes
693
    self.bad_nodes = bad_nodes
694
    self.live_data = live_data
695
    self.wrongnode_inst = wrongnode_inst
696

    
697
    # Used for individual rows
698
    self.inst_hvparams = None
699
    self.inst_beparams = None
700
    self.inst_nicparams = None
701

    
702
  def __iter__(self):
703
    """Iterate over all instances.
704

705
    This function has side-effects and only one instance of the resulting
706
    generator should be used at a time.
707

708
    """
709
    for inst in self.instances:
710
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
711
      self.inst_beparams = self.cluster.FillBE(inst)
712
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
713
                             for nic in inst.nics]
714

    
715
      yield inst
716

    
717

    
718
def _GetInstOperState(ctx, inst):
719
  """Get instance's operational status.
720

721
  @type ctx: L{InstanceQueryData}
722
  @type inst: L{objects.Instance}
723
  @param inst: Instance object
724

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

    
733

    
734
def _GetInstLiveData(name):
735
  """Build function for retrieving live data.
736

737
  @type name: string
738
  @param name: Live data field name
739

740
  """
741
  def fn(ctx, inst):
742
    """Get live data for an instance.
743

744
    @type ctx: L{InstanceQueryData}
745
    @type inst: L{objects.Instance}
746
    @param inst: Instance object
747

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

    
755
    if inst.name in ctx.live_data:
756
      data = ctx.live_data[inst.name]
757
      if name in data:
758
        return data[name]
759

    
760
    return _FS_UNAVAIL
761

    
762
  return fn
763

    
764

    
765
def _GetInstStatus(ctx, inst):
766
  """Get instance status.
767

768
  @type ctx: L{InstanceQueryData}
769
  @type inst: L{objects.Instance}
770
  @param inst: Instance object
771

772
  """
773
  if inst.primary_node in ctx.offline_nodes:
774
    return "ERROR_nodeoffline"
775

    
776
  if inst.primary_node in ctx.bad_nodes:
777
    return "ERROR_nodedown"
778

    
779
  if bool(ctx.live_data.get(inst.name)):
780
    if inst.name in ctx.wrongnode_inst:
781
      return "ERROR_wrongnode"
782
    elif inst.admin_up:
783
      return "running"
784
    else:
785
      return "ERROR_up"
786

    
787
  if inst.admin_up:
788
    return "ERROR_down"
789

    
790
  return "ADMIN_down"
791

    
792

    
793
def _GetInstDiskSize(index):
794
  """Build function for retrieving disk size.
795

796
  @type index: int
797
  @param index: Disk index
798

799
  """
800
  def fn(_, inst):
801
    """Get size of a disk.
802

803
    @type inst: L{objects.Instance}
804
    @param inst: Instance object
805

806
    """
807
    try:
808
      return inst.disks[index].size
809
    except IndexError:
810
      return _FS_UNAVAIL
811

    
812
  return fn
813

    
814

    
815
def _GetInstNic(index, cb):
816
  """Build function for calling another function with an instance NIC.
817

818
  @type index: int
819
  @param index: NIC index
820
  @type cb: callable
821
  @param cb: Callback
822

823
  """
824
  def fn(ctx, inst):
825
    """Call helper function with instance NIC.
826

827
    @type ctx: L{InstanceQueryData}
828
    @type inst: L{objects.Instance}
829
    @param inst: Instance object
830

831
    """
832
    try:
833
      nic = inst.nics[index]
834
    except IndexError:
835
      return _FS_UNAVAIL
836

    
837
    return cb(ctx, index, nic)
838

    
839
  return fn
840

    
841

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

845
  @type ctx: L{InstanceQueryData}
846
  @type nic: L{objects.NIC}
847
  @param nic: NIC object
848

849
  """
850
  if nic.ip is None:
851
    return _FS_UNAVAIL
852
  else:
853
    return nic.ip
854

    
855

    
856
def _GetInstNicBridge(ctx, index, _):
857
  """Get a NIC's bridge.
858

859
  @type ctx: L{InstanceQueryData}
860
  @type index: int
861
  @param index: NIC index
862

863
  """
864
  assert len(ctx.inst_nicparams) >= index
865

    
866
  nicparams = ctx.inst_nicparams[index]
867

    
868
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
869
    return nicparams[constants.NIC_LINK]
870
  else:
871
    return _FS_UNAVAIL
872

    
873

    
874
def _GetInstAllNicBridges(ctx, inst):
875
  """Get all network bridges for an instance.
876

877
  @type ctx: L{InstanceQueryData}
878
  @type inst: L{objects.Instance}
879
  @param inst: Instance object
880

881
  """
882
  assert len(ctx.inst_nicparams) == len(inst.nics)
883

    
884
  result = []
885

    
886
  for nicp in ctx.inst_nicparams:
887
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
888
      result.append(nicp[constants.NIC_LINK])
889
    else:
890
      result.append(None)
891

    
892
  assert len(result) == len(inst.nics)
893

    
894
  return result
895

    
896

    
897
def _GetInstNicParam(name):
898
  """Build function for retrieving a NIC parameter.
899

900
  @type name: string
901
  @param name: Parameter name
902

903
  """
904
  def fn(ctx, index, _):
905
    """Get a NIC's bridge.
906

907
    @type ctx: L{InstanceQueryData}
908
    @type inst: L{objects.Instance}
909
    @param inst: Instance object
910
    @type nic: L{objects.NIC}
911
    @param nic: NIC object
912

913
    """
914
    assert len(ctx.inst_nicparams) >= index
915
    return ctx.inst_nicparams[index][name]
916

    
917
  return fn
918

    
919

    
920
def _GetInstanceNetworkFields():
921
  """Get instance fields involving network interfaces.
922

923
  @return: List of field definitions used as input for L{_PrepareFieldList}
924

925
  """
926
  nic_mac_fn = lambda ctx, _, nic: nic.mac
927
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
928
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
929

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

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

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

    
975
  return fields
976

    
977

    
978
def _GetInstDiskUsage(ctx, inst):
979
  """Get disk usage for an instance.
980

981
  @type ctx: L{InstanceQueryData}
982
  @type inst: L{objects.Instance}
983
  @param inst: Instance object
984

985
  """
986
  usage = ctx.disk_usage[inst.name]
987

    
988
  if usage is None:
989
    usage = 0
990

    
991
  return usage
992

    
993

    
994
def _GetInstanceDiskFields():
995
  """Get instance fields involving disks.
996

997
  @return: List of field definitions used as input for L{_PrepareFieldList}
998

999
  """
1000
  fields = [
1001
    (_MakeField("disk_usage", "DiskUsage", QFT_UNIT), IQ_DISKUSAGE,
1002
     _GetInstDiskUsage),
1003
    (_MakeField("disk.count", "Disks", QFT_NUMBER), IQ_CONFIG,
1004
     lambda ctx, inst: len(inst.disks)),
1005
    (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER), IQ_CONFIG,
1006
     lambda ctx, inst: [disk.size for disk in inst.disks]),
1007
    ]
1008

    
1009
  # Disks by number
1010
  fields.extend([
1011
    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT),
1012
     IQ_CONFIG, _GetInstDiskSize(i))
1013
    for i in range(constants.MAX_DISKS)
1014
    ])
1015

    
1016
  return fields
1017

    
1018

    
1019
def _GetInstanceParameterFields():
1020
  """Get instance fields involving parameters.
1021

1022
  @return: List of field definitions used as input for L{_PrepareFieldList}
1023

1024
  """
1025
  # TODO: Consider moving titles closer to constants
1026
  be_title = {
1027
    constants.BE_AUTO_BALANCE: "Auto_balance",
1028
    constants.BE_MEMORY: "ConfigMemory",
1029
    constants.BE_VCPUS: "ConfigVCPUs",
1030
    }
1031

    
1032
  hv_title = {
1033
    constants.HV_ACPI: "ACPI",
1034
    constants.HV_BOOT_ORDER: "Boot_order",
1035
    constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1036
    constants.HV_DISK_TYPE: "Disk_type",
1037
    constants.HV_INITRD_PATH: "Initrd_path",
1038
    constants.HV_KERNEL_PATH: "Kernel_path",
1039
    constants.HV_NIC_TYPE: "NIC_type",
1040
    constants.HV_PAE: "PAE",
1041
    constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1042
    }
1043

    
1044
  fields = [
1045
    # Filled parameters
1046
    (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER),
1047
     IQ_CONFIG, lambda ctx, _: ctx.inst_hvparams),
1048
    (_MakeField("beparams", "BackendParameters", QFT_OTHER),
1049
     IQ_CONFIG, lambda ctx, _: ctx.inst_beparams),
1050

    
1051
    # Unfilled parameters
1052
    (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER),
1053
     IQ_CONFIG, _GetItemAttr("hvparams")),
1054
    (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER),
1055
     IQ_CONFIG, _GetItemAttr("beparams")),
1056
    (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER),
1057
     IQ_CONFIG, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1058
    ]
1059

    
1060
  # HV params
1061
  def _GetInstHvParam(name):
1062
    return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1063

    
1064
  fields.extend([
1065
    (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1066
                _VTToQFT[kind]),
1067
     IQ_CONFIG, _GetInstHvParam(name))
1068
    for name, kind in constants.HVS_PARAMETER_TYPES.items()
1069
    if name not in constants.HVC_GLOBALS
1070
    ])
1071

    
1072
  # BE params
1073
  def _GetInstBeParam(name):
1074
    return lambda ctx, _: ctx.inst_beparams.get(name, None)
1075

    
1076
  fields.extend([
1077
    (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1078
                _VTToQFT[kind]), IQ_CONFIG,
1079
     _GetInstBeParam(name))
1080
    for name, kind in constants.BES_PARAMETER_TYPES.items()
1081
    ])
1082

    
1083
  return fields
1084

    
1085

    
1086
_INST_SIMPLE_FIELDS = {
1087
  "disk_template": ("Disk_template", QFT_TEXT),
1088
  "hypervisor": ("Hypervisor", QFT_TEXT),
1089
  "name": ("Node", QFT_TEXT),
1090
  # Depending on the hypervisor, the port can be None
1091
  "network_port": ("Network_port", QFT_OTHER),
1092
  "os": ("OS", QFT_TEXT),
1093
  "serial_no": ("SerialNo", QFT_NUMBER),
1094
  "uuid": ("UUID", QFT_TEXT),
1095
  }
1096

    
1097

    
1098
def _BuildInstanceFields():
1099
  """Builds list of fields for instance queries.
1100

1101
  """
1102
  fields = [
1103
    (_MakeField("pnode", "Primary_node", QFT_TEXT), IQ_CONFIG,
1104
     _GetItemAttr("primary_node")),
1105
    (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER), IQ_CONFIG,
1106
     lambda ctx, inst: list(inst.secondary_nodes)),
1107
    (_MakeField("admin_state", "Autostart", QFT_BOOL), IQ_CONFIG,
1108
     _GetItemAttr("admin_up")),
1109
    (_MakeField("tags", "Tags", QFT_OTHER), IQ_CONFIG,
1110
     lambda ctx, inst: list(inst.GetTags())),
1111
    ]
1112

    
1113
  # Add simple fields
1114
  fields.extend([(_MakeField(name, title, kind), IQ_CONFIG, _GetItemAttr(name))
1115
                 for (name, (title, kind)) in _INST_SIMPLE_FIELDS.items()])
1116

    
1117
  # Fields requiring talking to the node
1118
  fields.extend([
1119
    (_MakeField("oper_state", "Running", QFT_BOOL), IQ_LIVE,
1120
     _GetInstOperState),
1121
    (_MakeField("oper_ram", "Memory", QFT_UNIT), IQ_LIVE,
1122
     _GetInstLiveData("memory")),
1123
    (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER), IQ_LIVE,
1124
     _GetInstLiveData("vcpus")),
1125
    (_MakeField("status", "Status", QFT_TEXT), IQ_LIVE, _GetInstStatus),
1126
    ])
1127

    
1128
  fields.extend(_GetInstanceParameterFields())
1129
  fields.extend(_GetInstanceDiskFields())
1130
  fields.extend(_GetInstanceNetworkFields())
1131
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1132

    
1133
  aliases = [
1134
    ("vcpus", "be/vcpus"),
1135
    ("sda_size", "disk.size/0"),
1136
    ("sdb_size", "disk.size/1"),
1137
    ]
1138

    
1139
  return _PrepareFieldList(fields, aliases)
1140

    
1141

    
1142
class LockQueryData:
1143
  """Data container for lock data queries.
1144

1145
  """
1146
  def __init__(self, lockdata):
1147
    """Initializes this class.
1148

1149
    """
1150
    self.lockdata = lockdata
1151

    
1152
  def __iter__(self):
1153
    """Iterate over all locks.
1154

1155
    """
1156
    return iter(self.lockdata)
1157

    
1158

    
1159
def _GetLockOwners(_, data):
1160
  """Returns a sorted list of a lock's current owners.
1161

1162
  """
1163
  (_, _, owners, _) = data
1164

    
1165
  if owners:
1166
    owners = utils.NiceSort(owners)
1167

    
1168
  return owners
1169

    
1170

    
1171
def _GetLockPending(_, data):
1172
  """Returns a sorted list of a lock's pending acquires.
1173

1174
  """
1175
  (_, _, _, pending) = data
1176

    
1177
  if pending:
1178
    pending = [(mode, utils.NiceSort(names))
1179
               for (mode, names) in pending]
1180

    
1181
  return pending
1182

    
1183

    
1184
def _BuildLockFields():
1185
  """Builds list of fields for lock queries.
1186

1187
  """
1188
  return _PrepareFieldList([
1189
    (_MakeField("name", "Name", QFT_TEXT), None,
1190
     lambda ctx, (name, mode, owners, pending): name),
1191
    (_MakeField("mode", "Mode", QFT_OTHER), LQ_MODE,
1192
     lambda ctx, (name, mode, owners, pending): mode),
1193
    (_MakeField("owner", "Owner", QFT_OTHER), LQ_OWNER, _GetLockOwners),
1194
    (_MakeField("pending", "Pending", QFT_OTHER), LQ_PENDING, _GetLockPending),
1195
    ], [])
1196

    
1197

    
1198
class GroupQueryData:
1199
  """Data container for node group data queries.
1200

1201
  """
1202
  def __init__(self, groups, group_to_nodes, group_to_instances):
1203
    """Initializes this class.
1204

1205
    @param groups: List of node group objects
1206
    @type group_to_nodes: dict; group UUID as key
1207
    @param group_to_nodes: Per-group list of nodes
1208
    @type group_to_instances: dict; group UUID as key
1209
    @param group_to_instances: Per-group list of (primary) instances
1210

1211
    """
1212
    self.groups = groups
1213
    self.group_to_nodes = group_to_nodes
1214
    self.group_to_instances = group_to_instances
1215

    
1216
  def __iter__(self):
1217
    """Iterate over all node groups.
1218

1219
    """
1220
    return iter(self.groups)
1221

    
1222

    
1223
_GROUP_SIMPLE_FIELDS = {
1224
  "alloc_policy": ("AllocPolicy", QFT_TEXT),
1225
  "name": ("Group", QFT_TEXT),
1226
  "serial_no": ("SerialNo", QFT_NUMBER),
1227
  "uuid": ("UUID", QFT_TEXT),
1228
  "ndparams": ("NDParams", QFT_OTHER),
1229
  }
1230

    
1231

    
1232
def _BuildGroupFields():
1233
  """Builds list of fields for node group queries.
1234

1235
  """
1236
  # Add simple fields
1237
  fields = [(_MakeField(name, title, kind), GQ_CONFIG, _GetItemAttr(name))
1238
            for (name, (title, kind)) in _GROUP_SIMPLE_FIELDS.items()]
1239

    
1240
  def _GetLength(getter):
1241
    return lambda ctx, group: len(getter(ctx)[group.uuid])
1242

    
1243
  def _GetSortedList(getter):
1244
    return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
1245

    
1246
  group_to_nodes = operator.attrgetter("group_to_nodes")
1247
  group_to_instances = operator.attrgetter("group_to_instances")
1248

    
1249
  # Add fields for nodes
1250
  fields.extend([
1251
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER),
1252
     GQ_NODE, _GetLength(group_to_nodes)),
1253
    (_MakeField("node_list", "NodeList", QFT_OTHER),
1254
     GQ_NODE, _GetSortedList(group_to_nodes)),
1255
    ])
1256

    
1257
  # Add fields for instances
1258
  fields.extend([
1259
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER),
1260
     GQ_INST, _GetLength(group_to_instances)),
1261
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER),
1262
     GQ_INST, _GetSortedList(group_to_instances)),
1263
    ])
1264

    
1265
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1266

    
1267
  return _PrepareFieldList(fields, [])
1268

    
1269

    
1270
#: Fields available for node queries
1271
NODE_FIELDS = _BuildNodeFields()
1272

    
1273
#: Fields available for instance queries
1274
INSTANCE_FIELDS = _BuildInstanceFields()
1275

    
1276
#: Fields available for lock queries
1277
LOCK_FIELDS = _BuildLockFields()
1278

    
1279
#: Fields available for node group queries
1280
GROUP_FIELDS = _BuildGroupFields()
1281

    
1282
#: All available field lists
1283
ALL_FIELD_LISTS = [NODE_FIELDS, INSTANCE_FIELDS, LOCK_FIELDS, GROUP_FIELDS]