Remove '/os-tenant-networks' REST API

Like '/os-networks', we can't remove this in its entirety due to the
fact that some of these are proxy URLs that also work with neutron.
These are retained but everything else is nuked.

Note that this highlights a bug in the API, whereby a missing
'objects.Network.cidr' value results in a value of 'None' being output
over the API. Clearly this is incorrect, but it's probably not worth
fixing for this deprecated API.

Change-Id: I31cb0891144bdd7945479bb6692b0a533de4c5d0
Signed-off-by: Stephen Finucane <sfinucan@redhat.com>
This commit is contained in:
Stephen Finucane 2019-09-21 22:38:51 +01:00
parent 991d675675
commit e5e477c42a
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.