diff options
-rw-r--r-- | nova/api/openstack/v2/contrib/networks.py | 117 | ||||
-rw-r--r-- | nova/api/openstack/v2/extensions.py | 11 | ||||
-rw-r--r-- | nova/network/api.py | 26 | ||||
-rw-r--r-- | nova/network/manager.py | 17 | ||||
-rw-r--r-- | nova/tests/api/openstack/v2/contrib/test_networks.py | 137 | ||||
-rw-r--r-- | nova/tests/api/openstack/v2/test_extensions.py | 1 | ||||
-rw-r--r-- | nova/tests/fake_network.py | 6 | ||||
-rw-r--r-- | nova/tests/test_network.py | 61 |
8 files changed, 374 insertions, 2 deletions
diff --git a/nova/api/openstack/v2/contrib/networks.py b/nova/api/openstack/v2/contrib/networks.py new file mode 100644 index 000000000..4a96e534f --- /dev/null +++ b/nova/api/openstack/v2/contrib/networks.py @@ -0,0 +1,117 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Grid Dynamics +# Copyright 2011 OpenStack LLC. +# 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. + + +from webob import exc + +from nova.api.openstack.v2 import extensions +from nova import exception +from nova import flags +from nova import log as logging +import nova.network.api + + +FLAGS = flags.FLAGS +LOG = logging.getLogger('nova.api.openstack.v2.contrib.networks') + + +def network_dict(network): + if network: + fields = ('bridge', 'vpn_public_port', 'dhcp_start', + 'bridge_interface', 'updated_at', 'id', 'cidr_v6', + 'deleted_at', 'gateway', 'label', 'project_id', + 'vpn_private_address', 'deleted', 'vlan', 'broadcast', + 'netmask', 'injected', 'cidr', 'vpn_public_address', + 'multi_host', 'dns1', 'host', 'gateway_v6', 'netmask_v6', + 'created_at') + return dict((field, network[field]) for field in fields) + else: + return {} + + +class NetworkController(object): + + def __init__(self, network_api=None): + self.network_api = network_api or nova.network.api.API() + + def action(self, req, id, body): + _actions = { + 'disassociate': self._disassociate, + } + + for action, data in body.iteritems(): + try: + return _actions[action](req, id, body) + except KeyError: + msg = _("Network does not have %s action") % action + raise exc.HTTPBadRequest(explanation=msg) + + raise exc.HTTPBadRequest(explanation=_("Invalid request body")) + + def _disassociate(self, request, network_id, body): + context = request.environ['nova.context'] + LOG.debug(_("Disassociating network with id %s" % network_id)) + try: + self.network_api.disassociate(context, network_id) + except exception.NetworkNotFound: + raise exc.HTTPNotFound(_("Network not found")) + return exc.HTTPAccepted() + + def index(self, req): + context = req.environ['nova.context'] + networks = self.network_api.get_all(context) + result = [network_dict(net_ref) for net_ref in networks] + return {'networks': result} + + def show(self, req, id): + context = req.environ['nova.context'] + LOG.debug(_("Showing network with id %s") % id) + try: + network = self.network_api.get(context, id) + except exception.NetworkNotFound: + raise exc.HTTPNotFound(_("Network not found")) + return {'network': network_dict(network)} + + def delete(self, req, id): + context = req.environ['nova.context'] + LOG.info(_("Deleting network with id %s") % id) + try: + self.network_api.delete(context, id) + except exception.NetworkNotFound: + raise exc.HTTPNotFound(_("Network not found")) + return exc.HTTPAccepted() + + def create(self, req, id, body=None): + raise exc.HTTPNotImplemented() + + +class Networks(extensions.ExtensionDescriptor): + """Admin-only Network Management Extension""" + + name = "Networks" + alias = "os-networks" + namespace = "http://docs.openstack.org/compute/ext/networks/api/v1.1" + updated = "2011-12-23 00:00:00" + admin_only = True + + def get_resources(self): + member_actions = {'action': 'POST'} + res = extensions.ResourceExtension('os-networks', + NetworkController(), + member_actions=member_actions) + return [res] diff --git a/nova/api/openstack/v2/extensions.py b/nova/api/openstack/v2/extensions.py index b6ed897cb..dfda7c0aa 100644 --- a/nova/api/openstack/v2/extensions.py +++ b/nova/api/openstack/v2/extensions.py @@ -547,6 +547,17 @@ class ExtensionsXMLSerializer(xmlutil.XMLTemplateSerializer): return ExtensionTemplate() +def require_admin(f): + @functools.wraps(f) + def wraps(self, req, *args, **kwargs): + if 'nova.context' in req.environ and\ + req.environ['nova.context'].is_admin: + return f(self, req, *args, **kwargs) + else: + raise exception.AdminRequired() + return wraps + + def wrap_errors(fn): """Ensure errors are not passed along.""" def wrapped(*args): diff --git a/nova/network/api.py b/nova/network/api.py index 89a746359..b80d15f2f 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -16,8 +16,6 @@ # License for the specific language governing permissions and limitations # under the License. -"""Handles all requests relating to instances (guest vms).""" - from nova.db import base from nova import exception from nova import flags @@ -33,6 +31,30 @@ LOG = logging.getLogger('nova.network') class API(base.Base): """API for interacting with the network manager.""" + def get_all(self, context): + return rpc.call(context, + FLAGS.network_topic, + {'method': 'get_all_networks'}) + + def get(self, context, fixed_range, network_uuid): + return rpc.call(context, + FLAGS.network_topic, + {'method': 'get_network', + 'args': {'network_uuid': network_uuid}}) + + def delete(self, context, network_uuid): + return rpc.call(context, + FLAGS.network_topic, + {'method': 'delete_network', + 'args': {'fixed_range': None, + 'uuid': network_uuid}}) + + def disassociate(self, context, network_uuid): + return rpc.call(context, + FLAGS.network_topic, + {'method': 'disassociate_network', + 'args': {'network_uuid': network_uuid}}) + def get_floating_ip(self, context, id): return rpc.call(context, FLAGS.network_topic, diff --git a/nova/network/manager.py b/nova/network/manager.py index 2d62581e0..d4f88988d 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -1106,6 +1106,23 @@ class NetworkManager(manager.SchedulerDependentManager): vifs = self.db.virtual_interface_get_by_instance(context, instance_id) return [dict(vif.iteritems()) for vif in vifs] + def get_network(self, context, network_uuid): + networks = self._get_networks_by_uuids(context, [network_uuid]) + try: + network = networks[0] + except (IndexError, TypeError): + raise exception.NetworkNotFound(network_id=network_uuid) + + return dict(network.iteritems()) + + def get_all_networks(self, context): + networks = self.db.network_get_all(context) + return [dict(network.iteritems()) for network in networks] + + def disassociate_network(self, context, network_uuid): + network = self.get_network(context, network_uuid) + self.db.network_disassociate(context, network['id']) + class FlatManager(NetworkManager): """Basic network where no vlans are used. diff --git a/nova/tests/api/openstack/v2/contrib/test_networks.py b/nova/tests/api/openstack/v2/contrib/test_networks.py new file mode 100644 index 000000000..04bd82e2c --- /dev/null +++ b/nova/tests/api/openstack/v2/contrib/test_networks.py @@ -0,0 +1,137 @@ +# Copyright 2011 Grid Dynamics +# Copyright 2011 OpenStack LLC. +# 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 copy + +import webob + +from nova.api.openstack.v2.contrib import networks +from nova import context +from nova import exception +from nova import test +from nova.tests.api.openstack import fakes + + +FAKE_NETWORKS = [ + { + 'bridge': 'br100', 'vpn_public_port': 1000, + 'dhcp_start': '10.0.0.3', 'bridge_interface': 'eth0', + 'updated_at': '2011-08-16 09:26:13.048257', 'id': 1, + 'cidr_v6': None, 'deleted_at': None, + 'gateway': '10.0.0.1', 'label': 'mynet_0', + 'project_id': '1234', + 'vpn_private_address': '10.0.0.2', 'deleted': False, + 'vlan': 100, 'broadcast': '10.0.0.7', + 'netmask': '255.255.255.248', 'injected': False, + 'cidr': '10.0.0.0/29', + 'vpn_public_address': '127.0.0.1', 'multi_host': False, + 'dns1': None, 'host': 'nsokolov-desktop', + 'gateway_v6': None, 'netmask_v6': None, + 'created_at': '2011-08-15 06:19:19.387525', + }, + { + 'bridge': 'br101', 'vpn_public_port': 1001, + 'dhcp_start': '10.0.0.11', 'bridge_interface': 'eth0', + 'updated_at': None, 'id': 2, 'cidr_v6': None, + 'deleted_at': None, 'gateway': '10.0.0.9', + 'label': 'mynet_1', 'project_id': None, + 'vpn_private_address': '10.0.0.10', 'deleted': False, + 'vlan': 101, 'broadcast': '10.0.0.15', + 'netmask': '255.255.255.248', 'injected': False, + 'cidr': '10.0.0.10/29', 'vpn_public_address': None, + 'multi_host': False, 'dns1': None, 'host': None, + 'gateway_v6': None, 'netmask_v6': None, + 'created_at': '2011-08-15 06:19:19.885495', + }, +] + + +class FakeNetworkAPI(object): + + def __init__(self): + self.networks = copy.deepcopy(FAKE_NETWORKS) + + def delete(self, context, network_id): + for i, network in enumerate(self.networks): + if network['id'] == network_id: + del self.networks[0] + return True + raise exception.NetworkNotFound() + + #NOTE(bcwaldon): this does nothing other than check for existance + def disassociate(self, context, network_id): + for i, network in enumerate(self.networks): + if network['id'] == network_id: + return True + raise exception.NetworkNotFound() + + def get_all(self, context): + return self.networks + + def get(self, context, network_id): + for network in self.networks: + if network['id'] == network_id: + return network + raise exception.NetworkNotFound() + + +class NetworksTest(test.TestCase): + + def setUp(self): + super(NetworksTest, self).setUp() + self.flags(allow_admin_api=True) + self.fake_network_api = FakeNetworkAPI() + self.controller = networks.NetworkController(self.fake_network_api) + fakes.stub_out_networking(self.stubs) + fakes.stub_out_rate_limiting(self.stubs) + self.context = context.RequestContext('user', '1234', is_admin=True) + + def test_network_list_all(self): + req = fakes.HTTPRequest.blank('/v2/1234/os-networks') + res_dict = self.controller.index(req) + self.assertEquals(res_dict, {'networks': FAKE_NETWORKS}) + + def test_network_disassociate(self): + req = fakes.HTTPRequest.blank('/v2/1234/os-networks/1/action') + res = self.controller.action(req, 1, {'disassociate': None}) + self.assertEqual(res.status_int, 202) + + def test_network_disassociate_not_found(self): + req = fakes.HTTPRequest.blank('/v2/1234/os-networks/100/action') + self.assertRaises(webob.exc.HTTPNotFound, + self.controller.action, + req, 100, {'disassociate': None}) + + def test_network_get(self): + req = fakes.HTTPRequest.blank('/v2/1234/os-networks/1') + res_dict = self.controller.show(req, 1) + expected = {'network': FAKE_NETWORKS[0]} + self.assertEqual(res_dict, expected) + + def test_network_get_not_found(self): + req = fakes.HTTPRequest.blank('/v2/1234/os-networks/100') + self.assertRaises(webob.exc.HTTPNotFound, + self.controller.show, req, 100) + + def test_network_delete(self): + req = fakes.HTTPRequest.blank('/v2/1234/os-networks/1') + res = self.controller.delete(req, 1) + self.assertEqual(res.status_int, 202) + + def test_network_delete_not_found(self): + req = fakes.HTTPRequest.blank('/v2/1234/os-networks/100') + self.assertRaises(webob.exc.HTTPNotFound, + self.controller.delete, req, 100) diff --git a/nova/tests/api/openstack/v2/test_extensions.py b/nova/tests/api/openstack/v2/test_extensions.py index 2063e6e2d..a63902bbb 100644 --- a/nova/tests/api/openstack/v2/test_extensions.py +++ b/nova/tests/api/openstack/v2/test_extensions.py @@ -122,6 +122,7 @@ class ExtensionControllerTest(ExtensionTestCase): "Volumes", "VolumeTypes", "Zones", + "Networks", ] self.ext_list.sort() diff --git a/nova/tests/fake_network.py b/nova/tests/fake_network.py index e1af85239..13e5d4f68 100644 --- a/nova/tests/fake_network.py +++ b/nova/tests/fake_network.py @@ -89,6 +89,12 @@ class FakeNetworkManager(network_manager.NetworkManager): def network_get_all(self, context): raise exception.NoNetworksFound() + def network_get_all_by_uuids(self, context): + raise exception.NoNetworksFound() + + def network_disassociate(self, context, network_id): + return True + def virtual_interface_get_all(self, context): floats = [{'address': '172.16.1.1'}, {'address': '172.16.1.2'}, diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index dc26b0298..a37abc0e7 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -1079,6 +1079,67 @@ class CommonNetworkTestCase(test.TestCase): self.assertEqual(len(res), 1) self.assertEqual(res[0]['instance_id'], _vifs[2]['instance_id']) + def test_get_network(self): + manager = fake_network.FakeNetworkManager() + fake_context = context.RequestContext('user', 'project') + self.mox.StubOutWithMock(manager.db, 'network_get_all_by_uuids') + manager.db.network_get_all_by_uuids(mox.IgnoreArg(), + mox.IgnoreArg()).\ + AndReturn(networks) + self.mox.ReplayAll() + uuid = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' + network = manager.get_network(fake_context, uuid) + self.assertEqual(network['uuid'], uuid) + + def test_get_network_not_found(self): + manager = fake_network.FakeNetworkManager() + fake_context = context.RequestContext('user', 'project') + self.mox.StubOutWithMock(manager.db, 'network_get_all_by_uuids') + manager.db.network_get_all_by_uuids(mox.IgnoreArg(), + mox.IgnoreArg()).\ + AndReturn([]) + self.mox.ReplayAll() + uuid = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee' + self.assertRaises(exception.NetworkNotFound, + manager.get_network, fake_context, uuid) + + def test_get_all_networks(self): + manager = fake_network.FakeNetworkManager() + fake_context = context.RequestContext('user', 'project') + self.mox.StubOutWithMock(manager.db, 'network_get_all') + manager.db.network_get_all(mox.IgnoreArg()).\ + AndReturn(networks) + self.mox.ReplayAll() + output = manager.get_all_networks(fake_context) + self.assertEqual(len(networks), 2) + self.assertEqual(output[0]['uuid'], + 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa') + self.assertEqual(output[1]['uuid'], + 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb') + + def test_disassociate_network(self): + manager = fake_network.FakeNetworkManager() + fake_context = context.RequestContext('user', 'project') + self.mox.StubOutWithMock(manager.db, 'network_get_all_by_uuids') + manager.db.network_get_all_by_uuids(mox.IgnoreArg(), + mox.IgnoreArg()).\ + AndReturn(networks) + self.mox.ReplayAll() + uuid = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' + manager.disassociate_network(fake_context, uuid) + + def test_disassociate_network_not_found(self): + manager = fake_network.FakeNetworkManager() + fake_context = context.RequestContext('user', 'project') + self.mox.StubOutWithMock(manager.db, 'network_get_all_by_uuids') + manager.db.network_get_all_by_uuids(mox.IgnoreArg(), + mox.IgnoreArg()).\ + AndReturn([]) + self.mox.ReplayAll() + uuid = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee' + self.assertRaises(exception.NetworkNotFound, + manager.disassociate_network, fake_context, uuid) + class TestRPCFixedManager(network_manager.RPCAllocateFixedIP, network_manager.NetworkManager): |