Support to update function timeout
Change-Id: I8c7c364af54a6e33406e8bece8337b1e7eeb75aa Story: 2002174 Task: 26341
This commit is contained in:
parent
cd688170da
commit
dd169d2ae1
@ -46,7 +46,7 @@ CONF = cfg.CONF
|
|||||||
POST_REQUIRED = set(['code'])
|
POST_REQUIRED = set(['code'])
|
||||||
CODE_SOURCE = set(['package', 'swift', 'image'])
|
CODE_SOURCE = set(['package', 'swift', 'image'])
|
||||||
UPDATE_ALLOWED = set(['name', 'description', 'code', 'package', 'entry',
|
UPDATE_ALLOWED = set(['name', 'description', 'code', 'package', 'entry',
|
||||||
'cpu', 'memory_size'])
|
'cpu', 'memory_size', 'timeout'])
|
||||||
|
|
||||||
|
|
||||||
class FunctionWorkerController(rest.RestController):
|
class FunctionWorkerController(rest.RestController):
|
||||||
@ -169,7 +169,10 @@ class FunctionsController(rest.RestController):
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check cpu and memory_size values.
|
common.validate_int_in_range(
|
||||||
|
'timeout', values['timeout'], CONF.resource_limits.min_timeout,
|
||||||
|
CONF.resource_limits.max_timeout
|
||||||
|
)
|
||||||
common.validate_int_in_range(
|
common.validate_int_in_range(
|
||||||
'cpu', values['cpu'], CONF.resource_limits.min_cpu,
|
'cpu', values['cpu'], CONF.resource_limits.min_cpu,
|
||||||
CONF.resource_limits.max_cpu
|
CONF.resource_limits.max_cpu
|
||||||
@ -364,7 +367,14 @@ class FunctionsController(rest.RestController):
|
|||||||
LOG.info('Update function %s, params: %s', id, values)
|
LOG.info('Update function %s, params: %s', id, values)
|
||||||
ctx = context.get_ctx()
|
ctx = context.get_ctx()
|
||||||
|
|
||||||
if set(values.keys()).issubset(set(['name', 'description'])):
|
if values.get('timeout'):
|
||||||
|
common.validate_int_in_range(
|
||||||
|
'timeout', values['timeout'], CONF.resource_limits.min_timeout,
|
||||||
|
CONF.resource_limits.max_timeout
|
||||||
|
)
|
||||||
|
|
||||||
|
db_update_only = set(['name', 'description', 'timeout'])
|
||||||
|
if set(values.keys()).issubset(db_update_only):
|
||||||
func_db = db_api.update_function(id, values)
|
func_db = db_api.update_function(id, values)
|
||||||
else:
|
else:
|
||||||
source = values.get('code', {}).get('source')
|
source = values.get('code', {}).get('source')
|
||||||
|
@ -158,11 +158,6 @@ class Link(Resource):
|
|||||||
target = wtypes.text
|
target = wtypes.text
|
||||||
rel = wtypes.text
|
rel = wtypes.text
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def sample(cls):
|
|
||||||
return cls(href='http://example.com/here',
|
|
||||||
target='here', rel='self')
|
|
||||||
|
|
||||||
|
|
||||||
class Function(Resource):
|
class Function(Resource):
|
||||||
id = wtypes.text
|
id = wtypes.text
|
||||||
@ -180,25 +175,6 @@ class Function(Resource):
|
|||||||
created_at = wtypes.text
|
created_at = wtypes.text
|
||||||
updated_at = wtypes.text
|
updated_at = wtypes.text
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def sample(cls):
|
|
||||||
return cls(
|
|
||||||
id='123e4567-e89b-12d3-a456-426655440000',
|
|
||||||
name='hello_world',
|
|
||||||
description='this is the first function.',
|
|
||||||
cpu=1,
|
|
||||||
memory_size=1,
|
|
||||||
timeout=1,
|
|
||||||
runtime_id='123e4567-e89b-12d3-a456-426655440001',
|
|
||||||
code={'zip': True},
|
|
||||||
entry='main',
|
|
||||||
count=10,
|
|
||||||
latest_version=0,
|
|
||||||
project_id='default',
|
|
||||||
created_at='1970-01-01T00:00:00.000000',
|
|
||||||
updated_at='1970-01-01T00:00:00.000000'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Functions(ResourceList):
|
class Functions(ResourceList):
|
||||||
functions = [Function]
|
functions = [Function]
|
||||||
@ -208,18 +184,6 @@ class Functions(ResourceList):
|
|||||||
|
|
||||||
super(Functions, self).__init__(**kwargs)
|
super(Functions, self).__init__(**kwargs)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def sample(cls):
|
|
||||||
sample = cls()
|
|
||||||
sample.functions = [Function.sample()]
|
|
||||||
sample.next = (
|
|
||||||
"http://localhost:7070/v1/functions?"
|
|
||||||
"sort_keys=id,name&sort_dirs=asc,desc&limit=10&"
|
|
||||||
"marker=123e4567-e89b-12d3-a456-426655440000"
|
|
||||||
)
|
|
||||||
|
|
||||||
return sample
|
|
||||||
|
|
||||||
|
|
||||||
class FunctionWorker(Resource):
|
class FunctionWorker(Resource):
|
||||||
function_id = wsme.wsattr(types.uuid, readonly=True)
|
function_id = wsme.wsattr(types.uuid, readonly=True)
|
||||||
|
@ -259,6 +259,16 @@ rlimits_opts = [
|
|||||||
default=5,
|
default=5,
|
||||||
help='Default function execution timeout(unit: seconds)'
|
help='Default function execution timeout(unit: seconds)'
|
||||||
),
|
),
|
||||||
|
cfg.IntOpt(
|
||||||
|
'min_timeout',
|
||||||
|
default=1,
|
||||||
|
help='Minimum function execution timeout(unit: seconds).'
|
||||||
|
),
|
||||||
|
cfg.IntOpt(
|
||||||
|
'max_timeout',
|
||||||
|
default=300,
|
||||||
|
help='Maximum function execution timeout(unit: seconds).'
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -58,7 +58,8 @@ class TestFunctionController(base.APITest):
|
|||||||
body.update(
|
body.update(
|
||||||
{
|
{
|
||||||
'entry': 'main.main',
|
'entry': 'main.main',
|
||||||
'code': {"source": "package", "md5sum": "fake_md5"}
|
'code': {"source": "package", "md5sum": "fake_md5"},
|
||||||
|
'timeout': cfg.CONF.resource_limits.default_timeout
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self._assertDictContainsSubset(resp.json, body)
|
self._assertDictContainsSubset(resp.json, body)
|
||||||
@ -82,6 +83,26 @@ class TestFunctionController(base.APITest):
|
|||||||
self.assertEqual(201, resp.status_int)
|
self.assertEqual(201, resp.status_int)
|
||||||
self.assertEqual(3, resp.json['timeout'])
|
self.assertEqual(3, resp.json['timeout'])
|
||||||
|
|
||||||
|
def test_post_timeout_invalid(self):
|
||||||
|
with tempfile.NamedTemporaryFile() as f:
|
||||||
|
body = {
|
||||||
|
'runtime_id': self.runtime_id,
|
||||||
|
'code': json.dumps({"source": "package"}),
|
||||||
|
'timeout': cfg.CONF.resource_limits.max_timeout + 1
|
||||||
|
}
|
||||||
|
resp = self.app.post(
|
||||||
|
'/v1/functions',
|
||||||
|
params=body,
|
||||||
|
upload_files=[('package', f.name, f.read())],
|
||||||
|
expect_errors=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(400, resp.status_int)
|
||||||
|
self.assertIn(
|
||||||
|
'timeout resource limitation not within the allowable range',
|
||||||
|
resp.json['faultstring']
|
||||||
|
)
|
||||||
|
|
||||||
@mock.patch("qinling.utils.openstack.keystone.create_trust")
|
@mock.patch("qinling.utils.openstack.keystone.create_trust")
|
||||||
@mock.patch('qinling.utils.openstack.keystone.get_swiftclient')
|
@mock.patch('qinling.utils.openstack.keystone.get_swiftclient')
|
||||||
@mock.patch('qinling.context.AuthHook.before')
|
@mock.patch('qinling.context.AuthHook.before')
|
||||||
@ -217,6 +238,32 @@ class TestFunctionController(base.APITest):
|
|||||||
self.assertEqual(200, resp.status_int)
|
self.assertEqual(200, resp.status_int)
|
||||||
self.assertEqual('new_name', resp.json['name'])
|
self.assertEqual('new_name', resp.json['name'])
|
||||||
|
|
||||||
|
def test_put_timeout(self):
|
||||||
|
db_func = self.create_function(runtime_id=self.runtime_id)
|
||||||
|
|
||||||
|
resp = self.app.put_json(
|
||||||
|
'/v1/functions/%s' % db_func.id, {'timeout': 10}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(200, resp.status_int)
|
||||||
|
self.assertEqual(10, resp.json['timeout'])
|
||||||
|
|
||||||
|
def test_put_timeout_invalid(self):
|
||||||
|
db_func = self.create_function(runtime_id=self.runtime_id)
|
||||||
|
|
||||||
|
# Check for type of cpu values.
|
||||||
|
resp = self.app.put_json(
|
||||||
|
'/v1/functions/%s' % db_func.id,
|
||||||
|
{'timeout': cfg.CONF.resource_limits.max_timeout + 1},
|
||||||
|
expect_errors=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(400, resp.status_int)
|
||||||
|
self.assertIn(
|
||||||
|
'timeout resource limitation not within the allowable range',
|
||||||
|
resp.json['faultstring']
|
||||||
|
)
|
||||||
|
|
||||||
@mock.patch('qinling.utils.etcd_util.delete_function')
|
@mock.patch('qinling.utils.etcd_util.delete_function')
|
||||||
@mock.patch('qinling.storage.file_system.FileSystemStorage.store')
|
@mock.patch('qinling.storage.file_system.FileSystemStorage.store')
|
||||||
@mock.patch('qinling.storage.file_system.FileSystemStorage.delete')
|
@mock.patch('qinling.storage.file_system.FileSystemStorage.delete')
|
||||||
|
@ -182,7 +182,7 @@ class DbTestCase(BaseTest):
|
|||||||
|
|
||||||
return runtime
|
return runtime
|
||||||
|
|
||||||
def create_function(self, runtime_id=None, code=None):
|
def create_function(self, runtime_id=None, code=None, timeout=None):
|
||||||
if not runtime_id:
|
if not runtime_id:
|
||||||
runtime_id = self.create_runtime().id
|
runtime_id = self.create_runtime().id
|
||||||
|
|
||||||
@ -197,7 +197,7 @@ class DbTestCase(BaseTest):
|
|||||||
'project_id': DEFAULT_PROJECT_ID,
|
'project_id': DEFAULT_PROJECT_ID,
|
||||||
'cpu': cfg.CONF.resource_limits.default_cpu,
|
'cpu': cfg.CONF.resource_limits.default_cpu,
|
||||||
'memory_size': cfg.CONF.resource_limits.default_memory,
|
'memory_size': cfg.CONF.resource_limits.default_memory,
|
||||||
'timeout': cfg.CONF.resource_limits.default_timeout,
|
'timeout': timeout or cfg.CONF.resource_limits.default_timeout
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -93,6 +93,12 @@ def generate_unicode_uuid(dashed=True):
|
|||||||
|
|
||||||
|
|
||||||
def validate_int_in_range(name, value, min_allowed, max_allowed):
|
def validate_int_in_range(name, value, min_allowed, max_allowed):
|
||||||
|
unit_mapping = {
|
||||||
|
"cpu": "millicpu",
|
||||||
|
"memory": "bytes",
|
||||||
|
"timeout": "seconds"
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
value_int = int(value)
|
value_int = int(value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@ -103,8 +109,8 @@ def validate_int_in_range(name, value, min_allowed, max_allowed):
|
|||||||
if (value_int < min_allowed or value_int > max_allowed):
|
if (value_int < min_allowed or value_int > max_allowed):
|
||||||
raise exc.InputException(
|
raise exc.InputException(
|
||||||
'%s resource limitation not within the allowable range: '
|
'%s resource limitation not within the allowable range: '
|
||||||
'%s ~ %s(%s).' % (name, min_allowed, max_allowed,
|
'%s ~ %s(%s).' %
|
||||||
'millicpu' if name == 'cpu' else 'bytes')
|
(name, min_allowed, max_allowed, unit_mapping[name])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -506,7 +506,27 @@ class ExecutionsTest(base.BaseQinlingTest):
|
|||||||
|
|
||||||
result = jsonutils.loads(body['result'])
|
result = jsonutils.loads(body['result'])
|
||||||
|
|
||||||
self.assertGreater(result['duration'], 5)
|
self.assertGreaterEqual(result['duration'], 5)
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
'Function execution timeout', result['output']
|
'Function execution timeout', result['output']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Update function timeout
|
||||||
|
resp, _ = self.client.update_function(
|
||||||
|
function_id,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
self.assertEqual(200, resp.status_code)
|
||||||
|
|
||||||
|
resp, body = self.client.create_execution(
|
||||||
|
function_id,
|
||||||
|
input='{"seconds": 7}'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(201, resp.status)
|
||||||
|
self.addCleanup(self.client.delete_resource, 'executions',
|
||||||
|
body['id'], ignore_notfound=True)
|
||||||
|
self.assertEqual('success', body['status'])
|
||||||
|
|
||||||
|
result = jsonutils.loads(body['result'])
|
||||||
|
self.assertGreaterEqual(result['duration'], 7)
|
||||||
|
Loading…
Reference in New Issue
Block a user