summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--nova/api/openstack/compute/contrib/floating_ips.py29
-rw-r--r--nova/exception.py9
-rw-r--r--nova/network/quantumv2/api.py183
-rw-r--r--nova/tests/network/test_quantumv2.py283
-rw-r--r--tools/pip-requires2
5 files changed, 482 insertions, 24 deletions
diff --git a/nova/api/openstack/compute/contrib/floating_ips.py b/nova/api/openstack/compute/contrib/floating_ips.py
index 1c17591a4..b835ca61d 100644
--- a/nova/api/openstack/compute/contrib/floating_ips.py
+++ b/nova/api/openstack/compute/contrib/floating_ips.py
@@ -118,18 +118,23 @@ class FloatingIPController(object):
return self.compute_api.get(context, instance_id)
def _set_metadata(self, context, floating_ip):
- fixed_ip_id = floating_ip['fixed_ip_id']
- floating_ip['fixed_ip'] = self._get_fixed_ip(context,
- fixed_ip_id)
- instance_uuid = None
- if floating_ip['fixed_ip']:
- instance_uuid = floating_ip['fixed_ip']['instance_uuid']
-
- if instance_uuid:
- floating_ip['instance'] = self._get_instance(context,
- instance_uuid)
- else:
- floating_ip['instance'] = None
+ # When Quantum v2 API is used, 'fixed_ip' and 'instance' are
+ # already set. In this case we don't need to update the fields.
+
+ if 'fixed_ip' not in floating_ip:
+ fixed_ip_id = floating_ip['fixed_ip_id']
+ floating_ip['fixed_ip'] = self._get_fixed_ip(context,
+ fixed_ip_id)
+ if 'instance' not in floating_ip:
+ instance_uuid = None
+ if floating_ip['fixed_ip']:
+ instance_uuid = floating_ip['fixed_ip']['instance_uuid']
+
+ if instance_uuid:
+ floating_ip['instance'] = self._get_instance(context,
+ instance_uuid)
+ else:
+ floating_ip['instance'] = None
@wsgi.serializers(xml=FloatingIPTemplate)
def show(self, req, id):
diff --git a/nova/exception.py b/nova/exception.py
index 0b969e625..4261ad3ab 100644
--- a/nova/exception.py
+++ b/nova/exception.py
@@ -648,6 +648,15 @@ class FloatingIpNotFoundForHost(FloatingIpNotFound):
message = _("Floating ip not found for host %(host)s.")
+class FloatingIpMultipleFoundForAddress(NovaException):
+ message = _("Multiple floating ips are found for address %(address)s.")
+
+
+class FloatingIpPoolNotFound(NotFound):
+ message = _("Floating ip pool not found.")
+ safe = True
+
+
class NoMoreFloatingIps(FloatingIpNotFound):
message = _("Zero floating ips available.")
safe = True
diff --git a/nova/network/quantumv2/api.py b/nova/network/quantumv2/api.py
index 6c47acb1d..049b005d1 100644
--- a/nova/network/quantumv2/api.py
+++ b/nova/network/quantumv2/api.py
@@ -25,6 +25,7 @@ from nova.network import quantumv2
from nova.openstack.common import cfg
from nova.openstack.common import excutils
from nova.openstack.common import log as logging
+from nova import utils
quantum_opts = [
@@ -49,10 +50,14 @@ quantum_opts = [
'quantum in admin context'),
]
+flags.DECLARE('default_floating_pool', 'nova.network.manager')
+
FLAGS = flags.FLAGS
FLAGS.register_opts(quantum_opts)
LOG = logging.getLogger(__name__)
+NET_EXTERNAL = 'router:external'
+
class API(base.Base):
"""API for interacting with the quantum 2.x API."""
@@ -261,12 +266,40 @@ class API(base.Base):
self.security_group_api.trigger_handler('security_group_members',
admin_context, group_ids)
+ def _get_port_id_by_fixed_address(self, client,
+ instance, address):
+ zone = 'compute:%s' % FLAGS.node_availability_zone
+ search_opts = {'device_id': instance['uuid'],
+ 'device_owner': zone}
+ data = client.list_ports(**search_opts)
+ ports = data['ports']
+ port_id = None
+ for p in ports:
+ for ip in p['fixed_ips']:
+ if ip['ip_address'] == address:
+ port_id = p['id']
+ break
+ if not port_id:
+ raise exception.FixedIpNotFoundForAddress(address=address)
+ return port_id
+
@refresh_cache
def associate_floating_ip(self, context, instance,
floating_address, fixed_address,
affect_auto_assigned=False):
"""Associate a floating ip with a fixed ip."""
- raise NotImplementedError()
+
+ # Note(amotoki): 'affect_auto_assigned' is not respected
+ # since it is not used anywhere in nova code and I could
+ # find why this parameter exists.
+
+ client = quantumv2.get_client(context)
+ port_id = self._get_port_id_by_fixed_address(client, instance,
+ fixed_address)
+ fip = self._get_floating_ip_by_address(client, floating_address)
+ param = {'port_id': port_id,
+ 'fixed_ip_address': fixed_address}
+ client.update_floatingip(fip['id'], {'floatingip': param})
def get_all(self, context):
raise NotImplementedError()
@@ -293,23 +326,94 @@ class API(base.Base):
raise exception.FixedIpAssociatedWithMultipleInstances(
address=address)
+ def _setup_net_dict(self, client, network_id):
+ if not network_id:
+ return {}
+ pool = client.show_network(network_id)['network']
+ return {pool['id']: pool}
+
+ def _setup_port_dict(self, client, port_id):
+ if not port_id:
+ return {}
+ port = client.show_port(port_id)['port']
+ return {port['id']: port}
+
+ def _setup_pools_dict(self, client):
+ pools = self._get_floating_ip_pools(client)
+ return dict([(i['id'], i) for i in pools])
+
+ def _setup_ports_dict(self, client, project_id=None):
+ search_opts = {'tenant_id': project_id} if project_id else {}
+ ports = client.list_ports(**search_opts)['ports']
+ return dict([(p['id'], p) for p in ports])
+
def get_floating_ip(self, context, id):
- raise NotImplementedError()
+ client = quantumv2.get_client(context)
+ fip = client.show_floatingip(id)['floatingip']
+ pool_dict = self._setup_net_dict(client,
+ fip['floating_network_id'])
+ port_dict = self._setup_port_dict(client, fip['port_id'])
+ return self._format_floating_ip_model(fip, pool_dict, port_dict)
+
+ def _get_floating_ip_pools(self, client, project_id=None):
+ search_opts = {NET_EXTERNAL: True}
+ if project_id:
+ search_opts.update({'tenant_id': project_id})
+ data = client.list_networks(**search_opts)
+ return data['networks']
def get_floating_ip_pools(self, context):
- return []
+ client = quantumv2.get_client(context)
+ pools = self._get_floating_ip_pools(client)
+ return [{'name': n['name'] or n['id']} for n in pools]
+
+ def _format_floating_ip_model(self, fip, pool_dict, port_dict):
+ pool = pool_dict[fip['floating_network_id']]
+ result = {'id': fip['id'],
+ 'address': fip['floating_ip_address'],
+ 'pool': pool['name'] or pool['id'],
+ 'project_id': fip['tenant_id'],
+ # In Quantum v2, an exact fixed_ip_id does not exist.
+ 'fixed_ip_id': fip['port_id'],
+ }
+ # In Quantum v2 API fixed_ip_address and instance uuid
+ # (= device_id) are known here, so pass it as a result.
+ result['fixed_ip'] = {'address': fip['fixed_ip_address']}
+ if fip['port_id']:
+ instance_uuid = port_dict[fip['port_id']]['device_id']
+ result['instance'] = {'uuid': instance_uuid}
+ else:
+ result['instance'] = None
+ return result
def get_floating_ip_by_address(self, context, address):
- raise NotImplementedError()
+ client = quantumv2.get_client(context)
+ fip = self._get_floating_ip_by_address(client, address)
+ pool_dict = self._setup_net_dict(client,
+ fip['floating_network_id'])
+ port_dict = self._setup_port_dict(client, fip['port_id'])
+ return self._format_floating_ip_model(fip, pool_dict, port_dict)
def get_floating_ips_by_project(self, context):
- return []
+ client = quantumv2.get_client(context)
+ project_id = context.project_id
+ fips = client.list_floatingips(tenant_id=project_id)['floatingips']
+ pool_dict = self._setup_pools_dict(client)
+ port_dict = self._setup_ports_dict(client, project_id)
+ return [self._format_floating_ip_model(fip, pool_dict, port_dict)
+ for fip in fips]
def get_floating_ips_by_fixed_address(self, context, fixed_address):
return []
def get_instance_id_by_floating_address(self, context, address):
- raise NotImplementedError()
+ """Returns the instance id a floating ip's fixed ip is allocated to"""
+ client = quantumv2.get_client(context)
+ fip = self._get_floating_ip_by_address(client, address)
+ if not fip['port_id']:
+ return None
+ port = client.show_port(fip['port_id'])['port']
+ return port['device_id']
def get_vifs_by_instance(self, context, instance):
raise NotImplementedError()
@@ -317,21 +421,78 @@ class API(base.Base):
def get_vif_by_mac_address(self, context, mac_address):
raise NotImplementedError()
+ def _get_floating_ip_pool_id_by_name_or_id(self, client, name_or_id):
+ search_opts = {NET_EXTERNAL: True, 'fields': 'id'}
+ if utils.is_uuid_like(name_or_id):
+ search_opts.update({'id': name_or_id})
+ else:
+ search_opts.update({'name': name_or_id})
+ data = client.list_networks(**search_opts)
+ nets = data['networks']
+
+ if len(nets) == 1:
+ return nets[0]['id']
+ elif len(nets) == 0:
+ raise exception.FloatingIpPoolNotFound()
+ else:
+ msg = (_("Multiple floating IP pools matches found for name '%s'")
+ % name_or_id)
+ raise exception.NovaException(message=msg)
+
def allocate_floating_ip(self, context, pool=None):
"""Add a floating ip to a project from a pool."""
- raise NotImplementedError()
+ client = quantumv2.get_client(context)
+ pool = pool or FLAGS.default_floating_pool
+ pool_id = self._get_floating_ip_pool_id_by_name_or_id(client, pool)
+
+ # TODO(amotoki): handle exception during create_floatingip()
+ # At this timing it is ensured that a network for pool exists.
+ # quota error may be returned.
+ param = {'floatingip': {'floating_network_id': pool_id}}
+ fip = client.create_floatingip(param)
+ return fip['floatingip']['floating_ip_address']
+
+ def _get_floating_ip_by_address(self, client, address):
+ """Get floatingip from floating ip address"""
+ data = client.list_floatingips(floating_ip_address=address)
+ fips = data['floatingips']
+ if len(fips) == 0:
+ raise exception.FloatingIpNotFoundForAddress(address=address)
+ elif len(fips) > 1:
+ raise exception.FloatingIpMultipleFoundForAddress(address=address)
+ return fips[0]
def release_floating_ip(self, context, address,
affect_auto_assigned=False):
"""Remove a floating ip with the given address from a project."""
- raise NotImplementedError()
+
+ # Note(amotoki): We cannot handle a case where multiple pools
+ # have overlapping IP address range. In this case we cannot use
+ # 'address' as a unique key.
+ # This is a limitation of the current nova.
+
+ # Note(amotoki): 'affect_auto_assigned' is not respected
+ # since it is not used anywhere in nova code and I could
+ # find why this parameter exists.
+
+ client = quantumv2.get_client(context)
+ fip = self._get_floating_ip_by_address(client, address)
+ if fip['port_id']:
+ raise exception.FloatingIpAssociated(address=address)
+ client.delete_floatingip(fip['id'])
@refresh_cache
def disassociate_floating_ip(self, context, instance, address,
affect_auto_assigned=False):
- """Disassociate a floating ip from the fixed ip
- it is associated with."""
- raise NotImplementedError()
+ """Disassociate a floating ip from the instance."""
+
+ # Note(amotoki): 'affect_auto_assigned' is not respected
+ # since it is not used anywhere in nova code and I could
+ # find why this parameter exists.
+
+ client = quantumv2.get_client(context)
+ fip = self._get_floating_ip_by_address(client, address)
+ client.update_floatingip(fip['id'], {'floatingip': {'port_id': None}})
def add_network_to_project(self, context, project_id, network_uuid=None):
"""Force add a network to the project."""
diff --git a/nova/tests/network/test_quantumv2.py b/nova/tests/network/test_quantumv2.py
index 888027b21..aacd19760 100644
--- a/nova/tests/network/test_quantumv2.py
+++ b/nova/tests/network/test_quantumv2.py
@@ -196,6 +196,30 @@ class TestQuantumv2(test.TestCase):
'gateway_ip': '10.0.2.1',
'dns_nameservers': ['8.8.2.1', '8.8.2.2']})
+ self.fip_pool = {'id': '4fdbfd74-eaf8-4884-90d9-00bd6f10c2d3',
+ 'name': 'ext_net',
+ 'router:external': True,
+ 'tenant_id': 'admin_tenantid'}
+ self.fip_pool_nova = {'id': '435e20c3-d9f1-4f1b-bee5-4611a1dd07db',
+ 'name': 'nova',
+ 'router:external': True,
+ 'tenant_id': 'admin_tenantid'}
+ self.fip_unassociated = {'tenant_id': 'my_tenantid',
+ 'id': 'fip_id1',
+ 'floating_ip_address': '172.24.4.227',
+ 'floating_network_id': self.fip_pool['id'],
+ 'port_id': None,
+ 'fixed_ip_address': None,
+ 'router_id': None}
+ fixed_ip_address = self.port_data2[1]['fixed_ips'][0]['ip_address']
+ self.fip_associated = {'tenant_id': 'my_tenantid',
+ 'id': 'fip_id2',
+ 'floating_ip_address': '172.24.4.228',
+ 'floating_network_id': self.fip_pool['id'],
+ 'port_id': self.port_data2[1]['id'],
+ 'fixed_ip_address': fixed_ip_address,
+ 'router_id': 'router_id1'}
+
def tearDown(self):
try:
self.mox.UnsetStubs()
@@ -618,3 +642,262 @@ class TestQuantumv2(test.TestCase):
# specify only first and last network
req_ids = [net['id'] for net in (self.nets3[0], self.nets3[-1])]
self._get_available_networks(prv_nets, pub_nets, req_ids)
+
+ def test_get_floating_ip_pools(self):
+ api = quantumapi.API()
+ search_opts = {'router:external': True}
+ self.moxed_client.list_networks(**search_opts).\
+ AndReturn({'networks': [self.fip_pool, self.fip_pool_nova]})
+ self.mox.ReplayAll()
+ pools = api.get_floating_ip_pools(self.context)
+ expected = [{'name': self.fip_pool['name']},
+ {'name': self.fip_pool_nova['name']}]
+ self.assertEqual(expected, pools)
+
+ def _get_expected_fip_model(self, fip_data, idx=0):
+ expected = {'id': fip_data['id'],
+ 'address': fip_data['floating_ip_address'],
+ 'pool': self.fip_pool['name'],
+ 'project_id': fip_data['tenant_id'],
+ 'fixed_ip_id': fip_data['port_id'],
+ 'fixed_ip':
+ {'address': fip_data['fixed_ip_address']},
+ 'instance': ({'uuid': self.port_data2[idx]['device_id']}
+ if fip_data['port_id']
+ else None)}
+ return expected
+
+ def _test_get_floating_ip(self, fip_data, idx=0, by_address=False):
+ api = quantumapi.API()
+ fip_id = fip_data['id']
+ net_id = fip_data['floating_network_id']
+ address = fip_data['floating_ip_address']
+ if by_address:
+ self.moxed_client.list_floatingips(floating_ip_address=address).\
+ AndReturn({'floatingips': [fip_data]})
+ else:
+ self.moxed_client.show_floatingip(fip_id).\
+ AndReturn({'floatingip': fip_data})
+ self.moxed_client.show_network(net_id).\
+ AndReturn({'network': self.fip_pool})
+ if fip_data['port_id']:
+ self.moxed_client.show_port(fip_data['port_id']).\
+ AndReturn({'port': self.port_data2[idx]})
+ self.mox.ReplayAll()
+
+ expected = self._get_expected_fip_model(fip_data, idx)
+
+ if by_address:
+ fip = api.get_floating_ip_by_address(self.context, address)
+ else:
+ fip = api.get_floating_ip(self.context, fip_id)
+ self.assertEqual(expected, fip)
+
+ def test_get_floating_ip_unassociated(self):
+ self._test_get_floating_ip(self.fip_unassociated, idx=0)
+
+ def test_get_floating_ip_associated(self):
+ self._test_get_floating_ip(self.fip_associated, idx=1)
+
+ def test_get_floating_ip_by_address(self):
+ self._test_get_floating_ip(self.fip_unassociated, idx=0,
+ by_address=True)
+
+ def test_get_floating_ip_by_address_associated(self):
+ self._test_get_floating_ip(self.fip_associated, idx=1,
+ by_address=True)
+
+ def test_get_floating_ip_by_address_not_found(self):
+ api = quantumapi.API()
+ address = self.fip_unassociated['floating_ip_address']
+ self.moxed_client.list_floatingips(floating_ip_address=address).\
+ AndReturn({'floatingips': []})
+ self.mox.ReplayAll()
+ self.assertRaises(exception.FloatingIpNotFoundForAddress,
+ api.get_floating_ip_by_address,
+ self.context, address)
+
+ def test_get_floating_ip_by_address_multiple_found(self):
+ api = quantumapi.API()
+ address = self.fip_unassociated['floating_ip_address']
+ self.moxed_client.list_floatingips(floating_ip_address=address).\
+ AndReturn({'floatingips': [self.fip_unassociated] * 2})
+ self.mox.ReplayAll()
+ self.assertRaises(exception.FloatingIpMultipleFoundForAddress,
+ api.get_floating_ip_by_address,
+ self.context, address)
+
+ def test_get_floating_ips_by_project(self):
+ api = quantumapi.API()
+ project_id = self.context.project_id
+ self.moxed_client.list_floatingips(tenant_id=project_id).\
+ AndReturn({'floatingips': [self.fip_unassociated,
+ self.fip_associated]})
+ search_opts = {'router:external': True}
+ self.moxed_client.list_networks(**search_opts).\
+ AndReturn({'networks': [self.fip_pool, self.fip_pool_nova]})
+ self.moxed_client.list_ports(tenant_id=project_id).\
+ AndReturn({'ports': self.port_data2})
+ self.mox.ReplayAll()
+
+ expected = [self._get_expected_fip_model(self.fip_unassociated),
+ self._get_expected_fip_model(self.fip_associated, idx=1)]
+ fips = api.get_floating_ips_by_project(self.context)
+ self.assertEqual(expected, fips)
+
+ def _test_get_instance_id_by_floating_address(self, fip_data,
+ associated=False):
+ api = quantumapi.API()
+ address = fip_data['floating_ip_address']
+ self.moxed_client.list_floatingips(floating_ip_address=address).\
+ AndReturn({'floatingips': [fip_data]})
+ if associated:
+ self.moxed_client.show_port(fip_data['port_id']).\
+ AndReturn({'port': self.port_data2[1]})
+ self.mox.ReplayAll()
+
+ if associated:
+ expected = self.port_data2[1]['device_id']
+ else:
+ expected = None
+ fip = api.get_instance_id_by_floating_address(self.context, address)
+ self.assertEqual(expected, fip)
+
+ def test_get_instance_id_by_floating_address(self):
+ self._test_get_instance_id_by_floating_address(self.fip_unassociated)
+
+ def test_get_instance_id_by_floating_address_associated(self):
+ self._test_get_instance_id_by_floating_address(self.fip_associated,
+ associated=True)
+
+ def test_allocate_floating_ip(self):
+ api = quantumapi.API()
+ pool_name = self.fip_pool['name']
+ pool_id = self.fip_pool['id']
+ search_opts = {'router:external': True,
+ 'fields': 'id',
+ 'name': pool_name}
+ self.moxed_client.list_networks(**search_opts).\
+ AndReturn({'networks': [self.fip_pool]})
+ self.moxed_client.create_floatingip(
+ {'floatingip': {'floating_network_id': pool_id}}).\
+ AndReturn({'floatingip': self.fip_unassociated})
+ self.mox.ReplayAll()
+ fip = api.allocate_floating_ip(self.context, 'ext_net')
+ self.assertEqual(fip, self.fip_unassociated['floating_ip_address'])
+
+ def test_allocate_floating_ip_with_pool_id(self):
+ api = quantumapi.API()
+ pool_name = self.fip_pool['name']
+ pool_id = self.fip_pool['id']
+ search_opts = {'router:external': True,
+ 'fields': 'id',
+ 'id': pool_id}
+ self.moxed_client.list_networks(**search_opts).\
+ AndReturn({'networks': [self.fip_pool]})
+ self.moxed_client.create_floatingip(
+ {'floatingip': {'floating_network_id': pool_id}}).\
+ AndReturn({'floatingip': self.fip_unassociated})
+ self.mox.ReplayAll()
+ fip = api.allocate_floating_ip(self.context, pool_id)
+ self.assertEqual(fip, self.fip_unassociated['floating_ip_address'])
+
+ def test_allocate_floating_ip_with_default_pool(self):
+ api = quantumapi.API()
+ pool_name = self.fip_pool_nova['name']
+ pool_id = self.fip_pool_nova['id']
+ search_opts = {'router:external': True,
+ 'fields': 'id',
+ 'name': pool_name}
+ self.moxed_client.list_networks(**search_opts).\
+ AndReturn({'networks': [self.fip_pool_nova]})
+ self.moxed_client.create_floatingip(
+ {'floatingip': {'floating_network_id': pool_id}}).\
+ AndReturn({'floatingip': self.fip_unassociated})
+ self.mox.ReplayAll()
+ fip = api.allocate_floating_ip(self.context)
+ self.assertEqual(fip, self.fip_unassociated['floating_ip_address'])
+
+ def test_release_floating_ip(self):
+ api = quantumapi.API()
+ address = self.fip_unassociated['floating_ip_address']
+ fip_id = self.fip_unassociated['id']
+
+ self.moxed_client.list_floatingips(floating_ip_address=address).\
+ AndReturn({'floatingips': [self.fip_unassociated]})
+ self.moxed_client.delete_floatingip(fip_id)
+ self.mox.ReplayAll()
+ api.release_floating_ip(self.context, address)
+
+ def test_release_floating_ip_associated(self):
+ api = quantumapi.API()
+ address = self.fip_associated['floating_ip_address']
+ fip_id = self.fip_associated['id']
+
+ self.moxed_client.list_floatingips(floating_ip_address=address).\
+ AndReturn({'floatingips': [self.fip_associated]})
+ self.mox.ReplayAll()
+ self.assertRaises(exception.FloatingIpAssociated,
+ api.release_floating_ip, self.context, address)
+
+ def _setup_mock_for_refresh_cache(self, api):
+ nw_info = self.mox.CreateMock(model.NetworkInfo)
+ nw_info.json()
+ self.mox.StubOutWithMock(api, '_get_instance_nw_info')
+ api._get_instance_nw_info(mox.IgnoreArg(), self.instance).\
+ AndReturn(nw_info)
+ self.mox.StubOutWithMock(api.db, 'instance_info_cache_update')
+ api.db.instance_info_cache_update(mox.IgnoreArg(),
+ self.instance['uuid'],
+ mox.IgnoreArg())
+
+ def test_associate_floating_ip(self):
+ api = quantumapi.API()
+ address = self.fip_associated['floating_ip_address']
+ fixed_address = self.fip_associated['fixed_ip_address']
+ fip_id = self.fip_associated['id']
+
+ search_opts = {'device_owner': 'compute:nova',
+ 'device_id': self.instance['uuid']}
+ self.moxed_client.list_ports(**search_opts).\
+ AndReturn({'ports': [self.port_data2[1]]})
+ self.moxed_client.list_floatingips(floating_ip_address=address).\
+ AndReturn({'floatingips': [self.fip_associated]})
+ self.moxed_client.update_floatingip(
+ fip_id, {'floatingip': {'port_id': self.fip_associated['port_id'],
+ 'fixed_ip_address': fixed_address}})
+ self._setup_mock_for_refresh_cache(api)
+
+ self.mox.ReplayAll()
+ api.associate_floating_ip(self.context, self.instance,
+ address, fixed_address)
+
+ def test_associate_floating_ip_not_found_fixed_ip(self):
+ api = quantumapi.API()
+ address = self.fip_associated['floating_ip_address']
+ fixed_address = self.fip_associated['fixed_ip_address']
+ fip_id = self.fip_associated['id']
+
+ search_opts = {'device_owner': 'compute:nova',
+ 'device_id': self.instance['uuid']}
+ self.moxed_client.list_ports(**search_opts).\
+ AndReturn({'ports': [self.port_data2[0]]})
+
+ self.mox.ReplayAll()
+ self.assertRaises(exception.FixedIpNotFoundForAddress,
+ api.associate_floating_ip, self.context,
+ self.instance, address, fixed_address)
+
+ def test_disassociate_floating_ip(self):
+ api = quantumapi.API()
+ address = self.fip_associated['floating_ip_address']
+ fip_id = self.fip_associated['id']
+
+ self.moxed_client.list_floatingips(floating_ip_address=address).\
+ AndReturn({'floatingips': [self.fip_associated]})
+ self.moxed_client.update_floatingip(
+ fip_id, {'floatingip': {'port_id': None}})
+ self._setup_mock_for_refresh_cache(api)
+
+ self.mox.ReplayAll()
+ api.disassociate_floating_ip(self.context, self.instance, address)
diff --git a/tools/pip-requires b/tools/pip-requires
index b3716352c..1e43a9c04 100644
--- a/tools/pip-requires
+++ b/tools/pip-requires
@@ -19,5 +19,5 @@ Babel>=0.9.6
iso8601>=0.1.4
httplib2
setuptools_git>=0.4
-python-quantumclient>=2.0
+python-quantumclient>=2.1
python-glanceclient>=0.5.0,<2