Cisco VPN device driver - support IPSec connection updates
Provides support for IPSec connection updates and state changes. To do this, the configuration of the connection is maintained, when the connection is created. This is checked against the current settings, at sync time, to determine whether a configuration change (as opposed to a state change) has occurred. If there is a change to the configuration detected, then the simple approach is taken of deleting and then re-creating the connection, with the new settings. In addition, if the admin state of the connection changes, the tunnel will be taken admin down/up, as needed. Admin down will occur if the IPSec connection or the associated VPN service is set to admin down. Admin up will occur, if both the IPSec connection and the VPN service are in admin up state. Added REST client method to allow changing the IPSec connection tunnel to admin up/down (effectively doing a no-shut/shut on the tunnel I/F), based on the above mentioned state. Modified UTs for the support of IPSec connection update requests (used to throw an "unsupported" exception), and to check that the configuration and state changing are processed correctly. Updated so that tunnel_ip is set in device driver, rather than hard coding, and then overriding in REST client. Since device driver has the same info, this will fit into future plans to obtain the info from router, vs reading an .ini file. Revised UTs as well. Change-Id: I184942d7f2f282c867ba020f62cd48ec53315d3e Closes-Bug: 1303830
This commit is contained in:
parent
897eb3c376
commit
d6ff755eb7
@ -214,9 +214,6 @@ class CsrRestClient(object):
|
||||
base_conn_info = {u'vpn-type': u'site-to-site',
|
||||
u'ip-version': u'ipv4'}
|
||||
connection_info.update(base_conn_info)
|
||||
# TODO(pcm) pass in value, when CSR is embedded as Neutron router.
|
||||
# Currently, get this from .INI file.
|
||||
connection_info[u'local-device'][u'tunnel-ip-address'] = self.tunnel_ip
|
||||
return self.post_request('vpn-svc/site-to-site',
|
||||
payload=connection_info)
|
||||
|
||||
@ -232,6 +229,14 @@ class CsrRestClient(object):
|
||||
def delete_static_route(self, route_id):
|
||||
return self.delete_request('routing-svc/static-routes/%s' % route_id)
|
||||
|
||||
def set_ipsec_connection_state(self, tunnel, admin_up=True):
|
||||
"""Set the IPSec site-to-site connection (tunnel) admin state.
|
||||
|
||||
Note: When a tunnel is created, it will be admin up.
|
||||
"""
|
||||
info = {u'vpn-interface-name': tunnel, u'enabled': admin_up}
|
||||
return self.put_request('vpn-svc/site-to-site/%s/state' % tunnel, info)
|
||||
|
||||
def delete_ipsec_connection(self, conn_id):
|
||||
return self.delete_request('vpn-svc/site-to-site/%s' % conn_id)
|
||||
|
||||
|
@ -54,6 +54,11 @@ class CsrResourceCreateFailure(exceptions.NeutronException):
|
||||
message = _("Cisco CSR failed to create %(resource)s (%(which)s)")
|
||||
|
||||
|
||||
class CsrAdminStateChangeFailure(exceptions.NeutronException):
|
||||
message = _("Cisco CSR failed to change %(tunnel)s admin state to "
|
||||
"%(state)s")
|
||||
|
||||
|
||||
class CsrDriverMismatchError(exceptions.NeutronException):
|
||||
message = _("Required %(resource)s attribute %(attr)s mapping for Cisco "
|
||||
"CSR is missing in device driver")
|
||||
@ -240,36 +245,37 @@ class CiscoCsrIPsecDriver(device_drivers.DeviceDriver):
|
||||
conn_id = conn_data['id']
|
||||
conn_is_admin_up = conn_data[u'admin_state_up']
|
||||
|
||||
if conn_id in vpn_service.conn_state:
|
||||
if conn_id in vpn_service.conn_state: # Existing connection...
|
||||
ipsec_conn = vpn_service.conn_state[conn_id]
|
||||
config_changed = ipsec_conn.check_for_changes(conn_data)
|
||||
if config_changed:
|
||||
LOG.debug(_("Update: Existing connection %s changed"), conn_id)
|
||||
ipsec_conn.delete_ipsec_site_connection(context, conn_id)
|
||||
ipsec_conn.create_ipsec_site_connection(context, conn_data)
|
||||
ipsec_conn.conn_info = conn_data
|
||||
|
||||
if ipsec_conn.forced_down:
|
||||
if vpn_service.is_admin_up and conn_is_admin_up:
|
||||
LOG.debug(_("Update: Connection %s no longer admin down"),
|
||||
conn_id)
|
||||
# TODO(pcm) Do no shut on tunnel, once CSR supports
|
||||
ipsec_conn.set_admin_state(is_up=True)
|
||||
ipsec_conn.forced_down = False
|
||||
ipsec_conn.create_ipsec_site_connection(context, conn_data)
|
||||
else:
|
||||
if not vpn_service.is_admin_up or not conn_is_admin_up:
|
||||
LOG.debug(_("Update: Connection %s forced to admin down"),
|
||||
conn_id)
|
||||
# TODO(pcm) Do shut on tunnel, once CSR supports
|
||||
ipsec_conn.set_admin_state(is_up=False)
|
||||
ipsec_conn.forced_down = True
|
||||
ipsec_conn.delete_ipsec_site_connection(context, conn_id)
|
||||
else:
|
||||
# TODO(pcm) FUTURE handle connection update
|
||||
LOG.debug(_("Update: Ignoring existing connection %s"),
|
||||
conn_id)
|
||||
else: # New connection...
|
||||
ipsec_conn = vpn_service.create_connection(conn_data)
|
||||
ipsec_conn.create_ipsec_site_connection(context, conn_data)
|
||||
if not vpn_service.is_admin_up or not conn_is_admin_up:
|
||||
# TODO(pcm) Create, but set tunnel down, once CSR supports
|
||||
LOG.debug(_("Update: Created new connection %s in admin down "
|
||||
"state"), conn_id)
|
||||
ipsec_conn.set_admin_state(is_up=False)
|
||||
ipsec_conn.forced_down = True
|
||||
else:
|
||||
LOG.debug(_("Update: Created new connection %s"), conn_id)
|
||||
ipsec_conn.create_ipsec_site_connection(context, conn_data)
|
||||
|
||||
ipsec_conn.is_dirty = False
|
||||
ipsec_conn.last_status = conn_data['status']
|
||||
@ -539,12 +545,33 @@ class CiscoCsrIPSecConnection(object):
|
||||
"""State and actions for IPSec site-to-site connections."""
|
||||
|
||||
def __init__(self, conn_info, csr):
|
||||
self.conn_id = conn_info['id']
|
||||
self.conn_info = conn_info
|
||||
self.csr = csr
|
||||
self.steps = []
|
||||
self.forced_down = False
|
||||
self.is_admin_up = conn_info[u'admin_state_up']
|
||||
self.tunnel = conn_info['cisco']['site_conn_id']
|
||||
self.changed = False
|
||||
|
||||
@property
|
||||
def conn_id(self):
|
||||
return self.conn_info['id']
|
||||
|
||||
@property
|
||||
def is_admin_up(self):
|
||||
return self.conn_info['admin_state_up']
|
||||
|
||||
@is_admin_up.setter
|
||||
def is_admin_up(self, is_up):
|
||||
self.conn_info['admin_state_up'] = is_up
|
||||
|
||||
@property
|
||||
def tunnel(self):
|
||||
return self.conn_info['cisco']['site_conn_id']
|
||||
|
||||
def check_for_changes(self, curr_conn):
|
||||
return not all([self.conn_info[attr] == curr_conn[attr]
|
||||
for attr in ('mtu', 'psk', 'peer_address',
|
||||
'peer_cidrs', 'ike_policy',
|
||||
'ipsec_policy', 'cisco')])
|
||||
|
||||
def find_current_status_in(self, statuses):
|
||||
if self.tunnel in statuses:
|
||||
@ -683,7 +710,7 @@ class CiscoCsrIPSecConnection(object):
|
||||
u'ip-address': u'GigabitEthernet3',
|
||||
# TODO(pcm): FUTURE - Get IP address of router's public
|
||||
# I/F, once CSR is used as embedded router.
|
||||
u'tunnel-ip-address': u'172.24.4.23'
|
||||
u'tunnel-ip-address': self.csr.tunnel_ip
|
||||
# u'tunnel-ip-address': u'%s' % gw_ip
|
||||
},
|
||||
u'remote-device': {
|
||||
@ -822,3 +849,12 @@ class CiscoCsrIPSecConnection(object):
|
||||
|
||||
LOG.info(_("SUCCESS: Deleted IPSec site-to-site connection %s"),
|
||||
conn_id)
|
||||
|
||||
def set_admin_state(self, is_up):
|
||||
"""Change the admin state for the IPSec connection."""
|
||||
self.csr.set_ipsec_connection_state(self.tunnel, admin_up=is_up)
|
||||
if self.csr.status != requests.codes.NO_CONTENT:
|
||||
state = "UP" if is_up else "DOWN"
|
||||
LOG.error(_("Unable to change %(tunnel)s admin state to "
|
||||
"%(state)s"), {'tunnel': self.tunnel, 'state': state})
|
||||
raise CsrAdminStateChangeFailure(tunnel=self.tunnel, state=state)
|
||||
|
@ -41,10 +41,6 @@ class CsrValidationFailure(exceptions.BadRequest):
|
||||
"with value '%(value)s'")
|
||||
|
||||
|
||||
class CsrUnsupportedError(exceptions.NeutronException):
|
||||
message = _("Cisco CSR does not currently support %(capability)s")
|
||||
|
||||
|
||||
class CiscoCsrIPsecVpnDriverCallBack(object):
|
||||
|
||||
"""Handler for agent to plugin RPC messaging."""
|
||||
@ -184,9 +180,11 @@ class CiscoCsrIPsecVPNDriver(service_drivers.VpnDriver):
|
||||
|
||||
def update_ipsec_site_connection(
|
||||
self, context, old_ipsec_site_connection, ipsec_site_connection):
|
||||
capability = _("update of IPSec connections. You can delete and "
|
||||
"re-add, as a workaround.")
|
||||
raise CsrUnsupportedError(capability=capability)
|
||||
vpnservice = self.service_plugin._get_vpnservice(
|
||||
context, ipsec_site_connection['vpnservice_id'])
|
||||
self.agent_rpc.vpnservice_updated(
|
||||
context, vpnservice['router_id'],
|
||||
reason='ipsec-conn-update')
|
||||
|
||||
def delete_ipsec_site_connection(self, context, ipsec_site_connection):
|
||||
vpnservice = self.service_plugin._get_vpnservice(
|
||||
|
@ -331,6 +331,34 @@ def get_unnumbered(url, request):
|
||||
return httmock.response(requests.codes.OK, content=content)
|
||||
|
||||
|
||||
@filter_request(['get'], 'vpn-svc/site-to-site/Tunnel')
|
||||
@httmock.urlmatch(netloc=r'localhost')
|
||||
def get_admin_down(url, request):
|
||||
if not request.headers.get('X-auth-token', None):
|
||||
return {'status_code': requests.codes.UNAUTHORIZED}
|
||||
# URI has .../Tunnel#/state, so get number from 2nd to last element
|
||||
tunnel = url.path.split('/')[-2]
|
||||
content = {u'kind': u'object#vpn-site-to-site-state',
|
||||
u'vpn-interface-name': u'%s' % tunnel,
|
||||
u'line-protocol-state': u'down',
|
||||
u'enabled': False}
|
||||
return httmock.response(requests.codes.OK, content=content)
|
||||
|
||||
|
||||
@filter_request(['get'], 'vpn-svc/site-to-site/Tunnel')
|
||||
@httmock.urlmatch(netloc=r'localhost')
|
||||
def get_admin_up(url, request):
|
||||
if not request.headers.get('X-auth-token', None):
|
||||
return {'status_code': requests.codes.UNAUTHORIZED}
|
||||
# URI has .../Tunnel#/state, so get number from 2nd to last element
|
||||
tunnel = url.path.split('/')[-2]
|
||||
content = {u'kind': u'object#vpn-site-to-site-state',
|
||||
u'vpn-interface-name': u'%s' % tunnel,
|
||||
u'line-protocol-state': u'down',
|
||||
u'enabled': True}
|
||||
return httmock.response(requests.codes.OK, content=content)
|
||||
|
||||
|
||||
@filter_request(['get'], 'vpn-svc/site-to-site')
|
||||
@httmock.urlmatch(netloc=r'localhost')
|
||||
def get_mtu(url, request):
|
||||
|
@ -1012,6 +1012,47 @@ class TestCsrRestIPSecConnectionCreate(base.BaseTestCase):
|
||||
expected_connection.update(connection_info)
|
||||
self.assertEqual(expected_connection, content)
|
||||
|
||||
def test_set_ipsec_connection_admin_state_changes(self):
|
||||
"""Create IPSec connection in admin down state."""
|
||||
tunnel_id, ipsec_policy_id = self._prepare_for_site_conn_create()
|
||||
tunnel = u'Tunnel%d' % tunnel_id
|
||||
with httmock.HTTMock(csr_request.token, csr_request.post):
|
||||
connection_info = {
|
||||
u'vpn-interface-name': tunnel,
|
||||
u'ipsec-policy-id': u'%d' % ipsec_policy_id,
|
||||
u'mtu': 1500,
|
||||
u'local-device': {u'ip-address': u'10.3.0.1/24',
|
||||
u'tunnel-ip-address': u'10.10.10.10'},
|
||||
u'remote-device': {u'tunnel-ip-address': u'10.10.10.20'}
|
||||
}
|
||||
location = self.csr.create_ipsec_connection(connection_info)
|
||||
self.addCleanup(self._remove_resource_for_test,
|
||||
self.csr.delete_ipsec_connection,
|
||||
tunnel)
|
||||
self.assertEqual(requests.codes.CREATED, self.csr.status)
|
||||
self.assertIn('vpn-svc/site-to-site/%s' % tunnel, location)
|
||||
state_uri = location + "/state"
|
||||
# Note: When created, the tunnel will be in admin 'up' state
|
||||
# Note: Line protocol state will be down, unless have an active conn.
|
||||
expected_state = {u'kind': u'object#vpn-site-to-site-state',
|
||||
u'vpn-interface-name': tunnel,
|
||||
u'line-protocol-state': u'down',
|
||||
u'enabled': False}
|
||||
with httmock.HTTMock(csr_request.put, csr_request.get_admin_down):
|
||||
self.csr.set_ipsec_connection_state(tunnel, admin_up=False)
|
||||
self.assertEqual(requests.codes.NO_CONTENT, self.csr.status)
|
||||
content = self.csr.get_request(state_uri, full_url=True)
|
||||
self.assertEqual(requests.codes.OK, self.csr.status)
|
||||
self.assertEqual(expected_state, content)
|
||||
|
||||
with httmock.HTTMock(csr_request.put, csr_request.get_admin_up):
|
||||
self.csr.set_ipsec_connection_state(tunnel, admin_up=True)
|
||||
self.assertEqual(requests.codes.NO_CONTENT, self.csr.status)
|
||||
content = self.csr.get_request(state_uri, full_url=True)
|
||||
self.assertEqual(requests.codes.OK, self.csr.status)
|
||||
expected_state[u'enabled'] = True
|
||||
self.assertEqual(expected_state, content)
|
||||
|
||||
def test_create_ipsec_connection_missing_ipsec_policy(self):
|
||||
"""Negative test of connection create without IPSec policy."""
|
||||
tunnel_id, ipsec_policy_id = self._prepare_for_site_conn_create(
|
||||
|
@ -14,6 +14,7 @@
|
||||
#
|
||||
# @author: Paul Michali, Cisco Systems, Inc.
|
||||
|
||||
import copy
|
||||
import httplib
|
||||
import os
|
||||
import tempfile
|
||||
@ -78,6 +79,7 @@ class TestCiscoCsrIPSecConnection(base.BaseTestCase):
|
||||
}
|
||||
self.csr = mock.Mock(spec=csr_client.CsrRestClient)
|
||||
self.csr.status = 201 # All calls to CSR REST API succeed
|
||||
self.csr.tunnel_ip = '172.24.4.23'
|
||||
self.ipsec_conn = ipsec_driver.CiscoCsrIPSecConnection(self.conn_info,
|
||||
self.csr)
|
||||
|
||||
@ -219,8 +221,10 @@ class TestCiscoCsrIPsecConnectionCreateTransforms(base.BaseTestCase):
|
||||
# TODO(pcm) get from vpnservice['external_ip']
|
||||
'router_public_ip': '172.24.4.23'}
|
||||
}
|
||||
self.csr = mock.Mock(spec=csr_client.CsrRestClient)
|
||||
self.csr.tunnel_ip = '172.24.4.23'
|
||||
self.ipsec_conn = ipsec_driver.CiscoCsrIPSecConnection(self.conn_info,
|
||||
mock.Mock())
|
||||
self.csr)
|
||||
|
||||
def test_invalid_attribute(self):
|
||||
"""Negative test of unknown attribute - programming error."""
|
||||
@ -360,7 +364,7 @@ class TestCiscoCsrIPsecConnectionCreateTransforms(base.BaseTestCase):
|
||||
u'ipsec-policy-id': 333,
|
||||
u'local-device': {
|
||||
u'ip-address': u'GigabitEthernet3',
|
||||
u'tunnel-ip-address': u'172.24.4.23'
|
||||
u'tunnel-ip-address': '172.24.4.23'
|
||||
},
|
||||
u'remote-device': {
|
||||
u'tunnel-ip-address': '192.168.1.2'
|
||||
@ -418,14 +422,36 @@ class TestCiscoCsrIPsecDeviceDriverSyncStatuses(base.BaseTestCase):
|
||||
self.conn_delete = mock.patch.object(
|
||||
ipsec_driver.CiscoCsrIPSecConnection,
|
||||
'delete_ipsec_site_connection').start()
|
||||
self.admin_state = mock.patch.object(
|
||||
ipsec_driver.CiscoCsrIPSecConnection,
|
||||
'set_admin_state').start()
|
||||
self.csr = mock.Mock()
|
||||
self.driver.csrs['1.1.1.1'] = self.csr
|
||||
self.service123_data = {u'id': u'123',
|
||||
u'status': constants.DOWN,
|
||||
u'admin_state_up': False,
|
||||
u'external_ip': u'1.1.1.1'}
|
||||
self.conn1_data = {u'id': u'1', u'status': constants.ACTIVE,
|
||||
self.conn1_data = {u'id': u'1',
|
||||
u'status': constants.ACTIVE,
|
||||
u'admin_state_up': True,
|
||||
u'mtu': 1500,
|
||||
u'psk': u'secret',
|
||||
u'peer_address': '192.168.1.2',
|
||||
u'peer_cidrs': ['10.1.0.0/24', '10.2.0.0/24'],
|
||||
u'ike_policy': {
|
||||
u'auth_algorithm': u'sha1',
|
||||
u'encryption_algorithm': u'aes-128',
|
||||
u'pfs': u'Group5',
|
||||
u'ike_version': u'v1',
|
||||
u'lifetime_units': u'seconds',
|
||||
u'lifetime_value': 3600},
|
||||
u'ipsec_policy': {
|
||||
u'transform_protocol': u'ah',
|
||||
u'encryption_algorithm': u'aes-128',
|
||||
u'auth_algorithm': u'sha1',
|
||||
u'pfs': u'group5',
|
||||
u'lifetime_units': u'seconds',
|
||||
u'lifetime_value': 3600},
|
||||
u'cisco': {u'site_conn_id': u'Tunnel0'}}
|
||||
|
||||
# NOTE: For sync, there is mark (trivial), update (tested),
|
||||
@ -435,9 +461,8 @@ class TestCiscoCsrIPsecDeviceDriverSyncStatuses(base.BaseTestCase):
|
||||
"""Notified of connection create request - create."""
|
||||
# Make the (existing) service
|
||||
self.driver.create_vpn_service(self.service123_data)
|
||||
conn_data = {u'id': u'1', u'status': constants.PENDING_CREATE,
|
||||
u'admin_state_up': True,
|
||||
u'cisco': {u'site_conn_id': u'Tunnel0'}}
|
||||
conn_data = copy.deepcopy(self.conn1_data)
|
||||
conn_data[u'status'] = constants.PENDING_CREATE
|
||||
|
||||
connection = self.driver.update_connection(self.context,
|
||||
u'123', conn_data)
|
||||
@ -446,17 +471,50 @@ class TestCiscoCsrIPsecDeviceDriverSyncStatuses(base.BaseTestCase):
|
||||
self.assertEqual(constants.PENDING_CREATE, connection.last_status)
|
||||
self.assertEqual(1, self.conn_create.call_count)
|
||||
|
||||
def test_update_ipsec_connection_changed_settings(self):
|
||||
"""Notified of connection changing config - update."""
|
||||
# TODO(pcm) Place holder for this condition
|
||||
# Make the (existing) service and connection
|
||||
def test_detect_no_change_to_ipsec_connection(self):
|
||||
"""No change to IPSec connection - nop."""
|
||||
# Make existing service, and connection that was active
|
||||
vpn_service = self.driver.create_vpn_service(self.service123_data)
|
||||
# TODO(pcm) add info that indicates that the connection has changed
|
||||
conn_data = {u'id': u'1', u'status': constants.ACTIVE,
|
||||
u'admin_state_up': True,
|
||||
u'cisco': {u'site_conn_id': u'Tunnel0'}}
|
||||
vpn_service.create_connection(conn_data)
|
||||
connection = vpn_service.create_connection(self.conn1_data)
|
||||
|
||||
self.assertFalse(connection.check_for_changes(self.conn1_data))
|
||||
|
||||
def test_detect_state_only_change_to_ipsec_connection(self):
|
||||
"""Only IPSec connection state changed - update."""
|
||||
# Make existing service, and connection that was active
|
||||
vpn_service = self.driver.create_vpn_service(self.service123_data)
|
||||
connection = vpn_service.create_connection(self.conn1_data)
|
||||
|
||||
conn_data = copy.deepcopy(self.conn1_data)
|
||||
conn_data[u'admin_state_up'] = False
|
||||
self.assertFalse(connection.check_for_changes(conn_data))
|
||||
|
||||
def test_detect_non_state_change_to_ipsec_connection(self):
|
||||
"""Connection change instead of/in addition to state - update."""
|
||||
# Make existing service, and connection that was active
|
||||
vpn_service = self.driver.create_vpn_service(self.service123_data)
|
||||
connection = vpn_service.create_connection(self.conn1_data)
|
||||
|
||||
conn_data = copy.deepcopy(self.conn1_data)
|
||||
conn_data[u'ipsec_policy'][u'encryption_algorithm'] = u'aes-256'
|
||||
self.assertTrue(connection.check_for_changes(conn_data))
|
||||
|
||||
def test_update_ipsec_connection_changed_admin_down(self):
|
||||
"""Notified of connection state change - update.
|
||||
|
||||
For a connection that was previously created, expect to
|
||||
force connection down on an admin down (only) change.
|
||||
"""
|
||||
|
||||
# Make existing service, and connection that was active
|
||||
vpn_service = self.driver.create_vpn_service(self.service123_data)
|
||||
connection = vpn_service.create_connection(self.conn1_data)
|
||||
|
||||
# Simulate that notification of connection update received
|
||||
self.driver.mark_existing_connections_as_dirty()
|
||||
# Modify the connection data for the 'sync'
|
||||
conn_data = copy.deepcopy(self.conn1_data)
|
||||
conn_data[u'admin_state_up'] = False
|
||||
|
||||
connection = self.driver.update_connection(self.context,
|
||||
'123', conn_data)
|
||||
@ -464,7 +522,37 @@ class TestCiscoCsrIPsecDeviceDriverSyncStatuses(base.BaseTestCase):
|
||||
self.assertEqual(u'Tunnel0', connection.tunnel)
|
||||
self.assertEqual(constants.ACTIVE, connection.last_status)
|
||||
self.assertFalse(self.conn_create.called)
|
||||
# TODO(pcm) FUTURE - handling for update (delete/create?)
|
||||
self.assertFalse(connection.is_admin_up)
|
||||
self.assertTrue(connection.forced_down)
|
||||
self.assertEqual(1, self.admin_state.call_count)
|
||||
|
||||
def test_update_ipsec_connection_changed_config(self):
|
||||
"""Notified of connection changing config - update.
|
||||
|
||||
Goal here is to detect that the connection is deleted and then
|
||||
created, but not that the specific values have changed, so picking
|
||||
arbitrary value (MTU).
|
||||
"""
|
||||
# Make existing service, and connection that was active
|
||||
vpn_service = self.driver.create_vpn_service(self.service123_data)
|
||||
connection = vpn_service.create_connection(self.conn1_data)
|
||||
|
||||
# Simulate that notification of connection update received
|
||||
self.driver.mark_existing_connections_as_dirty()
|
||||
# Modify the connection data for the 'sync'
|
||||
conn_data = copy.deepcopy(self.conn1_data)
|
||||
conn_data[u'mtu'] = 9200
|
||||
|
||||
connection = self.driver.update_connection(self.context,
|
||||
'123', conn_data)
|
||||
self.assertFalse(connection.is_dirty)
|
||||
self.assertEqual(u'Tunnel0', connection.tunnel)
|
||||
self.assertEqual(constants.ACTIVE, connection.last_status)
|
||||
self.assertEqual(1, self.conn_create.call_count)
|
||||
self.assertEqual(1, self.conn_delete.call_count)
|
||||
self.assertTrue(connection.is_admin_up)
|
||||
self.assertFalse(connection.forced_down)
|
||||
self.assertFalse(self.admin_state.called)
|
||||
|
||||
def test_update_of_unknown_ipsec_connection(self):
|
||||
"""Notified of update of unknown connection - create.
|
||||
@ -472,15 +560,14 @@ class TestCiscoCsrIPsecDeviceDriverSyncStatuses(base.BaseTestCase):
|
||||
Occurs if agent restarts and receives a notification of change
|
||||
to connection, but has no previous record of the connection.
|
||||
Result will be to rebuild the connection.
|
||||
|
||||
This can also happen, if a connection is changed from admin
|
||||
down to admin up (so don't need a separate test for admin up.
|
||||
"""
|
||||
# Will have previously created service, but don't know of connection
|
||||
self.driver.create_vpn_service(self.service123_data)
|
||||
conn_data = {u'id': u'1', u'status': constants.DOWN,
|
||||
u'admin_state_up': True,
|
||||
u'cisco': {u'site_conn_id': u'Tunnel0'}}
|
||||
|
||||
# Simulate that notification of connection update received
|
||||
self.driver.mark_existing_connections_as_dirty()
|
||||
conn_data = copy.deepcopy(self.conn1_data)
|
||||
conn_data[u'status'] = constants.DOWN
|
||||
|
||||
connection = self.driver.update_connection(self.context,
|
||||
u'123', conn_data)
|
||||
@ -488,91 +575,58 @@ class TestCiscoCsrIPsecDeviceDriverSyncStatuses(base.BaseTestCase):
|
||||
self.assertEqual(u'Tunnel0', connection.tunnel)
|
||||
self.assertEqual(constants.DOWN, connection.last_status)
|
||||
self.assertEqual(1, self.conn_create.call_count)
|
||||
|
||||
def test_update_unchanged_ipsec_connection(self):
|
||||
"""Unchanged state for connection during sync - nop."""
|
||||
# Make the (existing) service and connection
|
||||
vpn_service = self.driver.create_vpn_service(self.service123_data)
|
||||
conn_data = {u'id': u'1', u'status': constants.ACTIVE,
|
||||
u'admin_state_up': True,
|
||||
u'cisco': {u'site_conn_id': u'Tunnel0'}}
|
||||
vpn_service.create_connection(conn_data)
|
||||
self.driver.mark_existing_connections_as_dirty()
|
||||
# The notification (state) hasn't changed for the connection
|
||||
|
||||
connection = self.driver.update_connection(self.context,
|
||||
'123', conn_data)
|
||||
self.assertFalse(connection.is_dirty)
|
||||
self.assertEqual(u'Tunnel0', connection.tunnel)
|
||||
self.assertEqual(constants.ACTIVE, connection.last_status)
|
||||
self.assertFalse(self.conn_create.called)
|
||||
|
||||
def test_update_connection_admin_down(self):
|
||||
"""Connection updated to admin down state - force down."""
|
||||
# Make existing service, and connection that was active
|
||||
vpn_service = self.driver.create_vpn_service(self.service123_data)
|
||||
conn_data = {u'id': '1', u'status': constants.ACTIVE,
|
||||
u'admin_state_up': True,
|
||||
u'cisco': {u'site_conn_id': u'Tunnel0'}}
|
||||
vpn_service.create_connection(conn_data)
|
||||
self.driver.mark_existing_connections_as_dirty()
|
||||
# Now simulate that the notification shows the connection admin down
|
||||
conn_data[u'admin_state_up'] = False
|
||||
conn_data[u'status'] = constants.DOWN
|
||||
|
||||
connection = self.driver.update_connection(self.context,
|
||||
u'123', conn_data)
|
||||
self.assertFalse(connection.is_dirty)
|
||||
self.assertTrue(connection.forced_down)
|
||||
self.assertEqual(u'Tunnel0', connection.tunnel)
|
||||
self.assertEqual(constants.DOWN, connection.last_status)
|
||||
self.assertFalse(self.conn_create.called)
|
||||
self.assertTrue(connection.is_admin_up)
|
||||
self.assertFalse(connection.forced_down)
|
||||
self.assertFalse(self.admin_state.called)
|
||||
|
||||
def test_update_missing_connection_admin_down(self):
|
||||
"""Connection not present is in admin down state - nop.
|
||||
|
||||
If the agent has restarted, and a sync notification occurs with
|
||||
a connection that is in admin down state, create the structures,
|
||||
a connection that is in admin down state, recreate the connection,
|
||||
but indicate that the connection is down.
|
||||
"""
|
||||
# Make existing service, but no connection
|
||||
self.driver.create_vpn_service(self.service123_data)
|
||||
conn_data = {u'id': '1', u'status': constants.DOWN,
|
||||
u'admin_state_up': False,
|
||||
u'cisco': {u'site_conn_id': u'Tunnel0'}}
|
||||
|
||||
conn_data = copy.deepcopy(self.conn1_data)
|
||||
conn_data.update({u'status': constants.DOWN,
|
||||
u'admin_state_up': False})
|
||||
connection = self.driver.update_connection(self.context,
|
||||
u'123', conn_data)
|
||||
self.assertIsNotNone(connection)
|
||||
self.assertFalse(connection.is_dirty)
|
||||
self.assertEqual(1, self.conn_create.call_count)
|
||||
self.assertFalse(connection.is_admin_up)
|
||||
self.assertTrue(connection.forced_down)
|
||||
self.assertFalse(self.conn_create.called)
|
||||
self.assertEqual(1, self.admin_state.call_count)
|
||||
|
||||
def test_update_connection_admin_up(self):
|
||||
"""Connection updated to admin up state - record."""
|
||||
# Make existing service, and connection that was admin down
|
||||
conn_data = {u'id': '1', u'status': constants.DOWN,
|
||||
u'admin_state_up': False,
|
||||
u'cisco': {u'site_conn_id': u'Tunnel0'}}
|
||||
conn_data = copy.deepcopy(self.conn1_data)
|
||||
conn_data.update({u'status': constants.DOWN, u'admin_state_up': False})
|
||||
service_data = {u'id': u'123',
|
||||
u'status': constants.DOWN,
|
||||
u'external_ip': u'1.1.1.1',
|
||||
u'admin_state_up': True,
|
||||
u'ipsec_conns': [conn_data]}
|
||||
self.driver.update_service(self.context, service_data)
|
||||
|
||||
# Simulate that notification of connection update received
|
||||
self.driver.mark_existing_connections_as_dirty()
|
||||
# Now simulate that the notification shows the connection admin up
|
||||
conn_data[u'admin_state_up'] = True
|
||||
conn_data[u'status'] = constants.DOWN
|
||||
new_conn_data = copy.deepcopy(conn_data)
|
||||
new_conn_data[u'admin_state_up'] = True
|
||||
|
||||
connection = self.driver.update_connection(self.context,
|
||||
u'123', conn_data)
|
||||
u'123', new_conn_data)
|
||||
self.assertFalse(connection.is_dirty)
|
||||
self.assertFalse(connection.forced_down)
|
||||
self.assertEqual(u'Tunnel0', connection.tunnel)
|
||||
self.assertEqual(constants.DOWN, connection.last_status)
|
||||
self.assertEqual(1, self.conn_create.call_count)
|
||||
self.assertTrue(connection.is_admin_up)
|
||||
self.assertFalse(connection.forced_down)
|
||||
self.assertEqual(2, self.admin_state.call_count)
|
||||
|
||||
def test_update_for_vpn_service_create(self):
|
||||
"""Creation of new IPSec connection on new VPN service - create.
|
||||
@ -580,9 +634,8 @@ class TestCiscoCsrIPsecDeviceDriverSyncStatuses(base.BaseTestCase):
|
||||
Service will be created and marked as 'clean', and update
|
||||
processing for connection will occur (create).
|
||||
"""
|
||||
conn_data = {u'id': u'1', u'status': constants.PENDING_CREATE,
|
||||
u'admin_state_up': True,
|
||||
u'cisco': {u'site_conn_id': u'Tunnel0'}}
|
||||
conn_data = copy.deepcopy(self.conn1_data)
|
||||
conn_data[u'status'] = constants.PENDING_CREATE
|
||||
service_data = {u'id': u'123',
|
||||
u'status': constants.PENDING_CREATE,
|
||||
u'external_ip': u'1.1.1.1',
|
||||
@ -597,15 +650,17 @@ class TestCiscoCsrIPsecDeviceDriverSyncStatuses(base.BaseTestCase):
|
||||
self.assertEqual(u'Tunnel0', connection.tunnel)
|
||||
self.assertEqual(constants.PENDING_CREATE, connection.last_status)
|
||||
self.assertEqual(1, self.conn_create.call_count)
|
||||
self.assertTrue(connection.is_admin_up)
|
||||
self.assertFalse(connection.forced_down)
|
||||
self.assertFalse(self.admin_state.called)
|
||||
|
||||
def test_update_for_new_connection_on_existing_service(self):
|
||||
"""Creating a new IPSec connection on an existing service."""
|
||||
# Create the service before testing, and mark it dirty
|
||||
prev_vpn_service = self.driver.create_vpn_service(self.service123_data)
|
||||
self.driver.mark_existing_connections_as_dirty()
|
||||
conn_data = {u'id': u'1', u'status': constants.PENDING_CREATE,
|
||||
u'admin_state_up': True,
|
||||
u'cisco': {u'site_conn_id': u'Tunnel0'}}
|
||||
conn_data = copy.deepcopy(self.conn1_data)
|
||||
conn_data[u'status'] = constants.PENDING_CREATE
|
||||
service_data = {u'id': u'123',
|
||||
u'status': constants.ACTIVE,
|
||||
u'external_ip': u'1.1.1.1',
|
||||
@ -631,17 +686,15 @@ class TestCiscoCsrIPsecDeviceDriverSyncStatuses(base.BaseTestCase):
|
||||
"""
|
||||
# Create a service and add in a connection that is active
|
||||
prev_vpn_service = self.driver.create_vpn_service(self.service123_data)
|
||||
conn_data = {u'id': u'1', u'status': constants.ACTIVE,
|
||||
u'admin_state_up': True,
|
||||
u'cisco': {u'site_conn_id': u'Tunnel0'}}
|
||||
prev_vpn_service.create_connection(conn_data)
|
||||
prev_vpn_service.create_connection(self.conn1_data)
|
||||
|
||||
self.driver.mark_existing_connections_as_dirty()
|
||||
# Create notification with conn unchanged and service already created
|
||||
service_data = {u'id': u'123',
|
||||
u'status': constants.ACTIVE,
|
||||
u'external_ip': u'1.1.1.1',
|
||||
u'admin_state_up': True,
|
||||
u'ipsec_conns': [conn_data]}
|
||||
u'ipsec_conns': [self.conn1_data]}
|
||||
vpn_service = self.driver.update_service(self.context, service_data)
|
||||
# Should reuse the entry and update the status
|
||||
self.assertEqual(prev_vpn_service, vpn_service)
|
||||
@ -661,15 +714,13 @@ class TestCiscoCsrIPsecDeviceDriverSyncStatuses(base.BaseTestCase):
|
||||
"""
|
||||
# Create an "existing" service, prior to notification
|
||||
prev_vpn_service = self.driver.create_vpn_service(self.service123_data)
|
||||
|
||||
self.driver.mark_existing_connections_as_dirty()
|
||||
conn_data = {u'id': u'1', u'status': constants.ACTIVE,
|
||||
u'admin_state_up': True,
|
||||
u'cisco': {u'site_conn_id': u'Tunnel0'}}
|
||||
service_data = {u'id': u'123',
|
||||
u'status': constants.DOWN,
|
||||
u'external_ip': u'1.1.1.1',
|
||||
u'admin_state_up': False,
|
||||
u'ipsec_conns': [conn_data]}
|
||||
u'ipsec_conns': [self.conn1_data]}
|
||||
vpn_service = self.driver.update_service(self.context, service_data)
|
||||
self.assertEqual(prev_vpn_service, vpn_service)
|
||||
self.assertFalse(vpn_service.is_dirty)
|
||||
@ -688,14 +739,11 @@ class TestCiscoCsrIPsecDeviceDriverSyncStatuses(base.BaseTestCase):
|
||||
of a service that is in the admin down state. Structures will be
|
||||
created, but forced down.
|
||||
"""
|
||||
conn_data = {u'id': u'1', u'status': constants.ACTIVE,
|
||||
u'admin_state_up': True,
|
||||
u'cisco': {u'site_conn_id': u'Tunnel0'}}
|
||||
service_data = {u'id': u'123',
|
||||
u'status': constants.DOWN,
|
||||
u'external_ip': u'1.1.1.1',
|
||||
u'admin_state_up': False,
|
||||
u'ipsec_conns': [conn_data]}
|
||||
u'ipsec_conns': [self.conn1_data]}
|
||||
vpn_service = self.driver.update_service(self.context, service_data)
|
||||
self.assertIsNotNone(vpn_service)
|
||||
self.assertFalse(vpn_service.is_dirty)
|
||||
@ -888,7 +936,7 @@ class TestCiscoCsrIPsecDeviceDriverSyncStatuses(base.BaseTestCase):
|
||||
self.assertEqual(1, self.conn_delete.call_count)
|
||||
|
||||
def test_sweep_multiple_services(self):
|
||||
"""One service and conn udpated, one service and conn not."""
|
||||
"""One service and conn updated, one service and conn not."""
|
||||
# Create two services, each with a connection
|
||||
vpn_service1 = self.driver.create_vpn_service(self.service123_data)
|
||||
vpn_service1.create_connection(self.conn1_data)
|
||||
@ -1315,9 +1363,41 @@ class TestCiscoCsrIPsecDeviceDriverSyncStatuses(base.BaseTestCase):
|
||||
# Simulate one service with one connection up, one down
|
||||
conn1_data = {u'id': u'1', u'status': constants.ACTIVE,
|
||||
u'admin_state_up': True,
|
||||
u'mtu': 1500,
|
||||
u'psk': u'secret',
|
||||
u'peer_address': '192.168.1.2',
|
||||
u'peer_cidrs': ['10.1.0.0/24', '10.2.0.0/24'],
|
||||
u'ike_policy': {u'auth_algorithm': u'sha1',
|
||||
u'encryption_algorithm': u'aes-128',
|
||||
u'pfs': u'Group5',
|
||||
u'ike_version': u'v1',
|
||||
u'lifetime_units': u'seconds',
|
||||
u'lifetime_value': 3600},
|
||||
u'ipsec_policy': {u'transform_protocol': u'ah',
|
||||
u'encryption_algorithm': u'aes-128',
|
||||
u'auth_algorithm': u'sha1',
|
||||
u'pfs': u'group5',
|
||||
u'lifetime_units': u'seconds',
|
||||
u'lifetime_value': 3600},
|
||||
u'cisco': {u'site_conn_id': u'Tunnel1'}}
|
||||
conn2_data = {u'id': u'2', u'status': constants.DOWN,
|
||||
u'admin_state_up': True,
|
||||
u'mtu': 1500,
|
||||
u'psk': u'secret',
|
||||
u'peer_address': '192.168.1.2',
|
||||
u'peer_cidrs': ['10.1.0.0/24', '10.2.0.0/24'],
|
||||
u'ike_policy': {u'auth_algorithm': u'sha1',
|
||||
u'encryption_algorithm': u'aes-128',
|
||||
u'pfs': u'Group5',
|
||||
u'ike_version': u'v1',
|
||||
u'lifetime_units': u'seconds',
|
||||
u'lifetime_value': 3600},
|
||||
u'ipsec_policy': {u'transform_protocol': u'ah',
|
||||
u'encryption_algorithm': u'aes-128',
|
||||
u'auth_algorithm': u'sha1',
|
||||
u'pfs': u'group5',
|
||||
u'lifetime_units': u'seconds',
|
||||
u'lifetime_value': 3600},
|
||||
u'cisco': {u'site_conn_id': u'Tunnel2'}}
|
||||
service_data = {u'id': u'123',
|
||||
u'status': constants.ACTIVE,
|
||||
|
@ -309,12 +309,12 @@ class TestCiscoIPsecDriver(base.BaseTestCase):
|
||||
mock.patch.object(csr_db, 'create_tunnel_mapping').start()
|
||||
self.context = n_ctx.Context('some_user', 'some_tenant')
|
||||
|
||||
def _test_update(self, func, args, reason=None):
|
||||
def _test_update(self, func, args, additional_info=None):
|
||||
with mock.patch.object(self.driver.agent_rpc, 'cast') as cast:
|
||||
func(self.context, *args)
|
||||
cast.assert_called_once_with(
|
||||
self.context,
|
||||
{'args': reason,
|
||||
{'args': additional_info,
|
||||
'namespace': None,
|
||||
'method': 'vpnservice_updated'},
|
||||
version='1.0',
|
||||
@ -345,11 +345,9 @@ class TestCiscoIPsecDriver(base.BaseTestCase):
|
||||
constants.ERROR)
|
||||
|
||||
def test_update_ipsec_site_connection(self):
|
||||
# TODO(pcm) FUTURE - Update test, when supported
|
||||
self.assertRaises(ipsec_driver.CsrUnsupportedError,
|
||||
self._test_update,
|
||||
self.driver.update_ipsec_site_connection,
|
||||
[FAKE_VPN_CONNECTION, FAKE_VPN_CONNECTION])
|
||||
self._test_update(self.driver.update_ipsec_site_connection,
|
||||
[FAKE_VPN_CONNECTION, FAKE_VPN_CONNECTION],
|
||||
{'reason': 'ipsec-conn-update'})
|
||||
|
||||
def test_delete_ipsec_site_connection(self):
|
||||
self._test_update(self.driver.delete_ipsec_site_connection,
|
||||
|
Loading…
Reference in New Issue
Block a user