Coverage for python/lsst/ctrl/bps/parsl/site.py: 45%
52 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-13 01:48 -0800
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-13 01:48 -0800
1# This file is part of ctrl_bps_parsl.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org) and the LSST DESC (https://www.lsstdesc.org/).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This software is dual licensed under the GNU General Public License and also
10# under a 3-clause BSD license. Recipients may choose which of these licenses
11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
12# respectively. If you choose the GPL option then the following text applies
13# (but note that there is still no warranty even if you opt for BSD instead):
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU General Public License as published by
17# the Free Software Foundation, either version 3 of the License, or
18# (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU General Public License for more details.
24#
25# You should have received a copy of the GNU General Public License
26# along with this program. If not, see <https://www.gnu.org/licenses/>.
28from abc import ABC, abstractmethod
29from types import ModuleType
30from typing import TYPE_CHECKING
32import parsl.config
33from parsl.addresses import address_by_hostname
34from parsl.executors.base import ParslExecutor
35from parsl.monitoring import MonitoringHub
37from lsst.ctrl.bps import BpsConfig
38from lsst.utils import doImport
40from .configuration import get_bps_config_value, get_workflow_name
41from .environment import export_environment
43if TYPE_CHECKING:
44 from .job import ParslJob
46__all__ = ("SiteConfig",)
49class SiteConfig(ABC):
50 """Base class for site configuration.
52 Subclasses need to override at least the ``get_executors`` and
53 ``select_executor`` methods.
55 Parameters
56 ----------
57 config : `BpsConfig`
58 BPS configuration.
59 add_resources : `bool`
60 Add resource specification when submitting the job? This is only
61 appropriate for the ``WorkQueue`` executor; other executors will
62 raise an exception.
63 """
65 def __init__(self, config: BpsConfig, add_resources: bool = False):
66 self.config = config
67 self.site = self.get_site_subconfig(config)
68 self.add_resources = add_resources
70 @staticmethod
71 def get_site_subconfig(config: BpsConfig) -> BpsConfig:
72 """Get BPS configuration for the site of interest.
74 We return the BPS sub-configuration for the site indicated by the
75 ``computeSite`` value, which is ``site.<computeSite>``.
77 Parameters
78 ----------
79 config : `BpsConfig`
80 BPS configuration.
82 Returns
83 -------
84 site : `BpsConfig`
85 Site sub-configuration.
86 """
87 computeSite = get_bps_config_value(config, "computeSite", str, required=True)
88 return get_bps_config_value(config, f".site.{computeSite}", BpsConfig, required=True)
90 @classmethod
91 def from_config(cls, config: BpsConfig) -> "SiteConfig":
92 """Get the site configuration nominated in the BPS config.
94 The ``computeSite`` (`str`) value in the BPS configuration is used to
95 select a site configuration. The site configuration class to use is
96 specified by the BPS configuration as ``site.<computeSite>.class``
97 (`str`), which should be the fully-qualified name of a python class
98 that inherits from `SiteConfig`.
100 Parameters
101 ----------
102 config : `BpsConfig`
103 BPS configuration.
105 Returns
106 -------
107 site_config : subclass of `SiteConfig`
108 Site configuration.
109 """
110 site = cls.get_site_subconfig(config)
111 name = get_bps_config_value(site, "class", str, required=True)
112 site_config = doImport(name)
113 if isinstance(site_config, ModuleType) or not issubclass(site_config, SiteConfig):
114 raise RuntimeError(f"Site class={name} is not a SiteConfig subclass")
115 return site_config(config)
117 @abstractmethod
118 def get_executors(self) -> list[ParslExecutor]:
119 """Get a list of executors to be used in processing.
121 Each executor should have a unique ``label``.
122 """
123 raise NotImplementedError("Subclasses must define")
125 @abstractmethod
126 def select_executor(self, job: "ParslJob") -> str:
127 """Get the ``label`` of the executor to use to execute a job.
129 Parameters
130 ----------
131 job : `ParslJob`
132 Job to be executed.
134 Returns
135 -------
136 label : `str`
137 Label of executor to use to execute ``job``.
138 """
139 raise NotImplementedError("Subclasses must define")
141 def get_address(self) -> str:
142 """Return the IP address of the machine hosting the driver/submission.
144 This address should be accessible from the workers. This should
145 generally by the return value of one of the functions in
146 ``parsl.addresses``.
148 This is used by the default implementation of ``get_monitor``, but will
149 generally be used by ``get_executors`` too.
151 This default implementation gets the address from the hostname, but
152 that will not work if the workers don't access the driver/submission
153 node by that address.
154 """
155 return address_by_hostname()
157 def get_command_prefix(self) -> str:
158 """Return command(s) to add before each job command.
160 These may be used to configure the environment for the job.
162 This default implementation respects the BPS configuration elements:
164 - ``site.<computeSite>.commandPrefix`` (`str`): command(s) to use as a
165 prefix to executing a job command on a worker.
166 - ``site.<computeSite>.environment`` (`bool`): add bash commands that
167 replicate the environment on the driver/submit machine?
168 """
169 prefix = get_bps_config_value(self.site, "commandPrefix", str, "")
170 if get_bps_config_value(self.site, "environment", bool, False):
171 prefix += "\n" + export_environment()
172 return prefix
174 def get_monitor(self) -> MonitoringHub | None:
175 """Get parsl monitor.
177 The parsl monitor provides a database that tracks the progress of the
178 workflow and the use of resources on the workers.
180 This implementation respects the BPS configuration elements:
182 - ``site.<computeSite>.monitorEnable`` (`bool`): enable monitor?
183 - ``site.<computeSite>.monitorInterval`` (`float`): time interval (sec)
184 between logging of resource usage.
185 - ``site.<computeSite>.monitorFilename`` (`str`): name of file to use
186 for the monitor sqlite database.
188 Returns
189 -------
190 monitor : `MonitoringHub` or `None`
191 Parsl monitor, or `None` for no monitor.
192 """
193 if not get_bps_config_value(self.site, "monitorEnable", bool, False):
194 return None
195 return MonitoringHub(
196 workflow_name=get_workflow_name(self.config),
197 hub_address=self.get_address(),
198 resource_monitoring_interval=get_bps_config_value(self.site, "monitorInterval", float, 30.0),
199 logging_endpoint="sqlite:///"
200 + get_bps_config_value(self.site, "monitorFilename", str, "monitor.sqlite"),
201 )
203 def get_parsl_config(self) -> parsl.config.Config:
204 """Get Parsl configuration for this site.
206 Subclasses can overwrite this method to build a more specific Parsl
207 configuration, if required.
209 The retries are set from the ``site.<computeSite>.retries`` value
210 found in the BPS configuration file.
212 Returns
213 -------
214 config : `parsl.config.Config`
215 The configuration to be used for Parsl.
216 """
217 executors = self.get_executors()
218 monitor = self.get_monitor()
219 retries = get_bps_config_value(self.site, "retries", int, 1)
220 # Path to Parsl run directory. The default set by Parsl is
221 # 'runinfo' which is not explicit enough for end users given that
222 # we are using BPS + Parsl + Slurm to execute a workflow.
223 run_dir = get_bps_config_value(self.site, "run_dir", str, "runinfo")
224 return parsl.config.Config(
225 executors=executors,
226 monitoring=monitor,
227 retries=retries,
228 run_dir=run_dir,
229 checkpoint_mode="task_exit",
230 )