From 87edb8ed6443f41ec349c92d15f8bedd1a222058 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Thu, 18 Feb 2021 12:36:27 +0100 Subject: [PATCH 01/12] Added skipped jobs feature --- autosubmit/database/db_jobdata.py | 2 +- autosubmit/job/job.py | 1 + autosubmit/job/job_common.py | 6 +++-- autosubmit/job/job_dict.py | 7 +++++ autosubmit/job/job_list.py | 43 ++++++++++++++++++++++++++----- autosubmit/monitor/monitor.py | 10 ++++--- 6 files changed, 57 insertions(+), 12 deletions(-) diff --git a/autosubmit/database/db_jobdata.py b/autosubmit/database/db_jobdata.py index b313a427b..8075b6301 100644 --- a/autosubmit/database/db_jobdata.py +++ b/autosubmit/database/db_jobdata.py @@ -330,7 +330,7 @@ class JobData(object): :return: Queueing time in seconds. :rtype: int """ - if self.status in ["SUBMITTED", "QUEUING", "RUNNING", "COMPLETED", "HELD", "PREPARED", "FAILED"]: + if self.status in ["SUBMITTED", "QUEUING", "RUNNING", "COMPLETED", "HELD", "PREPARED", "FAILED", "SKIPPED"]: queue = int((self.start if self.start > 0 else time.time()) - self.submit) if queue > 0: diff --git a/autosubmit/job/job.py b/autosubmit/job/job.py index 67e524b33..98de28818 100644 --- a/autosubmit/job/job.py +++ b/autosubmit/job/job.py @@ -99,6 +99,7 @@ class Job(object): self.delay = None self.frequency = None self.synchronize = None + self.skippable = False self.repacked = 0 self._long_name = None self.long_name = name diff --git a/autosubmit/job/job_common.py b/autosubmit/job/job_common.py index 129e73822..f80bd6736 100644 --- a/autosubmit/job/job_common.py +++ b/autosubmit/job/job_common.py @@ -32,13 +32,14 @@ class Status: COMPLETED = 5 HELD = 6 PREPARED = 7 + SKIPPED = 8 FAILED = -1 UNKNOWN = -2 SUSPENDED = -3 ####### # Note: any change on constants must be applied on the dict below!!! VALUE_TO_KEY = {-3: 'SUSPENDED', -2: 'UNKNOWN', -1: 'FAILED', 0: 'WAITING', 1: 'READY', - 2: 'SUBMITTED', 3: 'QUEUING', 4: 'RUNNING', 5: 'COMPLETED', 6: 'HELD', 7: 'PREPARED'} + 2: 'SUBMITTED', 3: 'QUEUING', 4: 'RUNNING', 5: 'COMPLETED', 6: 'HELD', 7: 'PREPARED', 8: 'SKIPPED'} def retval(self, value): return getattr(self, value) @@ -62,12 +63,13 @@ class bcolors: QUEUING = '\033[35;1m' RUNNING = '\033[32m' COMPLETED = '\033[33m' + SKIPPED = '\033[33m' PREPARED = '\033[34;2m' HELD = '\033[34;1m' FAILED = '\033[31m' SUSPENDED = '\033[31;1m' CODE_TO_COLOR = {-3: SUSPENDED, -2: UNKNOWN, -1: FAILED, 0: WAITING, 1: READY, - 2: SUBMITTED, 3: QUEUING, 4: RUNNING, 5: COMPLETED, 6: HELD, 7: PREPARED} + 2: SUBMITTED, 3: QUEUING, 4: RUNNING, 5: COMPLETED, 6: HELD, 7: PREPARED, 8: SKIPPED} class Type: diff --git a/autosubmit/job/job_dict.py b/autosubmit/job/job_dict.py index e57bb1193..6c9444f8d 100644 --- a/autosubmit/job/job_dict.py +++ b/autosubmit/job/job_dict.py @@ -53,6 +53,7 @@ class DicJobs: self.default_retrials = default_retrials self._dic = dict() + def read_section(self, section, priority, default_job_type, jobs_data=dict()): """ Read a section from jobs conf and creates all jobs for it @@ -345,6 +346,12 @@ class DicJobs: job.notify_on = [x.upper() for x in self.get_option(section, "NOTIFY_ON", '').split(' ')] job.synchronize = self.get_option(section, "SYNCHRONIZE", None) job.check_warnings = str(self.get_option(section, "SHOW_CHECK_WARNINGS", 'false')).lower() + job.running = self._parser.get_option(section, 'RUNNING','once').lower() + + if self.get_option(section, "SKIPPABLE", "False").lower() == "true": + job.skippable = True + else: + job.skippable = False if job.check_warnings == 'true': job.check_warnings = True else: diff --git a/autosubmit/job/job_list.py b/autosubmit/job/job_list.py index b69268b42..9dee64346 100644 --- a/autosubmit/job/job_list.py +++ b/autosubmit/job/job_list.py @@ -886,7 +886,18 @@ class JobList(object): prepared = [job for job in self._job_list if (platform is None or job.platform.name.lower() == platform.name.lower()) and job.status == Status.PREPARED] return prepared + def get_skipped(self, platform=None): + """ + Returns a list of prepared jobs + :param platform: job platform + :type platform: HPCPlatform + :return: prepared jobs + :rtype: list + """ + skipped = [job for job in self._job_list if (platform is None or job.platform.name.lower() == platform.name.lower()) and + job.status == Status.SKIPPED] + return skipped def get_waiting(self, platform=None, wrapper=False): """ Returns a list of jobs waiting @@ -1196,7 +1207,15 @@ class JobList(object): move(os.path.join(self._persistence_path, self._update_file), os.path.join(self._persistence_path, self._update_file + "_" + output_date)) - + def get_skippable_jobs(self): + job_list_skip = [job for job in self.get_job_list() if job.skippable is True and (job.status == Status.RUNNING or job.status == Status.COMPLETED or job.status == Status.READY) ] + skip_by_section = dict() + for job in job_list_skip: + if job.section not in skip_by_section: + skip_by_section[job.section] = [job] + else: + skip_by_section[job.section].append(job) + return skip_by_section @property def parameters(self): """ @@ -1265,10 +1284,23 @@ class JobList(object): save = True Log.debug( "Job is failed".format(job.name)) + jobs_to_skip = self.get_skippable_jobs() # Get A Dict with all jobs that are listed as skipabble + for section in jobs_to_skip: + for job in jobs_to_skip[section]: + if job.status == Status.READY: #Check only jobs to be pending of be submitted + if job.running == 'chunk': + for related_job in jobs_to_skip[section]: + if job.chunk < related_job.chunk: # Check if there is some related job with an higher chunk + job.status = Status.SKIPPED + elif job.running == 'member': + for related_job in jobs_to_skip[section]: + if job.member < related_job.member: # Check if there is any other related job with an higher member ( to determine how, since member can be called non-integer) + job.status = Status.SKIPPED + + # if waiting jobs has all parents completed change its State to READY for job in self.get_completed(): if job.synchronize is not None: - #Log.debug('Updating SYNC jobs') tmp = [ parent for parent in job.parents if parent.status == Status.COMPLETED] if len(tmp) != len(job.parents): @@ -1276,13 +1308,12 @@ class JobList(object): save = True Log.debug( "Resetting sync job: {0} status to: WAITING for parents completion...".format(job.name)) - Log.debug('Update finished') + #Log.debug('Update finished') Log.debug('Updating WAITING jobs') if not fromSetStatus: all_parents_completed = [] for job in self.get_waiting(): - tmp = [ - parent for parent in job.parents if parent.status == Status.COMPLETED] + tmp = [parent for parent in job.parents if parent.status == Status.COMPLETED or parent.status == Status.SKIPPED] if job.parents is None or len(tmp) == len(job.parents): job.status = Status.READY job.hold = False @@ -1312,7 +1343,7 @@ class JobList(object): for job in self.get_waiting_remote_dependencies('slurm'): if job.name not in all_parents_completed: tmp = [parent for parent in job.parents if ( - (parent.status == Status.COMPLETED or parent.status == Status.QUEUING or parent.status == Status.RUNNING) and "setup" not in parent.name.lower())] + (parent.status == Status.SKIPPED or parent.status == Status.COMPLETED or parent.status == Status.QUEUING or parent.status == Status.RUNNING) and "setup" not in parent.name.lower())] if len(tmp) == len(job.parents): job.status = Status.PREPARED job.hold = True diff --git a/autosubmit/monitor/monitor.py b/autosubmit/monitor/monitor.py index ce97cd319..99f307d36 100644 --- a/autosubmit/monitor/monitor.py +++ b/autosubmit/monitor/monitor.py @@ -43,7 +43,7 @@ class Monitor: """Class to handle monitoring of Jobs at HPC.""" _table = dict([(Status.UNKNOWN, 'white'), (Status.WAITING, 'gray'), (Status.READY, 'lightblue'),(Status.PREPARED, 'skyblue'), (Status.SUBMITTED, 'cyan'), (Status.HELD, 'salmon'), (Status.QUEUING, 'pink'), (Status.RUNNING, 'green'), - (Status.COMPLETED, 'yellow'), (Status.FAILED, 'red'), (Status.SUSPENDED, 'orange')]) + (Status.COMPLETED, 'yellow'), (Status.FAILED, 'red'), (Status.SUSPENDED, 'orange'), (Status.SKIPPED, 'lightyellow')]) @staticmethod def color_status(status): @@ -71,7 +71,8 @@ class Monitor: return Monitor._table[Status.RUNNING] elif status == Status.COMPLETED: return Monitor._table[Status.COMPLETED] - + elif status == Status.SKIPPED: + return Monitor._table[Status.SKIPPED] elif status == Status.FAILED: return Monitor._table[Status.FAILED] elif status == Status.SUSPENDED: @@ -103,6 +104,7 @@ class Monitor: fillcolor=self._table[Status.READY])) legend.add_node(pydotplus.Node(name='PREPARED', shape='box', style="filled", fillcolor=self._table[Status.PREPARED])) + legend.add_node(pydotplus.Node(name='SUBMITTED', shape='box', style="filled", fillcolor=self._table[Status.SUBMITTED])) legend.add_node(pydotplus.Node(name='HELD', shape='box', style="filled", @@ -111,13 +113,15 @@ class Monitor: fillcolor=self._table[Status.QUEUING])) legend.add_node(pydotplus.Node(name='RUNNING', shape='box', style="filled", fillcolor=self._table[Status.RUNNING])) + legend.add_node(pydotplus.Node(name='SKIPPED', shape='box', style="filled", + fillcolor=self._table[Status.SKIPPED])) legend.add_node(pydotplus.Node(name='COMPLETED', shape='box', style="filled", fillcolor=self._table[Status.COMPLETED])) - legend.add_node(pydotplus.Node(name='FAILED', shape='box', style="filled", fillcolor=self._table[Status.FAILED])) legend.add_node(pydotplus.Node(name='SUSPENDED', shape='box', style="filled", fillcolor=self._table[Status.SUSPENDED])) + graph.add_subgraph(legend) exp = pydotplus.Subgraph(graph_name='Experiment', label=expid) -- GitLab From 1639f9f3b23e55a465ed8b8c2fe5d2a173b00886 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Thu, 18 Feb 2021 13:40:20 +0100 Subject: [PATCH 02/12] Added queuing jobs skip --- autosubmit/job/job_list.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/autosubmit/job/job_list.py b/autosubmit/job/job_list.py index 9dee64346..01557085b 100644 --- a/autosubmit/job/job_list.py +++ b/autosubmit/job/job_list.py @@ -1208,7 +1208,7 @@ class JobList(object): os.path.join(self._persistence_path, self._update_file + "_" + output_date)) def get_skippable_jobs(self): - job_list_skip = [job for job in self.get_job_list() if job.skippable is True and (job.status == Status.RUNNING or job.status == Status.COMPLETED or job.status == Status.READY) ] + job_list_skip = [job for job in self.get_job_list() if job.skippable is True and ( job.status == Status.QUEUING or job.status == Status.RUNNING or job.status == Status.COMPLETED or job.status == Status.READY) ] skip_by_section = dict() for job in job_list_skip: if job.section not in skip_by_section: @@ -1287,11 +1287,14 @@ class JobList(object): jobs_to_skip = self.get_skippable_jobs() # Get A Dict with all jobs that are listed as skipabble for section in jobs_to_skip: for job in jobs_to_skip[section]: - if job.status == Status.READY: #Check only jobs to be pending of be submitted + if job.status == Status.READY or job.status == Status.QUEUING: # Check only jobs to be pending of canceled if not started if job.running == 'chunk': for related_job in jobs_to_skip[section]: if job.chunk < related_job.chunk: # Check if there is some related job with an higher chunk + if job.status == Status.QUEUING: + job.platform.send_command(job.platform.cancel_cmd + " " + str(job.id), ignore_log=True) job.status = Status.SKIPPED + elif job.running == 'member': for related_job in jobs_to_skip[section]: if job.member < related_job.member: # Check if there is any other related job with an higher member ( to determine how, since member can be called non-integer) -- GitLab From 854da89820f81373af282ddef340c5a78b1e9896 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Mon, 22 Feb 2021 16:37:57 +0100 Subject: [PATCH 03/12] Finished skipped_jobs --- autosubmit/job/job_list.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/autosubmit/job/job_list.py b/autosubmit/job/job_list.py index 01557085b..686d35f49 100644 --- a/autosubmit/job/job_list.py +++ b/autosubmit/job/job_list.py @@ -1207,8 +1207,8 @@ class JobList(object): move(os.path.join(self._persistence_path, self._update_file), os.path.join(self._persistence_path, self._update_file + "_" + output_date)) - def get_skippable_jobs(self): - job_list_skip = [job for job in self.get_job_list() if job.skippable is True and ( job.status == Status.QUEUING or job.status == Status.RUNNING or job.status == Status.COMPLETED or job.status == Status.READY) ] + def get_skippable_jobs(self, jobs_in_wrapper): + job_list_skip = [job for job in self.get_job_list() if job.skippable is True and ( job.status == Status.QUEUING or job.status == Status.RUNNING or job.status == Status.COMPLETED or job.status == Status.READY) and jobs_in_wrapper.find(job.section) == -1 ] skip_by_section = dict() for job in job_list_skip: if job.section not in skip_by_section: @@ -1284,20 +1284,21 @@ class JobList(object): save = True Log.debug( "Job is failed".format(job.name)) - jobs_to_skip = self.get_skippable_jobs() # Get A Dict with all jobs that are listed as skipabble + jobs_to_skip = self.get_skippable_jobs(as_conf.get_wrapper_jobs()) # Get A Dict with all jobs that are listed as skipabble for section in jobs_to_skip: for job in jobs_to_skip[section]: if job.status == Status.READY or job.status == Status.QUEUING: # Check only jobs to be pending of canceled if not started if job.running == 'chunk': + chunks = as_conf.get_chunk_list() for related_job in jobs_to_skip[section]: - if job.chunk < related_job.chunk: # Check if there is some related job with an higher chunk + if chunks.index(job.chunk) < chunks.index(related_job.chunk): # Check if there is some related job with an higher chunk if job.status == Status.QUEUING: job.platform.send_command(job.platform.cancel_cmd + " " + str(job.id), ignore_log=True) job.status = Status.SKIPPED - elif job.running == 'member': + members = as_conf.get_member_list() for related_job in jobs_to_skip[section]: - if job.member < related_job.member: # Check if there is any other related job with an higher member ( to determine how, since member can be called non-integer) + if members.index(job.member) < members.index(related_job.member): job.status = Status.SKIPPED -- GitLab From 8fb8b080022eda37cdfeeed605bff36118ecf02d Mon Sep 17 00:00:00 2001 From: dbeltran Date: Tue, 23 Feb 2021 09:30:02 +0100 Subject: [PATCH 04/12] Finished skipped_jobs revert a change with chunks --- autosubmit/job/job_list.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/autosubmit/job/job_list.py b/autosubmit/job/job_list.py index 686d35f49..7d9b0a76f 100644 --- a/autosubmit/job/job_list.py +++ b/autosubmit/job/job_list.py @@ -1289,9 +1289,8 @@ class JobList(object): for job in jobs_to_skip[section]: if job.status == Status.READY or job.status == Status.QUEUING: # Check only jobs to be pending of canceled if not started if job.running == 'chunk': - chunks = as_conf.get_chunk_list() for related_job in jobs_to_skip[section]: - if chunks.index(job.chunk) < chunks.index(related_job.chunk): # Check if there is some related job with an higher chunk + if job.chunk < related_job.chunk: # Check if there is some related job with an higher chunk if job.status == Status.QUEUING: job.platform.send_command(job.platform.cancel_cmd + " " + str(job.id), ignore_log=True) job.status = Status.SKIPPED -- GitLab From 4f911b8c803e86f29bcb6cf01325170ce078c899 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Tue, 23 Feb 2021 09:37:57 +0100 Subject: [PATCH 05/12] PipeLine Fix --- autosubmit/job/job.py | 2 +- autosubmit/job/job_dict.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/autosubmit/job/job.py b/autosubmit/job/job.py index 98de28818..4e527f1f1 100644 --- a/autosubmit/job/job.py +++ b/autosubmit/job/job.py @@ -795,7 +795,7 @@ class Job(object): parameters.update(default_parameters) parameters['JOBNAME'] = self.name parameters['FAIL_COUNT'] = str(self.fail_count) - + parameters['PROJECT_TYPE'] = as_conf.get_project_type() parameters['SDATE'] = date2str(self.date, self.date_format) parameters['MEMBER'] = self.member diff --git a/autosubmit/job/job_dict.py b/autosubmit/job/job_dict.py index 6c9444f8d..ad948e2bb 100644 --- a/autosubmit/job/job_dict.py +++ b/autosubmit/job/job_dict.py @@ -346,7 +346,7 @@ class DicJobs: job.notify_on = [x.upper() for x in self.get_option(section, "NOTIFY_ON", '').split(' ')] job.synchronize = self.get_option(section, "SYNCHRONIZE", None) job.check_warnings = str(self.get_option(section, "SHOW_CHECK_WARNINGS", 'false')).lower() - job.running = self._parser.get_option(section, 'RUNNING','once').lower() + job.running = self.get_option(section, 'RUNNING', 'once').lower() if self.get_option(section, "SKIPPABLE", "False").lower() == "true": job.skippable = True -- GitLab From e21c8dae1638b41e0610b1b259d9cc4a62a783cb Mon Sep 17 00:00:00 2001 From: dbeltran Date: Tue, 23 Feb 2021 09:43:31 +0100 Subject: [PATCH 06/12] PipeLine Fix --- autosubmit/config/config_common.py | 1 + 1 file changed, 1 insertion(+) diff --git a/autosubmit/config/config_common.py b/autosubmit/config/config_common.py index 138fe5b60..352b648b8 100644 --- a/autosubmit/config/config_common.py +++ b/autosubmit/config/config_common.py @@ -846,6 +846,7 @@ class AutosubmitConfig(object): parameters[option] = self._conf_parser.get(section, option) project_type = self.get_project_type() + parameters['PROJECT_TYPE'] = self.get_project_type() if project_type != "none" and self._proj_parser is not None: # Load project parameters Log.debug("Loading project parameters...") -- GitLab From 36751d846124ecbe90023c4d5331c79281101ff4 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Tue, 23 Feb 2021 09:54:42 +0100 Subject: [PATCH 07/12] Added docs --- autosubmit/config/config_common.py | 2 +- autosubmit/job/job.py | 2 +- docs/source/usage/new_job.rst | 26 ++++++++++++++++++++++++++ docs/source/workflows/skip.png | Bin 0 -> 44265 bytes 4 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 docs/source/workflows/skip.png diff --git a/autosubmit/config/config_common.py b/autosubmit/config/config_common.py index 352b648b8..5f904222d 100644 --- a/autosubmit/config/config_common.py +++ b/autosubmit/config/config_common.py @@ -846,7 +846,7 @@ class AutosubmitConfig(object): parameters[option] = self._conf_parser.get(section, option) project_type = self.get_project_type() - parameters['PROJECT_TYPE'] = self.get_project_type() + if project_type != "none" and self._proj_parser is not None: # Load project parameters Log.debug("Loading project parameters...") diff --git a/autosubmit/job/job.py b/autosubmit/job/job.py index 4e527f1f1..ef81ffa45 100644 --- a/autosubmit/job/job.py +++ b/autosubmit/job/job.py @@ -795,7 +795,6 @@ class Job(object): parameters.update(default_parameters) parameters['JOBNAME'] = self.name parameters['FAIL_COUNT'] = str(self.fail_count) - parameters['PROJECT_TYPE'] = as_conf.get_project_type() parameters['SDATE'] = date2str(self.date, self.date_format) parameters['MEMBER'] = self.member @@ -936,6 +935,7 @@ class Job(object): :rtype: str """ parameters = self.parameters + parameters['PROJECT_TYPE'] = as_conf.get_project_type() if parameters['PROJECT_TYPE'].lower() != "none": template_file = open(os.path.join( as_conf.get_project_dir(), self.file), 'r') diff --git a/docs/source/usage/new_job.rst b/docs/source/usage/new_job.rst index f6270aa65..0e1b63367 100644 --- a/docs/source/usage/new_job.rst +++ b/docs/source/usage/new_job.rst @@ -56,6 +56,8 @@ There are also other, less used features that you can use: * CUSTOM_DIRECTIVES: Custom directives for the HPC resource manager headers of the platform used for that job. +* SKIPPABLE: When this is true, the job will be able to skip it work if there is an higher chunk or member already ready, running, queuing or in complete status. + Workflow examples: ------------------ @@ -114,5 +116,29 @@ In this workflow you can see an illustrated example of select_chunks used in an :align: center :alt: select_chunks_workflow +Example 3: + +In this workflow you can see an illustrated example of SKIPPABLE parameter used in an dummy workflow. + +.. code-block:: ini + [SIM] + FILE = sim.sh + DEPENDENCIES = INI POST-1 + WALLCLOCK = 00:15 + RUNNING = chunk + QUEUE = debug + SKIPPABLE = TRUE + [POST] + FILE = post.sh + DEPENDENCIES = SIM + WALLCLOCK = 00:05 + RUNNING = member + #QUEUE = debug + +.. figure:: workflows/skip.png + :name: simple + :width: 100% + :align: center + :alt: skip_workflow diff --git a/docs/source/workflows/skip.png b/docs/source/workflows/skip.png new file mode 100644 index 0000000000000000000000000000000000000000..6083dad8b3c16a5667a366adf2f210d3896c50bf GIT binary patch literal 44265 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 JMe0_8{{w%WBsBm4 literal 0 HcmV?d00001 -- GitLab From 2cbc29f045f5eb951f3971486408a96a04f5fa68 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Tue, 23 Feb 2021 10:05:18 +0100 Subject: [PATCH 08/12] Added docs --- autosubmit/job/job.py | 3 +-- autosubmit/job/job_packages.py | 31 ++++++++++++++++--------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/autosubmit/job/job.py b/autosubmit/job/job.py index ef81ffa45..b3c2b79eb 100644 --- a/autosubmit/job/job.py +++ b/autosubmit/job/job.py @@ -797,7 +797,7 @@ class Job(object): parameters['FAIL_COUNT'] = str(self.fail_count) parameters['SDATE'] = date2str(self.date, self.date_format) parameters['MEMBER'] = self.member - + parameters['PROJECT_TYPE'] = as_conf.get_project_type() if hasattr(self, 'retrials'): parameters['RETRIALS'] = self.retrials @@ -935,7 +935,6 @@ class Job(object): :rtype: str """ parameters = self.parameters - parameters['PROJECT_TYPE'] = as_conf.get_project_type() if parameters['PROJECT_TYPE'].lower() != "none": template_file = open(os.path.join( as_conf.get_project_dir(), self.file), 'r') diff --git a/autosubmit/job/job_packages.py b/autosubmit/job/job_packages.py index 7e219c689..a4f61ded8 100644 --- a/autosubmit/job/job_packages.py +++ b/autosubmit/job/job_packages.py @@ -118,6 +118,7 @@ class JobPackageBase(object): def _create_common_script(self): pass + def submit(self, configuration, parameters,only_generate=False,hold=False): """ :para configuration: Autosubmit basic configuration \n @@ -141,21 +142,21 @@ class JobPackageBase(object): try: if len(self.jobs) < thread_number: for job in self.jobs: - if job.check.lower() == Job.CHECK_ON_SUBMISSION.lower(): - if only_generate: - exit=True - break - if not os.path.exists(os.path.join(configuration.get_project_dir(), job.file)): - if configuration.get_project_type().lower() != "none": - raise AutosubmitCritical("Template [ {0} ] using CHECK=On_submission has some empty variable {0}".format(job.name),7014) - if not job.check_script(configuration, parameters,show_logs=job.check_warnings): - Log.warning("Script {0} check failed",job.name) - Log.warning("On submission script has some empty variables") - else: - Log.result("Script {0} OK",job.name) - job.update_parameters(configuration, parameters) - # looking for directives on jobs - self._custom_directives = self._custom_directives | set(job.custom_directives) + if job.check.lower() == Job.CHECK_ON_SUBMISSION.lower(): + if only_generate: + exit=True + break + if not os.path.exists(os.path.join(configuration.get_project_dir(), job.file)): + if configuration.get_project_type().lower() != "none": + raise AutosubmitCritical("Template [ {0} ] using CHECK=On_submission has some empty variable {0}".format(job.name),7014) + if not job.check_script(configuration, parameters,show_logs=job.check_warnings): + Log.warning("Script {0} check failed",job.name) + Log.warning("On submission script has some empty variables") + else: + Log.result("Script {0} OK",job.name) + job.update_parameters(configuration, parameters) + # looking for directives on jobs + self._custom_directives = self._custom_directives | set(job.custom_directives) else: Lhandle = list() for i in xrange(0, len(self.jobs), chunksize): -- GitLab From de15df17c473c4b53a4c602f5e7ddf54ae0a5d93 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Tue, 23 Feb 2021 10:06:09 +0100 Subject: [PATCH 09/12] Added docs --- autosubmit/job/job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autosubmit/job/job.py b/autosubmit/job/job.py index b3c2b79eb..18d2e938f 100644 --- a/autosubmit/job/job.py +++ b/autosubmit/job/job.py @@ -935,7 +935,7 @@ class Job(object): :rtype: str """ parameters = self.parameters - if parameters['PROJECT_TYPE'].lower() != "none": + if as_conf.get_project_type.lower() != "none": template_file = open(os.path.join( as_conf.get_project_dir(), self.file), 'r') template = template_file.read() -- GitLab From db113bfbf2f07eece4e971e256404728fcef5a0d Mon Sep 17 00:00:00 2001 From: dbeltran Date: Tue, 23 Feb 2021 10:18:38 +0100 Subject: [PATCH 10/12] Added project type as parameter --- autosubmit/config/config_common.py | 5 ++--- autosubmit/job/job.py | 4 ++-- test/unit/test_autosubmit_config.py | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/autosubmit/config/config_common.py b/autosubmit/config/config_common.py index 5f904222d..4d0b179b1 100644 --- a/autosubmit/config/config_common.py +++ b/autosubmit/config/config_common.py @@ -845,9 +845,8 @@ class AutosubmitConfig(object): for option in self._conf_parser.options(section): parameters[option] = self._conf_parser.get(section, option) - project_type = self.get_project_type() - - if project_type != "none" and self._proj_parser is not None: + parameters['PROJECT_TYPE'] = self.get_project_type() + if parameters['PROJECT_TYPE'] != "none" and self._proj_parser is not None: # Load project parameters Log.debug("Loading project parameters...") parameters2 = parameters.copy() diff --git a/autosubmit/job/job.py b/autosubmit/job/job.py index 18d2e938f..61bb2b954 100644 --- a/autosubmit/job/job.py +++ b/autosubmit/job/job.py @@ -797,7 +797,6 @@ class Job(object): parameters['FAIL_COUNT'] = str(self.fail_count) parameters['SDATE'] = date2str(self.date, self.date_format) parameters['MEMBER'] = self.member - parameters['PROJECT_TYPE'] = as_conf.get_project_type() if hasattr(self, 'retrials'): parameters['RETRIALS'] = self.retrials @@ -935,7 +934,8 @@ class Job(object): :rtype: str """ parameters = self.parameters - if as_conf.get_project_type.lower() != "none": + + if parameters['PROJECT_TYPE'].lower() != "none": template_file = open(os.path.join( as_conf.get_project_dir(), self.file), 'r') template = template_file.read() diff --git a/test/unit/test_autosubmit_config.py b/test/unit/test_autosubmit_config.py index 626b0ca74..a0b1aea63 100644 --- a/test/unit/test_autosubmit_config.py +++ b/test/unit/test_autosubmit_config.py @@ -299,7 +299,7 @@ class TestAutosubmitConfig(TestCase): returned_parameters = config.load_parameters() # assert - self.assertEquals(6, len(returned_parameters)) + self.assertEquals(7, len(returned_parameters)) self.assertTrue(returned_parameters.has_key('dummy-option1')) self.assertTrue(returned_parameters.has_key('dummy-option2')) self.assertTrue(returned_parameters.has_key('dummy-option3')) -- GitLab From 0fec7e6405a6ad0edb56370f7814155d0a451a88 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Tue, 23 Feb 2021 11:09:50 +0100 Subject: [PATCH 11/12] Added project type as parameter --- autosubmit/job/job.py | 47 ++++++++++++++++++---------------- autosubmit/job/job_packages.py | 1 + 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/autosubmit/job/job.py b/autosubmit/job/job.py index 61bb2b954..453ef5d8d 100644 --- a/autosubmit/job/job.py +++ b/autosubmit/job/job.py @@ -934,33 +934,36 @@ class Job(object): :rtype: str """ parameters = self.parameters + try: # issue in tests with project_type variable while using threads + if as_conf.get_project_type().lower() != "none": + template_file = open(os.path.join( + as_conf.get_project_dir(), self.file), 'r') + template = template_file.read() + else: + if self.type == Type.BASH: + template = 'sleep 5' + elif self.type == Type.PYTHON: + template = 'time.sleep(5)' + elif self.type == Type.R: + template = 'Sys.sleep(5)' + else: + template = '' - if parameters['PROJECT_TYPE'].lower() != "none": - template_file = open(os.path.join( - as_conf.get_project_dir(), self.file), 'r') - template = template_file.read() - else: if self.type == Type.BASH: - template = 'sleep 5' + snippet = StatisticsSnippetBash elif self.type == Type.PYTHON: - template = 'time.sleep(5)' + snippet = StatisticsSnippetPython elif self.type == Type.R: - template = 'Sys.sleep(5)' + snippet = StatisticsSnippetR else: - template = '' - - if self.type == Type.BASH: - snippet = StatisticsSnippetBash - elif self.type == Type.PYTHON: - snippet = StatisticsSnippetPython - elif self.type == Type.R: - snippet = StatisticsSnippetR - else: - raise Exception('Job type {0} not supported'.format(self.type)) - - template_content = self._get_template_content( - as_conf, snippet, template) - + raise Exception('Job type {0} not supported'.format(self.type)) + + template_content = self._get_template_content( + as_conf, snippet, template) + except Exception as e: + template_file = open(os.path.join( + as_conf.get_project_dir(), self.file), 'r') + template = template_file.read() return template_content def get_wrapped_content(self, as_conf): diff --git a/autosubmit/job/job_packages.py b/autosubmit/job/job_packages.py index a4f61ded8..c913d865a 100644 --- a/autosubmit/job/job_packages.py +++ b/autosubmit/job/job_packages.py @@ -204,6 +204,7 @@ class JobPackageSimple(JobPackageBase): def __init__(self, jobs): super(JobPackageSimple, self).__init__(jobs) self._job_scripts = {} + def _create_scripts(self, configuration): for job in self.jobs: self._job_scripts[job.name] = job.create_script(configuration) -- GitLab From df4f8a598cb7a89f46b21216485694120994165c Mon Sep 17 00:00:00 2001 From: dbeltran Date: Tue, 23 Feb 2021 11:21:45 +0100 Subject: [PATCH 12/12] pipeline threads --- autosubmit/job/job.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/autosubmit/job/job.py b/autosubmit/job/job.py index 453ef5d8d..c6355c21c 100644 --- a/autosubmit/job/job.py +++ b/autosubmit/job/job.py @@ -948,22 +948,20 @@ class Job(object): template = 'Sys.sleep(5)' else: template = '' + except: + template = '' + + if self.type == Type.BASH: + snippet = StatisticsSnippetBash + elif self.type == Type.PYTHON: + snippet = StatisticsSnippetPython + elif self.type == Type.R: + snippet = StatisticsSnippetR + else: + raise Exception('Job type {0} not supported'.format(self.type)) + template_content = self._get_template_content( + as_conf, snippet, template) - if self.type == Type.BASH: - snippet = StatisticsSnippetBash - elif self.type == Type.PYTHON: - snippet = StatisticsSnippetPython - elif self.type == Type.R: - snippet = StatisticsSnippetR - else: - raise Exception('Job type {0} not supported'.format(self.type)) - - template_content = self._get_template_content( - as_conf, snippet, template) - except Exception as e: - template_file = open(os.path.join( - as_conf.get_project_dir(), self.file), 'r') - template = template_file.read() return template_content def get_wrapped_content(self, as_conf): -- GitLab