def main(): """create Perforce user and client for Git Fusion""" p4gf_version.log_version() try: log_l10n() p4gf_version.version_check() # pylint: disable=W0703 # Catching too general exception except Exception as e: sys.stderr.write(e.args[0] + '\n') sys.exit(1) with p4gf_create_p4.Closer(): p4 = p4gf_create_p4.create_p4() if not p4: return 2 Verbosity.report(Verbosity.INFO, "P4PORT : {}".format(p4.port)) Verbosity.report(Verbosity.INFO, "P4USER : {}".format(p4.user)) p4gf_util.reset_git_enviro() p4gf_proc.init() init(p4) return 0
def main(): """set up repo for a view""" parser = p4gf_util.create_arg_parser( "Initializes Git Fusion Perforce client and Git repository.") parser.add_argument('--start', metavar="", help='Changelist number to start repo history, default=1') parser.add_argument('view', metavar='view', help='name of view to be initialized') args = parser.parse_args() p4gf_version.log_version() view_name = p4gf_util.argv_to_view_name(args.view) p4gf_util.reset_git_enviro() p4 = connect_p4() if not p4: return 2 LOG.debug("connected to P4 at %s", p4.port) try: with p4gf_lock.view_lock(p4, view_name) as view_lock: # ensure we have a sane environment p4gf_init.init(p4) # now initialize the repository print("Initializing {}...".format(view_name)) r = init_repo(p4, view_name) if r > INIT_REPO_OK: return r print("Initialization complete.") if args.start: start = args.start.lstrip('@') print("Copying changes from {}...".format(start)) copy_p2g_with_start(view_name, start, view_lock) print("Copying completed.") except P4.P4Exception as e: sys.stderr.write("Error occurred: {}\n".format(e)) return 0
def main(): """create Perforce user and client for Git Fusion""" p4gf_version.print_and_exit_if_argv() p4gf_version.log_version() try: p4gf_version.git_version_check() # pylint: disable=W0703 # Catching too general exception except Exception as e: sys.stderr.write(e.args[0] + '\n') exit(1) p4 = connect_p4() if not p4: return 2 LOG.debug("connected to P4 at %s", p4.port) p4gf_util.reset_git_enviro() init(p4) return 0
sys.stderr.write(_('Defined views:\n\t')) sys.stderr.write('\n\t'.join(view_list)) sys.stderr.write('\n') sys.exit(2) view_list = args.views for view_name in view_list: sys.argv = [ 'p4gf_auth_server.py' , '--user={}'.format(p4gf_const.P4GF_USER) , 'git-upload-pack' , view_name] p4gf_auth_server.main(poll_only=True) if __name__ == "__main__": # Ensure any errors occurring in the setup are sent to stderr, while the # code below directs them to stderr once rather than twice. try: # thwart pylint identical code detection with two empty lines below with p4gf_log.ExceptionLogger(squelch=False, write_to_stderr_=True): p4gf_log.record_argv() p4gf_version.log_version() log_l10n() p4gf_version.version_check() # pylint: disable=W0702 except: # Cannot continue if above code failed. exit(1) # main() already writes errors to stderr, so don't let logger do it again p4gf_log.run_with_exception_logger(main, write_to_stderr=False)
# we are about to delete the current working directory. os.chdir(cwd) cleanup_client(ctx, view_name) raise # Detach git repo's workspace from master before calling # original git, otherwise we won't be able to push master. p4gf_util.checkout_detached_master() # Flush stderr before returning control to Git. # Otherwise Git's own output might interrupt ours. sys.stderr.flush() return _call_original_git(ctx, args) if __name__ == "__main__": # Ensure any errors occurring in the setup are sent to stderr, while the # code below directs them to stderr once rather than twice. try: with p4gf_log.ExceptionLogger(squelch=False, write_to_stderr_=True): p4gf_audit_log.record_argv() p4gf_version.log_version() p4gf_version.git_version_check() # pylint: disable=W0702 except: # Cannot continue if above code failed. exit(1) # main() already writes errors to stderr, so don't let logger do it again p4gf_log.run_with_exception_logger(main, write_to_stderr=False)
def _wsgi_app(environ, start_response): """ WSGI application to process the incoming Git client request. This is nearly equivalent to p4gf_auth_server.main() with the exception of input validation and error handling. """ p4gf_log.record_http(environ) p4gf_version.log_version() _log_environ(environ) p4gf_version.version_check() LOG.debug("processing HTTP request, pid={}".format(os.getpid())) # Keep the content type to exactly 'text/plain' so there is at least # the remote chance that Git might show our error messages (does not # appear to work in practice, however). headers = [('Content-Type', 'text/plain')] encoding = sys.getfilesystemencoding() if encoding == 'ascii': # This encoding is wrong and will eventually lead to problems. LOG.error("Using 'ascii' file encoding will ultimately result in errors, " "please set LANG/LC_ALL to 'utf-8' in web server configuration.") start_response(_('500 Internal Server Error'), headers) return [b"Filesystem encoding not set to acceptable value.\n"] # Sanity check the request. for (name, status, msg) in _REQUIRED_HTTP_PARAMS: if name not in environ: start_response(status, headers) return [msg.encode('UTF-8')] input_name = environ['wsgi.input'] # Extract the view_name_git by removing the expected git request suffixes path_info = environ['PATH_INFO'] git_suffixes = ['/info/refs', '/HEAD', '/git-upload-pack', '/git-receive-pack'] path_end = len(path_info) for suffix in git_suffixes: try: path_end = path_info.index(suffix) break except ValueError: pass # slice away the leading slash and the trailing git request suffixes view_name_git = path_info[1:path_end] # and remove the view_name_git from the front of PATH_INFO environ['PATH_INFO'] = path_info[path_end:] LOG.debug("new PATH_INFO {0} view_name_git {1}".format(environ['PATH_INFO'], view_name_git)) if not view_name_git: start_response(_('400 Bad Request'), headers) msg = _('Missing required repository name in URL\n') return [msg.encode('UTF-8')] # translate '/' ':' ' ' .. etc .. for internal view_name view_name = p4gf_translate.TranslateReponame.git_to_repo(view_name_git) LOG.debug("public view_name: {0} internal view_name: {1}".format(view_name_git, view_name)) audit_logger = p4gf_server_common.ExceptionAuditLogger() p4_closer = p4gf_create_p4.Closer() sink = OutputSink() temp_deleter = deleting(input_name) mirror_closer = unmirror(view_name) with audit_logger \ , p4_closer \ , sink \ , temp_deleter \ , mirror_closer: LOG.debug(p4gf_log.memory_usage()) start_time = time.time() p4gf_util.reset_git_enviro() p4 = p4gf_create_p4.create_p4() if not p4: start_response(_('500 Internal Server Error'), headers) return [b"Perforce connection failed\n"] LOG.debug("connected to P4: %s", p4) p4gf_server_common.check_readiness(p4) p4gf_server_common.check_lock_perm(p4) if not p4gf_server_common.check_protects(p4): p4gf_server_common.raise_p4gf_perm() user = environ['REMOTE_USER'] if p4gf_server_common.run_special_command(view_name, p4, user): start_response(_('200 OK'), headers) return [sink.readall()] command = _get_command(environ) if not command: start_response(_('400 Bad Request'), headers) return [b"Unrecognized service\n"] # Other places in the Perforce-to-Git phase will need to know the # name of client user, so set that here. As for Git-to-Perforce, # that is handled later by setting the REMOTE_USER envar. Notice # also that we're setting os.environ and not 'environ'. os.environ[p4gf_const.P4GF_AUTH_P4USER] = user # Likewise, some code needs a hint that the request is coming over # one protocol (HTTP) or the other (SSH). os.environ['REMOTE_ADDR'] = environ['REMOTE_ADDR'] # Initialize the external process launcher early, before allocating lots # of memory, and just after all other conditions have been checked. p4gf_proc.init() # Prepare for possible spawn of GitMirror worker process by forking # now before allocating lots of memory. p4gf_gitmirror.setup_spawn(view_name) # Kick off garbage collection debugging, if enabled. p4gf_gc.init_gc() # Go no further, create NOTHING, if user not authorized. # We use the translated internal view name here for perm authorization required_perm = p4gf_server_common.COMMAND_TO_PERM[command] view_perm = p4gf_group.ViewPerm.for_user_and_view(p4, user, view_name, required_perm) try: p4gf_server_common.check_authorization(p4, view_perm, user, command, view_name) except p4gf_server_common.CommandError as ce: start_response(_('403 Forbidden'), headers) return [str(ce).encode('UTF-8')] # Create Git Fusion server depot, user, config. NOPs if already created. p4gf_init.init(p4) before_lock_time = time.time() with p4gf_lock.view_lock(p4, view_name) as view_lock: after_lock_time = time.time() # Create Git Fusion per-repo client view mapping and config. init_repo_status = p4gf_init_repo.init_repo(p4, view_name, view_lock) if init_repo_status == p4gf_init_repo.INIT_REPO_OK: repo_created = True elif init_repo_status == p4gf_init_repo.INIT_REPO_EXISTS: repo_created = False elif init_repo_status == p4gf_init_repo.INIT_REPO_NOVIEW: start_response(_('404 Not Found'), headers) return [sink.readall()] else: start_response(_('500 Internal Server Error'), headers) return [b"Repository initialization failed\n"] # If authorization came from default, not explicit group # membership, copy that authorization to a group now. Could # not do this until after p4gf_init_repo() has a chance to # create not-yet-existing groups. if view_perm: view_perm.write_if(p4) # Now that we have valid git-fusion-user and # git-fusion-<view> client, replace our temporary P4 # connection with a more permanent Context, shared for the # remainder of this process. with p4gf_context.create_context(view_name, view_lock) as ctx: LOG.debug("reconnected to P4, p4gf=%s", ctx.p4gf) ctx.log_context() # cd into the work directory. Not all git functions react well # to --work-tree=xxxx. cwd = os.getcwd() os.chdir(ctx.view_dirs.GIT_WORK_TREE) # Only copy from Perforce to Git if no other process is cloning # from this Git repo right now. shared_in_progress = p4gf_lock.shared_host_view_lock_exists(ctx.p4, view_name) if not shared_in_progress: # Copy any recent changes from Perforce to Git. try: LOG.debug("bare: No git-upload-pack in progress, force non-bare" " before update Git from Perforce.") p4gf_git.set_bare(False) p4gf_copy_p2g.copy_p2g_ctx(ctx) p4gf_init_repo.process_imports(ctx) # Now is also an appropriate time to clear out any stale Git # Swarm reviews. We're pre-pull, pre-push, time when we've # got exclusive write access to the Git repo, GSReviewCollection.delete_refs_for_closed_reviews(ctx) except p4gf_lock.LockCanceled as lc: LOG.warning(str(lc)) except: # Dump failure to log, BEFORE cleanup, just in case # cleanup ALSO fails and throws its own error (which # happens if we're out of memory). LOG.error(traceback.format_exc()) if repo_created: # Return to the original working directory to allow the # config code to call os.getcwd() without dying, since # we are about to delete the current working directory. os.chdir(cwd) p4gf_server_common.cleanup_client(ctx, view_name) raise try: exclusive = 'upload' not in command is_push = 'upload' not in command git_caller = functools.partial(_call_git, input_name, environ, ctx) p4gf_call_git.call_git(git_caller, ctx, view_name, view_lock, exclusive) if is_push: GSReviewCollection.post_push(ctx) except p4gf_atomic_lock.LockConflict as lc: start_response(_('500 Internal Server Error'), headers) return ["{}".format(lc).encode('UTF-8')] p4gf_gc.process_garbage('at end of auth_server') if LOG.isEnabledFor(logging.DEBUG): end_time = time.time() frm = NTR('Runtime: preparation {} ms, lock acquisition {} ms, processing {} ms') LOG.debug(frm.format(before_lock_time - start_time, after_lock_time - before_lock_time, end_time - after_lock_time)) return []
def main(): """set up repo for a view""" p4gf_util.has_server_id_or_exit() args = _parse_argv() p4gf_version.log_version() log_l10n() # !!! view_name_git the untranslated repo name # !!! view_name the translated repo name view_name_p4client = None if args.p4client: view_name_p4client = p4gf_util.argv_to_view_name(args.p4client) view_name_git = p4gf_util.argv_to_view_name(args.view) #strip leading '/' to conform with p4gf_auth_server behavior if view_name_git[0] == '/': view_name_git = view_name_git[1:] view_name = p4gf_translate.TranslateReponame.git_to_repo(view_name_git) p4gf_gitmirror.setup_spawn(view_name) p4gf_util.reset_git_enviro() p4 = p4gf_create_p4.create_p4() if not p4: return INIT_REPO_NOVIEW LOG.debug("connected to P4 at %s", p4.port) p4gf_proc.init() try: with p4gf_create_p4.Closer(): p4gf_version.version_check() with p4gf_lock.view_lock(p4, view_name) as view_lock: # Ensure we have a sane environment. p4gf_init.init(p4) # Now that we can trust that the git-fusion--p4 client exists, # switch to that. Change takes effect immediately, don't need to # re-run p4.connect(). p4.client = p4gf_util.get_object_client_name() # If local config file specified, validate it and store in # Perforce now. Even if client exists (aka repo was already # inited), this is one way for an admin to modify an existing # repo's config. if args.config: if not os.path.exists(args.config): _print_stderr(_("error: missing config file '{}'").format(args.config)) return INIT_REPO_CONFIG_FILE_MISSING with Validator.from_local_file(view_name, p4, args.config) as validator: if not validator.is_valid(args.enablemismatchedrhs): return INIT_REPO_CONFIG_FILE_BAD p4gf_config.create_file_repo_with_contents(p4, view_name, args.config) elif args.charset and not Validator.valid_charset(args.charset): _print_stderr(_("error: invalid charset: {}").format(args.charset)) return INIT_REPO_BAD_CHARSET # Initialize the repository if necessary. print(_("Initializing '{}'...").format(view_name)) r = init_repo(p4, view_name, view_lock, args.charset, args.enablemismatchedrhs, view_name_p4client) if r > INIT_REPO_OK: return r print(_("Initialization complete.")) # Write --enablemismatchedrhs to config file if args.enablemismatchedrhs: config = p4gf_config.read_repo(p4, view_name) config[p4gf_config.SECTION_REPO]\ [p4gf_config.KEY_ENABLE_MISMATCHED_RHS] = str(True) p4gf_config.write_repo_if(p4, p4.fetch_client(), view_name, config) # Populate the repo from Perforce unless --noclone. if not args.noclone: return populate_repo(view_name, view_lock, args.start) except P4.P4Exception as e: _print_stderr(_('Error occurred: {}').format(e)) return INIT_REPO_EXISTS
def main(): """set up repo for a view""" p4gf_util.has_server_id_or_exit() args = _parse_argv() p4gf_version.log_version() log_l10n() # !!! view_name_git the untranslated repo name # !!! view_name the translated repo name view_name_p4client = None if args.p4client: view_name_p4client = p4gf_util.argv_to_view_name(args.p4client) view_name_git = p4gf_util.argv_to_view_name(args.view) #strip leading '/' to conform with p4gf_auth_server behavior if view_name_git[0] == '/': view_name_git = view_name_git[1:] view_name = p4gf_translate.TranslateReponame.git_to_repo(view_name_git) p4gf_gitmirror.setup_spawn(view_name) p4gf_util.reset_git_enviro() p4 = p4gf_create_p4.create_p4() if not p4: return INIT_REPO_NOVIEW LOG.debug("connected to P4 at %s", p4.port) p4gf_proc.init() try: with p4gf_create_p4.Closer(): p4gf_version.version_check() with p4gf_lock.view_lock(p4, view_name) as view_lock: # Ensure we have a sane environment. p4gf_init.init(p4) # Now that we can trust that the git-fusion--p4 client exists, # switch to that. Change takes effect immediately, don't need to # re-run p4.connect(). p4.client = p4gf_util.get_object_client_name() # If local config file specified, validate it and store in # Perforce now. Even if client exists (aka repo was already # inited), this is one way for an admin to modify an existing # repo's config. if args.config: if not os.path.exists(args.config): _print_stderr( _("error: missing config file '{}'").format( args.config)) return INIT_REPO_CONFIG_FILE_MISSING with Validator.from_local_file(view_name, p4, args.config) as validator: if not validator.is_valid(args.enablemismatchedrhs): return INIT_REPO_CONFIG_FILE_BAD p4gf_config.create_file_repo_with_contents( p4, view_name, args.config) elif args.charset and not Validator.valid_charset( args.charset): _print_stderr( _("error: invalid charset: {}").format(args.charset)) return INIT_REPO_BAD_CHARSET # Initialize the repository if necessary. print(_("Initializing '{}'...").format(view_name)) r = init_repo(p4, view_name, view_lock, args.charset, args.enablemismatchedrhs, view_name_p4client) if r > INIT_REPO_OK: return r print(_("Initialization complete.")) # Write --enablemismatchedrhs to config file if args.enablemismatchedrhs: config = p4gf_config.read_repo(p4, view_name) config[p4gf_config.SECTION_REPO]\ [p4gf_config.KEY_ENABLE_MISMATCHED_RHS] = str(True) p4gf_config.write_repo_if(p4, p4.fetch_client(), view_name, config) # Populate the repo from Perforce unless --noclone. if not args.noclone: return populate_repo(view_name, view_lock, args.start) except P4.P4Exception as e: _print_stderr(_('Error occurred: {}').format(e)) return INIT_REPO_EXISTS
def _wsgi_app(environ, start_response): """ WSGI application to process the incoming Git client request. This is nearly equivalent to p4gf_auth_server.main() with the exception of input validation and error handling. """ p4gf_log.record_http(environ) p4gf_version.log_version() _log_environ(environ) p4gf_version.version_check() LOG.debug("processing HTTP request, pid={}".format(os.getpid())) # Keep the content type to exactly 'text/plain' so there is at least # the remote chance that Git might show our error messages (does not # appear to work in practice, however). headers = [('Content-Type', 'text/plain')] encoding = sys.getfilesystemencoding() if encoding == 'ascii': # This encoding is wrong and will eventually lead to problems. LOG.error( "Using 'ascii' file encoding will ultimately result in errors, " "please set LANG/LC_ALL to 'utf-8' in web server configuration.") start_response(_('500 Internal Server Error'), headers) return [b"Filesystem encoding not set to acceptable value.\n"] # Sanity check the request. for (name, status, msg) in _REQUIRED_HTTP_PARAMS: if name not in environ: start_response(status, headers) return [msg.encode('UTF-8')] input_name = environ['wsgi.input'] # Extract the view_name_git by removing the expected git request suffixes path_info = environ['PATH_INFO'] git_suffixes = [ '/info/refs', '/HEAD', '/git-upload-pack', '/git-receive-pack' ] path_end = len(path_info) for suffix in git_suffixes: try: path_end = path_info.index(suffix) break except ValueError: pass # slice away the leading slash and the trailing git request suffixes view_name_git = path_info[1:path_end] # and remove the view_name_git from the front of PATH_INFO environ['PATH_INFO'] = path_info[path_end:] LOG.debug("new PATH_INFO {0} view_name_git {1}".format( environ['PATH_INFO'], view_name_git)) if not view_name_git: start_response(_('400 Bad Request'), headers) msg = _('Missing required repository name in URL\n') return [msg.encode('UTF-8')] # translate '/' ':' ' ' .. etc .. for internal view_name view_name = p4gf_translate.TranslateReponame.git_to_repo(view_name_git) LOG.debug("public view_name: {0} internal view_name: {1}".format( view_name_git, view_name)) audit_logger = p4gf_server_common.ExceptionAuditLogger() p4_closer = p4gf_create_p4.Closer() sink = OutputSink() temp_deleter = deleting(input_name) mirror_closer = unmirror(view_name) with audit_logger \ , p4_closer \ , sink \ , temp_deleter \ , mirror_closer: LOG.debug(p4gf_log.memory_usage()) start_time = time.time() p4gf_util.reset_git_enviro() p4 = p4gf_create_p4.create_p4() if not p4: start_response(_('500 Internal Server Error'), headers) return [b"Perforce connection failed\n"] LOG.debug("connected to P4: %s", p4) p4gf_server_common.check_readiness(p4) p4gf_server_common.check_lock_perm(p4) if not p4gf_server_common.check_protects(p4): p4gf_server_common.raise_p4gf_perm() user = environ['REMOTE_USER'] if p4gf_server_common.run_special_command(view_name, p4, user): start_response(_('200 OK'), headers) return [sink.readall()] command = _get_command(environ) if not command: start_response(_('400 Bad Request'), headers) return [b"Unrecognized service\n"] # Other places in the Perforce-to-Git phase will need to know the # name of client user, so set that here. As for Git-to-Perforce, # that is handled later by setting the REMOTE_USER envar. Notice # also that we're setting os.environ and not 'environ'. os.environ[p4gf_const.P4GF_AUTH_P4USER] = user # Likewise, some code needs a hint that the request is coming over # one protocol (HTTP) or the other (SSH). os.environ['REMOTE_ADDR'] = environ['REMOTE_ADDR'] # Initialize the external process launcher early, before allocating lots # of memory, and just after all other conditions have been checked. p4gf_proc.init() # Prepare for possible spawn of GitMirror worker process by forking # now before allocating lots of memory. p4gf_gitmirror.setup_spawn(view_name) # Kick off garbage collection debugging, if enabled. p4gf_gc.init_gc() # Go no further, create NOTHING, if user not authorized. # We use the translated internal view name here for perm authorization required_perm = p4gf_server_common.COMMAND_TO_PERM[command] view_perm = p4gf_group.ViewPerm.for_user_and_view( p4, user, view_name, required_perm) try: p4gf_server_common.check_authorization(p4, view_perm, user, command, view_name) except p4gf_server_common.CommandError as ce: start_response(_('403 Forbidden'), headers) return [str(ce).encode('UTF-8')] # Create Git Fusion server depot, user, config. NOPs if already created. p4gf_init.init(p4) before_lock_time = time.time() with p4gf_lock.view_lock(p4, view_name) as view_lock: after_lock_time = time.time() # Create Git Fusion per-repo client view mapping and config. init_repo_status = p4gf_init_repo.init_repo( p4, view_name, view_lock) if init_repo_status == p4gf_init_repo.INIT_REPO_OK: repo_created = True elif init_repo_status == p4gf_init_repo.INIT_REPO_EXISTS: repo_created = False elif init_repo_status == p4gf_init_repo.INIT_REPO_NOVIEW: start_response(_('404 Not Found'), headers) return [sink.readall()] else: start_response(_('500 Internal Server Error'), headers) return [b"Repository initialization failed\n"] # If authorization came from default, not explicit group # membership, copy that authorization to a group now. Could # not do this until after p4gf_init_repo() has a chance to # create not-yet-existing groups. if view_perm: view_perm.write_if(p4) # Now that we have valid git-fusion-user and # git-fusion-<view> client, replace our temporary P4 # connection with a more permanent Context, shared for the # remainder of this process. with p4gf_context.create_context(view_name, view_lock) as ctx: LOG.debug("reconnected to P4, p4gf=%s", ctx.p4gf) ctx.log_context() # cd into the work directory. Not all git functions react well # to --work-tree=xxxx. cwd = os.getcwd() os.chdir(ctx.view_dirs.GIT_WORK_TREE) # Only copy from Perforce to Git if no other process is cloning # from this Git repo right now. shared_in_progress = p4gf_lock.shared_host_view_lock_exists( ctx.p4, view_name) if not shared_in_progress: # Copy any recent changes from Perforce to Git. try: LOG.debug( "bare: No git-upload-pack in progress, force non-bare" " before update Git from Perforce.") p4gf_git.set_bare(False) p4gf_copy_p2g.copy_p2g_ctx(ctx) p4gf_init_repo.process_imports(ctx) # Now is also an appropriate time to clear out any stale Git # Swarm reviews. We're pre-pull, pre-push, time when we've # got exclusive write access to the Git repo, GSReviewCollection.delete_refs_for_closed_reviews(ctx) except p4gf_lock.LockCanceled as lc: LOG.warning(str(lc)) except: # Dump failure to log, BEFORE cleanup, just in case # cleanup ALSO fails and throws its own error (which # happens if we're out of memory). LOG.error(traceback.format_exc()) if repo_created: # Return to the original working directory to allow the # config code to call os.getcwd() without dying, since # we are about to delete the current working directory. os.chdir(cwd) p4gf_server_common.cleanup_client(ctx, view_name) raise try: exclusive = 'upload' not in command is_push = 'upload' not in command git_caller = functools.partial(_call_git, input_name, environ, ctx) p4gf_call_git.call_git(git_caller, ctx, view_name, view_lock, exclusive) if is_push: GSReviewCollection.post_push(ctx) except p4gf_atomic_lock.LockConflict as lc: start_response(_('500 Internal Server Error'), headers) return ["{}".format(lc).encode('UTF-8')] p4gf_gc.process_garbage('at end of auth_server') if LOG.isEnabledFor(logging.DEBUG): end_time = time.time() frm = NTR( 'Runtime: preparation {} ms, lock acquisition {} ms, processing {} ms' ) LOG.debug( frm.format(before_lock_time - start_time, after_lock_time - before_lock_time, end_time - after_lock_time)) return []