From 8f945475324fd99293066e45b207f2638a065cdd Mon Sep 17 00:00:00 2001 From: Chris Yeoh Date: Wed, 10 Sep 2014 19:40:13 +0930 Subject: [PATCH] Port os-networks plugin to v2.1(v3) infrastructure Ports os-networks extension and adapts it to the v2.1/v3 API framework. API behaviour is identical. - unittest code modified to share testing with both v2/v2.1 - Adds expected error decorators for API methods Partially implements blueprint v2-on-v3-api Change-Id: I94b7b476cbb02725a396725a379417b64afd58a7 --- .../os-networks/network-add-req.json | 3 + .../os-networks/network-create-req.json | 12 + .../os-networks/network-create-resp.json | 36 +++ .../os-networks/network-show-resp.json | 36 +++ .../networks-disassociate-req.json | 3 + .../os-networks/networks-list-resp.json | 72 ++++++ etc/nova/policy.json | 3 + .../openstack/compute/plugins/v3/networks.py | 216 ++++++++++++++++++ .../compute/contrib/test_networks.py | 162 ++++++++----- nova/tests/fake_policy.py | 2 + .../os-networks/network-add-req.json.tpl | 3 + .../os-networks/network-create-req.json.tpl | 12 + .../os-networks/network-create-resp.json.tpl | 36 +++ .../os-networks/network-show-resp.json.tpl | 37 +++ .../networks-disassociate-req.json.tpl | 3 + .../os-networks/networks-list-resp.json.tpl | 72 ++++++ nova/tests/integrated/v3/test_networks.py | 73 ++++++ setup.cfg | 1 + 18 files changed, 720 insertions(+), 62 deletions(-) create mode 100644 doc/v3/api_samples/os-networks/network-add-req.json create mode 100644 doc/v3/api_samples/os-networks/network-create-req.json create mode 100644 doc/v3/api_samples/os-networks/network-create-resp.json create mode 100644 doc/v3/api_samples/os-networks/network-show-resp.json create mode 100644 doc/v3/api_samples/os-networks/networks-disassociate-req.json create mode 100644 doc/v3/api_samples/os-networks/networks-list-resp.json create mode 100644 nova/api/openstack/compute/plugins/v3/networks.py create mode 100644 nova/tests/integrated/v3/api_samples/os-networks/network-add-req.json.tpl create mode 100644 nova/tests/integrated/v3/api_samples/os-networks/network-create-req.json.tpl create mode 100644 nova/tests/integrated/v3/api_samples/os-networks/network-create-resp.json.tpl create mode 100644 nova/tests/integrated/v3/api_samples/os-networks/network-show-resp.json.tpl create mode 100644 nova/tests/integrated/v3/api_samples/os-networks/networks-disassociate-req.json.tpl create mode 100644 nova/tests/integrated/v3/api_samples/os-networks/networks-list-resp.json.tpl create mode 100644 nova/tests/integrated/v3/test_networks.py diff --git a/doc/v3/api_samples/os-networks/network-add-req.json b/doc/v3/api_samples/os-networks/network-add-req.json new file mode 100644 index 0000000000..aca6770b3b --- /dev/null +++ b/doc/v3/api_samples/os-networks/network-add-req.json @@ -0,0 +1,3 @@ +{ + "id": "1" +} diff --git a/doc/v3/api_samples/os-networks/network-create-req.json b/doc/v3/api_samples/os-networks/network-create-req.json new file mode 100644 index 0000000000..18515bd6c4 --- /dev/null +++ b/doc/v3/api_samples/os-networks/network-create-req.json @@ -0,0 +1,12 @@ +{ + "network": { + "label": "new net 111", + "cidr": "10.20.105.0/24", + "mtu": 9000, + "dhcp_server": "10.20.105.2", + "enable_dhcp": false, + "share_address": true, + "allowed_start": "10.20.105.10", + "allowed_end": "10.20.105.200" + } +} diff --git a/doc/v3/api_samples/os-networks/network-create-resp.json b/doc/v3/api_samples/os-networks/network-create-resp.json new file mode 100644 index 0000000000..4364e50b2d --- /dev/null +++ b/doc/v3/api_samples/os-networks/network-create-resp.json @@ -0,0 +1,36 @@ +{ + "network": { + "bridge": null, + "bridge_interface": null, + "broadcast": "10.20.105.255", + "cidr": "10.20.105.0/24", + "cidr_v6": null, + "created_at": null, + "deleted": null, + "deleted_at": null, + "dhcp_server": "10.20.105.2", + "dhcp_start": "10.20.105.2", + "dns1": null, + "dns2": null, + "enable_dhcp": false, + "gateway": "10.20.105.1", + "gateway_v6": null, + "host": null, + "id": "d7a17c0c-457e-4ab4-a99c-4fa1762f5359", + "injected": null, + "label": "new net 111", + "mtu": 9000, + "multi_host": null, + "netmask": "255.255.255.0", + "netmask_v6": null, + "priority": null, + "project_id": null, + "rxtx_base": null, + "share_address": true, + "updated_at": null, + "vlan": null, + "vpn_private_address": null, + "vpn_public_address": null, + "vpn_public_port": null + } +} \ No newline at end of file diff --git a/doc/v3/api_samples/os-networks/network-show-resp.json b/doc/v3/api_samples/os-networks/network-show-resp.json new file mode 100644 index 0000000000..9741395c63 --- /dev/null +++ b/doc/v3/api_samples/os-networks/network-show-resp.json @@ -0,0 +1,36 @@ +{ + "network": { + "bridge": "br100", + "bridge_interface": "eth0", + "broadcast": "10.0.0.7", + "cidr": "10.0.0.0/29", + "cidr_v6": null, + "created_at": "2011-08-15T06:19:19.387525", + "deleted": false, + "deleted_at": null, + "dhcp_server": "10.0.0.1", + "dhcp_start": "10.0.0.3", + "dns1": null, + "dns2": null, + "enable_dhcp": true, + "gateway": "10.0.0.1", + "gateway_v6": null, + "host": "nsokolov-desktop", + "id": "20c8acc0-f747-4d71-a389-46d078ebf047", + "injected": false, + "label": "mynet_0", + "mtu": null, + "multi_host": false, + "netmask": "255.255.255.248", + "netmask_v6": null, + "priority": null, + "project_id": "1234", + "rxtx_base": null, + "share_address": false, + "updated_at": "2011-08-16T09:26:13.048257", + "vlan": 100, + "vpn_private_address": "10.0.0.2", + "vpn_public_address": "127.0.0.1", + "vpn_public_port": 1000 + } +} \ No newline at end of file diff --git a/doc/v3/api_samples/os-networks/networks-disassociate-req.json b/doc/v3/api_samples/os-networks/networks-disassociate-req.json new file mode 100644 index 0000000000..2e09d15a60 --- /dev/null +++ b/doc/v3/api_samples/os-networks/networks-disassociate-req.json @@ -0,0 +1,3 @@ +{ + "disassociate": null +} diff --git a/doc/v3/api_samples/os-networks/networks-list-resp.json b/doc/v3/api_samples/os-networks/networks-list-resp.json new file mode 100644 index 0000000000..49bdad5826 --- /dev/null +++ b/doc/v3/api_samples/os-networks/networks-list-resp.json @@ -0,0 +1,72 @@ +{ + "networks": [ + { + "bridge": "br100", + "bridge_interface": "eth0", + "broadcast": "10.0.0.7", + "cidr": "10.0.0.0/29", + "cidr_v6": null, + "created_at": "2011-08-15T06:19:19.387525", + "deleted": false, + "deleted_at": null, + "dhcp_server": "10.0.0.1", + "dhcp_start": "10.0.0.3", + "dns1": null, + "dns2": null, + "enable_dhcp": true, + "gateway": "10.0.0.1", + "gateway_v6": null, + "host": "nsokolov-desktop", + "id": "20c8acc0-f747-4d71-a389-46d078ebf047", + "injected": false, + "label": "mynet_0", + "mtu": null, + "multi_host": false, + "netmask": "255.255.255.248", + "netmask_v6": null, + "priority": null, + "project_id": "1234", + "rxtx_base": null, + "share_address": false, + "updated_at": "2011-08-16T09:26:13.048257", + "vlan": 100, + "vpn_private_address": "10.0.0.2", + "vpn_public_address": "127.0.0.1", + "vpn_public_port": 1000 + }, + { + "bridge": "br101", + "bridge_interface": "eth0", + "broadcast": "10.0.0.15", + "cidr": "10.0.0.10/29", + "cidr_v6": null, + "created_at": "2011-08-15T06:19:19.885495", + "deleted": false, + "deleted_at": null, + "dhcp_server": "10.0.0.9", + "dhcp_start": "10.0.0.11", + "dns1": null, + "dns2": null, + "enable_dhcp": true, + "gateway": "10.0.0.9", + "gateway_v6": null, + "host": null, + "id": "20c8acc0-f747-4d71-a389-46d078ebf000", + "injected": false, + "label": "mynet_1", + "mtu": null, + "multi_host": false, + "netmask": "255.255.255.248", + "netmask_v6": null, + "priority": null, + "project_id": null, + "rxtx_base": null, + "share_address": false, + "updated_at": null, + "vlan": 101, + "vpn_private_address": "10.0.0.10", + "vpn_public_address": null, + "vpn_public_port": 1001 + } + ] +} \ No newline at end of file diff --git a/etc/nova/policy.json b/etc/nova/policy.json index cdb8014dcd..c45be3358b 100644 --- a/etc/nova/policy.json +++ b/etc/nova/policy.json @@ -197,6 +197,9 @@ "compute_extension:v3:os-multinic:discoverable": "", "compute_extension:networks": "rule:admin_api", "compute_extension:networks:view": "", + "compute_extension:v3:os-networks": "rule:admin_api", + "compute_extension:v3:os-networks:view": "", + "compute_extension:v3:os-networks:discoverable": "", "compute_extension:networks_associate": "rule:admin_api", "compute_extension:v3:os-pause-server:discoverable": "", "compute_extension:v3:os-pause-server:pause": "rule:admin_or_owner", diff --git a/nova/api/openstack/compute/plugins/v3/networks.py b/nova/api/openstack/compute/plugins/v3/networks.py new file mode 100644 index 0000000000..5597b4e2a9 --- /dev/null +++ b/nova/api/openstack/compute/plugins/v3/networks.py @@ -0,0 +1,216 @@ +# Copyright 2011 Grid Dynamics +# Copyright 2011 OpenStack Foundation +# 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 netaddr +import webob +from webob import exc + +from nova.api.openstack import extensions +from nova.api.openstack import wsgi +from nova import exception +from nova.i18n import _ +from nova import network +from nova.objects import base as base_obj +from nova.objects import fields as obj_fields +from nova.openstack.common import log as logging + +ALIAS = 'os-networks' +LOG = logging.getLogger(__name__) +authorize = extensions.extension_authorizer('compute', 'v3:' + ALIAS) +authorize_view = extensions.extension_authorizer('compute', + 'v3:' + ALIAS + ':view') + + +def network_dict(context, network): + fields = ('id', 'cidr', 'netmask', 'gateway', 'broadcast', 'dns1', 'dns2', + 'cidr_v6', 'gateway_v6', 'label', 'netmask_v6') + admin_fields = ('created_at', 'updated_at', 'deleted_at', 'deleted', + 'injected', 'bridge', 'vlan', 'vpn_public_address', + 'vpn_public_port', 'vpn_private_address', 'dhcp_start', + 'project_id', 'host', 'bridge_interface', 'multi_host', + 'priority', 'rxtx_base', 'mtu', 'dhcp_server', + 'enable_dhcp', 'share_address') + if network: + # NOTE(mnaser): We display a limited set of fields so users can know + # what networks are available, extra system-only fields + # are only visible if they are an admin. + if context.is_admin: + fields += admin_fields + # TODO(mriedem): Remove the NovaObject type check once the + # neutronv2 API is returning Network objects from get/get_all. + is_obj = isinstance(network, base_obj.NovaObject) + result = {} + for field in fields: + # NOTE(mriedem): If network is an object, IPAddress fields need to + # be cast to a string so they look the same in the response as + # before the objects conversion. + if is_obj and isinstance(network.fields[field].AUTO_TYPE, + obj_fields.IPAddress): + val = network.get(field) + if val is not None: + result[field] = str(network.get(field)) + else: + result[field] = val + else: + # It's either not an object or it's not an IPAddress field. + result[field] = network.get(field) + uuid = network.get('uuid') + if uuid: + result['id'] = uuid + return result + else: + return {} + + +class NetworkController(wsgi.Controller): + + def __init__(self, network_api=None): + self.network_api = network_api or network.API() + + @extensions.expected_errors(()) + def index(self, req): + context = req.environ['nova.context'] + authorize_view(context) + networks = self.network_api.get_all(context) + result = [network_dict(context, net_ref) for net_ref in networks] + return {'networks': result} + + @extensions.expected_errors((404, 501)) + @wsgi.action("disassociate") + @wsgi.response(202) + def _disassociate_host_and_project(self, req, id, body): + context = req.environ['nova.context'] + authorize(context) + + try: + self.network_api.associate(context, id, host=None, project=None) + except exception.NetworkNotFound: + msg = _("Network not found") + raise exc.HTTPNotFound(explanation=msg) + except NotImplementedError: + msg = _('Disassociate network is not implemented by the ' + 'configured Network API') + raise exc.HTTPNotImplemented(explanation=msg) + + @extensions.expected_errors(404) + def show(self, req, id): + context = req.environ['nova.context'] + authorize_view(context) + + try: + network = self.network_api.get(context, id) + except exception.NetworkNotFound: + msg = _("Network not found") + raise exc.HTTPNotFound(explanation=msg) + return {'network': network_dict(context, network)} + + @extensions.expected_errors((404, 409)) + def delete(self, req, id): + context = req.environ['nova.context'] + authorize(context) + + try: + self.network_api.delete(context, id) + except exception.NetworkInUse as e: + raise exc.HTTPConflict(explanation=e.format_message()) + except exception.NetworkNotFound: + msg = _("Network not found") + raise exc.HTTPNotFound(explanation=msg) + return webob.Response(status_int=202) + + @extensions.expected_errors((400, 409, 501)) + def create(self, req, body): + context = req.environ['nova.context'] + authorize(context) + + def bad(e): + return exc.HTTPBadRequest(explanation=e) + + if not (body and body.get("network")): + raise bad(_("Missing network in body")) + + params = body["network"] + if not params.get("label"): + raise bad(_("Network label is required")) + + cidr = params.get("cidr") or params.get("cidr_v6") + if not cidr: + raise bad(_("Network cidr or cidr_v6 is required")) + + if params.get("project_id") == "": + params["project_id"] = None + + try: + params["num_networks"] = 1 + try: + params["network_size"] = netaddr.IPNetwork(cidr).size + except netaddr.AddrFormatError: + raise exception.InvalidCidr(cidr=cidr) + + network = self.network_api.create(context, **params)[0] + except exception.NovaException as ex: + if ex.code == 400: + raise bad(ex.format_message()) + elif ex.code == 409: + raise exc.HTTPConflict(explanation=ex.format_message()) + raise + return {"network": network_dict(context, network)} + + @extensions.expected_errors((400, 409, 501)) + @wsgi.response(202) + def add(self, req, body): + context = req.environ['nova.context'] + authorize(context) + if not body: + msg = _("Missing request body") + raise exc.HTTPBadRequest(explanation=msg) + + network_id = body.get('id', None) + project_id = context.project_id + + try: + self.network_api.add_network_to_project( + context, project_id, network_id) + except NotImplementedError: + msg = (_("VLAN support must be enabled")) + raise exc.HTTPNotImplemented(explanation=msg) + except Exception as ex: + msg = (_("Cannot associate network %(network)s" + " with project %(project)s: %(message)s") % + {"network": network_id or "", + "project": project_id, + "message": getattr(ex, "value", ex)}) + raise exc.HTTPBadRequest(explanation=msg) + + +class Networks(extensions.V3APIExtensionBase): + """Admin-only Network Management Extension.""" + + name = "Networks" + alias = ALIAS + version = 1 + + def get_resources(self): + member_actions = {'action': 'POST'} + collection_actions = {'add': 'POST'} + res = extensions.ResourceExtension( + 'os-networks', NetworkController(), + member_actions=member_actions, + collection_actions=collection_actions) + return [res] + + def get_controller_extensions(self): + return [] diff --git a/nova/tests/api/openstack/compute/contrib/test_networks.py b/nova/tests/api/openstack/compute/contrib/test_networks.py index 061af8394b..857403af3f 100644 --- a/nova/tests/api/openstack/compute/contrib/test_networks.py +++ b/nova/tests/api/openstack/compute/contrib/test_networks.py @@ -28,6 +28,7 @@ import webob from nova.api.openstack.compute.contrib import networks_associate from nova.api.openstack.compute.contrib import os_networks as networks from nova.api.openstack.compute.contrib import os_tenant_networks as tnet +from nova.api.openstack.compute.plugins.v3 import networks as networks_v21 from nova.api.openstack import extensions import nova.context from nova import exception @@ -226,48 +227,48 @@ class FakeNetworkAPI(object): # NOTE(vish): tests that network create Exceptions actually return # the proper error responses -class NetworkCreateExceptionsTest(test.TestCase): +class NetworkCreateExceptionsTestV21(test.TestCase): + url_prefix = '/v2/1234' + + class PassthroughAPI(object): + def __init__(self): + self.network_manager = manager.FlatDHCPManager() + + def create(self, *args, **kwargs): + return self.network_manager.create_networks(*args, **kwargs) def setUp(self): - super(NetworkCreateExceptionsTest, self).setUp() - ext_mgr = extensions.ExtensionManager() - ext_mgr.extensions = {'os-extended-networks': 'fake'} - - class PassthroughAPI(): - def __init__(self): - self.network_manager = manager.FlatDHCPManager() - - def create(self, *args, **kwargs): - return self.network_manager.create_networks(*args, **kwargs) - - self.controller = networks.NetworkController( - PassthroughAPI(), ext_mgr) + super(NetworkCreateExceptionsTestV21, self).setUp() + self._setup() fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) + def _setup(self): + self.controller = networks_v21.NetworkController(self.PassthroughAPI()) + def test_network_create_bad_vlan(self): - req = fakes.HTTPRequest.blank('/v2/1234/os-networks') + req = fakes.HTTPRequest.blank(self.url_prefix + '/os-networks') net = copy.deepcopy(NEW_NETWORK) net['network']['vlan_start'] = 'foo' self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, req, net) def test_network_create_no_cidr(self): - req = fakes.HTTPRequest.blank('/v2/1234/os-networks') + req = fakes.HTTPRequest.blank(self.url_prefix + '/os-networks') net = copy.deepcopy(NEW_NETWORK) net['network']['cidr'] = '' self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, req, net) def test_network_create_invalid_fixed_cidr(self): - req = fakes.HTTPRequest.blank('/v2/1234/os-networks') + req = fakes.HTTPRequest.blank(self.url_prefix + '/os-networks') net = copy.deepcopy(NEW_NETWORK) net['network']['fixed_cidr'] = 'foo' self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, req, net) def test_network_create_invalid_start(self): - req = fakes.HTTPRequest.blank('/v2/1234/os-networks') + req = fakes.HTTPRequest.blank(self.url_prefix + '/os-networks') net = copy.deepcopy(NEW_NETWORK) net['network']['allowed_start'] = 'foo' self.assertRaises(webob.exc.HTTPBadRequest, @@ -284,26 +285,40 @@ class NetworkCreateExceptionsTest(test.TestCase): self.stubs.Set(objects.NetworkList, 'get_all', get_all) - req = fakes.HTTPRequest.blank('/v2/1234/os-networks') + req = fakes.HTTPRequest.blank(self.url_prefix + '/os-networks') net = copy.deepcopy(NEW_NETWORK) net['network']['cidr'] = '10.0.0.0/24' self.assertRaises(webob.exc.HTTPConflict, self.controller.create, req, net) -class NetworksTest(test.NoDBTestCase): +class NetworkCreateExceptionsTestV2(NetworkCreateExceptionsTestV21): - def setUp(self): - super(NetworksTest, self).setUp() - self.fake_network_api = FakeNetworkAPI() + def _setup(self): ext_mgr = extensions.ExtensionManager() ext_mgr.extensions = {'os-extended-networks': 'fake'} + self.controller = networks.NetworkController( - self.fake_network_api, - ext_mgr) + self.PassthroughAPI(), ext_mgr) + + +class NetworksTestV21(test.NoDBTestCase): + url_prefix = '/v2/1234' + + def setUp(self): + super(NetworksTestV21, self).setUp() + self.fake_network_api = FakeNetworkAPI() + self._setup() fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) + def _setup(self): + self.controller = networks_v21.NetworkController( + self.fake_network_api) + + def _check_status(self, res, method, code): + self.assertEqual(method.wsgi_code, 202) + @staticmethod def network_uuid_to_id(network): network['id'] = network['uuid'] @@ -311,7 +326,7 @@ class NetworksTest(test.NoDBTestCase): def test_network_list_all_as_user(self): self.maxDiff = None - req = fakes.HTTPRequest.blank('/v2/1234/os-networks') + req = fakes.HTTPRequest.blank(self.url_prefix + '/os-networks') res_dict = self.controller.index(req) self.assertEqual(res_dict, {'networks': []}) @@ -322,13 +337,13 @@ class NetworksTest(test.NoDBTestCase): network_uuid=uuid, project=project_id) res_dict = self.controller.index(req) - expected = [FAKE_USER_NETWORKS[0]] + expected = [copy.deepcopy(FAKE_USER_NETWORKS[0])] for network in expected: self.network_uuid_to_id(network) self.assertEqual({'networks': expected}, res_dict) def test_network_list_all_as_admin(self): - req = fakes.HTTPRequest.blank('/v2/1234/os-networks') + req = fakes.HTTPRequest.blank(self.url_prefix + '/os-networks') req.environ["nova.context"].is_admin = True res_dict = self.controller.index(req) expected = copy.deepcopy(FAKE_NETWORKS) @@ -338,22 +353,26 @@ class NetworksTest(test.NoDBTestCase): def test_network_disassociate(self): uuid = FAKE_NETWORKS[0]['uuid'] - req = fakes.HTTPRequest.blank('/v2/1234/os-networks/%s/action' % uuid) + req = fakes.HTTPRequest.blank(self.url_prefix + + '/os-networks/%s/action' % uuid) res = self.controller._disassociate_host_and_project( req, uuid, {'disassociate': None}) - self.assertEqual(res.status_int, 202) + self._check_status(res, self.controller._disassociate_host_and_project, + 202) self.assertIsNone(self.fake_network_api.networks[0]['project_id']) self.assertIsNone(self.fake_network_api.networks[0]['host']) def test_network_disassociate_not_found(self): - req = fakes.HTTPRequest.blank('/v2/1234/os-networks/100/action') + req = fakes.HTTPRequest.blank(self.url_prefix + + '/os-networks/100/action') self.assertRaises(webob.exc.HTTPNotFound, self.controller._disassociate_host_and_project, req, 100, {'disassociate': None}) def test_network_get_as_user(self): uuid = FAKE_USER_NETWORKS[0]['uuid'] - req = fakes.HTTPRequest.blank('/v2/1234/os-networks/%s' % uuid) + req = fakes.HTTPRequest.blank(self.url_prefix + + '/os-networks/%s' % uuid) res_dict = self.controller.show(req, uuid) expected = {'network': copy.deepcopy(FAKE_USER_NETWORKS[0])} self.network_uuid_to_id(expected['network']) @@ -361,7 +380,8 @@ class NetworksTest(test.NoDBTestCase): def test_network_get_as_admin(self): uuid = FAKE_NETWORKS[0]['uuid'] - req = fakes.HTTPRequest.blank('/v2/1234/os-networks/%s' % uuid) + req = fakes.HTTPRequest.blank(self.url_prefix + + '/os-networks/%s' % uuid) req.environ["nova.context"].is_admin = True res_dict = self.controller.show(req, uuid) expected = {'network': copy.deepcopy(FAKE_NETWORKS[0])} @@ -369,72 +389,61 @@ class NetworksTest(test.NoDBTestCase): self.assertEqual(expected, res_dict) def test_network_get_not_found(self): - req = fakes.HTTPRequest.blank('/v2/1234/os-networks/100') + req = fakes.HTTPRequest.blank(self.url_prefix + '/os-networks/100') self.assertRaises(webob.exc.HTTPNotFound, self.controller.show, req, 100) def test_network_delete(self): uuid = FAKE_NETWORKS[0]['uuid'] - req = fakes.HTTPRequest.blank('/v2/1234/os-networks/%s' % uuid) + req = fakes.HTTPRequest.blank(self.url_prefix + + '/os-networks/%s' % uuid) res = self.controller.delete(req, 1) - self.assertEqual(res.status_int, 202) + self._check_status(res, self.controller._disassociate_host_and_project, + 202) def test_network_delete_not_found(self): - req = fakes.HTTPRequest.blank('/v2/1234/os-networks/100') + req = fakes.HTTPRequest.blank(self.url_prefix + '/os-networks/100') self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete, req, 100) def test_network_delete_in_use(self): - req = fakes.HTTPRequest.blank('/v2/1234/os-networks/-1') + req = fakes.HTTPRequest.blank(self.url_prefix + '/os-networks/-1') self.assertRaises(webob.exc.HTTPConflict, self.controller.delete, req, -1) def test_network_add(self): uuid = FAKE_NETWORKS[1]['uuid'] - req = fakes.HTTPRequest.blank('/v2/1234/os-networks/add') + req = fakes.HTTPRequest.blank(self.url_prefix + '/os-networks/add') res = self.controller.add(req, {'id': uuid}) - self.assertEqual(res.status_int, 202) - req = fakes.HTTPRequest.blank('/v2/1234/os-networks/%s' % uuid) + self._check_status(res, self.controller._disassociate_host_and_project, + 202) + req = fakes.HTTPRequest.blank(self.url_prefix + + '/os-networks/%s' % uuid) req.environ["nova.context"].is_admin = True res_dict = self.controller.show(req, uuid) self.assertEqual(res_dict['network']['project_id'], 'fake') def test_network_create(self): - req = fakes.HTTPRequest.blank('/v2/1234/os-networks') + req = fakes.HTTPRequest.blank(self.url_prefix + '/os-networks') res_dict = self.controller.create(req, NEW_NETWORK) self.assertIn('network', res_dict) uuid = res_dict['network']['id'] - req = fakes.HTTPRequest.blank('/v2/1234/os-networks/%s' % uuid) + req = fakes.HTTPRequest.blank(self.url_prefix + + '/os-networks/%s' % uuid) res_dict = self.controller.show(req, uuid) self.assertTrue(res_dict['network']['label']. startswith(NEW_NETWORK['network']['label'])) def test_network_create_large(self): - req = fakes.HTTPRequest.blank('/v2/1234/os-networks') + req = fakes.HTTPRequest.blank(self.url_prefix + '/os-networks') large_network = copy.deepcopy(NEW_NETWORK) large_network['network']['cidr'] = '128.0.0.0/4' res_dict = self.controller.create(req, large_network) self.assertEqual(res_dict['network']['cidr'], large_network['network']['cidr']) - def test_network_create_not_extended(self): - self.stubs.Set(self.controller, 'extended', False) - - # NOTE(vish): Verify that new params are not passed through if - # extension is not enabled. - def no_mtu(*args, **kwargs): - if 'mtu' in kwargs: - raise test.TestingException("mtu should not pass through") - return [{}] - - self.stubs.Set(self.controller.network_api, 'create', no_mtu) - req = fakes.HTTPRequest.blank('/v2/1234/os-networks') - net = copy.deepcopy(NEW_NETWORK) - net['network']['mtu'] = 9000 - self.controller.create(req, net) - def test_network_create_bad_cidr(self): - req = fakes.HTTPRequest.blank('/v2/1234/os-networks') + req = fakes.HTTPRequest.blank(self.url_prefix + '/os-networks') net = copy.deepcopy(NEW_NETWORK) net['network']['cidr'] = '128.0.0.0/900' self.assertRaises(webob.exc.HTTPBadRequest, @@ -444,12 +453,41 @@ class NetworksTest(test.NoDBTestCase): uuid = FAKE_NETWORKS[1]['uuid'] self.flags(network_api_class='nova.network.neutronv2.api.API') controller = networks.NetworkController() - req = fakes.HTTPRequest.blank('/v2/1234/os-networks/%s/action' % uuid) + req = fakes.HTTPRequest.blank(self.url_prefix + + '/os-networks/%s/action' % uuid) self.assertRaises(webob.exc.HTTPNotImplemented, controller._disassociate_host_and_project, req, uuid, {'disassociate': None}) +class NetworksTestV2(NetworksTestV21): + + def _setup(self): + ext_mgr = extensions.ExtensionManager() + ext_mgr.extensions = {'os-extended-networks': 'fake'} + self.controller = networks.NetworkController(self.fake_network_api, + ext_mgr) + + def _check_status(self, res, method, code): + self.assertEqual(res.status_int, 202) + + def test_network_create_not_extended(self): + self.stubs.Set(self.controller, 'extended', False) + # NOTE(vish): Verify that new params are not passed through if + # extension is not enabled. + + def no_mtu(*args, **kwargs): + if 'mtu' in kwargs: + raise test.TestingException("mtu should not pass through") + return [{}] + + self.stubs.Set(self.controller.network_api, 'create', no_mtu) + req = fakes.HTTPRequest.blank(self.url_prefix + '/os-networks') + net = copy.deepcopy(NEW_NETWORK) + net['network']['mtu'] = 9000 + self.controller.create(req, net) + + class NetworksAssociateTest(test.NoDBTestCase): def setUp(self): diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py index 619730e5dc..951789803e 100644 --- a/nova/tests/fake_policy.py +++ b/nova/tests/fake_policy.py @@ -247,6 +247,8 @@ policy_data = """ "compute_extension:v3:os-multinic": "", "compute_extension:networks": "", "compute_extension:networks:view": "", + "compute_extension:v3:os-networks": "", + "compute_extension:v3:os-networks:view": "", "compute_extension:networks_associate": "", "compute_extension:os-tenant-networks": "", "compute_extension:v3:os-pause-server:pause": "", diff --git a/nova/tests/integrated/v3/api_samples/os-networks/network-add-req.json.tpl b/nova/tests/integrated/v3/api_samples/os-networks/network-add-req.json.tpl new file mode 100644 index 0000000000..aca6770b3b --- /dev/null +++ b/nova/tests/integrated/v3/api_samples/os-networks/network-add-req.json.tpl @@ -0,0 +1,3 @@ +{ + "id": "1" +} diff --git a/nova/tests/integrated/v3/api_samples/os-networks/network-create-req.json.tpl b/nova/tests/integrated/v3/api_samples/os-networks/network-create-req.json.tpl new file mode 100644 index 0000000000..18515bd6c4 --- /dev/null +++ b/nova/tests/integrated/v3/api_samples/os-networks/network-create-req.json.tpl @@ -0,0 +1,12 @@ +{ + "network": { + "label": "new net 111", + "cidr": "10.20.105.0/24", + "mtu": 9000, + "dhcp_server": "10.20.105.2", + "enable_dhcp": false, + "share_address": true, + "allowed_start": "10.20.105.10", + "allowed_end": "10.20.105.200" + } +} diff --git a/nova/tests/integrated/v3/api_samples/os-networks/network-create-resp.json.tpl b/nova/tests/integrated/v3/api_samples/os-networks/network-create-resp.json.tpl new file mode 100644 index 0000000000..5cf155b13f --- /dev/null +++ b/nova/tests/integrated/v3/api_samples/os-networks/network-create-resp.json.tpl @@ -0,0 +1,36 @@ +{ + "network": { + "bridge": null, + "vpn_public_port": null, + "dhcp_start": "%(ip)s", + "bridge_interface": null, + "updated_at": null, + "id": "%(id)s", + "cidr_v6": null, + "deleted_at": null, + "gateway": "%(ip)s", + "rxtx_base": null, + "label": "new net 111", + "priority": null, + "project_id": null, + "vpn_private_address": null, + "deleted": null, + "vlan": null, + "broadcast": "%(ip)s", + "netmask": "%(ip)s", + "injected": null, + "cidr": "10.20.105.0/24", + "vpn_public_address": null, + "multi_host": null, + "dns2": null, + "created_at": null, + "host": null, + "gateway_v6": null, + "netmask_v6": null, + "dns1": null, + "mtu": 9000, + "dhcp_server": "10.20.105.2", + "enable_dhcp": false, + "share_address": true + } +} diff --git a/nova/tests/integrated/v3/api_samples/os-networks/network-show-resp.json.tpl b/nova/tests/integrated/v3/api_samples/os-networks/network-show-resp.json.tpl new file mode 100644 index 0000000000..ac75fe7fb1 --- /dev/null +++ b/nova/tests/integrated/v3/api_samples/os-networks/network-show-resp.json.tpl @@ -0,0 +1,37 @@ +{ + "network": + { + "bridge": "br100", + "bridge_interface": "eth0", + "broadcast": "%(ip)s", + "cidr": "10.0.0.0/29", + "cidr_v6": null, + "created_at": "%(strtime)s", + "deleted": false, + "deleted_at": null, + "dhcp_start": "%(ip)s", + "dns1": null, + "dns2": null, + "gateway": "%(ip)s", + "gateway_v6": null, + "host": "nsokolov-desktop", + "id": "%(id)s", + "injected": false, + "label": "mynet_0", + "multi_host": false, + "netmask": "%(ip)s", + "netmask_v6": null, + "priority": null, + "project_id": "1234", + "rxtx_base": null, + "updated_at": "%(strtime)s", + "vlan": 100, + "vpn_private_address": "%(ip)s", + "vpn_public_address": "%(ip)s", + "vpn_public_port": 1000, + "mtu": null, + "dhcp_server": "%(ip)s", + "enable_dhcp": true, + "share_address": false + } +} diff --git a/nova/tests/integrated/v3/api_samples/os-networks/networks-disassociate-req.json.tpl b/nova/tests/integrated/v3/api_samples/os-networks/networks-disassociate-req.json.tpl new file mode 100644 index 0000000000..2e09d15a60 --- /dev/null +++ b/nova/tests/integrated/v3/api_samples/os-networks/networks-disassociate-req.json.tpl @@ -0,0 +1,3 @@ +{ + "disassociate": null +} diff --git a/nova/tests/integrated/v3/api_samples/os-networks/networks-list-resp.json.tpl b/nova/tests/integrated/v3/api_samples/os-networks/networks-list-resp.json.tpl new file mode 100644 index 0000000000..ccdd586a0f --- /dev/null +++ b/nova/tests/integrated/v3/api_samples/os-networks/networks-list-resp.json.tpl @@ -0,0 +1,72 @@ +{ + "networks": [ + { + "bridge": "br100", + "bridge_interface": "eth0", + "broadcast": "%(ip)s", + "cidr": "10.0.0.0/29", + "cidr_v6": null, + "created_at": "%(strtime)s", + "deleted": false, + "deleted_at": null, + "dhcp_start": "%(ip)s", + "dns1": null, + "dns2": null, + "gateway": "%(ip)s", + "gateway_v6": null, + "host": "nsokolov-desktop", + "id": "%(id)s", + "injected": false, + "label": "mynet_0", + "multi_host": false, + "netmask": "%(ip)s", + "netmask_v6": null, + "priority": null, + "project_id": "1234", + "rxtx_base": null, + "updated_at": "%(strtime)s", + "vlan": 100, + "vpn_private_address": "%(ip)s", + "vpn_public_address": "%(ip)s", + "vpn_public_port": 1000, + "mtu": null, + "dhcp_server": "%(ip)s", + "enable_dhcp": true, + "share_address": false + }, + { + "bridge": "br101", + "bridge_interface": "eth0", + "broadcast": "%(ip)s", + "cidr": "10.0.0.10/29", + "cidr_v6": null, + "created_at": "%(strtime)s", + "deleted": false, + "deleted_at": null, + "dhcp_start": "%(ip)s", + "dns1": null, + "dns2": null, + "gateway": "%(ip)s", + "gateway_v6": null, + "host": null, + "id": "%(id)s", + "injected": false, + "label": "mynet_1", + "multi_host": false, + "netmask": "%(ip)s", + "netmask_v6": null, + "priority": null, + "project_id": null, + "rxtx_base": null, + "updated_at": null, + "vlan": 101, + "vpn_private_address": "%(ip)s", + "vpn_public_address": null, + "vpn_public_port": 1001, + "mtu": null, + "dhcp_server": "%(ip)s", + "enable_dhcp": true, + "share_address": false + } + ] +} diff --git a/nova/tests/integrated/v3/test_networks.py b/nova/tests/integrated/v3/test_networks.py new file mode 100644 index 0000000000..df9a7184e7 --- /dev/null +++ b/nova/tests/integrated/v3/test_networks.py @@ -0,0 +1,73 @@ +# Copyright 2012 Nebula, Inc. +# Copyright 2013 IBM Corp. +# +# 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 nova.network import api as network_api +from nova.tests.api.openstack.compute.contrib import test_networks +from nova.tests.integrated.v3 import api_sample_base + + +class NetworksJsonTests(api_sample_base.ApiSampleTestBaseV3): + extension_name = "os-networks" + + def setUp(self): + super(NetworksJsonTests, self).setUp() + fake_network_api = test_networks.FakeNetworkAPI() + self.stubs.Set(network_api.API, "get_all", + fake_network_api.get_all) + self.stubs.Set(network_api.API, "get", + fake_network_api.get) + self.stubs.Set(network_api.API, "associate", + fake_network_api.associate) + self.stubs.Set(network_api.API, "delete", + fake_network_api.delete) + self.stubs.Set(network_api.API, "create", + fake_network_api.create) + self.stubs.Set(network_api.API, "add_network_to_project", + fake_network_api.add_network_to_project) + + def test_network_list(self): + response = self._do_get('os-networks') + subs = self._get_regexes() + self._verify_response('networks-list-resp', subs, response, 200) + + def test_network_disassociate(self): + uuid = test_networks.FAKE_NETWORKS[0]['uuid'] + response = self._do_post('os-networks/%s/action' % uuid, + 'networks-disassociate-req', {}) + self.assertEqual(response.status_code, 202) + self.assertEqual(response.content, "") + + def test_network_show(self): + uuid = test_networks.FAKE_NETWORKS[0]['uuid'] + response = self._do_get('os-networks/%s' % uuid) + subs = self._get_regexes() + self._verify_response('network-show-resp', subs, response, 200) + + def test_network_create(self): + response = self._do_post("os-networks", + 'network-create-req', {}) + subs = self._get_regexes() + self._verify_response('network-create-resp', subs, response, 200) + + def test_network_add(self): + response = self._do_post("os-networks/add", + 'network-add-req', {}) + self.assertEqual(response.status_code, 202) + self.assertEqual(response.content, "") + + def test_network_delete(self): + response = self._do_delete('os-networks/always_delete') + self.assertEqual(response.status_code, 202) + self.assertEqual(response.content, "") diff --git a/setup.cfg b/setup.cfg index dcbdaa100c..eafe29f4dc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -100,6 +100,7 @@ nova.api.v3.extensions = migrations = nova.api.openstack.compute.plugins.v3.migrations:Migrations multinic = nova.api.openstack.compute.plugins.v3.multinic:Multinic multiple_create = nova.api.openstack.compute.plugins.v3.multiple_create:MultipleCreate + networks = nova.api.openstack.compute.plugins.v3.networks:Networks pause_server = nova.api.openstack.compute.plugins.v3.pause_server:PauseServer pci = nova.api.openstack.compute.plugins.v3.pci:Pci quota_sets = nova.api.openstack.compute.plugins.v3.quota_sets:QuotaSets