diff --git a/src/python/OBis/obis/dm/checksum.py b/src/python/OBis/obis/dm/checksum.py index b10fba4225a2f00e13290bb5bd333bde4b6c5a9e..45a0bc7c829f8b08ea6b26d875ee1029241cb586 100644 --- a/src/python/OBis/obis/dm/checksum.py +++ b/src/python/OBis/obis/dm/checksum.py @@ -3,7 +3,7 @@ import json import os from abc import ABC, abstractmethod from .utils import run_shell -from .command_result import CommandException +from .command_result import CommandResult, CommandException def get_checksum_generator(checksum_type, default=None): @@ -19,6 +19,27 @@ def get_checksum_generator(checksum_type, default=None): return None +def validate_checksum(openbis, files, data_set_id, folder): + dataset_files = openbis.search_files(data_set_id)['objects'] + dataset_files_by_path = {} + for dataset_file in dataset_files: + dataset_files_by_path[dataset_file['path']] = dataset_file + for filename in files: + dataset_file = dataset_files_by_path[filename] + filename_dest = os.path.join(folder, filename) + checksum_generator = None + if dataset_file['checksumCRC32'] is not None and dataset_file['checksumCRC32'] > 0: + checksum_generator = ChecksumGeneratorCrc32() + expected_checksum = dataset_file['checksumCRC32'] + elif dataset_file['checksumType'] is not None: + checksum_generator = get_checksum_generator(dataset_file['checksumType']) + expected_checksum = dataset_file['checksum'] + if checksum_generator is not None: + checksum = checksum_generator.get_checksum(filename_dest)['checksum'] + if checksum != expected_checksum: + raise CommandException(CommandResult(returncode=-1, output="Checksum wrong for file {}. Expected {} but was {}.".format(filename_dest, expected_checksum, checksum))) + + class ChecksumGeneratorCrc32(object): def get_checksum(self, file): result = run_shell(['cksum', file]) diff --git a/src/python/OBis/obis/dm/commands/clone.py b/src/python/OBis/obis/dm/commands/clone.py index 103a3f5a88ebe5a6e5a2a429bf36443618cd91ae..7331d048903d849eeb6b1d0ed2d99c609006d964 100644 --- a/src/python/OBis/obis/dm/commands/clone.py +++ b/src/python/OBis/obis/dm/commands/clone.py @@ -2,6 +2,7 @@ import socket import os import pybis from .openbis_command import OpenbisCommand, ContentCopySelector +from ..checksum import validate_checksum from ..command_result import CommandResult from ..utils import cd from ..utils import run_shell @@ -54,6 +55,8 @@ class Clone(OpenbisCommand): result = self.checkout_commit(content_copy, path) if result.failure(): return result + data_set = self.openbis.get_dataset(self.data_set_id) + validate_checksum(self.openbis, data_set.file_list, data_set.permId, repository_folder) return self.add_content_copy_to_openbis(repository_folder) diff --git a/src/python/OBis/obis/dm/commands/download.py b/src/python/OBis/obis/dm/commands/download.py index 131c204af89cb94d10d1a5b889475f8b02453579..36a1c1906ad4aeb9af4fe1c5d55835cf3a5391fd 100644 --- a/src/python/OBis/obis/dm/commands/download.py +++ b/src/python/OBis/obis/dm/commands/download.py @@ -2,7 +2,7 @@ import os import pybis from .openbis_command import OpenbisCommand, ContentCopySelector from ..command_result import CommandResult -from ..checksum import get_checksum_generator, ChecksumGeneratorCrc32 +from ..checksum import validate_checksum class Download(OpenbisCommand): """ @@ -29,29 +29,5 @@ class Download(OpenbisCommand): content_copy_index = ContentCopySelector(data_set, self.content_copy_index, get_index=True).select() files = [self.file] if self.file is not None else data_set.file_list destination = data_set.download(files, linked_dataset_fileservice_url=self.fileservice_url(), content_copy_index=content_copy_index) - result = self._validate_checksum(files, data_set, destination) - if result.failure(): - return result + validate_checksum(self.openbis, files, data_set.permId, os.path.join(destination, data_set.permId)) return CommandResult(returncode=0, output="Files downloaded to: %s" % os.path.join(destination, data_set.permId)) - - - def _validate_checksum(self, files, data_set, destination): - dataset_files = self.openbis.search_files(data_set.permId)['objects'] - dataset_files_by_path = {} - for dataset_file in dataset_files: - dataset_files_by_path[dataset_file['path']] = dataset_file - for filename in files: - dataset_file = dataset_files_by_path[filename] - filename_dest = os.path.join(destination, data_set.permId, filename) - checksum_generator = None - if dataset_file['checksumCRC32'] is not None and dataset_file['checksumCRC32'] > 0: - checksum_generator = ChecksumGeneratorCrc32() - expected_checksum = dataset_file['checksumCRC32'] - elif dataset_file['checksumType'] is not None: - checksum_generator = get_checksum_generator(dataset_file['checksumType']) - expected_checksum = dataset_file['checksum'] - if checksum_generator is not None: - checksum = checksum_generator.get_checksum(filename_dest)['checksum'] - if checksum != expected_checksum: - return CommandResult(returncode=-1, output="Checksum wrong for file {}. Expected {} but was {}.".format(filename_dest, expected_checksum, checksum)) - return CommandResult(returncode=0, output="") diff --git a/src/python/OBis/obis/dm/data_mgmt.py b/src/python/OBis/obis/dm/data_mgmt.py index 711869b41abeafbbc36ca0ad26eabaa2426e74b5..3bf2a5d2455b40528e82b12ace1b0327189a67b0 100644 --- a/src/python/OBis/obis/dm/data_mgmt.py +++ b/src/python/OBis/obis/dm/data_mgmt.py @@ -34,7 +34,7 @@ from ..scripts import cli # noinspection PyPep8Naming -def DataMgmt(echo_func=None, settings_resolver=None, openbis_config={}, git_config={}, openbis=None): +def DataMgmt(echo_func=None, settings_resolver=None, openbis_config={}, git_config={}, openbis=None, debug=False): """Factory method for DataMgmt instances""" echo_func = echo_func if echo_func is not None else default_echo @@ -51,7 +51,7 @@ def DataMgmt(echo_func=None, settings_resolver=None, openbis_config={}, git_conf settings_resolver.set_resolver_location_roots('data_set', result.output) complete_openbis_config(openbis_config, settings_resolver) - return GitDataMgmt(settings_resolver, openbis_config, git_wrapper, openbis) + return GitDataMgmt(settings_resolver, openbis_config, git_wrapper, openbis, debug) class AbstractDataMgmt(metaclass=abc.ABCMeta): @@ -60,11 +60,12 @@ class AbstractDataMgmt(metaclass=abc.ABCMeta): All operations throw an exepction if they fail. """ - def __init__(self, settings_resolver, openbis_config, git_wrapper, openbis): + def __init__(self, settings_resolver, openbis_config, git_wrapper, openbis, debug=False): self.settings_resolver = settings_resolver self.openbis_config = openbis_config self.git_wrapper = git_wrapper self.openbis = openbis + self.debug = debug def error_raise(self, command, reason): """Raise an exception.""" @@ -194,6 +195,8 @@ def with_restore(f): return result except Exception as e: self.restore() + if self.debug == True: + raise e return CommandResult(returncode=-1, output="Error: " + str(e)) return f_with_restore diff --git a/src/python/OBis/obis/scripts/cli.py b/src/python/OBis/obis/scripts/cli.py index f1df55e16901b2e8d8a795d5dfb068a23c276504..58744918390189f38d22cbc4c678432966039c19 100644 --- a/src/python/OBis/obis/scripts/cli.py +++ b/src/python/OBis/obis/scripts/cli.py @@ -49,7 +49,7 @@ def shared_data_mgmt(context={}): openbis_config = {} if context.get('verify_certificates') is not None: openbis_config['verify_certificates'] = context['verify_certificates'] - return dm.DataMgmt(openbis_config=openbis_config, git_config=git_config) + return dm.DataMgmt(openbis_config=openbis_config, git_config=git_config, debug=context['debug']) def check_result(command, result): @@ -60,23 +60,27 @@ def check_result(command, result): return result.returncode -def run(function): +def run(ctx, function): try: return function() except CommandException as e: return e.command_result except Exception as e: + if ctx.obj['debug'] == True: + raise e return CommandResult(returncode=-1, output="Error: " + str(e)) @click.group() @click.option('-q', '--quiet', default=False, is_flag=True, help='Suppress status reporting.') @click.option('-s', '--skip_verification', default=False, is_flag=True, help='Do not verify cerficiates') +@click.option('-d', '--debug', default=False, is_flag=True, help="Show stack trace on error.") @click.pass_context -def cli(ctx, quiet, skip_verification): +def cli(ctx, quiet, skip_verification, debug): ctx.obj['quiet'] = quiet if skip_verification: ctx.obj['verify_certificates'] = False + ctx.obj['debug'] = debug def set_property(data_mgmt, resolver, prop, value, is_global, is_data_set_property=False): @@ -88,6 +92,8 @@ def set_property(data_mgmt, resolver, prop, value, is_global, is_data_set_proper else: resolver.set_value_for_parameter(prop, value, loc) except ValueError as e: + if data_mgmt.debug == True: + raise e return CommandResult(returncode=-1, output="Error: " + str(e)) if not is_global: return data_mgmt.commit_metadata_updates(prop) @@ -102,7 +108,7 @@ def init_data_impl(ctx, object_id, collection_id, repository, desc): click_echo("init_data {}".format(repository)) data_mgmt = shared_data_mgmt(ctx.obj) desc = desc if desc != "" else None - result = run(lambda: data_mgmt.init_data(repository, desc, create=True)) + result = run(ctx, lambda: data_mgmt.init_data(repository, desc, create=True)) init_handle_cleanup(result, object_id, collection_id, repository, data_mgmt) @@ -110,7 +116,7 @@ def init_analysis_impl(ctx, parent, object_id, collection_id, repository, descri click_echo("init_analysis {}".format(repository)) data_mgmt = shared_data_mgmt(ctx.obj) description = description if description != "" else None - result = run(lambda: data_mgmt.init_analysis(repository, parent, description, create=True)) + result = run(ctx, lambda: data_mgmt.init_analysis(repository, parent, description, create=True)) init_handle_cleanup(result, object_id, collection_id, repository, data_mgmt) @@ -327,21 +333,21 @@ def repository(ctx, is_global): @click.argument('settings', type=SettingsSet(), nargs=-1) @click.pass_context def repository_set(ctx, settings): - return check_result("repository_set", run(lambda: _set(ctx, settings))) + return check_result("repository_set", run(ctx, lambda: _set(ctx, settings))) @repository.command('get') @click.argument('settings', type=SettingsGet(), nargs=-1) @click.pass_context def repository_get(ctx, settings): - return check_result("repository_get", run(lambda: _get(ctx, settings))) + return check_result("repository_get", run(ctx, lambda: _get(ctx, settings))) @repository.command('clear') @click.argument('settings', type=SettingsClear(), nargs=-1) @click.pass_context def repository_clear(ctx, settings): - return check_result("repository_clear", run(lambda: _clear(ctx, settings))) + return check_result("repository_clear", run(ctx, lambda: _clear(ctx, settings))) ## data_set: type, properties @@ -364,21 +370,21 @@ def data_set(ctx, is_global, is_data_set_property): @click.argument('settings', type=SettingsSet(), nargs=-1) @click.pass_context def data_set_set(ctx, settings): - return check_result("data_set_set", run(lambda: _set(ctx, settings))) + return check_result("data_set_set", run(ctx, lambda: _set(ctx, settings))) @data_set.command('get') @click.argument('settings', type=SettingsGet(), nargs=-1) @click.pass_context def data_set_get(ctx, settings): - return check_result("data_set_get", run(lambda: _get(ctx, settings))) + return check_result("data_set_get", run(ctx, lambda: _get(ctx, settings))) @data_set.command('clear') @click.argument('settings', type=SettingsClear(), nargs=-1) @click.pass_context def data_set_clear(ctx, settings): - return check_result("data_set_clear", run(lambda: _clear(ctx, settings))) + return check_result("data_set_clear", run(ctx, lambda: _clear(ctx, settings))) ## object: object_id @@ -399,21 +405,21 @@ def object(ctx, is_global): @click.argument('settings', type=SettingsSet(), nargs=-1) @click.pass_context def object_set(ctx, settings): - return check_result("object_set", run(lambda: _set(ctx, settings))) + return check_result("object_set", run(ctx, lambda: _set(ctx, settings))) @object.command('get') @click.argument('settings', type=SettingsGet(), nargs=-1) @click.pass_context def object_get(ctx, settings): - return check_result("object_get", run(lambda: _get(ctx, settings))) + return check_result("object_get", run(ctx, lambda: _get(ctx, settings))) @object.command('clear') @click.argument('settings', type=SettingsClear(), nargs=-1) @click.pass_context def object_clear(ctx, settings): - return check_result("object_clear", run(lambda: _clear(ctx, settings))) + return check_result("object_clear", run(ctx, lambda: _clear(ctx, settings))) ## collection: collection_id @@ -434,21 +440,21 @@ def collection(ctx, is_global): @click.argument('settings', type=SettingsSet(), nargs=-1) @click.pass_context def collection_set(ctx, settings): - return check_result("collection_set", run(lambda: _set(ctx, settings))) + return check_result("collection_set", run(ctx, lambda: _set(ctx, settings))) @collection.command('get') @click.argument('settings', type=SettingsGet(), nargs=-1) @click.pass_context def collection_get(ctx, settings): - return check_result("collection_get", run(lambda: _get(ctx, settings))) + return check_result("collection_get", run(ctx, lambda: _get(ctx, settings))) @collection.command('clear') @click.argument('settings', type=SettingsClear(), nargs=-1) @click.pass_context def collection_clear(ctx, settings): - return check_result("collection_clear", run(lambda: _clear(ctx, settings))) + return check_result("collection_clear", run(ctx, lambda: _clear(ctx, settings))) ## config: fileservice_url, git_annex_hash_as_checksum, hostname, openbis_url, user, verify_certificates @@ -469,21 +475,21 @@ def config(ctx, is_global): @click.argument('settings', type=SettingsSet(), nargs=-1) @click.pass_context def config_set(ctx, settings): - return check_result("config_set", run(lambda: _set(ctx, settings))) + return check_result("config_set", run(ctx, lambda: _set(ctx, settings))) @config.command('get') @click.argument('settings', type=SettingsGet(), nargs=-1) @click.pass_context def config_get(ctx, settings): - return check_result("config_get", run(lambda: _get(ctx, settings))) + return check_result("config_get", run(ctx, lambda: _get(ctx, settings))) @config.command('clear') @click.argument('settings', type=SettingsClear(), nargs=-1) @click.pass_context def config_clear(ctx, settings): - return check_result("config_clear", run(lambda: _clear(ctx, settings))) + return check_result("config_clear", run(ctx, lambda: _clear(ctx, settings))) # repository commands: status, sync, commit, init, addref, removeref, init_analysis @@ -499,7 +505,7 @@ _commit_params = [ def _repository_commit(ctx, msg, auto_add, ignore_missing_parent): data_mgmt = shared_data_mgmt(ctx.obj) - return check_result("commit", run(lambda: data_mgmt.commit(msg, auto_add, ignore_missing_parent))) + return check_result("commit", run(ctx, lambda: data_mgmt.commit(msg, auto_add, ignore_missing_parent))) @repository.command("commit") @click.pass_context @@ -572,7 +578,7 @@ _status_params = [ def _repository_status(ctx): data_mgmt = shared_data_mgmt(ctx.obj) - result = run(data_mgmt.status) + result = run(ctx, data_mgmt.status) click.echo(result.output) @repository.command("status") @@ -603,7 +609,7 @@ _sync_params = [ def _repository_sync(ctx, ignore_missing_parent): data_mgmt = shared_data_mgmt(ctx.obj) - return check_result("sync", run(lambda: data_mgmt.sync(ignore_missing_parent))) + return check_result("sync", run(ctx, lambda: data_mgmt.sync(ignore_missing_parent))) @repository.command("sync") @click.pass_context @@ -632,7 +638,7 @@ _addref_params = [ def _repository_addref(ctx): data_mgmt = shared_data_mgmt(ctx.obj) - return check_result("addref", run(data_mgmt.addref)) + return check_result("addref", run(ctx, data_mgmt.addref)) @repository.command("addref") @click.pass_context @@ -661,7 +667,7 @@ _removeref_params = [ def _repository_removeref(ctx): data_mgmt = shared_data_mgmt(ctx.obj) - return check_result("addref", run(data_mgmt.removeref)) + return check_result("addref", run(ctx, data_mgmt.removeref)) @repository.command("removeref") @click.pass_context @@ -687,6 +693,8 @@ def removeref(ctx, repository): ## download +# TODO --skip_integrity_check flag for download, clone and move + _download_params = [ click.option('-c', '--content_copy_index', type=int, default=None, help='Index of the content copy to download from.'), click.option('-f', '--file', help='File in the data set to download - downloading all if not given.'), @@ -700,7 +708,7 @@ def data_set_download(ctx, content_copy_index, file, data_set_id): """ Download files of a linked data set. """ data_mgmt = shared_data_mgmt(ctx.obj) - return check_result("download", run(lambda: data_mgmt.download(data_set_id, content_copy_index, file))) + return check_result("download", run(ctx, lambda: data_mgmt.download(data_set_id, content_copy_index, file))) @cli.command() @add_params(_download_params) @@ -725,7 +733,7 @@ def data_set_clone(ctx, ssh_user, content_copy_index, data_set_id): """Clone the repository found in the given data set id. """ data_mgmt = shared_data_mgmt(ctx.obj) - return check_result("clone", run(lambda: data_mgmt.clone(data_set_id, ssh_user, content_copy_index))) + return check_result("clone", run(ctx, lambda: data_mgmt.clone(data_set_id, ssh_user, content_copy_index))) @cli.command() @click.pass_context