diff --git a/ovn_octavia_provider/common/clients.py b/ovn_octavia_provider/common/clients.py index 2bf1c705..ed463d7b 100644 --- a/ovn_octavia_provider/common/clients.py +++ b/ovn_octavia_provider/common/clients.py @@ -139,3 +139,37 @@ def get_neutron_client(): 'in Octavia API configuration.') % e raise driver_exceptions.DriverError( operator_fault_string=msg) + + +class OctaviaAuth(metaclass=Singleton): + def __init__(self): + """Create Octavia client object.""" + try: + ksession = KeystoneSession() + kwargs = {'region_name': CONF.service_auth.region_name} + # TODO(ricolin) `interface` option don't take list as option yet. + # We can move away from this when openstacksdk no longer depends + # on `interface`. + try: + interface = CONF.service_auth.valid_interfaces[0] + except (TypeError, LookupError): + interface = CONF.service_auth.valid_interfaces + if interface: + kwargs['interface'] = interface + + self.loadbalancer_proxy = openstack.connection.Connection( + session=ksession.session, **kwargs).load_balancer + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception("Error creating Octavia client.") + + +def get_octavia_client(): + try: + return OctaviaAuth().loadbalancer_proxy + except Exception as e: + msg = _('Cannot initialize OpenStackSDK. Exception: %s. ' + 'Please verify service_auth configuration ' + 'in Octavia API configuration.') % e + raise driver_exceptions.DriverError( + operator_fault_string=msg) diff --git a/ovn_octavia_provider/driver.py b/ovn_octavia_provider/driver.py index 5e60b34f..6d7dc215 100644 --- a/ovn_octavia_provider/driver.py +++ b/ovn_octavia_provider/driver.py @@ -21,6 +21,7 @@ from octavia_lib.api.drivers import provider_base as driver_base from octavia_lib.common import constants from oslo_log import log as logging +from ovn_octavia_provider.common import clients from ovn_octavia_provider.common import config as ovn_conf # TODO(mjozefcz): Start consuming const and utils # from neutron-lib once released. @@ -588,5 +589,8 @@ class OvnProviderDriver(driver_base.ProviderDriver): def do_sync(self, **lb_filters): LOG.info(f"Starting sync OVN DB with Loadbalancer filter {lb_filters}") - # TODO(froyo): get LBs from Octavia DB through openstack sdk client and - # call to helper methods to sync + octavia_client = clients.get_octavia_client() + # We can add project_id to lb_filters for lbs to limit the scope. + lbs = self._ovn_helper.get_octavia_lbs(octavia_client, **lb_filters) + for lb in lbs: + LOG.info(f"Starting sync OVN DB with Loadbalancer {lb.id}") diff --git a/ovn_octavia_provider/helper.py b/ovn_octavia_provider/helper.py index 3b5fa076..2d21d976 100644 --- a/ovn_octavia_provider/helper.py +++ b/ovn_octavia_provider/helper.py @@ -452,6 +452,15 @@ class OvnProviderHelper(): def _neutron_find_port(self, neutron_client, **params): return neutron_client.find_port(**params) + @tenacity.retry( + retry=tenacity.retry_if_exception_type( + openstack.exceptions.HttpException), + wait=tenacity.wait_exponential(), + stop=tenacity.stop_after_delay(10), + reraise=True) + def get_octavia_lbs(self, octavia_client, **params): + return octavia_client.load_balancers(**params) + def _find_ovn_lbs(self, lb_id, protocol=None): """Find the Loadbalancers in OVN with the given lb_id as its name diff --git a/ovn_octavia_provider/tests/unit/common/test_clients.py b/ovn_octavia_provider/tests/unit/common/test_clients.py index 601c57c4..e9ad4935 100644 --- a/ovn_octavia_provider/tests/unit/common/test_clients.py +++ b/ovn_octavia_provider/tests/unit/common/test_clients.py @@ -14,6 +14,7 @@ from unittest import mock from keystoneauth1 import exceptions as ks_exceptions +from octavia_lib.api.drivers import exceptions as driver_exceptions from oslo_config import cfg from oslo_config import fixture as oslo_fixture from oslotest import base @@ -127,3 +128,70 @@ class TestNeutronAuth(base.BaseTestCase): c3 = clients.NeutronAuth() self.assertIs(c2, c3) self.assertEqual(os_sdk._mock_call_count, 2) + + @mock.patch.object(clients, 'KeystoneSession') + def test_get_client(self, mock_ks): + clients.get_neutron_client() + self.mock_client.assert_called_once_with( + session=mock_ks().session) + + @mock.patch.object(clients, 'NeutronAuth', side_effect=[RuntimeError]) + def test_get_client_error(self, mock_ks): + msg = self.assertRaises( + driver_exceptions.DriverError, + clients.get_neutron_client) + self.assertEqual("An unknown driver error occurred.", str(msg)) + + +class TestOctaviaAuth(base.BaseTestCase): + def setUp(self): + super().setUp() + config.register_opts() + self.mock_client = mock.patch( + 'openstack.connection.Connection').start() + clients.Singleton._instances = {} + + @mock.patch.object(clients, 'KeystoneSession') + @mock.patch('openstack.connection.Connection') + def test_init(self, mock_conn, mock_ks): + clients.OctaviaAuth() + mock_conn.assert_called_once_with( + session=mock_ks().session, + region_name=mock.ANY + ) + + def test_singleton(self): + c1 = clients.OctaviaAuth() + c2 = clients.OctaviaAuth() + self.assertIs(c1, c2) + + def test_singleton_exception(self): + mock_client = mock.Mock() + mock_client.lbaas_proxy = 'foo' + with mock.patch( + 'openstack.connection.Connection', + side_effect=[RuntimeError, + mock_client, mock_client]) as os_sdk: + self.assertRaises( + RuntimeError, + clients.OctaviaAuth) + c2 = clients.OctaviaAuth() + c3 = clients.OctaviaAuth() + self.assertIs(c2, c3) + self.assertEqual(os_sdk._mock_call_count, 2) + + @mock.patch.object(clients, 'KeystoneSession') + @mock.patch('openstack.connection.Connection') + def test_get_client(self, mock_conn, mock_ks): + clients.get_octavia_client() + mock_conn.assert_called_once_with( + session=mock_ks().session, + region_name=mock.ANY + ) + + @mock.patch.object(clients, 'OctaviaAuth', side_effect=[RuntimeError]) + def test_get_client_error(self, mock_ks): + msg = self.assertRaises( + driver_exceptions.DriverError, + clients.get_octavia_client) + self.assertEqual("An unknown driver error occurred.", str(msg))