Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ d63bd540

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
                              QRFS_NORMAL, QRFS_UNKNOWN, QRFS_NODATA,
67
                              QRFS_UNAVAIL, QRFS_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 (QRFS_UNKNOWN, None)
248
  elif value is _FS_NODATA:
249
    return (QRFS_NODATA, None)
250
  elif value is _FS_UNAVAIL:
251
    return (QRFS_UNAVAIL, None)
252
  elif value is _FS_OFFLINE:
253
    return (QRFS_OFFLINE, None)
254
  else:
255
    return (QRFS_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 == QRFS_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):
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

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

    
687
    self.instances = instances
688
    self.cluster = cluster
689
    self.disk_usage = disk_usage
690
    self.offline_nodes = offline_nodes
691
    self.bad_nodes = bad_nodes
692
    self.live_data = live_data
693

    
694
    # Used for individual rows
695
    self.inst_hvparams = None
696
    self.inst_beparams = None
697
    self.inst_nicparams = None
698

    
699
  def __iter__(self):
700
    """Iterate over all instances.
701

702
    This function has side-effects and only one instance of the resulting
703
    generator should be used at a time.
704

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

    
712
      yield inst
713

    
714

    
715
def _GetInstOperState(ctx, inst):
716
  """Get instance's operational status.
717

718
  @type ctx: L{InstanceQueryData}
719
  @type inst: L{objects.Instance}
720
  @param inst: Instance object
721

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

    
730

    
731
def _GetInstLiveData(name):
732
  """Build function for retrieving live data.
733

734
  @type name: string
735
  @param name: Live data field name
736

737
  """
738
  def fn(ctx, inst):
739
    """Get live data for an instance.
740

741
    @type ctx: L{InstanceQueryData}
742
    @type inst: L{objects.Instance}
743
    @param inst: Instance object
744

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

    
752
    if inst.name in ctx.live_data:
753
      data = ctx.live_data[inst.name]
754
      if name in data:
755
        return data[name]
756

    
757
    return _FS_UNAVAIL
758

    
759
  return fn
760

    
761

    
762
def _GetInstStatus(ctx, inst):
763
  """Get instance status.
764

765
  @type ctx: L{InstanceQueryData}
766
  @type inst: L{objects.Instance}
767
  @param inst: Instance object
768

769
  """
770
  if inst.primary_node in ctx.offline_nodes:
771
    return "ERROR_nodeoffline"
772

    
773
  if inst.primary_node in ctx.bad_nodes:
774
    return "ERROR_nodedown"
775

    
776
  if bool(ctx.live_data.get(inst.name)):
777
    if inst.admin_up:
778
      return "running"
779
    else:
780
      return "ERROR_up"
781

    
782
  if inst.admin_up:
783
    return "ERROR_down"
784

    
785
  return "ADMIN_down"
786

    
787

    
788
def _GetInstDiskSize(index):
789
  """Build function for retrieving disk size.
790

791
  @type index: int
792
  @param index: Disk index
793

794
  """
795
  def fn(_, inst):
796
    """Get size of a disk.
797

798
    @type inst: L{objects.Instance}
799
    @param inst: Instance object
800

801
    """
802
    try:
803
      return inst.disks[index].size
804
    except IndexError:
805
      return _FS_UNAVAIL
806

    
807
  return fn
808

    
809

    
810
def _GetInstNic(index, cb):
811
  """Build function for calling another function with an instance NIC.
812

813
  @type index: int
814
  @param index: NIC index
815
  @type cb: callable
816
  @param cb: Callback
817

818
  """
819
  def fn(ctx, inst):
820
    """Call helper function with instance NIC.
821

822
    @type ctx: L{InstanceQueryData}
823
    @type inst: L{objects.Instance}
824
    @param inst: Instance object
825

826
    """
827
    try:
828
      nic = inst.nics[index]
829
    except IndexError:
830
      return _FS_UNAVAIL
831

    
832
    return cb(ctx, index, nic)
833

    
834
  return fn
835

    
836

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

840
  @type ctx: L{InstanceQueryData}
841
  @type nic: L{objects.NIC}
842
  @param nic: NIC object
843

844
  """
845
  if nic.ip is None:
846
    return _FS_UNAVAIL
847
  else:
848
    return nic.ip
849

    
850

    
851
def _GetInstNicBridge(ctx, index, _):
852
  """Get a NIC's bridge.
853

854
  @type ctx: L{InstanceQueryData}
855
  @type index: int
856
  @param index: NIC index
857

858
  """
859
  assert len(ctx.inst_nicparams) >= index
860

    
861
  nicparams = ctx.inst_nicparams[index]
862

    
863
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
864
    return nicparams[constants.NIC_LINK]
865
  else:
866
    return _FS_UNAVAIL
867

    
868

    
869
def _GetInstAllNicBridges(ctx, inst):
870
  """Get all network bridges for an instance.
871

872
  @type ctx: L{InstanceQueryData}
873
  @type inst: L{objects.Instance}
874
  @param inst: Instance object
875

876
  """
877
  assert len(ctx.inst_nicparams) == len(inst.nics)
878

    
879
  result = []
880

    
881
  for nicp in ctx.inst_nicparams:
882
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
883
      result.append(nicp[constants.NIC_LINK])
884
    else:
885
      result.append(None)
886

    
887
  assert len(result) == len(inst.nics)
888

    
889
  return result
890

    
891

    
892
def _GetInstNicParam(name):
893
  """Build function for retrieving a NIC parameter.
894

895
  @type name: string
896
  @param name: Parameter name
897

898
  """
899
  def fn(ctx, index, _):
900
    """Get a NIC's bridge.
901

902
    @type ctx: L{InstanceQueryData}
903
    @type inst: L{objects.Instance}
904
    @param inst: Instance object
905
    @type nic: L{objects.NIC}
906
    @param nic: NIC object
907

908
    """
909
    assert len(ctx.inst_nicparams) >= index
910
    return ctx.inst_nicparams[index][name]
911

    
912
  return fn
913

    
914

    
915
def _GetInstanceNetworkFields():
916
  """Get instance fields involving network interfaces.
917

918
  @return: List of field definitions used as input for L{_PrepareFieldList}
919

920
  """
921
  nic_mac_fn = lambda ctx, _, nic: nic.mac
922
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
923
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
924

    
925
  fields = [
926
    # First NIC (legacy)
927
    (_MakeField("ip", "IP_address", QFT_TEXT), IQ_CONFIG,
928
     _GetInstNic(0, _GetInstNicIp)),
929
    (_MakeField("mac", "MAC_address", QFT_TEXT), IQ_CONFIG,
930
     _GetInstNic(0, nic_mac_fn)),
931
    (_MakeField("bridge", "Bridge", QFT_TEXT), IQ_CONFIG,
932
     _GetInstNic(0, _GetInstNicBridge)),
933
    (_MakeField("nic_mode", "NIC_Mode", QFT_TEXT), IQ_CONFIG,
934
     _GetInstNic(0, nic_mode_fn)),
935
    (_MakeField("nic_link", "NIC_Link", QFT_TEXT), IQ_CONFIG,
936
     _GetInstNic(0, nic_link_fn)),
937

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

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

    
970
  return fields
971

    
972

    
973
def _GetInstDiskUsage(ctx, inst):
974
  """Get disk usage for an instance.
975

976
  @type ctx: L{InstanceQueryData}
977
  @type inst: L{objects.Instance}
978
  @param inst: Instance object
979

980
  """
981
  usage = ctx.disk_usage[inst.name]
982

    
983
  if usage is None:
984
    usage = 0
985

    
986
  return usage
987

    
988

    
989
def _GetInstanceDiskFields():
990
  """Get instance fields involving disks.
991

992
  @return: List of field definitions used as input for L{_PrepareFieldList}
993

994
  """
995
  fields = [
996
    (_MakeField("disk_usage", "DiskUsage", QFT_UNIT), IQ_DISKUSAGE,
997
     _GetInstDiskUsage),
998
    (_MakeField("sda_size", "LegacyDisk/0", QFT_UNIT), IQ_CONFIG,
999
     _GetInstDiskSize(0)),
1000
    (_MakeField("sdb_size", "LegacyDisk/1", QFT_UNIT), IQ_CONFIG,
1001
     _GetInstDiskSize(1)),
1002
    (_MakeField("disk.count", "Disks", QFT_NUMBER), IQ_CONFIG,
1003
     lambda ctx, inst: len(inst.disks)),
1004
    (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER), IQ_CONFIG,
1005
     lambda ctx, inst: [disk.size for disk in inst.disks]),
1006
    ]
1007

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

    
1015
  return fields
1016

    
1017

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

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

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

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

    
1043
  fields = [
1044
    # Filled parameters
1045
    (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER),
1046
     IQ_CONFIG, lambda ctx, _: ctx.inst_hvparams),
1047
    (_MakeField("beparams", "BackendParameters", QFT_OTHER),
1048
     IQ_CONFIG, lambda ctx, _: ctx.inst_beparams),
1049
    (_MakeField("vcpus", "LegacyVCPUs", QFT_NUMBER), IQ_CONFIG,
1050
     lambda ctx, _: ctx.inst_beparams[constants.BE_VCPUS]),
1051

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

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

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

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

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

    
1084
  return fields
1085

    
1086

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

    
1098

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

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

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

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

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

    
1134
  return _PrepareFieldList(fields, [])
1135

    
1136

    
1137
class LockQueryData:
1138
  """Data container for lock data queries.
1139

1140
  """
1141
  def __init__(self, lockdata):
1142
    """Initializes this class.
1143

1144
    """
1145
    self.lockdata = lockdata
1146

    
1147
  def __iter__(self):
1148
    """Iterate over all locks.
1149

1150
    """
1151
    return iter(self.lockdata)
1152

    
1153

    
1154
def _GetLockOwners(_, data):
1155
  """Returns a sorted list of a lock's current owners.
1156

1157
  """
1158
  (_, _, owners, _) = data
1159

    
1160
  if owners:
1161
    owners = utils.NiceSort(owners)
1162

    
1163
  return owners
1164

    
1165

    
1166
def _GetLockPending(_, data):
1167
  """Returns a sorted list of a lock's pending acquires.
1168

1169
  """
1170
  (_, _, _, pending) = data
1171

    
1172
  if pending:
1173
    pending = [(mode, utils.NiceSort(names))
1174
               for (mode, names) in pending]
1175

    
1176
  return pending
1177

    
1178

    
1179
def _BuildLockFields():
1180
  """Builds list of fields for lock queries.
1181

1182
  """
1183
  return _PrepareFieldList([
1184
    (_MakeField("name", "Name", QFT_TEXT), None,
1185
     lambda ctx, (name, mode, owners, pending): name),
1186
    (_MakeField("mode", "Mode", QFT_OTHER), LQ_MODE,
1187
     lambda ctx, (name, mode, owners, pending): mode),
1188
    (_MakeField("owner", "Owner", QFT_OTHER), LQ_OWNER, _GetLockOwners),
1189
    (_MakeField("pending", "Pending", QFT_OTHER), LQ_PENDING, _GetLockPending),
1190
    ], [])
1191

    
1192

    
1193
class GroupQueryData:
1194
  """Data container for node group data queries.
1195

1196
  """
1197
  def __init__(self, groups, group_to_nodes, group_to_instances):
1198
    """Initializes this class.
1199

1200
    @param groups: List of node group objects
1201
    @type group_to_nodes: dict; group UUID as key
1202
    @param group_to_nodes: Per-group list of nodes
1203
    @type group_to_instances: dict; group UUID as key
1204
    @param group_to_instances: Per-group list of (primary) instances
1205

1206
    """
1207
    self.groups = groups
1208
    self.group_to_nodes = group_to_nodes
1209
    self.group_to_instances = group_to_instances
1210

    
1211
  def __iter__(self):
1212
    """Iterate over all node groups.
1213

1214
    """
1215
    return iter(self.groups)
1216

    
1217

    
1218
_GROUP_SIMPLE_FIELDS = {
1219
  "alloc_policy": ("AllocPolicy", QFT_TEXT),
1220
  "name": ("Group", QFT_TEXT),
1221
  "serial_no": ("SerialNo", QFT_NUMBER),
1222
  "uuid": ("UUID", QFT_TEXT),
1223
  "ndparams": ("NDParams", QFT_OTHER),
1224
  }
1225

    
1226

    
1227
def _BuildGroupFields():
1228
  """Builds list of fields for node group queries.
1229

1230
  """
1231
  # Add simple fields
1232
  fields = [(_MakeField(name, title, kind), GQ_CONFIG, _GetItemAttr(name))
1233
            for (name, (title, kind)) in _GROUP_SIMPLE_FIELDS.items()]
1234

    
1235
  def _GetLength(getter):
1236
    return lambda ctx, group: len(getter(ctx)[group.uuid])
1237

    
1238
  def _GetSortedList(getter):
1239
    return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
1240

    
1241
  group_to_nodes = operator.attrgetter("group_to_nodes")
1242
  group_to_instances = operator.attrgetter("group_to_instances")
1243

    
1244
  # Add fields for nodes
1245
  fields.extend([
1246
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER),
1247
     GQ_NODE, _GetLength(group_to_nodes)),
1248
    (_MakeField("node_list", "NodeList", QFT_OTHER),
1249
     GQ_NODE, _GetSortedList(group_to_nodes)),
1250
    ])
1251

    
1252
  # Add fields for instances
1253
  fields.extend([
1254
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER),
1255
     GQ_INST, _GetLength(group_to_instances)),
1256
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER),
1257
     GQ_INST, _GetSortedList(group_to_instances)),
1258
    ])
1259

    
1260
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1261

    
1262
  return _PrepareFieldList(fields, [])
1263

    
1264

    
1265
#: Fields available for node queries
1266
NODE_FIELDS = _BuildNodeFields()
1267

    
1268
#: Fields available for instance queries
1269
INSTANCE_FIELDS = _BuildInstanceFields()
1270

    
1271
#: Fields available for lock queries
1272
LOCK_FIELDS = _BuildLockFields()
1273

    
1274
#: Fields available for node group queries
1275
GROUP_FIELDS = _BuildGroupFields()
1276

    
1277
#: All available field lists
1278
ALL_FIELD_LISTS = [NODE_FIELDS, INSTANCE_FIELDS, LOCK_FIELDS, GROUP_FIELDS]