Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ 75c866c2

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

    
65
# Constants for requesting data from the caller/data provider. Each property
66
# collected/computed separately by the data provider should have its own to
67
# only collect the requested data and not more.
68

    
69
(NQ_CONFIG,
70
 NQ_INST,
71
 NQ_LIVE,
72
 NQ_GROUP,
73
 NQ_OOB) = range(1, 6)
74

    
75
(IQ_CONFIG,
76
 IQ_LIVE,
77
 IQ_DISKUSAGE) = range(100, 103)
78

    
79
(LQ_MODE,
80
 LQ_OWNER,
81
 LQ_PENDING) = range(10, 13)
82

    
83
(GQ_CONFIG,
84
 GQ_NODE,
85
 GQ_INST) = range(200, 203)
86

    
87

    
88
FIELD_NAME_RE = re.compile(r"^[a-z0-9/._]+$")
89
TITLE_RE = re.compile(r"^[^\s]+$")
90

    
91
#: Verification function for each field type
92
_VERIFY_FN = {
93
  constants.QFT_UNKNOWN: ht.TNone,
94
  constants.QFT_TEXT: ht.TString,
95
  constants.QFT_BOOL: ht.TBool,
96
  constants.QFT_NUMBER: ht.TInt,
97
  constants.QFT_UNIT: ht.TInt,
98
  constants.QFT_TIMESTAMP: ht.TOr(ht.TInt, ht.TFloat),
99
  constants.QFT_OTHER: lambda _: True,
100
  }
101

    
102

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

106
  """
107
  return (constants.QRFS_UNKNOWN, None)
108

    
109

    
110
def _GetQueryFields(fielddefs, selected):
111
  """Calculates the internal list of selected fields.
112

113
  Unknown fields are returned as L{constants.QFT_UNKNOWN}.
114

115
  @type fielddefs: dict
116
  @param fielddefs: Field definitions
117
  @type selected: list of strings
118
  @param selected: List of selected fields
119

120
  """
121
  result = []
122

    
123
  for name in selected:
124
    try:
125
      fdef = fielddefs[name]
126
    except KeyError:
127
      fdef = (_MakeField(name, name, constants.QFT_UNKNOWN),
128
              None, _GetUnknownField)
129

    
130
    assert len(fdef) == 3
131

    
132
    result.append(fdef)
133

    
134
  return result
135

    
136

    
137
def GetAllFields(fielddefs):
138
  """Extract L{objects.QueryFieldDefinition} from field definitions.
139

140
  @rtype: list of L{objects.QueryFieldDefinition}
141

142
  """
143
  return [fdef for (fdef, _, _) in fielddefs]
144

    
145

    
146
class Query:
147
  def __init__(self, fieldlist, selected):
148
    """Initializes this class.
149

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

157
    Users of this class can call L{RequestedData} before preparing the data
158
    container to determine what data is needed.
159

160
    @type fieldlist: dictionary
161
    @param fieldlist: Field definitions
162
    @type selected: list of strings
163
    @param selected: List of selected fields
164

165
    """
166
    self._fields = _GetQueryFields(fieldlist, selected)
167

    
168
  def RequestedData(self):
169
    """Gets requested kinds of data.
170

171
    @rtype: frozenset
172

173
    """
174
    return frozenset(datakind
175
                     for (_, datakind, _) in self._fields
176
                     if datakind is not None)
177

    
178
  def GetFields(self):
179
    """Returns the list of fields for this query.
180

181
    Includes unknown fields.
182

183
    @rtype: List of L{objects.QueryFieldDefinition}
184

185
    """
186
    return GetAllFields(self._fields)
187

    
188
  def Query(self, ctx):
189
    """Execute a query.
190

191
    @param ctx: Data container passed to field retrieval functions, must
192
      support iteration using C{__iter__}
193

194
    """
195
    result = [[fn(ctx, item) for (_, _, fn) in self._fields]
196
              for item in ctx]
197

    
198
    # Verify result
199
    if __debug__:
200
      for (idx, row) in enumerate(result):
201
        assert _VerifyResultRow(self._fields, row), \
202
               ("Inconsistent result for fields %s in row %s: %r" %
203
                (GetAllFields(self._fields), idx, row))
204

    
205
    return result
206

    
207
  def OldStyleQuery(self, ctx):
208
    """Query with "old" query result format.
209

210
    See L{Query.Query} for arguments.
211

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

    
221
    return [[value for (_, value) in row]
222
            for row in self.Query(ctx)]
223

    
224

    
225
def _VerifyResultRow(fields, row):
226
  """Verifies the contents of a query result row.
227

228
  @type fields: list
229
  @param fields: Field definitions for result
230
  @type row: list of tuples
231
  @param row: Row data
232

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

    
241

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

245
  Converts the list to a dictionary and does some verification.
246

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

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

    
259
  result = {}
260

    
261
  for field in fields:
262
    (fdef, _, fn) = field
263

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

    
271
    result[fdef.name] = field
272

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

    
277
  return result
278

    
279

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

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

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

    
290

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

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

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

    
309
  return objects.QueryFieldsResponse(fields=fdefs).ToDict()
310

    
311

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

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

319
  """
320
  return objects.QueryFieldDefinition(name=name, title=title, kind=kind)
321

    
322

    
323
def _GetNodeRole(node, master_name):
324
  """Determine node role.
325

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

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

    
343

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

347
  @param attr: Attribute name
348

349
  """
350
  getter = operator.attrgetter(attr)
351
  return lambda _, item: (constants.QRFS_NORMAL, getter(item))
352

    
353

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

357
  @type getter: callable
358
  @param getter: Function to retrieve timestamp attribute
359

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

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

    
372
  return fn
373

    
374

    
375
def _GetItemTimestampFields(datatype):
376
  """Returns common timestamp fields.
377

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

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

    
388

    
389
class NodeQueryData:
390
  """Data container for node data queries.
391

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

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

    
407
    # Used for individual rows
408
    self.curlive_data = None
409

    
410
  def __iter__(self):
411
    """Iterate over all nodes.
412

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

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

    
424

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

    
437

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

    
451

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

455
  @param cb: The callback to be called with the nodegroup
456

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

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

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

    
471
    return cb(ctx, node, ng)
472

    
473
  return fn
474

    
475

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

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

485
  """
486
  return (constants.QRFS_NORMAL, ng.name)
487

    
488

    
489
def _GetNodePower(ctx, node):
490
  """Returns the node powered state
491

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

496
  """
497
  if ctx.oob_support[node.name]:
498
    return (constants.QRFS_NORMAL, node.powered)
499

    
500
  return (constants.QRFS_UNAVAIL, None)
501

    
502

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

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

512
  """
513
  return (constants.QRFS_NORMAL, ctx.cluster.SimpleFillND(ng.FillND(node)))
514

    
515

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

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

525
  """
526
  if node.offline:
527
    return (constants.QRFS_OFFLINE, None)
528

    
529
  if not ctx.curlive_data:
530
    return (constants.QRFS_NODATA, None)
531

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

    
537
  if kind == constants.QFT_TEXT:
538
    return (constants.QRFS_NORMAL, value)
539

    
540
  assert kind in (constants.QFT_NUMBER, constants.QFT_UNIT)
541

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

    
550

    
551
def _BuildNodeFields():
552
  """Builds list of fields for node queries.
553

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

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

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

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

    
600
  # Add simple fields
601
  fields.extend([(_MakeField(name, title, kind), NQ_CONFIG, _GetItemAttr(name))
602
                 for (name, (title, kind)) in _NODE_SIMPLE_FIELDS.items()])
603

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

    
611
  # Add timestamps
612
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
613

    
614
  return _PrepareFieldList(fields)
615

    
616

    
617
class InstanceQueryData:
618
  """Data container for instance data queries.
619

620
  """
621
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
622
               live_data):
623
    """Initializes this class.
624

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

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

    
642
    self.instances = instances
643
    self.cluster = cluster
644
    self.disk_usage = disk_usage
645
    self.offline_nodes = offline_nodes
646
    self.bad_nodes = bad_nodes
647
    self.live_data = live_data
648

    
649
    # Used for individual rows
650
    self.inst_hvparams = None
651
    self.inst_beparams = None
652
    self.inst_nicparams = None
653

    
654
  def __iter__(self):
655
    """Iterate over all instances.
656

657
    This function has side-effects and only one instance of the resulting
658
    generator should be used at a time.
659

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

    
667
      yield inst
668

    
669

    
670
def _GetInstOperState(ctx, inst):
671
  """Get instance's operational status.
672

673
  @type ctx: L{InstanceQueryData}
674
  @type inst: L{objects.Instance}
675
  @param inst: Instance object
676

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

    
685

    
686
def _GetInstLiveData(name):
687
  """Build function for retrieving live data.
688

689
  @type name: string
690
  @param name: Live data field name
691

692
  """
693
  def fn(ctx, inst):
694
    """Get live data for an instance.
695

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

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

    
707
    if inst.name in ctx.live_data:
708
      data = ctx.live_data[inst.name]
709
      if name in data:
710
        return (constants.QRFS_NORMAL, data[name])
711

    
712
    return (constants.QRFS_UNAVAIL, None)
713

    
714
  return fn
715

    
716

    
717
def _GetInstStatus(ctx, inst):
718
  """Get instance status.
719

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

724
  """
725
  if inst.primary_node in ctx.offline_nodes:
726
    return (constants.QRFS_NORMAL, "ERROR_nodeoffline")
727

    
728
  if inst.primary_node in ctx.bad_nodes:
729
    return (constants.QRFS_NORMAL, "ERROR_nodedown")
730

    
731
  if bool(ctx.live_data.get(inst.name)):
732
    if inst.admin_up:
733
      return (constants.QRFS_NORMAL, "running")
734
    else:
735
      return (constants.QRFS_NORMAL, "ERROR_up")
736

    
737
  if inst.admin_up:
738
    return (constants.QRFS_NORMAL, "ERROR_down")
739

    
740
  return (constants.QRFS_NORMAL, "ADMIN_down")
741

    
742

    
743
def _GetInstDiskSize(index):
744
  """Build function for retrieving disk size.
745

746
  @type index: int
747
  @param index: Disk index
748

749
  """
750
  def fn(_, inst):
751
    """Get size of a disk.
752

753
    @type inst: L{objects.Instance}
754
    @param inst: Instance object
755

756
    """
757
    try:
758
      return (constants.QRFS_NORMAL, inst.disks[index].size)
759
    except IndexError:
760
      return (constants.QRFS_UNAVAIL, None)
761

    
762
  return fn
763

    
764

    
765
def _GetInstNic(index, cb):
766
  """Build function for calling another function with an instance NIC.
767

768
  @type index: int
769
  @param index: NIC index
770
  @type cb: callable
771
  @param cb: Callback
772

773
  """
774
  def fn(ctx, inst):
775
    """Call helper function with instance NIC.
776

777
    @type ctx: L{InstanceQueryData}
778
    @type inst: L{objects.Instance}
779
    @param inst: Instance object
780

781
    """
782
    try:
783
      nic = inst.nics[index]
784
    except IndexError:
785
      return (constants.QRFS_UNAVAIL, None)
786

    
787
    return cb(ctx, index, nic)
788

    
789
  return fn
790

    
791

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

795
  @type ctx: L{InstanceQueryData}
796
  @type nic: L{objects.NIC}
797
  @param nic: NIC object
798

799
  """
800
  if nic.ip is None:
801
    return (constants.QRFS_UNAVAIL, None)
802
  else:
803
    return (constants.QRFS_NORMAL, nic.ip)
804

    
805

    
806
def _GetInstNicBridge(ctx, index, _):
807
  """Get a NIC's bridge.
808

809
  @type ctx: L{InstanceQueryData}
810
  @type index: int
811
  @param index: NIC index
812

813
  """
814
  assert len(ctx.inst_nicparams) >= index
815

    
816
  nicparams = ctx.inst_nicparams[index]
817

    
818
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
819
    return (constants.QRFS_NORMAL, nicparams[constants.NIC_LINK])
820
  else:
821
    return (constants.QRFS_UNAVAIL, None)
822

    
823

    
824
def _GetInstAllNicBridges(ctx, inst):
825
  """Get all network bridges for an instance.
826

827
  @type ctx: L{InstanceQueryData}
828
  @type inst: L{objects.Instance}
829
  @param inst: Instance object
830

831
  """
832
  assert len(ctx.inst_nicparams) == len(inst.nics)
833

    
834
  result = []
835

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

    
842
  assert len(result) == len(inst.nics)
843

    
844
  return (constants.QRFS_NORMAL, result)
845

    
846

    
847
def _GetInstNicParam(name):
848
  """Build function for retrieving a NIC parameter.
849

850
  @type name: string
851
  @param name: Parameter name
852

853
  """
854
  def fn(ctx, index, _):
855
    """Get a NIC's bridge.
856

857
    @type ctx: L{InstanceQueryData}
858
    @type inst: L{objects.Instance}
859
    @param inst: Instance object
860
    @type nic: L{objects.NIC}
861
    @param nic: NIC object
862

863
    """
864
    assert len(ctx.inst_nicparams) >= index
865
    return (constants.QRFS_NORMAL, ctx.inst_nicparams[index][name])
866

    
867
  return fn
868

    
869

    
870
def _GetInstanceNetworkFields():
871
  """Get instance fields involving network interfaces.
872

873
  @return: List of field definitions used as input for L{_PrepareFieldList}
874

875
  """
876
  nic_mac_fn = lambda ctx, _, nic: (constants.QRFS_NORMAL, nic.mac)
877
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
878
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
879

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

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

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

    
927
  return fields
928

    
929

    
930
def _GetInstDiskUsage(ctx, inst):
931
  """Get disk usage for an instance.
932

933
  @type ctx: L{InstanceQueryData}
934
  @type inst: L{objects.Instance}
935
  @param inst: Instance object
936

937
  """
938
  usage = ctx.disk_usage[inst.name]
939

    
940
  if usage is None:
941
    usage = 0
942

    
943
  return (constants.QRFS_NORMAL, usage)
944

    
945

    
946
def _GetInstanceDiskFields():
947
  """Get instance fields involving disks.
948

949
  @return: List of field definitions used as input for L{_PrepareFieldList}
950

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

    
966
  # Disks by number
967
  fields.extend([
968
    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, constants.QFT_UNIT),
969
     IQ_CONFIG, _GetInstDiskSize(i))
970
    for i in range(constants.MAX_DISKS)
971
    ])
972

    
973
  return fields
974

    
975

    
976
def _GetInstanceParameterFields():
977
  """Get instance fields involving parameters.
978

979
  @return: List of field definitions used as input for L{_PrepareFieldList}
980

981
  """
982
  # TODO: Consider moving titles closer to constants
983
  be_title = {
984
    constants.BE_AUTO_BALANCE: "Auto_balance",
985
    constants.BE_MEMORY: "Configured_memory",
986
    constants.BE_VCPUS: "VCPUs",
987
    }
988

    
989
  hv_title = {
990
    constants.HV_ACPI: "ACPI",
991
    constants.HV_BOOT_ORDER: "Boot_order",
992
    constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
993
    constants.HV_DISK_TYPE: "Disk_type",
994
    constants.HV_INITRD_PATH: "Initrd_path",
995
    constants.HV_KERNEL_PATH: "Kernel_path",
996
    constants.HV_NIC_TYPE: "NIC_type",
997
    constants.HV_PAE: "PAE",
998
    constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
999
    }
1000

    
1001
  fields = [
1002
    # Filled parameters
1003
    (_MakeField("hvparams", "HypervisorParameters", constants.QFT_OTHER),
1004
     IQ_CONFIG, lambda ctx, _: (constants.QRFS_NORMAL, ctx.inst_hvparams)),
1005
    (_MakeField("beparams", "BackendParameters", constants.QFT_OTHER),
1006
     IQ_CONFIG, lambda ctx, _: (constants.QRFS_NORMAL, ctx.inst_beparams)),
1007
    (_MakeField("vcpus", "LegacyVCPUs", constants.QFT_NUMBER), IQ_CONFIG,
1008
     lambda ctx, _: (constants.QRFS_NORMAL,
1009
                     ctx.inst_beparams[constants.BE_VCPUS])),
1010

    
1011
    # Unfilled parameters
1012
    (_MakeField("custom_hvparams", "CustomHypervisorParameters",
1013
                constants.QFT_OTHER),
1014
     IQ_CONFIG, lambda ctx, inst: (constants.QRFS_NORMAL, inst.hvparams)),
1015
    (_MakeField("custom_beparams", "CustomBackendParameters",
1016
                constants.QFT_OTHER),
1017
     IQ_CONFIG, lambda ctx, inst: (constants.QRFS_NORMAL, inst.beparams)),
1018
    (_MakeField("custom_nicparams", "CustomNicParameters",
1019
                constants.QFT_OTHER),
1020
     IQ_CONFIG, lambda ctx, inst: (constants.QRFS_NORMAL,
1021
                                   [nic.nicparams for nic in inst.nics])),
1022
    ]
1023

    
1024
  # HV params
1025
  def _GetInstHvParam(name):
1026
    return lambda ctx, _: (constants.QRFS_NORMAL,
1027
                           ctx.inst_hvparams.get(name, None))
1028

    
1029
  fields.extend([
1030
    # For now all hypervisor parameters are exported as QFT_OTHER
1031
    (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1032
                constants.QFT_OTHER),
1033
     IQ_CONFIG, _GetInstHvParam(name))
1034
    for name in constants.HVS_PARAMETERS
1035
    if name not in constants.HVC_GLOBALS
1036
    ])
1037

    
1038
  # BE params
1039
  def _GetInstBeParam(name):
1040
    return lambda ctx, _: (constants.QRFS_NORMAL,
1041
                           ctx.inst_beparams.get(name, None))
1042

    
1043
  fields.extend([
1044
    # For now all backend parameters are exported as QFT_OTHER
1045
    (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1046
                constants.QFT_OTHER),
1047
     IQ_CONFIG, _GetInstBeParam(name))
1048
    for name in constants.BES_PARAMETERS
1049
    ])
1050

    
1051
  return fields
1052

    
1053

    
1054
_INST_SIMPLE_FIELDS = {
1055
  "disk_template": ("Disk_template", constants.QFT_TEXT),
1056
  "hypervisor": ("Hypervisor", constants.QFT_TEXT),
1057
  "name": ("Node", constants.QFT_TEXT),
1058
  # Depending on the hypervisor, the port can be None
1059
  "network_port": ("Network_port", constants.QFT_OTHER),
1060
  "os": ("OS", constants.QFT_TEXT),
1061
  "serial_no": ("SerialNo", constants.QFT_NUMBER),
1062
  "uuid": ("UUID", constants.QFT_TEXT),
1063
  }
1064

    
1065

    
1066
def _BuildInstanceFields():
1067
  """Builds list of fields for instance queries.
1068

1069
  """
1070
  fields = [
1071
    (_MakeField("pnode", "Primary_node", constants.QFT_TEXT), IQ_CONFIG,
1072
     lambda ctx, inst: (constants.QRFS_NORMAL, inst.primary_node)),
1073
    (_MakeField("snodes", "Secondary_Nodes", constants.QFT_OTHER), IQ_CONFIG,
1074
     lambda ctx, inst: (constants.QRFS_NORMAL, list(inst.secondary_nodes))),
1075
    (_MakeField("admin_state", "Autostart", constants.QFT_BOOL), IQ_CONFIG,
1076
     lambda ctx, inst: (constants.QRFS_NORMAL, inst.admin_up)),
1077
    (_MakeField("tags", "Tags", constants.QFT_OTHER), IQ_CONFIG,
1078
     lambda ctx, inst: (constants.QRFS_NORMAL, list(inst.GetTags()))),
1079
    ]
1080

    
1081
  # Add simple fields
1082
  fields.extend([(_MakeField(name, title, kind), IQ_CONFIG, _GetItemAttr(name))
1083
                 for (name, (title, kind)) in _INST_SIMPLE_FIELDS.items()])
1084

    
1085
  # Fields requiring talking to the node
1086
  fields.extend([
1087
    (_MakeField("oper_state", "Running", constants.QFT_BOOL), IQ_LIVE,
1088
     _GetInstOperState),
1089
    (_MakeField("oper_ram", "RuntimeMemory", constants.QFT_UNIT), IQ_LIVE,
1090
     _GetInstLiveData("memory")),
1091
    (_MakeField("oper_vcpus", "RuntimeVCPUs", constants.QFT_NUMBER), IQ_LIVE,
1092
     _GetInstLiveData("vcpus")),
1093
    (_MakeField("status", "Status", constants.QFT_TEXT), IQ_LIVE,
1094
     _GetInstStatus),
1095
    ])
1096

    
1097
  fields.extend(_GetInstanceParameterFields())
1098
  fields.extend(_GetInstanceDiskFields())
1099
  fields.extend(_GetInstanceNetworkFields())
1100
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1101

    
1102
  return _PrepareFieldList(fields)
1103

    
1104

    
1105
class LockQueryData:
1106
  """Data container for lock data queries.
1107

1108
  """
1109
  def __init__(self, lockdata):
1110
    """Initializes this class.
1111

1112
    """
1113
    self.lockdata = lockdata
1114

    
1115
  def __iter__(self):
1116
    """Iterate over all locks.
1117

1118
    """
1119
    return iter(self.lockdata)
1120

    
1121

    
1122
def _GetLockOwners(_, data):
1123
  """Returns a sorted list of a lock's current owners.
1124

1125
  """
1126
  (_, _, owners, _) = data
1127

    
1128
  if owners:
1129
    owners = utils.NiceSort(owners)
1130

    
1131
  return (constants.QRFS_NORMAL, owners)
1132

    
1133

    
1134
def _GetLockPending(_, data):
1135
  """Returns a sorted list of a lock's pending acquires.
1136

1137
  """
1138
  (_, _, _, pending) = data
1139

    
1140
  if pending:
1141
    pending = [(mode, utils.NiceSort(names))
1142
               for (mode, names) in pending]
1143

    
1144
  return (constants.QRFS_NORMAL, pending)
1145

    
1146

    
1147
def _BuildLockFields():
1148
  """Builds list of fields for lock queries.
1149

1150
  """
1151
  return _PrepareFieldList([
1152
    (_MakeField("name", "Name", constants.QFT_TEXT), None,
1153
     lambda ctx, (name, mode, owners, pending): (constants.QRFS_NORMAL, name)),
1154
    (_MakeField("mode", "Mode", constants.QFT_OTHER), LQ_MODE,
1155
     lambda ctx, (name, mode, owners, pending): (constants.QRFS_NORMAL, mode)),
1156
    (_MakeField("owner", "Owner", constants.QFT_OTHER), LQ_OWNER,
1157
     _GetLockOwners),
1158
    (_MakeField("pending", "Pending", constants.QFT_OTHER), LQ_PENDING,
1159
     _GetLockPending),
1160
    ])
1161

    
1162

    
1163
class GroupQueryData:
1164
  """Data container for node group data queries.
1165

1166
  """
1167
  def __init__(self, groups, group_to_nodes, group_to_instances):
1168
    """Initializes this class.
1169

1170
    @param groups: List of node group objects
1171
    @type group_to_nodes: dict; group UUID as key
1172
    @param group_to_nodes: Per-group list of nodes
1173
    @type group_to_instances: dict; group UUID as key
1174
    @param group_to_instances: Per-group list of (primary) instances
1175

1176
    """
1177
    self.groups = groups
1178
    self.group_to_nodes = group_to_nodes
1179
    self.group_to_instances = group_to_instances
1180

    
1181
  def __iter__(self):
1182
    """Iterate over all node groups.
1183

1184
    """
1185
    return iter(self.groups)
1186

    
1187

    
1188
_GROUP_SIMPLE_FIELDS = {
1189
  "alloc_policy": ("AllocPolicy", constants.QFT_TEXT),
1190
  "name": ("Group", constants.QFT_TEXT),
1191
  "serial_no": ("SerialNo", constants.QFT_NUMBER),
1192
  "uuid": ("UUID", constants.QFT_TEXT),
1193
  "ndparams": ("NDParams", constants.QFT_OTHER),
1194
  }
1195

    
1196

    
1197
def _BuildGroupFields():
1198
  """Builds list of fields for node group queries.
1199

1200
  """
1201
  # Add simple fields
1202
  fields = [(_MakeField(name, title, kind), GQ_CONFIG, _GetItemAttr(name))
1203
            for (name, (title, kind)) in _GROUP_SIMPLE_FIELDS.items()]
1204

    
1205
  def _GetLength(getter):
1206
    return lambda ctx, group: (constants.QRFS_NORMAL,
1207
                               len(getter(ctx)[group.uuid]))
1208

    
1209
  def _GetSortedList(getter):
1210
    return lambda ctx, group: (constants.QRFS_NORMAL,
1211
                               utils.NiceSort(getter(ctx)[group.uuid]))
1212

    
1213
  group_to_nodes = operator.attrgetter("group_to_nodes")
1214
  group_to_instances = operator.attrgetter("group_to_instances")
1215

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

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

    
1232
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1233

    
1234
  return _PrepareFieldList(fields)
1235

    
1236

    
1237
#: Fields available for node queries
1238
NODE_FIELDS = _BuildNodeFields()
1239

    
1240
#: Fields available for instance queries
1241
INSTANCE_FIELDS = _BuildInstanceFields()
1242

    
1243
#: Fields available for lock queries
1244
LOCK_FIELDS = _BuildLockFields()
1245

    
1246
#: Fields available for node group queries
1247
GROUP_FIELDS = _BuildGroupFields()
1248

    
1249
#: All available field lists
1250
ALL_FIELD_LISTS = [NODE_FIELDS, INSTANCE_FIELDS, LOCK_FIELDS, GROUP_FIELDS]