Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ d1c3c3b3

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

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

117
  """
118
  return _FS_UNKNOWN
119

    
120

    
121
def _GetQueryFields(fielddefs, selected):
122
  """Calculates the internal list of selected fields.
123

124
  Unknown fields are returned as L{constants.QFT_UNKNOWN}.
125

126
  @type fielddefs: dict
127
  @param fielddefs: Field definitions
128
  @type selected: list of strings
129
  @param selected: List of selected fields
130

131
  """
132
  result = []
133

    
134
  for name in selected:
135
    try:
136
      fdef = fielddefs[name]
137
    except KeyError:
138
      fdef = (_MakeField(name, name, QFT_UNKNOWN), None, _GetUnknownField)
139

    
140
    assert len(fdef) == 3
141

    
142
    result.append(fdef)
143

    
144
  return result
145

    
146

    
147
def GetAllFields(fielddefs):
148
  """Extract L{objects.QueryFieldDefinition} from field definitions.
149

150
  @rtype: list of L{objects.QueryFieldDefinition}
151

152
  """
153
  return [fdef for (fdef, _, _) in fielddefs]
154

    
155

    
156
class Query:
157
  def __init__(self, fieldlist, selected):
158
    """Initializes this class.
159

160
    The field definition is a dictionary with the field's name as a key and a
161
    tuple containing, in order, the field definition object
162
    (L{objects.QueryFieldDefinition}, the data kind to help calling code
163
    collect data and a retrieval function. The retrieval function is called
164
    with two parameters, in order, the data container and the item in container
165
    (see L{Query.Query}).
166

167
    Users of this class can call L{RequestedData} before preparing the data
168
    container to determine what data is needed.
169

170
    @type fieldlist: dictionary
171
    @param fieldlist: Field definitions
172
    @type selected: list of strings
173
    @param selected: List of selected fields
174

175
    """
176
    self._fields = _GetQueryFields(fieldlist, selected)
177

    
178
  def RequestedData(self):
179
    """Gets requested kinds of data.
180

181
    @rtype: frozenset
182

183
    """
184
    return frozenset(datakind
185
                     for (_, datakind, _) in self._fields
186
                     if datakind is not None)
187

    
188
  def GetFields(self):
189
    """Returns the list of fields for this query.
190

191
    Includes unknown fields.
192

193
    @rtype: List of L{objects.QueryFieldDefinition}
194

195
    """
196
    return GetAllFields(self._fields)
197

    
198
  def Query(self, ctx):
199
    """Execute a query.
200

201
    @param ctx: Data container passed to field retrieval functions, must
202
      support iteration using C{__iter__}
203

204
    """
205
    result = [[_ProcessResult(fn(ctx, item)) for (_, _, fn) in self._fields]
206
              for item in ctx]
207

    
208
    # Verify result
209
    if __debug__:
210
      for row in result:
211
        _VerifyResultRow(self._fields, row)
212

    
213
    return result
214

    
215
  def OldStyleQuery(self, ctx):
216
    """Query with "old" query result format.
217

218
    See L{Query.Query} for arguments.
219

220
    """
221
    unknown = set(fdef.name
222
                  for (fdef, _, _) in self._fields if fdef.kind == QFT_UNKNOWN)
223
    if unknown:
224
      raise errors.OpPrereqError("Unknown output fields selected: %s" %
225
                                 (utils.CommaJoin(unknown), ),
226
                                 errors.ECODE_INVAL)
227

    
228
    return [[value for (_, value) in row]
229
            for row in self.Query(ctx)]
230

    
231

    
232
def _ProcessResult(value):
233
  """Converts result values into externally-visible ones.
234

235
  """
236
  if value is _FS_UNKNOWN:
237
    return (QRFS_UNKNOWN, None)
238
  elif value is _FS_NODATA:
239
    return (QRFS_NODATA, None)
240
  elif value is _FS_UNAVAIL:
241
    return (QRFS_UNAVAIL, None)
242
  elif value is _FS_OFFLINE:
243
    return (QRFS_OFFLINE, None)
244
  else:
245
    return (QRFS_NORMAL, value)
246

    
247

    
248
def _VerifyResultRow(fields, row):
249
  """Verifies the contents of a query result row.
250

251
  @type fields: list
252
  @param fields: Field definitions for result
253
  @type row: list of tuples
254
  @param row: Row data
255

256
  """
257
  assert len(row) == len(fields)
258
  errs = []
259
  for ((status, value), (fdef, _, _)) in zip(row, fields):
260
    if status == QRFS_NORMAL:
261
      if not _VERIFY_FN[fdef.kind](value):
262
        errs.append("normal field %s fails validation (value is %s)" %
263
                    (fdef.name, value))
264
    elif value is not None:
265
      errs.append("abnormal field %s has a non-None value" % fdef.name)
266
  assert not errs, ("Failed validation: %s in row %s" %
267
                    (utils.CommaJoin(errors), row))
268

    
269

    
270
def _PrepareFieldList(fields):
271
  """Prepares field list for use by L{Query}.
272

273
  Converts the list to a dictionary and does some verification.
274

275
  @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data kind,
276
    retrieval function)
277
  @param fields: List of fields, see L{Query.__init__} for a better description
278
  @rtype: dict
279
  @return: Field dictionary for L{Query}
280

281
  """
282
  if __debug__:
283
    duplicates = utils.FindDuplicates(fdef.title.lower()
284
                                      for (fdef, _, _) in fields)
285
    assert not duplicates, "Duplicate title(s) found: %r" % duplicates
286

    
287
  result = {}
288

    
289
  for field in fields:
290
    (fdef, _, fn) = field
291

    
292
    assert fdef.name and fdef.title, "Name and title are required"
293
    assert FIELD_NAME_RE.match(fdef.name)
294
    assert TITLE_RE.match(fdef.title)
295
    assert callable(fn)
296
    assert fdef.name not in result, \
297
           "Duplicate field name '%s' found" % fdef.name
298

    
299
    result[fdef.name] = field
300

    
301
  assert len(result) == len(fields)
302
  assert compat.all(name == fdef.name
303
                    for (name, (fdef, _, _)) in result.items())
304

    
305
  return result
306

    
307

    
308
def GetQueryResponse(query, ctx):
309
  """Prepares the response for a query.
310

311
  @type query: L{Query}
312
  @param ctx: Data container, see L{Query.Query}
313

314
  """
315
  return objects.QueryResponse(data=query.Query(ctx),
316
                               fields=query.GetFields()).ToDict()
317

    
318

    
319
def QueryFields(fielddefs, selected):
320
  """Returns list of available fields.
321

322
  @type fielddefs: dict
323
  @param fielddefs: Field definitions
324
  @type selected: list of strings
325
  @param selected: List of selected fields
326
  @return: List of L{objects.QueryFieldDefinition}
327

328
  """
329
  if selected is None:
330
    # Client requests all fields, sort by name
331
    fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
332
                           key=operator.attrgetter("name"))
333
  else:
334
    # Keep order as requested by client
335
    fdefs = Query(fielddefs, selected).GetFields()
336

    
337
  return objects.QueryFieldsResponse(fields=fdefs).ToDict()
338

    
339

    
340
def _MakeField(name, title, kind):
341
  """Wrapper for creating L{objects.QueryFieldDefinition} instances.
342

343
  @param name: Field name as a regular expression
344
  @param title: Human-readable title
345
  @param kind: Field type
346

347
  """
348
  return objects.QueryFieldDefinition(name=name, title=title, kind=kind)
349

    
350

    
351
def _GetNodeRole(node, master_name):
352
  """Determine node role.
353

354
  @type node: L{objects.Node}
355
  @param node: Node object
356
  @type master_name: string
357
  @param master_name: Master node name
358

359
  """
360
  if node.name == master_name:
361
    return "M"
362
  elif node.master_candidate:
363
    return "C"
364
  elif node.drained:
365
    return "D"
366
  elif node.offline:
367
    return "O"
368
  else:
369
    return "R"
370

    
371

    
372
def _GetItemAttr(attr):
373
  """Returns a field function to return an attribute of the item.
374

375
  @param attr: Attribute name
376

377
  """
378
  getter = operator.attrgetter(attr)
379
  return lambda _, item: getter(item)
380

    
381

    
382
def _GetItemTimestamp(getter):
383
  """Returns function for getting timestamp of item.
384

385
  @type getter: callable
386
  @param getter: Function to retrieve timestamp attribute
387

388
  """
389
  def fn(_, item):
390
    """Returns a timestamp of item.
391

392
    """
393
    timestamp = getter(item)
394
    if timestamp is None:
395
      # Old configs might not have all timestamps
396
      return _FS_UNAVAIL
397
    else:
398
      return timestamp
399

    
400
  return fn
401

    
402

    
403
def _GetItemTimestampFields(datatype):
404
  """Returns common timestamp fields.
405

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

408
  """
409
  return [
410
    (_MakeField("ctime", "CTime", QFT_TIMESTAMP), datatype,
411
     _GetItemTimestamp(operator.attrgetter("ctime"))),
412
    (_MakeField("mtime", "MTime", QFT_TIMESTAMP), datatype,
413
     _GetItemTimestamp(operator.attrgetter("mtime"))),
414
    ]
415

    
416

    
417
class NodeQueryData:
418
  """Data container for node data queries.
419

420
  """
421
  def __init__(self, nodes, live_data, master_name, node_to_primary,
422
               node_to_secondary, groups, oob_support, cluster):
423
    """Initializes this class.
424

425
    """
426
    self.nodes = nodes
427
    self.live_data = live_data
428
    self.master_name = master_name
429
    self.node_to_primary = node_to_primary
430
    self.node_to_secondary = node_to_secondary
431
    self.groups = groups
432
    self.oob_support = oob_support
433
    self.cluster = cluster
434

    
435
    # Used for individual rows
436
    self.curlive_data = None
437

    
438
  def __iter__(self):
439
    """Iterate over all nodes.
440

441
    This function has side-effects and only one instance of the resulting
442
    generator should be used at a time.
443

444
    """
445
    for node in self.nodes:
446
      if self.live_data:
447
        self.curlive_data = self.live_data.get(node.name, None)
448
      else:
449
        self.curlive_data = None
450
      yield node
451

    
452

    
453
#: Fields that are direct attributes of an L{objects.Node} object
454
_NODE_SIMPLE_FIELDS = {
455
  "drained": ("Drained", QFT_BOOL),
456
  "master_candidate": ("MasterC", QFT_BOOL),
457
  "master_capable": ("MasterCapable", QFT_BOOL),
458
  "name": ("Node", QFT_TEXT),
459
  "offline": ("Offline", QFT_BOOL),
460
  "serial_no": ("SerialNo", QFT_NUMBER),
461
  "uuid": ("UUID", QFT_TEXT),
462
  "vm_capable": ("VMCapable", QFT_BOOL),
463
  }
464

    
465

    
466
#: Fields requiring talking to the node
467
_NODE_LIVE_FIELDS = {
468
  "bootid": ("BootID", QFT_TEXT, "bootid"),
469
  "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes"),
470
  "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets"),
471
  "ctotal": ("CTotal", QFT_NUMBER, "cpu_total"),
472
  "dfree": ("DFree", QFT_UNIT, "vg_free"),
473
  "dtotal": ("DTotal", QFT_UNIT, "vg_size"),
474
  "mfree": ("MFree", QFT_UNIT, "memory_free"),
475
  "mnode": ("MNode", QFT_UNIT, "memory_dom0"),
476
  "mtotal": ("MTotal", QFT_UNIT, "memory_total"),
477
  }
478

    
479

    
480
def _GetGroup(cb):
481
  """Build function for calling another function with an node group.
482

483
  @param cb: The callback to be called with the nodegroup
484

485
  """
486
  def fn(ctx, node):
487
    """Get group data for a node.
488

489
    @type ctx: L{NodeQueryData}
490
    @type inst: L{objects.Node}
491
    @param inst: Node object
492

493
    """
494
    ng = ctx.groups.get(node.group, None)
495
    if ng is None:
496
      # Nodes always have a group, or the configuration is corrupt
497
      return _FS_UNAVAIL
498

    
499
    return cb(ctx, node, ng)
500

    
501
  return fn
502

    
503

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

507
  @type ctx: L{NodeQueryData}
508
  @type node: L{objects.Node}
509
  @param node: Node object
510
  @type ng: L{objects.NodeGroup}
511
  @param ng: The node group this node belongs to
512

513
  """
514
  return ng.name
515

    
516

    
517
def _GetNodePower(ctx, node):
518
  """Returns the node powered state
519

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

524
  """
525
  if ctx.oob_support[node.name]:
526
    return node.powered
527

    
528
  return _FS_UNAVAIL
529

    
530

    
531
def _GetNdParams(ctx, node, ng):
532
  """Returns the ndparams for this node.
533

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

540
  """
541
  return ctx.cluster.SimpleFillND(ng.FillND(node))
542

    
543

    
544
def _GetLiveNodeField(field, kind, ctx, node):
545
  """Gets the value of a "live" field from L{NodeQueryData}.
546

547
  @param field: Live field name
548
  @param kind: Data kind, one of L{constants.QFT_ALL}
549
  @type ctx: L{NodeQueryData}
550
  @type node: L{objects.Node}
551
  @param node: Node object
552

553
  """
554
  if node.offline:
555
    return _FS_OFFLINE
556

    
557
  if not ctx.curlive_data:
558
    return _FS_NODATA
559

    
560
  try:
561
    value = ctx.curlive_data[field]
562
  except KeyError:
563
    return _FS_UNAVAIL
564

    
565
  if kind == QFT_TEXT:
566
    return value
567

    
568
  assert kind in (QFT_NUMBER, QFT_UNIT)
569

    
570
  # Try to convert into number
571
  try:
572
    return int(value)
573
  except (ValueError, TypeError):
574
    logging.exception("Failed to convert node field '%s' (value %r) to int",
575
                      value, field)
576
    return _FS_UNAVAIL
577

    
578

    
579
def _BuildNodeFields():
580
  """Builds list of fields for node queries.
581

582
  """
583
  fields = [
584
    (_MakeField("pip", "PrimaryIP", QFT_TEXT), NQ_CONFIG,
585
     _GetItemAttr("primary_ip")),
586
    (_MakeField("sip", "SecondaryIP", QFT_TEXT), NQ_CONFIG,
587
     _GetItemAttr("secondary_ip")),
588
    (_MakeField("tags", "Tags", QFT_OTHER), NQ_CONFIG,
589
     lambda ctx, node: list(node.GetTags())),
590
    (_MakeField("master", "IsMaster", QFT_BOOL), NQ_CONFIG,
591
     lambda ctx, node: node.name == ctx.master_name),
592
    (_MakeField("role", "Role", QFT_TEXT), NQ_CONFIG,
593
     lambda ctx, node: _GetNodeRole(node, ctx.master_name)),
594
    (_MakeField("group", "Group", QFT_TEXT), NQ_GROUP,
595
     _GetGroup(_GetNodeGroup)),
596
    (_MakeField("group.uuid", "GroupUUID", QFT_TEXT),
597
     NQ_CONFIG, _GetItemAttr("group")),
598
    (_MakeField("powered", "Powered", QFT_BOOL), NQ_OOB, _GetNodePower),
599
    (_MakeField("ndparams", "NodeParameters", QFT_OTHER), NQ_GROUP,
600
      _GetGroup(_GetNdParams)),
601
    (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER),
602
      NQ_GROUP, _GetItemAttr("ndparams")),
603
    ]
604

    
605
  def _GetLength(getter):
606
    return lambda ctx, node: len(getter(ctx)[node.name])
607

    
608
  def _GetList(getter):
609
    return lambda ctx, node: list(getter(ctx)[node.name])
610

    
611
  # Add fields operating on instance lists
612
  for prefix, titleprefix, getter in \
613
      [("p", "Pri", operator.attrgetter("node_to_primary")),
614
       ("s", "Sec", operator.attrgetter("node_to_secondary"))]:
615
    fields.extend([
616
      (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER),
617
       NQ_INST, _GetLength(getter)),
618
      (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
619
                  QFT_OTHER),
620
       NQ_INST, _GetList(getter)),
621
      ])
622

    
623
  # Add simple fields
624
  fields.extend([(_MakeField(name, title, kind), NQ_CONFIG, _GetItemAttr(name))
625
                 for (name, (title, kind)) in _NODE_SIMPLE_FIELDS.items()])
626

    
627
  # Add fields requiring live data
628
  fields.extend([
629
    (_MakeField(name, title, kind), NQ_LIVE,
630
     compat.partial(_GetLiveNodeField, nfield, kind))
631
    for (name, (title, kind, nfield)) in _NODE_LIVE_FIELDS.items()
632
    ])
633

    
634
  # Add timestamps
635
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
636

    
637
  return _PrepareFieldList(fields)
638

    
639

    
640
class InstanceQueryData:
641
  """Data container for instance data queries.
642

643
  """
644
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
645
               live_data):
646
    """Initializes this class.
647

648
    @param instances: List of instance objects
649
    @param cluster: Cluster object
650
    @type disk_usage: dict; instance name as key
651
    @param disk_usage: Per-instance disk usage
652
    @type offline_nodes: list of strings
653
    @param offline_nodes: List of offline nodes
654
    @type bad_nodes: list of strings
655
    @param bad_nodes: List of faulty nodes
656
    @type live_data: dict; instance name as key
657
    @param live_data: Per-instance live data
658

659
    """
660
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
661
           "Offline nodes not included in bad nodes"
662
    assert not (set(live_data.keys()) & set(bad_nodes)), \
663
           "Found live data for bad or offline nodes"
664

    
665
    self.instances = instances
666
    self.cluster = cluster
667
    self.disk_usage = disk_usage
668
    self.offline_nodes = offline_nodes
669
    self.bad_nodes = bad_nodes
670
    self.live_data = live_data
671

    
672
    # Used for individual rows
673
    self.inst_hvparams = None
674
    self.inst_beparams = None
675
    self.inst_nicparams = None
676

    
677
  def __iter__(self):
678
    """Iterate over all instances.
679

680
    This function has side-effects and only one instance of the resulting
681
    generator should be used at a time.
682

683
    """
684
    for inst in self.instances:
685
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
686
      self.inst_beparams = self.cluster.FillBE(inst)
687
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
688
                             for nic in inst.nics]
689

    
690
      yield inst
691

    
692

    
693
def _GetInstOperState(ctx, inst):
694
  """Get instance's operational status.
695

696
  @type ctx: L{InstanceQueryData}
697
  @type inst: L{objects.Instance}
698
  @param inst: Instance object
699

700
  """
701
  # Can't use QRFS_OFFLINE here as it would describe the instance to
702
  # be offline when we actually don't know due to missing data
703
  if inst.primary_node in ctx.bad_nodes:
704
    return _FS_NODATA
705
  else:
706
    return bool(ctx.live_data.get(inst.name))
707

    
708

    
709
def _GetInstLiveData(name):
710
  """Build function for retrieving live data.
711

712
  @type name: string
713
  @param name: Live data field name
714

715
  """
716
  def fn(ctx, inst):
717
    """Get live data for an instance.
718

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

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

    
730
    if inst.name in ctx.live_data:
731
      data = ctx.live_data[inst.name]
732
      if name in data:
733
        return data[name]
734

    
735
    return _FS_UNAVAIL
736

    
737
  return fn
738

    
739

    
740
def _GetInstStatus(ctx, inst):
741
  """Get instance status.
742

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

747
  """
748
  if inst.primary_node in ctx.offline_nodes:
749
    return "ERROR_nodeoffline"
750

    
751
  if inst.primary_node in ctx.bad_nodes:
752
    return "ERROR_nodedown"
753

    
754
  if bool(ctx.live_data.get(inst.name)):
755
    if inst.admin_up:
756
      return "running"
757
    else:
758
      return "ERROR_up"
759

    
760
  if inst.admin_up:
761
    return "ERROR_down"
762

    
763
  return "ADMIN_down"
764

    
765

    
766
def _GetInstDiskSize(index):
767
  """Build function for retrieving disk size.
768

769
  @type index: int
770
  @param index: Disk index
771

772
  """
773
  def fn(_, inst):
774
    """Get size of a disk.
775

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

779
    """
780
    try:
781
      return inst.disks[index].size
782
    except IndexError:
783
      return _FS_UNAVAIL
784

    
785
  return fn
786

    
787

    
788
def _GetInstNic(index, cb):
789
  """Build function for calling another function with an instance NIC.
790

791
  @type index: int
792
  @param index: NIC index
793
  @type cb: callable
794
  @param cb: Callback
795

796
  """
797
  def fn(ctx, inst):
798
    """Call helper function with instance NIC.
799

800
    @type ctx: L{InstanceQueryData}
801
    @type inst: L{objects.Instance}
802
    @param inst: Instance object
803

804
    """
805
    try:
806
      nic = inst.nics[index]
807
    except IndexError:
808
      return _FS_UNAVAIL
809

    
810
    return cb(ctx, index, nic)
811

    
812
  return fn
813

    
814

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

818
  @type ctx: L{InstanceQueryData}
819
  @type nic: L{objects.NIC}
820
  @param nic: NIC object
821

822
  """
823
  if nic.ip is None:
824
    return _FS_UNAVAIL
825
  else:
826
    return nic.ip
827

    
828

    
829
def _GetInstNicBridge(ctx, index, _):
830
  """Get a NIC's bridge.
831

832
  @type ctx: L{InstanceQueryData}
833
  @type index: int
834
  @param index: NIC index
835

836
  """
837
  assert len(ctx.inst_nicparams) >= index
838

    
839
  nicparams = ctx.inst_nicparams[index]
840

    
841
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
842
    return nicparams[constants.NIC_LINK]
843
  else:
844
    return _FS_UNAVAIL
845

    
846

    
847
def _GetInstAllNicBridges(ctx, inst):
848
  """Get all network bridges for an instance.
849

850
  @type ctx: L{InstanceQueryData}
851
  @type inst: L{objects.Instance}
852
  @param inst: Instance object
853

854
  """
855
  assert len(ctx.inst_nicparams) == len(inst.nics)
856

    
857
  result = []
858

    
859
  for nicp in ctx.inst_nicparams:
860
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
861
      result.append(nicp[constants.NIC_LINK])
862
    else:
863
      result.append(None)
864

    
865
  assert len(result) == len(inst.nics)
866

    
867
  return result
868

    
869

    
870
def _GetInstNicParam(name):
871
  """Build function for retrieving a NIC parameter.
872

873
  @type name: string
874
  @param name: Parameter name
875

876
  """
877
  def fn(ctx, index, _):
878
    """Get a NIC's bridge.
879

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

886
    """
887
    assert len(ctx.inst_nicparams) >= index
888
    return ctx.inst_nicparams[index][name]
889

    
890
  return fn
891

    
892

    
893
def _GetInstanceNetworkFields():
894
  """Get instance fields involving network interfaces.
895

896
  @return: List of field definitions used as input for L{_PrepareFieldList}
897

898
  """
899
  nic_mac_fn = lambda ctx, _, nic: nic.mac
900
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
901
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
902

    
903
  fields = [
904
    # First NIC (legacy)
905
    (_MakeField("ip", "IP_address", QFT_TEXT), IQ_CONFIG,
906
     _GetInstNic(0, _GetInstNicIp)),
907
    (_MakeField("mac", "MAC_address", QFT_TEXT), IQ_CONFIG,
908
     _GetInstNic(0, nic_mac_fn)),
909
    (_MakeField("bridge", "Bridge", QFT_TEXT), IQ_CONFIG,
910
     _GetInstNic(0, _GetInstNicBridge)),
911
    (_MakeField("nic_mode", "NIC_Mode", QFT_TEXT), IQ_CONFIG,
912
     _GetInstNic(0, nic_mode_fn)),
913
    (_MakeField("nic_link", "NIC_Link", QFT_TEXT), IQ_CONFIG,
914
     _GetInstNic(0, nic_link_fn)),
915

    
916
    # All NICs
917
    (_MakeField("nic.count", "NICs", QFT_NUMBER), IQ_CONFIG,
918
     lambda ctx, inst: len(inst.nics)),
919
    (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER), IQ_CONFIG,
920
     lambda ctx, inst: [nic.mac for nic in inst.nics]),
921
    (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER), IQ_CONFIG,
922
     lambda ctx, inst: [nic.ip for nic in inst.nics]),
923
    (_MakeField("nic.modes", "NIC_modes", QFT_OTHER), IQ_CONFIG,
924
     lambda ctx, inst: [nicp[constants.NIC_MODE]
925
                        for nicp in ctx.inst_nicparams]),
926
    (_MakeField("nic.links", "NIC_links", QFT_OTHER), IQ_CONFIG,
927
     lambda ctx, inst: [nicp[constants.NIC_LINK]
928
                        for nicp in ctx.inst_nicparams]),
929
    (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER), IQ_CONFIG,
930
     _GetInstAllNicBridges),
931
    ]
932

    
933
  # NICs by number
934
  for i in range(constants.MAX_NICS):
935
    fields.extend([
936
      (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT),
937
       IQ_CONFIG, _GetInstNic(i, _GetInstNicIp)),
938
      (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT),
939
       IQ_CONFIG, _GetInstNic(i, nic_mac_fn)),
940
      (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT),
941
       IQ_CONFIG, _GetInstNic(i, nic_mode_fn)),
942
      (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT),
943
       IQ_CONFIG, _GetInstNic(i, nic_link_fn)),
944
      (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT),
945
       IQ_CONFIG, _GetInstNic(i, _GetInstNicBridge)),
946
      ])
947

    
948
  return fields
949

    
950

    
951
def _GetInstDiskUsage(ctx, inst):
952
  """Get disk usage for an instance.
953

954
  @type ctx: L{InstanceQueryData}
955
  @type inst: L{objects.Instance}
956
  @param inst: Instance object
957

958
  """
959
  usage = ctx.disk_usage[inst.name]
960

    
961
  if usage is None:
962
    usage = 0
963

    
964
  return usage
965

    
966

    
967
def _GetInstanceDiskFields():
968
  """Get instance fields involving disks.
969

970
  @return: List of field definitions used as input for L{_PrepareFieldList}
971

972
  """
973
  fields = [
974
    (_MakeField("disk_usage", "DiskUsage", QFT_UNIT), IQ_DISKUSAGE,
975
     _GetInstDiskUsage),
976
    (_MakeField("sda_size", "LegacyDisk/0", QFT_UNIT), IQ_CONFIG,
977
     _GetInstDiskSize(0)),
978
    (_MakeField("sdb_size", "LegacyDisk/1", QFT_UNIT), IQ_CONFIG,
979
     _GetInstDiskSize(1)),
980
    (_MakeField("disk.count", "Disks", QFT_NUMBER), IQ_CONFIG,
981
     lambda ctx, inst: len(inst.disks)),
982
    (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER), IQ_CONFIG,
983
     lambda ctx, inst: [disk.size for disk in inst.disks]),
984
    ]
985

    
986
  # Disks by number
987
  fields.extend([
988
    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT),
989
     IQ_CONFIG, _GetInstDiskSize(i))
990
    for i in range(constants.MAX_DISKS)
991
    ])
992

    
993
  return fields
994

    
995

    
996
def _GetInstanceParameterFields():
997
  """Get instance fields involving parameters.
998

999
  @return: List of field definitions used as input for L{_PrepareFieldList}
1000

1001
  """
1002
  # TODO: Consider moving titles closer to constants
1003
  be_title = {
1004
    constants.BE_AUTO_BALANCE: "Auto_balance",
1005
    constants.BE_MEMORY: "ConfigMemory",
1006
    constants.BE_VCPUS: "ConfigVCPUs",
1007
    }
1008

    
1009
  hv_title = {
1010
    constants.HV_ACPI: "ACPI",
1011
    constants.HV_BOOT_ORDER: "Boot_order",
1012
    constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1013
    constants.HV_DISK_TYPE: "Disk_type",
1014
    constants.HV_INITRD_PATH: "Initrd_path",
1015
    constants.HV_KERNEL_PATH: "Kernel_path",
1016
    constants.HV_NIC_TYPE: "NIC_type",
1017
    constants.HV_PAE: "PAE",
1018
    constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1019
    }
1020

    
1021
  fields = [
1022
    # Filled parameters
1023
    (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER),
1024
     IQ_CONFIG, lambda ctx, _: ctx.inst_hvparams),
1025
    (_MakeField("beparams", "BackendParameters", QFT_OTHER),
1026
     IQ_CONFIG, lambda ctx, _: ctx.inst_beparams),
1027
    (_MakeField("vcpus", "LegacyVCPUs", QFT_NUMBER), IQ_CONFIG,
1028
     lambda ctx, _: ctx.inst_beparams[constants.BE_VCPUS]),
1029

    
1030
    # Unfilled parameters
1031
    (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER),
1032
     IQ_CONFIG, _GetItemAttr("hvparams")),
1033
    (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER),
1034
     IQ_CONFIG, _GetItemAttr("beparams")),
1035
    (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER),
1036
     IQ_CONFIG, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1037
    ]
1038

    
1039
  # HV params
1040
  def _GetInstHvParam(name):
1041
    return lambda ctx, _: ctx.inst_hvparams.get(name, None)
1042

    
1043
  fields.extend([
1044
    # For now all hypervisor parameters are exported as QFT_OTHER
1045
    (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name), QFT_OTHER),
1046
     IQ_CONFIG, _GetInstHvParam(name))
1047
    for name in constants.HVS_PARAMETERS
1048
    if name not in constants.HVC_GLOBALS
1049
    ])
1050

    
1051
  # BE params
1052
  def _GetInstBeParam(name):
1053
    return lambda ctx, _: ctx.inst_beparams.get(name, None)
1054

    
1055
  fields.extend([
1056
    # For now all backend parameters are exported as QFT_OTHER
1057
    (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name), QFT_OTHER),
1058
     IQ_CONFIG, _GetInstBeParam(name))
1059
    for name in constants.BES_PARAMETERS
1060
    ])
1061

    
1062
  return fields
1063

    
1064

    
1065
_INST_SIMPLE_FIELDS = {
1066
  "disk_template": ("Disk_template", QFT_TEXT),
1067
  "hypervisor": ("Hypervisor", QFT_TEXT),
1068
  "name": ("Node", QFT_TEXT),
1069
  # Depending on the hypervisor, the port can be None
1070
  "network_port": ("Network_port", QFT_OTHER),
1071
  "os": ("OS", QFT_TEXT),
1072
  "serial_no": ("SerialNo", QFT_NUMBER),
1073
  "uuid": ("UUID", QFT_TEXT),
1074
  }
1075

    
1076

    
1077
def _BuildInstanceFields():
1078
  """Builds list of fields for instance queries.
1079

1080
  """
1081
  fields = [
1082
    (_MakeField("pnode", "Primary_node", QFT_TEXT), IQ_CONFIG,
1083
     _GetItemAttr("primary_node")),
1084
    (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER), IQ_CONFIG,
1085
     lambda ctx, inst: list(inst.secondary_nodes)),
1086
    (_MakeField("admin_state", "Autostart", QFT_BOOL), IQ_CONFIG,
1087
     _GetItemAttr("admin_up")),
1088
    (_MakeField("tags", "Tags", QFT_OTHER), IQ_CONFIG,
1089
     lambda ctx, inst: list(inst.GetTags())),
1090
    ]
1091

    
1092
  # Add simple fields
1093
  fields.extend([(_MakeField(name, title, kind), IQ_CONFIG, _GetItemAttr(name))
1094
                 for (name, (title, kind)) in _INST_SIMPLE_FIELDS.items()])
1095

    
1096
  # Fields requiring talking to the node
1097
  fields.extend([
1098
    (_MakeField("oper_state", "Running", QFT_BOOL), IQ_LIVE,
1099
     _GetInstOperState),
1100
    (_MakeField("oper_ram", "Memory", QFT_UNIT), IQ_LIVE,
1101
     _GetInstLiveData("memory")),
1102
    (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER), IQ_LIVE,
1103
     _GetInstLiveData("vcpus")),
1104
    (_MakeField("status", "Status", QFT_TEXT), IQ_LIVE, _GetInstStatus),
1105
    ])
1106

    
1107
  fields.extend(_GetInstanceParameterFields())
1108
  fields.extend(_GetInstanceDiskFields())
1109
  fields.extend(_GetInstanceNetworkFields())
1110
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1111

    
1112
  return _PrepareFieldList(fields)
1113

    
1114

    
1115
class LockQueryData:
1116
  """Data container for lock data queries.
1117

1118
  """
1119
  def __init__(self, lockdata):
1120
    """Initializes this class.
1121

1122
    """
1123
    self.lockdata = lockdata
1124

    
1125
  def __iter__(self):
1126
    """Iterate over all locks.
1127

1128
    """
1129
    return iter(self.lockdata)
1130

    
1131

    
1132
def _GetLockOwners(_, data):
1133
  """Returns a sorted list of a lock's current owners.
1134

1135
  """
1136
  (_, _, owners, _) = data
1137

    
1138
  if owners:
1139
    owners = utils.NiceSort(owners)
1140

    
1141
  return owners
1142

    
1143

    
1144
def _GetLockPending(_, data):
1145
  """Returns a sorted list of a lock's pending acquires.
1146

1147
  """
1148
  (_, _, _, pending) = data
1149

    
1150
  if pending:
1151
    pending = [(mode, utils.NiceSort(names))
1152
               for (mode, names) in pending]
1153

    
1154
  return pending
1155

    
1156

    
1157
def _BuildLockFields():
1158
  """Builds list of fields for lock queries.
1159

1160
  """
1161
  return _PrepareFieldList([
1162
    (_MakeField("name", "Name", QFT_TEXT), None,
1163
     lambda ctx, (name, mode, owners, pending): name),
1164
    (_MakeField("mode", "Mode", QFT_OTHER), LQ_MODE,
1165
     lambda ctx, (name, mode, owners, pending): mode),
1166
    (_MakeField("owner", "Owner", QFT_OTHER), LQ_OWNER, _GetLockOwners),
1167
    (_MakeField("pending", "Pending", QFT_OTHER), LQ_PENDING, _GetLockPending),
1168
    ])
1169

    
1170

    
1171
class GroupQueryData:
1172
  """Data container for node group data queries.
1173

1174
  """
1175
  def __init__(self, groups, group_to_nodes, group_to_instances):
1176
    """Initializes this class.
1177

1178
    @param groups: List of node group objects
1179
    @type group_to_nodes: dict; group UUID as key
1180
    @param group_to_nodes: Per-group list of nodes
1181
    @type group_to_instances: dict; group UUID as key
1182
    @param group_to_instances: Per-group list of (primary) instances
1183

1184
    """
1185
    self.groups = groups
1186
    self.group_to_nodes = group_to_nodes
1187
    self.group_to_instances = group_to_instances
1188

    
1189
  def __iter__(self):
1190
    """Iterate over all node groups.
1191

1192
    """
1193
    return iter(self.groups)
1194

    
1195

    
1196
_GROUP_SIMPLE_FIELDS = {
1197
  "alloc_policy": ("AllocPolicy", QFT_TEXT),
1198
  "name": ("Group", QFT_TEXT),
1199
  "serial_no": ("SerialNo", QFT_NUMBER),
1200
  "uuid": ("UUID", QFT_TEXT),
1201
  "ndparams": ("NDParams", QFT_OTHER),
1202
  }
1203

    
1204

    
1205
def _BuildGroupFields():
1206
  """Builds list of fields for node group queries.
1207

1208
  """
1209
  # Add simple fields
1210
  fields = [(_MakeField(name, title, kind), GQ_CONFIG, _GetItemAttr(name))
1211
            for (name, (title, kind)) in _GROUP_SIMPLE_FIELDS.items()]
1212

    
1213
  def _GetLength(getter):
1214
    return lambda ctx, group: len(getter(ctx)[group.uuid])
1215

    
1216
  def _GetSortedList(getter):
1217
    return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
1218

    
1219
  group_to_nodes = operator.attrgetter("group_to_nodes")
1220
  group_to_instances = operator.attrgetter("group_to_instances")
1221

    
1222
  # Add fields for nodes
1223
  fields.extend([
1224
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER),
1225
     GQ_NODE, _GetLength(group_to_nodes)),
1226
    (_MakeField("node_list", "NodeList", QFT_OTHER),
1227
     GQ_NODE, _GetSortedList(group_to_nodes)),
1228
    ])
1229

    
1230
  # Add fields for instances
1231
  fields.extend([
1232
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER),
1233
     GQ_INST, _GetLength(group_to_instances)),
1234
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER),
1235
     GQ_INST, _GetSortedList(group_to_instances)),
1236
    ])
1237

    
1238
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1239

    
1240
  return _PrepareFieldList(fields)
1241

    
1242

    
1243
#: Fields available for node queries
1244
NODE_FIELDS = _BuildNodeFields()
1245

    
1246
#: Fields available for instance queries
1247
INSTANCE_FIELDS = _BuildInstanceFields()
1248

    
1249
#: Fields available for lock queries
1250
LOCK_FIELDS = _BuildLockFields()
1251

    
1252
#: Fields available for node group queries
1253
GROUP_FIELDS = _BuildGroupFields()
1254

    
1255
#: All available field lists
1256
ALL_FIELD_LISTS = [NODE_FIELDS, INSTANCE_FIELDS, LOCK_FIELDS, GROUP_FIELDS]