qlang: Remove OP_GLOB operator
[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_nicparams = None
1263
1264   def __iter__(self):
1265     """Iterate over all instances.
1266
1267     This function has side-effects and only one instance of the resulting
1268     generator should be used at a time.
1269
1270     """
1271     for inst in self.instances:
1272       self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
1273       self.inst_beparams = self.cluster.FillBE(inst)
1274       self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
1275                              for nic in inst.nics]
1276
1277       yield inst
1278
1279
1280 def _GetInstOperState(ctx, inst):
1281   """Get instance's operational status.
1282
1283   @type ctx: L{InstanceQueryData}
1284   @type inst: L{objects.Instance}
1285   @param inst: Instance object
1286
1287   """
1288   # Can't use RS_OFFLINE here as it would describe the instance to
1289   # be offline when we actually don't know due to missing data
1290   if inst.primary_node in ctx.bad_nodes:
1291     return _FS_NODATA
1292   else:
1293     return bool(ctx.live_data.get(inst.name))
1294
1295
1296 def _GetInstLiveData(name):
1297   """Build function for retrieving live data.
1298
1299   @type name: string
1300   @param name: Live data field name
1301
1302   """
1303   def fn(ctx, inst):
1304     """Get live data for an instance.
1305
1306     @type ctx: L{InstanceQueryData}
1307     @type inst: L{objects.Instance}
1308     @param inst: Instance object
1309
1310     """
1311     if (inst.primary_node in ctx.bad_nodes or
1312         inst.primary_node in ctx.offline_nodes):
1313       # Can't use RS_OFFLINE here as it would describe the instance to be
1314       # offline when we actually don't know due to missing data
1315       return _FS_NODATA
1316
1317     if inst.name in ctx.live_data:
1318       data = ctx.live_data[inst.name]
1319       if name in data:
1320         return data[name]
1321
1322     return _FS_UNAVAIL
1323
1324   return fn
1325
1326
1327 def _GetInstStatus(ctx, inst):
1328   """Get instance status.
1329
1330   @type ctx: L{InstanceQueryData}
1331   @type inst: L{objects.Instance}
1332   @param inst: Instance object
1333
1334   """
1335   if inst.primary_node in ctx.offline_nodes:
1336     return constants.INSTST_NODEOFFLINE
1337
1338   if inst.primary_node in ctx.bad_nodes:
1339     return constants.INSTST_NODEDOWN
1340
1341   if bool(ctx.live_data.get(inst.name)):
1342     if inst.name in ctx.wrongnode_inst:
1343       return constants.INSTST_WRONGNODE
1344     elif inst.admin_up:
1345       return constants.INSTST_RUNNING
1346     else:
1347       return constants.INSTST_ERRORUP
1348
1349   if inst.admin_up:
1350     return constants.INSTST_ERRORDOWN
1351
1352   return constants.INSTST_ADMINDOWN
1353
1354
1355 def _GetInstDiskSize(index):
1356   """Build function for retrieving disk size.
1357
1358   @type index: int
1359   @param index: Disk index
1360
1361   """
1362   def fn(_, inst):
1363     """Get size of a disk.
1364
1365     @type inst: L{objects.Instance}
1366     @param inst: Instance object
1367
1368     """
1369     try:
1370       return inst.disks[index].size
1371     except IndexError:
1372       return _FS_UNAVAIL
1373
1374   return fn
1375
1376
1377 def _GetInstNic(index, cb):
1378   """Build function for calling another function with an instance NIC.
1379
1380   @type index: int
1381   @param index: NIC index
1382   @type cb: callable
1383   @param cb: Callback
1384
1385   """
1386   def fn(ctx, inst):
1387     """Call helper function with instance NIC.
1388
1389     @type ctx: L{InstanceQueryData}
1390     @type inst: L{objects.Instance}
1391     @param inst: Instance object
1392
1393     """
1394     try:
1395       nic = inst.nics[index]
1396     except IndexError:
1397       return _FS_UNAVAIL
1398
1399     return cb(ctx, index, nic)
1400
1401   return fn
1402
1403
1404 def _GetInstNicIp(ctx, _, nic): # pylint: disable-msg=W0613
1405   """Get a NIC's IP address.
1406
1407   @type ctx: L{InstanceQueryData}
1408   @type nic: L{objects.NIC}
1409   @param nic: NIC object
1410
1411   """
1412   if nic.ip is None:
1413     return _FS_UNAVAIL
1414   else:
1415     return nic.ip
1416
1417
1418 def _GetInstNicBridge(ctx, index, _):
1419   """Get a NIC's bridge.
1420
1421   @type ctx: L{InstanceQueryData}
1422   @type index: int
1423   @param index: NIC index
1424
1425   """
1426   assert len(ctx.inst_nicparams) >= index
1427
1428   nicparams = ctx.inst_nicparams[index]
1429
1430   if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1431     return nicparams[constants.NIC_LINK]
1432   else:
1433     return _FS_UNAVAIL
1434
1435
1436 def _GetInstAllNicBridges(ctx, inst):
1437   """Get all network bridges for an instance.
1438
1439   @type ctx: L{InstanceQueryData}
1440   @type inst: L{objects.Instance}
1441   @param inst: Instance object
1442
1443   """
1444   assert len(ctx.inst_nicparams) == len(inst.nics)
1445
1446   result = []
1447
1448   for nicp in ctx.inst_nicparams:
1449     if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1450       result.append(nicp[constants.NIC_LINK])
1451     else:
1452       result.append(None)
1453
1454   assert len(result) == len(inst.nics)
1455
1456   return result
1457
1458
1459 def _GetInstNicParam(name):
1460   """Build function for retrieving a NIC parameter.
1461
1462   @type name: string
1463   @param name: Parameter name
1464
1465   """
1466   def fn(ctx, index, _):
1467     """Get a NIC's bridge.
1468
1469     @type ctx: L{InstanceQueryData}
1470     @type inst: L{objects.Instance}
1471     @param inst: Instance object
1472     @type nic: L{objects.NIC}
1473     @param nic: NIC object
1474
1475     """
1476     assert len(ctx.inst_nicparams) >= index
1477     return ctx.inst_nicparams[index][name]
1478
1479   return fn
1480
1481
1482 def _GetInstanceNetworkFields():
1483   """Get instance fields involving network interfaces.
1484
1485   @return: Tuple containing list of field definitions used as input for
1486     L{_PrepareFieldList} and a list of aliases
1487
1488   """
1489   nic_mac_fn = lambda ctx, _, nic: nic.mac
1490   nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
1491   nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
1492
1493   fields = [
1494     # All NICs
1495     (_MakeField("nic.count", "NICs", QFT_NUMBER,
1496                 "Number of network interfaces"),
1497      IQ_CONFIG, 0, lambda ctx, inst: len(inst.nics)),
1498     (_MakeField("nic.macs", "NIC_MACs", QFT_OTHER,
1499                 "List containing each network interface's MAC address"),
1500      IQ_CONFIG, 0, lambda ctx, inst: [nic.mac for nic in inst.nics]),
1501     (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
1502                 "List containing each network interface's IP address"),
1503      IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
1504     (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
1505                 "List containing each network interface's mode"), IQ_CONFIG, 0,
1506      lambda ctx, inst: [nicp[constants.NIC_MODE]
1507                         for nicp in ctx.inst_nicparams]),
1508     (_MakeField("nic.links", "NIC_links", QFT_OTHER,
1509                 "List containing each network interface's link"), IQ_CONFIG, 0,
1510      lambda ctx, inst: [nicp[constants.NIC_LINK]
1511                         for nicp in ctx.inst_nicparams]),
1512     (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
1513                 "List containing each network interface's bridge"),
1514      IQ_CONFIG, 0, _GetInstAllNicBridges),
1515     ]
1516
1517   # NICs by number
1518   for i in range(constants.MAX_NICS):
1519     numtext = utils.FormatOrdinal(i + 1)
1520     fields.extend([
1521       (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, QFT_TEXT,
1522                   "IP address of %s network interface" % numtext),
1523        IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicIp)),
1524       (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
1525                   "MAC address of %s network interface" % numtext),
1526        IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
1527       (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
1528                   "Mode of %s network interface" % numtext),
1529        IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
1530       (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, QFT_TEXT,
1531                   "Link of %s network interface" % numtext),
1532        IQ_CONFIG, 0, _GetInstNic(i, nic_link_fn)),
1533       (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
1534                   "Bridge of %s network interface" % numtext),
1535        IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
1536       ])
1537
1538   aliases = [
1539     # Legacy fields for first NIC
1540     ("ip", "nic.ip/0"),
1541     ("mac", "nic.mac/0"),
1542     ("bridge", "nic.bridge/0"),
1543     ("nic_mode", "nic.mode/0"),
1544     ("nic_link", "nic.link/0"),
1545     ]
1546
1547   return (fields, aliases)
1548
1549
1550 def _GetInstDiskUsage(ctx, inst):
1551   """Get disk usage for an instance.
1552
1553   @type ctx: L{InstanceQueryData}
1554   @type inst: L{objects.Instance}
1555   @param inst: Instance object
1556
1557   """
1558   usage = ctx.disk_usage[inst.name]
1559
1560   if usage is None:
1561     usage = 0
1562
1563   return usage
1564
1565
1566 def _GetInstanceConsole(ctx, inst):
1567   """Get console information for instance.
1568
1569   @type ctx: L{InstanceQueryData}
1570   @type inst: L{objects.Instance}
1571   @param inst: Instance object
1572
1573   """
1574   consinfo = ctx.console[inst.name]
1575
1576   if consinfo is None:
1577     return _FS_UNAVAIL
1578
1579   return consinfo
1580
1581
1582 def _GetInstanceDiskFields():
1583   """Get instance fields involving disks.
1584
1585   @return: List of field definitions used as input for L{_PrepareFieldList}
1586
1587   """
1588   fields = [
1589     (_MakeField("disk_usage", "DiskUsage", QFT_UNIT,
1590                 "Total disk space used by instance on each of its nodes;"
1591                 " this is not the disk size visible to the instance, but"
1592                 " the usage on the node"),
1593      IQ_DISKUSAGE, 0, _GetInstDiskUsage),
1594     (_MakeField("disk.count", "Disks", QFT_NUMBER, "Number of disks"),
1595      IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
1596     (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
1597      IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
1598     ]
1599
1600   # Disks by number
1601   fields.extend([
1602     (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
1603                 "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
1604      IQ_CONFIG, 0, _GetInstDiskSize(i))
1605     for i in range(constants.MAX_DISKS)
1606     ])
1607
1608   return fields
1609
1610
1611 def _GetInstanceParameterFields():
1612   """Get instance fields involving parameters.
1613
1614   @return: List of field definitions used as input for L{_PrepareFieldList}
1615
1616   """
1617   # TODO: Consider moving titles closer to constants
1618   be_title = {
1619     constants.BE_AUTO_BALANCE: "Auto_balance",
1620     constants.BE_MEMORY: "ConfigMemory",
1621     constants.BE_VCPUS: "ConfigVCPUs",
1622     }
1623
1624   hv_title = {
1625     constants.HV_ACPI: "ACPI",
1626     constants.HV_BOOT_ORDER: "Boot_order",
1627     constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
1628     constants.HV_DISK_TYPE: "Disk_type",
1629     constants.HV_INITRD_PATH: "Initrd_path",
1630     constants.HV_KERNEL_PATH: "Kernel_path",
1631     constants.HV_NIC_TYPE: "NIC_type",
1632     constants.HV_PAE: "PAE",
1633     constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
1634     }
1635
1636   fields = [
1637     # Filled parameters
1638     (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
1639                 "Hypervisor parameters"),
1640      IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
1641     (_MakeField("beparams", "BackendParameters", QFT_OTHER,
1642                 "Backend parameters"),
1643      IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
1644
1645     # Unfilled parameters
1646     (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
1647                 "Custom hypervisor parameters"),
1648      IQ_CONFIG, 0, _GetItemAttr("hvparams")),
1649     (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
1650                 "Custom backend parameters",),
1651      IQ_CONFIG, 0, _GetItemAttr("beparams")),
1652     (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
1653                 "Custom network interface parameters"),
1654      IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
1655     ]
1656
1657   # HV params
1658   def _GetInstHvParam(name):
1659     return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
1660
1661   fields.extend([
1662     (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
1663                 _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
1664      IQ_CONFIG, 0, _GetInstHvParam(name))
1665     for name, kind in constants.HVS_PARAMETER_TYPES.items()
1666     if name not in constants.HVC_GLOBALS
1667     ])
1668
1669   # BE params
1670   def _GetInstBeParam(name):
1671     return lambda ctx, _: ctx.inst_beparams.get(name, None)
1672
1673   fields.extend([
1674     (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
1675                 _VTToQFT[kind], "The \"%s\" backend parameter" % name),
1676      IQ_CONFIG, 0, _GetInstBeParam(name))
1677     for name, kind in constants.BES_PARAMETER_TYPES.items()
1678     ])
1679
1680   return fields
1681
1682
1683 _INST_SIMPLE_FIELDS = {
1684   "disk_template": ("Disk_template", QFT_TEXT, 0, "Instance disk template"),
1685   "hypervisor": ("Hypervisor", QFT_TEXT, 0, "Hypervisor name"),
1686   "name": ("Instance", QFT_TEXT, QFF_HOSTNAME, "Instance name"),
1687   # Depending on the hypervisor, the port can be None
1688   "network_port": ("Network_port", QFT_OTHER, 0,
1689                    "Instance network port if available (e.g. for VNC console)"),
1690   "os": ("OS", QFT_TEXT, 0, "Operating system"),
1691   "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Instance"),
1692   "uuid": ("UUID", QFT_TEXT, 0, "Instance UUID"),
1693   }
1694
1695
1696 def _BuildInstanceFields():
1697   """Builds list of fields for instance queries.
1698
1699   """
1700   fields = [
1701     (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
1702      IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")),
1703     # TODO: Allow filtering by secondary node as hostname
1704     (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
1705                 "Secondary nodes; usually this will just be one node"),
1706      IQ_CONFIG, 0, lambda ctx, inst: list(inst.secondary_nodes)),
1707     (_MakeField("admin_state", "Autostart", QFT_BOOL,
1708                 "Desired state of instance (if set, the instance should be"
1709                 " up)"),
1710      IQ_CONFIG, 0, _GetItemAttr("admin_up")),
1711     (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
1712      lambda ctx, inst: list(inst.GetTags())),
1713     (_MakeField("console", "Console", QFT_OTHER,
1714                 "Instance console information"), IQ_CONSOLE, 0,
1715      _GetInstanceConsole),
1716     ]
1717
1718   # Add simple fields
1719   fields.extend([
1720     (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
1721     for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()
1722     ])
1723
1724   # Fields requiring talking to the node
1725   fields.extend([
1726     (_MakeField("oper_state", "Running", QFT_BOOL, "Actual state of instance"),
1727      IQ_LIVE, 0, _GetInstOperState),
1728     (_MakeField("oper_ram", "Memory", QFT_UNIT,
1729                 "Actual memory usage as seen by hypervisor"),
1730      IQ_LIVE, 0, _GetInstLiveData("memory")),
1731     (_MakeField("oper_vcpus", "VCPUs", QFT_NUMBER,
1732                 "Actual number of VCPUs as seen by hypervisor"),
1733      IQ_LIVE, 0, _GetInstLiveData("vcpus")),
1734     ])
1735
1736   # Status field
1737   status_values = (constants.INSTST_RUNNING, constants.INSTST_ADMINDOWN,
1738                    constants.INSTST_WRONGNODE, constants.INSTST_ERRORUP,
1739                    constants.INSTST_ERRORDOWN, constants.INSTST_NODEDOWN,
1740                    constants.INSTST_NODEOFFLINE)
1741   status_doc = ("Instance status; \"%s\" if instance is set to be running"
1742                 " and actually is, \"%s\" if instance is stopped and"
1743                 " is not running, \"%s\" if instance running, but not on its"
1744                 " designated primary node, \"%s\" if instance should be"
1745                 " stopped, but is actually running, \"%s\" if instance should"
1746                 " run, but doesn't, \"%s\" if instance's primary node is down,"
1747                 " \"%s\" if instance's primary node is marked offline" %
1748                 status_values)
1749   fields.append((_MakeField("status", "Status", QFT_TEXT, status_doc),
1750                  IQ_LIVE, 0, _GetInstStatus))
1751   assert set(status_values) == constants.INSTST_ALL, \
1752          "Status documentation mismatch"
1753
1754   (network_fields, network_aliases) = _GetInstanceNetworkFields()
1755
1756   fields.extend(network_fields)
1757   fields.extend(_GetInstanceParameterFields())
1758   fields.extend(_GetInstanceDiskFields())
1759   fields.extend(_GetItemTimestampFields(IQ_CONFIG))
1760
1761   aliases = [
1762     ("vcpus", "be/vcpus"),
1763     ("sda_size", "disk.size/0"),
1764     ("sdb_size", "disk.size/1"),
1765     ] + network_aliases
1766
1767   return _PrepareFieldList(fields, aliases)
1768
1769
1770 class LockQueryData:
1771   """Data container for lock data queries.
1772
1773   """
1774   def __init__(self, lockdata):
1775     """Initializes this class.
1776
1777     """
1778     self.lockdata = lockdata
1779
1780   def __iter__(self):
1781     """Iterate over all locks.
1782
1783     """
1784     return iter(self.lockdata)
1785
1786
1787 def _GetLockOwners(_, data):
1788   """Returns a sorted list of a lock's current owners.
1789
1790   """
1791   (_, _, owners, _) = data
1792
1793   if owners:
1794     owners = utils.NiceSort(owners)
1795
1796   return owners
1797
1798
1799 def _GetLockPending(_, data):
1800   """Returns a sorted list of a lock's pending acquires.
1801
1802   """
1803   (_, _, _, pending) = data
1804
1805   if pending:
1806     pending = [(mode, utils.NiceSort(names))
1807                for (mode, names) in pending]
1808
1809   return pending
1810
1811
1812 def _BuildLockFields():
1813   """Builds list of fields for lock queries.
1814
1815   """
1816   return _PrepareFieldList([
1817     # TODO: Lock names are not always hostnames. Should QFF_HOSTNAME be used?
1818     (_MakeField("name", "Name", QFT_TEXT, "Lock name"), None, 0,
1819      lambda ctx, (name, mode, owners, pending): name),
1820     (_MakeField("mode", "Mode", QFT_OTHER,
1821                 "Mode in which the lock is currently acquired"
1822                 " (exclusive or shared)"),
1823      LQ_MODE, 0, lambda ctx, (name, mode, owners, pending): mode),
1824     (_MakeField("owner", "Owner", QFT_OTHER, "Current lock owner(s)"),
1825      LQ_OWNER, 0, _GetLockOwners),
1826     (_MakeField("pending", "Pending", QFT_OTHER,
1827                 "Threads waiting for the lock"),
1828      LQ_PENDING, 0, _GetLockPending),
1829     ], [])
1830
1831
1832 class GroupQueryData:
1833   """Data container for node group data queries.
1834
1835   """
1836   def __init__(self, groups, group_to_nodes, group_to_instances):
1837     """Initializes this class.
1838
1839     @param groups: List of node group objects
1840     @type group_to_nodes: dict; group UUID as key
1841     @param group_to_nodes: Per-group list of nodes
1842     @type group_to_instances: dict; group UUID as key
1843     @param group_to_instances: Per-group list of (primary) instances
1844
1845     """
1846     self.groups = groups
1847     self.group_to_nodes = group_to_nodes
1848     self.group_to_instances = group_to_instances
1849
1850   def __iter__(self):
1851     """Iterate over all node groups.
1852
1853     """
1854     return iter(self.groups)
1855
1856
1857 _GROUP_SIMPLE_FIELDS = {
1858   "alloc_policy": ("AllocPolicy", QFT_TEXT, "Allocation policy for group"),
1859   "name": ("Group", QFT_TEXT, "Group name"),
1860   "serial_no": ("SerialNo", QFT_NUMBER, _SERIAL_NO_DOC % "Group"),
1861   "uuid": ("UUID", QFT_TEXT, "Group UUID"),
1862   "ndparams": ("NDParams", QFT_OTHER, "Node parameters"),
1863   }
1864
1865
1866 def _BuildGroupFields():
1867   """Builds list of fields for node group queries.
1868
1869   """
1870   # Add simple fields
1871   fields = [(_MakeField(name, title, kind, doc), GQ_CONFIG, 0,
1872              _GetItemAttr(name))
1873             for (name, (title, kind, doc)) in _GROUP_SIMPLE_FIELDS.items()]
1874
1875   def _GetLength(getter):
1876     return lambda ctx, group: len(getter(ctx)[group.uuid])
1877
1878   def _GetSortedList(getter):
1879     return lambda ctx, group: utils.NiceSort(getter(ctx)[group.uuid])
1880
1881   group_to_nodes = operator.attrgetter("group_to_nodes")
1882   group_to_instances = operator.attrgetter("group_to_instances")
1883
1884   # Add fields for nodes
1885   fields.extend([
1886     (_MakeField("node_cnt", "Nodes", QFT_NUMBER, "Number of nodes"),
1887      GQ_NODE, 0, _GetLength(group_to_nodes)),
1888     (_MakeField("node_list", "NodeList", QFT_OTHER, "List of nodes"),
1889      GQ_NODE, 0, _GetSortedList(group_to_nodes)),
1890     ])
1891
1892   # Add fields for instances
1893   fields.extend([
1894     (_MakeField("pinst_cnt", "Instances", QFT_NUMBER,
1895                 "Number of primary instances"),
1896      GQ_INST, 0, _GetLength(group_to_instances)),
1897     (_MakeField("pinst_list", "InstanceList", QFT_OTHER,
1898                 "List of primary instances"),
1899      GQ_INST, 0, _GetSortedList(group_to_instances)),
1900     ])
1901
1902   fields.extend(_GetItemTimestampFields(GQ_CONFIG))
1903
1904   return _PrepareFieldList(fields, [])
1905
1906
1907 class OsInfo(objects.ConfigObject):
1908   __slots__ = [
1909     "name",
1910     "valid",
1911     "hidden",
1912     "blacklisted",
1913     "variants",
1914     "api_versions",
1915     "parameters",
1916     "node_status",
1917     ]
1918
1919
1920 def _BuildOsFields():
1921   """Builds list of fields for operating system queries.
1922
1923   """
1924   fields = [
1925     (_MakeField("name", "Name", QFT_TEXT, "Operating system name"),
1926      None, 0, _GetItemAttr("name")),
1927     (_MakeField("valid", "Valid", QFT_BOOL,
1928                 "Whether operating system definition is valid"),
1929      None, 0, _GetItemAttr("valid")),
1930     (_MakeField("hidden", "Hidden", QFT_BOOL,
1931                 "Whether operating system is hidden"),
1932      None, 0, _GetItemAttr("hidden")),
1933     (_MakeField("blacklisted", "Blacklisted", QFT_BOOL,
1934                 "Whether operating system is blacklisted"),
1935      None, 0, _GetItemAttr("blacklisted")),
1936     (_MakeField("variants", "Variants", QFT_OTHER,
1937                 "Operating system variants"),
1938      None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("variants"))),
1939     (_MakeField("api_versions", "ApiVersions", QFT_OTHER,
1940                 "Operating system API versions"),
1941      None, 0, _ConvWrap(sorted, _GetItemAttr("api_versions"))),
1942     (_MakeField("parameters", "Parameters", QFT_OTHER,
1943                 "Operating system parameters"),
1944      None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("parameters"))),
1945     (_MakeField("node_status", "NodeStatus", QFT_OTHER,
1946                 "Status from node"),
1947      None, 0, _GetItemAttr("node_status")),
1948     ]
1949
1950   return _PrepareFieldList(fields, [])
1951
1952
1953 #: Fields available for node queries
1954 NODE_FIELDS = _BuildNodeFields()
1955
1956 #: Fields available for instance queries
1957 INSTANCE_FIELDS = _BuildInstanceFields()
1958
1959 #: Fields available for lock queries
1960 LOCK_FIELDS = _BuildLockFields()
1961
1962 #: Fields available for node group queries
1963 GROUP_FIELDS = _BuildGroupFields()
1964
1965 #: Fields available for operating system queries
1966 OS_FIELDS = _BuildOsFields()
1967
1968 #: All available resources
1969 ALL_FIELDS = {
1970   constants.QR_INSTANCE: INSTANCE_FIELDS,
1971   constants.QR_NODE: NODE_FIELDS,
1972   constants.QR_LOCK: LOCK_FIELDS,
1973   constants.QR_GROUP: GROUP_FIELDS,
1974   constants.QR_OS: OS_FIELDS,
1975   }
1976
1977 #: All available field lists
1978 ALL_FIELD_LISTS = ALL_FIELDS.values()