Add a test for attach/detach port on multiple servers

A regression was introduced in nova in newton when you
attach the same port across multiple instances (not at
the same time). The problem was nova started creating
some internal resources on attach but wasn't cleaning
them up on detach, so when you'd tried to attach the same
port to a second server it would fail because of a
unique constraint on the internal resource that nova creates.

We should have an integration test that covers this scenario
so we don't regress it again.

This change covers both booting servers with a pre-created
port and attaching a pre-created port to existing servers.

Depends-On: I2254bad0df3ccc00cd5c9438fa2684e705442e2d

Change-Id: I469b8ec426bd71dea515e99f76d415c62fff7dd3
Related-Bug: #1602357
This commit is contained in:
Matt Riedemann 2016-07-12 17:07:33 -04:00
parent ad37b61be6
commit a8c641a96e
2 changed files with 81 additions and 0 deletions

View File

@ -16,7 +16,9 @@
import time
from tempest.api.compute import base
from tempest.common import compute
from tempest.common.utils import net_utils
from tempest.common import waiters
from tempest import config
from tempest import exceptions
from tempest.lib import exceptions as lib_exc
@ -48,6 +50,7 @@ class AttachInterfacesTestJSON(base.BaseV2ComputeTest):
cls.networks_client = cls.os.networks_client
cls.subnets_client = cls.os.subnets_client
cls.ports_client = cls.os.ports_client
cls.servers_client = cls.servers_client
def wait_for_interface_status(self, server, port_id, status):
"""Waits for an interface to reach a given status."""
@ -73,6 +76,34 @@ class AttachInterfacesTestJSON(base.BaseV2ComputeTest):
return body
# TODO(mriedem): move this into a common waiters utility module
def wait_for_port_detach(self, port_id):
"""Waits for the port's device_id to be unset.
:param port_id: The id of the port being detached.
:returns: The final port dict from the show_port response.
"""
port = self.ports_client.show_port(port_id)['port']
device_id = port['device_id']
start = int(time.time())
# NOTE(mriedem): Nova updates the port's device_id to '' rather than
# None, but it's not contractual so handle Falsey either way.
while device_id:
time.sleep(self.build_interval)
port = self.ports_client.show_port(port_id)['port']
device_id = port['device_id']
timed_out = int(time.time()) - start >= self.build_timeout
if device_id and timed_out:
message = ('Port %s failed to detach (device_id %s) within '
'the required time (%s s).' %
(port_id, device_id, self.build_timeout))
raise exceptions.TimeoutException(message)
return port
def _check_interface(self, iface, port_id=None, network_id=None,
fixed_ip=None, mac_addr=None):
self.assertIn('port_state', iface)
@ -240,3 +271,40 @@ class AttachInterfacesTestJSON(base.BaseV2ComputeTest):
if fixed_ip is not None:
break
self.servers_client.remove_fixed_ip(server['id'], address=fixed_ip)
@test.idempotent_id('2f3a0127-95c7-4977-92d2-bc5aec602fb4')
def test_reassign_port_between_servers(self):
"""Tests the following:
1. Create a port in Neutron.
2. Create two servers in Nova.
3. Attach the port to the first server.
4. Detach the port from the first server.
5. Attach the port to the second server.
6. Detach the port from the second server.
"""
network = self.get_tenant_network()
network_id = network['id']
port = self.ports_client.create_port(network_id=network_id)
port_id = port['port']['id']
self.addCleanup(self.ports_client.delete_port, port_id)
# create two servers
_, servers = compute.create_test_server(
self.os, tenant_network=network, wait_until='ACTIVE', min_count=2)
# add our cleanups for the servers since we bypassed the base class
for server in servers:
self.addCleanup(waiters.wait_for_server_termination,
self.servers_client, server['id'])
self.addCleanup(self.servers_client.delete_server, server['id'])
for server in servers:
# attach the port to the server
iface = self.client.create_interface(
server['id'], port_id=port_id)['interfaceAttachment']
self._check_interface(iface, port_id=port_id)
# detach the port from the server; this is a cast in the compute
# API so we have to poll the port until the device_id is unset.
self.client.delete_interface(server['id'], port_id)
self.wait_for_port_detach(port_id)

View File

@ -642,6 +642,8 @@ class TestNetworkBasicOps(manager.NetworkScenarioTest):
Nova should unbind the port from the instance on delete if the port was
not created by Nova as part of the boot request.
We should also be able to boot another server with the same port.
"""
# Setup the network, create a port and boot the server from that port.
self._setup_network_and_servers(boot_with_port=True)
@ -670,6 +672,17 @@ class TestNetworkBasicOps(manager.NetworkScenarioTest):
self.assertEqual('', port['device_id'])
self.assertEqual('', port['device_owner'])
# Boot another server with the same port to make sure nothing was
# left around that could cause issues.
name = data_utils.rand_name('reuse-port')
server = self._create_server(name, self.network, port['id'])
port_list = self._list_ports(device_id=server['id'],
network_id=self.network['id'])
self.assertEqual(1, len(port_list),
'There should only be one port created for '
'server %s.' % server['id'])
self.assertEqual(port['id'], port_list[0]['id'])
@test.requires_ext(service='network', extension='l3_agent_scheduler')
@test.idempotent_id('2e788c46-fb3f-4ac9-8f82-0561555bea73')
@test.services('compute', 'network')