query: Add definition for instance queries
authorMichael Hanselmann <hansmi@google.com>
Tue, 30 Nov 2010 13:56:32 +0000 (14:56 +0100)
committerMichael Hanselmann <hansmi@google.com>
Fri, 10 Dec 2010 15:48:32 +0000 (16:48 +0100)
Signed-off-by: Michael Hanselmann <hansmi@google.com>
Reviewed-by: Iustin Pop <iustin@google.com>

lib/query.py
test/ganeti.query_unittest.py

index 9d64694..80cb68a 100644 (file)
@@ -38,6 +38,10 @@ from ganeti import ht
  NQ_LIVE,
  NQ_GROUP) = range(1, 5)
 
+(IQ_CONFIG,
+ IQ_LIVE,
+ IQ_DISKUSAGE) = range(100, 103)
+
 
 FIELD_NAME_RE = re.compile(r"^[a-z0-9/._]+$")
 TITLE_RE = re.compile(r"^[^\s]+$")
@@ -438,5 +442,493 @@ def _BuildNodeFields():
   return _PrepareFieldList(fields)
 
 
+class InstanceQueryData:
+  """Data container for instance data queries.
+
+  """
+  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
+               live_data):
+    """Initializes this class.
+
+    @param instances: List of instance objects
+    @param cluster: Cluster object
+    @type disk_usage: dict; instance name as key
+    @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
+    @param live_data: Per-instance live data
+
+    """
+    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
+           "Offline nodes not included in bad nodes"
+    assert not (set(live_data.keys()) & set(bad_nodes)), \
+           "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.live_data = live_data
+
+    # Used for individual rows
+    self.inst_hvparams = None
+    self.inst_beparams = None
+    self.inst_nicparams = None
+
+  def __iter__(self):
+    """Iterate over all instances.
+
+    This function has side-effects and only one instance of the resulting
+    generator should be used at a time.
+
+    """
+    for inst in self.instances:
+      self.inst_hvparams = self.cluster.FillHV(inst, skip_globals=True)
+      self.inst_beparams = self.cluster.FillBE(inst)
+      self.inst_nicparams = [self.cluster.SimpleFillNIC(nic.nicparams)
+                             for nic in inst.nics]
+
+      yield inst
+
+
+def _GetInstOperState(ctx, inst):
+  """Get instance's operational status.
+
+  @type ctx: L{InstanceQueryData}
+  @type inst: L{objects.Instance}
+  @param inst: Instance object
+
+  """
+  if inst.primary_node in ctx.bad_nodes:
+    return (constants.QRFS_NODATA, None)
+  else:
+    return (constants.QRFS_NORMAL, bool(ctx.live_data.get(inst.name)))
+
+
+def _GetInstLiveData(name):
+  """Build function for retrieving live data.
+
+  @type name: string
+  @param name: Live data field name
+
+  """
+  def fn(ctx, inst):
+    """Get live data for an instance.
+
+    @type ctx: L{InstanceQueryData}
+    @type inst: L{objects.Instance}
+    @param inst: Instance object
+
+    """
+    if (inst.primary_node in ctx.bad_nodes or
+        inst.primary_node in ctx.offline_nodes):
+      return (constants.QRFS_NODATA, None)
+
+    if inst.name in ctx.live_data:
+      data = ctx.live_data[inst.name]
+      if name in data:
+        return (constants.QRFS_NORMAL, data[name])
+
+    return (constants.QRFS_UNAVAIL, None)
+
+  return fn
+
+
+def _GetInstStatus(ctx, inst):
+  """Get instance status.
+
+  @type ctx: L{InstanceQueryData}
+  @type inst: L{objects.Instance}
+  @param inst: Instance object
+
+  """
+  if inst.primary_node in ctx.offline_nodes:
+    return (constants.QRFS_NORMAL, "ERROR_nodeoffline")
+
+  if inst.primary_node in ctx.bad_nodes:
+    return (constants.QRFS_NORMAL, "ERROR_nodedown")
+
+  if bool(ctx.live_data.get(inst.name)):
+    if inst.admin_up:
+      return (constants.QRFS_NORMAL, "running")
+    else:
+      return (constants.QRFS_NORMAL, "ERROR_up")
+
+  if inst.admin_up:
+    return (constants.QRFS_NORMAL, "ERROR_down")
+
+  return (constants.QRFS_NORMAL, "ADMIN_down")
+
+
+def _GetInstDiskSize(index):
+  """Build function for retrieving disk size.
+
+  @type index: int
+  @param index: Disk index
+
+  """
+  def fn(_, inst):
+    """Get size of a disk.
+
+    @type inst: L{objects.Instance}
+    @param inst: Instance object
+
+    """
+    try:
+      return (constants.QRFS_NORMAL, inst.disks[index].size)
+    except IndexError:
+      return (constants.QRFS_UNAVAIL, None)
+
+  return fn
+
+
+def _GetInstNic(index, cb):
+  """Build function for calling another function with an instance NIC.
+
+  @type index: int
+  @param index: NIC index
+  @type cb: callable
+  @param cb: Callback
+
+  """
+  def fn(ctx, inst):
+    """Call helper function with instance NIC.
+
+    @type ctx: L{InstanceQueryData}
+    @type inst: L{objects.Instance}
+    @param inst: Instance object
+
+    """
+    try:
+      nic = inst.nics[index]
+    except IndexError:
+      return (constants.QRFS_UNAVAIL, None)
+
+    return cb(ctx, index, nic)
+
+  return fn
+
+
+def _GetInstNicIp(ctx, _, nic): # pylint: disable-msg=W0613
+  """Get a NIC's IP address.
+
+  @type ctx: L{InstanceQueryData}
+  @type nic: L{objects.NIC}
+  @param nic: NIC object
+
+  """
+  if nic.ip is None:
+    return (constants.QRFS_UNAVAIL, None)
+  else:
+    return (constants.QRFS_NORMAL, nic.ip)
+
+
+def _GetInstNicBridge(ctx, index, _):
+  """Get a NIC's bridge.
+
+  @type ctx: L{InstanceQueryData}
+  @type index: int
+  @param index: NIC index
+
+  """
+  assert len(ctx.inst_nicparams) >= index
+
+  nicparams = ctx.inst_nicparams[index]
+
+  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
+    return (constants.QRFS_NORMAL, nicparams[constants.NIC_LINK])
+  else:
+    return (constants.QRFS_UNAVAIL, None)
+
+
+def _GetInstAllNicBridges(ctx, inst):
+  """Get all network bridges for an instance.
+
+  @type ctx: L{InstanceQueryData}
+  @type inst: L{objects.Instance}
+  @param inst: Instance object
+
+  """
+  assert len(ctx.inst_nicparams) == len(inst.nics)
+
+  result = []
+
+  for nicp in ctx.inst_nicparams:
+    if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
+      result.append(nicp[constants.NIC_LINK])
+    else:
+      result.append(None)
+
+  assert len(result) == len(inst.nics)
+
+  return (constants.QRFS_NORMAL, result)
+
+
+def _GetInstNicParam(name):
+  """Build function for retrieving a NIC parameter.
+
+  @type name: string
+  @param name: Parameter name
+
+  """
+  def fn(ctx, index, _):
+    """Get a NIC's bridge.
+
+    @type ctx: L{InstanceQueryData}
+    @type inst: L{objects.Instance}
+    @param inst: Instance object
+    @type nic: L{objects.NIC}
+    @param nic: NIC object
+
+    """
+    assert len(ctx.inst_nicparams) >= index
+    return (constants.QRFS_NORMAL, ctx.inst_nicparams[index][name])
+
+  return fn
+
+
+def _GetInstanceNetworkFields():
+  """Get instance fields involving network interfaces.
+
+  @return: List of field definitions used as input for L{_PrepareFieldList}
+
+  """
+  nic_mac_fn = lambda ctx, _, nic: (constants.QRFS_NORMAL, nic.mac)
+  nic_mode_fn = _GetInstNicParam(constants.NIC_MODE)
+  nic_link_fn = _GetInstNicParam(constants.NIC_LINK)
+
+  fields = [
+    # First NIC (legacy)
+    (_MakeField("ip", "IP_address", constants.QFT_TEXT), IQ_CONFIG,
+     _GetInstNic(0, _GetInstNicIp)),
+    (_MakeField("mac", "MAC_address", constants.QFT_TEXT), IQ_CONFIG,
+     _GetInstNic(0, nic_mac_fn)),
+    (_MakeField("bridge", "Bridge", constants.QFT_TEXT), IQ_CONFIG,
+     _GetInstNic(0, _GetInstNicBridge)),
+    (_MakeField("nic_mode", "NIC_Mode", constants.QFT_TEXT), IQ_CONFIG,
+     _GetInstNic(0, nic_mode_fn)),
+    (_MakeField("nic_link", "NIC_Link", constants.QFT_TEXT), IQ_CONFIG,
+     _GetInstNic(0, nic_link_fn)),
+
+    # All NICs
+    (_MakeField("nic.count", "NICs", constants.QFT_NUMBER), IQ_CONFIG,
+     lambda ctx, inst: (constants.QRFS_NORMAL, len(inst.nics))),
+    (_MakeField("nic.macs", "NIC_MACs", constants.QFT_OTHER), IQ_CONFIG,
+     lambda ctx, inst: (constants.QRFS_NORMAL, [nic.mac for nic in inst.nics])),
+    (_MakeField("nic.ips", "NIC_IPs", constants.QFT_OTHER), IQ_CONFIG,
+     lambda ctx, inst: (constants.QRFS_NORMAL, [nic.ip for nic in inst.nics])),
+    (_MakeField("nic.modes", "NIC_modes", constants.QFT_OTHER), IQ_CONFIG,
+     lambda ctx, inst: (constants.QRFS_NORMAL,
+                        [nicp[constants.NIC_MODE]
+                         for nicp in ctx.inst_nicparams])),
+    (_MakeField("nic.links", "NIC_links", constants.QFT_OTHER), IQ_CONFIG,
+     lambda ctx, inst: (constants.QRFS_NORMAL,
+                        [nicp[constants.NIC_LINK]
+                         for nicp in ctx.inst_nicparams])),
+    (_MakeField("nic.bridges", "NIC_bridges", constants.QFT_OTHER), IQ_CONFIG,
+     _GetInstAllNicBridges),
+    ]
+
+  # NICs by number
+  for i in range(constants.MAX_NICS):
+    fields.extend([
+      (_MakeField("nic.ip/%s" % i, "NicIP/%s" % i, constants.QFT_TEXT),
+       IQ_CONFIG, _GetInstNic(i, _GetInstNicIp)),
+      (_MakeField("nic.mac/%s" % i, "NicMAC/%s" % i, constants.QFT_TEXT),
+       IQ_CONFIG, _GetInstNic(i, nic_mac_fn)),
+      (_MakeField("nic.mode/%s" % i, "NicMode/%s" % i, constants.QFT_TEXT),
+       IQ_CONFIG, _GetInstNic(i, nic_mode_fn)),
+      (_MakeField("nic.link/%s" % i, "NicLink/%s" % i, constants.QFT_TEXT),
+       IQ_CONFIG, _GetInstNic(i, nic_link_fn)),
+      (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, constants.QFT_TEXT),
+       IQ_CONFIG, _GetInstNic(i, _GetInstNicBridge)),
+      ])
+
+  return fields
+
+
+def _GetInstDiskUsage(ctx, inst):
+  """Get disk usage for an instance.
+
+  @type ctx: L{InstanceQueryData}
+  @type inst: L{objects.Instance}
+  @param inst: Instance object
+
+  """
+  usage = ctx.disk_usage[inst.name]
+
+  if usage is None:
+    usage = 0
+
+  return (constants.QRFS_NORMAL, usage)
+
+
+def _GetInstanceDiskFields():
+  """Get instance fields involving disks.
+
+  @return: List of field definitions used as input for L{_PrepareFieldList}
+
+  """
+  fields = [
+    (_MakeField("disk_usage", "DiskUsage", constants.QFT_UNIT), IQ_DISKUSAGE,
+     _GetInstDiskUsage),
+    (_MakeField("sda_size", "LegacyDisk/0", constants.QFT_UNIT), IQ_CONFIG,
+     _GetInstDiskSize(0)),
+    (_MakeField("sdb_size", "LegacyDisk/1", constants.QFT_UNIT), IQ_CONFIG,
+     _GetInstDiskSize(1)),
+    (_MakeField("disk.count", "Disks", constants.QFT_NUMBER), IQ_CONFIG,
+     lambda ctx, inst: (constants.QRFS_NORMAL, len(inst.disks))),
+    (_MakeField("disk.sizes", "Disk_sizes", constants.QFT_OTHER), IQ_CONFIG,
+     lambda ctx, inst: (constants.QRFS_NORMAL,
+                        [disk.size for disk in inst.disks])),
+    ]
+
+  # Disks by number
+  fields.extend([
+    (_MakeField("disk.size/%s" % i, "Disk/%s" % i, constants.QFT_UNIT),
+     IQ_CONFIG, _GetInstDiskSize(i))
+    for i in range(constants.MAX_DISKS)
+    ])
+
+  return fields
+
+
+def _GetInstanceParameterFields():
+  """Get instance fields involving parameters.
+
+  @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_MEMORY: "Configured_memory",
+    constants.BE_VCPUS: "VCPUs",
+    }
+
+  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", constants.QFT_OTHER),
+     IQ_CONFIG, lambda ctx, _: (constants.QRFS_NORMAL, ctx.inst_hvparams)),
+    (_MakeField("beparams", "BackendParameters", constants.QFT_OTHER),
+     IQ_CONFIG, lambda ctx, _: (constants.QRFS_NORMAL, ctx.inst_beparams)),
+    (_MakeField("vcpus", "LegacyVCPUs", constants.QFT_NUMBER), IQ_CONFIG,
+     lambda ctx, _: (constants.QRFS_NORMAL,
+                     ctx.inst_beparams[constants.BE_VCPUS])),
+
+    # Unfilled parameters
+    (_MakeField("custom_hvparams", "CustomHypervisorParameters",
+                constants.QFT_OTHER),
+     IQ_CONFIG, lambda ctx, inst: (constants.QRFS_NORMAL, inst.hvparams)),
+    (_MakeField("custom_beparams", "CustomBackendParameters",
+                constants.QFT_OTHER),
+     IQ_CONFIG, lambda ctx, inst: (constants.QRFS_NORMAL, inst.beparams)),
+    (_MakeField("custom_nicparams", "CustomNicParameters",
+                constants.QFT_OTHER),
+     IQ_CONFIG, lambda ctx, inst: (constants.QRFS_NORMAL,
+                                   [nic.nicparams for nic in inst.nics])),
+    ]
+
+  # HV params
+  def _GetInstHvParam(name):
+    return lambda ctx, _: (constants.QRFS_NORMAL,
+                           ctx.inst_hvparams.get(name, None))
+
+  fields.extend([
+    # For now all hypervisor parameters are exported as QFT_OTHER
+    (_MakeField("hv/%s" % name, hv_title.get(name, "hv/%s" % name),
+                constants.QFT_OTHER),
+     IQ_CONFIG, _GetInstHvParam(name))
+    for name in constants.HVS_PARAMETERS
+    if name not in constants.HVC_GLOBALS
+    ])
+
+  # BE params
+  def _GetInstBeParam(name):
+    return lambda ctx, _: (constants.QRFS_NORMAL,
+                           ctx.inst_beparams.get(name, None))
+
+  fields.extend([
+    # For now all backend parameters are exported as QFT_OTHER
+    (_MakeField("be/%s" % name, be_title.get(name, "be/%s" % name),
+                constants.QFT_OTHER),
+     IQ_CONFIG, _GetInstBeParam(name))
+    for name in constants.BES_PARAMETERS
+    ])
+
+  return fields
+
+
+_INST_SIMPLE_FIELDS = {
+  "ctime": ("CTime", constants.QFT_TIMESTAMP),
+  "disk_template": ("Disk_template", constants.QFT_TEXT),
+  "hypervisor": ("Hypervisor", constants.QFT_TEXT),
+  "mtime": ("MTime", constants.QFT_TIMESTAMP),
+  "name": ("Node", constants.QFT_TEXT),
+  # Depending on the hypervisor, the port can be None
+  "network_port": ("Network_port", constants.QFT_OTHER),
+  "os": ("OS", constants.QFT_TEXT),
+  "serial_no": ("SerialNo", constants.QFT_NUMBER),
+  "uuid": ("UUID", constants.QFT_TEXT),
+  }
+
+
+def _BuildInstanceFields():
+  """Builds list of fields for instance queries.
+
+  """
+  fields = [
+    (_MakeField("pnode", "Primary_node", constants.QFT_TEXT), IQ_CONFIG,
+     lambda ctx, inst: (constants.QRFS_NORMAL, inst.primary_node)),
+    (_MakeField("snodes", "Secondary_Nodes", constants.QFT_OTHER), IQ_CONFIG,
+     lambda ctx, inst: (constants.QRFS_NORMAL, list(inst.secondary_nodes))),
+    (_MakeField("admin_state", "Autostart", constants.QFT_BOOL), IQ_CONFIG,
+     lambda ctx, inst: (constants.QRFS_NORMAL, inst.admin_up)),
+    (_MakeField("tags", "Tags", constants.QFT_OTHER), IQ_CONFIG,
+     lambda ctx, inst: (constants.QRFS_NORMAL, list(inst.GetTags()))),
+    ]
+
+  # Add simple fields
+  fields.extend([(_MakeField(name, title, kind), IQ_CONFIG, _GetItemAttr(name))
+                 for (name, (title, kind)) in _INST_SIMPLE_FIELDS.items()])
+
+  # Fields requiring talking to the node
+  fields.extend([
+    (_MakeField("oper_state", "Running", constants.QFT_BOOL), IQ_LIVE,
+     _GetInstOperState),
+    (_MakeField("oper_ram", "RuntimeMemory", constants.QFT_UNIT), IQ_LIVE,
+     _GetInstLiveData("memory")),
+    (_MakeField("oper_vcpus", "RuntimeVCPUs", constants.QFT_NUMBER), IQ_LIVE,
+     _GetInstLiveData("vcpus")),
+    (_MakeField("status", "Status", constants.QFT_TEXT), IQ_LIVE,
+     _GetInstStatus),
+    ])
+
+  fields.extend(_GetInstanceParameterFields())
+  fields.extend(_GetInstanceDiskFields())
+  fields.extend(_GetInstanceNetworkFields())
+
+  return _PrepareFieldList(fields)
+
+
 #: Fields available for node queries
 NODE_FIELDS = _BuildNodeFields()
+
+#: Fields available for instance queries
+INSTANCE_FIELDS = _BuildInstanceFields()
index 2e37fd8..48cb27b 100755 (executable)
@@ -30,6 +30,7 @@ from ganeti import compat
 from ganeti import errors
 from ganeti import query
 from ganeti import objects
+from ganeti import cmdlib
 
 import testutils
 
@@ -458,5 +459,270 @@ class TestNodeQuery(unittest.TestCase):
                       "hello", constants.QFT_BOOL, ctx, None)
 
 
+class TestInstanceQuery(unittest.TestCase):
+  def _Create(self, selected):
+    return query.Query(query.INSTANCE_FIELDS, selected)
+
+  def testSimple(self):
+    q = self._Create(["name", "be/memory", "ip"])
+    self.assertEqual(q.RequestedData(), set([query.IQ_CONFIG]))
+
+    cluster = objects.Cluster(cluster_name="testcluster",
+      hvparams=constants.HVC_DEFAULTS,
+      beparams={
+        constants.PP_DEFAULT: constants.BEC_DEFAULTS,
+        },
+      nicparams={
+        constants.PP_DEFAULT: constants.NICC_DEFAULTS,
+        })
+
+    instances = [
+      objects.Instance(name="inst1", hvparams={}, beparams={}, nics=[]),
+      objects.Instance(name="inst2", hvparams={}, nics=[],
+        beparams={
+          constants.BE_MEMORY: 512,
+        }),
+      objects.Instance(name="inst3", hvparams={}, beparams={},
+        nics=[objects.NIC(ip="192.0.2.99", nicparams={})]),
+      ]
+
+    iqd = query.InstanceQueryData(instances, cluster, None, [], [], {})
+    self.assertEqual(q.Query(iqd),
+      [[(constants.QRFS_NORMAL, "inst1"),
+        (constants.QRFS_NORMAL, 128),
+        (constants.QRFS_UNAVAIL, None),
+       ],
+       [(constants.QRFS_NORMAL, "inst2"),
+        (constants.QRFS_NORMAL, 512),
+        (constants.QRFS_UNAVAIL, None),
+       ],
+       [(constants.QRFS_NORMAL, "inst3"),
+        (constants.QRFS_NORMAL, 128),
+        (constants.QRFS_NORMAL, "192.0.2.99"),
+       ]])
+    self.assertEqual(q.OldStyleQuery(iqd),
+      [["inst1", 128, None],
+       ["inst2", 512, None],
+       ["inst3", 128, "192.0.2.99"]])
+
+  def test(self):
+    selected = query.INSTANCE_FIELDS.keys()
+    fieldidx = dict((field, idx) for idx, field in enumerate(selected))
+
+    macs = ["00:11:22:%02x:%02x:%02x" % (i % 255, i % 3, (i * 123) % 255)
+            for i in range(20)]
+
+    q = self._Create(selected)
+    self.assertEqual(q.RequestedData(),
+                     set([query.IQ_CONFIG, query.IQ_LIVE, query.IQ_DISKUSAGE]))
+
+    cluster = objects.Cluster(cluster_name="testcluster",
+      hvparams=constants.HVC_DEFAULTS,
+      beparams={
+        constants.PP_DEFAULT: constants.BEC_DEFAULTS,
+        },
+      nicparams={
+        constants.PP_DEFAULT: constants.NICC_DEFAULTS,
+        },
+      os_hvp={},
+      tcpudp_port_pool=set())
+
+    offline_nodes = ["nodeoff1", "nodeoff2"]
+    bad_nodes = ["nodebad1", "nodebad2", "nodebad3"] + offline_nodes
+    nodes = ["node%s" % i for i in range(10)] + bad_nodes
+
+    instances = [
+      objects.Instance(name="inst1", hvparams={}, beparams={}, nics=[],
+        uuid="f90eccb3-e227-4e3c-bf2a-94a21ca8f9cd",
+        ctime=1291244000, mtime=1291244400, serial_no=30,
+        admin_up=True, hypervisor=constants.HT_XEN_PVM, os="linux1",
+        primary_node="node1",
+        disk_template=constants.DT_PLAIN,
+        disks=[]),
+      objects.Instance(name="inst2", hvparams={}, nics=[],
+        uuid="73a0f8a7-068c-4630-ada2-c3440015ab1a",
+        ctime=1291211000, mtime=1291211077, serial_no=1,
+        admin_up=True, hypervisor=constants.HT_XEN_HVM, os="deb99",
+        primary_node="node5",
+        disk_template=constants.DT_DISKLESS,
+        disks=[],
+        beparams={
+          constants.BE_MEMORY: 512,
+        }),
+      objects.Instance(name="inst3", hvparams={}, beparams={},
+        uuid="11ec8dff-fb61-4850-bfe0-baa1803ff280",
+        ctime=1291011000, mtime=1291013000, serial_no=1923,
+        admin_up=False, hypervisor=constants.HT_KVM, os="busybox",
+        primary_node="node6",
+        disk_template=constants.DT_DRBD8,
+        disks=[],
+        nics=[
+          objects.NIC(ip="192.0.2.99", mac=macs.pop(),
+                      nicparams={
+                        constants.NIC_LINK: constants.DEFAULT_BRIDGE,
+                        }),
+          objects.NIC(ip=None, mac=macs.pop(), nicparams={}),
+          ]),
+      objects.Instance(name="inst4", hvparams={}, beparams={},
+        uuid="68dab168-3ef5-4c9d-b4d3-801e0672068c",
+        ctime=1291244390, mtime=1291244395, serial_no=25,
+        admin_up=False, hypervisor=constants.HT_XEN_PVM, os="linux1",
+        primary_node="nodeoff2",
+        disk_template=constants.DT_DRBD8,
+        disks=[],
+        nics=[
+          objects.NIC(ip="192.0.2.1", mac=macs.pop(),
+                      nicparams={
+                        constants.NIC_LINK: constants.DEFAULT_BRIDGE,
+                        }),
+          objects.NIC(ip="192.0.2.2", mac=macs.pop(), nicparams={}),
+          objects.NIC(ip="192.0.2.3", mac=macs.pop(),
+                      nicparams={
+                        constants.NIC_MODE: constants.NIC_MODE_ROUTED,
+                        }),
+          objects.NIC(ip="192.0.2.4", mac=macs.pop(),
+                      nicparams={
+                        constants.NIC_MODE: constants.NIC_MODE_BRIDGED,
+                        constants.NIC_LINK: "eth123",
+                        }),
+          ]),
+      objects.Instance(name="inst5", hvparams={}, nics=[],
+        uuid="0e3dca12-5b42-4e24-98a2-415267545bd0",
+        ctime=1231211000, mtime=1261200000, serial_no=3,
+        admin_up=True, hypervisor=constants.HT_XEN_HVM, os="deb99",
+        primary_node="nodebad2",
+        disk_template=constants.DT_DISKLESS,
+        disks=[],
+        beparams={
+          constants.BE_MEMORY: 512,
+        }),
+      objects.Instance(name="inst6", hvparams={}, nics=[],
+        uuid="72de6580-c8d5-4661-b902-38b5785bb8b3",
+        ctime=7513, mtime=11501, serial_no=13390,
+        admin_up=False, hypervisor=constants.HT_XEN_HVM, os="deb99",
+        primary_node="node7",
+        disk_template=constants.DT_DISKLESS,
+        disks=[],
+        beparams={
+          constants.BE_MEMORY: 768,
+        }),
+      ]
+
+    disk_usage = dict((inst.name,
+                       cmdlib._ComputeDiskSize(inst.disk_template,
+                                               [{"size": disk.size}
+                                                for disk in inst.disks]))
+                      for inst in instances)
+
+    inst_bridges = {
+      "inst3": [constants.DEFAULT_BRIDGE, constants.DEFAULT_BRIDGE],
+      "inst4": [constants.DEFAULT_BRIDGE, constants.DEFAULT_BRIDGE,
+                None, "eth123"],
+      }
+
+    live_data = {
+      "inst2": {
+        "vcpus": 3,
+        },
+      "inst4": {
+        "memory": 123,
+        },
+      "inst6": {
+        "memory": 768,
+        },
+      }
+
+    iqd = query.InstanceQueryData(instances, cluster, disk_usage,
+                                  offline_nodes, bad_nodes, live_data)
+    result = q.Query(iqd)
+    self.assertEqual(len(result), len(instances))
+    self.assert_(compat.all(len(row) == len(selected)
+                            for row in result))
+
+    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
+           "Offline nodes not included in bad nodes"
+
+    tested_status = set()
+
+    for (inst, row) in zip(instances, result):
+      assert inst.primary_node in nodes
+
+      self.assertEqual(row[fieldidx["name"]],
+                       (constants.QRFS_NORMAL, inst.name))
+
+      if inst.primary_node in offline_nodes:
+        exp_status = "ERROR_nodeoffline"
+      elif inst.primary_node in bad_nodes:
+        exp_status = "ERROR_nodedown"
+      elif inst.name in live_data:
+        if inst.admin_up:
+          exp_status = "running"
+        else:
+          exp_status = "ERROR_up"
+      elif inst.admin_up:
+        exp_status = "ERROR_down"
+      else:
+        exp_status = "ADMIN_down"
+
+      self.assertEqual(row[fieldidx["status"]],
+                       (constants.QRFS_NORMAL, exp_status))
+
+      (_, status) = row[fieldidx["status"]]
+      tested_status.add(status)
+
+      for (field, livefield) in [("oper_ram", "memory"),
+                                 ("oper_vcpus", "vcpus")]:
+        if inst.primary_node in bad_nodes:
+          exp = (constants.QRFS_NODATA, None)
+        elif inst.name in live_data:
+          value = live_data[inst.name].get(livefield, None)
+          if value is None:
+            exp = (constants.QRFS_UNAVAIL, None)
+          else:
+            exp = (constants.QRFS_NORMAL, value)
+        else:
+          exp = (constants.QRFS_UNAVAIL, None)
+
+        self.assertEqual(row[fieldidx[field]], exp)
+
+      bridges = inst_bridges.get(inst.name, [])
+      self.assertEqual(row[fieldidx["nic.bridges"]],
+                       (constants.QRFS_NORMAL, bridges))
+      if bridges:
+        self.assertEqual(row[fieldidx["bridge"]],
+                         (constants.QRFS_NORMAL, bridges[0]))
+      else:
+        self.assertEqual(row[fieldidx["bridge"]],
+                         (constants.QRFS_UNAVAIL, None))
+
+      for i in range(constants.MAX_NICS):
+        if i < len(bridges) and bridges[i] is not None:
+          exp = (constants.QRFS_NORMAL, bridges[i])
+        else:
+          exp = (constants.QRFS_UNAVAIL, None)
+        self.assertEqual(row[fieldidx["nic.bridge/%s" % i]], exp)
+
+      if inst.primary_node in bad_nodes:
+        exp = (constants.QRFS_NODATA, None)
+      else:
+        exp = (constants.QRFS_NORMAL, inst.name in live_data)
+      self.assertEqual(row[fieldidx["oper_state"]], exp)
+
+      usage = disk_usage[inst.name]
+      if usage is None:
+        usage = 0
+      self.assertEqual(row[fieldidx["disk_usage"]],
+                       (constants.QRFS_NORMAL, usage))
+
+      self.assertEqual(row[fieldidx["sda_size"]], row[fieldidx["disk.size/0"]])
+      self.assertEqual(row[fieldidx["sdb_size"]], row[fieldidx["disk.size/1"]])
+
+    # Ensure all possible status' have been tested
+    self.assertEqual(tested_status,
+                     set(["ERROR_nodeoffline", "ERROR_nodedown",
+                          "running", "ERROR_up", "ERROR_down",
+                          "ADMIN_down"]))
+
+
 if __name__ == "__main__":
   testutils.GanetiTestProgram()