Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ 8572f1fe

History | View | Annotate | Download (34.6 kB)

1
#
2
#
3

    
4
# Copyright (C) 2010 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
import logging
25
import operator
26
import re
27

    
28
from ganeti import constants
29
from ganeti import errors
30
from ganeti import utils
31
from ganeti import compat
32
from ganeti import objects
33
from ganeti import ht
34

    
35

    
36
(NQ_CONFIG,
37
 NQ_INST,
38
 NQ_LIVE,
39
 NQ_GROUP,
40
 NQ_OOB) = range(1, 6)
41

    
42
(IQ_CONFIG,
43
 IQ_LIVE,
44
 IQ_DISKUSAGE) = range(100, 103)
45

    
46
(LQ_MODE,
47
 LQ_OWNER,
48
 LQ_PENDING) = range(10, 13)
49

    
50
(GQ_CONFIG,
51
 GQ_NODE,
52
 GQ_INST) = range(200, 203)
53

    
54

    
55
FIELD_NAME_RE = re.compile(r"^[a-z0-9/._]+$")
56
TITLE_RE = re.compile(r"^[^\s]+$")
57

    
58
#: Verification function for each field type
59
_VERIFY_FN = {
60
  constants.QFT_UNKNOWN: ht.TNone,
61
  constants.QFT_TEXT: ht.TString,
62
  constants.QFT_BOOL: ht.TBool,
63
  constants.QFT_NUMBER: ht.TInt,
64
  constants.QFT_UNIT: ht.TInt,
65
  constants.QFT_TIMESTAMP: ht.TOr(ht.TInt, ht.TFloat),
66
  constants.QFT_OTHER: lambda _: True,
67
  }
68

    
69

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

73
  """
74
  return (constants.QRFS_UNKNOWN, None)
75

    
76

    
77
def _GetQueryFields(fielddefs, selected):
78
  """Calculates the internal list of selected fields.
79

80
  Unknown fields are returned as L{constants.QFT_UNKNOWN}.
81

82
  @type fielddefs: dict
83
  @param fielddefs: Field definitions
84
  @type selected: list of strings
85
  @param selected: List of selected fields
86

87
  """
88
  result = []
89

    
90
  for name in selected:
91
    try:
92
      fdef = fielddefs[name]
93
    except KeyError:
94
      fdef = (_MakeField(name, name, constants.QFT_UNKNOWN),
95
              None, _GetUnknownField)
96

    
97
    assert len(fdef) == 3
98

    
99
    result.append(fdef)
100

    
101
  return result
102

    
103

    
104
def GetAllFields(fielddefs):
105
  """Extract L{objects.QueryFieldDefinition} from field definitions.
106

107
  @rtype: list of L{objects.QueryFieldDefinition}
108

109
  """
110
  return [fdef for (fdef, _, _) in fielddefs]
111

    
112

    
113
class Query:
114
  def __init__(self, fieldlist, selected):
115
    """Initializes this class.
116

117
    The field definition is a dictionary with the field's name as a key and a
118
    tuple containing, in order, the field definition object
119
    (L{objects.QueryFieldDefinition}, the data kind to help calling code
120
    collect data and a retrieval function. The retrieval function is called
121
    with two parameters, in order, the data container and the item in container
122
    (see L{Query.Query}).
123

124
    Users of this class can call L{RequestedData} before preparing the data
125
    container to determine what data is needed.
126

127
    @type fieldlist: dictionary
128
    @param fieldlist: Field definitions
129
    @type selected: list of strings
130
    @param selected: List of selected fields
131

132
    """
133
    self._fields = _GetQueryFields(fieldlist, selected)
134

    
135
  def RequestedData(self):
136
    """Gets requested kinds of data.
137

138
    @rtype: frozenset
139

140
    """
141
    return frozenset(datakind
142
                     for (_, datakind, _) in self._fields
143
                     if datakind is not None)
144

    
145
  def GetFields(self):
146
    """Returns the list of fields for this query.
147

148
    Includes unknown fields.
149

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

152
    """
153
    return GetAllFields(self._fields)
154

    
155
  def Query(self, ctx):
156
    """Execute a query.
157

158
    @param ctx: Data container passed to field retrieval functions, must
159
      support iteration using C{__iter__}
160

161
    """
162
    result = [[fn(ctx, item) for (_, _, fn) in self._fields]
163
              for item in ctx]
164

    
165
    # Verify result
166
    if __debug__:
167
      for (idx, row) in enumerate(result):
168
        assert _VerifyResultRow(self._fields, row), \
169
               ("Inconsistent result for fields %s in row %s: %r" %
170
                (GetAllFields(self._fields), idx, row))
171

    
172
    return result
173

    
174
  def OldStyleQuery(self, ctx):
175
    """Query with "old" query result format.
176

177
    See L{Query.Query} for arguments.
178

179
    """
180
    unknown = set(fdef.name
181
                  for (fdef, _, _) in self._fields
182
                  if fdef.kind == constants.QFT_UNKNOWN)
183
    if unknown:
184
      raise errors.OpPrereqError("Unknown output fields selected: %s" %
185
                                 (utils.CommaJoin(unknown), ),
186
                                 errors.ECODE_INVAL)
187

    
188
    return [[value for (_, value) in row]
189
            for row in self.Query(ctx)]
190

    
191

    
192
def _VerifyResultRow(fields, row):
193
  """Verifies the contents of a query result row.
194

195
  @type fields: list
196
  @param fields: Field definitions for result
197
  @type row: list of tuples
198
  @param row: Row data
199

200
  """
201
  return (len(row) == len(fields) and
202
          compat.all((status == constants.QRFS_NORMAL and
203
                      _VERIFY_FN[fdef.kind](value)) or
204
                     # Value for an abnormal status must be None
205
                     (status != constants.QRFS_NORMAL and value is None)
206
                     for ((status, value), (fdef, _, _)) in zip(row, fields)))
207

    
208

    
209
def _PrepareFieldList(fields):
210
  """Prepares field list for use by L{Query}.
211

212
  Converts the list to a dictionary and does some verification.
213

214
  @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data kind,
215
    retrieval function)
216
  @param fields: List of fields
217
  @rtype: dict
218
  @return: Field dictionary for L{Query}
219

220
  """
221
  if __debug__:
222
    duplicates = utils.FindDuplicates(fdef.title.lower()
223
                                      for (fdef, _, _) in fields)
224
    assert not duplicates, "Duplicate title(s) found: %r" % duplicates
225

    
226
  result = {}
227

    
228
  for field in fields:
229
    (fdef, _, fn) = field
230

    
231
    assert fdef.name and fdef.title, "Name and title are required"
232
    assert FIELD_NAME_RE.match(fdef.name)
233
    assert TITLE_RE.match(fdef.title)
234
    assert callable(fn)
235
    assert fdef.name not in result, \
236
           "Duplicate field name '%s' found" % fdef.name
237

    
238
    result[fdef.name] = field
239

    
240
  assert len(result) == len(fields)
241
  assert compat.all(name == fdef.name
242
                    for (name, (fdef, _, _)) in result.items())
243

    
244
  return result
245

    
246

    
247
def GetQueryResponse(query, ctx):
248
  """Prepares the response for a query.
249

250
  @type query: L{Query}
251
  @param ctx: Data container, see L{Query.Query}
252

253
  """
254
  return objects.QueryResponse(data=query.Query(ctx),
255
                               fields=query.GetFields()).ToDict()
256

    
257

    
258
def QueryFields(fielddefs, selected):
259
  """Returns list of available fields.
260

261
  @type fielddefs: dict
262
  @param fielddefs: Field definitions
263
  @type selected: list of strings
264
  @param selected: List of selected fields
265
  @return: List of L{objects.QueryFieldDefinition}
266

267
  """
268
  if selected is None:
269
    # Client requests all fields, sort by name
270
    fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
271
                           key=operator.attrgetter("name"))
272
  else:
273
    # Keep order as requested by client
274
    fdefs = Query(fielddefs, selected).GetFields()
275

    
276
  return objects.QueryFieldsResponse(fields=fdefs).ToDict()
277

    
278

    
279
def _MakeField(name, title, kind):
280
  """Wrapper for creating L{objects.QueryFieldDefinition} instances.
281

282
  @param name: Field name as a regular expression
283
  @param title: Human-readable title
284
  @param kind: Field type
285

286
  """
287
  return objects.QueryFieldDefinition(name=name, title=title, kind=kind)
288

    
289

    
290
def _GetNodeRole(node, master_name):
291
  """Determine node role.
292

293
  @type node: L{objects.Node}
294
  @param node: Node object
295
  @type master_name: string
296
  @param master_name: Master node name
297

298
  """
299
  if node.name == master_name:
300
    return "M"
301
  elif node.master_candidate:
302
    return "C"
303
  elif node.drained:
304
    return "D"
305
  elif node.offline:
306
    return "O"
307
  else:
308
    return "R"
309

    
310

    
311
def _GetItemAttr(attr):
312
  """Returns a field function to return an attribute of the item.
313

314
  @param attr: Attribute name
315

316
  """
317
  getter = operator.attrgetter(attr)
318
  return lambda _, item: (constants.QRFS_NORMAL, getter(item))
319

    
320

    
321
def _GetItemTimestamp(getter):
322
  """Returns function for getting timestamp of item.
323

324
  @type getter: callable
325
  @param getter: Function to retrieve timestamp attribute
326

327
  """
328
  def fn(_, item):
329
    """Returns a timestamp of item.
330

331
    """
332
    timestamp = getter(item)
333
    if timestamp is None:
334
      # Old configs might not have all timestamps
335
      return (constants.QRFS_UNAVAIL, None)
336
    else:
337
      return (constants.QRFS_NORMAL, timestamp)
338

    
339
  return fn
340

    
341

    
342
def _GetItemTimestampFields(datatype):
343
  """Returns common timestamp fields.
344

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

347
  """
348
  return [
349
    (_MakeField("ctime", "CTime", constants.QFT_TIMESTAMP), datatype,
350
     _GetItemTimestamp(operator.attrgetter("ctime"))),
351
    (_MakeField("mtime", "MTime", constants.QFT_TIMESTAMP), datatype,
352
     _GetItemTimestamp(operator.attrgetter("mtime"))),
353
    ]
354

    
355

    
356
class NodeQueryData:
357
  """Data container for node data queries.
358

359
  """
360
  def __init__(self, nodes, live_data, master_name, node_to_primary,
361
               node_to_secondary, groups, oob_support, cluster):
362
    """Initializes this class.
363

364
    """
365
    self.nodes = nodes
366
    self.live_data = live_data
367
    self.master_name = master_name
368
    self.node_to_primary = node_to_primary
369
    self.node_to_secondary = node_to_secondary
370
    self.groups = groups
371
    self.oob_support = oob_support
372
    self.cluster = cluster
373

    
374
    # Used for individual rows
375
    self.curlive_data = None
376

    
377
  def __iter__(self):
378
    """Iterate over all nodes.
379

380
    This function has side-effects and only one instance of the resulting
381
    generator should be used at a time.
382

383
    """
384
    for node in self.nodes:
385
      if self.live_data:
386
        self.curlive_data = self.live_data.get(node.name, None)
387
      else:
388
        self.curlive_data = None
389
      yield node
390

    
391

    
392
#: Fields that are direct attributes of an L{objects.Node} object
393
_NODE_SIMPLE_FIELDS = {
394
  "drained": ("Drained", constants.QFT_BOOL),
395
  "master_candidate": ("MasterC", constants.QFT_BOOL),
396
  "master_capable": ("MasterCapable", constants.QFT_BOOL),
397
  "name": ("Node", constants.QFT_TEXT),
398
  "offline": ("Offline", constants.QFT_BOOL),
399
  "serial_no": ("SerialNo", constants.QFT_NUMBER),
400
  "uuid": ("UUID", constants.QFT_TEXT),
401
  "vm_capable": ("VMCapable", constants.QFT_BOOL),
402
  }
403

    
404

    
405
#: Fields requiring talking to the node
406
_NODE_LIVE_FIELDS = {
407
  "bootid": ("BootID", constants.QFT_TEXT, "bootid"),
408
  "cnodes": ("CNodes", constants.QFT_NUMBER, "cpu_nodes"),
409
  "csockets": ("CSockets", constants.QFT_NUMBER, "cpu_sockets"),
410
  "ctotal": ("CTotal", constants.QFT_NUMBER, "cpu_total"),
411
  "dfree": ("DFree", constants.QFT_UNIT, "vg_free"),
412
  "dtotal": ("DTotal", constants.QFT_UNIT, "vg_size"),
413
  "mfree": ("MFree", constants.QFT_UNIT, "memory_free"),
414
  "mnode": ("MNode", constants.QFT_UNIT, "memory_dom0"),
415
  "mtotal": ("MTotal", constants.QFT_UNIT, "memory_total"),
416
  }
417

    
418

    
419
def _GetGroup(cb):
420
  """Build function for calling another function with an node group.
421

422
  @param cb: The callback to be called with the nodegroup
423

424
  """
425
  def fn(ctx, node):
426
    """Get group data for a node.
427

428
    @type ctx: L{NodeQueryData}
429
    @type inst: L{objects.Node}
430
    @param inst: Node object
431

432
    """
433
    ng = ctx.groups.get(node.group, None)
434
    if ng is None:
435
      # Nodes always have a group, or the configuration is corrupt
436
      return (constants.QRFS_UNAVAIL, None)
437

    
438
    return cb(ctx, node, ng)
439

    
440
  return fn
441

    
442

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

446
  @type ctx: L{NodeQueryData}
447
  @type node: L{objects.Node}
448
  @param node: Node object
449
  @type ng: L{objects.NodeGroup}
450
  @param ng: The node group this node belongs to
451

452
  """
453
  return (constants.QRFS_NORMAL, ng.name)
454

    
455

    
456
def _GetNodePower(ctx, node):
457
  """Returns the node powered state
458

459
  @type ctx: L{NodeQueryData}
460
  @type node: L{objects.Node}
461
  @param node: Node object
462

463
  """
464
  if ctx.oob_support[node.name]:
465
    return (constants.QRFS_NORMAL, node.powered)
466

    
467
  return (constants.QRFS_UNAVAIL, None)
468

    
469

    
470
def _GetNdParams(ctx, node, ng):
471
  """Returns the ndparams for this node.
472

473
  @type ctx: L{NodeQueryData}
474
  @type node: L{objects.Node}
475
  @param node: Node object
476
  @type ng: L{objects.NodeGroup}
477
  @param ng: The node group this node belongs to
478

479
  """
480
  return (constants.QRFS_NORMAL, ctx.cluster.SimpleFillND(ng.FillND(node)))
481

    
482

    
483
def _GetLiveNodeField(field, kind, ctx, node):
484
  """Gets the value of a "live" field from L{NodeQueryData}.
485

486
  @param field: Live field name
487
  @param kind: Data kind, one of L{constants.QFT_ALL}
488
  @type ctx: L{NodeQueryData}
489
  @type node: L{objects.Node}
490
  @param node: Node object
491

492
  """
493
  if node.offline:
494
    return (constants.QRFS_OFFLINE, None)
495

    
496
  if not ctx.curlive_data:
497
    return (constants.QRFS_NODATA, None)
498

    
499
  try:
500
    value = ctx.curlive_data[field]
501
  except KeyError:
502
    return (constants.QRFS_UNAVAIL, None)
503

    
504
  if kind == constants.QFT_TEXT:
505
    return (constants.QRFS_NORMAL, value)
506

    
507
  assert kind in (constants.QFT_NUMBER, constants.QFT_UNIT)
508

    
509
  # Try to convert into number
510
  try:
511
    return (constants.QRFS_NORMAL, int(value))
512
  except (ValueError, TypeError):
513
    logging.exception("Failed to convert node field '%s' (value %r) to int",
514
                      value, field)
515
    return (constants.QRFS_UNAVAIL, None)
516

    
517

    
518
def _BuildNodeFields():
519
  """Builds list of fields for node queries.
520

521
  """
522
  fields = [
523
    (_MakeField("pip", "PrimaryIP", constants.QFT_TEXT), NQ_CONFIG,
524
     lambda ctx, node: (constants.QRFS_NORMAL, node.primary_ip)),
525
    (_MakeField("sip", "SecondaryIP", constants.QFT_TEXT), NQ_CONFIG,
526
     lambda ctx, node: (constants.QRFS_NORMAL, node.secondary_ip)),
527
    (_MakeField("tags", "Tags", constants.QFT_OTHER), NQ_CONFIG,
528
     lambda ctx, node: (constants.QRFS_NORMAL, list(node.GetTags()))),
529
    (_MakeField("master", "IsMaster", constants.QFT_BOOL), NQ_CONFIG,
530
     lambda ctx, node: (constants.QRFS_NORMAL, node.name == ctx.master_name)),
531
    (_MakeField("role", "Role", constants.QFT_TEXT), NQ_CONFIG,
532
     lambda ctx, node: (constants.QRFS_NORMAL,
533
                        _GetNodeRole(node, ctx.master_name))),
534
    (_MakeField("group", "Group", constants.QFT_TEXT), NQ_GROUP,
535
     _GetGroup(_GetNodeGroup)),
536
    (_MakeField("group.uuid", "GroupUUID", constants.QFT_TEXT),
537
     NQ_CONFIG, lambda ctx, node: (constants.QRFS_NORMAL, node.group)),
538
    (_MakeField("powered", "Powered", constants.QFT_BOOL), NQ_OOB,
539
      _GetNodePower),
540
    (_MakeField("ndparams", "NodeParameters", constants.QFT_OTHER), NQ_GROUP,
541
      _GetGroup(_GetNdParams)),
542
    (_MakeField("custom_ndparams", "CustomNodeParameters", constants.QFT_OTHER),
543
      NQ_GROUP, lambda ctx, node: (constants.QRFS_NORMAL, node.ndparams)),
544
    ]
545

    
546
  def _GetLength(getter):
547
    return lambda ctx, node: (constants.QRFS_NORMAL,
548
                              len(getter(ctx)[node.name]))
549

    
550
  def _GetList(getter):
551
    return lambda ctx, node: (constants.QRFS_NORMAL,
552
                              list(getter(ctx)[node.name]))
553

    
554
  # Add fields operating on instance lists
555
  for prefix, titleprefix, getter in \
556
      [("p", "Pri", operator.attrgetter("node_to_primary")),
557
       ("s", "Sec", operator.attrgetter("node_to_secondary"))]:
558
    fields.extend([
559
      (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(),
560
                  constants.QFT_NUMBER),
561
       NQ_INST, _GetLength(getter)),
562
      (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
563
                  constants.QFT_OTHER),
564
       NQ_INST, _GetList(getter)),
565
      ])
566

    
567
  # Add simple fields
568
  fields.extend([(_MakeField(name, title, kind), NQ_CONFIG, _GetItemAttr(name))
569
                 for (name, (title, kind)) in _NODE_SIMPLE_FIELDS.items()])
570

    
571
  # Add fields requiring live data
572
  fields.extend([
573
    (_MakeField(name, title, kind), NQ_LIVE,
574
     compat.partial(_GetLiveNodeField, nfield, kind))
575
    for (name, (title, kind, nfield)) in _NODE_LIVE_FIELDS.items()
576
    ])
577

    
578
  # Add timestamps
579
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
580

    
581
  return _PrepareFieldList(fields)
582

    
583

    
584
class InstanceQueryData:
585
  """Data container for instance data queries.
586

587
  """
588
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
589
               live_data):
590
    """Initializes this class.
591

592
    @param instances: List of instance objects
593
    @param cluster: Cluster object
594
    @type disk_usage: dict; instance name as key
595
    @param disk_usage: Per-instance disk usage
596
    @type offline_nodes: list of strings
597
    @param offline_nodes: List of offline nodes
598
    @type bad_nodes: list of strings
599
    @param bad_nodes: List of faulty nodes
600
    @type live_data: dict; instance name as key
601
    @param live_data: Per-instance live data
602

603
    """
604
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
605
           "Offline nodes not included in bad nodes"
606
    assert not (set(live_data.keys()) & set(bad_nodes)), \
607
           "Found live data for bad or offline nodes"
608

    
609
    self.instances = instances
610
    self.cluster = cluster
611
    self.disk_usage = disk_usage
612
    self.offline_nodes = offline_nodes
613
    self.bad_nodes = bad_nodes
614
    self.live_data = live_data
615

    
616
    # Used for individual rows
617
    self.inst_hvparams = None
618
    self.inst_beparams = None
619
    self.inst_nicparams = None
620

    
621
  def __iter__(self):
622
    """Iterate over all instances.
623

624
    This function has side-effects and only one instance of the resulting
625
    generator should be used at a time.
626

627
    """
628
    for inst in self.instances:
629
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
630
      self.inst_beparams = self.cluster.FillBE(inst)
631
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
632
                             for nic in inst.nics]
633

    
634
      yield inst
635

    
636

    
637
def _GetInstOperState(ctx, inst):
638
  """Get instance's operational status.
639

640
  @type ctx: L{InstanceQueryData}
641
  @type inst: L{objects.Instance}
642
  @param inst: Instance object
643

644
  """
645
  # Can't use QRFS_OFFLINE here as it would describe the instance to be offline
646
  # when we actually don't know due to missing data
647
  if inst.primary_node in ctx.bad_nodes:
648
    return (constants.QRFS_NODATA, None)
649
  else:
650
    return (constants.QRFS_NORMAL, bool(ctx.live_data.get(inst.name)))
651

    
652

    
653
def _GetInstLiveData(name):
654
  """Build function for retrieving live data.
655

656
  @type name: string
657
  @param name: Live data field name
658

659
  """
660
  def fn(ctx, inst):
661
    """Get live data for an instance.
662

663
    @type ctx: L{InstanceQueryData}
664
    @type inst: L{objects.Instance}
665
    @param inst: Instance object
666

667
    """
668
    if (inst.primary_node in ctx.bad_nodes or
669
        inst.primary_node in ctx.offline_nodes):
670
      # Can't use QRFS_OFFLINE here as it would describe the instance to be
671
      # offline when we actually don't know due to missing data
672
      return (constants.QRFS_NODATA, None)
673

    
674
    if inst.name in ctx.live_data:
675
      data = ctx.live_data[inst.name]
676
      if name in data:
677
        return (constants.QRFS_NORMAL, data[name])
678

    
679
    return (constants.QRFS_UNAVAIL, None)
680

    
681
  return fn
682

    
683

    
684
def _GetInstStatus(ctx, inst):
685
  """Get instance status.
686

687
  @type ctx: L{InstanceQueryData}
688
  @type inst: L{objects.Instance}
689
  @param inst: Instance object
690

691
  """
692
  if inst.primary_node in ctx.offline_nodes:
693
    return (constants.QRFS_NORMAL, "ERROR_nodeoffline")
694

    
695
  if inst.primary_node in ctx.bad_nodes:
696
    return (constants.QRFS_NORMAL, "ERROR_nodedown")
697

    
698
  if bool(ctx.live_data.get(inst.name)):
699
    if inst.admin_up:
700
      return (constants.QRFS_NORMAL, "running")
701
    else:
702
      return (constants.QRFS_NORMAL, "ERROR_up")
703

    
704
  if inst.admin_up:
705
    return (constants.QRFS_NORMAL, "ERROR_down")
706

    
707
  return (constants.QRFS_NORMAL, "ADMIN_down")
708

    
709

    
710
def _GetInstDiskSize(index):
711
  """Build function for retrieving disk size.
712

713
  @type index: int
714
  @param index: Disk index
715

716
  """
717
  def fn(_, inst):
718
    """Get size of a disk.
719

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

723
    """
724
    try:
725
      return (constants.QRFS_NORMAL, inst.disks[index].size)
726
    except IndexError:
727
      return (constants.QRFS_UNAVAIL, None)
728

    
729
  return fn
730

    
731

    
732
def _GetInstNic(index, cb):
733
  """Build function for calling another function with an instance NIC.
734

735
  @type index: int
736
  @param index: NIC index
737
  @type cb: callable
738
  @param cb: Callback
739

740
  """
741
  def fn(ctx, inst):
742
    """Call helper function with instance NIC.
743

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

748
    """
749
    try:
750
      nic = inst.nics[index]
751
    except IndexError:
752
      return (constants.QRFS_UNAVAIL, None)
753

    
754
    return cb(ctx, index, nic)
755

    
756
  return fn
757

    
758

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

762
  @type ctx: L{InstanceQueryData}
763
  @type nic: L{objects.NIC}
764
  @param nic: NIC object
765

766
  """
767
  if nic.ip is None:
768
    return (constants.QRFS_UNAVAIL, None)
769
  else:
770
    return (constants.QRFS_NORMAL, nic.ip)
771

    
772

    
773
def _GetInstNicBridge(ctx, index, _):
774
  """Get a NIC's bridge.
775

776
  @type ctx: L{InstanceQueryData}
777
  @type index: int
778
  @param index: NIC index
779

780
  """
781
  assert len(ctx.inst_nicparams) >= index
782

    
783
  nicparams = ctx.inst_nicparams[index]
784

    
785
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
786
    return (constants.QRFS_NORMAL, nicparams[constants.NIC_LINK])
787
  else:
788
    return (constants.QRFS_UNAVAIL, None)
789

    
790

    
791
def _GetInstAllNicBridges(ctx, inst):
792
  """Get all network bridges for an instance.
793

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

798
  """
799
  assert len(ctx.inst_nicparams) == len(inst.nics)
800

    
801
  result = []
802

    
803
  for nicp in ctx.inst_nicparams:
804
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
805
      result.append(nicp[constants.NIC_LINK])
806
    else:
807
      result.append(None)
808

    
809
  assert len(result) == len(inst.nics)
810

    
811
  return (constants.QRFS_NORMAL, result)
812

    
813

    
814
def _GetInstNicParam(name):
815
  """Build function for retrieving a NIC parameter.
816

817
  @type name: string
818
  @param name: Parameter name
819

820
  """
821
  def fn(ctx, index, _):
822
    """Get a NIC's bridge.
823

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

830
    """
831
    assert len(ctx.inst_nicparams) >= index
832
    return (constants.QRFS_NORMAL, ctx.inst_nicparams[index][name])
833

    
834
  return fn
835

    
836

    
837
def _GetInstanceNetworkFields():
838
  """Get instance fields involving network interfaces.
839

840
  @return: List of field definitions used as input for L{_PrepareFieldList}
841

842
  """
843
  nic_mac_fn = lambda ctx, _, nic: (constants.QRFS_NORMAL, nic.mac)
844
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
845
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
846

    
847
  fields = [
848
    # First NIC (legacy)
849
    (_MakeField("ip", "IP_address", constants.QFT_TEXT), IQ_CONFIG,
850
     _GetInstNic(0, _GetInstNicIp)),
851
    (_MakeField("mac", "MAC_address", constants.QFT_TEXT), IQ_CONFIG,
852
     _GetInstNic(0, nic_mac_fn)),
853
    (_MakeField("bridge", "Bridge", constants.QFT_TEXT), IQ_CONFIG,
854
     _GetInstNic(0, _GetInstNicBridge)),
855
    (_MakeField("nic_mode", "NIC_Mode", constants.QFT_TEXT), IQ_CONFIG,
856
     _GetInstNic(0, nic_mode_fn)),
857
    (_MakeField("nic_link", "NIC_Link", constants.QFT_TEXT), IQ_CONFIG,
858
     _GetInstNic(0, nic_link_fn)),
859

    
860
    # All NICs
861
    (_MakeField("nic.count", "NICs", constants.QFT_NUMBER), IQ_CONFIG,
862
     lambda ctx, inst: (constants.QRFS_NORMAL, len(inst.nics))),
863
    (_MakeField("nic.macs", "NIC_MACs", constants.QFT_OTHER), IQ_CONFIG,
864
     lambda ctx, inst: (constants.QRFS_NORMAL, [nic.mac for nic in inst.nics])),
865
    (_MakeField("nic.ips", "NIC_IPs", constants.QFT_OTHER), IQ_CONFIG,
866
     lambda ctx, inst: (constants.QRFS_NORMAL, [nic.ip for nic in inst.nics])),
867
    (_MakeField("nic.modes", "NIC_modes", constants.QFT_OTHER), IQ_CONFIG,
868
     lambda ctx, inst: (constants.QRFS_NORMAL,
869
                        [nicp[constants.NIC_MODE]
870
                         for nicp in ctx.inst_nicparams])),
871
    (_MakeField("nic.links", "NIC_links", constants.QFT_OTHER), IQ_CONFIG,
872
     lambda ctx, inst: (constants.QRFS_NORMAL,
873
                        [nicp[constants.NIC_LINK]
874
                         for nicp in ctx.inst_nicparams])),
875
    (_MakeField("nic.bridges", "NIC_bridges", constants.QFT_OTHER), IQ_CONFIG,
876
     _GetInstAllNicBridges),
877
    ]
878

    
879
  # NICs by number
880
  for i in range(constants.MAX_NICS):
881
    fields.extend([
882
      (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, constants.QFT_TEXT),
883
       IQ_CONFIG, _GetInstNic(i, _GetInstNicIp)),
884
      (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, constants.QFT_TEXT),
885
       IQ_CONFIG, _GetInstNic(i, nic_mac_fn)),
886
      (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, constants.QFT_TEXT),
887
       IQ_CONFIG, _GetInstNic(i, nic_mode_fn)),
888
      (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, constants.QFT_TEXT),
889
       IQ_CONFIG, _GetInstNic(i, nic_link_fn)),
890
      (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, constants.QFT_TEXT),
891
       IQ_CONFIG, _GetInstNic(i, _GetInstNicBridge)),
892
      ])
893

    
894
  return fields
895

    
896

    
897
def _GetInstDiskUsage(ctx, inst):
898
  """Get disk usage for an instance.
899

900
  @type ctx: L{InstanceQueryData}
901
  @type inst: L{objects.Instance}
902
  @param inst: Instance object
903

904
  """
905
  usage = ctx.disk_usage[inst.name]
906

    
907
  if usage is None:
908
    usage = 0
909

    
910
  return (constants.QRFS_NORMAL, usage)
911

    
912

    
913
def _GetInstanceDiskFields():
914
  """Get instance fields involving disks.
915

916
  @return: List of field definitions used as input for L{_PrepareFieldList}
917

918
  """
919
  fields = [
920
    (_MakeField("disk_usage", "DiskUsage", constants.QFT_UNIT), IQ_DISKUSAGE,
921
     _GetInstDiskUsage),
922
    (_MakeField("sda_size", "LegacyDisk/0", constants.QFT_UNIT), IQ_CONFIG,
923
     _GetInstDiskSize(0)),
924
    (_MakeField("sdb_size", "LegacyDisk/1", constants.QFT_UNIT), IQ_CONFIG,
925
     _GetInstDiskSize(1)),
926
    (_MakeField("disk.count", "Disks", constants.QFT_NUMBER), IQ_CONFIG,
927
     lambda ctx, inst: (constants.QRFS_NORMAL, len(inst.disks))),
928
    (_MakeField("disk.sizes", "Disk_sizes", constants.QFT_OTHER), IQ_CONFIG,
929
     lambda ctx, inst: (constants.QRFS_NORMAL,
930
                        [disk.size for disk in inst.disks])),
931
    ]
932

    
933
  # Disks by number
934
  fields.extend([
935
    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, constants.QFT_UNIT),
936
     IQ_CONFIG, _GetInstDiskSize(i))
937
    for i in range(constants.MAX_DISKS)
938
    ])
939

    
940
  return fields
941

    
942

    
943
def _GetInstanceParameterFields():
944
  """Get instance fields involving parameters.
945

946
  @return: List of field definitions used as input for L{_PrepareFieldList}
947

948
  """
949
  # TODO: Consider moving titles closer to constants
950
  be_title = {
951
    constants.BE_AUTO_BALANCE: "Auto_balance",
952
    constants.BE_MEMORY: "Configured_memory",
953
    constants.BE_VCPUS: "VCPUs",
954
    }
955

    
956
  hv_title = {
957
    constants.HV_ACPI: "ACPI",
958
    constants.HV_BOOT_ORDER: "Boot_order",
959
    constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
960
    constants.HV_DISK_TYPE: "Disk_type",
961
    constants.HV_INITRD_PATH: "Initrd_path",
962
    constants.HV_KERNEL_PATH: "Kernel_path",
963
    constants.HV_NIC_TYPE: "NIC_type",
964
    constants.HV_PAE: "PAE",
965
    constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
966
    }
967

    
968
  fields = [
969
    # Filled parameters
970
    (_MakeField("hvparams", "HypervisorParameters", constants.QFT_OTHER),
971
     IQ_CONFIG, lambda ctx, _: (constants.QRFS_NORMAL, ctx.inst_hvparams)),
972
    (_MakeField("beparams", "BackendParameters", constants.QFT_OTHER),
973
     IQ_CONFIG, lambda ctx, _: (constants.QRFS_NORMAL, ctx.inst_beparams)),
974
    (_MakeField("vcpus", "LegacyVCPUs", constants.QFT_NUMBER), IQ_CONFIG,
975
     lambda ctx, _: (constants.QRFS_NORMAL,
976
                     ctx.inst_beparams[constants.BE_VCPUS])),
977

    
978
    # Unfilled parameters
979
    (_MakeField("custom_hvparams", "CustomHypervisorParameters",
980
                constants.QFT_OTHER),
981
     IQ_CONFIG, lambda ctx, inst: (constants.QRFS_NORMAL, inst.hvparams)),
982
    (_MakeField("custom_beparams", "CustomBackendParameters",
983
                constants.QFT_OTHER),
984
     IQ_CONFIG, lambda ctx, inst: (constants.QRFS_NORMAL, inst.beparams)),
985
    (_MakeField("custom_nicparams", "CustomNicParameters",
986
                constants.QFT_OTHER),
987
     IQ_CONFIG, lambda ctx, inst: (constants.QRFS_NORMAL,
988
                                   [nic.nicparams for nic in inst.nics])),
989
    ]
990

    
991
  # HV params
992
  def _GetInstHvParam(name):
993
    return lambda ctx, _: (constants.QRFS_NORMAL,
994
                           ctx.inst_hvparams.get(name, None))
995

    
996
  fields.extend([
997
    # For now all hypervisor parameters are exported as QFT_OTHER
998
    (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
999
                constants.QFT_OTHER),
1000
     IQ_CONFIG, _GetInstHvParam(name))
1001
    for name in constants.HVS_PARAMETERS
1002
    if name not in constants.HVC_GLOBALS
1003
    ])
1004

    
1005
  # BE params
1006
  def _GetInstBeParam(name):
1007
    return lambda ctx, _: (constants.QRFS_NORMAL,
1008
                           ctx.inst_beparams.get(name, None))
1009

    
1010
  fields.extend([
1011
    # For now all backend parameters are exported as QFT_OTHER
1012
    (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1013
                constants.QFT_OTHER),
1014
     IQ_CONFIG, _GetInstBeParam(name))
1015
    for name in constants.BES_PARAMETERS
1016
    ])
1017

    
1018
  return fields
1019

    
1020

    
1021
_INST_SIMPLE_FIELDS = {
1022
  "disk_template": ("Disk_template", constants.QFT_TEXT),
1023
  "hypervisor": ("Hypervisor", constants.QFT_TEXT),
1024
  "name": ("Node", constants.QFT_TEXT),
1025
  # Depending on the hypervisor, the port can be None
1026
  "network_port": ("Network_port", constants.QFT_OTHER),
1027
  "os": ("OS", constants.QFT_TEXT),
1028
  "serial_no": ("SerialNo", constants.QFT_NUMBER),
1029
  "uuid": ("UUID", constants.QFT_TEXT),
1030
  }
1031

    
1032

    
1033
def _BuildInstanceFields():
1034
  """Builds list of fields for instance queries.
1035

1036
  """
1037
  fields = [
1038
    (_MakeField("pnode", "Primary_node", constants.QFT_TEXT), IQ_CONFIG,
1039
     lambda ctx, inst: (constants.QRFS_NORMAL, inst.primary_node)),
1040
    (_MakeField("snodes", "Secondary_Nodes", constants.QFT_OTHER), IQ_CONFIG,
1041
     lambda ctx, inst: (constants.QRFS_NORMAL, list(inst.secondary_nodes))),
1042
    (_MakeField("admin_state", "Autostart", constants.QFT_BOOL), IQ_CONFIG,
1043
     lambda ctx, inst: (constants.QRFS_NORMAL, inst.admin_up)),
1044
    (_MakeField("tags", "Tags", constants.QFT_OTHER), IQ_CONFIG,
1045
     lambda ctx, inst: (constants.QRFS_NORMAL, list(inst.GetTags()))),
1046
    ]
1047

    
1048
  # Add simple fields
1049
  fields.extend([(_MakeField(name, title, kind), IQ_CONFIG, _GetItemAttr(name))
1050
                 for (name, (title, kind)) in _INST_SIMPLE_FIELDS.items()])
1051

    
1052
  # Fields requiring talking to the node
1053
  fields.extend([
1054
    (_MakeField("oper_state", "Running", constants.QFT_BOOL), IQ_LIVE,
1055
     _GetInstOperState),
1056
    (_MakeField("oper_ram", "RuntimeMemory", constants.QFT_UNIT), IQ_LIVE,
1057
     _GetInstLiveData("memory")),
1058
    (_MakeField("oper_vcpus", "RuntimeVCPUs", constants.QFT_NUMBER), IQ_LIVE,
1059
     _GetInstLiveData("vcpus")),
1060
    (_MakeField("status", "Status", constants.QFT_TEXT), IQ_LIVE,
1061
     _GetInstStatus),
1062
    ])
1063

    
1064
  fields.extend(_GetInstanceParameterFields())
1065
  fields.extend(_GetInstanceDiskFields())
1066
  fields.extend(_GetInstanceNetworkFields())
1067
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1068

    
1069
  return _PrepareFieldList(fields)
1070

    
1071

    
1072
class LockQueryData:
1073
  """Data container for lock data queries.
1074

1075
  """
1076
  def __init__(self, lockdata):
1077
    """Initializes this class.
1078

1079
    """
1080
    self.lockdata = lockdata
1081

    
1082
  def __iter__(self):
1083
    """Iterate over all locks.
1084

1085
    """
1086
    return iter(self.lockdata)
1087

    
1088

    
1089
def _GetLockOwners(_, data):
1090
  """Returns a sorted list of a lock's current owners.
1091

1092
  """
1093
  (_, _, owners, _) = data
1094

    
1095
  if owners:
1096
    owners = utils.NiceSort(owners)
1097

    
1098
  return (constants.QRFS_NORMAL, owners)
1099

    
1100

    
1101
def _GetLockPending(_, data):
1102
  """Returns a sorted list of a lock's pending acquires.
1103

1104
  """
1105
  (_, _, _, pending) = data
1106

    
1107
  if pending:
1108
    pending = [(mode, utils.NiceSort(names))
1109
               for (mode, names) in pending]
1110

    
1111
  return (constants.QRFS_NORMAL, pending)
1112

    
1113

    
1114
def _BuildLockFields():
1115
  """Builds list of fields for lock queries.
1116

1117
  """
1118
  return _PrepareFieldList([
1119
    (_MakeField("name", "Name", constants.QFT_TEXT), None,
1120
     lambda ctx, (name, mode, owners, pending): (constants.QRFS_NORMAL, name)),
1121
    (_MakeField("mode", "Mode", constants.QFT_OTHER), LQ_MODE,
1122
     lambda ctx, (name, mode, owners, pending): (constants.QRFS_NORMAL, mode)),
1123
    (_MakeField("owner", "Owner", constants.QFT_OTHER), LQ_OWNER,
1124
     _GetLockOwners),
1125
    (_MakeField("pending", "Pending", constants.QFT_OTHER), LQ_PENDING,
1126
     _GetLockPending),
1127
    ])
1128

    
1129

    
1130
class GroupQueryData:
1131
  """Data container for node group data queries.
1132

1133
  """
1134
  def __init__(self, groups, group_to_nodes, group_to_instances):
1135
    """Initializes this class.
1136

1137
    @param groups: List of node group objects
1138
    @type group_to_nodes: dict; group UUID as key
1139
    @param group_to_nodes: Per-group list of nodes
1140
    @type group_to_instances: dict; group UUID as key
1141
    @param group_to_instances: Per-group list of (primary) instances
1142

1143
    """
1144
    self.groups = groups
1145
    self.group_to_nodes = group_to_nodes
1146
    self.group_to_instances = group_to_instances
1147

    
1148
  def __iter__(self):
1149
    """Iterate over all node groups.
1150

1151
    """
1152
    return iter(self.groups)
1153

    
1154

    
1155
_GROUP_SIMPLE_FIELDS = {
1156
  "alloc_policy": ("AllocPolicy", constants.QFT_TEXT),
1157
  "name": ("Group", constants.QFT_TEXT),
1158
  "serial_no": ("SerialNo", constants.QFT_NUMBER),
1159
  "uuid": ("UUID", constants.QFT_TEXT),
1160
  }
1161

    
1162

    
1163
def _BuildGroupFields():
1164
  """Builds list of fields for node group queries.
1165

1166
  """
1167
  # Add simple fields
1168
  fields = [(_MakeField(name, title, kind), GQ_CONFIG, _GetItemAttr(name))
1169
            for (name, (title, kind)) in _GROUP_SIMPLE_FIELDS.items()]
1170

    
1171
  def _GetLength(getter):
1172
    return lambda ctx, group: (constants.QRFS_NORMAL,
1173
                               len(getter(ctx)[group.uuid]))
1174

    
1175
  def _GetSortedList(getter):
1176
    return lambda ctx, group: (constants.QRFS_NORMAL,
1177
                               utils.NiceSort(getter(ctx)[group.uuid]))
1178

    
1179
  group_to_nodes = operator.attrgetter("group_to_nodes")
1180
  group_to_instances = operator.attrgetter("group_to_instances")
1181

    
1182
  # Add fields for nodes
1183
  fields.extend([
1184
    (_MakeField("node_cnt", "Nodes", constants.QFT_NUMBER),
1185
     GQ_NODE, _GetLength(group_to_nodes)),
1186
    (_MakeField("node_list", "NodeList", constants.QFT_OTHER),
1187
     GQ_NODE, _GetSortedList(group_to_nodes)),
1188
    ])
1189

    
1190
  # Add fields for instances
1191
  fields.extend([
1192
    (_MakeField("pinst_cnt", "Instances", constants.QFT_NUMBER),
1193
     GQ_INST, _GetLength(group_to_instances)),
1194
    (_MakeField("pinst_list", "InstanceList", constants.QFT_OTHER),
1195
     GQ_INST, _GetSortedList(group_to_instances)),
1196
    ])
1197

    
1198
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1199

    
1200
  return _PrepareFieldList(fields)
1201

    
1202

    
1203
#: Fields available for node queries
1204
NODE_FIELDS = _BuildNodeFields()
1205

    
1206
#: Fields available for instance queries
1207
INSTANCE_FIELDS = _BuildInstanceFields()
1208

    
1209
#: Fields available for lock queries
1210
LOCK_FIELDS = _BuildLockFields()
1211

    
1212
#: Fields available for node group queries
1213
GROUP_FIELDS = _BuildGroupFields()
1214

    
1215
#: All available field lists
1216
ALL_FIELD_LISTS = [NODE_FIELDS, INSTANCE_FIELDS, LOCK_FIELDS, GROUP_FIELDS]