diff --git a/qinling/api/controllers/v1/function.py b/qinling/api/controllers/v1/function.py index 993ff2e4..88d769ae 100644 --- a/qinling/api/controllers/v1/function.py +++ b/qinling/api/controllers/v1/function.py @@ -201,8 +201,15 @@ class FunctionsController(rest.RestController): data = kwargs['package'].file.read() elif source == constants.SWIFT_FUNCTION: swift_info = values['code'].get('swift', {}) - self._check_swift(swift_info.get('container'), - swift_info.get('object')) + + if not (swift_info.get('container') and swift_info.get('object')): + raise exc.InputException("Both container and object must be " + "provided for swift type function.") + + self._check_swift( + swift_info.get('container'), + swift_info.get('object') + ) else: create_trust = False values['entry'] = None @@ -341,11 +348,15 @@ class FunctionsController(rest.RestController): when function is updating. """ values = {} - for key in UPDATE_ALLOWED: - if kwargs.get(key) is not None: - if key == "code": - kwargs[key] = json.loads(kwargs[key]) - values.update({key: kwargs[key]}) + + try: + for key in UPDATE_ALLOWED: + if kwargs.get(key) is not None: + if key == "code": + kwargs[key] = json.loads(kwargs[key]) + values.update({key: kwargs[key]}) + except Exception as e: + raise exc.InputException("Invalid input, %s" % str(e)) LOG.info('Update function %s, params: %s', id, values) ctx = context.get_ctx() @@ -416,10 +427,29 @@ class FunctionsController(rest.RestController): values.pop('package') # Swift type function - if pre_source == constants.SWIFT_FUNCTION: - swift_info = values['code'].get('swift', {}) - self._check_swift(swift_info.get('container'), - swift_info.get('object')) + if (pre_source == constants.SWIFT_FUNCTION and + "swift" in values.get('code', {})): + swift_info = values['code']["swift"] + + if not (swift_info.get('container') or + swift_info.get('object')): + raise exc.InputException( + "Either container or object must be provided for " + "swift type function update." + ) + + new_swift_info = pre_func.code['swift'] + new_swift_info.update(swift_info) + + self._check_swift( + new_swift_info.get('container'), + new_swift_info.get('object') + ) + + values['code'] = { + "source": pre_source, + "swift": new_swift_info + } # Delete allocated resources in orchestrator and etcd. self.engine_client.delete_function(id) diff --git a/qinling/tests/unit/api/controllers/v1/test_function.py b/qinling/tests/unit/api/controllers/v1/test_function.py index d0a32a56..da43a696 100644 --- a/qinling/tests/unit/api/controllers/v1/test_function.py +++ b/qinling/tests/unit/api/controllers/v1/test_function.py @@ -102,6 +102,25 @@ class TestFunctionController(base.APITest): ) self._assertDictContainsSubset(resp.json, body) + def test_post_swift_not_enough_params(self): + body = { + 'name': 'swift_function', + 'code': json.dumps( + { + "source": "swift", + "swift": {"container": "fake-container"} + } + ), + 'runtime_id': self.runtime_id, + } + resp = self.app.post( + '/v1/functions', + params=body, + expect_errors=True + ) + + self.assertEqual(400, resp.status_int) + @mock.patch('qinling.utils.openstack.keystone.get_swiftclient') @mock.patch('qinling.context.AuthHook.before') def test_post_swift_size_exceed(self, mock_auth, mock_client): @@ -242,6 +261,78 @@ class TestFunctionController(base.APITest): self.assertEqual(400, resp.status_int) + @mock.patch('qinling.rpc.EngineClient.delete_function') + @mock.patch('qinling.utils.etcd_util.delete_function') + @mock.patch('qinling.utils.openstack.swift.check_object') + @mock.patch('qinling.context.AuthHook.before') + def test_put_swift_function(self, mock_auth, mock_check, mock_etcd_delete, + mock_func_delete): + self.override_config('auth_enable', True, group='pecan') + mock_check.return_value = True + + db_func = self.create_function( + runtime_id=self.runtime_id, + code={ + "source": "swift", + "swift": {"container": "fake-container", "object": "fake-obj"} + } + ) + + body = { + 'code': json.dumps( + { + "source": "swift", + "swift": {"object": "new-obj"} + } + ), + } + resp = self.app.put_json('/v1/functions/%s' % db_func.id, body) + + self.assertEqual(200, resp.status_int) + swift_info = { + 'code': { + "source": "swift", + "swift": {"container": "fake-container", "object": "new-obj"} + } + } + self._assertDictContainsSubset(resp.json, swift_info) + + @mock.patch('qinling.rpc.EngineClient.delete_function') + @mock.patch('qinling.utils.etcd_util.delete_function') + @mock.patch('qinling.utils.openstack.swift.check_object') + @mock.patch('qinling.context.AuthHook.before') + def test_put_swift_function_without_source(self, mock_auth, mock_check, + mock_etcd_delete, + mock_func_delete): + self.override_config('auth_enable', True, group='pecan') + mock_check.return_value = True + + db_func = self.create_function( + runtime_id=self.runtime_id, + code={ + "source": "swift", + "swift": {"container": "fake-container", "object": "fake-obj"} + } + ) + + body = { + 'code': json.dumps( + { + "swift": {"object": "new-obj"} + } + ), + } + resp = self.app.put_json('/v1/functions/%s' % db_func.id, body) + + self.assertEqual(200, resp.status_int) + swift_info = { + 'code': { + "source": "swift", + "swift": {"container": "fake-container", "object": "new-obj"} + } + } + self._assertDictContainsSubset(resp.json, swift_info) + def test_put_cpu_with_type_error(self): db_func = self.create_function(runtime_id=self.runtime_id) diff --git a/qinling/tests/unit/base.py b/qinling/tests/unit/base.py index 4e92c1cd..6ac445e2 100644 --- a/qinling/tests/unit/base.py +++ b/qinling/tests/unit/base.py @@ -182,7 +182,7 @@ class DbTestCase(BaseTest): return runtime - def create_function(self, runtime_id=None): + def create_function(self, runtime_id=None, code=None): if not runtime_id: runtime_id = self.create_runtime().id @@ -190,7 +190,7 @@ class DbTestCase(BaseTest): { 'name': self.rand_name('function', prefix=self.prefix), 'runtime_id': runtime_id, - 'code': {"source": "package", "md5sum": "fake_md5"}, + 'code': code or {"source": "package", "md5sum": "fake_md5"}, 'entry': 'main.main', # 'auth_enable' is disabled by default, we create runtime for # default tenant.