Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ e2d188cc

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 (idx, row) in enumerate(result):
211
        assert _VerifyResultRow(self._fields, row), \
212
               ("Inconsistent result for fields %s in row %s: %r" %
213
                (GetAllFields(self._fields), idx, row))
214

    
215
    return result
216

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

220
    See L{Query.Query} for arguments.
221

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

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

    
233

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

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

    
249

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

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

258
  """
259
  return (len(row) == len(fields) and
260
          compat.all((status == QRFS_NORMAL and _VERIFY_FN[fdef.kind](value)) or
261
                     # Value for an abnormal status must be None
262
                     (status != QRFS_NORMAL and value is None)
263
                     for ((status, value), (fdef, _, _)) in zip(row, fields)))
264

    
265

    
266
def _PrepareFieldList(fields):
267
  """Prepares field list for use by L{Query}.
268

269
  Converts the list to a dictionary and does some verification.
270

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

277
  """
278
  if __debug__:
279
    duplicates = utils.FindDuplicates(fdef.title.lower()
280
                                      for (fdef, _, _) in fields)
281
    assert not duplicates, "Duplicate title(s) found: %r" % duplicates
282

    
283
  result = {}
284

    
285
  for field in fields:
286
    (fdef, _, fn) = field
287

    
288
    assert fdef.name and fdef.title, "Name and title are required"
289
    assert FIELD_NAME_RE.match(fdef.name)
290
    assert TITLE_RE.match(fdef.title)
291
    assert callable(fn)
292
    assert fdef.name not in result, \
293
           "Duplicate field name '%s' found" % fdef.name
294

    
295
    result[fdef.name] = field
296

    
297
  assert len(result) == len(fields)
298
  assert compat.all(name == fdef.name
299
                    for (name, (fdef, _, _)) in result.items())
300

    
301
  return result
302

    
303

    
304
def GetQueryResponse(query, ctx):
305
  """Prepares the response for a query.
306

307
  @type query: L{Query}
308
  @param ctx: Data container, see L{Query.Query}
309

310
  """
311
  return objects.QueryResponse(data=query.Query(ctx),
312
                               fields=query.GetFields()).ToDict()
313

    
314

    
315
def QueryFields(fielddefs, selected):
316
  """Returns list of available fields.
317

318
  @type fielddefs: dict
319
  @param fielddefs: Field definitions
320
  @type selected: list of strings
321
  @param selected: List of selected fields
322
  @return: List of L{objects.QueryFieldDefinition}
323

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

    
333
  return objects.QueryFieldsResponse(fields=fdefs).ToDict()
334

    
335

    
336
def _MakeField(name, title, kind):
337
  """Wrapper for creating L{objects.QueryFieldDefinition} instances.
338

339
  @param name: Field name as a regular expression
340
  @param title: Human-readable title
341
  @param kind: Field type
342

343
  """
344
  return objects.QueryFieldDefinition(name=name, title=title, kind=kind)
345

    
346

    
347
def _GetNodeRole(node, master_name):
348
  """Determine node role.
349

350
  @type node: L{objects.Node}
351
  @param node: Node object
352
  @type master_name: string
353
  @param master_name: Master node name
354

355
  """
356
  if node.name == master_name:
357
    return "M"
358
  elif node.master_candidate:
359
    return "C"
360
  elif node.drained:
361
    return "D"
362
  elif node.offline:
363
    return "O"
364
  else:
365
    return "R"
366

    
367

    
368
def _GetItemAttr(attr):
369
  """Returns a field function to return an attribute of the item.
370

371
  @param attr: Attribute name
372

373
  """
374
  getter = operator.attrgetter(attr)
375
  return lambda _, item: getter(item)
376

    
377

    
378
def _GetItemTimestamp(getter):
379
  """Returns function for getting timestamp of item.
380

381
  @type getter: callable
382
  @param getter: Function to retrieve timestamp attribute
383

384
  """
385
  def fn(_, item):
386
    """Returns a timestamp of item.
387

388
    """
389
    timestamp = getter(item)
390
    if timestamp is None:
391
      # Old configs might not have all timestamps
392
      return _FS_UNAVAIL
393
    else:
394
      return timestamp
395

    
396
  return fn
397

    
398

    
399
def _GetItemTimestampFields(datatype):
400
  """Returns common timestamp fields.
401

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

404
  """
405
  return [
406
    (_MakeField("ctime", "CTime", QFT_TIMESTAMP), datatype,
407
     _GetItemTimestamp(operator.attrgetter("ctime"))),
408
    (_MakeField("mtime", "MTime", QFT_TIMESTAMP), datatype,
409
     _GetItemTimestamp(operator.attrgetter("mtime"))),
410
    ]
411

    
412

    
413
class NodeQueryData:
414
  """Data container for node data queries.
415

416
  """
417
  def __init__(self, nodes, live_data, master_name, node_to_primary,
418
               node_to_secondary, groups, oob_support, cluster):
419
    """Initializes this class.
420

421
    """
422
    self.nodes = nodes
423
    self.live_data = live_data
424
    self.master_name = master_name
425
    self.node_to_primary = node_to_primary
426
    self.node_to_secondary = node_to_secondary
427
    self.groups = groups
428
    self.oob_support = oob_support
429
    self.cluster = cluster
430

    
431
    # Used for individual rows
432
    self.curlive_data = None
433

    
434
  def __iter__(self):
435
    """Iterate over all nodes.
436

437
    This function has side-effects and only one instance of the resulting
438
    generator should be used at a time.
439

440
    """
441
    for node in self.nodes:
442
      if self.live_data:
443
        self.curlive_data = self.live_data.get(node.name, None)
444
      else:
445
        self.curlive_data = None
446
      yield node
447

    
448

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

    
461

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

    
475

    
476
def _GetGroup(cb):
477
  """Build function for calling another function with an node group.
478

479
  @param cb: The callback to be called with the nodegroup
480

481
  """
482
  def fn(ctx, node):
483
    """Get group data for a node.
484

485
    @type ctx: L{NodeQueryData}
486
    @type inst: L{objects.Node}
487
    @param inst: Node object
488

489
    """
490
    ng = ctx.groups.get(node.group, None)
491
    if ng is None:
492
      # Nodes always have a group, or the configuration is corrupt
493
      return _FS_UNAVAIL
494

    
495
    return cb(ctx, node, ng)
496

    
497
  return fn
498

    
499

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

503
  @type ctx: L{NodeQueryData}
504
  @type node: L{objects.Node}
505
  @param node: Node object
506
  @type ng: L{objects.NodeGroup}
507
  @param ng: The node group this node belongs to
508

509
  """
510
  return ng.name
511

    
512

    
513
def _GetNodePower(ctx, node):
514
  """Returns the node powered state
515

516
  @type ctx: L{NodeQueryData}
517
  @type node: L{objects.Node}
518
  @param node: Node object
519

520
  """
521
  if ctx.oob_support[node.name]:
522
    return node.powered
523

    
524
  return _FS_UNAVAIL
525

    
526

    
527
def _GetNdParams(ctx, node, ng):
528
  """Returns the ndparams for this node.
529

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

536
  """
537
  return ctx.cluster.SimpleFillND(ng.FillND(node))
538

    
539

    
540
def _GetLiveNodeField(field, kind, ctx, node):
541
  """Gets the value of a "live" field from L{NodeQueryData}.
542

543
  @param field: Live field name
544
  @param kind: Data kind, one of L{constants.QFT_ALL}
545
  @type ctx: L{NodeQueryData}
546
  @type node: L{objects.Node}
547
  @param node: Node object
548

549
  """
550
  if node.offline:
551
    return _FS_OFFLINE
552

    
553
  if not ctx.curlive_data:
554
    return _FS_NODATA
555

    
556
  try:
557
    value = ctx.curlive_data[field]
558
  except KeyError:
559
    return _FS_UNAVAIL
560

    
561
  if kind == QFT_TEXT:
562
    return value
563

    
564
  assert kind in (QFT_NUMBER, QFT_UNIT)
565

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

    
574

    
575
def _BuildNodeFields():
576
  """Builds list of fields for node queries.
577

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

    
601
  def _GetLength(getter):
602
    return lambda ctx, node: len(getter(ctx)[node.name])
603

    
604
  def _GetList(getter):
605
    return lambda ctx, node: list(getter(ctx)[node.name])
606

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

    
619
  # Add simple fields
620
  fields.extend([(_MakeField(name, title, kind), NQ_CONFIG, _GetItemAttr(name))
621
                 for (name, (title, kind)) in _NODE_SIMPLE_FIELDS.items()])
622

    
623
  # Add fields requiring live data
624
  fields.extend([
625
    (_MakeField(name, title, kind), NQ_LIVE,
626
     compat.partial(_GetLiveNodeField, nfield, kind))
627
    for (name, (title, kind, nfield)) in _NODE_LIVE_FIELDS.items()
628
    ])
629

    
630
  # Add timestamps
631
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
632

    
633
  return _PrepareFieldList(fields)
634

    
635

    
636
class InstanceQueryData:
637
  """Data container for instance data queries.
638

639
  """
640
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
641
               live_data):
642
    """Initializes this class.
643

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

655
    """
656
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
657
           "Offline nodes not included in bad nodes"
658
    assert not (set(live_data.keys()) & set(bad_nodes)), \
659
           "Found live data for bad or offline nodes"
660

    
661
    self.instances = instances
662
    self.cluster = cluster
663
    self.disk_usage = disk_usage
664
    self.offline_nodes = offline_nodes
665
    self.bad_nodes = bad_nodes
666
    self.live_data = live_data
667

    
668
    # Used for individual rows
669
    self.inst_hvparams = None
670
    self.inst_beparams = None
671
    self.inst_nicparams = None
672

    
673
  def __iter__(self):
674
    """Iterate over all instances.
675

676
    This function has side-effects and only one instance of the resulting
677
    generator should be used at a time.
678

679
    """
680
    for inst in self.instances:
681
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
682
      self.inst_beparams = self.cluster.FillBE(inst)
683
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
684
                             for nic in inst.nics]
685

    
686
      yield inst
687

    
688

    
689
def _GetInstOperState(ctx, inst):
690
  """Get instance's operational status.
691

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

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

    
704

    
705
def _GetInstLiveData(name):
706
  """Build function for retrieving live data.
707

708
  @type name: string
709
  @param name: Live data field name
710

711
  """
712
  def fn(ctx, inst):
713
    """Get live data for an instance.
714

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

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

    
726
    if inst.name in ctx.live_data:
727
      data = ctx.live_data[inst.name]
728
      if name in data:
729
        return data[name]
730

    
731
    return _FS_UNAVAIL
732

    
733
  return fn
734

    
735

    
736
def _GetInstStatus(ctx, inst):
737
  """Get instance status.
738

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

743
  """
744
  if inst.primary_node in ctx.offline_nodes:
745
    return "ERROR_nodeoffline"
746

    
747
  if inst.primary_node in ctx.bad_nodes:
748
    return "ERROR_nodedown"
749

    
750
  if bool(ctx.live_data.get(inst.name)):
751
    if inst.admin_up:
752
      return "running"
753
    else:
754
      return "ERROR_up"
755

    
756
  if inst.admin_up:
757
    return "ERROR_down"
758

    
759
  return "ADMIN_down"
760

    
761

    
762
def _GetInstDiskSize(index):
763
  """Build function for retrieving disk size.
764

765
  @type index: int
766
  @param index: Disk index
767

768
  """
769
  def fn(_, inst):
770
    """Get size of a disk.
771

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

775
    """
776
    try:
777
      return inst.disks[index].size
778
    except IndexError:
779
      return _FS_UNAVAIL
780

    
781
  return fn
782

    
783

    
784
def _GetInstNic(index, cb):
785
  """Build function for calling another function with an instance NIC.
786

787
  @type index: int
788
  @param index: NIC index
789
  @type cb: callable
790
  @param cb: Callback
791

792
  """
793
  def fn(ctx, inst):
794
    """Call helper function with instance NIC.
795

796
    @type ctx: L{InstanceQueryData}
797
    @type inst: L{objects.Instance}
798
    @param inst: Instance object
799

800
    """
801
    try:
802
      nic = inst.nics[index]
803
    except IndexError:
804
      return _FS_UNAVAIL
805

    
806
    return cb(ctx, index, nic)
807

    
808
  return fn
809

    
810

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

814
  @type ctx: L{InstanceQueryData}
815
  @type nic: L{objects.NIC}
816
  @param nic: NIC object
817

818
  """
819
  if nic.ip is None:
820
    return _FS_UNAVAIL
821
  else:
822
    return nic.ip
823

    
824

    
825
def _GetInstNicBridge(ctx, index, _):
826
  """Get a NIC's bridge.
827

828
  @type ctx: L{InstanceQueryData}
829
  @type index: int
830
  @param index: NIC index
831

832
  """
833
  assert len(ctx.inst_nicparams) >= index
834

    
835
  nicparams = ctx.inst_nicparams[index]
836

    
837
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
838
    return nicparams[constants.NIC_LINK]
839
  else:
840
    return _FS_UNAVAIL
841

    
842

    
843
def _GetInstAllNicBridges(ctx, inst):
844
  """Get all network bridges for an instance.
845

846
  @type ctx: L{InstanceQueryData}
847
  @type inst: L{objects.Instance}
848
  @param inst: Instance object
849

850
  """
851
  assert len(ctx.inst_nicparams) == len(inst.nics)
852

    
853
  result = []
854

    
855
  for nicp in ctx.inst_nicparams:
856
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
857
      result.append(nicp[constants.NIC_LINK])
858
    else:
859
      result.append(None)
860

    
861
  assert len(result) == len(inst.nics)
862

    
863
  return result
864

    
865

    
866
def _GetInstNicParam(name):
867
  """Build function for retrieving a NIC parameter.
868

869
  @type name: string
870
  @param name: Parameter name
871

872
  """
873
  def fn(ctx, index, _):
874
    """Get a NIC's bridge.
875

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

882
    """
883
    assert len(ctx.inst_nicparams) >= index
884
    return ctx.inst_nicparams[index][name]
885

    
886
  return fn
887

    
888

    
889
def _GetInstanceNetworkFields():
890
  """Get instance fields involving network interfaces.
891

892
  @return: List of field definitions used as input for L{_PrepareFieldList}
893

894
  """
895
  nic_mac_fn = lambda ctx, _, nic: nic.mac
896
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
897
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
898

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

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

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

    
944
  return fields
945

    
946

    
947
def _GetInstDiskUsage(ctx, inst):
948
  """Get disk usage for an instance.
949

950
  @type ctx: L{InstanceQueryData}
951
  @type inst: L{objects.Instance}
952
  @param inst: Instance object
953

954
  """
955
  usage = ctx.disk_usage[inst.name]
956

    
957
  if usage is None:
958
    usage = 0
959

    
960
  return usage
961

    
962

    
963
def _GetInstanceDiskFields():
964
  """Get instance fields involving disks.
965

966
  @return: List of field definitions used as input for L{_PrepareFieldList}
967

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

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

    
989
  return fields
990

    
991

    
992
def _GetInstanceParameterFields():
993
  """Get instance fields involving parameters.
994

995
  @return: List of field definitions used as input for L{_PrepareFieldList}
996

997
  """
998
  # TODO: Consider moving titles closer to constants
999
  be_title = {
1000
    constants.BE_AUTO_BALANCE: "Auto_balance",
1001
    constants.BE_MEMORY: "Configured_memory",
1002
    constants.BE_VCPUS: "VCPUs",
1003
    }
1004

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

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

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

    
1035
  # HV params
1036
  def _GetInstHvParam(name):
1037
    return lambda ctx, _: ctx.inst_hvparams.get(name, None)
1038

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

    
1047
  # BE params
1048
  def _GetInstBeParam(name):
1049
    return lambda ctx, _: ctx.inst_beparams.get(name, None)
1050

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

    
1058
  return fields
1059

    
1060

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

    
1072

    
1073
def _BuildInstanceFields():
1074
  """Builds list of fields for instance queries.
1075

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

    
1088
  # Add simple fields
1089
  fields.extend([(_MakeField(name, title, kind), IQ_CONFIG, _GetItemAttr(name))
1090
                 for (name, (title, kind)) in _INST_SIMPLE_FIELDS.items()])
1091

    
1092
  # Fields requiring talking to the node
1093
  fields.extend([
1094
    (_MakeField("oper_state", "Running", QFT_BOOL), IQ_LIVE,
1095
     _GetInstOperState),
1096
    (_MakeField("oper_ram", "RuntimeMemory", QFT_UNIT), IQ_LIVE,
1097
     _GetInstLiveData("memory")),
1098
    (_MakeField("oper_vcpus", "RuntimeVCPUs", QFT_NUMBER), IQ_LIVE,
1099
     _GetInstLiveData("vcpus")),
1100
    (_MakeField("status", "Status", QFT_TEXT), IQ_LIVE, _GetInstStatus),
1101
    ])
1102

    
1103
  fields.extend(_GetInstanceParameterFields())
1104
  fields.extend(_GetInstanceDiskFields())
1105
  fields.extend(_GetInstanceNetworkFields())
1106
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1107

    
1108
  return _PrepareFieldList(fields)
1109

    
1110

    
1111
class LockQueryData:
1112
  """Data container for lock data queries.
1113

1114
  """
1115
  def __init__(self, lockdata):
1116
    """Initializes this class.
1117

1118
    """
1119
    self.lockdata = lockdata
1120

    
1121
  def __iter__(self):
1122
    """Iterate over all locks.
1123

1124
    """
1125
    return iter(self.lockdata)
1126

    
1127

    
1128
def _GetLockOwners(_, data):
1129
  """Returns a sorted list of a lock's current owners.
1130

1131
  """
1132
  (_, _, owners, _) = data
1133

    
1134
  if owners:
1135
    owners = utils.NiceSort(owners)
1136

    
1137
  return owners
1138

    
1139

    
1140
def _GetLockPending(_, data):
1141
  """Returns a sorted list of a lock's pending acquires.
1142

1143
  """
1144
  (_, _, _, pending) = data
1145

    
1146
  if pending:
1147
    pending = [(mode, utils.NiceSort(names))
1148
               for (mode, names) in pending]
1149

    
1150
  return pending
1151

    
1152

    
1153
def _BuildLockFields():
1154
  """Builds list of fields for lock queries.
1155

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

    
1166

    
1167
class GroupQueryData:
1168
  """Data container for node group data queries.
1169

1170
  """
1171
  def __init__(self, groups, group_to_nodes, group_to_instances):
1172
    """Initializes this class.
1173

1174
    @param groups: List of node group objects
1175
    @type group_to_nodes: dict; group UUID as key
1176
    @param group_to_nodes: Per-group list of nodes
1177
    @type group_to_instances: dict; group UUID as key
1178
    @param group_to_instances: Per-group list of (primary) instances
1179

1180
    """
1181
    self.groups = groups
1182
    self.group_to_nodes = group_to_nodes
1183
    self.group_to_instances = group_to_instances
1184

    
1185
  def __iter__(self):
1186
    """Iterate over all node groups.
1187

1188
    """
1189
    return iter(self.groups)
1190

    
1191

    
1192
_GROUP_SIMPLE_FIELDS = {
1193
  "alloc_policy": ("AllocPolicy", QFT_TEXT),
1194
  "name": ("Group", QFT_TEXT),
1195
  "serial_no": ("SerialNo", QFT_NUMBER),
1196
  "uuid": ("UUID", QFT_TEXT),
1197
  "ndparams": ("NDParams", QFT_OTHER),
1198
  }
1199

    
1200

    
1201
def _BuildGroupFields():
1202
  """Builds list of fields for node group queries.
1203

1204
  """
1205
  # Add simple fields
1206
  fields = [(_MakeField(name, title, kind), GQ_CONFIG, _GetItemAttr(name))
1207
            for (name, (title, kind)) in _GROUP_SIMPLE_FIELDS.items()]
1208

    
1209
  def _GetLength(getter):
1210
    return lambda ctx, group: len(getter(ctx)[group.uuid])
1211

    
1212
  def _GetSortedList(getter):
1213
    return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
1214

    
1215
  group_to_nodes = operator.attrgetter("group_to_nodes")
1216
  group_to_instances = operator.attrgetter("group_to_instances")
1217

    
1218
  # Add fields for nodes
1219
  fields.extend([
1220
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER),
1221
     GQ_NODE, _GetLength(group_to_nodes)),
1222
    (_MakeField("node_list", "NodeList", QFT_OTHER),
1223
     GQ_NODE, _GetSortedList(group_to_nodes)),
1224
    ])
1225

    
1226
  # Add fields for instances
1227
  fields.extend([
1228
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER),
1229
     GQ_INST, _GetLength(group_to_instances)),
1230
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER),
1231
     GQ_INST, _GetSortedList(group_to_instances)),
1232
    ])
1233

    
1234
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1235

    
1236
  return _PrepareFieldList(fields)
1237

    
1238

    
1239
#: Fields available for node queries
1240
NODE_FIELDS = _BuildNodeFields()
1241

    
1242
#: Fields available for instance queries
1243
INSTANCE_FIELDS = _BuildInstanceFields()
1244

    
1245
#: Fields available for lock queries
1246
LOCK_FIELDS = _BuildLockFields()
1247

    
1248
#: Fields available for node group queries
1249
GROUP_FIELDS = _BuildGroupFields()
1250

    
1251
#: All available field lists
1252
ALL_FIELD_LISTS = [NODE_FIELDS, INSTANCE_FIELDS, LOCK_FIELDS, GROUP_FIELDS]