diff --git a/core/src/com/cloud/agent/resource/virtualnetwork/facade/IpAssociationConfigItem.java b/core/src/com/cloud/agent/resource/virtualnetwork/facade/IpAssociationConfigItem.java index bd499053ff36..64f953dc7264 100644 --- a/core/src/com/cloud/agent/resource/virtualnetwork/facade/IpAssociationConfigItem.java +++ b/core/src/com/cloud/agent/resource/virtualnetwork/facade/IpAssociationConfigItem.java @@ -41,7 +41,7 @@ public List generateConfig(final NetworkElementCommand cmd) { for (final IpAddressTO ip : command.getIpAddresses()) { final IpAddress ipAddress = new IpAddress(ip.getPublicIp(), ip.isSourceNat(), ip.isAdd(), ip.isOneToOneNat(), ip.isFirstIP(), ip.getVlanGateway(), ip.getVlanNetmask(), - ip.getVifMacAddress(), ip.getNicDevId(), ip.isNewNic()); + ip.getVifMacAddress(), ip.getNicDevId(), ip.isNewNic(), ip.getTrafficType().toString()); ips.add(ipAddress); } @@ -56,4 +56,4 @@ protected List generateConfigItems(final ConfigBase configuration) { return super.generateConfigItems(configuration); } -} \ No newline at end of file +} diff --git a/core/src/com/cloud/agent/resource/virtualnetwork/model/IpAddress.java b/core/src/com/cloud/agent/resource/virtualnetwork/model/IpAddress.java index 5889bd28ea29..70aeb4cfc890 100644 --- a/core/src/com/cloud/agent/resource/virtualnetwork/model/IpAddress.java +++ b/core/src/com/cloud/agent/resource/virtualnetwork/model/IpAddress.java @@ -31,13 +31,14 @@ public class IpAddress { private String vifMacAddress; private Integer nicDevId; private boolean newNic; + private String nwType; public IpAddress() { // Empty constructor for (de)serialization } public IpAddress(String publicIp, boolean sourceNat, boolean add, boolean oneToOneNat, boolean firstIP, String gateway, String netmask, String vifMacAddress, - Integer nicDevId, boolean newNic) { + Integer nicDevId, boolean newNic, String nwType) { super(); this.publicIp = publicIp; this.sourceNat = sourceNat; @@ -49,6 +50,7 @@ public IpAddress(String publicIp, boolean sourceNat, boolean add, boolean oneToO this.vifMacAddress = vifMacAddress; this.nicDevId = nicDevId; this.newNic = newNic; + this.nwType = nwType; } public String getPublicIp() { diff --git a/core/test/com/cloud/agent/resource/virtualnetwork/ConfigHelperTest.java b/core/test/com/cloud/agent/resource/virtualnetwork/ConfigHelperTest.java index b13c10cb1dbc..dbbdc8eeb048 100644 --- a/core/test/com/cloud/agent/resource/virtualnetwork/ConfigHelperTest.java +++ b/core/test/com/cloud/agent/resource/virtualnetwork/ConfigHelperTest.java @@ -57,6 +57,7 @@ import com.cloud.agent.resource.virtualnetwork.model.LoadBalancerRule; import com.cloud.agent.resource.virtualnetwork.model.LoadBalancerRules; import com.cloud.network.lb.LoadBalancingRule.LbDestination; +import com.cloud.network.Networks.TrafficType; public class ConfigHelperTest { @@ -272,9 +273,15 @@ protected DeleteIpAliasCommand generateDeleteIpAliasCommand() { protected IpAssocVpcCommand generateIpAssocVpcCommand() { final List ips = new ArrayList(); - ips.add(new IpAddressTO(1, "64.1.1.10", true, true, true, "vlan://64", "64.1.1.1", "255.255.255.0", "01:23:45:67:89:AB", 1000, false)); - ips.add(new IpAddressTO(2, "64.1.1.11", false, false, true, "vlan://64", "64.1.1.1", "255.255.255.0", "01:23:45:67:89:AB", 1000, false)); - ips.add(new IpAddressTO(3, "65.1.1.11", true, false, false, "vlan://65", "65.1.1.1", "255.255.255.0", "11:23:45:67:89:AB", 1000, false)); + IpAddressTO ip1 = new IpAddressTO(1, "64.1.1.10", true, true, true, "vlan://64", "64.1.1.1", "255.255.255.0", "01:23:45:67:89:AB", 1000, false); + IpAddressTO ip2 = new IpAddressTO(2, "64.1.1.11", false, false, true, "vlan://64", "64.1.1.1", "255.255.255.0", "01:23:45:67:89:AB", 1000, false); + IpAddressTO ip3 = new IpAddressTO(3, "65.1.1.11", true, false, false, "vlan://65", "65.1.1.1", "255.255.255.0", "11:23:45:67:89:AB", 1000, false); + ip1.setTrafficType(TrafficType.Public); + ip2.setTrafficType(TrafficType.Public); + ip3.setTrafficType(TrafficType.Public); + ips.add(ip1); + ips.add(ip2); + ips.add(ip3); final IpAddressTO[] ipArray = ips.toArray(new IpAddressTO[ips.size()]); final IpAssocVpcCommand cmd = new IpAssocVpcCommand(ipArray); @@ -283,4 +290,4 @@ protected IpAssocVpcCommand generateIpAssocVpcCommand() { return cmd; } -} \ No newline at end of file +} diff --git a/plugins/hypervisors/ovm3/src/test/java/com/cloud/hypervisor/ovm3/resources/Ovm3VirtualRoutingResourceTest.java b/plugins/hypervisors/ovm3/src/test/java/com/cloud/hypervisor/ovm3/resources/Ovm3VirtualRoutingResourceTest.java index d2a786a69182..5bcec76275a5 100644 --- a/plugins/hypervisors/ovm3/src/test/java/com/cloud/hypervisor/ovm3/resources/Ovm3VirtualRoutingResourceTest.java +++ b/plugins/hypervisors/ovm3/src/test/java/com/cloud/hypervisor/ovm3/resources/Ovm3VirtualRoutingResourceTest.java @@ -38,6 +38,7 @@ import com.cloud.hypervisor.ovm3.resources.helpers.Ovm3Configuration; import com.cloud.hypervisor.ovm3.resources.helpers.Ovm3ConfigurationTest; import com.cloud.hypervisor.ovm3.support.Ovm3SupportTest; +import com.cloud.network.Networks.TrafficType; import com.cloud.utils.ExecutionResult; public class Ovm3VirtualRoutingResourceTest { @@ -206,6 +207,7 @@ private IpAddressTO[] getIp(String mac) { List ips = new ArrayList(); IpAddressTO ip = new IpAddressTO(1, routerip, true, true, true, "vlan://" + br[1], "64.1.1.1", "255.255.255.0", mac, 1000, false); + ip.setTrafficType(TrafficType.Public); ips.add(ip); IpAddressTO[] ipArray = ips.toArray(new IpAddressTO[ips.size()]); return ipArray; diff --git a/systemvm/patches/debian/config/opt/cloud/bin/configure.py b/systemvm/patches/debian/config/opt/cloud/bin/configure.py index 3c01574bb2bd..dd62f1aa6058 100755 --- a/systemvm/patches/debian/config/opt/cloud/bin/configure.py +++ b/systemvm/patches/debian/config/opt/cloud/bin/configure.py @@ -883,6 +883,12 @@ def processStaticNatRule(self, rule): device = self.getDeviceByIp(rule["public_ip"]) if device is None: raise Exception("Ip address %s has no device in the ips databag" % rule["public_ip"]) + self.fw.append(["mangle", "", + "-A PREROUTING -s %s/32 -m state --state NEW -j MARK --set-xmark 0x%s/0xffffffff" % \ + (rule["internal_ip"], device[len("eth"):])]) + self.fw.append(["mangle", "", + "-A PREROUTING -s %s/32 -m state --state NEW -j CONNMARK --save-mark --nfmask 0xffffffff --ctmask 0xffffffff" % \ + rule["internal_ip"]]) self.fw.append(["nat", "front", "-A PREROUTING -d %s/32 -j DNAT --to-destination %s" % (rule["public_ip"], rule["internal_ip"])]) self.fw.append(["nat", "front", diff --git a/systemvm/patches/debian/config/opt/cloud/bin/cs/CsAddress.py b/systemvm/patches/debian/config/opt/cloud/bin/cs/CsAddress.py index 075fddb37602..c280555fd33c 100755 --- a/systemvm/patches/debian/config/opt/cloud/bin/cs/CsAddress.py +++ b/systemvm/patches/debian/config/opt/cloud/bin/cs/CsAddress.py @@ -114,7 +114,7 @@ def process(self): else: logging.info( "Address %s on device %s not configured", ip.ip(), dev) - + if CsDevice(dev, self.config).waitfordevice(): ip.configure(address) @@ -191,7 +191,7 @@ def is_public(self): if "nw_type" in self.address and self.address['nw_type'] in ['public']: return True return False - + def is_added(self): return self.get_attr("add") @@ -277,9 +277,10 @@ def configure(self, address): except Exception as e: logging.info("Exception occurred ==> %s" % e) + self.post_configure(address) else: + # delete method performs post_configure, so no need to call post_configure here self.delete(self.ip()) - self.post_configure(address) def post_configure(self, address): """ The steps that must be done after a device is configured """ @@ -291,7 +292,10 @@ def post_configure(self, address): interfaces = [CsInterface(address, self.config)] CsHelper.reconfigure_interfaces(self.cl, interfaces) - self.set_mark() + if not self.config.is_vpc() and (self.get_type() in ['public']): + self.set_mark() + if self.config.is_vpc() and (self.get_type() in ['public']): + self.set_mark() if 'gateway' in self.address: self.arpPing() @@ -351,7 +355,7 @@ def setup_router_control(self): self.fw.append(["filter", "", "-P INPUT DROP"]) self.fw.append(["filter", "", "-P FORWARD DROP"]) - + def fw_router(self): if self.config.is_vpc(): return @@ -386,6 +390,10 @@ def fw_router(self): "-j CONNMARK --set-xmark %s/0xffffffff" % self.dnum]) self.fw.append( ["mangle", "", "-A FIREWALL_%s -j DROP" % self.address['public_ip']]) + self.fw.append(["filter", "", + "-A FORWARD -i %s -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT" % self.dev]) + self.fw.append(["filter", "", + "-A FORWARD -i eth0 -o %s -j FW_OUTBOUND" % self.dev]) self.fw.append(["filter", "", "-A INPUT -d 224.0.0.18/32 -j ACCEPT"]) self.fw.append(["filter", "", "-A INPUT -d 225.0.0.50/32 -j ACCEPT"]) @@ -410,15 +418,8 @@ def fw_router(self): ["filter", "", "-A FORWARD -i %s -o eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT" % self.dev]) self.fw.append( ["filter", "", "-A FORWARD -i %s -o %s -m state --state NEW -j ACCEPT" % (self.dev, self.dev)]) - self.fw.append( - ["filter", "", "-A FORWARD -i eth2 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT"]) self.fw.append( ["filter", "", "-A FORWARD -i eth0 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT"]) - self.fw.append( - ["filter", "", "-A FORWARD -i eth0 -o eth2 -j FW_OUTBOUND"]) - self.fw.append(["mangle", "", - "-A PREROUTING -i %s -m state --state NEW " % self.dev + - "-j CONNMARK --set-xmark %s/0xffffffff" % self.dnum]) self.fw.append(['', 'front', '-A FORWARD -j NETWORK_STATS']) self.fw.append(['', 'front', '-A INPUT -j NETWORK_STATS']) @@ -427,17 +428,17 @@ def fw_router(self): self.fw.append(['', '', '-A NETWORK_STATS -i eth2 -o eth0']) self.fw.append(['', '', '-A NETWORK_STATS -o eth2 ! -i eth0 -p tcp']) self.fw.append(['', '', '-A NETWORK_STATS -i eth2 ! -o eth0 -p tcp']) - + def fw_vpcrouter(self): if not self.config.is_vpc(): return - self.fw.append(["mangle", "front", "-A PREROUTING " + - "-m state --state RELATED,ESTABLISHED " + - "-j CONNMARK --restore-mark --nfmask 0xffffffff --ctmask 0xffffffff"]) - + self.fw.append(["filter", "", "-A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT"]) if self.get_type() in ["guest"]: + self.fw.append(["mangle", "front", "-A PREROUTING " + + " -i %s -m state --state RELATED,ESTABLISHED " % self.dev + + "-j CONNMARK --restore-mark --nfmask 0xffffffff --ctmask 0xffffffff"]) guestNetworkCidr = self.address['network'] self.fw.append(["filter", "", "-A FORWARD -d %s -o %s -j ACL_INBOUND_%s" % (guestNetworkCidr, self.dev, self.dev)]) @@ -515,11 +516,36 @@ def fw_vpcrouter(self): def post_config_change(self, method): route = CsRoute() + tableName = "Table_" + self.dev + if method == "add": - route.add_table(self.dev) - route.add_route(self.dev, str(self.address["network"])) + if not self.config.is_vpc(): + # treat the first IP on a interface as special case to set up the routing rules + if self.get_type() in ["public"] and (len(self.iplist) == 1): + CsHelper.execute("sudo ip route add throw " + self.config.address().dbag['eth0'][0]['network'] + " table " + tableName + " proto static") + CsHelper.execute("sudo ip route add throw " + self.config.address().dbag['eth1'][0]['network'] + " table " + tableName + " proto static") + + # add 'defaul via gateway' rule in the device specific routing table + if "gateway" in self.address and self.address["gateway"] != "None": + route.add_route(self.dev, self.address["gateway"]) + + if self.get_type() in ["public"]: + CsRule(self.dev).addRule("from " + str(self.address["network"])) + + if self.config.is_vpc(): + if self.get_type() in ["public"] and "gateway" in self.address and self.address["gateway"] != "None": + route.add_route(self.dev, self.address["gateway"]) + route.add_network_route(self.dev, str(self.address["network"])) + + CsHelper.execute("sudo ip route flush cache") + elif method == "delete": - logging.warn("delete route not implemented") + # treat the last IP to be dis-associated with interface as special case to clean up the routing rules + if self.get_type() in ["public"] and (not self.config.is_vpc()) and (len(self.iplist) == 0): + CsHelper.execute("sudo ip rule delete table " + tableName) + CsHelper.execute("sudo ip route flush table " + tableName) + CsHelper.execute("sudo ip route flush cache") + CsRule(self.dev).delMark() self.fw_router() self.fw_vpcrouter() @@ -558,15 +584,8 @@ def list(self): for i in CsHelper.execute(cmd): vals = i.lstrip().split() if (vals[0] == 'inet'): - cidr = vals[1] - for ip, device in self.iplist.iteritems(): - logging.info( - "Iterating over the existing IPs. CIDR to be configured ==> %s, existing IP ==> %s on device ==> %s", - cidr, ip, device) - - if cidr[0] != ip[0] and device != self.dev: - self.iplist[cidr] = self.dev + self.iplist[cidr] = self.dev def configured(self): if self.address['cidr'] in self.iplist.keys(): diff --git a/systemvm/patches/debian/config/opt/cloud/bin/cs/CsRoute.py b/systemvm/patches/debian/config/opt/cloud/bin/cs/CsRoute.py index 1178fb04e1f5..927c2ae0d748 100755 --- a/systemvm/patches/debian/config/opt/cloud/bin/cs/CsRoute.py +++ b/systemvm/patches/debian/config/opt/cloud/bin/cs/CsRoute.py @@ -36,13 +36,28 @@ def add_table(self, devicename): filename = "/etc/iproute2/rt_tables" logging.info( "Adding route table: " + str + " to " + filename + " if not present ") - CsHelper.addifmissing(filename, str) + if not CsHelper.definedinfile(filename, str): + CsHelper.execute("sudo echo " + str + " >> /etc/iproute2/rt_tables") + # remove "from all table tablename" if exists, else it will interfer with + # routing of unintended traffic + if self.findRule("from all lookup " + tablename): + CsHelper.execute("sudo ip rule delete from all table " + tablename) def flush_table(self, tablename): CsHelper.execute("ip route flush table %s" % (tablename)) CsHelper.execute("ip route flush cache") def add_route(self, dev, address): + """ Wrapper method that adds table name and device to route statement """ + # ip route add dev eth1 table Table_eth1 10.0.2.0/24 + table = self.get_tablename(dev) + logging.info("Adding route: dev " + dev + " table: " + + table + " network: " + address + " if not present") + cmd = "dev %s table %s %s" % (dev, table, address) + cmd = "default via %s table %s proto static" % (address, table) + self.set_route(cmd) + + def add_network_route(self, dev, address): """ Wrapper method that adds table name and device to route statement """ # ip route add dev eth1 table Table_eth1 10.0.2.0/24 table = self.get_tablename(dev) @@ -73,7 +88,7 @@ def add_defaultroute(self, gateway): """ if not gateway: raise Exception("Gateway cannot be None.") - + if self.defaultroute_exists(): return False else: @@ -95,3 +110,9 @@ def defaultroute_exists(self): else: logging.warn("No default route found!") return False + + def findRule(self, rule): + for i in CsHelper.execute("ip rule show"): + if rule in i.strip(): + return True + return False \ No newline at end of file diff --git a/systemvm/patches/debian/config/opt/cloud/bin/cs/CsRule.py b/systemvm/patches/debian/config/opt/cloud/bin/cs/CsRule.py index ed164b3bdc66..9c48768b35ac 100755 --- a/systemvm/patches/debian/config/opt/cloud/bin/cs/CsRule.py +++ b/systemvm/patches/debian/config/opt/cloud/bin/cs/CsRule.py @@ -30,12 +30,30 @@ def __init__(self, dev): self.tableNo = int(dev[3:]) self.table = "Table_%s" % (dev) + def addRule(self, rule): + if not self.findRule(rule + " lookup " + self.table): + cmd = "ip rule add " + rule + " table " + self.table + CsHelper.execute(cmd) + logging.info("Added rule %s for %s" % (cmd, self.table)) + + def findRule(self, rule): + for i in CsHelper.execute("ip rule show"): + if rule in i.strip(): + return True + return False + def addMark(self): if not self.findMark(): cmd = "ip rule add fwmark %s table %s" % (self.tableNo, self.table) CsHelper.execute(cmd) logging.info("Added fwmark rule for %s" % (self.table)) + def delMark(self): + if self.findMark(): + cmd = "ip rule delete fwmark %s table %s" % (self.tableNo, self.table) + CsHelper.execute(cmd) + logging.info("Deleting fwmark rule for %s" % (self.table)) + def findMark(self): srch = "from all fwmark %s lookup %s" % (hex(self.tableNo), self.table) for i in CsHelper.execute("ip rule show"): diff --git a/systemvm/patches/debian/config/opt/cloud/bin/cs_ip.py b/systemvm/patches/debian/config/opt/cloud/bin/cs_ip.py index efcf311296fa..c34fc01d9e3f 100755 --- a/systemvm/patches/debian/config/opt/cloud/bin/cs_ip.py +++ b/systemvm/patches/debian/config/opt/cloud/bin/cs_ip.py @@ -21,15 +21,20 @@ def merge(dbag, ip): added = False + nic_dev_id = None for dev in dbag: if dev == "id": continue for address in dbag[dev]: if address['public_ip'] == ip['public_ip']: + if 'nic_dev_id' in address: + nic_dev_id = address['nic_dev_id'] dbag[dev].remove(address) ipo = IPNetwork(ip['public_ip'] + '/' + ip['netmask']) - ip['device'] = 'eth' + str(ip['nic_dev_id']) + if 'nic_dev_id' in ip: + nic_dev_id = ip['nic_dev_id'] + ip['device'] = 'eth' + str(nic_dev_id) ip['broadcast'] = str(ipo.broadcast) ip['cidr'] = str(ipo.ip) + '/' + str(ipo.prefixlen) ip['size'] = str(ipo.prefixlen) @@ -37,8 +42,8 @@ def merge(dbag, ip): if 'nw_type' not in ip.keys(): ip['nw_type'] = 'public' if ip['nw_type'] == 'control': - dbag['eth' + str(ip['nic_dev_id'])] = [ip] + dbag['eth' + str(nic_dev_id)] = [ip] else: - dbag.setdefault('eth' + str(ip['nic_dev_id']), []).append(ip) + dbag.setdefault('eth' + str(nic_dev_id), []).append(ip) return dbag diff --git a/test/integration/component/test_multiple_public_interfaces.py b/test/integration/component/test_multiple_public_interfaces.py new file mode 100644 index 000000000000..50ec552b7403 --- /dev/null +++ b/test/integration/component/test_multiple_public_interfaces.py @@ -0,0 +1,1312 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +""" BVT tests for network services on public IP's from different public IP + range than that of associated source NAT IP of the network. Each IP associated + with network from a different public IP range results in a new public + interface on VR (eth3, eth4 etc) and iptable +""" +# Import Local Modules +from marvin.codes import (FAILED, STATIC_NAT_RULE, LB_RULE, + NAT_RULE, PASS) +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.cloudstackException import CloudstackAPIException +from marvin.cloudstackAPI import rebootRouter +from marvin.sshClient import SshClient +from marvin.lib.utils import cleanup_resources, get_process_status +from marvin.lib.base import (Account, + VirtualMachine, + ServiceOffering, + NATRule, + PublicIPAddress, + StaticNATRule, + FireWallRule, + Network, + NetworkOffering, + LoadBalancerRule, + PublicIpRange, + Router, + VpcOffering, + VPC, + NetworkACLList, + NetworkACL) +from marvin.lib.common import (get_domain, + get_zone, + get_template, + list_hosts, + list_publicIP, + list_nat_rules, + list_routers, + list_virtual_machines, + list_lb_rules, + list_configurations, + verifyGuestTrafficPortGroups) +from nose.plugins.attrib import attr +from ddt import ddt, data +# Import System modules +import socket +import time +import logging + +_multiprocess_shared_ = True + +logger = logging.getLogger('TestNetworkOps') +stream_handler = logging.StreamHandler() +logger.setLevel(logging.DEBUG) +logger.addHandler(stream_handler) + +class TestPortForwarding(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + + testClient = super(TestPortForwarding, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + cls.hypervisor = testClient.getHypervisorInfo() + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls.services["zoneid"] = cls.zone.id + template = get_template( + cls.apiclient, + cls.zone.id, + cls.services["ostype"] + ) + if template == FAILED: + assert False, "get_template() failed to return template with description %s" % cls.services[ + "ostype"] + + # Create an account, network, VM and IP addresses + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + admin=True, + domainid=cls.domain.id + ) + cls.services["publiciprange"]["zoneid"] = cls.zone.id + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offerings"]["tiny"] + ) + cls.virtual_machine = VirtualMachine.create( + cls.apiclient, + cls.services["virtual_machine"], + templateid=template.id, + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.service_offering.id + ) + cls._cleanup = [ + cls.virtual_machine, + cls.account, + cls.service_offering + ] + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.cleanup = [] + return + + @classmethod + def tearDownClass(cls): + try: + cls.apiclient = super( + TestPortForwarding, + cls).getClsTestClient().getApiClient() + cleanup_resources(cls.apiclient, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + + def tearDown(self): + cleanup_resources(self.apiclient, self.cleanup) + return + + @attr(tags=["advanced", "smoke"], required_hardware="true") + def test_port_forwarding_on_ip_from_non_src_nat_ip_range(self): + """Test for port forwarding on a IP which is in pubic IP range different + from public IP range that has source NAT IP associated with network + """ + + # Validate the following: + # 1. Create a new public IP range and dedicate to a account + # 2. Acquire a IP from new public range + # 3. create a port forwarding on acquired IP from new range + # 4. Create a firewall rule to open up the port + # 5. Test SSH works to the VM + + self.public_ip_range = PublicIpRange.create( + self.apiclient, + self.services["publiciprange"] + ) + + logger.debug("Dedicating Public IP range to the account"); + dedicate_public_ip_range_response = PublicIpRange.dedicate( + self.apiclient, + self.public_ip_range.vlan.id, + account=self.account.name, + domainid=self.account.domainid + ) + ip_address = PublicIPAddress.create( + self.apiclient, + self.account.name, + self.zone.id, + self.account.domainid, + self.services["virtual_machine"] + ) + self.cleanup.append(ip_address) + self.cleanup.append(self.public_ip_range) + # Check if VM is in Running state before creating NAT and firewall rules + vm_response = VirtualMachine.list( + self.apiclient, + id=self.virtual_machine.id + ) + + self.assertEqual( + isinstance(vm_response, list), + True, + "Check list VM returns a valid list" + ) + + self.assertNotEqual( + len(vm_response), + 0, + "Check Port Forwarding Rule is created" + ) + self.assertEqual( + vm_response[0].state, + 'Running', + "VM state should be Running before creating a NAT rule." + ) + + # Open up firewall port for SSH + FireWallRule.create( + self.apiclient, + ipaddressid=ip_address.ipaddress.id, + protocol=self.services["natrule"]["protocol"], + cidrlist=['0.0.0.0/0'], + startport=self.services["natrule"]["publicport"], + endport=self.services["natrule"]["publicport"] + ) + + # Create PF rule + nat_rule = NATRule.create( + self.apiclient, + self.virtual_machine, + self.services["natrule"], + ip_address.ipaddress.id + ) + + try: + logger.debug("SSHing into VM with IP address %s with NAT IP %s" % + ( + self.virtual_machine.ipaddress, + ip_address.ipaddress.ipaddress + )) + self.virtual_machine.get_ssh_client(ip_address.ipaddress.ipaddress) + except Exception as e: + self.fail( + "SSH Access failed for %s: %s" % + (self.virtual_machine.ipaddress, e) + ) + + nat_rule.delete(self.apiclient) + +class TestStaticNat(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + + testClient = super(TestStaticNat, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + cls.hypervisor = testClient.getHypervisorInfo() + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls.services["zoneid"] = cls.zone.id + template = get_template( + cls.apiclient, + cls.zone.id, + cls.services["ostype"] + ) + if template == FAILED: + assert False, "get_template() failed to return template with description %s" % cls.services[ + "ostype"] + + # Create an account, network, VM and IP addresses + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + admin=True, + domainid=cls.domain.id + ) + cls.services["publiciprange"]["zoneid"] = cls.zone.id + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offerings"]["tiny"] + ) + cls.virtual_machine = VirtualMachine.create( + cls.apiclient, + cls.services["virtual_machine"], + templateid=template.id, + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.service_offering.id + ) + cls.defaultNetworkId = cls.virtual_machine.nic[0].networkid + cls._cleanup = [ + cls.virtual_machine, + cls.account, + cls.service_offering + ] + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.cleanup = [] + return + + @classmethod + def tearDownClass(cls): + try: + cls.apiclient = super( + TestStaticNat, + cls).getClsTestClient().getApiClient() + cleanup_resources(cls.apiclient, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + + def tearDown(self): + cleanup_resources(self.apiclient, self.cleanup) + return + + @attr(tags=["advanced", "smoke"], required_hardware="true") + def test_static_nat_on_ip_from_non_src_nat_ip_range(self): + """Test for static nat on a IP which is in pubic IP range different + from public IP range that has source NAT IP associated with network + """ + + # Validate the following: + # 1. Create a new public IP range and dedicate to a account + # 2. Acquire a IP from new public range + # 3. Enable static NAT on acquired IP from new range + # 4. Create a firewall rule to open up the port + # 5. Test SSH works to the VM + + self.public_ip_range = PublicIpRange.create( + self.apiclient, + self.services["publiciprange"] + ) + + logger.debug("Dedicating Public IP range to the account"); + dedicate_public_ip_range_response = PublicIpRange.dedicate( + self.apiclient, + self.public_ip_range.vlan.id, + account=self.account.name, + domainid=self.account.domainid + ) + ip_address = PublicIPAddress.create( + self.apiclient, + self.account.name, + self.zone.id, + self.account.domainid, + self.services["virtual_machine"] + ) + self.cleanup.append(ip_address) + self.cleanup.append(self.public_ip_range) + # Check if VM is in Running state before creating NAT and firewall rules + vm_response = VirtualMachine.list( + self.apiclient, + id=self.virtual_machine.id + ) + + self.assertEqual( + isinstance(vm_response, list), + True, + "Check list VM returns a valid list" + ) + + self.assertNotEqual( + len(vm_response), + 0, + "Check Port Forwarding Rule is created" + ) + self.assertEqual( + vm_response[0].state, + 'Running', + "VM state should be Running before creating a NAT rule." + ) + + # Open up firewall port for SSH + FireWallRule.create( + self.apiclient, + ipaddressid=ip_address.ipaddress.id, + protocol=self.services["natrule"]["protocol"], + cidrlist=['0.0.0.0/0'], + startport=self.services["natrule"]["publicport"], + endport=self.services["natrule"]["publicport"] + ) + + # Create Static NAT rule + StaticNATRule.enable( + self.apiclient, + ip_address.ipaddress.id, + self.virtual_machine.id, + self.defaultNetworkId + ) + + try: + logger.debug("SSHing into VM with IP address %s with NAT IP %s" % + ( + self.virtual_machine.ipaddress, + ip_address.ipaddress.ipaddress + )) + self.virtual_machine.get_ssh_client(ip_address.ipaddress.ipaddress) + except Exception as e: + self.fail( + "SSH Access failed for %s: %s" % + (self.virtual_machine.ipaddress, e) + ) + + StaticNATRule.disable( + self.apiclient, + ip_address.ipaddress.id, + self.virtual_machine.id + ) + +class TestRouting(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + + testClient = super(TestRouting, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + cls.hypervisor = testClient.getHypervisorInfo() + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls.services["zoneid"] = cls.zone.id + template = get_template( + cls.apiclient, + cls.zone.id, + cls.services["ostype"] + ) + if template == FAILED: + assert False, "get_template() failed to return template with description %s" % cls.services[ + "ostype"] + + # Create an account, network, VM and IP addresses + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + admin=True, + domainid=cls.domain.id + ) + cls.services["publiciprange"]["zoneid"] = cls.zone.id + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offerings"]["tiny"] + ) + cls.hostConfig = cls.config.__dict__["zones"][0].__dict__["pods"][0].__dict__["clusters"][0].__dict__["hosts"][0].__dict__ + cls.virtual_machine = VirtualMachine.create( + cls.apiclient, + cls.services["virtual_machine"], + templateid=template.id, + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.service_offering.id + ) + cls._cleanup = [ + cls.virtual_machine, + cls.account, + cls.service_offering + ] + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.cleanup = [] + return + + @classmethod + def tearDownClass(cls): + try: + cls.apiclient = super( + TestRouting, + cls).getClsTestClient().getApiClient() + cleanup_resources(cls.apiclient, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + + def tearDown(self): + cleanup_resources(self.apiclient, self.cleanup) + return + + @attr(tags=["advanced", "smoke"], required_hardware="true") + def test_routing_tables(self): + """Test routing table in case we have IP associated with a network which is in + different pubic IP range from that of public IP range that has source NAT IP. + When IP is associated we should see a new route table created. + When IP is associated we should see a that route table is deleted. + """ + + # Validate the following: + # 1. Create a new public IP range and dedicate to a account + # 2. Acquire a IP from new public range + # 3. Create a firewall rule to open up the port, so that IP is associated with network + # 5. Login to VR and verify routing tables, there should be Table_eth3 + # 6. Delete firewall rule, since its last IP, routing table Table_eth3 should be deleted + + self.public_ip_range = PublicIpRange.create( + self.apiclient, + self.services["publiciprange"] + ) + + logger.debug("Dedicating Public IP range to the account"); + dedicate_public_ip_range_response = PublicIpRange.dedicate( + self.apiclient, + self.public_ip_range.vlan.id, + account=self.account.name, + domainid=self.account.domainid + ) + ip_address = PublicIPAddress.create( + self.apiclient, + self.account.name, + self.zone.id, + self.account.domainid, + self.services["virtual_machine"] + ) + self.cleanup.append(ip_address) + self.cleanup.append(self.public_ip_range) + # Check if VM is in Running state before creating NAT and firewall rules + vm_response = VirtualMachine.list( + self.apiclient, + id=self.virtual_machine.id + ) + + self.assertEqual( + isinstance(vm_response, list), + True, + "Check list VM returns a valid list" + ) + + self.assertNotEqual( + len(vm_response), + 0, + "Check Port Forwarding Rule is created" + ) + self.assertEqual( + vm_response[0].state, + 'Running', + "VM state should be Running before creating Firewall rule." + ) + + # Open up firewall port for SSH, this will associate IP with VR + firewall_rule = FireWallRule.create( + self.apiclient, + ipaddressid=ip_address.ipaddress.id, + protocol=self.services["natrule"]["protocol"], + cidrlist=['0.0.0.0/0'], + startport=self.services["natrule"]["publicport"], + endport=self.services["natrule"]["publicport"] + ) + + # Get the router details associated with account + routers = list_routers( + self.apiclient, + account=self.account.name, + domainid=self.account.domainid, + ) + router = routers[0] + + if (self.hypervisor.lower() == 'vmware' + or self.hypervisor.lower() == 'hyperv'): + result = get_process_status( + self.apiclient.connection.mgtSvr, + 22, + self.apiclient.connection.user, + self.apiclient.connection.passwd, + router.linklocalip, + 'ip route list table Table_eth3', + hypervisor=self.hypervisor + ) + else: + hosts = list_hosts( + self.apiclient, + id=router.hostid, + ) + self.assertEqual( + isinstance(hosts, list), + True, + "Check for list hosts response return valid data" + ) + host = hosts[0] + host.user = self.hostConfig['username'] + host.passwd = self.hostConfig['password'] + try: + result = get_process_status( + host.ipaddress, + 22, + host.user, + host.passwd, + router.linklocalip, + 'ip route list table Table_eth3' + ) + except KeyError: + self.skipTest( + "Provide a marvin config file with host\ + credentials to run %s" % + self._testMethodName) + + logger.debug("ip route list table Table_eth3: %s" % result) + public_range_gateway = self.services["publiciprange"]["gateway"] + default_route_rule = "default via " + public_range_gateway + " dev eth3 proto static" + logger.debug("default route result: %s" % str(result[0])) + self.assertEqual( + default_route_rule, + str(result[0]), + "Check default route table entry for public ip range" + ) + + res = str(result) + self.assertEqual( + res.count("throw") == 2, + True, + "Check routing rules to throw rest of the traffic. Count shoule be Atleast 2 for the control and guest traffic " + ) + + firewall_rule.delete(self.apiclient) + + if (self.hypervisor.lower() == 'vmware' + or self.hypervisor.lower() == 'hyperv'): + result = get_process_status( + self.apiclient.connection.mgtSvr, + 22, + self.apiclient.connection.user, + self.apiclient.connection.passwd, + router.linklocalip, + 'ip route list table Table_eth3', + hypervisor=self.hypervisor + ) + else: + hosts = list_hosts( + self.apiclient, + id=router.hostid, + ) + self.assertEqual( + isinstance(hosts, list), + True, + "Check for list hosts response return valid data" + ) + host = hosts[0] + host.user = self.hostConfig['username'] + host.passwd = self.hostConfig['password'] + try: + result = get_process_status( + host.ipaddress, + 22, + host.user, + host.passwd, + router.linklocalip, + 'ip route list table Table_eth3' + ) + except KeyError: + self.skipTest( + "Provide a marvin config file with host\ + credentials to run %s" % + self._testMethodName) + + logger.debug("ip route list table Table_eth3: %s" % result) + res = str(result) + self.assertEqual( + res.count("default via"), + 0, + "Check to ensure there should not be any default rule" + ) + + self.assertEqual( + res.count("throw"), + 0, + "Check to ensure there should not be any throw rule" + ) + +class TestIptables(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + + testClient = super(TestIptables, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + cls.hypervisor = testClient.getHypervisorInfo() + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls.services["zoneid"] = cls.zone.id + template = get_template( + cls.apiclient, + cls.zone.id, + cls.services["ostype"] + ) + if template == FAILED: + assert False, "get_template() failed to return template with description %s" % cls.services[ + "ostype"] + + # Create an account, network, VM and IP addresses + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + admin=True, + domainid=cls.domain.id + ) + cls.services["publiciprange"]["zoneid"] = cls.zone.id + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offerings"]["tiny"] + ) + cls.hostConfig = cls.config.__dict__["zones"][0].__dict__["pods"][0].__dict__["clusters"][0].__dict__["hosts"][0].__dict__ + cls.virtual_machine = VirtualMachine.create( + cls.apiclient, + cls.services["virtual_machine"], + templateid=template.id, + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.service_offering.id + ) + cls._cleanup = [ + cls.virtual_machine, + cls.account, + cls.service_offering + ] + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.cleanup = [] + return + + @classmethod + def tearDownClass(cls): + try: + cls.apiclient = super( + TestIptables, + cls).getClsTestClient().getApiClient() + cleanup_resources(cls.apiclient, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + + def tearDown(self): + cleanup_resources(self.apiclient, self.cleanup) + return + + @attr(tags=["advanced", "smoke"], required_hardware="true") + def test_iptable_rules(self): + """Test iptable rules in case we have IP associated with a network which is in + different pubic IP range from that of public IP range that has source NAT IP. + When IP is associated we should see a rule '-i eth3 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT' in FORWARD table. + When IP is dis-associated we should see a rule in the FORWARD table is deleted. + """ + + # Validate the following: + # 1. Create a new public IP range and dedicate to a account + # 2. Acquire a IP from new public range + # 3. Create a firewall rule to open up the port, so that IP is associated with network + # 5. Login to VR and verify routing tables, there should be Table_eth3 + # 6. Delete firewall rule, since its last IP, routing table Table_eth3 should be deleted + + self.public_ip_range = PublicIpRange.create( + self.apiclient, + self.services["publiciprange"] + ) + + logger.debug("Dedicating Public IP range to the account"); + dedicate_public_ip_range_response = PublicIpRange.dedicate( + self.apiclient, + self.public_ip_range.vlan.id, + account=self.account.name, + domainid=self.account.domainid + ) + ip_address = PublicIPAddress.create( + self.apiclient, + self.account.name, + self.zone.id, + self.account.domainid, + self.services["virtual_machine"] + ) + self.cleanup.append(ip_address) + self.cleanup.append(self.public_ip_range) + # Check if VM is in Running state before creating NAT and firewall rules + vm_response = VirtualMachine.list( + self.apiclient, + id=self.virtual_machine.id + ) + + self.assertEqual( + isinstance(vm_response, list), + True, + "Check list VM returns a valid list" + ) + + self.assertNotEqual( + len(vm_response), + 0, + "Check Port Forwarding Rule is created" + ) + self.assertEqual( + vm_response[0].state, + 'Running', + "VM state should be Running before creating a NAT rule." + ) + + # Open up firewall port for SSH + firewall_rule = FireWallRule.create( + self.apiclient, + ipaddressid=ip_address.ipaddress.id, + protocol=self.services["natrule"]["protocol"], + cidrlist=['0.0.0.0/0'], + startport=self.services["natrule"]["publicport"], + endport=self.services["natrule"]["publicport"] + ) + + # Get the router details associated with account + routers = list_routers( + self.apiclient, + account=self.account.name, + domainid=self.account.domainid, + ) + router = routers[0] + + if (self.hypervisor.lower() == 'vmware' + or self.hypervisor.lower() == 'hyperv'): + result = get_process_status( + self.apiclient.connection.mgtSvr, + 22, + self.apiclient.connection.user, + self.apiclient.connection.passwd, + router.linklocalip, + 'iptables -t filter -L FORWARD -v', + hypervisor=self.hypervisor + ) + else: + hosts = list_hosts( + self.apiclient, + id=router.hostid, + ) + self.assertEqual( + isinstance(hosts, list), + True, + "Check for list hosts response return valid data" + ) + host = hosts[0] + host.user = self.hostConfig['username'] + host.passwd = self.hostConfig['password'] + try: + result = get_process_status( + host.ipaddress, + 22, + host.user, + host.passwd, + router.linklocalip, + 'iptables -t filter -L FORWARD -v' + ) + except KeyError: + self.skipTest( + "Provide a marvin config file with host\ + credentials to run %s" % + self._testMethodName) + + logger.debug("iptables -t filter -L FORWARD -v: %s" % result) + res = str(result) + self.assertEqual( + res.count("eth3 eth0 anywhere anywhere state RELATED,ESTABLISHED"), + 1, + "Check to ensure there is a iptable rule to accept the RELATED,ESTABLISHED traffic" + ) + + +class TestVPCPortForwarding(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + + socket.setdefaulttimeout(60) + + testClient = super(TestVPCPortForwarding, cls).getClsTestClient() + cls.api_client = cls.testClient.getApiClient() + + cls.services = testClient.getParsedTestDataConfig() + + # Get Zone, Domain and templates + cls.domain = get_domain(cls.api_client) + cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests()) + cls.template = get_template( + cls.api_client, + cls.zone.id, + cls.services["ostype"] + ) + cls.services["vpc_offering"] = { "name": 'VPC off', + "displaytext": 'VPC off', + "supportedservices": 'Dhcp,Dns,SourceNat,PortForwarding,Vpn,Lb,UserData,StaticNat', + } + cls.services["network_offering"] = { + "name": 'VPC Network offering', + "displaytext": 'VPC Network off', + "guestiptype": 'Isolated', + "supportedservices": 'Vpn,Dhcp,Dns,SourceNat,PortForwarding,Lb,UserData,StaticNat,NetworkACL', + "traffictype": 'GUEST', + "availability": 'Optional', + "useVpc": 'on', + "serviceProviderList": { + "Vpn": 'VpcVirtualRouter', + "Dhcp": 'VpcVirtualRouter', + "Dns": 'VpcVirtualRouter', + "SourceNat": 'VpcVirtualRouter', + "PortForwarding": 'VpcVirtualRouter', + "Lb": 'VpcVirtualRouter', + "UserData": 'VpcVirtualRouter', + "StaticNat": 'VpcVirtualRouter', + "NetworkACL": 'VpcVirtualRouter' + }, + } + cls.services["network"] = { + "name": "Test Network", + "displaytext": "Test Network", + "netmask": '255.255.255.0' + } + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls.services["virtual_machine"]["template"] = cls.template.id + cls.services["publiciprange"]["zoneid"] = cls.zone.id + + cls.service_offering = ServiceOffering.create( + cls.api_client, + cls.services["service_offering"] + ) + cls._cleanup = [cls.service_offering] + return + + + @classmethod + def tearDownClass(cls): + try: + #Cleanup resources used + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.account = Account.create( + self.apiclient, + self.services["account"], + admin=True, + domainid=self.domain.id + ) + self.cleanup = [self.account] + logger.debug("Creating a VPC offering..") + self.vpc_off = VpcOffering.create( + self.apiclient, + self.services["vpc_offering"] + ) + self._cleanup.append(self.vpc_off) + logger.debug("Enabling the VPC offering created") + self.vpc_off.update(self.apiclient, state='Enabled') + + logger.debug("Creating a VPC network in the account: %s" % self.account.name) + self.services["vpc"]["cidr"] = '10.1.0.0/16' + self.vpc = VPC.create( + self.apiclient, + self.services["vpc"], + vpcofferingid=self.vpc_off.id, + zoneid=self.zone.id, + account=self.account.name, + domainid=self.account.domainid + ) + return + + def tearDown(self): + try: + #Clean up, terminate the created network offerings + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + logger.debug("Warning: Exception during cleanup : %s" % e) + return + + def check_ssh_into_vm(self, vm, public_ip, testnegative=False): + logger.debug("Checking if we can SSH into VM=%s on public_ip=%s" % (vm.name, public_ip.ipaddress.ipaddress)) + try: + vm.get_ssh_client(ipaddress=public_ip.ipaddress.ipaddress) + if not testnegative: + logger.debug("SSH into VM=%s on public_ip=%s is successfully" % (vm.name, public_ip.ipaddress.ipaddress)) + else: + self.fail("SSH into VM=%s on public_ip=%s is successfully" % (vm.name, public_ip.ipaddress.ipaddress)) + except: + if not testnegative: + self.fail("Failed to SSH into VM - %s" % (public_ip.ipaddress.ipaddress)) + else: + logger.debug("Failed to SSH into VM - %s" % (public_ip.ipaddress.ipaddress)) + + def create_natrule(self, vm, public_ip, network, services=None): + logger.debug("Creating NAT rule in network for vm with public IP") + if not services: + services = self.services["natrule"] + nat_rule = NATRule.create(self.apiclient, + vm, + services, + ipaddressid=public_ip.ipaddress.id, + openfirewall=False, + networkid=network.id, + vpcid=self.vpc.id + ) + return nat_rule + + def acquire_publicip(self, network): + logger.debug("Associating public IP for network: %s" % network.name) + public_ip = PublicIPAddress.create(self.apiclient, + accountid=self.account.name, + zoneid=self.zone.id, + domainid=self.account.domainid, + networkid=network.id, + vpcid=self.vpc.id + ) + logger.debug("Associated %s with network %s" % (public_ip.ipaddress.ipaddress, + network.id + )) + return public_ip + + def create_network(self, net_offerring, gateway='10.1.1.1',vpc=None): + try: + logger.debug('Create NetworkOffering') + net_offerring["name"] = "NET_OFF-" + str(gateway) + nw_off = NetworkOffering.create(self.apiclient, + net_offerring, + conservemode=False + ) + # Enable Network offering + nw_off.update(self.apiclient, state='Enabled') + self._cleanup.append(nw_off) + logger.debug('Created and Enabled NetworkOffering') + + self.services["network"]["name"] = "NETWORK-" + str(gateway) + logger.debug('Adding Network=%s' % self.services["network"]) + default_acl = NetworkACLList.list(self.apiclient, name="default_allow")[0] + obj_network = Network.create(self.apiclient, + self.services["network"], + accountid=self.account.name, + domainid=self.account.domainid, + networkofferingid=nw_off.id, + zoneid=self.zone.id, + gateway=gateway, + aclid=default_acl.id, + vpcid=vpc.id if vpc else self.vpc.id + ) + logger.debug("Created network with ID: %s" % obj_network.id) + return obj_network + except Exception, e: + self.fail('Unable to create a Network with offering=%s because of %s ' % (net_offerring, e)) + + def deployvm_in_network(self, network, host_id=None): + try: + logger.debug('Creating VM in network=%s' % network.name) + vm = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + networkids=[str(network.id)], + hostid=host_id + ) + logger.debug('Created VM=%s in network=%s' % (vm.id, network.name)) + + return vm + except: + self.fail('Unable to create VM in a Network=%s' % network.name) + + @attr(tags=["advanced", "intervlan"], required_hardware="true") + def test_network_services_VPC_CreatePF(self): + """ Test Create VPC PF rules on acquired public ip when VpcVirtualRouter is Running + """ + + # Validate the following + # 1. Create a VPC with cidr - 10.1.1.1/16 + # 2. Create a Network offering - NO1 with all supported services + # 3. Add network1(10.1.1.1/24) using N01 to this VPC. + # 4. Deploy vm1 in network1. + # 5. Use the Create PF rule for vm in network1. + # 6. Successfully ssh into the Guest VM using the PF rule + + network_1 = self.create_network(self.services["network_offering"]) + vm_1 = self.deployvm_in_network(network_1) + self.public_ip_range = PublicIpRange.create( + self.apiclient, + self.services["publiciprange"] + ) + self._cleanup.append(self.public_ip_range) + logger.debug("Dedicating Public IP range to the account"); + dedicate_public_ip_range_response = PublicIpRange.dedicate( + self.apiclient, + self.public_ip_range.vlan.id, + account=self.account.name, + domainid=self.account.domainid + ) + public_ip_1 = self.acquire_publicip(network_1) + self.create_natrule( vm_1, public_ip_1, network_1) + self.check_ssh_into_vm(vm_1, public_ip_1, testnegative=False) + self.public_ip_range.release(self.apiclient) + return + +class TestVPCStaticNat(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + + socket.setdefaulttimeout(60) + + testClient = super(TestVPCStaticNat, cls).getClsTestClient() + cls.api_client = cls.testClient.getApiClient() + + cls.services = testClient.getParsedTestDataConfig() + + # Get Zone, Domain and templates + cls.domain = get_domain(cls.api_client) + cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests()) + cls.template = get_template( + cls.api_client, + cls.zone.id, + cls.services["ostype"] + ) + cls.services["vpc_offering"] = { "name": 'VPC off', + "displaytext": 'VPC off', + "supportedservices": 'Dhcp,Dns,SourceNat,PortForwarding,Vpn,Lb,UserData,StaticNat', + } + cls.services["network_offering"] = { + "name": 'VPC Network offering', + "displaytext": 'VPC Network off', + "guestiptype": 'Isolated', + "supportedservices": 'Vpn,Dhcp,Dns,SourceNat,PortForwarding,Lb,UserData,StaticNat,NetworkACL', + "traffictype": 'GUEST', + "availability": 'Optional', + "useVpc": 'on', + "serviceProviderList": { + "Vpn": 'VpcVirtualRouter', + "Dhcp": 'VpcVirtualRouter', + "Dns": 'VpcVirtualRouter', + "SourceNat": 'VpcVirtualRouter', + "PortForwarding": 'VpcVirtualRouter', + "Lb": 'VpcVirtualRouter', + "UserData": 'VpcVirtualRouter', + "StaticNat": 'VpcVirtualRouter', + "NetworkACL": 'VpcVirtualRouter' + }, + } + cls.services["network"] = { + "name": "Test Network", + "displaytext": "Test Network", + "netmask": '255.255.255.0' + } + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls.services["virtual_machine"]["template"] = cls.template.id + cls.services["publiciprange"]["zoneid"] = cls.zone.id + + cls.service_offering = ServiceOffering.create( + cls.api_client, + cls.services["service_offering"] + ) + cls._cleanup = [cls.service_offering] + return + + + @classmethod + def tearDownClass(cls): + try: + #Cleanup resources used + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.account = Account.create( + self.apiclient, + self.services["account"], + admin=True, + domainid=self.domain.id + ) + self.cleanup = [self.account] + logger.debug("Creating a VPC offering..") + self.vpc_off = VpcOffering.create( + self.apiclient, + self.services["vpc_offering"] + ) + self._cleanup.append(self.vpc_off) + logger.debug("Enabling the VPC offering created") + self.vpc_off.update(self.apiclient, state='Enabled') + + logger.debug("Creating a VPC network in the account: %s" % self.account.name) + self.services["vpc"]["cidr"] = '10.1.0.0/16' + self.vpc = VPC.create( + self.apiclient, + self.services["vpc"], + vpcofferingid=self.vpc_off.id, + zoneid=self.zone.id, + account=self.account.name, + domainid=self.account.domainid + ) + return + + def tearDown(self): + try: + #Clean up, terminate the created network offerings + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + logger.debug("Warning: Exception during cleanup : %s" % e) + return + + def check_ssh_into_vm(self, vm, public_ip, testnegative=False): + logger.debug("Checking if we can SSH into VM=%s on public_ip=%s" % (vm.name, public_ip.ipaddress.ipaddress)) + try: + vm.get_ssh_client(ipaddress=public_ip.ipaddress.ipaddress) + if not testnegative: + logger.debug("SSH into VM=%s on public_ip=%s is successfully" % (vm.name, public_ip.ipaddress.ipaddress)) + else: + self.fail("SSH into VM=%s on public_ip=%s is successfully" % (vm.name, public_ip.ipaddress.ipaddress)) + except: + if not testnegative: + self.fail("Failed to SSH into VM - %s" % (public_ip.ipaddress.ipaddress)) + else: + logger.debug("Failed to SSH into VM - %s" % (public_ip.ipaddress.ipaddress)) + + + def acquire_publicip(self, network): + logger.debug("Associating public IP for network: %s" % network.name) + public_ip = PublicIPAddress.create(self.apiclient, + accountid=self.account.name, + zoneid=self.zone.id, + domainid=self.account.domainid, + networkid=network.id, + vpcid=self.vpc.id + ) + logger.debug("Associated %s with network %s" % (public_ip.ipaddress.ipaddress, + network.id + )) + return public_ip + + def create_network(self, net_offerring, gateway='10.1.1.1',vpc=None): + try: + logger.debug('Create NetworkOffering') + net_offerring["name"] = "NET_OFF-" + str(gateway) + nw_off = NetworkOffering.create(self.apiclient, + net_offerring, + conservemode=False + ) + # Enable Network offering + nw_off.update(self.apiclient, state='Enabled') + self._cleanup.append(nw_off) + logger.debug('Created and Enabled NetworkOffering') + + self.services["network"]["name"] = "NETWORK-" + str(gateway) + logger.debug('Adding Network=%s' % self.services["network"]) + default_acl = NetworkACLList.list(self.apiclient, name="default_allow")[0] + obj_network = Network.create(self.apiclient, + self.services["network"], + accountid=self.account.name, + domainid=self.account.domainid, + networkofferingid=nw_off.id, + zoneid=self.zone.id, + gateway=gateway, + aclid=default_acl.id, + vpcid=vpc.id if vpc else self.vpc.id + ) + logger.debug("Created network with ID: %s" % obj_network.id) + return obj_network + except Exception, e: + self.fail('Unable to create a Network with offering=%s because of %s ' % (net_offerring, e)) + + def deployvm_in_network(self, network, host_id=None): + try: + logger.debug('Creating VM in network=%s' % network.name) + vm = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + networkids=[str(network.id)], + hostid=host_id + ) + logger.debug('Created VM=%s in network=%s' % (vm.id, network.name)) + + return vm + except: + self.fail('Unable to create VM in a Network=%s' % network.name) + + def create_StaticNatRule_For_VM(self, vm, public_ip, network, services=None): + logger.debug("Enabling static NAT for IP: %s" %public_ip.ipaddress.ipaddress) + if not services: + services = self.services["natrule"] + try: + StaticNATRule.enable( + self.apiclient, + ipaddressid=public_ip.ipaddress.id, + virtualmachineid=vm.id, + networkid=network.id + ) + logger.debug("Static NAT enabled for IP: %s" % + public_ip.ipaddress.ipaddress) + logger.debug("Adding NetworkACL rules to make NAT rule accessible") + except Exception as e: + self.fail("Failed to enable static NAT on IP: %s - %s" % ( + public_ip.ipaddress.ipaddress, e)) + + @attr(tags=["advanced", "intervlan"], required_hardware="true") + def test_network_services_VPC_CreatePF(self): + """ Test Create VPC PF rules on acquired public ip when VpcVirtualRouter is Running + """ + + # Validate the following + # 1. Create a VPC with cidr - 10.1.1.1/16 + # 2. Create a Network offering - NO1 with all supported services + # 3. Add network1(10.1.1.1/24) using N01 to this VPC. + # 4. Deploy vm1 in network1. + # 5. Use the Create PF rule for vm in network1. + # 6. Successfully ssh into the Guest VM using the PF rule + + network_1 = self.create_network(self.services["network_offering"]) + vm_1 = self.deployvm_in_network(network_1) + self.public_ip_range = PublicIpRange.create( + self.apiclient, + self.services["publiciprange"] + ) + self._cleanup.append(self.public_ip_range) + logger.debug("Dedicating Public IP range to the account"); + dedicate_public_ip_range_response = PublicIpRange.dedicate( + self.apiclient, + self.public_ip_range.vlan.id, + account=self.account.name, + domainid=self.account.domainid + ) + public_ip_1 = self.acquire_publicip(network_1) + self.create_StaticNatRule_For_VM( vm_1, public_ip_1, network_1) + self.check_ssh_into_vm(vm_1, public_ip_1, testnegative=False) + self.public_ip_range.release(self.apiclient) + return