Adding initial update logic for orders

Adding the ability to update orders for certificates
in the controller. Connects the update order all the way
to the certificate resources. Also updated an issue
if order type was not listed, nothing would happen.

Implements: blueprint add-ssl-ca-support
Change-Id: I08e58cc618c5e21520f4d46cc35e10da1a02d0c8
This commit is contained in:
Chelsea Winfree 2014-08-27 16:29:09 -05:00
parent 6ebb6b72df
commit 7fcf415515
9 changed files with 296 additions and 7 deletions

View File

@ -44,20 +44,51 @@ def _order_update_not_supported():
pecan.abort(405, u._("Order update is not supported."))
def _order_type_not_in_order():
"""Throw exception that order type is not available in the order."""
pecan.abort(400, u._("Order type is expected but not received."))
def _order_meta_not_in_update():
"""Throw exception that order meta is not available for an order update."""
pecan.abort(400, u._("Order meta is expected for order updates."))
def _order_update_not_supported_for_type(order_type):
"""Throw exception that update is not supported."""
pecan.abort(400, u._("Updates are not supported for order type "
"{0}.").format(order_type))
def _order_cannot_be_updated_if_not_pending(order_status):
"""Throw exception that order cannot be updated if not PENDING."""
pecan.abort(400, u._("Only PENDING orders can be updated. Order is in the"
"{0} state.").format(order_status))
def order_cannot_modify_order_type():
"""Throw exception that order type cannot be modified."""
pecan.abort(400, u._("Cannot modify order type."))
class OrderController(object):
"""Handles Order retrieval and deletion requests."""
def __init__(self, order_id, order_repo=None):
def __init__(self, order_id, order_repo=None,
queue_resource=None):
self.order_id = order_id
self.repo = order_repo or repo.OrderRepo()
self.order_repo = order_repo or repo.OrderRepo()
self.queue = queue_resource or async_client.TaskClient()
self.type_order_validator = validators.TypeOrderValidator()
@pecan.expose(generic=True, template='json')
@controllers.handle_exceptions(u._('Order retrieval'))
@controllers.enforce_rbac('order:get')
def index(self, keystone_id):
order = self.repo.get(entity_id=self.order_id, keystone_id=keystone_id,
suppress_exception=True)
order = self.order_repo.get(entity_id=self.order_id,
keystone_id=keystone_id,
suppress_exception=True)
if not order:
_order_not_found()
@ -65,8 +96,46 @@ class OrderController(object):
@index.when(method='PUT')
@controllers.handle_exceptions(u._('Order update'))
@controllers.enforce_rbac('order:put')
@controllers.enforce_content_types(['application/json'])
def on_put(self, keystone_id, **kwargs):
_order_update_not_supported()
raw_body = pecan.request.body
order_type = None
if raw_body:
order_type = json.loads(raw_body).get('type')
if not order_type:
_order_type_not_in_order()
order_model = self.order_repo.get(entity_id=self.order_id,
keystone_id=keystone_id,
suppress_exception=True)
if not order_model:
_order_not_found()
if order_model.type != order_type:
order_cannot_modify_order_type()
if models.OrderType.CERTIFICATE != order_model.type:
_order_update_not_supported_for_type(order_type)
if models.States.PENDING != order_model.status:
_order_cannot_be_updated_if_not_pending(order_model.status)
body = api.load_body(pecan.request,
validator=self.type_order_validator)
updated_meta = body.get('meta')
if not updated_meta:
_order_meta_not_in_update()
# TODO(chellygel): Put updated_meta into a separate order association
# entity.
self.queue.update_order(order_id=self.order_id,
keystone_id=keystone_id,
updated_meta=updated_meta)
@index.when(method='DELETE')
@controllers.handle_exceptions(u._('Order deletion'))
@ -74,8 +143,8 @@ class OrderController(object):
def on_delete(self, keystone_id, **kwargs):
try:
self.repo.delete_entity_by_id(entity_id=self.order_id,
keystone_id=keystone_id)
self.order_repo.delete_entity_by_id(entity_id=self.order_id,
keystone_id=keystone_id)
except exception.NotFound:
LOG.exception('Problem deleting order')
_order_not_found()

View File

@ -49,6 +49,14 @@ class TaskClient(object):
self._cast('process_type_order', order_id=order_id,
keystone_id=keystone_id)
def update_order(self, order_id, keystone_id, updated_meta):
"""Update Order."""
self._cast('update_order',
order_id=order_id,
keystone_id=keystone_id,
updated_meta=updated_meta)
def _cast(self, name, **kwargs):
"""Asynchronous call handler. Barbican probably only needs casts.

View File

@ -60,6 +60,15 @@ class Tasks(object):
LOG.exception(">>>>> Task exception seen, details reported "
"on the Orders entity.")
def update_order(self, context, order_id, keystone_id, updated_meta):
"""Update Order."""
task = resources.UpdateOrder()
try:
task.process(order_id, keystone_id, updated_meta)
except Exception:
LOG.exception(">>>>> Task exception seen, details reported "
"on the Orders entity.")
class TaskServer(Tasks, service.Service):
"""Server to process asynchronous tasking from Barbican API nodes.

View File

@ -16,9 +16,11 @@
from barbican.common import hrefs
import barbican.common.utils as utils
from barbican.model import models
from barbican.plugin.interface import certificate_manager as cert
from barbican.plugin import resources as plugin
LOG = utils.getLogger(__name__)
# Order sub-status definitions
ORDER_STATUS_REQUEST_PENDING = models.OrderStatus(
@ -163,6 +165,13 @@ def check_certificate_request(order_model, tenant_model, plugin_name, repos):
return container_model
def modify_certificate_request(order_model, updated_meta, repos):
"""Update the order with CA."""
# TODO(chellygel): Add the modify certificate request logic.
LOG.debug('in modify_certificate_request')
raise NotImplementedError # pragma: no cover
def _schedule_cert_retry_task(cert_result_dto, cert_plugin, order_model,
plugin_meta,
retry_method=None,

View File

@ -301,3 +301,68 @@ class BeginTypeOrder(BaseTask):
if new_container:
order.container_id = new_container.id
LOG.debug("...done requesting a certificate.")
else:
raise NotImplementedError(
'Order type "{0}" not implemented.'.format(order_type))
class UpdateOrder(BaseTask):
"""Handles updating an order"""
def get_name(self):
return u._('Update Order')
def __init__(self, tenant_repo=None, order_repo=None,
secret_repo=None, tenant_secret_repo=None, datum_repo=None,
kek_repo=None, container_repo=None,
container_secret_repo=None, secret_meta_repo=None):
LOG.debug('Creating UpdateOrder task processor')
self.repos = rep.Repositories(
tenant_repo=tenant_repo,
order_repo=order_repo,
secret_repo=secret_repo,
tenant_secret_repo=tenant_secret_repo,
datum_repo=datum_repo,
kek_repo=kek_repo,
container_repo=container_repo,
container_secret_repo=container_secret_repo,
secret_meta_repo=secret_meta_repo
)
def retrieve_entity(self, order_id, keystone_id):
return self.repos.order_repo.get(entity_id=order_id,
keystone_id=keystone_id)
def handle_processing(self, order, updated_meta):
self.handle_order(order, updated_meta)
def handle_error(self, order, status, message, exception,
*args, **kwargs):
order.status = models.States.ERROR
order.error_status_code = status
order.error_reason = message
LOG.exception(u._("An error has occurred updating the order."))
self.repos.order_repo.save(order)
def handle_success(self, order, *args, **kwargs):
# TODO(chellygel): Handle sub-status on a pending order.
order.status = models.States.ACTIVE
self.repos.order_repo.save(order)
def handle_order(self, order, updated_meta):
"""Handle Order Update
:param order: Order to update.
"""
order_info = order.to_dict_fields()
order_type = order_info.get('type')
if order_type == models.OrderType.CERTIFICATE:
# Update a certificate request
cert.modify_certificate_request(order, updated_meta, self.repos)
LOG.debug("...done updating a certificate order.")
else:
raise NotImplementedError(
'Order type "{0}" not implemented.'.format(order_type))
LOG.debug("...done updating order.")

View File

@ -89,6 +89,17 @@ def create_order(id_ref="id",
return order
def create_order_with_meta(id_ref="id", order_type="certificate", meta={},
status='PENDING'):
"""Generate an Order entity instance with Metadata."""
order = models.Order()
order.id = id_ref
order.type = order_type
order.meta = meta
order.status = status
return order
def validate_datum(test, datum):
test.assertIsNone(datum.kek_meta_extended)
test.assertIsNotNone(datum.kek_meta_tenant)
@ -1632,6 +1643,7 @@ class WhenGettingOrDeletingOrderUsingOrderResource(FunctionalTest):
self.order_repo = mock.MagicMock()
self.order_repo.get.return_value = self.order
self.order_repo.save.return_value = None
self.order_repo.delete_entity_by_id.return_value = None
self.tenant_repo = mock.MagicMock()
@ -1670,6 +1682,99 @@ class WhenGettingOrDeletingOrderUsingOrderResource(FunctionalTest):
self.assertEqual(resp.content_type, "application/json")
class WhenPuttingOrderWithMetadataUsingOrderResource(FunctionalTest):
def setUp(self):
super(
WhenPuttingOrderWithMetadataUsingOrderResource, self
).setUp()
self.app = webtest.TestApp(app.PecanAPI(self.root))
self.app.extra_environ = get_barbican_env(self.tenant_keystone_id)
@property
def root(self):
self._init()
class RootController(object):
orders = controllers.orders.OrdersController(self.tenant_repo,
self.order_repo,
self.queue_resource)
return RootController()
def _init(self):
self.tenant_keystone_id = 'keystoneid1234'
self.requestor = 'requestor1234'
self.order = create_order_with_meta(
id_ref='id1',
order_type='certificate',
meta={'email': 'email@email.com'},
status="PENDING"
)
self.order_repo = mock.MagicMock()
self.order_repo.get.return_value = self.order
self.order_repo.save.return_value = None
self.order_repo.delete_entity_by_id.return_value = None
self.type = 'certificate'
self.meta = {'email': 'newemail@email.com'}
self.params = {'type': self.type, 'meta': self.meta}
self.tenant_repo = mock.MagicMock()
self.queue_resource = mock.MagicMock()
def test_should_put_order(self):
resp = self.app.put_json(
'/orders/{0}/'.format(self.order.id),
self.params,
headers={
'Content-Type': 'application/json'
}
)
self.assertEqual(resp.status_int, 204)
self.order_repo.get.assert_called_once_with(
entity_id=self.order.id,
keystone_id=self.tenant_keystone_id,
suppress_exception=True)
def test_should_fail_bad_type(self):
self.order['type'] = 'secret'
resp = self.app.put_json(
'/orders/{0}/'.format(self.order.id),
self.params,
headers={
'Content-Type': 'application/json'
},
expect_errors=True
)
self.assertEqual(resp.status_int, 400)
self.order_repo.get.assert_called_once_with(
entity_id=self.order.id,
keystone_id=self.tenant_keystone_id,
suppress_exception=True)
def test_should_fail_bad_status(self):
self.order['status'] = 'DONE'
resp = self.app.put_json(
'/orders/{0}/'.format(self.order.id),
self.params,
headers={
'Content-Type': 'application/json'
},
expect_errors=True
)
self.assertEqual(resp.status_int, 400)
self.order_repo.get.assert_called_once_with(
entity_id=self.order.id,
keystone_id=self.tenant_keystone_id,
suppress_exception=True)
class WhenCreatingTypeOrdersUsingOrdersResource(FunctionalTest):
def setUp(self):
super(

View File

@ -40,6 +40,17 @@ class WhenUsingAsyncTaskClient(utils.BaseTestCase):
order_id=self.order_id,
keystone_id=self.keystone_id)
def test_should_update_order(self):
updated_meta = {}
self.client.update_order(order_id=self.order_id,
keystone_id=self.keystone_id,
updated_meta=updated_meta)
queue.get_client.assert_called_with()
self.mock_client.cast.assert_called_with({}, 'update_order',
order_id=self.order_id,
keystone_id=self.keystone_id,
updated_meta=updated_meta)
class WhenCreatingDirectTaskClient(utils.BaseTestCase):
"""Test using the synchronous task client (i.e. standalone mode)."""

View File

@ -36,6 +36,18 @@ class WhenUsingBeginOrderTask(utils.BaseTestCase):
mock_begin_order.return_value.process.assert_called_with(
self.order_id, self.keystone_id)
@mock.patch('barbican.tasks.resources.UpdateOrder')
def test_should_update_order(self, mock_update_order):
mock_update_order.return_value.process.return_value = None
updated_meta = {}
self.tasks.update_order(context=None,
order_id=self.order_id,
keystone_id=self.keystone_id,
updated_meta=updated_meta)
mock_update_order.return_value.process.assert_called_with(
self.order_id, self.keystone_id, updated_meta
)
class WhenUsingTaskServer(utils.BaseTestCase):
"""Test using the asynchronous task client."""

View File

@ -9,6 +9,7 @@
"orders:post": "rule:admin_or_creator",
"orders:get": "rule:all_but_audit",
"order:get": "rule:all_users",
"order:put":"rule:admin_or_creator",
"order:delete": "rule:admin",
"consumer:get": "rule:all_users",
"consumers:get": "rule:all_users",