Tempest: NSX-v external network supports multiple subnets
NSX-v at VIO-2.5 release supports external network with multiple subnets. However, this feature requires network admin to configure the physcial router to route the 2nd subnet CIDR to the OS environment. In our QA environment, we are not able to configure physical router to route the 2nd subnet, this test can only be executed at devstack environment. And ONLY DO: 1. PING to servers' floating-ip 2. SSH sever using its floating-ip 3. From server it can ping other server's private address. For VIO or other environments, you need to have the right to change physcial router, and change tempest.conf accoridingly. 3 tests in this module, so run this test sequencially. Change-Id: I5fc0d137f5b95101bf7b9485a2ca03e61d13d766
This commit is contained in:
parent
841f3a631b
commit
fc05b43d32
@ -32,10 +32,17 @@ ScenarioGroup = [
|
||||
" & DNS are local to provider's settings."),
|
||||
cfg.DictOpt('flat_alloc_pool_dict',
|
||||
default={},
|
||||
help=" Define flat network ip range."
|
||||
help="Define flat network ip range."
|
||||
" required attributes are gateway, start, end"
|
||||
" and cidr. Example value: gateway:10.1.1.253,"
|
||||
" start:10.1.1.30,end:10.1.1.49,cidr=10.1.1.0/24"),
|
||||
cfg.DictOpt('xnet_multiple_subnets_dict',
|
||||
default={},
|
||||
help="External network with multiple subnets."
|
||||
" The primary subnet ip-range will be shrinked,"
|
||||
" This is for the 2nd subnet, required attrs:"
|
||||
" start:10.1.1.31,end:10.1.1.33,cidr=10.1.2.0/24"
|
||||
" AND limit to only 3 ip addresses defined."),
|
||||
]
|
||||
|
||||
network_group = config.network_group
|
||||
|
@ -14,6 +14,7 @@
|
||||
# under the License.
|
||||
|
||||
import collections
|
||||
from fixtures._fixtures import timeout as fixture_timeout
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
@ -21,6 +22,7 @@ import time
|
||||
import traceback
|
||||
|
||||
import net_resources
|
||||
import netaddr
|
||||
|
||||
from tempest.common.utils.linux import remote_client
|
||||
from tempest.common import waiters
|
||||
@ -28,7 +30,6 @@ from tempest import config
|
||||
from tempest.scenario import manager
|
||||
from tempest import test
|
||||
|
||||
import netaddr
|
||||
from tempest.lib.common.utils import data_utils
|
||||
from tempest.lib import exceptions
|
||||
|
||||
@ -133,8 +134,8 @@ class TopoDeployScenarioManager(manager.NetworkScenarioTest):
|
||||
tenant_id = routers_client.tenant_id
|
||||
distributed = kwargs.pop('distributed', None)
|
||||
router_type = kwargs.pop('router_type', None)
|
||||
if distributed in (True, False):
|
||||
kwargs['distributed'] = distributed
|
||||
if distributed:
|
||||
kwargs['distributed'] = True
|
||||
elif router_type in ('shared', 'exclusive'):
|
||||
kwargs['router_type'] = router_type
|
||||
name = data_utils.rand_name(namestart)
|
||||
@ -253,23 +254,25 @@ class TopoDeployScenarioManager(manager.NetworkScenarioTest):
|
||||
def setup_project_network(self, external_network_id,
|
||||
client_mgr=None,
|
||||
namestart=None, client=None,
|
||||
tenant_id=None, cidr_offset=0):
|
||||
tenant_id=None, cidr_offset=0,
|
||||
**kwargs):
|
||||
"""NOTE:
|
||||
|
||||
Refer to create_networks@scenario/manager.py which might refer
|
||||
to public_router_id which we dont' want to use.
|
||||
|
||||
The test class can define class variable tenant_router_attrs
|
||||
to create different type of routers.
|
||||
to create different type of routers, or overwrite with kwargs.
|
||||
"""
|
||||
# namestart = namestart if namestart else 'topo-deploy-tenant'
|
||||
name = namestart or data_utils.rand_name('topo-deploy-tenant')
|
||||
client_mgr = client_mgr or self.manager
|
||||
# _create_router() editing distributed and router_type
|
||||
distributed = self.tenant_router_attrs.get('distributed')
|
||||
router_type = self.tenant_router_attrs.get('router_type')
|
||||
# child class use class var tenant_router_attrs to define
|
||||
# tenant's router type.
|
||||
# _create_router() edits distributed and router_type
|
||||
# Child classes use class var tenant_router_attrs to define
|
||||
# tenant's router type, however, caller can overwrite it with kwargs.
|
||||
distributed = kwargs.get('distributed',
|
||||
self.tenant_router_attrs.get('distributed'))
|
||||
router_type = kwargs.get('router_type',
|
||||
self.tenant_router_attrs.get('router_type'))
|
||||
net_router = self._create_router(
|
||||
client_mgr=client_mgr, tenant_id=tenant_id,
|
||||
namestart=name,
|
||||
@ -460,6 +463,27 @@ class TopoDeployScenarioManager(manager.NetworkScenarioTest):
|
||||
def get_server_flavor(self):
|
||||
return CONF.compute.flavor_ref
|
||||
|
||||
# replaced by call_and_ignore_notfound_exc method
|
||||
# at tempest/lib/common/utils/test_utils.py
|
||||
def delete_wrapper(self, delete_thing, *args, **kwargs):
|
||||
"""Ignores NotFound exceptions for delete operations.
|
||||
|
||||
@param delete_thing: delete method of a resource. method will be
|
||||
executed as delete_thing(*args, **kwargs)
|
||||
|
||||
"""
|
||||
try:
|
||||
delete_thing(*args, **kwargs)
|
||||
except exceptions.NotFound:
|
||||
# If the resource is already missing, mission accomplished.
|
||||
pass
|
||||
except fixture_timeout.TimeoutException:
|
||||
# one more time
|
||||
try:
|
||||
delete_thing(*args, **kwargs)
|
||||
except exceptions.NotFound:
|
||||
pass
|
||||
|
||||
|
||||
# common utilities
|
||||
def make_node_info(net_floatingip, username, password,
|
||||
@ -690,7 +714,7 @@ def get_remote_client_by_password(client_ip, username, password):
|
||||
return ssh_client
|
||||
|
||||
|
||||
def delete_all_servers(tenant_servers_client, trys=3):
|
||||
def delete_all_servers(tenant_servers_client, trys=5):
|
||||
# try at least trys+1 time to delete servers, otherwise
|
||||
# network resources can not be deleted
|
||||
for s in tenant_servers_client.list_servers()['servers']:
|
||||
|
@ -69,26 +69,14 @@ class DeletableRouter(n_resources.DeletableRouter):
|
||||
return self.add_interface(subnet)
|
||||
|
||||
def add_interface(self, subnet):
|
||||
# should not let subnet add interface to router as
|
||||
# the router might be crated by admin.
|
||||
try:
|
||||
self.client.add_router_interface(
|
||||
self.id, subnet_id=subnet.id)
|
||||
except Exception:
|
||||
x_method(self.client, 'add_router_interface_with_subnet_id',
|
||||
self.id, subnet_id=subnet.id)
|
||||
self.client.add_router_interface(self.id, subnet_id=subnet.id)
|
||||
self._subnets.add(subnet)
|
||||
|
||||
def delete_subnet(self, subnet):
|
||||
return self.delete_interface(subnet)
|
||||
|
||||
def delete_interface(self, subnet):
|
||||
try:
|
||||
self.client.remove_router_interface(
|
||||
self.id, subnet_id=subnet.id)
|
||||
except Exception:
|
||||
x_method(self.client, 'remove_router_interface_with_subnet_id',
|
||||
self.id, subnet_id=subnet.id)
|
||||
self.client.remove_router_interface(self.id, subnet_id=subnet.id)
|
||||
self._subnets.remove(subnet)
|
||||
|
||||
def update_extra_routes(self, nexthop, destination):
|
||||
@ -110,13 +98,3 @@ class DeletableRouter(n_resources.DeletableRouter):
|
||||
for subnet in self._subnets.copy():
|
||||
self.delete_interface(subnet)
|
||||
super(DeletableRouter, self).delete()
|
||||
|
||||
|
||||
# Workaround solution
|
||||
def x_method(target_obj, method_name, *args, **kwargs):
|
||||
_method = getattr(target_obj, method_name, None)
|
||||
if _method is None:
|
||||
raise Exception("Method[%s] is not defined at instance[%s]" %
|
||||
(method_name, str(target_obj)))
|
||||
results = _method(*args, **kwargs)
|
||||
return results
|
||||
|
@ -0,0 +1,307 @@
|
||||
# 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 time
|
||||
|
||||
from tempest.common import waiters
|
||||
from tempest import config
|
||||
from tempest import test
|
||||
|
||||
from vmware_nsx_tempest.tests.nsxv.scenario import (
|
||||
manager_topo_deployment as dmgr)
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class TestXnetMultiSubnetsOps(dmgr.TopoDeployScenarioManager):
|
||||
"""Test NSX external network can support multiple subnets/cidrs.
|
||||
|
||||
With multiple subnets, VMs get its floatingip from all subnets
|
||||
attached to the external network.
|
||||
|
||||
This test validates that VM can get its floatingip from all subnets,
|
||||
and are reachable. However due to the physical network routing issue,
|
||||
we can only validate at devstack environment:
|
||||
|
||||
1. VM's floatingip is pingable
|
||||
2. can ssh to VM's floatingip.
|
||||
3. from VM can ping other VMs' private address.
|
||||
|
||||
If this test fail and were not able to revert to its original subnet
|
||||
ip ranges, other tempest tests require floatingip's might FAIL.
|
||||
|
||||
The test will shrink the primary subnet range to 3 ip addresses.
|
||||
Note: the 1st one is already used by the router1@devstack.
|
||||
|
||||
The 2nd subnet is set with CONF.scenario.xnet_multiple_subnets_dict,
|
||||
and no-gateway is required. Make sure the 2nd CIRD is reachable by
|
||||
your devstack.
|
||||
|
||||
LIMITATION:
|
||||
This test can only be done at devstack environment, other environment,
|
||||
for example VIO can not be executed unless you can modify the physical
|
||||
network to route the 2nd subnet cidr to the OS environment.
|
||||
|
||||
This test validates data-path from the devstack host itself:
|
||||
1. Ping to floating-ips
|
||||
2. ssh to VM
|
||||
3. from VM ping other VMs' private ip address
|
||||
|
||||
ATTENTION:
|
||||
Because, this test consumes floatingip's so both subnets ip-ranges
|
||||
will be used. NO OTHER TESTS should run when execute this test.
|
||||
|
||||
Run this test module sequencially :
|
||||
|
||||
./run_tempest.sh -t <tests>
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def skip_checks(cls):
|
||||
super(TestXnetMultiSubnetsOps, cls).skip_checks()
|
||||
if not CONF.scenario.xnet_multiple_subnets_dict:
|
||||
msg = 'scenario.xnet_multiple_subnets_dict must be set.'
|
||||
raise cls.skipException(msg)
|
||||
if not CONF.network.public_network_id:
|
||||
msg = ('network.public_network_id must be defined.')
|
||||
raise cls.skipException(msg)
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(TestXnetMultiSubnetsOps, cls).resource_setup()
|
||||
cls.xnet_subnets = [None, None]
|
||||
cls.public_network_id = CONF.network.public_network_id
|
||||
# primary user
|
||||
cls.primary_tenant_id = cls.manager.networks_client.tenant_id
|
||||
cls.floating_ips_client = cls.manager.floating_ips_client
|
||||
cls.servers_client = cls.manager.servers_client
|
||||
|
||||
@classmethod
|
||||
def resource_cleanup(cls):
|
||||
cls.remove_this_test_resources()
|
||||
super(TestXnetMultiSubnetsOps, cls).resource_cleanup()
|
||||
|
||||
@classmethod
|
||||
def remove_this_test_resources(cls):
|
||||
dmgr.delete_all_servers(cls.manager.servers_client)
|
||||
subnets_client = cls.admin_manager.subnets_client
|
||||
subnet_1 = cls.xnet_subnets[0]
|
||||
subnet_2 = cls.xnet_subnets[1]
|
||||
if subnet_2:
|
||||
subnets_client.delete_subnet(subnet_2['id'])
|
||||
cls.xnet_subnets[1] = None
|
||||
if subnet_1:
|
||||
subnets_client.update_subnet(
|
||||
subnet_1['id'],
|
||||
allocation_pools=subnet_1['allocation_pools'])
|
||||
cls.xnet_subnets[0] = None
|
||||
|
||||
@classmethod
|
||||
def create_no_gateway_subnet(cls, network_id, cidr, allocation_pool,
|
||||
ip_version=4, dns_nameservers=None,
|
||||
name=None, client_mgr=None, **kwargs):
|
||||
"""Subnets, except the 1st one, no-gateway should be applied."""
|
||||
|
||||
client_mgr = client_mgr or cls.admin_manager
|
||||
subnets_client = client_mgr.subnets_client
|
||||
post_body = {'network_id': network_id,
|
||||
'cidr': cidr,
|
||||
'allocation_pools': [allocation_pool],
|
||||
'ip_version': ip_version,
|
||||
'gateway_ip': None,
|
||||
'enable_dhcp': False}
|
||||
if name:
|
||||
post_body['name'] = name
|
||||
if dns_nameservers:
|
||||
post_body['dns_nameservers'] = dns_nameservers
|
||||
body = subnets_client.create_subnet(**post_body)
|
||||
subnet_2 = subnets_client.show_subnet(body['subnet']['id'])
|
||||
# no addCleanup, it is to be done at tearDown
|
||||
return subnet_2['subnet']
|
||||
|
||||
def setUp(self):
|
||||
"""Create the 2nd subnet attached to public network.
|
||||
|
||||
Idealy this is at class method. However we need to validate that
|
||||
the public network and its subnets are correctly configured.
|
||||
|
||||
External network/subnet configured here, so assert* can be called.
|
||||
"""
|
||||
super(TestXnetMultiSubnetsOps, self).setUp()
|
||||
# only admin user can manage external network/subnets
|
||||
networks_client = self.admin_manager.networks_client
|
||||
subnets_client = self.admin_manager.subnets_client
|
||||
self.sub2_dict = CONF.scenario.xnet_multiple_subnets_dict
|
||||
# limited to only one subnet available when test started.
|
||||
subnet_id_list = networks_client.show_network(
|
||||
self.public_network_id)["network"]["subnets"]
|
||||
self.assertEqual(1, len(subnet_id_list))
|
||||
subnet_1 = subnets_client.show_subnet(
|
||||
subnet_id_list[0])["subnet"]
|
||||
self.assertEqual(1, len(subnet_1["allocation_pools"]))
|
||||
pool_start = subnet_1["allocation_pools"][0]["start"]
|
||||
iprange = pool_start.split(".")
|
||||
iprange[3] = str(int(iprange[3]) + 3)
|
||||
pool_end = ".".join(iprange)
|
||||
sub1_allocation = {'start': pool_start, 'end': pool_end}
|
||||
self.xnet_subnets[0] = subnet_1
|
||||
# update the 1st subnet so it only has 3 ip addresses
|
||||
subnet1 = subnets_client.update_subnet(
|
||||
subnet_1['id'],
|
||||
allocation_pools=[sub1_allocation])['subnet']
|
||||
alloc_pool1 = subnet1['allocation_pools']
|
||||
self.assertEqual(1, len(alloc_pool1))
|
||||
alloc_pool1 = alloc_pool1[0]
|
||||
self.assertEqual(pool_start, alloc_pool1['start'])
|
||||
self.assertEqual(pool_end, alloc_pool1['end'])
|
||||
# create the 2nd subnet under external network
|
||||
alloc_pool2 = {'start': self.sub2_dict['start'],
|
||||
'end': self.sub2_dict['end']}
|
||||
dns_nameservers = subnet_1['dns_nameservers']
|
||||
subnet_2 = self.create_no_gateway_subnet(
|
||||
subnet_1['network_id'], cidr=self.sub2_dict['cidr'],
|
||||
allocation_pool=alloc_pool2, dns_nameservers=dns_nameservers,
|
||||
name='public-xnet-subnet2')
|
||||
self.xnet_subnets[1] = subnet_2
|
||||
self.my_network = None
|
||||
self.user_sg = self._create_security_group(
|
||||
security_groups_client=self.manager.security_groups_client,
|
||||
namestart='xnet-subnets')
|
||||
|
||||
def tearDown(self):
|
||||
if self.my_network:
|
||||
self.delete_floatingips_and_servers()
|
||||
if self.my_network['router']:
|
||||
self.delete_wrapper(self.my_network['router'].delete)
|
||||
# Delete subnet - distributed router take longer time.
|
||||
if self.my_network['subnet']:
|
||||
self.delete_wrapper(self.my_network['subnet'].delete)
|
||||
if self.my_network['network']:
|
||||
self.delete_wrapper(self.my_network['network'].delete)
|
||||
super(TestXnetMultiSubnetsOps, self).tearDown()
|
||||
|
||||
def create_user_servers(self, num_servers=5):
|
||||
network = self.my_network['network']
|
||||
user_sg = [{'name': self.user_sg['id']}]
|
||||
self.my_network['servers'] = []
|
||||
server_id_list = []
|
||||
for num in range(0, num_servers):
|
||||
vm_name = 'xnet-subnet-%d' % (num + 1)
|
||||
sv = self.create_server_on_network(
|
||||
network,
|
||||
security_groups=user_sg,
|
||||
name=vm_name, wait_on_boot=False)
|
||||
self.my_network['servers'].append(sv)
|
||||
server_id_list.append(sv['id'])
|
||||
self.wait_for_servers_become_active(server_id_list,
|
||||
self.servers_client)
|
||||
|
||||
def wait_for_servers_become_active(self, server_id_list,
|
||||
servers_client):
|
||||
for server_id in server_id_list:
|
||||
waiters.wait_for_server_status(
|
||||
servers_client, server_id, 'ACTIVE')
|
||||
|
||||
def create_floatingips_and_assign_to_servers(self):
|
||||
self.my_network['floatingips'] = []
|
||||
self.fixed_ip_addresses = []
|
||||
for sv in self.my_network['servers']:
|
||||
floatingip, sshc = self.create_floatingip_for_server(sv)
|
||||
self.my_network['floatingips'].append(floatingip)
|
||||
self.fixed_ip_addresses.append(floatingip.fixed_ip_address)
|
||||
# check inside this tenant network, all VMs are reachable.
|
||||
self.validate_all_servers_private_address_are_reachable(
|
||||
sshc, self.fixed_ip_addresses)
|
||||
|
||||
def create_floatingip_for_server(self, server):
|
||||
# project/tenant create the server, not the ADMIN
|
||||
username, password = self.get_image_userpass()
|
||||
# Only admin can create resource with tenant_id attributes, so
|
||||
# always providing the admin_manager as client to create_floatingip
|
||||
# as scenario/manager.py always insert tenant_id attribe
|
||||
# while creating the serve..
|
||||
floatingip = super(TestXnetMultiSubnetsOps,
|
||||
self).create_floatingip_for_server(
|
||||
server,
|
||||
external_network_id=self.public_network_id,
|
||||
client_mgr=self.admin_manager)
|
||||
msg = ("Associate floatingip[%s] to server[%s]"
|
||||
% (floatingip, server['name']))
|
||||
self._check_floatingip_connectivity(
|
||||
floatingip, server, should_connect=True, msg=msg)
|
||||
serv_fip = floatingip.floating_ip_address
|
||||
dmgr.rm_sshkey(serv_fip)
|
||||
ssh_client = dmgr.get_remote_client_by_password(
|
||||
serv_fip, username, password)
|
||||
return (floatingip, ssh_client)
|
||||
|
||||
def delete_floatingips_and_servers(self):
|
||||
for net_floatingip in self.my_network['floatingips']:
|
||||
self.delete_wrapper(net_floatingip.delete)
|
||||
fip_list = self.floating_ips_client.list_floatingips()['floatingips']
|
||||
if len(fip_list) > 0:
|
||||
time.sleep(dmgr.WAITTIME_AFTER_DISASSOC_FLOATINGIP)
|
||||
self.my_network['floatingips'] = []
|
||||
dmgr.delete_all_servers(self.servers_client)
|
||||
|
||||
def validate_all_servers_private_address_are_reachable(self,
|
||||
ssh_client,
|
||||
ip_addresses):
|
||||
for ip_addr in ip_addresses:
|
||||
msg = "VM private address[%s] is not reachable." % ip_addr
|
||||
reachable = dmgr.is_reachable(ssh_client, ip_addr)
|
||||
self.assertTrue(reachable, msg)
|
||||
|
||||
def _test_xnet_multiple_subnets_basic_ops(self,
|
||||
router_type='exclusive',
|
||||
distributed=None):
|
||||
network, subnet, router = self.setup_project_network(
|
||||
self.public_network_id,
|
||||
client_mgr=self.admin_manager,
|
||||
tenant_id=self.primary_tenant_id,
|
||||
namestart='xnet-subnets',
|
||||
router_type=router_type, distributed=distributed)
|
||||
self.my_network = {'router': router,
|
||||
'subnet': subnet,
|
||||
'network': network,
|
||||
'servers': [],
|
||||
'floatingips': []}
|
||||
self.create_user_servers()
|
||||
self.create_floatingips_and_assign_to_servers()
|
||||
self.delete_floatingips_and_servers()
|
||||
|
||||
|
||||
class TestXnetMultiSubnetsOpsOnSharedRouter(TestXnetMultiSubnetsOps):
|
||||
|
||||
@test.idempotent_id('e25d030f-7fdf-4500-bd55-4ed6f62c0a5c')
|
||||
def test_xnet_multiple_subnets_basic_ops_on_shared_router(self):
|
||||
return self._test_xnet_multiple_subnets_basic_ops(
|
||||
'shared', False)
|
||||
|
||||
|
||||
class TestXnetMultiSubnetsOpsOnExclusiveRouter(TestXnetMultiSubnetsOps):
|
||||
|
||||
@test.idempotent_id('5b09351a-0560-4555-99f0-a1f80d54d435')
|
||||
def test_xnet_multiple_subnets_basic_ops_on_exclusive_router(self):
|
||||
return self._test_xnet_multiple_subnets_basic_ops(
|
||||
'exclusive', False)
|
||||
|
||||
|
||||
class TestXnetMultiSubnetsOpsOnDistributedRouter(TestXnetMultiSubnetsOps):
|
||||
|
||||
@test.idempotent_id('9652d36b-8816-4212-a6e1-3a8b2580deee')
|
||||
def test_xnet_multiple_subnets_basic_ops_on_distributed_router(self):
|
||||
return self._test_xnet_multiple_subnets_basic_ops(
|
||||
'', True)
|
Loading…
Reference in New Issue
Block a user