Consolidate multiple controllers into one

* In before, container requests are handle by multiple controllers.
  This commit consolidates them into one to reduce the complexity.
* Change the method of the following endpoints from PUT to POST.
  Both Nova and Docker uses POST in similiar cases.
  - /containers/%id/pause
  - /containers/%id/unpause
  - /containers/%id/execute
  - /containers/%id/reboot
  - /containers/%id/start
  - /containers/%id/stop

Change-Id: I9307038e5b78574bd4fa1867c5355d0681cfc4db
This commit is contained in:
Hongbin Lu 2016-08-28 15:40:19 -05:00
parent 6030cb0e48
commit 6df752e3b4
2 changed files with 120 additions and 216 deletions

View File

@ -16,6 +16,7 @@
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import timeutils from oslo_utils import timeutils
import pecan import pecan
from pecan import rest
from zun.api.controllers import base from zun.api.controllers import base
from zun.api.controllers import link from zun.api.controllers import link
@ -166,158 +167,29 @@ class ContainerCollection(collection.Collection):
return sample return sample
class StartController(object): class ContainersController(rest.RestController):
def __init__(self, container_id):
self.container_id = container_id
@pecan.expose(generic=True)
def index(self, **kwargs):
pecan.abort(405)
@index.when(method='PUT', template='json')
@exception.wrap_pecan_controller_exception
def on_put(self, **kw):
container = _get_container(self.container_id)
check_policy_on_container(container, "container:start")
LOG.debug('Calling compute.container_start with %s',
container.uuid)
context = pecan.request.context
pecan.request.rpcapi.container_start(context, container)
return Container.convert_with_links(container)
class StopController(object):
def __init__(self, container_id):
self.container_id = container_id
@pecan.expose(generic=True)
def index(self, **kwargs):
pecan.abort(405)
@index.when(method='PUT', template='json')
@exception.wrap_pecan_controller_exception
def on_put(self, **kw):
container = _get_container(self.container_id)
check_policy_on_container(container, "container:stop")
LOG.debug('Calling compute.container_stop with %s' %
container.uuid)
context = pecan.request.context
pecan.request.rpcapi.container_stop(context, container)
return Container.convert_with_links(container)
class RebootController(object):
def __init__(self, container_id):
self.container_id = container_id
@pecan.expose(generic=True)
def index(self, **kwargs):
pecan.abort(405)
@index.when(method='PUT', template='json')
@exception.wrap_pecan_controller_exception
def on_put(self, **kw):
container = _get_container(self.container_id)
check_policy_on_container(container, "container:reboot")
LOG.debug('Calling compute.container_reboot with %s' %
container.uuid)
context = pecan.request.context
pecan.request.rpcapi.container_reboot(context, container)
return Container.convert_with_links(container)
class PauseController(object):
def __init__(self, container_id):
self.container_id = container_id
@pecan.expose(generic=True)
def index(self, **kwargs):
pecan.abort(405)
@index.when(method='PUT', template='json')
@exception.wrap_pecan_controller_exception
def on_put(self, **kw):
container = _get_container(self.container_id)
check_policy_on_container(container, "container:pause")
LOG.debug('Calling compute.container_pause with %s' %
container.uuid)
context = pecan.request.context
pecan.request.rpcapi.container_pause(context, container)
return Container.convert_with_links(container)
class UnpauseController(object):
def __init__(self, container_id):
self.container_id = container_id
@pecan.expose(generic=True)
def index(self, **kwargs):
pecan.abort(405)
@index.when(method='PUT', template='json')
@exception.wrap_pecan_controller_exception
def on_put(self, **kw):
container = _get_container(self.container_id)
check_policy_on_container(container, "container:unpause")
LOG.debug('Calling compute.container_unpause with %s' %
container.uuid)
context = pecan.request.context
pecan.request.rpcapi.container_unpause(context, container)
return Container.convert_with_links(container)
class LogsController(object):
def __init__(self, container_id):
self.container_id = container_id
@pecan.expose(generic=True)
def index(self, **kwargs):
pecan.abort(405) # HTTP 405 Method Not Allowed as default
@index.when(method='GET', template='json')
@exception.wrap_pecan_controller_exception
def on_get(self, **kwargs):
container = _get_container(self.container_id)
check_policy_on_container(container, "container:logs")
LOG.debug('Calling compute.container_logs with %s' %
container.uuid)
context = pecan.request.context
return pecan.request.rpcapi.container_logs(context, container)
class ExecuteController(object):
def __init__(self, container_id):
self.container_id = container_id
@pecan.expose(generic=True)
def index(self, **kwargs):
pecan.abort(405)
@index.when(method='PUT', template='json')
@exception.wrap_pecan_controller_exception
def on_put(self, **kw):
container = _get_container(self.container_id)
check_policy_on_container(container, "container:execute")
LOG.debug('Calling compute.container_exec with %s command %s'
% (container.uuid, kw['command']))
context = pecan.request.context
return pecan.request.rpcapi.container_exec(context, container,
kw['command'])
class ContainersController(object):
"""Controller for Containers.""" """Controller for Containers."""
@pecan.expose() _custom_actions = {
def _lookup(self, container_id, *remainder): 'start': ['POST'],
return ContainerController(container_id), remainder 'stop': ['POST'],
'reboot': ['POST'],
'pause': ['POST'],
'unpause': ['POST'],
'logs': ['GET'],
'execute': ['POST']
}
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def get_all(self, **kwargs):
"""Retrieve a list of containers.
"""
context = pecan.request.context
policy.enforce(context, "container:get_all",
action="container:get_all")
return self._get_containers_collection(**kwargs)
def _get_containers_collection(self, **kwargs): def _get_containers_collection(self, **kwargs):
context = pecan.request.context context = pecan.request.context
@ -355,25 +227,21 @@ class ContainersController(object):
sort_key=sort_key, sort_key=sort_key,
sort_dir=sort_dir) sort_dir=sort_dir)
@pecan.expose(generic=True) @pecan.expose('json')
def index(self, **kwargs):
pecan.abort(405) # HTTP 405 Method Not Allowed as default
@index.when(method='GET', template='json')
@exception.wrap_pecan_controller_exception @exception.wrap_pecan_controller_exception
def on_get(self, **kwargs): def get_one(self, container_id):
"""Retrieve a list of containers. """Retrieve information about the given container.
:param container_ident: UUID or name of a container.
""" """
context = pecan.request.context container = _get_container(container_id)
policy.enforce(context, "container:get_all", check_policy_on_container(container, "container:get")
action="container:get_all") return Container.convert_with_links(container)
return self._get_containers_collection(**kwargs)
@index.when(method='POST', template='json') @pecan.expose('json')
@api_utils.enforce_content_types(['application/json']) @api_utils.enforce_content_types(['application/json'])
@exception.wrap_pecan_controller_exception @exception.wrap_pecan_controller_exception
def on_post(self, **container_dict): def post(self, **container_dict):
"""Create a new container. """Create a new container.
:param container: a container within the request body. :param container: a container within the request body.
@ -395,54 +263,14 @@ class ContainersController(object):
pecan.response.status = 202 pecan.response.status = 202
return Container.convert_with_links(new_container) return Container.convert_with_links(new_container)
@pecan.expose('json')
class ContainerController(object):
def __init__(self, container_id):
self.container_id = container_id
@pecan.expose()
def _lookup(self, sub_resource, *remainder):
if sub_resource == 'start':
return StartController(self.container_id), remainder
elif sub_resource == 'stop':
return StopController(self.container_id), remainder
elif sub_resource == 'pause':
return PauseController(self.container_id), remainder
elif sub_resource == 'unpause':
return UnpauseController(self.container_id), remainder
elif sub_resource == 'reboot':
return RebootController(self.container_id), remainder
elif sub_resource == 'logs':
return LogsController(self.container_id), remainder
elif sub_resource == 'execute':
return ExecuteController(self.container_id), remainder
else:
pecan.abort(405)
@pecan.expose(generic=True)
def index(self, **kwargs):
pecan.abort(405) # HTTP 405 Method Not Allowed as default
@index.when(method='GET', template='json')
@exception.wrap_pecan_controller_exception @exception.wrap_pecan_controller_exception
def on_get(self, **kwargs): def patch(self, container_id, **kwargs):
"""Retrieve information about the given container.
:param container_ident: UUID or name of a container.
"""
container = _get_container(self.container_id)
check_policy_on_container(container, "container:get")
return Container.convert_with_links(container)
@index.when(method='PATCH', template='json')
@exception.wrap_pecan_controller_exception
def on_patch(self, **kwargs):
"""Update an existing container. """Update an existing container.
:param patch: a json PATCH document to apply to this container. :param patch: a json PATCH document to apply to this container.
""" """
container = _get_container(self.container_id) container = _get_container(container_id)
check_policy_on_container(container, "container:update") check_policy_on_container(container, "container:update")
try: try:
patch = kwargs.get('patch') patch = kwargs.get('patch')
@ -465,16 +293,92 @@ class ContainerController(object):
container.save() container.save()
return Container.convert_with_links(container) return Container.convert_with_links(container)
@index.when(method='DELETE', template='json') @pecan.expose('json')
@exception.wrap_pecan_controller_exception @exception.wrap_pecan_controller_exception
def on_delete(self, **kwargs): def delete(self, container_id):
"""Delete a container. """Delete a container.
:param container_ident: UUID or Name of a container. :param container_ident: UUID or Name of a container.
""" """
container = _get_container(self.container_id) container = _get_container(container_id)
check_policy_on_container(container, "container:delete") check_policy_on_container(container, "container:delete")
context = pecan.request.context context = pecan.request.context
pecan.request.rpcapi.container_delete(context, container) pecan.request.rpcapi.container_delete(context, container)
container.destroy() container.destroy()
pecan.response.status = 204 pecan.response.status = 204
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def start(self, container_id, **kw):
container = _get_container(container_id)
check_policy_on_container(container, "container:start")
LOG.debug('Calling compute.container_start with %s',
container.uuid)
context = pecan.request.context
pecan.request.rpcapi.container_start(context, container)
return Container.convert_with_links(container)
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def stop(self, container_id, **kw):
container = _get_container(container_id)
check_policy_on_container(container, "container:stop")
LOG.debug('Calling compute.container_stop with %s' %
container.uuid)
context = pecan.request.context
pecan.request.rpcapi.container_stop(context, container)
return Container.convert_with_links(container)
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def reboot(self, container_id, **kw):
container = _get_container(container_id)
check_policy_on_container(container, "container:reboot")
LOG.debug('Calling compute.container_reboot with %s' %
container.uuid)
context = pecan.request.context
pecan.request.rpcapi.container_reboot(context, container)
return Container.convert_with_links(container)
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def pause(self, container_id, **kw):
container = _get_container(container_id)
check_policy_on_container(container, "container:pause")
LOG.debug('Calling compute.container_pause with %s' %
container.uuid)
context = pecan.request.context
pecan.request.rpcapi.container_pause(context, container)
return Container.convert_with_links(container)
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def unpause(self, container_id, **kw):
container = _get_container(container_id)
check_policy_on_container(container, "container:unpause")
LOG.debug('Calling compute.container_unpause with %s' %
container.uuid)
context = pecan.request.context
pecan.request.rpcapi.container_unpause(context, container)
return Container.convert_with_links(container)
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def logs(self, container_id):
container = _get_container(container_id)
check_policy_on_container(container, "container:logs")
LOG.debug('Calling compute.container_logs with %s' %
container.uuid)
context = pecan.request.context
return pecan.request.rpcapi.container_logs(context, container)
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def execute(self, container_id, **kw):
container = _get_container(container_id)
check_policy_on_container(container, "container:execute")
LOG.debug('Calling compute.container_exec with %s command %s'
% (container.uuid, kw['command']))
context = pecan.request.context
return pecan.request.rpcapi.container_exec(context, container,
kw['command'])

View File

@ -336,7 +336,7 @@ class TestContainerController(api_base.FunctionalTest):
get_by_ident_loc = 'zun.objects.Container.get_by_%s' % ident_field get_by_ident_loc = 'zun.objects.Container.get_by_%s' % ident_field
with patch(get_by_ident_loc) as mock_get_by_indent: with patch(get_by_ident_loc) as mock_get_by_indent:
mock_get_by_indent.return_value = test_container_obj mock_get_by_indent.return_value = test_container_obj
response = self.app.put('/v1/containers/%s/%s/' % (ident, response = self.app.post('/v1/containers/%s/%s/' % (ident,
action)) action))
self.assertEqual(200, response.status_int) self.assertEqual(200, response.status_int)
@ -455,7 +455,7 @@ class TestContainerController(api_base.FunctionalTest):
mock_get_by_uuid.return_value = test_container_obj mock_get_by_uuid.return_value = test_container_obj
container_uuid = test_container.get('uuid') container_uuid = test_container.get('uuid')
self.assertRaises(AppError, self.app.put, self.assertRaises(AppError, self.app.post,
'/v1/containers/%s/logs/' % container_uuid) '/v1/containers/%s/logs/' % container_uuid)
self.assertFalse(mock_container_logs.called) self.assertFalse(mock_container_logs.called)
@ -471,7 +471,7 @@ class TestContainerController(api_base.FunctionalTest):
container_uuid = test_container.get('uuid') container_uuid = test_container.get('uuid')
url = '/v1/containers/%s/%s/' % (container_uuid, 'execute') url = '/v1/containers/%s/%s/' % (container_uuid, 'execute')
cmd = {'command': 'ls'} cmd = {'command': 'ls'}
response = self.app.put(url, cmd) response = self.app.post(url, cmd)
self.assertEqual(200, response.status_int) self.assertEqual(200, response.status_int)
mock_container_exec.assert_called_once_with( mock_container_exec.assert_called_once_with(
mock.ANY, test_container_obj, cmd['command']) mock.ANY, test_container_obj, cmd['command'])
@ -488,7 +488,7 @@ class TestContainerController(api_base.FunctionalTest):
container_name = test_container.get('name') container_name = test_container.get('name')
url = '/v1/containers/%s/%s/' % (container_name, 'execute') url = '/v1/containers/%s/%s/' % (container_name, 'execute')
cmd = {'command': 'ls'} cmd = {'command': 'ls'}
response = self.app.put(url, cmd) response = self.app.post(url, cmd)
self.assertEqual(200, response.status_int) self.assertEqual(200, response.status_int)
mock_container_exec.assert_called_once_with( mock_container_exec.assert_called_once_with(
mock.ANY, test_container_obj, cmd['command']) mock.ANY, test_container_obj, cmd['command'])
@ -622,7 +622,7 @@ class TestContainerEnforcement(api_base.FunctionalTest):
def test_policy_only_owner_execute(self): def test_policy_only_owner_execute(self):
container = obj_utils.create_test_container(self.context, container = obj_utils.create_test_container(self.context,
user_id='another') user_id='another')
self._owner_check("container:execute", self.put_json, self._owner_check("container:execute", self.post_json,
'/containers/%s/execute/' % container.uuid, '/containers/%s/execute/' % container.uuid,
params={'command': 'ls'}, expect_errors=True) params={'command': 'ls'}, expect_errors=True)
@ -631,6 +631,6 @@ class TestContainerEnforcement(api_base.FunctionalTest):
container = obj_utils.create_test_container(self.context, container = obj_utils.create_test_container(self.context,
user_id='another') user_id='another')
for action in actions: for action in actions:
self._owner_check('container:%s' % action, self.put_json, self._owner_check('container:%s' % action, self.post_json,
'/containers/%s/%s/' % (container.uuid, action), '/containers/%s/%s/' % (container.uuid, action),
{}, expect_errors=True) {}, expect_errors=True)