blob: 95fe20759f2a516bf0a3eee67b8dc9b95327dbf4 [file] [log] [blame]
Solio Sarabia60095ff2017-02-28 18:18:26 -06001# Copyright 2012 OpenStack Foundation
2# Copyright 2013 IBM Corp.
3# All Rights Reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16
17# NOTE(soliosg) Do not edit this file. It will only stay temporarily
18# in ironic, while QA refactors the tempest.scenario interface. This
19# file was copied from openstack/tempest/tempest/scenario/manager.py,
20# openstack/tempest commit: 82a278e88c9e9f9ba49f81c1f8dba0bca7943daf
21
22import subprocess
23
Solio Sarabia60095ff2017-02-28 18:18:26 -060024from oslo_log import log
Solio Sarabia60095ff2017-02-28 18:18:26 -060025from oslo_utils import netutils
Solio Sarabia60095ff2017-02-28 18:18:26 -060026from tempest.common import compute
Solio Sarabia60095ff2017-02-28 18:18:26 -060027from tempest.common.utils.linux import remote_client
28from tempest.common.utils import net_utils
29from tempest.common import waiters
30from tempest import config
31from tempest import exceptions
32from tempest.lib.common.utils import data_utils
33from tempest.lib.common.utils import test_utils
34from tempest.lib import exceptions as lib_exc
35import tempest.test
36
37CONF = config.CONF
38
39LOG = log.getLogger(__name__)
40
41
42class ScenarioTest(tempest.test.BaseTestCase):
43 """Base class for scenario tests. Uses tempest own clients. """
44
Julia Kreger3a07c4d2021-06-22 10:27:56 -070045 credentials = ['primary', 'admin', 'system_admin']
Solio Sarabia60095ff2017-02-28 18:18:26 -060046
47 @classmethod
48 def setup_clients(cls):
49 super(ScenarioTest, cls).setup_clients()
50 # Clients (in alphabetical order)
Vu Cong Tuanf825d192017-06-21 18:32:15 +070051 cls.flavors_client = cls.os_primary.flavors_client
Solio Sarabia60095ff2017-02-28 18:18:26 -060052 cls.compute_floating_ips_client = (
Vu Cong Tuanf825d192017-06-21 18:32:15 +070053 cls.os_primary.compute_floating_ips_client)
Solio Sarabia60095ff2017-02-28 18:18:26 -060054 if CONF.service_available.glance:
55 # Check if glance v1 is available to determine which client to use.
56 if CONF.image_feature_enabled.api_v1:
Vu Cong Tuanf825d192017-06-21 18:32:15 +070057 cls.image_client = cls.os_primary.image_client
Solio Sarabia60095ff2017-02-28 18:18:26 -060058 elif CONF.image_feature_enabled.api_v2:
Vu Cong Tuanf825d192017-06-21 18:32:15 +070059 cls.image_client = cls.os_primary.image_client_v2
Solio Sarabia60095ff2017-02-28 18:18:26 -060060 else:
61 raise lib_exc.InvalidConfiguration(
62 'Either api_v1 or api_v2 must be True in '
63 '[image-feature-enabled].')
64 # Compute image client
Vu Cong Tuanf825d192017-06-21 18:32:15 +070065 cls.compute_images_client = cls.os_primary.compute_images_client
66 cls.keypairs_client = cls.os_primary.keypairs_client
Solio Sarabia60095ff2017-02-28 18:18:26 -060067 # Nova security groups client
68 cls.compute_security_groups_client = (
Vu Cong Tuanf825d192017-06-21 18:32:15 +070069 cls.os_primary.compute_security_groups_client)
Solio Sarabia60095ff2017-02-28 18:18:26 -060070 cls.compute_security_group_rules_client = (
Vu Cong Tuanf825d192017-06-21 18:32:15 +070071 cls.os_primary.compute_security_group_rules_client)
72 cls.servers_client = cls.os_primary.servers_client
73 cls.interface_client = cls.os_primary.interfaces_client
Solio Sarabia60095ff2017-02-28 18:18:26 -060074 # Neutron network client
Vu Cong Tuanf825d192017-06-21 18:32:15 +070075 cls.networks_client = cls.os_primary.networks_client
76 cls.ports_client = cls.os_primary.ports_client
77 cls.routers_client = cls.os_primary.routers_client
78 cls.subnets_client = cls.os_primary.subnets_client
79 cls.floating_ips_client = cls.os_primary.floating_ips_client
80 cls.security_groups_client = cls.os_primary.security_groups_client
Solio Sarabia60095ff2017-02-28 18:18:26 -060081 cls.security_group_rules_client = (
Vu Cong Tuanf825d192017-06-21 18:32:15 +070082 cls.os_primary.security_group_rules_client)
Solio Sarabia60095ff2017-02-28 18:18:26 -060083
Ghanshyam Mann3b663f62019-12-12 17:01:16 +000084 cls.volumes_client = cls.os_primary.volumes_client_latest
85 cls.snapshots_client = cls.os_primary.snapshots_client_latest
Solio Sarabia60095ff2017-02-28 18:18:26 -060086
87 # ## Test functions library
88 #
89 # The create_[resource] functions only return body and discard the
90 # resp part which is not used in scenario tests
91
92 def _create_port(self, network_id, client=None, namestart='port-quotatest',
93 **kwargs):
94 if not client:
Julia Kreger3a07c4d2021-06-22 10:27:56 -070095 client = self.os_primary.ports_client
Solio Sarabia60095ff2017-02-28 18:18:26 -060096 name = data_utils.rand_name(namestart)
97 result = client.create_port(
98 name=name,
99 network_id=network_id,
100 **kwargs)
101 self.assertIsNotNone(result, 'Unable to allocate port')
102 port = result['port']
103 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
104 client.delete_port, port['id'])
105 return port
106
107 def create_keypair(self, client=None):
108 if not client:
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700109 client = self.os_primary.keypairs_client
Solio Sarabia60095ff2017-02-28 18:18:26 -0600110 name = data_utils.rand_name(self.__class__.__name__)
111 # We don't need to create a keypair by pubkey in scenario
112 body = client.create_keypair(name=name)
113 self.addCleanup(client.delete_keypair, name)
114 return body['keypair']
115
116 def create_server(self, name=None, image_id=None, flavor=None,
117 validatable=False, wait_until='ACTIVE',
118 clients=None, **kwargs):
119 """Wrapper utility that returns a test server.
120
121 This wrapper utility calls the common create test server and
122 returns a test server. The purpose of this wrapper is to minimize
123 the impact on the code of the tests already using this
124 function.
125 """
126
127 # NOTE(jlanoux): As a first step, ssh checks in the scenario
128 # tests need to be run regardless of the run_validation and
129 # validatable parameters and thus until the ssh validation job
130 # becomes voting in CI. The test resources management and IP
131 # association are taken care of in the scenario tests.
132 # Therefore, the validatable parameter is set to false in all
133 # those tests. In this way create_server just return a standard
134 # server and the scenario tests always perform ssh checks.
135
136 # Needed for the cross_tenant_traffic test:
137 if clients is None:
Vu Cong Tuanf825d192017-06-21 18:32:15 +0700138 clients = self.os_primary
Solio Sarabia60095ff2017-02-28 18:18:26 -0600139
140 if name is None:
141 name = data_utils.rand_name(self.__class__.__name__ + "-server")
142
143 vnic_type = CONF.network.port_vnic_type
144
145 # If vnic_type is configured create port for
146 # every network
147 if vnic_type:
148 ports = []
149
150 create_port_body = {'binding:vnic_type': vnic_type,
151 'namestart': 'port-smoke'}
152 if kwargs:
153 # Convert security group names to security group ids
154 # to pass to create_port
155 if 'security_groups' in kwargs:
156 security_groups = \
157 clients.security_groups_client.list_security_groups(
158 ).get('security_groups')
159 sec_dict = dict([(s['name'], s['id'])
160 for s in security_groups])
161
162 sec_groups_names = [s['name'] for s in kwargs.pop(
163 'security_groups')]
164 security_groups_ids = [sec_dict[s]
165 for s in sec_groups_names]
166
167 if security_groups_ids:
168 create_port_body[
169 'security_groups'] = security_groups_ids
170 networks = kwargs.pop('networks', [])
171 else:
172 networks = []
173
174 # If there are no networks passed to us we look up
175 # for the project's private networks and create a port.
176 # The same behaviour as we would expect when passing
177 # the call to the clients with no networks
178 if not networks:
179 networks = clients.networks_client.list_networks(
180 **{'router:external': False, 'fields': 'id'})['networks']
181
182 # It's net['uuid'] if networks come from kwargs
183 # and net['id'] if they come from
184 # clients.networks_client.list_networks
185 for net in networks:
186 net_id = net.get('uuid', net.get('id'))
187 if 'port' not in net:
188 port = self._create_port(network_id=net_id,
189 client=clients.ports_client,
190 **create_port_body)
191 ports.append({'port': port['id']})
192 else:
193 ports.append({'port': net['port']})
194 if ports:
195 kwargs['networks'] = ports
196 self.ports = ports
197
198 tenant_network = self.get_tenant_network()
199
200 body, servers = compute.create_test_server(
201 clients,
202 tenant_network=tenant_network,
203 wait_until=wait_until,
204 name=name, flavor=flavor,
205 image_id=image_id, **kwargs)
206
207 self.addCleanup(waiters.wait_for_server_termination,
208 clients.servers_client, body['id'])
209 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
210 clients.servers_client.delete_server, body['id'])
211 server = clients.servers_client.show_server(body['id'])['server']
212 return server
213
Solio Sarabia60095ff2017-02-28 18:18:26 -0600214 def get_remote_client(self, ip_address, username=None, private_key=None):
215 """Get a SSH client to a remote server
216
217 @param ip_address the server floating or fixed IP address to use
218 for ssh validation
219 @param username name of the Linux account on the remote server
220 @param private_key the SSH private key to use
221 @return a RemoteClient object
222 """
223
224 if username is None:
225 username = CONF.validation.image_ssh_user
226 # Set this with 'keypair' or others to log in with keypair or
227 # username/password.
228 if CONF.validation.auth_method == 'keypair':
229 password = None
230 if private_key is None:
231 private_key = self.keypair['private_key']
232 else:
233 password = CONF.validation.image_ssh_password
234 private_key = None
235 linux_client = remote_client.RemoteClient(ip_address, username,
236 pkey=private_key,
237 password=password)
238 try:
239 linux_client.validate_authentication()
240 except Exception as e:
241 message = ('Initializing SSH connection to %(ip)s failed. '
242 'Error: %(error)s' % {'ip': ip_address,
243 'error': e})
244 caller = test_utils.find_test_caller()
245 if caller:
246 message = '(%s) %s' % (caller, message)
247 LOG.exception(message)
248 self._log_console_output()
249 raise
250
251 return linux_client
252
Solio Sarabia60095ff2017-02-28 18:18:26 -0600253 def _log_console_output(self, servers=None):
254 if not CONF.compute_feature_enabled.console_output:
255 LOG.debug('Console output not supported, cannot log')
256 return
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700257 client = self.os_primary.servers_client
Solio Sarabia60095ff2017-02-28 18:18:26 -0600258 if not servers:
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700259 servers = client.list_servers()
Solio Sarabia60095ff2017-02-28 18:18:26 -0600260 servers = servers['servers']
261 for server in servers:
262 try:
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700263 console_output = client.get_console_output(
Solio Sarabia60095ff2017-02-28 18:18:26 -0600264 server['id'])['output']
265 LOG.debug('Console output for %s\nbody=\n%s',
266 server['id'], console_output)
267 except lib_exc.NotFound:
268 LOG.debug("Server %s disappeared(deleted) while looking "
269 "for the console log", server['id'])
270
Solio Sarabia60095ff2017-02-28 18:18:26 -0600271 def rebuild_server(self, server_id, image=None,
272 preserve_ephemeral=False, wait=True,
273 rebuild_kwargs=None):
274 if image is None:
275 image = CONF.compute.image_ref
276
277 rebuild_kwargs = rebuild_kwargs or {}
278
279 LOG.debug("Rebuilding server (id: %s, image: %s, preserve eph: %s)",
280 server_id, image, preserve_ephemeral)
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700281 self.os_primary.servers_client.rebuild_server(
Solio Sarabia60095ff2017-02-28 18:18:26 -0600282 server_id=server_id, image_ref=image,
283 preserve_ephemeral=preserve_ephemeral,
284 **rebuild_kwargs)
285 if wait:
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700286 waiters.wait_for_server_status(self.os_primary.servers_client,
Solio Sarabia60095ff2017-02-28 18:18:26 -0600287 server_id, 'ACTIVE')
288
289 def ping_ip_address(self, ip_address, should_succeed=True,
290 ping_timeout=None, mtu=None):
291 timeout = ping_timeout or CONF.validation.ping_timeout
292 cmd = ['ping', '-c1', '-w1']
293
294 if mtu:
295 cmd += [
296 # don't fragment
297 '-M', 'do',
298 # ping receives just the size of ICMP payload
299 '-s', str(net_utils.get_ping_payload_size(mtu, 4))
300 ]
301 cmd.append(ip_address)
302
303 def ping():
304 proc = subprocess.Popen(cmd,
305 stdout=subprocess.PIPE,
306 stderr=subprocess.PIPE)
307 proc.communicate()
308
309 return (proc.returncode == 0) == should_succeed
310
311 caller = test_utils.find_test_caller()
312 LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
313 ' expected result is %(should_succeed)s', {
314 'caller': caller, 'ip': ip_address, 'timeout': timeout,
315 'should_succeed':
316 'reachable' if should_succeed else 'unreachable'
317 })
318 result = test_utils.call_until_true(ping, timeout, 1)
319 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
320 'ping result is %(result)s', {
321 'caller': caller, 'ip': ip_address, 'timeout': timeout,
322 'result': 'expected' if result else 'unexpected'
323 })
324 return result
325
326 def check_vm_connectivity(self, ip_address,
327 username=None,
328 private_key=None,
329 should_connect=True,
330 mtu=None):
331 """Check server connectivity
332
333 :param ip_address: server to test against
334 :param username: server's ssh username
335 :param private_key: server's ssh private key to be used
336 :param should_connect: True/False indicates positive/negative test
337 positive - attempt ping and ssh
338 negative - attempt ping and fail if succeed
339 :param mtu: network MTU to use for connectivity validation
340
341 :raises: AssertError if the result of the connectivity check does
342 not match the value of the should_connect param
343 """
344 if should_connect:
345 msg = "Timed out waiting for %s to become reachable" % ip_address
346 else:
347 msg = "ip address %s is reachable" % ip_address
348 self.assertTrue(self.ping_ip_address(ip_address,
349 should_succeed=should_connect,
350 mtu=mtu),
351 msg=msg)
352 if should_connect:
353 # no need to check ssh for negative connectivity
354 self.get_remote_client(ip_address, username, private_key)
355
Solio Sarabia60095ff2017-02-28 18:18:26 -0600356 def create_floating_ip(self, thing, pool_name=None):
357 """Create a floating IP and associates to a server on Nova"""
358
359 if not pool_name:
360 pool_name = CONF.network.floating_network_name
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700361 client = self.os_primary.compute_floating_ips_client
362 floating_ip = (client.
Solio Sarabia60095ff2017-02-28 18:18:26 -0600363 create_floating_ip(pool=pool_name)['floating_ip'])
364 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700365 client.delete_floating_ip,
Solio Sarabia60095ff2017-02-28 18:18:26 -0600366 floating_ip['id'])
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700367 client.associate_floating_ip_to_server(
Solio Sarabia60095ff2017-02-28 18:18:26 -0600368 floating_ip['ip'], thing['id'])
369 return floating_ip
370
371 def create_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
372 private_key=None):
373 ssh_client = self.get_remote_client(ip_address,
374 private_key=private_key)
375 if dev_name is not None:
376 ssh_client.make_fs(dev_name)
377 ssh_client.exec_command('sudo mount /dev/%s %s' % (dev_name,
378 mount_path))
379 cmd_timestamp = 'sudo sh -c "date > %s/timestamp; sync"' % mount_path
380 ssh_client.exec_command(cmd_timestamp)
381 timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
382 % mount_path)
383 if dev_name is not None:
384 ssh_client.exec_command('sudo umount %s' % mount_path)
385 return timestamp
386
Solio Sarabia60095ff2017-02-28 18:18:26 -0600387 def get_server_ip(self, server):
388 """Get the server fixed or floating IP.
389
390 Based on the configuration we're in, return a correct ip
391 address for validating that a guest is up.
392 """
393 if CONF.validation.connect_method == 'floating':
394 # The tests calling this method don't have a floating IP
395 # and can't make use of the validation resources. So the
396 # method is creating the floating IP there.
397 return self.create_floating_ip(server)['ip']
398 elif CONF.validation.connect_method == 'fixed':
399 # Determine the network name to look for based on config or creds
400 # provider network resources.
401 if CONF.validation.network_for_ssh:
402 addresses = server['addresses'][
403 CONF.validation.network_for_ssh]
404 else:
405 creds_provider = self._get_credentials_provider()
406 net_creds = creds_provider.get_primary_creds()
407 network = getattr(net_creds, 'network', None)
408 addresses = (server['addresses'][network['name']]
409 if network else [])
410 for address in addresses:
411 if (address['version'] == CONF.validation.ip_version_for_ssh
412 and address['OS-EXT-IPS:type'] == 'fixed'):
413 return address['addr']
414 raise exceptions.ServerUnreachable(server_id=server['id'])
415 else:
416 raise lib_exc.InvalidConfiguration()
417
Pavlo Shchelokovskyy40db0332017-03-21 08:00:17 +0000418 def _get_router(self, client=None, tenant_id=None):
419 """Retrieve a router for the given tenant id.
420
421 If a public router has been configured, it will be returned.
422
423 If a public router has not been configured, but a public
424 network has, a tenant router will be created and returned that
425 routes traffic to the public network.
426 """
427 if not client:
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700428 client = self.os_primary.routers_client
Pavlo Shchelokovskyy40db0332017-03-21 08:00:17 +0000429 if not tenant_id:
430 tenant_id = client.tenant_id
431 router_id = CONF.network.public_router_id
432 network_id = CONF.network.public_network_id
433 if router_id:
434 body = client.show_router(router_id)
435 return body['router']
436 elif network_id:
437 router = self._create_router(client, tenant_id)
438 kwargs = {'external_gateway_info': dict(network_id=network_id)}
439 router = client.update_router(router['id'], **kwargs)['router']
440 return router
441 else:
442 raise Exception("Neither of 'public_router_id' or "
443 "'public_network_id' has been defined.")
444
445 def _create_router(self, client=None, tenant_id=None,
446 namestart='router-smoke'):
447 if not client:
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700448 client = self.os_primary.routers_client
Pavlo Shchelokovskyy40db0332017-03-21 08:00:17 +0000449 if not tenant_id:
450 tenant_id = client.tenant_id
451 name = data_utils.rand_name(namestart)
452 result = client.create_router(name=name,
453 admin_state_up=True,
454 tenant_id=tenant_id)
455 router = result['router']
456 self.assertEqual(router['name'], name)
457 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
458 client.delete_router,
459 router['id'])
460 return router
461
Solio Sarabia60095ff2017-02-28 18:18:26 -0600462
463class NetworkScenarioTest(ScenarioTest):
464 """Base class for network scenario tests.
465
466 This class provide helpers for network scenario tests, using the neutron
467 API. Helpers from ancestor which use the nova network API are overridden
468 with the neutron API.
469
470 This Class also enforces using Neutron instead of novanetwork.
471 Subclassed tests will be skipped if Neutron is not enabled
472
473 """
474
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700475 credentials = ['primary', 'admin', 'system_admin']
Solio Sarabia60095ff2017-02-28 18:18:26 -0600476
477 @classmethod
478 def skip_checks(cls):
479 super(NetworkScenarioTest, cls).skip_checks()
480 if not CONF.service_available.neutron:
481 raise cls.skipException('Neutron not available')
482
483 def _create_network(self, networks_client=None,
484 tenant_id=None,
485 namestart='network-smoke-',
486 port_security_enabled=True):
487 if not networks_client:
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700488 networks_client = self.os_primary.networks_client
Solio Sarabia60095ff2017-02-28 18:18:26 -0600489 if not tenant_id:
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700490 tenant_id = self.os_primary.networks_client.tenant_id
Solio Sarabia60095ff2017-02-28 18:18:26 -0600491 name = data_utils.rand_name(namestart)
492 network_kwargs = dict(name=name, tenant_id=tenant_id)
493 # Neutron disables port security by default so we have to check the
494 # config before trying to create the network with port_security_enabled
495 if CONF.network_feature_enabled.port_security:
496 network_kwargs['port_security_enabled'] = port_security_enabled
497 result = networks_client.create_network(**network_kwargs)
498 network = result['network']
499
500 self.assertEqual(network['name'], name)
501 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
502 networks_client.delete_network,
503 network['id'])
504 return network
505
Solio Sarabia60095ff2017-02-28 18:18:26 -0600506 def _get_server_port_id_and_ip4(self, server, ip_addr=None):
Hongbin Lu43015f02018-07-19 15:17:19 +0000507 if ip_addr:
508 ports = self.os_admin.ports_client.list_ports(
509 device_id=server['id'],
510 fixed_ips='ip_address=%s' % ip_addr)['ports']
511 else:
512 ports = self.os_admin.ports_client.list_ports(
513 device_id=server['id'])['ports']
Solio Sarabia60095ff2017-02-28 18:18:26 -0600514 # A port can have more than one IP address in some cases.
515 # If the network is dual-stack (IPv4 + IPv6), this port is associated
516 # with 2 subnets
517 p_status = ['ACTIVE']
518 # NOTE(vsaienko) With Ironic, instances live on separate hardware
519 # servers. Neutron does not bind ports for Ironic instances, as a
520 # result the port remains in the DOWN state.
521 # TODO(vsaienko) remove once bug: #1599836 is resolved.
522 if getattr(CONF.service_available, 'ironic', False):
523 p_status.append('DOWN')
524 port_map = [(p["id"], fxip["ip_address"])
525 for p in ports
526 for fxip in p["fixed_ips"]
527 if netutils.is_valid_ipv4(fxip["ip_address"])
528 and p['status'] in p_status]
529 inactive = [p for p in ports if p['status'] != 'ACTIVE']
530 if inactive:
531 LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
532
533 self.assertNotEqual(0, len(port_map),
534 "No IPv4 addresses found in: %s" % ports)
535 self.assertEqual(len(port_map), 1,
536 "Found multiple IPv4 addresses: %s. "
537 "Unable to determine which port to target."
538 % port_map)
539 return port_map[0]
540
Solio Sarabia60095ff2017-02-28 18:18:26 -0600541 def create_floating_ip(self, thing, external_network_id=None,
542 port_id=None, client=None):
543 """Create a floating IP and associates to a resource/port on Neutron"""
544 if not external_network_id:
545 external_network_id = CONF.network.public_network_id
546 if not client:
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700547 client = self.os_primary.floating_ips_client
Solio Sarabia60095ff2017-02-28 18:18:26 -0600548 if not port_id:
549 port_id, ip4 = self._get_server_port_id_and_ip4(thing)
550 else:
551 ip4 = None
552 result = client.create_floatingip(
553 floating_network_id=external_network_id,
554 port_id=port_id,
555 tenant_id=thing['tenant_id'],
556 fixed_ip_address=ip4
557 )
558 floating_ip = result['floatingip']
559 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
560 client.delete_floatingip,
561 floating_ip['id'])
562 return floating_ip