From 23037823adbbf0bfc9c6398e8d460e84d49e4e6f Mon Sep 17 00:00:00 2001 From: Paul Michali Date: Fri, 14 Mar 2014 15:21:03 +0000 Subject: [PATCH] VPNaaS support for VPN service admin state change and reporting For VPN service admin state changes, hook up the API call to the service driver so that the configuration changes can be applied. Modify the status reporting, so that the VPN service and IPSec connection status' match the actual status when admin state down actions are performed (on both the service and connection). Change-Id: Ife7176675d20bb3ea529a4d79fa92a61f9550a6a Closes-Bug: 1291609 --- neutron/services/vpn/device_drivers/ipsec.py | 22 ++++++- .../template/openswan/ipsec.conf.template | 2 +- neutron/services/vpn/plugin.py | 9 +++ .../services/vpn/device_drivers/test_ipsec.py | 66 +++++++++++++++++++ .../services/vpn/test_vpnaas_driver_plugin.py | 5 ++ 5 files changed, 102 insertions(+), 2 deletions(-) diff --git a/neutron/services/vpn/device_drivers/ipsec.py b/neutron/services/vpn/device_drivers/ipsec.py index cdf9c5871..90e95c96d 100644 --- a/neutron/services/vpn/device_drivers/ipsec.py +++ b/neutron/services/vpn/device_drivers/ipsec.py @@ -77,6 +77,8 @@ STATUS_MAP = { 'unrouted': constants.DOWN } +IPSEC_CONNS = 'ipsec_site_connections' + def _get_template(template_file): global JINJA_ENV @@ -622,14 +624,32 @@ class IPsecDriver(device_drivers.DeviceDriver): 'ipsec_site_connections': copy.deepcopy(process.connection_status) } + def update_downed_connections(self, process_id, new_status): + """Update info to be reported, if connections just went down. + + If there is no longer any information for a connection (because it + has been removed (e.g. due to an admin down of VPN service or IPSec + connection), but there was previous status information for the + connection, mark the connection as down for reporting purposes. + """ + if process_id in self.process_status_cache: + for conn in self.process_status_cache[process_id][IPSEC_CONNS]: + if conn not in new_status[IPSEC_CONNS]: + new_status[IPSEC_CONNS][conn] = { + 'status': constants.DOWN, + 'updated_pending_status': True + } + def report_status(self, context): status_changed_vpn_services = [] for process in self.processes.values(): previous_status = self.get_process_status_cache(process) if self.is_status_updated(process, previous_status): new_status = self.copy_process_status(process) - self.process_status_cache[process.id] = new_status + self.update_downed_connections(process.id, new_status) status_changed_vpn_services.append(new_status) + self.process_status_cache[process.id] = ( + self.copy_process_status(process)) # We need unset updated_pending status after it # is reported to the server side self.unset_updated_pending_status(process) diff --git a/neutron/services/vpn/device_drivers/template/openswan/ipsec.conf.template b/neutron/services/vpn/device_drivers/template/openswan/ipsec.conf.template index 180b4a19e..546e27ec6 100644 --- a/neutron/services/vpn/device_drivers/template/openswan/ipsec.conf.template +++ b/neutron/services/vpn/device_drivers/template/openswan/ipsec.conf.template @@ -6,7 +6,7 @@ conn %default ikelifetime=480m keylife=60m keyingtries=%forever -{% for ipsec_site_connection in vpnservice.ipsec_site_connections +{% for ipsec_site_connection in vpnservice.ipsec_site_connections if ipsec_site_connection.admin_state_up %}conn {{ipsec_site_connection.id}} # NOTE: a default route is required for %defaultroute to work... left={{vpnservice.external_ip}} diff --git a/neutron/services/vpn/plugin.py b/neutron/services/vpn/plugin.py index 1a128dafc..771188d65 100644 --- a/neutron/services/vpn/plugin.py +++ b/neutron/services/vpn/plugin.py @@ -91,6 +91,15 @@ class VPNDriverPlugin(VPNPlugin, vpn_db.VPNPluginRpcDbMixin): context, old_ipsec_site_connection, ipsec_site_connection) return ipsec_site_connection + def update_vpnservice(self, context, vpnservice_id, vpnservice): + old_vpn_service = self.get_vpnservice(context, vpnservice_id) + new_vpn_service = super( + VPNDriverPlugin, self).update_vpnservice(context, vpnservice_id, + vpnservice) + driver = self._get_driver_for_vpnservice(old_vpn_service) + driver.update_vpnservice(context, old_vpn_service, new_vpn_service) + return new_vpn_service + def delete_vpnservice(self, context, vpnservice_id): vpnservice = self._get_vpnservice(context, vpnservice_id) super(VPNDriverPlugin, self).delete_vpnservice(context, vpnservice_id) diff --git a/neutron/tests/unit/services/vpn/device_drivers/test_ipsec.py b/neutron/tests/unit/services/vpn/device_drivers/test_ipsec.py index bbd65928a..c7bc4d719 100644 --- a/neutron/tests/unit/services/vpn/device_drivers/test_ipsec.py +++ b/neutron/tests/unit/services/vpn/device_drivers/test_ipsec.py @@ -190,3 +190,69 @@ class TestIPsecDeviceDriver(base.BaseTestCase): process_id = _uuid() self.driver.sync(context, [{'id': process_id}]) self.assertNotIn(process_id, self.driver.processes) + + def test_status_updated_on_connection_admin_down(self): + self.driver.process_status_cache = { + '1': { + 'status': constants.ACTIVE, + 'id': 123, + 'updated_pending_status': False, + 'ipsec_site_connections': { + '10': { + 'status': constants.ACTIVE, + 'updated_pending_status': False, + }, + '20': { + 'status': constants.ACTIVE, + 'updated_pending_status': False, + } + } + } + } + # Simulate that there is no longer status for connection '20' + # e.g. connection admin down + new_status = { + 'ipsec_site_connections': { + '10': { + 'status': constants.ACTIVE, + 'updated_pending_status': False + } + } + } + self.driver.update_downed_connections('1', new_status) + existing_conn = new_status['ipsec_site_connections'].get('10') + self.assertIsNotNone(existing_conn) + self.assertEqual(constants.ACTIVE, existing_conn['status']) + missing_conn = new_status['ipsec_site_connections'].get('20') + self.assertIsNotNone(missing_conn) + self.assertEqual(constants.DOWN, missing_conn['status']) + + def test_status_updated_on_service_admin_down(self): + self.driver.process_status_cache = { + '1': { + 'status': constants.ACTIVE, + 'id': 123, + 'updated_pending_status': False, + 'ipsec_site_connections': { + '10': { + 'status': constants.ACTIVE, + 'updated_pending_status': False, + }, + '20': { + 'status': constants.ACTIVE, + 'updated_pending_status': False, + } + } + } + } + # Simulate that there are no connections now + new_status = { + 'ipsec_site_connections': {} + } + self.driver.update_downed_connections('1', new_status) + missing_conn = new_status['ipsec_site_connections'].get('10') + self.assertIsNotNone(missing_conn) + self.assertEqual(constants.DOWN, missing_conn['status']) + missing_conn = new_status['ipsec_site_connections'].get('20') + self.assertIsNotNone(missing_conn) + self.assertEqual(constants.DOWN, missing_conn['status']) diff --git a/neutron/tests/unit/services/vpn/test_vpnaas_driver_plugin.py b/neutron/tests/unit/services/vpn/test_vpnaas_driver_plugin.py index f5ed5bc01..45f932ec1 100644 --- a/neutron/tests/unit/services/vpn/test_vpnaas_driver_plugin.py +++ b/neutron/tests/unit/services/vpn/test_vpnaas_driver_plugin.py @@ -58,6 +58,11 @@ class TestVPNDriverPlugin(test_db_vpnaas.TestVpnaas, self.driver.delete_vpnservice.assert_called_once_with( mock.ANY, mock.ANY) + def test_update_vpnservice(self, **extras): + super(TestVPNDriverPlugin, self).test_update_vpnservice() + self.driver.update_vpnservice.assert_called_once_with( + mock.ANY, mock.ANY, mock.ANY) + @contextlib.contextmanager def vpnservice_set(self): """Test case to create a ipsec_site_connection.""" -- 2.45.2