Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ 8e21cfc0

History | View | Annotate | Download (33.1 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) = range(1, 5)
40

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

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

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

    
53

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

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

    
68

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

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

    
75

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

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

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

86
  """
87
  result = []
88

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

    
96
    assert len(fdef) == 3
97

    
98
    result.append(fdef)
99

    
100
  return result
101

    
102

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

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

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

    
111

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

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

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

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

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

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

137
    @rtype: frozenset
138

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

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

147
    Includes unknown fields.
148

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

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

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

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

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

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

    
171
    return result
172

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

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

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

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

    
190

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

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

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

    
207

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

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

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

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

    
225
  result = {}
226

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

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

    
237
    result[fdef.name] = field
238

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

    
243
  return result
244

    
245

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

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

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

    
256

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

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

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

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

    
277

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

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

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

    
288

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

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

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

    
309

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

313
  @param attr: Attribute name
314

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

    
319

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

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

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

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

    
338
  return fn
339

    
340

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

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

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

    
354

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

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

363
    """
364
    self.nodes = nodes
365
    self.live_data = live_data
366
    self.master_name = master_name
367
    self.node_to_primary = node_to_primary
368
    self.node_to_secondary = node_to_secondary
369
    self.groups = groups
370

    
371
    # Used for individual rows
372
    self.curlive_data = None
373

    
374
  def __iter__(self):
375
    """Iterate over all nodes.
376

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

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

    
388

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

    
401

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

    
415

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

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

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

    
429
  return (constants.QRFS_NORMAL, ng.name)
430

    
431

    
432
def _GetLiveNodeField(field, kind, ctx, node):
433
  """Gets the value of a "live" field from L{NodeQueryData}.
434

435
  @param field: Live field name
436
  @param kind: Data kind, one of L{constants.QFT_ALL}
437
  @type ctx: L{NodeQueryData}
438
  @type node: L{objects.Node}
439
  @param node: Node object
440

441
  """
442
  if node.offline:
443
    return (constants.QRFS_OFFLINE, None)
444

    
445
  if not ctx.curlive_data:
446
    return (constants.QRFS_NODATA, None)
447

    
448
  try:
449
    value = ctx.curlive_data[field]
450
  except KeyError:
451
    return (constants.QRFS_UNAVAIL, None)
452

    
453
  if kind == constants.QFT_TEXT:
454
    return (constants.QRFS_NORMAL, value)
455

    
456
  assert kind in (constants.QFT_NUMBER, constants.QFT_UNIT)
457

    
458
  # Try to convert into number
459
  try:
460
    return (constants.QRFS_NORMAL, int(value))
461
  except (ValueError, TypeError):
462
    logging.exception("Failed to convert node field '%s' (value %r) to int",
463
                      value, field)
464
    return (constants.QRFS_UNAVAIL, None)
465

    
466

    
467
def _BuildNodeFields():
468
  """Builds list of fields for node queries.
469

470
  """
471
  fields = [
472
    (_MakeField("pip", "PrimaryIP", constants.QFT_TEXT), NQ_CONFIG,
473
     lambda ctx, node: (constants.QRFS_NORMAL, node.primary_ip)),
474
    (_MakeField("sip", "SecondaryIP", constants.QFT_TEXT), NQ_CONFIG,
475
     lambda ctx, node: (constants.QRFS_NORMAL, node.secondary_ip)),
476
    (_MakeField("tags", "Tags", constants.QFT_OTHER), NQ_CONFIG,
477
     lambda ctx, node: (constants.QRFS_NORMAL, list(node.GetTags()))),
478
    (_MakeField("master", "IsMaster", constants.QFT_BOOL), NQ_CONFIG,
479
     lambda ctx, node: (constants.QRFS_NORMAL, node.name == ctx.master_name)),
480
    (_MakeField("role", "Role", constants.QFT_TEXT), NQ_CONFIG,
481
     lambda ctx, node: (constants.QRFS_NORMAL,
482
                        _GetNodeRole(node, ctx.master_name))),
483
    (_MakeField("group", "Group", constants.QFT_TEXT), NQ_GROUP, _GetNodeGroup),
484
    (_MakeField("group.uuid", "GroupUUID", constants.QFT_TEXT),
485
     NQ_CONFIG, lambda ctx, node: (constants.QRFS_NORMAL, node.group)),
486
    ]
487

    
488
  def _GetLength(getter):
489
    return lambda ctx, node: (constants.QRFS_NORMAL,
490
                              len(getter(ctx)[node.name]))
491

    
492
  def _GetList(getter):
493
    return lambda ctx, node: (constants.QRFS_NORMAL,
494
                              list(getter(ctx)[node.name]))
495

    
496
  # Add fields operating on instance lists
497
  for prefix, titleprefix, getter in \
498
      [("p", "Pri", operator.attrgetter("node_to_primary")),
499
       ("s", "Sec", operator.attrgetter("node_to_secondary"))]:
500
    fields.extend([
501
      (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(),
502
                  constants.QFT_NUMBER),
503
       NQ_INST, _GetLength(getter)),
504
      (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
505
                  constants.QFT_OTHER),
506
       NQ_INST, _GetList(getter)),
507
      ])
508

    
509
  # Add simple fields
510
  fields.extend([(_MakeField(name, title, kind), NQ_CONFIG, _GetItemAttr(name))
511
                 for (name, (title, kind)) in _NODE_SIMPLE_FIELDS.items()])
512

    
513
  # Add fields requiring live data
514
  fields.extend([
515
    (_MakeField(name, title, kind), NQ_LIVE,
516
     compat.partial(_GetLiveNodeField, nfield, kind))
517
    for (name, (title, kind, nfield)) in _NODE_LIVE_FIELDS.items()
518
    ])
519

    
520
  # Add timestamps
521
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
522

    
523
  return _PrepareFieldList(fields)
524

    
525

    
526
class InstanceQueryData:
527
  """Data container for instance data queries.
528

529
  """
530
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
531
               live_data):
532
    """Initializes this class.
533

534
    @param instances: List of instance objects
535
    @param cluster: Cluster object
536
    @type disk_usage: dict; instance name as key
537
    @param disk_usage: Per-instance disk usage
538
    @type offline_nodes: list of strings
539
    @param offline_nodes: List of offline nodes
540
    @type bad_nodes: list of strings
541
    @param bad_nodes: List of faulty nodes
542
    @type live_data: dict; instance name as key
543
    @param live_data: Per-instance live data
544

545
    """
546
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
547
           "Offline nodes not included in bad nodes"
548
    assert not (set(live_data.keys()) & set(bad_nodes)), \
549
           "Found live data for bad or offline nodes"
550

    
551
    self.instances = instances
552
    self.cluster = cluster
553
    self.disk_usage = disk_usage
554
    self.offline_nodes = offline_nodes
555
    self.bad_nodes = bad_nodes
556
    self.live_data = live_data
557

    
558
    # Used for individual rows
559
    self.inst_hvparams = None
560
    self.inst_beparams = None
561
    self.inst_nicparams = None
562

    
563
  def __iter__(self):
564
    """Iterate over all instances.
565

566
    This function has side-effects and only one instance of the resulting
567
    generator should be used at a time.
568

569
    """
570
    for inst in self.instances:
571
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
572
      self.inst_beparams = self.cluster.FillBE(inst)
573
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
574
                             for nic in inst.nics]
575

    
576
      yield inst
577

    
578

    
579
def _GetInstOperState(ctx, inst):
580
  """Get instance's operational status.
581

582
  @type ctx: L{InstanceQueryData}
583
  @type inst: L{objects.Instance}
584
  @param inst: Instance object
585

586
  """
587
  # Can't use QRFS_OFFLINE here as it would describe the instance to be offline
588
  # when we actually don't know due to missing data
589
  if inst.primary_node in ctx.bad_nodes:
590
    return (constants.QRFS_NODATA, None)
591
  else:
592
    return (constants.QRFS_NORMAL, bool(ctx.live_data.get(inst.name)))
593

    
594

    
595
def _GetInstLiveData(name):
596
  """Build function for retrieving live data.
597

598
  @type name: string
599
  @param name: Live data field name
600

601
  """
602
  def fn(ctx, inst):
603
    """Get live data for an instance.
604

605
    @type ctx: L{InstanceQueryData}
606
    @type inst: L{objects.Instance}
607
    @param inst: Instance object
608

609
    """
610
    if (inst.primary_node in ctx.bad_nodes or
611
        inst.primary_node in ctx.offline_nodes):
612
      # Can't use QRFS_OFFLINE here as it would describe the instance to be
613
      # offline when we actually don't know due to missing data
614
      return (constants.QRFS_NODATA, None)
615

    
616
    if inst.name in ctx.live_data:
617
      data = ctx.live_data[inst.name]
618
      if name in data:
619
        return (constants.QRFS_NORMAL, data[name])
620

    
621
    return (constants.QRFS_UNAVAIL, None)
622

    
623
  return fn
624

    
625

    
626
def _GetInstStatus(ctx, inst):
627
  """Get instance status.
628

629
  @type ctx: L{InstanceQueryData}
630
  @type inst: L{objects.Instance}
631
  @param inst: Instance object
632

633
  """
634
  if inst.primary_node in ctx.offline_nodes:
635
    return (constants.QRFS_NORMAL, "ERROR_nodeoffline")
636

    
637
  if inst.primary_node in ctx.bad_nodes:
638
    return (constants.QRFS_NORMAL, "ERROR_nodedown")
639

    
640
  if bool(ctx.live_data.get(inst.name)):
641
    if inst.admin_up:
642
      return (constants.QRFS_NORMAL, "running")
643
    else:
644
      return (constants.QRFS_NORMAL, "ERROR_up")
645

    
646
  if inst.admin_up:
647
    return (constants.QRFS_NORMAL, "ERROR_down")
648

    
649
  return (constants.QRFS_NORMAL, "ADMIN_down")
650

    
651

    
652
def _GetInstDiskSize(index):
653
  """Build function for retrieving disk size.
654

655
  @type index: int
656
  @param index: Disk index
657

658
  """
659
  def fn(_, inst):
660
    """Get size of a disk.
661

662
    @type inst: L{objects.Instance}
663
    @param inst: Instance object
664

665
    """
666
    try:
667
      return (constants.QRFS_NORMAL, inst.disks[index].size)
668
    except IndexError:
669
      return (constants.QRFS_UNAVAIL, None)
670

    
671
  return fn
672

    
673

    
674
def _GetInstNic(index, cb):
675
  """Build function for calling another function with an instance NIC.
676

677
  @type index: int
678
  @param index: NIC index
679
  @type cb: callable
680
  @param cb: Callback
681

682
  """
683
  def fn(ctx, inst):
684
    """Call helper function with instance NIC.
685

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

690
    """
691
    try:
692
      nic = inst.nics[index]
693
    except IndexError:
694
      return (constants.QRFS_UNAVAIL, None)
695

    
696
    return cb(ctx, index, nic)
697

    
698
  return fn
699

    
700

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

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

708
  """
709
  if nic.ip is None:
710
    return (constants.QRFS_UNAVAIL, None)
711
  else:
712
    return (constants.QRFS_NORMAL, nic.ip)
713

    
714

    
715
def _GetInstNicBridge(ctx, index, _):
716
  """Get a NIC's bridge.
717

718
  @type ctx: L{InstanceQueryData}
719
  @type index: int
720
  @param index: NIC index
721

722
  """
723
  assert len(ctx.inst_nicparams) >= index
724

    
725
  nicparams = ctx.inst_nicparams[index]
726

    
727
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
728
    return (constants.QRFS_NORMAL, nicparams[constants.NIC_LINK])
729
  else:
730
    return (constants.QRFS_UNAVAIL, None)
731

    
732

    
733
def _GetInstAllNicBridges(ctx, inst):
734
  """Get all network bridges for an instance.
735

736
  @type ctx: L{InstanceQueryData}
737
  @type inst: L{objects.Instance}
738
  @param inst: Instance object
739

740
  """
741
  assert len(ctx.inst_nicparams) == len(inst.nics)
742

    
743
  result = []
744

    
745
  for nicp in ctx.inst_nicparams:
746
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
747
      result.append(nicp[constants.NIC_LINK])
748
    else:
749
      result.append(None)
750

    
751
  assert len(result) == len(inst.nics)
752

    
753
  return (constants.QRFS_NORMAL, result)
754

    
755

    
756
def _GetInstNicParam(name):
757
  """Build function for retrieving a NIC parameter.
758

759
  @type name: string
760
  @param name: Parameter name
761

762
  """
763
  def fn(ctx, index, _):
764
    """Get a NIC's bridge.
765

766
    @type ctx: L{InstanceQueryData}
767
    @type inst: L{objects.Instance}
768
    @param inst: Instance object
769
    @type nic: L{objects.NIC}
770
    @param nic: NIC object
771

772
    """
773
    assert len(ctx.inst_nicparams) >= index
774
    return (constants.QRFS_NORMAL, ctx.inst_nicparams[index][name])
775

    
776
  return fn
777

    
778

    
779
def _GetInstanceNetworkFields():
780
  """Get instance fields involving network interfaces.
781

782
  @return: List of field definitions used as input for L{_PrepareFieldList}
783

784
  """
785
  nic_mac_fn = lambda ctx, _, nic: (constants.QRFS_NORMAL, nic.mac)
786
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
787
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
788

    
789
  fields = [
790
    # First NIC (legacy)
791
    (_MakeField("ip", "IP_address", constants.QFT_TEXT), IQ_CONFIG,
792
     _GetInstNic(0, _GetInstNicIp)),
793
    (_MakeField("mac", "MAC_address", constants.QFT_TEXT), IQ_CONFIG,
794
     _GetInstNic(0, nic_mac_fn)),
795
    (_MakeField("bridge", "Bridge", constants.QFT_TEXT), IQ_CONFIG,
796
     _GetInstNic(0, _GetInstNicBridge)),
797
    (_MakeField("nic_mode", "NIC_Mode", constants.QFT_TEXT), IQ_CONFIG,
798
     _GetInstNic(0, nic_mode_fn)),
799
    (_MakeField("nic_link", "NIC_Link", constants.QFT_TEXT), IQ_CONFIG,
800
     _GetInstNic(0, nic_link_fn)),
801

    
802
    # All NICs
803
    (_MakeField("nic.count", "NICs", constants.QFT_NUMBER), IQ_CONFIG,
804
     lambda ctx, inst: (constants.QRFS_NORMAL, len(inst.nics))),
805
    (_MakeField("nic.macs", "NIC_MACs", constants.QFT_OTHER), IQ_CONFIG,
806
     lambda ctx, inst: (constants.QRFS_NORMAL, [nic.mac for nic in inst.nics])),
807
    (_MakeField("nic.ips", "NIC_IPs", constants.QFT_OTHER), IQ_CONFIG,
808
     lambda ctx, inst: (constants.QRFS_NORMAL, [nic.ip for nic in inst.nics])),
809
    (_MakeField("nic.modes", "NIC_modes", constants.QFT_OTHER), IQ_CONFIG,
810
     lambda ctx, inst: (constants.QRFS_NORMAL,
811
                        [nicp[constants.NIC_MODE]
812
                         for nicp in ctx.inst_nicparams])),
813
    (_MakeField("nic.links", "NIC_links", constants.QFT_OTHER), IQ_CONFIG,
814
     lambda ctx, inst: (constants.QRFS_NORMAL,
815
                        [nicp[constants.NIC_LINK]
816
                         for nicp in ctx.inst_nicparams])),
817
    (_MakeField("nic.bridges", "NIC_bridges", constants.QFT_OTHER), IQ_CONFIG,
818
     _GetInstAllNicBridges),
819
    ]
820

    
821
  # NICs by number
822
  for i in range(constants.MAX_NICS):
823
    fields.extend([
824
      (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, constants.QFT_TEXT),
825
       IQ_CONFIG, _GetInstNic(i, _GetInstNicIp)),
826
      (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, constants.QFT_TEXT),
827
       IQ_CONFIG, _GetInstNic(i, nic_mac_fn)),
828
      (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, constants.QFT_TEXT),
829
       IQ_CONFIG, _GetInstNic(i, nic_mode_fn)),
830
      (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, constants.QFT_TEXT),
831
       IQ_CONFIG, _GetInstNic(i, nic_link_fn)),
832
      (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, constants.QFT_TEXT),
833
       IQ_CONFIG, _GetInstNic(i, _GetInstNicBridge)),
834
      ])
835

    
836
  return fields
837

    
838

    
839
def _GetInstDiskUsage(ctx, inst):
840
  """Get disk usage for an instance.
841

842
  @type ctx: L{InstanceQueryData}
843
  @type inst: L{objects.Instance}
844
  @param inst: Instance object
845

846
  """
847
  usage = ctx.disk_usage[inst.name]
848

    
849
  if usage is None:
850
    usage = 0
851

    
852
  return (constants.QRFS_NORMAL, usage)
853

    
854

    
855
def _GetInstanceDiskFields():
856
  """Get instance fields involving disks.
857

858
  @return: List of field definitions used as input for L{_PrepareFieldList}
859

860
  """
861
  fields = [
862
    (_MakeField("disk_usage", "DiskUsage", constants.QFT_UNIT), IQ_DISKUSAGE,
863
     _GetInstDiskUsage),
864
    (_MakeField("sda_size", "LegacyDisk/0", constants.QFT_UNIT), IQ_CONFIG,
865
     _GetInstDiskSize(0)),
866
    (_MakeField("sdb_size", "LegacyDisk/1", constants.QFT_UNIT), IQ_CONFIG,
867
     _GetInstDiskSize(1)),
868
    (_MakeField("disk.count", "Disks", constants.QFT_NUMBER), IQ_CONFIG,
869
     lambda ctx, inst: (constants.QRFS_NORMAL, len(inst.disks))),
870
    (_MakeField("disk.sizes", "Disk_sizes", constants.QFT_OTHER), IQ_CONFIG,
871
     lambda ctx, inst: (constants.QRFS_NORMAL,
872
                        [disk.size for disk in inst.disks])),
873
    ]
874

    
875
  # Disks by number
876
  fields.extend([
877
    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, constants.QFT_UNIT),
878
     IQ_CONFIG, _GetInstDiskSize(i))
879
    for i in range(constants.MAX_DISKS)
880
    ])
881

    
882
  return fields
883

    
884

    
885
def _GetInstanceParameterFields():
886
  """Get instance fields involving parameters.
887

888
  @return: List of field definitions used as input for L{_PrepareFieldList}
889

890
  """
891
  # TODO: Consider moving titles closer to constants
892
  be_title = {
893
    constants.BE_AUTO_BALANCE: "Auto_balance",
894
    constants.BE_MEMORY: "Configured_memory",
895
    constants.BE_VCPUS: "VCPUs",
896
    }
897

    
898
  hv_title = {
899
    constants.HV_ACPI: "ACPI",
900
    constants.HV_BOOT_ORDER: "Boot_order",
901
    constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
902
    constants.HV_DISK_TYPE: "Disk_type",
903
    constants.HV_INITRD_PATH: "Initrd_path",
904
    constants.HV_KERNEL_PATH: "Kernel_path",
905
    constants.HV_NIC_TYPE: "NIC_type",
906
    constants.HV_PAE: "PAE",
907
    constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
908
    }
909

    
910
  fields = [
911
    # Filled parameters
912
    (_MakeField("hvparams", "HypervisorParameters", constants.QFT_OTHER),
913
     IQ_CONFIG, lambda ctx, _: (constants.QRFS_NORMAL, ctx.inst_hvparams)),
914
    (_MakeField("beparams", "BackendParameters", constants.QFT_OTHER),
915
     IQ_CONFIG, lambda ctx, _: (constants.QRFS_NORMAL, ctx.inst_beparams)),
916
    (_MakeField("vcpus", "LegacyVCPUs", constants.QFT_NUMBER), IQ_CONFIG,
917
     lambda ctx, _: (constants.QRFS_NORMAL,
918
                     ctx.inst_beparams[constants.BE_VCPUS])),
919

    
920
    # Unfilled parameters
921
    (_MakeField("custom_hvparams", "CustomHypervisorParameters",
922
                constants.QFT_OTHER),
923
     IQ_CONFIG, lambda ctx, inst: (constants.QRFS_NORMAL, inst.hvparams)),
924
    (_MakeField("custom_beparams", "CustomBackendParameters",
925
                constants.QFT_OTHER),
926
     IQ_CONFIG, lambda ctx, inst: (constants.QRFS_NORMAL, inst.beparams)),
927
    (_MakeField("custom_nicparams", "CustomNicParameters",
928
                constants.QFT_OTHER),
929
     IQ_CONFIG, lambda ctx, inst: (constants.QRFS_NORMAL,
930
                                   [nic.nicparams for nic in inst.nics])),
931
    ]
932

    
933
  # HV params
934
  def _GetInstHvParam(name):
935
    return lambda ctx, _: (constants.QRFS_NORMAL,
936
                           ctx.inst_hvparams.get(name, None))
937

    
938
  fields.extend([
939
    # For now all hypervisor parameters are exported as QFT_OTHER
940
    (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
941
                constants.QFT_OTHER),
942
     IQ_CONFIG, _GetInstHvParam(name))
943
    for name in constants.HVS_PARAMETERS
944
    if name not in constants.HVC_GLOBALS
945
    ])
946

    
947
  # BE params
948
  def _GetInstBeParam(name):
949
    return lambda ctx, _: (constants.QRFS_NORMAL,
950
                           ctx.inst_beparams.get(name, None))
951

    
952
  fields.extend([
953
    # For now all backend parameters are exported as QFT_OTHER
954
    (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
955
                constants.QFT_OTHER),
956
     IQ_CONFIG, _GetInstBeParam(name))
957
    for name in constants.BES_PARAMETERS
958
    ])
959

    
960
  return fields
961

    
962

    
963
_INST_SIMPLE_FIELDS = {
964
  "disk_template": ("Disk_template", constants.QFT_TEXT),
965
  "hypervisor": ("Hypervisor", constants.QFT_TEXT),
966
  "name": ("Node", constants.QFT_TEXT),
967
  # Depending on the hypervisor, the port can be None
968
  "network_port": ("Network_port", constants.QFT_OTHER),
969
  "os": ("OS", constants.QFT_TEXT),
970
  "serial_no": ("SerialNo", constants.QFT_NUMBER),
971
  "uuid": ("UUID", constants.QFT_TEXT),
972
  }
973

    
974

    
975
def _BuildInstanceFields():
976
  """Builds list of fields for instance queries.
977

978
  """
979
  fields = [
980
    (_MakeField("pnode", "Primary_node", constants.QFT_TEXT), IQ_CONFIG,
981
     lambda ctx, inst: (constants.QRFS_NORMAL, inst.primary_node)),
982
    (_MakeField("snodes", "Secondary_Nodes", constants.QFT_OTHER), IQ_CONFIG,
983
     lambda ctx, inst: (constants.QRFS_NORMAL, list(inst.secondary_nodes))),
984
    (_MakeField("admin_state", "Autostart", constants.QFT_BOOL), IQ_CONFIG,
985
     lambda ctx, inst: (constants.QRFS_NORMAL, inst.admin_up)),
986
    (_MakeField("tags", "Tags", constants.QFT_OTHER), IQ_CONFIG,
987
     lambda ctx, inst: (constants.QRFS_NORMAL, list(inst.GetTags()))),
988
    ]
989

    
990
  # Add simple fields
991
  fields.extend([(_MakeField(name, title, kind), IQ_CONFIG, _GetItemAttr(name))
992
                 for (name, (title, kind)) in _INST_SIMPLE_FIELDS.items()])
993

    
994
  # Fields requiring talking to the node
995
  fields.extend([
996
    (_MakeField("oper_state", "Running", constants.QFT_BOOL), IQ_LIVE,
997
     _GetInstOperState),
998
    (_MakeField("oper_ram", "RuntimeMemory", constants.QFT_UNIT), IQ_LIVE,
999
     _GetInstLiveData("memory")),
1000
    (_MakeField("oper_vcpus", "RuntimeVCPUs", constants.QFT_NUMBER), IQ_LIVE,
1001
     _GetInstLiveData("vcpus")),
1002
    (_MakeField("status", "Status", constants.QFT_TEXT), IQ_LIVE,
1003
     _GetInstStatus),
1004
    ])
1005

    
1006
  fields.extend(_GetInstanceParameterFields())
1007
  fields.extend(_GetInstanceDiskFields())
1008
  fields.extend(_GetInstanceNetworkFields())
1009
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1010

    
1011
  return _PrepareFieldList(fields)
1012

    
1013

    
1014
class LockQueryData:
1015
  """Data container for lock data queries.
1016

1017
  """
1018
  def __init__(self, lockdata):
1019
    """Initializes this class.
1020

1021
    """
1022
    self.lockdata = lockdata
1023

    
1024
  def __iter__(self):
1025
    """Iterate over all locks.
1026

1027
    """
1028
    return iter(self.lockdata)
1029

    
1030

    
1031
def _GetLockOwners(_, data):
1032
  """Returns a sorted list of a lock's current owners.
1033

1034
  """
1035
  (_, _, owners, _) = data
1036

    
1037
  if owners:
1038
    owners = utils.NiceSort(owners)
1039

    
1040
  return (constants.QRFS_NORMAL, owners)
1041

    
1042

    
1043
def _GetLockPending(_, data):
1044
  """Returns a sorted list of a lock's pending acquires.
1045

1046
  """
1047
  (_, _, _, pending) = data
1048

    
1049
  if pending:
1050
    pending = [(mode, utils.NiceSort(names))
1051
               for (mode, names) in pending]
1052

    
1053
  return (constants.QRFS_NORMAL, pending)
1054

    
1055

    
1056
def _BuildLockFields():
1057
  """Builds list of fields for lock queries.
1058

1059
  """
1060
  return _PrepareFieldList([
1061
    (_MakeField("name", "Name", constants.QFT_TEXT), None,
1062
     lambda ctx, (name, mode, owners, pending): (constants.QRFS_NORMAL, name)),
1063
    (_MakeField("mode", "Mode", constants.QFT_OTHER), LQ_MODE,
1064
     lambda ctx, (name, mode, owners, pending): (constants.QRFS_NORMAL, mode)),
1065
    (_MakeField("owner", "Owner", constants.QFT_OTHER), LQ_OWNER,
1066
     _GetLockOwners),
1067
    (_MakeField("pending", "Pending", constants.QFT_OTHER), LQ_PENDING,
1068
     _GetLockPending),
1069
    ])
1070

    
1071

    
1072
class GroupQueryData:
1073
  """Data container for node group data queries.
1074

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

1079
    @param groups: List of node group objects
1080
    @type group_to_nodes: dict; group UUID as key
1081
    @param group_to_nodes: Per-group list of nodes
1082
    @type group_to_instances: dict; group UUID as key
1083
    @param group_to_instances: Per-group list of (primary) instances
1084

1085
    """
1086
    self.groups = groups
1087
    self.group_to_nodes = group_to_nodes
1088
    self.group_to_instances = group_to_instances
1089

    
1090
  def __iter__(self):
1091
    """Iterate over all node groups.
1092

1093
    """
1094
    return iter(self.groups)
1095

    
1096

    
1097
_GROUP_SIMPLE_FIELDS = {
1098
  "alloc_policy": ("AllocPolicy", constants.QFT_TEXT),
1099
  "name": ("Group", constants.QFT_TEXT),
1100
  "serial_no": ("SerialNo", constants.QFT_NUMBER),
1101
  "uuid": ("UUID", constants.QFT_TEXT),
1102
  }
1103

    
1104

    
1105
def _BuildGroupFields():
1106
  """Builds list of fields for node group queries.
1107

1108
  """
1109
  # Add simple fields
1110
  fields = [(_MakeField(name, title, kind), GQ_CONFIG, _GetItemAttr(name))
1111
            for (name, (title, kind)) in _GROUP_SIMPLE_FIELDS.items()]
1112

    
1113
  def _GetLength(getter):
1114
    return lambda ctx, group: (constants.QRFS_NORMAL,
1115
                               len(getter(ctx)[group.uuid]))
1116

    
1117
  def _GetSortedList(getter):
1118
    return lambda ctx, group: (constants.QRFS_NORMAL,
1119
                               utils.NiceSort(getter(ctx)[group.uuid]))
1120

    
1121
  group_to_nodes = operator.attrgetter("group_to_nodes")
1122
  group_to_instances = operator.attrgetter("group_to_instances")
1123

    
1124
  # Add fields for nodes
1125
  fields.extend([
1126
    (_MakeField("node_cnt", "Nodes", constants.QFT_NUMBER),
1127
     GQ_NODE, _GetLength(group_to_nodes)),
1128
    (_MakeField("node_list", "NodeList", constants.QFT_OTHER),
1129
     GQ_NODE, _GetSortedList(group_to_nodes)),
1130
    ])
1131

    
1132
  # Add fields for instances
1133
  fields.extend([
1134
    (_MakeField("pinst_cnt", "Instances", constants.QFT_NUMBER),
1135
     GQ_INST, _GetLength(group_to_instances)),
1136
    (_MakeField("pinst_list", "InstanceList", constants.QFT_OTHER),
1137
     GQ_INST, _GetSortedList(group_to_instances)),
1138
    ])
1139

    
1140
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1141

    
1142
  return _PrepareFieldList(fields)
1143

    
1144

    
1145
#: Fields available for node queries
1146
NODE_FIELDS = _BuildNodeFields()
1147

    
1148
#: Fields available for instance queries
1149
INSTANCE_FIELDS = _BuildInstanceFields()
1150

    
1151
#: Fields available for lock queries
1152
LOCK_FIELDS = _BuildLockFields()
1153

    
1154
#: Fields available for node group queries
1155
GROUP_FIELDS = _BuildGroupFields()
1156

    
1157
#: All available field lists
1158
ALL_FIELD_LISTS = [NODE_FIELDS, INSTANCE_FIELDS, LOCK_FIELDS, GROUP_FIELDS]