From 23e856bd722082028d35b4dfad64aad4fceec86d Mon Sep 17 00:00:00 2001 From: nfedotov Date: Wed, 25 Feb 2015 17:54:33 +0300 Subject: [PATCH] Scenario test for vpnaas: ipsec-site-connection The test verifies that it is possible to establish a vpn connection between two sites. And then it performs basic connectivity test. It pings IP address of site2 from an IP address of site1. The patchset implements scenario test for both openswan and strongswan device drivers. Change-Id: I5ff943d4cb4de06c79c2f2d0fee178ad992f36d1 --- .../tests/functional/common/test_scenario.py | 307 +++++++++++++++++- 1 file changed, 300 insertions(+), 7 deletions(-) diff --git a/neutron_vpnaas/tests/functional/common/test_scenario.py b/neutron_vpnaas/tests/functional/common/test_scenario.py index e0d7e3b88..ff698376f 100644 --- a/neutron_vpnaas/tests/functional/common/test_scenario.py +++ b/neutron_vpnaas/tests/functional/common/test_scenario.py @@ -10,17 +10,310 @@ # License for the specific language governing permissions and limitations # under the License. -# NOTE: The purpose of this module is to provide nop tests to verify that -# the functional gate is working. - -# TODO(pcm): In the future, this will be replaced with a real scenario test. +import copy +import os +import fixtures +import mock +import netaddr +from neutron.agent.common import config as agent_config +from neutron.agent import l3_agent as l3_agent_main +from neutron.agent.linux import ip_lib +from neutron.agent.linux import utils as linux_utils +from neutron.common import config as common_config +from neutron.common import utils as common_utils +from neutron.plugins.common import constants +from neutron.services.provider_configuration import serviceprovider_opts +from neutron.tests.common import net_helpers from neutron.tests.functional import base +from oslo_config import cfg +from oslo_log import log as logging +from oslo_utils import uuidutils + +from neutron_vpnaas.services.vpn import agent as vpn_agent +from neutron_vpnaas.services.vpn.agent import vpn_agent_opts +from neutron_vpnaas.services.vpn.device_drivers import ipsec + +_uuid = uuidutils.generate_uuid +FAKE_IKE_POLICY = { + 'auth_algorithm': 'sha1', + "ike_version": "v1", + 'encryption_algorithm': 'aes-128', + 'pfs': 'group5', + 'phase1_negotiation_mode': 'main', + 'lifetime_units': 'seconds', + 'lifetime_value': 3600 +} + +FAKE_IPSEC_POLICY = { + "encapsulation_mode": "tunnel", + "encryption_algorithm": "aes-128", + "pfs": "group5", + "lifetime_units": "seconds", + "lifetime_value": 3600, + "transform_protocol": "esp", + "auth_algorithm": "sha1", +} + +FAKE_VPN_SERVICE = { + "id": _uuid(), + "router_id": _uuid(), + "status": constants.PENDING_CREATE, + "admin_state_up": True, + 'external_ip': "172.24.4.8", + 'subnet': {'cidr': "10.100.255.224/28"} +} + +FAKE_IPSEC_CONNECTION = { + "vpnservice_id": _uuid(), + "status": "PENDING_CREATE", + "psk": "969022489", + "initiator": "bi-directional", + "admin_state_up": True, + "auth_mode": "psk", + 'external_ip': "172.24.4.8", + "peer_cidrs": ["10.100.255.224/28"], + "mtu": 1500, + "dpd_action": "hold", + "dpd_interval": 30, + "dpd_timeout": 120, + "route_mode": "static", + "ikepolicy": FAKE_IKE_POLICY, + "ipsecpolicy": FAKE_IPSEC_POLICY, + "peer_address": "172.24.4.8", + "peer_id": "172.24.4.8", + "id": _uuid() +} + +PUBLIC_NET = netaddr.IPNetwork('19.4.4.0/24') +PRIVATE_NET = netaddr.IPNetwork('35.4.0.0/16') +FAKE_PUBLIC_SUBNET_ID = _uuid() +FAKE_PRIVATE_SUBNET_ID = _uuid() + +MAC_BASE = cfg.CONF.base_mac.split(':') +FAKE_ROUTER = { + 'id': _uuid(), + '_interfaces': [ + { + 'id': _uuid(), + 'admin_state_up': True, + 'network_id': _uuid(), + 'mac_address': common_utils.get_random_mac(MAC_BASE), + 'subnets': [ + { + 'ipv6_ra_mode': None, + 'cidr': str(PRIVATE_NET), + 'gateway_ip': str(PRIVATE_NET[1]), + 'id': FAKE_PRIVATE_SUBNET_ID, + 'ipv6_address_mode': None + } + ], + 'fixed_ips': [ + { + 'subnet_id': FAKE_PRIVATE_SUBNET_ID, + 'prefixlen': 24, + 'ip_address': PRIVATE_NET[4] + } + ] + } + ], + 'enable_snat': True, + 'gw_port': { + 'network_id': _uuid(), + 'subnets': [ + { + 'cidr': str(PUBLIC_NET), + 'gateway_ip': str(PUBLIC_NET[1]), + 'id': FAKE_PUBLIC_SUBNET_ID + } + ], + 'fixed_ips': [ + { + 'subnet_id': FAKE_PUBLIC_SUBNET_ID, + 'prefixlen': PUBLIC_NET.prefixlen, + 'ip_address': str(PUBLIC_NET[4]) + } + ], + 'id': _uuid(), + 'mac_address': common_utils.get_random_mac(MAC_BASE) + }, + 'distributed': False, + '_floatingips': [], + 'routes': [] +} + + +class RouterFixture(fixtures.Fixture): + + def __init__(self, l3_agent, public_ip, private_cidr): + self.l3_agent = l3_agent + self.router_info = self._generate_info(public_ip, private_cidr) + self.router_id = self.router_info['id'] + + def setUp(self): + super(RouterFixture, self).setUp() + self.l3_agent._process_added_router(self.router_info) + self.router = self.l3_agent.router_info[self.router_id] + self.addCleanup(self.l3_agent._router_removed, self.router_id) + + def _generate_info(self, public_ip, private_cidr): + info = copy.deepcopy(FAKE_ROUTER) + info['id'] = _uuid() + info['_interfaces'][0]['id'] = _uuid() + (info['_interfaces'][0] + ['mac_address']) = common_utils.get_random_mac(MAC_BASE) + (info['_interfaces'][0]['fixed_ips'][0] + ['ip_address']) = str(private_cidr[4]) + info['_interfaces'][0]['subnets'][0].update({ + 'cidr': str(private_cidr), + 'gateway_ip': str(private_cidr[1])}) + info['gw_port']['id'] = _uuid() + info['gw_port']['fixed_ips'][0]['ip_address'] = str(public_ip) + info['gw_port']['mac_address'] = common_utils.get_random_mac(MAC_BASE) + return info class TestIPSecScenario(base.BaseSudoTestCase): - """Test end-to-end IPSec connection.""" + vpn_agent_ini = os.environ.get('VPN_AGENT_INI', + '/etc/neutron/vpn_agent.ini') - def test_dummy(self): - pass + def setUp(self): + super(TestIPSecScenario, self).setUp() + + mock.patch('neutron.agent.l3.agent.L3PluginApi').start() + + cfg.CONF.set_override('debug', True) + agent_config.setup_logging() + config = cfg.ConfigOpts() + config.register_opts(common_config.core_opts) + config.register_opts(common_config.core_cli_opts) + logging.register_options(config) + agent_config.register_process_monitor_opts(config) + l3_agent_main.register_opts(config) + config.set_override( + 'interface_driver', + 'neutron.agent.linux.interface.OVSInterfaceDriver') + config.set_override('router_delete_namespaces', True) + config.register_opts(serviceprovider_opts, 'service_providers') + config.register_opts(vpn_agent_opts, 'vpnagent') + config.register_opts(ipsec.ipsec_opts, 'ipsec') + config.register_opts(ipsec.openswan_opts, 'openswan') + config.set_override('state_path', self.get_new_temp_dir().path) + + self.br_int = self.useFixture(net_helpers.OVSBridgeFixture()).bridge + self.br_ex = self.useFixture(net_helpers.OVSBridgeFixture()).bridge + config.set_override('ovs_integration_bridge', self.br_int.br_name) + config.set_override('external_network_bridge', self.br_ex.br_name) + + config(['--config-file', self.vpn_agent_ini]) + self.vpn_agent = vpn_agent.VPNAgent('agent1', config) + + # Assign ip address to br-ex port because it is a gateway + ex_port = ip_lib.IPDevice(self.br_ex.br_name) + ex_port.addr.add(str(PUBLIC_NET[1])) + + def prepare_vpn_service_info(self, router_id, external_ip, subnet_cidr): + service = copy.deepcopy(FAKE_VPN_SERVICE) + service.update({ + 'id': _uuid(), + 'router_id': router_id, + 'external_ip': str(external_ip), + 'subnet': {'cidr': str(subnet_cidr)}}) + return service + + def prepare_ipsec_conn_info(self, vpn_service, peer_vpn_service): + ipsec_conn = copy.deepcopy(FAKE_IPSEC_CONNECTION) + ipsec_conn.update({ + 'id': _uuid(), + 'vpnservice_id': vpn_service['id'], + 'external_ip': vpn_service['external_ip'], + 'peer_cidrs': [peer_vpn_service['subnet']['cidr']], + 'peer_address': peer_vpn_service['external_ip'], + 'peer_id': peer_vpn_service['external_ip'] + }) + vpn_service['ipsec_site_connections'] = [ipsec_conn] + + def port_setup(self, router): + """Creates namespace and a port inside it on a client site.""" + client_ns = self.useFixture(net_helpers.NamespaceFixture()).ip_wrapper + router_ip_cidr = self._port_first_ip_cidr(router.internal_ports[0]) + + port_ip_cidr = net_helpers.increment_ip_cidr(router_ip_cidr) + port = self.useFixture( + net_helpers.OVSPortFixture(self.br_int, client_ns.namespace)).port + port.addr.add(port_ip_cidr) + port.route.add_gateway(router_ip_cidr.partition('/')[0]) + return client_ns.namespace, port_ip_cidr.partition('/')[0] + + def _port_first_ip_cidr(self, port): + fixed_ip = port['fixed_ips'][0] + return common_utils.ip_to_cidr(fixed_ip['ip_address'], + fixed_ip['prefixlen']) + + def site_setup(self, router_public_ip, private_net_cidr): + router = self.useFixture( + RouterFixture(self.vpn_agent, router_public_ip, + private_net_cidr)).router + port_namespace, port_ip = self.port_setup(router) + + vpn_service = self.prepare_vpn_service_info( + router.router_id, router_public_ip, private_net_cidr) + return {"router": router, "port_namespace": port_namespace, + "port_ip": port_ip, "vpn_service": vpn_service} + + def _ping(self, namespace, ip): + """Pings ip address from network namespace. + + In order to ping it uses following cli command: + ip netns exec ping -c 4 -q + """ + try: + count = 4 + cmd = ['ping', '-w', 2 * count, '-c', count, ip] + cmd = ip_lib.add_namespace_to_cmd(cmd, namespace) + linux_utils.execute(cmd, run_as_root=True) + return True + except RuntimeError: + return False + + def test_ipsec_site_connections(self): + device = self.vpn_agent.device_drivers[0] + # Mock the method below because it causes Exception: + # RuntimeError: Second simultaneous read on fileno 5 detected. + # Unless you really know what you're doing, make sure that only + # one greenthread can read any particular socket. Consider using + # a pools.Pool. If you do know what you're doing and want to disable + # this error, call eventlet.debug.hub_prevent_multiple_readers(False) + # Can reproduce the exception in the test only + ip_lib.send_ip_addr_adv_notif = mock.Mock() + # There are no vpn services yet. get_vpn_services_on_host returns + # empty list + device.agent_rpc.get_vpn_services_on_host = mock.Mock( + return_value=[]) + # instantiate network resources "router", "private network" + private_nets = list(PRIVATE_NET.subnet(24)) + site1 = self.site_setup(PUBLIC_NET[4], private_nets[1]) + site2 = self.site_setup(PUBLIC_NET[5], private_nets[2]) + # build vpn resources + self.prepare_ipsec_conn_info(site1['vpn_service'], + site2['vpn_service']) + self.prepare_ipsec_conn_info(site2['vpn_service'], + site1['vpn_service']) + + device.report_status = mock.Mock() + device.agent_rpc.get_vpn_services_on_host = mock.Mock( + return_value=[site1['vpn_service'], + site2['vpn_service']]) + + self.assertFalse(self._ping(site1['port_namespace'], site2['port_ip'])) + self.assertFalse(self._ping(site2['port_namespace'], site1['port_ip'])) + + device.sync(mock.Mock(), [{'id': site1['router'].router_id}, + {'id': site2['router'].router_id}]) + self.addCleanup( + device._delete_vpn_processes, + [site1['router'].router_id, site2['router'].router_id], []) + + self.assertTrue(self._ping(site1['port_namespace'], site2['port_ip'])) + self.assertTrue(self._ping(site2['port_namespace'], site1['port_ip']))