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