Migrate routed provider network tests
Moved downstream tests with minimal adjustments, changed uuids. Added necessary config values. Change-Id: I763726b2a676b6f0f8908adc775d45d2e1daace1
This commit is contained in:
@@ -65,6 +65,10 @@ WhiteboxNeutronPluginOptions = [
|
|||||||
help='Specify explicitly whether to run traffic flow tests.'
|
help='Specify explicitly whether to run traffic flow tests.'
|
||||||
' This is needed because some ML2 plugins (e.g. ovn ) do '
|
' This is needed because some ML2 plugins (e.g. ovn ) do '
|
||||||
'not expose api_extensions like dvr for some purposes.'),
|
'not expose api_extensions like dvr for some purposes.'),
|
||||||
|
cfg.BoolOpt('provroutednetwork_support',
|
||||||
|
default=False,
|
||||||
|
help='Boolean that specifies if Provider Routed Networks'
|
||||||
|
'are supported or not'),
|
||||||
cfg.IntOpt('broadcast_receivers_count',
|
cfg.IntOpt('broadcast_receivers_count',
|
||||||
default=2,
|
default=2,
|
||||||
help='How many receivers to use in broadcast tests. Default '
|
help='How many receivers to use in broadcast tests. Default '
|
||||||
@@ -91,13 +95,16 @@ WhiteboxNeutronPluginOptions = [
|
|||||||
cfg.StrOpt('neutron_config',
|
cfg.StrOpt('neutron_config',
|
||||||
default='/etc/neutron/neutron.conf',
|
default='/etc/neutron/neutron.conf',
|
||||||
help='Path to neutron configuration file.'),
|
help='Path to neutron configuration file.'),
|
||||||
|
cfg.StrOpt('ml2_plugin_config',
|
||||||
|
default='/etc/neutron/plugins/ml2/ml2_conf.ini',
|
||||||
|
help='Path to ml2 plugin config.'),
|
||||||
|
cfg.StrOpt('ext_bridge',
|
||||||
|
default='br-ex',
|
||||||
|
help="OpenvSwitch bridge dedicated for external network."),
|
||||||
cfg.IntOpt('ovn_max_controller_gw_ports_per_router',
|
cfg.IntOpt('ovn_max_controller_gw_ports_per_router',
|
||||||
default=1,
|
default=1,
|
||||||
help='The number of network nodes used '
|
help='The number of network nodes used '
|
||||||
'for the OVN router HA.'),
|
'for the OVN router HA.'),
|
||||||
cfg.StrOpt('ml2_plugin_config',
|
|
||||||
default='/etc/neutron/plugins/ml2/ml2_conf.ini',
|
|
||||||
help='Path to ml2 plugin config.'),
|
|
||||||
cfg.IntOpt('minbw_placement_nic_kbps_egress',
|
cfg.IntOpt('minbw_placement_nic_kbps_egress',
|
||||||
default=None,
|
default=None,
|
||||||
help='BW configured per NIC for the minimum BW placement '
|
help='BW configured per NIC for the minimum BW placement '
|
||||||
|
|||||||
@@ -0,0 +1,682 @@
|
|||||||
|
# Copyright 2024 Red Hat, 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 testtools
|
||||||
|
|
||||||
|
from neutron_tempest_plugin.common import ssh
|
||||||
|
from neutron_tempest_plugin.common import utils
|
||||||
|
from neutron_tempest_plugin import config
|
||||||
|
from paramiko import ssh_exception as ssh_exc
|
||||||
|
from tempest.common import waiters
|
||||||
|
from tempest.lib.common import fixed_network
|
||||||
|
from tempest.lib.common.utils import data_utils
|
||||||
|
from tempest.lib.common.utils import test_utils
|
||||||
|
from tempest.lib import decorators
|
||||||
|
from tempest.lib import exceptions as lib_exc
|
||||||
|
|
||||||
|
from whitebox_neutron_tempest_plugin.tests.scenario import base
|
||||||
|
|
||||||
|
CONF = config.CONF
|
||||||
|
WB_CONF = config.CONF.whitebox_neutron_plugin_options
|
||||||
|
|
||||||
|
|
||||||
|
class ProviderRoutedNetworkBaseTest(base.ProviderBaseTest):
|
||||||
|
"""Base class for tests using provisioning networks"""
|
||||||
|
|
||||||
|
required_extensions = ['segment']
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setup_clients(cls):
|
||||||
|
super(ProviderRoutedNetworkBaseTest, cls).setup_clients()
|
||||||
|
cls.client = cls.os_adm.network_client
|
||||||
|
cls.keypairs_client = cls.os_adm.keypairs_client
|
||||||
|
cls.servers_client = cls.os_adm.servers_client
|
||||||
|
cls.resources = cls.map_resources()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def skip_checks(cls):
|
||||||
|
super(ProviderRoutedNetworkBaseTest, cls).skip_checks()
|
||||||
|
if not WB_CONF.provroutednetwork_support:
|
||||||
|
raise cls.skipException("Skipped because Provider Routed Networks "
|
||||||
|
"are not supported")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_networks_with_segments(cls):
|
||||||
|
"""Collect list of networks that have segments"""
|
||||||
|
|
||||||
|
client = cls.os_adm.network_client
|
||||||
|
nets = client.list_networks()['networks']
|
||||||
|
nets_with_segments = []
|
||||||
|
for net in nets:
|
||||||
|
if net.get('segments'):
|
||||||
|
nets_with_segments.append(net['id'])
|
||||||
|
return nets_with_segments
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_host_physical_network(cls, host):
|
||||||
|
"""Searches for the name of the physical network attached to the host
|
||||||
|
|
||||||
|
It is not possible to request the name of the physical network that
|
||||||
|
is attached to the specific hypervisor. In order to get it we can
|
||||||
|
create port with specified network and hypervisor so there will
|
||||||
|
be information about the subnet that the port has fixed IP address.
|
||||||
|
That subnet belongs to the segment that we can get the physical
|
||||||
|
network name from.
|
||||||
|
"""
|
||||||
|
|
||||||
|
client = cls.os_adm.network_client
|
||||||
|
network = fixed_network.get_network_from_name(
|
||||||
|
CONF.network.floating_network_name, client)
|
||||||
|
port = client.create_port(network_id=network['id'],
|
||||||
|
**{'binding:host_id': host})['port']
|
||||||
|
subnet_id = port['fixed_ips'][0]['subnet_id']
|
||||||
|
client.delete_port(port['id'])
|
||||||
|
subnet = client.show_subnet(subnet_id)['subnet']
|
||||||
|
segment_id = subnet.get('segment_id')
|
||||||
|
if not segment_id:
|
||||||
|
return ''
|
||||||
|
segment = client.show_segment(segment_id)
|
||||||
|
return segment['segment']['physical_network']
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def map_resources(cls):
|
||||||
|
"""Collect the information about resources in the availability zone
|
||||||
|
|
||||||
|
Returnes the dictionary of the following schema:
|
||||||
|
{<az_name>: {'hosts': <list_of_hypervisors_in_az>,
|
||||||
|
'physical_network': <name_of_the_physical_network>,
|
||||||
|
'networks':
|
||||||
|
{<net_id>:
|
||||||
|
{<segment_id>: [<subnet_ids>]}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
<az2_name>: {...},
|
||||||
|
<az3_name>: {...}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
resources = {}
|
||||||
|
client = cls.os_adm.network_client
|
||||||
|
az_list = cls.os_admin.az_client.list_availability_zones(detail=True)
|
||||||
|
ext_nets = cls.get_networks_with_segments()
|
||||||
|
for az in az_list['availabilityZoneInfo']:
|
||||||
|
az_computes = []
|
||||||
|
physical_net = ''
|
||||||
|
for host in az.get('hosts'):
|
||||||
|
if az['hosts'][host].get('nova-compute'):
|
||||||
|
az_computes.append(host)
|
||||||
|
if not physical_net:
|
||||||
|
physical_net = cls.get_host_physical_network(host)
|
||||||
|
if not az_computes:
|
||||||
|
continue
|
||||||
|
networks = {}
|
||||||
|
for net in ext_nets:
|
||||||
|
networks[net] = {}
|
||||||
|
segments = client.list_segments(**{'network_id': net})
|
||||||
|
for segment in segments['segments']:
|
||||||
|
if segment['physical_network'] == physical_net:
|
||||||
|
networks[net][segment['id']] = []
|
||||||
|
subnets = client.list_subnets(
|
||||||
|
**{'network_id': net})
|
||||||
|
for subnet in subnets['subnets']:
|
||||||
|
if subnet.get('segment_id') == segment['id']:
|
||||||
|
networks[net][segment['id']].append(
|
||||||
|
subnet['id'])
|
||||||
|
resources[az['zoneName']] = {'hosts': az_computes,
|
||||||
|
'physical_network': physical_net,
|
||||||
|
'networks': networks}
|
||||||
|
return resources
|
||||||
|
|
||||||
|
def create_server_with_params(self, ports=(), nets=(), az='', client=None):
|
||||||
|
"""Create server with specified ports, networks, availability zone
|
||||||
|
|
||||||
|
Ports and nets are the lists of the corresponding UUIDs. Ports have to
|
||||||
|
be created in advance.
|
||||||
|
"""
|
||||||
|
srv_params = {'networks': []}
|
||||||
|
if not ports and not nets:
|
||||||
|
network = fixed_network.get_network_from_name(
|
||||||
|
CONF.network.floating_network_name, self.client)
|
||||||
|
nets = (network['id'], )
|
||||||
|
if nets:
|
||||||
|
srv_params['security_groups'] = [{'name': self.secgroup['name']}]
|
||||||
|
for network in nets:
|
||||||
|
srv_params['networks'].append({'uuid': network})
|
||||||
|
if ports:
|
||||||
|
for port in ports:
|
||||||
|
srv_params['networks'].append({'port': port})
|
||||||
|
if az:
|
||||||
|
srv_params['availability_zone'] = az
|
||||||
|
srv_params['name'] = data_utils.rand_name(self._testMethodName)
|
||||||
|
client = client or self.servers_client
|
||||||
|
server = client.create_server(flavorRef=CONF.compute.flavor_ref,
|
||||||
|
imageRef=CONF.compute.image_ref,
|
||||||
|
key_name=self.keypair['name'],
|
||||||
|
**srv_params)['server']
|
||||||
|
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
|
||||||
|
waiters.wait_for_server_termination,
|
||||||
|
client,
|
||||||
|
server['id'])
|
||||||
|
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
|
||||||
|
client.delete_server,
|
||||||
|
server['id'])
|
||||||
|
return server
|
||||||
|
|
||||||
|
def wait_for_server_ready(self, server, client=None):
|
||||||
|
"""Wait for server is ACTIVE and booted"""
|
||||||
|
client = client or self.os_adm.servers_client
|
||||||
|
self.wait_for_server_active(server, client)
|
||||||
|
|
||||||
|
def system_booted():
|
||||||
|
console = client.get_console_output(server['id'])['output']
|
||||||
|
for line in console.split('\n'):
|
||||||
|
if 'login:' in line.lower():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
utils.wait_until_true(system_booted, timeout=300, sleep=5)
|
||||||
|
except utils.WaitTimeout:
|
||||||
|
pass
|
||||||
|
return client.show_server(server['id'])['server']
|
||||||
|
|
||||||
|
def get_provider_network_ssh_client(self, server, key=None, timeout=300):
|
||||||
|
"""Return SSH client for the server connected to the provider network
|
||||||
|
|
||||||
|
As there might be more than one interface for the server, function
|
||||||
|
will iterate over all the available IPv4 interfaces
|
||||||
|
"""
|
||||||
|
if not key:
|
||||||
|
key = self.keypair
|
||||||
|
for addresses in server['addresses'].values():
|
||||||
|
for address in addresses:
|
||||||
|
ssh_client = ssh.Client(address['addr'],
|
||||||
|
self.username,
|
||||||
|
pkey=key['private_key'],
|
||||||
|
timeout=timeout)
|
||||||
|
try:
|
||||||
|
ssh_client.test_connection_auth()
|
||||||
|
return ssh_client
|
||||||
|
except (lib_exc.SSHTimeout,
|
||||||
|
ssh_exc.AuthenticationException,
|
||||||
|
ssh_exc.NoValidConnectionsError):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
raise lib_exc.TimeoutException(
|
||||||
|
'Can not establish SSH '
|
||||||
|
'connection with one of the available addresses: '
|
||||||
|
'{}'.format(server['addresses']))
|
||||||
|
|
||||||
|
def verify_ips_on_interfaces(self, server):
|
||||||
|
"""Make sure that all IP addresses are configured on interfaces
|
||||||
|
|
||||||
|
For cirros images the second interface should be enabled manually
|
||||||
|
after the server is created. All other OSes (RHEL, Centos, Ubuntu)
|
||||||
|
are configured automatically still there is `nmcli` command specified
|
||||||
|
to create link with DHCP on the supported images
|
||||||
|
"""
|
||||||
|
ssh_client = self.get_provider_network_ssh_client(server)
|
||||||
|
ifs = ssh_client.exec_command("PATH=$PATH:/sbin:/usr/bin; ip -f inet"
|
||||||
|
" -o addr | awk '{{print $2}}'")
|
||||||
|
links_with_ips = ifs.strip().split('\n')
|
||||||
|
links = ssh_client.exec_command("PATH=$PATH:/sbin:/usr/bin; ip -f "
|
||||||
|
"inet -o link | awk '{{print $2}}'")
|
||||||
|
all_links = [link.strip(':') for link in links.strip().split('\n')]
|
||||||
|
links_with_no_ip = []
|
||||||
|
for link in all_links:
|
||||||
|
if link not in links_with_ips:
|
||||||
|
links_with_no_ip.append(link)
|
||||||
|
if not links_with_no_ip:
|
||||||
|
return
|
||||||
|
for link in links_with_no_ip:
|
||||||
|
cmd = 'sudo chmod 777 {net_file} && '\
|
||||||
|
'sudo echo "auto {link}" >> {net_file} && '\
|
||||||
|
'sudo echo "iface {link} inet dhcp" >> {net_file} && '\
|
||||||
|
'sudo chmod 664 {net_file} || '\
|
||||||
|
'sudo nmcli con add type ethernet con-name et ifname {link}'\
|
||||||
|
.format(net_file='/etc/network/interfaces', link=link)
|
||||||
|
ssh_client.exec_command(cmd)
|
||||||
|
self.os_adm.servers_client.reboot_server(server['id'],
|
||||||
|
**{"type": "SOFT"})
|
||||||
|
|
||||||
|
def create_network_with_segment(self):
|
||||||
|
"""Creates dummy provider network with segment
|
||||||
|
|
||||||
|
This function is helpful when you need to check the configuration
|
||||||
|
or the database entries as it will not take any parameters for
|
||||||
|
the created network and so all the connectivity tests will fail
|
||||||
|
"""
|
||||||
|
segments = self.client.list_segments()
|
||||||
|
for segment in segments['segments']:
|
||||||
|
if segment['physical_network'] and \
|
||||||
|
segment['network_type'] == 'vlan':
|
||||||
|
physical_network = segment['physical_network']
|
||||||
|
break
|
||||||
|
net = self.create_network(external=True,
|
||||||
|
shared=True,
|
||||||
|
provider_network_type='vlan',
|
||||||
|
provider_physical_network=physical_network,
|
||||||
|
provider_segmentation_id='666')
|
||||||
|
net_id = net['id']
|
||||||
|
segment = self.client.list_segments(**{'network_id': net_id})
|
||||||
|
segment_id = segment['segments'][0]['id']
|
||||||
|
return [net_id, segment_id]
|
||||||
|
|
||||||
|
def create_segment_for_network(self, network_id):
|
||||||
|
"""Creates dummy segment for the provided network
|
||||||
|
|
||||||
|
This function is helpful when you need to check the configuration
|
||||||
|
or the database entries as it will not take any valuable parameters for
|
||||||
|
the segment to be created
|
||||||
|
"""
|
||||||
|
segments = self.client.list_segments()
|
||||||
|
for segment in segments['segments']:
|
||||||
|
if segment['physical_network'] and \
|
||||||
|
segment['network_type'] == 'vlan':
|
||||||
|
physical_network = segment['physical_network']
|
||||||
|
break
|
||||||
|
segment = self.client.create_segment(
|
||||||
|
**{'network_id': network_id,
|
||||||
|
'physical_network': physical_network,
|
||||||
|
'network_type': 'vlan',
|
||||||
|
'segmentation_id': '654'})['segment']
|
||||||
|
return segment['id']
|
||||||
|
|
||||||
|
|
||||||
|
class ProviderRoutedNetworkOVNConfigTest(ProviderRoutedNetworkBaseTest,
|
||||||
|
base.BaseTempestTestCaseOvn):
|
||||||
|
"""Class contains tests for verification routed provider network OVN config
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_host_ovn_bridge_mapping(self, host):
|
||||||
|
"""Get the bridge mapping from the host
|
||||||
|
|
||||||
|
Mapping has the following format: '"leaf0:br-ex"'. Need to be careful
|
||||||
|
with quotes. We return the dictionalry with bridge name as a key and
|
||||||
|
physical network name as a value
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not hasattr(self, 'nodes'):
|
||||||
|
self.discover_nodes()
|
||||||
|
cmd = 'sudo ovs-vsctl get open . external_ids:ovn-bridge-mappings'
|
||||||
|
for node in self.nodes:
|
||||||
|
if host.startswith(node['name']):
|
||||||
|
output = node['client'].exec_command(cmd).strip()
|
||||||
|
if output:
|
||||||
|
mapping = {}
|
||||||
|
bridges = output.strip('"').split(',')
|
||||||
|
for bridge in bridges:
|
||||||
|
bridge_name = bridge.split(':')[1]
|
||||||
|
physical_network_name = bridge.split(':')[0]
|
||||||
|
mapping[bridge_name] = physical_network_name
|
||||||
|
return mapping
|
||||||
|
return {}
|
||||||
|
|
||||||
|
@decorators.idempotent_id('2c2ee2b7-ab4f-4b2b-aeb8-66ddad841129')
|
||||||
|
def test_bridge_mappings(self):
|
||||||
|
"""Verifies that correct physical networks attached to computes
|
||||||
|
|
||||||
|
Each compute node should have the physical network attached according
|
||||||
|
to its availability zone.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ext_bridge = WB_CONF.ext_bridge
|
||||||
|
for _, details in self.resources.items():
|
||||||
|
for host in details['hosts']:
|
||||||
|
mapping = self.get_host_ovn_bridge_mapping(host)
|
||||||
|
physical_network = mapping.get(ext_bridge, '')
|
||||||
|
self.assertEqual(details['physical_network'], physical_network)
|
||||||
|
|
||||||
|
@decorators.idempotent_id('93df90c3-de2e-4e2d-9ec3-82dc7fe4a8f3')
|
||||||
|
def test_shared_network_ovn_db_configuration(self):
|
||||||
|
"""Test that all external networks and ports are created in OVN DB
|
||||||
|
|
||||||
|
There supposed to be separate logical switch for each external
|
||||||
|
network. Each logical switch should contain ports for every segment
|
||||||
|
that is available for the external network.
|
||||||
|
"""
|
||||||
|
port_to_net = []
|
||||||
|
port_to_physical_net = []
|
||||||
|
for _, details in self.resources.items():
|
||||||
|
for net_id, net_details in details['networks'].items():
|
||||||
|
for segment_id in net_details:
|
||||||
|
port_name = 'provnet-{sid}'.format(sid=segment_id)
|
||||||
|
phys_net = details['physical_network']
|
||||||
|
net_name = 'neutron-{nid}'.format(nid=net_id)
|
||||||
|
port_to_net.append([port_name, net_name])
|
||||||
|
port_to_physical_net.append([port_name, phys_net])
|
||||||
|
for port, net in port_to_net:
|
||||||
|
self.assertEqual(net, self.get_logical_switch(port))
|
||||||
|
for port, net in port_to_physical_net:
|
||||||
|
self.assertEqual(net, self.get_physical_net(port))
|
||||||
|
|
||||||
|
@decorators.idempotent_id('0de60d28-5c63-4825-b2a9-f5c070a7e7a3')
|
||||||
|
def test_segment_removed_from_ovn_db(self):
|
||||||
|
"""Test that segment is removed from the OVN DB correctly
|
||||||
|
|
||||||
|
When we delete the network segment we should not see it in the OVN
|
||||||
|
database anymore. We should make sure that the relevant records are
|
||||||
|
created before we will verify the deletion.
|
||||||
|
"""
|
||||||
|
network_id, segment_id = self.create_network_with_segment()
|
||||||
|
new_segment_id = self.create_segment_for_network(network_id)
|
||||||
|
for sid in segment_id, new_segment_id:
|
||||||
|
port = 'provnet-{sid}'.format(sid=sid)
|
||||||
|
net = 'neutron-{nid}'.format(nid=network_id)
|
||||||
|
self.assertEqual(net, self.get_logical_switch(port))
|
||||||
|
self.client.delete_segment(new_segment_id)
|
||||||
|
self.verify_that_segment_deleted(new_segment_id)
|
||||||
|
|
||||||
|
|
||||||
|
class RoutedProviderNetworkConnectivityTests(ProviderRoutedNetworkBaseTest):
|
||||||
|
"""Class to test connectivity through the routed provider networks"""
|
||||||
|
|
||||||
|
def _get_pingable_ips(self, servers):
|
||||||
|
"""Get all the IP addresses assigned to servers from the list
|
||||||
|
|
||||||
|
All IP addresses are divided by protocol (IPv4 and IPv6)
|
||||||
|
"""
|
||||||
|
ips_to_ping = {4: [], 6: []}
|
||||||
|
for server in servers:
|
||||||
|
srv_details = self.os_adm.servers_client.show_server(server['id'])
|
||||||
|
for addresses in srv_details['server']['addresses'].values():
|
||||||
|
for address in addresses:
|
||||||
|
ips_to_ping[address['version']].append(address['addr'])
|
||||||
|
return ips_to_ping
|
||||||
|
|
||||||
|
def _get_servers_ssh_clients(self, servers, key=None):
|
||||||
|
"""Get list of ssh clients together with supported IP versions
|
||||||
|
"""
|
||||||
|
client = self.os_adm.servers_client
|
||||||
|
ssh_clients = []
|
||||||
|
for srv in servers:
|
||||||
|
srv_info = client.show_server(srv['id'])['server']
|
||||||
|
supported_ip_versions = set()
|
||||||
|
for addresses in srv_info['addresses'].values():
|
||||||
|
for address in addresses:
|
||||||
|
supported_ip_versions.add(address['version'])
|
||||||
|
ssh_client = self.get_provider_network_ssh_client(srv_info, key)
|
||||||
|
self.assertIsNotNone(ssh_client)
|
||||||
|
ssh_clients.append([ssh_client, supported_ip_versions])
|
||||||
|
return ssh_clients
|
||||||
|
|
||||||
|
def _ping_supported_ips(self, ssh_client, ips_to_ping, ip_versions):
|
||||||
|
"""Try to ping all the IP addresses of supported version using ssh
|
||||||
|
"""
|
||||||
|
for ip_ver in ip_versions:
|
||||||
|
for ip_addr in ips_to_ping.get(ip_ver, []):
|
||||||
|
self.check_remote_connectivity(ssh_client, ip_addr)
|
||||||
|
|
||||||
|
@decorators.idempotent_id('2250efcc-6ff9-493a-9884-a7164e88b0fe')
|
||||||
|
def test_cross_segment_connectivity(self):
|
||||||
|
"""Test connectivity between different segments
|
||||||
|
|
||||||
|
Connectivity is verified between different segments and different
|
||||||
|
networks too if more than one provider network is created
|
||||||
|
"""
|
||||||
|
servers = []
|
||||||
|
for az, details in self.resources.items():
|
||||||
|
for net_id in details.get('networks'):
|
||||||
|
servers.append(self.create_server_with_params(
|
||||||
|
nets=(net_id, ),
|
||||||
|
az=az))
|
||||||
|
for srv in servers:
|
||||||
|
self.wait_for_server_ready(srv)
|
||||||
|
ssh_clients = self._get_servers_ssh_clients(servers)
|
||||||
|
ips_to_ping = self._get_pingable_ips(servers)
|
||||||
|
for ssh_client, ip_versions in ssh_clients:
|
||||||
|
self._ping_supported_ips(ssh_client, ips_to_ping, ip_versions)
|
||||||
|
|
||||||
|
@decorators.idempotent_id('f6846357-ab69-40d0-86d6-1f2574adb070')
|
||||||
|
def test_cross_segment_connectivity_with_preconfigured_ports(self):
|
||||||
|
"""Test connectivity between different segments with preconf ports
|
||||||
|
|
||||||
|
Connectivity is verified between different segments and different
|
||||||
|
networks too if more than one provider network is created. Servers are
|
||||||
|
spawened with preconfigured ports
|
||||||
|
"""
|
||||||
|
servers = []
|
||||||
|
for az, details in self.resources.items():
|
||||||
|
for net_id, net_details in details.get('networks', {}).items():
|
||||||
|
for subnets in net_details.values():
|
||||||
|
fixed_subnets = []
|
||||||
|
for subnet_id in subnets:
|
||||||
|
fixed_subnets.append({'subnet_id': subnet_id})
|
||||||
|
port_id = self.create_port(
|
||||||
|
network={'id': net_id},
|
||||||
|
security_groups=[self.secgroup['id']],
|
||||||
|
fixed_ips=fixed_subnets)['id']
|
||||||
|
servers.append(self.create_server_with_params(
|
||||||
|
ports=(port_id, ),
|
||||||
|
az=az))
|
||||||
|
for srv in servers:
|
||||||
|
self.wait_for_server_ready(srv)
|
||||||
|
ssh_clients = self._get_servers_ssh_clients(servers)
|
||||||
|
ips_to_ping = self._get_pingable_ips(servers)
|
||||||
|
for ssh_client, ip_versions in ssh_clients:
|
||||||
|
self._ping_supported_ips(ssh_client, ips_to_ping, ip_versions)
|
||||||
|
|
||||||
|
@decorators.idempotent_id('4e49bf45-b928-4689-afc3-a75736f83891')
|
||||||
|
def test_create_server_by_non_admin_user(self):
|
||||||
|
"""Create server in provider network by non admin user"""
|
||||||
|
|
||||||
|
client = self.os_primary.servers_client
|
||||||
|
key = self.create_keypair(client=self.os_primary.keypairs_client)
|
||||||
|
sg_name = data_utils.rand_name('secgroup')
|
||||||
|
sg = self.os_primary.network_client.create_security_group(
|
||||||
|
name=sg_name)['security_group']
|
||||||
|
self.security_groups.append(sg)
|
||||||
|
self.create_loginable_secgroup_rule(secgroup_id=sg['id'],
|
||||||
|
client=self.client)
|
||||||
|
self.create_pingable_secgroup_rule(secgroup_id=sg['id'],
|
||||||
|
client=self.client)
|
||||||
|
servers = []
|
||||||
|
for az, az_details in self.resources.items():
|
||||||
|
for network in az_details['networks']:
|
||||||
|
network_id = network
|
||||||
|
break
|
||||||
|
params = {'name': data_utils.rand_name(self._testMethodName),
|
||||||
|
'networks': [{'uuid': network_id}],
|
||||||
|
'flavorRef': CONF.compute.flavor_ref,
|
||||||
|
'imageRef': CONF.compute.image_ref,
|
||||||
|
'key_name': key['name'],
|
||||||
|
'security_groups': [{'name': sg_name}],
|
||||||
|
'availability_zone': az}
|
||||||
|
server = client.create_server(**params)['server']
|
||||||
|
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
|
||||||
|
waiters.wait_for_server_termination,
|
||||||
|
self.servers_client,
|
||||||
|
server['id'])
|
||||||
|
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
|
||||||
|
self.servers_client.delete_server,
|
||||||
|
server['id'])
|
||||||
|
servers.append(server)
|
||||||
|
for srv in servers:
|
||||||
|
server = self.wait_for_server_ready(srv)
|
||||||
|
ssh_clients = self._get_servers_ssh_clients(servers, key)
|
||||||
|
ips_to_ping = self._get_pingable_ips(servers)
|
||||||
|
for ssh_client, ip_versions in ssh_clients:
|
||||||
|
self._ping_supported_ips(ssh_client, ips_to_ping, ip_versions)
|
||||||
|
|
||||||
|
@decorators.idempotent_id('329342b5-9ec6-433a-96bc-82619a549343')
|
||||||
|
def test_instance_with_two_provider_networks_connectivity(self):
|
||||||
|
"""Test connectivity between servers with two interfaces
|
||||||
|
|
||||||
|
Servers are created with two interfaces in two different networks
|
||||||
|
one server per availability zone.
|
||||||
|
"""
|
||||||
|
nets = self.get_networks_with_segments()
|
||||||
|
if len(nets) < 2:
|
||||||
|
raise self.skipException('Only one provider network available')
|
||||||
|
servers = []
|
||||||
|
for az in self.resources:
|
||||||
|
servers.append(self.create_server_with_params(
|
||||||
|
nets=(nets[0], nets[1]), az=az))
|
||||||
|
for srv in servers:
|
||||||
|
server = self.wait_for_server_ready(srv)
|
||||||
|
self.verify_ips_on_interfaces(server)
|
||||||
|
for srv in servers:
|
||||||
|
server = self.wait_for_server_ready(srv)
|
||||||
|
ssh_clients = self._get_servers_ssh_clients(servers)
|
||||||
|
ips_to_ping = self._get_pingable_ips(servers)
|
||||||
|
for ssh_client, ip_versions in ssh_clients:
|
||||||
|
self._ping_supported_ips(ssh_client, ips_to_ping, ip_versions)
|
||||||
|
|
||||||
|
|
||||||
|
class RoutedProviderNetworkMigrationTests(
|
||||||
|
ProviderRoutedNetworkBaseTest, base.BaseTempestWhiteboxTestCase):
|
||||||
|
"""Class to test migration in routed provider networks
|
||||||
|
|
||||||
|
Migration should work in only availability zones with more than one
|
||||||
|
compute node available. If there is no availability zone with a single
|
||||||
|
compute node the negative tests will be skipped
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resource_setup(cls):
|
||||||
|
super(RoutedProviderNetworkMigrationTests, cls).resource_setup()
|
||||||
|
cls.overcloudrc_path = cls.get_stack_rc_file_path()
|
||||||
|
|
||||||
|
def _migrate_servers(self, servers, live_migration=False):
|
||||||
|
"""Perform and verify migration for several VMs"""
|
||||||
|
if live_migration:
|
||||||
|
block_migration = (CONF.compute_feature_enabled.
|
||||||
|
block_migration_for_live_migration)
|
||||||
|
for srv in servers:
|
||||||
|
self.servers_client.live_migrate_server(
|
||||||
|
srv['id'],
|
||||||
|
block_migration=block_migration,
|
||||||
|
host=None,
|
||||||
|
disk_over_commit=False)
|
||||||
|
else:
|
||||||
|
for srv in servers:
|
||||||
|
self.servers_client.migrate_server(srv['id'])
|
||||||
|
for srv in servers:
|
||||||
|
waiters.wait_for_server_status(self.servers_client,
|
||||||
|
srv['id'],
|
||||||
|
'VERIFY_RESIZE')
|
||||||
|
self.servers_client.confirm_resize_server(srv['id'])
|
||||||
|
for srv in servers:
|
||||||
|
server = self.wait_for_server_ready(srv)
|
||||||
|
old_host = srv['OS-EXT-SRV-ATTR:host']
|
||||||
|
new_host = server['OS-EXT-SRV-ATTR:host']
|
||||||
|
self.assertNotEqual(old_host, new_host, 'VM did not migrate')
|
||||||
|
self.assertIsNotNone(
|
||||||
|
self.get_provider_network_ssh_client(server, timeout=60))
|
||||||
|
|
||||||
|
@testtools.skipUnless(CONF.compute_feature_enabled.live_migration,
|
||||||
|
'Live migration is not available.')
|
||||||
|
@testtools.skipUnless(
|
||||||
|
CONF.compute_feature_enabled.block_migration_for_live_migration,
|
||||||
|
'Block migration for live migration is not available.')
|
||||||
|
@decorators.idempotent_id('337c57f9-5dd5-4cc2-b68e-a14701081735')
|
||||||
|
def test_live_migration_in_multiple_compute_zone(self):
|
||||||
|
"""Test live migration in availability zone with more than one compute
|
||||||
|
"""
|
||||||
|
skip_migration = True
|
||||||
|
servers = []
|
||||||
|
servers_details = []
|
||||||
|
for az, details in self.resources.items():
|
||||||
|
if len(details.get('hosts', [])) > 1:
|
||||||
|
skip_migration = False
|
||||||
|
servers.append(self.create_server_with_params(az=az))
|
||||||
|
if skip_migration:
|
||||||
|
raise self.skipException(
|
||||||
|
'No availability zone with more than one compute')
|
||||||
|
for srv in servers:
|
||||||
|
servers_details.append(self.wait_for_server_ready(srv))
|
||||||
|
self._migrate_servers(servers_details, live_migration=True)
|
||||||
|
|
||||||
|
@testtools.skipUnless(CONF.compute_feature_enabled.live_migration,
|
||||||
|
'Live migration is not available.')
|
||||||
|
@testtools.skipUnless(
|
||||||
|
CONF.compute_feature_enabled.block_migration_for_live_migration,
|
||||||
|
'Block migration for live migration is not available.')
|
||||||
|
@decorators.idempotent_id('d9f8c7ea-8469-4be7-86b3-e89113c5b8e0')
|
||||||
|
def test_live_migration_in_single_compute_zone(self):
|
||||||
|
"""Test live migration in availability zone with one compute"""
|
||||||
|
skip_migration = True
|
||||||
|
servers = []
|
||||||
|
for az, details in self.resources.items():
|
||||||
|
if len(details.get('hosts', [])) == 1:
|
||||||
|
skip_migration = False
|
||||||
|
servers.append(self.create_server_with_params(az=az))
|
||||||
|
if skip_migration:
|
||||||
|
raise self.skipException(
|
||||||
|
'No availability zone with only one compute')
|
||||||
|
for srv in servers:
|
||||||
|
self.wait_for_server_ready(srv)
|
||||||
|
self.assertRaises(lib_exc.BadRequest,
|
||||||
|
self._migrate_servers,
|
||||||
|
[srv, ],
|
||||||
|
live_migration=True)
|
||||||
|
|
||||||
|
@testtools.skipUnless(CONF.compute_feature_enabled.cold_migration,
|
||||||
|
'Cold migration is not available.')
|
||||||
|
@decorators.idempotent_id('e9ca4aee-3b90-467a-b6c8-d9f97afbf6ae')
|
||||||
|
def test_cold_migration_in_multiple_compute_zone(self):
|
||||||
|
"""Test cold migration in availability zone with more than one compute
|
||||||
|
"""
|
||||||
|
skip_migration = True
|
||||||
|
servers = []
|
||||||
|
servers_details = []
|
||||||
|
for az, details in self.resources.items():
|
||||||
|
if len(details.get('hosts', [])) > 1:
|
||||||
|
skip_migration = False
|
||||||
|
servers.append(self.create_server_with_params(az=az))
|
||||||
|
if skip_migration:
|
||||||
|
raise self.skipException(
|
||||||
|
'No availability zone with more than one compute')
|
||||||
|
for srv in servers:
|
||||||
|
servers_details.append(self.wait_for_server_ready(srv))
|
||||||
|
self._migrate_servers(servers_details)
|
||||||
|
|
||||||
|
@testtools.skipUnless(CONF.compute_feature_enabled.cold_migration,
|
||||||
|
'Cold migration is not available.')
|
||||||
|
@decorators.attr(type='negative')
|
||||||
|
@decorators.idempotent_id('7fc70f13-5712-46d9-ba17-5d7c6b73b203')
|
||||||
|
def test_negative_cold_migration_in_single_compute_zone(self):
|
||||||
|
"""Negative test cold migration in availability zone with one compute
|
||||||
|
"""
|
||||||
|
wait_err = (
|
||||||
|
"Negative test failed, didn't respond with 400 bad request "
|
||||||
|
"status or showed error state when migration was listed.")
|
||||||
|
skip_migration = True
|
||||||
|
servers = []
|
||||||
|
for az, details in self.resources.items():
|
||||||
|
if len(details.get('hosts', [])) == 1:
|
||||||
|
skip_migration = False
|
||||||
|
servers.append(self.create_server_with_params(az=az))
|
||||||
|
if skip_migration:
|
||||||
|
raise self.skipException(
|
||||||
|
'No availability zone with only one compute')
|
||||||
|
for srv in servers:
|
||||||
|
self.wait_for_server_ready(srv)
|
||||||
|
try:
|
||||||
|
self.assertRaises(lib_exc.BadRequest,
|
||||||
|
self._migrate_servers,
|
||||||
|
[srv, ])
|
||||||
|
# Nova API on OSP>17 sets error status when listing
|
||||||
|
# migrations instead of 400 response (BadRequest) for this test
|
||||||
|
except AssertionError:
|
||||||
|
cmd = ('. {} && '
|
||||||
|
'openstack server migration list --server {} '
|
||||||
|
' -f value -c Status').format(
|
||||||
|
self.overcloudrc_path, srv['id'])
|
||||||
|
utils.wait_until_true(lambda: self.validate_command(
|
||||||
|
cmd,
|
||||||
|
pattern='error',
|
||||||
|
ret_bool_pattern=True),
|
||||||
|
timeout=90,
|
||||||
|
sleep=10,
|
||||||
|
exception=RuntimeError(wait_err))
|
||||||
Reference in New Issue
Block a user