#!/usr/bin/env python # 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. import requests from marvin.cloudstackTestCase import cloudstackTestCase from marvin.lib.utils import cleanup_resources from marvin.lib.base import ( PhysicalNetwork, NetworkOffering, NiciraNvp, ServiceOffering, NATRule, PublicIPAddress, Network, VirtualMachine ) from marvin.lib.common import ( get_domain, get_zone, get_template, list_routers, list_hosts, findSuitableHostForMigration, migrate_router ) from nose.plugins.attrib import attr from marvin.codes import (FAILED, PASS) import time import logging class TestNiciraContoller(cloudstackTestCase): ''' Example of marvin config with NSX specific values "niciraNvp": { "hosts": [ "nsxcon1.cloud.lan", "nsxcon2.cloud.lan", "nsxcon3.cloud.lan" ], "shared_network": { "l2gatewayserviceuuid": "3595ca67-959f-47d4-8f01-0492a8e96205", "iprange" : { "startip": "192.168.26.2", "endip": "192.168.26.20", "netmask": "255.255.255.0", "gateway": "192.168.26.1", "vlan": "50", "vlan_uuid": "5cdaa49d-06cd-488a-9ca4-e954a3181f54" } } } ''' @classmethod def setUpClass(cls): test_case = super(TestNiciraContoller, cls) test_client = test_case.getClsTestClient() cls.config = test_case.getClsConfig() cls.api_client = test_client.getApiClient() cls.physical_networks = cls.config.zones[0].physical_networks cls.nicira_hosts = cls.config.niciraNvp.hosts cls.nicira_shared_network_iprange = cls.config.niciraNvp.shared_network.iprange cls.l2gatewayserviceuuid = cls.config.niciraNvp.shared_network.l2gatewayserviceuuid cls.physical_network_id = cls.get_nicira_enabled_physical_network_id(cls.physical_networks) cls.network_offerring_services = [ { 'name': 'NiciraEnabledIsolatedNetwork', 'displaytext': 'NiciraEnabledIsolatedNetwork', 'guestiptype': 'Isolated', 'supportedservices': 'SourceNat,Dhcp,Dns,Firewall,PortForwarding,Connectivity', 'traffictype': 'GUEST', 'availability': 'Optional', 'serviceProviderList': { 'SourceNat': 'VirtualRouter', 'Dhcp': 'VirtualRouter', 'Dns': 'VirtualRouter', 'Firewall': 'VirtualRouter', 'PortForwarding': 'VirtualRouter', 'Connectivity': 'NiciraNvp' } }, { 'name': 'NiciraEnabledSharedNetwork', 'displaytext': 'NiciraEnabledSharedNetwork', 'guestiptype': 'Shared', 'supportedservices': 'Connectivity,Dhcp,UserData,SourceNat,StaticNat,Lb,PortForwarding', 'traffictype': 'GUEST', 'availability': 'Optional', 'specifyVlan': 'true', 'specifyIpRanges': 'true', 'serviceProviderList': { 'Connectivity': 'NiciraNvp', 'Dhcp': 'VirtualRouter', 'SourceNat': 'VirtualRouter', 'StaticNat': 'VirtualRouter', 'Lb': 'VirtualRouter', 'PortForwarding': 'VirtualRouter', 'UserData': 'VirtualRouter' } } ] cls.network_offering_isolated = NetworkOffering.create(cls.api_client, cls.network_offerring_services[0]) cls.network_offering_isolated.update(cls.api_client, state='Enabled') cls.network_offering_shared = NetworkOffering.create(cls.api_client, cls.network_offerring_services[1]) cls.network_offering_shared.update(cls.api_client, state='Enabled') cls.nicira_credentials = { 'username': 'admin', 'password': 'admin' } cls.nicira_primary_controller = cls.determine_primary_controller( cls.nicira_hosts, cls.nicira_credentials ) cls.transport_zone_uuid = cls.get_transport_zone_from_controller( cls.nicira_primary_controller, cls.nicira_credentials ) cls.admin_account = 'admin' cls.admin_domain = get_domain(cls.api_client) cls.zone = get_zone(cls.api_client, test_client.getZoneForTests()) template = get_template( cls.api_client, cls.zone.id ) if template == FAILED: raise Exception("get_template() failed to return template with description %s" % cls.services['ostype']) cls.vm_services = { 'mode': cls.zone.networktype, 'small': { 'zoneid': cls.zone.id, 'template': template.id, 'displayname': 'testserver', 'username': cls.config.zones[0].pods[0].clusters[0].hosts[0].username, 'password': cls.config.zones[0].pods[0].clusters[0].hosts[0].password, 'ssh_port': 22, 'hypervisor': cls.config.zones[0].pods[0].clusters[0].hypervisor, 'privateport': 22, 'publicport': 22, 'protocol': 'TCP', }, 'service_offerings': { 'tiny': { 'name': 'Tiny Instance', 'displaytext': 'Tiny Instance', 'cpunumber': 1, 'cpuspeed': 100, 'memory': 64, 'offerha': 'true' } } } if cls.zone.localstorageenabled == True: cls.vm_services['service_offerings']['tiny']['storagetype'] = 'local' cls.service_offering = ServiceOffering.create( cls.api_client, cls.vm_services['service_offerings']['tiny'] ) cls.cleanup = [ cls.network_offering_isolated, cls.service_offering, cls.network_offering_shared ] cls.logger = logging.getLogger('TestNiciraContoller') cls.stream_handler = logging.StreamHandler() cls.logger.setLevel(logging.DEBUG) cls.logger.addHandler(cls.stream_handler) @classmethod def tearDownClass(cls): try: cleanup_resources(cls.api_client, reversed(cls.cleanup)) except Exception as e: raise Exception("Warning: Exception during class cleanup : %s" % e) def setUp(self): self.test_cleanup = [] def tearDown(self): try: cleanup_resources(self.api_client, reversed(self.test_cleanup)) except Exception as e: raise Exception("Warning: Exception during test cleanup : %s" % e) @classmethod def determine_primary_controller(cls, hosts, credentials): for host in hosts: r1 = requests.post("https://%s/ws.v1/login" % host, credentials, verify=False) r2 = requests.get("https://%s/ws.v1/control-cluster/status" % host, verify=False, cookies=r1.cookies) status_code = r2.status_code if status_code == 401: continue elif status_code == 200: return host raise Exception("None of the supplied hosts (%s) is a Nicira controller" % hosts) @classmethod def get_transport_zone_from_controller(cls, controller_host, credentials): r1 = requests.post("https://%s/ws.v1/login" % controller_host, credentials, verify=False) r2 = requests.get("https://%s/ws.v1/transport-zone" % controller_host, verify=False, cookies=r1.cookies) status_code = r2.status_code if status_code == 200: list_transport_zone_response = r2.json() result_count = list_transport_zone_response['result_count'] if result_count == 0: raise Exception('Nicira controller did not return any Transport Zones') elif result_count > 1: cls.logger.debug("Nicira controller returned %s Transport Zones, picking first one" % resultCount) transport_zone_api_url = list_transport_zone_response['results'][0]['_href'] r3 = requests.get( "https://%s%s" % (controller_host, transport_zone_api_url), verify=False, cookies=r1.cookies ) return r3.json()['uuid'] else: raise Exception("Unexpected response from Nicira controller. Status code = %s, content = %s" % status_code) @classmethod def get_nicira_enabled_physical_network_id(cls, physical_networks): nicira_physical_network_name = None for physical_network in physical_networks: for provider in physical_network.providers: if provider.name == 'NiciraNvp': nicira_physical_network_name = physical_network.name if nicira_physical_network_name is None: raise Exception('Did not find a Nicira enabled physical network in configuration') return PhysicalNetwork.list(cls.api_client, name=nicira_physical_network_name)[0].id def determine_secondary_conroller(self, hosts, primary_controller): secondary = [ s for s in hosts if s != primary_controller ] if len(secondary) > 0: return secondary[0] else: raise Exception("None of the supplied hosts (%s) is a Nicira secondary" % hosts) def add_nicira_device(self, hostname, l2gatewayserviceuuid=None): nicira_device = NiciraNvp.add( self.api_client, None, self.physical_network_id, hostname=hostname, username=self.nicira_credentials['username'], password=self.nicira_credentials['password'], transportzoneuuid=self.transport_zone_uuid, l2gatewayserviceuuid=l2gatewayserviceuuid ) self.test_cleanup.append(nicira_device) def create_guest_isolated_network(self): network_services = { 'name' : 'nicira_enabled_network_isolated', 'displaytext' : 'nicira_enabled_network_isolated', 'zoneid' : self.zone.id, 'networkoffering' : self.network_offering_isolated.id } network = Network.create( self.api_client, network_services, accountid=self.admin_account, domainid=self.admin_domain.id ) self.test_cleanup.append(network) return network def create_guest_shared_network_numerical_vlanid(self): network_services = { 'name' : 'nicira_enabled_network_shared', 'displaytext' : 'nicira_enabled_network_shared', 'zoneid' : self.zone.id, 'networkoffering' : self.network_offering_shared.id, 'startip' : self.nicira_shared_network_iprange.startip, 'endip' : self.nicira_shared_network_iprange.endip, 'netmask' : self.nicira_shared_network_iprange.netmask, 'gateway' : self.nicira_shared_network_iprange.gateway, 'vlan' : self.nicira_shared_network_iprange.vlan } network = Network.create( self.api_client, network_services, accountid=self.admin_account, domainid=self.admin_domain.id ) self.test_cleanup.append(network) return network def create_guest_shared_network_uuid_vlanid(self): network_services = { 'name' : 'nicira_enabled_network_shared', 'displaytext' : 'nicira_enabled_network_shared', 'zoneid' : self.zone.id, 'networkoffering' : self.network_offering_shared.id, 'startip' : self.nicira_shared_network_iprange.startip, 'endip' : self.nicira_shared_network_iprange.endip, 'netmask' : self.nicira_shared_network_iprange.netmask, 'gateway' : self.nicira_shared_network_iprange.gateway, 'vlan' : self.nicira_shared_network_iprange.vlan_uuid } network = Network.create( self.api_client, network_services, accountid=self.admin_account, domainid=self.admin_domain.id ) self.test_cleanup.append(network) return network def create_guest_shared_network_services(self): network_services = { 'name' : 'nicira_enabled_network_shared', 'displaytext' : 'nicira_enabled_network_shared', 'zoneid' : self.zone.id, 'networkoffering' : self.network_offering_shared.id, 'startip' : self.nicira_shared_network_iprange.startip, 'endip' : self.nicira_shared_network_iprange.endip, 'netmask' : self.nicira_shared_network_iprange.netmask, 'gateway' : self.nicira_shared_network_iprange.gateway } network = Network.create( self.api_client, network_services, accountid=self.admin_account, domainid=self.admin_domain.id, ) self.test_cleanup.append(network) return network def create_virtual_machine(self, network): virtual_machine = VirtualMachine.create( self.api_client, self.vm_services['small'], accountid=self.admin_account, domainid=self.admin_domain.id, serviceofferingid=self.service_offering.id, networkids=[network.id], mode=self.vm_services['mode'] ) self.test_cleanup.append(virtual_machine) return virtual_machine def create_virtual_machine_shared_networks(self, network): virtual_machine = VirtualMachine.create( self.api_client, self.vm_services['small'], accountid=self.admin_account, domainid=self.admin_domain.id, serviceofferingid=self.service_offering.id, networkids=[network.id], mode='BASIC' ) self.test_cleanup.append(virtual_machine) return virtual_machine def get_routers_for_network(self, network): return list_routers( self.api_client, accountid=self.admin_account, domainid=self.admin_domain.id, networkid=network.id ) def get_hosts(self): return list_hosts( self.api_client, accountid=self.admin_account, domainid=self.admin_domain.id ) def get_primary_router(self, routers): primary = [r for r in routers if r.redundantstate == 'PRIMARY'] self.logger.debug("Found %s primary router(s): %s" % (primary.size(), primary)) return primary[0] def distribute_vm_and_routers_by_hosts(self, virtual_machine, routers): if len(routers) > 1: router = self.get_router(routers) self.logger.debug("Primary Router VM is %s" % router) else: router = routers[0] if router.hostid == virtual_machine.hostid: self.logger.debug("Primary Router VM is on the same host as VM") host = findSuitableHostForMigration(self.api_client, router.id) if host is not None: migrate_router(self.api_client, router.id, host.id) self.logger.debug("Migrated Primary Router VM to host %s" % host.name) else: self.fail('No suitable host to migrate Primary Router VM to') else: self.logger.debug("Primary Router VM is not on the same host as VM: %s, %s" % (router.hostid, virtual_machine.hostid)) def acquire_publicip(self, network): self.logger.debug("Associating public IP for network: %s" % network.name) public_ip = PublicIPAddress.create( self.api_client, accountid=self.admin_account, zoneid=self.zone.id, domainid=self.admin_domain.id, networkid=network.id ) self.logger.debug("Associated %s with network %s" % (public_ip.ipaddress.ipaddress, network.id)) self.test_cleanup.append(public_ip) return public_ip def create_natrule(self, vm, public_ip, network): self.logger.debug("Creating NAT rule in network for vm with public IP") nat_rule = NATRule.create( self.api_client, vm, self.vm_services['small'], ipaddressid=public_ip.ipaddress.id, openfirewall=True, networkid=network.id ) self.test_cleanup.append(nat_rule) return nat_rule @attr(tags = ["advanced", "smoke", "nicira"], required_hardware="true") def test_01_nicira_controller(self): self.add_nicira_device(self.nicira_primary_controller) network = self.create_guest_isolated_network() virtual_machine = self.create_virtual_machine(network) list_vm_response = VirtualMachine.list(self.api_client, id=virtual_machine.id) self.logger.debug("Verify listVirtualMachines response for virtual machine: %s" % virtual_machine.id) self.assertEqual(isinstance(list_vm_response, list), True, 'Response did not return a valid list') self.assertNotEqual(len(list_vm_response), 0, 'List of VMs is empty') vm_response = list_vm_response[0] self.assertEqual(vm_response.id, virtual_machine.id, 'Virtual machine in response does not match request') self.assertEqual(vm_response.state, 'Running', 'VM is not in Running state') @attr(tags = ["advanced", "smoke", "nicira"], required_hardware="true") def test_02_nicira_controller_redirect(self): """ Nicira clusters will redirect clients (in this case ACS) to the primary node. This test assumes that a Nicira cluster is present and configured properly, and that it has at least two controller nodes. The test will check that ASC follows redirects by: - adding a Nicira Nvp device that points to one of the cluster's secondary controllers, - create a VM in a Nicira backed network If all is well, no matter what controller is specified (secondary or primary), the vm (and respective router VM) should be created without issues. """ nicira_secondary = self.determine_secondary_conroller(self.nicira_hosts, self.nicira_primary_controller) self.logger.debug("Nicira secondary controller is: %s " % nicira_secondary) self.add_nicira_device(nicira_secondary) network = self.create_guest_isolated_network() virtual_machine = self.create_virtual_machine(network) list_vm_response = VirtualMachine.list(self.api_client, id=virtual_machine.id) self.logger.debug("Verify listVirtualMachines response for virtual machine: %s" % virtual_machine.id) self.assertEqual(isinstance(list_vm_response, list), True, 'Response did not return a valid list') self.assertNotEqual(len(list_vm_response), 0, 'List of VMs is empty') vm_response = list_vm_response[0] self.assertEqual(vm_response.id, virtual_machine.id, 'Virtual machine in response does not match request') self.assertEqual(vm_response.state, 'Running', 'VM is not in Running state') @attr(tags = ["advanced", "smoke", "nicira"], required_hardware="true") def test_03_nicira_tunnel_guest_network(self): self.add_nicira_device(self.nicira_primary_controller) network = self.create_guest_isolated_network() virtual_machine = self.create_virtual_machine(network) public_ip = self.acquire_publicip(network) nat_rule = self.create_natrule(virtual_machine, public_ip, network) list_vm_response = VirtualMachine.list(self.api_client, id=virtual_machine.id) self.logger.debug("Verify listVirtualMachines response for virtual machine: %s" % virtual_machine.id) self.assertEqual(isinstance(list_vm_response, list), True, 'Response did not return a valid list') self.assertNotEqual(len(list_vm_response), 0, 'List of VMs is empty') vm_response = list_vm_response[0] self.assertEqual(vm_response.id, virtual_machine.id, 'Virtual machine in response does not match request') self.assertEqual(vm_response.state, 'Running', 'VM is not in Running state') routers = self.get_routers_for_network(network) self.distribute_vm_and_routers_by_hosts(virtual_machine, routers) ssh_command = 'ping -c 3 google.com' result = 'failed' try: self.logger.debug("SSH into VM: %s" % public_ip.ipaddress.ipaddress) ssh = virtual_machine.get_ssh_client(ipaddress=public_ip.ipaddress.ipaddress) self.logger.debug('Ping to google.com from VM') result = str(ssh.execute(ssh_command)) self.logger.debug("SSH result: %s; COUNT is ==> %s" % (result, result.count("3 packets received"))) except Exception as e: self.fail("SSH Access failed for %s: %s" % (virtual_machine.get_ip(), e)) self.assertEqual(result.count('3 packets received'), 1, 'Ping to outside world from VM should be successful') @attr(tags = ["advanced", "smoke", "nicira"], required_hardware="true") def test_04_nicira_shared_networks_numerical_vlanid(self): """ Shared Networks Support CASE 1) Numerical VLAN_ID provided in network creation """ self.debug("Starting test case 1 for Shared Networks") self.add_nicira_device(self.nicira_primary_controller, self.l2gatewayserviceuuid) network = self.create_guest_shared_network_numerical_vlanid() virtual_machine = self.create_virtual_machine_shared_networks(network) list_vm_response = VirtualMachine.list(self.api_client, id=virtual_machine.id) self.debug("Verify listVirtualMachines response for virtual machine: %s" % virtual_machine.id) self.assertEqual(isinstance(list_vm_response, list), True, 'Response did not return a valid list') self.assertNotEqual(len(list_vm_response), 0, 'List of VMs is empty') vm_response = list_vm_response[0] self.assertEqual(vm_response.id, virtual_machine.id, 'Virtual machine in response does not match request') self.assertEqual(vm_response.state, 'Running', 'VM is not in Running state') @attr(tags = ["advanced", "smoke", "nicira"], required_hardware="true") def test_05_nicira_shared_networks_lrouter_uuid_vlan_id(self): """ Shared Networks Support CASE 2) Logical Router's UUID as VLAN_ID provided in network creation """ self.debug("Starting test case 2 for Shared Networks") self.add_nicira_device(self.nicira_primary_controller, self.l2gatewayserviceuuid) network = self.create_guest_shared_network_uuid_vlanid() virtual_machine = self.create_virtual_machine_shared_networks(network) list_vm_response = VirtualMachine.list(self.api_client, id=virtual_machine.id) self.debug("Verify listVirtualMachines response for virtual machine: %s" % virtual_machine.id) self.assertEqual(isinstance(list_vm_response, list), True, 'Response did not return a valid list') self.assertNotEqual(len(list_vm_response), 0, 'List of VMs is empty') vm_response = list_vm_response[0] self.assertEqual(vm_response.id, virtual_machine.id, 'Virtual machine in response does not match request') self.assertEqual(vm_response.state, 'Running', 'VM is not in Running state')