Merge "Support alias for execution creation"

This commit is contained in:
Zuul 2018-07-17 09:00:50 +00:00 committed by Gerrit Code Review
commit 527474fe30
7 changed files with 169 additions and 13 deletions

View File

@ -23,6 +23,7 @@ from qinling.api.controllers.v1 import resources
from qinling.api.controllers.v1 import types from qinling.api.controllers.v1 import types
from qinling import context from qinling import context
from qinling.db import api as db_api from qinling.db import api as db_api
from qinling import exceptions as exc
from qinling import rpc from qinling import rpc
from qinling.utils import executions from qinling.utils import executions
from qinling.utils import rest_utils from qinling.utils import rest_utils
@ -60,6 +61,11 @@ class ExecutionsController(rest.RestController):
acl.enforce('execution:create', ctx) acl.enforce('execution:create', ctx)
params = body.to_dict() params = body.to_dict()
if not (params.get("function_id") or params.get("function_alias")):
raise exc.InputException(
'Either function_alias or function_id must be provided.'
)
LOG.info("Creating %s. [params=%s]", self.type, params) LOG.info("Creating %s. [params=%s]", self.type, params)
db_model = executions.create_execution(self.engine_client, params) db_model = executions.create_execution(self.engine_client, params)

View File

@ -294,8 +294,9 @@ class RuntimePool(Resource):
class Execution(Resource): class Execution(Resource):
id = types.uuid id = types.uuid
function_id = wsme.wsattr(types.uuid, mandatory=True) function_id = wsme.wsattr(types.uuid)
function_version = wsme.wsattr(int, default=0) function_version = wsme.wsattr(int, default=0)
function_alias = wtypes.text
description = wtypes.text description = wtypes.text
status = wsme.wsattr(wtypes.text, readonly=True) status = wsme.wsattr(wtypes.text, readonly=True)
sync = bool sync = bool

View File

@ -58,6 +58,39 @@ class TestExecutionController(base.APITest):
resp = self.app.get('/v1/functions/%s/versions/1' % self.func_id) resp = self.app.get('/v1/functions/%s/versions/1' % self.func_id)
self.assertEqual(1, resp.json.get('count')) self.assertEqual(1, resp.json.get('count'))
@mock.patch('qinling.rpc.EngineClient.create_execution')
def test_post_with_alias(self, mock_rpc):
db_api.increase_function_version(self.func_id, 0,
description="version 1")
name = self.rand_name(name="alias", prefix=self.prefix)
body = {
'function_id': self.func_id,
'function_version': 1,
'name': name
}
db_api.create_function_alias(**body)
execution_body = {
'function_alias': name
}
resp = self.app.post_json('/v1/executions', execution_body)
self.assertEqual(201, resp.status_int)
resp = self.app.get('/v1/functions/%s' % self.func_id)
self.assertEqual(0, resp.json.get('count'))
resp = self.app.get('/v1/functions/%s/versions/1' % self.func_id)
self.assertEqual(1, resp.json.get('count'))
def test_post_without_required_params(self):
resp = self.app.post(
'/v1/executions',
params={},
expect_errors=True
)
self.assertEqual(400, resp.status_int)
@mock.patch('qinling.rpc.EngineClient.create_execution') @mock.patch('qinling.rpc.EngineClient.create_execution')
def test_post_rpc_error(self, mock_create_execution): def test_post_rpc_error(self, mock_create_execution):
mock_create_execution.side_effect = exc.QinlingException mock_create_execution.side_effect = exc.QinlingException

View File

@ -70,10 +70,18 @@ def _update_function_version_db(version_id, pre_count):
def create_execution(engine_client, params): def create_execution(engine_client, params):
function_id = params['function_id'] function_alias = params.get('function_alias')
function_id = params.get('function_id')
version = params.get('function_version', 0)
is_sync = params.get('sync', True) is_sync = params.get('sync', True)
input = params.get('input') input = params.get('input')
version = params.get('function_version', 0)
if function_alias:
alias_db = db_api.get_function_alias(function_alias)
function_id = alias_db.function_id
version = alias_db.function_version
params.update({'function_id': function_id,
'version': version})
func_db = db_api.get_function(function_id) func_db = db_api.get_function(function_id)
runtime_id = func_db.runtime_id runtime_id = func_db.runtime_id

View File

@ -127,13 +127,29 @@ class QinlingClient(client_base.QinlingClientBase):
return self.post(url, None, headers={}) return self.post(url, None, headers={})
def create_execution(self, function_id, input=None, sync=True, version=0): def create_execution(self, function_id=None, alias_name=None, input=None,
req_body = { sync=True, version=0):
'function_id': function_id, """Create execution.
'function_version': version,
'sync': sync, alias_name takes precedence over function_id.
'input': input """
} if alias_name:
req_body = {
'function_alias': alias_name,
'sync': sync,
'input': input
}
elif function_id:
req_body = {
'function_id': function_id,
'function_version': version,
'sync': sync,
'input': input
}
else:
raise Exception("Either alias_name or function_id must be "
"provided.")
resp, body = self.post_json('executions', req_body) resp, body = self.post_json('executions', req_body)
return resp, body return resp, body
@ -208,3 +224,46 @@ class QinlingClient(client_base.QinlingClientBase):
) )
return resp, json.loads(body) return resp, json.loads(body)
def create_function_alias(self, name, function_id,
function_version=0, description=None):
req_body = {
'function_id': function_id,
'function_version': function_version,
'name': name
}
if description is not None:
req_body['description'] = description
resp, body = self.post_json('/aliases', req_body)
return resp, body
def delete_function_alias(self, alias_name, ignore_notfound=False):
try:
resp, _ = self.delete('/v1/aliases/%s' % alias_name)
return resp
except exceptions.NotFound:
if ignore_notfound:
pass
else:
raise
def get_function_alias(self, alias_name):
resp, body = self.get('/v1/aliases/%s' % alias_name)
return resp, json.loads(body)
def update_function_alias(self, alias_name, function_id=None,
function_version=None, description=None):
req_body = {}
if function_id is not None:
req_body['function_id'] = function_id
if function_version is not None:
req_body['function_version'] = function_version
if description is not None:
req_body['description'] = description
resp, body = self.put_json('/v1/aliases/%s' % alias_name, req_body)
return resp, body

View File

@ -121,6 +121,32 @@ class ExecutionsTest(base.BaseQinlingTest):
self.assertEqual(200, resp.status) self.assertEqual(200, resp.status)
self.assertNotIn('Hello, World', body) self.assertNotIn('Hello, World', body)
@decorators.idempotent_id('dbf4bd84-bde3-4d1d-8dec-93aaf18b4b5f')
def test_create_with_function_alias(self):
function_id = self.create_function()
alias_name = self.create_function_alias(function_id)
execution_id = self.create_execution(alias_name=alias_name)
resp, body = self.client.get_execution_log(execution_id)
self.assertEqual(200, resp.status)
self.assertIn('Hello, World', body)
version_1 = self.create_function_version(function_id)
alias_name_1 = self.create_function_alias(function_id, version_1)
execution_id = self.create_execution(alias_name=alias_name_1)
resp, body = self.client.get_execution_log(execution_id)
self.assertEqual(200, resp.status)
self.assertIn('Hello, World', body)
self.update_function_package(function_id,
"python/test_python_sleep.py")
version_2 = self.create_function_version(function_id)
alias_name_2 = self.create_function_alias(function_id, version_2)
execution_id = self.create_execution(alias_name=alias_name_2)
resp, body = self.client.get_execution_log(execution_id)
self.assertEqual(200, resp.status)
self.assertNotIn('Hello, World', body)
@decorators.idempotent_id('8096cc52-64d2-4660-a657-9ac0bdd743ae') @decorators.idempotent_id('8096cc52-64d2-4660-a657-9ac0bdd743ae')
def test_execution_async(self): def test_execution_async(self):
function_id = self.create_function() function_id = self.create_function()

View File

@ -213,9 +213,15 @@ class BaseQinlingTest(test.BaseTestCase):
return version return version
def create_execution(self, function_id, version=0, input=None): def create_execution(self, function_id=None, alias_name=None, version=0,
resp, body = self.client.create_execution(function_id, version=version, input=None):
input=input) if alias_name:
resp, body = self.client.create_execution(alias_name=alias_name,
input=input)
else:
resp, body = self.client.create_execution(function_id,
version=version,
input=input)
self.assertEqual(201, resp.status) self.assertEqual(201, resp.status)
@ -226,3 +232,20 @@ class BaseQinlingTest(test.BaseTestCase):
self.assertEqual('success', body['status']) self.assertEqual('success', body['status'])
return execution_id return execution_id
def create_function_alias(self, function_id=None, function_version=0):
name = data_utils.rand_name(name="alias", prefix=self.name_prefix)
if not function_id:
function_id = self.create_function()
resp, body = self.client.create_function_alias(name,
function_id,
function_version)
self.assertEqual(201, resp.status)
alias_name = body['name']
self.addCleanup(self.client.delete_function_alias, alias_name,
ignore_notfound=True)
return alias_name