From 077cd9ad691f64e9d37bea3d1f951a2633883dad Mon Sep 17 00:00:00 2001 From: dbeltran Date: Thu, 28 Jul 2022 16:19:58 +0200 Subject: [PATCH 01/45] over_wallclock fix --- autosubmit/job/job.py | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/autosubmit/job/job.py b/autosubmit/job/job.py index 226b85c37..948269142 100644 --- a/autosubmit/job/job.py +++ b/autosubmit/job/job.py @@ -768,6 +768,22 @@ class Job(object): except BaseException as e: pass return + def parse_time(self,wallclock): + format = "minute" + regex = re.compile(r'(((?P\d+):)((?P\d+)))(:(?P\d+))?') + parts = regex.match(wallclock) + if not parts: + return + parts = parts.groupdict() + if int(parts['hours']) > 0 : + format = "hour" + else: + format = "minute" + time_params = {} + for name, param in parts.items(): + if param: + time_params[name] = int(param) + return datetime.timedelta(**time_params),format # Duplicated for wrappers and jobs to fix in 4.0.0 def is_over_wallclock(self, start_time, wallclock): """ @@ -777,25 +793,13 @@ class Job(object): :return: """ elapsed = datetime.datetime.now() - start_time - wallclock = datetime.datetime.strptime(wallclock, '%H:%M') - total = 0.0 - if wallclock.hour > 0: - total = wallclock.hour - format = "hour" - else: - format = "minute" - if format == "hour": - if wallclock.minute > 0: - total += wallclock.minute / 60.0 - if wallclock.second > 0: - total += wallclock.second / 60.0 / 60.0 + wallclock,time_format = self.parse_time(wallclock) + if time_format == "hour": + total = wallclock.days * 24 + wallclock.seconds / 60 / 60 else: - if wallclock.minute > 0: - total += wallclock.minute - if wallclock.second > 0: - total += wallclock.second / 60.0 + total = wallclock.days * 24 + wallclock.seconds / 60 total = total * 1.30 # in this case we only want to avoid slurm issues so the time is increased by 50% - if format == "hour": + if time_format == "hour": hour = int(total) minute = int((total - int(total)) * 60.0) second = int(((total - int(total)) * 60 - -- GitLab From 45008de2246909d4c4c0d441ce2647d234f95089 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Thu, 4 Aug 2022 04:21:27 +0200 Subject: [PATCH 02/45] adds support for post+1 --- autosubmit/job/job_list.py | 13 ++++++++++++- docs/source/usage/configuration/new_job.rst | 4 +++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/autosubmit/job/job_list.py b/autosubmit/job/job_list.py index 3d55bb040..d880e3dc4 100644 --- a/autosubmit/job/job_list.py +++ b/autosubmit/job/job_list.py @@ -376,6 +376,8 @@ class JobList(object): # Get current job dependency relations. Used for select chunk option. This is the job in where select chunks option is defined if len(dependency.select_chunks_orig) > 0: # find chunk relation other_parents = dic_jobs.get_jobs(dependency.section, date, member, None) + jobs_by_section = [p for p in other_parents if p.section == dependency.section] + chunk_relation_indx = 0 while chunk_relation_indx < len(dependency.select_chunks_orig): if job.running in ["once"] or len(dependency.select_chunks_orig[chunk_relation_indx]) == 0 or job.chunk in dependency.select_chunks_orig[chunk_relation_indx]: @@ -425,7 +427,16 @@ class JobList(object): JobList._add_edge(graph, job, parent) other_parents.remove(parent) visited_parents.add(parent) - + # If job doesn't have any parent after a first search, search in all dependency.section. This is to avoid +1 being added only to the last one. + if len(job.parents) <= 0: + for relation_indx in chunk_relations_to_add: + for parent in jobs_by_section: + if parent.chunk in dependency.select_chunks_dest[relation_indx] or len( + dependency.select_chunks_dest[relation_indx]) == 0: + if parent not in visited_parents: + job.add_parent(parent) + JobList._add_edge(graph, job, parent) + visited_parents.add(parent) JobList.handle_frequency_interval_dependencies(chunk, chunk_list, date, date_list, dic_jobs, job, member, member_list, dependency.section, graph, other_parents) diff --git a/docs/source/usage/configuration/new_job.rst b/docs/source/usage/configuration/new_job.rst index e8fb39692..b9099e348 100644 --- a/docs/source/usage/configuration/new_job.rst +++ b/docs/source/usage/configuration/new_job.rst @@ -31,10 +31,12 @@ This is the minimum job definition and usually is not enough. You usually will n .. code-block:: ini [jobs] - SELECT_CHUNKS = SIM*[1]*[3] # Enables the dependency of chunk 1 with chunk 3. While chunks 2,4 won't be linked. + SELECT_CHUNKS = SIM*[1:3] # Enables the dependency of chunk 1,2 and 3. While 4 won't be linked. SELECT_CHUNKS = SIM*[1,3] # Enables the dependency of chunk 1 and 3. While 2 and 4 won't be linked SELECT_CHUNKS = SIM*[1] # Enables the dependency of chunk 1. While 2, 3 and 4 won't be linked + SELECT_CHUNKS = SIM*[1]*[3] # Enables the dependency of SIM_1 with CHILD_3. While chunks 2,4 won't be linked. + SELECT_CHUNKS = SIM*[2:4]*[2:4] SIM*[2]*[1] # Links SIM_2:4 with CHILDREN_2:4 and links SIM_2 with CHILD_1 * SELECT_MEMBERS (optional): by default, all sections depend on all jobs the items specified on the DEPENDENCIES parameter. However, with this parameter, you could select the members of a specific job section. At the end of this doc, you will find diverse examples of this feature. Caution, you must pick the member index, not the member name. -- GitLab From 1cec0bab0ee05c47f8bc2b5b3bc5de6c6422b6ec Mon Sep 17 00:00:00 2001 From: dbeltran Date: Mon, 8 Aug 2022 16:36:54 +0200 Subject: [PATCH 03/45] fix project_Destination --- autosubmit/autosubmit.py | 3 ++- autosubmit/config/config_common.py | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/autosubmit/autosubmit.py b/autosubmit/autosubmit.py index 19dc23baf..5bdb10116 100644 --- a/autosubmit/autosubmit.py +++ b/autosubmit/autosubmit.py @@ -4324,7 +4324,8 @@ class Autosubmit: """ project_destination = as_conf.get_project_destination() if project_destination is None or len(project_destination) == 0: - raise AutosubmitCritical("Autosubmit couldn't identify the project destination.", 7014) + if project_type.lower() != "none": + raise AutosubmitCritical("Autosubmit couldn't identify the project destination.", 7014) if project_type == "git": submitter = Autosubmit._get_submitter(as_conf) diff --git a/autosubmit/config/config_common.py b/autosubmit/config/config_common.py index 7b2a6a12b..e3e9188a4 100644 --- a/autosubmit/config/config_common.py +++ b/autosubmit/config/config_common.py @@ -1119,11 +1119,14 @@ class AutosubmitConfig(object): elif self.get_project_type().lower() == "git": value = self.get_git_project_origin().split( '/')[-1].split('.')[-2] - return value + if value != "": + return value + else: + return "project_files" except Exception as exp: Log.debug(str(exp)) Log.debug(traceback.format_exc()) - return '' + return "project_files" def set_git_project_commit(self, as_conf): """ -- GitLab From 3fbfdb808c309b83d63192cd78aa2212ce77f191 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Tue, 9 Aug 2022 15:09:33 +0200 Subject: [PATCH 04/45] tkinter --- docs/source/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 2b4a65497..e6112df7d 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -11,7 +11,7 @@ The Autosubmit code is maintained in *PyPi*, the main source for python packages .. important:: (SYSTEM) Graphviz version must be >= 2.38 except 2.40(not working). You can check the version using dot -v. -- Python dependencies: argparse, python-dateutil, pyparsing, numpy, pydotplus, matplotlib, paramiko, python2-pythondialog, portalocker, requests, typing +- Python dependencies: argparse, python-dateutil, pyparsing, numpy, pydotplus, matplotlib, paramiko, python2-pythondialog, portalocker, requests, typing, six >= 1.10, tkinter .. important:: dot -v command should contain "dot",pdf,png,svg,xlib in device section. -- GitLab From 71cd5a6bed9d30231a562d9664f89d585934b5f5 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Tue, 9 Aug 2022 14:56:12 +0200 Subject: [PATCH 05/45] tkinter --- docs/source/installation.rst | 4 ++-- requeriments.txt | 1 + setup.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/source/installation.rst b/docs/source/installation.rst index e6112df7d..5dd60a136 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -7,11 +7,11 @@ How to install The Autosubmit code is maintained in *PyPi*, the main source for python packages. -- Pre-requisites: bash, python2, sqlite3, git-scm > 1.8.2, subversion, dialog, curl, python-tk, python2-dev, graphviz >= 2.41, pip2 +- Pre-requisites: bash, python2, sqlite3, git-scm > 1.8.2, subversion, dialog, curl, python-tk(tkinter in centOS), python2-dev, graphviz >= 2.41, pip2 .. important:: (SYSTEM) Graphviz version must be >= 2.38 except 2.40(not working). You can check the version using dot -v. -- Python dependencies: argparse, python-dateutil, pyparsing, numpy, pydotplus, matplotlib, paramiko, python2-pythondialog, portalocker, requests, typing, six >= 1.10, tkinter +- Python dependencies: argparse, python-dateutil, pyparsing, numpy, pydotplus, matplotlib, paramiko, python2-pythondialog, portalocker, requests, typing, six >= 1.10 .. important:: dot -v command should contain "dot",pdf,png,svg,xlib in device section. diff --git a/requeriments.txt b/requeriments.txt index f2dfdd0aa..d57974475 100644 --- a/requeriments.txt +++ b/requeriments.txt @@ -13,6 +13,7 @@ typing bscearth.utils cryptography==3.3.2 PyNaCl==1.4.0 +six>=1.10.0 requests xlib Pygments \ No newline at end of file diff --git a/setup.py b/setup.py index 35e8f4f4f..7935f7a42 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ setup( url='http://www.bsc.es/projects/earthscience/autosubmit/', download_url='https://earth.bsc.es/wiki/doku.php?id=tools:autosubmit', keywords=['climate', 'weather', 'workflow', 'HPC'], - install_requires=['argparse>=1.2,<2','argcomplete==1.10.3', 'python-dateutil>2', 'pydotplus>=2', 'pyparsing>=2.0.1', + install_requires=['argparse>=1.2,<2','six>=1.10.0','argcomplete==1.10.3', 'python-dateutil>2', 'pydotplus>=2', 'pyparsing>=2.0.1', 'numpy', 'matplotlib', 'typing', 'paramiko == 2.7.1', 'mock>=1.3.0', 'portalocker==0.5.7', 'networkx', 'bscearth.utils', 'Xlib == 0.21'], extras_require={ -- GitLab From 46b2c19842702a6f82aef43089326fd1d385f86d Mon Sep 17 00:00:00 2001 From: dbeltran Date: Tue, 9 Aug 2022 15:42:36 +0200 Subject: [PATCH 06/45] author change --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 7935f7a42..d4d0f0179 100644 --- a/setup.py +++ b/setup.py @@ -34,8 +34,8 @@ setup( version=version, description='Autosubmit: a versatile tool to manage Weather and Climate Experiments in diverse ' 'Supercomputing Environments', - author='Domingo Manubens-Gil', - author_email='domingo.manubens@bsc.es', + author='Daniel Beltran Mora', + author_email='daniel.beltran@bsc.es', url='http://www.bsc.es/projects/earthscience/autosubmit/', download_url='https://earth.bsc.es/wiki/doku.php?id=tools:autosubmit', keywords=['climate', 'weather', 'workflow', 'HPC'], -- GitLab From f7a014ba3d3b554ddc52e6a27be42de501e0f6ff Mon Sep 17 00:00:00 2001 From: dbeltran Date: Wed, 10 Aug 2022 15:08:10 +0200 Subject: [PATCH 07/45] Added requests, improvement exception recovery for wrappers , added more info, bugfixed status appearing in log.out , bug fixed lc level not being able to change --- autosubmit/autosubmit.py | 54 ++++++++++++++--------- autosubmit/platforms/paramiko_platform.py | 32 ++++++++------ autosubmit/platforms/platform.py | 2 +- environment.yml | 1 + log/log.py | 15 ++++++- setup.py | 2 +- 6 files changed, 69 insertions(+), 37 deletions(-) diff --git a/autosubmit/autosubmit.py b/autosubmit/autosubmit.py index 5bdb10116..8704d27f3 100644 --- a/autosubmit/autosubmit.py +++ b/autosubmit/autosubmit.py @@ -162,7 +162,7 @@ class Autosubmit: parser.add_argument('-v', '--version', action='version', version=Autosubmit.autosubmit_version) parser.add_argument('-lf', '--logfile', choices=('NO_LOG', 'INFO', 'WARNING', 'DEBUG'), - default='WARNING', type=str, + default='DEBUG', type=str, help="sets file's log level.") parser.add_argument('-lc', '--logconsole', choices=('NO_LOG', 'INFO', 'WARNING', 'DEBUG'), default='INFO', type=str, @@ -1659,7 +1659,11 @@ class Autosubmit: Log.debug('Checking Wrapper {0}'.format(str(job_id))) wrapper_job.checked_time = datetime.datetime.now() # This is where wrapper will be checked on the slurm platform, update takes place. - platform.check_job(wrapper_job) + try: + platform.check_job(wrapper_job,is_wrapper=True) + except BaseException as e: + job_list.save() + raise AutosubmitError("The communication with {0} went wrong while checking wrapper {1}\n{2}".format(platform.name,wrapper_job.id,str(e))) #Log.info("FD 3Wrapper checked: {0}".format(log.fd_show.fd_table_status_str())) try: if wrapper_job.status != wrapper_job.new_status: @@ -1671,8 +1675,12 @@ class Autosubmit: "Wrapper is in Unknown Status couldn't get wrapper parameters", 7050) # New status will be saved and inner_jobs will be checked. - wrapper_job.check_status( - wrapper_job.new_status) + try: + wrapper_job.check_status(wrapper_job.new_status) + except: + job_list.save() + raise AutosubmitError("The communication with {0} went wrong while checking the inner_jobs of {1}\n{2}".format(platform.name,wrapper_job.id,str(e))) + # Erase from packages if the wrapper failed to be queued ( Hold Admin bug ) if wrapper_job.status == Status.WAITING: for inner_job in wrapper_job.job_list: @@ -1782,9 +1790,18 @@ class Autosubmit: # No need to wait until the remote platform reconnection recovery = False as_conf = AutosubmitConfig(expid, BasicConfig, ConfigParserFactory()) - consecutive_retrials = 1 - delay = min(15*consecutive_retrials,120) + consecutive_retrials = 0 + failed_names = {} + Log.info("Storing failed job count...") + try: + for job in job_list.get_job_list(): + if job.fail_count > 0: + failed_names[job.name] = job.fail_count + except BaseException as e: + Log.printlog("Error trying to store failed job count",Log.WARNING) + Log.result("Storing failed job count...done") while not recovery and main_loop_retrials > 0: + delay = min(15 * consecutive_retrials, 120) main_loop_retrials = main_loop_retrials - 1 sleep(delay) consecutive_retrials = consecutive_retrials + 1 @@ -1794,6 +1811,7 @@ class Autosubmit: Log.info("Recovering job_list...") job_list = Autosubmit.load_job_list( expid, as_conf, notransitive=notransitive) + Log.info("Recovering job_list... Done") if allowed_members: # Set allowed members after checks have been performed. This triggers the setter and main logic of the -rm feature. job_list.run_members = allowed_members @@ -1801,26 +1819,20 @@ class Autosubmit: "Only jobs with member value in {0} or no member will be allowed in this run. Also, those jobs already SUBMITTED, QUEUING, or RUNNING will be allowed to complete and will be tracked.".format( str(allowed_members))) platforms_to_test = set() + Log.info("Recovering platform information...") for job in job_list.get_job_list(): if job.platform_name is None: job.platform_name = hpcarch job.platform = submitter.platforms[job.platform_name.lower()] platforms_to_test.add(job.platform) - #Recover job_list while keeping job.fail_count - failed_names = {} - for job in job_list.get_job_list(): - if job.platform_name is None: - job.platform_name = hpcarch - job.platform = submitter.platforms[job.platform_name.lower()] - platforms_to_test.add(job.platform) - if job.fail_count > 0: - failed_names[job.name] = job.fail_count + + Log.info("Recovering platform information... Done") + Log.info("Recovering Failure count...") for job in job_list.get_job_list(): if job.name in failed_names.keys(): job.fail_count = failed_names[job.name] - if job.platform_name is None: - job.platform_name = hpcarch - job.platform = submitter.platforms[job.platform_name.lower()] + Log.info("Recovering Failure count... Done") + Log.info("Recovering parameters...") Autosubmit._load_parameters(as_conf, job_list, submitter.platforms) # Recovery wrapper [Packages] @@ -1876,9 +1888,11 @@ class Autosubmit: None, None, jobs[0].platform, as_conf, jobs[0].hold) job_list.job_package_map[jobs[0].id] = wrapper_job + Log.info("Recovering wrappers... Done") job_list.update_list(as_conf) Log.info("Saving recovered job list...") job_list.save() + Log.info("Saving recovered job list... Done") recovery = True Log.result("Recover of job_list is completed") except AutosubmitError as e: @@ -1886,10 +1900,10 @@ class Autosubmit: Log.result("Recover of job_list has fail {0}".format(e.message)) except IOError as e: recovery = False - Log.result("Recover of job_list has fail".format(e.message)) + Log.result("Recover of job_list has fail {0}".format(e.message)) except BaseException as e: recovery = False - Log.result("Recover of job_list has fail".format(e.message)) + Log.result("Recover of job_list has fail {0}".format(e.message)) # Restore platforms and try again, to avoid endless loop with failed configuration, a hard limit is set. reconnected = False mail_notify = True diff --git a/autosubmit/platforms/paramiko_platform.py b/autosubmit/platforms/paramiko_platform.py index 43adfd5c6..e57512f55 100644 --- a/autosubmit/platforms/paramiko_platform.py +++ b/autosubmit/platforms/paramiko_platform.py @@ -452,17 +452,20 @@ class ParamikoPlatform(Platform): """ raise NotImplementedError - def check_job(self, job, default_status=Status.COMPLETED, retries=5, submit_hold_check=False): + def check_job(self, job, default_status=Status.COMPLETED, retries=5, submit_hold_check=False, is_wrapper=False): """ Checks job running status :param retries: retries :param job: job + :type job: autosubmit.job.job.Job + :param default_status: default status if job is not found :type job: class(job) :param default_status: status to assign if it can be retrieved from the platform :type default_status: autosubmit.job.job_common.Status :return: current job status :rtype: autosubmit.job.job_common.Status + """ job_id = job.id job_status = Status.UNKNOWN @@ -491,19 +494,20 @@ class ParamikoPlatform(Platform): job_status = Status.COMPLETED elif job_status in self.job_status['RUNNING']: job_status = Status.RUNNING - if job.status != Status.RUNNING: - job.start_time = datetime.datetime.now() # URi: start time - if job.start_time is not None and str(job.wrapper_type).lower() == "none": - wallclock = job.wallclock - if job.wallclock == "00:00": - wallclock == job.platform.max_wallclock - if wallclock != "00:00" and wallclock != "00:00:00" and wallclock != "": - if job.is_over_wallclock(job.start_time,wallclock): - try: - job.platform.get_completed_files(job.name) - job_status = job.check_completion(over_wallclock=True) - except: - job_status = Status.FAILED + if not is_wrapper: + if job.status != Status.RUNNING: + job.start_time = datetime.datetime.now() # URi: start time + if job.start_time is not None and str(job.wrapper_type).lower() == "none": + wallclock = job.wallclock + if job.wallclock == "00:00": + wallclock == job.platform.max_wallclock + if wallclock != "00:00" and wallclock != "00:00:00" and wallclock != "": + if job.is_over_wallclock(job.start_time,wallclock): + try: + job.platform.get_completed_files(job.name) + job_status = job.check_completion(over_wallclock=True) + except: + job_status = Status.FAILED elif job_status in self.job_status['QUEUING'] and job.hold is False: job_status = Status.QUEUING elif job_status in self.job_status['QUEUING'] and job.hold is True: diff --git a/autosubmit/platforms/platform.py b/autosubmit/platforms/platform.py index c2ccf3575..acbb20aa7 100644 --- a/autosubmit/platforms/platform.py +++ b/autosubmit/platforms/platform.py @@ -384,7 +384,7 @@ class Platform(object): """ raise NotImplementedError - def check_job(self, jobid, default_status=Status.COMPLETED, retries=5): + def check_job(self, job, default_status=Status.COMPLETED, retries=5, submit_hold_check=False, is_wrapper=False): """ Checks job running status diff --git a/environment.yml b/environment.yml index 4585486d9..bc6e7308b 100644 --- a/environment.yml +++ b/environment.yml @@ -16,6 +16,7 @@ dependencies: - portalocker - networkx - python=2.7 +- requests - pip: - bscearth.utils - Xlib diff --git a/log/log.py b/log/log.py index ae3ca5a74..216fc23eb 100644 --- a/log/log.py +++ b/log/log.py @@ -161,7 +161,7 @@ class Log: logging.getLogger(name) @staticmethod - def set_file(file_path, type='out', level=WARNING): + def set_file(file_path, type='out', level="WARNING"): """ Configure the file to store the log. If another file was specified earlier, new messages will only go to the new file. @@ -169,6 +169,19 @@ class Log: :param file_path: file to store the log :type file_path: str """ + levels = {} + levels["STATUS_FAILED"] = 500 + levels["STATUS"] = 1000 + levels["DEBUG"] = 2000 + levels["WARNING"] = 3000 + levels["INFO"] = 4000 + levels["RESULT"] = 5000 + levels["ERROR"] = 6000 + levels["CRITICAL"] = 7000 + levels["NO_LOG"] = levels["CRITICAL"] + 1000 + + level = levels.get(str(level).upper(),"DEBUG") + max_retrials = 3 retrials = 0 timeout = 5 diff --git a/setup.py b/setup.py index d4d0f0179..8e56eb8c5 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ setup( keywords=['climate', 'weather', 'workflow', 'HPC'], install_requires=['argparse>=1.2,<2','six>=1.10.0','argcomplete==1.10.3', 'python-dateutil>2', 'pydotplus>=2', 'pyparsing>=2.0.1', 'numpy', 'matplotlib', 'typing', 'paramiko == 2.7.1', - 'mock>=1.3.0', 'portalocker==0.5.7', 'networkx', 'bscearth.utils', 'Xlib == 0.21'], + 'mock>=1.3.0', 'portalocker==0.5.7', 'networkx', 'bscearth.utils', 'Xlib == 0.21', 'requests'], extras_require={ 'dialog': ["python2-pythondialog>=3.3.0"] }, -- GitLab From 417305910c1b0331d341cde655b9ad518b6bed96 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Wed, 10 Aug 2022 15:53:09 +0200 Subject: [PATCH 08/45] stat fix --- autosubmit/autosubmit.py | 1 + autosubmit/job/job.py | 13 ++++++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/autosubmit/autosubmit.py b/autosubmit/autosubmit.py index 8704d27f3..03853b178 100644 --- a/autosubmit/autosubmit.py +++ b/autosubmit/autosubmit.py @@ -1763,6 +1763,7 @@ class Autosubmit: save2 = job_list.update_list( as_conf, submitter=submitter) job_list.save() + if len(job_list.get_ready()) > 0: save = Autosubmit.submit_ready_jobs( as_conf, job_list, platforms_to_test, packages_persistence, hold=False) diff --git a/autosubmit/job/job.py b/autosubmit/job/job.py index 948269142..28c9b2be9 100644 --- a/autosubmit/job/job.py +++ b/autosubmit/job/job.py @@ -630,10 +630,9 @@ class Job(object): found = False retrials = 0 while retrials < 3 and not found: - sleep(2) if platform.check_stat_file_by_retrials(stat_file + str(max_logs)): found = True - retrials = retrials - 1 + retrials = retrials + 1 for i in range(max_logs-1,-1,-1): if platform.check_stat_file_by_retrials(stat_file + str(i)): last_log = i @@ -1181,18 +1180,18 @@ class Job(object): if self.type == Type.BASH: template = 'sleep 5' + "\n" elif self.type == Type.PYTHON: - template = 'time.sleep(30)' + "\n" + template = 'time.sleep(5)' + "\n" elif self.type == Type.R: - template = 'Sys.sleep(30)' + "\n" + template = 'Sys.sleep(5)' + "\n" template += template_file.read() template_file.close() else: if self.type == Type.BASH: - template = 'sleep 35' + template = 'sleep 5' elif self.type == Type.PYTHON: - template = 'time.sleep(35)' + template = 'time.sleep(5)' elif self.type == Type.R: - template = 'Sys.sleep(35)' + template = 'Sys.sleep(5)' else: template = '' except: -- GitLab From 29676178bca47777f1e5561eedd1f001bf73a7a2 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Wed, 10 Aug 2022 16:44:23 +0200 Subject: [PATCH 09/45] wrapper_type is now being saved correctly --- autosubmit/autosubmit.py | 1 - autosubmit/job/job_list.py | 9 +++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/autosubmit/autosubmit.py b/autosubmit/autosubmit.py index 03853b178..8704d27f3 100644 --- a/autosubmit/autosubmit.py +++ b/autosubmit/autosubmit.py @@ -1763,7 +1763,6 @@ class Autosubmit: save2 = job_list.update_list( as_conf, submitter=submitter) job_list.save() - if len(job_list.get_ready()) > 0: save = Autosubmit.submit_ready_jobs( as_conf, job_list, platforms_to_test, packages_persistence, hold=False) diff --git a/autosubmit/job/job_list.py b/autosubmit/job/job_list.py index d880e3dc4..395e07467 100644 --- a/autosubmit/job/job_list.py +++ b/autosubmit/job/job_list.py @@ -215,6 +215,15 @@ class JobList(object): new, notransitive, update_structure=update_structure) for job in self._job_list: job.parameters = parameters + job_data = jobs_data.get(job.name,"none") + try: + if job_data != "none": + job.wrapper_type = job_data[12] + else: + job.wrapper_type = "none" + except BaseException as e: + job.wrapper_type = "none" + # Checking for member constraints if len(run_only_members) > 0: # Found -- GitLab From 5137b0022be260c23150e66bb153a52d86abf5a9 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Mon, 29 Aug 2022 08:45:34 +0200 Subject: [PATCH 10/45] erased debug info, changed exception for baseexception --- autosubmit/job/job.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/autosubmit/job/job.py b/autosubmit/job/job.py index 28c9b2be9..1056b93f6 100644 --- a/autosubmit/job/job.py +++ b/autosubmit/job/job.py @@ -620,10 +620,9 @@ class Job(object): submitter = self._get_submitter(as_conf) submitter.load_platforms(as_conf) platform = submitter.platforms[platform_name.lower()] - try: - platform.test_connection() - except: - pass + + platform.test_connection() + max_logs = int(as_conf.get_retrials()) - fail_count last_log = int(as_conf.get_retrials()) - fail_count if self.wrapper_type is not None and self.wrapper_type == "vertical": @@ -643,7 +642,7 @@ class Job(object): else: remote_logs = (self.script_name + ".out."+str(fail_count), self.script_name + ".err." + str(fail_count)) - except Exception as e: + except BaseException as e: Log.printlog( "{0} \n Couldn't connect to the remote platform for this {1} job err/out files. ".format(e.message, self.name), 6001) out_exist = False -- GitLab From ce7a4b1be6d3a6bd1ca782d1e3fda5ee2545f4ab Mon Sep 17 00:00:00 2001 From: dbeltran Date: Mon, 29 Aug 2022 12:53:18 +0200 Subject: [PATCH 11/45] Fixed delay issue #862 --- autosubmit/job/job_list.py | 60 +++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/autosubmit/job/job_list.py b/autosubmit/job/job_list.py index 395e07467..395c97e4c 100644 --- a/autosubmit/job/job_list.py +++ b/autosubmit/job/job_list.py @@ -415,37 +415,37 @@ class JobList(object): if dependency.splits is not None: parent = filter( lambda _parent: _parent.split in dependency.splits, parent) - #Select chunk + select member - if parent.running in ["once"] or ( len(dependency.select_members_orig) <= 0 and len(dependency.select_chunks_orig) <= 0): - job.add_parent(parent) - JobList._add_edge(graph, job, parent) - elif len(dependency.select_members_orig) > 0: - for relation_indx in member_relations_to_add: - if member_list.index(parent.member) in dependency.select_members_dest[relation_indx] or len(dependency.select_members_dest[relation_indx]) <= 0: - if parent not in visited_parents: - job.add_parent(parent) - JobList._add_edge(graph, job, parent) - other_parents.remove(parent) - visited_parents.add(parent) - elif len(dependency.select_chunks_orig) > 0: + #Select chunk + select member + if parent.running in ["once"] or ( len(dependency.select_members_orig) <= 0 and len(dependency.select_chunks_orig) <= 0): + job.add_parent(parent) + JobList._add_edge(graph, job, parent) + elif len(dependency.select_members_orig) > 0: + for relation_indx in member_relations_to_add: + if member_list.index(parent.member) in dependency.select_members_dest[relation_indx] or len(dependency.select_members_dest[relation_indx]) <= 0: + if parent not in visited_parents: + job.add_parent(parent) + JobList._add_edge(graph, job, parent) + other_parents.remove(parent) + visited_parents.add(parent) + elif len(dependency.select_chunks_orig) > 0: + for relation_indx in chunk_relations_to_add: + if parent.chunk in dependency.select_chunks_dest[relation_indx] or len( + dependency.select_chunks_dest[relation_indx]) == 0: + if parent not in visited_parents: + job.add_parent(parent) + JobList._add_edge(graph, job, parent) + other_parents.remove(parent) + visited_parents.add(parent) + # If job doesn't have any parent after a first search, search in all dependency.section. This is to avoid +1 being added only to the last one. + if len(job.parents) <= 0: for relation_indx in chunk_relations_to_add: - if parent.chunk in dependency.select_chunks_dest[relation_indx] or len( - dependency.select_chunks_dest[relation_indx]) == 0: - if parent not in visited_parents: - job.add_parent(parent) - JobList._add_edge(graph, job, parent) - other_parents.remove(parent) - visited_parents.add(parent) - # If job doesn't have any parent after a first search, search in all dependency.section. This is to avoid +1 being added only to the last one. - if len(job.parents) <= 0: - for relation_indx in chunk_relations_to_add: - for parent in jobs_by_section: - if parent.chunk in dependency.select_chunks_dest[relation_indx] or len( - dependency.select_chunks_dest[relation_indx]) == 0: - if parent not in visited_parents: - job.add_parent(parent) - JobList._add_edge(graph, job, parent) - visited_parents.add(parent) + for parent in jobs_by_section: + if parent.chunk in dependency.select_chunks_dest[relation_indx] or len( + dependency.select_chunks_dest[relation_indx]) == 0: + if parent not in visited_parents: + job.add_parent(parent) + JobList._add_edge(graph, job, parent) + visited_parents.add(parent) JobList.handle_frequency_interval_dependencies(chunk, chunk_list, date, date_list, dic_jobs, job, member, member_list, dependency.section, graph, other_parents) -- GitLab From 9e68d54234bbbf51a089776dde1db8e9e494e195 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Mon, 29 Aug 2022 13:40:57 +0200 Subject: [PATCH 12/45] Added 5min retrial in case that something is wrong while recovering the As_conf info inside a thread. --- autosubmit/job/job.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/autosubmit/job/job.py b/autosubmit/job/job.py index 1056b93f6..325564bec 100644 --- a/autosubmit/job/job.py +++ b/autosubmit/job/job.py @@ -614,15 +614,27 @@ class Job(object): max_logs = 0 sleep(5) stat_file = self.script_name[:-4] + "_STAT_" + retries = 2 + count = 0 + success = False + error_message = "" + while count < retries or success: + try: + as_conf = AutosubmitConfig(expid, BasicConfig, ConfigParserFactory()) + as_conf.reload() + submitter = self._get_submitter(as_conf) + submitter.load_platforms(as_conf) + success = True + except BaseException as e: + error_message = str(e) + sleep(60*5) + pass + count=count+1 + if not success: + raise AutosubmitError("Couldn't load the autosubmit platforms, seems that the local platform has some issue\n:{0}".format(error_message),6006) + platform = submitter.platforms[platform_name.lower()] try: - as_conf = AutosubmitConfig(expid, BasicConfig, ConfigParserFactory()) - as_conf.reload() - submitter = self._get_submitter(as_conf) - submitter.load_platforms(as_conf) - platform = submitter.platforms[platform_name.lower()] - platform.test_connection() - max_logs = int(as_conf.get_retrials()) - fail_count last_log = int(as_conf.get_retrials()) - fail_count if self.wrapper_type is not None and self.wrapper_type == "vertical": @@ -644,7 +656,7 @@ class Job(object): except BaseException as e: Log.printlog( - "{0} \n Couldn't connect to the remote platform for this {1} job err/out files. ".format(e.message, self.name), 6001) + "{0} \n Couldn't connect to the remote platform for {1} job err/out files. ".format(e.message, self.name), 6001) out_exist = False err_exist = False retries = 3 -- GitLab From e065788458a9e0ec3077463c0ef24844e2eeebcc Mon Sep 17 00:00:00 2001 From: dbeltran Date: Wed, 31 Aug 2022 15:30:45 +0200 Subject: [PATCH 13/45] e --- autosubmit/autosubmit.py | 2 +- autosubmit/job/job.py | 2 +- autosubmit/platforms/slurmplatform.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/autosubmit/autosubmit.py b/autosubmit/autosubmit.py index 8704d27f3..b299c7dcc 100644 --- a/autosubmit/autosubmit.py +++ b/autosubmit/autosubmit.py @@ -2227,7 +2227,7 @@ class Autosubmit: "{0} submission failed, some hold jobs failed to be held".format(platform.name), 6015) except WrongTemplateException as e: raise AutosubmitCritical("Invalid parameter substitution in {0} template".format( - e.job_name), 7014, e.message) + e.job_name), 7014, str(e)) except AutosubmitError as e: raise except AutosubmitCritical as e: diff --git a/autosubmit/job/job.py b/autosubmit/job/job.py index 325564bec..1068dca65 100644 --- a/autosubmit/job/job.py +++ b/autosubmit/job/job.py @@ -618,7 +618,7 @@ class Job(object): count = 0 success = False error_message = "" - while count < retries or success: + while (count < retries) or success: try: as_conf = AutosubmitConfig(expid, BasicConfig, ConfigParserFactory()) as_conf.reload() diff --git a/autosubmit/platforms/slurmplatform.py b/autosubmit/platforms/slurmplatform.py index cd96b21cc..5d31690c4 100644 --- a/autosubmit/platforms/slurmplatform.py +++ b/autosubmit/platforms/slurmplatform.py @@ -362,8 +362,8 @@ class SlurmPlatform(ParamikoPlatform): return export + self._submit_hold_cmd + job_script else: if not hold: - self._submit_script_file.write( - export + self._submit_cmd + job_script + "\n") + write_this = export + self._submit_cmd + job_script +"\n" + self._submit_script_file.write(write_this) else: self._submit_script_file.write( export + self._submit_hold_cmd + job_script + "\n") -- GitLab From 1d79e5d748f7e7071ec0d4650c1b16d11bff7c96 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Wed, 31 Aug 2022 15:34:19 +0200 Subject: [PATCH 14/45] e --- 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 1068dca65..9365e516f 100644 --- a/autosubmit/job/job.py +++ b/autosubmit/job/job.py @@ -618,7 +618,7 @@ class Job(object): count = 0 success = False error_message = "" - while (count < retries) or success: + while (count < retries) or not success: try: as_conf = AutosubmitConfig(expid, BasicConfig, ConfigParserFactory()) as_conf.reload() -- GitLab From 4ce7f18eaa5288980bffa600211e8c6cb884675e Mon Sep 17 00:00:00 2001 From: dbeltran Date: Wed, 31 Aug 2022 15:50:30 +0200 Subject: [PATCH 15/45] fixed message --- autosubmit/platforms/paramiko_submitter.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/autosubmit/platforms/paramiko_submitter.py b/autosubmit/platforms/paramiko_submitter.py index c597274f7..acba2bcce 100644 --- a/autosubmit/platforms/paramiko_submitter.py +++ b/autosubmit/platforms/paramiko_submitter.py @@ -184,8 +184,9 @@ class ParamikoSubmitter(Submitter): None) remote_platform.custom_directives = parser.get_option(section, 'CUSTOM_DIRECTIVES', None) - Log.debug("Custom directives from platform.conf: {0}".format( - remote_platform.custom_directives)) + if remote_platform.custom_directives is not None and remote_platform.custom_directives != '' and remote_platform.custom_directives != 'None': + Log.debug("Custom directives from platform.conf: {0}".format( + remote_platform.custom_directives)) remote_platform.scratch_free_space = parser.get_option(section, 'SCRATCH_FREE_SPACE', None) remote_platform.root_dir = os.path.join(remote_platform.scratch, remote_platform.project, -- GitLab From e768dde8f1892ce6c5c75712a539f129c21fff7b Mon Sep 17 00:00:00 2001 From: dbeltran Date: Tue, 6 Sep 2022 10:53:49 +0200 Subject: [PATCH 16/45] conda fix --- docs/source/installation.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 5dd60a136..9a90c4e54 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -172,9 +172,9 @@ Sequence of instructions to install Autosubmit and its dependencies with conda. .. code-block:: bash # Download conda - wget https://repo.anaconda.com/miniconda/Miniconda3-py39_4.12.0-Linux-x86_64.sh./Miniconda3-py39_4.12.0-Linux-x86_64.sh + wget https://repo.anaconda.com/miniconda/Miniconda3-py39_4.12.0-Linux-x86_64.sh # Launch it - ./Miniconda3-py39_4.12.0-Linux-x86_64.sh + chmod + x ; ./Miniconda3-py39_4.12.0-Linux-x86_64.sh # Download git apt install git -y -q # Download autosubmit -- GitLab From 578751f40bf1a910b3adcba0862dec86c455e6be Mon Sep 17 00:00:00 2001 From: dbeltran Date: Tue, 6 Sep 2022 11:01:23 +0200 Subject: [PATCH 17/45] conda fix --- docs/source/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 9a90c4e54..64b314886 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -174,7 +174,7 @@ Sequence of instructions to install Autosubmit and its dependencies with conda. # Download conda wget https://repo.anaconda.com/miniconda/Miniconda3-py39_4.12.0-Linux-x86_64.sh # Launch it - chmod + x ; ./Miniconda3-py39_4.12.0-Linux-x86_64.sh + chmod +x ./Miniconda3-py39_4.12.0-Linux-x86_64.sh ; ./Miniconda3-py39_4.12.0-Linux-x86_64.sh # Download git apt install git -y -q # Download autosubmit -- GitLab From 3cdfa7f700b99e04217cf77ad570ce332980fb9d Mon Sep 17 00:00:00 2001 From: dbeltran Date: Wed, 7 Sep 2022 15:23:52 +0200 Subject: [PATCH 18/45] Wrapper is now fully independent from total and waiting jobs as expected #857 --- autosubmit/autosubmit.py | 5 +-- autosubmit/config/config_common.py | 9 ++-- autosubmit/job/job_packager.py | 52 +++++++++++----------- autosubmit/platforms/paramiko_submitter.py | 4 +- 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/autosubmit/autosubmit.py b/autosubmit/autosubmit.py index b299c7dcc..6fd5932a3 100644 --- a/autosubmit/autosubmit.py +++ b/autosubmit/autosubmit.py @@ -1372,8 +1372,8 @@ class Autosubmit: while job_list.get_active(): Autosubmit.submit_ready_jobs(as_conf, job_list, platforms_to_test, packages_persistence, True, only_wrappers, hold=False) - for job in job_list.get_uncompleted_and_not_waiting(): - job.status = Status.COMPLETED + #for job in job_list.get_uncompleted_and_not_waiting(): + # job.status = Status.COMPLETED job_list.update_list(as_conf, False) @staticmethod @@ -2071,7 +2071,6 @@ class Autosubmit: platform.open_submit_script() valid_packages_to_submit = [] # type: List[JobPackageBase] for package in packages_to_submit: - try: # If called from inspect command or -cw if only_wrappers or inspect: diff --git a/autosubmit/config/config_common.py b/autosubmit/config/config_common.py index e3e9188a4..3f5c39a3b 100644 --- a/autosubmit/config/config_common.py +++ b/autosubmit/config/config_common.py @@ -1600,7 +1600,9 @@ class AutosubmitConfig(object): :return: maximum number of jobs (or total jobs) :rtype: int """ - return int(self._conf_parser.get_option(wrapper_section_name, 'MAX_WRAPPED', self.get_total_jobs())) + #total_jobs = self.get_total_jobs() + #unlimited because wrapper should count as one + return int(self._conf_parser.get_option(wrapper_section_name, 'MAX_WRAPPED', 999999999)) def get_max_wrapped_jobs_vertical(self, wrapper_section_name="wrapper"): """ @@ -1609,8 +1611,7 @@ class AutosubmitConfig(object): :return: maximum number of jobs (or total jobs) :rtype: int """ - max_wrapped = self.get_max_wrapped_jobs(wrapper_section_name) - return int(self._conf_parser.get_option(wrapper_section_name, 'MAX_WRAPPED_V', max_wrapped)) + return int(self._conf_parser.get_option(wrapper_section_name, 'MAX_WRAPPED_V', -1)) def get_max_wrapped_jobs_horizontal(self, wrapper_section_name="wrapper"): """ @@ -1620,7 +1621,7 @@ class AutosubmitConfig(object): :rtype: int """ max_wrapped = self.get_max_wrapped_jobs(wrapper_section_name) - return int(self._conf_parser.get_option(wrapper_section_name, 'MAX_WRAPPED_H', max_wrapped)) + return int(self._conf_parser.get_option(wrapper_section_name, 'MAX_WRAPPED_H', -1)) def get_min_wrapped_jobs_vertical(self, wrapper_section_name="wrapper"): """ diff --git a/autosubmit/job/job_packager.py b/autosubmit/job/job_packager.py index 54a6268c3..cfc1235e8 100644 --- a/autosubmit/job/job_packager.py +++ b/autosubmit/job/job_packager.py @@ -57,7 +57,12 @@ class JobPackager(object): # Submitted + Queuing Jobs for specific Platform queuing_jobs = jobs_list.get_queuing(platform) # We now consider the running jobs count - running_jobs_count = len(jobs_list.get_running(platform)) + running_jobs = jobs_list.get_running(platform) + running_by_id = dict() + for running_job in running_jobs: + running_by_id[running_job.id] = running_job + running_jobs_len = len(running_by_id.keys()) + queued_by_id = dict() for queued_job in queuing_jobs: queued_by_id[queued_job.id] = queued_job @@ -76,10 +81,9 @@ class JobPackager(object): # .total_jobs Maximum number of jobs at the same time self._max_jobs_to_submit = platform.total_jobs - queuing_jobs_len # Substracting running jobs - self._max_jobs_to_submit = self._max_jobs_to_submit - running_jobs_count + self._max_jobs_to_submit = self._max_jobs_to_submit - running_jobs_len self._max_jobs_to_submit = self._max_jobs_to_submit if self._max_jobs_to_submit > 0 else 0 - self.max_jobs = min(self._max_wait_jobs_to_submit, - self._max_jobs_to_submit) + self.max_jobs = min(self._max_wait_jobs_to_submit,self._max_jobs_to_submit) self.wrapper_type["wrapper"] = self._as_config.get_wrapper_type() self.wrapper_policy["wrapper"] = self._as_config.get_wrapper_policy() @@ -94,24 +98,15 @@ class JobPackager(object): self.jobs_in_wrapper[wrapper_section] = self._as_config.get_wrapper_jobs(wrapper_section) self.extensible_wallclock[wrapper_section] = int(self._as_config.get_extensible_wallclock(wrapper_section)) self.wrapper_info = [self.wrapper_type,self.wrapper_policy,self.wrapper_method,self.jobs_in_wrapper,self.extensible_wallclock] # to pass to job_packages - - - # True or False - - Log.debug( - "Number of jobs available: {0}", self._max_wait_jobs_to_submit) + Log.debug("Number of jobs available: {0}", self._max_wait_jobs_to_submit) if self.hold: - Log.debug("Number of jobs prepared: {0}", len( - jobs_list.get_prepared(platform))) + Log.debug("Number of jobs prepared: {0}", len(jobs_list.get_prepared(platform))) if len(jobs_list.get_prepared(platform)) > 0: - Log.debug("Jobs ready for {0}: {1}", self._platform.name, len( - jobs_list.get_prepared(platform))) + Log.debug("Jobs ready for {0}: {1}", self._platform.name, len(jobs_list.get_prepared(platform))) else: - Log.debug("Number of jobs ready: {0}", len( - jobs_list.get_ready(platform, hold=False))) + Log.debug("Number of jobs ready: {0}", len(jobs_list.get_ready(platform, hold=False))) if len(jobs_list.get_ready(platform)) > 0: - Log.debug("Jobs ready for {0}: {1}", self._platform.name, len( - jobs_list.get_ready(platform))) + Log.debug("Jobs ready for {0}: {1}", self._platform.name, len(jobs_list.get_ready(platform))) self._maxTotalProcessors = 0 def compute_weight(self, job_list): @@ -210,8 +205,7 @@ class JobPackager(object): # Sort by Priority, highest first list_of_available = sorted( available_sorted, key=lambda k: k.priority, reverse=True) - num_jobs_to_submit = min(self._max_wait_jobs_to_submit, len( - jobs_ready), self._max_jobs_to_submit) + num_jobs_to_submit = min(self._max_wait_jobs_to_submit, len(jobs_ready), self._max_jobs_to_submit) # Take the first num_jobs_to_submit from the list of available jobs_to_submit_tmp = list_of_available[0:num_jobs_to_submit] #jobs_to_submit = [ @@ -248,6 +242,10 @@ class JobPackager(object): wrapper_limits["max_h"] = self._as_config.get_max_wrapped_jobs_horizontal(self.current_wrapper_section) if wrapper_limits["max"] < wrapper_limits["max_v"] * wrapper_limits["max_h"]: wrapper_limits["max"] = wrapper_limits["max_v"] * wrapper_limits["max_h"] + if wrapper_limits["max_v"] == -1: + wrapper_limits["max_v"] = wrapper_limits["max"] + if wrapper_limits["max_h"] == -1: + wrapper_limits["max_h"] = wrapper_limits["max"] if '&' not in section: if self._as_config.jobs_parser.has_option(section, 'DEPENDENCIES'): dependencies_keys = self._as_config.jobs_parser.get( @@ -552,7 +550,7 @@ class JobPackager(object): def _build_horizontal_packages(self, section_list, wrapper_limits, section): packages = [] horizontal_packager = JobPackagerHorizontal(section_list, self._platform.max_processors, wrapper_limits, - self.max_jobs, self._platform.processors_per_node, self.wrapper_method[self.current_wrapper_section]) + wrapper_limits["max"], self._platform.processors_per_node, self.wrapper_method[self.current_wrapper_section]) package_jobs = horizontal_packager.build_horizontal_package() @@ -585,11 +583,11 @@ class JobPackager(object): """ packages = [] for job in section_list: - if self.max_jobs > 0: + if wrapper_limits["max"] > 0: if job.packed is False: job.packed = True dict_jobs = self._jobs_list.get_ordered_jobs_by_date_member(self.current_wrapper_section) - job_vertical_packager = JobPackagerVerticalMixed(dict_jobs, job, [job], job.wallclock, self.max_jobs, wrapper_limits, self._platform.max_wallclock) + job_vertical_packager = JobPackagerVerticalMixed(dict_jobs, job, [job], job.wallclock, wrapper_limits["max"], wrapper_limits, self._platform.max_wallclock) jobs_list = job_vertical_packager.build_vertical_package(job) packages.append(JobPackageVertical(jobs_list, configuration=self._as_config,wrapper_section=self.current_wrapper_section,wrapper_info=wrapper_info)) @@ -605,7 +603,7 @@ class JobPackager(object): ## READY JOBS ## ## Create the horizontal ## horizontal_packager = JobPackagerHorizontal(jobs_list, self._platform.max_processors, wrapper_limits, - self.max_jobs, self._platform.processors_per_node) + wrapper_limits["max"], self._platform.processors_per_node) if self.wrapper_type[self.current_wrapper_section] == 'vertical-horizontal': return self._build_vertical_horizontal_package(horizontal_packager, jobs_resources) @@ -654,7 +652,7 @@ class JobPackager(object): horizontal_packager.wrapper_limits["max_by_section"][section] = horizontal_packager.wrapper_limits["max_by_section"][section] - 1 horizontal_packager.wrapper_limits["max"] = horizontal_packager.wrapper_limits["max"] - actual_wrapped_jobs for job in horizontal_package: - job_list = JobPackagerVerticalSimple([job], job.wallclock, self.max_jobs, + job_list = JobPackagerVerticalSimple([job], job.wallclock, horizontal_packager.wrapper_limits["max"], horizontal_packager.wrapper_limits, self._platform.max_wallclock).build_vertical_package(job) @@ -706,7 +704,7 @@ class JobPackagerVertical(object): :rtype: List() of Job Object \n """ # self.jobs_list starts as only 1 member, but wrapped jobs are added in the recursion - if len(self.jobs_list) >= self.max_jobs or len(self.jobs_list) >= self.wrapper_limits["max_v"] or len(self.jobs_list) >= self.wrapper_limits["max_by_section"][job.section] or len(self.jobs_list) >= self.wrapper_limits["max"]: + if len(self.jobs_list) >= self.wrapper_limits["max_v"] or len(self.jobs_list) >= self.wrapper_limits["max_by_section"][job.section] or len(self.jobs_list) >= self.wrapper_limits["max"]: return self.jobs_list child = self.get_wrappable_child(job) # If not None, it is wrappable @@ -897,7 +895,7 @@ class JobPackagerHorizontal(object): for section in jobs_by_section: current_package_by_section[section] = 0 for job in jobs_by_section[section]: - if self.max_jobs > 0 and len(current_package) < self.wrapper_limits["max_h"] and len(current_package) < self.wrapper_limits["max"] and current_package_by_section[section] < self.wrapper_limits["max_by_section"][section]: + if len(current_package) < self.wrapper_limits["max_h"] and len(current_package) < self.wrapper_limits["max"] and current_package_by_section[section] < self.wrapper_limits["max_by_section"][section]: if int(job.tasks) != 0 and int(job.tasks) != int(self.processors_node) and \ int(job.tasks) < job.total_processors: nodes = int( diff --git a/autosubmit/platforms/paramiko_submitter.py b/autosubmit/platforms/paramiko_submitter.py index acba2bcce..1f577426f 100644 --- a/autosubmit/platforms/paramiko_submitter.py +++ b/autosubmit/platforms/paramiko_submitter.py @@ -159,8 +159,10 @@ class ParamikoSubmitter(Submitter): asconf.get_max_processors()) remote_platform.max_waiting_jobs = int(parser.get_option(section, 'MAX_WAITING_JOBS', asconf.get_max_waiting_jobs())) - remote_platform.total_jobs = int(parser.get_option(section, 'TOTAL_JOBS', + totaljobs = int(parser.get_option(section, 'TOTALJOBS', asconf.get_total_jobs())) + total_jobs = int(parser.get_option(section, 'TOTAL_JOBS', asconf.get_total_jobs())) + remote_platform.total_jobs = min(min(totaljobs, total_jobs),asconf.get_total_jobs()) remote_platform.hyperthreading = parser.get_option(section, 'HYPERTHREADING', 'false').lower() remote_platform.project = parser.get_option( -- GitLab From f8a51172cb2f483cac5c013cfc090de213de1353 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Wed, 14 Sep 2022 11:45:53 +0200 Subject: [PATCH 19/45] error message fix --- autosubmit/platforms/paramiko_platform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autosubmit/platforms/paramiko_platform.py b/autosubmit/platforms/paramiko_platform.py index e57512f55..e1b36f116 100644 --- a/autosubmit/platforms/paramiko_platform.py +++ b/autosubmit/platforms/paramiko_platform.py @@ -901,7 +901,7 @@ class ParamikoPlatform(Platform): except AutosubmitError as e: raise except IOError as e: - raise AutosubmitError(e.message,6016) + raise AutosubmitError("IO issues, something seems wrong with {0}".format(self.name),6016,e.message) except BaseException as e: raise AutosubmitError('Command {0} in {1} warning: {2}'.format( command, self.host, '\n'.join(stderr_readlines)), 6005, e.message) -- GitLab From 835215e84ce616477a46c4233eff5a5ec41e7114 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Wed, 14 Sep 2022 15:45:24 +0200 Subject: [PATCH 20/45] docs update --- docs/source/devel_proj.rst | 19 ++++++++++++++++++- docs/source/faq.rst | 4 +++- .../usage/configuration/new_platform.rst | 4 ++-- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/docs/source/devel_proj.rst b/docs/source/devel_proj.rst index 056fb3265..17caddcf5 100644 --- a/docs/source/devel_proj.rst +++ b/docs/source/devel_proj.rst @@ -699,4 +699,21 @@ The custom directives can be used for multiple parameters at the same time using # [test [80] // small [40] // large [1040] MAX_PROCESSORS = 80 # test [40] / small [40] // large [40] - PROCESSORS_PER_NODE = 40 \ No newline at end of file + PROCESSORS_PER_NODE = 40 + +Controling the number of active concurrent tasks in an experiment +---------------------------------------------------------------------- + +In some cases, you may want to control the number of concurrent tasks/jobs that can be active in an experiment. + +To set the maximum number of concurrent tasks/jobs, you can use the ``TOTAL_JOBS`` and ``MAX_WAITING_JOBS`` variable in the ``conf/autosubmit_cxxx.conf`` file. + + vi /conf/autosubmit_cxxx.conf + +.. code-block:: ini + + # Maximum number of submitted,waiting and running tasks + TOTAL_JOBS = 10 + # Maximum number of submitted and waiting tasks + MAX_WAITING_JOBS = 10 + diff --git a/docs/source/faq.rst b/docs/source/faq.rst index 7d1e31b34..b659c6bdc 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -155,7 +155,9 @@ Minor errors - Error codes [6000+] +------+------------------------------------------------------+------------------------------------------------------------------------------------------------+ | 6013 | Configuration issues | Check log output for more info | +------+------------------------------------------------------+------------------------------------------------------------------------------------------------+ -| 6014 | Git Can't clone repository submodule | Check submodule url, perform a refresh | +| 6014 | Git Can't clone repository submodule | Check submodule url, perform a refresh | +------+------------------------------------------------------+------------------------------------------------------------------------------------------------+ | 6015 | Submission failed | Automatically, if there aren't bigger issues | +------+------------------------------------------------------+------------------------------------------------------------------------------------------------+ +| 6016 | Temporal connection issues | Automatically, if there aren't bigger issues | ++------+------------------------------------------------------+------------------------------------------------------------------------------------------------+ diff --git a/docs/source/usage/configuration/new_platform.rst b/docs/source/usage/configuration/new_platform.rst index 173dafae4..675d4edc6 100644 --- a/docs/source/usage/configuration/new_platform.rst +++ b/docs/source/usage/configuration/new_platform.rst @@ -53,9 +53,9 @@ There are some other parameters that you may need to specify: * TEST_SUITE: if true, autosubmit test command can use this queue as a main queue. Defaults to false -* MAX_WAITING_JOBS: maximum number of jobs to be waiting in this platform. +* MAX_WAITING_JOBS: maximum number of jobs to be queuing or submitted in this platform. -* TOTAL_JOBS: maximum number of jobs to be running at the same time in this platform. +* TOTAL_JOBS: Maximum number of jobs to be queuing, running or submitted at the same time in this platform. * CUSTOM_DIRECTIVES: Custom directives for the resource manager of this platform. -- GitLab From 50b2db0ce581933ea7c3f9e2f510ebee574fbc0b Mon Sep 17 00:00:00 2001 From: dbeltran Date: Fri, 16 Sep 2022 15:48:55 +0200 Subject: [PATCH 21/45] Now critical issues messages is always shown --- autosubmit/autosubmit.py | 7 ++++--- autosubmit/config/config_common.py | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/autosubmit/autosubmit.py b/autosubmit/autosubmit.py index 6fd5932a3..355260a76 100644 --- a/autosubmit/autosubmit.py +++ b/autosubmit/autosubmit.py @@ -4341,12 +4341,13 @@ class Autosubmit: raise AutosubmitCritical("Autosubmit couldn't identify the project destination.", 7014) if project_type == "git": - submitter = Autosubmit._get_submitter(as_conf) - submitter.load_platforms(as_conf) + try: + submitter = Autosubmit._get_submitter(as_conf) + submitter.load_platforms(as_conf) hpcarch = submitter.platforms[as_conf.get_platform()] except BaseException as e: - raise AutosubmitCritical("Can't set main platform", 7014, e.message) + raise AutosubmitCritical("Can't set main platform\nCheck the hpcarch platform configuration inside platform.conf", 7014) return AutosubmitGit.clone_repository(as_conf, force, hpcarch) elif project_type == "svn": diff --git a/autosubmit/config/config_common.py b/autosubmit/config/config_common.py index 3f5c39a3b..cc8aa3e1c 100644 --- a/autosubmit/config/config_common.py +++ b/autosubmit/config/config_common.py @@ -540,6 +540,8 @@ class AutosubmitConfig(object): # In case that there are critical errors in the configuration, Autosubmit won't continue. if running_time is True: raise AutosubmitCritical(e.message, e.code, e.trace) + else: + Log.printlog(e.message+"\n") except Exception as e: raise AutosubmitCritical( "There was an error while showing the config log messages", 7014, str(e)) -- GitLab From 022a881c7c83e2d40665d5267a984206985a4db0 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Wed, 21 Sep 2022 16:09:27 +0200 Subject: [PATCH 22/45] Patch for db_fix --- autosubmit/autosubmit.py | 20 ++++++++++++-------- requeriments.txt | 1 + 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/autosubmit/autosubmit.py b/autosubmit/autosubmit.py index 355260a76..60b064de9 100644 --- a/autosubmit/autosubmit.py +++ b/autosubmit/autosubmit.py @@ -1556,7 +1556,8 @@ class Autosubmit: exp_history.process_status_changes(job_list.get_job_list(), as_conf.get_chunk_size_unit(), as_conf.get_chunk_size(), current_config=as_conf.get_full_config_as_json()) except Exception as e: # This error is important - raise AutosubmitCritical("Error while processing historical database.", 7005, str(e)) + Log.printlog("Error while processing historical database.", 7005, str(e)) + try: ExperimentStatus(expid).set_as_running() except Exception as e: @@ -4224,13 +4225,16 @@ class Autosubmit: except BaseException as e: Log.printlog("Historic database seems corrupted, AS will repair it and resume the run", Log.INFO) - Autosubmit.database_fix(expid) - exp_history = ExperimentHistory(expid, jobdata_dir_path=BasicConfig.JOBDATA_DIR, - historiclog_dir_path=BasicConfig.HISTORICAL_LOG_DIR) - exp_history.initialize_database() - exp_history.create_new_experiment_run(as_conf.get_chunk_size_unit(), as_conf.get_chunk_size(), - as_conf.get_full_config_as_json(), - job_list.get_job_list()) + try: + Autosubmit.database_fix(expid) + exp_history = ExperimentHistory(expid, jobdata_dir_path=BasicConfig.JOBDATA_DIR, + historiclog_dir_path=BasicConfig.HISTORICAL_LOG_DIR) + exp_history.initialize_database() + exp_history.create_new_experiment_run(as_conf.get_chunk_size_unit(), as_conf.get_chunk_size(), + as_conf.get_full_config_as_json(), + job_list.get_job_list()) + except: + Log.warning("Couldn't recover the Historical database, AS will continue without it, GUI may be affected") if not noplot: if group_by: status = list() diff --git a/requeriments.txt b/requeriments.txt index d57974475..c34451db2 100644 --- a/requeriments.txt +++ b/requeriments.txt @@ -1,3 +1,4 @@ +configparser argparse>=1.2,<2 python-dateutil>2 matplotlib -- GitLab From d680e3652664c7acc4a05a80aef869392667d8c8 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Thu, 22 Sep 2022 09:53:04 +0200 Subject: [PATCH 23/45] Patch for db_fix (1) --- autosubmit/autosubmit.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/autosubmit/autosubmit.py b/autosubmit/autosubmit.py index 60b064de9..153c0c8a3 100644 --- a/autosubmit/autosubmit.py +++ b/autosubmit/autosubmit.py @@ -1776,9 +1776,22 @@ class Autosubmit: job_list.update_list(as_conf, submitter=submitter) job_list.save() # Safe spot to store changes - exp_history = ExperimentHistory(expid, jobdata_dir_path=BasicConfig.JOBDATA_DIR, historiclog_dir_path=BasicConfig.HISTORICAL_LOG_DIR) - if len(job_changes_tracker) > 0: - exp_history.process_job_list_changes_to_experiment_totals(job_list.get_job_list()) + try: + exp_history = ExperimentHistory(expid, jobdata_dir_path=BasicConfig.JOBDATA_DIR, + historiclog_dir_path=BasicConfig.HISTORICAL_LOG_DIR) + if len(job_changes_tracker) > 0: + exp_history.process_job_list_changes_to_experiment_totals(job_list.get_job_list()) + except BaseException as e: + Log.printlog("Historic database seems corrupted, AS will repair it and resume the run", + Log.INFO) + try: + Autosubmit.database_fix(expid) + exp_history = ExperimentHistory(expid, jobdata_dir_path=BasicConfig.JOBDATA_DIR, + historiclog_dir_path=BasicConfig.HISTORICAL_LOG_DIR) + if len(job_changes_tracker) > 0: + exp_history.process_job_list_changes_to_experiment_totals(job_list.get_job_list()) + except: + Log.warning("Couldn't recover the Historical database, AS will continue without it, GUI may be affected") job_changes_tracker = {} if Autosubmit.exit: job_list.save() @@ -1949,8 +1962,16 @@ class Autosubmit: raise AutosubmitCritical("There is a bug in the code, please contact via git",7070,e.message) Log.result("No more jobs to run.") # Updating job data header with current information when experiment ends - exp_history = ExperimentHistory(expid, jobdata_dir_path=BasicConfig.JOBDATA_DIR, historiclog_dir_path=BasicConfig.HISTORICAL_LOG_DIR) - exp_history.process_job_list_changes_to_experiment_totals(job_list.get_job_list()) + try: + exp_history = ExperimentHistory(expid, jobdata_dir_path=BasicConfig.JOBDATA_DIR, historiclog_dir_path=BasicConfig.HISTORICAL_LOG_DIR) + exp_history.process_job_list_changes_to_experiment_totals(job_list.get_job_list()) + except: + try: + Autosubmit.database_fix(expid) + exp_history = ExperimentHistory(expid, jobdata_dir_path=BasicConfig.JOBDATA_DIR, historiclog_dir_path=BasicConfig.HISTORICAL_LOG_DIR) + exp_history.process_job_list_changes_to_experiment_totals(job_list.get_job_list()) + except: + Log.printlog() # Wait for all remaining threads of I/O, close remaining connections timeout = 0 active_threads = True -- GitLab From 6b7ff9ef2c0f5cd355530e4a3971ae382e5dedb9 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Fri, 23 Sep 2022 15:05:21 +0200 Subject: [PATCH 24/45] Does an sql dump everytime a change is detected. Then db_fix load this sql dump --- autosubmit/autosubmit.py | 102 ++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 55 deletions(-) diff --git a/autosubmit/autosubmit.py b/autosubmit/autosubmit.py index 153c0c8a3..75baab6de 100644 --- a/autosubmit/autosubmit.py +++ b/autosubmit/autosubmit.py @@ -58,6 +58,7 @@ import locale from distutils.util import strtobool from log.log import Log, AutosubmitError, AutosubmitCritical from typing import Set +import sqlite3 try: import dialog @@ -71,6 +72,7 @@ import tarfile import time import copy import os +import glob import pwd import sys import shutil @@ -1553,11 +1555,14 @@ class Autosubmit: # Historical Database: Can create a new run if there is a difference in the number of jobs or if the current run does not exist. exp_history = ExperimentHistory(expid, jobdata_dir_path=BasicConfig.JOBDATA_DIR, historiclog_dir_path=BasicConfig.HISTORICAL_LOG_DIR) exp_history.initialize_database() - exp_history.process_status_changes(job_list.get_job_list(), as_conf.get_chunk_size_unit(), as_conf.get_chunk_size(), current_config=as_conf.get_full_config_as_json()) + exp_history.process_status_changes(job_list.get_job_list(), as_conf.get_chunk_size_unit(), as_conf.get_chunk_size(), current_config=as_conf.get_full_config_as_json()) + Autosubmit.database_backup(expid) except Exception as e: - # This error is important - Log.printlog("Error while processing historical database.", 7005, str(e)) - + try: + Autosubmit.database_fix(expid) + # This error is important + except: + pass try: ExperimentStatus(expid).set_as_running() except Exception as e: @@ -1781,6 +1786,7 @@ class Autosubmit: historiclog_dir_path=BasicConfig.HISTORICAL_LOG_DIR) if len(job_changes_tracker) > 0: exp_history.process_job_list_changes_to_experiment_totals(job_list.get_job_list()) + Autosubmit.database_backup(expid) except BaseException as e: Log.printlog("Historic database seems corrupted, AS will repair it and resume the run", Log.INFO) @@ -1790,6 +1796,7 @@ class Autosubmit: historiclog_dir_path=BasicConfig.HISTORICAL_LOG_DIR) if len(job_changes_tracker) > 0: exp_history.process_job_list_changes_to_experiment_totals(job_list.get_job_list()) + Autosubmit.database_backup(expid) except: Log.warning("Couldn't recover the Historical database, AS will continue without it, GUI may be affected") job_changes_tracker = {} @@ -1965,13 +1972,12 @@ class Autosubmit: try: exp_history = ExperimentHistory(expid, jobdata_dir_path=BasicConfig.JOBDATA_DIR, historiclog_dir_path=BasicConfig.HISTORICAL_LOG_DIR) exp_history.process_job_list_changes_to_experiment_totals(job_list.get_job_list()) + Autosubmit.database_backup(expid) except: try: Autosubmit.database_fix(expid) - exp_history = ExperimentHistory(expid, jobdata_dir_path=BasicConfig.JOBDATA_DIR, historiclog_dir_path=BasicConfig.HISTORICAL_LOG_DIR) - exp_history.process_job_list_changes_to_experiment_totals(job_list.get_job_list()) except: - Log.printlog() + pass # Wait for all remaining threads of I/O, close remaining connections timeout = 0 active_threads = True @@ -3901,6 +3907,17 @@ class Autosubmit: raise @staticmethod + def database_backup(expid): + try: + database_path= os.path.join(BasicConfig.JOBDATA_DIR, "job_data_{0}.db".format(expid)) + backup_path = os.path.join(BasicConfig.JOBDATA_DIR, "job_data_{0}.sql".format(expid)) + command = "sqlite3 {0} .dump > {1} ".format(database_path, backup_path) + Log.info("Backing up jobs_data...") + subprocess.call(command, shell=True) + Log.result("Jobs_data database backup completed.") + except BaseException as e: + Log.info("Jobs_data database backup failed.") + @staticmethod def database_fix(expid): """ Database methods. Performs a sql dump of the database and restores it. @@ -3912,52 +3929,31 @@ class Autosubmit: """ os.umask(0) # Overrides user permissions current_time = int(time.time()) + corrupted_db_path = os.path.join(BasicConfig.JOBDATA_DIR, "job_data_{0}_corrupted.db".format(expid)) + database_path = os.path.join(BasicConfig.JOBDATA_DIR, "job_data_{0}.db".format(expid)) - database_backup_path = os.path.join(BasicConfig.JOBDATA_DIR, "job_data_{0}_{1}.db".format(expid, str(current_time))) - dump_file_name = 'job_data_{0}_{1}.sql'.format(expid, current_time) + database_backup_path = os.path.join(BasicConfig.JOBDATA_DIR, "job_data_{0}.sql".format(expid)) + dump_file_name = 'job_data_{0}.sql'.format(expid, current_time) dump_file_path = os.path.join(BasicConfig.JOBDATA_DIR, dump_file_name) - bash_command = 'sqlite3 {0} .dump > {1}'.format(database_path, dump_file_path) + bash_command = 'cat {1} | sqlite3 {0}'.format(database_path, dump_file_path) try: - if os.path.exists(database_path): + if os.path.exists(database_path): + result = os.popen("mv {0} {1}".format(database_path, corrupted_db_path)).read() + time.sleep(10) + Log.info("Original database moved.") + try: + exp_history = ExperimentHistory(expid, jobdata_dir_path=BasicConfig.JOBDATA_DIR, + historiclog_dir_path=BasicConfig.HISTORICAL_LOG_DIR) + exp_history.initialize_database() + Log.info("Restoring from sql") result = os.popen(bash_command).read() - if result is not None and os.path.exists(dump_file_path): - Log.info("sqldump {0} created".format(dump_file_path)) - Log.info( - "Backing up original database {0}".format(database_path)) - result = os.popen("mv {0} {1}".format(database_path, database_backup_path)).read() - time.sleep(10) - if result is not None and not os.path.exists(database_path): - Log.info("Original database moved.") - Log.info("Restoring from sqldump") - HUtils.create_file_with_full_permissions(database_path) - result = os.popen("cat {0} | sqlite3 {1}".format( - dump_file_path, database_path)).read() - time.sleep(10) - if result is not None and os.path.exists(database_path): - Log.info( - "Database {0} restored.".format(database_path)) - Log.info("Deleting sqldump.") - result = os.popen( - "rm {0}".format(dump_file_path)).read() - sleep(5) - if result is not None and not os.path.exists(dump_file_path): - ExperimentHistory(expid).initialize_database() - Log.info("sqldump file deleted.") - Log.result( - "The database {0} has been fixed.".format(database_path)) - else: - raise Exception( - "The sqldump file could not be removed.") - else: - raise Exception( - "It was not possible to restore the sqldump file.") - else: - raise Exception( - "It was not possible to delete the original database.") - else: - raise Exception("The sqldump file couldn't be created.") - else: - raise Exception("The database file doesn't exist.") + except: + Log.warning("It was not possible to restore the jobs_data.db file... , a new blank db will be created") + result = os.popen("rm {0}".format(database_path)).read() + + exp_history = ExperimentHistory(expid, jobdata_dir_path=BasicConfig.JOBDATA_DIR, + historiclog_dir_path=BasicConfig.HISTORICAL_LOG_DIR) + exp_history.initialize_database() except Exception as exp: Log.critical(str(exp)) @@ -4243,17 +4239,12 @@ class Autosubmit: exp_history = ExperimentHistory(expid, jobdata_dir_path=BasicConfig.JOBDATA_DIR, historiclog_dir_path=BasicConfig.HISTORICAL_LOG_DIR) exp_history.initialize_database() exp_history.create_new_experiment_run(as_conf.get_chunk_size_unit(), as_conf.get_chunk_size(), as_conf.get_full_config_as_json(), job_list.get_job_list()) + Autosubmit.database_backup(expid) except BaseException as e: Log.printlog("Historic database seems corrupted, AS will repair it and resume the run", Log.INFO) try: Autosubmit.database_fix(expid) - exp_history = ExperimentHistory(expid, jobdata_dir_path=BasicConfig.JOBDATA_DIR, - historiclog_dir_path=BasicConfig.HISTORICAL_LOG_DIR) - exp_history.initialize_database() - exp_history.create_new_experiment_run(as_conf.get_chunk_size_unit(), as_conf.get_chunk_size(), - as_conf.get_full_config_as_json(), - job_list.get_job_list()) except: Log.warning("Couldn't recover the Historical database, AS will continue without it, GUI may be affected") if not noplot: @@ -5018,6 +5009,7 @@ class Autosubmit: exp_history = ExperimentHistory(expid, jobdata_dir_path=BasicConfig.JOBDATA_DIR, historiclog_dir_path=BasicConfig.HISTORICAL_LOG_DIR) exp_history.initialize_database() exp_history.process_status_changes(job_list.get_job_list(), chunk_unit=as_conf.get_chunk_size_unit(), chunk_size=as_conf.get_chunk_size(), current_config=as_conf.get_full_config_as_json()) + Autosubmit.database_backup(expid) else: Log.printlog( "Changes NOT saved to the JobList!!!!: use -s option to save", 3000) -- GitLab From 7213cb18e2abe25090b6a75f440eda6e730b4302 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Tue, 27 Sep 2022 09:21:49 +0200 Subject: [PATCH 25/45] database changes #870 --- autosubmit/autosubmit.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/autosubmit/autosubmit.py b/autosubmit/autosubmit.py index 75baab6de..337247605 100644 --- a/autosubmit/autosubmit.py +++ b/autosubmit/autosubmit.py @@ -3912,11 +3912,11 @@ class Autosubmit: database_path= os.path.join(BasicConfig.JOBDATA_DIR, "job_data_{0}.db".format(expid)) backup_path = os.path.join(BasicConfig.JOBDATA_DIR, "job_data_{0}.sql".format(expid)) command = "sqlite3 {0} .dump > {1} ".format(database_path, backup_path) - Log.info("Backing up jobs_data...") + Log.debug("Backing up jobs_data...") subprocess.call(command, shell=True) - Log.result("Jobs_data database backup completed.") + Log.debug("Jobs_data database backup completed.") except BaseException as e: - Log.info("Jobs_data database backup failed.") + Log.debug("Jobs_data database backup failed.") @staticmethod def database_fix(expid): """ -- GitLab From f1f3ea23923b2eabb669cdf3ecb517915c9365d9 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Fri, 30 Sep 2022 13:50:03 +0200 Subject: [PATCH 26/45] #877 conda typo --- docs/source/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 64b314886..4f68c3788 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -183,7 +183,7 @@ Sequence of instructions to install Autosubmit and its dependencies with conda. # Create conda environment conda env update -f environment.yml -n autosubmit python=2 # Activate env - source activate autosubmit + conda activate autosubmit # Test autosubmit autosubmit -v # Configure autosubmitrc and install database as indicated in this doc -- GitLab From 2867216631fe6d9c1017af331afc13c0635f2dc3 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Fri, 30 Sep 2022 13:50:53 +0200 Subject: [PATCH 27/45] #877 changed version to the lastest one (3.14.0b) --- docs/source/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 4f68c3788..7159ac7c0 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -178,7 +178,7 @@ Sequence of instructions to install Autosubmit and its dependencies with conda. # Download git apt install git -y -q # Download autosubmit - git clone https://earth.bsc.es/gitlab/es/autosubmit.git -b v3.14.0 + git clone https://earth.bsc.es/gitlab/es/autosubmit.git -b v3.14.0b cd autosubmit # Create conda environment conda env update -f environment.yml -n autosubmit python=2 -- GitLab From e41fab2383d907df115cbeaf1310755e60a0878c Mon Sep 17 00:00:00 2001 From: dbeltran Date: Mon, 3 Oct 2022 13:03:40 +0200 Subject: [PATCH 28/45] #inline comments, fixes for slrum --- autosubmit/autosubmit.py | 20 ++++-- autosubmit/platforms/paramiko_platform.py | 74 ++++++++++++++++------- test/regression/tests_runner.py | 1 + 3 files changed, 69 insertions(+), 26 deletions(-) diff --git a/autosubmit/autosubmit.py b/autosubmit/autosubmit.py index 337247605..09ce96335 100644 --- a/autosubmit/autosubmit.py +++ b/autosubmit/autosubmit.py @@ -1584,7 +1584,7 @@ class Autosubmit: if unparsed_two_step_start != "": job_list.parse_jobs_by_filter(unparsed_two_step_start) - main_loop_retrials = 3650 # Hard limit of tries 3650 tries at 15-120seconds sleep each try + main_loop_retrials = 11250*2 # Hard limit of tries ( 48h min 72h max), 2 retrials per stop # establish the connection to all platforms Autosubmit.restore_platforms(platforms_to_test) @@ -1822,7 +1822,7 @@ class Autosubmit: Log.printlog("Error trying to store failed job count",Log.WARNING) Log.result("Storing failed job count...done") while not recovery and main_loop_retrials > 0: - delay = min(15 * consecutive_retrials, 120) + delay = min(15 * consecutive_retrials, 30) main_loop_retrials = main_loop_retrials - 1 sleep(delay) consecutive_retrials = consecutive_retrials + 1 @@ -1959,7 +1959,7 @@ class Autosubmit: except BaseException: reconnected = False if main_loop_retrials <= 0: - raise AutosubmitCritical("Autosubmit Encounter too much errors during running time, limit of 4hours reached", 7051, e.message) + raise AutosubmitCritical("Autosubmit Encounter too much errors during running time, limit of {0} retrials reached".format(main_loop_retrials), 7051, e.message) except AutosubmitCritical as e: # Critical errors can't be recovered. Failed configuration or autosubmit error raise AutosubmitCritical(e.message, e.code, e.trace) except portalocker.AlreadyLocked: @@ -3322,7 +3322,12 @@ class Autosubmit: raise except BaseException as e: raise AutosubmitCritical("Unknown error while reporting the parameters list, likely it is due IO issues",7040,e.message) - + @staticmethod + def removeInlineComments(cfgparser): + for section in cfgparser.sections(): + for item in cfgparser.items(section): + cfgparser.set(section, item[0], item[1].split("#")[0].strip()) + return cfgparser @staticmethod def describe(experiment_id): """ @@ -3497,6 +3502,7 @@ class Autosubmit: parser.set("autosubmitapi", "url", autosubmitapi_url) #parser.add_section("hosts") #parser.set("hosts", "whitelist", " localhost # Add your machine names") + parser = Autosubmit.removeInlineComments(parser) parser.write(config_file) config_file.close() Log.result("Configuration file written successfully: \n\t{0}".format(rc_path)) @@ -3591,6 +3597,8 @@ class Autosubmit: parser = SafeConfigParser() parser.optionxform = str parser.read(path) + parser = Autosubmit.removeInlineComments(parser) + if parser.has_option('database', 'path'): database_path = parser.get('database', 'path') if parser.has_option('database', 'filename'): @@ -3723,11 +3731,15 @@ class Autosubmit: parser.add_section('mail') parser.set('mail', 'smtp_server', smtp_hostname) parser.set('mail', 'mail_from', mail_from) + parser = Autosubmit.removeInlineComments(parser) + parser.write(config_file) config_file.close() d.msgbox("Configuration file written successfully", width=50, height=5) os.system('clear') + + except (IOError, OSError) as e: raise AutosubmitCritical( "Can not write config file", 7012, e.message) diff --git a/autosubmit/platforms/paramiko_platform.py b/autosubmit/platforms/paramiko_platform.py index e1b36f116..fb9059915 100644 --- a/autosubmit/platforms/paramiko_platform.py +++ b/autosubmit/platforms/paramiko_platform.py @@ -550,35 +550,63 @@ class ParamikoPlatform(Platform): cmd = self.get_checkAlljobs_cmd(job_list_cmd) sleep_time = 5 sleep(sleep_time) - self.send_command(cmd) - while not self._check_jobid_in_queue(self.get_ssh_output(), job_list_cmd) and retries > 0: + slurm_error = False + e_msg = "" + try: self.send_command(cmd) - Log.debug('Retrying check job command: {0}', cmd) - Log.debug('retries left {0}', retries) - Log.debug('Will be retrying in {0} seconds', sleep_time) - retries -= 1 - sleep(sleep_time) - sleep_time = sleep_time + 5 + except AutosubmitError as e: + e_msg = e.trace+" "+e.message + slurm_error = True + if not slurm_error: + while not self._check_jobid_in_queue(self.get_ssh_output(), job_list_cmd) and retries > 0: + try: + self.send_command(cmd) + except AutosubmitError as e: + e_msg = e.trace + " " + e.message + slurm_error = True + break + Log.debug('Retrying check job command: {0}', cmd) + Log.debug('retries left {0}', retries) + Log.debug('Will be retrying in {0} seconds', sleep_time) + retries -= 1 + sleep(sleep_time) + sleep_time = sleep_time + 5 + job_list_status = self.get_ssh_output() if retries >= 0: Log.debug('Successful check job command') in_queue_jobs = [] list_queue_jobid = "" for job in job_list: - job_id = job.id - job_status = self.parse_Alljobs_output(job_list_status, job_id) - while len(job_status) <= 0 and retries >= 0: - retries -= 1 - self.send_command(cmd) - job_list_status = self.get_ssh_output() + if not slurm_error: + job_id = job.id job_status = self.parse_Alljobs_output(job_list_status, job_id) - if len(job_status) <= 0: - Log.debug('Retrying check job command: {0}', cmd) - Log.debug('retries left {0}', retries) - Log.debug('Will be retrying in {0} seconds', sleep_time) - sleep(sleep_time) - sleep_time = sleep_time + 5 - # URi: define status list in HPC Queue Class + while len(job_status) <= 0 and retries >= 0: + retries -= 1 + self.send_command(cmd) + job_list_status = self.get_ssh_output() + job_status = self.parse_Alljobs_output(job_list_status, job_id) + if len(job_status) <= 0: + Log.debug('Retrying check job command: {0}', cmd) + Log.debug('retries left {0}', retries) + Log.debug('Will be retrying in {0} seconds', sleep_time) + sleep(sleep_time) + sleep_time = sleep_time + 5 + # URi: define status list in HPC Queue Class + else: + if job.status != Status.RUNNING: + job.start_time = datetime.datetime.now() # URi: start time + if job.start_time is not None and str(job.wrapper_type).lower() == "none": + wallclock = job.wallclock + if job.wallclock == "00:00": + wallclock == job.platform.max_wallclock + if wallclock != "00:00" and wallclock != "00:00:00" and wallclock != "": + if job.is_over_wallclock(job.start_time,wallclock): + try: + job.platform.get_completed_files(job.name) + job_status = job.check_completion(over_wallclock=True) + except: + job_status = Status.FAILED if job_status in self.job_status['COMPLETED']: job_status = Status.COMPLETED elif job_status in self.job_status['RUNNING']: @@ -595,12 +623,12 @@ class ParamikoPlatform(Platform): elif retries == 0: job_status = Status.COMPLETED job.update_status(remote_logs) - else: job_status = Status.UNKNOWN Log.error( 'check_job() The job id ({0}) status is {1}.', job_id, job_status) job.new_status = job_status + reason = str() if self.type == 'slurm' and len(in_queue_jobs) > 0: cmd = self.get_queue_status_cmd(list_queue_jobid) @@ -639,6 +667,8 @@ class ParamikoPlatform(Platform): 'check_job() The job id ({0}) from platform {1} has an status of {2}.', job.id, self.name, job_status) raise AutosubmitError("Some Jobs are in Unknown status", 6008) # job.new_status=job_status + if slurm_error: + raise AutosubmitError(e_msg, 6000) def get_jobid_by_jobname(self,job_name,retries=2): """ diff --git a/test/regression/tests_runner.py b/test/regression/tests_runner.py index ffd490888..ab186e849 100644 --- a/test/regression/tests_runner.py +++ b/test/regression/tests_runner.py @@ -79,6 +79,7 @@ def run(current_experiment_id, only_list=None, exclude_list=None, max_threads=5) tests_parser.optionxform = str tests_parser.read(tests_parser_file) + # Resetting the database clean_database(db_path) create_database() -- GitLab From 540e8a02d20e486d3302df07ab938aaa778d3eb8 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Mon, 3 Oct 2022 15:43:21 +0200 Subject: [PATCH 29/45] Remove inline comments working #870 --- autosubmit/autosubmit.py | 13 +++---------- autosubmit/config/config_common.py | 9 +++++++++ autosubmit/config/config_parser.py | 5 ++++- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/autosubmit/autosubmit.py b/autosubmit/autosubmit.py index 09ce96335..82e4b44e9 100644 --- a/autosubmit/autosubmit.py +++ b/autosubmit/autosubmit.py @@ -3322,12 +3322,7 @@ class Autosubmit: raise except BaseException as e: raise AutosubmitCritical("Unknown error while reporting the parameters list, likely it is due IO issues",7040,e.message) - @staticmethod - def removeInlineComments(cfgparser): - for section in cfgparser.sections(): - for item in cfgparser.items(section): - cfgparser.set(section, item[0], item[1].split("#")[0].strip()) - return cfgparser + @staticmethod def describe(experiment_id): """ @@ -3502,7 +3497,6 @@ class Autosubmit: parser.set("autosubmitapi", "url", autosubmitapi_url) #parser.add_section("hosts") #parser.set("hosts", "whitelist", " localhost # Add your machine names") - parser = Autosubmit.removeInlineComments(parser) parser.write(config_file) config_file.close() Log.result("Configuration file written successfully: \n\t{0}".format(rc_path)) @@ -3597,7 +3591,6 @@ class Autosubmit: parser = SafeConfigParser() parser.optionxform = str parser.read(path) - parser = Autosubmit.removeInlineComments(parser) if parser.has_option('database', 'path'): database_path = parser.get('database', 'path') @@ -3731,8 +3724,6 @@ class Autosubmit: parser.add_section('mail') parser.set('mail', 'smtp_server', smtp_hostname) parser.set('mail', 'mail_from', mail_from) - parser = Autosubmit.removeInlineComments(parser) - parser.write(config_file) config_file.close() d.msgbox("Configuration file written successfully", @@ -5398,10 +5389,12 @@ class Autosubmit: raise AutosubmitCritical('Can not test a RERUN experiment', 7014) content = open(as_conf.experiment_file).read() + if random_select: if hpc is None: platforms_parser = as_conf.get_parser( ConfigParserFactory(), as_conf.platforms_file) + test_platforms = list() for section in platforms_parser.sections(): if platforms_parser.get_option(section, 'TEST_SUITE', 'false').lower() == 'true': diff --git a/autosubmit/config/config_common.py b/autosubmit/config/config_common.py index cc8aa3e1c..74dcc3e1e 100644 --- a/autosubmit/config/config_common.py +++ b/autosubmit/config/config_common.py @@ -1759,6 +1759,13 @@ class AutosubmitConfig(object): commit = self.get_git_project_commit() return origin_exists and (branch is not None or commit is not None) + @staticmethod + def removeInlineComments(cfgparser): + for section in cfgparser.sections(): + for item in cfgparser.items(section): + cfgparser.set(section, item[0], item[1].split("#")[0].strip()) + return cfgparser + @staticmethod def get_parser(parser_factory, file_path): """ @@ -1794,5 +1801,7 @@ class AutosubmitConfig(object): raise Exception( "{}\n This file and the correctness of its content are necessary.".format(str(exp))) # parser.read(file_path) + #remove inline comments + parser = AutosubmitConfig.removeInlineComments(parser) return parser diff --git a/autosubmit/config/config_parser.py b/autosubmit/config/config_parser.py index 87b28456a..99d92fd8c 100644 --- a/autosubmit/config/config_parser.py +++ b/autosubmit/config/config_parser.py @@ -14,8 +14,11 @@ class ConfigParserFactory: def __init__(self): pass + + def create_parser(self): - return ConfigParser() + parser = ConfigParser() + return parser class ConfigParser(ConfPar, object): -- GitLab From 31c925f9c2718cba9f48a62eae37dc589f490eb3 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Mon, 3 Oct 2022 16:04:02 +0200 Subject: [PATCH 30/45] setstatus doesn't crash anymore if the id does not exists --- autosubmit/autosubmit.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/autosubmit/autosubmit.py b/autosubmit/autosubmit.py index 82e4b44e9..37aa84475 100644 --- a/autosubmit/autosubmit.py +++ b/autosubmit/autosubmit.py @@ -4467,7 +4467,10 @@ class Autosubmit: if job.status in [Status.SUBMITTED, Status.QUEUING, Status.HELD] and final_status not in [Status.QUEUING, Status.HELD, Status.SUSPENDED]: job.hold = False if job.platform_name and job.platform_name.lower() != "local": - job.platform.send_command(job.platform.cancel_cmd + " " + str(job.id), ignore_log=True) + try: + job.platform.send_command(job.platform.cancel_cmd + " " + str(job.id), ignore_log=True) + except: + pass elif job.status in [Status.QUEUING, Status.RUNNING, Status.SUBMITTED] and final_status == Status.SUSPENDED: if job.platform_name and job.platform_name.lower() != "local": job.platform.send_command("scontrol hold " + "{0}".format(job.id), ignore_log=True) -- GitLab From 25c11e3c0521957fec64cec162ae96001b2bab8a Mon Sep 17 00:00:00 2001 From: dbeltran Date: Tue, 4 Oct 2022 11:08:39 +0200 Subject: [PATCH 31/45] Fixed e message error --- autosubmit/autosubmit.py | 14 +++++++------- autosubmit/config/config_common.py | 4 ++-- autosubmit/git/autosubmit_git.py | 4 ++-- autosubmit/job/job.py | 8 ++++---- autosubmit/job/job_dict.py | 2 +- autosubmit/job/job_list.py | 6 +++--- autosubmit/monitor/monitor.py | 2 +- autosubmit/platforms/paramiko_platform.py | 16 ++++++++-------- test/regression/tests_utils.py | 2 +- 9 files changed, 29 insertions(+), 29 deletions(-) diff --git a/autosubmit/autosubmit.py b/autosubmit/autosubmit.py index 37aa84475..2fca7cb7b 100644 --- a/autosubmit/autosubmit.py +++ b/autosubmit/autosubmit.py @@ -590,7 +590,7 @@ class Autosubmit: except Exception as e: if type(e) is SystemExit: # Version keyword force an exception in parse arg due and os_exit(0) but the program is succesfully finished - if e.message == 0: + if str(e) == 0: print(Autosubmit.autosubmit_version) os._exit(0) raise AutosubmitCritical( @@ -836,28 +836,28 @@ class Autosubmit: if ret: Log.result("Experiment {0} deleted".format(expid_delete)) except BaseException as e: - error_message += 'Can not delete experiment entry: {0}\n'.format(e.message) + error_message += 'Can not delete experiment entry: {0}\n'.format(str(e)) Log.info("Removing experiment directory...") try: shutil.rmtree(os.path.join(BasicConfig.LOCAL_ROOT_DIR, expid_delete)) except BaseException as e: - error_message += 'Can not delete directory: {0}\n'.format(e.message) + error_message += 'Can not delete directory: {0}\n'.format(str(e)) try: Log.info("Removing Structure db...") structures_path = os.path.join(BasicConfig.LOCAL_ROOT_DIR, BasicConfig.STRUCTURES_DIR, "structure_{0}.db".format(expid_delete)) if os.path.exists(structures_path): os.remove(structures_path) except BaseException as e: - error_message += 'Can not delete structure: {0}\n'.format(e.message) + error_message += 'Can not delete structure: {0}\n'.format(str(e)) try: Log.info("Removing job_data db...") job_data_path = os.path.join(BasicConfig.LOCAL_ROOT_DIR, BasicConfig.JOBDATA_DIR, "job_data_{0}.db".format(expid_delete)) if os.path.exists(job_data_path): os.remove(job_data_path) except BaseException as e: - error_message += 'Can not delete job_data: {0}\n'.format(e.message) + error_message += 'Can not delete job_data: {0}\n'.format(str(e)) except OSError as e: - error_message += 'Can not delete directory: {0}\n'.format(e.message) + error_message += 'Can not delete directory: {0}\n'.format(str(e)) else: if not eadmin: raise AutosubmitCritical( @@ -1811,7 +1811,7 @@ class Autosubmit: # No need to wait until the remote platform reconnection recovery = False as_conf = AutosubmitConfig(expid, BasicConfig, ConfigParserFactory()) - consecutive_retrials = 0 + consecutive_retrials = 1 failed_names = {} Log.info("Storing failed job count...") try: diff --git a/autosubmit/config/config_common.py b/autosubmit/config/config_common.py index 74dcc3e1e..ddbb04c78 100644 --- a/autosubmit/config/config_common.py +++ b/autosubmit/config/config_common.py @@ -513,11 +513,11 @@ class AutosubmitConfig(object): self.reload() except IOError as e: raise AutosubmitError( - "I/O Issues con config files", 6016, e.message) + "I/O Issues con config files", 6016, str(e)) except (AutosubmitCritical, AutosubmitError) as e: raise except BaseException as e: - raise AutosubmitCritical("Unknown issue while checking the configulation files (check_conf_files)",7040,e.message) + raise AutosubmitCritical("Unknown issue while checking the configulation files (check_conf_files)",7040,str(e)) # Annotates all errors found in the configuration files in dictionaries self.warn_config and self.wrong_config. self.check_expdef_conf() self.check_platforms_conf() diff --git a/autosubmit/git/autosubmit_git.py b/autosubmit/git/autosubmit_git.py index 817b5e09b..c191c21df 100644 --- a/autosubmit/git/autosubmit_git.py +++ b/autosubmit/git/autosubmit_git.py @@ -60,7 +60,7 @@ class AutosubmitGit: shell=True) except subprocess.CalledProcessError as e: raise AutosubmitCritical( - "Failed to retrieve git info ...", 7064, e.message) + "Failed to retrieve git info ...", 7064, str(e)) if output: Log.info("Changes not committed detected... SKIPPING!") raise AutosubmitCritical("Commit needed!", 7013) @@ -231,7 +231,7 @@ class AutosubmitGit: output_1 = subprocess.check_output(command_1, shell=True) except BaseException as e: submodule_failure = True - Log.printlog("Trace: {0}".format(e.message), 6014) + Log.printlog("Trace: {0}".format(str(e)), 6014) Log.printlog( "Submodule {0} has a wrong configuration".format(submodule), 6014) else: diff --git a/autosubmit/job/job.py b/autosubmit/job/job.py index 9365e516f..6653c51f9 100644 --- a/autosubmit/job/job.py +++ b/autosubmit/job/job.py @@ -600,13 +600,13 @@ class Job(object): self._tmp_path, 'LOG_' + str(self.expid), local_log)) except BaseException as e: Log.printlog("Trace {0} \n Failed to write the {1} e=6001".format( - e.message, self.name)) + str(e), self.name)) except AutosubmitError as e: Log.printlog("Trace {0} \nFailed to retrieve log file for job {1}".format( - e.message, self.name), 6001) + str(e), self.name), 6001) except AutosubmitCritical as e: # Critical errors can't be recovered. Failed configuration or autosubmit error Log.printlog("Trace {0} \nFailed to retrieve log file for job {0}".format( - e.message, self.name), 6001) + str(e), self.name), 6001) return @threaded @@ -656,7 +656,7 @@ class Job(object): except BaseException as e: Log.printlog( - "{0} \n Couldn't connect to the remote platform for {1} job err/out files. ".format(e.message, self.name), 6001) + "{0} \n Couldn't connect to the remote platform for {1} job err/out files. ".format(str(e), self.name), 6001) out_exist = False err_exist = False retries = 3 diff --git a/autosubmit/job/job_dict.py b/autosubmit/job/job_dict.py index 0b16d29af..d0aef9f42 100644 --- a/autosubmit/job/job_dict.py +++ b/autosubmit/job/job_dict.py @@ -126,7 +126,7 @@ class DicJobs: except BaseException as e: raise AutosubmitCritical( "Wrong format for {1} parameter in section {0}".format(section,called_from), 7011, - e.message) + str(e)) pass return parsed_list def read_section(self, section, priority, default_job_type, jobs_data=dict()): diff --git a/autosubmit/job/job_list.py b/autosubmit/job/job_list.py index 395c97e4c..ae52a0c78 100644 --- a/autosubmit/job/job_list.py +++ b/autosubmit/job/job_list.py @@ -249,7 +249,7 @@ class JobList(object): else: self._ordered_jobs_by_date_member[wrapper_section] = {} except BaseException as e: - raise AutosubmitCritical("Some section jobs of the wrapper:{0} are not in the current job_list defined in jobs.conf".format(wrapper_section),7014,e.message) + raise AutosubmitCritical("Some section jobs of the wrapper:{0} are not in the current job_list defined in jobs.conf".format(wrapper_section),7014,str(e)) pass @@ -1419,11 +1419,11 @@ class JobList(object): self._persistence_file, self._job_list if self.run_members is None or job_list is None else job_list) pass except BaseException as e: - raise AutosubmitError(e.message,6040,"Failure while saving the job_list") + raise AutosubmitError(str(e),6040,"Failure while saving the job_list") except AutosubmitError as e: raise except BaseException as e: - raise AutosubmitError(e.message,6040,"Unknown failure while saving the job_list") + raise AutosubmitError(str(e),6040,"Unknown failure while saving the job_list") def backup_save(self): diff --git a/autosubmit/monitor/monitor.py b/autosubmit/monitor/monitor.py index 55c60156a..9556e7d3d 100644 --- a/autosubmit/monitor/monitor.py +++ b/autosubmit/monitor/monitor.py @@ -353,7 +353,7 @@ class Monitor: except: pass - Log.printlog("{0}\nSpecified output doesn't have an available viewer installed or graphviz is not installed. The output was only writted in txt".format(e.message),7014) + Log.printlog("{0}\nSpecified output doesn't have an available viewer installed or graphviz is not installed. The output was only written in txt".format(e.message),7014) def generate_output_txt(self, expid, joblist, path, classictxt=False, job_list_object=None): diff --git a/autosubmit/platforms/paramiko_platform.py b/autosubmit/platforms/paramiko_platform.py index fb9059915..1c1177510 100644 --- a/autosubmit/platforms/paramiko_platform.py +++ b/autosubmit/platforms/paramiko_platform.py @@ -112,7 +112,7 @@ class ParamikoPlatform(Platform): except EOFError as e: self.connected = False raise AutosubmitError("[{0}] not alive. Host: {1}".format( - self.name, self.host), 6002, e.message) + self.name, self.host), 6002, str(e)) except (AutosubmitError,AutosubmitCritical,IOError): self.connected = False raise @@ -136,7 +136,7 @@ class ParamikoPlatform(Platform): self.host.split(',')[0]), 6002) else: raise AutosubmitCritical( - "First connection to {0} is failed, check host configuration or try another login node ".format(self.host), 7050,e.message) + "First connection to {0} is failed, check host configuration or try another login node ".format(self.host), 7050,str(e)) while self.connected is False and retry < retries: try: self.connect(True) @@ -155,7 +155,7 @@ class ParamikoPlatform(Platform): raise except Exception as e: raise AutosubmitCritical( - 'Cant connect to this platform due an unknown error', 7050, e.message) + 'Cant connect to this platform due an unknown error', 7050, str(e)) def threaded(fn): def wrapper(*args, **kwargs): @@ -219,12 +219,12 @@ class ParamikoPlatform(Platform): elif "name or service not known" in e.strerror.lower(): raise SSHException(" {0} doesn't accept remote connections. Check if there is an typo in the hostname".format(self.host)) else: - raise AutosubmitError("File can't be located due an slow connection", 6016, e.message) + raise AutosubmitError("File can't be located due an slow connection", 6016, str(e)) except BaseException as e: self.connected = False - if "Authentication failed." in e.message: + if "Authentication failed." in str(e): raise AutosubmitCritical("Authentication Failed, please check the platform.conf of {0}".format( - self._host_config['hostname']), 7050, e.message) + self._host_config['hostname']), 7050, str(e)) if not reconnect and "," in self._host_config['hostname']: self.restore_connection(reconnect=True) else: @@ -284,7 +284,7 @@ class ParamikoPlatform(Platform): return True except IOError as e: raise AutosubmitError('Can not send file {0} to {1}'.format(os.path.join( - self.tmp_path, filename)), os.path.join(self.get_files_path(), filename), 6004, e.message) + self.tmp_path, filename)), os.path.join(self.get_files_path(), filename), 6004, str(e)) except BaseException as e: raise AutosubmitError( 'Send file failed. Connection seems to no be active', 6004) @@ -358,7 +358,7 @@ class ParamikoPlatform(Platform): except BaseException as e: Log.error('Could not remove file {0} due a wrong configuration'.format( os.path.join(self.get_files_path(), filename))) - if e.message.lower().find("garbage") != -1: + if str(e).lower().find("garbage") != -1: raise AutosubmitCritical( "Wrong User or invalid .ssh/config. Or invalid user in platform.conf or public key not set ", 7051, e.message) diff --git a/test/regression/tests_utils.py b/test/regression/tests_utils.py index 297fb8f75..53ead0dd5 100644 --- a/test/regression/tests_utils.py +++ b/test/regression/tests_utils.py @@ -23,7 +23,7 @@ def check_cmd(command, path=BIN_PATH, verbose='AS_TEST_VERBOSE' in os.environ): except subprocess.CalledProcessError as e: if verbose: - print e.output + print str(e) return False -- GitLab From 5640508259cc54472d91afa33ea3e2e6eb60e1a9 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Tue, 4 Oct 2022 13:16:06 +0200 Subject: [PATCH 32/45] log error --- autosubmit/config/config_common.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/autosubmit/config/config_common.py b/autosubmit/config/config_common.py index ddbb04c78..4b683f1e4 100644 --- a/autosubmit/config/config_common.py +++ b/autosubmit/config/config_common.py @@ -1763,7 +1763,10 @@ class AutosubmitConfig(object): def removeInlineComments(cfgparser): for section in cfgparser.sections(): for item in cfgparser.items(section): - cfgparser.set(section, item[0], item[1].split("#")[0].strip()) + try: + cfgparser.set(section, item[0], item[1].split("#")[0].strip()) + except: + pass return cfgparser @staticmethod -- GitLab From 3d42d2e3f4f5af861b9244b88334ed92ee46403f Mon Sep 17 00:00:00 2001 From: dbeltran Date: Tue, 4 Oct 2022 13:33:32 +0200 Subject: [PATCH 33/45] CUSTOM directive has # crashing with the removeinlinecomments --- autosubmit/config/config_common.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/autosubmit/config/config_common.py b/autosubmit/config/config_common.py index 4b683f1e4..50c4d69e8 100644 --- a/autosubmit/config/config_common.py +++ b/autosubmit/config/config_common.py @@ -1764,7 +1764,10 @@ class AutosubmitConfig(object): for section in cfgparser.sections(): for item in cfgparser.items(section): try: - cfgparser.set(section, item[0], item[1].split("#")[0].strip()) + if str(item[0]).upper() == "CUSTOM_DIRECTIVES": + pass + else: + cfgparser.set(section, item[0], item[1].split("#")[0].strip()) except: pass return cfgparser -- GitLab From b6609fb36022d6d6c1a8fceafed01083246b3e54 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Tue, 4 Oct 2022 15:01:34 +0200 Subject: [PATCH 34/45] Changed delete message, added complete list of directories --- autosubmit/autosubmit.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/autosubmit/autosubmit.py b/autosubmit/autosubmit.py index 2fca7cb7b..0720672e7 100644 --- a/autosubmit/autosubmit.py +++ b/autosubmit/autosubmit.py @@ -818,6 +818,16 @@ class Autosubmit: :return: True if succesfully deleted, False otherwise :rtype: boolean """ + message = "The {0} experiment was removed from the local disk and from the database.".format(expid_delete) + message+= " Note that this action does not delete any data written by the experiment.\n" + message+= "Complete list of files/directories deleted:\n" + for root, dirs, files in os.walk(os.path.join(BasicConfig.LOCAL_ROOT_DIR, expid_delete)): + for dir in dirs: + message += os.path.join(root, dir) + "\n" + message += os.path.join(BasicConfig.LOCAL_ROOT_DIR, BasicConfig.STRUCTURES_DIR, + "structure_{0}.db".format(expid_delete)) + "\n" + message += os.path.join(BasicConfig.LOCAL_ROOT_DIR, BasicConfig.JOBDATA_DIR, + "job_data_{0}.db".format(expid_delete)) + "\n" owner,eadmin,currentOwner = Autosubmit._check_ownership(expid_delete) if expid_delete == '' or expid_delete is None and not os.path.exists(os.path.join(BasicConfig.LOCAL_ROOT_DIR,expid_delete)): Log.printlog("Experiment directory does not exist.",Log.WARNING) @@ -865,6 +875,7 @@ class Autosubmit: else: raise AutosubmitCritical( 'Current user is not the owner of the experiment. {0} can not be deleted!'.format(expid_delete), 7012) + Log.printlog(message, Log.RESULT) except Exception as e: # Avoid calling Log at this point since it is possible that tmp folder is already deleted. error_message += "Couldn't delete the experiment".format(e.message) -- GitLab From 70f6711de4d5cedba985e576472bdbf6ed559e8e Mon Sep 17 00:00:00 2001 From: dbeltran Date: Tue, 4 Oct 2022 15:19:47 +0200 Subject: [PATCH 35/45] disable inline delete --- autosubmit/config/config_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autosubmit/config/config_common.py b/autosubmit/config/config_common.py index 50c4d69e8..63b31483d 100644 --- a/autosubmit/config/config_common.py +++ b/autosubmit/config/config_common.py @@ -1808,6 +1808,6 @@ class AutosubmitConfig(object): "{}\n This file and the correctness of its content are necessary.".format(str(exp))) # parser.read(file_path) #remove inline comments - parser = AutosubmitConfig.removeInlineComments(parser) + #parser = AutosubmitConfig.removeInlineComments(parser) return parser -- GitLab From 2942e7e6462fa9c136c31a42bf62858c47db592a Mon Sep 17 00:00:00 2001 From: dbeltran Date: Tue, 4 Oct 2022 16:11:47 +0200 Subject: [PATCH 36/45] Fixed node missconfiguration slurm message not being detected correclty --- autosubmit/autosubmit.py | 10 ++++++---- autosubmit/job/job_packages.py | 2 +- autosubmit/platforms/paramiko_submitter.py | 4 +++- autosubmit/platforms/slurmplatform.py | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/autosubmit/autosubmit.py b/autosubmit/autosubmit.py index 0720672e7..48e5b2e28 100644 --- a/autosubmit/autosubmit.py +++ b/autosubmit/autosubmit.py @@ -2191,11 +2191,11 @@ class Autosubmit: platform.cancel_job(id) jobs_id = None platform.connected = False - if type(e.trace) is not None: - has_trace_bad_parameters = e.trace.lower().find("bad parameters") != -1 + if e.trace is not None: + has_trace_bad_parameters = str(e.trace).lower().find("bad parameters") != -1 else: has_trace_bad_parameters = False - if has_trace_bad_parameters or e.message.lower().find("invalid partition") != -1 or e.message.lower().find(" invalid qos") != -1 or e.message.lower().find("scheduler is not installed") != -1: + if has_trace_bad_parameters or e.message.lower().find("invalid partition") != -1 or e.message.lower().find(" invalid qos") != -1 or e.message.lower().find("scheduler is not installed") != -1 or e.message.lower().find("failed") != -1 or e.message.lower().find("not available") != -1: error_msg = "" for package_tmp in valid_packages_to_submit: for job_tmp in package_tmp.jobs: @@ -2206,7 +2206,9 @@ class Autosubmit: else: error_message+="Check that {1} platform has set the correct scheduler. Sections that could be affected: {0}".format( error_msg[:-1], platform.name) - raise AutosubmitCritical(error_message,7014,e.message+"\n"+e.trace) + if e.trace is None: + e.trace = "" + raise AutosubmitCritical(error_message,7014,e.message+"\n"+str(e.trace)) except IOError as e: raise AutosubmitError( "IO issues ", 6016, e.message) diff --git a/autosubmit/job/job_packages.py b/autosubmit/job/job_packages.py index 52afa70cc..a3a6a3b58 100644 --- a/autosubmit/job/job_packages.py +++ b/autosubmit/job/job_packages.py @@ -155,7 +155,7 @@ class JobPackageBase(object): exit=True break if not os.path.exists(os.path.join(configuration.get_project_dir(), job.file)): - if configuration.get_project_type().lower() != "none": + if str(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) diff --git a/autosubmit/platforms/paramiko_submitter.py b/autosubmit/platforms/paramiko_submitter.py index 1f577426f..12e1e70bc 100644 --- a/autosubmit/platforms/paramiko_submitter.py +++ b/autosubmit/platforms/paramiko_submitter.py @@ -203,6 +203,8 @@ class ParamikoSubmitter(Submitter): if parser.has_option(section, 'SERIAL_PLATFORM'): platforms[section.lower()].serial_platform = platforms[parser.get_option(section, 'SERIAL_PLATFORM', - None).lower()] + None)] + if platforms[section.lower()].serial_platform is not None: + platforms[section.lower()].serial_platform = platforms[section.lower()].serial_platform.lower() self.platforms = platforms diff --git a/autosubmit/platforms/slurmplatform.py b/autosubmit/platforms/slurmplatform.py index 5d31690c4..d757256a4 100644 --- a/autosubmit/platforms/slurmplatform.py +++ b/autosubmit/platforms/slurmplatform.py @@ -466,7 +466,7 @@ class SlurmPlatform(ParamikoPlatform): else: retries = 9999 except BaseException as e: # Unrecoverable error - if e.message.lower().find("garbage") != -1: + if str(e).lower().find("garbage") != -1: if not wrapper_failed: sleep(sleeptime) sleeptime = sleeptime + 5 -- GitLab From df2165954c91e66e713605cbc1d74644c207abaa Mon Sep 17 00:00:00 2001 From: dbeltran Date: Wed, 5 Oct 2022 11:16:06 +0200 Subject: [PATCH 37/45] Added include_members and chunks #748 --- autosubmit/job/job_dict.py | 36 ++++++++++++++++++++++++++++++------ test/unit/test_dic_jobs.py | 2 +- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/autosubmit/job/job_dict.py b/autosubmit/job/job_dict.py index d0aef9f42..b7e6b4a6d 100644 --- a/autosubmit/job/job_dict.py +++ b/autosubmit/job/job_dict.py @@ -152,11 +152,19 @@ class DicJobs: elif running == 'date': self._create_jobs_startdate(section, priority, frequency, default_job_type, jobs_data,splits) elif running == 'member': - self._create_jobs_member(section, priority, frequency, default_job_type, jobs_data,splits,self.parse_relation(section,True,self.get_option(section, "EXCLUDED_MEMBERS", []),"EXCLUDED_MEMBERS")) + self._create_jobs_member(section, priority, frequency, default_job_type, jobs_data,splits, \ + self.parse_relation(section,True,self.get_option(section, "EXCLUDED_MEMBERS", []),"EXCLUDED_MEMBERS"), \ + self.parse_relation(section,True,self.get_option(section, "INCLUDED_MEMBERS", []),"INCLUDED_MEMBERS")) + elif running == 'chunk': synchronize = self.get_option(section, "SYNCHRONIZE", None) delay = int(self.get_option(section, "DELAY", -1)) - self._create_jobs_chunk(section, priority, frequency, default_job_type, synchronize, delay, splits, jobs_data,excluded_chunks=self.parse_relation(section,False,self.get_option(section, "EXCLUDED_CHUNKS", []),"EXCLUDED_CHUNKS"),excluded_members=self.parse_relation(section,True,self.get_option(section, "EXCLUDED_MEMBERS", []),"EXCLUDED_MEMBERS")) + self._create_jobs_chunk(section, priority, frequency, default_job_type, synchronize, delay, splits, jobs_data, \ + excluded_chunks=self.parse_relation(section,False,self.get_option(section, "EXCLUDED_CHUNKS", []),"EXCLUDED_CHUNKS"), \ + excluded_members=self.parse_relation(section,True,self.get_option(section, "EXCLUDED_MEMBERS", []),"EXCLUDED_MEMBERS"), \ + included_chunks=self.parse_relation(section,False,self.get_option(section, "INCLUDED_CHUNKS", []),"INCLUDED_CHUNKS"), \ + included_members=self.parse_relation(section,True,self.get_option(section, "INCLUDED_MEMBERS", []),"INCLUDED_MEMBERS")) + pass def _create_jobs_once(self, section, priority, default_job_type, jobs_data=dict(),splits=0): @@ -218,7 +226,7 @@ class DicJobs: - def _create_jobs_member(self, section, priority, frequency, default_job_type, jobs_data=dict(),splits=-1,excluded_members=[]): + def _create_jobs_member(self, section, priority, frequency, default_job_type, jobs_data=dict(),splits=-1,excluded_members=[],included_members=[]): """ Create jobs to be run once per member @@ -242,11 +250,18 @@ class DicJobs: count = 0 if splits > 0: for member in self._member_list: - if self._member_list.index(member) not in excluded_members: - tmp_dic[section][date][member] = [] + if len(included_members) == 0: + if self._member_list.index(member) not in excluded_members: + tmp_dic[section][date][member] = [] + else: + if self._member_list.index(member) in included_members: + tmp_dic[section][date][member] = [] for member in self._member_list: if self._member_list.index(member) in excluded_members: continue + if len(included_members) > 0: + if self._member_list.index(member) not in included_members: + continue count += 1 if count % frequency == 0 or count == len(self._member_list): if splits <= 0: @@ -259,7 +274,7 @@ class DicJobs: - def _create_jobs_chunk(self, section, priority, frequency, default_job_type, synchronize=None, delay=0, splits=0, jobs_data=dict(),excluded_chunks=[],excluded_members=[]): + def _create_jobs_chunk(self, section, priority, frequency, default_job_type, synchronize=None, delay=0, splits=0, jobs_data=dict(),excluded_chunks=[],excluded_members=[],included_chunks=[],included_members=[]): """ Create jobs to be run once per chunk @@ -282,6 +297,9 @@ class DicJobs: for chunk in self._chunk_list: if chunk in excluded_chunks: continue + if len(included_chunks) > 0: + if chunk not in included_chunks: + continue count += 1 if delay == -1 or delay < chunk: if count % frequency == 0 or count == len(self._chunk_list): @@ -311,6 +329,9 @@ class DicJobs: for date in self._date_list: self._dic[section][date] = dict() for member in self._member_list: + if len(included_members) > 0: + if self._member_list.index(member) not in included_members: + continue if self._member_list.index(member) in excluded_members: continue self._dic[section][date][member] = dict() @@ -318,6 +339,9 @@ class DicJobs: for chunk in self._chunk_list: if chunk in excluded_chunks: continue + if len(included_chunks) > 0: + if chunk not in included_chunks: + continue count += 1 if delay == -1 or delay < chunk: if count % frequency == 0 or count == len(self._chunk_list): diff --git a/test/unit/test_dic_jobs.py b/test/unit/test_dic_jobs.py index 5565c9328..39f7690b2 100644 --- a/test/unit/test_dic_jobs.py +++ b/test/unit/test_dic_jobs.py @@ -123,7 +123,7 @@ class TestDicJobs(TestCase): self.dictionary._create_jobs_once.assert_not_called() self.dictionary._create_jobs_startdate.assert_not_called() self.dictionary._create_jobs_member.assert_not_called() - self.dictionary._create_jobs_chunk.assert_called_once_with(section, priority, frequency, Type.BASH, synchronize, delay, splits, {},excluded_chunks=[],excluded_members=[]) + self.dictionary._create_jobs_chunk.assert_called_once_with(section, priority, frequency, Type.BASH, synchronize, delay, splits, {},excluded_chunks=[],excluded_members=[],included_chunks=[],included_members=[]) def test_dic_creates_right_jobs_by_startdate(self): # arrange -- GitLab From 6127001e9093604718f0f1546b4de2a0eef92bb7 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Wed, 5 Oct 2022 13:17:17 +0200 Subject: [PATCH 38/45] Bugfix timeout #812 --- autosubmit/platforms/locplatform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autosubmit/platforms/locplatform.py b/autosubmit/platforms/locplatform.py index 3fe62f5cc..e7734b133 100644 --- a/autosubmit/platforms/locplatform.py +++ b/autosubmit/platforms/locplatform.py @@ -83,7 +83,7 @@ class LocalPlatform(ParamikoPlatform): def get_submit_cmd(self, job_script, job, hold=False, export=""): wallclock = self.parse_time(job.wallclock) - seconds = int(wallclock.days * 86400 + wallclock.seconds + 60) + seconds = int(wallclock.days * 86400 + wallclock.seconds * 60) if export == "none" or export == "None" or export is None or export == "": export = "" else: -- GitLab From 99aec684a83eeafd9db75d2f9f9c0378f23ef9e9 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Wed, 5 Oct 2022 13:29:32 +0200 Subject: [PATCH 39/45] Erased wrong info about TOTAL_JOBS --- .../usage/configuration/new_platform.rst | 2 +- docs/source/usage/run_modes/wrappers.rst | 34 ++----------------- 2 files changed, 4 insertions(+), 32 deletions(-) diff --git a/docs/source/usage/configuration/new_platform.rst b/docs/source/usage/configuration/new_platform.rst index 675d4edc6..971778061 100644 --- a/docs/source/usage/configuration/new_platform.rst +++ b/docs/source/usage/configuration/new_platform.rst @@ -53,7 +53,7 @@ There are some other parameters that you may need to specify: * TEST_SUITE: if true, autosubmit test command can use this queue as a main queue. Defaults to false -* MAX_WAITING_JOBS: maximum number of jobs to be queuing or submitted in this platform. +* MAX_WAITING_JOBS: Maximum number of jobs to be queuing or submitted in this platform. * TOTAL_JOBS: Maximum number of jobs to be queuing, running or submitted at the same time in this platform. diff --git a/docs/source/usage/run_modes/wrappers.rst b/docs/source/usage/run_modes/wrappers.rst index 8085e4884..388c215ef 100644 --- a/docs/source/usage/run_modes/wrappers.rst +++ b/docs/source/usage/run_modes/wrappers.rst @@ -14,34 +14,6 @@ At the moment there are 4 types of wrappers that can be used depending on the ex When using the wrapper, it is useful to be able to visualize which packages are being created. So, when executing *autosubmit monitor cxxx*, a dashed box indicates the jobs that are wrapped together in the same job package. -How to configure -======================== - -In ``autosubmit_cxxx.conf``, regardless of the wrapper type, you need to make sure that the values of the variables **MAXWAITINGJOBS** and **TOTALJOBS** are increased according to the number of jobs expected to be waiting/running at the same time in your experiment. - -For example: - -.. code-block:: ini - - [config] - EXPID = .... - AUTOSUBMIT_VERSION = 3.13.0 - ... - - MAXWAITINGJOBS = 100 - TOTALJOBS = 100 - ... - -and below the [config] block, add the wrapper directive, indicating the wrapper type: - -.. code-block:: ini - - [wrapper] - TYPE = - -You can also specify which job types should be wrapped. This can be done using the **JOBS_IN_WRAPPER** parameter. -It is only required for the vertical-mixed type (in which the specified job types will be wrapped together), so if nothing is specified, all jobs will be wrapped. -By default, jobs of the same type will be wrapped together, as long as the constraints are satisfied. Number of jobs in a package *************************** @@ -57,7 +29,7 @@ Number of jobs in a package - **MAX_WRAPPED** can be defined in ``jobs_cxxx.conf`` in order to limit the number of jobs wrapped for the corresponding job section - If not defined, it considers the **MAX_WRAPPED** defined under [wrapper] in ``autosubmit_cxxx.conf`` - - If **MAX_WRAPPED** is not defined, then **TOTALJOBS** is used by default + - If **MAX_WRAPPED** is not defined, then the max_wallclock of the platform will be final factor. - **MIN_WRAPPED** can be defined in ``autosubmit_cxxx.conf`` in order to limit the minimum number of jobs that a wrapper can contain - If not defined, it considers that **MIN_WRAPPED** is 2. - If **POLICY** is flexible and it is not possible to wrap **MIN_WRAPPED** or more tasks, these tasks will be submitted as individual jobs, as long as the condition is not satisfied. @@ -241,7 +213,7 @@ In `autosubmit_cxxx.conf`: # JOBS_IN_WRAPPER = Sections that should be wrapped together ex SIM # METHOD : Select between MACHINESFILES or Shared-Memory. # MIN_WRAPPED set the minim number of jobs that should be included in the wrapper. DEFAULT = 2 - # MAX_WRAPPED set the maxim number of jobs that should be included in the wrapper. DEFAULT = TOTALJOBS + # MAX_WRAPPED set the maxim number of jobs that should be included in the wrapper. DEFAULT = 99999999999 # Policy : Select the behaviour of the inner jobs Strict/Flexible/Mixed # EXTEND_WALLCLOCK: Allows to extend the wallclock by the max wallclock of the horizontal package (max inner job). Values are integer units (0,1,2) # RETRIALS : Enables a retrial mechanism for vertical wrappers, or default retrial mechanism for the other wrappers @@ -250,7 +222,7 @@ In `autosubmit_cxxx.conf`: TYPE = Vertical #REQUIRED JOBS_IN_WRAPPER = SIM # Job types (as defined in jobs_cxxx.conf) separated by space. REQUIRED only if vertical-mixed MIN_WRAPPED = 2 - MAX_WRAPPED = 9999 # OPTIONAL. Integer value, overrides TOTALJOBS + MAX_WRAPPED = 999999 # OPTIONAL. Integer value. CHECK_TIME_WRAPPER = # OPTIONAL. Time in seconds, overrides SAFETYSLEEPTIME POLICY = flexible # OPTIONAL, Wrapper policy, mixed, flexible, strict QUEUE = bsc_es # If not specified, queue will be the same of the first SECTION specified on JOBS_IN_WRAPPER -- GitLab From 03fef134b138167b7ad8bfaeabac899011ccbbf2 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Wed, 5 Oct 2022 13:38:51 +0200 Subject: [PATCH 40/45] Added wrapper info under devel_proj -> Controling the number of active concurrent tasks in an experiment #857 --- docs/source/devel_proj.rst | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/docs/source/devel_proj.rst b/docs/source/devel_proj.rst index 17caddcf5..0dda37b3c 100644 --- a/docs/source/devel_proj.rst +++ b/docs/source/devel_proj.rst @@ -712,8 +712,34 @@ To set the maximum number of concurrent tasks/jobs, you can use the ``TOTAL_JOBS .. code-block:: ini - # Maximum number of submitted,waiting and running tasks - TOTAL_JOBS = 10 - # Maximum number of submitted and waiting tasks - MAX_WAITING_JOBS = 10 + # Controls the maximum number of submitted,waiting and running tasks + TOTAL_JOBS = 10 + # Controls the maximum number of submitted and waiting tasks + MAX_WAITING_JOBS = 10 +To control the number of jobs included in a wrapper, you can use the `MAX_WRAPPED_JOBS` and `MIN_WRAPPED_JOBS` variables in the ``conf/autosubmit_cxxx.conf`` file. + +Note that a wrapped job is counted as a single job regardless of the number of tasks it contains. Therefore, `TOTAL_JOBS` and `MAX_WAITING_JOBS` won't have an impact inside a wrapper. + + vi /conf/autosubmit_cxxx.conf + +.. code-block:: ini + + [wrapper] + TYPE = + MIN_WRAPPED = 2 # Minium amount of jobs that will be wrapped together in any given time. + MIN_WRAPPED_H = 2 # Same as above but only for the horizontal packages. + MIN_WRAPPED_V = 2 # Same as above but only for the vertical packages. + MAX_WRAPPED = 99999 # Maximum amount of jobs that will be wrapped together in any given time. + MAX_WRAPPED_H = 99999 # Same as above but only for the horizontal packages. + MAX_WRAPPED_V = 99999 # Same as above but only for the vertical packages. + +- **MAX_WRAPPED** can be defined in ``jobs_cxxx.conf`` in order to limit the number of jobs wrapped for the corresponding job section + - If not defined, it considers the **MAX_WRAPPED** defined under [wrapper] in ``autosubmit_cxxx.conf`` + - If **MAX_WRAPPED** is not defined, then the max_wallclock of the platform will be final factor. +- **MIN_WRAPPED** can be defined in ``autosubmit_cxxx.conf`` in order to limit the minimum number of jobs that a wrapper can contain + - If not defined, it considers that **MIN_WRAPPED** is 2. + - If **POLICY** is flexible and it is not possible to wrap **MIN_WRAPPED** or more tasks, these tasks will be submitted as individual jobs, as long as the condition is not satisfied. + - If **POLICY** is mixed and there are failed jobs inside a wrapper, these jobs will be submitted as individual jobs. + - If **POLICY** is strict and it is not possible to wrap **MIN_WRAPPED** or more tasks, these tasks will not be submitted until there are enough tasks to build a package. + - strict and mixed policies can cause **deadlocks**. -- GitLab From 0408c45ce3d202f46502b837f91e08cde02f082a Mon Sep 17 00:00:00 2001 From: dbeltran Date: Wed, 5 Oct 2022 16:07:50 +0200 Subject: [PATCH 41/45] Deleted argcomplete --- autosubmit/autosubmit.py | 4 +--- setup.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/autosubmit/autosubmit.py b/autosubmit/autosubmit.py index 48e5b2e28..ccb1bbac9 100644 --- a/autosubmit/autosubmit.py +++ b/autosubmit/autosubmit.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# PYTHON_ARGCOMPLETE_OK # Copyright 2015-2020 Earth Sciences Department, BSC-CNS @@ -65,7 +64,7 @@ try: except Exception: dialog = None from time import sleep -import argparse, argcomplete +import argparse import subprocess import json import tarfile @@ -583,7 +582,6 @@ class Autosubmit: # Changelog subparsers.add_parser('changelog', description='show changelog') - argcomplete.autocomplete(parser) args = parser.parse_args() diff --git a/setup.py b/setup.py index 8e56eb8c5..a5a7801ef 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ setup( url='http://www.bsc.es/projects/earthscience/autosubmit/', download_url='https://earth.bsc.es/wiki/doku.php?id=tools:autosubmit', keywords=['climate', 'weather', 'workflow', 'HPC'], - install_requires=['argparse>=1.2,<2','six>=1.10.0','argcomplete==1.10.3', 'python-dateutil>2', 'pydotplus>=2', 'pyparsing>=2.0.1', + install_requires=['argparse>=1.2,<2','six>=1.10.0', 'python-dateutil>2', 'pydotplus>=2', 'pyparsing>=2.0.1', 'numpy', 'matplotlib', 'typing', 'paramiko == 2.7.1', 'mock>=1.3.0', 'portalocker==0.5.7', 'networkx', 'bscearth.utils', 'Xlib == 0.21', 'requests'], extras_require={ -- GitLab From c6f88d53a0c5b7d30be2c8187fbae4c46f859404 Mon Sep 17 00:00:00 2001 From: dbeltran Date: Thu, 6 Oct 2022 14:33:10 +0200 Subject: [PATCH 42/45] Fixed an issue with main_platform = local and no platforms configured --- autosubmit/config/config_common.py | 8 +++++--- autosubmit/history/data_classes/job_data.py | 3 ++- autosubmit/job/job_dict.py | 7 ++++--- autosubmit/platforms/psplatform.py | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/autosubmit/config/config_common.py b/autosubmit/config/config_common.py index 63b31483d..26ce6ec50 100644 --- a/autosubmit/config/config_common.py +++ b/autosubmit/config/config_common.py @@ -607,9 +607,7 @@ class AutosubmitConfig(object): """ Checks experiment's queues configuration file. """ - if len(self._platforms_parser.sections()) == 0: - self.wrong_config["Platform"] += [["Global", - "Platform file is not well-configured or found"]] + if len(self._platforms_parser.sections()) != len(set(self._platforms_parser.sections())): self.wrong_config["Platform"] += [["Global", @@ -619,7 +617,11 @@ class AutosubmitConfig(object): main_platform_found = True elif self.ignore_undefined_platforms: main_platform_found = True + if len(self._platforms_parser.sections()) == 0 and not main_platform_found: + self.wrong_config["Platform"] += [["Global", + "Platform file is not well-configured or found"]] for section in self._platforms_parser.sections(): + if section in self.hpcarch: main_platform_found = True if not self._platforms_parser.check_exists(section, 'TYPE'): diff --git a/autosubmit/history/data_classes/job_data.py b/autosubmit/history/data_classes/job_data.py index b5249b797..93a88797a 100644 --- a/autosubmit/history/data_classes/job_data.py +++ b/autosubmit/history/data_classes/job_data.py @@ -57,7 +57,8 @@ class JobData(object): platform) > 0 else "NA" self.job_id = job_id if job_id else 0 try: - self.extra_data_parsed = loads(extra_data) + if extra_data != "": + self.extra_data_parsed = loads(extra_data) except Exception as exp: self.extra_data_parsed = {} # Fail fast self.extra_data = extra_data diff --git a/autosubmit/job/job_dict.py b/autosubmit/job/job_dict.py index b7e6b4a6d..29ca59e28 100644 --- a/autosubmit/job/job_dict.py +++ b/autosubmit/job/job_dict.py @@ -402,9 +402,10 @@ class DicJobs: for d in self._date_list: self._get_date(jobs, dic, d, member, chunk) try: - if type(jobs[0]) is list: - jobs_flattened = [job for jobs_to_flatten in jobs for job in jobs_to_flatten] - jobs = jobs_flattened + if len(jobs) > 0: + if type(jobs[0]) is list: + jobs_flattened = [job for jobs_to_flatten in jobs for job in jobs_to_flatten] + jobs = jobs_flattened except BaseException as e: pass return jobs diff --git a/autosubmit/platforms/psplatform.py b/autosubmit/platforms/psplatform.py index aee3e4eb7..e2c3ede88 100644 --- a/autosubmit/platforms/psplatform.py +++ b/autosubmit/platforms/psplatform.py @@ -76,7 +76,7 @@ class PsPlatform(ParamikoPlatform): def get_submit_cmd(self, job_script, job, hold=False, export=""): wallclock = self.parse_time(job.wallclock) - seconds = int(wallclock.days * 86400 + wallclock.seconds + 60) + seconds = int(wallclock.days * 86400 + wallclock.seconds * 60) if export == "none" or export == "None" or export is None or export == "": export = "" else: -- GitLab From e0564c48d6230b7fd80bc048dff78a806f30069b Mon Sep 17 00:00:00 2001 From: dbeltran Date: Thu, 6 Oct 2022 14:41:04 +0200 Subject: [PATCH 43/45] fixed tests --- requeriments.txt | 1 + test/unit/test_dic_jobs.py | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/requeriments.txt b/requeriments.txt index c34451db2..b5783046b 100644 --- a/requeriments.txt +++ b/requeriments.txt @@ -1,3 +1,4 @@ +pytest==2.9.2 configparser argparse>=1.2,<2 python-dateutil>2 diff --git a/test/unit/test_dic_jobs.py b/test/unit/test_dic_jobs.py index 39f7690b2..f955f96dc 100644 --- a/test/unit/test_dic_jobs.py +++ b/test/unit/test_dic_jobs.py @@ -81,9 +81,10 @@ class TestDicJobs(TestCase): frequency = 123 splits = 0 excluded_list_m = [] + included_list_m = [] self.parser_mock.has_option = Mock(return_value=True) self.parser_mock.get = Mock(return_value='member') - self.dictionary.get_option = Mock(side_effect=[splits,frequency,excluded_list_m]) + self.dictionary.get_option = Mock(side_effect=[splits,frequency,excluded_list_m,included_list_m]) self.dictionary._create_jobs_once = Mock() self.dictionary._create_jobs_startdate = Mock() self.dictionary._create_jobs_member = Mock() @@ -95,7 +96,7 @@ class TestDicJobs(TestCase): # assert self.dictionary._create_jobs_once.assert_not_called() self.dictionary._create_jobs_startdate.assert_not_called() - self.dictionary._create_jobs_member.assert_called_once_with(section, priority, frequency, Type.BASH, {},splits,excluded_list_m) + self.dictionary._create_jobs_member.assert_called_once_with(section, priority, frequency, Type.BASH, {},splits,excluded_list_m,included_list_m) self.dictionary._create_jobs_chunk.assert_not_called() def test_read_section_running_chunk_create_jobs_chunk(self): @@ -108,9 +109,11 @@ class TestDicJobs(TestCase): splits = 0 excluded_list_c = [] excluded_list_m = [] + included_list_c = [] + included_list_m = [] self.parser_mock.has_option = Mock(return_value=True) self.parser_mock.get = Mock(return_value='chunk') - self.dictionary.get_option = Mock(side_effect=[splits,frequency, synchronize, delay,excluded_list_c,excluded_list_m]) + self.dictionary.get_option = Mock(side_effect=[splits,frequency, synchronize, delay,excluded_list_c,excluded_list_m,included_list_c,included_list_m]) self.dictionary._create_jobs_once = Mock() self.dictionary._create_jobs_startdate = Mock() self.dictionary._create_jobs_member = Mock() -- GitLab From 425f667a50ffa4b3577f4405bfbcbff0353e24cf Mon Sep 17 00:00:00 2001 From: jberlin Date: Fri, 7 Oct 2022 11:24:08 +0200 Subject: [PATCH 44/45] Made small changes to documentation concerning the Conda installation - #864 --- docs/source/installation.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 7159ac7c0..157f28ecc 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -160,7 +160,7 @@ Sequence of instructions to install Autosubmit and its dependencies in Ubuntu. autosubmit install # Get expid - autosubmit expid -H TEST -d "Test exp." + autosubmit expid -H local -d "Test exp." # Create with -np # Since it was a new install the expid will be a000 @@ -175,7 +175,7 @@ Sequence of instructions to install Autosubmit and its dependencies with conda. wget https://repo.anaconda.com/miniconda/Miniconda3-py39_4.12.0-Linux-x86_64.sh # Launch it chmod +x ./Miniconda3-py39_4.12.0-Linux-x86_64.sh ; ./Miniconda3-py39_4.12.0-Linux-x86_64.sh - # Download git + # Download git (if it is not already installed) apt install git -y -q # Download autosubmit git clone https://earth.bsc.es/gitlab/es/autosubmit.git -b v3.14.0b @@ -186,4 +186,7 @@ Sequence of instructions to install Autosubmit and its dependencies with conda. conda activate autosubmit # Test autosubmit autosubmit -v - # Configure autosubmitrc and install database as indicated in this doc + # Configure autosubmitrc and install the database as indicated in the installation instructions above this section + +.. hint:: + After installing conda, you may need to close the terminal and re-open it so the installation takes effect. \ No newline at end of file -- GitLab From 8bf3fcf6546aedb4364f8d8987b5166bc6f11a56 Mon Sep 17 00:00:00 2001 From: jberlin Date: Thu, 22 Dec 2022 17:31:03 +0100 Subject: [PATCH 45/45] Add the initial version of the Autosubmit database documentation #920 --- .../fig/Autosubmit Databases-highlevel.png | Bin 0 -> 127352 bytes docs/source/database/index.rst | 45 ++++++++++++++++++ docs/source/index.rst | 6 +++ 3 files changed, 51 insertions(+) create mode 100644 docs/source/database/fig/Autosubmit Databases-highlevel.png create mode 100644 docs/source/database/index.rst diff --git a/docs/source/database/fig/Autosubmit Databases-highlevel.png b/docs/source/database/fig/Autosubmit Databases-highlevel.png new file mode 100644 index 0000000000000000000000000000000000000000..2985181bc3a7c6eac42264040e09493c9679bed0 GIT binary patch literal 127352 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