X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/822ba3d61d93ff19c41861ddf1be2fcca20765fb..7bdffdeb9ccec113d1d9b848423be60d85a501ed:/sdk/python/arvados/commands/arv_copy.py diff --git a/sdk/python/arvados/commands/arv_copy.py b/sdk/python/arvados/commands/arv_copy.py index 64523b4750..7f5245db86 100755 --- a/sdk/python/arvados/commands/arv_copy.py +++ b/sdk/python/arvados/commands/arv_copy.py @@ -146,30 +146,39 @@ def main(): # Identify the kind of object we have been given, and begin copying. t = uuid_type(src_arv, args.object_uuid) - if t == 'Collection': - set_src_owner_uuid(src_arv.collections(), args.object_uuid, args) - result = copy_collection(args.object_uuid, - src_arv, dst_arv, - args) - elif t == 'Workflow': - set_src_owner_uuid(src_arv.workflows(), args.object_uuid, args) - result = copy_workflow(args.object_uuid, src_arv, dst_arv, args) - elif t == 'Group': - set_src_owner_uuid(src_arv.groups(), args.object_uuid, args) - result = copy_project(args.object_uuid, src_arv, dst_arv, args.project_uuid, args) - elif t == 'httpURL': - result = copy_from_http(args.object_uuid, src_arv, dst_arv, args) - else: - abort("cannot copy object {} of type {}".format(args.object_uuid, t)) + + try: + if t == 'Collection': + set_src_owner_uuid(src_arv.collections(), args.object_uuid, args) + result = copy_collection(args.object_uuid, + src_arv, dst_arv, + args) + elif t == 'Workflow': + set_src_owner_uuid(src_arv.workflows(), args.object_uuid, args) + result = copy_workflow(args.object_uuid, src_arv, dst_arv, args) + elif t == 'Group': + set_src_owner_uuid(src_arv.groups(), args.object_uuid, args) + result = copy_project(args.object_uuid, src_arv, dst_arv, args.project_uuid, args) + elif t == 'httpURL': + result = copy_from_http(args.object_uuid, src_arv, dst_arv, args) + else: + abort("cannot copy object {} of type {}".format(args.object_uuid, t)) + except Exception as e: + logger.error("%s", e, exc_info=args.verbose) + exit(1) # Clean up any outstanding temp git repositories. for d in listvalues(local_repo_dir): shutil.rmtree(d, ignore_errors=True) + if not result: + exit(1) + # If no exception was thrown and the response does not have an # error_token field, presume success - if 'error_token' in result or 'uuid' not in result: - logger.error("API server returned an error result: {}".format(result)) + if result is None or 'error_token' in result or 'uuid' not in result: + if result: + logger.error("API server returned an error result: {}".format(result)) exit(1) print(result['uuid']) @@ -318,21 +327,26 @@ def copy_workflow(wf_uuid, src, dst, args): # copy collections and docker images if args.recursive and wf["definition"]: - wf_def = yaml.safe_load(wf["definition"]) - if wf_def is not None: - locations = [] - docker_images = {} - graph = wf_def.get('$graph', None) - if graph is not None: - workflow_collections(graph, locations, docker_images) - else: - workflow_collections(wf_def, locations, docker_images) + env = {"ARVADOS_API_HOST": urllib.parse.urlparse(src._rootDesc["rootUrl"]).netloc, + "ARVADOS_API_TOKEN": src.api_token, + "PATH": os.environ["PATH"]} + try: + result = subprocess.run(["arvados-cwl-runner", "--quiet", "--print-keep-deps", "arvwf:"+wf_uuid], + capture_output=True, env=env) + except FileNotFoundError: + no_arv_copy = True + else: + no_arv_copy = result.returncode == 2 + + if no_arv_copy: + raise Exception('Copying workflows requires arvados-cwl-runner 2.7.1 or later to be installed in PATH.') + elif result.returncode != 0: + raise Exception('There was an error getting Keep dependencies from workflow using arvados-cwl-runner --print-keep-deps') - if locations: - copy_collections(locations, src, dst, args) + locations = json.loads(result.stdout) - for image in docker_images: - copy_docker_image(image, docker_images[image], src, dst, args) + if locations: + copy_collections(locations, src, dst, args) # copy the workflow itself del wf['uuid'] @@ -569,7 +583,7 @@ def copy_collection(obj_uuid, src, dst, args): dst_keep = arvados.keep.KeepClient(api_client=dst, num_retries=args.retries) dst_manifest = io.StringIO() dst_locators = {} - bytes_written = [0] + bytes_written = 0 bytes_expected = total_collection_size(manifest) if args.progress: progress_writer = ProgressWriter(human_progress) @@ -585,31 +599,88 @@ def copy_collection(obj_uuid, src, dst, args): # again and build dst_manifest lock = threading.Lock() + + # the get queue should be unbounded because we'll add all the + # block hashes we want to get, but these are small get_queue = queue.Queue() - put_queue = queue.Queue() + + threadcount = 4 + + # the put queue contains full data blocks + # and if 'get' is faster than 'put' we could end up consuming + # a great deal of RAM if it isn't bounded. + put_queue = queue.Queue(threadcount) + transfer_error = [] def get_thread(): while True: word = get_queue.get() if word is None: + put_queue.put(None) + get_queue.task_done() return - data = src_keep.get(word) - put_queue.put((word, data)) - get_queue.task_done() + + blockhash = arvados.KeepLocator(word).md5sum + with lock: + if blockhash in dst_locators: + # Already uploaded + get_queue.task_done() + continue + + try: + logger.debug("Getting block %s", word) + data = src_keep.get(word) + put_queue.put((word, data)) + except e: + logger.error("Error getting block %s: %s", word, e) + transfer_error.append(e) + try: + # Drain the 'get' queue so we end early + while True: + get_queue.get(False) + get_queue.task_done() + except queue.Empty: + pass + finally: + get_queue.task_done() def put_thread(): + nonlocal bytes_written while True: item = put_queue.get() if item is None: + put_queue.task_done() return + word, data = item loc = arvados.KeepLocator(word) blockhash = loc.md5sum - dst_locator = dst_keep.put(data, classes=(args.storage_classes or [])) with lock: - dst_locators[blockhash] = dst_locator - bytes_written[0] += loc.size - put_queue.task_done() + if blockhash in dst_locators: + # Already uploaded + put_queue.task_done() + continue + + try: + logger.debug("Putting block %s (%s bytes)", blockhash, loc.size) + dst_locator = dst_keep.put(data, classes=(args.storage_classes or [])) + with lock: + dst_locators[blockhash] = dst_locator + bytes_written += loc.size + if progress_writer: + progress_writer.report(obj_uuid, bytes_written, bytes_expected) + except e: + logger.error("Error putting block %s (%s bytes): %s", blockhash, loc.size, e) + try: + # Drain the 'get' queue so we end early + while True: + get_queue.get(False) + get_queue.task_done() + except queue.Empty: + pass + transfer_error.append(e) + finally: + put_queue.task_done() for line in manifest.splitlines(): words = line.split() @@ -620,32 +691,24 @@ def copy_collection(obj_uuid, src, dst, args): # If 'word' can't be parsed as a locator, # presume it's a filename. continue - blockhash = loc.md5sum - # copy this block if we haven't seen it before - # (otherwise, just reuse the existing dst_locator) - with lock: - if blockhash in dst_locators: - continue - logger.debug("Copying block %s (%s bytes)", blockhash, loc.size) - if progress_writer: - progress_writer.report(obj_uuid, bytes_written[0], bytes_expected) - # queue it up. get_queue.put(word) - threading.Thread(target=get_thread, daemon=True).start() - threading.Thread(target=get_thread, daemon=True).start() - threading.Thread(target=get_thread, daemon=True).start() - threading.Thread(target=get_thread, daemon=True).start() + for i in range(0, threadcount): + get_queue.put(None) - threading.Thread(target=put_thread, daemon=True).start() - threading.Thread(target=put_thread, daemon=True).start() - threading.Thread(target=put_thread, daemon=True).start() - threading.Thread(target=put_thread, daemon=True).start() + for i in range(0, threadcount): + threading.Thread(target=get_thread, daemon=True).start() + + for i in range(0, threadcount): + threading.Thread(target=put_thread, daemon=True).start() get_queue.join() put_queue.join() + if len(transfer_error) > 0: + return {"error_token": "Failed to transfer blocks"} + for line in manifest.splitlines(): words = line.split() dst_manifest.write(words[0]) @@ -664,7 +727,7 @@ def copy_collection(obj_uuid, src, dst, args): dst_manifest.write("\n") if progress_writer: - progress_writer.report(obj_uuid, bytes_written[0], bytes_expected) + progress_writer.report(obj_uuid, bytes_written, bytes_expected) progress_writer.finish() # Copy the manifest and save the collection.