From 1742b10f1b2f0c04df6dff504cdd90bb6e7b2bc3 Mon Sep 17 00:00:00 2001 From: wilderrodrigues Date: Fri, 4 Sep 2015 09:27:20 +0200 Subject: [PATCH] CLOUDSTACK-8688 - Adding Marvin tests in order to cover the fixes applied - Changing refactored the utils.get_process_status() function - Adding 2 tests: test_01_single_VPC_iptables_policies and test_02_routervm_iptables_policies --- .../test_routers_iptables_default_policy.py | 649 ++++++++++++++++++ tools/marvin/marvin/lib/utils.py | 75 +- 2 files changed, 694 insertions(+), 30 deletions(-) create mode 100644 test/integration/component/test_routers_iptables_default_policy.py diff --git a/test/integration/component/test_routers_iptables_default_policy.py b/test/integration/component/test_routers_iptables_default_policy.py new file mode 100644 index 00000000000..b72e45faa61 --- /dev/null +++ b/test/integration/component/test_routers_iptables_default_policy.py @@ -0,0 +1,649 @@ +# 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. + +""" Test VPC nics after router is destroyed """ + +from nose.plugins.attrib import attr +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib.base import (stopRouter, + startRouter, + destroyRouter, + Account, + VpcOffering, + VPC, + ServiceOffering, + NATRule, + NetworkACL, + PublicIPAddress, + NetworkOffering, + Network, + VirtualMachine, + LoadBalancerRule) +from marvin.lib.common import (get_domain, + get_zone, + get_template, + list_routers, + list_hosts) +from marvin.lib.utils import (cleanup_resources, + get_process_status) +import socket +import time +import inspect +import logging + + +class Services: + """Test VPC network services - Port Forwarding Rules Test Data Class. + """ + + def __init__(self): + self.services = { + "configurableData": { + "host": { + "password": "password", + "username": "root", + "port": 22 + }, + "input": "INPUT", + "forward": "FORWARD" + }, + "account": { + "email": "test@test.com", + "firstname": "Test", + "lastname": "User", + "username": "test", + # Random characters are appended for unique + # username + "password": "password", + }, + "service_offering": { + "name": "Tiny Instance", + "displaytext": "Tiny Instance", + "cpunumber": 1, + "cpuspeed": 100, + "memory": 128, + }, + "shared_network_offering_sg": { + "name": "MySharedOffering-sg", + "displaytext": "MySharedOffering-sg", + "guestiptype": "Shared", + "supportedservices": "Dhcp,Dns,UserData,SecurityGroup", + "specifyVlan": "False", + "specifyIpRanges": "False", + "traffictype": "GUEST", + "serviceProviderList": { + "Dhcp": "VirtualRouter", + "Dns": "VirtualRouter", + "UserData": "VirtualRouter", + "SecurityGroup": "SecurityGroupProvider" + } + }, + "network_offering": { + "name": 'Test Network offering', + "displaytext": 'Test Network offering', + "guestiptype": 'Isolated', + "supportedservices": 'Dhcp,Dns,SourceNat,PortForwarding', + "traffictype": 'GUEST', + "availability": 'Optional', + "serviceProviderList": { + "Dhcp": 'VirtualRouter', + "Dns": 'VirtualRouter', + "SourceNat": 'VirtualRouter', + "PortForwarding": 'VirtualRouter', + }, + }, + "vpc_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' + }, + }, + "vpc_network_offering_no_lb": { + "name": 'VPC Network offering', + "displaytext": 'VPC Network off', + "guestiptype": 'Isolated', + "supportedservices": 'Dhcp,Dns,SourceNat,PortForwarding,UserData,StaticNat,NetworkACL', + "traffictype": 'GUEST', + "availability": 'Optional', + "useVpc": 'on', + "serviceProviderList": { + "Dhcp": 'VpcVirtualRouter', + "Dns": 'VpcVirtualRouter', + "SourceNat": 'VpcVirtualRouter', + "PortForwarding": 'VpcVirtualRouter', + "UserData": 'VpcVirtualRouter', + "StaticNat": 'VpcVirtualRouter', + "NetworkACL": 'VpcVirtualRouter' + }, + }, + "vpc_offering": { + "name": 'VPC off', + "displaytext": 'VPC off', + "supportedservices": 'Dhcp,Dns,SourceNat,PortForwarding,Vpn,Lb,UserData,StaticNat', + }, + "redundant_vpc_offering": { + "name": 'Redundant VPC off', + "displaytext": 'Redundant VPC off', + "supportedservices": 'Dhcp,Dns,SourceNat,PortForwarding,Vpn,Lb,UserData,StaticNat', + "serviceProviderList": { + "Vpn": 'VpcVirtualRouter', + "Dhcp": 'VpcVirtualRouter', + "Dns": 'VpcVirtualRouter', + "SourceNat": 'VpcVirtualRouter', + "PortForwarding": 'VpcVirtualRouter', + "Lb": 'VpcVirtualRouter', + "UserData": 'VpcVirtualRouter', + "StaticNat": 'VpcVirtualRouter', + "NetworkACL": 'VpcVirtualRouter' + }, + "serviceCapabilityList": { + "SourceNat": { + "RedundantRouter": 'true' + } + }, + }, + "vpc": { + "name": "TestVPC", + "displaytext": "TestVPC", + "cidr": '10.1.1.1/16' + }, + "network": { + "name": "Test Network", + "displaytext": "Test Network", + "netmask": '255.255.255.0' + }, + "natrule": { + "privateport": 22, + "publicport": 22, + "startport": 22, + "endport": 22, + "protocol": "TCP", + "cidrlist": '0.0.0.0/0', + }, + "virtual_machine": { + "displayname": "Test VM", + "username": "root", + "password": "password", + "ssh_port": 22, + "privateport": 22, + "publicport": 22, + "protocol": 'TCP', + }, + "ostype": 'CentOS 5.3 (64-bit)', + "timeout": 10, + } + + +class TestVPCIpTablesPolicies(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + # We want to fail quicker if it's failure + socket.setdefaulttimeout(60) + + cls.testClient = super(TestVPCIpTablesPolicies, cls).getClsTestClient() + cls.apiclient = cls.testClient.getApiClient() + + cls.services = Services().services + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) + cls.template = get_template( + cls.apiclient, + cls.zone.id, + cls.services["ostype"]) + + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls.services["virtual_machine"]["template"] = cls.template.id + + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + admin=True, + domainid=cls.domain.id) + + cls._cleanup = [cls.account] + + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offering"]) + + cls._cleanup.append(cls.service_offering) + + cls.logger = logging.getLogger('TestVPCIpTablesPolicies') + cls.stream_handler = logging.StreamHandler() + cls.logger.setLevel(logging.DEBUG) + cls.logger.addHandler(cls.stream_handler) + + cls.entity_manager = EntityManager(cls.apiclient, cls.services, cls.service_offering, cls.account, cls.zone, cls._cleanup, cls.logger) + + return + + @classmethod + def tearDownClass(cls): + try: + cleanup_resources(cls.apiclient, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.logger.debug("Creating a VPC offering.") + self.vpc_off = VpcOffering.create( + self.apiclient, + self.services["vpc_offering"]) + + self.logger.debug("Enabling the VPC offering created") + self.vpc_off.update(self.apiclient, state='Enabled') + + self.logger.debug("Creating a VPC network in the account: %s" % self.account.name) + + 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 + + @attr(tags=["advanced", "intervlan"], required_hardware="true") + def test_01_single_VPC_iptables_policies(self): + """ Test iptables default INPUT/FORWARD policies on VPC router """ + self.logger.debug("Starting test_01_single_VPC_iptables_policies") + + routers = self.entity_manager.query_routers() + + self.assertEqual( + isinstance(routers, list), True, + "Check for list routers response return valid data") + + self.entity_manager.create_network(self.services["vpc_network_offering"], self.vpc.id, "10.1.1.1") + self.entity_manager.create_network(self.services["vpc_network_offering_no_lb"], self.vpc.id, "10.1.2.1") + + self.entity_manager.add_nat_rules(self.vpc.id) + self.entity_manager.do_vpc_test() + + for router in routers: + if not router.isredundantrouter and router.vpcid: + 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.services["configurableData"]["host"]["username"] + host.passwd = self.services["configurableData"]["host"]["password"] + host.port = self.services["configurableData"]["host"]["port"] + tables = [self.services["configurableData"]["input"], self.services["configurableData"]["forward"]] + + for table in tables: + try: + result = get_process_status( + host.ipaddress, + host.port, + host.user, + host.passwd, + router.linklocalip, + 'iptables -L %s' % table) + except KeyError: + self.skipTest( + "Provide a marvin config file with host\ + credentials to run %s" % + self._testMethodName) + + self.logger.debug("iptables -L %s: %s" % (table, result)) + res = str(result) + + self.assertEqual( + res.count("DROP"), + 1, + "%s Default Policy should be DROP" % table) + + +class TestRouterIpTablesPolicies(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + # We want to fail quicker if it's failure + socket.setdefaulttimeout(60) + + cls.testClient = super(TestRouterIpTablesPolicies, cls).getClsTestClient() + cls.apiclient = cls.testClient.getApiClient() + + cls.services = Services().services + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) + cls.template = get_template( + cls.apiclient, + cls.zone.id, + cls.services["ostype"]) + + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls.services["virtual_machine"]["template"] = cls.template.id + + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + admin=True, + domainid=cls.domain.id) + + cls._cleanup = [cls.account] + + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offering"]) + + cls._cleanup.append(cls.service_offering) + + cls.logger = logging.getLogger('TestRouterIpTablesPolicies') + cls.stream_handler = logging.StreamHandler() + cls.logger.setLevel(logging.DEBUG) + cls.logger.addHandler(cls.stream_handler) + + cls.entity_manager = EntityManager(cls.apiclient, cls.services, cls.service_offering, cls.account, cls.zone, cls._cleanup, cls.logger) + + return + + @classmethod + def tearDownClass(cls): + try: + cleanup_resources(cls.apiclient, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr(tags=["advanced", "intervlan"], required_hardware="true") + def test_02_routervm_iptables_policies(self): + """ Test iptables default INPUT/FORWARD policy on RouterVM """ + + self.logger.debug("Starting test_02_routervm_iptables_policies") + + vm1 = self.entity_manager.deployvm() + + routers = self.entity_manager.query_routers() + + self.assertEqual( + isinstance(routers, list), True, + "Check for list routers response return valid data") + + for router in routers: + if not router.isredundantrouter and not router.vpcid: + 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.services["configurableData"]["host"]["username"] + host.passwd = self.services["configurableData"]["host"]["password"] + host.port = self.services["configurableData"]["host"]["port"] + tables = [self.services["configurableData"]["input"], self.services["configurableData"]["forward"]] + + for table in tables: + try: + result = get_process_status( + host.ipaddress, + host.port, + host.user, + host.passwd, + router.linklocalip, + 'iptables -L %s' % table) + except KeyError: + self.skipTest( + "Provide a marvin config file with host\ + credentials to run %s" % + self._testMethodName) + + self.logger.debug("iptables -L %s: %s" % (table, result)) + res = str(result) + + self.assertEqual( + res.count("DROP"), + 1, + "%s Default Policy should be DROP" % table) + + +class EntityManager(object): + + def __init__(self, apiclient, services, service_offering, account, zone, cleanup, logger): + self.apiclient = apiclient + self.services = services + self.service_offering = service_offering + self.account = account + self.zone = zone + self.cleanup = cleanup + self.logger = logger + + self.networks = [] + self.routers = [] + self.ips = [] + + def add_nat_rules(self, vpc_id): + for o in self.networks: + for vm in o.get_vms(): + if vm.get_ip() is None: + vm.set_ip(self.acquire_publicip(o.get_net(), vpc_id)) + if vm.get_nat() is None: + vm.set_nat(self.create_natrule(vm.get_vm(), vm.get_ip(), o.get_net(), vpc_id)) + time.sleep(5) + + def do_vpc_test(self): + for o in self.networks: + for vm in o.get_vms(): + self.check_ssh_into_vm(vm.get_vm(), vm.get_ip()) + + def create_natrule(self, vm, public_ip, network, vpc_id): + self.logger.debug("Creating NAT rule in network for vm with public IP") + + nat_rule_services = self.services["natrule"] + + nat_rule = NATRule.create( + self.apiclient, + vm, + nat_rule_services, + ipaddressid=public_ip.ipaddress.id, + openfirewall=False, + networkid=network.id, + vpcid=vpc_id) + + self.logger.debug("Adding NetworkACL rules to make NAT rule accessible") + nwacl_nat = NetworkACL.create( + self.apiclient, + networkid=network.id, + services=nat_rule_services, + traffictype='Ingress' + ) + self.logger.debug('nwacl_nat=%s' % nwacl_nat.__dict__) + return nat_rule + + def check_ssh_into_vm(self, vm, public_ip): + self.logger.debug("Checking if we can SSH into VM=%s on public_ip=%s" % + (vm.name, public_ip.ipaddress.ipaddress)) + vm.ssh_client = None + try: + vm.get_ssh_client(ipaddress=public_ip.ipaddress.ipaddress) + self.logger.debug("SSH into VM=%s on public_ip=%s is successful" % + (vm.name, public_ip.ipaddress.ipaddress)) + except: + raise Exception("Failed to SSH into VM - %s" % (public_ip.ipaddress.ipaddress)) + + def create_network(self, net_offerring, vpc_id, gateway='10.1.1.1'): + try: + self.logger.debug('Create NetworkOffering') + net_offerring["name"] = "NET_OFF-" + str(gateway) + nw_off = NetworkOffering.create( + self.apiclient, + net_offerring, + conservemode=False) + + nw_off.update(self.apiclient, state='Enabled') + self.cleanup.append(nw_off) + self.logger.debug('Created and Enabled NetworkOffering') + + self.services["network"]["name"] = "NETWORK-" + str(gateway) + self.logger.debug('Adding Network=%s to VPC ID %s' % (self.services["network"], vpc_id)) + 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, + vpcid=vpc_id) + self.logger.debug("Created network with ID: %s" % obj_network.id) + except Exception, e: + raise Exception('Unable to create a Network with offering=%s because of %s ' % (net_offerring, e)) + + o = networkO(obj_network) + o.add_vm(self.deployvm_in_network(obj_network)) + + self.networks.append(o) + return o + + def deployvm_in_network(self, network): + try: + self.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)]) + self.logger.debug('Created VM=%s in network=%s' % (vm.id, network.name)) + return vm + except: + raise Exception('Unable to create VM in a Network=%s' % network.name) + + def deployvm(self): + try: + self.logger.debug('Creating VM') + vm = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id) + self.logger.debug('Created VM=%s' % vm.id) + return vm + except: + raise Exception('Unable to create VM') + + def acquire_publicip(self, network, vpc_id): + self.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=vpc_id) + self.logger.debug("Associated %s with network %s" % ( + public_ip.ipaddress.ipaddress, + network.id)) + + self.ips.append(public_ip) + return public_ip + + def query_routers(self): + self.routers = list_routers(self.apiclient, + account=self.account.name, + domainid=self.account.domainid) + + return self.routers + + def stop_router(self): + self.logger.debug('Stopping router') + for router in self.routers: + cmd = stopRouter.stopRouterCmd() + cmd.id = router.id + self.apiclient.stopRouter(cmd) + + def destroy_router(self): + self.logger.debug('Destroying router') + for router in self.routers: + cmd = destroyRouter.destroyRouterCmd() + cmd.id = router.id + self.apiclient.destroyRouter(cmd) + + def start_router(self): + self.logger.debug('Starting router') + for router in self.routers: + cmd = startRouter.startRouterCmd() + cmd.id = router.id + self.apiclient.startRouter(cmd) + + +class networkO(object): + def __init__(self, net): + self.network = net + self.vms = [] + + def get_net(self): + return self.network + + def add_vm(self, vm): + self.vms.append(vmsO(vm)) + + def get_vms(self): + return self.vms + + +class vmsO(object): + def __init__(self, vm): + self.vm = vm + self.ip = None + self.nat = None + + def get_vm(self): + return self.vm + + def get_ip(self): + return self.ip + + def get_nat(self): + return self.nat + + def set_ip(self, ip): + self.ip = ip + + def set_nat(self, nat): + self.nat = nat diff --git a/tools/marvin/marvin/lib/utils.py b/tools/marvin/marvin/lib/utils.py index 4faced546e2..97b80ac308a 100644 --- a/tools/marvin/marvin/lib/utils.py +++ b/tools/marvin/marvin/lib/utils.py @@ -41,6 +41,43 @@ from marvin.codes import ( EMPTY_LIST, FAILED) +def _configure_ssh_credentials(hypervisor): + ssh_command = "ssh -i ~/.ssh/id_rsa.cloud -ostricthostkeychecking=no " + + if (str(hypervisor).lower() == 'vmware' + or str(hypervisor).lower() == 'hyperv'): + ssh_command = "ssh -i /var/cloudstack/management/.ssh/id_rsa -ostricthostkeychecking=no " + + return ssh_command + + +def _configure_timeout(hypervisor): + timeout = 5 + + # Increase hop into router + if str(hypervisor).lower() == 'hyperv': + timeout = 12 + + return timeout + + +def _execute_ssh_command(hostip, port, username, password, ssh_command): + #SSH to the machine + ssh = SshClient(hostip, port, username, password) + # Ensure the SSH login is successful + while True: + res = ssh.execute(ssh_command) + if "Connection refused".lower() in res[0].lower(): + pass + elif res[0] != "Host key verification failed.": + break + elif timeout == 0: + break + + time.sleep(5) + timeout = timeout - 1 + return res + def restart_mgmt_server(server): """Restarts the management server""" @@ -191,40 +228,19 @@ def get_host_credentials(config, hostip): raise KeyError("Please provide the marvin configuration file with credentials to your hosts") -def get_process_status(hostip, port, username, password, linklocalip, process, hypervisor=None): - """Double hop and returns a process status""" +def get_process_status(hostip, port, username, password, linklocalip, command, hypervisor=None): + """Double hop and returns a command execution result""" - #SSH to the machine - ssh = SshClient(hostip, port, username, password) - if (str(hypervisor).lower() == 'vmware' - or str(hypervisor).lower() == 'hyperv'): - ssh_command = "ssh -i /var/cloudstack/management/.ssh/id_rsa -ostricthostkeychecking=no " - else: - ssh_command = "ssh -i ~/.ssh/id_rsa.cloud -ostricthostkeychecking=no " + ssh_command = _configure_ssh_credentials(hypervisor) ssh_command = ssh_command +\ "-oUserKnownHostsFile=/dev/null -p 3922 %s %s" % ( linklocalip, - process) + command) + timeout = _configure_timeout(hypervisor) - # Double hop into router - if str(hypervisor).lower() == 'hyperv': - timeout = 12 - else: - timeout = 5 - # Ensure the SSH login is successful - while True: - res = ssh.execute(ssh_command) - if "Connection refused".lower() in res[0].lower(): - pass - elif res[0] != "Host key verification failed.": - break - elif timeout == 0: - break - - time.sleep(5) - timeout = timeout - 1 - return res + result = _execute_ssh_command(hostip, port, username, password, ssh_command) + return result def isAlmostEqual(first_digit, second_digit, range=0): @@ -504,5 +520,4 @@ def verifyRouterState(apiclient, routerid, allowedstates): if routers[0].state.lower() not in allowedstates: return [FAIL, "state of the router should be in %s but is %s" % (allowedstates, routers[0].state)] - return [PASS, None] - + return [PASS, None] \ No newline at end of file