diff --git a/tobiko/openstack/neutron/_port.py b/tobiko/openstack/neutron/_port.py index 45d5624fb..fbadf1dd6 100644 --- a/tobiko/openstack/neutron/_port.py +++ b/tobiko/openstack/neutron/_port.py @@ -58,10 +58,13 @@ def list_device_ip_addresses(device_id: str, ip_version: typing.Optional[int] = None, check_connectivity: bool = False, ssh_client: ssh.SSHClientFixture = None, + need_dhcp: typing.Optional[bool] = None, **subnet_params) -> \ tobiko.Selection[netaddr.IPAddress]: ports = _client.list_ports(device_id=device_id, network_id=network_id) + if need_dhcp is not None: + subnet_params['enable_dhcp'] = bool(need_dhcp) subnets = _client.list_subnets(network_id=network_id, ip_version=ip_version, **subnet_params) diff --git a/tobiko/openstack/stacks/_cirros.py b/tobiko/openstack/stacks/_cirros.py index c08837d9d..009a0801a 100644 --- a/tobiko/openstack/stacks/_cirros.py +++ b/tobiko/openstack/stacks/_cirros.py @@ -53,6 +53,9 @@ class CirrosServerStackFixture(_nova.ServerStackFixture): #: Flavor used to create a Nova server instance flavor_stack = tobiko.required_setup_fixture(CirrosFlavorStackFixture) + #: CirrOS can't get IP addresses from config-drive + need_dhcp = True + class CirrosPeerServerStackFixture(CirrosServerStackFixture, _nova.PeerServerStackFixture): diff --git a/tobiko/openstack/stacks/_nova.py b/tobiko/openstack/stacks/_nova.py index f398608cb..d83094689 100644 --- a/tobiko/openstack/stacks/_nova.py +++ b/tobiko/openstack/stacks/_nova.py @@ -97,6 +97,9 @@ class ServerStackFixture(heat.HeatStackFixture, abc.ABC): #: stack with the internal where the server port is created network_stack = tobiko.required_setup_fixture(_neutron.NetworkStackFixture) + #: whenever the server relies only on DHCP for address assignation + need_dhcp = False + def create_stack(self, retry=None): self.ensure_quota_limits() super(ServerStackFixture, self).create_stack(retry=retry) diff --git a/tobiko/tests/scenario/neutron/test_port.py b/tobiko/tests/scenario/neutron/test_port.py index 0f4946856..6ab296e2a 100644 --- a/tobiko/tests/scenario/neutron/test_port.py +++ b/tobiko/tests/scenario/neutron/test_port.py @@ -41,15 +41,28 @@ class PortTest(testtools.TestCase): def test_port_ips(self, ip_version: typing.Optional[int] = None): """Checks port IPS has been assigned to server via DHCP protocol""" - device_ips = set(neutron.list_device_ip_addresses( + port_ips = set(neutron.list_device_ip_addresses( device_id=self.stack.server_id, network_id=self.stack.network_stack.network_id, - enable_dhcp=True, + need_dhcp=self.stack.need_dhcp, ip_version=ip_version)) - if device_ips: - server_ips = set(ip.list_ip_addresses( - scope='global', ssh_client=self.stack.ssh_client)) - self.assertEqual(device_ips, device_ips & server_ips) + if port_ips: + # verify neutron port IPs and VM port IPs match + # when a VM connected to the external network has been just + # created, it may need some time to receive its IPv6 address + for attempt in tobiko.retry(timeout=60., interval=4.): + server_ips = set(ip.list_ip_addresses( + scope='global', ssh_client=self.stack.ssh_client)) + server_ips &= port_ips # ignore other server IPs + LOG.debug("Neutron IPs and VM IPs should match...") + try: + self.assertEqual( + port_ips, server_ips, + f"Server {self.stack.server_id} is missing port " + f"IP(s): {port_ips - server_ips}") + break + except self.failureException: + attempt.check_limits() elif ip_version: self.skipTest(f"No port IPv{ip_version} addresses found") else: