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