Statistics
| Branch: | Tag: | Revision:

root / lib / query.py @ 679cba3a

History | View | Annotate | Download (38.6 kB)

1
#
2
#
3

    
4
# Copyright (C) 2010, 2011 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Module for query operations
23

24
How it works:
25

26
  - Add field definitions
27
    - See how L{NODE_FIELDS} is built
28
    - Each field gets:
29
      - Query field definition (L{objects.QueryFieldDefinition}, use
30
        L{_MakeField} for creating), containing:
31
          - Name, must be lowercase and match L{FIELD_NAME_RE}
32
          - Title for tables, must not contain whitespace and match
33
            L{TITLE_RE}
34
          - Value data type, e.g. L{constants.QFT_NUMBER}
35
      - Data request type, see e.g. C{NQ_*}
36
      - A retrieval function, see L{Query.__init__} for description
37
    - Pass list of fields through L{_PrepareFieldList} for preparation and
38
      checks
39
  - Instantiate L{Query} with prepared field list definition and selected fields
40
  - Call L{Query.RequestedData} to determine what data to collect/compute
41
  - Call L{Query.Query} or L{Query.OldStyleQuery} with collected data and use
42
    result
43
      - Data container must support iteration using C{__iter__}
44
      - Items are passed to retrieval functions and can have any format
45
  - Call L{Query.GetFields} to get list of definitions for selected fields
46

47
@attention: Retrieval functions must be idempotent. They can be called multiple
48
  times, in any order and any number of times. This is important to keep in
49
  mind for implementing filters in the future.
50

51
"""
52

    
53
import logging
54
import operator
55
import re
56

    
57
from ganeti import constants
58
from ganeti import errors
59
from ganeti import utils
60
from ganeti import compat
61
from ganeti import objects
62
from ganeti import ht
63

    
64
from ganeti.constants import (QFT_UNKNOWN, QFT_TEXT, QFT_BOOL, QFT_NUMBER,
65
                              QFT_UNIT, QFT_TIMESTAMP, QFT_OTHER,
66
                              RS_NORMAL, RS_UNKNOWN, RS_NODATA,
67
                              RS_UNAVAIL, RS_OFFLINE)
68

    
69

    
70
# Constants for requesting data from the caller/data provider. Each property
71
# collected/computed separately by the data provider should have its own to
72
# only collect the requested data and not more.
73

    
74
(NQ_CONFIG,
75
 NQ_INST,
76
 NQ_LIVE,
77
 NQ_GROUP,
78
 NQ_OOB) = range(1, 6)
79

    
80
(IQ_CONFIG,
81
 IQ_LIVE,
82
 IQ_DISKUSAGE,
83
 IQ_CONSOLE) = range(100, 104)
84

    
85
(LQ_MODE,
86
 LQ_OWNER,
87
 LQ_PENDING) = range(10, 13)
88

    
89
(GQ_CONFIG,
90
 GQ_NODE,
91
 GQ_INST) = range(200, 203)
92

    
93
(NETQ_CONFIG,
94
 NETQ_GROUP,
95
 NETQ_STATS,
96
 NETQ_INST) = range(300, 304)
97

    
98
FIELD_NAME_RE = re.compile(r"^[a-z0-9/._]+$")
99
TITLE_RE = re.compile(r"^[^\s]+$")
100

    
101
#: Verification function for each field type
102
_VERIFY_FN = {
103
  QFT_UNKNOWN: ht.TNone,
104
  QFT_TEXT: ht.TString,
105
  QFT_BOOL: ht.TBool,
106
  QFT_NUMBER: ht.TInt,
107
  QFT_UNIT: ht.TInt,
108
  QFT_TIMESTAMP: ht.TOr(ht.TInt, ht.TFloat),
109
  QFT_OTHER: lambda _: True,
110
  }
111

    
112
# Unique objects for special field statuses
113
_FS_UNKNOWN = object()
114
_FS_NODATA = object()
115
_FS_UNAVAIL = object()
116
_FS_OFFLINE = object()
117

    
118
#: VType to QFT mapping
119
_VTToQFT = {
120
  # TODO: fix validation of empty strings
121
  constants.VTYPE_STRING: QFT_OTHER, # since VTYPE_STRINGs can be empty
122
  constants.VTYPE_MAYBE_STRING: QFT_OTHER,
123
  constants.VTYPE_BOOL: QFT_BOOL,
124
  constants.VTYPE_SIZE: QFT_UNIT,
125
  constants.VTYPE_INT: QFT_NUMBER,
126
  }
127

    
128

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

132
  """
133
  return _FS_UNKNOWN
134

    
135

    
136
def _GetQueryFields(fielddefs, selected):
137
  """Calculates the internal list of selected fields.
138

139
  Unknown fields are returned as L{constants.QFT_UNKNOWN}.
140

141
  @type fielddefs: dict
142
  @param fielddefs: Field definitions
143
  @type selected: list of strings
144
  @param selected: List of selected fields
145

146
  """
147
  result = []
148

    
149
  for name in selected:
150
    try:
151
      fdef = fielddefs[name]
152
    except KeyError:
153
      fdef = (_MakeField(name, name, QFT_UNKNOWN), None, _GetUnknownField)
154

    
155
    assert len(fdef) == 3
156

    
157
    result.append(fdef)
158

    
159
  return result
160

    
161

    
162
def GetAllFields(fielddefs):
163
  """Extract L{objects.QueryFieldDefinition} from field definitions.
164

165
  @rtype: list of L{objects.QueryFieldDefinition}
166

167
  """
168
  return [fdef for (fdef, _, _) in fielddefs]
169

    
170

    
171
class Query:
172
  def __init__(self, fieldlist, selected):
173
    """Initializes this class.
174

175
    The field definition is a dictionary with the field's name as a key and a
176
    tuple containing, in order, the field definition object
177
    (L{objects.QueryFieldDefinition}, the data kind to help calling code
178
    collect data and a retrieval function. The retrieval function is called
179
    with two parameters, in order, the data container and the item in container
180
    (see L{Query.Query}).
181

182
    Users of this class can call L{RequestedData} before preparing the data
183
    container to determine what data is needed.
184

185
    @type fieldlist: dictionary
186
    @param fieldlist: Field definitions
187
    @type selected: list of strings
188
    @param selected: List of selected fields
189

190
    """
191
    self._fields = _GetQueryFields(fieldlist, selected)
192

    
193
  def RequestedData(self):
194
    """Gets requested kinds of data.
195

196
    @rtype: frozenset
197

198
    """
199
    return frozenset(datakind
200
                     for (_, datakind, _) in self._fields
201
                     if datakind is not None)
202

    
203
  def GetFields(self):
204
    """Returns the list of fields for this query.
205

206
    Includes unknown fields.
207

208
    @rtype: List of L{objects.QueryFieldDefinition}
209

210
    """
211
    return GetAllFields(self._fields)
212

    
213
  def Query(self, ctx):
214
    """Execute a query.
215

216
    @param ctx: Data container passed to field retrieval functions, must
217
      support iteration using C{__iter__}
218

219
    """
220
    result = [[_ProcessResult(fn(ctx, item)) for (_, _, fn) in self._fields]
221
              for item in ctx]
222

    
223
    # Verify result
224
    if __debug__:
225
      for row in result:
226
        _VerifyResultRow(self._fields, row)
227

    
228
    return result
229

    
230
  def OldStyleQuery(self, ctx):
231
    """Query with "old" query result format.
232

233
    See L{Query.Query} for arguments.
234

235
    """
236
    unknown = set(fdef.name
237
                  for (fdef, _, _) in self._fields if fdef.kind == QFT_UNKNOWN)
238
    if unknown:
239
      raise errors.OpPrereqError("Unknown output fields selected: %s" %
240
                                 (utils.CommaJoin(unknown), ),
241
                                 errors.ECODE_INVAL)
242

    
243
    return [[value for (_, value) in row]
244
            for row in self.Query(ctx)]
245

    
246

    
247
def _ProcessResult(value):
248
  """Converts result values into externally-visible ones.
249

250
  """
251
  if value is _FS_UNKNOWN:
252
    return (RS_UNKNOWN, None)
253
  elif value is _FS_NODATA:
254
    return (RS_NODATA, None)
255
  elif value is _FS_UNAVAIL:
256
    return (RS_UNAVAIL, None)
257
  elif value is _FS_OFFLINE:
258
    return (RS_OFFLINE, None)
259
  else:
260
    return (RS_NORMAL, value)
261

    
262

    
263
def _VerifyResultRow(fields, row):
264
  """Verifies the contents of a query result row.
265

266
  @type fields: list
267
  @param fields: Field definitions for result
268
  @type row: list of tuples
269
  @param row: Row data
270

271
  """
272
  assert len(row) == len(fields)
273
  errs = []
274
  for ((status, value), (fdef, _, _)) in zip(row, fields):
275
    if status == RS_NORMAL:
276
      if not _VERIFY_FN[fdef.kind](value):
277
        errs.append("normal field %s fails validation (value is %s)" %
278
                    (fdef.name, value))
279
    elif value is not None:
280
      errs.append("abnormal field %s has a non-None value" % fdef.name)
281
  assert not errs, ("Failed validation: %s in row %s" %
282
                    (utils.CommaJoin(errors), row))
283

    
284

    
285
def _PrepareFieldList(fields, aliases):
286
  """Prepares field list for use by L{Query}.
287

288
  Converts the list to a dictionary and does some verification.
289

290
  @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data
291
      kind, retrieval function)
292
  @param fields: List of fields, see L{Query.__init__} for a better
293
      description
294
  @type aliases: list of tuples; (alias, target)
295
  @param aliases: list of tuples containing aliases; for each
296
      alias/target pair, a duplicate will be created in the field list
297
  @rtype: dict
298
  @return: Field dictionary for L{Query}
299

300
  """
301
  if __debug__:
302
    duplicates = utils.FindDuplicates(fdef.title.lower()
303
                                      for (fdef, _, _) in fields)
304
    assert not duplicates, "Duplicate title(s) found: %r" % duplicates
305

    
306
  result = {}
307

    
308
  for field in fields:
309
    (fdef, _, fn) = field
310

    
311
    assert fdef.name and fdef.title, "Name and title are required"
312
    assert FIELD_NAME_RE.match(fdef.name)
313
    assert TITLE_RE.match(fdef.title)
314
    assert callable(fn)
315
    assert fdef.name not in result, \
316
           "Duplicate field name '%s' found" % fdef.name
317

    
318
    result[fdef.name] = field
319

    
320
  for alias, target in aliases:
321
    assert alias not in result, "Alias %s overrides an existing field" % alias
322
    assert target in result, "Missing target %s for alias %s" % (target, alias)
323
    (fdef, k, fn) = result[target]
324
    fdef = fdef.Copy()
325
    fdef.name = alias
326
    result[alias] = (fdef, k, fn)
327

    
328
  assert len(result) == len(fields) + len(aliases)
329
  assert compat.all(name == fdef.name
330
                    for (name, (fdef, _, _)) in result.items())
331

    
332
  return result
333

    
334

    
335
def GetQueryResponse(query, ctx):
336
  """Prepares the response for a query.
337

338
  @type query: L{Query}
339
  @param ctx: Data container, see L{Query.Query}
340

341
  """
342
  return objects.QueryResponse(data=query.Query(ctx),
343
                               fields=query.GetFields()).ToDict()
344

    
345

    
346
def QueryFields(fielddefs, selected):
347
  """Returns list of available fields.
348

349
  @type fielddefs: dict
350
  @param fielddefs: Field definitions
351
  @type selected: list of strings
352
  @param selected: List of selected fields
353
  @return: List of L{objects.QueryFieldDefinition}
354

355
  """
356
  if selected is None:
357
    # Client requests all fields, sort by name
358
    fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
359
                           key=operator.attrgetter("name"))
360
  else:
361
    # Keep order as requested by client
362
    fdefs = Query(fielddefs, selected).GetFields()
363

    
364
  return objects.QueryFieldsResponse(fields=fdefs).ToDict()
365

    
366

    
367
def _MakeField(name, title, kind):
368
  """Wrapper for creating L{objects.QueryFieldDefinition} instances.
369

370
  @param name: Field name as a regular expression
371
  @param title: Human-readable title
372
  @param kind: Field type
373

374
  """
375
  return objects.QueryFieldDefinition(name=name, title=title, kind=kind)
376

    
377

    
378
def _GetNodeRole(node, master_name):
379
  """Determine node role.
380

381
  @type node: L{objects.Node}
382
  @param node: Node object
383
  @type master_name: string
384
  @param master_name: Master node name
385

386
  """
387
  if node.name == master_name:
388
    return "M"
389
  elif node.master_candidate:
390
    return "C"
391
  elif node.drained:
392
    return "D"
393
  elif node.offline:
394
    return "O"
395
  else:
396
    return "R"
397

    
398

    
399
def _GetItemAttr(attr):
400
  """Returns a field function to return an attribute of the item.
401

402
  @param attr: Attribute name
403

404
  """
405
  getter = operator.attrgetter(attr)
406
  return lambda _, item: getter(item)
407

    
408

    
409
def _GetItemTimestamp(getter):
410
  """Returns function for getting timestamp of item.
411

412
  @type getter: callable
413
  @param getter: Function to retrieve timestamp attribute
414

415
  """
416
  def fn(_, item):
417
    """Returns a timestamp of item.
418

419
    """
420
    timestamp = getter(item)
421
    if timestamp is None:
422
      # Old configs might not have all timestamps
423
      return _FS_UNAVAIL
424
    else:
425
      return timestamp
426

    
427
  return fn
428

    
429

    
430
def _GetItemTimestampFields(datatype):
431
  """Returns common timestamp fields.
432

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

435
  """
436
  return [
437
    (_MakeField("ctime", "CTime", QFT_TIMESTAMP), datatype,
438
     _GetItemTimestamp(operator.attrgetter("ctime"))),
439
    (_MakeField("mtime", "MTime", QFT_TIMESTAMP), datatype,
440
     _GetItemTimestamp(operator.attrgetter("mtime"))),
441
    ]
442

    
443

    
444
class NodeQueryData:
445
  """Data container for node data queries.
446

447
  """
448
  def __init__(self, nodes, live_data, master_name, node_to_primary,
449
               node_to_secondary, groups, oob_support, cluster):
450
    """Initializes this class.
451

452
    """
453
    self.nodes = nodes
454
    self.live_data = live_data
455
    self.master_name = master_name
456
    self.node_to_primary = node_to_primary
457
    self.node_to_secondary = node_to_secondary
458
    self.groups = groups
459
    self.oob_support = oob_support
460
    self.cluster = cluster
461

    
462
    # Used for individual rows
463
    self.curlive_data = None
464

    
465
  def __iter__(self):
466
    """Iterate over all nodes.
467

468
    This function has side-effects and only one instance of the resulting
469
    generator should be used at a time.
470

471
    """
472
    for node in self.nodes:
473
      if self.live_data:
474
        self.curlive_data = self.live_data.get(node.name, None)
475
      else:
476
        self.curlive_data = None
477
      yield node
478

    
479

    
480
#: Fields that are direct attributes of an L{objects.Node} object
481
_NODE_SIMPLE_FIELDS = {
482
  "drained": ("Drained", QFT_BOOL),
483
  "master_candidate": ("MasterC", QFT_BOOL),
484
  "master_capable": ("MasterCapable", QFT_BOOL),
485
  "name": ("Node", QFT_TEXT),
486
  "offline": ("Offline", QFT_BOOL),
487
  "serial_no": ("SerialNo", QFT_NUMBER),
488
  "uuid": ("UUID", QFT_TEXT),
489
  "vm_capable": ("VMCapable", QFT_BOOL),
490
  }
491

    
492

    
493
#: Fields requiring talking to the node
494
# Note that none of these are available for non-vm_capable nodes
495
_NODE_LIVE_FIELDS = {
496
  "bootid": ("BootID", QFT_TEXT, "bootid"),
497
  "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes"),
498
  "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets"),
499
  "ctotal": ("CTotal", QFT_NUMBER, "cpu_total"),
500
  "dfree": ("DFree", QFT_UNIT, "vg_free"),
501
  "dtotal": ("DTotal", QFT_UNIT, "vg_size"),
502
  "mfree": ("MFree", QFT_UNIT, "memory_free"),
503
  "mnode": ("MNode", QFT_UNIT, "memory_dom0"),
504
  "mtotal": ("MTotal", QFT_UNIT, "memory_total"),
505
  }
506

    
507

    
508
def _GetGroup(cb):
509
  """Build function for calling another function with an node group.
510

511
  @param cb: The callback to be called with the nodegroup
512

513
  """
514
  def fn(ctx, node):
515
    """Get group data for a node.
516

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

521
    """
522
    ng = ctx.groups.get(node.group, None)
523
    if ng is None:
524
      # Nodes always have a group, or the configuration is corrupt
525
      return _FS_UNAVAIL
526

    
527
    return cb(ctx, node, ng)
528

    
529
  return fn
530

    
531

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

535
  @type ctx: L{NodeQueryData}
536
  @type node: L{objects.Node}
537
  @param node: Node object
538
  @type ng: L{objects.NodeGroup}
539
  @param ng: The node group this node belongs to
540

541
  """
542
  return ng.name
543

    
544

    
545
def _GetNodePower(ctx, node):
546
  """Returns the node powered state
547

548
  @type ctx: L{NodeQueryData}
549
  @type node: L{objects.Node}
550
  @param node: Node object
551

552
  """
553
  if ctx.oob_support[node.name]:
554
    return node.powered
555

    
556
  return _FS_UNAVAIL
557

    
558

    
559
def _GetNdParams(ctx, node, ng):
560
  """Returns the ndparams for this node.
561

562
  @type ctx: L{NodeQueryData}
563
  @type node: L{objects.Node}
564
  @param node: Node object
565
  @type ng: L{objects.NodeGroup}
566
  @param ng: The node group this node belongs to
567

568
  """
569
  return ctx.cluster.SimpleFillND(ng.FillND(node))
570

    
571

    
572
def _GetLiveNodeField(field, kind, ctx, node):
573
  """Gets the value of a "live" field from L{NodeQueryData}.
574

575
  @param field: Live field name
576
  @param kind: Data kind, one of L{constants.QFT_ALL}
577
  @type ctx: L{NodeQueryData}
578
  @type node: L{objects.Node}
579
  @param node: Node object
580

581
  """
582
  if node.offline:
583
    return _FS_OFFLINE
584

    
585
  if not node.vm_capable:
586
    return _FS_UNAVAIL
587

    
588
  if not ctx.curlive_data:
589
    return _FS_NODATA
590

    
591
  try:
592
    value = ctx.curlive_data[field]
593
  except KeyError:
594
    return _FS_UNAVAIL
595

    
596
  if kind == QFT_TEXT:
597
    return value
598

    
599
  assert kind in (QFT_NUMBER, QFT_UNIT)
600

    
601
  # Try to convert into number
602
  try:
603
    return int(value)
604
  except (ValueError, TypeError):
605
    logging.exception("Failed to convert node field '%s' (value %r) to int",
606
                      value, field)
607
    return _FS_UNAVAIL
608

    
609

    
610
def _BuildNodeFields():
611
  """Builds list of fields for node queries.
612

613
  """
614
  fields = [
615
    (_MakeField("pip", "PrimaryIP", QFT_TEXT), NQ_CONFIG,
616
     _GetItemAttr("primary_ip")),
617
    (_MakeField("sip", "SecondaryIP", QFT_TEXT), NQ_CONFIG,
618
     _GetItemAttr("secondary_ip")),
619
    (_MakeField("tags", "Tags", QFT_OTHER), NQ_CONFIG,
620
     lambda ctx, node: list(node.GetTags())),
621
    (_MakeField("master", "IsMaster", QFT_BOOL), NQ_CONFIG,
622
     lambda ctx, node: node.name == ctx.master_name),
623
    (_MakeField("role", "Role", QFT_TEXT), NQ_CONFIG,
624
     lambda ctx, node: _GetNodeRole(node, ctx.master_name)),
625
    (_MakeField("group", "Group", QFT_TEXT), NQ_GROUP,
626
     _GetGroup(_GetNodeGroup)),
627
    (_MakeField("group.uuid", "GroupUUID", QFT_TEXT),
628
     NQ_CONFIG, _GetItemAttr("group")),
629
    (_MakeField("powered", "Powered", QFT_BOOL), NQ_OOB, _GetNodePower),
630
    (_MakeField("ndparams", "NodeParameters", QFT_OTHER), NQ_GROUP,
631
      _GetGroup(_GetNdParams)),
632
    (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER),
633
      NQ_GROUP, _GetItemAttr("ndparams")),
634
    ]
635

    
636
  def _GetLength(getter):
637
    return lambda ctx, node: len(getter(ctx)[node.name])
638

    
639
  def _GetList(getter):
640
    return lambda ctx, node: list(getter(ctx)[node.name])
641

    
642
  # Add fields operating on instance lists
643
  for prefix, titleprefix, getter in \
644
      [("p", "Pri", operator.attrgetter("node_to_primary")),
645
       ("s", "Sec", operator.attrgetter("node_to_secondary"))]:
646
    fields.extend([
647
      (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER),
648
       NQ_INST, _GetLength(getter)),
649
      (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
650
                  QFT_OTHER),
651
       NQ_INST, _GetList(getter)),
652
      ])
653

    
654
  # Add simple fields
655
  fields.extend([(_MakeField(name, title, kind), NQ_CONFIG, _GetItemAttr(name))
656
                 for (name, (title, kind)) in _NODE_SIMPLE_FIELDS.items()])
657

    
658
  # Add fields requiring live data
659
  fields.extend([
660
    (_MakeField(name, title, kind), NQ_LIVE,
661
     compat.partial(_GetLiveNodeField, nfield, kind))
662
    for (name, (title, kind, nfield)) in _NODE_LIVE_FIELDS.items()
663
    ])
664

    
665
  # Add timestamps
666
  fields.extend(_GetItemTimestampFields(NQ_CONFIG))
667

    
668
  return _PrepareFieldList(fields, [])
669

    
670

    
671
class InstanceQueryData:
672
  """Data container for instance data queries.
673

674
  """
675
  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
676
               live_data, wrongnode_inst, console):
677
    """Initializes this class.
678

679
    @param instances: List of instance objects
680
    @param cluster: Cluster object
681
    @type disk_usage: dict; instance name as key
682
    @param disk_usage: Per-instance disk usage
683
    @type offline_nodes: list of strings
684
    @param offline_nodes: List of offline nodes
685
    @type bad_nodes: list of strings
686
    @param bad_nodes: List of faulty nodes
687
    @type live_data: dict; instance name as key
688
    @param live_data: Per-instance live data
689
    @type wrongnode_inst: set
690
    @param wrongnode_inst: Set of instances running on wrong node(s)
691
    @type console: dict; instance name as key
692
    @param console: Per-instance console information
693

694
    """
695
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
696
           "Offline nodes not included in bad nodes"
697
    assert not (set(live_data.keys()) & set(bad_nodes)), \
698
           "Found live data for bad or offline nodes"
699

    
700
    self.instances = instances
701
    self.cluster = cluster
702
    self.disk_usage = disk_usage
703
    self.offline_nodes = offline_nodes
704
    self.bad_nodes = bad_nodes
705
    self.live_data = live_data
706
    self.wrongnode_inst = wrongnode_inst
707
    self.console = console
708

    
709
    # Used for individual rows
710
    self.inst_hvparams = None
711
    self.inst_beparams = None
712
    self.inst_nicparams = None
713

    
714
  def __iter__(self):
715
    """Iterate over all instances.
716

717
    This function has side-effects and only one instance of the resulting
718
    generator should be used at a time.
719

720
    """
721
    for inst in self.instances:
722
      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
723
      self.inst_beparams = self.cluster.FillBE(inst)
724
      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
725
                             for nic in inst.nics]
726

    
727
      yield inst
728

    
729

    
730
def _GetInstOperState(ctx, inst):
731
  """Get instance's operational status.
732

733
  @type ctx: L{InstanceQueryData}
734
  @type inst: L{objects.Instance}
735
  @param inst: Instance object
736

737
  """
738
  # Can't use RS_OFFLINE here as it would describe the instance to
739
  # be offline when we actually don't know due to missing data
740
  if inst.primary_node in ctx.bad_nodes:
741
    return _FS_NODATA
742
  else:
743
    return bool(ctx.live_data.get(inst.name))
744

    
745

    
746
def _GetInstLiveData(name):
747
  """Build function for retrieving live data.
748

749
  @type name: string
750
  @param name: Live data field name
751

752
  """
753
  def fn(ctx, inst):
754
    """Get live data for an instance.
755

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

760
    """
761
    if (inst.primary_node in ctx.bad_nodes or
762
        inst.primary_node in ctx.offline_nodes):
763
      # Can't use RS_OFFLINE here as it would describe the instance to be
764
      # offline when we actually don't know due to missing data
765
      return _FS_NODATA
766

    
767
    if inst.name in ctx.live_data:
768
      data = ctx.live_data[inst.name]
769
      if name in data:
770
        return data[name]
771

    
772
    return _FS_UNAVAIL
773

    
774
  return fn
775

    
776

    
777
def _GetInstStatus(ctx, inst):
778
  """Get instance status.
779

780
  @type ctx: L{InstanceQueryData}
781
  @type inst: L{objects.Instance}
782
  @param inst: Instance object
783

784
  """
785
  if inst.primary_node in ctx.offline_nodes:
786
    return "ERROR_nodeoffline"
787

    
788
  if inst.primary_node in ctx.bad_nodes:
789
    return "ERROR_nodedown"
790

    
791
  if bool(ctx.live_data.get(inst.name)):
792
    if inst.name in ctx.wrongnode_inst:
793
      return "ERROR_wrongnode"
794
    elif inst.admin_up:
795
      return "running"
796
    else:
797
      return "ERROR_up"
798

    
799
  if inst.admin_up:
800
    return "ERROR_down"
801

    
802
  return "ADMIN_down"
803

    
804

    
805
def _GetInstDiskSize(index):
806
  """Build function for retrieving disk size.
807

808
  @type index: int
809
  @param index: Disk index
810

811
  """
812
  def fn(_, inst):
813
    """Get size of a disk.
814

815
    @type inst: L{objects.Instance}
816
    @param inst: Instance object
817

818
    """
819
    try:
820
      return inst.disks[index].size
821
    except IndexError:
822
      return _FS_UNAVAIL
823

    
824
  return fn
825

    
826

    
827
def _GetInstNic(index, cb):
828
  """Build function for calling another function with an instance NIC.
829

830
  @type index: int
831
  @param index: NIC index
832
  @type cb: callable
833
  @param cb: Callback
834

835
  """
836
  def fn(ctx, inst):
837
    """Call helper function with instance NIC.
838

839
    @type ctx: L{InstanceQueryData}
840
    @type inst: L{objects.Instance}
841
    @param inst: Instance object
842

843
    """
844
    try:
845
      nic = inst.nics[index]
846
    except IndexError:
847
      return _FS_UNAVAIL
848

    
849
    return cb(ctx, index, nic)
850

    
851
  return fn
852

    
853

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

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

861
  """
862
  if nic.ip is None:
863
    return _FS_UNAVAIL
864
  else:
865
    return nic.ip
866

    
867

    
868
def _GetInstNicBridge(ctx, index, _):
869
  """Get a NIC's bridge.
870

871
  @type ctx: L{InstanceQueryData}
872
  @type index: int
873
  @param index: NIC index
874

875
  """
876
  assert len(ctx.inst_nicparams) >= index
877

    
878
  nicparams = ctx.inst_nicparams[index]
879

    
880
  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
881
    return nicparams[constants.NIC_LINK]
882
  else:
883
    return _FS_UNAVAIL
884

    
885

    
886
def _GetInstAllNicBridges(ctx, inst):
887
  """Get all network bridges for an instance.
888

889
  @type ctx: L{InstanceQueryData}
890
  @type inst: L{objects.Instance}
891
  @param inst: Instance object
892

893
  """
894
  assert len(ctx.inst_nicparams) == len(inst.nics)
895

    
896
  result = []
897

    
898
  for nicp in ctx.inst_nicparams:
899
    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
900
      result.append(nicp[constants.NIC_LINK])
901
    else:
902
      result.append(None)
903

    
904
  assert len(result) == len(inst.nics)
905

    
906
  return result
907

    
908

    
909
def _GetInstNicParam(name):
910
  """Build function for retrieving a NIC parameter.
911

912
  @type name: string
913
  @param name: Parameter name
914

915
  """
916
  def fn(ctx, index, _):
917
    """Get a NIC's bridge.
918

919
    @type ctx: L{InstanceQueryData}
920
    @type inst: L{objects.Instance}
921
    @param inst: Instance object
922
    @type nic: L{objects.NIC}
923
    @param nic: NIC object
924

925
    """
926
    assert len(ctx.inst_nicparams) >= index
927
    return ctx.inst_nicparams[index][name]
928

    
929
  return fn
930

    
931

    
932
def _GetInstanceNetworkFields():
933
  """Get instance fields involving network interfaces.
934

935
  @return: List of field definitions used as input for L{_PrepareFieldList}
936

937
  """
938
  nic_mac_fn = lambda ctx, _, nic: nic.mac
939
  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
940
  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
941

    
942
  fields = [
943
    # First NIC (legacy)
944
    (_MakeField("ip", "IP_address", QFT_TEXT), IQ_CONFIG,
945
     _GetInstNic(0, _GetInstNicIp)),
946
    (_MakeField("mac", "MAC_address", QFT_TEXT), IQ_CONFIG,
947
     _GetInstNic(0, nic_mac_fn)),
948
    (_MakeField("bridge", "Bridge", QFT_TEXT), IQ_CONFIG,
949
     _GetInstNic(0, _GetInstNicBridge)),
950
    (_MakeField("nic_mode", "NIC_Mode", QFT_TEXT), IQ_CONFIG,
951
     _GetInstNic(0, nic_mode_fn)),
952
    (_MakeField("nic_link", "NIC_Link", QFT_TEXT), IQ_CONFIG,
953
     _GetInstNic(0, nic_link_fn)),
954

    
955
    # All NICs
956
    (_MakeField("nic.count", "NICs", QFT_NUMBER), IQ_CONFIG,
957
     lambda ctx, inst: len(inst.nics)),
958
    (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER), IQ_CONFIG,
959
     lambda ctx, inst: [nic.mac for nic in inst.nics]),
960
    (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER), IQ_CONFIG,
961
     lambda ctx, inst: [nic.ip for nic in inst.nics]),
962
    (_MakeField("nic.modes", "NIC_modes", QFT_OTHER), IQ_CONFIG,
963
     lambda ctx, inst: [nicp[constants.NIC_MODE]
964
                        for nicp in ctx.inst_nicparams]),
965
    (_MakeField("nic.links", "NIC_links", QFT_OTHER), IQ_CONFIG,
966
     lambda ctx, inst: [nicp[constants.NIC_LINK]
967
                        for nicp in ctx.inst_nicparams]),
968
    (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER), IQ_CONFIG,
969
     _GetInstAllNicBridges),
970
    ]
971

    
972
  # NICs by number
973
  for i in range(constants.MAX_NICS):
974
    fields.extend([
975
      (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT),
976
       IQ_CONFIG, _GetInstNic(i, _GetInstNicIp)),
977
      (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT),
978
       IQ_CONFIG, _GetInstNic(i, nic_mac_fn)),
979
      (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT),
980
       IQ_CONFIG, _GetInstNic(i, nic_mode_fn)),
981
      (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT),
982
       IQ_CONFIG, _GetInstNic(i, nic_link_fn)),
983
      (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT),
984
       IQ_CONFIG, _GetInstNic(i, _GetInstNicBridge)),
985
      ])
986

    
987
  return fields
988

    
989

    
990
def _GetInstDiskUsage(ctx, inst):
991
  """Get disk usage for an instance.
992

993
  @type ctx: L{InstanceQueryData}
994
  @type inst: L{objects.Instance}
995
  @param inst: Instance object
996

997
  """
998
  usage = ctx.disk_usage[inst.name]
999

    
1000
  if usage is None:
1001
    usage = 0
1002

    
1003
  return usage
1004

    
1005

    
1006
def _GetInstanceConsole(ctx, inst):
1007
  """Get console information for instance.
1008

1009
  @type ctx: L{InstanceQueryData}
1010
  @type inst: L{objects.Instance}
1011
  @param inst: Instance object
1012

1013
  """
1014
  consinfo = ctx.console[inst.name]
1015

    
1016
  if consinfo is None:
1017
    return _FS_UNAVAIL
1018

    
1019
  return consinfo
1020

    
1021

    
1022
def _GetInstanceDiskFields():
1023
  """Get instance fields involving disks.
1024

1025
  @return: List of field definitions used as input for L{_PrepareFieldList}
1026

1027
  """
1028
  fields = [
1029
    (_MakeField("disk_usage", "DiskUsage", QFT_UNIT), IQ_DISKUSAGE,
1030
     _GetInstDiskUsage),
1031
    (_MakeField("disk.count", "Disks", QFT_NUMBER), IQ_CONFIG,
1032
     lambda ctx, inst: len(inst.disks)),
1033
    (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER), IQ_CONFIG,
1034
     lambda ctx, inst: [disk.size for disk in inst.disks]),
1035
    ]
1036

    
1037
  # Disks by number
1038
  fields.extend([
1039
    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT),
1040
     IQ_CONFIG, _GetInstDiskSize(i))
1041
    for i in range(constants.MAX_DISKS)
1042
    ])
1043

    
1044
  return fields
1045

    
1046

    
1047
def _GetInstanceParameterFields():
1048
  """Get instance fields involving parameters.
1049

1050
  @return: List of field definitions used as input for L{_PrepareFieldList}
1051

1052
  """
1053
  # TODO: Consider moving titles closer to constants
1054
  be_title = {
1055
    constants.BE_AUTO_BALANCE: "Auto_balance",
1056
    constants.BE_MEMORY: "ConfigMemory",
1057
    constants.BE_VCPUS: "ConfigVCPUs",
1058
    }
1059

    
1060
  hv_title = {
1061
    constants.HV_ACPI: "ACPI",
1062
    constants.HV_BOOT_ORDER: "Boot_order",
1063
    constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1064
    constants.HV_DISK_TYPE: "Disk_type",
1065
    constants.HV_INITRD_PATH: "Initrd_path",
1066
    constants.HV_KERNEL_PATH: "Kernel_path",
1067
    constants.HV_NIC_TYPE: "NIC_type",
1068
    constants.HV_PAE: "PAE",
1069
    constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1070
    }
1071

    
1072
  fields = [
1073
    # Filled parameters
1074
    (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER),
1075
     IQ_CONFIG, lambda ctx, _: ctx.inst_hvparams),
1076
    (_MakeField("beparams", "BackendParameters", QFT_OTHER),
1077
     IQ_CONFIG, lambda ctx, _: ctx.inst_beparams),
1078

    
1079
    # Unfilled parameters
1080
    (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER),
1081
     IQ_CONFIG, _GetItemAttr("hvparams")),
1082
    (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER),
1083
     IQ_CONFIG, _GetItemAttr("beparams")),
1084
    (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER),
1085
     IQ_CONFIG, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1086
    ]
1087

    
1088
  # HV params
1089
  def _GetInstHvParam(name):
1090
    return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1091

    
1092
  fields.extend([
1093
    (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1094
                _VTToQFT[kind]),
1095
     IQ_CONFIG, _GetInstHvParam(name))
1096
    for name, kind in constants.HVS_PARAMETER_TYPES.items()
1097
    if name not in constants.HVC_GLOBALS
1098
    ])
1099

    
1100
  # BE params
1101
  def _GetInstBeParam(name):
1102
    return lambda ctx, _: ctx.inst_beparams.get(name, None)
1103

    
1104
  fields.extend([
1105
    (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1106
                _VTToQFT[kind]), IQ_CONFIG,
1107
     _GetInstBeParam(name))
1108
    for name, kind in constants.BES_PARAMETER_TYPES.items()
1109
    ])
1110

    
1111
  return fields
1112

    
1113

    
1114
_INST_SIMPLE_FIELDS = {
1115
  "disk_template": ("Disk_template", QFT_TEXT),
1116
  "hypervisor": ("Hypervisor", QFT_TEXT),
1117
  "name": ("Instance", QFT_TEXT),
1118
  # Depending on the hypervisor, the port can be None
1119
  "network_port": ("Network_port", QFT_OTHER),
1120
  "os": ("OS", QFT_TEXT),
1121
  "serial_no": ("SerialNo", QFT_NUMBER),
1122
  "uuid": ("UUID", QFT_TEXT),
1123
  }
1124

    
1125

    
1126
def _BuildInstanceFields():
1127
  """Builds list of fields for instance queries.
1128

1129
  """
1130
  fields = [
1131
    (_MakeField("pnode", "Primary_node", QFT_TEXT), IQ_CONFIG,
1132
     _GetItemAttr("primary_node")),
1133
    (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER), IQ_CONFIG,
1134
     lambda ctx, inst: list(inst.secondary_nodes)),
1135
    (_MakeField("admin_state", "Autostart", QFT_BOOL), IQ_CONFIG,
1136
     _GetItemAttr("admin_up")),
1137
    (_MakeField("tags", "Tags", QFT_OTHER), IQ_CONFIG,
1138
     lambda ctx, inst: list(inst.GetTags())),
1139
    (_MakeField("console", "Console", QFT_OTHER), IQ_CONSOLE,
1140
     _GetInstanceConsole),
1141
    ]
1142

    
1143
  # Add simple fields
1144
  fields.extend([(_MakeField(name, title, kind), IQ_CONFIG, _GetItemAttr(name))
1145
                 for (name, (title, kind)) in _INST_SIMPLE_FIELDS.items()])
1146

    
1147
  # Fields requiring talking to the node
1148
  fields.extend([
1149
    (_MakeField("oper_state", "Running", QFT_BOOL), IQ_LIVE,
1150
     _GetInstOperState),
1151
    (_MakeField("oper_ram", "Memory", QFT_UNIT), IQ_LIVE,
1152
     _GetInstLiveData("memory")),
1153
    (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER), IQ_LIVE,
1154
     _GetInstLiveData("vcpus")),
1155
    (_MakeField("status", "Status", QFT_TEXT), IQ_LIVE, _GetInstStatus),
1156
    ])
1157

    
1158
  fields.extend(_GetInstanceParameterFields())
1159
  fields.extend(_GetInstanceDiskFields())
1160
  fields.extend(_GetInstanceNetworkFields())
1161
  fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1162

    
1163
  aliases = [
1164
    ("vcpus", "be/vcpus"),
1165
    ("sda_size", "disk.size/0"),
1166
    ("sdb_size", "disk.size/1"),
1167
    ]
1168

    
1169
  return _PrepareFieldList(fields, aliases)
1170

    
1171

    
1172
class LockQueryData:
1173
  """Data container for lock data queries.
1174

1175
  """
1176
  def __init__(self, lockdata):
1177
    """Initializes this class.
1178

1179
    """
1180
    self.lockdata = lockdata
1181

    
1182
  def __iter__(self):
1183
    """Iterate over all locks.
1184

1185
    """
1186
    return iter(self.lockdata)
1187

    
1188

    
1189
def _GetLockOwners(_, data):
1190
  """Returns a sorted list of a lock's current owners.
1191

1192
  """
1193
  (_, _, owners, _) = data
1194

    
1195
  if owners:
1196
    owners = utils.NiceSort(owners)
1197

    
1198
  return owners
1199

    
1200

    
1201
def _GetLockPending(_, data):
1202
  """Returns a sorted list of a lock's pending acquires.
1203

1204
  """
1205
  (_, _, _, pending) = data
1206

    
1207
  if pending:
1208
    pending = [(mode, utils.NiceSort(names))
1209
               for (mode, names) in pending]
1210

    
1211
  return pending
1212

    
1213

    
1214
def _BuildLockFields():
1215
  """Builds list of fields for lock queries.
1216

1217
  """
1218
  return _PrepareFieldList([
1219
    (_MakeField("name", "Name", QFT_TEXT), None,
1220
     lambda ctx, (name, mode, owners, pending): name),
1221
    (_MakeField("mode", "Mode", QFT_OTHER), LQ_MODE,
1222
     lambda ctx, (name, mode, owners, pending): mode),
1223
    (_MakeField("owner", "Owner", QFT_OTHER), LQ_OWNER, _GetLockOwners),
1224
    (_MakeField("pending", "Pending", QFT_OTHER), LQ_PENDING, _GetLockPending),
1225
    ], [])
1226

    
1227

    
1228
class GroupQueryData:
1229
  """Data container for node group data queries.
1230

1231
  """
1232
  def __init__(self, groups, group_to_nodes, group_to_instances):
1233
    """Initializes this class.
1234

1235
    @param groups: List of node group objects
1236
    @type group_to_nodes: dict; group UUID as key
1237
    @param group_to_nodes: Per-group list of nodes
1238
    @type group_to_instances: dict; group UUID as key
1239
    @param group_to_instances: Per-group list of (primary) instances
1240

1241
    """
1242
    self.groups = groups
1243
    self.group_to_nodes = group_to_nodes
1244
    self.group_to_instances = group_to_instances
1245

    
1246
  def __iter__(self):
1247
    """Iterate over all node groups.
1248

1249
    """
1250
    return iter(self.groups)
1251

    
1252

    
1253
_GROUP_SIMPLE_FIELDS = {
1254
  "alloc_policy": ("AllocPolicy", QFT_TEXT),
1255
  "name": ("Group", QFT_TEXT),
1256
  "serial_no": ("SerialNo", QFT_NUMBER),
1257
  "uuid": ("UUID", QFT_TEXT),
1258
  "ndparams": ("NDParams", QFT_OTHER),
1259
  }
1260

    
1261

    
1262
def _BuildGroupFields():
1263
  """Builds list of fields for node group queries.
1264

1265
  """
1266
  # Add simple fields
1267
  fields = [(_MakeField(name, title, kind), GQ_CONFIG, _GetItemAttr(name))
1268
            for (name, (title, kind)) in _GROUP_SIMPLE_FIELDS.items()]
1269

    
1270
  def _GetLength(getter):
1271
    return lambda ctx, group: len(getter(ctx)[group.uuid])
1272

    
1273
  def _GetSortedList(getter):
1274
    return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
1275

    
1276
  group_to_nodes = operator.attrgetter("group_to_nodes")
1277
  group_to_instances = operator.attrgetter("group_to_instances")
1278

    
1279
  # Add fields for nodes
1280
  fields.extend([
1281
    (_MakeField("node_cnt", "Nodes", QFT_NUMBER),
1282
     GQ_NODE, _GetLength(group_to_nodes)),
1283
    (_MakeField("node_list", "NodeList", QFT_OTHER),
1284
     GQ_NODE, _GetSortedList(group_to_nodes)),
1285
    ])
1286

    
1287
  # Add fields for instances
1288
  fields.extend([
1289
    (_MakeField("pinst_cnt", "Instances", QFT_NUMBER),
1290
     GQ_INST, _GetLength(group_to_instances)),
1291
    (_MakeField("pinst_list", "InstanceList", QFT_OTHER),
1292
     GQ_INST, _GetSortedList(group_to_instances)),
1293
    ])
1294

    
1295
  fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1296

    
1297
  return _PrepareFieldList(fields, [])
1298

    
1299

    
1300
class NetworkQueryData:
1301
  """Data container for network data queries.
1302

1303
  """
1304
  def __init__(self, networks, network_to_groups,
1305
               network_to_instances, stats):
1306
    """Initializes this class.
1307

1308
    @param networks: List of network objects
1309
    @type network_to_groups: dict; network UUID as key
1310
    @param network_to_groups: Per-network list of groups
1311
    @type network_to_instances: dict; network UUID as key
1312
    @param network_to_instances: Per-network list of instances
1313
    @type stats: dict; network UUID as key
1314
    @param stats: Per-network usage statistics
1315

1316
    """
1317
    self.networks = networks
1318
    self.network_to_groups = network_to_groups
1319
    self.network_to_instances = network_to_instances
1320
    self.stats = stats
1321

    
1322
  def __iter__(self):
1323
    """Iterate over all node groups.
1324

1325
    """
1326
    for net in self.networks:
1327
      if self.stats:
1328
        self.curstats = self.stats.get(net.uuid, None)
1329
        logging.warn(self.curstats)
1330
      else:
1331
        self.curstats = None
1332
      yield net
1333

    
1334

    
1335
_NETWORK_SIMPLE_FIELDS = {
1336
  "name": ("Network", QFT_TEXT),
1337
  "network": ("Subnet", QFT_TEXT),
1338
  "gateway": ("Gateway", QFT_TEXT),
1339
  }
1340

    
1341

    
1342
_NETWORK_STATS_FIELDS = {
1343
  "free_count": ("FreeCount", QFT_NUMBER),
1344
  "reserved_count": ("ReservedCount", QFT_NUMBER),
1345
  "map": ("Map", QFT_TEXT),
1346
  }
1347

    
1348

    
1349
def _GetNetworkStatsField(field, kind, ctx, net):
1350
  """Gets the value of a "stats" field from L{NetworkQueryData}.
1351

1352
  @param field: Field name
1353
  @param kind: Data kind, one of L{constants.QFT_ALL}
1354
  @type ctx: L{NetworkQueryData}
1355

1356
  """
1357

    
1358
  try:
1359
    value = ctx.curstats[field]
1360
  except KeyError:
1361
    return _FS_UNAVAIL
1362

    
1363
  if kind == QFT_TEXT:
1364
    return value
1365

    
1366
  assert kind in (QFT_NUMBER, QFT_UNIT)
1367

    
1368
  # Try to convert into number
1369
  try:
1370
    return int(value)
1371
  except (ValueError, TypeError):
1372
    logging.exception("Failed to convert network field '%s' (value %r) to int",
1373
                      value, field)
1374
    return _FS_UNAVAIL
1375

    
1376

    
1377
def _BuildNetworkFields():
1378
  """Builds list of fields for network queries.
1379

1380
  """
1381
  # Add simple fields
1382
  fields = [(_MakeField(name, title, kind), NETQ_CONFIG, _GetItemAttr(name))
1383
            for (name, (title, kind)) in _NETWORK_SIMPLE_FIELDS.items()]
1384

    
1385
  def _GetLength(getter):
1386
    return lambda ctx, network: len(getter(ctx)[network.uuid])
1387

    
1388
  def _GetSortedList(getter):
1389
    return lambda ctx, network: utils.NiceSort(getter(ctx)[network.uuid])
1390

    
1391
  network_to_groups = operator.attrgetter("network_to_groups")
1392
  network_to_instances = operator.attrgetter("network_to_instances")
1393

    
1394
  # Add fields for node groups
1395
  fields.extend([
1396
    (_MakeField("group_cnt", "Node_Groups", QFT_NUMBER),
1397
     NETQ_GROUP, _GetLength(network_to_groups)),
1398
    (_MakeField("group_links", "Group_Links", QFT_OTHER),
1399
     NETQ_GROUP, _GetSortedList(network_to_groups)),
1400
    ])
1401

    
1402
  # Add fields for instances
1403
  fields.extend([
1404
    (_MakeField("inst_cnt", "Instances", QFT_NUMBER),
1405
     NETQ_INST, _GetLength(network_to_instances)),
1406
    (_MakeField("inst_list", "InstanceList", QFT_OTHER),
1407
     NETQ_INST, _GetSortedList(network_to_instances)),
1408
    ])
1409

    
1410
  # Add fields for usage statistics
1411
  fields.extend([
1412
    (_MakeField(name, title, kind), NETQ_STATS,
1413
    compat.partial(_GetNetworkStatsField, name, kind))
1414
    for (name, (title, kind)) in _NETWORK_STATS_FIELDS.items()
1415
    ])
1416

    
1417
  return _PrepareFieldList(fields, [])
1418

    
1419

    
1420
#: Fields available for node queries
1421
NODE_FIELDS = _BuildNodeFields()
1422

    
1423
#: Fields available for instance queries
1424
INSTANCE_FIELDS = _BuildInstanceFields()
1425

    
1426
#: Fields available for lock queries
1427
LOCK_FIELDS = _BuildLockFields()
1428

    
1429
#: Fields available for node group queries
1430
GROUP_FIELDS = _BuildGroupFields()
1431

    
1432
#: Fields available for network queries
1433
NETWORK_FIELDS = _BuildNetworkFields()
1434

    
1435
#: All available field lists
1436
ALL_FIELD_LISTS = [NODE_FIELDS, INSTANCE_FIELDS, LOCK_FIELDS,
1437
                   GROUP_FIELDS, NETWORK_FIELDS]