diff --git a/systemvm/patches/debian/config/opt/cloud/bin/configure.py b/systemvm/patches/debian/config/opt/cloud/bin/configure.py index 9efc07c71880..5b4b062dba22 100755 --- a/systemvm/patches/debian/config/opt/cloud/bin/configure.py +++ b/systemvm/patches/debian/config/opt/cloud/bin/configure.py @@ -566,7 +566,7 @@ def configure_ipsec(self, obj): file.addeq(" lifetime=%s" % self.convert_sec_to_h(obj['esp_lifetime'])) file.addeq(" pfs=%s" % pfs) file.addeq(" keyingtries=2") - file.addeq(" auto=start") + file.addeq(" auto=route") if 'encap' not in obj: obj['encap']=False file.addeq(" forceencaps=%s" % CsHelper.bool_to_yn(obj['encap'])) @@ -582,10 +582,21 @@ def configure_ipsec(self, obj): logging.info("Configured vpn %s %s", leftpeer, rightpeer) CsHelper.execute("ipsec rereadsecrets") - # This will load the new config and start the connection when needed since auto=start in the config + # This will load the new config CsHelper.execute("ipsec reload") os.chmod(vpnsecretsfile, 0400) + # Check that the ipsec config is ready + for i in range(2): + result = CsHelper.execute('ipsec status vpn-%s | grep "%s"' % (rightpeer, peerlist.split(",", 1)[0])) + if len(result) > 0: + break + time.sleep(1) + + # With 'auto=route', connections are established with an attempt to communicate over the S2S VPN + # Attempt to ping the other side to initialize the connection of the S2S VPN configuration + CsHelper.execute("timeout 0.5 ping -c 1 %s" % (peerlist.split("/", 1)[0])) + def convert_sec_to_h(self, val): hrs = int(val) / 3600 return "%sh" % hrs diff --git a/test/integration/smoke/test_vpc_vpn.py b/test/integration/smoke/test_vpc_vpn.py index ddf769300504..9ed1d0109ac4 100644 --- a/test/integration/smoke/test_vpc_vpn.py +++ b/test/integration/smoke/test_vpc_vpn.py @@ -57,6 +57,7 @@ from nose.plugins.attrib import attr import logging +import subprocess import time @@ -1177,3 +1178,447 @@ def tearDownClass(cls): cleanup_resources(cls.apiclient, cls.cleanup) except Exception, e: raise Exception("Cleanup failed with %s" % e) + + +class TestVPCSite2SiteVPNMultipleOptions(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.logger = logging.getLogger('TestVPCSite2SiteVPNMultipleOptions') + cls.stream_handler = logging.StreamHandler() + cls.logger.setLevel(logging.DEBUG) + cls.logger.addHandler(cls.stream_handler) + + testClient = super(TestVPCSite2SiteVPNMultipleOptions, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = Services().services + + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.domain = get_domain(cls.apiclient) + + cls.compute_offering = ServiceOffering.create( + cls.apiclient, + cls.services["compute_offering"] + ) + + cls.account = Account.create( + cls.apiclient, services=cls.services["account"]) + + cls.hypervisor = testClient.getHypervisorInfo() + + cls.logger.debug("Downloading Template: %s from: %s" % (cls.services["template"][ + cls.hypervisor.lower()], cls.services["template"][cls.hypervisor.lower()]["url"])) + cls.template = Template.register(cls.apiclient, cls.services["template"][cls.hypervisor.lower( + )], cls.zone.id, hypervisor=cls.hypervisor.lower(), account=cls.account.name, domainid=cls.domain.id) + cls.template.download(cls.apiclient) + + if cls.template == FAILED: + assert False, "get_template() failed to return template" + + cls.logger.debug("Successfully created account: %s, id: \ + %s" % (cls.account.name, + cls.account.id)) + + cls.cleanup = [cls.account, cls.compute_offering] + return + + def _get_ssh_client(self, virtual_machine, services, retries): + """ Setup ssh client connection and return connection + vm requires attributes public_ip, public_port, username, password """ + + try: + ssh_client = SshClient( + virtual_machine.public_ip, + services["virtual_machine"]["ssh_port"], + services["virtual_machine"]["username"], + services["virtual_machine"]["password"], + retries) + + except Exception as e: + self.fail("Unable to create ssh connection: " % e) + + self.assertIsNotNone( + ssh_client, "Failed to setup ssh connection to vm=%s on public_ip=%s" % (virtual_machine.name, virtual_machine.public_ip)) + + return ssh_client + + def _create_natrule(self, vpc, vm, public_port, private_port, public_ip, network, services=None): + self.logger.debug("Creating NAT rule in network for vm with public IP") + if not services: + self.services["natrule"]["privateport"] = private_port + self.services["natrule"]["publicport"] = public_port + self.services["natrule"]["startport"] = public_port + self.services["natrule"]["endport"] = public_port + services = self.services["natrule"] + + nat_rule = NATRule.create( + apiclient=self.apiclient, + services=services, + ipaddressid=public_ip.ipaddress.id, + virtual_machine=vm, + networkid=network.id + ) + self.assertIsNotNone( + nat_rule, "Failed to create NAT Rule for %s" % public_ip.ipaddress.ipaddress) + self.logger.debug( + "Adding NetworkACL rules to make NAT rule accessible") + + vm.ssh_ip = nat_rule.ipaddress + vm.public_ip = nat_rule.ipaddress + vm.public_port = int(public_port) + return nat_rule + + def _validate_vpc_offering(self, vpc_offering): + + self.logger.debug("Check if the VPC offering is created successfully?") + vpc_offs = VpcOffering.list( + self.apiclient, + id=vpc_offering.id + ) + offering_list = validateList(vpc_offs) + self.assertEqual(offering_list[0], + PASS, + "List VPC offerings should return a valid list" + ) + self.assertEqual( + vpc_offering.name, + vpc_offs[0].name, + "Name of the VPC offering should match with listVPCOff data" + ) + self.logger.debug( + "VPC offering is created successfully - %s" % + vpc_offering.name) + return + + def _create_vpc_offering(self, offering_name): + + vpc_off = None + if offering_name is not None: + + self.logger.debug("Creating VPC offering: %s", offering_name) + vpc_off = VpcOffering.create( + self.apiclient, + self.services[offering_name] + ) + + self._validate_vpc_offering(vpc_off) + self.cleanup.append(vpc_off) + + return vpc_off + + @attr(tags=["advanced"], required_hardware="true") + def test_01_vpc_site2site_vpn_multiple_options(self): + """Test Site 2 Site VPN Across VPCs""" + self.logger.debug("Starting test: test_01_vpc_site2site_vpn_multiple_options") + # 0) Get the default network offering for VPC + networkOffering = NetworkOffering.list( + self.apiclient, name="DefaultIsolatedNetworkOfferingForVpcNetworks") + self.assert_(networkOffering is not None and len( + networkOffering) > 0, "No VPC based network offering") + + # Create and Enable VPC offering + vpc_offering = self._create_vpc_offering('vpc_offering') + self.assert_(vpc_offering is not None, "Failed to create VPC Offering") + vpc_offering.update(self.apiclient, state='Enabled') + + vpc1 = None + # Create VPC 1 + try: + vpc1 = VPC.create( + apiclient=self.apiclient, + services=self.services["vpc"], + networkDomain="vpc1.vpn", + vpcofferingid=vpc_offering.id, + zoneid=self.zone.id, + account=self.account.name, + domainid=self.domain.id + ) + except Exception as e: + self.fail(e) + finally: + self.assert_(vpc1 is not None, "VPC1 creation failed") + + self.logger.debug("VPC1 %s created" % vpc1.id) + + vpc2 = None + # Create VPC 2 + try: + vpc2 = VPC.create( + apiclient=self.apiclient, + services=self.services["vpc2"], + networkDomain="vpc2.vpn", + vpcofferingid=vpc_offering.id, + zoneid=self.zone.id, + account=self.account.name, + domainid=self.domain.id + ) + except Exception as e: + self.fail(e) + finally: + self.assert_(vpc2 is not None, "VPC2 creation failed") + + self.logger.debug("VPC2 %s created" % vpc2.id) + + default_acl = NetworkACLList.list( + self.apiclient, name="default_allow")[0] + + ntwk1 = None + # Create network in VPC 1 + try: + ntwk1 = Network.create( + apiclient=self.apiclient, + services=self.services["network_1"], + accountid=self.account.name, + domainid=self.account.domainid, + networkofferingid=networkOffering[0].id, + zoneid=self.zone.id, + vpcid=vpc1.id, + aclid=default_acl.id + ) + except Exception as e: + self.fail(e) + finally: + self.assertIsNotNone(ntwk1, "Network failed to create") + + self.logger.debug("Network %s created in VPC %s" % (ntwk1.id, vpc1.id)) + + ntwk2 = None + # Create network in VPC 2 + try: + ntwk2 = Network.create( + apiclient=self.apiclient, + services=self.services["network_2"], + accountid=self.account.name, + domainid=self.account.domainid, + networkofferingid=networkOffering[0].id, + zoneid=self.zone.id, + vpcid=vpc2.id, + aclid=default_acl.id + ) + except Exception as e: + self.fail(e) + finally: + self.assertIsNotNone(ntwk2, "Network failed to create") + + self.logger.debug("Network %s created in VPC %s" % (ntwk2.id, vpc2.id)) + + vm1 = None + # Deploy a vm in network 2 + try: + vm1 = VirtualMachine.create(self.apiclient, services=self.services["virtual_machine"], + templateid=self.template.id, + zoneid=self.zone.id, + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.compute_offering.id, + networkids=ntwk1.id, + hypervisor=self.hypervisor + ) + except Exception as e: + self.fail(e) + finally: + self.assert_(vm1 is not None, "VM failed to deploy") + self.assert_(vm1.state == 'Running', "VM is not running") + + self.logger.debug("VM %s deployed in VPC %s" % (vm1.id, vpc1.id)) + + vm2 = None + # Deploy a vm in network 2 + try: + vm2 = VirtualMachine.create(self.apiclient, services=self.services["virtual_machine"], + templateid=self.template.id, + zoneid=self.zone.id, + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.compute_offering.id, + networkids=ntwk2.id, + hypervisor=self.hypervisor + ) + except Exception as e: + self.fail(e) + finally: + self.assert_(vm2 is not None, "VM failed to deploy") + self.assert_(vm2.state == 'Running', "VM is not running") + + self.debug("VM %s deployed in VPC %s" % (vm2.id, vpc2.id)) + + # default config + config = { + 'ike_enc' :'aes128', + 'ike_hash' :'sha1', + 'ike_dh' :'modp1536', + 'esp_enc' :'aes128', + 'esp_hash' :'sha1', + 'esp_pfs' :'modp1536', + 'psk' :'secreatKey', + 'ike_life' :86400, + 'esp_life' :3600, + 'dpd' :True, + 'force_encap' :False, + 'passive_1' :False, + 'passive_2' :False + } + test_confs = [ + {}, # default + {'force_encap': True}, + {'ike_life': ''}, + {'esp_life': ''}, + {'ike_life': '', 'esp_life': ''}, + {'passive_1': True, 'passive_2': True}, + {'passive_1': False, 'passive_2': True}, + {'passive_1': True, 'passive_2': False}, + {'passive_1': False, 'passive_2': False, 'dpd': False}, + {'passive_1': True, 'passive_2': True, 'dpd': False}, + {'passive_1': True, 'passive_2': False, 'dpd': False}, + {'passive_1': False, 'passive_2': True, 'dpd': False}, + {'passive_1': True, 'passive_2': False, 'esp_pfs': ''}, + {'ike_dh': 'modp3072', 'ike_hash': 'sha256', 'esp_pfs': 'modp2048', 'esp_hash':'sha384'}, + {'ike_dh': 'modp4096', 'ike_hash': 'sha384', 'esp_pfs': 'modp6144', 'esp_hash':'sha512'}, + {'ike_dh': 'modp8192', 'ike_hash': 'sha512', 'esp_pfs': 'modp8192', 'esp_hash':'sha384'} + ] + + # 4) Enable Site-to-Site VPN for VPC + vpn1_response = Vpn.createVpnGateway(self.apiclient, vpc1.id) + self.assert_( + vpn1_response is not None, "Failed to enable VPN Gateway 1") + self.logger.debug("VPN gateway for VPC %s enabled" % vpc1.id) + + vpn2_response = Vpn.createVpnGateway(self.apiclient, vpc2.id) + self.assert_( + vpn2_response is not None, "Failed to enable VPN Gateway 2") + self.logger.debug("VPN gateway for VPC %s enabled" % vpc2.id) + + # 5) Add VPN Customer gateway info + src_nat_list = PublicIPAddress.list( + self.apiclient, + account=self.account.name, + domainid=self.account.domainid, + listall=True, + issourcenat=True, + vpcid=vpc1.id + ) + ip1 = src_nat_list[0] + src_nat_list = PublicIPAddress.list( + self.apiclient, + account=self.account.name, + domainid=self.account.domainid, + listall=True, + issourcenat=True, + vpcid=vpc2.id + ) + ip2 = src_nat_list[0] + + # acquire an extra ip address to use to ssh into vm2 + try: + services = self.services.copy() + del services["account"] + vm2.public_ip = PublicIPAddress.create( + apiclient=self.apiclient, + zoneid=self.zone.id, + account=self.account.name, + domainid=self.account.domainid, + services=services, + networkid=ntwk2.id, + vpcid=vpc2.id) + except Exception as e: + self.fail(e) + finally: + self.assert_( + vm2.public_ip is not None, "Failed to aqcuire public ip for vm2") + + natrule = None + # Create port forward to be able to ssh into vm2 + try: + natrule = self._create_natrule( + vpc2, vm2, 22, 22, vm2.public_ip, ntwk2) + except Exception as e: + self.fail(e) + finally: + self.assert_( + natrule is not None, "Failed to create portforward for vm2") + time.sleep(20) + + # setup ssh connection to vm2 + ssh_client = self._get_ssh_client(vm2, self.services, 10) + if not ssh_client: + self.fail("Failed to setup ssh connection to %s" % vm2.public_ip) + + for test_c in test_confs: + c = config.copy() + c.update(test_c) + services = self._get_vpn_config(c) + self.logger.debug(services) + customer1_response = VpnCustomerGateway.create( + self.apiclient, + services, + "Peer VPC1", + ip1.ipaddress, + vpc1.cidr, + account=self.account.name, + domainid=self.account.domainid) + self.logger.debug("VPN customer gateway added for VPC %s enabled" % vpc1.id) + self.logger.debug(vars(customer1_response)) + + customer2_response = VpnCustomerGateway.create( + self.apiclient, + services, + "Peer VPC2", + ip2.ipaddress, + vpc2.cidr, + account=self.account.name, + domainid=self.account.domainid) + self.logger.debug("VPN customer gateway added for VPC %s enabled" % vpc2.id) + self.logger.debug(vars(customer2_response)) + + # 6) Connect two VPCs + vpnconn1_response = Vpn.createVpnConnection( + self.apiclient, customer1_response.id, vpn2_response['id'], c['passive_1']) + self.logger.debug("VPN connection created for VPC %s" % vpc2.id) + time.sleep(5) + vpnconn2_response = Vpn.createVpnConnection( + self.apiclient, customer2_response.id, vpn1_response['id'], c['passive_2']) + self.logger.debug("VPN connection created for VPC %s" % vpc1.id) + + # Wait for config + time.sleep(15) + # Run ping test + packet_loss = ssh_client.execute( + "/bin/ping -c 3 -t 10 " + vm1.nic[0].ipaddress + " |grep packet|cut -d ' ' -f 7| cut -f1 -d'%'")[0] + self.logger.debug("Packet loss %s" % packet_loss) + self.assert_(int(packet_loss) == 0, "Ping did not succeed") + + # Cleanup + Vpn.deleteVpnConnection(self.apiclient, vpnconn1_response['id']) + Vpn.deleteVpnConnection(self.apiclient, vpnconn2_response['id']) + cleanup_resources(self.apiclient, [customer1_response, customer2_response]) + # Wait 130s for complete cleanup + time.sleep(130) + + def _get_vpn_config(self, c): + ike_policy = '%s-%s;%s' % (c['ike_enc'], c['ike_hash'], c['ike_dh']) if c['ike_dh'] else '%s-%s' % (c['ike_enc'], c['ike_hash']) + esp_policy = '%s-%s;%s' % (c['esp_enc'], c['esp_hash'], c['esp_pfs']) if c['esp_pfs'] else '%s-%s' % (c['esp_enc'], c['esp_hash']) + + out = { + 'ipsecpsk': c['psk'], + 'ikepolicy':ike_policy, + 'esppolicy':esp_policy, + 'dpd':c['dpd'], + 'forceencap':c['force_encap'] + } + + if c['ike_life']: + out['ikelifetime'] = c['ike_life'] + if c['esp_life']: + out['esplifetime'] = c['esp_life'] + + return out + + @classmethod + def tearDownClass(cls): + try: + try: + cls.template.delete(cls.apiclient) + except Exception: pass + cleanup_resources(cls.apiclient, cls.cleanup) + except Exception, e: + raise Exception("Cleanup failed with %s" % e)