From 1406327ceeb190ef4584116e49424f2c36dc4a91 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Mon, 13 Feb 2012 09:58:14 -0800 Subject: Expand Quantum Manager Unit Tests + Associated Fixes - Add fake quantum client to support more complete testing of Quantum Mgr - Fix issue related to filters not being appended to Quantum queries - Expand unit tests to check for # vifs, ports created, nic order. - Add direct unit tests for quantum_connection class. - improve error reporting for get_port_by_attachment - remove invalid comment about not supporting floating IPs - Remove E-3 hack that only allowed L3 gateway if DHCP was enabled. Proper way to disable L3 gateway is to use the nova.network.l3.NullL3 driver. - fix delete_network to properly find UUIDs for project specific networks - fix issue with 'requested_networks' not working properly for a provider network. - remove dead chunk of code that is unused because all networks in quantum currently correspond to an entry in the nova db. - make sure validate_networks allows both provider + tenant networks. Update: incorporated feedback from first round of reviews. Change-Id: Id63d27d457c63efe7ea27a7fbd7470a0a5709125 --- nova/tests/test_quantum.py | 471 +++++++++++++++++++++++---------------------- 1 file changed, 236 insertions(+), 235 deletions(-) (limited to 'nova/tests') diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py index 96c259bd5..cf15cc02f 100644 --- a/nova/tests/test_quantum.py +++ b/nova/tests/test_quantum.py @@ -1,6 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2011 Nicira, Inc. +# Copyright 2011,2012 Nicira, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -20,118 +20,34 @@ from nova import db from nova.db.sqlalchemy import models from nova.db.sqlalchemy.session import get_session from nova import exception +from nova import flags from nova import log as logging +from nova.network.quantum import client as quantum_client +from nova.network.quantum import fake_client from nova.network.quantum import manager as quantum_manager from nova.network.quantum import melange_connection +from nova.network.quantum import quantum_connection from nova import test from nova import utils from nova.network import manager LOG = logging.getLogger(__name__) - - -# this class can be used for unit functional/testing on nova, -# as it does not actually make remote calls to the Quantum service -class FakeQuantumClientConnection(object): - - def __init__(self): - self.nets = {} - - def get_networks_for_tenant(self, tenant_id): - net_ids = [] - for net_id, n in self.nets.items(): - if n['tenant-id'] == tenant_id: - net_ids.append(net_id) - return {'networks': net_ids} - - def create_network(self, tenant_id, network_name, **kwargs): - - uuid = str(utils.gen_uuid()) - self.nets[uuid] = {'net-name': network_name, - 'tenant-id': tenant_id, - 'ports': {}} - return uuid - - def delete_network(self, tenant_id, net_id): - if self.nets[net_id]['tenant-id'] == tenant_id: - del self.nets[net_id] - - def network_exists(self, tenant_id, net_id): - try: - return self.nets[net_id]['tenant-id'] == tenant_id - except KeyError: - return False - - def get_network_name(self, tenant_id, net_id): - return self.nets[net_id]['net-name'] - - def _confirm_not_attached(self, interface_id): - for n in self.nets.values(): - for p in n['ports'].values(): - if p['attachment-id'] == interface_id: - raise Exception(_("interface '%s' is already attached" % - interface_id)) - - def create_and_attach_port(self, tenant_id, net_id, interface_id, - **kwargs): - if not self.network_exists(tenant_id, net_id): - raise Exception( - _("network %(net_id)s does not exist for tenant %(tenant_id)" - % locals())) - - self._confirm_not_attached(interface_id) - uuid = str(utils.gen_uuid()) - self.nets[net_id]['ports'][uuid] = \ - {"port-state": "ACTIVE", - "attachment-id": interface_id} - - def detach_and_delete_port(self, tenant_id, net_id, port_id): - if not self.network_exists(tenant_id, net_id): - raise exception.NotFound( - _("network %(net_id)s does not exist " - "for tenant %(tenant_id)s" % locals())) - del self.nets[net_id]['ports'][port_id] - - def get_port_by_attachment(self, tenant_id, net_id, attachment_id): - for nid, n in self.nets.items(): - if nid == net_id and n['tenant-id'] == tenant_id: - for port_id, p in n['ports'].items(): - if p['attachment-id'] == attachment_id: - return port_id - return None - - def get_attached_ports(self, tenant_id, net_id): - ports = [] - for nid, n in self.nets.items(): - if nid == net_id and n['tenant-id'] == tenant_id: - for port_id, p in n['ports'].items(): - ports.append({'port-id': port_id, - 'attachment': p['attachment-id']}) - return ports - - def get_networks(self, tenant_id): - nets = [] - for nid, n in self.nets.items(): - if n['tenant-id'] == tenant_id: - x = {'id': nid} - nets.append(x) - return {'networks': nets} +FLAGS = flags.FLAGS networks = [{'label': 'project1-net1', 'injected': False, 'multi_host': False, - 'cidr': '192.168.0.0/24', - 'cidr_v6': '2001:1db8::/64', - 'gateway_v6': '2001:1db8::1', + 'cidr': '100.168.0.0/24', + 'cidr_v6': '100:1db8::/64', + 'gateway_v6': '100:1db8::1', 'netmask_v6': '64', 'netmask': '255.255.255.0', 'bridge': None, 'bridge_interface': None, - 'gateway': '192.168.0.1', - 'broadcast': '192.168.0.255', - 'dns1': '192.168.0.1', - 'dns2': '192.168.0.2', + 'gateway': '100.168.0.1', + 'broadcast': '100.168.0.255', + 'dns1': '8.8.8.8', 'vlan': None, 'host': None, 'vpn_public_address': None, @@ -140,17 +56,16 @@ networks = [{'label': 'project1-net1', {'label': 'project2-net1', 'injected': False, 'multi_host': False, - 'cidr': '192.168.1.0/24', - 'cidr_v6': '2001:1db9::/64', - 'gateway_v6': '2001:1db9::1', + 'cidr': '101.168.1.0/24', + 'cidr_v6': '101:1db9::/64', + 'gateway_v6': '101:1db9::1', 'netmask_v6': '64', 'netmask': '255.255.255.0', 'bridge': None, 'bridge_interface': None, - 'gateway': '192.168.1.1', - 'broadcast': '192.168.1.255', - 'dns1': '192.168.0.1', - 'dns2': '192.168.0.2', + 'gateway': '101.168.1.1', + 'broadcast': '101.168.1.255', + 'dns1': '8.8.8.8', 'vlan': None, 'host': None, 'project_id': 'fake_project2', @@ -158,17 +73,16 @@ networks = [{'label': 'project1-net1', {'label': "public", 'injected': False, 'multi_host': False, - 'cidr': '10.0.0.0/24', - 'cidr_v6': '2001:1dba::/64', - 'gateway_v6': '2001:1dba::1', + 'cidr': '102.0.0.0/24', + 'cidr_v6': '102:1dba::/64', + 'gateway_v6': '102:1dba::1', 'netmask_v6': '64', 'netmask': '255.255.255.0', 'bridge': None, 'bridge_interface': None, - 'gateway': '10.0.0.1', - 'broadcast': '10.0.0.255', - 'dns1': '10.0.0.1', - 'dns2': '10.0.0.2', + 'gateway': '102.0.0.1', + 'broadcast': '102.0.0.255', + 'dns1': '8.8.8.8', 'vlan': None, 'host': None, 'project_id': None, @@ -176,31 +90,99 @@ networks = [{'label': 'project1-net1', {'label': "project2-net2", 'injected': False, 'multi_host': False, - 'cidr': '9.0.0.0/24', - 'cidr_v6': '2001:1dbb::/64', - 'gateway_v6': '2001:1dbb::1', + 'cidr': '103.0.0.0/24', + 'cidr_v6': '103:1dbb::/64', + 'gateway_v6': '103:1dbb::1', 'netmask_v6': '64', 'netmask': '255.255.255.0', 'bridge': None, 'bridge_interface': None, - 'gateway': '9.0.0.1', - 'broadcast': '9.0.0.255', - 'dns1': '9.0.0.1', - 'dns2': '9.0.0.2', + 'gateway': '103.0.0.1', + 'broadcast': '103.0.0.255', + 'dns1': '8.8.8.8', 'vlan': None, 'host': None, 'project_id': "fake_project2", 'priority': 2}] -# this is a base class to be used by all other Quantum Test classes +class QuantumConnectionTestCase(test.TestCase): + + def test_connection(self): + fc = fake_client.FakeClient(LOG) + qc = quantum_connection.QuantumClientConnection(client=fc) + t = "tenant1" + net1_name = "net1" + net1_uuid = qc.create_network(t, net1_name) + self.assertEquals(net1_name, qc.get_network_name(t, net1_uuid)) + self.assertTrue(qc.network_exists(t, net1_uuid)) + self.assertFalse(qc.network_exists(t, "fake-uuid")) + self.assertFalse(qc.network_exists("fake-tenant", net1_uuid)) + + nets = qc.get_networks(t)['networks'] + self.assertEquals(len(nets), 1) + self.assertEquals(nets[0]['id'], net1_uuid) + + num_ports = 10 + for i in range(0, num_ports): + qc.create_and_attach_port(t, net1_uuid, + 'iface' + str(i), state='ACTIVE') + + self.assertEquals(len(qc.get_attached_ports(t, net1_uuid)), num_ports) + + for i in range(0, num_ports): + port_uuid = qc.get_port_by_attachment(t, net1_uuid, + 'iface' + str(i)) + self.assertTrue(port_uuid) + qc.detach_and_delete_port(t, net1_uuid, port_uuid) + + self.assertEquals(len(qc.get_attached_ports(t, net1_uuid)), 0) + + # test port not found + qc.create_and_attach_port(t, net1_uuid, 'foo', state='ACTIVE') + port_uuid = qc.get_port_by_attachment(t, net1_uuid, 'foo') + qc.detach_and_delete_port(t, net1_uuid, port_uuid) + self.assertRaises(quantum_client.QuantumNotFoundException, + qc.detach_and_delete_port, t, + net1_uuid, port_uuid) + + qc.delete_network(t, net1_uuid) + self.assertFalse(qc.network_exists(t, net1_uuid)) + self.assertEquals(len(qc.get_networks(t)['networks']), 0) + + self.assertRaises(quantum_client.QuantumNotFoundException, + qc.get_network_name, t, net1_uuid) + + +# this is a base class to be used by other QuantumManager Test classes class QuantumNovaTestCase(test.TestCase): + def setUp(self): super(QuantumNovaTestCase, self).setUp() + self.flags(quantum_use_dhcp=True) + self.flags(l3_lib="nova.network.l3.LinuxNetL3") + linuxdrv = "nova.network.linux_net.LinuxOVSInterfaceDriver" + self.flags(linuxnet_interface_driver=linuxdrv) + fc = fake_client.FakeClient(LOG) + qc = quantum_connection.QuantumClientConnection(client=fc) + self.net_man = quantum_manager.QuantumManager( ipam_lib="nova.network.quantum.nova_ipam_lib", - q_conn=FakeQuantumClientConnection()) + q_conn=qc) + + def func(arg1, arg2): + pass + + def func2(arg1, arg2, arg3): + pass + + def func1(arg1): + pass + + self.net_man.driver.update_dhcp_hostfile_with_text = func + self.net_man.driver.restart_dhcp = func2 + self.net_man.driver.kill_dhcp = func1 # Tests seem to create some networks by default, which # we don't want. So we delete them. @@ -219,6 +201,8 @@ class QuantumNovaTestCase(test.TestCase): for fip_ref in result: session.delete(fip_ref) + self.net_man.init_host() + def _create_network(self, n): ctx = context.RequestContext('user1', n['project_id']) nwks = self.net_man.create_networks( @@ -230,13 +214,12 @@ class QuantumNovaTestCase(test.TestCase): gateway=n['gateway'], gateway_v6=n['gateway_v6'], bridge=None, bridge_interface=None, dns1=n['dns1'], - dns2=n['dns2'], project_id=n['project_id'], priority=n['priority']) n['uuid'] = nwks[0]['uuid'] -class QuantumNovaIPAMTestCase(QuantumNovaTestCase): +class QuantumManagerTestCase(QuantumNovaTestCase): def test_create_and_delete_nets(self): self._create_nets() self._delete_nets() @@ -248,143 +231,131 @@ class QuantumNovaIPAMTestCase(QuantumNovaTestCase): def _delete_nets(self): for n in networks: ctx = context.RequestContext('user1', n['project_id']) - db_nets = db.network_get_all(ctx.elevated()) - for x in db_nets: - if x['label'] == n['label']: - n['uuid'] = x['uuid'] self.net_man.delete_network(ctx, None, n['uuid']) + self.assertRaises(exception.NoNetworksFound, + db.network_get_all, ctx.elevated()) - def test_allocate_and_deallocate_instance_static(self): - self._create_nets() + def _validate_nw_info(self, nw_info, expected_net_labels): - project_id = "fake_project1" - ctx = context.RequestContext('user1', project_id) + self.assertEquals(len(nw_info), len(expected_net_labels)) - instance_ref = db.instance_create(ctx, - {"project_id": project_id}) + ctx = context.RequestContext('user1', 'foo').elevated() + all_net_map = {} + for n in db.network_get_all(ctx): + all_net_map[n['label']] = n - def func(arg1, arg2): - pass + for i in range(0, len(nw_info)): + vif = nw_info[i] + net = all_net_map[expected_net_labels[i]] - def func2(arg1, arg2, arg3): - pass + # simple test assumes that each starting prefix is unique + expected_v4_cidr_start = net['cidr'].split(".")[0].lower() + expected_v6_cidr_start = net['cidr_v6'].split(":")[0].lower() - def func1(arg1): - pass + for subnet in vif['network']['subnets']: + addr = subnet['ips'][0]['address'] + if subnet['version'] == 4: + address_start = addr.split(".")[0].lower() + self.assertTrue(expected_v4_cidr_start, address_start) + else: + address_start = addr.split(":")[0].lower() + self.assertTrue(expected_v6_cidr_start, address_start) + + # confirm that there is a DHCP device on corresponding net + for l in expected_net_labels: + n = all_net_map[l] + tenant_id = (n['project_id'] or + FLAGS.quantum_default_tenant_id) + ports = self.net_man.q_conn.get_attached_ports( + tenant_id, n['uuid']) + self.assertEquals(len(ports), 2) # gw + instance VIF + + # make sure we aren't allowed to delete network with + # active port + self.assertRaises(Exception, self.net_man.delete_network, + ctx, None, n['uuid']) + + def _check_vifs(self, expect_num_vifs): + ctx = context.RequestContext('user1', "").elevated() + self.assertEqual(len(db.virtual_interface_get_all(ctx)), + expect_num_vifs) + + def _allocate_and_deallocate_instance(self, project_id, requested_networks, + expected_labels): + + ctx = context.RequestContext('user1', project_id) + self._check_vifs(0) + + instance_ref = db.instance_create(ctx, + {"project_id": project_id}) - self.net_man.driver.update_dhcp_hostfile_with_text = func - self.net_man.driver.restart_dhcp = func2 - self.net_man.driver.kill_dhcp = func1 nw_info = self.net_man.allocate_for_instance(ctx.elevated(), instance_id=instance_ref['id'], host="", rxtx_factor=3, - project_id=project_id) + project_id=project_id, + requested_networks=requested_networks) + + self._check_vifs(len(nw_info)) - self.assertEquals(len(nw_info), 2) + self._validate_nw_info(nw_info, expected_labels) - cidrs = ['10.', '192.'] - addrs = ['10.', '192.'] - cidrs_v6 = ['2001:1dba:', '2001:1db8:'] - addrs_v6 = ['2001:1dba:', '2001:1db8:'] + nw_info = self.net_man.get_instance_nw_info(ctx, instance_ref['id'], + instance_ref['uuid'], + instance_ref['instance_type_id'], "") - def check_for_startswith(choices, choice): - for v in choices: - if choice.startswith(v): - choices.remove(v) - return True - return False + self._check_vifs(len(nw_info)) + self._validate_nw_info(nw_info, expected_labels) - # we don't know which order the NICs will be in until we - # introduce the notion of priority + port_net_pairs = [] for vif in nw_info: - for subnet in vif['network']['subnets']: - cidr = subnet['cidr'].lower() - if subnet['version'] == 4: - # v4 cidr - self.assertTrue(check_for_startswith(cidrs, cidr)) - # v4 address - address = subnet['ips'][0]['address'] - self.assertTrue(check_for_startswith(addrs, address)) - else: - # v6 cidr - self.assertTrue(check_for_startswith(cidrs_v6, cidr)) - # v6 address - address = subnet['ips'][0]['address'] - self.assertTrue(check_for_startswith(addrs_v6, address)) + nid = vif['network']['id'] + pid = self.net_man.q_conn.get_port_by_attachment( + project_id, nid, vif['id']) + if pid is None: + pid = self.net_man.q_conn.get_port_by_attachment( + FLAGS.quantum_default_tenant_id, + nid, vif['id']) + self.assertTrue(pid is not None) + port_net_pairs.append((pid, nid)) self.net_man.deallocate_for_instance(ctx, instance_id=instance_ref['id'], project_id=project_id) + for pid, nid in port_net_pairs: + self.assertRaises(quantum_client.QuantumNotFoundException, + self.net_man.q_conn.detach_and_delete_port, + project_id, nid, pid) + self.assertRaises(quantum_client.QuantumNotFoundException, + self.net_man.q_conn.detach_and_delete_port, + FLAGS.quantum_default_tenant_id, nid, pid) + + self._check_vifs(0) + + def test_allocate_and_deallocate_instance_static(self): + self._create_nets() + self._allocate_and_deallocate_instance("fake_project1", None, + ['public', 'project1-net1']) self._delete_nets() def test_allocate_and_deallocate_instance_dynamic(self): + self._create_nets() project_id = "fake_project2" ctx = context.RequestContext('user1', project_id) - - net_ids = self.net_man.q_conn.get_networks_for_tenant(project_id) - requested_networks = [(net_id, None) for net_id in - net_ids['networks']] + all_valid_networks = self.net_man.ipam.get_project_and_global_net_ids( + ctx, project_id) + requested_networks = [(n[0], None) for n in all_valid_networks] self.net_man.validate_networks(ctx, requested_networks) - instance_ref = db.instance_create(ctx, - {"project_id": project_id}) - - def func(arg1, arg2): - pass - - def func1(arg1): - pass - - def func2(arg1, arg2, arg3): - pass - - self.net_man.driver.update_dhcp_hostfile_with_text = func - self.net_man.driver.restart_dhcp = func2 - self.net_man.driver.kill_dhcp = func1 - nw_info = self.net_man.allocate_for_instance(ctx, - instance_id=instance_ref['id'], host="", - rxtx_factor=3, - project_id=project_id, - requested_networks=requested_networks) - - self.assertEquals(len(nw_info), 2) - - cidrs = ['9.', '192.'] - addrs = ['9.', '192.'] - cidrs_v6 = ['2001:1dbb:', '2001:1db9:'] - addrs_v6 = ['2001:1dbb:', '2001:1db9:'] - - def check_for_startswith(choices, choice): - for v in choices: - if choice.startswith(v): - choices.remove(v) - return True - - # we don't know which order the NICs will be in until we - # introduce the notion of priority - for vif in nw_info: - for subnet in vif['network']['subnets']: - cidr = subnet['cidr'].lower() - if subnet['version'] == 4: - # v4 cidr - self.assertTrue(check_for_startswith(cidrs, cidr)) - # v4 address - address = subnet['ips'][0]['address'] - self.assertTrue(check_for_startswith(addrs, address)) - else: - # v6 cidr - self.assertTrue(check_for_startswith(cidrs_v6, cidr)) - # v6 address - address = subnet['ips'][0]['address'] - self.assertTrue(check_for_startswith(addrs_v6, address)) - - self.net_man.deallocate_for_instance(ctx, - instance_id=instance_ref['id'], - project_id=project_id) + label_map = {} + for n in db.network_get_all(ctx.elevated()): + label_map[n['uuid']] = n['label'] + expected_labels = [label_map[uid] for uid, _i in requested_networks] + self._allocate_and_deallocate_instance(project_id, requested_networks, + expected_labels) self._delete_nets() def test_validate_bad_network(self): @@ -392,6 +363,32 @@ class QuantumNovaIPAMTestCase(QuantumNovaTestCase): self.assertRaises(exception.NetworkNotFound, self.net_man.validate_networks, ctx, [("", None)]) + def test_create_net_external_uuid(self): + """Tests use case where network can be created directly via + Quantum API, then the UUID is passed in via nova-manage""" + project_id = "foo_project" + ctx = context.RequestContext('user1', project_id) + net_id = self.net_man.q_conn.create_network(project_id, 'net1') + self.net_man.create_networks( + ctx, + label='achtungbaby', + cidr="9.9.9.0/24", + multi_host=False, + num_networks=1, + network_size=256, + cidr_v6=None, + gateway="9.9.9.1", + gateway_v6=None, + bridge=None, + bridge_interface=None, + dns1="8.8.8.8", + project_id=project_id, + priority=9, + uuid=net_id) + net = db.network_get_by_uuid(ctx.elevated(), net_id) + self.assertTrue(net is not None) + self.assertEquals(net['uuid'], net_id) + class QuantumNovaMACGenerationTestCase(QuantumNovaTestCase): def test_local_mac_address_creation(self): @@ -403,8 +400,9 @@ class QuantumNovaMACGenerationTestCase(QuantumNovaTestCase): ctx = context.RequestContext('user1', project_id) self._create_network(networks[0]) - net_ids = self.net_man.q_conn.get_networks_for_tenant(project_id) - requested_networks = [(net_id, None) for net_id in net_ids['networks']] + all_valid_networks = self.net_man.ipam.get_project_and_global_net_ids( + ctx, project_id) + requested_networks = [(n[0], None) for n in all_valid_networks] instance_ref = db.api.instance_create(ctx, {"project_id": project_id}) @@ -424,8 +422,9 @@ class QuantumNovaMACGenerationTestCase(QuantumNovaTestCase): ctx = context.RequestContext('user1', project_id) self._create_network(networks[0]) - net_ids = self.net_man.q_conn.get_networks_for_tenant(project_id) - requested_networks = [(net_id, None) for net_id in net_ids['networks']] + all_valid_networks = self.net_man.ipam.get_project_and_global_net_ids( + ctx, project_id) + requested_networks = [(n[0], None) for n in all_valid_networks] instance_ref = db.api.instance_create(ctx, {"project_id": project_id}) @@ -448,8 +447,9 @@ class QuantumNovaPortSecurityTestCase(QuantumNovaTestCase): ctx = context.RequestContext('user1', project_id) self._create_network(networks[0]) - net_ids = self.net_man.q_conn.get_networks_for_tenant(project_id) - requested_networks = [(net_id, None) for net_id in net_ids['networks']] + all_valid_networks = self.net_man.ipam.get_project_and_global_net_ids( + ctx, project_id) + requested_networks = [(n[0], None) for n in all_valid_networks] instance_ref = db.api.instance_create(ctx, {"project_id": project_id}) @@ -483,8 +483,9 @@ class QuantumNovaPortSecurityTestCase(QuantumNovaTestCase): ctx = context.RequestContext('user1', project_id) self._create_network(networks[0]) - net_ids = self.net_man.q_conn.get_networks_for_tenant(project_id) - requested_networks = [(net_id, None) for net_id in net_ids['networks']] + all_valid_networks = self.net_man.ipam.get_project_and_global_net_ids( + ctx, project_id) + requested_networks = [(n[0], None) for n in all_valid_networks] instance_ref = db.api.instance_create(ctx, {"project_id": project_id}) -- cgit