Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ 52b5d286

History | View | Annotate | Download (33.5 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):
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

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

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

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

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

    
390

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

    
403

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

    
417

    
418
def _GetNodeGroup(ctx, node):
419
  """Returns the name of a node's group.
420

421
  @type ctx: L{NodeQueryData}
422
  @type node: L{objects.Node}
423
  @param node: Node object
424

425
  """
426
  ng = ctx.groups.get(node.group, None)
427
  if ng is None:
428
    # Nodes always have a group, or the configuration is corrupt
429
    return (constants.QRFS_UNAVAIL, None)
430

    
431
  return (constants.QRFS_NORMAL, ng.name)
432

    
433

    
434
def _GetNodePower(ctx, node):
435
  """Returns the node powered state
436

437
  @type ctx: L{NodeQueryData}
438
  @type node: L{objects.Node}
439
  @param node: Node object
440

441
  """
442
  if ctx.oob_support[node.name]:
443
    return (constants.QRFS_NORMAL, node.powered)
444

    
445
  return (constants.QRFS_UNAVAIL, None)
446

    
447

    
448
def _GetLiveNodeField(field, kind, ctx, node):
449
  """Gets the value of a "live" field from L{NodeQueryData}.
450

451
  @param field: Live field name
452
  @param kind: Data kind, one of L{constants.QFT_ALL}
453
  @type ctx: L{NodeQueryData}
454
  @type node: L{objects.Node}
455
  @param node: Node object
456

457
  """
458
  if node.offline:
459
    return (constants.QRFS_OFFLINE, None)
460

    
461
  if not ctx.curlive_data:
462
    return (constants.QRFS_NODATA, None)
463

    
464
  try:
465
    value = ctx.curlive_data[field]
466
  except KeyError:
467
    return (constants.QRFS_UNAVAIL, None)
468

    
469
  if kind == constants.QFT_TEXT:
470
    return (constants.QRFS_NORMAL, value)
471

    
472
  assert kind in (constants.QFT_NUMBER, constants.QFT_UNIT)
473

    
474
  # Try to convert into number
475
  try:
476
    return (constants.QRFS_NORMAL, int(value))
477
  except (ValueError, TypeError):
478
    logging.exception("Failed to convert node field '%s' (value %r) to int",
479
                      value, field)
480
    return (constants.QRFS_UNAVAIL, None)
481

    
482

    
483
def _BuildNodeFields():
484
  """Builds list of fields for node queries.
485

486
  """
487
  fields = [
488
    (_MakeField("pip", "PrimaryIP", constants.QFT_TEXT), NQ_CONFIG,
489
     lambda ctx, node: (constants.QRFS_NORMAL, node.primary_ip)),
490
    (_MakeField("sip", "SecondaryIP", constants.QFT_TEXT), NQ_CONFIG,
491
     lambda ctx, node: (constants.QRFS_NORMAL, node.secondary_ip)),
492
    (_MakeField("tags", "Tags", constants.QFT_OTHER), NQ_CONFIG,
493
     lambda ctx, node: (constants.QRFS_NORMAL, list(node.GetTags()))),
494
    (_MakeField("master", "IsMaster", constants.QFT_BOOL), NQ_CONFIG,
495
     lambda ctx, node: (constants.QRFS_NORMAL, node.name == ctx.master_name)),
496
    (_MakeField("role", "Role", constants.QFT_TEXT), NQ_CONFIG,
497
     lambda ctx, node: (constants.QRFS_NORMAL,
498
                        _GetNodeRole(node, ctx.master_name))),
499
    (_MakeField("group", "Group", constants.QFT_TEXT), NQ_GROUP, _GetNodeGroup),
500
    (_MakeField("group.uuid", "GroupUUID", constants.QFT_TEXT),
501
     NQ_CONFIG, lambda ctx, node: (constants.QRFS_NORMAL, node.group)),
502
    (_MakeField("powered", "Powered", constants.QFT_BOOL), NQ_OOB,
503
      _GetNodePower),
504
    ]
505

    
506
  def _GetLength(getter):
507
    return lambda ctx, node: (constants.QRFS_NORMAL,
508
                              len(getter(ctx)[node.name]))
509

    
510
  def _GetList(getter):
511
    return lambda ctx, node: (constants.QRFS_NORMAL,
512
                              list(getter(ctx)[node.name]))
513

    
514
  # Add fields operating on instance lists
515
  for prefix, titleprefix, getter in \
516
      [("p", "Pri", operator.attrgetter("node_to_primary")),
517
       ("s", "Sec", operator.attrgetter("node_to_secondary"))]:
518
    fields.extend([
519
      (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(),
520
                  constants.QFT_NUMBER),
521
       NQ_INST, _GetLength(getter)),
522
      (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
523
                  constants.QFT_OTHER),
524
       NQ_INST, _GetList(getter)),
525
      ])
526

    
527
  # Add simple fields
528
  fields.extend([(_MakeField(name, title, kind), NQ_CONFIG, _GetItemAttr(name))
529
                 for (name, (title, kind)) in _NODE_SIMPLE_FIELDS.items()])
530

    
531
  # Add fields requiring live data
532
  fields.extend([
533
    (_MakeField(name, title, kind), NQ_LIVE,
534
     compat.partial(_GetLiveNodeField, nfield, kind))
535
    for (name, (title, kind, nfield)) in _NODE_LIVE_FIELDS.items()
536
    ])
537

    
538
  # Add timestamps
539
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
540

    
541
  return _PrepareFieldList(fields)
542

    
543

    
544
class InstanceQueryData:
545
  """Data container for instance data queries.
546

547
  """
548
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
549
               live_data):
550
    """Initializes this class.
551

552
    @param instances: List of instance objects
553
    @param cluster: Cluster object
554
    @type disk_usage: dict; instance name as key
555
    @param disk_usage: Per-instance disk usage
556
    @type offline_nodes: list of strings
557
    @param offline_nodes: List of offline nodes
558
    @type bad_nodes: list of strings
559
    @param bad_nodes: List of faulty nodes
560
    @type live_data: dict; instance name as key
561
    @param live_data: Per-instance live data
562

563
    """
564
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
565
           "Offline nodes not included in bad nodes"
566
    assert not (set(live_data.keys()) & set(bad_nodes)), \
567
           "Found live data for bad or offline nodes"
568

    
569
    self.instances = instances
570
    self.cluster = cluster
571
    self.disk_usage = disk_usage
572
    self.offline_nodes = offline_nodes
573
    self.bad_nodes = bad_nodes
574
    self.live_data = live_data
575

    
576
    # Used for individual rows
577
    self.inst_hvparams = None
578
    self.inst_beparams = None
579
    self.inst_nicparams = None
580

    
581
  def __iter__(self):
582
    """Iterate over all instances.
583

584
    This function has side-effects and only one instance of the resulting
585
    generator should be used at a time.
586

587
    """
588
    for inst in self.instances:
589
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
590
      self.inst_beparams = self.cluster.FillBE(inst)
591
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
592
                             for nic in inst.nics]
593

    
594
      yield inst
595

    
596

    
597
def _GetInstOperState(ctx, inst):
598
  """Get instance's operational status.
599

600
  @type ctx: L{InstanceQueryData}
601
  @type inst: L{objects.Instance}
602
  @param inst: Instance object
603

604
  """
605
  # Can't use QRFS_OFFLINE here as it would describe the instance to be offline
606
  # when we actually don't know due to missing data
607
  if inst.primary_node in ctx.bad_nodes:
608
    return (constants.QRFS_NODATA, None)
609
  else:
610
    return (constants.QRFS_NORMAL, bool(ctx.live_data.get(inst.name)))
611

    
612

    
613
def _GetInstLiveData(name):
614
  """Build function for retrieving live data.
615

616
  @type name: string
617
  @param name: Live data field name
618

619
  """
620
  def fn(ctx, inst):
621
    """Get live data for an instance.
622

623
    @type ctx: L{InstanceQueryData}
624
    @type inst: L{objects.Instance}
625
    @param inst: Instance object
626

627
    """
628
    if (inst.primary_node in ctx.bad_nodes or
629
        inst.primary_node in ctx.offline_nodes):
630
      # Can't use QRFS_OFFLINE here as it would describe the instance to be
631
      # offline when we actually don't know due to missing data
632
      return (constants.QRFS_NODATA, None)
633

    
634
    if inst.name in ctx.live_data:
635
      data = ctx.live_data[inst.name]
636
      if name in data:
637
        return (constants.QRFS_NORMAL, data[name])
638

    
639
    return (constants.QRFS_UNAVAIL, None)
640

    
641
  return fn
642

    
643

    
644
def _GetInstStatus(ctx, inst):
645
  """Get instance status.
646

647
  @type ctx: L{InstanceQueryData}
648
  @type inst: L{objects.Instance}
649
  @param inst: Instance object
650

651
  """
652
  if inst.primary_node in ctx.offline_nodes:
653
    return (constants.QRFS_NORMAL, "ERROR_nodeoffline")
654

    
655
  if inst.primary_node in ctx.bad_nodes:
656
    return (constants.QRFS_NORMAL, "ERROR_nodedown")
657

    
658
  if bool(ctx.live_data.get(inst.name)):
659
    if inst.admin_up:
660
      return (constants.QRFS_NORMAL, "running")
661
    else:
662
      return (constants.QRFS_NORMAL, "ERROR_up")
663

    
664
  if inst.admin_up:
665
    return (constants.QRFS_NORMAL, "ERROR_down")
666

    
667
  return (constants.QRFS_NORMAL, "ADMIN_down")
668

    
669

    
670
def _GetInstDiskSize(index):
671
  """Build function for retrieving disk size.
672

673
  @type index: int
674
  @param index: Disk index
675

676
  """
677
  def fn(_, inst):
678
    """Get size of a disk.
679

680
    @type inst: L{objects.Instance}
681
    @param inst: Instance object
682

683
    """
684
    try:
685
      return (constants.QRFS_NORMAL, inst.disks[index].size)
686
    except IndexError:
687
      return (constants.QRFS_UNAVAIL, None)
688

    
689
  return fn
690

    
691

    
692
def _GetInstNic(index, cb):
693
  """Build function for calling another function with an instance NIC.
694

695
  @type index: int
696
  @param index: NIC index
697
  @type cb: callable
698
  @param cb: Callback
699

700
  """
701
  def fn(ctx, inst):
702
    """Call helper function with instance NIC.
703

704
    @type ctx: L{InstanceQueryData}
705
    @type inst: L{objects.Instance}
706
    @param inst: Instance object
707

708
    """
709
    try:
710
      nic = inst.nics[index]
711
    except IndexError:
712
      return (constants.QRFS_UNAVAIL, None)
713

    
714
    return cb(ctx, index, nic)
715

    
716
  return fn
717

    
718

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

722
  @type ctx: L{InstanceQueryData}
723
  @type nic: L{objects.NIC}
724
  @param nic: NIC object
725

726
  """
727
  if nic.ip is None:
728
    return (constants.QRFS_UNAVAIL, None)
729
  else:
730
    return (constants.QRFS_NORMAL, nic.ip)
731

    
732

    
733
def _GetInstNicBridge(ctx, index, _):
734
  """Get a NIC's bridge.
735

736
  @type ctx: L{InstanceQueryData}
737
  @type index: int
738
  @param index: NIC index
739

740
  """
741
  assert len(ctx.inst_nicparams) >= index
742

    
743
  nicparams = ctx.inst_nicparams[index]
744

    
745
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
746
    return (constants.QRFS_NORMAL, nicparams[constants.NIC_LINK])
747
  else:
748
    return (constants.QRFS_UNAVAIL, None)
749

    
750

    
751
def _GetInstAllNicBridges(ctx, inst):
752
  """Get all network bridges for an instance.
753

754
  @type ctx: L{InstanceQueryData}
755
  @type inst: L{objects.Instance}
756
  @param inst: Instance object
757

758
  """
759
  assert len(ctx.inst_nicparams) == len(inst.nics)
760

    
761
  result = []
762

    
763
  for nicp in ctx.inst_nicparams:
764
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
765
      result.append(nicp[constants.NIC_LINK])
766
    else:
767
      result.append(None)
768

    
769
  assert len(result) == len(inst.nics)
770

    
771
  return (constants.QRFS_NORMAL, result)
772

    
773

    
774
def _GetInstNicParam(name):
775
  """Build function for retrieving a NIC parameter.
776

777
  @type name: string
778
  @param name: Parameter name
779

780
  """
781
  def fn(ctx, index, _):
782
    """Get a NIC's bridge.
783

784
    @type ctx: L{InstanceQueryData}
785
    @type inst: L{objects.Instance}
786
    @param inst: Instance object
787
    @type nic: L{objects.NIC}
788
    @param nic: NIC object
789

790
    """
791
    assert len(ctx.inst_nicparams) >= index
792
    return (constants.QRFS_NORMAL, ctx.inst_nicparams[index][name])
793

    
794
  return fn
795

    
796

    
797
def _GetInstanceNetworkFields():
798
  """Get instance fields involving network interfaces.
799

800
  @return: List of field definitions used as input for L{_PrepareFieldList}
801

802
  """
803
  nic_mac_fn = lambda ctx, _, nic: (constants.QRFS_NORMAL, nic.mac)
804
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
805
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
806

    
807
  fields = [
808
    # First NIC (legacy)
809
    (_MakeField("ip", "IP_address", constants.QFT_TEXT), IQ_CONFIG,
810
     _GetInstNic(0, _GetInstNicIp)),
811
    (_MakeField("mac", "MAC_address", constants.QFT_TEXT), IQ_CONFIG,
812
     _GetInstNic(0, nic_mac_fn)),
813
    (_MakeField("bridge", "Bridge", constants.QFT_TEXT), IQ_CONFIG,
814
     _GetInstNic(0, _GetInstNicBridge)),
815
    (_MakeField("nic_mode", "NIC_Mode", constants.QFT_TEXT), IQ_CONFIG,
816
     _GetInstNic(0, nic_mode_fn)),
817
    (_MakeField("nic_link", "NIC_Link", constants.QFT_TEXT), IQ_CONFIG,
818
     _GetInstNic(0, nic_link_fn)),
819

    
820
    # All NICs
821
    (_MakeField("nic.count", "NICs", constants.QFT_NUMBER), IQ_CONFIG,
822
     lambda ctx, inst: (constants.QRFS_NORMAL, len(inst.nics))),
823
    (_MakeField("nic.macs", "NIC_MACs", constants.QFT_OTHER), IQ_CONFIG,
824
     lambda ctx, inst: (constants.QRFS_NORMAL, [nic.mac for nic in inst.nics])),
825
    (_MakeField("nic.ips", "NIC_IPs", constants.QFT_OTHER), IQ_CONFIG,
826
     lambda ctx, inst: (constants.QRFS_NORMAL, [nic.ip for nic in inst.nics])),
827
    (_MakeField("nic.modes", "NIC_modes", constants.QFT_OTHER), IQ_CONFIG,
828
     lambda ctx, inst: (constants.QRFS_NORMAL,
829
                        [nicp[constants.NIC_MODE]
830
                         for nicp in ctx.inst_nicparams])),
831
    (_MakeField("nic.links", "NIC_links", constants.QFT_OTHER), IQ_CONFIG,
832
     lambda ctx, inst: (constants.QRFS_NORMAL,
833
                        [nicp[constants.NIC_LINK]
834
                         for nicp in ctx.inst_nicparams])),
835
    (_MakeField("nic.bridges", "NIC_bridges", constants.QFT_OTHER), IQ_CONFIG,
836
     _GetInstAllNicBridges),
837
    ]
838

    
839
  # NICs by number
840
  for i in range(constants.MAX_NICS):
841
    fields.extend([
842
      (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, constants.QFT_TEXT),
843
       IQ_CONFIG, _GetInstNic(i, _GetInstNicIp)),
844
      (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, constants.QFT_TEXT),
845
       IQ_CONFIG, _GetInstNic(i, nic_mac_fn)),
846
      (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, constants.QFT_TEXT),
847
       IQ_CONFIG, _GetInstNic(i, nic_mode_fn)),
848
      (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, constants.QFT_TEXT),
849
       IQ_CONFIG, _GetInstNic(i, nic_link_fn)),
850
      (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, constants.QFT_TEXT),
851
       IQ_CONFIG, _GetInstNic(i, _GetInstNicBridge)),
852
      ])
853

    
854
  return fields
855

    
856

    
857
def _GetInstDiskUsage(ctx, inst):
858
  """Get disk usage for an instance.
859

860
  @type ctx: L{InstanceQueryData}
861
  @type inst: L{objects.Instance}
862
  @param inst: Instance object
863

864
  """
865
  usage = ctx.disk_usage[inst.name]
866

    
867
  if usage is None:
868
    usage = 0
869

    
870
  return (constants.QRFS_NORMAL, usage)
871

    
872

    
873
def _GetInstanceDiskFields():
874
  """Get instance fields involving disks.
875

876
  @return: List of field definitions used as input for L{_PrepareFieldList}
877

878
  """
879
  fields = [
880
    (_MakeField("disk_usage", "DiskUsage", constants.QFT_UNIT), IQ_DISKUSAGE,
881
     _GetInstDiskUsage),
882
    (_MakeField("sda_size", "LegacyDisk/0", constants.QFT_UNIT), IQ_CONFIG,
883
     _GetInstDiskSize(0)),
884
    (_MakeField("sdb_size", "LegacyDisk/1", constants.QFT_UNIT), IQ_CONFIG,
885
     _GetInstDiskSize(1)),
886
    (_MakeField("disk.count", "Disks", constants.QFT_NUMBER), IQ_CONFIG,
887
     lambda ctx, inst: (constants.QRFS_NORMAL, len(inst.disks))),
888
    (_MakeField("disk.sizes", "Disk_sizes", constants.QFT_OTHER), IQ_CONFIG,
889
     lambda ctx, inst: (constants.QRFS_NORMAL,
890
                        [disk.size for disk in inst.disks])),
891
    ]
892

    
893
  # Disks by number
894
  fields.extend([
895
    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, constants.QFT_UNIT),
896
     IQ_CONFIG, _GetInstDiskSize(i))
897
    for i in range(constants.MAX_DISKS)
898
    ])
899

    
900
  return fields
901

    
902

    
903
def _GetInstanceParameterFields():
904
  """Get instance fields involving parameters.
905

906
  @return: List of field definitions used as input for L{_PrepareFieldList}
907

908
  """
909
  # TODO: Consider moving titles closer to constants
910
  be_title = {
911
    constants.BE_AUTO_BALANCE: "Auto_balance",
912
    constants.BE_MEMORY: "Configured_memory",
913
    constants.BE_VCPUS: "VCPUs",
914
    }
915

    
916
  hv_title = {
917
    constants.HV_ACPI: "ACPI",
918
    constants.HV_BOOT_ORDER: "Boot_order",
919
    constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
920
    constants.HV_DISK_TYPE: "Disk_type",
921
    constants.HV_INITRD_PATH: "Initrd_path",
922
    constants.HV_KERNEL_PATH: "Kernel_path",
923
    constants.HV_NIC_TYPE: "NIC_type",
924
    constants.HV_PAE: "PAE",
925
    constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
926
    }
927

    
928
  fields = [
929
    # Filled parameters
930
    (_MakeField("hvparams", "HypervisorParameters", constants.QFT_OTHER),
931
     IQ_CONFIG, lambda ctx, _: (constants.QRFS_NORMAL, ctx.inst_hvparams)),
932
    (_MakeField("beparams", "BackendParameters", constants.QFT_OTHER),
933
     IQ_CONFIG, lambda ctx, _: (constants.QRFS_NORMAL, ctx.inst_beparams)),
934
    (_MakeField("vcpus", "LegacyVCPUs", constants.QFT_NUMBER), IQ_CONFIG,
935
     lambda ctx, _: (constants.QRFS_NORMAL,
936
                     ctx.inst_beparams[constants.BE_VCPUS])),
937

    
938
    # Unfilled parameters
939
    (_MakeField("custom_hvparams", "CustomHypervisorParameters",
940
                constants.QFT_OTHER),
941
     IQ_CONFIG, lambda ctx, inst: (constants.QRFS_NORMAL, inst.hvparams)),
942
    (_MakeField("custom_beparams", "CustomBackendParameters",
943
                constants.QFT_OTHER),
944
     IQ_CONFIG, lambda ctx, inst: (constants.QRFS_NORMAL, inst.beparams)),
945
    (_MakeField("custom_nicparams", "CustomNicParameters",
946
                constants.QFT_OTHER),
947
     IQ_CONFIG, lambda ctx, inst: (constants.QRFS_NORMAL,
948
                                   [nic.nicparams for nic in inst.nics])),
949
    ]
950

    
951
  # HV params
952
  def _GetInstHvParam(name):
953
    return lambda ctx, _: (constants.QRFS_NORMAL,
954
                           ctx.inst_hvparams.get(name, None))
955

    
956
  fields.extend([
957
    # For now all hypervisor parameters are exported as QFT_OTHER
958
    (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
959
                constants.QFT_OTHER),
960
     IQ_CONFIG, _GetInstHvParam(name))
961
    for name in constants.HVS_PARAMETERS
962
    if name not in constants.HVC_GLOBALS
963
    ])
964

    
965
  # BE params
966
  def _GetInstBeParam(name):
967
    return lambda ctx, _: (constants.QRFS_NORMAL,
968
                           ctx.inst_beparams.get(name, None))
969

    
970
  fields.extend([
971
    # For now all backend parameters are exported as QFT_OTHER
972
    (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
973
                constants.QFT_OTHER),
974
     IQ_CONFIG, _GetInstBeParam(name))
975
    for name in constants.BES_PARAMETERS
976
    ])
977

    
978
  return fields
979

    
980

    
981
_INST_SIMPLE_FIELDS = {
982
  "disk_template": ("Disk_template", constants.QFT_TEXT),
983
  "hypervisor": ("Hypervisor", constants.QFT_TEXT),
984
  "name": ("Node", constants.QFT_TEXT),
985
  # Depending on the hypervisor, the port can be None
986
  "network_port": ("Network_port", constants.QFT_OTHER),
987
  "os": ("OS", constants.QFT_TEXT),
988
  "serial_no": ("SerialNo", constants.QFT_NUMBER),
989
  "uuid": ("UUID", constants.QFT_TEXT),
990
  }
991

    
992

    
993
def _BuildInstanceFields():
994
  """Builds list of fields for instance queries.
995

996
  """
997
  fields = [
998
    (_MakeField("pnode", "Primary_node", constants.QFT_TEXT), IQ_CONFIG,
999
     lambda ctx, inst: (constants.QRFS_NORMAL, inst.primary_node)),
1000
    (_MakeField("snodes", "Secondary_Nodes", constants.QFT_OTHER), IQ_CONFIG,
1001
     lambda ctx, inst: (constants.QRFS_NORMAL, list(inst.secondary_nodes))),
1002
    (_MakeField("admin_state", "Autostart", constants.QFT_BOOL), IQ_CONFIG,
1003
     lambda ctx, inst: (constants.QRFS_NORMAL, inst.admin_up)),
1004
    (_MakeField("tags", "Tags", constants.QFT_OTHER), IQ_CONFIG,
1005
     lambda ctx, inst: (constants.QRFS_NORMAL, list(inst.GetTags()))),
1006
    ]
1007

    
1008
  # Add simple fields
1009
  fields.extend([(_MakeField(name, title, kind), IQ_CONFIG, _GetItemAttr(name))
1010
                 for (name, (title, kind)) in _INST_SIMPLE_FIELDS.items()])
1011

    
1012
  # Fields requiring talking to the node
1013
  fields.extend([
1014
    (_MakeField("oper_state", "Running", constants.QFT_BOOL), IQ_LIVE,
1015
     _GetInstOperState),
1016
    (_MakeField("oper_ram", "RuntimeMemory", constants.QFT_UNIT), IQ_LIVE,
1017
     _GetInstLiveData("memory")),
1018
    (_MakeField("oper_vcpus", "RuntimeVCPUs", constants.QFT_NUMBER), IQ_LIVE,
1019
     _GetInstLiveData("vcpus")),
1020
    (_MakeField("status", "Status", constants.QFT_TEXT), IQ_LIVE,
1021
     _GetInstStatus),
1022
    ])
1023

    
1024
  fields.extend(_GetInstanceParameterFields())
1025
  fields.extend(_GetInstanceDiskFields())
1026
  fields.extend(_GetInstanceNetworkFields())
1027
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1028

    
1029
  return _PrepareFieldList(fields)
1030

    
1031

    
1032
class LockQueryData:
1033
  """Data container for lock data queries.
1034

1035
  """
1036
  def __init__(self, lockdata):
1037
    """Initializes this class.
1038

1039
    """
1040
    self.lockdata = lockdata
1041

    
1042
  def __iter__(self):
1043
    """Iterate over all locks.
1044

1045
    """
1046
    return iter(self.lockdata)
1047

    
1048

    
1049
def _GetLockOwners(_, data):
1050
  """Returns a sorted list of a lock's current owners.
1051

1052
  """
1053
  (_, _, owners, _) = data
1054

    
1055
  if owners:
1056
    owners = utils.NiceSort(owners)
1057

    
1058
  return (constants.QRFS_NORMAL, owners)
1059

    
1060

    
1061
def _GetLockPending(_, data):
1062
  """Returns a sorted list of a lock's pending acquires.
1063

1064
  """
1065
  (_, _, _, pending) = data
1066

    
1067
  if pending:
1068
    pending = [(mode, utils.NiceSort(names))
1069
               for (mode, names) in pending]
1070

    
1071
  return (constants.QRFS_NORMAL, pending)
1072

    
1073

    
1074
def _BuildLockFields():
1075
  """Builds list of fields for lock queries.
1076

1077
  """
1078
  return _PrepareFieldList([
1079
    (_MakeField("name", "Name", constants.QFT_TEXT), None,
1080
     lambda ctx, (name, mode, owners, pending): (constants.QRFS_NORMAL, name)),
1081
    (_MakeField("mode", "Mode", constants.QFT_OTHER), LQ_MODE,
1082
     lambda ctx, (name, mode, owners, pending): (constants.QRFS_NORMAL, mode)),
1083
    (_MakeField("owner", "Owner", constants.QFT_OTHER), LQ_OWNER,
1084
     _GetLockOwners),
1085
    (_MakeField("pending", "Pending", constants.QFT_OTHER), LQ_PENDING,
1086
     _GetLockPending),
1087
    ])
1088

    
1089

    
1090
class GroupQueryData:
1091
  """Data container for node group data queries.
1092

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

1097
    @param groups: List of node group objects
1098
    @type group_to_nodes: dict; group UUID as key
1099
    @param group_to_nodes: Per-group list of nodes
1100
    @type group_to_instances: dict; group UUID as key
1101
    @param group_to_instances: Per-group list of (primary) instances
1102

1103
    """
1104
    self.groups = groups
1105
    self.group_to_nodes = group_to_nodes
1106
    self.group_to_instances = group_to_instances
1107

    
1108
  def __iter__(self):
1109
    """Iterate over all node groups.
1110

1111
    """
1112
    return iter(self.groups)
1113

    
1114

    
1115
_GROUP_SIMPLE_FIELDS = {
1116
  "alloc_policy": ("AllocPolicy", constants.QFT_TEXT),
1117
  "name": ("Group", constants.QFT_TEXT),
1118
  "serial_no": ("SerialNo", constants.QFT_NUMBER),
1119
  "uuid": ("UUID", constants.QFT_TEXT),
1120
  }
1121

    
1122

    
1123
def _BuildGroupFields():
1124
  """Builds list of fields for node group queries.
1125

1126
  """
1127
  # Add simple fields
1128
  fields = [(_MakeField(name, title, kind), GQ_CONFIG, _GetItemAttr(name))
1129
            for (name, (title, kind)) in _GROUP_SIMPLE_FIELDS.items()]
1130

    
1131
  def _GetLength(getter):
1132
    return lambda ctx, group: (constants.QRFS_NORMAL,
1133
                               len(getter(ctx)[group.uuid]))
1134

    
1135
  def _GetSortedList(getter):
1136
    return lambda ctx, group: (constants.QRFS_NORMAL,
1137
                               utils.NiceSort(getter(ctx)[group.uuid]))
1138

    
1139
  group_to_nodes = operator.attrgetter("group_to_nodes")
1140
  group_to_instances = operator.attrgetter("group_to_instances")
1141

    
1142
  # Add fields for nodes
1143
  fields.extend([
1144
    (_MakeField("node_cnt", "Nodes", constants.QFT_NUMBER),
1145
     GQ_NODE, _GetLength(group_to_nodes)),
1146
    (_MakeField("node_list", "NodeList", constants.QFT_OTHER),
1147
     GQ_NODE, _GetSortedList(group_to_nodes)),
1148
    ])
1149

    
1150
  # Add fields for instances
1151
  fields.extend([
1152
    (_MakeField("pinst_cnt", "Instances", constants.QFT_NUMBER),
1153
     GQ_INST, _GetLength(group_to_instances)),
1154
    (_MakeField("pinst_list", "InstanceList", constants.QFT_OTHER),
1155
     GQ_INST, _GetSortedList(group_to_instances)),
1156
    ])
1157

    
1158
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1159

    
1160
  return _PrepareFieldList(fields)
1161

    
1162

    
1163
#: Fields available for node queries
1164
NODE_FIELDS = _BuildNodeFields()
1165

    
1166
#: Fields available for instance queries
1167
INSTANCE_FIELDS = _BuildInstanceFields()
1168

    
1169
#: Fields available for lock queries
1170
LOCK_FIELDS = _BuildLockFields()
1171

    
1172
#: Fields available for node group queries
1173
GROUP_FIELDS = _BuildGroupFields()
1174

    
1175
#: All available field lists
1176
ALL_FIELD_LISTS = [NODE_FIELDS, INSTANCE_FIELDS, LOCK_FIELDS, GROUP_FIELDS]