2d66355407
When ssh_client is passed, the argument is not used. Change-Id: Ibf73130fbf82c2ed85e16b3f69aacbc2930efb3d
483 lines
21 KiB
Python
483 lines
21 KiB
Python
# 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 collections
|
|
|
|
from neutron_lib import constants
|
|
from neutron_lib.utils import test
|
|
from oslo_log import log as logging
|
|
from tempest.common import utils as tutils
|
|
from tempest.lib.common.utils import data_utils
|
|
from tempest.lib import decorators
|
|
import testtools
|
|
|
|
from neutron_tempest_plugin.common import ip
|
|
from neutron_tempest_plugin.common import ssh
|
|
from neutron_tempest_plugin.common import utils
|
|
from neutron_tempest_plugin import config
|
|
from neutron_tempest_plugin.scenario import base
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
CONF = config.CONF
|
|
|
|
|
|
ServerWithTrunkPort = collections.namedtuple(
|
|
'ServerWithTrunkPort',
|
|
['port', 'subport', 'trunk', 'floating_ip', 'server',
|
|
'ssh_client'])
|
|
|
|
|
|
class TrunkTest(base.BaseTempestTestCase):
|
|
credentials = ['primary', 'admin']
|
|
force_tenant_isolation = False
|
|
|
|
@classmethod
|
|
@tutils.requires_ext(extension="trunk", service="network")
|
|
def resource_setup(cls):
|
|
super(TrunkTest, cls).resource_setup()
|
|
# setup basic topology for servers we can log into
|
|
cls.rand_name = data_utils.rand_name(
|
|
cls.__name__.rsplit('.', 1)[-1])
|
|
cls.network = cls.create_network(name=cls.rand_name)
|
|
cls.subnet = cls.create_subnet(network=cls.network,
|
|
name=cls.rand_name)
|
|
cls.router = cls.create_router_by_client()
|
|
cls.create_router_interface(cls.router['id'], cls.subnet['id'])
|
|
cls.keypair = cls.create_keypair(name=cls.rand_name)
|
|
|
|
def setUp(self):
|
|
super(TrunkTest, self).setUp()
|
|
self.security_group = self.create_security_group(name=self.rand_name)
|
|
self.create_loginable_secgroup_rule(self.security_group['id'])
|
|
|
|
def _create_server_with_network(self, network, use_advanced_image=False):
|
|
port = self._create_server_port(network=network)
|
|
floating_ip = self.create_floatingip(port=port)
|
|
ssh_client = self._create_ssh_client(
|
|
floating_ip=floating_ip, use_advanced_image=use_advanced_image)
|
|
server = self._create_server(port=port,
|
|
use_advanced_image=use_advanced_image)
|
|
return ServerWithTrunkPort(port=port, subport=None, trunk=None,
|
|
floating_ip=floating_ip, server=server,
|
|
ssh_client=ssh_client)
|
|
|
|
def _create_server_with_trunk_port(self, subport_network=None,
|
|
segmentation_id=None,
|
|
use_advanced_image=False):
|
|
port = self._create_server_port()
|
|
floating_ip = self.create_floatingip(port=port)
|
|
ssh_client = self._create_ssh_client(
|
|
floating_ip=floating_ip, use_advanced_image=use_advanced_image)
|
|
|
|
subport = None
|
|
subports = None
|
|
if subport_network:
|
|
subport = self._create_server_port(
|
|
network=subport_network, mac_address=port['mac_address'])
|
|
subports = [{'port_id': subport['id'],
|
|
'segmentation_type': 'vlan',
|
|
'segmentation_id': segmentation_id}]
|
|
trunk = self.create_trunk(port=port, subports=subports)
|
|
|
|
server = self._create_server(port=port,
|
|
use_advanced_image=use_advanced_image)
|
|
return ServerWithTrunkPort(port=port, subport=subport, trunk=trunk,
|
|
floating_ip=floating_ip, server=server,
|
|
ssh_client=ssh_client)
|
|
|
|
def _create_advanced_servers_with_trunk_port(self, num_servers=1,
|
|
subport_network=None,
|
|
segmentation_id=None,
|
|
vlan_subnet=None,
|
|
use_advanced_image=False):
|
|
server_list = []
|
|
for _ in range(0, num_servers):
|
|
vm = self._create_server_with_trunk_port(
|
|
subport_network,
|
|
segmentation_id,
|
|
use_advanced_image)
|
|
server_list.append(vm)
|
|
self._configure_vlan_subport(
|
|
vm=vm,
|
|
vlan_tag=segmentation_id,
|
|
vlan_subnet=vlan_subnet)
|
|
|
|
return server_list
|
|
|
|
def _check_servers_remote_connectivity(self, vms=None,
|
|
should_succeed=True):
|
|
self.check_remote_connectivity(
|
|
vms[0].ssh_client,
|
|
vms[1].subport['fixed_ips'][0]['ip_address'],
|
|
should_succeed=should_succeed)
|
|
|
|
def _create_server_port(self, network=None, **params):
|
|
network = network or self.network
|
|
return self.create_port(network=network, name=self.rand_name,
|
|
security_groups=[self.security_group['id']],
|
|
**params)
|
|
|
|
def _create_server(self, port, use_advanced_image=False, **params):
|
|
if use_advanced_image:
|
|
flavor_ref = CONF.neutron_plugin_options.advanced_image_flavor_ref
|
|
image_ref = CONF.neutron_plugin_options.advanced_image_ref
|
|
else:
|
|
flavor_ref = CONF.compute.flavor_ref
|
|
image_ref = CONF.compute.image_ref
|
|
return self.create_server(flavor_ref=flavor_ref,
|
|
image_ref=image_ref,
|
|
key_name=self.keypair['name'],
|
|
networks=[{'port': port['id']}],
|
|
**params)['server']
|
|
|
|
def _show_port(self, port, update=False):
|
|
observed = self.client.show_port(port['id'])['port']
|
|
if update:
|
|
port.update(observed)
|
|
return observed
|
|
|
|
def _show_trunk(self, trunk, update=False):
|
|
observed = self.client.show_trunk(trunk['id'])['trunk']
|
|
if update:
|
|
trunk.update(observed)
|
|
return observed
|
|
|
|
def _is_trunk_status(self, trunk, status, update=False):
|
|
return self._show_trunk(trunk, update)['status'] == status
|
|
|
|
def _is_port_status(self, port, status, update=False):
|
|
return self._show_port(port, update)['status'] == status
|
|
|
|
def _wait_for_port(self, port, status=constants.ACTIVE):
|
|
utils.wait_until_true(
|
|
lambda: self._is_port_status(port, status),
|
|
exception=RuntimeError(
|
|
"Timed out waiting for port {!r} to transition to get "
|
|
"status {!r}.".format(port['id'], status)))
|
|
|
|
def _wait_for_trunk(self, trunk, status=constants.ACTIVE):
|
|
utils.wait_until_true(
|
|
lambda: self._is_trunk_status(trunk, status),
|
|
exception=RuntimeError(
|
|
"Timed out waiting for trunk {!r} to transition to get "
|
|
"status {!r}.".format(trunk['id'], status)))
|
|
|
|
def _create_ssh_client(self, floating_ip, use_advanced_image=False):
|
|
if use_advanced_image:
|
|
username = CONF.neutron_plugin_options.advanced_image_ssh_user
|
|
else:
|
|
username = CONF.validation.image_ssh_user
|
|
return ssh.Client(host=floating_ip['floating_ip_address'],
|
|
username=username,
|
|
pkey=self.keypair['private_key'])
|
|
|
|
def _assert_has_ssh_connectivity(self, ssh_client):
|
|
ssh_client.exec_command("true")
|
|
|
|
def _configure_vlan_subport(self, vm, vlan_tag, vlan_subnet):
|
|
self.wait_for_server_active(server=vm.server)
|
|
self.wait_for_guest_os_ready(vm.server)
|
|
self._wait_for_trunk(trunk=vm.trunk)
|
|
self._wait_for_port(port=vm.port)
|
|
self._wait_for_port(port=vm.subport)
|
|
self.check_connectivity(
|
|
ssh_client=vm.ssh_client,
|
|
servers=[vm.server])
|
|
|
|
ip_command = ip.IPCommand(ssh_client=vm.ssh_client)
|
|
for address in ip_command.list_addresses(port=vm.port):
|
|
port_iface = address.device.name
|
|
break
|
|
else:
|
|
self.fail("Parent port fixed IP not found on server.")
|
|
|
|
subport_iface = ip_command.configure_vlan_subport(
|
|
port=vm.port, subport=vm.subport, vlan_tag=vlan_tag,
|
|
subnets=[vlan_subnet])
|
|
for address in ip_command.list_addresses(port=vm.subport):
|
|
self.assertEqual(subport_iface, address.device.name)
|
|
self.assertEqual(port_iface, address.device.parent)
|
|
break
|
|
else:
|
|
self.fail("Sub-port fixed IP not found on server.")
|
|
|
|
@decorators.idempotent_id('bb13fe28-f152-4000-8131-37890a40c79e')
|
|
def test_trunk_subport_lifecycle(self):
|
|
"""Test trunk creation and subport transition to ACTIVE status.
|
|
|
|
This is a basic test for the trunk extension to ensure that we
|
|
can create a trunk, attach it to a server, add/remove subports,
|
|
while ensuring the status transitions as appropriate.
|
|
|
|
This test does not assert any dataplane behavior for the subports.
|
|
It's just a high-level check to ensure the agents claim to have
|
|
wired the port correctly and that the trunk port itself maintains
|
|
connectivity.
|
|
"""
|
|
vm1 = self._create_server_with_trunk_port()
|
|
vm2 = self._create_server_with_trunk_port()
|
|
for vm in (vm1, vm2):
|
|
self.wait_for_server_active(server=vm.server)
|
|
self.wait_for_guest_os_ready(vm.server)
|
|
self._wait_for_trunk(vm.trunk)
|
|
self._assert_has_ssh_connectivity(vm.ssh_client)
|
|
|
|
# create a few more networks and ports for subports
|
|
# check limit of networks per project
|
|
segment_ids = range(
|
|
3, 3 + CONF.neutron_plugin_options.max_networks_per_project)
|
|
tagged_networks = [self.create_network() for _ in segment_ids]
|
|
tagged_ports = [self.create_port(network=network)
|
|
for network in tagged_networks]
|
|
subports = [{'port_id': tagged_ports[i]['id'],
|
|
'segmentation_type': 'vlan',
|
|
'segmentation_id': segment_id}
|
|
for i, segment_id in enumerate(segment_ids)]
|
|
|
|
# add all subports to server1
|
|
self.client.add_subports(vm1.trunk['id'], subports)
|
|
self._wait_for_trunk(vm1.trunk)
|
|
for port in tagged_ports:
|
|
self._wait_for_port(port)
|
|
|
|
# ensure main data-plane wasn't interrupted
|
|
self._assert_has_ssh_connectivity(vm1.ssh_client)
|
|
|
|
# move subports over to other server
|
|
self.client.remove_subports(vm1.trunk['id'], subports)
|
|
# ensure all subports go down
|
|
for port in tagged_ports:
|
|
self._wait_for_port(port, status=constants.DOWN)
|
|
|
|
self.client.add_subports(vm2.trunk['id'], subports)
|
|
|
|
# wait for both trunks to go back to ACTIVE
|
|
for vm in [vm1, vm2]:
|
|
self._wait_for_trunk(vm.trunk)
|
|
|
|
# ensure subports come up on other trunk
|
|
for port in tagged_ports:
|
|
self._wait_for_port(port)
|
|
|
|
# final connectivity check
|
|
for vm in [vm1, vm2]:
|
|
self._wait_for_trunk(vm.trunk)
|
|
self._assert_has_ssh_connectivity(vm1.ssh_client)
|
|
|
|
@test.unstable_test("bug 2033887 / bug 2024160")
|
|
@testtools.skipUnless(
|
|
(CONF.neutron_plugin_options.advanced_image_ref or
|
|
CONF.neutron_plugin_options.default_image_is_advanced),
|
|
"Advanced image is required to run this test.")
|
|
@testtools.skipUnless(
|
|
(CONF.neutron_plugin_options.reboots_in_test > 0),
|
|
"Number of reboots > 0 is reqired for this test")
|
|
@decorators.idempotent_id('a8a02c9b-b453-49b5-89a2-cce7da6680fb')
|
|
def test_subport_connectivity_soft_reboot(self):
|
|
vlan_tag = 10
|
|
vlan_network = self.create_network()
|
|
vlan_subnet = self.create_subnet(network=vlan_network, gateway=None)
|
|
use_advanced_image = (
|
|
not CONF.neutron_plugin_options.default_image_is_advanced)
|
|
|
|
# allow intra-security-group traffic
|
|
sg_rule = self.create_pingable_secgroup_rule(self.security_group['id'])
|
|
self.addCleanup(
|
|
self.os_primary.network_client.delete_security_group_rule,
|
|
sg_rule['id'])
|
|
|
|
vms = self._create_advanced_servers_with_trunk_port(
|
|
num_servers=2,
|
|
subport_network=vlan_network,
|
|
segmentation_id=vlan_tag,
|
|
vlan_subnet=vlan_subnet,
|
|
use_advanced_image=use_advanced_image)
|
|
# check remote connectivity true before reboots
|
|
self._check_servers_remote_connectivity(vms=vms)
|
|
client = self.os_tempest.compute.ServersClient()
|
|
for _ in range(CONF.neutron_plugin_options.reboots_in_test):
|
|
client.reboot_server(vms[1].server['id'],
|
|
**{'type': 'SOFT'})
|
|
self.wait_for_server_active(vms[1].server)
|
|
self._configure_vlan_subport(vm=vms[1],
|
|
vlan_tag=vlan_tag,
|
|
vlan_subnet=vlan_subnet)
|
|
self._check_servers_remote_connectivity(vms=vms)
|
|
|
|
@test.unstable_test("bug 1897796")
|
|
@testtools.skipUnless(
|
|
(CONF.neutron_plugin_options.advanced_image_ref or
|
|
CONF.neutron_plugin_options.default_image_is_advanced),
|
|
"Advanced image is required to run this test.")
|
|
@decorators.idempotent_id('a8a02c9b-b453-49b5-89a2-cce7da66bbcb')
|
|
def test_subport_connectivity(self):
|
|
vlan_tag = 10
|
|
vlan_network = self.create_network()
|
|
vlan_subnet = self.create_subnet(network=vlan_network, gateway=None)
|
|
use_advanced_image = (
|
|
not CONF.neutron_plugin_options.default_image_is_advanced)
|
|
vms = self._create_advanced_servers_with_trunk_port(
|
|
num_servers=2,
|
|
subport_network=vlan_network,
|
|
segmentation_id=vlan_tag,
|
|
vlan_subnet=vlan_subnet,
|
|
use_advanced_image=use_advanced_image)
|
|
# Ping from server1 to server2 via VLAN interface should fail because
|
|
# we haven't allowed ICMP
|
|
self._check_servers_remote_connectivity(vms=vms,
|
|
should_succeed=False)
|
|
# allow intra-security-group traffic
|
|
sg_rule = self.create_pingable_secgroup_rule(self.security_group['id'])
|
|
self.addCleanup(
|
|
self.os_primary.network_client.delete_security_group_rule,
|
|
sg_rule['id'])
|
|
self._check_servers_remote_connectivity(vms=vms)
|
|
|
|
@testtools.skipUnless(CONF.compute_feature_enabled.cold_migration,
|
|
'Cold migration is not available.')
|
|
@testtools.skipUnless(CONF.compute.min_compute_nodes > 1,
|
|
'Less than 2 compute nodes, skipping multinode '
|
|
'tests.')
|
|
@testtools.skipUnless(
|
|
(CONF.neutron_plugin_options.advanced_image_ref or
|
|
CONF.neutron_plugin_options.default_image_is_advanced),
|
|
"Advanced image is required to run this test.")
|
|
@decorators.attr(type='slow')
|
|
@decorators.idempotent_id('ecd7de30-1c90-4280-b97c-1bed776d5d07')
|
|
def test_trunk_vm_migration(self):
|
|
'''Test connectivity after migration of the server with trunk
|
|
|
|
A successfully migrated server shows a VERIFY_RESIZE status that
|
|
requires confirmation. Need to reconfigure VLAN interface on server
|
|
side after migration is finished as the configuration doesn't survive
|
|
the reboot.
|
|
'''
|
|
vlan_tag = 10
|
|
vlan_network = self.create_network()
|
|
vlan_subnet = self.create_subnet(vlan_network)
|
|
sg_rule = self.create_pingable_secgroup_rule(self.security_group['id'])
|
|
self.addCleanup(
|
|
self.os_primary.network_client.delete_security_group_rule,
|
|
sg_rule['id'])
|
|
|
|
use_advanced_image = (
|
|
not CONF.neutron_plugin_options.default_image_is_advanced)
|
|
servers = {}
|
|
for role in ['migrate', 'connection_test']:
|
|
servers[role] = self._create_server_with_trunk_port(
|
|
subport_network=vlan_network,
|
|
segmentation_id=vlan_tag,
|
|
use_advanced_image=use_advanced_image)
|
|
for role in ['migrate', 'connection_test']:
|
|
self.wait_for_server_active(servers[role].server)
|
|
self.wait_for_guest_os_ready(servers[role].server)
|
|
self._configure_vlan_subport(vm=servers[role],
|
|
vlan_tag=vlan_tag,
|
|
vlan_subnet=vlan_subnet)
|
|
|
|
self.check_remote_connectivity(
|
|
servers['connection_test'].ssh_client,
|
|
servers['migrate'].subport['fixed_ips'][0]['ip_address'])
|
|
|
|
client = self.os_admin.compute.ServersClient()
|
|
client.migrate_server(servers['migrate'].server['id'])
|
|
self.wait_for_server_status(servers['migrate'].server,
|
|
'VERIFY_RESIZE')
|
|
client.confirm_resize_server(servers['migrate'].server['id'])
|
|
self._configure_vlan_subport(vm=servers['migrate'],
|
|
vlan_tag=vlan_tag,
|
|
vlan_subnet=vlan_subnet)
|
|
|
|
self.check_remote_connectivity(
|
|
servers['connection_test'].ssh_client,
|
|
servers['migrate'].subport['fixed_ips'][0]['ip_address'])
|
|
|
|
@testtools.skipUnless(
|
|
(CONF.neutron_plugin_options.advanced_image_ref or
|
|
CONF.neutron_plugin_options.default_image_is_advanced),
|
|
"Advanced image is required to run this test.")
|
|
@testtools.skipUnless(
|
|
CONF.neutron_plugin_options.q_agent == "linuxbridge",
|
|
"Linux bridge agent is required to run this test.")
|
|
@decorators.idempotent_id('d61cbdf6-1896-491c-b4b4-871caf7fbffe')
|
|
def test_parent_port_connectivity_after_trunk_deleted_lb(self):
|
|
vlan_tag = 10
|
|
vlan_network = self.create_network()
|
|
vlan_subnet = self.create_subnet(vlan_network)
|
|
self.create_router_interface(self.router['id'], vlan_subnet['id'])
|
|
|
|
use_advanced_image = (
|
|
not CONF.neutron_plugin_options.default_image_is_advanced)
|
|
|
|
# Create servers
|
|
trunk_network_server = self._create_server_with_trunk_port(
|
|
subport_network=vlan_network,
|
|
segmentation_id=vlan_tag,
|
|
use_advanced_image=use_advanced_image)
|
|
normal_network_server = self._create_server_with_network(self.network)
|
|
vlan_network_server = self._create_server_with_network(vlan_network)
|
|
vms = [normal_network_server, vlan_network_server]
|
|
|
|
self._configure_vlan_subport(vm=trunk_network_server,
|
|
vlan_tag=vlan_tag,
|
|
vlan_subnet=vlan_subnet)
|
|
for vm in vms:
|
|
self.wait_for_server_active(vm.server)
|
|
self.wait_for_guest_os_ready(vm.server)
|
|
|
|
# allow ICMP traffic
|
|
sg_rule = self.create_pingable_secgroup_rule(self.security_group['id'])
|
|
self.addCleanup(
|
|
self.os_primary.network_client.delete_security_group_rule,
|
|
sg_rule['id'])
|
|
|
|
# Ping from trunk_network_server to normal_network_server
|
|
# via parent port
|
|
self.check_remote_connectivity(
|
|
trunk_network_server.ssh_client,
|
|
normal_network_server.port['fixed_ips'][0]['ip_address'],
|
|
should_succeed=True,
|
|
servers=vms)
|
|
|
|
# Ping from trunk_network_server to vlan_network_server via VLAN
|
|
# interface should success
|
|
self.check_remote_connectivity(
|
|
trunk_network_server.ssh_client,
|
|
vlan_network_server.port['fixed_ips'][0]['ip_address'],
|
|
should_succeed=True,
|
|
servers=vms)
|
|
|
|
# Delete the trunk
|
|
self.delete_trunk(
|
|
trunk_network_server.trunk,
|
|
detach_parent_port=False)
|
|
LOG.debug("Trunk %s is deleted.",
|
|
trunk_network_server.trunk['id'])
|
|
|
|
# Ping from trunk_network_server to normal_network_server
|
|
# via parent port success after trunk deleted
|
|
self.check_remote_connectivity(
|
|
trunk_network_server.ssh_client,
|
|
normal_network_server.port['fixed_ips'][0]['ip_address'],
|
|
should_succeed=True,
|
|
servers=vms)
|
|
|
|
# Ping from trunk_network_server to vlan_network_server via VLAN
|
|
# interface should fail after trunk deleted
|
|
self.check_remote_connectivity(
|
|
trunk_network_server.ssh_client,
|
|
vlan_network_server.port['fixed_ips'][0]['ip_address'],
|
|
should_succeed=False)
|