summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorChris Yeoh <cyeoh@au1.ibm.com>2012-10-24 16:42:03 +1030
committerChris Yeoh <cyeoh@au1.ibm.com>2012-11-13 23:56:40 +1030
commitf1e19b46cb6efdcd4b446095ef5efcb2a7c8359c (patch)
treec06deebf9c7acd1efc9a39537e698d579293a1a1 /nova
parent3c9e48d1119b77428346680c2a009c44ff9bf2ce (diff)
downloadnova-f1e19b46cb6efdcd4b446095ef5efcb2a7c8359c.tar.gz
nova-f1e19b46cb6efdcd4b446095ef5efcb2a7c8359c.tar.xz
nova-f1e19b46cb6efdcd4b446095ef5efcb2a7c8359c.zip
Adds REST API support for Fixed IPs
This adds an extension that provides a REST API for getting information about a fixed ip, reserving a fixed ip and unreserving a fixed ip. The interface is accessed via /v2/{tenant_id}/os-fixed-ips/<ip_address> # GET ip info /v2/{tenant_id}/os-fixed-ips/<ip_address>/action # POST reserve/unreserve ip This forms part of the work to provide APIs for functionality currently implemented by nova-manage that needs direct db access so nova-manage can eventually be removed Adds db function fixed_ip_get_by_address_detailed in order to optimise being able to get the instance and network information for a fixed ip at the same time as the rest of the fixed ip information Change-Id: I92edf4e6b74b14bb9c49d5bc0c79e40d3a496bc5 Implements: blueprint apis-for-nova-manage DocImpact
Diffstat (limited to 'nova')
-rw-r--r--nova/api/openstack/compute/contrib/fixed_ips.py100
-rw-r--r--nova/db/api.py5
-rw-r--r--nova/db/sqlalchemy/api.py23
-rw-r--r--nova/tests/api/openstack/compute/contrib/test_fixed_ips.py164
-rw-r--r--nova/tests/api/openstack/compute/test_extensions.py1
-rw-r--r--nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl8
-rw-r--r--nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl3
-rw-r--r--nova/tests/policy.json1
8 files changed, 305 insertions, 0 deletions
diff --git a/nova/api/openstack/compute/contrib/fixed_ips.py b/nova/api/openstack/compute/contrib/fixed_ips.py
new file mode 100644
index 000000000..178847d64
--- /dev/null
+++ b/nova/api/openstack/compute/contrib/fixed_ips.py
@@ -0,0 +1,100 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 IBM
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import urllib
+import webob.exc
+
+from nova.api.openstack import extensions
+from nova.api.openstack import wsgi
+from nova import db
+from nova import exception
+from nova.openstack.common import log as logging
+
+LOG = logging.getLogger(__name__)
+authorize = extensions.extension_authorizer('compute', 'fixed_ips')
+
+
+class FixedIPController(object):
+ def show(self, req, id):
+ """Return data about the given fixed ip."""
+ context = req.environ['nova.context']
+ authorize(context)
+
+ try:
+ fixed_ip = db.fixed_ip_get_by_address_detailed(context, id)
+ except exception.FixedIpNotFoundForAddress as ex:
+ raise webob.exc.HTTPNotFound(explanation=str(ex))
+
+ fixed_ip_info = {"fixed_ip": {}}
+ if fixed_ip[1] is None:
+ msg = _("Fixed IP %s has been deleted") % id
+ raise webob.exc.HTTPNotFound(explanation=msg)
+
+ fixed_ip_info['fixed_ip']['cidr'] = fixed_ip[1]['cidr']
+ fixed_ip_info['fixed_ip']['address'] = fixed_ip[0]['address']
+
+ if fixed_ip[2]:
+ fixed_ip_info['fixed_ip']['hostname'] = fixed_ip[2]['hostname']
+ fixed_ip_info['fixed_ip']['host'] = fixed_ip[2]['host']
+ else:
+ fixed_ip_info['fixed_ip']['hostname'] = None
+ fixed_ip_info['fixed_ip']['host'] = None
+
+ return fixed_ip_info
+
+ def action(self, req, id, body):
+ context = req.environ['nova.context']
+ authorize(context)
+ if 'reserve' in body:
+ return self._set_reserved(context, id, True)
+ elif 'unreserve' in body:
+ return self._set_reserved(context, id, False)
+ else:
+ raise webob.exc.HTTPBadRequest(
+ explanation="No valid action specified")
+
+ def _set_reserved(self, context, address, reserved):
+ try:
+ fixed_ip = db.fixed_ip_get_by_address(context, address)
+ db.fixed_ip_update(context, fixed_ip['address'],
+ {'reserved': reserved})
+ except exception.FixedIpNotFoundForAddress as ex:
+ msg = _("Fixed IP %s not found") % address
+ raise webob.exc.HTTPNotFound(explanation=msg)
+
+ return webob.exc.HTTPAccepted()
+
+
+class Fixed_ips(extensions.ExtensionDescriptor):
+ """Fixed IPs support"""
+
+ name = "FixedIPs"
+ alias = "os-fixed-ips"
+ namespace = "http://docs.openstack.org/compute/ext/fixed_ips/api/v2"
+ updated = "2012-10-18T13:25:27-06:00"
+
+ def __init__(self, ext_mgr):
+ ext_mgr.register(self)
+
+ def get_resources(self):
+ member_actions = {'action': 'POST'}
+ resources = []
+ resource = extensions.ResourceExtension('os-fixed-ips',
+ FixedIPController(),
+ member_actions=member_actions)
+ resources.append(resource)
+ return resources
diff --git a/nova/db/api.py b/nova/db/api.py
index 04786a1fb..45686d9ef 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -466,6 +466,11 @@ def fixed_ip_get_by_address(context, address):
return IMPL.fixed_ip_get_by_address(context, address)
+def fixed_ip_get_by_address_detailed(context, address):
+ """Get detailed fixed ip info by address or raise if it does not exist."""
+ return IMPL.fixed_ip_get_by_address_detailed(context, address)
+
+
def fixed_ip_get_by_instance(context, instance_uuid):
"""Get fixed ips by instance or raise if none exist."""
return IMPL.fixed_ip_get_by_instance(context, instance_uuid)
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 72caad74d..bc92aaa62 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -1151,6 +1151,29 @@ def fixed_ip_get_by_address(context, address, session=None):
return result
+@require_admin_context
+def fixed_ip_get_by_address_detailed(context, address, session=None):
+ # This method returns a tuple of (models.FixedIp, models.Network,
+ # models.Instance).
+ if not session:
+ session = get_session()
+
+ result = session.query(models.FixedIp, models.Network, models.Instance).\
+ filter_by(address=address).\
+ outerjoin((models.Network,
+ models.Network.id ==
+ models.FixedIp.network_id)).\
+ outerjoin((models.Instance,
+ models.Instance.uuid ==
+ models.FixedIp.instance_uuid)).\
+ first()
+
+ if not result:
+ raise exception.FixedIpNotFoundForAddress(address=address)
+
+ return result
+
+
@require_context
def fixed_ip_get_by_instance(context, instance_uuid):
if not uuidutils.is_uuid_like(instance_uuid):
diff --git a/nova/tests/api/openstack/compute/contrib/test_fixed_ips.py b/nova/tests/api/openstack/compute/contrib/test_fixed_ips.py
new file mode 100644
index 000000000..4d5ca5e5c
--- /dev/null
+++ b/nova/tests/api/openstack/compute/contrib/test_fixed_ips.py
@@ -0,0 +1,164 @@
+# Copyright 2012 IBM
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import webob
+
+from nova.api.openstack.compute.contrib import fixed_ips
+from nova import context
+from nova import db
+from nova import exception
+from nova.openstack.common import jsonutils
+from nova import test
+from nova.tests.api.openstack import fakes
+
+
+fake_fixed_ips = [{'id': 1,
+ 'address': '192.168.1.1',
+ 'network_id': 1,
+ 'virtual_interface_id': 1,
+ 'instance_uuid': '1',
+ 'allocated': False,
+ 'leased': False,
+ 'reserved': False,
+ 'host': None},
+ {'id': 2,
+ 'address': '192.168.1.2',
+ 'network_id': 1,
+ 'virtual_interface_id': 2,
+ 'instance_uuid': '2',
+ 'allocated': False,
+ 'leased': False,
+ 'reserved': False,
+ 'host': None},
+ ]
+
+
+def fake_fixed_ip_get_by_address(context, address):
+ for fixed_ip in fake_fixed_ips:
+ if fixed_ip['address'] == address:
+ return fixed_ip
+ raise exception.FixedIpNotFoundForAddress(address=address)
+
+
+def fake_fixed_ip_get_by_address_detailed(context, address):
+ network = {'id': 1,
+ 'cidr': "192.168.1.0/24"}
+ for fixed_ip in fake_fixed_ips:
+ if fixed_ip['address'] == address:
+ return (fixed_ip, FakeModel(network), None)
+ raise exception.FixedIpNotFoundForAddress(address=address)
+
+
+def fake_fixed_ip_update(context, address, values):
+ fixed_ip = fake_fixed_ip_get_by_address(context, address)
+ if fixed_ip is None:
+ raise exception.FixedIpNotFoundForAddress(address=address)
+ else:
+ for key in values:
+ fixed_ip[key] = values[key]
+
+
+class FakeModel(object):
+ """Stubs out for model."""
+ def __init__(self, values):
+ self.values = values
+
+ def __getattr__(self, name):
+ return self.values[name]
+
+ def __getitem__(self, key):
+ if key in self.values:
+ return self.values[key]
+ else:
+ raise NotImplementedError()
+
+ def __repr__(self):
+ return '<FakeModel: %s>' % self.values
+
+
+def fake_network_get_all(context):
+ network = {'id': 1,
+ 'cidr': "192.168.1.0/24"}
+ return [FakeModel(network)]
+
+
+class FixedIpTest(test.TestCase):
+
+ def setUp(self):
+ super(FixedIpTest, self).setUp()
+
+ self.stubs.Set(db, "fixed_ip_get_by_address",
+ fake_fixed_ip_get_by_address)
+ self.stubs.Set(db, "fixed_ip_get_by_address_detailed",
+ fake_fixed_ip_get_by_address_detailed)
+ self.stubs.Set(db, "fixed_ip_update", fake_fixed_ip_update)
+
+ self.context = context.get_admin_context()
+ self.controller = fixed_ips.FixedIPController()
+
+ def tearDown(self):
+ super(FixedIpTest, self).tearDown()
+
+ def test_fixed_ips_get(self):
+ req = fakes.HTTPRequest.blank('/v2/fake/os-fixed-ips/192.168.1.1')
+ res_dict = self.controller.show(req, '192.168.1.1')
+ response = {'fixed_ip': {'cidr': '192.168.1.0/24',
+ 'hostname': None,
+ 'host': None,
+ 'address': '192.168.1.1'}}
+ self.assertEqual(response, res_dict)
+
+ def test_fixed_ips_get_fail(self):
+ req = fakes.HTTPRequest.blank('/v2/fake/os-fixed-ips/10.0.0.1')
+ self.assertRaises(webob.exc.HTTPNotFound, self.controller.show, req,
+ '10.0.0.1')
+
+ def test_fixed_ip_reserve(self):
+ fake_fixed_ips[0]['reserved'] = False
+ ip_addr = '192.168.1.1'
+ body = {'reserve': None}
+ req = fakes.HTTPRequest.blank(
+ '/v2/fake/os-fixed-ips/192.168.1.1/action')
+ result = self.controller.action(req, "192.168.1.1", body)
+
+ self.assertEqual('202 Accepted', result.status)
+ self.assertEqual(fake_fixed_ips[0]['reserved'], True)
+
+ def test_fixed_ip_reserve_bad_ip(self):
+ ip_addr = '10.0.0.1'
+ body = {'reserve': None}
+ req = fakes.HTTPRequest.blank(
+ '/v2/fake/os-fixed-ips/10.0.0.1/action')
+ self.assertRaises(webob.exc.HTTPNotFound, self.controller.action, req,
+ '10.0.0.1', body)
+
+ def test_fixed_ip_unreserve(self):
+ fake_fixed_ips[0]['reserved'] = True
+ ip_addr = '192.168.1.1'
+ body = {'unreserve': None}
+ req = fakes.HTTPRequest.blank(
+ '/v2/fake/os-fixed-ips/192.168.1.1/action')
+ result = self.controller.action(req, "192.168.1.1", body)
+
+ self.assertEqual('202 Accepted', result.status)
+ self.assertEqual(fake_fixed_ips[0]['reserved'], False)
+
+ def test_fixed_ip_unreserve_bad_ip(self):
+ ip_addr = '10.0.0.1'
+ body = {'unreserve': None}
+ req = fakes.HTTPRequest.blank(
+ '/v2/fake/os-fixed-ips/10.0.0.1/action')
+ self.assertRaises(webob.exc.HTTPNotFound, self.controller.action, req,
+ '10.0.0.1', body)
diff --git a/nova/tests/api/openstack/compute/test_extensions.py b/nova/tests/api/openstack/compute/test_extensions.py
index f3e65a150..8282c4c19 100644
--- a/nova/tests/api/openstack/compute/test_extensions.py
+++ b/nova/tests/api/openstack/compute/test_extensions.py
@@ -167,6 +167,7 @@ class ExtensionControllerTest(ExtensionTestCase):
"DiskConfig",
"ExtendedStatus",
"ExtendedServerAttributes",
+ "FixedIPs",
"FlavorAccess",
"FlavorDisabled",
"FlavorExtraSpecs",
diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl
index 34f305967..2ed3486eb 100644
--- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl
+++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl
@@ -137,6 +137,14 @@
"updated": "%(timestamp)s"
},
{
+ "alias": "os-fixed-ips",
+ "description": "Fixed IPs support",
+ "links": [],
+ "name": "FixedIPs",
+ "namespace": "http://docs.openstack.org/compute/ext/fixed_ips/api/v2",
+ "updated": "2012-10-18T13:25:27-06:00"
+ },
+ {
"alias": "os-flavor-access",
"description": "%(text)s",
"links": [],
diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl
index 44aa5ee81..3962ebe9b 100644
--- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl
+++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl
@@ -51,6 +51,9 @@
<extension alias="os-deferred-delete" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/deferred-delete/api/v1.1" name="DeferredDelete">
<description>%(text)s</description>
</extension>
+ <extension alias="os-fixed-ips" name="FixedIPs" namespace="http://docs.openstack.org/compute/ext/fixed_ips/api/v2" updated="2012-10-18T13:25:27-06:00">
+ <description>Fixed IPs support</description>
+ </extension>
<extension alias="os-flavor-access" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/flavor_access/api/v2" name="FlavorAccess">
<description>%(text)s</description>
</extension>
diff --git a/nova/tests/policy.json b/nova/tests/policy.json
index bf94d4e49..6381a9031 100644
--- a/nova/tests/policy.json
+++ b/nova/tests/policy.json
@@ -94,6 +94,7 @@
"compute_extension:disk_config": "",
"compute_extension:extended_server_attributes": "",
"compute_extension:extended_status": "",
+ "compute_extension:fixed_ips": "",
"compute_extension:flavor_access": "",
"compute_extension:flavor_disabled": "",
"compute_extension:flavor_rxtx": "",