diff --git a/shade/openstackcloud.py b/shade/openstackcloud.py index 76ff9ce99..c5c3785f8 100644 --- a/shade/openstackcloud.py +++ b/shade/openstackcloud.py @@ -3320,6 +3320,34 @@ class OpenStackCloud(object): fip_id=floating_ip_id, msg=str(e))) return True + def delete_unattached_floating_ips(self, wait=False, timeout=60): + """Safely delete unattached floating ips. + + If the cloud can safely purge any unattached floating ips without + race conditions, do so. + + Safely here means a specific thing. It means that you are not running + this while another process that might do a two step create/attach + is running. You can safely run this method while another process + is creating servers and attaching floating IPs to them if either that + process is using add_auto_ip from shade, or is creating the floating + IPs by passing in a server to the create_floating_ip call. + + :param wait: Whether to wait for each IP to be deleted + :param timeout: How long to wait for each IP + + :returns: True if Floating IPs have been deleted, False if not + + :raises: ``OpenStackCloudException``, on operation error. + """ + processed = [] + if self._use_neutron_floating(): + for ip in self.list_floating_ips(): + if not ip['attached']: + processed.append(self.delete_floating_ip( + id=ip['id'], wait=wait, timeout=timeout)) + return all(processed) if processed else False + def _attach_ip_to_server( self, server, floating_ip, fixed_address=None, wait=False, diff --git a/shade/tests/unit/test_floating_ip_neutron.py b/shade/tests/unit/test_floating_ip_neutron.py index d0ebad059..85fb977ea 100644 --- a/shade/tests/unit/test_floating_ip_neutron.py +++ b/shade/tests/unit/test_floating_ip_neutron.py @@ -531,3 +531,36 @@ class TestFloatingIP(base.TestCase): fixed_address='192.0.2.129') self.assertEqual(server, self.fake_server) + + @patch.object(OpenStackCloud, 'delete_floating_ip') + @patch.object(OpenStackCloud, 'list_floating_ips') + @patch.object(OpenStackCloud, '_use_neutron_floating') + def test_cleanup_floating_ips( + self, mock_use_neutron_floating, mock_list_floating_ips, + mock_delete_floating_ip): + mock_use_neutron_floating.return_value = True + floating_ips = [{ + "id": "this-is-a-floating-ip-id", + "fixed_ip_address": None, + "internal_network": None, + "floating_ip_address": "203.0.113.29", + "network": "this-is-a-net-or-pool-id", + "attached": False, + "status": "ACTIVE" + }, { + "id": "this-is-an-attached-floating-ip-id", + "fixed_ip_address": None, + "internal_network": None, + "floating_ip_address": "203.0.113.29", + "network": "this-is-a-net-or-pool-id", + "attached": True, + "status": "ACTIVE" + }] + + mock_list_floating_ips.return_value = floating_ips + + self.client.delete_unattached_floating_ips() + + mock_delete_floating_ip.assert_called_once_with( + id="this-is-a-floating-ip-id", + timeout=60, wait=False) diff --git a/shade/tests/unit/test_floating_ip_nova.py b/shade/tests/unit/test_floating_ip_nova.py index 2ad83b894..34bee06c3 100644 --- a/shade/tests/unit/test_floating_ip_nova.py +++ b/shade/tests/unit/test_floating_ip_nova.py @@ -248,3 +248,16 @@ class TestFloatingIP(base.TestCase): fixed_address='192.0.2.129') self.assertEqual(server, self.fake_server) + + @patch.object(OpenStackCloud, 'delete_floating_ip') + @patch.object(OpenStackCloud, 'list_floating_ips') + @patch.object(OpenStackCloud, '_use_neutron_floating') + def test_cleanup_floating_ips( + self, mock_use_neutron_floating, mock_list_floating_ips, + mock_delete_floating_ip): + mock_use_neutron_floating.return_value = False + + self.client.delete_unattached_floating_ips() + + mock_delete_floating_ip.assert_not_called() + mock_list_floating_ips.assert_not_called()