Support for async bay operations
Current implementation of magnum bay operations are synchronous and as a result API requests are blocked until response from HEAT service is received. With this change bay-create, bay-update and bay-delete calls will be asynchronous. Please note that with this change bay-create/bay-update api calls will return bay uuid instead of bay object and also microversion 1.2 is added for new behavior. Change-Id: I4ca1f9f386b6417726154c466e7a9104b6e6e5e1 Closes-Bug: #1588425
This commit is contained in:
parent
fc9c1a8fc0
commit
bf30b9b4cb
@ -13,6 +13,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import uuid
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import timeutils
|
||||
import pecan
|
||||
@ -54,6 +56,13 @@ class BayPatchType(types.JsonPatchType):
|
||||
return types.JsonPatchType.internal_attrs() + internal_attrs
|
||||
|
||||
|
||||
class BayID(wtypes.Base):
|
||||
uuid = types.uuid
|
||||
|
||||
def __init__(self, uuid):
|
||||
self.uuid = uuid
|
||||
|
||||
|
||||
class Bay(base.APIBase):
|
||||
"""API representation of a bay.
|
||||
|
||||
@ -319,12 +328,33 @@ class BaysController(base.Controller):
|
||||
|
||||
return bay
|
||||
|
||||
@base.Controller.api_version("1.1", "1.1")
|
||||
@expose.expose(Bay, body=Bay, status_code=201)
|
||||
def post(self, bay):
|
||||
"""Create a new bay.
|
||||
|
||||
:param bay: a bay within the request body.
|
||||
"""
|
||||
new_bay = self._post(bay)
|
||||
res_bay = pecan.request.rpcapi.bay_create(new_bay,
|
||||
bay.bay_create_timeout)
|
||||
|
||||
# Set the HTTP Location Header
|
||||
pecan.response.location = link.build_url('bays', res_bay.uuid)
|
||||
return Bay.convert_with_links(res_bay)
|
||||
|
||||
@base.Controller.api_version("1.2") # noqa
|
||||
@expose.expose(BayID, body=Bay, status_code=202)
|
||||
def post(self, bay):
|
||||
"""Create a new bay.
|
||||
|
||||
:param bay: a bay within the request body.
|
||||
"""
|
||||
new_bay = self._post(bay)
|
||||
pecan.request.rpcapi.bay_create_async(new_bay, bay.bay_create_timeout)
|
||||
return BayID(new_bay.uuid)
|
||||
|
||||
def _post(self, bay):
|
||||
context = pecan.request.context
|
||||
policy.enforce(context, 'bay:create',
|
||||
action='bay:create')
|
||||
@ -340,13 +370,10 @@ class BaysController(base.Controller):
|
||||
bay_dict['name'] = name
|
||||
|
||||
new_bay = objects.Bay(context, **bay_dict)
|
||||
res_bay = pecan.request.rpcapi.bay_create(new_bay,
|
||||
bay.bay_create_timeout)
|
||||
|
||||
# Set the HTTP Location Header
|
||||
pecan.response.location = link.build_url('bays', res_bay.uuid)
|
||||
return Bay.convert_with_links(res_bay)
|
||||
new_bay.uuid = uuid.uuid4()
|
||||
return new_bay
|
||||
|
||||
@base.Controller.api_version("1.1", "1.1")
|
||||
@wsme.validate(types.uuid, [BayPatchType])
|
||||
@expose.expose(Bay, types.uuid_or_name, body=[BayPatchType])
|
||||
def patch(self, bay_ident, patch):
|
||||
@ -355,6 +382,25 @@ class BaysController(base.Controller):
|
||||
:param bay_ident: UUID or logical name of a bay.
|
||||
:param patch: a json PATCH document to apply to this bay.
|
||||
"""
|
||||
bay = self._patch(bay_ident, patch)
|
||||
res_bay = pecan.request.rpcapi.bay_update(bay)
|
||||
return Bay.convert_with_links(res_bay)
|
||||
|
||||
@base.Controller.api_version("1.2") # noqa
|
||||
@wsme.validate(types.uuid, [BayPatchType])
|
||||
@expose.expose(BayID, types.uuid_or_name, body=[BayPatchType],
|
||||
status_code=202)
|
||||
def patch(self, bay_ident, patch):
|
||||
"""Update an existing bay.
|
||||
|
||||
:param bay_ident: UUID or logical name of a bay.
|
||||
:param patch: a json PATCH document to apply to this bay.
|
||||
"""
|
||||
bay = self._patch(bay_ident, patch)
|
||||
pecan.request.rpcapi.bay_update_async(bay)
|
||||
return BayID(bay.uuid)
|
||||
|
||||
def _patch(self, bay_ident, patch):
|
||||
context = pecan.request.context
|
||||
bay = api_utils.get_resource('Bay', bay_ident)
|
||||
policy.enforce(context, 'bay:update', bay,
|
||||
@ -380,19 +426,33 @@ class BaysController(base.Controller):
|
||||
delta = bay.obj_what_changed()
|
||||
|
||||
validate_bay_properties(delta)
|
||||
return bay
|
||||
|
||||
res_bay = pecan.request.rpcapi.bay_update(bay)
|
||||
return Bay.convert_with_links(res_bay)
|
||||
|
||||
@base.Controller.api_version("1.1", "1.1")
|
||||
@expose.expose(None, types.uuid_or_name, status_code=204)
|
||||
def delete(self, bay_ident):
|
||||
"""Delete a bay.
|
||||
|
||||
:param bay_ident: UUID of a bay or logical name of the bay.
|
||||
"""
|
||||
bay = self._delete(bay_ident)
|
||||
|
||||
pecan.request.rpcapi.bay_delete(bay.uuid)
|
||||
|
||||
@base.Controller.api_version("1.2") # noqa
|
||||
@expose.expose(None, types.uuid_or_name, status_code=204)
|
||||
def delete(self, bay_ident):
|
||||
"""Delete a bay.
|
||||
|
||||
:param bay_ident: UUID of a bay or logical name of the bay.
|
||||
"""
|
||||
bay = self._delete(bay_ident)
|
||||
|
||||
pecan.request.rpcapi.bay_delete_async(bay.uuid)
|
||||
|
||||
def _delete(self, bay_ident):
|
||||
context = pecan.request.context
|
||||
bay = api_utils.get_resource('Bay', bay_ident)
|
||||
policy.enforce(context, 'bay:delete', bay,
|
||||
action='bay:delete')
|
||||
|
||||
pecan.request.rpcapi.bay_delete(bay.uuid)
|
||||
return bay
|
||||
|
@ -28,7 +28,9 @@ from magnum.i18n import _
|
||||
# Add details of new api versions here:
|
||||
|
||||
BASE_VER = '1.1'
|
||||
CURRENT_MAX_VER = '1.1'
|
||||
CURRENT_MAX_VER = '1.2'
|
||||
# 1.2 Async bay operations support
|
||||
# 1.1 Initial version
|
||||
|
||||
|
||||
class Version(object):
|
||||
|
@ -34,12 +34,22 @@ class API(rpc_service.API):
|
||||
return self._call('bay_create', bay=bay,
|
||||
bay_create_timeout=bay_create_timeout)
|
||||
|
||||
def bay_create_async(self, bay, bay_create_timeout):
|
||||
self._cast('bay_create', bay=bay,
|
||||
bay_create_timeout=bay_create_timeout)
|
||||
|
||||
def bay_delete(self, uuid):
|
||||
return self._call('bay_delete', uuid=uuid)
|
||||
|
||||
def bay_delete_async(self, uuid):
|
||||
self._cast('bay_delete', uuid=uuid)
|
||||
|
||||
def bay_update(self, bay):
|
||||
return self._call('bay_update', bay=bay)
|
||||
|
||||
def bay_update_async(self, bay):
|
||||
self._cast('bay_update', bay=bay)
|
||||
|
||||
# CA operations
|
||||
|
||||
def sign_certificate(self, bay, certificate):
|
||||
|
@ -13,7 +13,6 @@
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
import uuid
|
||||
|
||||
from heatclient.common import template_utils
|
||||
from heatclient import exc
|
||||
@ -145,7 +144,6 @@ class Handler(object):
|
||||
|
||||
osc = clients.OpenStackClients(context)
|
||||
|
||||
bay.uuid = uuid.uuid4()
|
||||
try:
|
||||
# Create trustee/trust and set them to bay
|
||||
trust_manager.create_trustee_and_trust(osc, bay)
|
||||
|
@ -13,6 +13,7 @@
|
||||
import fixtures
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import uuidutils
|
||||
from tempest.lib.common.utils import data_utils
|
||||
from tempest.lib import exceptions
|
||||
import testtools
|
||||
@ -90,16 +91,21 @@ class BayTest(base.BaseTempestTest):
|
||||
resp, model = self.baymodel_client.delete_baymodel(baymodel_id)
|
||||
return resp, model
|
||||
|
||||
def _create_bay(self, bay_model):
|
||||
def _create_bay(self, bay_model, is_async=False):
|
||||
self.LOG.debug('We will create bay for %s' % bay_model)
|
||||
resp, model = self.bay_client.post_bay(bay_model)
|
||||
headers = {'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'}
|
||||
if is_async:
|
||||
headers["OpenStack-API-Version"] = "container-infra 1.2"
|
||||
resp, model = self.bay_client.post_bay(bay_model, headers=headers)
|
||||
self.LOG.debug('Response: %s' % resp)
|
||||
self.assertEqual(201, resp.status)
|
||||
if is_async:
|
||||
self.assertEqual(202, resp.status)
|
||||
else:
|
||||
self.assertEqual(201, resp.status)
|
||||
self.assertIsNotNone(model.uuid)
|
||||
self.assertTrue(uuidutils.is_uuid_like(model.uuid))
|
||||
self.bays.append(model.uuid)
|
||||
self.assertEqual(BayStatus.CREATE_IN_PROGRESS, model.status)
|
||||
self.assertIsNone(model.status_reason)
|
||||
self.assertEqual(model.baymodel_id, self.baymodel.uuid)
|
||||
self.bay_uuid = model.uuid
|
||||
if config.Config.copy_logs:
|
||||
self.addOnException(self.copy_logs_handler(
|
||||
@ -134,6 +140,8 @@ class BayTest(base.BaseTempestTest):
|
||||
|
||||
# test bay create
|
||||
_, temp_model = self._create_bay(gen_model)
|
||||
self.assertEqual(BayStatus.CREATE_IN_PROGRESS, temp_model.status)
|
||||
self.assertIsNone(temp_model.status_reason)
|
||||
|
||||
# test bay list
|
||||
resp, model = self.bay_client.list_bays()
|
||||
@ -153,6 +161,26 @@ class BayTest(base.BaseTempestTest):
|
||||
self._delete_bay(temp_model.uuid)
|
||||
self.bays.remove(temp_model.uuid)
|
||||
|
||||
@testtools.testcase.attr('positive')
|
||||
def test_create_delete_bays_async(self):
|
||||
gen_model = datagen.valid_bay_data(
|
||||
baymodel_id=self.baymodel.uuid, node_count=1)
|
||||
|
||||
# test bay create
|
||||
_, temp_model = self._create_bay(gen_model, is_async=True)
|
||||
self.assertNotIn('status', temp_model)
|
||||
|
||||
# test bay list
|
||||
resp, model = self.bay_client.list_bays()
|
||||
self.assertEqual(200, resp.status)
|
||||
self.assertGreater(len(model.bays), 0)
|
||||
self.assertIn(
|
||||
temp_model.uuid, list([x['uuid'] for x in model.bays]))
|
||||
|
||||
# test bay delete
|
||||
self._delete_bay(temp_model.uuid)
|
||||
self.bays.remove(temp_model.uuid)
|
||||
|
||||
@testtools.testcase.attr('negative')
|
||||
def test_create_bay_for_nonexisting_baymodel(self):
|
||||
gen_model = datagen.valid_bay_data(baymodel_id='this-does-not-exist')
|
||||
|
@ -40,7 +40,7 @@ class TestRootController(api_base.FunctionalTest):
|
||||
[{u'href': u'http://localhost/v1/',
|
||||
u'rel': u'self'}],
|
||||
u'status': u'CURRENT',
|
||||
u'max_version': u'1.1',
|
||||
u'max_version': u'1.2',
|
||||
u'min_version': u'1.1'}]}
|
||||
|
||||
self.v1_expected = {
|
||||
|
@ -16,7 +16,6 @@ import mock
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import timeutils
|
||||
from oslo_utils import uuidutils
|
||||
from six.moves.urllib import parse as urlparse
|
||||
|
||||
from magnum.api import attr_validator
|
||||
from magnum.api.controllers.v1 import bay as api_bay
|
||||
@ -428,10 +427,7 @@ class TestPost(api_base.FunctionalTest):
|
||||
self.assertEqual(201, response.status_int)
|
||||
# Check location header
|
||||
self.assertIsNotNone(response.location)
|
||||
expected_location = '/v1/bays/%s' % bdict['uuid']
|
||||
self.assertEqual(expected_location,
|
||||
urlparse.urlparse(response.location).path)
|
||||
self.assertEqual(bdict['uuid'], response.json['uuid'])
|
||||
self.assertTrue(uuidutils.is_uuid_like(response.json['uuid']))
|
||||
self.assertNotIn('updated_at', response.json.keys)
|
||||
return_created_at = timeutils.parse_isotime(
|
||||
response.json['created_at']).replace(tzinfo=None)
|
||||
|
@ -15,7 +15,6 @@
|
||||
# under the License.
|
||||
|
||||
import six
|
||||
import uuid
|
||||
|
||||
from heatclient import exc
|
||||
import mock
|
||||
@ -172,14 +171,11 @@ class TestHandler(db_base.DbTestCase):
|
||||
@patch('magnum.conductor.handlers.bay_conductor.trust_manager')
|
||||
@patch('magnum.conductor.handlers.bay_conductor.cert_manager')
|
||||
@patch('magnum.conductor.handlers.bay_conductor._create_stack')
|
||||
@patch('magnum.conductor.handlers.bay_conductor.uuid')
|
||||
@patch('magnum.common.clients.OpenStackClients')
|
||||
def test_create(self, mock_openstack_client_class, mock_uuid,
|
||||
def test_create(self, mock_openstack_client_class,
|
||||
mock_create_stack, mock_cert_manager, mock_trust_manager,
|
||||
mock_heat_poller_class):
|
||||
timeout = 15
|
||||
test_uuid = uuid.uuid4()
|
||||
mock_uuid.uuid4.return_value = test_uuid
|
||||
mock_poller = mock.MagicMock()
|
||||
mock_poller.poll_and_check.return_value = loopingcall.LoopingCallDone()
|
||||
mock_heat_poller_class.return_value = mock_poller
|
||||
@ -187,7 +183,6 @@ class TestHandler(db_base.DbTestCase):
|
||||
mock_openstack_client_class.return_value = osc
|
||||
|
||||
def create_stack_side_effect(context, osc, bay, timeout):
|
||||
self.assertEqual(str(test_uuid), bay.uuid)
|
||||
return {'stack': {'id': 'stack-id'}}
|
||||
|
||||
mock_create_stack.side_effect = create_stack_side_effect
|
||||
@ -334,16 +329,12 @@ class TestHandler(db_base.DbTestCase):
|
||||
@patch('magnum.conductor.handlers.bay_conductor.trust_manager')
|
||||
@patch('magnum.conductor.handlers.bay_conductor.cert_manager')
|
||||
@patch('magnum.conductor.handlers.bay_conductor._create_stack')
|
||||
@patch('magnum.conductor.handlers.bay_conductor.uuid')
|
||||
@patch('magnum.common.clients.OpenStackClients')
|
||||
def test_create_with_invalid_unicode_name(self,
|
||||
mock_openstack_client_class,
|
||||
mock_uuid,
|
||||
mock_create_stack,
|
||||
mock_cert_manager,
|
||||
mock_trust_manager):
|
||||
test_uuid = uuid.uuid4()
|
||||
mock_uuid.uuid4.return_value = test_uuid
|
||||
error_message = six.u("""Invalid stack name 测试集群-zoyh253geukk
|
||||
must contain only alphanumeric or "_-."
|
||||
characters, must start with alpha""")
|
||||
@ -376,11 +367,9 @@ class TestHandler(db_base.DbTestCase):
|
||||
@patch('magnum.conductor.handlers.bay_conductor.trust_manager')
|
||||
@patch('magnum.conductor.handlers.bay_conductor.cert_manager')
|
||||
@patch('magnum.conductor.handlers.bay_conductor.short_id')
|
||||
@patch('magnum.conductor.handlers.bay_conductor.uuid')
|
||||
@patch('magnum.common.clients.OpenStackClients')
|
||||
def test_create_with_environment(self,
|
||||
mock_openstack_client_class,
|
||||
mock_uuid,
|
||||
mock_short_id,
|
||||
mock_cert_manager,
|
||||
mock_trust_manager,
|
||||
@ -390,8 +379,6 @@ class TestHandler(db_base.DbTestCase):
|
||||
mock_heat_poller_class):
|
||||
timeout = 15
|
||||
self.bay.baymodel_id = self.baymodel.uuid
|
||||
test_uuid = uuid.uuid4()
|
||||
mock_uuid.uuid4.return_value = test_uuid
|
||||
bay_name = self.bay.name
|
||||
mock_short_id.generate_id.return_value = 'short_id'
|
||||
mock_poller = mock.MagicMock()
|
||||
|
@ -0,0 +1,16 @@
|
||||
---
|
||||
features:
|
||||
- Current implementation of magnum bay operations are
|
||||
synchronous and as a result API requests are blocked
|
||||
until response from HEAT service is received. This release
|
||||
adds support for asynchronous bay operations (bay-create,
|
||||
bay-update, and bay-delete). Please note that with this
|
||||
change, bay-create, bay-update API calls will return bay uuid
|
||||
instead of bay object and also return HTTP status code 202
|
||||
instead of 201. Microversion 1.2 is added for new behavior.
|
||||
|
||||
upgrade:
|
||||
- Magnum bay operations API default behavior changed from
|
||||
synchronous to asynchronous. User can specify
|
||||
OpenStack-API-Version 1.1 in request header for synchronous
|
||||
bay operations.
|
Loading…
Reference in New Issue
Block a user