query: Add operator for truth
[ganeti-local] / lib / query.py
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           - Human-readable description, must not end with punctuation or
36             contain newlines
37       - Data request type, see e.g. C{NQ_*}
38       - OR-ed flags, see C{QFF_*}
39       - A retrieval function, see L{Query.__init__} for description
40     - Pass list of fields through L{_PrepareFieldList} for preparation and
41       checks
42   - Instantiate L{Query} with prepared field list definition and selected fields
43   - Call L{Query.RequestedData} to determine what data to collect/compute
44   - Call L{Query.Query} or L{Query.OldStyleQuery} with collected data and use
45     result
46       - Data container must support iteration using C{__iter__}
47       - Items are passed to retrieval functions and can have any format
48   - Call L{Query.GetFields} to get list of definitions for selected fields
49
50 @attention: Retrieval functions must be idempotent. They can be called multiple
51   times, in any order and any number of times.
52
53 """
54
55 import logging
56 import operator
57 import re
58
59 from ganeti import constants
60 from ganeti import errors
61 from ganeti import utils
62 from ganeti import compat
63 from ganeti import objects
64 from ganeti import ht
65 from ganeti import qlang
66
67 from ganeti.constants import (QFT_UNKNOWN, QFT_TEXT, QFT_BOOL, QFT_NUMBER,
68                               QFT_UNIT, QFT_TIMESTAMP, QFT_OTHER,
69                               RS_NORMAL, RS_UNKNOWN, RS_NODATA,
70                               RS_UNAVAIL, RS_OFFLINE)
71
72
73 # Constants for requesting data from the caller/data provider. Each property
74 # collected/computed separately by the data provider should have its own to
75 # only collect the requested data and not more.
76
77 (NQ_CONFIG,
78  NQ_INST,
79  NQ_LIVE,
80  NQ_GROUP,
81  NQ_OOB) = range(1, 6)
82
83 (IQ_CONFIG,
84  IQ_LIVE,
85  IQ_DISKUSAGE,
86  IQ_CONSOLE) = range(100, 104)
87
88 (LQ_MODE,
89  LQ_OWNER,
90  LQ_PENDING) = range(10, 13)
91
92 (GQ_CONFIG,
93  GQ_NODE,
94  GQ_INST) = range(200, 203)
95
96 # Query field flags
97 QFF_HOSTNAME = 0x01
98 QFF_IP_ADDRESS = 0x02
99 # Next values: 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200
100 QFF_ALL = (QFF_HOSTNAME | QFF_IP_ADDRESS)
101
102 FIELD_NAME_RE = re.compile(r"^[a-z0-9/._]+$")
103 TITLE_RE = re.compile(r"^[^\s]+$")
104 DOC_RE = re.compile(r"^[A-Z].*[^.,?!]$")
105
106 #: Verification function for each field type
107 _VERIFY_FN = {
108   QFT_UNKNOWN: ht.TNone,
109   QFT_TEXT: ht.TString,
110   QFT_BOOL: ht.TBool,
111   QFT_NUMBER: ht.TInt,
112   QFT_UNIT: ht.TInt,
113   QFT_TIMESTAMP: ht.TOr(ht.TInt, ht.TFloat),
114   QFT_OTHER: lambda _: True,
115   }
116
117 # Unique objects for special field statuses
118 _FS_UNKNOWN = object()
119 _FS_NODATA = object()
120 _FS_UNAVAIL = object()
121 _FS_OFFLINE = object()
122
123 #: VType to QFT mapping
124 _VTToQFT = {
125   # TODO: fix validation of empty strings
126   constants.VTYPE_STRING: QFT_OTHER, # since VTYPE_STRINGs can be empty
127   constants.VTYPE_MAYBE_STRING: QFT_OTHER,
128   constants.VTYPE_BOOL: QFT_BOOL,
129   constants.VTYPE_SIZE: QFT_UNIT,
130   constants.VTYPE_INT: QFT_NUMBER,
131   }
132
133 _SERIAL_NO_DOC = "%s object serial number, incremented on each modification"
134
135
136 def _GetUnknownField(ctx, item): # pylint: disable-msg=W0613
137   """Gets the contents of an unknown field.
138
139   """
140   return _FS_UNKNOWN
141
142
143 def _GetQueryFields(fielddefs, selected):
144   """Calculates the internal list of selected fields.
145
146   Unknown fields are returned as L{constants.QFT_UNKNOWN}.
147
148   @type fielddefs: dict
149   @param fielddefs: Field definitions
150   @type selected: list of strings
151   @param selected: List of selected fields
152
153   """
154   result = []
155
156   for name in selected:
157     try:
158       fdef = fielddefs[name]
159     except KeyError:
160       fdef = (_MakeField(name, name, QFT_UNKNOWN, "Unknown field '%s'" % name),
161               None, 0, _GetUnknownField)
162
163     assert len(fdef) == 4
164
165     result.append(fdef)
166
167   return result
168
169
170 def GetAllFields(fielddefs):
171   """Extract L{objects.QueryFieldDefinition} from field definitions.
172
173   @rtype: list of L{objects.QueryFieldDefinition}
174
175   """
176   return [fdef for (fdef, _, _, _) in fielddefs]
177
178
179 class _FilterHints:
180   """Class for filter analytics.
181
182   When filters are used, the user of the L{Query} class usually doesn't know
183   exactly which items will be necessary for building the result. It therefore
184   has to prepare and compute the input data for potentially returning
185   everything.
186
187   There are two ways to optimize this. The first, and simpler, is to assign
188   each field a group of data, so that the caller can determine which
189   computations are necessary depending on the data groups requested. The list
190   of referenced groups must also be computed for fields referenced in the
191   filter.
192
193   The second is restricting the items based on a primary key. The primary key
194   is usually a unique name (e.g. a node name). This class extracts all
195   referenced names from a filter. If it encounters any filter condition which
196   disallows such a list to be determined (e.g. a non-equality filter), all
197   names will be requested.
198
199   The end-effect is that any operation other than L{qlang.OP_OR} and
200   L{qlang.OP_EQUAL} will make the query more expensive.
201
202   """
203   def __init__(self, namefield):
204     """Initializes this class.
205
206     @type namefield: string
207     @param namefield: Field caller is interested in
208
209     """
210     self._namefield = namefield
211
212     #: Whether all names need to be requested (e.g. if a non-equality operator
213     #: has been used)
214     self._allnames = False
215
216     #: Which names to request
217     self._names = None
218
219     #: Data kinds referenced by the filter (used by L{Query.RequestedData})
220     self._datakinds = set()
221
222   def RequestedNames(self):
223     """Returns all requested values.
224
225     Returns C{None} if list of values can't be determined (e.g. encountered
226     non-equality operators).
227
228     @rtype: list
229
230     """
231     if self._allnames or self._names is None:
232       return None
233
234     return utils.UniqueSequence(self._names)
235
236   def ReferencedData(self):
237     """Returns all kinds of data referenced by the filter.
238
239     """
240     return frozenset(self._datakinds)
241
242   def _NeedAllNames(self):
243     """Changes internal state to request all names.
244
245     """
246     self._allnames = True
247     self._names = None
248
249   def NoteLogicOp(self, op):
250     """Called when handling a logic operation.
251
252     @type op: string
253     @param op: Operator
254
255     """
256     if op != qlang.OP_OR:
257       self._NeedAllNames()
258
259   def NoteUnaryOp(self, op): # pylint: disable-msg=W0613
260     """Called when handling an unary operation.
261
262     @type op: string
263     @param op: Operator
264
265     """
266     self._NeedAllNames()
267
268   def NoteBinaryOp(self, op, datakind, name, value):
269     """Called when handling a binary operation.
270
271     @type op: string
272     @param op: Operator
273     @type name: string
274     @param name: Left-hand side of operator (field name)
275     @param value: Right-hand side of operator
276
277     """
278     if datakind is not None:
279       self._datakinds.add(datakind)
280
281     if self._allnames:
282       return
283
284     # If any operator other than equality was used, all names need to be
285     # retrieved
286     if op == qlang.OP_EQUAL and name == self._namefield:
287       if self._names is None:
288         self._names = []
289       self._names.append(value)
290     else:
291       self._NeedAllNames()
292
293
294 def _WrapLogicOp(op_fn, sentences, ctx, item):
295   """Wrapper for logic operator functions.
296
297   """
298   return op_fn(fn(ctx, item) for fn in sentences)
299
300
301 def _WrapUnaryOp(op_fn, inner, ctx, item):
302   """Wrapper for unary operator functions.
303
304   """
305   return op_fn(inner(ctx, item))
306
307
308 def _WrapBinaryOp(op_fn, retrieval_fn, value, ctx, item):
309   """Wrapper for binary operator functions.
310
311   """
312   return op_fn(retrieval_fn(ctx, item), value)
313
314
315 def _WrapNot(fn, lhs, rhs):
316   """Negates the result of a wrapped function.
317
318   """
319   return not fn(lhs, rhs)
320
321
322 class _FilterCompilerHelper:
323   """Converts a query filter to a callable usable for filtering.
324
325   """
326   # String statement has no effect, pylint: disable-msg=W0105
327
328   #: How deep filters can be nested
329   _LEVELS_MAX = 10
330
331   # Unique identifiers for operator groups
332   (_OPTYPE_LOGIC,
333    _OPTYPE_UNARY,
334    _OPTYPE_BINARY) = range(1, 4)
335
336   """Functions for equality checks depending on field flags.
337
338   List of tuples containing flags and a callable receiving the left- and
339   right-hand side of the operator. The flags are an OR-ed value of C{QFF_*}
340   (e.g. L{QFF_HOSTNAME}).
341
342   Order matters. The first item with flags will be used. Flags are checked
343   using binary AND.
344
345   """
346   _EQUALITY_CHECKS = [
347     (QFF_HOSTNAME,
348      lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
349                                                case_sensitive=False)),
350     (None, operator.eq),
351     ]
352
353   """Known operators
354
355   Operator as key (C{qlang.OP_*}), value a tuple of operator group
356   (C{_OPTYPE_*}) and a group-specific value:
357
358     - C{_OPTYPE_LOGIC}: Callable taking any number of arguments; used by
359       L{_HandleLogicOp}
360     - C{_OPTYPE_UNARY}: Always C{None}; details handled by L{_HandleUnaryOp}
361     - C{_OPTYPE_BINARY}: Callable taking exactly two parameters, the left- and
362       right-hand side of the operator, used by L{_HandleBinaryOp}
363
364   """
365   _OPS = {
366     # Logic operators
367     qlang.OP_OR: (_OPTYPE_LOGIC, compat.any),
368     qlang.OP_AND: (_OPTYPE_LOGIC, compat.all),
369
370     # Unary operators
371     qlang.OP_NOT: (_OPTYPE_UNARY, None),
372     qlang.OP_TRUE: (_OPTYPE_UNARY, None),
373
374     # Binary operators
375     qlang.OP_EQUAL: (_OPTYPE_BINARY, _EQUALITY_CHECKS),
376     qlang.OP_NOT_EQUAL:
377       (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn))
378                         for (flags, fn) in _EQUALITY_CHECKS]),
379     qlang.OP_GLOB: (_OPTYPE_BINARY, NotImplemented),
380     qlang.OP_REGEXP: (_OPTYPE_BINARY, NotImplemented),
381     qlang.OP_CONTAINS: (_OPTYPE_BINARY, [
382       (None, operator.contains),
383       ]),
384     }
385
386   def __init__(self, fields):
387     """Initializes this class.
388
389     @param fields: Field definitions (return value of L{_PrepareFieldList})
390
391     """
392     self._fields = fields
393     self._hints = None
394     self._op_handler = None
395
396   def __call__(self, hints, filter_):
397     """Converts a query filter into a callable function.
398
399     @type hints: L{_FilterHints} or None
400     @param hints: Callbacks doing analysis on filter
401     @type filter_: list
402     @param filter_: Filter structure
403     @rtype: callable
404     @return: Function receiving context and item as parameters, returning
405              boolean as to whether item matches filter
406
407     """
408     self._op_handler = {
409       self._OPTYPE_LOGIC:
410         (self._HandleLogicOp, getattr(hints, "NoteLogicOp", None)),
411       self._OPTYPE_UNARY:
412         (self._HandleUnaryOp, getattr(hints, "NoteUnaryOp", None)),
413       self._OPTYPE_BINARY:
414         (self._HandleBinaryOp, getattr(hints, "NoteBinaryOp", None)),
415       }
416
417     try:
418       filter_fn = self._Compile(filter_, 0)
419     finally:
420       self._op_handler = None
421
422     return filter_fn
423
424   def _Compile(self, filter_, level):
425     """Inner function for converting filters.
426
427     Calls the correct handler functions for the top-level operator. This
428     function is called recursively (e.g. for logic operators).
429
430     """
431     if not (isinstance(filter_, (list, tuple)) and filter_):
432       raise errors.ParameterError("Invalid filter on level %s" % level)
433
434     # Limit recursion
435     if level >= self._LEVELS_MAX:
436       raise errors.ParameterError("Only up to %s levels are allowed (filter"
437                                   " nested too deep)" % self._LEVELS_MAX)
438
439     # Create copy to be modified
440     operands = filter_[:]
441     op = operands.pop(0)
442
443     try:
444       (kind, op_data) = self._OPS[op]
445     except KeyError:
446       raise errors.ParameterError("Unknown operator '%s'" % op)
447
448     (handler, hints_cb) = self._op_handler[kind]
449
450     return handler(hints_cb, level, op, op_data, operands)
451
452   def _LookupField(self, name):
453     """Returns a field definition by name.
454
455     """
456     try:
457       return self._fields[name]
458     except KeyError:
459       raise errors.ParameterError("Unknown field '%s'" % name)
460
461   def _HandleLogicOp(self, hints_fn, level, op, op_fn, operands):
462     """Handles logic operators.
463
464     @type hints_fn: callable
465     @param hints_fn: Callback doing some analysis on the filter
466     @type level: integer
467     @param level: Current depth
468     @type op: string
469     @param op: Operator
470     @type op_fn: callable
471     @param op_fn: Function implementing operator
472     @type operands: list
473     @param operands: List of operands
474
475     """
476     if hints_fn:
477       hints_fn(op)
478
479     return compat.partial(_WrapLogicOp, op_fn,
480                           [self._Compile(op, level + 1) for op in operands])
481
482   def _HandleUnaryOp(self, hints_fn, level, op, op_fn, operands):
483     """Handles unary operators.
484
485     @type hints_fn: callable
486     @param hints_fn: Callback doing some analysis on the filter
487     @type level: integer
488     @param level: Current depth
489     @type op: string
490     @param op: Operator
491     @type op_fn: callable
492     @param op_fn: Function implementing operator
493     @type operands: list
494     @param operands: List of operands
495
496     """
497     assert op_fn is None
498
499     if hints_fn:
500       hints_fn(op)
501
502     if len(operands) != 1:
503       raise errors.ParameterError("Unary operator '%s' expects exactly one"
504                                   " operand" % op)
505
506     if op == qlang.OP_TRUE:
507       (_, _, _, retrieval_fn) = self._LookupField(operands[0])
508
509       op_fn = operator.truth
510       arg = retrieval_fn
511     elif op == qlang.OP_NOT:
512       op_fn = operator.not_
513       arg = self._Compile(operands[0], level + 1)
514     else:
515       raise errors.ProgrammerError("Can't handle operator '%s'" % op)
516
517     return compat.partial(_WrapUnaryOp, op_fn, arg)
518
519   def _HandleBinaryOp(self, hints_fn, level, op, op_data, operands):
520     """Handles binary operators.
521
522     @type hints_fn: callable
523     @param hints_fn: Callback doing some analysis on the filter
524     @type level: integer
525     @param level: Current depth
526     @type op: string
527     @param op: Operator
528     @param op_data: Functions implementing operators
529     @type operands: list
530     @param operands: List of operands
531
532     """
533     # Unused arguments, pylint: disable-msg=W0613
534     try:
535       (name, value) = operands
536     except (ValueError, TypeError):
537       raise errors.ParameterError("Invalid binary operator, expected exactly"
538                                   " two operands")
539
540     (fdef, datakind, field_flags, retrieval_fn) = self._LookupField(name)
541
542     assert fdef.kind != QFT_UNKNOWN
543
544     # TODO: Type conversions?
545
546     verify_fn = _VERIFY_FN[fdef.kind]
547     if not verify_fn(value):
548       raise errors.ParameterError("Unable to compare field '%s' (type '%s')"
549                                   " with '%s', expected %s" %
550                                   (name, fdef.kind, value.__class__.__name__,
551                                    verify_fn))
552
553     if hints_fn:
554       hints_fn(op, datakind, name, value)
555
556     for (fn_flags, fn) in op_data:
557       if fn_flags is None or fn_flags & field_flags:
558         return compat.partial(_WrapBinaryOp, fn, retrieval_fn, value)
559
560     raise errors.ProgrammerError("Unable to find operator implementation"
561                                  " (op '%s', flags %s)" % (op, field_flags))
562
563
564 def _CompileFilter(fields, hints, filter_):
565   """Converts a query filter into a callable function.
566
567   See L{_FilterCompilerHelper} for details.
568
569   @rtype: callable
570
571   """
572   return _FilterCompilerHelper(fields)(hints, filter_)
573
574
575 class Query:
576   def __init__(self, fieldlist, selected, filter_=None, namefield=None):
577     """Initializes this class.
578
579     The field definition is a dictionary with the field's name as a key and a
580     tuple containing, in order, the field definition object
581     (L{objects.QueryFieldDefinition}, the data kind to help calling code
582     collect data and a retrieval function. The retrieval function is called
583     with two parameters, in order, the data container and the item in container
584     (see L{Query.Query}).
585
586     Users of this class can call L{RequestedData} before preparing the data
587     container to determine what data is needed.
588
589     @type fieldlist: dictionary
590     @param fieldlist: Field definitions
591     @type selected: list of strings
592     @param selected: List of selected fields
593
594     """
595     assert namefield is None or namefield in fieldlist
596
597     self._fields = _GetQueryFields(fieldlist, selected)
598
599     self._filter_fn = None
600     self._requested_names = None
601     self._filter_datakinds = frozenset()
602
603     if filter_ is not None:
604       # Collect requested names if wanted
605       if namefield:
606         hints = _FilterHints(namefield)
607       else:
608         hints = None
609
610       # Build filter function
611       self._filter_fn = _CompileFilter(fieldlist, hints, filter_)
612       if hints:
613         self._requested_names = hints.RequestedNames()
614         self._filter_datakinds = hints.ReferencedData()
615
616     if namefield is None:
617       self._name_fn = None
618     else:
619       (_, _, _, self._name_fn) = fieldlist[namefield]
620
621   def RequestedNames(self):
622     """Returns all names referenced in the filter.
623
624     If there is no filter or operators are preventing determining the exact
625     names, C{None} is returned.
626
627     """
628     return self._requested_names
629
630   def RequestedData(self):
631     """Gets requested kinds of data.
632
633     @rtype: frozenset
634
635     """
636     return (self._filter_datakinds |
637             frozenset(datakind for (_, datakind, _, _) in self._fields
638                       if datakind is not None))
639
640   def GetFields(self):
641     """Returns the list of fields for this query.
642
643     Includes unknown fields.
644
645     @rtype: List of L{objects.QueryFieldDefinition}
646
647     """
648     return GetAllFields(self._fields)
649
650   def Query(self, ctx, sort_by_name=True):
651     """Execute a query.
652
653     @param ctx: Data container passed to field retrieval functions, must
654       support iteration using C{__iter__}
655     @type sort_by_name: boolean
656     @param sort_by_name: Whether to sort by name or keep the input data's
657       ordering
658
659     """
660     sort = (self._name_fn and sort_by_name)
661
662     result = []
663
664     for idx, item in enumerate(ctx):
665       if not (self._filter_fn is None or self._filter_fn(ctx, item)):
666         continue
667
668       row = [_ProcessResult(fn(ctx, item)) for (_, _, _, fn) in self._fields]
669
670       # Verify result
671       if __debug__:
672         _VerifyResultRow(self._fields, row)
673
674       if sort:
675         (status, name) = _ProcessResult(self._name_fn(ctx, item))
676         assert status == constants.RS_NORMAL
677         # TODO: Are there cases where we wouldn't want to use NiceSort?
678         result.append((utils.NiceSortKey(name), idx, row))
679       else:
680         result.append(row)
681
682     if not sort:
683       return result
684
685     # TODO: Would "heapq" be more efficient than sorting?
686
687     # Sorting in-place instead of using "sorted()"
688     result.sort()
689
690     assert not result or (len(result[0]) == 3 and len(result[-1]) == 3)
691
692     return map(operator.itemgetter(2), result)
693
694   def OldStyleQuery(self, ctx, sort_by_name=True):
695     """Query with "old" query result format.
696
697     See L{Query.Query} for arguments.
698
699     """
700     unknown = set(fdef.name for (fdef, _, _, _) in self._fields
701                   if fdef.kind == QFT_UNKNOWN)
702     if unknown:
703       raise errors.OpPrereqError("Unknown output fields selected: %s" %
704                                  (utils.CommaJoin(unknown), ),
705                                  errors.ECODE_INVAL)
706
707     return [[value for (_, value) in row]
708             for row in self.Query(ctx, sort_by_name=sort_by_name)]
709
710
711 def _ProcessResult(value):
712   """Converts result values into externally-visible ones.
713
714   """
715   if value is _FS_UNKNOWN:
716     return (RS_UNKNOWN, None)
717   elif value is _FS_NODATA:
718     return (RS_NODATA, None)
719   elif value is _FS_UNAVAIL:
720     return (RS_UNAVAIL, None)
721   elif value is _FS_OFFLINE:
722     return (RS_OFFLINE, None)
723   else:
724     return (RS_NORMAL, value)
725
726
727 def _VerifyResultRow(fields, row):
728   """Verifies the contents of a query result row.
729
730   @type fields: list
731   @param fields: Field definitions for result
732   @type row: list of tuples
733   @param row: Row data
734
735   """
736   assert len(row) == len(fields)
737   errs = []
738   for ((status, value), (fdef, _, _, _)) in zip(row, fields):
739     if status == RS_NORMAL:
740       if not _VERIFY_FN[fdef.kind](value):
741         errs.append("normal field %s fails validation (value is %s)" %
742                     (fdef.name, value))
743     elif value is not None:
744       errs.append("abnormal field %s has a non-None value" % fdef.name)
745   assert not errs, ("Failed validation: %s in row %s" %
746                     (utils.CommaJoin(errors), row))
747
748
749 def _PrepareFieldList(fields, aliases):
750   """Prepares field list for use by L{Query}.
751
752   Converts the list to a dictionary and does some verification.
753
754   @type fields: list of tuples; (L{objects.QueryFieldDefinition}, data
755       kind, retrieval function)
756   @param fields: List of fields, see L{Query.__init__} for a better
757       description
758   @type aliases: list of tuples; (alias, target)
759   @param aliases: list of tuples containing aliases; for each
760       alias/target pair, a duplicate will be created in the field list
761   @rtype: dict
762   @return: Field dictionary for L{Query}
763
764   """
765   if __debug__:
766     duplicates = utils.FindDuplicates(fdef.title.lower()
767                                       for (fdef, _, _, _) in fields)
768     assert not duplicates, "Duplicate title(s) found: %r" % duplicates
769
770   result = {}
771
772   for field in fields:
773     (fdef, _, flags, fn) = field
774
775     assert fdef.name and fdef.title, "Name and title are required"
776     assert FIELD_NAME_RE.match(fdef.name)
777     assert TITLE_RE.match(fdef.title)
778     assert (DOC_RE.match(fdef.doc) and len(fdef.doc.splitlines()) == 1 and
779             fdef.doc.strip() == fdef.doc), \
780            "Invalid description for field '%s'" % fdef.name
781     assert callable(fn)
782     assert fdef.name not in result, \
783            "Duplicate field name '%s' found" % fdef.name
784     assert (flags & ~QFF_ALL) == 0, "Unknown flags for field '%s'" % fdef.name
785
786     result[fdef.name] = field
787
788   for alias, target in aliases:
789     assert alias not in result, "Alias %s overrides an existing field" % alias
790     assert target in result, "Missing target %s for alias %s" % (target, alias)
791     (fdef, k, flags, fn) = result[target]
792     fdef = fdef.Copy()
793     fdef.name = alias
794     result[alias] = (fdef, k, flags, fn)
795
796   assert len(result) == len(fields) + len(aliases)
797   assert compat.all(name == fdef.name
798                     for (name, (fdef, _, _, _)) in result.items())
799
800   return result
801
802
803 def GetQueryResponse(query, ctx, sort_by_name=True):
804   """Prepares the response for a query.
805
806   @type query: L{Query}
807   @param ctx: Data container, see L{Query.Query}
808   @type sort_by_name: boolean
809   @param sort_by_name: Whether to sort by name or keep the input data's
810     ordering
811
812   """
813   return objects.QueryResponse(data=query.Query(ctx, sort_by_name=sort_by_name),
814                                fields=query.GetFields()).ToDict()
815
816
817 def QueryFields(fielddefs, selected):
818   """Returns list of available fields.
819
820   @type fielddefs: dict
821   @param fielddefs: Field definitions
822   @type selected: list of strings
823   @param selected: List of selected fields
824   @return: List of L{objects.QueryFieldDefinition}
825
826   """
827   if selected is None:
828     # Client requests all fields, sort by name
829     fdefs = utils.NiceSort(GetAllFields(fielddefs.values()),
830                            key=operator.attrgetter("name"))
831   else:
832     # Keep order as requested by client
833     fdefs = Query(fielddefs, selected).GetFields()
834
835   return objects.QueryFieldsResponse(fields=fdefs).ToDict()
836
837
838 def _MakeField(name, title, kind, doc):
839   """Wrapper for creating L{objects.QueryFieldDefinition} instances.
840
841   @param name: Field name as a regular expression
842   @param title: Human-readable title
843   @param kind: Field type
844   @param doc: Human-readable description
845
846   """
847   return objects.QueryFieldDefinition(name=name, title=title, kind=kind,
848                                       doc=doc)
849
850
851 def _GetNodeRole(node, master_name):
852   """Determine node role.
853
854   @type node: L{objects.Node}
855   @param node: Node object
856   @type master_name: string
857   @param master_name: Master node name
858
859   """
860   if node.name == master_name:
861     return constants.NR_MASTER
862   elif node.master_candidate:
863     return constants.NR_MCANDIDATE
864   elif node.drained:
865     return constants.NR_DRAINED
866   elif node.offline:
867     return constants.NR_OFFLINE
868   else:
869     return constants.NR_REGULAR
870
871
872 def _GetItemAttr(attr):
873   """Returns a field function to return an attribute of the item.
874
875   @param attr: Attribute name
876
877   """
878   getter = operator.attrgetter(attr)
879   return lambda _, item: getter(item)
880
881
882 def _GetItemTimestamp(getter):
883   """Returns function for getting timestamp of item.
884
885   @type getter: callable
886   @param getter: Function to retrieve timestamp attribute
887
888   """
889   def fn(_, item):
890     """Returns a timestamp of item.
891
892     """
893     timestamp = getter(item)
894     if timestamp is None:
895       # Old configs might not have all timestamps
896       return _FS_UNAVAIL
897     else:
898       return timestamp
899
900   return fn
901
902
903 def _GetItemTimestampFields(datatype):
904   """Returns common timestamp fields.
905
906   @param datatype: Field data type for use by L{Query.RequestedData}
907
908   """
909   return [
910     (_MakeField("ctime", "CTime", QFT_TIMESTAMP, "Creation timestamp"),
911      datatype, 0, _GetItemTimestamp(operator.attrgetter("ctime"))),
912     (_MakeField("mtime", "MTime", QFT_TIMESTAMP, "Modification timestamp"),
913      datatype, 0, _GetItemTimestamp(operator.attrgetter("mtime"))),
914     ]
915
916
917 class NodeQueryData:
918   """Data container for node data queries.
919
920   """
921   def __init__(self, nodes, live_data, master_name, node_to_primary,
922                node_to_secondary, groups, oob_support, cluster):
923     """Initializes this class.
924
925     """
926     self.nodes = nodes
927     self.live_data = live_data
928     self.master_name = master_name
929     self.node_to_primary = node_to_primary
930     self.node_to_secondary = node_to_secondary
931     self.groups = groups
932     self.oob_support = oob_support
933     self.cluster = cluster
934
935     # Used for individual rows
936     self.curlive_data = None
937
938   def __iter__(self):
939     """Iterate over all nodes.
940
941     This function has side-effects and only one instance of the resulting
942     generator should be used at a time.
943
944     """
945     for node in self.nodes:
946       if self.live_data:
947         self.curlive_data = self.live_data.get(node.name, None)
948       else:
949         self.curlive_data = None
950       yield node
951
952
953 #: Fields that are direct attributes of an L{objects.Node} object
954 _NODE_SIMPLE_FIELDS = {
955   "drained": ("Drained", QFT_BOOL, 0, "Whether node is drained"),
956   "master_candidate": ("MasterC", QFT_BOOL, 0,
957                        "Whether node is a master candidate"),
958   "master_capable": ("MasterCapable", QFT_BOOL, 0,
959                      "Whether node can become a master candidate"),
960   "name": ("Node", QFT_TEXT, QFF_HOSTNAME, "Node name"),
961   "offline": ("Offline", QFT_BOOL, 0, "Whether node is marked offline"),
962   "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Node"),
963   "uuid": ("UUID", QFT_TEXT, 0, "Node UUID"),
964   "vm_capable": ("VMCapable", QFT_BOOL, 0, "Whether node can host instances"),
965   }
966
967
968 #: Fields requiring talking to the node
969 # Note that none of these are available for non-vm_capable nodes
970 _NODE_LIVE_FIELDS = {
971   "bootid": ("BootID", QFT_TEXT, "bootid",
972              "Random UUID renewed for each system reboot, can be used"
973              " for detecting reboots by tracking changes"),
974   "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes",
975              "Number of NUMA domains on node (if exported by hypervisor)"),
976   "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets",
977                "Number of physical CPU sockets (if exported by hypervisor)"),
978   "ctotal": ("CTotal", QFT_NUMBER, "cpu_total", "Number of logical processors"),
979   "dfree": ("DFree", QFT_UNIT, "vg_free",
980             "Available disk space in volume group"),
981   "dtotal": ("DTotal", QFT_UNIT, "vg_size",
982              "Total disk space in volume group used for instance disk"
983              " allocation"),
984   "mfree": ("MFree", QFT_UNIT, "memory_free",
985             "Memory available for instance allocations"),
986   "mnode": ("MNode", QFT_UNIT, "memory_dom0",
987             "Amount of memory used by node (dom0 for Xen)"),
988   "mtotal": ("MTotal", QFT_UNIT, "memory_total",
989              "Total amount of memory of physical machine"),
990   }
991
992
993 def _GetGroup(cb):
994   """Build function for calling another function with an node group.
995
996   @param cb: The callback to be called with the nodegroup
997
998   """
999   def fn(ctx, node):
1000     """Get group data for a node.
1001
1002     @type ctx: L{NodeQueryData}
1003     @type inst: L{objects.Node}
1004     @param inst: Node object
1005
1006     """
1007     ng = ctx.groups.get(node.group, None)
1008     if ng is None:
1009       # Nodes always have a group, or the configuration is corrupt
1010       return _FS_UNAVAIL
1011
1012     return cb(ctx, node, ng)
1013
1014   return fn
1015
1016
1017 def _GetNodeGroup(ctx, node, ng): # pylint: disable-msg=W0613
1018   """Returns the name of a node's group.
1019
1020   @type ctx: L{NodeQueryData}
1021   @type node: L{objects.Node}
1022   @param node: Node object
1023   @type ng: L{objects.NodeGroup}
1024   @param ng: The node group this node belongs to
1025
1026   """
1027   return ng.name
1028
1029
1030 def _GetNodePower(ctx, node):
1031   """Returns the node powered state
1032
1033   @type ctx: L{NodeQueryData}
1034   @type node: L{objects.Node}
1035   @param node: Node object
1036
1037   """
1038   if ctx.oob_support[node.name]:
1039     return node.powered
1040
1041   return _FS_UNAVAIL
1042
1043
1044 def _GetNdParams(ctx, node, ng):
1045   """Returns the ndparams for this node.
1046
1047   @type ctx: L{NodeQueryData}
1048   @type node: L{objects.Node}
1049   @param node: Node object
1050   @type ng: L{objects.NodeGroup}
1051   @param ng: The node group this node belongs to
1052
1053   """
1054   return ctx.cluster.SimpleFillND(ng.FillND(node))
1055
1056
1057 def _GetLiveNodeField(field, kind, ctx, node):
1058   """Gets the value of a "live" field from L{NodeQueryData}.
1059
1060   @param field: Live field name
1061   @param kind: Data kind, one of L{constants.QFT_ALL}
1062   @type ctx: L{NodeQueryData}
1063   @type node: L{objects.Node}
1064   @param node: Node object
1065
1066   """
1067   if node.offline:
1068     return _FS_OFFLINE
1069
1070   if not node.vm_capable:
1071     return _FS_UNAVAIL
1072
1073   if not ctx.curlive_data:
1074     return _FS_NODATA
1075
1076   try:
1077     value = ctx.curlive_data[field]
1078   except KeyError:
1079     return _FS_UNAVAIL
1080
1081   if kind == QFT_TEXT:
1082     return value
1083
1084   assert kind in (QFT_NUMBER, QFT_UNIT)
1085
1086   # Try to convert into number
1087   try:
1088     return int(value)
1089   except (ValueError, TypeError):
1090     logging.exception("Failed to convert node field '%s' (value %r) to int",
1091                       value, field)
1092     return _FS_UNAVAIL
1093
1094
1095 def _BuildNodeFields():
1096   """Builds list of fields for node queries.
1097
1098   """
1099   fields = [
1100     (_MakeField("pip", "PrimaryIP", QFT_TEXT, "Primary IP address"),
1101      NQ_CONFIG, 0, _GetItemAttr("primary_ip")),
1102     (_MakeField("sip", "SecondaryIP", QFT_TEXT, "Secondary IP address"),
1103      NQ_CONFIG, 0, _GetItemAttr("secondary_ip")),
1104     (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0,
1105      lambda ctx, node: list(node.GetTags())),
1106     (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"),
1107      NQ_CONFIG, 0, lambda ctx, node: node.name == ctx.master_name),
1108     (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0,
1109      _GetGroup(_GetNodeGroup)),
1110     (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"),
1111      NQ_CONFIG, 0, _GetItemAttr("group")),
1112     (_MakeField("powered", "Powered", QFT_BOOL,
1113                 "Whether node is thought to be powered on"),
1114      NQ_OOB, 0, _GetNodePower),
1115     (_MakeField("ndparams", "NodeParameters", QFT_OTHER,
1116                 "Merged node parameters"),
1117      NQ_GROUP, 0, _GetGroup(_GetNdParams)),
1118     (_MakeField("custom_ndparams", "CustomNodeParameters", QFT_OTHER,
1119                 "Custom node parameters"),
1120       NQ_GROUP, 0, _GetItemAttr("ndparams")),
1121     ]
1122
1123   # Node role
1124   role_values = (constants.NR_MASTER, constants.NR_MCANDIDATE,
1125                  constants.NR_REGULAR, constants.NR_DRAINED,
1126                  constants.NR_OFFLINE)
1127   role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate,"
1128               " \"%s\" for regular, \"%s\" for a drained, \"%s\" for offline" %
1129               role_values)
1130   fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
1131                  lambda ctx, node: _GetNodeRole(node, ctx.master_name)))
1132   assert set(role_values) == constants.NR_ALL
1133
1134   def _GetLength(getter):
1135     return lambda ctx, node: len(getter(ctx)[node.name])
1136
1137   def _GetList(getter):
1138     return lambda ctx, node: list(getter(ctx)[node.name])
1139
1140   # Add fields operating on instance lists
1141   for prefix, titleprefix, docword, getter in \
1142       [("p", "Pri", "primary", operator.attrgetter("node_to_primary")),
1143        ("s", "Sec", "secondary", operator.attrgetter("node_to_secondary"))]:
1144     # TODO: Allow filterting by hostname in list
1145     fields.extend([
1146       (_MakeField("%sinst_cnt" % prefix, "%sinst" % prefix.upper(), QFT_NUMBER,
1147                   "Number of instances with this node as %s" % docword),
1148        NQ_INST, 0, _GetLength(getter)),
1149       (_MakeField("%sinst_list" % prefix, "%sInstances" % titleprefix,
1150                   QFT_OTHER,
1151                   "List of instances with this node as %s" % docword),
1152        NQ_INST, 0, _GetList(getter)),
1153       ])
1154
1155   # Add simple fields
1156   fields.extend([
1157     (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
1158     for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()
1159     ])
1160
1161   # Add fields requiring live data
1162   fields.extend([
1163     (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
1164      compat.partial(_GetLiveNodeField, nfield, kind))
1165     for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()
1166     ])
1167
1168   # Add timestamps
1169   fields.extend(_GetItemTimestampFields(NQ_CONFIG))
1170
1171   return _PrepareFieldList(fields, [])
1172
1173
1174 class InstanceQueryData:
1175   """Data container for instance data queries.
1176
1177   """
1178   def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
1179                live_data, wrongnode_inst, console):
1180     """Initializes this class.
1181
1182     @param instances: List of instance objects
1183     @param cluster: Cluster object
1184     @type disk_usage: dict; instance name as key
1185     @param disk_usage: Per-instance disk usage
1186     @type offline_nodes: list of strings
1187     @param offline_nodes: List of offline nodes
1188     @type bad_nodes: list of strings
1189     @param bad_nodes: List of faulty nodes
1190     @type live_data: dict; instance name as key
1191     @param live_data: Per-instance live data
1192     @type wrongnode_inst: set
1193     @param wrongnode_inst: Set of instances running on wrong node(s)
1194     @type console: dict; instance name as key
1195     @param console: Per-instance console information
1196
1197     """
1198     assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
1199            "Offline nodes not included in bad nodes"
1200     assert not (set(live_data.keys()) & set(bad_nodes)), \
1201            "Found live data for bad or offline nodes"
1202
1203     self.instances = instances
1204     self.cluster = cluster
1205     self.disk_usage = disk_usage
1206     self.offline_nodes = offline_nodes
1207     self.bad_nodes = bad_nodes
1208     self.live_data = live_data
1209     self.wrongnode_inst = wrongnode_inst
1210     self.console = console
1211
1212     # Used for individual rows
1213     self.inst_hvparams = None
1214     self.inst_beparams = None
1215     self.inst_nicparams = None
1216
1217   def __iter__(self):
1218     """Iterate over all instances.
1219
1220     This function has side-effects and only one instance of the resulting
1221     generator should be used at a time.
1222
1223     """
1224     for inst in self.instances:
1225       self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1226       self.inst_beparams = self.cluster.FillBE(inst)
1227       self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1228                              for nic in inst.nics]
1229
1230       yield inst
1231
1232
1233 def _GetInstOperState(ctx, inst):
1234   """Get instance's operational status.
1235
1236   @type ctx: L{InstanceQueryData}
1237   @type inst: L{objects.Instance}
1238   @param inst: Instance object
1239
1240   """
1241   # Can't use RS_OFFLINE here as it would describe the instance to
1242   # be offline when we actually don't know due to missing data
1243   if inst.primary_node in ctx.bad_nodes:
1244     return _FS_NODATA
1245   else:
1246     return bool(ctx.live_data.get(inst.name))
1247
1248
1249 def _GetInstLiveData(name):
1250   """Build function for retrieving live data.
1251
1252   @type name: string
1253   @param name: Live data field name
1254
1255   """
1256   def fn(ctx, inst):
1257     """Get live data for an instance.
1258
1259     @type ctx: L{InstanceQueryData}
1260     @type inst: L{objects.Instance}
1261     @param inst: Instance object
1262
1263     """
1264     if (inst.primary_node in ctx.bad_nodes or
1265         inst.primary_node in ctx.offline_nodes):
1266       # Can't use RS_OFFLINE here as it would describe the instance to be
1267       # offline when we actually don't know due to missing data
1268       return _FS_NODATA
1269
1270     if inst.name in ctx.live_data:
1271       data = ctx.live_data[inst.name]
1272       if name in data:
1273         return data[name]
1274
1275     return _FS_UNAVAIL
1276
1277   return fn
1278
1279
1280 def _GetInstStatus(ctx, inst):
1281   """Get instance status.
1282
1283   @type ctx: L{InstanceQueryData}
1284   @type inst: L{objects.Instance}
1285   @param inst: Instance object
1286
1287   """
1288   if inst.primary_node in ctx.offline_nodes:
1289     return constants.INSTST_NODEOFFLINE
1290
1291   if inst.primary_node in ctx.bad_nodes:
1292     return constants.INSTST_NODEDOWN
1293
1294   if bool(ctx.live_data.get(inst.name)):
1295     if inst.name in ctx.wrongnode_inst:
1296       return constants.INSTST_WRONGNODE
1297     elif inst.admin_up:
1298       return constants.INSTST_RUNNING
1299     else:
1300       return constants.INSTST_ERRORUP
1301
1302   if inst.admin_up:
1303     return constants.INSTST_ERRORDOWN
1304
1305   return constants.INSTST_ADMINDOWN
1306
1307
1308 def _GetInstDiskSize(index):
1309   """Build function for retrieving disk size.
1310
1311   @type index: int
1312   @param index: Disk index
1313
1314   """
1315   def fn(_, inst):
1316     """Get size of a disk.
1317
1318     @type inst: L{objects.Instance}
1319     @param inst: Instance object
1320
1321     """
1322     try:
1323       return inst.disks[index].size
1324     except IndexError:
1325       return _FS_UNAVAIL
1326
1327   return fn
1328
1329
1330 def _GetInstNic(index, cb):
1331   """Build function for calling another function with an instance NIC.
1332
1333   @type index: int
1334   @param index: NIC index
1335   @type cb: callable
1336   @param cb: Callback
1337
1338   """
1339   def fn(ctx, inst):
1340     """Call helper function with instance NIC.
1341
1342     @type ctx: L{InstanceQueryData}
1343     @type inst: L{objects.Instance}
1344     @param inst: Instance object
1345
1346     """
1347     try:
1348       nic = inst.nics[index]
1349     except IndexError:
1350       return _FS_UNAVAIL
1351
1352     return cb(ctx, index, nic)
1353
1354   return fn
1355
1356
1357 def _GetInstNicIp(ctx, _, nic): # pylint: disable-msg=W0613
1358   """Get a NIC's IP address.
1359
1360   @type ctx: L{InstanceQueryData}
1361   @type nic: L{objects.NIC}
1362   @param nic: NIC object
1363
1364   """
1365   if nic.ip is None:
1366     return _FS_UNAVAIL
1367   else:
1368     return nic.ip
1369
1370
1371 def _GetInstNicBridge(ctx, index, _):
1372   """Get a NIC's bridge.
1373
1374   @type ctx: L{InstanceQueryData}
1375   @type index: int
1376   @param index: NIC index
1377
1378   """
1379   assert len(ctx.inst_nicparams) >= index
1380
1381   nicparams = ctx.inst_nicparams[index]
1382
1383   if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1384     return nicparams[constants.NIC_LINK]
1385   else:
1386     return _FS_UNAVAIL
1387
1388
1389 def _GetInstAllNicBridges(ctx, inst):
1390   """Get all network bridges for an instance.
1391
1392   @type ctx: L{InstanceQueryData}
1393   @type inst: L{objects.Instance}
1394   @param inst: Instance object
1395
1396   """
1397   assert len(ctx.inst_nicparams) == len(inst.nics)
1398
1399   result = []
1400
1401   for nicp in ctx.inst_nicparams:
1402     if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1403       result.append(nicp[constants.NIC_LINK])
1404     else:
1405       result.append(None)
1406
1407   assert len(result) == len(inst.nics)
1408
1409   return result
1410
1411
1412 def _GetInstNicParam(name):
1413   """Build function for retrieving a NIC parameter.
1414
1415   @type name: string
1416   @param name: Parameter name
1417
1418   """
1419   def fn(ctx, index, _):
1420     """Get a NIC's bridge.
1421
1422     @type ctx: L{InstanceQueryData}
1423     @type inst: L{objects.Instance}
1424     @param inst: Instance object
1425     @type nic: L{objects.NIC}
1426     @param nic: NIC object
1427
1428     """
1429     assert len(ctx.inst_nicparams) >= index
1430     return ctx.inst_nicparams[index][name]
1431
1432   return fn
1433
1434
1435 def _GetInstanceNetworkFields():
1436   """Get instance fields involving network interfaces.
1437
1438   @return: Tuple containing list of field definitions used as input for
1439     L{_PrepareFieldList} and a list of aliases
1440
1441   """
1442   nic_mac_fn = lambda ctx, _, nic: nic.mac
1443   nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1444   nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1445
1446   fields = [
1447     # All NICs
1448     (_MakeField("nic.count", "NICs", QFT_NUMBER,
1449                 "Number of network interfaces"),
1450      IQ_CONFIG, 0, lambda ctx, inst: len(inst.nics)),
1451     (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER,
1452                 "List containing each network interface's MAC address"),
1453      IQ_CONFIG, 0, lambda ctx, inst: [nic.mac for nic in inst.nics]),
1454     (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
1455                 "List containing each network interface's IP address"),
1456      IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
1457     (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
1458                 "List containing each network interface's mode"), IQ_CONFIG, 0,
1459      lambda ctx, inst: [nicp[constants.NIC_MODE]
1460                         for nicp in ctx.inst_nicparams]),
1461     (_MakeField("nic.links", "NIC_links", QFT_OTHER,
1462                 "List containing each network interface's link"), IQ_CONFIG, 0,
1463      lambda ctx, inst: [nicp[constants.NIC_LINK]
1464                         for nicp in ctx.inst_nicparams]),
1465     (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
1466                 "List containing each network interface's bridge"),
1467      IQ_CONFIG, 0, _GetInstAllNicBridges),
1468     ]
1469
1470   # NICs by number
1471   for i in range(constants.MAX_NICS):
1472     numtext = utils.FormatOrdinal(i + 1)
1473     fields.extend([
1474       (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT,
1475                   "IP address of %s network interface" % numtext),
1476        IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)),
1477       (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
1478                   "MAC address of %s network interface" % numtext),
1479        IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
1480       (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
1481                   "Mode of %s network interface" % numtext),
1482        IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
1483       (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT,
1484                   "Link of %s network interface" % numtext),
1485        IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)),
1486       (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
1487                   "Bridge of %s network interface" % numtext),
1488        IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
1489       ])
1490
1491   aliases = [
1492     # Legacy fields for first NIC
1493     ("ip", "nic.ip/0"),
1494     ("mac", "nic.mac/0"),
1495     ("bridge", "nic.bridge/0"),
1496     ("nic_mode", "nic.mode/0"),
1497     ("nic_link", "nic.link/0"),
1498     ]
1499
1500   return (fields, aliases)
1501
1502
1503 def _GetInstDiskUsage(ctx, inst):
1504   """Get disk usage for an instance.
1505
1506   @type ctx: L{InstanceQueryData}
1507   @type inst: L{objects.Instance}
1508   @param inst: Instance object
1509
1510   """
1511   usage = ctx.disk_usage[inst.name]
1512
1513   if usage is None:
1514     usage = 0
1515
1516   return usage
1517
1518
1519 def _GetInstanceConsole(ctx, inst):
1520   """Get console information for instance.
1521
1522   @type ctx: L{InstanceQueryData}
1523   @type inst: L{objects.Instance}
1524   @param inst: Instance object
1525
1526   """
1527   consinfo = ctx.console[inst.name]
1528
1529   if consinfo is None:
1530     return _FS_UNAVAIL
1531
1532   return consinfo
1533
1534
1535 def _GetInstanceDiskFields():
1536   """Get instance fields involving disks.
1537
1538   @return: List of field definitions used as input for L{_PrepareFieldList}
1539
1540   """
1541   fields = [
1542     (_MakeField("disk_usage", "DiskUsage", QFT_UNIT,
1543                 "Total disk space used by instance on each of its nodes;"
1544                 " this is not the disk size visible to the instance, but"
1545                 " the usage on the node"),
1546      IQ_DISKUSAGE, 0, _GetInstDiskUsage),
1547     (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"),
1548      IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
1549     (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
1550      IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
1551     ]
1552
1553   # Disks by number
1554   fields.extend([
1555     (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1556                 "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
1557      IQ_CONFIG, 0, _GetInstDiskSize(i))
1558     for i in range(constants.MAX_DISKS)
1559     ])
1560
1561   return fields
1562
1563
1564 def _GetInstanceParameterFields():
1565   """Get instance fields involving parameters.
1566
1567   @return: List of field definitions used as input for L{_PrepareFieldList}
1568
1569   """
1570   # TODO: Consider moving titles closer to constants
1571   be_title = {
1572     constants.BE_AUTO_BALANCE: "Auto_balance",
1573     constants.BE_MEMORY: "ConfigMemory",
1574     constants.BE_VCPUS: "ConfigVCPUs",
1575     }
1576
1577   hv_title = {
1578     constants.HV_ACPI: "ACPI",
1579     constants.HV_BOOT_ORDER: "Boot_order",
1580     constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1581     constants.HV_DISK_TYPE: "Disk_type",
1582     constants.HV_INITRD_PATH: "Initrd_path",
1583     constants.HV_KERNEL_PATH: "Kernel_path",
1584     constants.HV_NIC_TYPE: "NIC_type",
1585     constants.HV_PAE: "PAE",
1586     constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1587     }
1588
1589   fields = [
1590     # Filled parameters
1591     (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
1592                 "Hypervisor parameters"),
1593      IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
1594     (_MakeField("beparams", "BackendParameters", QFT_OTHER,
1595                 "Backend parameters"),
1596      IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
1597
1598     # Unfilled parameters
1599     (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
1600                 "Custom hypervisor parameters"),
1601      IQ_CONFIG, 0, _GetItemAttr("hvparams")),
1602     (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
1603                 "Custom backend parameters",),
1604      IQ_CONFIG, 0, _GetItemAttr("beparams")),
1605     (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
1606                 "Custom network interface parameters"),
1607      IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1608     ]
1609
1610   # HV params
1611   def _GetInstHvParam(name):
1612     return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1613
1614   fields.extend([
1615     (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1616                 _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1617      IQ_CONFIG, 0, _GetInstHvParam(name))
1618     for name, kind in constants.HVS_PARAMETER_TYPES.items()
1619     if name not in constants.HVC_GLOBALS
1620     ])
1621
1622   # BE params
1623   def _GetInstBeParam(name):
1624     return lambda ctx, _: ctx.inst_beparams.get(name, None)
1625
1626   fields.extend([
1627     (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1628                 _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1629      IQ_CONFIG, 0, _GetInstBeParam(name))
1630     for name, kind in constants.BES_PARAMETER_TYPES.items()
1631     ])
1632
1633   return fields
1634
1635
1636 _INST_SIMPLE_FIELDS = {
1637   "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"),
1638   "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"),
1639   "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"),
1640   # Depending on the hypervisor, the port can be None
1641   "network_port": ("Network_port", QFT_OTHER, 0,
1642                    "Instance network port if available (e.g. for VNC console)"),
1643   "os": ("OS", QFT_TEXT, 0, "Operating system"),
1644   "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"),
1645   "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"),
1646   }
1647
1648
1649 def _BuildInstanceFields():
1650   """Builds list of fields for instance queries.
1651
1652   """
1653   fields = [
1654     (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
1655      IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")),
1656     # TODO: Allow filtering by secondary node as hostname
1657     (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
1658                 "Secondary nodes; usually this will just be one node"),
1659      IQ_CONFIG, 0, lambda ctx, inst: list(inst.secondary_nodes)),
1660     (_MakeField("admin_state", "Autostart", QFT_BOOL,
1661                 "Desired state of instance (if set, the instance should be"
1662                 " up)"),
1663      IQ_CONFIG, 0, _GetItemAttr("admin_up")),
1664     (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
1665      lambda ctx, inst: list(inst.GetTags())),
1666     (_MakeField("console", "Console", QFT_OTHER,
1667                 "Instance console information"), IQ_CONSOLE, 0,
1668      _GetInstanceConsole),
1669     ]
1670
1671   # Add simple fields
1672   fields.extend([
1673     (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1674     for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()
1675     ])
1676
1677   # Fields requiring talking to the node
1678   fields.extend([
1679     (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
1680      IQ_LIVE, 0, _GetInstOperState),
1681     (_MakeField("oper_ram", "Memory", QFT_UNIT,
1682                 "Actual memory usage as seen by hypervisor"),
1683      IQ_LIVE, 0, _GetInstLiveData("memory")),
1684     (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
1685                 "Actual number of VCPUs as seen by hypervisor"),
1686      IQ_LIVE, 0, _GetInstLiveData("vcpus")),
1687     ])
1688
1689   # Status field
1690   status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
1691                    constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
1692                    constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
1693                    constants.INSTST_NODEOFFLINE)
1694   status_doc = ("Instance status; \"%s\" if instance is set to be running"
1695                 " and actually is, \"%s\" if instance is stopped and"
1696                 " is not running, \"%s\" if instance running, but not on its"
1697                 " designated primary node, \"%s\" if instance should be"
1698                 " stopped, but is actually running, \"%s\" if instance should"
1699                 " run, but doesn't, \"%s\" if instance's primary node is down,"
1700                 " \"%s\" if instance's primary node is marked offline" %
1701                 status_values)
1702   fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
1703                  IQ_LIVE, 0, _GetInstStatus))
1704   assert set(status_values) == constants.INSTST_ALL, \
1705          "Status documentation mismatch"
1706
1707   (network_fields, network_aliases) = _GetInstanceNetworkFields()
1708
1709   fields.extend(network_fields)
1710   fields.extend(_GetInstanceParameterFields())
1711   fields.extend(_GetInstanceDiskFields())
1712   fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1713
1714   aliases = [
1715     ("vcpus", "be/vcpus"),
1716     ("sda_size", "disk.size/0"),
1717     ("sdb_size", "disk.size/1"),
1718     ] + network_aliases
1719
1720   return _PrepareFieldList(fields, aliases)
1721
1722
1723 class LockQueryData:
1724   """Data container for lock data queries.
1725
1726   """
1727   def __init__(self, lockdata):
1728     """Initializes this class.
1729
1730     """
1731     self.lockdata = lockdata
1732
1733   def __iter__(self):
1734     """Iterate over all locks.
1735
1736     """
1737     return iter(self.lockdata)
1738
1739
1740 def _GetLockOwners(_, data):
1741   """Returns a sorted list of a lock's current owners.
1742
1743   """
1744   (_, _, owners, _) = data
1745
1746   if owners:
1747     owners = utils.NiceSort(owners)
1748
1749   return owners
1750
1751
1752 def _GetLockPending(_, data):
1753   """Returns a sorted list of a lock's pending acquires.
1754
1755   """
1756   (_, _, _, pending) = data
1757
1758   if pending:
1759     pending = [(mode, utils.NiceSort(names))
1760                for (mode, names) in pending]
1761
1762   return pending
1763
1764
1765 def _BuildLockFields():
1766   """Builds list of fields for lock queries.
1767
1768   """
1769   return _PrepareFieldList([
1770     # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
1771     (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
1772      lambda ctx, (name, mode, owners, pending): name),
1773     (_MakeField("mode", "Mode", QFT_OTHER,
1774                 "Mode in which the lock is currently acquired"
1775                 " (exclusive or shared)"),
1776      LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
1777     (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
1778      LQ_OWNER, 0, _GetLockOwners),
1779     (_MakeField("pending", "Pending", QFT_OTHER,
1780                 "Threads waiting for the lock"),
1781      LQ_PENDING, 0, _GetLockPending),
1782     ], [])
1783
1784
1785 class GroupQueryData:
1786   """Data container for node group data queries.
1787
1788   """
1789   def __init__(self, groups, group_to_nodes, group_to_instances):
1790     """Initializes this class.
1791
1792     @param groups: List of node group objects
1793     @type group_to_nodes: dict; group UUID as key
1794     @param group_to_nodes: Per-group list of nodes
1795     @type group_to_instances: dict; group UUID as key
1796     @param group_to_instances: Per-group list of (primary) instances
1797
1798     """
1799     self.groups = groups
1800     self.group_to_nodes = group_to_nodes
1801     self.group_to_instances = group_to_instances
1802
1803   def __iter__(self):
1804     """Iterate over all node groups.
1805
1806     """
1807     return iter(self.groups)
1808
1809
1810 _GROUP_SIMPLE_FIELDS = {
1811   "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
1812   "name": ("Group", QFT_TEXT, "Group name"),
1813   "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
1814   "uuid": ("UUID", QFT_TEXT, "Group UUID"),
1815   "ndparams": ("NDParams", QFT_OTHER, "Node parameters"),
1816   }
1817
1818
1819 def _BuildGroupFields():
1820   """Builds list of fields for node group queries.
1821
1822   """
1823   # Add simple fields
1824   fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
1825              _GetItemAttr(name))
1826             for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
1827
1828   def _GetLength(getter):
1829     return lambda ctx, group: len(getter(ctx)[group.uuid])
1830
1831   def _GetSortedList(getter):
1832     return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
1833
1834   group_to_nodes = operator.attrgetter("group_to_nodes")
1835   group_to_instances = operator.attrgetter("group_to_instances")
1836
1837   # Add fields for nodes
1838   fields.extend([
1839     (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
1840      GQ_NODE, 0, _GetLength(group_to_nodes)),
1841     (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
1842      GQ_NODE, 0, _GetSortedList(group_to_nodes)),
1843     ])
1844
1845   # Add fields for instances
1846   fields.extend([
1847     (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
1848                 "Number of primary instances"),
1849      GQ_INST, 0, _GetLength(group_to_instances)),
1850     (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
1851                 "List of primary instances"),
1852      GQ_INST, 0, _GetSortedList(group_to_instances)),
1853     ])
1854
1855   fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1856
1857   return _PrepareFieldList(fields, [])
1858
1859
1860 #: Fields available for node queries
1861 NODE_FIELDS = _BuildNodeFields()
1862
1863 #: Fields available for instance queries
1864 INSTANCE_FIELDS = _BuildInstanceFields()
1865
1866 #: Fields available for lock queries
1867 LOCK_FIELDS = _BuildLockFields()
1868
1869 #: Fields available for node group queries
1870 GROUP_FIELDS = _BuildGroupFields()
1871
1872 #: All available resources
1873 ALL_FIELDS = {
1874   constants.QR_INSTANCE: INSTANCE_FIELDS,
1875   constants.QR_NODE: NODE_FIELDS,
1876   constants.QR_LOCK: LOCK_FIELDS,
1877   constants.QR_GROUP: GROUP_FIELDS,
1878   }
1879
1880 #: All available field lists
1881 ALL_FIELD_LISTS = ALL_FIELDS.values()