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