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:
parent
6ebb6b72df
commit
7fcf415515
|
@ -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()
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.")
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)."""
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue