rlib2: Convert /2/instances/[inst]/deactivate-disks to OpcodeResource
[ganeti-local] / lib / query.py
index 7c74933..ae7da5a 100644 (file)
@@ -83,7 +83,8 @@ from ganeti.constants import (QFT_UNKNOWN, QFT_TEXT, QFT_BOOL, QFT_NUMBER,
 (IQ_CONFIG,
  IQ_LIVE,
  IQ_DISKUSAGE,
- IQ_CONSOLE) = range(100, 104)
+ IQ_CONSOLE,
+ IQ_NODES) = range(100, 105)
 
 (LQ_MODE,
  LQ_OWNER,
@@ -110,7 +111,7 @@ _VERIFY_FN = {
   QFT_BOOL: ht.TBool,
   QFT_NUMBER: ht.TInt,
   QFT_UNIT: ht.TInt,
-  QFT_TIMESTAMP: ht.TOr(ht.TInt, ht.TFloat),
+  QFT_TIMESTAMP: ht.TNumber,
   QFT_OTHER: lambda _: True,
   }
 
@@ -120,6 +121,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 +323,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 +360,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
@@ -374,12 +389,13 @@ class _FilterCompilerHelper:
     # 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),
       ]),
     }
 
@@ -553,8 +569,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"
@@ -879,6 +899,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.
 
@@ -1176,7 +1224,7 @@ class InstanceQueryData:
 
   """
   def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
-               live_data, wrongnode_inst, console):
+               live_data, wrongnode_inst, console, nodes, groups):
     """Initializes this class.
 
     @param instances: List of instance objects
@@ -1193,6 +1241,8 @@ class InstanceQueryData:
     @param wrongnode_inst: Set of instances running on wrong node(s)
     @type console: dict; instance name as key
     @param console: Per-instance console information
+    @type nodes: dict; node name as key
+    @param nodes: Node objects
 
     """
     assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
@@ -1208,10 +1258,13 @@ class InstanceQueryData:
     self.live_data = live_data
     self.wrongnode_inst = wrongnode_inst
     self.console = console
+    self.nodes = nodes
+    self.groups = groups
 
     # Used for individual rows
     self.inst_hvparams = None
     self.inst_beparams = None
+    self.inst_osparams = None
     self.inst_nicparams = None
 
   def __iter__(self):
@@ -1224,6 +1277,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]
 
@@ -1589,11 +1643,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,
@@ -1602,6 +1659,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]),
@@ -1646,6 +1706,45 @@ _INST_SIMPLE_FIELDS = {
   }
 
 
+def _GetInstNodeGroup(ctx, default, node_name):
+  """Gets group UUID of an instance node.
+
+  @type ctx: L{InstanceQueryData}
+  @param default: Default value
+  @type node_name: string
+  @param node_name: Node name
+
+  """
+  try:
+    node = ctx.nodes[node_name]
+  except KeyError:
+    return default
+  else:
+    return node.group
+
+
+def _GetInstNodeGroupName(ctx, default, node_name):
+  """Gets group name of an instance node.
+
+  @type ctx: L{InstanceQueryData}
+  @param default: Default value
+  @type node_name: string
+  @param node_name: Node name
+
+  """
+  try:
+    node = ctx.nodes[node_name]
+  except KeyError:
+    return default
+
+  try:
+    group = ctx.groups[node.group]
+  except KeyError:
+    return default
+
+  return group.name
+
+
 def _BuildInstanceFields():
   """Builds list of fields for instance queries.
 
@@ -1653,10 +1752,29 @@ def _BuildInstanceFields():
   fields = [
     (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
      IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")),
+    (_MakeField("pnode.group", "PrimaryNodeGroup", QFT_TEXT,
+                "Primary node's group"),
+     IQ_NODES, 0,
+     lambda ctx, inst: _GetInstNodeGroupName(ctx, _FS_UNAVAIL,
+                                             inst.primary_node)),
+    (_MakeField("pnode.group.uuid", "PrimaryNodeGroupUUID", QFT_TEXT,
+                "Primary node's group UUID"),
+     IQ_NODES, 0,
+     lambda ctx, inst: _GetInstNodeGroup(ctx, _FS_UNAVAIL, inst.primary_node)),
     # TODO: Allow filtering by secondary node as hostname
     (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
                 "Secondary nodes; usually this will just be one node"),
      IQ_CONFIG, 0, lambda ctx, inst: list(inst.secondary_nodes)),
+    (_MakeField("snodes.group", "SecondaryNodesGroups", QFT_OTHER,
+                "Node groups of secondary nodes"),
+     IQ_NODES, 0,
+     lambda ctx, inst: map(compat.partial(_GetInstNodeGroupName, ctx, None),
+                           inst.secondary_nodes)),
+    (_MakeField("snodes.group.uuid", "SecondaryNodesGroupsUUID", QFT_OTHER,
+                "Node group UUIDs of secondary nodes"),
+     IQ_NODES, 0,
+     lambda ctx, inst: map(compat.partial(_GetInstNodeGroup, ctx, None),
+                           inst.secondary_nodes)),
     (_MakeField("admin_state", "Autostart", QFT_BOOL,
                 "Desired state of instance (if set, the instance should be"
                 " up)"),
@@ -1852,11 +1970,64 @@ 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(compat.partial(utils.NiceSort, key=compat.fst),
+                        _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()
 
@@ -1869,12 +2040,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