--select-instances hbal manpage update
[ganeti-local] / lib / query.py
index 6497c60..51448ba 100644 (file)
@@ -120,6 +120,9 @@ _FS_NODATA = object()
 _FS_UNAVAIL = object()
 _FS_OFFLINE = object()
 
+#: List of all special status
+_FS_ALL = frozenset([_FS_UNKNOWN, _FS_NODATA, _FS_UNAVAIL, _FS_OFFLINE])
+
 #: VType to QFT mapping
 _VTToQFT = {
   # TODO: fix validation of empty strings
@@ -319,6 +322,16 @@ def _WrapNot(fn, lhs, rhs):
   return not fn(lhs, rhs)
 
 
+def _PrepareRegex(pattern):
+  """Compiles a regular expression.
+
+  """
+  try:
+    return re.compile(pattern)
+  except re.error, err:
+    raise errors.ParameterError("Invalid regex pattern (%s)" % err)
+
+
 class _FilterCompilerHelper:
   """Converts a query filter to a callable usable for filtering.
 
@@ -346,8 +359,9 @@ class _FilterCompilerHelper:
   _EQUALITY_CHECKS = [
     (QFF_HOSTNAME,
      lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
-                                               case_sensitive=False)),
-    (None, operator.eq),
+                                               case_sensitive=False),
+     None),
+    (None, operator.eq, None),
     ]
 
   """Known operators
@@ -357,8 +371,7 @@ class _FilterCompilerHelper:
 
     - C{_OPTYPE_LOGIC}: Callable taking any number of arguments; used by
       L{_HandleLogicOp}
-    - C{_OPTYPE_UNARY}: Callable taking exactly one parameter; used by
-      L{_HandleUnaryOp}
+    - C{_OPTYPE_UNARY}: Always C{None}; details handled by L{_HandleUnaryOp}
     - C{_OPTYPE_BINARY}: Callable taking exactly two parameters, the left- and
       right-hand side of the operator, used by L{_HandleBinaryOp}
 
@@ -369,17 +382,19 @@ class _FilterCompilerHelper:
     qlang.OP_AND: (_OPTYPE_LOGIC, compat.all),
 
     # Unary operators
-    qlang.OP_NOT: (_OPTYPE_UNARY, operator.not_),
+    qlang.OP_NOT: (_OPTYPE_UNARY, None),
+    qlang.OP_TRUE: (_OPTYPE_UNARY, None),
 
     # Binary operators
     qlang.OP_EQUAL: (_OPTYPE_BINARY, _EQUALITY_CHECKS),
     qlang.OP_NOT_EQUAL:
-      (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn))
-                        for (flags, fn) in _EQUALITY_CHECKS]),
-    qlang.OP_GLOB: (_OPTYPE_BINARY, NotImplemented),
-    qlang.OP_REGEXP: (_OPTYPE_BINARY, NotImplemented),
+      (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn), valprepfn)
+                        for (flags, fn, valprepfn) in _EQUALITY_CHECKS]),
+    qlang.OP_REGEXP: (_OPTYPE_BINARY, [
+      (None, lambda lhs, rhs: rhs.search(lhs), _PrepareRegex),
+      ]),
     qlang.OP_CONTAINS: (_OPTYPE_BINARY, [
-      (None, operator.contains),
+      (None, operator.contains, None),
       ]),
     }
 
@@ -449,6 +464,15 @@ class _FilterCompilerHelper:
 
     return handler(hints_cb, level, op, op_data, operands)
 
+  def _LookupField(self, name):
+    """Returns a field definition by name.
+
+    """
+    try:
+      return self._fields[name]
+    except KeyError:
+      raise errors.ParameterError("Unknown field '%s'" % name)
+
   def _HandleLogicOp(self, hints_fn, level, op, op_fn, operands):
     """Handles logic operators.
 
@@ -485,6 +509,8 @@ class _FilterCompilerHelper:
     @param operands: List of operands
 
     """
+    assert op_fn is None
+
     if hints_fn:
       hints_fn(op)
 
@@ -492,8 +518,18 @@ class _FilterCompilerHelper:
       raise errors.ParameterError("Unary operator '%s' expects exactly one"
                                   " operand" % op)
 
-    return compat.partial(_WrapUnaryOp, op_fn,
-                          self._Compile(operands[0], level + 1))
+    if op == qlang.OP_TRUE:
+      (_, _, _, retrieval_fn) = self._LookupField(operands[0])
+
+      op_fn = operator.truth
+      arg = retrieval_fn
+    elif op == qlang.OP_NOT:
+      op_fn = operator.not_
+      arg = self._Compile(operands[0], level + 1)
+    else:
+      raise errors.ProgrammerError("Can't handle operator '%s'" % op)
+
+    return compat.partial(_WrapUnaryOp, op_fn, arg)
 
   def _HandleBinaryOp(self, hints_fn, level, op, op_data, operands):
     """Handles binary operators.
@@ -516,10 +552,7 @@ class _FilterCompilerHelper:
       raise errors.ParameterError("Invalid binary operator, expected exactly"
                                   " two operands")
 
-    try:
-      (fdef, datakind, field_flags, retrieval_fn) = self._fields[name]
-    except KeyError:
-      raise errors.ParameterError("Unknown field '%s'" % name)
+    (fdef, datakind, field_flags, retrieval_fn) = self._LookupField(name)
 
     assert fdef.kind != QFT_UNKNOWN
 
@@ -535,8 +568,12 @@ class _FilterCompilerHelper:
     if hints_fn:
       hints_fn(op, datakind, name, value)
 
-    for (fn_flags, fn) in op_data:
+    for (fn_flags, fn, valprepfn) in op_data:
       if fn_flags is None or fn_flags & field_flags:
+        # Prepare value if necessary (e.g. compile regular expression)
+        if valprepfn:
+          value = valprepfn(value)
+
         return compat.partial(_WrapBinaryOp, fn, retrieval_fn, value)
 
     raise errors.ProgrammerError("Unable to find operator implementation"
@@ -861,6 +898,34 @@ def _GetItemAttr(attr):
   return lambda _, item: getter(item)
 
 
+def _ConvWrapInner(convert, fn, ctx, item):
+  """Wrapper for converting values.
+
+  @param convert: Conversion function receiving value as single parameter
+  @param fn: Retrieval function
+
+  """
+  value = fn(ctx, item)
+
+  # Is the value an abnormal status?
+  if compat.any(value is fs for fs in _FS_ALL):
+    # Return right away
+    return value
+
+  # TODO: Should conversion function also receive context, item or both?
+  return convert(value)
+
+
+def _ConvWrap(convert, fn):
+  """Convenience wrapper for L{_ConvWrapInner}.
+
+  @param convert: Conversion function receiving value as single parameter
+  @param fn: Retrieval function
+
+  """
+  return compat.partial(_ConvWrapInner, convert, fn)
+
+
 def _GetItemTimestamp(getter):
   """Returns function for getting timestamp of item.
 
@@ -1194,6 +1259,7 @@ class InstanceQueryData:
     # Used for individual rows
     self.inst_hvparams = None
     self.inst_beparams = None
+    self.inst_osparams = None
     self.inst_nicparams = None
 
   def __iter__(self):
@@ -1206,6 +1272,7 @@ class InstanceQueryData:
     for inst in self.instances:
       self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
       self.inst_beparams = self.cluster.FillBE(inst)
+      self.inst_osparams = self.cluster.SimpleFillOS(inst.os, inst.osparams)
       self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
                              for nic in inst.nics]
 
@@ -1571,11 +1638,14 @@ def _GetInstanceParameterFields():
   fields = [
     # Filled parameters
     (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
-                "Hypervisor parameters"),
+                "Hypervisor parameters (merged)"),
      IQ_CONFIG, 0, lambda ctx, _: ctx.inst_hvparams),
     (_MakeField("beparams", "BackendParameters", QFT_OTHER,
-                "Backend parameters"),
+                "Backend parameters (merged)"),
      IQ_CONFIG, 0, lambda ctx, _: ctx.inst_beparams),
+    (_MakeField("osparams", "OpSysParameters", QFT_OTHER,
+                "Operating system parameters (merged)"),
+     IQ_CONFIG, 0, lambda ctx, _: ctx.inst_osparams),
 
     # Unfilled parameters
     (_MakeField("custom_hvparams", "CustomHypervisorParameters", QFT_OTHER,
@@ -1584,6 +1654,9 @@ def _GetInstanceParameterFields():
     (_MakeField("custom_beparams", "CustomBackendParameters", QFT_OTHER,
                 "Custom backend parameters",),
      IQ_CONFIG, 0, _GetItemAttr("beparams")),
+    (_MakeField("custom_osparams", "CustomOpSysParameters", QFT_OTHER,
+                "Custom operating system parameters",),
+     IQ_CONFIG, 0, _GetItemAttr("osparams")),
     (_MakeField("custom_nicparams", "CustomNicParameters", QFT_OTHER,
                 "Custom network interface parameters"),
      IQ_CONFIG, 0, lambda ctx, inst: [nic.nicparams for nic in inst.nics]),
@@ -1834,11 +1907,63 @@ def _BuildGroupFields():
      GQ_INST, 0, _GetSortedList(group_to_instances)),
     ])
 
+  # Other fields
+  fields.extend([
+    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), GQ_CONFIG, 0,
+     lambda ctx, group: list(group.GetTags())),
+    ])
+
   fields.extend(_GetItemTimestampFields(GQ_CONFIG))
 
   return _PrepareFieldList(fields, [])
 
 
+class OsInfo(objects.ConfigObject):
+  __slots__ = [
+    "name",
+    "valid",
+    "hidden",
+    "blacklisted",
+    "variants",
+    "api_versions",
+    "parameters",
+    "node_status",
+    ]
+
+
+def _BuildOsFields():
+  """Builds list of fields for operating system queries.
+
+  """
+  fields = [
+    (_MakeField("name", "Name", QFT_TEXT, "Operating system name"),
+     None, 0, _GetItemAttr("name")),
+    (_MakeField("valid", "Valid", QFT_BOOL,
+                "Whether operating system definition is valid"),
+     None, 0, _GetItemAttr("valid")),
+    (_MakeField("hidden", "Hidden", QFT_BOOL,
+                "Whether operating system is hidden"),
+     None, 0, _GetItemAttr("hidden")),
+    (_MakeField("blacklisted", "Blacklisted", QFT_BOOL,
+                "Whether operating system is blacklisted"),
+     None, 0, _GetItemAttr("blacklisted")),
+    (_MakeField("variants", "Variants", QFT_OTHER,
+                "Operating system variants"),
+     None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("variants"))),
+    (_MakeField("api_versions", "ApiVersions", QFT_OTHER,
+                "Operating system API versions"),
+     None, 0, _ConvWrap(sorted, _GetItemAttr("api_versions"))),
+    (_MakeField("parameters", "Parameters", QFT_OTHER,
+                "Operating system parameters"),
+     None, 0, _ConvWrap(utils.NiceSort, _GetItemAttr("parameters"))),
+    (_MakeField("node_status", "NodeStatus", QFT_OTHER,
+                "Status from node"),
+     None, 0, _GetItemAttr("node_status")),
+    ]
+
+  return _PrepareFieldList(fields, [])
+
+
 #: Fields available for node queries
 NODE_FIELDS = _BuildNodeFields()
 
@@ -1851,12 +1976,16 @@ LOCK_FIELDS = _BuildLockFields()
 #: Fields available for node group queries
 GROUP_FIELDS = _BuildGroupFields()
 
+#: Fields available for operating system queries
+OS_FIELDS = _BuildOsFields()
+
 #: All available resources
 ALL_FIELDS = {
   constants.QR_INSTANCE: INSTANCE_FIELDS,
   constants.QR_NODE: NODE_FIELDS,
   constants.QR_LOCK: LOCK_FIELDS,
   constants.QR_GROUP: GROUP_FIELDS,
+  constants.QR_OS: OS_FIELDS,
   }
 
 #: All available field lists