blob: 5d4bd39f43ef3c86d9ad94c05117c093b3fbf924 [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
Roman Popelka7e962d62022-02-28 09:10:31 +010042class ScenarioTest(tempest.scenario.manager.ScenarioTest):
Solio Sarabia60095ff2017-02-28 18:18:26 -060043 """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
Solio Sarabia60095ff2017-02-28 18:18:26 -060092 def create_server(self, name=None, image_id=None, flavor=None,
93 validatable=False, wait_until='ACTIVE',
94 clients=None, **kwargs):
95 """Wrapper utility that returns a test server.
96
97 This wrapper utility calls the common create test server and
98 returns a test server. The purpose of this wrapper is to minimize
99 the impact on the code of the tests already using this
100 function.
101 """
102
103 # NOTE(jlanoux): As a first step, ssh checks in the scenario
104 # tests need to be run regardless of the run_validation and
105 # validatable parameters and thus until the ssh validation job
106 # becomes voting in CI. The test resources management and IP
107 # association are taken care of in the scenario tests.
108 # Therefore, the validatable parameter is set to false in all
109 # those tests. In this way create_server just return a standard
110 # server and the scenario tests always perform ssh checks.
111
112 # Needed for the cross_tenant_traffic test:
113 if clients is None:
Vu Cong Tuanf825d192017-06-21 18:32:15 +0700114 clients = self.os_primary
Solio Sarabia60095ff2017-02-28 18:18:26 -0600115
116 if name is None:
117 name = data_utils.rand_name(self.__class__.__name__ + "-server")
118
119 vnic_type = CONF.network.port_vnic_type
120
121 # If vnic_type is configured create port for
122 # every network
123 if vnic_type:
124 ports = []
125
126 create_port_body = {'binding:vnic_type': vnic_type,
127 'namestart': 'port-smoke'}
128 if kwargs:
129 # Convert security group names to security group ids
130 # to pass to create_port
131 if 'security_groups' in kwargs:
132 security_groups = \
133 clients.security_groups_client.list_security_groups(
134 ).get('security_groups')
135 sec_dict = dict([(s['name'], s['id'])
136 for s in security_groups])
137
138 sec_groups_names = [s['name'] for s in kwargs.pop(
139 'security_groups')]
140 security_groups_ids = [sec_dict[s]
141 for s in sec_groups_names]
142
143 if security_groups_ids:
144 create_port_body[
145 'security_groups'] = security_groups_ids
146 networks = kwargs.pop('networks', [])
147 else:
148 networks = []
149
150 # If there are no networks passed to us we look up
151 # for the project's private networks and create a port.
152 # The same behaviour as we would expect when passing
153 # the call to the clients with no networks
154 if not networks:
155 networks = clients.networks_client.list_networks(
156 **{'router:external': False, 'fields': 'id'})['networks']
157
158 # It's net['uuid'] if networks come from kwargs
159 # and net['id'] if they come from
160 # clients.networks_client.list_networks
161 for net in networks:
162 net_id = net.get('uuid', net.get('id'))
163 if 'port' not in net:
Roman Popelka7e962d62022-02-28 09:10:31 +0100164 port = self.create_port(network_id=net_id,
165 client=clients.ports_client,
166 namestart='port-quotatest',
167 **create_port_body)
Solio Sarabia60095ff2017-02-28 18:18:26 -0600168 ports.append({'port': port['id']})
169 else:
170 ports.append({'port': net['port']})
171 if ports:
172 kwargs['networks'] = ports
173 self.ports = ports
174
175 tenant_network = self.get_tenant_network()
176
177 body, servers = compute.create_test_server(
178 clients,
179 tenant_network=tenant_network,
180 wait_until=wait_until,
181 name=name, flavor=flavor,
182 image_id=image_id, **kwargs)
183
184 self.addCleanup(waiters.wait_for_server_termination,
185 clients.servers_client, body['id'])
186 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
187 clients.servers_client.delete_server, body['id'])
188 server = clients.servers_client.show_server(body['id'])['server']
189 return server
190
Solio Sarabia60095ff2017-02-28 18:18:26 -0600191 def get_remote_client(self, ip_address, username=None, private_key=None):
192 """Get a SSH client to a remote server
193
194 @param ip_address the server floating or fixed IP address to use
195 for ssh validation
196 @param username name of the Linux account on the remote server
197 @param private_key the SSH private key to use
198 @return a RemoteClient object
199 """
200
201 if username is None:
202 username = CONF.validation.image_ssh_user
203 # Set this with 'keypair' or others to log in with keypair or
204 # username/password.
205 if CONF.validation.auth_method == 'keypair':
206 password = None
207 if private_key is None:
208 private_key = self.keypair['private_key']
209 else:
210 password = CONF.validation.image_ssh_password
211 private_key = None
212 linux_client = remote_client.RemoteClient(ip_address, username,
213 pkey=private_key,
214 password=password)
215 try:
216 linux_client.validate_authentication()
217 except Exception as e:
218 message = ('Initializing SSH connection to %(ip)s failed. '
219 'Error: %(error)s' % {'ip': ip_address,
220 'error': e})
221 caller = test_utils.find_test_caller()
222 if caller:
223 message = '(%s) %s' % (caller, message)
224 LOG.exception(message)
225 self._log_console_output()
226 raise
227
228 return linux_client
229
Solio Sarabia60095ff2017-02-28 18:18:26 -0600230 def _log_console_output(self, servers=None):
231 if not CONF.compute_feature_enabled.console_output:
232 LOG.debug('Console output not supported, cannot log')
233 return
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700234 client = self.os_primary.servers_client
Solio Sarabia60095ff2017-02-28 18:18:26 -0600235 if not servers:
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700236 servers = client.list_servers()
Solio Sarabia60095ff2017-02-28 18:18:26 -0600237 servers = servers['servers']
238 for server in servers:
239 try:
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700240 console_output = client.get_console_output(
Solio Sarabia60095ff2017-02-28 18:18:26 -0600241 server['id'])['output']
242 LOG.debug('Console output for %s\nbody=\n%s',
243 server['id'], console_output)
244 except lib_exc.NotFound:
245 LOG.debug("Server %s disappeared(deleted) while looking "
246 "for the console log", server['id'])
247
Solio Sarabia60095ff2017-02-28 18:18:26 -0600248 def rebuild_server(self, server_id, image=None,
249 preserve_ephemeral=False, wait=True,
250 rebuild_kwargs=None):
251 if image is None:
252 image = CONF.compute.image_ref
253
254 rebuild_kwargs = rebuild_kwargs or {}
255
256 LOG.debug("Rebuilding server (id: %s, image: %s, preserve eph: %s)",
257 server_id, image, preserve_ephemeral)
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700258 self.os_primary.servers_client.rebuild_server(
Solio Sarabia60095ff2017-02-28 18:18:26 -0600259 server_id=server_id, image_ref=image,
260 preserve_ephemeral=preserve_ephemeral,
261 **rebuild_kwargs)
262 if wait:
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700263 waiters.wait_for_server_status(self.os_primary.servers_client,
Solio Sarabia60095ff2017-02-28 18:18:26 -0600264 server_id, 'ACTIVE')
265
266 def ping_ip_address(self, ip_address, should_succeed=True,
267 ping_timeout=None, mtu=None):
268 timeout = ping_timeout or CONF.validation.ping_timeout
269 cmd = ['ping', '-c1', '-w1']
270
271 if mtu:
272 cmd += [
273 # don't fragment
274 '-M', 'do',
275 # ping receives just the size of ICMP payload
276 '-s', str(net_utils.get_ping_payload_size(mtu, 4))
277 ]
278 cmd.append(ip_address)
279
280 def ping():
281 proc = subprocess.Popen(cmd,
282 stdout=subprocess.PIPE,
283 stderr=subprocess.PIPE)
284 proc.communicate()
285
286 return (proc.returncode == 0) == should_succeed
287
288 caller = test_utils.find_test_caller()
289 LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
290 ' expected result is %(should_succeed)s', {
291 'caller': caller, 'ip': ip_address, 'timeout': timeout,
292 'should_succeed':
293 'reachable' if should_succeed else 'unreachable'
294 })
295 result = test_utils.call_until_true(ping, timeout, 1)
296 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
297 'ping result is %(result)s', {
298 'caller': caller, 'ip': ip_address, 'timeout': timeout,
299 'result': 'expected' if result else 'unexpected'
300 })
301 return result
302
303 def check_vm_connectivity(self, ip_address,
304 username=None,
305 private_key=None,
306 should_connect=True,
307 mtu=None):
308 """Check server connectivity
309
310 :param ip_address: server to test against
311 :param username: server's ssh username
312 :param private_key: server's ssh private key to be used
313 :param should_connect: True/False indicates positive/negative test
314 positive - attempt ping and ssh
315 negative - attempt ping and fail if succeed
316 :param mtu: network MTU to use for connectivity validation
317
318 :raises: AssertError if the result of the connectivity check does
319 not match the value of the should_connect param
320 """
321 if should_connect:
322 msg = "Timed out waiting for %s to become reachable" % ip_address
323 else:
324 msg = "ip address %s is reachable" % ip_address
325 self.assertTrue(self.ping_ip_address(ip_address,
326 should_succeed=should_connect,
327 mtu=mtu),
328 msg=msg)
329 if should_connect:
330 # no need to check ssh for negative connectivity
331 self.get_remote_client(ip_address, username, private_key)
332
Solio Sarabia60095ff2017-02-28 18:18:26 -0600333 def create_floating_ip(self, thing, pool_name=None):
334 """Create a floating IP and associates to a server on Nova"""
335
336 if not pool_name:
337 pool_name = CONF.network.floating_network_name
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700338 client = self.os_primary.compute_floating_ips_client
339 floating_ip = (client.
Solio Sarabia60095ff2017-02-28 18:18:26 -0600340 create_floating_ip(pool=pool_name)['floating_ip'])
341 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700342 client.delete_floating_ip,
Solio Sarabia60095ff2017-02-28 18:18:26 -0600343 floating_ip['id'])
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700344 client.associate_floating_ip_to_server(
Solio Sarabia60095ff2017-02-28 18:18:26 -0600345 floating_ip['ip'], thing['id'])
346 return floating_ip
347
348 def create_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
349 private_key=None):
350 ssh_client = self.get_remote_client(ip_address,
351 private_key=private_key)
352 if dev_name is not None:
353 ssh_client.make_fs(dev_name)
354 ssh_client.exec_command('sudo mount /dev/%s %s' % (dev_name,
355 mount_path))
356 cmd_timestamp = 'sudo sh -c "date > %s/timestamp; sync"' % mount_path
357 ssh_client.exec_command(cmd_timestamp)
358 timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
359 % mount_path)
360 if dev_name is not None:
361 ssh_client.exec_command('sudo umount %s' % mount_path)
362 return timestamp
363
Solio Sarabia60095ff2017-02-28 18:18:26 -0600364 def get_server_ip(self, server):
365 """Get the server fixed or floating IP.
366
367 Based on the configuration we're in, return a correct ip
368 address for validating that a guest is up.
369 """
370 if CONF.validation.connect_method == 'floating':
371 # The tests calling this method don't have a floating IP
372 # and can't make use of the validation resources. So the
373 # method is creating the floating IP there.
374 return self.create_floating_ip(server)['ip']
375 elif CONF.validation.connect_method == 'fixed':
376 # Determine the network name to look for based on config or creds
377 # provider network resources.
378 if CONF.validation.network_for_ssh:
379 addresses = server['addresses'][
380 CONF.validation.network_for_ssh]
381 else:
382 creds_provider = self._get_credentials_provider()
383 net_creds = creds_provider.get_primary_creds()
384 network = getattr(net_creds, 'network', None)
385 addresses = (server['addresses'][network['name']]
386 if network else [])
387 for address in addresses:
388 if (address['version'] == CONF.validation.ip_version_for_ssh
389 and address['OS-EXT-IPS:type'] == 'fixed'):
390 return address['addr']
391 raise exceptions.ServerUnreachable(server_id=server['id'])
392 else:
393 raise lib_exc.InvalidConfiguration()
394
Pavlo Shchelokovskyy40db0332017-03-21 08:00:17 +0000395 def _get_router(self, client=None, tenant_id=None):
396 """Retrieve a router for the given tenant id.
397
398 If a public router has been configured, it will be returned.
399
400 If a public router has not been configured, but a public
401 network has, a tenant router will be created and returned that
402 routes traffic to the public network.
403 """
404 if not client:
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700405 client = self.os_primary.routers_client
Pavlo Shchelokovskyy40db0332017-03-21 08:00:17 +0000406 if not tenant_id:
407 tenant_id = client.tenant_id
408 router_id = CONF.network.public_router_id
409 network_id = CONF.network.public_network_id
410 if router_id:
411 body = client.show_router(router_id)
412 return body['router']
413 elif network_id:
414 router = self._create_router(client, tenant_id)
415 kwargs = {'external_gateway_info': dict(network_id=network_id)}
416 router = client.update_router(router['id'], **kwargs)['router']
417 return router
418 else:
419 raise Exception("Neither of 'public_router_id' or "
420 "'public_network_id' has been defined.")
421
422 def _create_router(self, client=None, tenant_id=None,
423 namestart='router-smoke'):
424 if not client:
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700425 client = self.os_primary.routers_client
Pavlo Shchelokovskyy40db0332017-03-21 08:00:17 +0000426 if not tenant_id:
427 tenant_id = client.tenant_id
428 name = data_utils.rand_name(namestart)
429 result = client.create_router(name=name,
430 admin_state_up=True,
431 tenant_id=tenant_id)
432 router = result['router']
433 self.assertEqual(router['name'], name)
434 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
435 client.delete_router,
436 router['id'])
437 return router
438
Solio Sarabia60095ff2017-02-28 18:18:26 -0600439
440class NetworkScenarioTest(ScenarioTest):
441 """Base class for network scenario tests.
442
443 This class provide helpers for network scenario tests, using the neutron
444 API. Helpers from ancestor which use the nova network API are overridden
445 with the neutron API.
446
447 This Class also enforces using Neutron instead of novanetwork.
448 Subclassed tests will be skipped if Neutron is not enabled
449
450 """
451
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700452 credentials = ['primary', 'admin', 'system_admin']
Solio Sarabia60095ff2017-02-28 18:18:26 -0600453
454 @classmethod
455 def skip_checks(cls):
456 super(NetworkScenarioTest, cls).skip_checks()
457 if not CONF.service_available.neutron:
458 raise cls.skipException('Neutron not available')
459
460 def _create_network(self, networks_client=None,
461 tenant_id=None,
462 namestart='network-smoke-',
463 port_security_enabled=True):
464 if not networks_client:
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700465 networks_client = self.os_primary.networks_client
Solio Sarabia60095ff2017-02-28 18:18:26 -0600466 if not tenant_id:
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700467 tenant_id = self.os_primary.networks_client.tenant_id
Solio Sarabia60095ff2017-02-28 18:18:26 -0600468 name = data_utils.rand_name(namestart)
469 network_kwargs = dict(name=name, tenant_id=tenant_id)
470 # Neutron disables port security by default so we have to check the
471 # config before trying to create the network with port_security_enabled
472 if CONF.network_feature_enabled.port_security:
473 network_kwargs['port_security_enabled'] = port_security_enabled
474 result = networks_client.create_network(**network_kwargs)
475 network = result['network']
476
477 self.assertEqual(network['name'], name)
478 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
479 networks_client.delete_network,
480 network['id'])
481 return network
482
Solio Sarabia60095ff2017-02-28 18:18:26 -0600483 def _get_server_port_id_and_ip4(self, server, ip_addr=None):
Hongbin Lu43015f02018-07-19 15:17:19 +0000484 if ip_addr:
485 ports = self.os_admin.ports_client.list_ports(
486 device_id=server['id'],
487 fixed_ips='ip_address=%s' % ip_addr)['ports']
488 else:
489 ports = self.os_admin.ports_client.list_ports(
490 device_id=server['id'])['ports']
Solio Sarabia60095ff2017-02-28 18:18:26 -0600491 # A port can have more than one IP address in some cases.
492 # If the network is dual-stack (IPv4 + IPv6), this port is associated
493 # with 2 subnets
494 p_status = ['ACTIVE']
495 # NOTE(vsaienko) With Ironic, instances live on separate hardware
496 # servers. Neutron does not bind ports for Ironic instances, as a
497 # result the port remains in the DOWN state.
498 # TODO(vsaienko) remove once bug: #1599836 is resolved.
499 if getattr(CONF.service_available, 'ironic', False):
500 p_status.append('DOWN')
501 port_map = [(p["id"], fxip["ip_address"])
502 for p in ports
503 for fxip in p["fixed_ips"]
504 if netutils.is_valid_ipv4(fxip["ip_address"])
505 and p['status'] in p_status]
506 inactive = [p for p in ports if p['status'] != 'ACTIVE']
507 if inactive:
508 LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
509
510 self.assertNotEqual(0, len(port_map),
511 "No IPv4 addresses found in: %s" % ports)
512 self.assertEqual(len(port_map), 1,
513 "Found multiple IPv4 addresses: %s. "
514 "Unable to determine which port to target."
515 % port_map)
516 return port_map[0]
517
Solio Sarabia60095ff2017-02-28 18:18:26 -0600518 def create_floating_ip(self, thing, external_network_id=None,
519 port_id=None, client=None):
520 """Create a floating IP and associates to a resource/port on Neutron"""
521 if not external_network_id:
522 external_network_id = CONF.network.public_network_id
523 if not client:
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700524 client = self.os_primary.floating_ips_client
Solio Sarabia60095ff2017-02-28 18:18:26 -0600525 if not port_id:
526 port_id, ip4 = self._get_server_port_id_and_ip4(thing)
527 else:
528 ip4 = None
529 result = client.create_floatingip(
530 floating_network_id=external_network_id,
531 port_id=port_id,
532 tenant_id=thing['tenant_id'],
533 fixed_ip_address=ip4
534 )
535 floating_ip = result['floatingip']
536 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
537 client.delete_floatingip,
538 floating_ip['id'])
539 return floating_ip