lsst-ctrl-bps-htcondor 29.2025.4500__tar.gz → 29.2025.4600__tar.gz
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.
- {lsst_ctrl_bps_htcondor-29.2025.4500/python/lsst_ctrl_bps_htcondor.egg-info → lsst_ctrl_bps_htcondor-29.2025.4600}/PKG-INFO +4 -1
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/doc/lsst.ctrl.bps.htcondor/userguide.rst +41 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/pyproject.toml +4 -1
- lsst_ctrl_bps_htcondor-29.2025.4600/python/lsst/ctrl/bps/htcondor/dagman_configurator.py +196 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/python/lsst/ctrl/bps/htcondor/etc/htcondor_defaults.yaml +12 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/python/lsst/ctrl/bps/htcondor/htcondor_service.py +12 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/python/lsst/ctrl/bps/htcondor/lssthtc.py +24 -17
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/python/lsst/ctrl/bps/htcondor/prepare_utils.py +0 -2
- lsst_ctrl_bps_htcondor-29.2025.4600/python/lsst/ctrl/bps/htcondor/version.py +2 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600/python/lsst_ctrl_bps_htcondor.egg-info}/PKG-INFO +4 -1
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/python/lsst_ctrl_bps_htcondor.egg-info/SOURCES.txt +2 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/python/lsst_ctrl_bps_htcondor.egg-info/requires.txt +3 -0
- lsst_ctrl_bps_htcondor-29.2025.4600/tests/test_dagman_configurator.py +143 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/tests/test_lssthtc.py +81 -28
- lsst_ctrl_bps_htcondor-29.2025.4500/python/lsst/ctrl/bps/htcondor/version.py +0 -2
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/COPYRIGHT +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/LICENSE +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/MANIFEST.in +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/README.rst +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/bsd_license.txt +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/doc/lsst.ctrl.bps.htcondor/CHANGES.rst +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/doc/lsst.ctrl.bps.htcondor/index.rst +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/gpl-v3.0.txt +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/python/lsst/ctrl/bps/htcondor/__init__.py +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/python/lsst/ctrl/bps/htcondor/common_utils.py +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/python/lsst/ctrl/bps/htcondor/etc/__init__.py +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/python/lsst/ctrl/bps/htcondor/final_post.sh +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/python/lsst/ctrl/bps/htcondor/handlers.py +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/python/lsst/ctrl/bps/htcondor/htcondor_config.py +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/python/lsst/ctrl/bps/htcondor/htcondor_workflow.py +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/python/lsst/ctrl/bps/htcondor/provisioner.py +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/python/lsst/ctrl/bps/htcondor/report_utils.py +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/python/lsst_ctrl_bps_htcondor.egg-info/dependency_links.txt +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/python/lsst_ctrl_bps_htcondor.egg-info/top_level.txt +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/python/lsst_ctrl_bps_htcondor.egg-info/zip-safe +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/setup.cfg +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/tests/test_common_utils.py +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/tests/test_handlers.py +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/tests/test_htcondor_service.py +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/tests/test_prepare_utils.py +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/tests/test_provisioner.py +0 -0
- {lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/tests/test_report_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lsst-ctrl-bps-htcondor
|
|
3
|
-
Version: 29.2025.
|
|
3
|
+
Version: 29.2025.4600
|
|
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"
|
|
@@ -150,6 +150,46 @@ available in your BPS configuration file. For example:
|
|
|
150
150
|
.. __: https://pipelines.lsst.io/v/weekly/modules/lsst.ctrl.bps/quickstart.html#bps-configuration-file
|
|
151
151
|
.. __: https://pipelines.lsst.io/v/weekly/modules/lsst.ctrl.bps/quickstart.html#supported-settings
|
|
152
152
|
|
|
153
|
+
Configuring DAGMan
|
|
154
|
+
^^^^^^^^^^^^^^^^^^
|
|
155
|
+
|
|
156
|
+
`DAGMan`_ is a `HTCondor`_ tool that allows multiple jobs to be organized in
|
|
157
|
+
workflows. It orchestrates the execution of jobs in a workflow to satisfy their
|
|
158
|
+
data dependencies. DAGman workflows are described in the DAG description files.
|
|
159
|
+
|
|
160
|
+
`HTCondor`_ has many settings that affect the operation of `DAGMan`_. Any of
|
|
161
|
+
these settings can be managed via the submit YAML by specifying their values
|
|
162
|
+
in the ``wmsConfig`` section. For example, including the lines below in your
|
|
163
|
+
submit YAML will instruct DAGMan to throttle the number of jobs DAGMan will
|
|
164
|
+
submit at once for execution to 256:
|
|
165
|
+
|
|
166
|
+
.. code-block:: YAML
|
|
167
|
+
|
|
168
|
+
wmsConfig:
|
|
169
|
+
DAGMAN_MAX_JOBS_IDLE: 256
|
|
170
|
+
|
|
171
|
+
A complete list of the supported settings, their descriptions, and default
|
|
172
|
+
values can be found `here`__.
|
|
173
|
+
|
|
174
|
+
.. note::
|
|
175
|
+
|
|
176
|
+
Make sure to select the version of the documentation that corresponds to the
|
|
177
|
+
version of the HTCondor you're using. Supported settings may vary between
|
|
178
|
+
different versions.
|
|
179
|
+
|
|
180
|
+
When customizing DAGMan's settings, make sure the value you provide has the
|
|
181
|
+
appropriate type. Using incorrect value type will result in an error during
|
|
182
|
+
the BPS submission.
|
|
183
|
+
|
|
184
|
+
The settings are for the entire workflow, so the ``wmsConfig`` section can go
|
|
185
|
+
at the root level or inside a ``site`` section, but not inside a ``pipetask``,
|
|
186
|
+
``clusterorfinalJob`` section.
|
|
187
|
+
|
|
188
|
+
If your main workflow contains sub-workflow defined in individual DAG
|
|
189
|
+
description files, they will use the same configuration as the main workflow.
|
|
190
|
+
|
|
191
|
+
.. __: https://htcondor.readthedocs.io/en/latest/admin-manual/configuration-macros.html#dagman-configuration-file-entries
|
|
192
|
+
|
|
153
193
|
.. .. _htc-plugin-authenticating:
|
|
154
194
|
|
|
155
195
|
.. Authenticating
|
|
@@ -629,6 +669,7 @@ complete your run.
|
|
|
629
669
|
|
|
630
670
|
.. __: https://developer.lsst.io/usdf/batch.html#ctrl-bps-htcondor
|
|
631
671
|
|
|
672
|
+
.. _DAGMan: https://htcondor.readthedocs.io/en/latest/automated-workflows/index.html#dagman-workflows
|
|
632
673
|
.. _HTCondor: https://htcondor.readthedocs.io/en/latest/
|
|
633
674
|
.. _Slurm: https://slurm.schedmd.com/overview.html
|
|
634
675
|
.. _bps cancel: https://pipelines.lsst.io/v/weekly/modules/lsst.ctrl.bps/quickstart.html#canceling-submitted-jobs
|
|
@@ -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
|
-
|
|
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
|
-
|
|
1528
|
-
#
|
|
1529
|
-
#
|
|
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,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lsst-ctrl-bps-htcondor
|
|
3
|
-
Version: 29.2025.
|
|
3
|
+
Version: 29.2025.4600
|
|
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"
|
|
@@ -11,6 +11,7 @@ doc/lsst.ctrl.bps.htcondor/index.rst
|
|
|
11
11
|
doc/lsst.ctrl.bps.htcondor/userguide.rst
|
|
12
12
|
python/lsst/ctrl/bps/htcondor/__init__.py
|
|
13
13
|
python/lsst/ctrl/bps/htcondor/common_utils.py
|
|
14
|
+
python/lsst/ctrl/bps/htcondor/dagman_configurator.py
|
|
14
15
|
python/lsst/ctrl/bps/htcondor/final_post.sh
|
|
15
16
|
python/lsst/ctrl/bps/htcondor/handlers.py
|
|
16
17
|
python/lsst/ctrl/bps/htcondor/htcondor_config.py
|
|
@@ -30,6 +31,7 @@ python/lsst_ctrl_bps_htcondor.egg-info/requires.txt
|
|
|
30
31
|
python/lsst_ctrl_bps_htcondor.egg-info/top_level.txt
|
|
31
32
|
python/lsst_ctrl_bps_htcondor.egg-info/zip-safe
|
|
32
33
|
tests/test_common_utils.py
|
|
34
|
+
tests/test_dagman_configurator.py
|
|
33
35
|
tests/test_handlers.py
|
|
34
36
|
tests/test_htcondor_service.py
|
|
35
37
|
tests/test_lssthtc.py
|
|
@@ -0,0 +1,143 @@
|
|
|
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
|
+
"""Unit tests for DagmanConfigurator class."""
|
|
29
|
+
|
|
30
|
+
import logging
|
|
31
|
+
import os
|
|
32
|
+
import tempfile
|
|
33
|
+
import unittest
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
|
|
36
|
+
from pydantic import ValidationError
|
|
37
|
+
|
|
38
|
+
from lsst.ctrl.bps import BpsConfig
|
|
39
|
+
from lsst.ctrl.bps.htcondor import HTCDag
|
|
40
|
+
from lsst.ctrl.bps.htcondor.dagman_configurator import DagmanConfigurator
|
|
41
|
+
|
|
42
|
+
logger = logging.getLogger("lsst.ctrl.bps.htcondor")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class DagmanConfiguratorTestCase(unittest.TestCase):
|
|
46
|
+
"""Unit tests for DagmanConfigurator class."""
|
|
47
|
+
|
|
48
|
+
def setUp(self):
|
|
49
|
+
self.config = BpsConfig(
|
|
50
|
+
{
|
|
51
|
+
"site": {
|
|
52
|
+
"foo": {
|
|
53
|
+
"wmsConfig": {"DAGMAN_USE_STRICT": 1},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
"wmsConfig": {"DAGMAN_USE_STRICT": 0},
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def tearDown(self):
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
def testInitDefaultSearchOptions(self):
|
|
64
|
+
"""Test object instantiation with default search options."""
|
|
65
|
+
configurator = DagmanConfigurator(self.config)
|
|
66
|
+
self.assertIn("DAGMAN_USE_STRICT", configurator.options)
|
|
67
|
+
self.assertEqual(configurator.options["DAGMAN_USE_STRICT"], 0)
|
|
68
|
+
self.assertIsNone(configurator.config_path)
|
|
69
|
+
self.assertIsNone(configurator.prefix)
|
|
70
|
+
|
|
71
|
+
def testInitCustomSearchOptions(self):
|
|
72
|
+
"""Test object instantiation with custom search options."""
|
|
73
|
+
configurator = DagmanConfigurator(self.config, search_opts={"curvals": {"computeSite": "foo"}})
|
|
74
|
+
self.assertIn("DAGMAN_USE_STRICT", configurator.options)
|
|
75
|
+
self.assertEqual(configurator.options["DAGMAN_USE_STRICT"], 1)
|
|
76
|
+
self.assertIsNone(configurator.config_path)
|
|
77
|
+
self.assertIsNone(configurator.prefix)
|
|
78
|
+
|
|
79
|
+
def testInitWrongOptionType(self):
|
|
80
|
+
self.config[".wmsConfig.DAGMAN_USE_STRICT"] = "foo"
|
|
81
|
+
with self.assertRaisesRegex(ValidationError, "DAGMAN_USE_STRICT".lower()):
|
|
82
|
+
DagmanConfigurator(self.config)
|
|
83
|
+
|
|
84
|
+
def testInitUnsupportedDagmanOption(self):
|
|
85
|
+
"""Test object instantiation with unsupported DAGMAN options."""
|
|
86
|
+
self.config[".wmsConfig.DAGMAN_UNSUPPORTED_OPTION"] = "foo"
|
|
87
|
+
with self.assertLogs(logger=logger, level="WARNING") as cm:
|
|
88
|
+
configurator = DagmanConfigurator(self.config)
|
|
89
|
+
self.assertIn("DAGMAN_UNSUPPORTED_OPTION", cm.output[0])
|
|
90
|
+
self.assertNotIn("DAGMAN_UNSUPPORTED_OPTION", configurator.options)
|
|
91
|
+
|
|
92
|
+
def testInitNoWmsConfig(self):
|
|
93
|
+
"""Test object instantiation fails when no WMS-specific options."""
|
|
94
|
+
del self.config["wmsConfig"]
|
|
95
|
+
with self.assertRaisesRegex(KeyError, "not found"):
|
|
96
|
+
DagmanConfigurator(self.config)
|
|
97
|
+
|
|
98
|
+
def testPrepare(self):
|
|
99
|
+
"""Test if the method creates the configuration file."""
|
|
100
|
+
configurator = DagmanConfigurator(self.config)
|
|
101
|
+
with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as tmpdir:
|
|
102
|
+
configurator.prepare("dagman.conf", prefix=tmpdir)
|
|
103
|
+
self.assertIn(Path(tmpdir), list(configurator.config_path.parents))
|
|
104
|
+
self.assertTrue(configurator.config_path.is_file())
|
|
105
|
+
self.assertEqual(configurator.config_path.read_text(), "DAGMAN_USE_STRICT = 0")
|
|
106
|
+
|
|
107
|
+
def testPrepareWithUnsupportedOption(self):
|
|
108
|
+
"""Test if the method does not include unsupported options."""
|
|
109
|
+
self.config[".wmsConfig.DAGMAN_UNSUPPORTED_OPTION"] = "foo"
|
|
110
|
+
configurator = DagmanConfigurator(self.config)
|
|
111
|
+
with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as tmpdir:
|
|
112
|
+
configurator.prepare("dagman.conf", prefix=tmpdir)
|
|
113
|
+
self.assertIn(Path(tmpdir), list(configurator.config_path.parents))
|
|
114
|
+
self.assertTrue(configurator.config_path.is_file())
|
|
115
|
+
self.assertEqual(configurator.config_path.read_text(), "DAGMAN_USE_STRICT = 0")
|
|
116
|
+
|
|
117
|
+
def testPrepareConfigWriteFailure(self):
|
|
118
|
+
"""Test if the method raises when it can't create the configuration."""
|
|
119
|
+
with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as tmpdir:
|
|
120
|
+
os.chmod(tmpdir, 0o500)
|
|
121
|
+
|
|
122
|
+
configurator = DagmanConfigurator(self.config)
|
|
123
|
+
with self.assertLogs(logger=logger, level="ERROR") as cm, self.assertRaises(OSError):
|
|
124
|
+
configurator.prepare("dagman.conf", f"{tmpdir}/subdir")
|
|
125
|
+
self.assertIn("Could not write", cm.output[0])
|
|
126
|
+
|
|
127
|
+
os.chmod(tmpdir, 0o700)
|
|
128
|
+
|
|
129
|
+
def testConfigure(self):
|
|
130
|
+
with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as tmpdir:
|
|
131
|
+
dag = HTCDag(name="test_configure")
|
|
132
|
+
configurator = DagmanConfigurator(self.config)
|
|
133
|
+
configurator.prepare("dagman.conf", prefix=tmpdir)
|
|
134
|
+
configurator.configure(dag)
|
|
135
|
+
self.assertIn("bps_wms_config_path", dag.graph["attr"])
|
|
136
|
+
self.assertEqual(dag.graph["attr"]["bps_wms_config_path"], "dagman.conf")
|
|
137
|
+
|
|
138
|
+
def testConfigureIfNotPrepared(self):
|
|
139
|
+
"""Test if the method raises when prepare step was skipped."""
|
|
140
|
+
dag = HTCDag(name="test_configure_not_prepared")
|
|
141
|
+
configurator = DagmanConfigurator(self.config)
|
|
142
|
+
with self.assertRaisesRegex(RuntimeError, "file does not exist"):
|
|
143
|
+
configurator.configure(dag)
|
{lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/tests/test_lssthtc.py
RENAMED
|
@@ -38,7 +38,9 @@ from shutil import copy2, copytree, ignore_patterns, rmtree, which
|
|
|
38
38
|
|
|
39
39
|
import htcondor
|
|
40
40
|
|
|
41
|
-
from lsst.ctrl.bps
|
|
41
|
+
from lsst.ctrl.bps import BpsConfig
|
|
42
|
+
from lsst.ctrl.bps.htcondor import dagman_configurator, htcondor_config, lssthtc
|
|
43
|
+
from lsst.daf.butler import Config
|
|
42
44
|
from lsst.utils.tests import temporaryDirectory
|
|
43
45
|
|
|
44
46
|
logger = logging.getLogger("lsst.ctrl.bps.htcondor")
|
|
@@ -1190,7 +1192,6 @@ class HtcCreateSubmitFromDagTestCase(unittest.TestCase):
|
|
|
1190
1192
|
dag_filename = pathlib.Path(tmp_dir) / "tiny_success.dag"
|
|
1191
1193
|
submit = lssthtc.htc_create_submit_from_dag(str(dag_filename), {})
|
|
1192
1194
|
self.assertIn("-MaxIdle 42", submit["arguments"])
|
|
1193
|
-
self.assertEqual("true", os.environ["_CONDOR_DAGMAN_MANAGER_JOB_APPEND_GETENV"].lower())
|
|
1194
1195
|
|
|
1195
1196
|
@unittest.mock.patch.dict(os.environ, {})
|
|
1196
1197
|
def testMaxIdleGiven(self):
|
|
@@ -1199,7 +1200,6 @@ class HtcCreateSubmitFromDagTestCase(unittest.TestCase):
|
|
|
1199
1200
|
dag_filename = pathlib.Path(tmp_dir) / "tiny_success.dag"
|
|
1200
1201
|
submit = lssthtc.htc_create_submit_from_dag(str(dag_filename), {"MaxIdle": 37})
|
|
1201
1202
|
self.assertIn("-MaxIdle 37", submit["arguments"])
|
|
1202
|
-
self.assertEqual("true", os.environ["_CONDOR_DAGMAN_MANAGER_JOB_APPEND_GETENV"].lower())
|
|
1203
1203
|
|
|
1204
1204
|
@unittest.mock.patch.dict(os.environ, {})
|
|
1205
1205
|
def testNoMaxJobsIdle(self):
|
|
@@ -1215,37 +1215,90 @@ class HtcCreateSubmitFromDagTestCase(unittest.TestCase):
|
|
|
1215
1215
|
with unittest.mock.patch("htcondor.param") as mock_param:
|
|
1216
1216
|
mock_param.__contains__.return_value = False
|
|
1217
1217
|
_ = lssthtc.htc_create_submit_from_dag(str(dag_filename), {})
|
|
1218
|
-
self.assertEqual("true", os.environ["_CONDOR_DAGMAN_MANAGER_JOB_APPEND_GETENV"].lower())
|
|
1219
1218
|
submit_mock.assert_called_once_with(str(dag_filename), {})
|
|
1220
1219
|
|
|
1221
|
-
@unittest.mock.patch.dict(os.environ, {})
|
|
1222
|
-
def testDoRecurseGivenWithNoEnv(self):
|
|
1223
|
-
with temporaryDirectory() as tmp_dir:
|
|
1224
|
-
copy2(f"{TESTDIR}/data/tiny_success/tiny_success.dag", tmp_dir)
|
|
1225
|
-
dag_filename = pathlib.Path(tmp_dir) / "tiny_success.dag"
|
|
1226
|
-
submit = lssthtc.htc_create_submit_from_dag(str(dag_filename), {"do_recurse": True})
|
|
1227
|
-
self.assertIn("-do_recurse", submit["arguments"])
|
|
1228
|
-
self.assertEqual("true", os.environ["_CONDOR_DAGMAN_GENERATE_SUBDAG_SUBMITS"].lower())
|
|
1229
|
-
self.assertEqual("true", os.environ["_CONDOR_DAGMAN_MANAGER_JOB_APPEND_GETENV"].lower())
|
|
1230
1220
|
|
|
1231
|
-
|
|
1232
|
-
|
|
1221
|
+
class HtcDagTestCase(unittest.TestCase):
|
|
1222
|
+
"""Test for HTCDag class."""
|
|
1223
|
+
|
|
1224
|
+
def setUp(self):
|
|
1225
|
+
job = lssthtc.HTCJob(name="test_job")
|
|
1226
|
+
job.add_job_cmds(
|
|
1227
|
+
{
|
|
1228
|
+
"executable": "/usr/bin/echo",
|
|
1229
|
+
"arguments": "foo",
|
|
1230
|
+
"output": "test_job.$(Cluster).out",
|
|
1231
|
+
"error": "test_job.$(Cluster).out",
|
|
1232
|
+
"log": "test_job.$(Cluster).log",
|
|
1233
|
+
}
|
|
1234
|
+
)
|
|
1235
|
+
job.subfile = f"{job.name}.sub"
|
|
1236
|
+
|
|
1237
|
+
self.dag = lssthtc.HTCDag(name="test_workflow")
|
|
1238
|
+
self.dag.add_job(job)
|
|
1239
|
+
|
|
1240
|
+
self.subfile_expected = [
|
|
1241
|
+
"executable=/usr/bin/echo\n",
|
|
1242
|
+
"arguments=foo\n",
|
|
1243
|
+
"output=test_job.$(Cluster).out\n",
|
|
1244
|
+
"error=test_job.$(Cluster).out\n",
|
|
1245
|
+
"log=test_job.$(Cluster).log\n",
|
|
1246
|
+
"queue\n",
|
|
1247
|
+
]
|
|
1248
|
+
|
|
1249
|
+
def tearDown(self):
|
|
1250
|
+
pass
|
|
1251
|
+
|
|
1252
|
+
def testWriteWithDagConfig(self):
|
|
1233
1253
|
with temporaryDirectory() as tmp_dir:
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1254
|
+
config = BpsConfig(Config(htcondor_config.HTC_DEFAULTS_URI))
|
|
1255
|
+
job = self.dag.nodes["test_job"]["data"]
|
|
1256
|
+
wms_config_filename = "dagman.conf"
|
|
1257
|
+
wms_configurator = dagman_configurator.DagmanConfigurator(config)
|
|
1258
|
+
wms_configurator.prepare(wms_config_filename, prefix=tmp_dir)
|
|
1259
|
+
wms_configurator.configure(self.dag)
|
|
1260
|
+
dagfile_expected = [
|
|
1261
|
+
f"CONFIG {wms_config_filename}\n",
|
|
1262
|
+
f'JOB {job.name} "{job.subfile}"\n',
|
|
1263
|
+
f"DOT {self.dag.name}.dot\n",
|
|
1264
|
+
f"NODE_STATUS_FILE {self.dag.name}.node_status\n",
|
|
1265
|
+
f'SET_JOB_ATTR bps_wms_config_path= "{wms_config_filename}"\n',
|
|
1266
|
+
]
|
|
1267
|
+
|
|
1268
|
+
self.dag.write(tmp_dir, "", "")
|
|
1240
1269
|
|
|
1241
|
-
|
|
1242
|
-
|
|
1270
|
+
self.assertIn("submit_path", self.dag.graph)
|
|
1271
|
+
self.assertEqual(self.dag.graph["submit_path"], tmp_dir)
|
|
1272
|
+
self.assertIn("dag_filename", self.dag.graph)
|
|
1273
|
+
self.assertEqual(self.dag.graph["dag_filename"], f"{self.dag.graph['name']}.dag")
|
|
1274
|
+
with open(os.path.join(tmp_dir, self.dag.graph["dag_filename"]), encoding="utf-8") as f:
|
|
1275
|
+
dagfile_actual = f.readlines()
|
|
1276
|
+
self.assertEqual(dagfile_actual, dagfile_expected)
|
|
1277
|
+
with open(os.path.join(tmp_dir, job.subfile), encoding="utf-8") as f:
|
|
1278
|
+
subfile_actual = f.readlines()
|
|
1279
|
+
self.assertEqual(subfile_actual, self.subfile_expected)
|
|
1280
|
+
|
|
1281
|
+
def testWriteWithoutDagConfig(self):
|
|
1243
1282
|
with temporaryDirectory() as tmp_dir:
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1283
|
+
job = self.dag.nodes["test_job"]["data"]
|
|
1284
|
+
dagfile_expected = [
|
|
1285
|
+
f'JOB {job.name} "{job.subfile}"\n',
|
|
1286
|
+
f"DOT {self.dag.name}.dot\n",
|
|
1287
|
+
f"NODE_STATUS_FILE {self.dag.name}.node_status\n",
|
|
1288
|
+
]
|
|
1289
|
+
|
|
1290
|
+
self.dag.write(tmp_dir, "", "")
|
|
1291
|
+
|
|
1292
|
+
self.assertIn("submit_path", self.dag.graph)
|
|
1293
|
+
self.assertEqual(self.dag.graph["submit_path"], tmp_dir)
|
|
1294
|
+
self.assertIn("dag_filename", self.dag.graph)
|
|
1295
|
+
self.assertEqual(self.dag.graph["dag_filename"], f"{self.dag.graph['name']}.dag")
|
|
1296
|
+
with open(os.path.join(tmp_dir, self.dag.graph["dag_filename"]), encoding="utf-8") as f:
|
|
1297
|
+
dagfile_actual = f.readlines()
|
|
1298
|
+
self.assertEqual(dagfile_actual, dagfile_expected)
|
|
1299
|
+
with open(os.path.join(tmp_dir, job.subfile), encoding="utf-8") as f:
|
|
1300
|
+
subfile_actual = f.readlines()
|
|
1301
|
+
self.assertEqual(subfile_actual, self.subfile_expected)
|
|
1249
1302
|
|
|
1250
1303
|
|
|
1251
1304
|
if __name__ == "__main__":
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps_htcondor-29.2025.4500 → lsst_ctrl_bps_htcondor-29.2025.4600}/tests/test_handlers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|