From 79cd851548b87fb81ccad9e7a2ced717d4ccd58e Mon Sep 17 00:00:00 2001 From: German Eichberger Date: Thu, 20 Sep 2018 14:32:41 -0700 Subject: [PATCH] Refactor the AAP driver to not depend on nova Replace calls to the nova client with calls to the compute driver. This will help non vm efforts (e.g. zune) and also make the code easier to break up later. Change-Id: I7ee175c5ecb98af0ca5e299c2ac10e43eb40ed30 --- octavia/compute/compute_base.py | 25 ++++++ octavia/compute/drivers/noop_driver/driver.py | 33 ++++++++ octavia/compute/drivers/nova_driver.py | 44 +++++++++++ .../drivers/neutron/allowed_address_pairs.py | 37 ++++----- .../drivers/test_compute_noop_driver.py | 19 +++++ .../unit/compute/drivers/test_nova_driver.py | 29 +++++++ .../neutron/test_allowed_address_pairs.py | 78 ++++++++----------- 7 files changed, 197 insertions(+), 68 deletions(-) diff --git a/octavia/compute/compute_base.py b/octavia/compute/compute_base.py index 4e69b2c976..071eefd86c 100644 --- a/octavia/compute/compute_base.py +++ b/octavia/compute/compute_base.py @@ -96,3 +96,28 @@ class ComputeBase(object): :param server_group_id: the uuid of a server group """ pass + + @abc.abstractmethod + def attach_network_or_port(self, compute_id, network_id=None, + ip_address=None, port_id=None): + """Connects an existing amphora to an existing network. + + :param compute_id: id of an amphora in the compute service + :param network_id: id of a network + :param ip_address: ip address to attempt to be assigned to interface + :param port_id: id of the neutron port + :return: nova interface + :raises: Exception + """ + pass + + @abc.abstractmethod + def detach_port(self, compute_id, port_id): + """Disconnects an existing amphora from an existing port. + + :param compute_id: id of an amphora in the compute service + :param port_id: id of the port + :return: None + :raises: Exception + """ + pass diff --git a/octavia/compute/drivers/noop_driver/driver.py b/octavia/compute/drivers/noop_driver/driver.py index ca3aefcedc..de2a680b6d 100644 --- a/octavia/compute/drivers/noop_driver/driver.py +++ b/octavia/compute/drivers/noop_driver/driver.py @@ -18,6 +18,7 @@ from oslo_utils import uuidutils from octavia.common import constants from octavia.common import data_models from octavia.compute import compute_base as driver_base +from octavia.network import data_models as network_models LOG = logging.getLogger(__name__) @@ -81,6 +82,30 @@ class NoopManager(object): self.__class__.__name__, server_group_id) self.computeconfig[server_group_id] = (server_group_id, 'delete') + def attach_network_or_port(self, compute_id, network_id, ip_address=None, + port_id=None): + LOG.debug("Compute %s no-op, attach_network_or_port compute_id %s," + "network_id %s, ip_address %s, port_id %s", + self.__class__.__name__, compute_id, + network_id, ip_address, port_id) + self.computeconfig[(compute_id, network_id, ip_address, port_id)] = ( + compute_id, network_id, ip_address, port_id, + 'attach_network_or_port') + return network_models.Interface( + id=uuidutils.generate_uuid(), + compute_id=compute_id, + network_id=network_id, + fixed_ips=[], + port_id=uuidutils.generate_uuid() + ) + + def detach_port(self, compute_id, port_id): + LOG.debug("Compute %s no-op, detach_network compute_id %s, " + "port_id %s", + self.__class__.__name__, compute_id, port_id) + self.computeconfig[(compute_id, port_id)] = ( + compute_id, port_id, 'detach_port') + class NoopComputeDriver(driver_base.ComputeBase): def __init__(self): @@ -114,3 +139,11 @@ class NoopComputeDriver(driver_base.ComputeBase): def delete_server_group(self, server_group_id): self.driver.delete_server_group(server_group_id) + + def attach_network_or_port(self, compute_id, network_id, ip_address=None, + port_id=None): + self.driver.attach_network_or_port(compute_id, network_id, ip_address, + port_id) + + def detach_port(self, compute_id, port_id): + self.driver.detach_port(compute_id, port_id) diff --git a/octavia/compute/drivers/nova_driver.py b/octavia/compute/drivers/nova_driver.py index 18bb940c21..3190f4d65a 100644 --- a/octavia/compute/drivers/nova_driver.py +++ b/octavia/compute/drivers/nova_driver.py @@ -24,6 +24,7 @@ from octavia.common import constants from octavia.common import data_models as models from octavia.common import exceptions from octavia.compute import compute_base +from octavia.i18n import _ LOG = logging.getLogger(__name__) @@ -282,3 +283,46 @@ class VirtualMachineManager(compute_base.ComputeBase): except Exception: LOG.exception("Error delete server group instance.") raise exceptions.ServerGroupObjectDeleteException() + + def attach_network_or_port(self, compute_id, network_id, ip_address=None, + port_id=None): + """Attaching a port or a network to an existing amphora + + :param compute_id: id of an amphora in the compute service + :param network_id: id of a network + :param ip_address: ip address to attempt to be assigned to interface + :param port_id: id of the neutron port + :return: nova interface instance + :raises: Exception + """ + try: + interface = self.manager.interface_attach( + server=compute_id, net_id=network_id, fixed_ip=ip_address, + port_id=port_id) + except Exception: + message = _('Error attaching network {network_id} with ip ' + '{ip_address} and port {port} to amphora ' + '(compute_id: {compute_id}) ').format( + compute_id=compute_id, + network_id=network_id, + ip_address=ip_address, + port=port_id) + LOG.error(message) + raise + return interface + + def detach_port(self, compute_id, port_id): + """Detaches a port from an existing amphora. + + :param compute_id: id of an amphora in the compute service + :param port_id: id of the port + :return: None + """ + try: + self.manager.interface_detach(server=compute_id, + port_id=port_id) + except Exception: + LOG.error('Error detaching port {port_id} from amphora ' + 'with compute ID {compute_id}. ' + 'Skipping.'.format(port_id=port_id, + compute_id=compute_id)) diff --git a/octavia/network/drivers/neutron/allowed_address_pairs.py b/octavia/network/drivers/neutron/allowed_address_pairs.py index 7846697c2d..aae3b5e26d 100644 --- a/octavia/network/drivers/neutron/allowed_address_pairs.py +++ b/octavia/network/drivers/neutron/allowed_address_pairs.py @@ -20,8 +20,8 @@ from novaclient import exceptions as nova_client_exceptions from oslo_config import cfg from oslo_log import log as logging import six +from stevedore import driver as stevedore_driver -from octavia.common import clients from octavia.common import constants from octavia.common import data_models from octavia.common import exceptions @@ -45,14 +45,11 @@ class AllowedAddressPairsDriver(neutron_base.BaseNeutronDriver): def __init__(self): super(AllowedAddressPairsDriver, self).__init__() self._check_aap_loaded() - self.nova_client = clients.NovaAuth.get_nova_client( - endpoint=CONF.nova.endpoint, - region=CONF.nova.region_name, - endpoint_type=CONF.nova.endpoint_type, - service_name=CONF.nova.service_name, - insecure=CONF.nova.insecure, - cacert=CONF.nova.ca_certificates_file - ) + self.compute = stevedore_driver.DriverManager( + namespace='octavia.compute.drivers', + name=CONF.controller_worker.compute_driver, + invoke_on_load=True + ).driver def _check_aap_loaded(self): if not self._check_extension_enabled(AAP_EXT_ALIAS): @@ -481,9 +478,9 @@ class AllowedAddressPairsDriver(neutron_base.BaseNeutronDriver): def plug_network(self, compute_id, network_id, ip_address=None): try: - interface = self.nova_client.servers.interface_attach( - server=compute_id, net_id=network_id, fixed_ip=ip_address, - port_id=None) + interface = self.compute.attach_network_or_port( + compute_id=compute_id, network_id=network_id, + ip_address=ip_address) except nova_client_exceptions.NotFound as e: if 'Instance' in str(e): raise base.AmphoraNotFound(str(e)) @@ -511,14 +508,8 @@ class AllowedAddressPairsDriver(neutron_base.BaseNeutronDriver): unpluggers = self._get_interfaces_to_unplug(interfaces, network_id, ip_address=ip_address) for index, unplugger in enumerate(unpluggers): - try: - self.nova_client.servers.interface_detach( - server=compute_id, port_id=unplugger.port_id) - except Exception: - LOG.warning('Error unplugging port {port_id} from amphora ' - 'with compute ID {compute_id}. ' - 'Skipping.'.format(port_id=unplugger.port_id, - compute_id=compute_id)) + self.compute.detach_port( + compute_id=compute_id, port_id=unplugger.port_id) def update_vip(self, load_balancer, for_delete=False): sec_grp = self._get_lb_security_group(load_balancer.id) @@ -561,9 +552,9 @@ class AllowedAddressPairsDriver(neutron_base.BaseNeutronDriver): def plug_port(self, amphora, port): try: - interface = self.nova_client.servers.interface_attach( - server=amphora.compute_id, net_id=None, - fixed_ip=None, port_id=port.id) + interface = self.compute.attach_network_or_port( + compute_id=amphora.compute_id, network_id=None, + ip_address=None, port_id=port.id) plugged_interface = self._nova_interface_to_octavia_interface( amphora.compute_id, interface) except nova_client_exceptions.NotFound as e: diff --git a/octavia/tests/unit/compute/drivers/test_compute_noop_driver.py b/octavia/tests/unit/compute/drivers/test_compute_noop_driver.py index 42d39a8f6d..6989734f9d 100644 --- a/octavia/tests/unit/compute/drivers/test_compute_noop_driver.py +++ b/octavia/tests/unit/compute/drivers/test_compute_noop_driver.py @@ -46,6 +46,9 @@ class TestNoopComputeDriver(base.TestCase): self.server_group_name = 'my_server_group' self.server_group_id = self.FAKE_UUID_6 self.port_ids = ['port-id-1'] + self.port_id = 88 + self.network_id = uuidutils.generate_uuid() + self.ip_address = "192.0.2.2" def test_build(self): self.driver.build(self.name, self.amphora_flavor, @@ -101,3 +104,19 @@ class TestNoopComputeDriver(base.TestCase): self.assertEqual((self.server_group_id, 'delete'), self.driver.driver.computeconfig[ self.server_group_id]) + + def test_attach_network_or_port(self): + self.driver.attach_network_or_port(self.amphora_id, self.network_id, + self.ip_address, self.port_id) + self.assertEqual((self.amphora_id, self.network_id, self.ip_address, + self.port_id, 'attach_network_or_port'), + self.driver.driver.computeconfig[( + self.amphora_id, self.network_id, + self.ip_address, self.port_id)]) + + def test_detach_port(self): + self.driver.detach_port(self.amphora_id, self.port_id) + self.assertEqual((self.amphora_id, self.port_id, + 'detach_port'), + self.driver.driver.computeconfig[( + self.amphora_id, self.port_id)]) diff --git a/octavia/tests/unit/compute/drivers/test_nova_driver.py b/octavia/tests/unit/compute/drivers/test_nova_driver.py index 1d80adc7e0..10adf3c65c 100644 --- a/octavia/tests/unit/compute/drivers/test_nova_driver.py +++ b/octavia/tests/unit/compute/drivers/test_nova_driver.py @@ -144,6 +144,10 @@ class TestNovaClient(base.TestCase): self.server_group_mock.policy = self.server_group_policy self.server_group_mock.id = self.server_group_id + self.port_id = uuidutils.generate_uuid() + self.compute_id = uuidutils.generate_uuid() + self.network_id = uuidutils.generate_uuid() + super(TestNovaClient, self).setUp() def test_build(self): @@ -342,3 +346,28 @@ class TestNovaClient(base.TestCase): self.manager.delete_server_group, self.server_group_id) self.manager.server_groups.delete.called_with(self.server_group_id) + + def test_attach_network_or_port(self): + self.manager.attach_network_or_port(self.compute_id, + self.network_id) + self.manager.manager.interface_attach.assert_called_with( + server=self.compute_id, net_id=self.network_id, fixed_ip=None, + port_id=None) + + def test_attach_network_or_port_exception(self): + self.manager.manager.interface_attach.side_effect = [ + nova_exceptions.NotFound('test_exception')] + self.assertRaises(nova_exceptions.NotFound, + self.manager.attach_network_or_port, + self.compute_id, self.network_id) + + def test_detach_network(self): + self.manager.detach_port(self.compute_id, + self.port_id) + self.manager.manager.interface_detach.assert_called_with( + server=self.compute_id, port_id=self.port_id) + + def test_detach_network_with_exception(self): + self.manager.manager.interface_detach.side_effect = [Exception] + self.manager.detach_port(self.compute_id, + self.port_id) diff --git a/octavia/tests/unit/network/drivers/neutron/test_allowed_address_pairs.py b/octavia/tests/unit/network/drivers/neutron/test_allowed_address_pairs.py index 84d9e71024..f8db7637e5 100644 --- a/octavia/tests/unit/network/drivers/neutron/test_allowed_address_pairs.py +++ b/octavia/tests/unit/network/drivers/neutron/test_allowed_address_pairs.py @@ -61,7 +61,7 @@ class TestAllowedAddressPairsDriver(base.TestCase): super(TestAllowedAddressPairsDriver, self).setUp() with mock.patch('octavia.common.clients.neutron_client.Client', autospec=True) as neutron_client: - with mock.patch('octavia.common.clients.nova_client.Client', + with mock.patch('stevedore.driver.DriverManager.driver', autospec=True): client = neutron_client(clients.NEUTRON_VERSION) client.list_extensions.return_value = { @@ -317,8 +317,8 @@ class TestAllowedAddressPairsDriver(base.TestCase): ] list_security_groups.side_effect = lsc_side_effect - interface_attach = self.driver.nova_client.servers.interface_attach - interface_attach.side_effect = nova_exceptions.NotFound(404, "Network") + network_attach = self.driver.compute.attach_network_or_port + network_attach.side_effect = nova_exceptions.NotFound(404, "Network") self.assertRaises(network_base.PlugVIPException, self.driver.plug_vip, lb, lb.vip) @@ -339,8 +339,8 @@ class TestAllowedAddressPairsDriver(base.TestCase): } ] list_security_groups.side_effect = lsc_side_effect - interface_attach = self.driver.nova_client.servers.interface_attach - interface_attach.return_value = t_constants.MOCK_NOVA_INTERFACE + network_attach = self.driver.compute.attach_network_or_port + network_attach.return_value = t_constants.MOCK_NOVA_INTERFACE update_port = self.driver.neutron_client.update_port update_port.side_effect = neutron_exceptions.PortNotFoundClient @@ -360,9 +360,9 @@ class TestAllowedAddressPairsDriver(base.TestCase): port1 = t_constants.MOCK_MANAGEMENT_PORT1['port'] port2 = t_constants.MOCK_MANAGEMENT_PORT2['port'] list_ports.side_effect = [{'ports': [port1]}, {'ports': [port2]}] - interface_attach = self.driver.nova_client.servers.interface_attach - interface_attach.side_effect = [t_constants.MOCK_VRRP_INTERFACE1, - t_constants.MOCK_VRRP_INTERFACE2] + network_attach = self.driver.compute.attach_network_or_port + network_attach.side_effect = [t_constants.MOCK_VRRP_INTERFACE1, + t_constants.MOCK_VRRP_INTERFACE2] list_security_groups = self.driver.neutron_client.list_security_groups list_security_groups.return_value = { 'security_groups': [ @@ -408,7 +408,7 @@ class TestAllowedAddressPairsDriver(base.TestCase): self._set_safely(t_constants.MOCK_MANAGEMENT_FIXED_IPS2[0], 'ip_address', lb.amphorae[1].lb_network_ip) list_ports.side_effect = [{'ports': [port1]}, {'ports': [port2]}] - interface_attach = self.driver.nova_client.servers.interface_attach + network_attach = self.driver.compute.attach_network_or_port self._set_safely(t_constants.MOCK_VRRP_INTERFACE1, 'net_id', t_constants.MOCK_MANAGEMENT_NET_ID) self._set_safely(t_constants.MOCK_VRRP_FIXED_IPS1[0], @@ -417,8 +417,8 @@ class TestAllowedAddressPairsDriver(base.TestCase): 'net_id', t_constants.MOCK_MANAGEMENT_NET_ID) self._set_safely(t_constants.MOCK_VRRP_FIXED_IPS2[0], 'subnet_id', t_constants.MOCK_MANAGEMENT_SUBNET_ID) - interface_attach.side_effect = [t_constants.MOCK_VRRP_INTERFACE1, - t_constants.MOCK_VRRP_INTERFACE2] + network_attach.side_effect = [t_constants.MOCK_VRRP_INTERFACE1, + t_constants.MOCK_VRRP_INTERFACE2] list_security_groups = self.driver.neutron_client.list_security_groups list_security_groups.return_value = { 'security_groups': [ @@ -595,8 +595,8 @@ class TestAllowedAddressPairsDriver(base.TestCase): def test_plug_network_when_compute_instance_cant_be_found(self): net_id = t_constants.MOCK_NOVA_INTERFACE.net_id - interface_attach = self.driver.nova_client.servers.interface_attach - interface_attach.side_effect = nova_exceptions.NotFound( + network_attach = self.driver.compute.attach_network_or_port + network_attach.side_effect = nova_exceptions.NotFound( 404, message='Instance not found') self.assertRaises(network_base.AmphoraNotFound, self.driver.plug_network, @@ -604,8 +604,8 @@ class TestAllowedAddressPairsDriver(base.TestCase): def test_plug_network_when_network_cant_be_found(self): net_id = t_constants.MOCK_NOVA_INTERFACE.net_id - interface_attach = self.driver.nova_client.servers.interface_attach - interface_attach.side_effect = nova_exceptions.NotFound( + network_attach = self.driver.compute.attach_network_or_port + network_attach.side_effect = nova_exceptions.NotFound( 404, message='Network not found') self.assertRaises(network_base.NetworkException, self.driver.plug_network, @@ -613,16 +613,16 @@ class TestAllowedAddressPairsDriver(base.TestCase): def test_plug_network_when_interface_attach_fails(self): net_id = t_constants.MOCK_NOVA_INTERFACE.net_id - interface_attach = self.driver.nova_client.servers.interface_attach - interface_attach.side_effect = TypeError + network_attach = self.driver.compute.attach_network_or_port + network_attach.side_effect = TypeError self.assertRaises(network_base.PlugNetworkException, self.driver.plug_network, t_constants.MOCK_COMPUTE_ID, net_id) def test_plug_network(self): net_id = t_constants.MOCK_NOVA_INTERFACE.net_id - interface_attach = self.driver.nova_client.servers.interface_attach - interface_attach.return_value = t_constants.MOCK_NOVA_INTERFACE + network_attach = self.driver.compute.attach_network_or_port + network_attach.return_value = t_constants.MOCK_NOVA_INTERFACE oct_interface = self.driver.plug_network( t_constants.MOCK_COMPUTE_ID, net_id) exp_ips = [fixed_ip.get('ip_address') @@ -650,19 +650,6 @@ class TestAllowedAddressPairsDriver(base.TestCase): self.driver.unplug_network, t_constants.MOCK_COMPUTE_ID, net_id) - def test_unplug_network_when_interface_detach_fails(self): - list_ports = self.driver.neutron_client.list_ports - port1 = t_constants.MOCK_NEUTRON_PORT['port'] - port2 = { - 'id': '4', 'network_id': '3', 'fixed_ips': - [{'ip_address': '10.0.0.2'}] - } - list_ports.return_value = {'ports': [port1, port2]} - interface_detach = self.driver.nova_client.servers.interface_detach - interface_detach.side_effect = Exception - self.driver.unplug_network(t_constants.MOCK_COMPUTE_ID, - port2.get('network_id')) - def test_unplug_network(self): list_ports = self.driver.neutron_client.list_ports port1 = t_constants.MOCK_NEUTRON_PORT['port'] @@ -671,11 +658,11 @@ class TestAllowedAddressPairsDriver(base.TestCase): [{'ip_address': '10.0.0.2'}] } list_ports.return_value = {'ports': [port1, port2]} - interface_detach = self.driver.nova_client.servers.interface_detach + port_detach = self.driver.compute.detach_port self.driver.unplug_network(t_constants.MOCK_COMPUTE_ID, port2.get('network_id')) - interface_detach.assert_called_once_with( - server=t_constants.MOCK_COMPUTE_ID, port_id=port2.get('id')) + port_detach.assert_called_once_with( + compute_id=t_constants.MOCK_COMPUTE_ID, port_id=port2.get('id')) def test_update_vip(self): listeners = [data_models.Listener(protocol_port=80, peer_port=1024, @@ -907,7 +894,8 @@ class TestAllowedAddressPairsDriver(base.TestCase): def test_plug_port(self): port = mock.MagicMock() port.id = self.PORT_ID - interface_attach = self.driver.nova_client.servers.interface_attach + network_attach = self.driver.compute.attach_network_or_port + network_attach.return_value = t_constants.MOCK_NOVA_INTERFACE amphora = data_models.Amphora( id=self.AMPHORA_ID, load_balancer_id=self.LB_ID, compute_id=self.COMPUTE_ID, status=self.ACTIVE, @@ -915,25 +903,25 @@ class TestAllowedAddressPairsDriver(base.TestCase): ha_ip=self.HA_IP) self.driver.plug_port(amphora, port) - interface_attach.assert_called_once_with(server=amphora.compute_id, - net_id=None, - fixed_ip=None, - port_id=self.PORT_ID) + network_attach.assert_called_once_with(compute_id=amphora.compute_id, + network_id=None, + ip_address=None, + port_id=self.PORT_ID) # NotFound cases - interface_attach.side_effect = nova_exceptions.NotFound( + network_attach.side_effect = nova_exceptions.NotFound( 1, message='Instance') self.assertRaises(network_base.AmphoraNotFound, self.driver.plug_port, amphora, port) - interface_attach.side_effect = nova_exceptions.NotFound( + network_attach.side_effect = nova_exceptions.NotFound( 1, message='Network') self.assertRaises(network_base.NetworkNotFound, self.driver.plug_port, amphora, port) - interface_attach.side_effect = nova_exceptions.NotFound( + network_attach.side_effect = nova_exceptions.NotFound( 1, message='bogus') self.assertRaises(network_base.PlugNetworkException, self.driver.plug_port, @@ -941,11 +929,11 @@ class TestAllowedAddressPairsDriver(base.TestCase): port) # Already plugged case should not raise an exception - interface_attach.side_effect = nova_exceptions.Conflict(1) + network_attach.side_effect = nova_exceptions.Conflict(1) self.driver.plug_port(amphora, port) # Unknown error case - interface_attach.side_effect = TypeError + network_attach.side_effect = TypeError self.assertRaises(network_base.PlugNetworkException, self.driver.plug_port, amphora,