Fix package function update with same md5

Do not update or delete the function package if updating the function
with the same package but not providing the md5.

Change-Id: Iea24978106984111b61b0b19336cf1eb9ded9ed6
Story: 2003482
Task: 24749
This commit is contained in:
Lingxian Kong 2018-08-29 02:04:27 +12:00
parent 1448ac031a
commit cd88b217ca
5 changed files with 71 additions and 17 deletions

View File

@ -224,7 +224,7 @@ class FunctionsController(rest.RestController):
if store: if store:
try: try:
ctx = context.get_ctx() ctx = context.get_ctx()
actual_md5 = self.storage_provider.store( _, actual_md5 = self.storage_provider.store(
ctx.projectid, func_db.id, data, md5sum=md5sum ctx.projectid, func_db.id, data, md5sum=md5sum
) )
values['code'].update({"md5sum": actual_md5}) values['code'].update({"md5sum": actual_md5})
@ -394,7 +394,7 @@ class FunctionsController(rest.RestController):
# Package type function. 'code' and 'entry' make sense only if # Package type function. 'code' and 'entry' make sense only if
# 'package' is provided # 'package' is provided
update_package = False package_updated = False
if (pre_source == constants.PACKAGE_FUNCTION and if (pre_source == constants.PACKAGE_FUNCTION and
values.get('package') is not None): values.get('package') is not None):
if md5sum and md5sum == pre_md5sum: if md5sum and md5sum == pre_md5sum:
@ -404,7 +404,7 @@ class FunctionsController(rest.RestController):
# Update the package data. # Update the package data.
data = values['package'].file.read() data = values['package'].file.read()
md5sum = self.storage_provider.store( package_updated, md5sum = self.storage_provider.store(
ctx.projectid, ctx.projectid,
id, id,
data, data,
@ -414,7 +414,6 @@ class FunctionsController(rest.RestController):
{"md5sum": md5sum, "source": pre_source} {"md5sum": md5sum, "source": pre_source}
) )
values.pop('package') values.pop('package')
update_package = True
# Swift type function # Swift type function
if pre_source == constants.SWIFT_FUNCTION: if pre_source == constants.SWIFT_FUNCTION:
@ -429,7 +428,7 @@ class FunctionsController(rest.RestController):
func_db = db_api.update_function(id, values) func_db = db_api.update_function(id, values)
# Delete the old function package if needed # Delete the old function package if needed
if update_package: if package_updated:
self.storage_provider.delete(ctx.projectid, id, pre_md5sum) self.storage_provider.delete(ctx.projectid, id, pre_md5sum)
pecan.response.status = 200 pecan.response.status = 200

View File

@ -35,7 +35,7 @@ class PackageStorage(object):
:param data: Package file content. :param data: Package file content.
:param kwargs: A dict may including :param kwargs: A dict may including
- md5sum: The MD5 provided by the user. - md5sum: The MD5 provided by the user.
:return: MD5 value of the package. :return: A tuple (if the package is updated, MD5 value of the package)
""" """
raise NotImplementedError raise NotImplementedError

View File

@ -44,7 +44,7 @@ class FileSystemStorage(base.PackageStorage):
:param function: Function ID. :param function: Function ID.
:param data: Package file content. :param data: Package file content.
:param md5sum: The MD5 provided by the user. :param md5sum: The MD5 provided by the user.
:return: MD5 value of the package. :return: A tuple (if the package is updated, MD5 value of the package)
""" """
LOG.debug( LOG.debug(
'Store package, function: %s, project: %s', function, project_id 'Store package, function: %s, project: %s', function, project_id
@ -58,12 +58,15 @@ class FileSystemStorage(base.PackageStorage):
if md5sum and md5_actual != md5sum: if md5sum and md5_actual != md5sum:
raise exc.InputException("Package md5 mismatch.") raise exc.InputException("Package md5 mismatch.")
# The md5 is contained in the package path. func_zip = os.path.join(
new_func_zip = os.path.join(project_path, '%s.zip.new' % function) project_path,
func_zip = os.path.join(project_path, PACKAGE_NAME_TEMPLATE % (function, md5_actual)
PACKAGE_NAME_TEMPLATE % (function, md5_actual)) )
if os.path.exists(func_zip):
return False, md5_actual
# Store package # Save package
new_func_zip = os.path.join(project_path, '%s.zip.new' % function)
with open(new_func_zip, 'wb') as fd: with open(new_func_zip, 'wb') as fd:
fd.write(data) fd.write(data)
@ -72,7 +75,8 @@ class FileSystemStorage(base.PackageStorage):
raise exc.InputException("Package is not a valid ZIP package.") raise exc.InputException("Package is not a valid ZIP package.")
os.rename(new_func_zip, func_zip) os.rename(new_func_zip, func_zip)
return md5_actual
return True, md5_actual
def retrieve(self, project_id, function, md5sum, version=0): def retrieve(self, project_id, function, md5sum, version=0):
"""Get function package data. """Get function package data.

View File

@ -38,7 +38,7 @@ class TestFunctionController(base.APITest):
@mock.patch('qinling.storage.file_system.FileSystemStorage.store') @mock.patch('qinling.storage.file_system.FileSystemStorage.store')
def test_post(self, mock_store): def test_post(self, mock_store):
mock_store.return_value = 'fake_md5' mock_store.return_value = (True, 'fake_md5')
with tempfile.NamedTemporaryFile() as f: with tempfile.NamedTemporaryFile() as f:
body = { body = {
@ -184,7 +184,7 @@ class TestFunctionController(base.APITest):
def test_put_package(self, mock_delete_func, mock_delete, mock_store, def test_put_package(self, mock_delete_func, mock_delete, mock_store,
mock_etcd_del): mock_etcd_del):
db_func = self.create_function(runtime_id=self.runtime_id) db_func = self.create_function(runtime_id=self.runtime_id)
mock_store.return_value = "fake_md5_changed" mock_store.return_value = (True, "fake_md5_changed")
with tempfile.NamedTemporaryFile() as f: with tempfile.NamedTemporaryFile() as f:
resp = self.app.put( resp = self.app.put(
@ -202,7 +202,30 @@ class TestFunctionController(base.APITest):
mock_delete.assert_called_once_with(unit_base.DEFAULT_PROJECT_ID, mock_delete.assert_called_once_with(unit_base.DEFAULT_PROJECT_ID,
db_func.id, "fake_md5") db_func.id, "fake_md5")
def test_put_package_same_md5(self): @mock.patch('qinling.storage.file_system.FileSystemStorage.store')
@mock.patch('qinling.rpc.EngineClient.delete_function')
@mock.patch('qinling.utils.etcd_util.delete_function')
@mock.patch('qinling.storage.file_system.FileSystemStorage.delete')
def test_put_package_md5_not_change(self, file_delete_mock,
etcd_delete_mock, function_delete_mock,
store_mock):
db_func = self.create_function(runtime_id=self.runtime_id)
store_mock.return_value = (False, "fake_md5")
with tempfile.NamedTemporaryFile() as f:
resp = self.app.put(
'/v1/functions/%s' % db_func.id,
params={},
upload_files=[('package', f.name, f.read())]
)
self.assertEqual(200, resp.status_int)
self.assertEqual('fake_md5', resp.json['code'].get('md5sum'))
function_delete_mock.assert_called_once_with(db_func.id)
etcd_delete_mock.assert_called_once_with(db_func.id)
self.assertFalse(file_delete_mock.called)
def test_put_package_same_md5_provided(self):
db_func = self.create_function(runtime_id=self.runtime_id) db_func = self.create_function(runtime_id=self.runtime_id)
with tempfile.NamedTemporaryFile() as f: with tempfile.NamedTemporaryFile() as f:

View File

@ -49,7 +49,12 @@ class TestFileSystemStorage(base.BaseTest):
function_data = "Some data".encode('utf8') function_data = "Some data".encode('utf8')
md5 = common.md5(content=function_data) md5 = common.md5(content=function_data)
self.storage.store(self.project_id, function, function_data) package_updated, ret_md5 = self.storage.store(
self.project_id, function, function_data
)
self.assertTrue(package_updated)
self.assertEqual(md5, ret_md5)
temp_package_path = os.path.join(FAKE_STORAGE_PATH, self.project_id, temp_package_path = os.path.join(FAKE_STORAGE_PATH, self.project_id,
'%s.zip.new' % function) '%s.zip.new' % function)
@ -65,6 +70,29 @@ class TestFileSystemStorage(base.BaseTest):
is_zipfile_mock.assert_called_once_with(temp_package_path) is_zipfile_mock.assert_called_once_with(temp_package_path)
rename_mock.assert_called_once_with(temp_package_path, package_path) rename_mock.assert_called_once_with(temp_package_path, package_path)
@mock.patch('oslo_utils.fileutils.ensure_tree')
@mock.patch('os.path.exists')
def test_store_zip_exists(self, exists_mock, ensure_tree_mock):
function = self.rand_name('function', prefix='TestFileSystemStorage')
function_data = "Some data".encode('utf8')
md5 = common.md5(content=function_data)
exists_mock.return_value = True
package_updated, ret_md5 = self.storage.store(
self.project_id, function, function_data
)
self.assertFalse(package_updated)
self.assertEqual(md5, ret_md5)
package_path = os.path.join(
FAKE_STORAGE_PATH,
file_system.PACKAGE_PATH_TEMPLATE % (self.project_id, function,
md5)
)
exists_mock.assert_called_once_with(package_path)
@mock.patch('oslo_utils.fileutils.ensure_tree') @mock.patch('oslo_utils.fileutils.ensure_tree')
def test_store_md5_mismatch(self, ensure_tree_mock): def test_store_md5_mismatch(self, ensure_tree_mock):
function = self.rand_name('function', prefix='TestFileSystemStorage') function = self.rand_name('function', prefix='TestFileSystemStorage')