Merge branch 'stable-2.9' into stable-2.10
[ganeti-local] / lib / query.py
index ffd4d20..58278d5 100644 (file)
@@ -1,7 +1,7 @@
 #
 #
 
 #
 #
 
-# Copyright (C) 2010, 2011, 2012 Google Inc.
+# Copyright (C) 2010, 2011, 2012, 2013 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -64,12 +64,17 @@ from ganeti import objects
 from ganeti import ht
 from ganeti import runtime
 from ganeti import qlang
 from ganeti import ht
 from ganeti import runtime
 from ganeti import qlang
+from ganeti import jstore
 
 from ganeti.constants import (QFT_UNKNOWN, QFT_TEXT, QFT_BOOL, QFT_NUMBER,
                               QFT_UNIT, QFT_TIMESTAMP, QFT_OTHER,
                               RS_NORMAL, RS_UNKNOWN, RS_NODATA,
                               RS_UNAVAIL, RS_OFFLINE)
 
 
 from ganeti.constants import (QFT_UNKNOWN, QFT_TEXT, QFT_BOOL, QFT_NUMBER,
                               QFT_UNIT, QFT_TIMESTAMP, QFT_OTHER,
                               RS_NORMAL, RS_UNKNOWN, RS_NODATA,
                               RS_UNAVAIL, RS_OFFLINE)
 
+(NETQ_CONFIG,
+ NETQ_GROUP,
+ NETQ_STATS,
+ NETQ_INST) = range(300, 304)
 
 # Constants for requesting data from the caller/data provider. Each property
 # collected/computed separately by the data provider should have its own to
 
 # Constants for requesting data from the caller/data provider. Each property
 # collected/computed separately by the data provider should have its own to
@@ -85,7 +90,8 @@ from ganeti.constants import (QFT_UNKNOWN, QFT_TEXT, QFT_BOOL, QFT_NUMBER,
  IQ_LIVE,
  IQ_DISKUSAGE,
  IQ_CONSOLE,
  IQ_LIVE,
  IQ_DISKUSAGE,
  IQ_CONSOLE,
- IQ_NODES) = range(100, 105)
+ IQ_NODES,
+ IQ_NETWORKS) = range(100, 106)
 
 (LQ_MODE,
  LQ_OWNER,
 
 (LQ_MODE,
  LQ_OWNER,
@@ -100,11 +106,15 @@ from ganeti.constants import (QFT_UNKNOWN, QFT_TEXT, QFT_BOOL, QFT_NUMBER,
  CQ_QUEUE_DRAINED,
  CQ_WATCHER_PAUSE) = range(300, 303)
 
  CQ_QUEUE_DRAINED,
  CQ_WATCHER_PAUSE) = range(300, 303)
 
+(JQ_ARCHIVED, ) = range(400, 401)
+
 # Query field flags
 QFF_HOSTNAME = 0x01
 QFF_IP_ADDRESS = 0x02
 # Query field flags
 QFF_HOSTNAME = 0x01
 QFF_IP_ADDRESS = 0x02
-# Next values: 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200
-QFF_ALL = (QFF_HOSTNAME | QFF_IP_ADDRESS)
+QFF_JOB_ID = 0x04
+QFF_SPLIT_TIMESTAMP = 0x08
+# Next values: 0x10, 0x20, 0x40, 0x80, 0x100, 0x200
+QFF_ALL = (QFF_HOSTNAME | QFF_IP_ADDRESS | QFF_JOB_ID | QFF_SPLIT_TIMESTAMP)
 
 FIELD_NAME_RE = re.compile(r"^[a-z0-9/._]+$")
 TITLE_RE = re.compile(r"^[^\s]+$")
 
 FIELD_NAME_RE = re.compile(r"^[a-z0-9/._]+$")
 TITLE_RE = re.compile(r"^[^\s]+$")
@@ -128,7 +138,12 @@ _FS_UNAVAIL = object()
 _FS_OFFLINE = object()
 
 #: List of all special status
 _FS_OFFLINE = object()
 
 #: List of all special status
-_FS_ALL = frozenset([_FS_UNKNOWN, _FS_NODATA, _FS_UNAVAIL, _FS_OFFLINE])
+_FS_ALL = compat.UniqueFrozenset([
+  _FS_UNKNOWN,
+  _FS_NODATA,
+  _FS_UNAVAIL,
+  _FS_OFFLINE,
+  ])
 
 #: VType to QFT mapping
 _VTToQFT = {
 
 #: VType to QFT mapping
 _VTToQFT = {
@@ -142,12 +157,6 @@ _VTToQFT = {
 
 _SERIAL_NO_DOC = "%s object serial number, incremented on each modification"
 
 
 _SERIAL_NO_DOC = "%s object serial number, incremented on each modification"
 
-# TODO: Consider moving titles closer to constants
-NDP_TITLE = {
-  constants.ND_OOB_PROGRAM: "OutOfBandProgram",
-  constants.ND_SPINDLE_COUNT: "SpindleCount",
-  }
-
 
 def _GetUnknownField(ctx, item): # pylint: disable=W0613
   """Gets the contents of an unknown field.
 
 def _GetUnknownField(ctx, item): # pylint: disable=W0613
   """Gets the contents of an unknown field.
@@ -272,13 +281,16 @@ class _FilterHints:
     if op != qlang.OP_OR:
       self._NeedAllNames()
 
     if op != qlang.OP_OR:
       self._NeedAllNames()
 
-  def NoteUnaryOp(self, op): # pylint: disable=W0613
+  def NoteUnaryOp(self, op, datakind): # pylint: disable=W0613
     """Called when handling an unary operation.
 
     @type op: string
     @param op: Operator
 
     """
     """Called when handling an unary operation.
 
     @type op: string
     @param op: Operator
 
     """
+    if datakind is not None:
+      self._datakinds.add(datakind)
+
     self._NeedAllNames()
 
   def NoteBinaryOp(self, op, datakind, name, value):
     self._NeedAllNames()
 
   def NoteBinaryOp(self, op, datakind, name, value):
@@ -345,6 +357,36 @@ def _PrepareRegex(pattern):
     raise errors.ParameterError("Invalid regex pattern (%s)" % err)
 
 
     raise errors.ParameterError("Invalid regex pattern (%s)" % err)
 
 
+def _PrepareSplitTimestamp(value):
+  """Prepares a value for comparison by L{_MakeSplitTimestampComparison}.
+
+  """
+  if ht.TNumber(value):
+    return value
+  else:
+    return utils.MergeTime(value)
+
+
+def _MakeSplitTimestampComparison(fn):
+  """Compares split timestamp values after converting to float.
+
+  """
+  return lambda lhs, rhs: fn(utils.MergeTime(lhs), rhs)
+
+
+def _MakeComparisonChecks(fn):
+  """Prepares flag-specific comparisons using a comparison function.
+
+  """
+  return [
+    (QFF_SPLIT_TIMESTAMP, _MakeSplitTimestampComparison(fn),
+     _PrepareSplitTimestamp),
+    (QFF_JOB_ID, lambda lhs, rhs: fn(jstore.ParseJobId(lhs), rhs),
+     jstore.ParseJobId),
+    (None, fn, None),
+    ]
+
+
 class _FilterCompilerHelper:
   """Converts a query filter to a callable usable for filtering.
 
 class _FilterCompilerHelper:
   """Converts a query filter to a callable usable for filtering.
 
@@ -363,7 +405,7 @@ class _FilterCompilerHelper:
 
   List of tuples containing flags and a callable receiving the left- and
   right-hand side of the operator. The flags are an OR-ed value of C{QFF_*}
 
   List of tuples containing flags and a callable receiving the left- and
   right-hand side of the operator. The flags are an OR-ed value of C{QFF_*}
-  (e.g. L{QFF_HOSTNAME}).
+  (e.g. L{QFF_HOSTNAME} or L{QFF_SPLIT_TIMESTAMP}).
 
   Order matters. The first item with flags will be used. Flags are checked
   using binary AND.
 
   Order matters. The first item with flags will be used. Flags are checked
   using binary AND.
@@ -374,6 +416,8 @@ class _FilterCompilerHelper:
      lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
                                                case_sensitive=False),
      None),
      lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
                                                case_sensitive=False),
      None),
+    (QFF_SPLIT_TIMESTAMP, _MakeSplitTimestampComparison(operator.eq),
+     _PrepareSplitTimestamp),
     (None, operator.eq, None),
     ]
 
     (None, operator.eq, None),
     ]
 
@@ -403,6 +447,10 @@ class _FilterCompilerHelper:
     qlang.OP_NOT_EQUAL:
       (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn), valprepfn)
                         for (flags, fn, valprepfn) in _EQUALITY_CHECKS]),
     qlang.OP_NOT_EQUAL:
       (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn), valprepfn)
                         for (flags, fn, valprepfn) in _EQUALITY_CHECKS]),
+    qlang.OP_LT: (_OPTYPE_BINARY, _MakeComparisonChecks(operator.lt)),
+    qlang.OP_LE: (_OPTYPE_BINARY, _MakeComparisonChecks(operator.le)),
+    qlang.OP_GT: (_OPTYPE_BINARY, _MakeComparisonChecks(operator.gt)),
+    qlang.OP_GE: (_OPTYPE_BINARY, _MakeComparisonChecks(operator.ge)),
     qlang.OP_REGEXP: (_OPTYPE_BINARY, [
       (None, lambda lhs, rhs: rhs.search(lhs), _PrepareRegex),
       ]),
     qlang.OP_REGEXP: (_OPTYPE_BINARY, [
       (None, lambda lhs, rhs: rhs.search(lhs), _PrepareRegex),
       ]),
@@ -524,19 +572,22 @@ class _FilterCompilerHelper:
     """
     assert op_fn is None
 
     """
     assert op_fn is None
 
-    if hints_fn:
-      hints_fn(op)
-
     if len(operands) != 1:
       raise errors.ParameterError("Unary operator '%s' expects exactly one"
                                   " operand" % op)
 
     if op == qlang.OP_TRUE:
     if len(operands) != 1:
       raise errors.ParameterError("Unary operator '%s' expects exactly one"
                                   " operand" % op)
 
     if op == qlang.OP_TRUE:
-      (_, _, _, retrieval_fn) = self._LookupField(operands[0])
+      (_, datakind, _, retrieval_fn) = self._LookupField(operands[0])
+
+      if hints_fn:
+        hints_fn(op, datakind)
 
       op_fn = operator.truth
       arg = retrieval_fn
     elif op == qlang.OP_NOT:
 
       op_fn = operator.truth
       arg = retrieval_fn
     elif op == qlang.OP_NOT:
+      if hints_fn:
+        hints_fn(op, None)
+
       op_fn = operator.not_
       arg = self._Compile(operands[0], level + 1)
     else:
       op_fn = operator.not_
       arg = self._Compile(operands[0], level + 1)
     else:
@@ -707,6 +758,7 @@ class Query:
         (status, name) = _ProcessResult(self._name_fn(ctx, item))
         assert status == constants.RS_NORMAL
         # TODO: Are there cases where we wouldn't want to use NiceSort?
         (status, name) = _ProcessResult(self._name_fn(ctx, item))
         assert status == constants.RS_NORMAL
         # TODO: Are there cases where we wouldn't want to use NiceSort?
+        # Answer: if the name field is non-string...
         result.append((utils.NiceSortKey(name), idx, row))
       else:
         result.append(row)
         result.append((utils.NiceSortKey(name), idx, row))
       else:
         result.append(row)
@@ -925,6 +977,23 @@ def _GetItemAttr(attr):
   return lambda _, item: getter(item)
 
 
   return lambda _, item: getter(item)
 
 
+def _GetItemMaybeAttr(attr):
+  """Returns a field function to return a not-None attribute of the item.
+
+  If the value is None, then C{_FS_UNAVAIL} will be returned instead.
+
+  @param attr: Attribute name
+
+  """
+  def _helper(_, obj):
+    val = getattr(obj, attr)
+    if val is None:
+      return _FS_UNAVAIL
+    else:
+      return val
+  return _helper
+
+
 def _GetNDParam(name):
   """Return a field function to return an ND parameter out of the context.
 
 def _GetNDParam(name):
   """Return a field function to return an ND parameter out of the context.
 
@@ -947,7 +1016,9 @@ def _BuildNDFields(is_group):
     field_kind = GQ_CONFIG
   else:
     field_kind = NQ_GROUP
     field_kind = GQ_CONFIG
   else:
     field_kind = NQ_GROUP
-  return [(_MakeField("ndp/%s" % name, NDP_TITLE.get(name, "ndp/%s" % name),
+  return [(_MakeField("ndp/%s" % name,
+                      constants.NDS_PARAMETER_TITLES.get(name,
+                                                         "ndp/%s" % name),
                       _VTToQFT[kind], "The \"%s\" node parameter" % name),
            field_kind, 0, _GetNDParam(name))
           for name, kind in constants.NDS_PARAMETER_TYPES.items()]
                       _VTToQFT[kind], "The \"%s\" node parameter" % name),
            field_kind, 0, _GetNDParam(name))
           for name, kind in constants.NDS_PARAMETER_TYPES.items()]
@@ -1020,16 +1091,18 @@ class NodeQueryData:
   """Data container for node data queries.
 
   """
   """Data container for node data queries.
 
   """
-  def __init__(self, nodes, live_data, master_name, node_to_primary,
-               node_to_secondary, groups, oob_support, cluster):
+  def __init__(self, nodes, live_data, master_uuid, node_to_primary,
+               node_to_secondary, inst_uuid_to_inst_name, groups, oob_support,
+               cluster):
     """Initializes this class.
 
     """
     self.nodes = nodes
     self.live_data = live_data
     """Initializes this class.
 
     """
     self.nodes = nodes
     self.live_data = live_data
-    self.master_name = master_name
+    self.master_uuid = master_uuid
     self.node_to_primary = node_to_primary
     self.node_to_secondary = node_to_secondary
     self.node_to_primary = node_to_primary
     self.node_to_secondary = node_to_secondary
+    self.inst_uuid_to_inst_name = inst_uuid_to_inst_name
     self.groups = groups
     self.oob_support = oob_support
     self.cluster = cluster
     self.groups = groups
     self.oob_support = oob_support
     self.cluster = cluster
@@ -1052,7 +1125,7 @@ class NodeQueryData:
       else:
         self.ndparams = self.cluster.FillND(node, group)
       if self.live_data:
       else:
         self.ndparams = self.cluster.FillND(node, group)
       if self.live_data:
-        self.curlive_data = self.live_data.get(node.name, None)
+        self.curlive_data = self.live_data.get(node.uuid, None)
       else:
         self.curlive_data = None
       yield node
       else:
         self.curlive_data = None
       yield node
@@ -1081,14 +1154,20 @@ _NODE_LIVE_FIELDS = {
              " for detecting reboots by tracking changes"),
   "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes",
              "Number of NUMA domains on node (if exported by hypervisor)"),
              " for detecting reboots by tracking changes"),
   "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes",
              "Number of NUMA domains on node (if exported by hypervisor)"),
+  "cnos": ("CNOs", QFT_NUMBER, "cpu_dom0",
+            "Number of logical processors used by the node OS (dom0 for Xen)"),
   "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets",
                "Number of physical CPU sockets (if exported by hypervisor)"),
   "ctotal": ("CTotal", QFT_NUMBER, "cpu_total", "Number of logical processors"),
   "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets",
                "Number of physical CPU sockets (if exported by hypervisor)"),
   "ctotal": ("CTotal", QFT_NUMBER, "cpu_total", "Number of logical processors"),
-  "dfree": ("DFree", QFT_UNIT, "vg_free",
-            "Available disk space in volume group"),
-  "dtotal": ("DTotal", QFT_UNIT, "vg_size",
-             "Total disk space in volume group used for instance disk"
+  "dfree": ("DFree", QFT_UNIT, "storage_free",
+            "Available storage space in storage unit"),
+  "dtotal": ("DTotal", QFT_UNIT, "storage_size",
+             "Total storage space in storage unit used for instance disk"
              " allocation"),
              " allocation"),
+  "spfree": ("SpFree", QFT_NUMBER, "spindles_free",
+             "Available spindles in volume group (exclusive storage only)"),
+  "sptotal": ("SpTotal", QFT_NUMBER, "spindles_total",
+              "Total spindles in volume group (exclusive storage only)"),
   "mfree": ("MFree", QFT_UNIT, "memory_free",
             "Memory available for instance allocations"),
   "mnode": ("MNode", QFT_UNIT, "memory_dom0",
   "mfree": ("MFree", QFT_UNIT, "memory_free",
             "Memory available for instance allocations"),
   "mnode": ("MNode", QFT_UNIT, "memory_dom0",
@@ -1143,7 +1222,7 @@ def _GetNodePower(ctx, node):
   @param node: Node object
 
   """
   @param node: Node object
 
   """
-  if ctx.oob_support[node.name]:
+  if ctx.oob_support[node.uuid]:
     return node.powered
 
   return _FS_UNAVAIL
     return node.powered
 
   return _FS_UNAVAIL
@@ -1181,8 +1260,24 @@ def _GetLiveNodeField(field, kind, ctx, node):
   if not ctx.curlive_data:
     return _FS_NODATA
 
   if not ctx.curlive_data:
     return _FS_NODATA
 
+  return _GetStatsField(field, kind, ctx.curlive_data)
+
+
+def _GetStatsField(field, kind, data):
+  """Gets a value from live statistics.
+
+  If the value is not found, L{_FS_UNAVAIL} is returned. If the field kind is
+  numeric a conversion to integer is attempted. If that fails, L{_FS_UNAVAIL}
+  is returned.
+
+  @param field: Live field name
+  @param kind: Data kind, one of L{constants.QFT_ALL}
+  @type data: dict
+  @param data: Statistics
+
+  """
   try:
   try:
-    value = ctx.curlive_data[field]
+    value = data[field]
   except KeyError:
     return _FS_UNAVAIL
 
   except KeyError:
     return _FS_UNAVAIL
 
@@ -1196,7 +1291,7 @@ def _GetLiveNodeField(field, kind, ctx, node):
     return int(value)
   except (ValueError, TypeError):
     logging.exception("Failed to convert node field '%s' (value %r) to int",
     return int(value)
   except (ValueError, TypeError):
     logging.exception("Failed to convert node field '%s' (value %r) to int",
-                      value, field)
+                      field, value)
     return _FS_UNAVAIL
 
 
     return _FS_UNAVAIL
 
 
@@ -1238,7 +1333,7 @@ def _BuildNodeFields():
     (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0,
      lambda ctx, node: list(node.GetTags())),
     (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"),
     (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0,
      lambda ctx, node: list(node.GetTags())),
     (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"),
-     NQ_CONFIG, 0, lambda ctx, node: node.name == ctx.master_name),
+     NQ_CONFIG, 0, lambda ctx, node: node.uuid == ctx.master_uuid),
     (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0,
      _GetGroup(_GetNodeGroup)),
     (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"),
     (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0,
      _GetGroup(_GetNodeGroup)),
     (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"),
@@ -1265,17 +1360,19 @@ def _BuildNodeFields():
                  constants.NR_REGULAR, constants.NR_DRAINED,
                  constants.NR_OFFLINE)
   role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate,"
                  constants.NR_REGULAR, constants.NR_DRAINED,
                  constants.NR_OFFLINE)
   role_doc = ("Node role; \"%s\" for master, \"%s\" for master candidate,"
-              " \"%s\" for regular, \"%s\" for a drained, \"%s\" for offline" %
+              " \"%s\" for regular, \"%s\" for drained, \"%s\" for offline" %
               role_values)
   fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
               role_values)
   fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
-                 lambda ctx, node: _GetNodeRole(node, ctx.master_name)))
+                 lambda ctx, node: _GetNodeRole(node, ctx.master_uuid)))
   assert set(role_values) == constants.NR_ALL
 
   def _GetLength(getter):
   assert set(role_values) == constants.NR_ALL
 
   def _GetLength(getter):
-    return lambda ctx, node: len(getter(ctx)[node.name])
+    return lambda ctx, node: len(getter(ctx)[node.uuid])
 
   def _GetList(getter):
 
   def _GetList(getter):
-    return lambda ctx, node: list(getter(ctx)[node.name])
+    return lambda ctx, node: utils.NiceSort(
+                               [ctx.inst_uuid_to_inst_name[uuid]
+                                for uuid in getter(ctx)[node.uuid]])
 
   # Add fields operating on instance lists
   for prefix, titleprefix, docword, getter in \
 
   # Add fields operating on instance lists
   for prefix, titleprefix, docword, getter in \
@@ -1295,15 +1392,13 @@ def _BuildNodeFields():
   # Add simple fields
   fields.extend([
     (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
   # Add simple fields
   fields.extend([
     (_MakeField(name, title, kind, doc), NQ_CONFIG, flags, _GetItemAttr(name))
-    for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()
-    ])
+    for (name, (title, kind, flags, doc)) in _NODE_SIMPLE_FIELDS.items()])
 
   # Add fields requiring live data
   fields.extend([
     (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
      compat.partial(_GetLiveNodeField, nfield, kind))
 
   # Add fields requiring live data
   fields.extend([
     (_MakeField(name, title, kind, doc), NQ_LIVE, 0,
      compat.partial(_GetLiveNodeField, nfield, kind))
-    for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()
-    ])
+    for (name, (title, kind, nfield, doc)) in _NODE_LIVE_FIELDS.items()])
 
   # Add timestamps
   fields.extend(_GetItemTimestampFields(NQ_CONFIG))
 
   # Add timestamps
   fields.extend(_GetItemTimestampFields(NQ_CONFIG))
@@ -1315,43 +1410,48 @@ class InstanceQueryData:
   """Data container for instance data queries.
 
   """
   """Data container for instance data queries.
 
   """
-  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
-               live_data, wrongnode_inst, console, nodes, groups):
+  def __init__(self, instances, cluster, disk_usage, offline_node_uuids,
+               bad_node_uuids, live_data, wrongnode_inst, console, nodes,
+               groups, networks):
     """Initializes this class.
 
     @param instances: List of instance objects
     @param cluster: Cluster object
     """Initializes this class.
 
     @param instances: List of instance objects
     @param cluster: Cluster object
-    @type disk_usage: dict; instance name as key
+    @type disk_usage: dict; instance UUID as key
     @param disk_usage: Per-instance disk usage
     @param disk_usage: Per-instance disk usage
-    @type offline_nodes: list of strings
-    @param offline_nodes: List of offline nodes
-    @type bad_nodes: list of strings
-    @param bad_nodes: List of faulty nodes
-    @type live_data: dict; instance name as key
+    @type offline_node_uuids: list of strings
+    @param offline_node_uuids: List of offline nodes
+    @type bad_node_uuids: list of strings
+    @param bad_node_uuids: List of faulty nodes
+    @type live_data: dict; instance UUID as key
     @param live_data: Per-instance live data
     @type wrongnode_inst: set
     @param wrongnode_inst: Set of instances running on wrong node(s)
     @param live_data: Per-instance live data
     @type wrongnode_inst: set
     @param wrongnode_inst: Set of instances running on wrong node(s)
-    @type console: dict; instance name as key
+    @type console: dict; instance UUID as key
     @param console: Per-instance console information
     @param console: Per-instance console information
-    @type nodes: dict; node name as key
+    @type nodes: dict; node UUID as key
     @param nodes: Node objects
     @param nodes: Node objects
+    @type networks: dict; net_uuid as key
+    @param networks: Network objects
 
     """
 
     """
-    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
+    assert len(set(bad_node_uuids) & set(offline_node_uuids)) == \
+           len(offline_node_uuids), \
            "Offline nodes not included in bad nodes"
            "Offline nodes not included in bad nodes"
-    assert not (set(live_data.keys()) & set(bad_nodes)), \
+    assert not (set(live_data.keys()) & set(bad_node_uuids)), \
            "Found live data for bad or offline nodes"
 
     self.instances = instances
     self.cluster = cluster
     self.disk_usage = disk_usage
            "Found live data for bad or offline nodes"
 
     self.instances = instances
     self.cluster = cluster
     self.disk_usage = disk_usage
-    self.offline_nodes = offline_nodes
-    self.bad_nodes = bad_nodes
+    self.offline_nodes = offline_node_uuids
+    self.bad_nodes = bad_node_uuids
     self.live_data = live_data
     self.wrongnode_inst = wrongnode_inst
     self.console = console
     self.nodes = nodes
     self.groups = groups
     self.live_data = live_data
     self.wrongnode_inst = wrongnode_inst
     self.console = console
     self.nodes = nodes
     self.groups = groups
+    self.networks = networks
 
     # Used for individual rows
     self.inst_hvparams = None
 
     # Used for individual rows
     self.inst_hvparams = None
@@ -1389,7 +1489,7 @@ def _GetInstOperState(ctx, inst):
   if inst.primary_node in ctx.bad_nodes:
     return _FS_NODATA
   else:
   if inst.primary_node in ctx.bad_nodes:
     return _FS_NODATA
   else:
-    return bool(ctx.live_data.get(inst.name))
+    return bool(ctx.live_data.get(inst.uuid))
 
 
 def _GetInstLiveData(name):
 
 
 def _GetInstLiveData(name):
@@ -1413,8 +1513,8 @@ def _GetInstLiveData(name):
       # offline when we actually don't know due to missing data
       return _FS_NODATA
 
       # offline when we actually don't know due to missing data
       return _FS_NODATA
 
-    if inst.name in ctx.live_data:
-      data = ctx.live_data[inst.name]
+    if inst.uuid in ctx.live_data:
+      data = ctx.live_data[inst.uuid]
       if name in data:
         return data[name]
 
       if name in data:
         return data[name]
 
@@ -1437,8 +1537,8 @@ def _GetInstStatus(ctx, inst):
   if inst.primary_node in ctx.bad_nodes:
     return constants.INSTST_NODEDOWN
 
   if inst.primary_node in ctx.bad_nodes:
     return constants.INSTST_NODEDOWN
 
-  if bool(ctx.live_data.get(inst.name)):
-    if inst.name in ctx.wrongnode_inst:
+  if bool(ctx.live_data.get(inst.uuid)):
+    if inst.uuid in ctx.wrongnode_inst:
       return constants.INSTST_WRONGNODE
     elif inst.admin_state == constants.ADMINST_UP:
       return constants.INSTST_RUNNING
       return constants.INSTST_WRONGNODE
     elif inst.admin_state == constants.ADMINST_UP:
       return constants.INSTST_RUNNING
@@ -1453,28 +1553,88 @@ def _GetInstStatus(ctx, inst):
   return constants.INSTST_ADMINOFFLINE
 
 
   return constants.INSTST_ADMINOFFLINE
 
 
-def _GetInstDiskSize(index):
-  """Build function for retrieving disk size.
+def _GetInstDisk(index, cb):
+  """Build function for calling another function with an instance Disk.
 
   @type index: int
   @param index: Disk index
 
   @type index: int
   @param index: Disk index
+  @type cb: callable
+  @param cb: Callback
 
   """
 
   """
-  def fn(_, inst):
-    """Get size of a disk.
+  def fn(ctx, inst):
+    """Call helper function with instance Disk.
 
 
+    @type ctx: L{InstanceQueryData}
     @type inst: L{objects.Instance}
     @param inst: Instance object
 
     """
     try:
     @type inst: L{objects.Instance}
     @param inst: Instance object
 
     """
     try:
-      return inst.disks[index].size
+      nic = inst.disks[index]
     except IndexError:
       return _FS_UNAVAIL
 
     except IndexError:
       return _FS_UNAVAIL
 
+    return cb(ctx, index, nic)
+
   return fn
 
 
   return fn
 
 
+def _GetInstDiskSize(ctx, _, disk): # pylint: disable=W0613
+  """Get a Disk's size.
+
+  @type ctx: L{InstanceQueryData}
+  @type disk: L{objects.Disk}
+  @param disk: The Disk object
+
+  """
+  if disk.size is None:
+    return _FS_UNAVAIL
+  else:
+    return disk.size
+
+
+def _GetInstDiskSpindles(ctx, _, disk): # pylint: disable=W0613
+  """Get a Disk's spindles.
+
+  @type disk: L{objects.Disk}
+  @param disk: The Disk object
+
+  """
+  if disk.spindles is None:
+    return _FS_UNAVAIL
+  else:
+    return disk.spindles
+
+
+def _GetInstDeviceName(ctx, _, device): # pylint: disable=W0613
+  """Get a Device's Name.
+
+  @type ctx: L{InstanceQueryData}
+  @type device: L{objects.NIC} or L{objects.Disk}
+  @param device: The NIC or Disk object
+
+  """
+  if device.name is None:
+    return _FS_UNAVAIL
+  else:
+    return device.name
+
+
+def _GetInstDeviceUUID(ctx, _, device): # pylint: disable=W0613
+  """Get a Device's UUID.
+
+  @type ctx: L{InstanceQueryData}
+  @type device: L{objects.NIC} or L{objects.Disk}
+  @param device: The NIC or Disk object
+
+  """
+  if device.uuid is None:
+    return _FS_UNAVAIL
+  else:
+    return device.uuid
+
+
 def _GetInstNic(index, cb):
   """Build function for calling another function with an instance NIC.
 
 def _GetInstNic(index, cb):
   """Build function for calling another function with an instance NIC.
 
@@ -1502,6 +1662,34 @@ def _GetInstNic(index, cb):
   return fn
 
 
   return fn
 
 
+def _GetInstNicNetworkName(ctx, _, nic): # pylint: disable=W0613
+  """Get a NIC's Network.
+
+  @type ctx: L{InstanceQueryData}
+  @type nic: L{objects.NIC}
+  @param nic: NIC object
+
+  """
+  if nic.network is None:
+    return _FS_UNAVAIL
+  else:
+    return ctx.networks[nic.network].name
+
+
+def _GetInstNicNetwork(ctx, _, nic): # pylint: disable=W0613
+  """Get a NIC's Network.
+
+  @type ctx: L{InstanceQueryData}
+  @type nic: L{objects.NIC}
+  @param nic: NIC object
+
+  """
+  if nic.network is None:
+    return _FS_UNAVAIL
+  else:
+    return nic.network
+
+
 def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613
   """Get a NIC's IP address.
 
 def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613
   """Get a NIC's IP address.
 
@@ -1534,6 +1722,27 @@ def _GetInstNicBridge(ctx, index, _):
     return _FS_UNAVAIL
 
 
     return _FS_UNAVAIL
 
 
+def _GetInstAllNicNetworkNames(ctx, inst):
+  """Get all network names for an instance.
+
+  @type ctx: L{InstanceQueryData}
+  @type inst: L{objects.Instance}
+  @param inst: Instance object
+
+  """
+  result = []
+
+  for nic in inst.nics:
+    name = None
+    if nic.network:
+      name = ctx.networks[nic.network].name
+    result.append(name)
+
+  assert len(result) == len(inst.nics)
+
+  return result
+
+
 def _GetInstAllNicBridges(ctx, inst):
   """Get all network bridges for an instance.
 
 def _GetInstAllNicBridges(ctx, inst):
   """Get all network bridges for an instance.
 
@@ -1602,6 +1811,12 @@ def _GetInstanceNetworkFields():
     (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
                 "List containing each network interface's IP address"),
      IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
     (_MakeField("nic.ips", "NIC_IPs", QFT_OTHER,
                 "List containing each network interface's IP address"),
      IQ_CONFIG, 0, lambda ctx, inst: [nic.ip for nic in inst.nics]),
+    (_MakeField("nic.names", "NIC_Names", QFT_OTHER,
+                "List containing each network interface's name"),
+     IQ_CONFIG, 0, lambda ctx, inst: [nic.name for nic in inst.nics]),
+    (_MakeField("nic.uuids", "NIC_UUIDs", QFT_OTHER,
+                "List containing each network interface's UUID"),
+     IQ_CONFIG, 0, lambda ctx, inst: [nic.uuid for nic in inst.nics]),
     (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
                 "List containing each network interface's mode"), IQ_CONFIG, 0,
      lambda ctx, inst: [nicp[constants.NIC_MODE]
     (_MakeField("nic.modes", "NIC_modes", QFT_OTHER,
                 "List containing each network interface's mode"), IQ_CONFIG, 0,
      lambda ctx, inst: [nicp[constants.NIC_MODE]
@@ -1613,6 +1828,12 @@ def _GetInstanceNetworkFields():
     (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
                 "List containing each network interface's bridge"),
      IQ_CONFIG, 0, _GetInstAllNicBridges),
     (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
                 "List containing each network interface's bridge"),
      IQ_CONFIG, 0, _GetInstAllNicBridges),
+    (_MakeField("nic.networks", "NIC_networks", QFT_OTHER,
+                "List containing each interface's network"), IQ_CONFIG, 0,
+     lambda ctx, inst: [nic.network for nic in inst.nics]),
+    (_MakeField("nic.networks.names", "NIC_networks_names", QFT_OTHER,
+                "List containing each interface's network"),
+     IQ_NETWORKS, 0, _GetInstAllNicNetworkNames)
     ]
 
   # NICs by number
     ]
 
   # NICs by number
@@ -1625,6 +1846,12 @@ def _GetInstanceNetworkFields():
       (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
                   "MAC address of %s network interface" % numtext),
        IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
       (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, QFT_TEXT,
                   "MAC address of %s network interface" % numtext),
        IQ_CONFIG, 0, _GetInstNic(i, nic_mac_fn)),
+      (_MakeField("nic.name/%s" % i, "NicName/%s" % i, QFT_TEXT,
+                  "Name address of %s network interface" % numtext),
+       IQ_CONFIG, 0, _GetInstNic(i, _GetInstDeviceName)),
+      (_MakeField("nic.uuid/%s" % i, "NicUUID/%s" % i, QFT_TEXT,
+                  "UUID address of %s network interface" % numtext),
+       IQ_CONFIG, 0, _GetInstNic(i, _GetInstDeviceUUID)),
       (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
                   "Mode of %s network interface" % numtext),
        IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
       (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, QFT_TEXT,
                   "Mode of %s network interface" % numtext),
        IQ_CONFIG, 0, _GetInstNic(i, nic_mode_fn)),
@@ -1634,6 +1861,12 @@ def _GetInstanceNetworkFields():
       (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
                   "Bridge of %s network interface" % numtext),
        IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
       (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
                   "Bridge of %s network interface" % numtext),
        IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
+      (_MakeField("nic.network/%s" % i, "NicNetwork/%s" % i, QFT_TEXT,
+                  "Network of %s network interface" % numtext),
+       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicNetwork)),
+      (_MakeField("nic.network.name/%s" % i, "NicNetworkName/%s" % i, QFT_TEXT,
+                  "Network name of %s network interface" % numtext),
+       IQ_NETWORKS, 0, _GetInstNic(i, _GetInstNicNetworkName)),
       ])
 
   aliases = [
       ])
 
   aliases = [
@@ -1643,6 +1876,7 @@ def _GetInstanceNetworkFields():
     ("bridge", "nic.bridge/0"),
     ("nic_mode", "nic.mode/0"),
     ("nic_link", "nic.link/0"),
     ("bridge", "nic.bridge/0"),
     ("nic_mode", "nic.mode/0"),
     ("nic_link", "nic.link/0"),
+    ("nic_network", "nic.network/0"),
     ]
 
   return (fields, aliases)
     ]
 
   return (fields, aliases)
@@ -1656,7 +1890,7 @@ def _GetInstDiskUsage(ctx, inst):
   @param inst: Instance object
 
   """
   @param inst: Instance object
 
   """
-  usage = ctx.disk_usage[inst.name]
+  usage = ctx.disk_usage[inst.uuid]
 
   if usage is None:
     usage = 0
 
   if usage is None:
     usage = 0
@@ -1672,7 +1906,7 @@ def _GetInstanceConsole(ctx, inst):
   @param inst: Instance object
 
   """
   @param inst: Instance object
 
   """
-  consinfo = ctx.console[inst.name]
+  consinfo = ctx.console[inst.uuid]
 
   if consinfo is None:
     return _FS_UNAVAIL
 
   if consinfo is None:
     return _FS_UNAVAIL
@@ -1696,15 +1930,31 @@ def _GetInstanceDiskFields():
      IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
     (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
      IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
      IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
     (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
      IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
+    (_MakeField("disk.spindles", "Disk_spindles", QFT_OTHER,
+                "List of disk spindles"),
+     IQ_CONFIG, 0, lambda ctx, inst: [disk.spindles for disk in inst.disks]),
+    (_MakeField("disk.names", "Disk_names", QFT_OTHER, "List of disk names"),
+     IQ_CONFIG, 0, lambda ctx, inst: [disk.name for disk in inst.disks]),
+    (_MakeField("disk.uuids", "Disk_UUIDs", QFT_OTHER, "List of disk UUIDs"),
+     IQ_CONFIG, 0, lambda ctx, inst: [disk.uuid for disk in inst.disks]),
     ]
 
   # Disks by number
     ]
 
   # Disks by number
-  fields.extend([
-    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
-                "Disk size of %s disk" % utils.FormatOrdinal(i + 1)),
-     IQ_CONFIG, 0, _GetInstDiskSize(i))
-    for i in range(constants.MAX_DISKS)
-    ])
+  for i in range(constants.MAX_DISKS):
+    numtext = utils.FormatOrdinal(i + 1)
+    fields.extend([
+        (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
+                    "Disk size of %s disk" % numtext),
+         IQ_CONFIG, 0, _GetInstDisk(i, _GetInstDiskSize)),
+        (_MakeField("disk.spindles/%s" % i, "DiskSpindles/%s" % i, QFT_NUMBER,
+                    "Spindles of %s disk" % numtext),
+         IQ_CONFIG, 0, _GetInstDisk(i, _GetInstDiskSpindles)),
+        (_MakeField("disk.name/%s" % i, "DiskName/%s" % i, QFT_TEXT,
+                    "Name of %s disk" % numtext),
+         IQ_CONFIG, 0, _GetInstDisk(i, _GetInstDeviceName)),
+        (_MakeField("disk.uuid/%s" % i, "DiskUUID/%s" % i, QFT_TEXT,
+                    "UUID of %s disk" % numtext),
+         IQ_CONFIG, 0, _GetInstDisk(i, _GetInstDeviceUUID))])
 
   return fields
 
 
   return fields
 
@@ -1715,26 +1965,6 @@ def _GetInstanceParameterFields():
   @return: List of field definitions used as input for L{_PrepareFieldList}
 
   """
   @return: List of field definitions used as input for L{_PrepareFieldList}
 
   """
-  # TODO: Consider moving titles closer to constants
-  be_title = {
-    constants.BE_AUTO_BALANCE: "Auto_balance",
-    constants.BE_MAXMEM: "ConfigMaxMem",
-    constants.BE_MINMEM: "ConfigMinMem",
-    constants.BE_VCPUS: "ConfigVCPUs",
-    }
-
-  hv_title = {
-    constants.HV_ACPI: "ACPI",
-    constants.HV_BOOT_ORDER: "Boot_order",
-    constants.HV_CDROM_IMAGE_PATH: "CDROM_image_path",
-    constants.HV_DISK_TYPE: "Disk_type",
-    constants.HV_INITRD_PATH: "Initrd_path",
-    constants.HV_KERNEL_PATH: "Kernel_path",
-    constants.HV_NIC_TYPE: "NIC_type",
-    constants.HV_PAE: "PAE",
-    constants.HV_VNC_BIND_ADDRESS: "VNC_bind_address",
-    }
-
   fields = [
     # Filled parameters
     (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
   fields = [
     # Filled parameters
     (_MakeField("hvparams", "HypervisorParameters", QFT_OTHER,
@@ -1767,23 +1997,23 @@ def _GetInstanceParameterFields():
     return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
 
   fields.extend([
     return lambda ctx, _: ctx.inst_hvparams.get(name, _FS_UNAVAIL)
 
   fields.extend([
-    (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
+    (_MakeField("hv/%s" % name,
+                constants.HVS_PARAMETER_TITLES.get(name, "hv/%s" % name),
                 _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
      IQ_CONFIG, 0, _GetInstHvParam(name))
     for name, kind in constants.HVS_PARAMETER_TYPES.items()
                 _VTToQFT[kind], "The \"%s\" hypervisor parameter" % name),
      IQ_CONFIG, 0, _GetInstHvParam(name))
     for name, kind in constants.HVS_PARAMETER_TYPES.items()
-    if name not in constants.HVC_GLOBALS
-    ])
+    if name not in constants.HVC_GLOBALS])
 
   # BE params
   def _GetInstBeParam(name):
     return lambda ctx, _: ctx.inst_beparams.get(name, None)
 
   fields.extend([
 
   # BE params
   def _GetInstBeParam(name):
     return lambda ctx, _: ctx.inst_beparams.get(name, None)
 
   fields.extend([
-    (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
+    (_MakeField("be/%s" % name,
+                constants.BES_PARAMETER_TITLES.get(name, "be/%s" % name),
                 _VTToQFT[kind], "The \"%s\" backend parameter" % name),
      IQ_CONFIG, 0, _GetInstBeParam(name))
                 _VTToQFT[kind], "The \"%s\" backend parameter" % name),
      IQ_CONFIG, 0, _GetInstBeParam(name))
-    for name, kind in constants.BES_PARAMETER_TYPES.items()
-    ])
+    for name, kind in constants.BES_PARAMETER_TYPES.items()])
 
   return fields
 
 
   return fields
 
@@ -1801,34 +2031,51 @@ _INST_SIMPLE_FIELDS = {
   }
 
 
   }
 
 
-def _GetInstNodeGroup(ctx, default, node_name):
+def _GetNodeName(ctx, default, node_uuid):
+  """Gets node name of a node.
+
+  @type ctx: L{InstanceQueryData}
+  @param default: Default value
+  @type node_uuid: string
+  @param node_uuid: Node UUID
+
+  """
+  try:
+    node = ctx.nodes[node_uuid]
+  except KeyError:
+    return default
+  else:
+    return node.name
+
+
+def _GetInstNodeGroup(ctx, default, node_uuid):
   """Gets group UUID of an instance node.
 
   @type ctx: L{InstanceQueryData}
   @param default: Default value
   """Gets group UUID of an instance node.
 
   @type ctx: L{InstanceQueryData}
   @param default: Default value
-  @type node_name: string
-  @param node_name: Node name
+  @type node_uuid: string
+  @param node_uuid: Node UUID
 
   """
   try:
 
   """
   try:
-    node = ctx.nodes[node_name]
+    node = ctx.nodes[node_uuid]
   except KeyError:
     return default
   else:
     return node.group
 
 
   except KeyError:
     return default
   else:
     return node.group
 
 
-def _GetInstNodeGroupName(ctx, default, node_name):
+def _GetInstNodeGroupName(ctx, default, node_uuid):
   """Gets group name of an instance node.
 
   @type ctx: L{InstanceQueryData}
   @param default: Default value
   """Gets group name of an instance node.
 
   @type ctx: L{InstanceQueryData}
   @param default: Default value
-  @type node_name: string
-  @param node_name: Node name
+  @type node_uuid: string
+  @param node_uuid: Node UUID
 
   """
   try:
 
   """
   try:
-    node = ctx.nodes[node_name]
+    node = ctx.nodes[node_uuid]
   except KeyError:
     return default
 
   except KeyError:
     return default
 
@@ -1846,7 +2093,8 @@ def _BuildInstanceFields():
   """
   fields = [
     (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
   """
   fields = [
     (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
-     IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")),
+     IQ_NODES, QFF_HOSTNAME,
+     lambda ctx, inst: _GetNodeName(ctx, None, inst.primary_node)),
     (_MakeField("pnode.group", "PrimaryNodeGroup", QFT_TEXT,
                 "Primary node's group"),
      IQ_NODES, 0,
     (_MakeField("pnode.group", "PrimaryNodeGroup", QFT_TEXT,
                 "Primary node's group"),
      IQ_NODES, 0,
@@ -1859,7 +2107,9 @@ def _BuildInstanceFields():
     # TODO: Allow filtering by secondary node as hostname
     (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
                 "Secondary nodes; usually this will just be one 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)),
+     IQ_NODES, 0,
+     lambda ctx, inst: map(compat.partial(_GetNodeName, ctx, None),
+                           inst.secondary_nodes)),
     (_MakeField("snodes.group", "SecondaryNodesGroups", QFT_OTHER,
                 "Node groups of secondary nodes"),
      IQ_NODES, 0,
     (_MakeField("snodes.group", "SecondaryNodesGroups", QFT_OTHER,
                 "Node groups of secondary nodes"),
      IQ_NODES, 0,
@@ -1876,6 +2126,9 @@ def _BuildInstanceFields():
     (_MakeField("admin_up", "Autostart", QFT_BOOL,
                 "Desired state of instance"),
      IQ_CONFIG, 0, lambda ctx, inst: inst.admin_state == constants.ADMINST_UP),
     (_MakeField("admin_up", "Autostart", QFT_BOOL,
                 "Desired state of instance"),
      IQ_CONFIG, 0, lambda ctx, inst: inst.admin_state == constants.ADMINST_UP),
+    (_MakeField("disks_active", "DisksActive", QFT_BOOL,
+                "Desired state of instance disks"),
+     IQ_CONFIG, 0, _GetItemAttr("disks_active")),
     (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
      lambda ctx, inst: list(inst.GetTags())),
     (_MakeField("console", "Console", QFT_OTHER,
     (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
      lambda ctx, inst: list(inst.GetTags())),
     (_MakeField("console", "Console", QFT_OTHER,
@@ -1886,8 +2139,7 @@ def _BuildInstanceFields():
   # Add simple fields
   fields.extend([
     (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
   # Add simple fields
   fields.extend([
     (_MakeField(name, title, kind, doc), IQ_CONFIG, flags, _GetItemAttr(name))
-    for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()
-    ])
+    for (name, (title, kind, flags, doc)) in _INST_SIMPLE_FIELDS.items()])
 
   # Fields requiring talking to the node
   fields.extend([
 
   # Fields requiring talking to the node
   fields.extend([
@@ -2168,6 +2420,36 @@ def _BuildOsFields():
   return _PrepareFieldList(fields, [])
 
 
   return _PrepareFieldList(fields, [])
 
 
+class ExtStorageInfo(objects.ConfigObject):
+  __slots__ = [
+    "name",
+    "node_status",
+    "nodegroup_status",
+    "parameters",
+    ]
+
+
+def _BuildExtStorageFields():
+  """Builds list of fields for extstorage provider queries.
+
+  """
+  fields = [
+    (_MakeField("name", "Name", QFT_TEXT, "ExtStorage provider name"),
+     None, 0, _GetItemAttr("name")),
+    (_MakeField("node_status", "NodeStatus", QFT_OTHER,
+                "Status from node"),
+     None, 0, _GetItemAttr("node_status")),
+    (_MakeField("nodegroup_status", "NodegroupStatus", QFT_OTHER,
+                "Overall Nodegroup status"),
+     None, 0, _GetItemAttr("nodegroup_status")),
+    (_MakeField("parameters", "Parameters", QFT_OTHER,
+                "ExtStorage provider parameters"),
+     None, 0, _GetItemAttr("parameters")),
+    ]
+
+  return _PrepareFieldList(fields, [])
+
+
 def _JobUnavailInner(fn, ctx, (job_id, job)): # pylint: disable=W0613
   """Return L{_FS_UNAVAIL} if job is None.
 
 def _JobUnavailInner(fn, ctx, (job_id, job)): # pylint: disable=W0613
   """Return L{_FS_UNAVAIL} if job is None.
 
@@ -2226,14 +2508,16 @@ def _BuildJobFields():
 
   """
   fields = [
 
   """
   fields = [
-    (_MakeField("id", "ID", QFT_TEXT, "Job ID"),
-     None, 0, lambda _, (job_id, job): job_id),
+    (_MakeField("id", "ID", QFT_NUMBER, "Job ID"),
+     None, QFF_JOB_ID, lambda _, (job_id, job): job_id),
     (_MakeField("status", "Status", QFT_TEXT, "Job status"),
      None, 0, _JobUnavail(lambda job: job.CalcStatus())),
     (_MakeField("priority", "Priority", QFT_NUMBER,
                 ("Current job priority (%s to %s)" %
                  (constants.OP_PRIO_LOWEST, constants.OP_PRIO_HIGHEST))),
      None, 0, _JobUnavail(lambda job: job.CalcPriority())),
     (_MakeField("status", "Status", QFT_TEXT, "Job status"),
      None, 0, _JobUnavail(lambda job: job.CalcStatus())),
     (_MakeField("priority", "Priority", QFT_NUMBER,
                 ("Current job priority (%s to %s)" %
                  (constants.OP_PRIO_LOWEST, constants.OP_PRIO_HIGHEST))),
      None, 0, _JobUnavail(lambda job: job.CalcPriority())),
+    (_MakeField("archived", "Archived", QFT_BOOL, "Whether job is archived"),
+     JQ_ARCHIVED, 0, lambda _, (job_id, job): job.archived),
     (_MakeField("ops", "OpCodes", QFT_OTHER, "List of all opcodes"),
      None, 0, _PerJobOp(lambda op: op.input.__getstate__())),
     (_MakeField("opresult", "OpCode_result", QFT_OTHER,
     (_MakeField("ops", "OpCodes", QFT_OTHER, "List of all opcodes"),
      None, 0, _PerJobOp(lambda op: op.input.__getstate__())),
     (_MakeField("opresult", "OpCode_result", QFT_OTHER,
@@ -2258,20 +2542,25 @@ def _BuildJobFields():
     (_MakeField("oppriority", "OpCode_prio", QFT_OTHER,
                 "List of opcode priorities"),
      None, 0, _PerJobOp(operator.attrgetter("priority"))),
     (_MakeField("oppriority", "OpCode_prio", QFT_OTHER,
                 "List of opcode priorities"),
      None, 0, _PerJobOp(operator.attrgetter("priority"))),
-    (_MakeField("received_ts", "Received", QFT_OTHER,
-                "Timestamp of when job was received"),
-     None, 0, _JobTimestamp(operator.attrgetter("received_timestamp"))),
-    (_MakeField("start_ts", "Start", QFT_OTHER,
-                "Timestamp of job start"),
-     None, 0, _JobTimestamp(operator.attrgetter("start_timestamp"))),
-    (_MakeField("end_ts", "End", QFT_OTHER,
-                "Timestamp of job end"),
-     None, 0, _JobTimestamp(operator.attrgetter("end_timestamp"))),
     (_MakeField("summary", "Summary", QFT_OTHER,
                 "List of per-opcode summaries"),
      None, 0, _PerJobOp(lambda op: op.input.Summary())),
     ]
 
     (_MakeField("summary", "Summary", QFT_OTHER,
                 "List of per-opcode summaries"),
      None, 0, _PerJobOp(lambda op: op.input.Summary())),
     ]
 
+  # Timestamp fields
+  for (name, attr, title, desc) in [
+    ("received_ts", "received_timestamp", "Received",
+     "Timestamp of when job was received"),
+    ("start_ts", "start_timestamp", "Start", "Timestamp of job start"),
+    ("end_ts", "end_timestamp", "End", "Timestamp of job end"),
+    ]:
+    getter = operator.attrgetter(attr)
+    fields.extend([
+      (_MakeField(name, title, QFT_OTHER,
+                  "%s (tuple containing seconds and microseconds)" % desc),
+       None, QFF_SPLIT_TIMESTAMP, _JobTimestamp(getter)),
+      ])
+
   return _PrepareFieldList(fields, [])
 
 
   return _PrepareFieldList(fields, [])
 
 
@@ -2280,7 +2569,7 @@ def _GetExportName(_, (node_name, expname)): # pylint: disable=W0613
 
   """
   if expname is None:
 
   """
   if expname is None:
-    return _FS_UNAVAIL
+    return _FS_NODATA
   else:
     return expname
 
   else:
     return expname
 
@@ -2311,22 +2600,25 @@ _CLUSTER_VERSION_FIELDS = {
                      "API version for OS template scripts"),
   "export_version": ("ExportVersion", QFT_NUMBER, constants.EXPORT_VERSION,
                      "Import/export file format version"),
                      "API version for OS template scripts"),
   "export_version": ("ExportVersion", QFT_NUMBER, constants.EXPORT_VERSION,
                      "Import/export file format version"),
+  "vcs_version": ("VCSVersion", QFT_TEXT, constants.VCS_VERSION,
+                     "VCS version"),
   }
 
 
 _CLUSTER_SIMPLE_FIELDS = {
   "cluster_name": ("Name", QFT_TEXT, QFF_HOSTNAME, "Cluster name"),
   }
 
 
 _CLUSTER_SIMPLE_FIELDS = {
   "cluster_name": ("Name", QFT_TEXT, QFF_HOSTNAME, "Cluster name"),
-  "master_node": ("Master", QFT_TEXT, QFF_HOSTNAME, "Master node name"),
   "volume_group_name": ("VgName", QFT_TEXT, 0, "LVM volume group name"),
   }
 
 
 class ClusterQueryData:
   "volume_group_name": ("VgName", QFT_TEXT, 0, "LVM volume group name"),
   }
 
 
 class ClusterQueryData:
-  def __init__(self, cluster, drain_flag, watcher_pause):
+  def __init__(self, cluster, nodes, drain_flag, watcher_pause):
     """Initializes this class.
 
     @type cluster: L{objects.Cluster}
     @param cluster: Instance of cluster object
     """Initializes this class.
 
     @type cluster: L{objects.Cluster}
     @param cluster: Instance of cluster object
+    @type nodes: dict; node UUID as key
+    @param nodes: Node objects
     @type drain_flag: bool
     @param drain_flag: Whether job queue is drained
     @type watcher_pause: number
     @type drain_flag: bool
     @param drain_flag: Whether job queue is drained
     @type watcher_pause: number
@@ -2334,6 +2626,7 @@ class ClusterQueryData:
 
     """
     self._cluster = cluster
 
     """
     self._cluster = cluster
+    self.nodes = nodes
     self.drain_flag = drain_flag
     self.watcher_pause = watcher_pause
 
     self.drain_flag = drain_flag
     self.watcher_pause = watcher_pause
 
@@ -2367,27 +2660,147 @@ def _BuildClusterFields():
     (_MakeField("watcher_pause", "WatcherPause", QFT_TIMESTAMP,
                 "Until when watcher is paused"), CQ_WATCHER_PAUSE, 0,
      _ClusterWatcherPause),
     (_MakeField("watcher_pause", "WatcherPause", QFT_TIMESTAMP,
                 "Until when watcher is paused"), CQ_WATCHER_PAUSE, 0,
      _ClusterWatcherPause),
+    (_MakeField("master_node", "Master", QFT_TEXT, "Master node name"),
+     CQ_CONFIG, QFF_HOSTNAME,
+     lambda ctx, cluster: _GetNodeName(ctx, None, cluster.master_node)),
     ]
 
   # Simple fields
   fields.extend([
     (_MakeField(name, title, kind, doc), CQ_CONFIG, flags, _GetItemAttr(name))
     for (name, (title, kind, flags, doc)) in _CLUSTER_SIMPLE_FIELDS.items()
     ]
 
   # Simple fields
   fields.extend([
     (_MakeField(name, title, kind, doc), CQ_CONFIG, flags, _GetItemAttr(name))
     for (name, (title, kind, flags, doc)) in _CLUSTER_SIMPLE_FIELDS.items()
-    ])
+    ],)
 
   # Version fields
   fields.extend([
     (_MakeField(name, title, kind, doc), None, 0, _StaticValue(value))
 
   # Version fields
   fields.extend([
     (_MakeField(name, title, kind, doc), None, 0, _StaticValue(value))
-    for (name, (title, kind, value, doc)) in _CLUSTER_VERSION_FIELDS.items()
-    ])
+    for (name, (title, kind, value, doc)) in _CLUSTER_VERSION_FIELDS.items()])
 
   # Add timestamps
   fields.extend(_GetItemTimestampFields(CQ_CONFIG))
 
   return _PrepareFieldList(fields, [
 
   # Add timestamps
   fields.extend(_GetItemTimestampFields(CQ_CONFIG))
 
   return _PrepareFieldList(fields, [
-    ("name", "cluster_name"),
+    ("name", "cluster_name")])
+
+
+class NetworkQueryData:
+  """Data container for network data queries.
+
+  """
+  def __init__(self, networks, network_to_groups,
+               network_to_instances, stats):
+    """Initializes this class.
+
+    @param networks: List of network objects
+    @type network_to_groups: dict; network UUID as key
+    @param network_to_groups: Per-network list of groups
+    @type network_to_instances: dict; network UUID as key
+    @param network_to_instances: Per-network list of instances
+    @type stats: dict; network UUID as key
+    @param stats: Per-network usage statistics
+
+    """
+    self.networks = networks
+    self.network_to_groups = network_to_groups
+    self.network_to_instances = network_to_instances
+    self.stats = stats
+
+  def __iter__(self):
+    """Iterate over all networks.
+
+    """
+    for net in self.networks:
+      if self.stats:
+        self.curstats = self.stats.get(net.uuid, None)
+      else:
+        self.curstats = None
+      yield net
+
+
+_NETWORK_SIMPLE_FIELDS = {
+  "name": ("Network", QFT_TEXT, 0, "Name"),
+  "network": ("Subnet", QFT_TEXT, 0, "IPv4 subnet"),
+  "gateway": ("Gateway", QFT_OTHER, 0, "IPv4 gateway"),
+  "network6": ("IPv6Subnet", QFT_OTHER, 0, "IPv6 subnet"),
+  "gateway6": ("IPv6Gateway", QFT_OTHER, 0, "IPv6 gateway"),
+  "mac_prefix": ("MacPrefix", QFT_OTHER, 0, "MAC address prefix"),
+  "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Network"),
+  "uuid": ("UUID", QFT_TEXT, 0, "Network UUID"),
+  }
+
+
+_NETWORK_STATS_FIELDS = {
+  "free_count": ("FreeCount", QFT_NUMBER, 0, "Number of available addresses"),
+  "reserved_count":
+    ("ReservedCount", QFT_NUMBER, 0, "Number of reserved addresses"),
+  "map": ("Map", QFT_TEXT, 0, "Actual mapping"),
+  "external_reservations":
+    ("ExternalReservations", QFT_TEXT, 0, "External reservations"),
+  }
+
+
+def _GetNetworkStatsField(field, kind, ctx, _):
+  """Gets the value of a "stats" field from L{NetworkQueryData}.
+
+  @param field: Field name
+  @param kind: Data kind, one of L{constants.QFT_ALL}
+  @type ctx: L{NetworkQueryData}
+
+  """
+  return _GetStatsField(field, kind, ctx.curstats)
+
+
+def _BuildNetworkFields():
+  """Builds list of fields for network queries.
+
+  """
+  fields = [
+    (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), IQ_CONFIG, 0,
+     lambda ctx, inst: list(inst.GetTags())),
+    ]
+
+  # Add simple fields
+  fields.extend([
+    (_MakeField(name, title, kind, doc),
+     NETQ_CONFIG, 0, _GetItemMaybeAttr(name))
+     for (name, (title, kind, _, doc)) in _NETWORK_SIMPLE_FIELDS.items()])
+
+  def _GetLength(getter):
+    return lambda ctx, network: len(getter(ctx)[network.uuid])
+
+  def _GetSortedList(getter):
+    return lambda ctx, network: utils.NiceSort(getter(ctx)[network.uuid])
+
+  network_to_groups = operator.attrgetter("network_to_groups")
+  network_to_instances = operator.attrgetter("network_to_instances")
+
+  # Add fields for node groups
+  fields.extend([
+    (_MakeField("group_cnt", "NodeGroups", QFT_NUMBER, "Number of nodegroups"),
+     NETQ_GROUP, 0, _GetLength(network_to_groups)),
+    (_MakeField("group_list", "GroupList", QFT_OTHER,
+     "List of nodegroups (group name, NIC mode, NIC link)"),
+     NETQ_GROUP, 0, lambda ctx, network: network_to_groups(ctx)[network.uuid]),
     ])
 
     ])
 
+  # Add fields for instances
+  fields.extend([
+    (_MakeField("inst_cnt", "Instances", QFT_NUMBER, "Number of instances"),
+     NETQ_INST, 0, _GetLength(network_to_instances)),
+    (_MakeField("inst_list", "InstanceList", QFT_OTHER, "List of instances"),
+     NETQ_INST, 0, _GetSortedList(network_to_instances)),
+    ])
+
+  # Add fields for usage statistics
+  fields.extend([
+    (_MakeField(name, title, kind, doc), NETQ_STATS, 0,
+     compat.partial(_GetNetworkStatsField, name, kind))
+    for (name, (title, kind, _, doc)) in _NETWORK_STATS_FIELDS.items()])
+
+  # Add timestamps
+  fields.extend(_GetItemTimestampFields(IQ_NETWORKS))
+
+  return _PrepareFieldList(fields, [])
 
 #: Fields for cluster information
 CLUSTER_FIELDS = _BuildClusterFields()
 
 #: Fields for cluster information
 CLUSTER_FIELDS = _BuildClusterFields()
@@ -2407,12 +2820,18 @@ GROUP_FIELDS = _BuildGroupFields()
 #: Fields available for operating system queries
 OS_FIELDS = _BuildOsFields()
 
 #: Fields available for operating system queries
 OS_FIELDS = _BuildOsFields()
 
+#: Fields available for extstorage provider queries
+EXTSTORAGE_FIELDS = _BuildExtStorageFields()
+
 #: Fields available for job queries
 JOB_FIELDS = _BuildJobFields()
 
 #: Fields available for exports
 EXPORT_FIELDS = _BuildExportFields()
 
 #: Fields available for job queries
 JOB_FIELDS = _BuildJobFields()
 
 #: Fields available for exports
 EXPORT_FIELDS = _BuildExportFields()
 
+#: Fields available for network queries
+NETWORK_FIELDS = _BuildNetworkFields()
+
 #: All available resources
 ALL_FIELDS = {
   constants.QR_CLUSTER: CLUSTER_FIELDS,
 #: All available resources
 ALL_FIELDS = {
   constants.QR_CLUSTER: CLUSTER_FIELDS,
@@ -2421,8 +2840,10 @@ ALL_FIELDS = {
   constants.QR_LOCK: LOCK_FIELDS,
   constants.QR_GROUP: GROUP_FIELDS,
   constants.QR_OS: OS_FIELDS,
   constants.QR_LOCK: LOCK_FIELDS,
   constants.QR_GROUP: GROUP_FIELDS,
   constants.QR_OS: OS_FIELDS,
+  constants.QR_EXTSTORAGE: EXTSTORAGE_FIELDS,
   constants.QR_JOB: JOB_FIELDS,
   constants.QR_EXPORT: EXPORT_FIELDS,
   constants.QR_JOB: JOB_FIELDS,
   constants.QR_EXPORT: EXPORT_FIELDS,
+  constants.QR_NETWORK: NETWORK_FIELDS,
   }
 
 #: All available field lists
   }
 
 #: All available field lists