Repository: HECBioSim/Longbow

Issue:
Implement update mode for disconnected sessions
When using the disconnect mode with --disconnect, currently the only way to reconnect is via the --recover option, but this reconnects permanently without then disconnecting again. A new mode should be implemented to reconnect to update the status of the jobs, transfer files and then disconnect again. This should be able to be done as many times as the user wishes up until completion of all jobs.

Discussion:

Patch:
diff --git a/longbow/applications.py b/longbow/applications.py
index 8f99c1e..b2ee695 100644
--- a/longbow/applications.py
+++ b/longbow/applications.py
@@ -82,7 +82,7 @@ def checkapp(jobs):
 
     LOG.info("Testing the executables defined for each job.")
 
-    for job in jobs:
+    for job in [a for a in jobs if "lbowconf" not in a]:
 
         # If we haven't checked this resource then it is likely not in the dict
         if jobs[job]["resource"] not in checked:
@@ -143,7 +143,7 @@ def processjobs(jobs):
     LOG.info("Processing job/s and detecting files that require upload.")
 
     # Process each job.
-    for job in jobs:
+    for job in [a for a in jobs if "lbowconf" not in a]:
 
         filelist = []
         appplugins = getattr(apps, "PLUGINEXECS")
@@ -166,7 +166,7 @@ def processjobs(jobs):
                     "not supported".format(job))
 
         # If we have multiple jobs.
-        if len(jobs) > 1:
+        if len([a for a in jobs if "lbowconf" not in a]) > 1:
 
             # Add the job name to the path.
             jobs[job]["localworkdir"] = os.path.join(
diff --git a/longbow/configuration.py b/longbow/configuration.py
index c8af848..432f2d8 100644
--- a/longbow/configuration.py
+++ b/longbow/configuration.py
@@ -409,14 +409,13 @@ def saveini(inifile, params):
 
     ini = open(inifile, "w")
 
-    for section in params:
+    for obj in params:
 
-        ini.write("[" + str(section) + "]\n")
+        ini.write("[" + str(obj) + "]\n")
 
-        for option in params[section]:
+        for opt in params[obj]:
 
-            ini.write(str(option) + " = " + str(params[section][option]) +
-                      "\n")
+            ini.write(str(opt) + " = " + str(params[obj][opt]) + "\n")
 
         ini.write("\n")
 
@@ -429,7 +428,7 @@ def _processconfigsfinalinit(jobs):
     modules = getattr(apps, "PLUGINEXECS")
     modules[""] = ""
 
-    for job in jobs:
+    for job in [a for a in jobs if "lbowconf" not in a]:
 
         # This is just for logging messages.
         jobs[job]["jobname"] = job
@@ -463,10 +462,13 @@ def _processconfigsfinalinit(jobs):
         LOG.debug("Job '%s' will be run in the '%s' directory on the remote "
                   "resource.", job, jobs[job]["destdir"])
 
-        # Create a recovery file.
-        jobs[job]["recoveryfile"] = (
-            os.path.join(os.path.expanduser('~/.longbow'), "recovery-" +
-                         time.strftime("%Y%m%d-%H%M%S")))
+    # Create a recovery file.
+    if "lbowconf" not in jobs:
+
+        jobs["lbowconf"] = {}
+
+    jobs["lbowconf"]["recoveryfile"] = (
+        "recovery-" + time.strftime("%Y%m%d-%H%M%S"))
 
 
 def _processconfigsparams(jobs, parameters, jobdata, hostdata):
@@ -498,9 +500,10 @@ def _processconfigsparams(jobs, parameters, jobdata, hostdata):
 
 def _processconfigsresource(parameters, jobdata, hostsections):
     """Check which HPC each job should use."""
-    # Initialise.
+    # Initialise
     jobs = {}
 
+
     # Process resource/s for job/s.
     for job in jobdata:
 
@@ -573,7 +576,7 @@ def _processconfigsvalidate(jobs):
                       "specifying a value for it in the configuration file."
     }
     # Check parameters that are required for running jobs are provided.
-    for job in jobs:
+    for job in [a for a in jobs if "lbowconf" not in a]:
 
         # Validate required parameters have been set.
         for validationitem in required:
diff --git a/longbow/entrypoints.py b/longbow/entrypoints.py
index d67b351..bddf6cc 100644
--- a/longbow/entrypoints.py
+++ b/longbow/entrypoints.py
@@ -110,44 +110,30 @@ def launcher():
         "recover": "",
         "resource": "",
         "replicates": "",
+        "update": "",
         "verbose": False
     }
 
     # Specify all recognised longbow arguments
     alllongbowargs = [
-        "-about",
         "--about",
-        "-debug",
         "--debug",
-        "-disconnect",
         "--disconnect",
-        "-examples",
         "--examples",
         "-h",
-        "-help",
         "--help",
-        "-hosts",
         "--hosts",
-        "-job",
         "--job",
-        "-jobname",
         "--jobname",
-        "-log",
         "--log",
-        "-maxtime",
         "--maxtime",
-        "-nochecks",
         "--nochecks",
-        "-recover",
         "--recover",
-        "-resource",
         "--resource",
-        "-replicates",
         "--replicates",
+        "--update",
         "-V",
-        "-verbose",
         "--verbose",
-        "-version",
         "--version"
     ]
 
@@ -209,7 +195,7 @@ def launcher():
 
         # If no executable and jobfile has been given then fail.
         if (parameters["executable"] == "" and parameters["job"] == "" and
-                parameters["recover"] == ""):
+                parameters["recover"] == "" and parameters["update"] == ""):
 
             raise exceptions.RequiredinputError(
                 "There was no executable or job file given on the "
@@ -219,19 +205,108 @@ def launcher():
         # ---------------------------------------------------------------------
         # Call one of the main methods at the top level of the library.
 
-        # Are we trying to recover or are we running as normal.
-        if parameters["recover"] == "":
+        jobs = {}
+
+        # If recovery or update mode is not active then this is a new run.
+        if parameters["recover"] == "" and parameters["update"] == "":
 
             LOG.info("Initialisation complete.")
 
-            longbow(parameters)
+            longbow(jobs, parameters)
+
+        # If recovery mode is set then start the recovery process.
+        elif parameters["recover"] != "" and parameters["update"] == "":
+
+            LOG.info("Starting recovery mode to reconnect monitoring of jobs.")
+
+            recovery(jobs, parameters["recover"])
+
+        # If update mode is set then start the update process.
+        elif parameters["recover"] == "" and parameters["update"] != "":
+
+            LOG.info("Starting update mode to refresh progress of jobs.")
+
+            update(jobs, parameters["update"])
 
+        # If too many arguments are set, we have a problem
         else:
 
-            LOG.info("Entering recovery mode.")
+            raise exceptions.CommandlineargsError(
+                "You have both the --recover and --update command-line flags "
+                "set, these cannot be used together as they enable "
+                "conflicting functionality. Either reconnect with persistent "
+                "monitoring (--recover) or reconnect to refresh the status of "
+                "jobs and sync current files before disconnecting again "
+                "(--update).")
+
+    # If the user interrupts Longbow then they are aborting the jobs, so kill
+    # off any running jobs and then remove the job directories. Otherwise just
+    # raise all other errors to the top level where in future we can attempt to
+    # recover.
+    except KeyboardInterrupt:
+
+        LOG.info("User interrupt detected.")
+
+        if len([a for a in jobs if "lbowconf" not in a]) >= 1:
+
+            LOG.info("Kill any queued or running jobs and clean up.")
+
+            # If we are exiting at this stage then we need to kill off
+            for item in [a for a in jobs if "lbowconf" not in a]:
+
+                job = jobs[item]
 
-            recovery(parameters["recover"])
+                if "laststatus" in job:
 
+                    # If job is not finished delete and stage.
+                    if (job["laststatus"] != "Complete" and
+                            job["laststatus"] != "Finished" and
+                            job["laststatus"] != "Submit Error"):
+
+                        # Kill it.
+                        scheduling.delete(job)
+
+                        # Transfer the directories as they are.
+                        staging.stage_downstream(job)
+
+                    # Job is finished then just stage.
+                    elif job["laststatus"] != "Submit Error":
+
+                        # Transfer the directories as they are.
+                        staging.stage_downstream(job)
+
+            staging.cleanup(jobs)
+
+    # If disconnect mode is enabled then the disconnect exception is raised,
+    # allow to disconnect gracefully.
+    except exceptions.DisconnectException:
+
+        LOG.info("User specified --disconnect flag on command-line, so "
+                 "Longbow will exit.")
+        LOG.info("You can reconnect this session for persistent monitoring by "
+                 "using the recovery file:")
+        LOG.info("longbow --recover {0} --verbose"
+                 .format(jobs["lbowconf"]["recoveryfile"]))
+        LOG.info("Or an update of current progress followed by disconnecting "
+                 "can be done using:")
+        LOG.info("longbow --update {0} --verbose"
+                 .format(jobs["lbowconf"]["recoveryfile"]))
+
+    # If disconnect mode is enabled then the disconnect exception is raised,
+    # allow to disconnect gracefully.
+    except exceptions.UpdateExit:
+
+        LOG.info("Update of current job progress has completed, exiting.")
+        LOG.info("You can reconnect this session for persistent monitoring by "
+                 "using the recovery file:")
+        LOG.info("longbow --recover {0} --verbose"
+                 .format(jobs["lbowconf"]["recoveryfile"]))
+        LOG.info("Or an update of current progress followed by disconnecting "
+                 "can be done using:")
+        LOG.info("longbow --update {0} --verbose"
+                 .format(jobs["lbowconf"]["recoveryfile"]))
+
+    # If a problem happens assign the correct level of debug logging.
     except Exception as err:
 
         if parameters["debug"] is True:
@@ -242,6 +317,7 @@ def launcher():
 
             LOG.error(err)
 
+    # Show nice exit message.
     finally:
 
         LOG.info("Good bye from Longbow!")
@@ -249,7 +325,7 @@ def launcher():
                  "powerful biomolecular simulation software tools.")
 
 
-def longbow(parameters):
+def longbow(jobs, parameters):
     """Entry point at the top level of the Longbow library.
 
     Being the top level method that makes calls on the Longbow library.
@@ -266,7 +342,12 @@ def longbow(parameters):
     # escalating the exception to trigger graceful exit.
 
     # Load configurations and initialise Longbow data structures.
-    jobs = configuration.processconfigs(parameters)
+    jobparams = configuration.processconfigs(parameters)
+
+    # Copy to jobs so when exceptions are raised the structure is available.
+    for param in jobparams:
+
+        jobs[param] = jobparams[param]
 
     # Test all connection/s specified in the job configurations
     shellwrappers.checkconnections(jobs)
@@ -289,70 +370,25 @@ def longbow(parameters):
     # uploading.
     scheduling.prepare(jobs)
 
-    # Exceptions that occur before here don't require cleanup operations before
-    # reporting up.
-    try:
-
-        # Stage all of the job files along with the scheduling script.
-        staging.stage_upstream(jobs)
+    # Stage all of the job files along with the scheduling script.
+    staging.stage_upstream(jobs)
 
-        # Submit all jobs.
-        scheduling.submit(jobs)
+    # Submit all jobs.
+    scheduling.submit(jobs)
 
-        # Process the disconnect function.
-        if parameters["disconnect"] is True:
+    # Process the disconnect function.
+    if parameters["disconnect"] is True:
 
-            raise exceptions.DisconnectException
+        raise exceptions.DisconnectException
 
-        # Monitor all jobs.
-        scheduling.monitor(jobs)
+    # Monitor all jobs.
+    scheduling.monitor(jobs)
 
-        # Clean up all jobs
-        staging.cleanup(jobs)
+    # Clean up all jobs
+    staging.cleanup(jobs)
 
-    # If the user interrupts Longbow at this stage then it they are aborting
-    # the jobs, so kill off any running jobs and then remove the job
-    # directories. Otherwise just raise all other errors to the top level where
-    # in future we can attempt to recover.
-    except KeyboardInterrupt:
 
-        LOG.info("User interrupt detected, kill any queued or running jobs "
-                 "and removed any files staged.")
-
-        # If we are exiting at this stage then we need to kill off
-        for item in jobs:
-
-            job = jobs[item]
-
-            if "laststatus" in job:
-
-                # If job is not finished delete and stage.
-                if (job["laststatus"] != "Complete" and
-                        job["laststatus"] != "Submit Error"):
-
-                    # Kill it.
-                    scheduling.delete(job)
-
-                    # Transfer the directories as they are.
-                    staging.stage_downstream(job)
-
-                # Job is finished then just stage.
-                elif job["laststatus"] != "Submit Error":
-
-                    # Transfer the directories as they are.
-                    staging.stage_downstream(job)
-
-        staging.cleanup(jobs)
-
-    except exceptions.DisconnectException:
-
-        LOG.info("User specified --disconnect flag on command-line, so "
-                 "Longbow will exit. You can reconnect this session by using "
-                 "the recovery file, details of this file will be listed in "
-                 "the logs")
-
-
-def recovery(recoveryfile):
+def recovery(jobs, recoveryfile):
     """Recover a Longbow session.
 
     This method is for attempting to recover a failed Longbow session or to
@@ -367,67 +403,76 @@ def recovery(recoveryfile):
     recoveryfile (string): A path to the recovery file.
 
     """
-    LOG.info("Attempting to find the recovery files")
 
-    longbowdir = os.path.expanduser('~/.longbow')
-    jobfile = os.path.join(longbowdir, recoveryfile)
+    jobfile = os.path.join(os.path.expanduser('~/.longbow'), recoveryfile)
+
+    LOG.info("Attempting to find the recovery file '{0}'".format(jobfile))
 
     # Load the jobs recovery file.
     if os.path.isfile(jobfile):
 
-        LOG.info("Recovery file found at '%s'", jobfile)
+        LOG.info("Recovery file found.")
+
+        _, _, jobparams = configuration.loadconfigs(jobfile)
+
+        # Copy to jobs so when exceptions are raised the structure is available.
+        for param in jobparams:
 
-        _, _, jobs = configuration.loadconfigs(jobfile)
+            jobs[param] = jobparams[param]
 
     else:
 
         raise exceptions.RequiredinputError(
-            "Recovery file could not be found at '{0}' make sure you are "
-            "running the recovery from the job directory that was initially "
-            "used to launch the failed job".format(jobfile))
+            "Recovery file could not be found, make sure you haven't deleted "
+            "the recovery file and that you are not providing the full path, "
+            "just the file name is needed.")
 
-    try:
+    # Rejoin at the monitoring stage. This will assume that all jobs that
+    # are no longer in the queue have completed.
+    scheduling.monitor(jobs)
 
-        # Rejoin at the monitoring stage. This will assume that all jobs that
-        # are no longer in the queue have completed.
-        scheduling.monitor(jobs)
+    # Cleanup the remote working directory.
+    staging.cleanup(jobs)
 
-        # Cleanup the remote working directory.
-        staging.cleanup(jobs)
 
-    # If the user interrupts Longbow at this stage then it they are aborting
-    # the jobs, so kill off any running jobs and then remove the job
-    # directories. Otherwise just raise all other errors to the top level where
-    # in future we can attempt to recover.
-    except KeyboardInterrupt:
+def update(jobs, updatefile):
+    """Trigger update of a disconnected Longbow session.
 
-        LOG.info("User interrupt detected, kill any queued or running jobs "
-                 "and removed any files staged.")
+    This method will start the update process on an existing but disconnected
+    Longbow session. All job statuses will be checked and updated in the
+    recovery file and all output files will be synced before disconnecting."""
 
-        # If we are exiting at this stage then we need to kill off
-        for item in jobs:
+    jobfile = os.path.join(os.path.expanduser('~/.longbow'), updatefile)
 
-            job = jobs[item]
+    LOG.info("Attempting to find the recovery file '{0}'".format(jobfile))
 
-            if "laststatus" in job:
+    # Load the jobs recovery file.
+    if os.path.isfile(jobfile):
+
+        LOG.info("Recovery file found.")
+
+        _, _, jobparams = configuration.loadconfigs(jobfile)
 
-                # If job is not finished delete and stage.
-                if (job["laststatus"] != "Complete" and
-                        job["laststatus"] != "Submit Error"):
+        # Copy to jobs so when exceptions are raised the structure is available.
+        for param in jobparams:
 
-                    # Kill it.
-                    scheduling.delete(job)
+            jobs[param] = jobparams[param]
 
-                    # Transfer the directories as they are.
-                    staging.stage_downstream(job)
+    else:
+
+        raise exceptions.RequiredinputError(
+            "Recovery file could not be found, make sure you haven't deleted "
+            "the recovery file and that you are not providing the full path, "
+            "just the file name is needed.")
 
-                # Job is finished then just stage.
-                elif job["laststatus"] != "Submit Error":
+    # Add the updater key
+    jobs["lbowconf"]["update"] = True
 
-                    # Transfer the directories as they are.
-                    staging.stage_downstream(job)
+    # Enter monitoring loop
+    scheduling.monitor(jobs)
 
-        staging.cleanup(jobs)
+    # Cleanup the remote working directory.
+    staging.cleanup(jobs)
 
 
 def _commandlineproc(alllongbowargs, cmdlnargs, parameters):
@@ -668,7 +713,7 @@ def _messageflags(longbowargs):
               "--debug                   : additional output to assist "
               "debugging.\n"
               "--disconnect              : instructs Longbow to disconnect and"
-              " exit\n after submitting jobs.\n"
+              " exit\n                            after submitting jobs.\n"
               "--examples                : downloads example files to "
               "./LongbowExamples\n"
               "--help, -h                : prints Longbow help.\n"
@@ -680,14 +725,14 @@ def _messageflags(longbowargs):
               "submitted.\n"
               "--log [file name]         : specifies the file Longbow output "
               "should be directed to.\n"
-              "--maxtime [HH:MM]         : set the maximum job time for all "
-              "jobs.\n"
-              "--recover [file name]     : launches the recovery mode.\n"
+              "--recover [file name]     : Launches the recovery mode.\n"
               "--resource [name]         : specifies the remote resource.\n"
               "--replicates [number]     : number of replicate jobs to be "
               "submitted.\n"
               "--verbose                 : additional run-time info to be "
               "output.\n"
+              "--update [file name]      : launches the update mode to sync "
+              "current job progress and files.\n"
               "--version, -V             : prints Longbow version number.\n"
               "\n"
               "Read the documentation at http://www.hecbiosim.ac.uk/ for more "
diff --git a/longbow/exceptions.py b/longbow/exceptions.py
index 068e179..c0c371b 100644
--- a/longbow/exceptions.py
+++ b/longbow/exceptions.py
@@ -72,6 +72,13 @@ class DisconnectException(Exception):
 
     pass
 
+
+class UpdateExit(Exception):
+
+    """Exception, to exit gracefully after update of job progress."""
+
+    pass
+
 # -----------------------------------------------------------------------------
 # Exceptions for applications.py
 
diff --git a/longbow/scheduling.py b/longbow/scheduling.py
index 43bbd81..72b5cc7 100644
--- a/longbow/scheduling.py
+++ b/longbow/scheduling.py
@@ -73,7 +73,6 @@ import longbow.schedulers as schedulers
 
 
 LOG = logging.getLogger("longbow.corelibs.scheduling")
-QUEUEINFO = {}
 
 
 def checkenv(jobs, hostconf):
@@ -101,7 +100,7 @@ def checkenv(jobs, hostconf):
     saveparams = {}
 
     # Take a look at each job.
-    for item in jobs:
+    for item in [a for a in jobs if "lbowconf" not in a]:
 
         job = jobs[item]
 
@@ -221,13 +220,17 @@ def monitor(jobs):
     allfinished = False
     lastpolltime = 0
     laststagetime = 0
-    recoveryfile = jobs[list(jobs.keys())[0]]["recoveryfile"]
+    basepath = os.path.expanduser('~/.longbow')
+    recoveryfile = os.path.join(basepath, jobs["lbowconf"]["recoveryfile"])
     saverecoveryfile = True
     recoveryfileerror = False
 
     # Loop until all jobs are done.
     while allcomplete is False:
 
+        # Sane time interval (CPU core maxes out easily otherwise).
+        time.sleep(1.0)
+
         now = time.time()
 
         # Check if we should be polling.
@@ -244,15 +247,6 @@ def monitor(jobs):
             laststagetime = int(now)
             saverecoveryfile = _stagejobfiles(jobs, saverecoveryfile)
 
-        # Update the queue info settings to each job just in case something
-        # happens requiring user to use recovery.
-        for job in jobs:
-
-            jobs[job]["queue-slots"] = \
-                QUEUEINFO[jobs[job]["resource"]]["queue-slots"]
-            jobs[job]["queue-max"] = \
-                QUEUEINFO[jobs[job]["resource"]]["queue-max"]
-
         # Save out the recovery files.
         if (os.path.isdir(os.path.expanduser('~/.longbow')) and
                 saverecoveryfile is True and recoveryfileerror is False and
@@ -273,13 +267,18 @@ def monitor(jobs):
 
         allcomplete, allfinished = _checkcomplete(jobs)
 
-        # Sane time interval (CPU core maxes out easily otherwise).
-        time.sleep(1.0)
+        if ("update" in jobs["lbowconf"] and allfinished is False and
+                allcomplete is False):
+
+            if jobs["lbowconf"]["update"] is True:
+
+                jobs["lbowconf"]["update"] = False
+                raise exceptions.UpdateExit
 
     complete = 0
     error = 0
 
-    for job in jobs:
+    for job in [a for a in jobs if "lbowconf" not in a]:
 
         if jobs[job]["laststatus"] == "Submit Error":
 
@@ -310,7 +309,7 @@ def prepare(jobs):
     """
     LOG.info("Creating submit files for job/s.")
 
-    for item in jobs:
+    for item in [a for a in jobs if "lbowconf" not in a]:
 
         job = jobs[item]
         scheduler = job["scheduler"]
@@ -330,8 +329,6 @@ def prepare(jobs):
                 LOG.info("For job '%s' user has supplied their own job submit "
                          "script - skipping creation.", item)
 
-                job["upload-include"] = job["upload-include"] + ", " + job["subfile"]
-
         except AttributeError:
 
             raise exceptions.PluginattributeError(
@@ -361,18 +358,15 @@ def submit(jobs):
 
     LOG.info("Submitting job/s.")
 
-    for item in jobs:
+    for item in [a for a in jobs if "lbowconf" not in a]:
 
         job = jobs[item]
 
-        # Have we got this resource already?
-        if job["resource"] not in QUEUEINFO:
-
-            # no, well create it.
-            QUEUEINFO[job["resource"]] = {"queue-slots": str(0),
-                                          "queue-max": str(0)}
+        # Set up counters for each resource.
+        jobs["lbowconf"][job["resource"] + "-" + "queue-slots"] = str(0)
+        jobs["lbowconf"][job["resource"] + "-" + "queue-max"] = str(0)
 
-    for item in jobs:
+    for item in [a for a in jobs if "lbowconf" not in a]:
 
         job = jobs[item]
         scheduler = job["scheduler"]
@@ -387,8 +381,8 @@ def submit(jobs):
             job["laststatus"] = "Queued"
 
             # Increment the queue counter by one (used to count the slots).
-            QUEUEINFO[job["resource"]]["queue-slots"] = \
-                str(int(QUEUEINFO[job["resource"]]["queue-slots"]) + 1)
+            jobs["lbowconf"][job["resource"] + "-" + "queue-slots"] = str(int(
+                jobs["lbowconf"][job["resource"] + "-" + "queue-slots"]) + 1)
 
             submitted += 1
 
@@ -411,7 +405,7 @@ def submit(jobs):
         # Hit maximum slots on resource, Longbow will sub-schedule these.
         except exceptions.QueuemaxError:
 
-            for item in jobs:
+            for item in [a for a in jobs if "lbowconf" not in a]:
 
                 if "laststatus" not in jobs[item]:
 
@@ -428,31 +422,25 @@ def submit(jobs):
             break
 
         # We want to find out what the maximum number of slots we have are.
-        if int(QUEUEINFO[job["resource"]]["queue-slots"]) > \
-                int(QUEUEINFO[job["resource"]]["queue-max"]):
-
-            QUEUEINFO[job["resource"]]["queue-max"] = \
-                QUEUEINFO[job["resource"]]["queue-slots"]
+        if int(jobs["lbowconf"][job["resource"] + "-" + "queue-slots"]) > \
+                int(jobs["lbowconf"][job["resource"] + "-" + "queue-max"]):
 
-    # Store a copy of the queueinfo data in the jobs data structure in case
-    # recovery is needed.
-    for item in jobs:
-
-        job = jobs[item]
-
-        job["queue-slots"] = QUEUEINFO[job["resource"]]["queue-slots"]
-        job["queue-max"] = QUEUEINFO[job["resource"]]["queue-max"]
+            jobs["lbowconf"][job["resource"] + "-" + "queue-max"] = \
+                jobs["lbowconf"][job["resource"] + "-" + "queue-slots"]
 
     # Save out the recovery files.
     if (os.path.isdir(os.path.expanduser('~/.longbow')) and
-            job["recoveryfile"] != ""):
+            jobs["lbowconf"]["recoveryfile"] != ""):
+
+        basepath = os.path.expanduser('~/.longbow')
+        recoveryfile = os.path.join(basepath, jobs["lbowconf"]["recoveryfile"])
 
         try:
 
             LOG.info("Recovery file will be placed at path '%s'",
-                     job["recoveryfile"])
+                     recoveryfile)
 
-            configuration.saveini(job["recoveryfile"], jobs)
+            configuration.saveini(recoveryfile, jobs)
 
         except (OSError, IOError):
 
@@ -543,15 +531,7 @@ def _monitorinitialise(jobs):
     stageinterval = 0
 
     # Sort out some defaults.
-    for job in jobs:
-
-        # If we came from recovery mode then rebuild the queueinfo structure.
-        if jobs[job]["resource"] not in QUEUEINFO:
-
-            QUEUEINFO[jobs[job]["resource"]] = {
-                "queue-slots": jobs[job]["queue-slots"],
-                "queue-max": jobs[job]["queue-max"]
-            }
+    for job in [a for a in jobs if "lbowconf" not in a]:
 
         # This should always be present.
         if "laststatus" not in jobs[job]:
@@ -584,7 +564,7 @@ def _polljobs(jobs, save):
     finihed.
 
     """
-    for job in jobs:
+    for job in [a for a in jobs if "lbowconf" not in a]:
 
         if (jobs[job]["laststatus"] != "Finished" and
                 jobs[job]["laststatus"] != "Complete" and
@@ -614,9 +594,9 @@ def _polljobs(jobs, save):
 
                 if status == "Finished":
 
-                    resource = jobs[job]["resource"]
-                    QUEUEINFO[resource]["queue-slots"] = \
-                        str(int(QUEUEINFO[resource]["queue-slots"]) - 1)
+                    qslots = jobs[job]["resource"] + "-" + "queue-slots"
+                    jobs["lbowconf"][qslots] = str(int(
+                        jobs["lbowconf"][qslots]) - 1)
 
                 LOG.info("Status of job '%s' with id '%s' is '%s'", job,
                          jobs[job]["jobid"], status)
@@ -632,7 +612,7 @@ def _stagejobfiles(jobs, save):
     complete. This will stop future staging.
 
     """
-    for job in jobs:
+    for job in [a for a in jobs if "lbowconf" not in a]:
 
         if (jobs[job]["laststatus"] == "Running" or
                 jobs[job]["laststatus"] == "Subjob(s) running" or
@@ -651,12 +631,13 @@ def _stagejobfiles(jobs, save):
 
 def _checkwaitingjobs(jobs, save):
     """Check if any jobs marked as "Waiting Submission" can be submitted."""
-    for job in jobs:
+    for job in [a for a in jobs if "lbowconf" not in a]:
 
         # Check if we can submit any further jobs.
+        resource = jobs[job]["resource"]
         if (jobs[job]["laststatus"] == "Waiting Submission" and
-                int(QUEUEINFO[jobs[job]["resource"]]["queue-slots"]) <
-                int(QUEUEINFO[jobs[job]["resource"]]["queue-max"])):
+                int(jobs["lbowconf"][resource + "-" + "queue-slots"]) <
+                int(jobs["lbowconf"][resource + "-" + "queue-max"])):
 
             # Try and submit this job.
             try:
@@ -670,8 +651,8 @@ def _checkwaitingjobs(jobs, save):
                          jobs[job]["jobid"])
 
                 # Increment the queue counter by one (used to count the slots).
-                QUEUEINFO[jobs[job]["resource"]]["queue-slots"] = str(
-                    int(QUEUEINFO[jobs[job]["resource"]]["queue-slots"]) + 1)
+                jobs["lbowconf"][resource + "-" + "queue-slots"] = str(int(
+                    jobs["lbowconf"][resource + "-" + "queue-slots"]) + 1)
 
                 save = True
 
@@ -711,7 +692,7 @@ def _checkcomplete(jobs):
     error = []
     finished = []
 
-    for job in jobs:
+    for job in [a for a in jobs if "lbowconf" not in a]:
 
         if jobs[job]["laststatus"] != "Submit Error":
 
@@ -730,7 +711,7 @@ def _checkcomplete(jobs):
 
         allcomplete = True
 
-    if len(error) == len(jobs):
+    if len(error) == len([a for a in jobs if "lbowconf" not in a]):
 
         allcomplete = True
 
diff --git a/longbow/shellwrappers.py b/longbow/shellwrappers.py
index 95ce3bf..9dfaf09 100644
--- a/longbow/shellwrappers.py
+++ b/longbow/shellwrappers.py
@@ -129,7 +129,7 @@ def checkconnections(jobs):
     # Test all of the computers listed in jobs in the job configuration
     # file, there is no need to check all the ones listed in host
     # configuration each time if they are not used.
-    for item in jobs:
+    for item in [a for a in jobs if "lbowconf" not in a]:
 
         # Have we checked this connection already?
         if jobs[item]["resource"] not in checked:
@@ -160,7 +160,7 @@ def checkconnections(jobs):
 
                     # Go over all jobs referencing this machine and switch on
                     # the environment fix.
-                    for job in jobs:
+                    for job in [a for a in jobs if "lbowconf" not in a]:
 
                         if jobs[job]["resource"] == jobs[item]["resource"]:
 
diff --git a/longbow/staging.py b/longbow/staging.py
index 842cae5..49043f2 100644
--- a/longbow/staging.py
+++ b/longbow/staging.py
@@ -84,7 +84,7 @@ def stage_upstream(jobs):
     """
     LOG.info("Staging files for job/s.")
 
-    for item in jobs:
+    for item in [a for a in jobs if "lbowconf" not in a]:
 
         job = jobs[item]
         destdir = job["destdir"]
@@ -173,7 +173,7 @@ def cleanup(jobs):
     """
     LOG.info("Cleaning up the work directories.")
 
-    for item in jobs:
+    for item in [a for a in jobs if "lbowconf" not in a]:
 
         job = jobs[item]
         destdir = job["destdir"]
@@ -221,11 +221,13 @@ def cleanup(jobs):
 
             pass
 
-    if (jobs[list(jobs.keys())[0]]["recoveryfile"] != "" and
-            os.path.isfile(jobs[list(jobs.keys())[0]]["recoveryfile"])):
+    recfile = jobs["lbowconf"]["recoveryfile"]
+    fpath = os.path.expanduser('~/.longbow')
+
+    if (recfile != "" and os.path.isfile(os.path.join(fpath, recfile))):
 
         LOG.info("Removing the recovery file.")
 
-        os.remove(jobs[list(jobs.keys())[0]]["recoveryfile"])
+        os.remove(os.path.join(fpath, recfile))
 
     LOG.info("Cleaning up complete.")


Test patch:
diff --git a/tests/unit/configuration/test_saveini.py b/tests/unit/configuration/test_saveini.py
index 2cd4a83..d0cd843 100644
--- a/tests/unit/configuration/test_saveini.py
+++ b/tests/unit/configuration/test_saveini.py
@@ -56,3 +56,50 @@ def test_saveini_test1():
     assert open("/tmp/initest", "rb").read() == open(
         os.path.join(os.getcwd(),
                      "tests/standards/saveini.txt"), "rb").read()
+
+
+def test_saveini_test2():
+
+    """
+    A more advanced test checking that the internal configuration data is
+    saved.
+    """
+
+    params = {
+        "lbowconf": {
+            "update": False,
+            "hpc1-queue-max": 0,
+            "hpc1-queue-slots": 0
+        },
+        "job1": {
+            "param1": "val1",
+            "param2": "val2",
+        },
+        "job2": {
+            "parama": "vala",
+            "paramb": "valb",
+        },
+        "job3": {
+            "parami": "vali",
+            "paramii": "valii",
+        }
+    }
+
+    saveini("/tmp/initest2", params)
+
+    with open("/tmp/initest2", "rb") as tmpfile:
+        tmpcontents = tmpfile.read().decode("utf-8")
+
+    assert "[lbowconf]\n" in tmpcontents
+    assert "hpc1-queue-max = 0\n" in tmpcontents
+    assert "update = False\n" in tmpcontents
+    assert "hpc1-queue-slots = 0\n" in tmpcontents
+    assert "[job3]\n" in tmpcontents
+    assert "[job2]\n" in tmpcontents
+    assert "[job1]\n" in tmpcontents
+    assert "param1 = val1\n" in tmpcontents
+    assert "param2 = val2\n" in tmpcontents
+    assert "parama = vala\n" in tmpcontents
+    assert "paramb = valb\n" in tmpcontents
+    assert "parami = vali\n" in tmpcontents
+    assert "paramii = valii\n" in tmpcontents
diff --git a/tests/unit/entrypoints/test_launcher.py b/tests/unit/entrypoints/test_launcher.py
index 1054867..9da91c4 100644
--- a/tests/unit/entrypoints/test_launcher.py
+++ b/tests/unit/entrypoints/test_launcher.py
@@ -47,6 +47,43 @@ except ImportError:
 
 import longbow.exceptions as exceptions
 from longbow.entrypoints import launcher
+from longbow.exceptions import UpdateExit
+
+
+def _configload(_):
+
+    "Mock configuration"
+
+    jobs={"lbowconf": {"recoveryfile": "recovery.file"}}
+
+    return jobs
+
+
+def _runningjobs(_):
+
+    "Set up two running jobs"
+
+    jobs = {"job1": {"laststatus": "Running"}, "job2": {"laststatus": "Running"}}
+
+    return jobs
+
+
+def _finishedjobs(_):
+
+    "Set up two running jobs"
+
+    jobs = {"job1": {"laststatus": "Finished"}, "job2": {"laststatus": "Finished"}}
+
+    return jobs
+
+
+def _completejobs(_):
+
+    "Set up two running jobs"
+
+    jobs = {"job1": {"laststatus": "Complete"}, "job2": {"laststatus": "Complete"}}
+
+    return jobs
 
 
 @mock.patch('longbow.entrypoints.longbow')
@@ -67,7 +104,7 @@ def test_main_test1(m_isfile, m_longbowmain):
 
         launcher()
 
-    params = m_longbowmain.call_args[0][0]
+    params = m_longbowmain.call_args[0][1]
 
     assert m_longbowmain.call_count == 1
     assert params["debug"] is False
@@ -104,7 +141,7 @@ def test_main_test2(m_isfile, m_longbowmain):
 
         launcher()
 
-    params = m_longbowmain.call_args[0][0]
+    params = m_longbowmain.call_args[0][1]
 
     assert m_longbowmain.call_count == 1
     assert params["debug"] is False
@@ -139,15 +176,66 @@ def test_main_test3(m_isfile, m_recovery):
 
         launcher()
 
-    params = m_recovery.call_args[0][0]
+    params = m_recovery.call_args[0][1]
 
     assert m_recovery.call_count == 1
     assert params == "recovery.file"
 
 
 @mock.patch('longbow.entrypoints.longbow')
+@mock.patch('longbow.entrypoints.recovery')
+@mock.patch('longbow.entrypoints.update')
 @mock.patch('os.path.isfile')
-def test_main_test4(m_isfile, m_longbowmain):
+def test_main_test4(m_isfile, m_update, m_recovery, m_longbow):
+
+    """
+    Check that the update method gets called, this is a rudimentary test.
+    """
+
+    m_isfile.return_value = True
+
+    args = ["longbow", "--update", "update.file", "--log", "new-log.file",
+            "--verbose"]
+
+    with mock.patch('sys.argv', args):
+
+        launcher()
+
+    params = m_update.call_args[0][1]
+
+    assert m_longbow.call_count == 0
+    assert m_recovery.call_count == 0
+    assert m_update.call_count == 1
+    assert params == "update.file"
+
+
+@mock.patch('longbow.entrypoints.longbow')
+@mock.patch('longbow.entrypoints.recovery')
+@mock.patch('longbow.entrypoints.update')
+@mock.patch('os.path.isfile')
+def test_main_test5(m_isfile, m_update, m_recovery, m_longbow):
+
+    """
+    Check that longbow doesn't launch if too many args are given.
+    """
+
+    m_isfile.return_value = True
+
+    args = ["longbow", "--recover", "recovery.file", "--update", "update.file",
+            "--log", "new-log.file", "--verbose"]
+
+    with mock.patch('sys.argv', args):
+
+        launcher()
+
+    assert m_longbow.call_count == 0
+    assert m_recovery.call_count == 0
+    assert m_update.call_count == 0
+
+
+@mock.patch('longbow.entrypoints.longbow')
+@mock.patch('os.path.isfile')
+def test_main_test6(m_isfile, m_longbowmain):
 
     """
     Test that exception handling happens properly.
@@ -165,7 +253,7 @@ def test_main_test4(m_isfile, m_longbowmain):
 
         launcher()
 
-    params = m_longbowmain.call_args[0][0]
+    params = m_longbowmain.call_args[0][1]
 
     assert m_longbowmain.call_count == 1
     assert params["debug"] is False
@@ -185,7 +273,7 @@ def test_main_test4(m_isfile, m_longbowmain):
 
 @mock.patch('longbow.entrypoints.longbow')
 @mock.patch('os.path.isfile')
-def test_main_test5(m_isfile, m_longbowmain):
+def test_main_test7(m_isfile, m_longbowmain):
 
     """
     Test that exception handling happens properly.
@@ -203,7 +291,7 @@ def test_main_test5(m_isfile, m_longbowmain):
 
         launcher()
 
-    params = m_longbowmain.call_args[0][0]
+    params = m_longbowmain.call_args[0][1]
 
     assert m_longbowmain.call_count == 1
     assert params["debug"] is True
@@ -224,7 +312,7 @@ def test_main_test5(m_isfile, m_longbowmain):
 @mock.patch('longbow.entrypoints.recovery')
 @mock.patch('longbow.entrypoints.longbow')
 @mock.patch('os.path.isfile')
-def test_main_test6(m_isfile, m_longbowmain, m_recovery):
+def test_main_test8(m_isfile, m_longbowmain, m_recovery):
 
     """
     Test that exception handling happens properly.
@@ -241,3 +329,226 @@ def test_main_test6(m_isfile, m_longbowmain, m_recovery):
 
     assert m_longbowmain.call_count == 0
     assert m_recovery.call_count == 0
+
+
+@mock.patch('longbow.staging.cleanup')
+@mock.patch('longbow.staging.stage_downstream')
+@mock.patch('longbow.scheduling.delete')
+@mock.patch('longbow.scheduling.monitor')
+@mock.patch('longbow.scheduling.submit')
+@mock.patch('longbow.staging.stage_upstream')
+@mock.patch('longbow.scheduling.prepare')
+@mock.patch('longbow.applications.processjobs')
+@mock.patch('longbow.applications.checkapp')
+@mock.patch('longbow.scheduling.checkenv')
+@mock.patch('longbow.shellwrappers.checkconnections')
+@mock.patch('longbow.configuration.processconfigs')
+@mock.patch('os.path.isfile')
+def test_main_test9(m_isfile, m_procconf, m_testcon, m_testenv, m_testapp,
+                    m_procjob, m_schedprep, m_stagup, m_sub, m_mon, m_del,
+                    m_stagdown, m_clean):
+
+    """Test the keyboard interrupt feature with running jobs."""
+
+    m_isfile.return_value = True
+
+    args = ["longbow", "--job", "testjob", "--resource", "big-machine",
+            "--debug"]
+
+    m_procconf.side_effect = _runningjobs
+    m_mon.side_effect = KeyboardInterrupt
+
+    with mock.patch('sys.argv', args):
+
+        launcher()
+
+    assert m_procconf.call_count == 1
+    assert m_testcon.call_count == 1
+    assert m_testenv.call_count == 1
+    assert m_testapp.call_count == 1
+    assert m_procjob.call_count == 1
+    assert m_schedprep.call_count == 1
+    assert m_stagup.call_count == 1
+    assert m_sub.call_count == 1
+    assert m_mon.call_count == 1
+    assert m_del.call_count == 2
+    assert m_stagdown.call_count == 2
+    assert m_clean.call_count == 1
+
+
+@mock.patch('longbow.staging.cleanup')
+@mock.patch('longbow.staging.stage_downstream')
+@mock.patch('longbow.scheduling.delete')
+@mock.patch('longbow.scheduling.monitor')
+@mock.patch('longbow.scheduling.submit')
+@mock.patch('longbow.staging.stage_upstream')
+@mock.patch('longbow.scheduling.prepare')
+@mock.patch('longbow.applications.processjobs')
+@mock.patch('longbow.applications.checkapp')
+@mock.patch('longbow.scheduling.checkenv')
+@mock.patch('longbow.shellwrappers.checkconnections')
+@mock.patch('longbow.configuration.processconfigs')
+@mock.patch('os.path.isfile')
+def test_main_test10(m_isfile, m_procconf, m_testcon, m_testenv, m_testapp,
+                     m_procjob, m_schedprep, m_stagup, m_sub, m_mon, m_del,
+                     m_stagdown, m_clean):
+
+    """Test the keyboard interrupt feature with running jobs."""
+
+    m_isfile.return_value = True
+
+    args = ["longbow", "--job", "testjob", "--resource", "big-machine",
+            "--debug"]
+
+    m_procconf.side_effect = _finishedjobs
+    m_mon.side_effect = KeyboardInterrupt
+
+    with mock.patch('sys.argv', args):
+
+        launcher()
+
+    assert m_procconf.call_count == 1
+    assert m_testcon.call_count == 1
+    assert m_testenv.call_count == 1
+    assert m_testapp.call_count == 1
+    assert m_procjob.call_count == 1
+    assert m_schedprep.call_count == 1
+    assert m_stagup.call_count == 1
+    assert m_sub.call_count == 1
+    assert m_mon.call_count == 1
+    assert m_del.call_count == 0
+    assert m_stagdown.call_count == 2
+    assert m_clean.call_count == 1
+
+
+@mock.patch('longbow.staging.cleanup')
+@mock.patch('longbow.staging.stage_downstream')
+@mock.patch('longbow.scheduling.delete')
+@mock.patch('longbow.scheduling.monitor')
+@mock.patch('longbow.scheduling.submit')
+@mock.patch('longbow.staging.stage_upstream')
+@mock.patch('longbow.scheduling.prepare')
+@mock.patch('longbow.applications.processjobs')
+@mock.patch('longbow.applications.checkapp')
+@mock.patch('longbow.scheduling.checkenv')
+@mock.patch('longbow.shellwrappers.checkconnections')
+@mock.patch('longbow.configuration.processconfigs')
+@mock.patch('os.path.isfile')
+def test_main_test11(m_isfile, m_procconf, m_testcon, m_testenv, m_testapp,
+                     m_procjob, m_schedprep, m_stagup, m_sub, m_mon, m_del,
+                     m_stagdown, m_clean):
+
+    """Test the keyboard interrupt feature with complete jobs."""
+
+    m_isfile.return_value = True
+
+    args = ["longbow", "--job", "testjob", "--resource", "big-machine",
+            "--debug"]
+
+    m_procconf.side_effect = _completejobs
+    m_mon.side_effect = KeyboardInterrupt
+
+    with mock.patch('sys.argv', args):
+
+        launcher()
+
+    assert m_procconf.call_count == 1
+    assert m_testcon.call_count == 1
+    assert m_testenv.call_count == 1
+    assert m_testapp.call_count == 1
+    assert m_procjob.call_count == 1
+    assert m_schedprep.call_count == 1
+    assert m_stagup.call_count == 1
+    assert m_sub.call_count == 1
+    assert m_mon.call_count == 1
+    assert m_del.call_count == 0
+    assert m_stagdown.call_count == 2
+    assert m_clean.call_count == 1
+
+
+@mock.patch('longbow.staging.cleanup')
+@mock.patch('longbow.staging.stage_downstream')
+@mock.patch('longbow.scheduling.delete')
+@mock.patch('longbow.scheduling.monitor')
+@mock.patch('longbow.scheduling.submit')
+@mock.patch('longbow.staging.stage_upstream')
+@mock.patch('longbow.scheduling.prepare')
+@mock.patch('longbow.applications.processjobs')
+@mock.patch('longbow.applications.checkapp')
+@mock.patch('longbow.scheduling.checkenv')
+@mock.patch('longbow.shellwrappers.checkconnections')
+@mock.patch('longbow.configuration.processconfigs')
+@mock.patch('os.path.isfile')
+def test_main_test12(m_isfile, m_procconf, m_testcon, m_testenv, m_testapp,
+                     m_procjob, m_schedprep, m_stagup, m_sub, m_mon, m_del,
+                     m_stagdown, m_clean):
+
+    """Test the disconnect feature with complete jobs."""
+
+    m_isfile.return_value = True
+
+    args = ["longbow", "--job", "testjob", "--disconnect", "--debug"]
+
+    m_procconf.side_effect = _configload
+
+    with mock.patch('sys.argv', args):
+
+        launcher()
+
+    assert m_procconf.call_count == 1
+    assert m_testcon.call_count == 1
+    assert m_testenv.call_count == 1
+    assert m_testapp.call_count == 1
+    assert m_procjob.call_count == 1
+    assert m_schedprep.call_count == 1
+    assert m_stagup.call_count == 1
+    assert m_sub.call_count == 1
+    assert m_mon.call_count == 0
+    assert m_del.call_count == 0
+    assert m_stagdown.call_count == 0
+    assert m_clean.call_count == 0
+
+
+@mock.patch('longbow.staging.cleanup')
+@mock.patch('longbow.staging.stage_downstream')
+@mock.patch('longbow.scheduling.delete')
+@mock.patch('longbow.scheduling.monitor')
+@mock.patch('longbow.scheduling.submit')
+@mock.patch('longbow.staging.stage_upstream')
+@mock.patch('longbow.scheduling.prepare')
+@mock.patch('longbow.applications.processjobs')
+@mock.patch('longbow.applications.checkapp')
+@mock.patch('longbow.scheduling.checkenv')
+@mock.patch('longbow.shellwrappers.checkconnections')
+@mock.patch('longbow.configuration.processconfigs')
+@mock.patch('os.path.isfile')
+def test_main_test13(m_isfile, m_procconf, m_testcon, m_testenv, m_testapp,
+                     m_procjob, m_schedprep, m_stagup, m_sub, m_mon, m_del,
+                     m_stagdown, m_clean):
+
+    """Test the keyboard interrupt feature with complete jobs."""
+
+    m_isfile.return_value = True
+
+    args = ["longbow", "--job", "testjob", "--resource", "big-machine",
+            "--debug"]
+
+    m_procconf.side_effect = _configload
+    m_mon.side_effect = UpdateExit
+
+    with mock.patch('sys.argv', args):
+
+        launcher()
+
+    assert m_procconf.call_count == 1
+    assert m_testcon.call_count == 1
+    assert m_testenv.call_count == 1
+    assert m_testapp.call_count == 1
+    assert m_procjob.call_count == 1
+    assert m_schedprep.call_count == 1
+    assert m_stagup.call_count == 1
+    assert m_sub.call_count == 1
+    assert m_mon.call_count == 1
+    assert m_del.call_count == 0
+    assert m_stagdown.call_count == 0
+    assert m_clean.call_count == 0
diff --git a/tests/unit/entrypoints/test_longbow.py b/tests/unit/entrypoints/test_longbow.py
index 5493f68..89b49e8 100644
--- a/tests/unit/entrypoints/test_longbow.py
+++ b/tests/unit/entrypoints/test_longbow.py
@@ -43,6 +43,8 @@ except ImportError:
 
     import mock
 
+import pytest
+import longbow.exceptions as exceptions
 from longbow.entrypoints import longbow
 
 
@@ -69,7 +71,8 @@ def test_longbowmain_disconnect(m_procconf, m_testcon, m_testenv, m_testapp,
         "nochecks": False
         }
 
-    longbow(params)
+    with pytest.raises(exceptions.DisconnectException):
+        longbow({}, params)
 
     assert m_procconf.call_count == 1
     assert m_testcon.call_count == 1
@@ -106,7 +109,7 @@ def test_longbowmain_testcalls1(m_procconf, m_testcon, m_testenv, m_testapp,
         "nochecks": False
         }
 
-    longbow(params)
+    longbow({}, params)
 
     assert m_procconf.call_count == 1
     assert m_testcon.call_count == 1
@@ -144,7 +147,7 @@ def test_longbowmain_testcalls2(m_procconf, m_testcon, m_testenv, m_testapp,
         "nochecks": True
         }
 
-    longbow(params)
+    longbow({}, params)
 
     assert m_procconf.call_count == 1
     assert m_testcon.call_count == 1
@@ -157,108 +160,3 @@ def test_longbowmain_testcalls2(m_procconf, m_testcon, m_testenv, m_testapp,
     assert m_mon.call_count == 1
     assert m_clean.call_count == 1
 
-
-@mock.patch('longbow.staging.cleanup')
-@mock.patch('longbow.staging.stage_downstream')
-@mock.patch('longbow.scheduling.delete')
-@mock.patch('longbow.scheduling.monitor')
-@mock.patch('longbow.scheduling.submit')
-@mock.patch('longbow.staging.stage_upstream')
-@mock.patch('longbow.scheduling.prepare')
-@mock.patch('longbow.applications.processjobs')
-@mock.patch('longbow.applications.checkapp')
-@mock.patch('longbow.scheduling.checkenv')
-@mock.patch('longbow.shellwrappers.checkconnections')
-@mock.patch('longbow.configuration.processconfigs')
-def test_longbowmain_killrunning(m_procconf, m_testcon, m_testenv, m_testapp,
-                                 m_procjob, m_schedprep, m_stagup, m_sub,
-                                 m_mon, m_del, m_stagdown, m_clean):
-
-    """
-    Check that the correct function calls are made.
-    """
-
-    params = {
-        "hosts": "some/file",
-        "disconnect": False,
-        "nochecks": False
-        }
-
-    m_procconf.return_value = {
-        "job1": {
-            "laststatus": "Running"
-            },
-        "job2": {
-            "laststatus": "Running"
-            }
-        }
-
-    m_mon.side_effect = KeyboardInterrupt
-
-    longbow(params)
-
-    assert m_procconf.call_count == 1
-    assert m_testcon.call_count == 1
-    assert m_testenv.call_count == 1
-    assert m_testapp.call_count == 1
-    assert m_procjob.call_count == 1
-    assert m_schedprep.call_count == 1
-    assert m_stagup.call_count == 1
-    assert m_sub.call_count == 1
-    assert m_mon.call_count == 1
-    assert m_del.call_count == 2
-    assert m_stagdown.call_count == 2
-    assert m_clean.call_count == 1
-
-
-@mock.patch('longbow.staging.cleanup')
-@mock.patch('longbow.staging.stage_downstream')
-@mock.patch('longbow.scheduling.delete')
-@mock.patch('longbow.scheduling.monitor')
-@mock.patch('longbow.scheduling.submit')
-@mock.patch('longbow.staging.stage_upstream')
-@mock.patch('longbow.scheduling.prepare')
-@mock.patch('longbow.applications.processjobs')
-@mock.patch('longbow.applications.checkapp')
-@mock.patch('longbow.scheduling.checkenv')
-@mock.patch('longbow.shellwrappers.checkconnections')
-@mock.patch('longbow.configuration.processconfigs')
-def test_longbowmain_killcomplete(m_procconf, m_testcon, m_testenv, m_testapp,
-                                  m_procjob, m_schedprep, m_stagup, m_sub,
-                                  m_mon, m_del, m_stagdown, m_clean):
-
-    """
-    Check that the correct function calls are made.
-    """
-
-    params = {
-        "hosts": "some/file",
-        "disconnect": False,
-        "nochecks": False
-        }
-
-    m_procconf.return_value = {
-        "job1": {
-            "laststatus": "Complete"
-            },
-        "job2": {
-            "laststatus": "Complete"
-            }
-        }
-
-    m_mon.side_effect = KeyboardInterrupt
-
-    longbow(params)
-
-    assert m_procconf.call_count == 1
-    assert m_testcon.call_count == 1
-    assert m_testenv.call_count == 1
-    assert m_testapp.call_count == 1
-    assert m_procjob.call_count == 1
-    assert m_schedprep.call_count == 1
-    assert m_stagup.call_count == 1
-    assert m_sub.call_count == 1
-    assert m_mon.call_count == 1
-    assert m_del.call_count == 0
-    assert m_stagdown.call_count == 2
-    assert m_clean.call_count == 1
diff --git a/tests/unit/entrypoints/test_recovery.py b/tests/unit/entrypoints/test_recovery.py
index d7bf881..3a36404 100644
--- a/tests/unit/entrypoints/test_recovery.py
+++ b/tests/unit/entrypoints/test_recovery.py
@@ -60,12 +60,12 @@ def test_recovery_check(mock_file, mock_mon, mock_clean, mock_load):
     """
 
     mock_file.return_value = True
-    mock_load.return_value = ("", "", "testjobs")
+    mock_load.return_value = ("", "", {"testjobs": {}})
 
-    recovery("recovery.file")
+    recovery({}, "recovery.file")
 
-    assert mock_mon.call_args[0][0] == "testjobs"
-    assert mock_clean.call_args[0][0] == "testjobs"
+    assert mock_mon.call_args[0][0] == {"testjobs": {}}
+    assert mock_clean.call_args[0][0] == {"testjobs": {}}
 
 
 @mock.patch('longbow.staging.cleanup')
@@ -83,84 +83,5 @@ def test_recovery_except(mock_isfile, mock_monitor, mock_cleanup):
 
     with pytest.raises(exceptions.RequiredinputError):
 
-        recovery("recovery.file")
+        recovery({}, "recovery.file")
 
-
-@mock.patch('longbow.staging.stage_downstream')
-@mock.patch('longbow.scheduling.delete')
-@mock.patch('longbow.configuration.loadconfigs')
-@mock.patch('longbow.staging.cleanup')
-@mock.patch('longbow.scheduling.monitor')
-@mock.patch('os.path.isfile')
-def test_recovery_interupt(m_file, m_mon, m_clean, m_load, m_del, m_down):
-
-    """
-    Check that user interrupt doesn't result in unhandled exception.
-    """
-
-    m_file.return_value = True
-    m_mon.side_effect = KeyboardInterrupt
-    m_clean.return_value = None
-    m_load.return_value = ("", "", {"testjobs": {}})
-    m_del.return_value = None
-    m_down.return_value = None
-
-    recovery("recovery.file")
-
-    assert m_clean.call_count == 1
-    assert m_down.call_count == 0
-    assert m_del.call_count == 0
-
-
-@mock.patch('longbow.staging.stage_downstream')
-@mock.patch('longbow.scheduling.delete')
-@mock.patch('longbow.configuration.loadconfigs')
-@mock.patch('longbow.staging.cleanup')
-@mock.patch('longbow.scheduling.monitor')
-@mock.patch('os.path.isfile')
-def test_recovery_interupt2(m_file, m_mon, m_clean, m_load, m_del, m_down):
-
-    """
-    Check that user interrupt doesn't result in unhandled exception. Also test
-    the case where jobs are running.
-    """
-
-    m_file.return_value = True
-    m_mon.side_effect = KeyboardInterrupt
-    m_clean.return_value = None
-    m_load.return_value = ("", "", {"testjobs": {"laststatus": "Running"}})
-    m_del.return_value = None
-    m_down.return_value = None
-
-    recovery("recovery.file")
-
-    assert m_clean.call_count == 1
-    assert m_down.call_count == 1
-    assert m_del.call_count == 1
-
-
-@mock.patch('longbow.staging.stage_downstream')
-@mock.patch('longbow.scheduling.delete')
-@mock.patch('longbow.configuration.loadconfigs')
-@mock.patch('longbow.staging.cleanup')
-@mock.patch('longbow.scheduling.monitor')
-@mock.patch('os.path.isfile')
-def test_recovery_interupt3(m_file, m_mon, m_clean, m_load, m_del, m_down):
-
-    """
-    Check that user interrupt doesn't result in unhandled exception. Also test
-    the case where jobs are finished.
-    """
-
-    m_file.return_value = True
-    m_mon.side_effect = KeyboardInterrupt
-    m_clean.return_value = None
-    m_load.return_value = ("", "", {"testjobs": {"laststatus": "Complete"}})
-    m_del.return_value = None
-    m_down.return_value = None
-
-    recovery("recovery.file")
-
-    assert m_clean.call_count == 1
-    assert m_down.call_count == 1
-    assert m_del.call_count == 0
diff --git a/tests/unit/entrypoints/test_update.py b/tests/unit/entrypoints/test_update.py
new file mode 100644
index 0000000..ee65629
--- /dev/null
+++ b/tests/unit/entrypoints/test_update.py
@@ -0,0 +1,87 @@
+# BSD 3-Clause License
+#
+# Copyright (c) 2017, Science and Technology Facilities Council and
+# The University of Nottingham
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice, this
+#   list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright notice,
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.
+#
+# * Neither the name of the copyright holder nor the names of its
+#   contributors may be used to endorse or promote products derived from
+#   this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+"""
+This testing module contains the tests for the recovery method within the
+entrypoint module.
+"""
+
+try:
+
+    from unittest import mock
+
+except ImportError:
+
+    import mock
+
+import pytest
+
+import longbow.exceptions as exceptions
+from longbow.entrypoints import update
+
+
+@mock.patch('longbow.staging.cleanup')
+@mock.patch('longbow.configuration.loadconfigs')
+@mock.patch('longbow.scheduling.monitor')
+@mock.patch('os.path.isfile')
+def test_update_check(mock_file, mock_mon, mock_load, m_clean):
+
+    """
+    Check that the correct function calls are made.
+    """
+
+    mock_file.return_value = True
+    mock_load.return_value = ("", "", {
+        "testparam": "test", "lbowconf": {"update": False}})
+
+    update({}, "update.file")
+
+    params = mock_mon.call_args[0][0]
+
+    assert params["testparam"] == "test"
+    assert params["lbowconf"]["update"] is True
+
+
+@mock.patch('longbow.scheduling.monitor')
+@mock.patch('os.path.isfile')
+def test_recovery_except(mock_isfile, mock_monitor):
+
+    """
+    Check that exception is raised on bad file.
+    """
+
+    mock_isfile.return_value = False
+    mock_monitor.return_value = None
+
+    with pytest.raises(exceptions.RequiredinputError):
+
+        update({}, "update.file")
diff --git a/tests/unit/scheduling/test_checkwaitingjobs.py b/tests/unit/scheduling/test_checkwaitingjobs.py
index f760780..36c33b6 100644
--- a/tests/unit/scheduling/test_checkwaitingjobs.py
+++ b/tests/unit/scheduling/test_checkwaitingjobs.py
@@ -46,7 +46,7 @@ except ImportError:
 import pytest
 
 import longbow.exceptions as exceptions
-from longbow.scheduling import _checkwaitingjobs, QUEUEINFO
+from longbow.scheduling import _checkwaitingjobs
 
 
 def addjobid(job):
@@ -67,13 +67,16 @@ def test_checkwaitingjobs_none(mock_submit):
 
     jobs = {
         "jobone": {
-            "laststatus": "Running"
+            "laststatus": "Running",
+            "resource": "hpc-1"
         },
         "jobtwo": {
-            "laststatus": "Finished"
+            "laststatus": "Finished",
+            "resource": "hpc-1"
         },
         "jobthree": {
-            "laststatus": "Complete"
+            "laststatus": "Complete",
+            "resource": "hpc-1"
         }
     }
 
@@ -91,6 +94,10 @@ def test_checkwaitingjobs_one(mock_submit):
     """
 
     jobs = {
+        "lbowconf": {
+            "test-machine-queue-slots": 1,
+            "test-machine-queue-max": 2
+        },
         "jobone": {
             "laststatus": "Running",
             "resource": "test-machine",
@@ -109,14 +116,11 @@ def test_checkwaitingjobs_one(mock_submit):
     }
 
     mock_submit.side_effect = addjobid
-    QUEUEINFO["test-machine"] = {}
-    QUEUEINFO["test-machine"]["queue-slots"] = 1
-    QUEUEINFO["test-machine"]["queue-max"] = 2
 
     _checkwaitingjobs(jobs, False)
 
     assert mock_submit.call_count == 1, "Should be submitting one job"
-    assert QUEUEINFO["test-machine"]["queue-slots"] == "2"
+    assert jobs["lbowconf"]["test-machine-queue-slots"] == "2"
 
 
 @mock.patch('longbow.schedulers.lsf.submit')
@@ -128,6 +132,10 @@ def test_checkwaitingjobs_two(mock_submit):
     """
 
     jobs = {
+        "lbowconf": {
+            "test-machine-queue-slots": 1,
+            "test-machine-queue-max": 8
+        },
         "jobone": {
             "laststatus": "Running",
             "resource": "test-machine",
@@ -146,13 +154,11 @@ def test_checkwaitingjobs_two(mock_submit):
     }
 
     mock_submit.side_effect = addjobid
-    QUEUEINFO["test-machine"]["queue-slots"] = 1
-    QUEUEINFO["test-machine"]["queue-max"] = 8
 
     _checkwaitingjobs(jobs, False)
 
     assert mock_submit.call_count == 2, "Should be submitting two jobs"
-    assert QUEUEINFO["test-machine"]["queue-slots"] == "3"
+    assert jobs["lbowconf"]["test-machine-queue-slots"] == "3"
 
 
 @mock.patch('longbow.schedulers.lsf.submit')
@@ -163,6 +169,10 @@ def test_checkwaitingjobs_except1(mock_submit):
     """
 
     jobs = {
+        "lbowconf": {
+            "test-machine-queue-slots": 1,
+            "test-machine-queue-max": 8
+        },
         "jobone": {
             "laststatus": "Running",
             "resource": "test-machine",
@@ -181,8 +191,6 @@ def test_checkwaitingjobs_except1(mock_submit):
     }
 
     mock_submit.side_effect = AttributeError
-    QUEUEINFO["test-machine"]["queue-slots"] = 1
-    QUEUEINFO["test-machine"]["queue-max"] = 8
 
     with pytest.raises(exceptions.PluginattributeError):
 
@@ -198,6 +206,10 @@ def test_checkwaitingjobs_except2(mock_submit):
     """
 
     jobs = {
+        "lbowconf": {
+            "test-machine-queue-slots": 1,
+            "test-machine-queue-max": 8
+        },
         "jobone": {
             "laststatus": "Running",
             "resource": "test-machine",
@@ -216,8 +228,6 @@ def test_checkwaitingjobs_except2(mock_submit):
     }
 
     mock_submit.side_effect = exceptions.JobsubmitError
-    QUEUEINFO["test-machine"]["queue-slots"] = 1
-    QUEUEINFO["test-machine"]["queue-max"] = 8
 
     _checkwaitingjobs(jobs, False)
 
@@ -235,6 +245,10 @@ def test_checkwaitingjobs_except3(mock_submit):
     """
 
     jobs = {
+        "lbowconf": {
+            "test-machine-queue-slots": 1,
+            "test-machine-queue-max": 8
+        },
         "jobone": {
             "laststatus": "Running",
             "resource": "test-machine",
@@ -253,8 +267,6 @@ def test_checkwaitingjobs_except3(mock_submit):
     }
 
     mock_submit.side_effect = exceptions.QueuemaxError
-    QUEUEINFO["test-machine"]["queue-slots"] = "1"
-    QUEUEINFO["test-machine"]["queue-max"] = "8"
 
     _checkwaitingjobs(jobs, False)
 
diff --git a/tests/unit/scheduling/test_monitor.py b/tests/unit/scheduling/test_monitor.py
index 2b2def8..513647d 100644
--- a/tests/unit/scheduling/test_monitor.py
+++ b/tests/unit/scheduling/test_monitor.py
@@ -46,7 +46,7 @@ except ImportError:
 import pytest
 
 import longbow.exceptions as exceptions
-from longbow.scheduling import monitor, QUEUEINFO
+from longbow.scheduling import monitor
 
 
 def jobstatus(jobs, _):
@@ -55,13 +55,13 @@ def jobstatus(jobs, _):
     Change status of the job
     """
 
-    for job in jobs:
+    for job in [a for a in jobs if "lbowconf" not in a]:
 
         if jobs[job]["laststatus"] == "Queued":
 
             jobs[job]["laststatus"] = "Running"
 
-        if jobs[job]["laststatus"] == "Running":
+        elif jobs[job]["laststatus"] == "Running":
 
             jobs[job]["laststatus"] = "Finished"
 
@@ -78,17 +78,16 @@ def test_monitor_testpollfrequency(mock_init, mock_poll, mock_wait):
     import time
 
     jobs = {
+        "lbowconf": {
+            "recoveryfile": "recovery-YYMMDD-HHMMSS",
+            "hpc1-queue-slots": 1,
+            "hpc1-queue-max": 2
+        },
         "jobone": {
             "resource": "hpc1",
-            "queue-max": "0",
-            "queue-slots": "0",
-            "laststatus": "Running",
-            "recoveryfile": "recovery-YYMMDD-HHMMSS"
+            "laststatus": "Running"
         }
     }
-    QUEUEINFO["hpc1"] = {}
-    QUEUEINFO["hpc1"]["queue-slots"] = "1"
-    QUEUEINFO["hpc1"]["queue-max"] = "2"
 
     mock_init.return_value = 0, 2
     mock_poll.return_value = False
@@ -120,17 +119,16 @@ def test_monitor_teststagefreq(mock_init, mock_poll, mock_wait, mock_down):
     import time
 
     jobs = {
+        "lbowconf": {
+            "recoveryfile": "recovery-YYMMDD-HHMMSS",
+            "hpc1-queue-slots": 1,
+            "hpc1-queue-max": 2
+        },
         "jobone": {
             "resource": "hpc1",
-            "queue-max": "0",
-            "queue-slots": "0",
-            "laststatus": "Running",
-            "recoveryfile": "recovery-YYMMDD-HHMMSS"
+            "laststatus": "Running"
         }
     }
-    QUEUEINFO["hpc1"] = {}
-    QUEUEINFO["hpc1"]["queue-slots"] = "1"
-    QUEUEINFO["hpc1"]["queue-max"] = "2"
 
     mock_init.return_value = 1, 1
     mock_poll.return_value = False
@@ -164,33 +162,25 @@ def test_monitor_complete1(mock_init, mock_poll, mock_wait, mock_down,
     """
 
     jobs = {
+        "lbowconf": {
+            "recoveryfile": "recovery-YYMMDD-HHMMSS",
+            "hpc1-queue-slots": 1,
+            "hpc1-queue-max": 2
+        },
         "jobone": {
             "resource": "hpc1",
-            "queue-max": "0",
-            "queue-slots": "0",
-            "laststatus": "Finished",
-            "recoveryfile": "recovery-YYMMDD-HHMMSS"
+            "laststatus": "Finished"
         },
         "jobtwo": {
             "resource": "hpc1",
-            "queue-max": "0",
-            "queue-slots": "0",
-            "laststatus": "Complete",
-            "recoveryfile": "recovery-YYMMDD-HHMMSS"
+            "laststatus": "Complete"
         },
         "jobthree": {
             "resource": "hpc1",
-            "queue-max": "0",
-            "queue-slots": "0",
-            "laststatus": "Submit Error",
-            "recoveryfile": "recovery-YYMMDD-HHMMSS"
+            "laststatus": "Submit Error"
         }
     }
 
-    QUEUEINFO["hpc1"] = {}
-    QUEUEINFO["hpc1"]["queue-slots"] = "1"
-    QUEUEINFO["hpc1"]["queue-max"] = "2"
-
     mock_init.return_value = 0, 1
     mock_poll.return_value = False
     mock_wait.return_value = False
@@ -218,47 +208,33 @@ def test_monitor_complete2(mock_init, mock_poll, mock_wait, mock_down,
     """
 
     jobs = {
+        "lbowconf": {
+            "recoveryfile": "recovery-YYMMDD-HHMMSS",
+            "hpc1-queue-slots": 1,
+            "hpc1-queue-max": 2
+        },
         "jobone": {
             "resource": "hpc1",
-            "queue-max": "0",
-            "queue-slots": "0",
-            "laststatus": "Finished",
-            "recoveryfile": "recovery-YYMMDD-HHMMSS"
+            "laststatus": "Finished"
         },
         "jobtwo": {
             "resource": "hpc1",
-            "queue-max": "0",
-            "queue-slots": "0",
-            "laststatus": "Complete",
-            "recoveryfile": "recovery-YYMMDD-HHMMSS"
+            "laststatus": "Complete"
         },
         "jobthree": {
             "resource": "hpc1",
-            "queue-max": "0",
-            "queue-slots": "0",
-            "laststatus": "Submit Error",
-            "recoveryfile": "recovery-YYMMDD-HHMMSS"
+            "laststatus": "Submit Error"
         },
         "jobfour": {
             "resource": "hpc1",
-            "queue-max": "0",
-            "queue-slots": "0",
-            "laststatus": "Queued",
-            "recoveryfile": "recovery-YYMMDD-HHMMSS"
+            "laststatus": "Queued"
         },
         "jobfive": {
             "resource": "hpc1",
-            "queue-max": "0",
-            "queue-slots": "0",
-            "laststatus": "Running",
-            "recoveryfile": "recovery-YYMMDD-HHMMSS"
+            "laststatus": "Running"
         }
     }
 
-    QUEUEINFO["hpc1"] = {}
-    QUEUEINFO["hpc1"]["queue-slots"] = "1"
-    QUEUEINFO["hpc1"]["queue-max"] = "2"
-
     mock_init.return_value = 0, 1
     mock_poll.return_value = False
     mock_poll.side_effect = jobstatus
@@ -290,47 +266,33 @@ def test_monitor_run1(mock_init, mock_poll, mock_wait, mock_down,
     """
 
     jobs = {
+        "lbowconf": {
+            "recoveryfile": "recovery-YYMMDD-HHMMSS",
+            "hpc1-queue-slots": 1,
+            "hpc1-queue-max": 2
+        },
         "jobone": {
             "resource": "hpc1",
-            "queue-max": "0",
-            "queue-slots": "0",
-            "laststatus": "Running",
-            "recoveryfile": "recovery-YYMMDD-HHMMSS"
+            "laststatus": "Running"
         },
         "jobtwo": {
             "resource": "hpc1",
-            "queue-max": "0",
-            "queue-slots": "0",
-            "laststatus": "Running",
-            "recoveryfile": "recovery-YYMMDD-HHMMSS"
+            "laststatus": "Running"
         },
         "jobthree": {
             "resource": "hpc1",
-            "queue-max": "0",
-            "queue-slots": "0",
-            "laststatus": "Queued",
-            "recoveryfile": "recovery-YYMMDD-HHMMSS"
+            "laststatus": "Queued"
         },
         "jobfour": {
             "resource": "hpc1",
-            "queue-max": "0",
-            "queue-slots": "0",
-            "laststatus": "Queued",
-            "recoveryfile": "recovery-YYMMDD-HHMMSS"
+            "laststatus": "Queued"
         },
         "jobfive": {
             "resource": "hpc1",
-            "queue-max": "0",
-            "queue-slots": "0",
-            "laststatus": "Queued",
-            "recoveryfile": "recovery-YYMMDD-HHMMSS"
+            "laststatus": "Queued"
         }
     }
 
-    QUEUEINFO["hpc1"] = {}
-    QUEUEINFO["hpc1"]["queue-slots"] = "1"
-    QUEUEINFO["hpc1"]["queue-max"] = "2"
-
     mock_init.return_value = 0, 1
     mock_poll.return_value = False
     mock_poll.side_effect = jobstatus
@@ -363,31 +325,24 @@ def test_monitor_except(mock_init, mock_poll, mock_wait, mock_down,
     """
 
     jobs = {
+        "lbowconf": {
+            "recoveryfile": "recovery-YYMMDD-HHMMSS",
+            "hpc1-queue-slots": 1,
+            "hpc1-queue-max": 2
+        },
         "jobone": {
             "resource": "hpc1",
-            "queue-max": "0",
-            "queue-slots": "0",
-            "laststatus": "Finished",
-            "recoveryfile": "recovery-YYMMDD-HHMMSS"
+            "laststatus": "Finished"
         },
         "jobtwo": {
             "resource": "hpc1",
-            "queue-max": "0",
-            "queue-slots": "0",
-            "laststatus": "Complete",
-            "recoveryfile": "recovery-YYMMDD-HHMMSS"
+            "laststatus": "Complete"
         },
         "jobthree": {
             "resource": "hpc1",
-            "queue-max": "0",
-            "queue-slots": "0",
-            "laststatus": "Submit Error",
-            "recoveryfile": "recovery-YYMMDD-HHMMSS"
+            "laststatus": "Submit Error"
         }
     }
-    QUEUEINFO["hpc1"] = {}
-    QUEUEINFO["hpc1"]["queue-slots"] = "1"
-    QUEUEINFO["hpc1"]["queue-max"] = "2"
 
     mock_init.return_value = 0, 1
     mock_poll.return_value = False
@@ -399,3 +354,64 @@ def test_monitor_except(mock_init, mock_poll, mock_wait, mock_down,
 
     assert jobs["jobone"]["laststatus"] == "Complete"
     assert mock_save.call_count == 1
+
+
+@mock.patch('longbow.configuration.saveini')
+@mock.patch('longbow.staging.stage_downstream')
+@mock.patch('longbow.scheduling._checkwaitingjobs')
+@mock.patch('longbow.scheduling._polljobs')
+@mock.patch('longbow.scheduling._monitorinitialise')
+def test_monitor_update(mock_init, mock_poll, mock_wait, mock_down, mock_save):
+
+    """
+    Test that when all jobs complete the method exits.
+    """
+
+    jobs = {
+        "lbowconf": {
+            "update": True,
+            "recoveryfile": "recovery-YYMMDD-HHMMSS",
+            "hpc1-queue-slots": 2,
+            "hpc1-queue-max": 8
+        },
+        "jobone": {
+            "resource": "hpc1",
+            "laststatus": "Running"
+        },
+        "jobtwo": {
+            "resource": "hpc1",
+            "laststatus": "Running"
+        },
+        "jobthree": {
+            "resource": "hpc1",
+            "laststatus": "Queued"
+        },
+        "jobfour": {
+            "resource": "hpc1",
+            "laststatus": "Queued"
+        },
+        "jobfive": {
+            "resource": "hpc1",
+            "laststatus": "Queued"
+        }
+    }
+
+    mock_init.return_value = 0, 1
+    mock_poll.return_value = True
+    mock_poll.side_effect = jobstatus
+    mock_wait.return_value = True
+
+    with pytest.raises(exceptions.UpdateExit):
+
+        monitor(jobs)
+
+    assert jobs["lbowconf"]["update"] is False
+    assert jobs["jobone"]["laststatus"] == "Finished"
+    assert jobs["jobtwo"]["laststatus"] == "Finished"
+    assert jobs["jobthree"]["laststatus"] == "Running"
+    assert jobs["jobfour"]["laststatus"] == "Running"
+    assert jobs["jobfive"]["laststatus"] == "Running"
+    assert mock_poll.call_count == 1
+    assert mock_wait.call_count == 1
+    assert mock_down.call_count == 0
+    assert mock_save.call_count == 1
diff --git a/tests/unit/scheduling/test_polljobs.py b/tests/unit/scheduling/test_polljobs.py
index cf0e30d..582e122 100644
--- a/tests/unit/scheduling/test_polljobs.py
+++ b/tests/unit/scheduling/test_polljobs.py
@@ -46,7 +46,7 @@ except ImportError:
 import pytest
 
 import longbow.exceptions as exceptions
-from longbow.scheduling import _polljobs, QUEUEINFO
+from longbow.scheduling import _polljobs
 
 
 @mock.patch('longbow.schedulers.lsf.status')
@@ -106,6 +106,10 @@ def test_polljobs_finished(mock_status):
     """
 
     jobs = {
+        "lbowconf": {
+            "test-machine-queue-slots": 2,
+            "test-machine-queue-max": 4
+        },
         "jobone": {
             "resource": "test-machine",
             "laststatus": "Running",
@@ -144,7 +148,6 @@ def test_polljobs_finished(mock_status):
         }
     }
 
-    QUEUEINFO["test-machine"]["queue-slots"] = "2"
     mock_status.return_value = "Finished"
     _polljobs(jobs, False)
 
@@ -152,7 +155,7 @@ def test_polljobs_finished(mock_status):
         "Should only be polling running and queued jobs"
     assert jobs["jobone"]["laststatus"] == "Finished"
     assert jobs["jobtwo"]["laststatus"] == "Finished"
-    assert QUEUEINFO["test-machine"]["queue-slots"] == "0"
+    assert jobs["lbowconf"]["test-machine-queue-slots"] == "0"
 
 
 @mock.patch('longbow.schedulers.lsf.status')
diff --git a/tests/unit/scheduling/test_prepare.py b/tests/unit/scheduling/test_prepare.py
index 8a69f3e..dff69d5 100644
--- a/tests/unit/scheduling/test_prepare.py
+++ b/tests/unit/scheduling/test_prepare.py
@@ -141,8 +141,7 @@ def test_prepare_ownscript(mock_prepare):
             "resource": "test-machine",
             "scheduler": "LSF",
             "jobid": "test456",
-            "subfile": "test.lsf",
-            "upload-include": "file1, file2, file3"
+            "subfile": "test.lsf"
         }
     }
 
@@ -150,4 +149,3 @@ def test_prepare_ownscript(mock_prepare):
 
     assert mock_prepare.call_count == 0, \
         "This method shouldn't be called at all in this case."
-    assert jobs["job-one"]["upload-include"] == "file1, file2, file3, test.lsf"
diff --git a/tests/unit/scheduling/test_submit.py b/tests/unit/scheduling/test_submit.py
index f12b6f0..aaac87b 100644
--- a/tests/unit/scheduling/test_submit.py
+++ b/tests/unit/scheduling/test_submit.py
@@ -46,7 +46,7 @@ except ImportError:
 import pytest
 
 import longbow.exceptions as exceptions
-from longbow.scheduling import submit, QUEUEINFO
+from longbow.scheduling import submit
 
 
 @mock.patch('longbow.schedulers.lsf.submit')
@@ -58,6 +58,7 @@ def test_submit_single(mock_isdir, mock_submit):
     """
 
     jobs = {
+        "lbowconf": {},
         "job-one": {
             "resource": "test-machine",
             "scheduler": "LSF",
@@ -83,6 +84,7 @@ def test_submit_multiplesame(mock_isdir, mock_lsf):
     """
 
     jobs = {
+        "lbowconf": {},
         "job-one": {
             "resource": "test-machine",
             "scheduler": "LSF",
@@ -119,6 +121,7 @@ def test_submit_multiplediff(mock_isdir, mock_lsf, mock_pbs, mock_slurm):
     """
 
     jobs = {
+        "lbowconf": {},
         "job-one": {
             "resource": "lsf-machine",
             "scheduler": "LSF",
@@ -158,11 +161,13 @@ def test_submit_filewrite(mock_isdir, mock_submit, mock_savini):
     """
 
     jobs = {
+        "lbowconf": {
+            "recoveryfile": "recovery-YYMMDD-HHMMSS"
+        },
         "job-one": {
             "resource": "test-machine",
             "scheduler": "LSF",
             "jobid": "test456",
-            "recoveryfile": "recovery-YYMMDD-HHMMSS"
         }
     }
 
@@ -184,11 +189,13 @@ def test_submit_fileuninit(mock_isdir, mock_submit, mock_savini):
     """
 
     jobs = {
+        "lbowconf": {
+            "recoveryfile": ""
+        },
         "job-one": {
             "resource": "test-machine",
             "scheduler": "LSF",
             "jobid": "test456",
-            "recoveryfile": ""
         }
     }
 
@@ -210,11 +217,13 @@ def test_submit_fileexcept1(mock_isdir, mock_submit, mock_savini):
     """
 
     jobs = {
+        "lbowconf": {
+            "recoveryfile": "recovery-YYMMDD-HHMMSS"
+        },
         "job-one": {
             "resource": "test-machine",
             "scheduler": "LSF",
             "jobid": "test456",
-            "recoveryfile": "recovery-YYMMDD-HHMMSS"
         }
     }
 
@@ -235,11 +244,13 @@ def test_submit_fileexcept2(mock_isdir, mock_submit, mock_savini):
     """
 
     jobs = {
+        "lbowconf": {
+            "recoveryfile": "recovery-YYMMDD-HHMMSS"
+        },
         "job-one": {
             "resource": "test-machine",
             "scheduler": "LSF",
             "jobid": "test456",
-            "recoveryfile": "recovery-YYMMDD-HHMMSS"
         }
     }
 
@@ -260,6 +271,7 @@ def test_submit_attrexcept(mock_isdir, mock_submit, mock_savini):
     """
 
     jobs = {
+        "lbowconf": {},
         "job-one": {
             "resource": "test-machine",
             "scheduler": "LSF",
@@ -286,6 +298,7 @@ def test_submit_submitexcept(mock_isdir, mock_submit, mock_savini):
     """
 
     jobs = {
+        "lbowconf": {},
         "job-one": {
             "resource": "test-machine",
             "scheduler": "LSF",
@@ -313,6 +326,7 @@ def test_submit_queueexcept(mock_isdir, mock_submit, mock_savini):
     """
 
     jobs = {
+        "lbowconf": {},
         "job-one": {
             "resource": "test-machine",
             "scheduler": "LSF",
@@ -339,6 +353,10 @@ def test_submit_queueinfo(mock_isdir, mock_submit, mock_savini):
     """
 
     jobs = {
+        "lbowconf": {
+            "test-machine-queue-slots": 0,
+            "test-machine-queue-max": 0
+        },
         "job-one": {
             "resource": "test-machine",
             "scheduler": "LSF",
@@ -355,8 +373,6 @@ def test_submit_queueinfo(mock_isdir, mock_submit, mock_savini):
             "jobid": "test789"
         }
     }
-    QUEUEINFO["test-machine"]["queue-slots"] = "0"
-    QUEUEINFO["test-machine"]["queue-max"] = "0"
 
     mock_isdir.return_value = False
     mock_savini.return_value = None
@@ -364,5 +380,5 @@ def test_submit_queueinfo(mock_isdir, mock_submit, mock_savini):
 
     submit(jobs)
 
-    assert QUEUEINFO["test-machine"]["queue-slots"] == "3"
-    assert QUEUEINFO["test-machine"]["queue-max"] == "3"
+    assert jobs["lbowconf"]["test-machine-queue-slots"] == "3"
+    assert jobs["lbowconf"]["test-machine-queue-max"] == "3"
diff --git a/tests/unit/staging/test_cleanup.py b/tests/unit/staging/test_cleanup.py
index 76d6595..d386c1f 100644
--- a/tests/unit/staging/test_cleanup.py
+++ b/tests/unit/staging/test_cleanup.py
@@ -56,10 +56,12 @@ def test_cleanup_single(mock_delete, mock_list):
     """
 
     jobs = {
+        "lbowconf": {
+            "recoveryfile": "rec.file"
+        },
         "jobone": {
             "destdir": "/path/to/jobone12484",
             "remoteworkdir": "/path/to/local/dir",
-            "recoveryfile": ""
             }
     }
 
@@ -80,20 +82,20 @@ def test_cleanup_multiple(mock_delete, mock_list):
     """
 
     jobs = {
+        "lbowconf": {
+            "recoveryfile": "rec.file"
+        },
         "jobone": {
             "destdir": "/path/to/jobone12484",
             "remoteworkdir": "/path/to/local/dir",
-            "recoveryfile": ""
             },
         "jobtwo": {
             "destdir": "/path/to/jobtwo12484",
             "remoteworkdir": "/path/to/local/dir",
-            "recoveryfile": ""
             },
         "jobthree": {
             "destdir": "/path/to/jobthree12484",
             "remoteworkdir": "/path/to/local/dir",
-            "recoveryfile": ""
             }
     }
 
@@ -114,10 +116,12 @@ def test_cleanup_params(mock_delete, mock_list):
     """
 
     jobs = {
+        "lbowconf": {
+            "recoveryfile": "rec.file"
+        },
         "jobone": {
             "destdir": "/path/to/jobone12484",
             "remoteworkdir": "/path/to/local/dir",
-            "recoveryfile": ""
             }
     }
 
@@ -139,10 +143,12 @@ def test_cleanup_nodelete(mock_delete, mock_list):
     """
 
     jobs = {
+        "lbowconf": {
+            "recoveryfile": "rec.file"
+        },
         "jobone": {
             "destdir": "/path/to/jobone12484",
             "remoteworkdir": "/path/to/jobone12484",
-            "recoveryfile": ""
             }
     }
 
@@ -164,10 +170,12 @@ def test_cleanup_excepttest1(mock_delete, mock_list):
     """
 
     jobs = {
+        "lbowconf": {
+            "recoveryfile": "rec.file"
+        },
         "jobone": {
             "destdir": "/path/to/jobone12484",
             "remoteworkdir": "/path/to/local/dir",
-            "recoveryfile": ""
             }
     }
 
@@ -187,10 +195,12 @@ def test_cleanup_excepttest2(mock_delete, mock_list):
     """
 
     jobs = {
+        "lbowconf": {
+            "recoveryfile": "rec.file"
+        },
         "jobone": {
             "destdir": "/path/to/jobone12484",
             "remoteworkdir": "/path/to/local/dir",
-            "recoveryfile": ""
             }
     }
 
@@ -210,10 +220,12 @@ def test_cleanup_excepttest3(mock_delete, mock_list):
     """
 
     jobs = {
+        "lbowconf": {
+            "recoveryfile": "rec.file"
+        },
         "jobone": {
             "destdir": "/path/to/jobone12484",
             "remoteworkdir": "/path/to/local/dir",
-            "recoveryfile": ""
             }
     }
 
@@ -233,10 +245,12 @@ def test_cleanup_excepttest4(mock_delete, mock_list):
     """
 
     jobs = {
+        "lbowconf": {
+            "recoveryfile": "rec.file"
+        },
         "jobone": {
             "destdir": "/path/to/jobone12484",
             "remoteworkdir": "/path/to/local/dir",
-            "recoveryfile": ""
             }
     }
 
@@ -257,10 +271,12 @@ def test_cleanup_recoveryfilerm1(m_delete, m_list, m_isfile, m_remove):
     """
 
     jobs = {
+        "lbowconf": {
+            "recoveryfile": "rec.file"
+        },
         "jobone": {
             "destdir": "/path/to/jobone12484",
             "remoteworkdir": "/path/to/local/dir",
-            "recoveryfile": "recovery-YYMMDD-HHMMSS"
             }
     }
 
@@ -284,10 +300,12 @@ def test_cleanup_recoveryfilerm2(m_delete, m_list, m_isfile, m_remove):
     """
 
     jobs = {
+        "lbowconf": {
+            "recoveryfile": ""
+        },
         "jobone": {
             "destdir": "/path/to/jobone12484",
             "remoteworkdir": "/path/to/local/dir",
-            "recoveryfile": ""
             }
     }
 
@@ -311,10 +329,12 @@ def test_cleanup_recoveryfilerm3(m_delete, m_list, m_isfile, m_remove):
     """
 
     jobs = {
+        "lbowconf": {
+            "recoveryfile": "rec.file"
+        },
         "jobone": {
             "destdir": "/path/to/jobone12484",
             "remoteworkdir": "/path/to/local/dir",
-            "recoveryfile": "recovery-YYMMDD-HHMMSS"
             }
     }