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 -*- .. -*- 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) Project networks (os-tenant-networks) (DEPRECATED)
=================================================== ====================================================
.. warning:: .. warning::
@ -57,7 +55,8 @@ through the ``policy.json`` file.
Normal response codes: 200 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** **Example Create Project Network: JSON request**
@ -121,7 +120,8 @@ can change these permissions through the ``policy.json`` file.
Normal response codes: 202 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 Request
------- -------

View File

@ -1,14 +1,9 @@
{ {
"networks": [ "networks": [
{ {
"cidr": "10.0.0.0/29", "cidr": "None",
"id": "616fb98f-46ca-475e-917e-2563e5a8cd19", "id": "3cb9bc59-5699-4588-a4b1-b87f96708bc6",
"label": "test_0" "label": "private"
},
{
"cidr": "10.0.0.8/29",
"id": "616fb98f-46ca-475e-917e-2563e5a8cd20",
"label": "test_1"
} }
] ]
} }

View File

@ -441,6 +441,12 @@ API endpoints as below::
release. On deployments newer than this, some endpoints of the API will release. On deployments newer than this, some endpoints of the API will
return HTTP 410 (Gone) regadless of the requested microversion. 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 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 # License for the specific language governing permissions and limitations
# under the License. # under the License.
import netaddr
import netaddr.core as netexc
from oslo_log import log as logging from oslo_log import log as logging
import six
from webob import exc from webob import exc
from nova.api.openstack.api_version_request \ from nova.api.openstack.api_version_request \
import MAX_PROXY_API_SUPPORT_VERSION 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.openstack import wsgi
from nova.api import validation
import nova.conf import nova.conf
from nova import context as nova_context from nova import context as nova_context
from nova import exception from nova import exception
from nova.i18n import _ from nova.i18n import _
import nova.network import nova.network
from nova import objects
from nova.policies import tenant_networks as tn_policies from nova.policies import tenant_networks as tn_policies
from nova import quota from nova import quota
@ -97,86 +90,13 @@ class TenantNetworkController(wsgi.Controller):
raise exc.HTTPNotFound(explanation=msg) raise exc.HTTPNotFound(explanation=msg)
return {'network': network_dict(network)} return {'network': network_dict(network)}
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.expected_errors(410)
@wsgi.expected_errors((403, 404, 409))
@wsgi.response(202)
def delete(self, req, id): def delete(self, req, id):
context = req.environ['nova.context'] raise exc.HTTPGone()
context.can(tn_policies.BASE_POLICY_NAME)
try: @wsgi.expected_errors(410)
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)
def create(self, req, body): def create(self, req, body):
context = req.environ["nova.context"] raise exc.HTTPGone()
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])}
def _network_count(context, project_id): def _network_count(context, project_id):

View File

@ -250,13 +250,6 @@ non_negative_integer = {
'pattern': '^[0-9]*$', 'minimum': 0, 'minLength': 1 '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 = { hostname = {
'type': 'string', 'minLength': 1, 'maxLength': 255, 'type': 'string', 'minLength': 1, 'maxLength': 255,
# NOTE: 'host' is defined in "services" table, and that # NOTE: 'host' is defined in "services" table, and that

View File

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

View File

@ -1,14 +1,9 @@
{ {
"networks": [ "networks": [
{ {
"cidr": "10.0.0.0/29", "cidr": "None",
"id": "%(id)s", "id": "%(uuid)s",
"label": "test_0" "label": "private"
},
{
"cidr": "10.0.0.8/29",
"id": "%(id)s",
"label": "test_1"
} }
] ]
} }

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 # License for the specific language governing permissions and limitations
# under the License. # under the License.
from oslo_serialization import jsonutils
import nova.conf import nova.conf
from nova.tests import fixtures as nova_fixtures
from nova.tests.functional.api_sample_tests import api_sample_base from nova.tests.functional.api_sample_tests import api_sample_base
CONF = nova.conf.CONF CONF = nova.conf.CONF
# TODO(stephenfin): Remove the parts of this test that are nova-network only
class TenantNetworksJsonTests(api_sample_base.ApiSampleTestBaseV21): class TenantNetworksJsonTests(api_sample_base.ApiSampleTestBaseV21):
USE_NEUTRON = False # partially nova-net only
ADMIN_API = True ADMIN_API = True
sample_dir = "os-tenant-networks" 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): def test_list_networks(self):
response = self._do_get('os-tenant-networks') response = self._do_get('os-tenant-networks')
self._verify_response('networks-list-res', {}, response, 200) self._verify_response('networks-list-res', {}, response, 200)
# TODO(stephenfin): Remove this API since it's nova-network only
def test_create_network(self): def test_create_network(self):
response = self._do_post('os-tenant-networks', "networks-post-req", {}) self.api.api_post('os-tenant-networks', {},
self._verify_response('networks-post-res', {}, response, 200) check_response_status=[410])
# TODO(stephenfin): Remove this API since it's nova-network only
def test_delete_network(self): def test_delete_network(self):
response = self._do_post('os-tenant-networks', "networks-post-req", {}) self.api.api_delete('os-tenant-networks/1',
net = jsonutils.loads(response.content) check_response_status=[410])
response = self._do_delete('os-tenant-networks/%s' %
net["network"]["id"])
self.assertEqual(202, response.status_code)
self.assertEqual("", response.text)

View File

@ -64,15 +64,14 @@ def fake_network_api_get_all(context):
class TenantNetworksTestV21(test.NoDBTestCase): class TenantNetworksTestV21(test.NoDBTestCase):
ctrlr = networks_v21.TenantNetworkController ctrlr = networks_v21.TenantNetworkController
validation_error = exception.ValidationError validation_error = exception.ValidationError
use_neutron = False
def setUp(self): def setUp(self):
# TODO(stephenfin): We should probably use NeutronFixture here
super(TenantNetworksTestV21, self).setUp() super(TenantNetworksTestV21, self).setUp()
# os-tenant-networks only supports Neutron when listing networks or # os-tenant-networks only supports Neutron when listing networks or
# showing details about a network, create and delete operations # showing details about a network, create and delete operations
# result in a 503 and 500 response, respectively. # result in a 503 and 500 response, respectively.
self.flags(enable_network_quota=True, self.flags(enable_network_quota=True)
use_neutron=self.use_neutron)
self.useFixture(nova_fixtures.RegisterNetworkQuota()) self.useFixture(nova_fixtures.RegisterNetworkQuota())
self.controller = self.ctrlr() self.controller = self.ctrlr()
self.req = fakes.HTTPRequest.blank('') self.req = fakes.HTTPRequest.blank('')
@ -83,63 +82,6 @@ class TenantNetworksTestV21(test.NoDBTestCase):
CONF.set_override("use_neutron_default_nets", self.original_value, CONF.set_override("use_neutron_default_nets", self.original_value,
group='api') 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): def test_network_show(self):
with mock.patch.object(self.controller.network_api, 'get', with mock.patch.object(self.controller.network_api, 'get',
return_value=NETWORKS[0]): return_value=NETWORKS[0]):
@ -173,155 +115,6 @@ class TenantNetworksTestV21(test.NoDBTestCase):
def test_network_index_without_default_net(self): def test_network_index_without_default_net(self):
self._test_network_index(default_net=False) 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): class TenantNetworksEnforcementV21(test.NoDBTestCase):
@ -330,18 +123,6 @@ class TenantNetworksEnforcementV21(test.NoDBTestCase):
self.controller = networks_v21.TenantNetworkController() self.controller = networks_v21.TenantNetworkController()
self.req = fakes.HTTPRequest.blank('') 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): def test_index_policy_failed(self):
rule_name = 'os_compute_api:os-tenant-networks' rule_name = 'os_compute_api:os-tenant-networks'
self.policy.set_rules({rule_name: "project:non_fake"}) 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, "Policy doesn't allow %s to be performed." % rule_name,
exc.format_message()) 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): def test_show_policy_failed(self):
rule_name = 'os_compute_api:os-tenant-networks' rule_name = 'os_compute_api:os-tenant-networks'
self.policy.set_rules({rule_name: "project:non_fake"}) self.policy.set_rules({rule_name: "project:non_fake"})
@ -388,7 +158,3 @@ class TenantNetworksDeprecationTest(test.NoDBTestCase):
self.controller.index, self.req) self.controller.index, self.req)
self.assertRaises(exception.VersionNotFoundForAPIMethod, self.assertRaises(exception.VersionNotFoundForAPIMethod,
self.controller.show, self.req, fakes.FAKE_UUID) 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)``
* ``POST /os-networks/{id} (disassociate_host)`` * ``POST /os-networks/{id} (disassociate_host)``
* ``POST /os-networks/{id} (disassociate_project)`` * ``POST /os-networks/{id} (disassociate_project)``
* ``POST /os-tenant-networks``
* ``DELETE /os-tenant-networks``
The following policies have also been removed. The following policies have also been removed.