diff --git a/vmware_nsx_tempest/services/nsxv_client.py b/vmware_nsx_tempest/services/nsxv_client.py index b8dffeb4ed..7010d2e921 100644 --- a/vmware_nsx_tempest/services/nsxv_client.py +++ b/vmware_nsx_tempest/services/nsxv_client.py @@ -14,6 +14,7 @@ import base64 from oslo_log import log as logging from oslo_serialization import jsonutils +import re import requests from tempest import config @@ -249,6 +250,45 @@ class VSMClient(object): LOG.debug('Found edge: %s' % edge) return edge + def get_dhcp_edge_config(self, edge_id): + """Get dhcp edge config. + + Return edge information. + """ + self.__set_api_version('4.0') + self.__set_endpoint('/edges/%s/dhcp/config' % edge_id) + response = self.get() + return response + + def get_dhcp_edge_info(self): + """Get dhcp edge info. + + Return edge if found, else return None. + """ + edges = self.get_all_edges() + edge_list = [] + for e in edges: + if (not e['edgeStatus'] == 'GREY' + and not e['state'] == 'undeployed'): + p = re.compile(r'dhcp*') + if (p.match(e['name'])): + edge_list.append(e['recentJobInfo']['edgeId']) + count = 0 + result_edge = {} + for edge_id in edge_list: + response = self.get_dhcp_edge_config(edge_id) + paging_info = response.json() + if (paging_info['staticBindings']['staticBindings']): + result_edge[count] = paging_info + count += 1 + else: + LOG.debug('Host Routes are not avilable for %s ' % edge_id) + if (count > 0): + edge = result_edge[0] + else: + edge = None + return edge + def get_vsm_version(self): """Get the VSM client version including major, minor, patch, & build#. diff --git a/vmware_nsx_tempest/tests/nsxv/scenario/test_dhcp_121.py b/vmware_nsx_tempest/tests/nsxv/scenario/test_dhcp_121.py new file mode 100644 index 0000000000..c712af16c4 --- /dev/null +++ b/vmware_nsx_tempest/tests/nsxv/scenario/test_dhcp_121.py @@ -0,0 +1,421 @@ +# Copyright 2016 OpenStack Foundation +# Copyright 2016 VMware Inc +# All Rights Reserved. +# +# Licensed 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 re +import sys +import time + +from tempest import config +from tempest.lib.common import ssh +from tempest.lib.common.utils import data_utils +from tempest.lib import exceptions +from tempest import test + +from vmware_nsx_tempest._i18n import _LI +from vmware_nsx_tempest.services import nsxv_client +from vmware_nsx_tempest.tests.nsxv.scenario import ( + manager_topo_deployment as dmgr) + +CONF = config.CONF +LOG = dmgr.manager.log.getLogger(__name__) + +DHCP_121_DEPLOY_TOPO = "Testcase DHCP-121 option [%s] deploying" +DHCP_121_DEPLOY_COMPLETED = "Testcase [%s] deploy test-completed." +Metadataserver_ip = '169.254.169.254' + + +class TestDHCP121BasicOps(dmgr.TopoDeployScenarioManager): + """Base class provides DHCP 121 options operations. + + 1) Creates an instance + 2) Ssh to instance and then check below information: + a) check metadata routes avialable or not + b) check host routes avialble or not + c) clear host-routes from subnet and check routes present on vm or not + d) update subnet to disbale dhcp and check metadata routes not visible + on instance + 3) Check at beckend(nsx-v) for host-routes and metadata route information + 4) Delete of host routes from subnet will make it deleted from beckend + 5) Negative test where try to make subnet dhcp disable but host-routes + present and vice-versa + 6) Create large no of host-routes for subnet and check validation at + beckend + """ + @classmethod + def skip_checks(cls): + super(TestDHCP121BasicOps, cls).skip_checks() + if not (CONF.network.project_networks_reachable + or CONF.network.public_network_id): + msg = ('Either project_networks_reachable must be "true", or ' + 'public_network_id must be defined.') + raise cls.skipException(msg) + manager_ip = re.search(r"(\d{1,3}\.){3}\d{1,3}", + CONF.nsxv.manager_uri).group(0) + cls.vsm = nsxv_client.VSMClient( + manager_ip, CONF.nsxv.user, CONF.nsxv.password) + nsxv_version = cls.vsm.get_vsm_version() + # Raise skip testcase exception if nsx-v version is less than 6.2.3 + if (nsxv_version and nsxv_version < '6.2.3'): + msg = ('NSX-v version should be greater than or equal to 6.2.3') + raise cls.skipException(msg) + + @classmethod + def resource_setup(cls): + super(TestDHCP121BasicOps, cls).resource_setup() + + @classmethod + def resource_cleanup(cls): + super(TestDHCP121BasicOps, cls).resource_cleanup() + + def setUp(self): + super(TestDHCP121BasicOps, self).setUp() + + def tearDown(self): + try: + self.remove_project_network(False) + except Exception: + pass + super(TestDHCP121BasicOps, self).tearDown() + + @test.attr(type='nsxv') + @test.idempotent_id('95d06aba-895f-47f8-b47d-ae48c6853a85') + def test_dhcp_121_metadata_check_on_vm_nsxv(self): + LOG.info(_LI("Testcase DHCP-121 option metadata check on vm and \ + on nsx deploying")) + self.vm_env = self.setup_vm_enviornment(self.manager, 'green', True) + self.green = self.dhcp_121_metadata_hostroutes_check_on_vm_nsxv( + self.vm_env) + self.remove_project_network() + self.green['router'].unset_gateway() + self.green['router'].delete() + LOG.info(_LI("Testcase DHCP-121 option metadata check on vm and on \ + nsx completed")) + + @test.attr(type='nsxv') + @test.idempotent_id('6bec6eb4-8632-493d-a895-a3ee87cb3002') + def test_dhcp_121_hostroutes_clear(self): + LOG.info(_LI("Testcase DHCP-121 option host routes clear deploying")) + self.vm_env = self.setup_vm_enviornment(self.manager, 'green', True) + self.green = self.dhcp_121_hostroutes_clear(self.vm_env) + self.remove_project_network() + self.green['router'].unset_gateway() + self.green['router'].delete() + LOG.info(_LI("Testcase DHCP-121 option host routes clear completed")) + + @test.attr(type='nsxv') + @test.idempotent_id('a58dc6c5-9f28-4184-baf7-37ded52593c4') + def test_dhcp121_negative_test(self): + LOG.info(_LI("Testcase DHCP-121 option negative test deploying")) + t_net_id, t_network, t_subnet =\ + self.create_project_network_subnet('admin') + subnet_id = t_subnet['id'] + kwargs = {'enable_dhcp': 'false'} + new_name = "New_subnet" + # Update subnet with disable dhcp subnet + self.subnets_client.update_subnet( + subnet_id, name=new_name, **kwargs) + # Fetch next hop information from tempest.conf + next_hop = CONF.network.project_network_cidr + self.nexthop_host_route = next_hop.rsplit('.', 1)[0] + self.nexthop1 = self.nexthop_host_route + ".2" + username, password = self.get_image_userpass() + # Update subnet with host routes + _subnet_data = {'host_routes': [{'destination': '10.20.0.0/32', + 'nexthop': '10.100.1.1'}], + 'new_host_routes': [{'destination': '10.20.0.0/32', + 'nexthop': self.nexthop1}]} + new_host_routes = _subnet_data['new_host_routes'] + kwargs = {'host_routes': new_host_routes} + new_name = "New_subnet" + # Update subnet with host-route info + try: + self.subnets_client.update_subnet( + subnet_id, name=new_name, **kwargs) + except exceptions.BadRequest: + e = sys.exc_info()[0].__dict__['message'] + if (e == "Bad request"): + LOG.info(_LI("Invalid input for operation:\ + Host routes can only be supported when\ + DHCP is enabled")) + pass + subnet_id = t_subnet['id'] + kwargs = {'enable_dhcp': 'true'} + new_name = "New_subnet" + # Update subnet with disable dhcp subnet + self.subnets_client.update_subnet( + subnet_id, name=new_name, **kwargs) + # Update subnet with host routes + _subnet_data = {'host_routes': [{'destination': '10.20.0.0/32', + 'nexthop': '10.100.1.1'}], + 'new_host_routes': [{'destination': '10.20.0.0/32', + 'nexthop': self.nexthop1}]} + new_host_routes = _subnet_data['new_host_routes'] + kwargs = {'host_routes': new_host_routes} + new_name = "Subnet_host_routes" + # Update subnet with host-route info + self.subnets_client.update_subnet( + subnet_id, name=new_name, **kwargs) + # Disable dhcp subnet + kwargs = {'enable_dhcp': 'false'} + # Update subnet with disable dhcp subnet + try: + self.subnets_client.update_subnet( + subnet_id, name=new_name, **kwargs) + except exceptions.BadRequest: + e = sys.exc_info()[0].__dict__['message'] + if (e == "Bad request"): + LOG.info(_LI("Can't disable DHCP while using host routes")) + pass + LOG.info(_LI("Testcase DHCP-121 option negative test completed")) + + @test.attr(type='nsxv') + @test.idempotent_id('c3ca96d7-b704-4d94-b42d-e7bae94b82cd') + def test_dhcp121_multi_host_route(self): + LOG.info(_LI("Testcase DHCP-121 option multi host routes deploying")) + t_net_id, t_network, t_subnet =\ + self.create_project_network_subnet('admin') + # Fetch next hop information from tempest.conf + next_hop = CONF.network.project_network_cidr + self.nexthop_host_route = next_hop.rsplit('.', 1)[0] + self.nexthop1 = self.nexthop_host_route + ".2" + # Update subnet with host routes + _subnet_data = {'host_routes': [{'destination': '10.20.0.0/32', + 'nexthop': '10.100.1.1'}], + 'new_host_routes': [{'destination': '10.20.0.0/32', + 'nexthop': self.nexthop1}, + {'destination': '10.21.0.0/32', + 'nexthop': self.nexthop1}, + {'destination': '10.22.0.0/32', + 'nexthop': self.nexthop1}, + {'destination': '10.23.0.0/32', + 'nexthop': self.nexthop1}, + {'destination': '10.24.0.0/32', + 'nexthop': self.nexthop1}, + {'destination': '10.25.0.0/32', + 'nexthop': self.nexthop1}, + {'destination': '10.26.0.0/32', + 'nexthop': self.nexthop1}, + {'destination': '10.27.0.0/32', + 'nexthop': self.nexthop1}, + {'destination': '10.28.0.0/32', + 'nexthop': self.nexthop1}, + {'destination': '10.29.0.0/32', + 'nexthop': self.nexthop1}, + {'destination': '10.30.0.0/32', + 'nexthop': self.nexthop1}, + {'destination': '10.31.0.0/32', + 'nexthop': self.nexthop1}, + {'destination': '10.32.0.0/32', + 'nexthop': self.nexthop1}, + {'destination': '10.33.0.0/32', + 'nexthop': self.nexthop1}, + {'destination': '10.34.0.0/32', + 'nexthop': self.nexthop1}, + {'destination': '10.35.0.0/32', + 'nexthop': self.nexthop1}, + {'destination': '10.36.0.0/32', + 'nexthop': self.nexthop1}, + {'destination': '10.37.0.0/32', + 'nexthop': self.nexthop1}, + {'destination': '10.38.0.0/32', + 'nexthop': self.nexthop1}]} + new_host_routes = _subnet_data['new_host_routes'] + kwargs = {'host_routes': new_host_routes} + new_name = "New_subnet" + subnet_id = t_subnet['id'] + # Update subnet with host-route info + subnet = self.subnets_client.update_subnet( + subnet_id, name=new_name, **kwargs) + ''' + Above No of host-routes added are 19 so checking len of + subnet host_routes equal to 19 or not + ''' + if (len(subnet['subnet']['host_routes']) == 19): + LOG.info(_LI("Multiple entries for host routes available")) + LOG.info(_LI("Testcase DHCP-121 option multi host routes completed")) + + def remove_project_network(self, from_test=True): + project_name = 'green' + tenant = getattr(self, project_name, None) + servers_client = tenant['client_mgr'].servers_client + dmgr.delete_all_servers(servers_client) + self.disassociate_floatingip(tenant['fip1']) + if from_test: + time.sleep(dmgr.WAITTIME_AFTER_DISASSOC_FLOATINGIP) + fip_client = tenant['client_mgr'].floating_ips_client + fip_client.delete_floatingip(tenant['fip1'].id) + tenant.pop('fip1') + tenant['router'].delete_subnet(tenant['subnet']) + tenant['subnet'].delete() + tenant['network'].delete() + + def check_server_connected(self, serv): + # Fetch tenant-network from where vm deployed + serv_net = list(serv['addresses'].keys())[0] + serv_addr = serv['addresses'][serv_net][0] + host_ip = serv_addr['addr'] + self.waitfor_host_connected(host_ip) + + def create_project_network_subnet(self, + name_prefix='dhcp-project'): + network_name = data_utils.rand_name(name_prefix) + network, subnet = self.create_network_subnet( + name=network_name) + return (network.id, network, subnet) + + def dhcp_121_metadata_hostroutes_check_on_vm_nsxv(self, vm_env): + self.serv_fip = vm_env['fip1'].floating_ip_address + username, password = self.get_image_userpass() + # Connect to instance launched using ssh lib + client = ssh.Client(self.serv_fip, username=username, + password=password) + # Executes route over launched instance + cmd = ('route -n') + out_data = client.exec_command(cmd) + self.assertIn(Metadataserver_ip, out_data) + LOG.info(_LI("Metadata routes available on vm")) + cmd = ('wget http://169.254.169.254 -O sample.txt') + client.exec_command(cmd) + cmd = ('cat sample.txt') + out_data = client.exec_command(cmd) + # Check metadata server inforamtion available or not + self.assertIn('latest', out_data) + LOG.info(_LI("metadata server is acessible")) + # Fetch dhcp edge infor from nsx-v + exc_edge = self.vsm.get_dhcp_edge_info() + self.assertIsNotNone(exc_edge) + # Fetch host-route and metadata info from nsx-v + dhcp_options_info = {} + dhcp_options_info = \ + exc_edge['staticBindings']['staticBindings'][0]['dhcpOptions'] + # Check Host Route information avaialable at beckend + self.assertIn( + Metadataserver_ip, + dhcp_options_info['option121'][ + 'staticRoutes'][0]['destinationSubnet']) + # Storing sec-group, network, subnet, router, server info in dict + project_dict = dict(security_group=vm_env['security_group'], + network=vm_env['network'], subnet=vm_env['subnet'], + router=vm_env['router'], + client_mgr=vm_env['client_mgr'], + serv1=vm_env['serv1'], fip1=vm_env['fip1']) + return project_dict + + def dhcp_121_hostroutes_clear(self, vm_env): + # Fetch next hop information from tempest.conf + next_hop = CONF.network.project_network_cidr + self.nexthop_host_route = next_hop.rsplit('.', 1)[0] + self.nexthop1 = self.nexthop_host_route + ".2" + # Floating-ip of VM + self.serv_fip = vm_env['fip1'].floating_ip_address + username, password = self.get_image_userpass() + # Update subnet with host routes + _subnet_data = {'host_routes': [{'destination': '10.20.0.0/32', + 'nexthop': '10.100.1.1'}], + 'new_host_routes': [{'destination': '10.20.0.0/32', + 'nexthop': self.nexthop1}]} + new_host_routes = _subnet_data['new_host_routes'] + kwargs = {'host_routes': new_host_routes} + new_name = "New_subnet" + subnet_id = vm_env['subnet']['id'] + # Update subnet with host-route info + self.subnets_client.update_subnet( + subnet_id, name=new_name, **kwargs) + # Connect to instance launched using ssh lib + client = ssh.Client(self.serv_fip, username=username, + password=password) + # Executes route over instance launched + cmd = ('route -n') + out_data = client.exec_command(cmd) + self.assertIn( + _subnet_data['new_host_routes'][0]['nexthop'], out_data) + self.assertIn(self.nexthop_host_route, out_data) + LOG.info(_LI("Host routes available on vm")) + # Check Host route info at beckend + exc_edge = self.vsm.get_dhcp_edge_info() + self.assertIsNotNone(exc_edge) + # Fetch host-route and metadata info from nsx-v + dhcp_options_info = {} + dhcp_options_info = exc_edge['staticBindings']['staticBindings'][0][ + 'dhcpOptions']['option121']['staticRoutes'] + # Check Host Route information avaialable at beckend + for destination_net in dhcp_options_info: + if _subnet_data['new_host_routes'][0]['destination']\ + in destination_net['destinationSubnet'] and\ + self.nexthop1 in destination_net['router']: + LOG.info(_LI("Host routes available on nsxv")) + # Update subnet with no host-routes + _subnet_data1 = {'new_host_routes': []} + new_host_routes = _subnet_data1['new_host_routes'] + kwargs = {'host_routes': new_host_routes} + new_name = "New_subnet" + self.subnets_client.update_subnet( + subnet_id, name=new_name, **kwargs) + # Executes route over instance launched + cmd = ('dhclient eth0') + client.exec_command(cmd) + cmd = ('route -n') + out_data = client.exec_command(cmd) + self.assertIsNotNone(out_data) + # Check Host routes on VM shouldn't be avialable + self.assertNotIn( + _subnet_data['new_host_routes'][0]['destination'], out_data) + # Check Host-routes at beckend after deletion + exc_edge = self.vsm.get_dhcp_edge_info() + self.assertIsNotNone(exc_edge) + dhcp_options_info = [] + dhcp_options_info = exc_edge['staticBindings']['staticBindings'][0][ + 'dhcpOptions']['option121']['staticRoutes'] + # Check Host Route information avaialable at beckend + for destination_net in dhcp_options_info: + if (_subnet_data['new_host_routes'][0]['destination'] + not in destination_net['destinationSubnet']): + LOG.info(_LI("Host routes not available on nsxv")) + project_dict = dict(security_group=vm_env['security_group'], + network=vm_env['network'], subnet=vm_env['subnet'], + router=vm_env['router'], + client_mgr=vm_env['client_mgr'], + serv1=vm_env['serv1'], fip1=vm_env['fip1']) + return project_dict + + def setup_vm_enviornment(self, client_mgr, t_id, + check_outside_world=True, + cidr_offset=0): + t_network, t_subnet, t_router = self.setup_project_network( + self.public_network_id, namestart=("deploy-%s-tenant" % t_id)) + t_security_group = self._create_security_group( + security_groups_client=self.security_groups_client, + security_group_rules_client=self.security_group_rules_client, + namestart='adm') + username, password = self.get_image_userpass() + security_groups = [{'name': t_security_group['name']}] + t_serv1 = self.create_server_on_network( + t_network, security_groups, + image=self.get_server_image(), + flavor=self.get_server_flavor(), + name=t_network['name']) + self.check_server_connected(t_serv1) + t_floatingip = self.create_floatingip_for_server( + t_serv1, client_mgr=self.admin_manager) + msg = ("Associate t_floatingip[%s] to server[%s]" + % (t_floatingip, t_serv1['name'])) + self._check_floatingip_connectivity( + t_floatingip, t_serv1, should_connect=True, msg=msg) + vm_enviornment = dict(security_group=t_security_group, + network=t_network, subnet=t_subnet, + router=t_router, client_mgr=client_mgr, + serv1=t_serv1, fip1=t_floatingip) + return vm_enviornment