Validate bay type on creating resources

Ensure k8s resource (i.e. Pod/Service/RC) to be created only on a
k8s bay, and docker swarm resource (i.e. Container) to be created
only on a swarm bay. Also, return a 400 error if the bay type is
not matched.

Change-Id: I88b747c590f13a2e0da2bd59f1b0cbcc389d9b14
Closes-Bug: 1470963
This commit is contained in:
Hongbin Lu 2015-07-03 16:24:47 -04:00
parent eb7abda967
commit f6963a52c6
10 changed files with 171 additions and 3 deletions

View File

@ -27,6 +27,7 @@ from magnum.api.controllers import link
from magnum.api.controllers.v1 import collection
from magnum.api.controllers.v1 import types
from magnum.api.controllers.v1 import utils as api_utils
from magnum.api import validation
from magnum.common import exception
from magnum import objects
@ -346,6 +347,7 @@ class ContainersController(rest.RestController):
return Container.convert_with_links(res_container)
@wsme_pecan.wsexpose(Container, body=Container, status_code=201)
@validation.enforce_bay_types('swarm')
def post(self, container):
"""Create a new container.

View File

@ -24,6 +24,7 @@ from magnum.api.controllers.v1 import base as v1_base
from magnum.api.controllers.v1 import collection
from magnum.api.controllers.v1 import types
from magnum.api.controllers.v1 import utils as api_utils
from magnum.api import validation
from magnum.common import exception
from magnum.common import k8s_manifest
from magnum import objects
@ -249,6 +250,7 @@ class PodsController(rest.RestController):
return Pod.convert_with_links(rpc_pod)
@wsme_pecan.wsexpose(Pod, body=Pod, status_code=201)
@validation.enforce_bay_types('kubernetes')
def post(self, pod):
"""Create a new pod.

View File

@ -25,6 +25,7 @@ from magnum.api.controllers.v1 import base as v1_base
from magnum.api.controllers.v1 import collection
from magnum.api.controllers.v1 import types
from magnum.api.controllers.v1 import utils as api_utils
from magnum.api import validation
from magnum.common import exception
from magnum.common import k8s_manifest
from magnum import objects
@ -283,6 +284,7 @@ class ReplicationControllersController(rest.RestController):
@wsme_pecan.wsexpose(ReplicationController, body=ReplicationController,
status_code=201)
@validation.enforce_bay_types('kubernetes')
def post(self, rc):
"""Create a new ReplicationController.

View File

@ -23,6 +23,7 @@ from magnum.api.controllers.v1 import base as v1_base
from magnum.api.controllers.v1 import collection
from magnum.api.controllers.v1 import types
from magnum.api.controllers.v1 import utils as api_utils
from magnum.api import validation
from magnum.common import exception
from magnum.common import k8s_manifest
from magnum import objects
@ -260,6 +261,7 @@ class ServicesController(rest.RestController):
return Service.convert_with_links(rpc_service)
@wsme_pecan.wsexpose(Service, body=Service, status_code=201)
@validation.enforce_bay_types('kubernetes')
def post(self, service):
"""Create a new service.

41
magnum/api/validation.py Normal file
View File

@ -0,0 +1,41 @@
# Copyright 2015 Huawei Technologies Co.,LTD.
#
# 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 decorator
import pecan
from magnum.common import exception
from magnum import objects
def enforce_bay_types(*bay_types):
def decorate(func):
@decorator.decorator
def handler(func, *args, **kwargs):
obj = args[1]
bay = objects.Bay.get_by_uuid(pecan.request.context, obj.bay_uuid)
baymodel = objects.BayModel.get_by_uuid(pecan.request.context,
bay.baymodel_id)
if baymodel.coe not in bay_types:
raise exception.InvalidParameterValue(
'cannot fulfill request with a %(bay_type)s bay, '
'expecting a %(supported_bay_types)s bay.' %
{'bay_type': baymodel.coe,
'supported_bay_types': '/'.join(bay_types)})
return func(*args, **kwargs)
return handler(func)
return decorate

View File

@ -22,14 +22,18 @@ from webtest.app import AppError
class TestContainerController(db_base.DbTestCase):
def setUp(self):
super(TestContainerController, self).setUp()
self.bay_get_by_uuid_patch = patch('magnum.objects.Bay.get_by_uuid')
self.mock_bay_get_by_uuid = self.bay_get_by_uuid_patch.start()
self.addCleanup(self.bay_get_by_uuid_patch.stop)
p = patch('magnum.objects.Bay.get_by_uuid')
self.mock_bay_get_by_uuid = p.start()
self.addCleanup(p.stop)
p = patch('magnum.objects.BayModel.get_by_uuid')
self.mock_baymodel_get_by_uuid = p.start()
self.addCleanup(p.stop)
def fake_get_by_uuid(context, uuid):
return objects.Bay(self.context, **utils.get_test_bay(uuid=uuid))
self.mock_bay_get_by_uuid.side_effect = fake_get_by_uuid
self.mock_baymodel_get_by_uuid.return_value.coe = 'swarm'
@patch('magnum.conductor.api.API.container_create')
def test_create_container(self, mock_container_create,):

View File

@ -371,6 +371,10 @@ class TestPost(api_base.FunctionalTest):
self.mock_pod_create = p.start()
self.mock_pod_create.side_effect = self._simulate_rpc_pod_create
self.addCleanup(p.stop)
p = mock.patch('magnum.objects.BayModel.get_by_uuid')
self.mock_baymodel_get_by_uuid = p.start()
self.mock_baymodel_get_by_uuid.return_value.coe = 'kubernetes'
self.addCleanup(p.stop)
def _simulate_rpc_pod_create(self, pod):
pod.create()

View File

@ -358,6 +358,10 @@ class TestPost(api_base.FunctionalTest):
self.mock_rc_create = p.start()
self.mock_rc_create.side_effect = self._simulate_rpc_rc_create
self.addCleanup(p.stop)
p = mock.patch('magnum.objects.BayModel.get_by_uuid')
self.mock_baymodel_get_by_uuid = p.start()
self.mock_baymodel_get_by_uuid.return_value.coe = 'kubernetes'
self.addCleanup(p.stop)
def _simulate_rpc_rc_create(self, rc):
rc.create(self.context)

View File

@ -311,6 +311,10 @@ class TestPost(api_base.FunctionalTest):
self.mock_service_create.side_effect = (
self._simulate_rpc_service_create)
self.addCleanup(p.stop)
p = mock.patch('magnum.objects.BayModel.get_by_uuid')
self.mock_baymodel_get_by_uuid = p.start()
self.mock_baymodel_get_by_uuid.return_value.coe = 'kubernetes'
self.addCleanup(p.stop)
def _simulate_rpc_service_create(self, service):
service.create()

View File

@ -0,0 +1,103 @@
# Copyright 2015 Huawei Technologies Co.,LTD.
#
# 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 mock
from magnum.api import validation as v
from magnum.common import exception
from magnum.tests import base
class TestValidation(base.BaseTestCase):
def _test_enforce_bay_types(
self,
mock_bay_get_by_uuid,
mock_baymodel_get_by_uuid,
mock_pecan_request,
bay_type,
allowed_bay_types,
assert_raised=False):
@v.enforce_bay_types(*allowed_bay_types)
def test(self, obj):
return obj.name
context = mock_pecan_request.context
obj = mock.MagicMock()
obj.name = 'test_object'
obj.bay_uuid = 'bay_uuid'
bay = mock.MagicMock()
bay.baymodel_id = 'baymodel_id'
baymodel = mock.MagicMock()
baymodel.coe = bay_type
mock_bay_get_by_uuid.return_value = bay
mock_baymodel_get_by_uuid.return_value = baymodel
if assert_raised:
self.assertRaises(exception.InvalidParameterValue, test, self, obj)
else:
ret = test(self, obj)
mock_bay_get_by_uuid.assert_called_once_with(context, 'bay_uuid')
mock_baymodel_get_by_uuid.assert_called_once_with(
context, 'baymodel_id')
self.assertEqual(ret, 'test_object')
@mock.patch('pecan.request')
@mock.patch('magnum.objects.BayModel.get_by_uuid')
@mock.patch('magnum.objects.Bay.get_by_uuid')
def test_enforce_bay_types_one_allowed(
self,
mock_bay_get_by_uuid,
mock_baymodel_get_by_uuid,
mock_pecan_request):
bay_type = 'type1'
allowed_bay_types = ['type1']
self._test_enforce_bay_types(
mock_bay_get_by_uuid, mock_baymodel_get_by_uuid,
mock_pecan_request, bay_type, allowed_bay_types)
@mock.patch('pecan.request')
@mock.patch('magnum.objects.BayModel.get_by_uuid')
@mock.patch('magnum.objects.Bay.get_by_uuid')
def test_enforce_bay_types_two_allowed(
self,
mock_bay_get_by_uuid,
mock_baymodel_get_by_uuid,
mock_pecan_request):
bay_type = 'type1'
allowed_bay_types = ['type1', 'type2']
self._test_enforce_bay_types(
mock_bay_get_by_uuid, mock_baymodel_get_by_uuid,
mock_pecan_request, bay_type, allowed_bay_types)
@mock.patch('pecan.request')
@mock.patch('magnum.objects.BayModel.get_by_uuid')
@mock.patch('magnum.objects.Bay.get_by_uuid')
def test_enforce_bay_types_not_allowed(
self,
mock_bay_get_by_uuid,
mock_baymodel_get_by_uuid,
mock_pecan_request):
bay_type = 'type1'
allowed_bay_types = ['type2']
self._test_enforce_bay_types(
mock_bay_get_by_uuid, mock_baymodel_get_by_uuid,
mock_pecan_request, bay_type, allowed_bay_types,
assert_raised=True)