summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAkihiro MOTOKI <motoki@da.jp.nec.com>2012-09-13 21:22:33 +0900
committerAkihiro MOTOKI <motoki@da.jp.nec.com>2012-10-09 15:28:26 +0900
commit7948b7a572e33e3fd541e27f2617d832eb43b504 (patch)
treedca8b796c48f6518a525a53082fe2997620c2d75
parentfb101685cc14ed9b0396ce966e571d3fb457c32f (diff)
downloadnova-7948b7a572e33e3fd541e27f2617d832eb43b504.tar.gz
nova-7948b7a572e33e3fd541e27f2617d832eb43b504.tar.xz
nova-7948b7a572e33e3fd541e27f2617d832eb43b504.zip
Proxy floating IP calls to quantum
Fixes bug 1023169. Also fixes bug 1031119. In Folsom, floating IP support is moved to Quantum. By this commit floating IP calls to nova are proxied to Quantum and nova command can be used to manage floating IPs. pip-requires is also updated and now requires python-quantumclient >=2.1 which supports floating IP feature in Quantum. Change-Id: I2c32948a8fe291601216dc62d546da64a8fb8428
-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