lsst-ctrl-bps-htcondor 29.2025.4500__py3-none-any.whl → 29.2025.4700__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,196 @@
1
+ # This file is part of ctrl_bps_htcondor.
2
+ #
3
+ # Developed for the LSST Data Management System.
4
+ # This product includes software developed by the LSST Project
5
+ # (https://www.lsst.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/>.
27
+
28
+ """Module enabling configuring DAGMan via submit YAML."""
29
+
30
+ __all__ = ["DagmanConfigurator"]
31
+
32
+ import logging
33
+ import os
34
+ from pathlib import Path
35
+ from typing import Any
36
+
37
+ import htcondor
38
+ from pydantic import AliasGenerator, ConfigDict, create_model
39
+
40
+ from lsst.ctrl.bps import BpsConfig
41
+
42
+ from .lssthtc import HTCDag
43
+
44
+ _LOG = logging.getLogger(__name__)
45
+
46
+ # Extract DAGMan configuration options with their types and default values from
47
+ # the local HTCondor configuration.
48
+ #
49
+ # Notes
50
+ # -----
51
+ # There are some DAGMan configuration options that names do not start with
52
+ # ``DAGMAN_`` (e.g., ``MAX_DAGMAN_LOG``). Hence, do not use
53
+ # ``key.startswith("DAGMAN_")``.
54
+ _fields = {key.lower(): (type(val), val) for key, val in htcondor.param.items() if "DAGMAN_" in key}
55
+
56
+ # Add some valid configuration options are not set by default by HTCondor and
57
+ # are missing from ``htcondor.param``.
58
+ #
59
+ # Notes
60
+ # -----
61
+ # A complete list of configuration options HTCondor supports can be found in
62
+ # ``src/condor_utils/param_info.in`` in
63
+ # `HTCondor GitHub repository <https://github.com/htcondor/htcondor>`_.
64
+ _fields.update(
65
+ {
66
+ "dagman_debug": (str, ""),
67
+ "dagman_node_record_info": (str, ""),
68
+ "dagman_record_machine_attrs": (str, ""),
69
+ }
70
+ )
71
+
72
+ # Dynamically create a Pydantic model encapsulating the DAGMan configuration
73
+ # options gathered above.
74
+ _DagmanOptions = create_model(
75
+ "DagmanOptions",
76
+ __config__=ConfigDict(
77
+ alias_generator=AliasGenerator(
78
+ serialization_alias=lambda name: name.upper(),
79
+ ),
80
+ extra="allow",
81
+ serialize_by_alias=True,
82
+ ),
83
+ **_fields,
84
+ )
85
+
86
+
87
+ class DagmanConfigurator:
88
+ """Class responsible for setting WMS-specific configuration options.
89
+
90
+ Parameters
91
+ ----------
92
+ config : `lsst.ctrl.bps.BpsConfig`
93
+ BPS configuration.
94
+ search_opts : `dict` [`str`, `Any`], optional
95
+ Options to use while searching the BPS configuration for values.
96
+
97
+ Raises
98
+ ------
99
+ KeyError
100
+ Raised if DAGMan configuration is missing from the BPS configuration.
101
+ """
102
+
103
+ def __init__(self, config: BpsConfig, search_opts: dict[str, Any] | None = None) -> None:
104
+ if search_opts is None:
105
+ search_opts = {}
106
+ _, site = config.search("computeSite", search_opts)
107
+ if site:
108
+ search_opts["curvals"] = {"curr_site": site}
109
+ _, wms_config = config.search("wmsConfig", search_opts)
110
+ if not wms_config:
111
+ raise KeyError("WMS-specific configuration not found")
112
+ self._options = _DagmanOptions.model_validate({key.lower(): val for key, val in wms_config.items()})
113
+ if self._options.model_extra:
114
+ unknown_opts = [key.upper() for key in self._options.model_extra]
115
+ _LOG.warning(
116
+ "The following WMS-specific config options were not recognized and will be ignored: %s.",
117
+ ", ".join(unknown_opts),
118
+ )
119
+ self.config_path: Path | None = None
120
+ self.prefix: Path | None = None
121
+
122
+ @property
123
+ def options(self) -> dict[str, Any]:
124
+ """DAGMan configuration options set via BPS (`dict` [`str`, `Any`])."""
125
+ return {
126
+ key: val
127
+ for key, val in self._options.model_dump(exclude_unset=True).items()
128
+ if key not in self._options.model_extra
129
+ }
130
+
131
+ def prepare(self, filename: os.PathLike | str, prefix: os.PathLike | str | None) -> None:
132
+ """Write WMS-specific configuration to a file.
133
+
134
+ Parameters
135
+ ----------
136
+ filename : `str`, optional
137
+ Name of the file to use when creating the DAG configuration.
138
+ prefix : `pathlib.Path` | `str`, optional
139
+ Directory in which to output the DAG configuration file. If not
140
+ provided, the script will be written to the current directory.
141
+
142
+ Raises
143
+ ------
144
+ OSError
145
+ Raised if the configuration file cannot be created.
146
+ """
147
+ if prefix:
148
+ self.prefix = Path(prefix)
149
+ self.config_path = self.prefix / filename if self.prefix else Path(filename)
150
+ try:
151
+ self.config_path.parent.mkdir(parents=True, exist_ok=True)
152
+ except OSError as exc:
153
+ _LOG.error(
154
+ "Could not write WMS-specific configuration file '%s': %s",
155
+ self.config_path,
156
+ exc.strerror,
157
+ )
158
+ raise
159
+
160
+ # Populate the DAG configuration file only with options that were
161
+ # explicitly set in the BPS configuration.
162
+ #
163
+ # Notes
164
+ # -----
165
+ # The Pydantic model we are using to represent the DAGMan configuration
166
+ # options allows for extra fields. However, it seems that
167
+ # BaseModel.model_dump() does not support excluding these fields during
168
+ # serialization at the moment (Pydantic ver. 2.12), so we have to do it
169
+ # manually.
170
+ self.config_path.write_text("\n".join(f"{key} = {val}" for key, val in self.options.items()))
171
+
172
+ def configure(self, dag: HTCDag) -> None:
173
+ """Add DAG configuration file to the workflow.
174
+
175
+ Parameters
176
+ ----------
177
+ dag : `lsst.ctrl.bps.htcondor.HTCDag`
178
+ HTCondor DAG.
179
+
180
+ Raises
181
+ ------
182
+ RuntimeError
183
+ Raised if the prepare step was omitted.
184
+
185
+ Notes
186
+ -----
187
+ The path to the DAG configuration is added as a DAG attribute named
188
+ ``bps_wms_config_path``. The stored path is relative to the prefix.
189
+ """
190
+ if self.config_path is None:
191
+ raise RuntimeError(
192
+ f"cannot add WMS-specific configuration to the workflow: file does not exist. "
193
+ f"(hint: run {type(self).__qualname__}.prepare() to create it)"
194
+ )
195
+ config_path = self.config_path.relative_to(self.prefix) if self.prefix else self.config_path
196
+ dag.add_attribs({"bps_wms_config_path": str(config_path)})
@@ -43,3 +43,15 @@ provisionResources: false
43
43
  overwriteJobFiles: true
44
44
  finalJob:
45
45
  overwriteJobFiles: false
46
+
47
+ # Define the default DAGMan configuration.
48
+ wmsConfig:
49
+ # A comma separated list of variable names to add to the DAGMan *.condor.sub
50
+ # file’s getenv option. If set to "TRUE" (a string, not a boolean!), DAGMan
51
+ # will effectively lift all environmental variables from the user's shell
52
+ # environment.
53
+ DAGMAN_MANAGER_JOB_APPEND_GETENV: "TRUE"
54
+
55
+ # A boolean flag controlling whether DAGMan should generate submit files for
56
+ # nested DAGs automatically.
57
+ DAGMAN_GENERATE_SUBDAG_SUBMITS: true
@@ -46,6 +46,7 @@ from lsst.daf.butler import Config
46
46
  from lsst.utils.timer import time_this
47
47
 
48
48
  from .common_utils import WmsIdType, _wms_id_to_cluster, _wms_id_to_dir, _wms_id_type
49
+ from .dagman_configurator import DagmanConfigurator
49
50
  from .htcondor_config import HTC_DEFAULTS_URI
50
51
  from .htcondor_workflow import HTCondorWorkflow
51
52
  from .lssthtc import (
@@ -118,6 +119,17 @@ class HTCondorService(BaseWmsService):
118
119
  provisioner.prepare("provisioningJob.bash", prefix=out_prefix)
119
120
  provisioner.provision(workflow.dag)
120
121
 
122
+ try:
123
+ configurator = DagmanConfigurator(config)
124
+ except KeyError:
125
+ _LOG.debug(
126
+ "No DAGMan-specific settings were found in BPS config; "
127
+ "skipping writing DAG-specific configuration file."
128
+ )
129
+ else:
130
+ configurator.prepare("dagman.conf", prefix=out_prefix)
131
+ configurator.configure(workflow.dag)
132
+
121
133
  with time_this(
122
134
  log=_LOG, level=logging.INFO, prefix=None, msg="Completed writing out HTCondor workflow"
123
135
  ):
@@ -725,19 +725,6 @@ def htc_create_submit_from_dag(dag_filename: str, submit_options: dict[str, Any]
725
725
  Use with HTCondor versions which support htcondor.Submit.from_dag(),
726
726
  i.e., 8.9.3 or newer.
727
727
  """
728
- # Passing do_recurse as submit_option does not seem to
729
- # override DAGMAN_GENERATE_SUBDAG_SUBMITS as manual implies.
730
- # So setting it and the other bps required setting here as
731
- # environment variables if they don't exist.
732
- var_name = "_CONDOR_DAGMAN_MANAGER_JOB_APPEND_GETENV"
733
- if var_name not in os.environ:
734
- os.environ[var_name] = "True"
735
-
736
- if "do_recurse" in submit_options:
737
- var_name = "_CONDOR_DAGMAN_GENERATE_SUBDAG_SUBMITS"
738
- if var_name not in os.environ:
739
- os.environ[var_name] = str(submit_options["do_recurse"])
740
-
741
728
  # Config and environment variables do not seem to override -MaxIdle
742
729
  # on the .dag.condor.sub's command line (broken in some 24.0.x versions).
743
730
  # Explicitly forward them as a submit_option if either exists.
@@ -1164,7 +1151,15 @@ class HTCDag(networkx.DiGraph):
1164
1151
  self.graph["dag_filename"] = os.path.join(dag_subdir, f"{self.graph['name']}.dag")
1165
1152
  full_filename = os.path.join(submit_path, self.graph["dag_filename"])
1166
1153
  os.makedirs(os.path.dirname(full_filename), exist_ok=True)
1154
+
1155
+ try:
1156
+ dagman_config_path = Path(self.graph["attr"]["bps_wms_config_path"])
1157
+ except KeyError:
1158
+ dagman_config_path = None
1167
1159
  with open(full_filename, "w") as fh:
1160
+ if dagman_config_path is not None:
1161
+ fh.write(f"CONFIG {dag_rel_path / dagman_config_path}\n")
1162
+
1168
1163
  for name, nodeval in self.nodes().items():
1169
1164
  try:
1170
1165
  job = nodeval["data"]
@@ -1177,6 +1172,8 @@ class HTCDag(networkx.DiGraph):
1177
1172
  subdir = job.dagcmds["dir"]
1178
1173
  else:
1179
1174
  subdir = job_subdir
1175
+ if dagman_config_path is not None:
1176
+ job.subdag.add_attribs({"bps_wms_config_path": str(dagman_config_path)})
1180
1177
  job.subdag.write(submit_path, subdir, dag_subdir, "../..")
1181
1178
  fh.write(
1182
1179
  f"SUBDAG EXTERNAL {job.name} {Path(job.subdag.graph['dag_filename']).name} "
@@ -1468,7 +1465,17 @@ def count_jobs_in_single_dag(
1468
1465
  job_name_to_type: dict[str, WmsNodeType] = {}
1469
1466
  with open(filename) as fh:
1470
1467
  for line in fh:
1471
- job_name = ""
1468
+ # Skip any line that contains commands irrelevant to job counting.
1469
+ if not line.startswith(
1470
+ (
1471
+ "JOB",
1472
+ "FINAL",
1473
+ "SERVICE",
1474
+ "SUBDAG EXTERNAL",
1475
+ )
1476
+ ):
1477
+ continue
1478
+
1472
1479
  m = re.match(
1473
1480
  r"(?P<command>JOB|FINAL|SERVICE|SUBDAG EXTERNAL)\s+"
1474
1481
  r'(?P<jobname>(?P<wms>wms_)?\S+)\s+"?(?P<subfile>\S+)"?\s*'
@@ -1524,9 +1531,9 @@ def count_jobs_in_single_dag(
1524
1531
 
1525
1532
  job_name_to_label[job_name] = label
1526
1533
  job_name_to_type[job_name] = job_type
1527
- elif not line.startswith(("VARS", "PARENT", "DOT", "NODE_STATUS_FILE", "SET_JOB_ATTR", "SCRIPT")):
1528
- # Only print warning if not a line wanting to skip
1529
- # Probably means problem with regex in above match pattern.
1534
+ else:
1535
+ # The line should, but didn't match the pattern above. Probably
1536
+ # problems with regex.
1530
1537
  _LOG.warning("Unexpected skipping of dag line: %s", line)
1531
1538
 
1532
1539
  return counts, job_name_to_label, job_name_to_type
@@ -897,8 +897,6 @@ def _generic_workflow_to_htcondor_dag(
897
897
  elif gwjob.node_type == GenericWorkflowNodeType.GROUP:
898
898
  gwjob = cast(GenericWorkflowGroup, gwjob)
899
899
  htc_job = _group_to_subdag(config, gwjob, out_prefix)
900
- # In case DAGMAN_GENERATE_SUBDAG_SUBMITS is False,
901
- dag.graph["submit_options"]["do_recurse"] = True
902
900
  else:
903
901
  raise RuntimeError(f"Unsupported generic workflow node type {gwjob.node_type} ({gwjob.name})")
904
902
  _LOG.debug("Calling adding job %s %s", htc_job.name, htc_job.label)
@@ -1,2 +1,2 @@
1
1
  __all__ = ["__version__"]
2
- __version__ = "29.2025.4500"
2
+ __version__ = "29.2025.4700"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lsst-ctrl-bps-htcondor
3
- Version: 29.2025.4500
3
+ Version: 29.2025.4700
4
4
  Summary: HTCondor plugin for lsst-ctrl-bps.
5
5
  Author-email: Rubin Observatory Data Management <dm-admin@lists.lsst.org>
6
6
  License-Expression: BSD-3-Clause OR GPL-3.0-or-later
@@ -23,7 +23,10 @@ License-File: gpl-v3.0.txt
23
23
  Requires-Dist: htcondor>=8.8
24
24
  Requires-Dist: lsst-ctrl-bps
25
25
  Requires-Dist: lsst-daf-butler
26
+ Requires-Dist: lsst-pipe-base
26
27
  Requires-Dist: lsst-utils
28
+ Requires-Dist: packaging
29
+ Requires-Dist: pydantic<3.0,>=2
27
30
  Provides-Extra: test
28
31
  Requires-Dist: pytest>=3.2; extra == "test"
29
32
  Requires-Dist: pytest-openfiles>=0.5.0; extra == "test"
@@ -1,23 +1,24 @@
1
1
  lsst/ctrl/bps/htcondor/__init__.py,sha256=8PVRtHS2tn_BDH9gnWQ-Fbg7o7wTbZoW7f41RGf0g7A,1450
2
2
  lsst/ctrl/bps/htcondor/common_utils.py,sha256=cHdPaOwS2I5T4RpGChdMAaqis7T3B2dR45zu8cVzpto,9744
3
+ lsst/ctrl/bps/htcondor/dagman_configurator.py,sha256=YKqQr7_8sTMvrhorsQQEShxPNQTBFYI0vo62tpasihs,7231
3
4
  lsst/ctrl/bps/htcondor/final_post.sh,sha256=chfaQV6Q7rGsK-8Hx58ch52m-PofvBanrl7VwCssHec,248
4
5
  lsst/ctrl/bps/htcondor/handlers.py,sha256=fkTEKulfwYOMofya9PzbvCiI9WNLfj_yTnno8Sm3srQ,14860
5
6
  lsst/ctrl/bps/htcondor/htcondor_config.py,sha256=c4lCiYEwEXFdxgbMfEkbDm4LrvkRMF31SqLtQqzqIV4,1523
6
- lsst/ctrl/bps/htcondor/htcondor_service.py,sha256=dnpxje5XRI0TEui-oXdp9kKlnTMiOZZNk0jDJIjNFDE,22177
7
+ lsst/ctrl/bps/htcondor/htcondor_service.py,sha256=raGVthF4Q92B0VKeE5T1eNzOnmL6u4ni2Dcr3gThvd8,22671
7
8
  lsst/ctrl/bps/htcondor/htcondor_workflow.py,sha256=wkANkAA4Ciq9WP_DWkjH2k0xWz9_i6gaNHWcxWQ4zkM,3071
8
- lsst/ctrl/bps/htcondor/lssthtc.py,sha256=c7eecmDgEkfxASwPJ645rireM0Pe6WdMImnUT_3y-SA,81454
9
- lsst/ctrl/bps/htcondor/prepare_utils.py,sha256=rkh41PGijZuO-LbxBpM-XQOAn9srK_rjVWPIj4n9j9c,36738
9
+ lsst/ctrl/bps/htcondor/lssthtc.py,sha256=2XVwlDByOHvOyB9yrjY_UJeoZgmh_OLVhg782PLC-fg,81485
10
+ lsst/ctrl/bps/htcondor/prepare_utils.py,sha256=yjd31cG28zpvkDbyrI6gtRnkF2Wnzz_HzBEAyH_3zbs,36614
10
11
  lsst/ctrl/bps/htcondor/provisioner.py,sha256=DxhCOCpqyBXIBR2m8VL_FwaDMr2scQIOe8ArWjgQ_Ls,7929
11
12
  lsst/ctrl/bps/htcondor/report_utils.py,sha256=7idf6GRfMhCt5OuuCzHnnhPq5YVYm8k0bqS301MEjC8,31123
12
- lsst/ctrl/bps/htcondor/version.py,sha256=ec8d6sw9aRuINU5Qs4LjOV9LhqSISytoOfEoRYO2XzU,55
13
+ lsst/ctrl/bps/htcondor/version.py,sha256=xCUcvtDm7ffryaF3_oc2AhBXCqIFOubJb8jOIg_6D6E,55
13
14
  lsst/ctrl/bps/htcondor/etc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- lsst/ctrl/bps/htcondor/etc/htcondor_defaults.yaml,sha256=C6DKJKmKFKczukpXVXev9u1-vmv2IcgcdtjTtgJWDQM,1561
15
- lsst_ctrl_bps_htcondor-29.2025.4500.dist-info/licenses/COPYRIGHT,sha256=Lc6NoAEFQ65v_SmtS9NwfHTOuSUtC2Umbjv5zyowiQM,61
16
- lsst_ctrl_bps_htcondor-29.2025.4500.dist-info/licenses/LICENSE,sha256=pRExkS03v0MQW-neNfIcaSL6aiAnoLxYgtZoFzQ6zkM,232
17
- lsst_ctrl_bps_htcondor-29.2025.4500.dist-info/licenses/bsd_license.txt,sha256=7MIcv8QRX9guUtqPSBDMPz2SnZ5swI-xZMqm_VDSfxY,1606
18
- lsst_ctrl_bps_htcondor-29.2025.4500.dist-info/licenses/gpl-v3.0.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
19
- lsst_ctrl_bps_htcondor-29.2025.4500.dist-info/METADATA,sha256=oUkqyydYwm4_SrWvV65WD51DDCOD-NXUpUs90FGoul0,2213
20
- lsst_ctrl_bps_htcondor-29.2025.4500.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
21
- lsst_ctrl_bps_htcondor-29.2025.4500.dist-info/top_level.txt,sha256=eUWiOuVVm9wwTrnAgiJT6tp6HQHXxIhj2QSZ7NYZH80,5
22
- lsst_ctrl_bps_htcondor-29.2025.4500.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
23
- lsst_ctrl_bps_htcondor-29.2025.4500.dist-info/RECORD,,
15
+ lsst/ctrl/bps/htcondor/etc/htcondor_defaults.yaml,sha256=0mIBmXBy5TBBtw1g6RL5TNjbnWM_ADeLp94r1-gfwAg,2061
16
+ lsst_ctrl_bps_htcondor-29.2025.4700.dist-info/licenses/COPYRIGHT,sha256=Lc6NoAEFQ65v_SmtS9NwfHTOuSUtC2Umbjv5zyowiQM,61
17
+ lsst_ctrl_bps_htcondor-29.2025.4700.dist-info/licenses/LICENSE,sha256=pRExkS03v0MQW-neNfIcaSL6aiAnoLxYgtZoFzQ6zkM,232
18
+ lsst_ctrl_bps_htcondor-29.2025.4700.dist-info/licenses/bsd_license.txt,sha256=7MIcv8QRX9guUtqPSBDMPz2SnZ5swI-xZMqm_VDSfxY,1606
19
+ lsst_ctrl_bps_htcondor-29.2025.4700.dist-info/licenses/gpl-v3.0.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
20
+ lsst_ctrl_bps_htcondor-29.2025.4700.dist-info/METADATA,sha256=NyRVqSZBmYe6ooKa84VMEUIpGPB6pV3LR9KN9JX3Ddk,2300
21
+ lsst_ctrl_bps_htcondor-29.2025.4700.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
22
+ lsst_ctrl_bps_htcondor-29.2025.4700.dist-info/top_level.txt,sha256=eUWiOuVVm9wwTrnAgiJT6tp6HQHXxIhj2QSZ7NYZH80,5
23
+ lsst_ctrl_bps_htcondor-29.2025.4700.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
24
+ lsst_ctrl_bps_htcondor-29.2025.4700.dist-info/RECORD,,