Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ 82599b3e

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

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

111
  """
112
  return (QRFS_UNKNOWN, None)
113

    
114

    
115
def _GetQueryFields(fielddefs, selected):
116
  """Calculates the internal list of selected fields.
117

118
  Unknown fields are returned as L{constants.QFT_UNKNOWN}.
119

120
  @type fielddefs: dict
121
  @param fielddefs: Field definitions
122
  @type selected: list of strings
123
  @param selected: List of selected fields
124

125
  """
126
  result = []
127

    
128
  for name in selected:
129
    try:
130
      fdef = fielddefs[name]
131
    except KeyError:
132
      fdef = (_MakeField(name, name, QFT_UNKNOWN), None, _GetUnknownField)
133

    
134
    assert len(fdef) == 3
135

    
136
    result.append(fdef)
137

    
138
  return result
139

    
140

    
141
def GetAllFields(fielddefs):
142
  """Extract L{objects.QueryFieldDefinition} from field definitions.
143

144
  @rtype: list of L{objects.QueryFieldDefinition}
145

146
  """
147
  return [fdef for (fdef, _, _) in fielddefs]
148

    
149

    
150
class Query:
151
  def __init__(self, fieldlist, selected):
152
    """Initializes this class.
153

154
    The field definition is a dictionary with the field's name as a key and a
155
    tuple containing, in order, the field definition object
156
    (L{objects.QueryFieldDefinition}, the data kind to help calling code
157
    collect data and a retrieval function. The retrieval function is called
158
    with two parameters, in order, the data container and the item in container
159
    (see L{Query.Query}).
160

161
    Users of this class can call L{RequestedData} before preparing the data
162
    container to determine what data is needed.
163

164
    @type fieldlist: dictionary
165
    @param fieldlist: Field definitions
166
    @type selected: list of strings
167
    @param selected: List of selected fields
168

169
    """
170
    self._fields = _GetQueryFields(fieldlist, selected)
171

    
172
  def RequestedData(self):
173
    """Gets requested kinds of data.
174

175
    @rtype: frozenset
176

177
    """
178
    return frozenset(datakind
179
                     for (_, datakind, _) in self._fields
180
                     if datakind is not None)
181

    
182
  def GetFields(self):
183
    """Returns the list of fields for this query.
184

185
    Includes unknown fields.
186

187
    @rtype: List of L{objects.QueryFieldDefinition}
188

189
    """
190
    return GetAllFields(self._fields)
191

    
192
  def Query(self, ctx):
193
    """Execute a query.
194

195
    @param ctx: Data container passed to field retrieval functions, must
196
      support iteration using C{__iter__}
197

198
    """
199
    result = [[fn(ctx, item) for (_, _, fn) in self._fields]
200
              for item in ctx]
201

    
202
    # Verify result
203
    if __debug__:
204
      for (idx, row) in enumerate(result):
205
        assert _VerifyResultRow(self._fields, row), \
206
               ("Inconsistent result for fields %s in row %s: %r" %
207
                (GetAllFields(self._fields), idx, row))
208

    
209
    return result
210

    
211
  def OldStyleQuery(self, ctx):
212
    """Query with "old" query result format.
213

214
    See L{Query.Query} for arguments.
215

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

    
224
    return [[value for (_, value) in row]
225
            for row in self.Query(ctx)]
226

    
227

    
228
def _VerifyResultRow(fields, row):
229
  """Verifies the contents of a query result row.
230

231
  @type fields: list
232
  @param fields: Field definitions for result
233
  @type row: list of tuples
234
  @param row: Row data
235

236
  """
237
  return (len(row) == len(fields) and
238
          compat.all((status == QRFS_NORMAL and _VERIFY_FN[fdef.kind](value)) or
239
                     # Value for an abnormal status must be None
240
                     (status != QRFS_NORMAL and value is None)
241
                     for ((status, value), (fdef, _, _)) in zip(row, fields)))
242

    
243

    
244
def _PrepareFieldList(fields):
245
  """Prepares field list for use by L{Query}.
246

247
  Converts the list to a dictionary and does some verification.
248

249
  @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data kind,
250
    retrieval function)
251
  @param fields: List of fields, see L{Query.__init__} for a better description
252
  @rtype: dict
253
  @return: Field dictionary for L{Query}
254

255
  """
256
  if __debug__:
257
    duplicates = utils.FindDuplicates(fdef.title.lower()
258
                                      for (fdef, _, _) in fields)
259
    assert not duplicates, "Duplicate title(s) found: %r" % duplicates
260

    
261
  result = {}
262

    
263
  for field in fields:
264
    (fdef, _, fn) = field
265

    
266
    assert fdef.name and fdef.title, "Name and title are required"
267
    assert FIELD_NAME_RE.match(fdef.name)
268
    assert TITLE_RE.match(fdef.title)
269
    assert callable(fn)
270
    assert fdef.name not in result, \
271
           "Duplicate field name '%s' found" % fdef.name
272

    
273
    result[fdef.name] = field
274

    
275
  assert len(result) == len(fields)
276
  assert compat.all(name == fdef.name
277
                    for (name, (fdef, _, _)) in result.items())
278

    
279
  return result
280

    
281

    
282
def GetQueryResponse(query, ctx):
283
  """Prepares the response for a query.
284

285
  @type query: L{Query}
286
  @param ctx: Data container, see L{Query.Query}
287

288
  """
289
  return objects.QueryResponse(data=query.Query(ctx),
290
                               fields=query.GetFields()).ToDict()
291

    
292

    
293
def QueryFields(fielddefs, selected):
294
  """Returns list of available fields.
295

296
  @type fielddefs: dict
297
  @param fielddefs: Field definitions
298
  @type selected: list of strings
299
  @param selected: List of selected fields
300
  @return: List of L{objects.QueryFieldDefinition}
301

302
  """
303
  if selected is None:
304
    # Client requests all fields, sort by name
305
    fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
306
                           key=operator.attrgetter("name"))
307
  else:
308
    # Keep order as requested by client
309
    fdefs = Query(fielddefs, selected).GetFields()
310

    
311
  return objects.QueryFieldsResponse(fields=fdefs).ToDict()
312

    
313

    
314
def _MakeField(name, title, kind):
315
  """Wrapper for creating L{objects.QueryFieldDefinition} instances.
316

317
  @param name: Field name as a regular expression
318
  @param title: Human-readable title
319
  @param kind: Field type
320

321
  """
322
  return objects.QueryFieldDefinition(name=name, title=title, kind=kind)
323

    
324

    
325
def _GetNodeRole(node, master_name):
326
  """Determine node role.
327

328
  @type node: L{objects.Node}
329
  @param node: Node object
330
  @type master_name: string
331
  @param master_name: Master node name
332

333
  """
334
  if node.name == master_name:
335
    return "M"
336
  elif node.master_candidate:
337
    return "C"
338
  elif node.drained:
339
    return "D"
340
  elif node.offline:
341
    return "O"
342
  else:
343
    return "R"
344

    
345

    
346
def _GetItemAttr(attr):
347
  """Returns a field function to return an attribute of the item.
348

349
  @param attr: Attribute name
350

351
  """
352
  getter = operator.attrgetter(attr)
353
  return lambda _, item: (QRFS_NORMAL, getter(item))
354

    
355

    
356
def _GetItemTimestamp(getter):
357
  """Returns function for getting timestamp of item.
358

359
  @type getter: callable
360
  @param getter: Function to retrieve timestamp attribute
361

362
  """
363
  def fn(_, item):
364
    """Returns a timestamp of item.
365

366
    """
367
    timestamp = getter(item)
368
    if timestamp is None:
369
      # Old configs might not have all timestamps
370
      return (QRFS_UNAVAIL, None)
371
    else:
372
      return (QRFS_NORMAL, timestamp)
373

    
374
  return fn
375

    
376

    
377
def _GetItemTimestampFields(datatype):
378
  """Returns common timestamp fields.
379

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

382
  """
383
  return [
384
    (_MakeField("ctime", "CTime", QFT_TIMESTAMP), datatype,
385
     _GetItemTimestamp(operator.attrgetter("ctime"))),
386
    (_MakeField("mtime", "MTime", QFT_TIMESTAMP), datatype,
387
     _GetItemTimestamp(operator.attrgetter("mtime"))),
388
    ]
389

    
390

    
391
class NodeQueryData:
392
  """Data container for node data queries.
393

394
  """
395
  def __init__(self, nodes, live_data, master_name, node_to_primary,
396
               node_to_secondary, groups, oob_support, cluster):
397
    """Initializes this class.
398

399
    """
400
    self.nodes = nodes
401
    self.live_data = live_data
402
    self.master_name = master_name
403
    self.node_to_primary = node_to_primary
404
    self.node_to_secondary = node_to_secondary
405
    self.groups = groups
406
    self.oob_support = oob_support
407
    self.cluster = cluster
408

    
409
    # Used for individual rows
410
    self.curlive_data = None
411

    
412
  def __iter__(self):
413
    """Iterate over all nodes.
414

415
    This function has side-effects and only one instance of the resulting
416
    generator should be used at a time.
417

418
    """
419
    for node in self.nodes:
420
      if self.live_data:
421
        self.curlive_data = self.live_data.get(node.name, None)
422
      else:
423
        self.curlive_data = None
424
      yield node
425

    
426

    
427
#: Fields that are direct attributes of an L{objects.Node} object
428
_NODE_SIMPLE_FIELDS = {
429
  "drained": ("Drained", QFT_BOOL),
430
  "master_candidate": ("MasterC", QFT_BOOL),
431
  "master_capable": ("MasterCapable", QFT_BOOL),
432
  "name": ("Node", QFT_TEXT),
433
  "offline": ("Offline", QFT_BOOL),
434
  "serial_no": ("SerialNo", QFT_NUMBER),
435
  "uuid": ("UUID", QFT_TEXT),
436
  "vm_capable": ("VMCapable", QFT_BOOL),
437
  }
438

    
439

    
440
#: Fields requiring talking to the node
441
_NODE_LIVE_FIELDS = {
442
  "bootid": ("BootID", QFT_TEXT, "bootid"),
443
  "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes"),
444
  "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets"),
445
  "ctotal": ("CTotal", QFT_NUMBER, "cpu_total"),
446
  "dfree": ("DFree", QFT_UNIT, "vg_free"),
447
  "dtotal": ("DTotal", QFT_UNIT, "vg_size"),
448
  "mfree": ("MFree", QFT_UNIT, "memory_free"),
449
  "mnode": ("MNode", QFT_UNIT, "memory_dom0"),
450
  "mtotal": ("MTotal", QFT_UNIT, "memory_total"),
451
  }
452

    
453

    
454
def _GetGroup(cb):
455
  """Build function for calling another function with an node group.
456

457
  @param cb: The callback to be called with the nodegroup
458

459
  """
460
  def fn(ctx, node):
461
    """Get group data for a node.
462

463
    @type ctx: L{NodeQueryData}
464
    @type inst: L{objects.Node}
465
    @param inst: Node object
466

467
    """
468
    ng = ctx.groups.get(node.group, None)
469
    if ng is None:
470
      # Nodes always have a group, or the configuration is corrupt
471
      return (QRFS_UNAVAIL, None)
472

    
473
    return cb(ctx, node, ng)
474

    
475
  return fn
476

    
477

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

481
  @type ctx: L{NodeQueryData}
482
  @type node: L{objects.Node}
483
  @param node: Node object
484
  @type ng: L{objects.NodeGroup}
485
  @param ng: The node group this node belongs to
486

487
  """
488
  return (QRFS_NORMAL, ng.name)
489

    
490

    
491
def _GetNodePower(ctx, node):
492
  """Returns the node powered state
493

494
  @type ctx: L{NodeQueryData}
495
  @type node: L{objects.Node}
496
  @param node: Node object
497

498
  """
499
  if ctx.oob_support[node.name]:
500
    return (QRFS_NORMAL, node.powered)
501

    
502
  return (QRFS_UNAVAIL, None)
503

    
504

    
505
def _GetNdParams(ctx, node, ng):
506
  """Returns the ndparams for this node.
507

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

514
  """
515
  return (QRFS_NORMAL, ctx.cluster.SimpleFillND(ng.FillND(node)))
516

    
517

    
518
def _GetLiveNodeField(field, kind, ctx, node):
519
  """Gets the value of a "live" field from L{NodeQueryData}.
520

521
  @param field: Live field name
522
  @param kind: Data kind, one of L{constants.QFT_ALL}
523
  @type ctx: L{NodeQueryData}
524
  @type node: L{objects.Node}
525
  @param node: Node object
526

527
  """
528
  if node.offline:
529
    return (QRFS_OFFLINE, None)
530

    
531
  if not ctx.curlive_data:
532
    return (QRFS_NODATA, None)
533

    
534
  try:
535
    value = ctx.curlive_data[field]
536
  except KeyError:
537
    return (QRFS_UNAVAIL, None)
538

    
539
  if kind == QFT_TEXT:
540
    return (QRFS_NORMAL, value)
541

    
542
  assert kind in (QFT_NUMBER, QFT_UNIT)
543

    
544
  # Try to convert into number
545
  try:
546
    return (QRFS_NORMAL, int(value))
547
  except (ValueError, TypeError):
548
    logging.exception("Failed to convert node field '%s' (value %r) to int",
549
                      value, field)
550
    return (QRFS_UNAVAIL, None)
551

    
552

    
553
def _BuildNodeFields():
554
  """Builds list of fields for node queries.
555

556
  """
557
  fields = [
558
    (_MakeField("pip", "PrimaryIP", QFT_TEXT), NQ_CONFIG,
559
     lambda ctx, node: (QRFS_NORMAL, node.primary_ip)),
560
    (_MakeField("sip", "SecondaryIP", QFT_TEXT), NQ_CONFIG,
561
     lambda ctx, node: (QRFS_NORMAL, node.secondary_ip)),
562
    (_MakeField("tags", "Tags", QFT_OTHER), NQ_CONFIG,
563
     lambda ctx, node: (QRFS_NORMAL, list(node.GetTags()))),
564
    (_MakeField("master", "IsMaster", QFT_BOOL), NQ_CONFIG,
565
     lambda ctx, node: (QRFS_NORMAL, node.name == ctx.master_name)),
566
    (_MakeField("role", "Role", QFT_TEXT), NQ_CONFIG,
567
     lambda ctx, node: (QRFS_NORMAL, _GetNodeRole(node, ctx.master_name))),
568
    (_MakeField("group", "Group", QFT_TEXT), NQ_GROUP,
569
     _GetGroup(_GetNodeGroup)),
570
    (_MakeField("group.uuid", "GroupUUID", QFT_TEXT),
571
     NQ_CONFIG, lambda ctx, node: (QRFS_NORMAL, node.group)),
572
    (_MakeField("powered", "Powered", QFT_BOOL), NQ_OOB, _GetNodePower),
573
    (_MakeField("ndparams", "NodeParameters", QFT_OTHER), NQ_GROUP,
574
      _GetGroup(_GetNdParams)),
575
    (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER),
576
      NQ_GROUP, lambda ctx, node: (QRFS_NORMAL, node.ndparams)),
577
    ]
578

    
579
  def _GetLength(getter):
580
    return lambda ctx, node: (QRFS_NORMAL, len(getter(ctx)[node.name]))
581

    
582
  def _GetList(getter):
583
    return lambda ctx, node: (QRFS_NORMAL, list(getter(ctx)[node.name]))
584

    
585
  # Add fields operating on instance lists
586
  for prefix, titleprefix, getter in \
587
      [("p", "Pri", operator.attrgetter("node_to_primary")),
588
       ("s", "Sec", operator.attrgetter("node_to_secondary"))]:
589
    fields.extend([
590
      (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER),
591
       NQ_INST, _GetLength(getter)),
592
      (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
593
                  QFT_OTHER),
594
       NQ_INST, _GetList(getter)),
595
      ])
596

    
597
  # Add simple fields
598
  fields.extend([(_MakeField(name, title, kind), NQ_CONFIG, _GetItemAttr(name))
599
                 for (name, (title, kind)) in _NODE_SIMPLE_FIELDS.items()])
600

    
601
  # Add fields requiring live data
602
  fields.extend([
603
    (_MakeField(name, title, kind), NQ_LIVE,
604
     compat.partial(_GetLiveNodeField, nfield, kind))
605
    for (name, (title, kind, nfield)) in _NODE_LIVE_FIELDS.items()
606
    ])
607

    
608
  # Add timestamps
609
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
610

    
611
  return _PrepareFieldList(fields)
612

    
613

    
614
class InstanceQueryData:
615
  """Data container for instance data queries.
616

617
  """
618
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
619
               live_data):
620
    """Initializes this class.
621

622
    @param instances: List of instance objects
623
    @param cluster: Cluster object
624
    @type disk_usage: dict; instance name as key
625
    @param disk_usage: Per-instance disk usage
626
    @type offline_nodes: list of strings
627
    @param offline_nodes: List of offline nodes
628
    @type bad_nodes: list of strings
629
    @param bad_nodes: List of faulty nodes
630
    @type live_data: dict; instance name as key
631
    @param live_data: Per-instance live data
632

633
    """
634
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
635
           "Offline nodes not included in bad nodes"
636
    assert not (set(live_data.keys()) & set(bad_nodes)), \
637
           "Found live data for bad or offline nodes"
638

    
639
    self.instances = instances
640
    self.cluster = cluster
641
    self.disk_usage = disk_usage
642
    self.offline_nodes = offline_nodes
643
    self.bad_nodes = bad_nodes
644
    self.live_data = live_data
645

    
646
    # Used for individual rows
647
    self.inst_hvparams = None
648
    self.inst_beparams = None
649
    self.inst_nicparams = None
650

    
651
  def __iter__(self):
652
    """Iterate over all instances.
653

654
    This function has side-effects and only one instance of the resulting
655
    generator should be used at a time.
656

657
    """
658
    for inst in self.instances:
659
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
660
      self.inst_beparams = self.cluster.FillBE(inst)
661
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
662
                             for nic in inst.nics]
663

    
664
      yield inst
665

    
666

    
667
def _GetInstOperState(ctx, inst):
668
  """Get instance's operational status.
669

670
  @type ctx: L{InstanceQueryData}
671
  @type inst: L{objects.Instance}
672
  @param inst: Instance object
673

674
  """
675
  # Can't use QRFS_OFFLINE here as it would describe the instance to be offline
676
  # when we actually don't know due to missing data
677
  if inst.primary_node in ctx.bad_nodes:
678
    return (QRFS_NODATA, None)
679
  else:
680
    return (QRFS_NORMAL, bool(ctx.live_data.get(inst.name)))
681

    
682

    
683
def _GetInstLiveData(name):
684
  """Build function for retrieving live data.
685

686
  @type name: string
687
  @param name: Live data field name
688

689
  """
690
  def fn(ctx, inst):
691
    """Get live data for an instance.
692

693
    @type ctx: L{InstanceQueryData}
694
    @type inst: L{objects.Instance}
695
    @param inst: Instance object
696

697
    """
698
    if (inst.primary_node in ctx.bad_nodes or
699
        inst.primary_node in ctx.offline_nodes):
700
      # Can't use QRFS_OFFLINE here as it would describe the instance to be
701
      # offline when we actually don't know due to missing data
702
      return (QRFS_NODATA, None)
703

    
704
    if inst.name in ctx.live_data:
705
      data = ctx.live_data[inst.name]
706
      if name in data:
707
        return (QRFS_NORMAL, data[name])
708

    
709
    return (QRFS_UNAVAIL, None)
710

    
711
  return fn
712

    
713

    
714
def _GetInstStatus(ctx, inst):
715
  """Get instance status.
716

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

721
  """
722
  if inst.primary_node in ctx.offline_nodes:
723
    return (QRFS_NORMAL, "ERROR_nodeoffline")
724

    
725
  if inst.primary_node in ctx.bad_nodes:
726
    return (QRFS_NORMAL, "ERROR_nodedown")
727

    
728
  if bool(ctx.live_data.get(inst.name)):
729
    if inst.admin_up:
730
      return (QRFS_NORMAL, "running")
731
    else:
732
      return (QRFS_NORMAL, "ERROR_up")
733

    
734
  if inst.admin_up:
735
    return (QRFS_NORMAL, "ERROR_down")
736

    
737
  return (QRFS_NORMAL, "ADMIN_down")
738

    
739

    
740
def _GetInstDiskSize(index):
741
  """Build function for retrieving disk size.
742

743
  @type index: int
744
  @param index: Disk index
745

746
  """
747
  def fn(_, inst):
748
    """Get size of a disk.
749

750
    @type inst: L{objects.Instance}
751
    @param inst: Instance object
752

753
    """
754
    try:
755
      return (QRFS_NORMAL, inst.disks[index].size)
756
    except IndexError:
757
      return (QRFS_UNAVAIL, None)
758

    
759
  return fn
760

    
761

    
762
def _GetInstNic(index, cb):
763
  """Build function for calling another function with an instance NIC.
764

765
  @type index: int
766
  @param index: NIC index
767
  @type cb: callable
768
  @param cb: Callback
769

770
  """
771
  def fn(ctx, inst):
772
    """Call helper function with instance NIC.
773

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

778
    """
779
    try:
780
      nic = inst.nics[index]
781
    except IndexError:
782
      return (QRFS_UNAVAIL, None)
783

    
784
    return cb(ctx, index, nic)
785

    
786
  return fn
787

    
788

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

792
  @type ctx: L{InstanceQueryData}
793
  @type nic: L{objects.NIC}
794
  @param nic: NIC object
795

796
  """
797
  if nic.ip is None:
798
    return (QRFS_UNAVAIL, None)
799
  else:
800
    return (QRFS_NORMAL, nic.ip)
801

    
802

    
803
def _GetInstNicBridge(ctx, index, _):
804
  """Get a NIC's bridge.
805

806
  @type ctx: L{InstanceQueryData}
807
  @type index: int
808
  @param index: NIC index
809

810
  """
811
  assert len(ctx.inst_nicparams) >= index
812

    
813
  nicparams = ctx.inst_nicparams[index]
814

    
815
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
816
    return (QRFS_NORMAL, nicparams[constants.NIC_LINK])
817
  else:
818
    return (QRFS_UNAVAIL, None)
819

    
820

    
821
def _GetInstAllNicBridges(ctx, inst):
822
  """Get all network bridges for an instance.
823

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

828
  """
829
  assert len(ctx.inst_nicparams) == len(inst.nics)
830

    
831
  result = []
832

    
833
  for nicp in ctx.inst_nicparams:
834
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
835
      result.append(nicp[constants.NIC_LINK])
836
    else:
837
      result.append(None)
838

    
839
  assert len(result) == len(inst.nics)
840

    
841
  return (QRFS_NORMAL, result)
842

    
843

    
844
def _GetInstNicParam(name):
845
  """Build function for retrieving a NIC parameter.
846

847
  @type name: string
848
  @param name: Parameter name
849

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

854
    @type ctx: L{InstanceQueryData}
855
    @type inst: L{objects.Instance}
856
    @param inst: Instance object
857
    @type nic: L{objects.NIC}
858
    @param nic: NIC object
859

860
    """
861
    assert len(ctx.inst_nicparams) >= index
862
    return (QRFS_NORMAL, ctx.inst_nicparams[index][name])
863

    
864
  return fn
865

    
866

    
867
def _GetInstanceNetworkFields():
868
  """Get instance fields involving network interfaces.
869

870
  @return: List of field definitions used as input for L{_PrepareFieldList}
871

872
  """
873
  nic_mac_fn = lambda ctx, _, nic: (QRFS_NORMAL, nic.mac)
874
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
875
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
876

    
877
  fields = [
878
    # First NIC (legacy)
879
    (_MakeField("ip", "IP_address", QFT_TEXT), IQ_CONFIG,
880
     _GetInstNic(0, _GetInstNicIp)),
881
    (_MakeField("mac", "MAC_address", QFT_TEXT), IQ_CONFIG,
882
     _GetInstNic(0, nic_mac_fn)),
883
    (_MakeField("bridge", "Bridge", QFT_TEXT), IQ_CONFIG,
884
     _GetInstNic(0, _GetInstNicBridge)),
885
    (_MakeField("nic_mode", "NIC_Mode", QFT_TEXT), IQ_CONFIG,
886
     _GetInstNic(0, nic_mode_fn)),
887
    (_MakeField("nic_link", "NIC_Link", QFT_TEXT), IQ_CONFIG,
888
     _GetInstNic(0, nic_link_fn)),
889

    
890
    # All NICs
891
    (_MakeField("nic.count", "NICs", QFT_NUMBER), IQ_CONFIG,
892
     lambda ctx, inst: (QRFS_NORMAL, len(inst.nics))),
893
    (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER), IQ_CONFIG,
894
     lambda ctx, inst: (QRFS_NORMAL, [nic.mac for nic in inst.nics])),
895
    (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER), IQ_CONFIG,
896
     lambda ctx, inst: (QRFS_NORMAL, [nic.ip for nic in inst.nics])),
897
    (_MakeField("nic.modes", "NIC_modes", QFT_OTHER), IQ_CONFIG,
898
     lambda ctx, inst: (QRFS_NORMAL, [nicp[constants.NIC_MODE]
899
                                      for nicp in ctx.inst_nicparams])),
900
    (_MakeField("nic.links", "NIC_links", QFT_OTHER), IQ_CONFIG,
901
     lambda ctx, inst: (QRFS_NORMAL, [nicp[constants.NIC_LINK]
902
                                      for nicp in ctx.inst_nicparams])),
903
    (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER), IQ_CONFIG,
904
     _GetInstAllNicBridges),
905
    ]
906

    
907
  # NICs by number
908
  for i in range(constants.MAX_NICS):
909
    fields.extend([
910
      (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT),
911
       IQ_CONFIG, _GetInstNic(i, _GetInstNicIp)),
912
      (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT),
913
       IQ_CONFIG, _GetInstNic(i, nic_mac_fn)),
914
      (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT),
915
       IQ_CONFIG, _GetInstNic(i, nic_mode_fn)),
916
      (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT),
917
       IQ_CONFIG, _GetInstNic(i, nic_link_fn)),
918
      (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT),
919
       IQ_CONFIG, _GetInstNic(i, _GetInstNicBridge)),
920
      ])
921

    
922
  return fields
923

    
924

    
925
def _GetInstDiskUsage(ctx, inst):
926
  """Get disk usage for an instance.
927

928
  @type ctx: L{InstanceQueryData}
929
  @type inst: L{objects.Instance}
930
  @param inst: Instance object
931

932
  """
933
  usage = ctx.disk_usage[inst.name]
934

    
935
  if usage is None:
936
    usage = 0
937

    
938
  return (QRFS_NORMAL, usage)
939

    
940

    
941
def _GetInstanceDiskFields():
942
  """Get instance fields involving disks.
943

944
  @return: List of field definitions used as input for L{_PrepareFieldList}
945

946
  """
947
  fields = [
948
    (_MakeField("disk_usage", "DiskUsage", QFT_UNIT), IQ_DISKUSAGE,
949
     _GetInstDiskUsage),
950
    (_MakeField("sda_size", "LegacyDisk/0", QFT_UNIT), IQ_CONFIG,
951
     _GetInstDiskSize(0)),
952
    (_MakeField("sdb_size", "LegacyDisk/1", QFT_UNIT), IQ_CONFIG,
953
     _GetInstDiskSize(1)),
954
    (_MakeField("disk.count", "Disks", QFT_NUMBER), IQ_CONFIG,
955
     lambda ctx, inst: (QRFS_NORMAL, len(inst.disks))),
956
    (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER), IQ_CONFIG,
957
     lambda ctx, inst: (QRFS_NORMAL, [disk.size for disk in inst.disks])),
958
    ]
959

    
960
  # Disks by number
961
  fields.extend([
962
    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT),
963
     IQ_CONFIG, _GetInstDiskSize(i))
964
    for i in range(constants.MAX_DISKS)
965
    ])
966

    
967
  return fields
968

    
969

    
970
def _GetInstanceParameterFields():
971
  """Get instance fields involving parameters.
972

973
  @return: List of field definitions used as input for L{_PrepareFieldList}
974

975
  """
976
  # TODO: Consider moving titles closer to constants
977
  be_title = {
978
    constants.BE_AUTO_BALANCE: "Auto_balance",
979
    constants.BE_MEMORY: "Configured_memory",
980
    constants.BE_VCPUS: "VCPUs",
981
    }
982

    
983
  hv_title = {
984
    constants.HV_ACPI: "ACPI",
985
    constants.HV_BOOT_ORDER: "Boot_order",
986
    constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
987
    constants.HV_DISK_TYPE: "Disk_type",
988
    constants.HV_INITRD_PATH: "Initrd_path",
989
    constants.HV_KERNEL_PATH: "Kernel_path",
990
    constants.HV_NIC_TYPE: "NIC_type",
991
    constants.HV_PAE: "PAE",
992
    constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
993
    }
994

    
995
  fields = [
996
    # Filled parameters
997
    (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER),
998
     IQ_CONFIG, lambda ctx, _: (QRFS_NORMAL, ctx.inst_hvparams)),
999
    (_MakeField("beparams", "BackendParameters", QFT_OTHER),
1000
     IQ_CONFIG, lambda ctx, _: (QRFS_NORMAL, ctx.inst_beparams)),
1001
    (_MakeField("vcpus", "LegacyVCPUs", QFT_NUMBER), IQ_CONFIG,
1002
     lambda ctx, _: (QRFS_NORMAL, ctx.inst_beparams[constants.BE_VCPUS])),
1003

    
1004
    # Unfilled parameters
1005
    (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER),
1006
     IQ_CONFIG, lambda ctx, inst: (QRFS_NORMAL, inst.hvparams)),
1007
    (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER),
1008
     IQ_CONFIG, lambda ctx, inst: (QRFS_NORMAL, inst.beparams)),
1009
    (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER),
1010
     IQ_CONFIG, lambda ctx, inst: (QRFS_NORMAL,
1011
                                   [nic.nicparams for nic in inst.nics])),
1012
    ]
1013

    
1014
  # HV params
1015
  def _GetInstHvParam(name):
1016
    return lambda ctx, _: (QRFS_NORMAL, ctx.inst_hvparams.get(name, None))
1017

    
1018
  fields.extend([
1019
    # For now all hypervisor parameters are exported as QFT_OTHER
1020
    (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name), QFT_OTHER),
1021
     IQ_CONFIG, _GetInstHvParam(name))
1022
    for name in constants.HVS_PARAMETERS
1023
    if name not in constants.HVC_GLOBALS
1024
    ])
1025

    
1026
  # BE params
1027
  def _GetInstBeParam(name):
1028
    return lambda ctx, _: (QRFS_NORMAL, ctx.inst_beparams.get(name, None))
1029

    
1030
  fields.extend([
1031
    # For now all backend parameters are exported as QFT_OTHER
1032
    (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name), QFT_OTHER),
1033
     IQ_CONFIG, _GetInstBeParam(name))
1034
    for name in constants.BES_PARAMETERS
1035
    ])
1036

    
1037
  return fields
1038

    
1039

    
1040
_INST_SIMPLE_FIELDS = {
1041
  "disk_template": ("Disk_template", QFT_TEXT),
1042
  "hypervisor": ("Hypervisor", QFT_TEXT),
1043
  "name": ("Node", QFT_TEXT),
1044
  # Depending on the hypervisor, the port can be None
1045
  "network_port": ("Network_port", QFT_OTHER),
1046
  "os": ("OS", QFT_TEXT),
1047
  "serial_no": ("SerialNo", QFT_NUMBER),
1048
  "uuid": ("UUID", QFT_TEXT),
1049
  }
1050

    
1051

    
1052
def _BuildInstanceFields():
1053
  """Builds list of fields for instance queries.
1054

1055
  """
1056
  fields = [
1057
    (_MakeField("pnode", "Primary_node", QFT_TEXT), IQ_CONFIG,
1058
     lambda ctx, inst: (QRFS_NORMAL, inst.primary_node)),
1059
    (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER), IQ_CONFIG,
1060
     lambda ctx, inst: (QRFS_NORMAL, list(inst.secondary_nodes))),
1061
    (_MakeField("admin_state", "Autostart", QFT_BOOL), IQ_CONFIG,
1062
     lambda ctx, inst: (QRFS_NORMAL, inst.admin_up)),
1063
    (_MakeField("tags", "Tags", QFT_OTHER), IQ_CONFIG,
1064
     lambda ctx, inst: (QRFS_NORMAL, list(inst.GetTags()))),
1065
    ]
1066

    
1067
  # Add simple fields
1068
  fields.extend([(_MakeField(name, title, kind), IQ_CONFIG, _GetItemAttr(name))
1069
                 for (name, (title, kind)) in _INST_SIMPLE_FIELDS.items()])
1070

    
1071
  # Fields requiring talking to the node
1072
  fields.extend([
1073
    (_MakeField("oper_state", "Running", QFT_BOOL), IQ_LIVE,
1074
     _GetInstOperState),
1075
    (_MakeField("oper_ram", "RuntimeMemory", QFT_UNIT), IQ_LIVE,
1076
     _GetInstLiveData("memory")),
1077
    (_MakeField("oper_vcpus", "RuntimeVCPUs", QFT_NUMBER), IQ_LIVE,
1078
     _GetInstLiveData("vcpus")),
1079
    (_MakeField("status", "Status", QFT_TEXT), IQ_LIVE, _GetInstStatus),
1080
    ])
1081

    
1082
  fields.extend(_GetInstanceParameterFields())
1083
  fields.extend(_GetInstanceDiskFields())
1084
  fields.extend(_GetInstanceNetworkFields())
1085
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1086

    
1087
  return _PrepareFieldList(fields)
1088

    
1089

    
1090
class LockQueryData:
1091
  """Data container for lock data queries.
1092

1093
  """
1094
  def __init__(self, lockdata):
1095
    """Initializes this class.
1096

1097
    """
1098
    self.lockdata = lockdata
1099

    
1100
  def __iter__(self):
1101
    """Iterate over all locks.
1102

1103
    """
1104
    return iter(self.lockdata)
1105

    
1106

    
1107
def _GetLockOwners(_, data):
1108
  """Returns a sorted list of a lock's current owners.
1109

1110
  """
1111
  (_, _, owners, _) = data
1112

    
1113
  if owners:
1114
    owners = utils.NiceSort(owners)
1115

    
1116
  return (QRFS_NORMAL, owners)
1117

    
1118

    
1119
def _GetLockPending(_, data):
1120
  """Returns a sorted list of a lock's pending acquires.
1121

1122
  """
1123
  (_, _, _, pending) = data
1124

    
1125
  if pending:
1126
    pending = [(mode, utils.NiceSort(names))
1127
               for (mode, names) in pending]
1128

    
1129
  return (QRFS_NORMAL, pending)
1130

    
1131

    
1132
def _BuildLockFields():
1133
  """Builds list of fields for lock queries.
1134

1135
  """
1136
  return _PrepareFieldList([
1137
    (_MakeField("name", "Name", QFT_TEXT), None,
1138
     lambda ctx, (name, mode, owners, pending): (QRFS_NORMAL, name)),
1139
    (_MakeField("mode", "Mode", QFT_OTHER), LQ_MODE,
1140
     lambda ctx, (name, mode, owners, pending): (QRFS_NORMAL, mode)),
1141
    (_MakeField("owner", "Owner", QFT_OTHER), LQ_OWNER, _GetLockOwners),
1142
    (_MakeField("pending", "Pending", QFT_OTHER), LQ_PENDING, _GetLockPending),
1143
    ])
1144

    
1145

    
1146
class GroupQueryData:
1147
  """Data container for node group data queries.
1148

1149
  """
1150
  def __init__(self, groups, group_to_nodes, group_to_instances):
1151
    """Initializes this class.
1152

1153
    @param groups: List of node group objects
1154
    @type group_to_nodes: dict; group UUID as key
1155
    @param group_to_nodes: Per-group list of nodes
1156
    @type group_to_instances: dict; group UUID as key
1157
    @param group_to_instances: Per-group list of (primary) instances
1158

1159
    """
1160
    self.groups = groups
1161
    self.group_to_nodes = group_to_nodes
1162
    self.group_to_instances = group_to_instances
1163

    
1164
  def __iter__(self):
1165
    """Iterate over all node groups.
1166

1167
    """
1168
    return iter(self.groups)
1169

    
1170

    
1171
_GROUP_SIMPLE_FIELDS = {
1172
  "alloc_policy": ("AllocPolicy", QFT_TEXT),
1173
  "name": ("Group", QFT_TEXT),
1174
  "serial_no": ("SerialNo", QFT_NUMBER),
1175
  "uuid": ("UUID", QFT_TEXT),
1176
  "ndparams": ("NDParams", QFT_OTHER),
1177
  }
1178

    
1179

    
1180
def _BuildGroupFields():
1181
  """Builds list of fields for node group queries.
1182

1183
  """
1184
  # Add simple fields
1185
  fields = [(_MakeField(name, title, kind), GQ_CONFIG, _GetItemAttr(name))
1186
            for (name, (title, kind)) in _GROUP_SIMPLE_FIELDS.items()]
1187

    
1188
  def _GetLength(getter):
1189
    return lambda ctx, group: (QRFS_NORMAL, len(getter(ctx)[group.uuid]))
1190

    
1191
  def _GetSortedList(getter):
1192
    return lambda ctx, group: (QRFS_NORMAL,
1193
                               utils.NiceSort(getter(ctx)[group.uuid]))
1194

    
1195
  group_to_nodes = operator.attrgetter("group_to_nodes")
1196
  group_to_instances = operator.attrgetter("group_to_instances")
1197

    
1198
  # Add fields for nodes
1199
  fields.extend([
1200
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER),
1201
     GQ_NODE, _GetLength(group_to_nodes)),
1202
    (_MakeField("node_list", "NodeList", QFT_OTHER),
1203
     GQ_NODE, _GetSortedList(group_to_nodes)),
1204
    ])
1205

    
1206
  # Add fields for instances
1207
  fields.extend([
1208
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER),
1209
     GQ_INST, _GetLength(group_to_instances)),
1210
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER),
1211
     GQ_INST, _GetSortedList(group_to_instances)),
1212
    ])
1213

    
1214
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1215

    
1216
  return _PrepareFieldList(fields)
1217

    
1218

    
1219
#: Fields available for node queries
1220
NODE_FIELDS = _BuildNodeFields()
1221

    
1222
#: Fields available for instance queries
1223
INSTANCE_FIELDS = _BuildInstanceFields()
1224

    
1225
#: Fields available for lock queries
1226
LOCK_FIELDS = _BuildLockFields()
1227

    
1228
#: Fields available for node group queries
1229
GROUP_FIELDS = _BuildGroupFields()
1230

    
1231
#: All available field lists
1232
ALL_FIELD_LISTS = [NODE_FIELDS, INSTANCE_FIELDS, LOCK_FIELDS, GROUP_FIELDS]