From 129fd64ef8f29f5e511eb7c72b1bd9856900905a Mon Sep 17 00:00:00 2001 From: Matt Dietz Date: Mon, 17 Dec 2012 18:25:27 +0000 Subject: [PATCH] Adds a new tenant-centric network extension Implements: blueprint tenant-networks Adds a new API extension that supports a more tenant-centric model for interacting with isolated networks. Additionally, the patch renames the existing networks extension as os-admin-networks to more fully fit the self-described implementation. Two new novaclient extensions and a novaclient change accompany this patch. DocImpact Change-Id: Ib90a65c0a92445f86e930fab88ab4495aefff7c7 --- .../all_extensions/extensions-get-resp.json | 16 +- .../all_extensions/extensions-get-resp.xml | 7 +- .../os-networks/networks-list-res.json | 14 ++ .../os-networks/networks-post-res.json | 7 + .../quotas-show-defaults-get-resp.json | 2 +- .../quotas-show-defaults-get-resp.xml | 2 +- .../os-quota-sets/quotas-show-get-resp.json | 2 +- .../os-quota-sets/quotas-show-get-resp.xml | 2 +- .../quotas-update-post-resp.json | 2 +- .../os-quota-sets/quotas-update-post-resp.xml | 2 +- .../{networks.py => admin_networks.py} | 22 +- .../compute/contrib/networks_associate.py | 2 +- .../openstack/compute/contrib/os_networks.py | 213 ++++++++++++++++++ nova/network/manager.py | 17 +- .../compute/contrib/test_createserverext.py | 2 +- .../compute/contrib/test_networks.py | 5 +- .../compute/contrib/test_quota_classes.py | 3 +- .../openstack/compute/contrib/test_quotas.py | 6 +- .../api/openstack/compute/test_extensions.py | 2 +- nova/tests/fake_policy.py | 6 +- .../extensions-get-resp.json.tpl | 12 +- .../extensions-get-resp.xml.tpl | 5 +- .../os-networks/networks-list-res.json.tpl | 14 ++ .../os-networks/networks-post-req.json.tpl | 9 + .../os-networks/networks-post-res.json.tpl | 7 + .../quotas-show-defaults-get-resp.json.tpl | 2 +- .../quotas-show-defaults-get-resp.xml.tpl | 2 +- .../quotas-show-get-resp.json.tpl | 2 +- .../quotas-show-get-resp.xml.tpl | 2 +- .../quotas-update-post-resp.json.tpl | 2 +- .../quotas-update-post-resp.xml.tpl | 2 +- nova/tests/integrated/test_api_samples.py | 53 ++++- 32 files changed, 389 insertions(+), 57 deletions(-) create mode 100644 doc/api_samples/os-networks/networks-list-res.json create mode 100644 doc/api_samples/os-networks/networks-post-res.json rename nova/api/openstack/compute/contrib/{networks.py => admin_networks.py} (90%) create mode 100644 nova/api/openstack/compute/contrib/os_networks.py create mode 100644 nova/tests/integrated/api_samples/os-networks/networks-list-res.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-networks/networks-post-req.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-networks/networks-post-res.json.tpl diff --git a/doc/api_samples/all_extensions/extensions-get-resp.json b/doc/api_samples/all_extensions/extensions-get-resp.json index 791830f423f1..5f92c1366d6f 100644 --- a/doc/api_samples/all_extensions/extensions-get-resp.json +++ b/doc/api_samples/all_extensions/extensions-get-resp.json @@ -297,11 +297,19 @@ "updated": "2012-08-07T00:00:00+00:00" }, { - "alias": "os-networks", - "description": "Admin-only Network Management Extension.", + "alias": "os-admin-networks", + "description": "Admin-only Network Management Extension", "links": [], - "name": "Networks", - "namespace": "http://docs.openstack.org/compute/ext/networks/api/v1.1", + "name": "AdminNetworks", + "namespace": "http://docs.openstack.org/compute/ext/os-admin-networks/api/v1.1", + "updated": "2011-12-23T00:00:00+00:00" + }, + { + "alias": "os-networks", + "description": "Tenant-based Network Management Extension", + "links": [], + "name": "OSNetworks", + "namespace": "http://docs.openstack.org/compute/ext/os-networks/api/v1.1", "updated": "2011-12-23T00:00:00+00:00" }, { diff --git a/doc/api_samples/all_extensions/extensions-get-resp.xml b/doc/api_samples/all_extensions/extensions-get-resp.xml index 178c8c283ccc..8f92b274b216 100644 --- a/doc/api_samples/all_extensions/extensions-get-resp.xml +++ b/doc/api_samples/all_extensions/extensions-get-resp.xml @@ -125,12 +125,15 @@ Allow multiple create in the Create Server v1.1 API. - - Admin-only Network Management Extension. + + Admin-only Network Management Extension Network association support. + + Tenant-based Network Management Extension + Quota classes management support. diff --git a/doc/api_samples/os-networks/networks-list-res.json b/doc/api_samples/os-networks/networks-list-res.json new file mode 100644 index 000000000000..b857e8112af5 --- /dev/null +++ b/doc/api_samples/os-networks/networks-list-res.json @@ -0,0 +1,14 @@ +{ + "networks": [ + { + "cidr": "10.0.0.0/29", + "id": "616fb98f-46ca-475e-917e-2563e5a8cd19", + "label": "test_0" + }, + { + "cidr": "10.0.0.8/29", + "id": "616fb98f-46ca-475e-917e-2563e5a8cd20", + "label": "test_1" + } + ] +} diff --git a/doc/api_samples/os-networks/networks-post-res.json b/doc/api_samples/os-networks/networks-post-res.json new file mode 100644 index 000000000000..536a9a0a4afa --- /dev/null +++ b/doc/api_samples/os-networks/networks-post-res.json @@ -0,0 +1,7 @@ +{ + "network": { + "cidr": "172.0.0.0/24", + "id": "5bbcc3c4-1da2-4437-a48a-66f15b1b13f9", + "label": "public" + } +} diff --git a/doc/api_samples/os-quota-sets/quotas-show-defaults-get-resp.json b/doc/api_samples/os-quota-sets/quotas-show-defaults-get-resp.json index 70d4b66eb79f..ee1f6a3972d4 100644 --- a/doc/api_samples/os-quota-sets/quotas-show-defaults-get-resp.json +++ b/doc/api_samples/os-quota-sets/quotas-show-defaults-get-resp.json @@ -13,4 +13,4 @@ "security_group_rules": 20, "security_groups": 10 } -} \ No newline at end of file +} diff --git a/doc/api_samples/os-quota-sets/quotas-show-defaults-get-resp.xml b/doc/api_samples/os-quota-sets/quotas-show-defaults-get-resp.xml index 7dfdddeb2789..6a39c850685b 100644 --- a/doc/api_samples/os-quota-sets/quotas-show-defaults-get-resp.xml +++ b/doc/api_samples/os-quota-sets/quotas-show-defaults-get-resp.xml @@ -11,4 +11,4 @@ 51200 20 10 - \ No newline at end of file + diff --git a/doc/api_samples/os-quota-sets/quotas-show-get-resp.json b/doc/api_samples/os-quota-sets/quotas-show-get-resp.json index 70d4b66eb79f..ee1f6a3972d4 100644 --- a/doc/api_samples/os-quota-sets/quotas-show-get-resp.json +++ b/doc/api_samples/os-quota-sets/quotas-show-get-resp.json @@ -13,4 +13,4 @@ "security_group_rules": 20, "security_groups": 10 } -} \ No newline at end of file +} diff --git a/doc/api_samples/os-quota-sets/quotas-show-get-resp.xml b/doc/api_samples/os-quota-sets/quotas-show-get-resp.xml index 7dfdddeb2789..6a39c850685b 100644 --- a/doc/api_samples/os-quota-sets/quotas-show-get-resp.xml +++ b/doc/api_samples/os-quota-sets/quotas-show-get-resp.xml @@ -11,4 +11,4 @@ 51200 20 10 - \ No newline at end of file + diff --git a/doc/api_samples/os-quota-sets/quotas-update-post-resp.json b/doc/api_samples/os-quota-sets/quotas-update-post-resp.json index 6581c6354769..c16dc6bb5a26 100644 --- a/doc/api_samples/os-quota-sets/quotas-update-post-resp.json +++ b/doc/api_samples/os-quota-sets/quotas-update-post-resp.json @@ -12,4 +12,4 @@ "security_group_rules": 20, "security_groups": 45 } -} \ No newline at end of file +} diff --git a/doc/api_samples/os-quota-sets/quotas-update-post-resp.xml b/doc/api_samples/os-quota-sets/quotas-update-post-resp.xml index aef4761f832b..126c3fced0ac 100644 --- a/doc/api_samples/os-quota-sets/quotas-update-post-resp.xml +++ b/doc/api_samples/os-quota-sets/quotas-update-post-resp.xml @@ -11,4 +11,4 @@ 51200 20 45 - \ No newline at end of file + diff --git a/nova/api/openstack/compute/contrib/networks.py b/nova/api/openstack/compute/contrib/admin_networks.py similarity index 90% rename from nova/api/openstack/compute/contrib/networks.py rename to nova/api/openstack/compute/contrib/admin_networks.py index e21c06813a8e..cdcee02d02f9 100644 --- a/nova/api/openstack/compute/contrib/networks.py +++ b/nova/api/openstack/compute/contrib/admin_networks.py @@ -27,8 +27,9 @@ from nova import network from nova.openstack.common import log as logging LOG = logging.getLogger(__name__) -authorize = extensions.extension_authorizer('compute', 'networks') -authorize_view = extensions.extension_authorizer('compute', 'networks:view') +authorize = extensions.extension_authorizer('compute', 'admin_networks') +authorize_view = extensions.extension_authorizer('compute', + 'admin_networks:view') def network_dict(context, network): @@ -53,7 +54,7 @@ def network_dict(context, network): return {} -class NetworkController(wsgi.Controller): +class AdminNetworkController(wsgi.Controller): def __init__(self, network_api=None): self.network_api = network_api or network.API() @@ -149,20 +150,21 @@ class NetworkController(wsgi.Controller): return webob.Response(status_int=202) -class Networks(extensions.ExtensionDescriptor): - """Admin-only Network Management Extension.""" +class Admin_networks(extensions.ExtensionDescriptor): + """Admin-only Network Management Extension""" - name = "Networks" - alias = "os-networks" - namespace = "http://docs.openstack.org/compute/ext/networks/api/v1.1" + name = "AdminNetworks" + alias = "os-admin-networks" + namespace = ("http://docs.openstack.org/compute/" + "ext/os-admin-networks/api/v1.1") updated = "2011-12-23T00:00:00+00:00" def get_resources(self): member_actions = {'action': 'POST'} collection_actions = {'add': 'POST'} res = extensions.ResourceExtension( - 'os-networks', - NetworkController(), + 'os-admin-networks', + AdminNetworkController(), member_actions=member_actions, collection_actions=collection_actions) return [res] diff --git a/nova/api/openstack/compute/contrib/networks_associate.py b/nova/api/openstack/compute/contrib/networks_associate.py index 3cdda1d761a8..4990c1b5e679 100644 --- a/nova/api/openstack/compute/contrib/networks_associate.py +++ b/nova/api/openstack/compute/contrib/networks_associate.py @@ -62,6 +62,6 @@ class Networks_associate(extensions.ExtensionDescriptor): def get_controller_extensions(self): extension = extensions.ControllerExtension( - self, 'os-networks', NetworkAssociateActionController()) + self, 'os-admin-networks', NetworkAssociateActionController()) return [extension] diff --git a/nova/api/openstack/compute/contrib/os_networks.py b/nova/api/openstack/compute/contrib/os_networks.py new file mode 100644 index 000000000000..61a9d3af6c4a --- /dev/null +++ b/nova/api/openstack/compute/contrib/os_networks.py @@ -0,0 +1,213 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 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 netaddr +import netaddr.core as netexc +from webob import exc + +from nova.api.openstack import extensions +from nova import context as nova_context +from nova import exception +import nova.network +from nova.openstack.common import cfg +from nova.openstack.common import log as logging +from nova import quota + + +CONF = cfg.CONF + +try: + os_network_opts = [ + cfg.BoolOpt("enable_network_quota", + default=False, + help="Enables or disables quotaing of tenant networks"), + cfg.StrOpt('use_quantum_default_nets', + default="False", + help=('Control for checking for default networks')), + cfg.StrOpt('quantum_default_tenant_id', + default="default", + help=('Default tenant id when creating quantum ' + 'networks')) + ] + CONF.register_opts(os_network_opts) +except cfg.DuplicateOptError: + # NOTE(jkoelker) These options are verbatim elsewhere this is here + # to make sure they are registered for our use. + pass + +if CONF.enable_network_quota: + opts = [ + cfg.IntOpt('quota_networks', + default=3, + help='number of private networks allowed per project'), + ] + CONF.register_opts(opts) + +QUOTAS = quota.QUOTAS +LOG = logging.getLogger(__name__) +authorize = extensions.extension_authorizer('compute', 'os-networks') + + +def network_dict(network): + return {"id": network.get("uuid") or network["id"], + "cidr": network["cidr"], + "label": network["label"]} + + +class NetworkController(object): + def __init__(self, network_api=None): + self.network_api = nova.network.API() + self._default_networks = [] + + def _refresh_default_networks(self): + self._default_networks = [] + if CONF.use_quantum_default_nets == "True": + try: + self._default_networks = self._get_default_networks() + except Exception: + LOG.exception("Failed to get default networks") + + def _get_default_networks(self): + project_id = CONF.quantum_default_tenant_id + ctx = nova_context.RequestContext(user_id=None, + project_id=project_id) + networks = {} + for n in self.network_api.get_all(ctx): + networks[n['id']] = n['label'] + return [{'id': k, 'label': v} for k, v in networks.iteritems()] + + def index(self, req): + context = req.environ['nova.context'] + authorize(context) + networks = self.network_api.get_all(context) + if not self._default_networks: + self._refresh_default_networks() + networks.extend(self._default_networks) + return {'networks': [network_dict(n) for n in networks]} + + def show(self, req, id): + context = req.environ['nova.context'] + authorize(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_dict(network) + + def delete(self, req, id): + context = req.environ['nova.context'] + authorize(context) + try: + if CONF.enable_network_quota: + reservation = QUOTAS.reserve(context, networks=-1) + except Exception: + reservation = None + LOG.exception(_("Failed to update usages deallocating " + "network.")) + + LOG.info(_("Deleting network with id %s") % id) + + try: + self.network_api.delete(context, id) + if CONF.enable_network_quota and reservation: + QUOTAS.commit(context, reservation) + response = exc.HTTPAccepted() + except exception.NetworkNotFound: + response = exc.HTTPNotFound(_("Network not found")) + + return response + + def create(self, req, body): + if not body: + raise exc.HTTPUnprocessableEntity() + + context = req.environ["nova.context"] + authorize(context) + + network = body["network"] + keys = ["cidr", "cidr_v6", "ipam", "vlan_start", "network_size", + "num_networks"] + kwargs = dict((k, network.get(k)) for k in keys) + + label = network["label"] + + if not (kwargs["cidr"] or kwargs["cidr_v6"]): + msg = _("No CIDR requested") + raise exc.HTTPBadRequest(explanation=msg) + if kwargs["cidr"]: + try: + net = netaddr.IPNetwork(kwargs["cidr"]) + if net.size < 4: + msg = _("Requested network does not contain " + "enough (2+) usable hosts") + raise exc.HTTPBadRequest(explanation=msg) + except netexc.AddrFormatError: + msg = _("CIDR is malformed.") + raise exc.HTTPBadRequest(explanation=msg) + except netexc.AddrConversionError: + msg = _("Address could not be converted.") + raise exc.HTTPBadRequest(explanation=msg) + + networks = [] + try: + if CONF.enable_network_quota: + reservation = QUOTAS.reserve(context, networks=1) + except exception.OverQuota: + msg = _("Quota exceeded, too many networks.") + raise exc.HTTPBadRequest(explanation=msg) + + try: + networks = self.network_api.create(context, + label=label, **kwargs) + if CONF.enable_network_quota: + QUOTAS.commit(context, reservation) + except Exception: + if CONF.enable_network_quota: + QUOTAS.rollback(context, reservation) + msg = _("Create networks failed") + LOG.exception(msg, extra=network) + raise exc.HTTPServiceUnavailable(explanation=msg) + return {"network": network_dict(networks[0])} + + +class Os_networks(extensions.ExtensionDescriptor): + """Tenant-based Network Management Extension""" + + name = "OSNetworks" + alias = "os-networks" + namespace = "http://docs.openstack.org/compute/ext/os-networks/api/v1.1" + updated = "2012-03-07T09:46:43-05:00" + + def get_resources(self): + ext = extensions.ResourceExtension('os-networks', + NetworkController()) + return [ext] + + +def _sync_networks(context, project_id, session): + ctx = nova_context.RequestContext(user_id=None, project_id=project_id) + ctx = ctx.elevated() + networks = nova.network.api.API().get_all(ctx) + return dict(networks=len(networks)) + + +if CONF.enable_network_quota: + QUOTAS.register_resource(quota.ReservableResource('networks', + _sync_networks, + 'quota_networks')) diff --git a/nova/network/manager.py b/nova/network/manager.py index e263ac73092e..955bb3cd8814 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -1609,7 +1609,7 @@ class NetworkManager(manager.SchedulerDependentManager): fixnet = netaddr.IPNetwork(kwargs["cidr"]) each_subnet_size = fixnet.size / kwargs["num_networks"] if each_subnet_size > CONF.network_size: - subnet = 32 - int(math.log(CONF.network_size_size, 2)) + subnet = 32 - int(math.log(CONF.network_size, 2)) oversize_msg = _( 'Subnet(s) too large, defaulting to /%s.' ' To override, specify network_size flag.') % subnet @@ -2303,18 +2303,23 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): """Create networks based on parameters.""" self._convert_int_args(kwargs) + kwargs["vlan_start"] = kwargs.get("vlan_start") or CONF.vlan_start + kwargs["num_networks"] = (kwargs.get("num_networks") or + CONF.num_networks) + kwargs["network_size"] = (kwargs.get("network_size") or + CONF.network_size) # Check that num_networks + vlan_start is not > 4094, fixes lp708025 - if kwargs['num_networks'] + kwargs['vlan_start'] > 4094: + if kwargs["num_networks"] + kwargs["vlan_start"] > 4094: raise ValueError(_('The sum between the number of networks and' ' the vlan start cannot be greater' ' than 4094')) # check that num networks and network size fits in fixed_net fixed_net = netaddr.IPNetwork(kwargs['cidr']) - if len(fixed_net) < kwargs['num_networks'] * kwargs['network_size']: - raise ValueError(_('The network range is not big enough to fit ' - '%(num_networks)s. Network size is %(network_size)s') % - kwargs) + if fixed_net.size < kwargs['num_networks'] * kwargs['network_size']: + raise ValueError(_('The network range is not ' + 'big enough to fit %(num_networks)s networks. Network ' + 'size is %(network_size)s') % kwargs) kwargs['bridge_interface'] = (kwargs.get('bridge_interface') or CONF.vlan_interface) diff --git a/nova/tests/api/openstack/compute/contrib/test_createserverext.py b/nova/tests/api/openstack/compute/contrib/test_createserverext.py index a37c6889dcdd..9ec866172acc 100644 --- a/nova/tests/api/openstack/compute/contrib/test_createserverext.py +++ b/nova/tests/api/openstack/compute/contrib/test_createserverext.py @@ -99,7 +99,7 @@ class CreateserverextTest(test.TestCase): osapi_compute_extension=[ 'nova.api.openstack.compute.contrib.select_extensions'], osapi_compute_ext_list=['Createserverext', 'User_data', - 'Security_groups', 'Networks']) + 'Security_groups', 'Os_networks']) def _make_stub_method(self, canned_return): def stub_method(*args, **kwargs): diff --git a/nova/tests/api/openstack/compute/contrib/test_networks.py b/nova/tests/api/openstack/compute/contrib/test_networks.py index 5cd522f72586..ba65e8f6a932 100644 --- a/nova/tests/api/openstack/compute/contrib/test_networks.py +++ b/nova/tests/api/openstack/compute/contrib/test_networks.py @@ -21,7 +21,7 @@ import uuid import webob -from nova.api.openstack.compute.contrib import networks +from nova.api.openstack.compute.contrib import admin_networks as networks from nova.api.openstack.compute.contrib import networks_associate from nova import exception from nova.openstack.common import cfg @@ -177,7 +177,8 @@ class NetworksTest(test.TestCase): def setUp(self): super(NetworksTest, self).setUp() self.fake_network_api = FakeNetworkAPI() - self.controller = networks.NetworkController(self.fake_network_api) + self.controller = networks.AdminNetworkController( + self.fake_network_api) self.associate_controller = networks_associate\ .NetworkAssociateActionController(self.fake_network_api) fakes.stub_out_networking(self.stubs) diff --git a/nova/tests/api/openstack/compute/contrib/test_quota_classes.py b/nova/tests/api/openstack/compute/contrib/test_quota_classes.py index a8d651977a5b..a72f5bf0f918 100644 --- a/nova/tests/api/openstack/compute/contrib/test_quota_classes.py +++ b/nova/tests/api/openstack/compute/contrib/test_quota_classes.py @@ -91,8 +91,7 @@ class QuotaClassSetsTest(test.TestCase): 'injected_file_path_bytes': 255, 'security_groups': 10, 'security_group_rules': 20, - 'key_pairs': 100, - }} + 'key_pairs': 100}} req = fakes.HTTPRequest.blank( '/v2/fake4/os-quota-class-sets/test_class', diff --git a/nova/tests/api/openstack/compute/contrib/test_quotas.py b/nova/tests/api/openstack/compute/contrib/test_quotas.py index 47761b6a156b..dab8c136edc7 100644 --- a/nova/tests/api/openstack/compute/contrib/test_quotas.py +++ b/nova/tests/api/openstack/compute/contrib/test_quotas.py @@ -51,8 +51,7 @@ class QuotaSetsTest(test.TestCase): 'injected_file_content_bytes': 10240, 'security_groups': 10, 'security_group_rules': 20, - 'key_pairs': 100, - } + 'key_pairs': 100} quota_set = self.controller._format_quota_set('1234', raw_quota_set) qs = quota_set['quota_set'] @@ -88,8 +87,7 @@ class QuotaSetsTest(test.TestCase): 'injected_file_content_bytes': 10240, 'security_groups': 10, 'security_group_rules': 20, - 'key_pairs': 100, - }} + 'key_pairs': 100}} self.assertEqual(res_dict, expected) diff --git a/nova/tests/api/openstack/compute/test_extensions.py b/nova/tests/api/openstack/compute/test_extensions.py index ebb3baf954e7..b52953569735 100644 --- a/nova/tests/api/openstack/compute/test_extensions.py +++ b/nova/tests/api/openstack/compute/test_extensions.py @@ -186,7 +186,7 @@ class ExtensionControllerTest(ExtensionTestCase): "Keypairs", "Multinic", "MultipleCreate", - "Networks", + "OSNetworks", "QuotaClasses", "Quotas", "Rescue", diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py index 58ff3289cd65..c5d160209293 100644 --- a/nova/tests/fake_policy.py +++ b/nova/tests/fake_policy.py @@ -136,9 +136,10 @@ policy_data = """ "compute_extension:instance_usage_audit_log": "", "compute_extension:keypairs": "", "compute_extension:multinic": "", - "compute_extension:networks": "", - "compute_extension:networks:view": "", + "compute_extension:admin_networks": "", + "compute_extension:admin_networks:view": "", "compute_extension:networks_associate": "", + "compute_extension:os-networks": "", "compute_extension:quotas:show": "", "compute_extension:quotas:update": "", "compute_extension:quota_classes": "", @@ -191,6 +192,7 @@ policy_data = """ "network:get_all_networks": "", "network:get_network": "", + "network:create_networks": "", "network:delete_network": "", "network:disassociate_network": "", "network:get_vifs_by_instance": "", 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 3021bac56f79..0dd777fe2c48 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 @@ -304,12 +304,20 @@ "namespace": "http://docs.openstack.org/compute/ext/multiplecreate/api/v1.1", "updated": "%(timestamp)s" }, + { + "alias": "os-admin-networks", + "description": "%(text)s", + "links": [], + "name": "AdminNetworks", + "namespace": "http://docs.openstack.org/compute/ext/os-admin-networks/api/v1.1", + "updated": "%(timestamp)s" + }, { "alias": "os-networks", "description": "%(text)s", "links": [], - "name": "Networks", - "namespace": "http://docs.openstack.org/compute/ext/networks/api/v1.1", + "name": "OSNetworks", + "namespace": "http://docs.openstack.org/compute/ext/os-networks/api/v1.1", "updated": "%(timestamp)s" }, { 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 d78d1ba93b5c..fe34f369b090 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 @@ -114,7 +114,10 @@ %(text)s - + + %(text)s + + %(text)s diff --git a/nova/tests/integrated/api_samples/os-networks/networks-list-res.json.tpl b/nova/tests/integrated/api_samples/os-networks/networks-list-res.json.tpl new file mode 100644 index 000000000000..757084d2f37b --- /dev/null +++ b/nova/tests/integrated/api_samples/os-networks/networks-list-res.json.tpl @@ -0,0 +1,14 @@ +{ + "networks": [ + { + "cidr": "10.0.0.0/29", + "id": "%(id)s", + "label": "test_0" + }, + { + "cidr": "10.0.0.8/29", + "id": "%(id)s", + "label": "test_1" + } + ] +} diff --git a/nova/tests/integrated/api_samples/os-networks/networks-post-req.json.tpl b/nova/tests/integrated/api_samples/os-networks/networks-post-req.json.tpl new file mode 100644 index 000000000000..fb1c2d3d062c --- /dev/null +++ b/nova/tests/integrated/api_samples/os-networks/networks-post-req.json.tpl @@ -0,0 +1,9 @@ +{ + "network": { + "label": "public", + "cidr": "172.0.0.0/24", + "vlan_start": 1, + "num_networks": 1, + "network_size": 255 + } +} diff --git a/nova/tests/integrated/api_samples/os-networks/networks-post-res.json.tpl b/nova/tests/integrated/api_samples/os-networks/networks-post-res.json.tpl new file mode 100644 index 000000000000..ff9e2273d306 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-networks/networks-post-res.json.tpl @@ -0,0 +1,7 @@ +{ + "network": { + "cidr": "172.0.0.0/24", + "id": "%(id)s", + "label": "public" + } +} diff --git a/nova/tests/integrated/api_samples/os-quota-sets/quotas-show-defaults-get-resp.json.tpl b/nova/tests/integrated/api_samples/os-quota-sets/quotas-show-defaults-get-resp.json.tpl index 70d4b66eb79f..ee1f6a3972d4 100644 --- a/nova/tests/integrated/api_samples/os-quota-sets/quotas-show-defaults-get-resp.json.tpl +++ b/nova/tests/integrated/api_samples/os-quota-sets/quotas-show-defaults-get-resp.json.tpl @@ -13,4 +13,4 @@ "security_group_rules": 20, "security_groups": 10 } -} \ No newline at end of file +} diff --git a/nova/tests/integrated/api_samples/os-quota-sets/quotas-show-defaults-get-resp.xml.tpl b/nova/tests/integrated/api_samples/os-quota-sets/quotas-show-defaults-get-resp.xml.tpl index 7dfdddeb2789..6a39c850685b 100644 --- a/nova/tests/integrated/api_samples/os-quota-sets/quotas-show-defaults-get-resp.xml.tpl +++ b/nova/tests/integrated/api_samples/os-quota-sets/quotas-show-defaults-get-resp.xml.tpl @@ -11,4 +11,4 @@ 51200 20 10 - \ No newline at end of file + diff --git a/nova/tests/integrated/api_samples/os-quota-sets/quotas-show-get-resp.json.tpl b/nova/tests/integrated/api_samples/os-quota-sets/quotas-show-get-resp.json.tpl index 70d4b66eb79f..ee1f6a3972d4 100644 --- a/nova/tests/integrated/api_samples/os-quota-sets/quotas-show-get-resp.json.tpl +++ b/nova/tests/integrated/api_samples/os-quota-sets/quotas-show-get-resp.json.tpl @@ -13,4 +13,4 @@ "security_group_rules": 20, "security_groups": 10 } -} \ No newline at end of file +} diff --git a/nova/tests/integrated/api_samples/os-quota-sets/quotas-show-get-resp.xml.tpl b/nova/tests/integrated/api_samples/os-quota-sets/quotas-show-get-resp.xml.tpl index 7dfdddeb2789..6a39c850685b 100644 --- a/nova/tests/integrated/api_samples/os-quota-sets/quotas-show-get-resp.xml.tpl +++ b/nova/tests/integrated/api_samples/os-quota-sets/quotas-show-get-resp.xml.tpl @@ -11,4 +11,4 @@ 51200 20 10 - \ No newline at end of file + diff --git a/nova/tests/integrated/api_samples/os-quota-sets/quotas-update-post-resp.json.tpl b/nova/tests/integrated/api_samples/os-quota-sets/quotas-update-post-resp.json.tpl index 6581c6354769..c16dc6bb5a26 100644 --- a/nova/tests/integrated/api_samples/os-quota-sets/quotas-update-post-resp.json.tpl +++ b/nova/tests/integrated/api_samples/os-quota-sets/quotas-update-post-resp.json.tpl @@ -12,4 +12,4 @@ "security_group_rules": 20, "security_groups": 45 } -} \ No newline at end of file +} diff --git a/nova/tests/integrated/api_samples/os-quota-sets/quotas-update-post-resp.xml.tpl b/nova/tests/integrated/api_samples/os-quota-sets/quotas-update-post-resp.xml.tpl index aef4761f832b..126c3fced0ac 100644 --- a/nova/tests/integrated/api_samples/os-quota-sets/quotas-update-post-resp.xml.tpl +++ b/nova/tests/integrated/api_samples/os-quota-sets/quotas-update-post-resp.xml.tpl @@ -11,4 +11,4 @@ 51200 20 45 - \ No newline at end of file + diff --git a/nova/tests/integrated/test_api_samples.py b/nova/tests/integrated/test_api_samples.py index c15767a82700..877ed4af66e2 100644 --- a/nova/tests/integrated/test_api_samples.py +++ b/nova/tests/integrated/test_api_samples.py @@ -16,6 +16,7 @@ import base64 import datetime import inspect +import json import os import re import urllib @@ -29,7 +30,6 @@ from nova.api.openstack.compute.contrib import coverage_ext # Import extensions to pull in osapi_compute_extension CONF option used below. from nova.api.openstack.compute import extensions from nova.cloudpipe.pipelib import CloudPipe -from nova.compute import api from nova import context from nova import db from nova.db.sqlalchemy import models @@ -41,6 +41,7 @@ from nova.openstack.common import importutils from nova.openstack.common import jsonutils from nova.openstack.common.log import logging from nova.openstack.common import timeutils +import nova.quota from nova.scheduler import driver from nova import test from nova.tests import fake_network @@ -139,6 +140,7 @@ class ApiSampleTestBase(integrated_helpers._IntegratedTestBase): return cls._get_sample_path(name, dirname, suffix='.tpl') def _read_template(self, name): + template = self._get_template(name) if self.generate_samples and not os.path.exists(template): with open(template, 'w') as outf: @@ -371,7 +373,7 @@ class ApiSamplesTrap(ApiSampleTestBase): do_not_approve_additions.append('os-fping') do_not_approve_additions.append('os-hypervisors') do_not_approve_additions.append('os-instance_usage_audit_log') - do_not_approve_additions.append('os-networks') + do_not_approve_additions.append('os-admin-networks') do_not_approve_additions.append('os-services') do_not_approve_additions.append('os-volumes') @@ -2308,6 +2310,43 @@ class DiskConfigXmlTest(DiskConfigJsonTest): ctype = 'xml' +class OsNetworksJsonTests(ApiSampleTestBase): + extension_name = ("nova.api.openstack.compute.contrib.os_networks" + ".Os_networks") + + def setUp(self): + super(OsNetworksJsonTests, self).setUp() + CONF.set_override("enable_network_quota", True) + + def fake(*args, **kwargs): + pass + + self.stubs.Set(nova.quota.QUOTAS, "reserve", fake) + self.stubs.Set(nova.quota.QUOTAS, "commit", fake) + self.stubs.Set(nova.quota.QUOTAS, "rollback", fake) + self.stubs.Set(nova.quota.QuotaEngine, "reserve", fake) + self.stubs.Set(nova.quota.QuotaEngine, "commit", fake) + self.stubs.Set(nova.quota.QuotaEngine, "rollback", fake) + + def test_list_networks(self): + response = self._do_get('os-networks') + self.assertEqual(response.status, 200) + subs = self._get_regexes() + return self._verify_response('networks-list-res', subs, response) + + def test_create_network(self): + response = self._do_post('os-networks', "networks-post-req", {}) + self.assertEqual(response.status, 200) + subs = self._get_regexes() + self._verify_response('networks-post-res', subs, response) + + def test_delete_networK(self): + response = self._do_post('os-networks', "networks-post-req", {}) + net = json.loads(response.read()) + response = self._do_delete('os-networks/%s' % net["network"]["id"]) + self.assertEqual(response.status, 202) + + class NetworksAssociateJsonTests(ApiSampleTestBase): extension_name = ("nova.api.openstack.compute.contrib" ".networks_associate.Networks_associate") @@ -2319,7 +2358,7 @@ class NetworksAssociateJsonTests(ApiSampleTestBase): f['osapi_compute_extension'] = CONF.osapi_compute_extension[:] # Networks_associate requires Networks to be update f['osapi_compute_extension'].append( - 'nova.api.openstack.compute.contrib.networks.Networks') + 'nova.api.openstack.compute.contrib.admin_networks.Admin_networks') return f def setUp(self): @@ -2333,25 +2372,25 @@ class NetworksAssociateJsonTests(ApiSampleTestBase): self.stubs.Set(api.API, "associate", fake_associate) def test_disassociate(self): - response = self._do_post('os-networks/1/action', + response = self._do_post('os-admin-networks/1/action', 'network-disassociate-req', {}) self.assertEqual(response.status, 202) def test_disassociate_host(self): - response = self._do_post('os-networks/1/action', + response = self._do_post('os-admin-networks/1/action', 'network-disassociate-host-req', {}) self.assertEqual(response.status, 202) def test_disassociate_project(self): - response = self._do_post('os-networks/1/action', + response = self._do_post('os-admin-networks/1/action', 'network-disassociate-project-req', {}) self.assertEqual(response.status, 202) def test_associate_host(self): - response = self._do_post('os-networks/1/action', + response = self._do_post('os-admin-networks/1/action', 'network-associate-host-req', {"host": "testHost"}) self.assertEqual(response.status, 202)