Port os-networks-associate plugin to v2.1(v3) infrastructure

Ports os-networks-associate extension and adapts it to the
v2.1/v3 API framework. API behaviour is identical.

- unittest code modified to share testing with both v2/v2.1
- Adds expected error decorators for API methods

Partially implements blueprint v2-on-v3-api

Change-Id: I6ccf9613abf999784c548fb058522ac60db7f052
This commit is contained in:
Chris Yeoh 2014-09-17 16:36:38 +09:30
parent cc452485c2
commit 0c1ad907c4
14 changed files with 241 additions and 13 deletions

View File

@ -0,0 +1,3 @@
{
"associate_host": "testHost"
}

View File

@ -0,0 +1,3 @@
{
"disassociate_host": null
}

View File

@ -0,0 +1,3 @@
{
"disassociate_project": null
}

View File

@ -0,0 +1,3 @@
{
"disassociate": null
}

View File

@ -210,6 +210,8 @@
"compute_extension:v3:os-networks:view": "",
"compute_extension:v3:os-networks:discoverable": "",
"compute_extension:networks_associate": "rule:admin_api",
"compute_extension:v3:os-networks-associate": "rule:admin_api",
"compute_extension:v3:os-networks-associate:discoverable": "",
"compute_extension:v3:os-pause-server:discoverable": "",
"compute_extension:v3:os-pause-server:pause": "rule:admin_or_owner",
"compute_extension:v3:os-pause-server:unpause": "rule:admin_or_owner",

View File

@ -0,0 +1,102 @@
# 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 webob import exc
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
from nova import exception
from nova.i18n import _
from nova import network
from nova.openstack.common import log as logging
ALIAS = "os-networks-associate"
LOG = logging.getLogger(__name__)
authorize = extensions.extension_authorizer('compute', 'v3:' + ALIAS)
class NetworkAssociateActionController(wsgi.Controller):
"""Network Association API Controller."""
def __init__(self, network_api=None):
self.network_api = network_api or network.API()
@wsgi.action("disassociate_host")
@wsgi.response(202)
@extensions.expected_errors((404, 501))
def _disassociate_host_only(self, req, id, body):
context = req.environ['nova.context']
authorize(context)
LOG.debug("Disassociating host with network with id %s", id)
try:
self.network_api.associate(context, id, host=None)
except exception.NetworkNotFound:
msg = _("Network not found")
raise exc.HTTPNotFound(explanation=msg)
except NotImplementedError:
msg = _('Disassociate host is not implemented by the configured '
'Network API')
raise exc.HTTPNotImplemented(explanation=msg)
@wsgi.action("disassociate_project")
@wsgi.response(202)
@extensions.expected_errors((404, 501))
def _disassociate_project_only(self, req, id, body):
context = req.environ['nova.context']
authorize(context)
LOG.debug("Disassociating project with network with id %s", id)
try:
self.network_api.associate(context, id, project=None)
except exception.NetworkNotFound:
msg = _("Network not found")
raise exc.HTTPNotFound(explanation=msg)
except NotImplementedError:
msg = _('Disassociate project is not implemented by the '
'configured Network API')
raise exc.HTTPNotImplemented(explanation=msg)
@wsgi.action("associate_host")
@wsgi.response(202)
@extensions.expected_errors((404, 501))
def _associate_host(self, req, id, body):
context = req.environ['nova.context']
authorize(context)
try:
self.network_api.associate(context, id,
host=body['associate_host'])
except exception.NetworkNotFound:
msg = _("Network not found")
raise exc.HTTPNotFound(explanation=msg)
except NotImplementedError:
msg = _('Associate host is not implemented by the configured '
'Network API')
raise exc.HTTPNotImplemented(explanation=msg)
class NetworksAssociate(extensions.V3APIExtensionBase):
"""Network association support."""
name = "NetworkAssociationSupport"
alias = ALIAS
version = 1
def get_controller_extensions(self):
extension = extensions.ControllerExtension(
self, 'os-networks', NetworkAssociateActionController())
return [extension]
def get_resources(self):
return []

View File

@ -29,6 +29,8 @@ from nova.api.openstack.compute.contrib import networks_associate
from nova.api.openstack.compute.contrib import os_networks as networks
from nova.api.openstack.compute.contrib import os_tenant_networks as tnet
from nova.api.openstack.compute.plugins.v3 import networks as networks_v21
from nova.api.openstack.compute.plugins.v3 import networks_associate as \
networks_associate_v21
from nova.api.openstack.compute.plugins.v3 import tenant_networks as tnet_v21
from nova.api.openstack import extensions
import nova.context
@ -489,27 +491,31 @@ class NetworksTestV2(NetworksTestV21):
self.controller.create(req, net)
class NetworksAssociateTest(test.NoDBTestCase):
class NetworksAssociateTestV21(test.NoDBTestCase):
def setUp(self):
super(NetworksAssociateTest, self).setUp()
super(NetworksAssociateTestV21, self).setUp()
self.fake_network_api = FakeNetworkAPI()
ext_mgr = extensions.ExtensionManager()
ext_mgr.extensions = {'os-extended-networks': 'fake'}
self.controller = networks.NetworkController(
self.fake_network_api,
ext_mgr)
self.associate_controller = networks_associate\
.NetworkAssociateActionController(self.fake_network_api)
self._setup()
fakes.stub_out_networking(self.stubs)
fakes.stub_out_rate_limiting(self.stubs)
def _setup(self):
self.controller = networks.NetworkController(self.fake_network_api)
self.associate_controller = networks_associate_v21\
.NetworkAssociateActionController(self.fake_network_api)
def _check_status(self, res, method, code):
self.assertEqual(method.wsgi_code, code)
def test_network_disassociate_host_only(self):
uuid = FAKE_NETWORKS[0]['uuid']
req = fakes.HTTPRequest.blank('/v2/1234/os-networks/%s/action' % uuid)
res = self.associate_controller._disassociate_host_only(
req, uuid, {'disassociate_host': None})
self.assertEqual(res.status_int, 202)
self._check_status(res,
self.associate_controller._disassociate_host_only,
202)
self.assertIsNotNone(self.fake_network_api.networks[0]['project_id'])
self.assertIsNone(self.fake_network_api.networks[0]['host'])
@ -518,16 +524,17 @@ class NetworksAssociateTest(test.NoDBTestCase):
req = fakes.HTTPRequest.blank('/v2/1234/os-networks/%s/action' % uuid)
res = self.associate_controller._disassociate_project_only(
req, uuid, {'disassociate_project': None})
self.assertEqual(res.status_int, 202)
self._check_status(
res, self.associate_controller._disassociate_project_only, 202)
self.assertIsNone(self.fake_network_api.networks[0]['project_id'])
self.assertIsNotNone(self.fake_network_api.networks[0]['host'])
def test_network_associate_with_host(self):
uuid = FAKE_NETWORKS[1]['uuid']
req = fakes.HTTPRequest.blank('/v2/1234/os-networks/%s/action' % uuid)
req = fakes.HTTPRequest.blank('/v2/1234//os-networks/%s/action' % uuid)
res = self.associate_controller._associate_host(
req, uuid, {'associate_host': "TestHost"})
self.assertEqual(res.status_int, 202)
self._check_status(res, self.associate_controller._associate_host, 202)
req = fakes.HTTPRequest.blank('/v2/1234/os-networks/%s' % uuid)
req.environ["nova.context"].is_admin = True
res_dict = self.controller.show(req, uuid)
@ -563,6 +570,21 @@ class NetworksAssociateTest(test.NoDBTestCase):
req, uuid, {'disassociate_host': None})
class NetworksAssociateTestV2(NetworksAssociateTestV21):
def _setup(self):
ext_mgr = extensions.ExtensionManager()
ext_mgr.extensions = {'os-extended-networks': 'fake'}
self.controller = networks.NetworkController(
self.fake_network_api,
ext_mgr)
self.associate_controller = networks_associate\
.NetworkAssociateActionController(self.fake_network_api)
def _check_status(self, res, method, code):
self.assertEqual(res.status_int, 202)
class TenantNetworksTestV21(test.NoDBTestCase):
ctrlr = tnet_v21.TenantNetworkController

View File

@ -255,6 +255,7 @@ policy_data = """
"compute_extension:v3:os-networks": "",
"compute_extension:v3:os-networks:view": "",
"compute_extension:networks_associate": "",
"compute_extension:v3:os-networks-associate": "",
"compute_extension:os-tenant-networks": "",
"compute_extension:v3:os-tenant-networks": "",
"compute_extension:v3:os-pause-server:pause": "",

View File

@ -0,0 +1,3 @@
{
"associate_host": "%(host)s"
}

View File

@ -0,0 +1,3 @@
{
"disassociate_host": null
}

View File

@ -0,0 +1,3 @@
{
"disassociate_project": null
}

View File

@ -0,0 +1,3 @@
{
"disassociate": null
}

View File

@ -0,0 +1,76 @@
# Copyright 2012 Nebula, Inc.
# Copyright 2014 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo.config import cfg
from nova.network import api as network_api
from nova.tests.integrated.v3 import api_sample_base
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
class NetworksAssociateJsonTests(api_sample_base.ApiSampleTestBaseV3):
extension_name = "os-networks-associate"
extra_extensions_to_load = ["os-networks"]
_sentinel = object()
def _get_flags(self):
f = super(NetworksAssociateJsonTests, self)._get_flags()
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.os_networks.Os_networks')
return f
def setUp(self):
super(NetworksAssociateJsonTests, self).setUp()
def fake_associate(self, context, network_id,
host=NetworksAssociateJsonTests._sentinel,
project=NetworksAssociateJsonTests._sentinel):
return True
self.stubs.Set(network_api.API, "associate", fake_associate)
def test_disassociate(self):
response = self._do_post('os-networks/1/action',
'network-disassociate-req',
{})
self.assertEqual(response.status_code, 202)
self.assertEqual(response.content, "")
def test_disassociate_host(self):
response = self._do_post('os-networks/1/action',
'network-disassociate-host-req',
{})
self.assertEqual(response.status_code, 202)
self.assertEqual(response.content, "")
def test_disassociate_project(self):
response = self._do_post('os-networks/1/action',
'network-disassociate-project-req',
{})
self.assertEqual(response.status_code, 202)
self.assertEqual(response.content, "")
def test_associate_host(self):
response = self._do_post('os-networks/1/action',
'network-associate-host-req',
{"host": "testHost"})
self.assertEqual(response.status_code, 202)
self.assertEqual(response.content, "")

View File

@ -103,6 +103,7 @@ nova.api.v3.extensions =
multinic = nova.api.openstack.compute.plugins.v3.multinic:Multinic
multiple_create = nova.api.openstack.compute.plugins.v3.multiple_create:MultipleCreate
networks = nova.api.openstack.compute.plugins.v3.networks:Networks
networks_associate = nova.api.openstack.compute.plugins.v3.networks_associate:NetworksAssociate
pause_server = nova.api.openstack.compute.plugins.v3.pause_server:PauseServer
pci = nova.api.openstack.compute.plugins.v3.pci:Pci
quota_sets = nova.api.openstack.compute.plugins.v3.quota_sets:QuotaSets