Merge "Remove '/os-tenant-networks' REST API"

This commit is contained in:
Zuul 2019-11-22 20:57:31 +00:00 committed by Gerrit Code Review
commit bf10180e64
13 changed files with 30 additions and 453 deletions

View File

@ -1,10 +1,8 @@
.. -*- rst -*-
.. NOTE(gmann): These APIs are deprecated so do not update this
file even body, example or parameters are not complete.
===================================================
====================================================
Project networks (os-tenant-networks) (DEPRECATED)
===================================================
====================================================
.. warning::
@ -57,7 +55,8 @@ through the ``policy.json`` file.
Normal response codes: 200
Error response codes: badRequest(400), unauthorized(401), forbidden(403), conflict(409), serviceUnavailable(503)
Error response codes: badRequest(400), unauthorized(401), forbidden(403),
conflict(409), gone(410), serviceUnavailable(503)
**Example Create Project Network: JSON request**
@ -121,7 +120,8 @@ can change these permissions through the ``policy.json`` file.
Normal response codes: 202
Error response codes: unauthorized(401), forbidden(403), itemNotFound(404), conflict(409)
Error response codes: unauthorized(401), forbidden(403), itemNotFound(404),
conflict(409), gone(410)
Request
-------

View File

@ -1,14 +1,9 @@
{
"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"
"cidr": "None",
"id": "3cb9bc59-5699-4588-a4b1-b87f96708bc6",
"label": "private"
}
]
}

View File

@ -441,6 +441,12 @@ API endpoints as below::
release. On deployments newer than this, some endpoints of the API will
return HTTP 410 (Gone) regadless of the requested microversion.
.. versionchanged:: 21.0.0
The ``os-tenant-networks`` API was partially removed in the 21.0.0 (Ussuri)
release. On deployments newer than this, some endpoints of the API will
return HTTP 410 (Gone) regadless of the requested microversion.
2.37
----

View File

@ -1,44 +0,0 @@
# Copyright 2015 NEC Corporation. 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 nova.api.validation import parameter_types
create = {
'type': 'object',
'properties': {
'network': {
'type': 'object',
'properties': {
'label': {
'type': 'string', 'maxLength': 255
},
'ipam': parameter_types.boolean,
'cidr': parameter_types.cidr,
'cidr_v6': parameter_types.cidr,
'vlan_start': parameter_types.positive_integer_with_empty_str,
'network_size':
parameter_types.positive_integer_with_empty_str,
'num_networks': parameter_types.positive_integer_with_empty_str
},
'required': ['label'],
'oneOf': [
{'required': ['cidr']},
{'required': ['cidr_v6']}
],
'additionalProperties': False,
},
},
'required': ['network'],
'additionalProperties': False,
}

View File

@ -13,24 +13,17 @@
# License for the specific language governing permissions and limitations
# under the License.
import netaddr
import netaddr.core as netexc
from oslo_log import log as logging
import six
from webob import exc
from nova.api.openstack.api_version_request \
import MAX_PROXY_API_SUPPORT_VERSION
from nova.api.openstack.compute.schemas import tenant_networks as schema
from nova.api.openstack import wsgi
from nova.api import validation
import nova.conf
from nova import context as nova_context
from nova import exception
from nova.i18n import _
import nova.network
from nova import objects
from nova.policies import tenant_networks as tn_policies
from nova import quota
@ -97,86 +90,13 @@ class TenantNetworkController(wsgi.Controller):
raise exc.HTTPNotFound(explanation=msg)
return {'network': network_dict(network)}
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((403, 404, 409))
@wsgi.response(202)
@wsgi.expected_errors(410)
def delete(self, req, id):
context = req.environ['nova.context']
context.can(tn_policies.BASE_POLICY_NAME)
raise exc.HTTPGone()
try:
self.network_api.disassociate(context, id)
self.network_api.delete(context, id)
except exception.PolicyNotAuthorized as e:
raise exc.HTTPForbidden(explanation=six.text_type(e))
except exception.NetworkInUse as e:
raise exc.HTTPConflict(explanation=e.format_message())
except exception.NetworkNotFound:
msg = _("Network not found")
raise exc.HTTPNotFound(explanation=msg)
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((400, 403, 409, 503))
@validation.schema(schema.create)
@wsgi.expected_errors(410)
def create(self, req, body):
context = req.environ["nova.context"]
context.can(tn_policies.BASE_POLICY_NAME)
network = body["network"]
keys = ["cidr", "cidr_v6", "ipam", "vlan_start", "network_size",
"num_networks"]
kwargs = {k: network.get(k) for k in keys}
label = network["label"]
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.AddrConversionError:
msg = _("Address could not be converted.")
raise exc.HTTPBadRequest(explanation=msg)
try:
if CONF.enable_network_quota:
objects.Quotas.check_deltas(context, {'networks': 1},
context.project_id)
except exception.OverQuota:
msg = _("Quota exceeded, too many networks.")
raise exc.HTTPForbidden(explanation=msg)
kwargs['project_id'] = context.project_id
try:
networks = self.network_api.create(context,
label=label, **kwargs)
except exception.PolicyNotAuthorized as e:
raise exc.HTTPForbidden(explanation=six.text_type(e))
except exception.CidrConflict as e:
raise exc.HTTPConflict(explanation=e.format_message())
except Exception:
msg = _("Create networks failed")
LOG.exception(msg, extra=network)
raise exc.HTTPServiceUnavailable(explanation=msg)
# NOTE(melwitt): We recheck the quota after creating the object to
# prevent users from allocating more resources than their allowed quota
# in the event of a race. This is configurable because it can be
# expensive if strict quota limits are not required in a deployment.
if CONF.quota.recheck_quota and CONF.enable_network_quota:
try:
objects.Quotas.check_deltas(context, {'networks': 0},
context.project_id)
except exception.OverQuota:
self.network_api.delete(context,
network_dict(networks[0])['id'])
msg = _("Quota exceeded, too many networks.")
raise exc.HTTPForbidden(explanation=msg)
return {"network": network_dict(networks[0])}
raise exc.HTTPGone()
def _network_count(context, project_id):

View File

@ -250,13 +250,6 @@ non_negative_integer = {
'pattern': '^[0-9]*$', 'minimum': 0, 'minLength': 1
}
# This only be used by nova-network specific APIs. It will be removed when
# those API removed.
positive_integer_with_empty_str = {
'type': ['integer', 'string'],
'pattern': '^[0-9]*$', 'minimum': 1,
}
hostname = {
'type': 'string', 'minLength': 1, 'maxLength': 255,
# NOTE: 'host' is defined in "services" table, and that

View File

@ -34,22 +34,10 @@ deprecated.""",
'method': 'GET',
'path': '/os-tenant-networks'
},
{
'method': 'POST',
'path': '/os-tenant-networks'
},
{
'method': 'GET',
'path': '/os-tenant-networks/{network_id}'
},
{
'method': 'DELETE',
'path': '/os-tenant-networks/{network_id}'
}
]),
]

View File

@ -1,14 +1,9 @@
{
"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"
"cidr": "None",
"id": "%(uuid)s",
"label": "private"
}
]
}

View File

@ -1,9 +0,0 @@
{
"network": {
"label": "public",
"cidr": "172.0.0.0/24",
"vlan_start": 1,
"num_networks": 1,
"network_size": 255
}
}

View File

@ -1,7 +0,0 @@
{
"network": {
"cidr": "172.0.0.0/24",
"id": "%(id)s",
"label": "public"
}
}

View File

@ -13,52 +13,24 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_serialization import jsonutils
import nova.conf
from nova.tests import fixtures as nova_fixtures
from nova.tests.functional.api_sample_tests import api_sample_base
CONF = nova.conf.CONF
# TODO(stephenfin): Remove the parts of this test that are nova-network only
class TenantNetworksJsonTests(api_sample_base.ApiSampleTestBaseV21):
USE_NEUTRON = False # partially nova-net only
ADMIN_API = True
sample_dir = "os-tenant-networks"
def setUp(self):
super(TenantNetworksJsonTests, self).setUp()
CONF.set_override("enable_network_quota", True)
self.useFixture(nova_fixtures.RegisterNetworkQuota())
def fake(*args, **kwargs):
pass
self.stub_out("nova.quota.QUOTAS.reserve", fake)
self.stub_out("nova.quota.QUOTAS.commit", fake)
self.stub_out("nova.quota.QUOTAS.rollback", fake)
self.stub_out("nova.quota.QuotaEngine.reserve", fake)
self.stub_out("nova.quota.QuotaEngine.commit", fake)
self.stub_out("nova.quota.QuotaEngine.rollback", fake)
# TODO(stephenfin): Rework this to work with neutron
def test_list_networks(self):
response = self._do_get('os-tenant-networks')
self._verify_response('networks-list-res', {}, response, 200)
# TODO(stephenfin): Remove this API since it's nova-network only
def test_create_network(self):
response = self._do_post('os-tenant-networks', "networks-post-req", {})
self._verify_response('networks-post-res', {}, response, 200)
self.api.api_post('os-tenant-networks', {},
check_response_status=[410])
# TODO(stephenfin): Remove this API since it's nova-network only
def test_delete_network(self):
response = self._do_post('os-tenant-networks', "networks-post-req", {})
net = jsonutils.loads(response.content)
response = self._do_delete('os-tenant-networks/%s' %
net["network"]["id"])
self.assertEqual(202, response.status_code)
self.assertEqual("", response.text)
self.api.api_delete('os-tenant-networks/1',
check_response_status=[410])

View File

@ -64,15 +64,14 @@ def fake_network_api_get_all(context):
class TenantNetworksTestV21(test.NoDBTestCase):
ctrlr = networks_v21.TenantNetworkController
validation_error = exception.ValidationError
use_neutron = False
def setUp(self):
# TODO(stephenfin): We should probably use NeutronFixture here
super(TenantNetworksTestV21, self).setUp()
# os-tenant-networks only supports Neutron when listing networks or
# showing details about a network, create and delete operations
# result in a 503 and 500 response, respectively.
self.flags(enable_network_quota=True,
use_neutron=self.use_neutron)
self.flags(enable_network_quota=True)
self.useFixture(nova_fixtures.RegisterNetworkQuota())
self.controller = self.ctrlr()
self.req = fakes.HTTPRequest.blank('')
@ -83,63 +82,6 @@ class TenantNetworksTestV21(test.NoDBTestCase):
CONF.set_override("use_neutron_default_nets", self.original_value,
group='api')
def _fake_network_api_create(self, context, **kwargs):
self.assertEqual(context.project_id, kwargs['project_id'])
return NETWORKS
@mock.patch('nova.network.api.API.disassociate')
@mock.patch('nova.network.api.API.delete')
def _test_network_delete_exception(self, delete_ex, disassociate_ex, expex,
delete_mock, disassociate_mock):
ctxt = self.req.environ['nova.context']
if delete_mock:
delete_mock.side_effect = delete_ex
if disassociate_ex:
disassociate_mock.side_effect = disassociate_ex
if self.use_neutron:
expex = webob.exc.HTTPInternalServerError
self.assertRaises(expex, self.controller.delete, self.req, 1)
if not self.use_neutron:
disassociate_mock.assert_called_once_with(ctxt, 1)
if not disassociate_ex:
delete_mock.assert_called_once_with(ctxt, 1)
def test_network_delete_exception_network_not_found(self):
ex = exception.NetworkNotFound(network_id=1)
expex = webob.exc.HTTPNotFound
self._test_network_delete_exception(None, ex, expex)
def test_network_delete_exception_policy_failed(self):
ex = exception.PolicyNotAuthorized(action='dummy')
expex = webob.exc.HTTPForbidden
self._test_network_delete_exception(ex, None, expex)
def test_network_delete_exception_network_in_use(self):
ex = exception.NetworkInUse(network_id=1)
expex = webob.exc.HTTPConflict
self._test_network_delete_exception(ex, None, expex)
@mock.patch('nova.network.api.API.delete')
@mock.patch('nova.network.api.API.disassociate')
def test_network_delete(self, disassociate_mock, delete_mock):
ctxt = self.req.environ['nova.context']
delete_method = self.controller.delete
res = delete_method(self.req, 1)
# NOTE: on v2.1, http status code is set as wsgi_code of API
# method instead of status_int in a response object.
if isinstance(self.controller, networks_v21.TenantNetworkController):
status_int = delete_method.wsgi_code
else:
status_int = res.status_int
self.assertEqual(202, status_int)
disassociate_mock.assert_called_once_with(ctxt, 1)
delete_mock.assert_called_once_with(ctxt, 1)
def test_network_show(self):
with mock.patch.object(self.controller.network_api, 'get',
return_value=NETWORKS[0]):
@ -173,155 +115,6 @@ class TenantNetworksTestV21(test.NoDBTestCase):
def test_network_index_without_default_net(self):
self._test_network_index(default_net=False)
@mock.patch('nova.objects.Quotas.check_deltas')
@mock.patch('nova.network.api.API.create')
def test_network_create(self, create_mock, check_mock):
create_mock.side_effect = self._fake_network_api_create
body = copy.deepcopy(NETWORKS[0])
del body['id']
body = {'network': body}
res = self.controller.create(self.req, body=body)
self.assertEqual(NETWORKS[0], res['network'])
@mock.patch('nova.objects.Quotas.check_deltas')
@mock.patch('nova.network.api.API.delete')
@mock.patch('nova.network.api.API.create')
def test_network_create_quota_error_during_recheck(self, create_mock,
delete_mock,
check_mock):
create_mock.side_effect = self._fake_network_api_create
ctxt = self.req.environ['nova.context']
# Simulate a race where the first check passes and the recheck fails.
check_mock.side_effect = [None, exception.OverQuota(overs='networks')]
body = copy.deepcopy(NETWORKS[0])
del body['id']
body = {'network': body}
self.assertRaises(webob.exc.HTTPForbidden,
self.controller.create, self.req, body=body)
self.assertEqual(2, check_mock.call_count)
call1 = mock.call(ctxt, {'networks': 1}, ctxt.project_id)
call2 = mock.call(ctxt, {'networks': 0}, ctxt.project_id)
check_mock.assert_has_calls([call1, call2])
# Verify we removed the network that was added after the first quota
# check passed.
delete_mock.assert_called_once_with(ctxt, NETWORKS[0]['id'])
@mock.patch('nova.objects.Quotas.check_deltas')
@mock.patch('nova.network.api.API.create')
def test_network_create_no_quota_recheck(self, create_mock, check_mock):
create_mock.side_effect = self._fake_network_api_create
ctxt = self.req.environ['nova.context']
# Disable recheck_quota.
self.flags(recheck_quota=False, group='quota')
body = copy.deepcopy(NETWORKS[0])
del body['id']
body = {'network': body}
self.controller.create(self.req, body=body)
# check_deltas should have been called only once.
check_mock.assert_called_once_with(ctxt, {'networks': 1},
ctxt.project_id)
@mock.patch('nova.objects.Quotas.check_deltas')
def test_network_create_quota_error(self, check_mock):
ctxt = self.req.environ['nova.context']
check_mock.side_effect = exception.OverQuota(overs='networks')
body = {'network': {"cidr": "10.20.105.0/24",
"label": "new net 1"}}
self.assertRaises(webob.exc.HTTPForbidden,
self.controller.create, self.req, body=body)
check_mock.assert_called_once_with(ctxt, {'networks': 1},
ctxt.project_id)
@mock.patch('nova.objects.Quotas.check_deltas')
@mock.patch('nova.network.api.API.create')
def _test_network_create_exception(self, ex, expex, create_mock,
check_mock):
ctxt = self.req.environ['nova.context']
create_mock.side_effect = ex
body = {'network': {"cidr": "10.20.105.0/24",
"label": "new net 1"}}
if self.use_neutron:
expex = webob.exc.HTTPServiceUnavailable
self.assertRaises(expex, self.controller.create, self.req, body=body)
check_mock.assert_called_once_with(ctxt, {'networks': 1},
ctxt.project_id)
def test_network_create_exception_policy_failed(self):
ex = exception.PolicyNotAuthorized(action='dummy')
expex = webob.exc.HTTPForbidden
self._test_network_create_exception(ex, expex)
def test_network_create_exception_conflictcidr(self):
ex = exception.CidrConflict(cidr='dummy', other='dummy')
expex = webob.exc.HTTPConflict
self._test_network_create_exception(ex, expex)
def test_network_create_exception_service_unavailable(self):
ex = Exception
expex = webob.exc.HTTPServiceUnavailable
self._test_network_create_exception(ex, expex)
def test_network_create_empty_body(self):
self.assertRaises(exception.ValidationError,
self.controller.create, self.req, body={})
def test_network_create_without_cidr(self):
body = {'network': {"label": "new net 1"}}
self.assertRaises(self.validation_error,
self.controller.create, self.req, body=body)
def test_network_create_bad_format_cidr(self):
body = {'network': {"cidr": "123",
"label": "new net 1"}}
self.assertRaises(self.validation_error,
self.controller.create, self.req, body=body)
def test_network_create_empty_network(self):
body = {'network': {}}
self.assertRaises(self.validation_error,
self.controller.create, self.req, body=body)
def test_network_create_without_label(self):
body = {'network': {"cidr": "10.20.105.0/24"}}
self.assertRaises(self.validation_error,
self.controller.create, self.req, body=body)
class TenantNeutronNetworksTestV21(TenantNetworksTestV21):
use_neutron = True
def test_network_create(self):
self.assertRaises(
webob.exc.HTTPServiceUnavailable,
super(TenantNeutronNetworksTestV21, self).test_network_create)
def test_network_create_quota_error_during_recheck(self):
self.assertRaises(
webob.exc.HTTPServiceUnavailable,
super(TenantNeutronNetworksTestV21, self)
.test_network_create_quota_error_during_recheck)
def test_network_create_no_quota_recheck(self):
self.assertRaises(
webob.exc.HTTPServiceUnavailable,
super(TenantNeutronNetworksTestV21, self)
.test_network_create_no_quota_recheck)
def test_network_delete(self):
self.assertRaises(
webob.exc.HTTPInternalServerError,
super(TenantNeutronNetworksTestV21, self).test_network_delete)
class TenantNetworksEnforcementV21(test.NoDBTestCase):
@ -330,18 +123,6 @@ class TenantNetworksEnforcementV21(test.NoDBTestCase):
self.controller = networks_v21.TenantNetworkController()
self.req = fakes.HTTPRequest.blank('')
def test_create_policy_failed(self):
rule_name = 'os_compute_api:os-tenant-networks'
self.policy.set_rules({rule_name: "project:non_fake"})
exc = self.assertRaises(
exception.PolicyNotAuthorized,
self.controller.create,
self.req, body={'network': {'label': 'test',
'cidr': '10.0.0.0/32'}})
self.assertEqual(
"Policy doesn't allow %s to be performed." % rule_name,
exc.format_message())
def test_index_policy_failed(self):
rule_name = 'os_compute_api:os-tenant-networks'
self.policy.set_rules({rule_name: "project:non_fake"})
@ -353,17 +134,6 @@ class TenantNetworksEnforcementV21(test.NoDBTestCase):
"Policy doesn't allow %s to be performed." % rule_name,
exc.format_message())
def test_delete_policy_failed(self):
rule_name = 'os_compute_api:os-tenant-networks'
self.policy.set_rules({rule_name: "project:non_fake"})
exc = self.assertRaises(
exception.PolicyNotAuthorized,
self.controller.delete,
self.req, fakes.FAKE_UUID)
self.assertEqual(
"Policy doesn't allow %s to be performed." % rule_name,
exc.format_message())
def test_show_policy_failed(self):
rule_name = 'os_compute_api:os-tenant-networks'
self.policy.set_rules({rule_name: "project:non_fake"})
@ -388,7 +158,3 @@ class TenantNetworksDeprecationTest(test.NoDBTestCase):
self.controller.index, self.req)
self.assertRaises(exception.VersionNotFoundForAPIMethod,
self.controller.show, self.req, fakes.FAKE_UUID)
self.assertRaises(exception.VersionNotFoundForAPIMethod,
self.controller.delete, self.req, fakes.FAKE_UUID)
self.assertRaises(exception.VersionNotFoundForAPIMethod,
self.controller.create, self.req, {})

View File

@ -17,6 +17,8 @@ upgrade:
* ``POST /os-networks/{id} (disassociate)``
* ``POST /os-networks/{id} (disassociate_host)``
* ``POST /os-networks/{id} (disassociate_project)``
* ``POST /os-tenant-networks``
* ``DELETE /os-tenant-networks``
The following policies have also been removed.