From 1c8addc6459d26e589b7e21e21a77141a76b4d43 Mon Sep 17 00:00:00 2001 From: Michael Hanselmann Date: Tue, 30 Nov 2010 14:56:32 +0100 Subject: [PATCH] query: Add definition for instance queries Signed-off-by: Michael Hanselmann Reviewed-by: Iustin Pop --- lib/query.py | 492 +++++++++++++++++++++++++++++++++++++++++ test/ganeti.query_unittest.py | 266 ++++++++++++++++++++++ 2 files changed, 758 insertions(+) diff --git a/lib/query.py b/lib/query.py index 9d64694..80cb68a 100644 --- a/lib/query.py +++ b/lib/query.py @@ -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() diff --git a/test/ganeti.query_unittest.py b/test/ganeti.query_unittest.py index 2e37fd8..48cb27b 100755 --- a/test/ganeti.query_unittest.py +++ b/test/ganeti.query_unittest.py @@ -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() -- 1.7.10.4