Support to update function timeout

Change-Id: I8c7c364af54a6e33406e8bece8337b1e7eeb75aa
Story: 2002174
Task: 26341
This commit is contained in:
Lingxian Kong 2018-09-08 01:25:46 +12:00
parent cd688170da
commit dd169d2ae1
7 changed files with 102 additions and 45 deletions

View File

@ -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')

View File

@ -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)

View File

@ -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).'
),
] ]

View File

@ -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')

View File

@ -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
} }
) )

View File

@ -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])
) )

View File

@ -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)