lsst-ctrl-bps-panda 29.2025.3900__py3-none-any.whl → 29.2025.4200__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.
@@ -25,6 +25,6 @@
25
25
  # You should have received a copy of the GNU General Public License
26
26
  # along with this program. If not, see <https://www.gnu.org/licenses/>.
27
27
 
28
- __all__ = ["clean", "reset", "status"]
28
+ __all__ = ["clean", "reset", "refresh", "status"]
29
29
 
30
- from .panda_auth_commands import clean, reset, status
30
+ from .panda_auth_commands import clean, reset, refresh, status
@@ -28,6 +28,7 @@
28
28
 
29
29
  __all__ = [
30
30
  "clean",
31
+ "refresh",
31
32
  "reset",
32
33
  "status",
33
34
  ]
@@ -37,7 +38,12 @@ import click
37
38
 
38
39
  from lsst.daf.butler.cli.utils import MWCommand
39
40
 
40
- from ...panda_auth_drivers import panda_auth_clean_driver, panda_auth_reset_driver, panda_auth_status_driver
41
+ from ...panda_auth_drivers import (
42
+ panda_auth_clean_driver,
43
+ panda_auth_refresh_driver,
44
+ panda_auth_reset_driver,
45
+ panda_auth_status_driver,
46
+ )
41
47
 
42
48
 
43
49
  class PandaAuthCommand(MWCommand):
@@ -62,3 +68,13 @@ def reset(*args, **kwargs):
62
68
  def clean(*args, **kwargs):
63
69
  """Clean up token and token cache files."""
64
70
  panda_auth_clean_driver(*args, **kwargs)
71
+
72
+
73
+ @click.command(cls=PandaAuthCommand)
74
+ @click.option("--days", default=4, help="The earlist remaining days to refresh the token.")
75
+ @click.option("--verbose", is_flag=True, help="Enable verbose output")
76
+ def refresh(*args, **kwargs):
77
+ """Refresh auth tocken."""
78
+ days = kwargs.get("days", 4)
79
+ verbose = kwargs.get("verbose", False)
80
+ panda_auth_refresh_driver(days, verbose)
@@ -33,6 +33,7 @@ the subcommand method.
33
33
 
34
34
  __all__ = [
35
35
  "panda_auth_clean_driver",
36
+ "panda_auth_refresh_driver",
36
37
  "panda_auth_reset_driver",
37
38
  "panda_auth_status_driver",
38
39
  ]
@@ -41,7 +42,19 @@ __all__ = [
41
42
  import logging
42
43
  from datetime import datetime
43
44
 
44
- from .panda_auth_utils import panda_auth_clean, panda_auth_status, panda_auth_update
45
+ from lsst.ctrl.bps.panda.panda_exceptions import (
46
+ PandaAuthError,
47
+ TokenExpiredError,
48
+ TokenNotFoundError,
49
+ TokenTooEarlyError,
50
+ )
51
+
52
+ from .panda_auth_utils import (
53
+ panda_auth_clean,
54
+ panda_auth_refresh,
55
+ panda_auth_status,
56
+ panda_auth_update,
57
+ )
45
58
 
46
59
  _LOG = logging.getLogger(__name__)
47
60
 
@@ -56,6 +69,20 @@ def panda_auth_reset_driver():
56
69
  panda_auth_update(None, True)
57
70
 
58
71
 
72
+ def panda_auth_refresh_driver(days, verbose):
73
+ """Refresh auth token."""
74
+ try:
75
+ panda_auth_refresh(days, verbose)
76
+ except TokenNotFoundError as e:
77
+ print(f"[ERROR] {e}")
78
+ except TokenExpiredError as e:
79
+ print(f"[ERROR] {e}")
80
+ except TokenTooEarlyError as e:
81
+ print(f"[INFO] {e}")
82
+ except PandaAuthError as e:
83
+ print(f"[FAIL] {e}")
84
+
85
+
59
86
  def panda_auth_status_driver():
60
87
  """Gather information about a token if it exists."""
61
88
  status = panda_auth_status()
@@ -30,19 +30,32 @@
30
30
  __all__ = [
31
31
  "panda_auth_clean",
32
32
  "panda_auth_expiration",
33
+ "panda_auth_refresh",
33
34
  "panda_auth_setup",
34
35
  "panda_auth_status",
35
36
  "panda_auth_update",
36
37
  ]
37
38
 
38
39
 
40
+ import base64
41
+ import json
39
42
  import logging
40
43
  import os
44
+ from datetime import UTC, datetime, timedelta
41
45
 
42
46
  import idds.common.utils as idds_utils
43
47
  import pandaclient.idds_api
44
48
  from pandaclient.openidc_utils import OpenIdConnect_Utils
45
49
 
50
+ from lsst.ctrl.bps.panda.panda_exceptions import (
51
+ AuthConfigError,
52
+ PandaAuthError,
53
+ TokenExpiredError,
54
+ TokenNotFoundError,
55
+ TokenRefreshError,
56
+ TokenTooEarlyError,
57
+ )
58
+
46
59
  _LOG = logging.getLogger(__name__)
47
60
 
48
61
 
@@ -151,3 +164,87 @@ def panda_auth_update(idds_server=None, reset=False):
151
164
  # idds server given. So for now, check result string for keywords.
152
165
  if "request_id" not in ret[1][-1] or "status" not in ret[1][-1]:
153
166
  raise RuntimeError(f"Error contacting PanDA service: {ret}")
167
+
168
+
169
+ def panda_auth_refresh(days=4, verbose=False):
170
+ """
171
+ Refresh the current valid IAM OpenID authentication token.
172
+
173
+ This function checks the expiration time of the existing token stored
174
+ in the local token file and attempts to refresh it if it is close to
175
+ expiring (within a specified number of days).
176
+
177
+ Parameters
178
+ ----------
179
+ days : `int`, optional
180
+ The minimum number of days before token expiration to trigger a
181
+ refresh. If the token expires in more than this number of days,
182
+ the refresh is skipped. Default is 4.
183
+ verbose : `bool`, optional
184
+ If True, enables verbose output for debugging or logging.
185
+ Default is False.
186
+
187
+ Returns
188
+ -------
189
+ status: `dict`
190
+ A dictionary containing the refreshed token status
191
+ """
192
+ panda_url = os.environ.get("PANDA_URL")
193
+ panda_auth_vo = os.environ.get("PANDA_AUTH_VO")
194
+
195
+ if not panda_url or not panda_auth_vo:
196
+ raise PandaAuthError("Missing required environment variables: PANDA_URL or PANDA_AUTH_VO")
197
+
198
+ url_prefix = panda_url.split("/server", 1)[0]
199
+ auth_url = f"{url_prefix}/auth/{panda_auth_vo}_auth_config.json"
200
+ open_id = OpenIdConnect_Utils(auth_url, log_stream=_LOG, verbose=verbose)
201
+
202
+ token_file = open_id.get_token_path()
203
+ if not os.path.exists(token_file):
204
+ raise TokenNotFoundError("Cannot find token file. Use 'panda_auth reset' to obtain a new token.")
205
+
206
+ with open(token_file) as f:
207
+ data = json.load(f)
208
+ enc = data["id_token"].split(".")[1]
209
+ enc += "=" * (-len(enc) % 4)
210
+ dec = json.loads(base64.urlsafe_b64decode(enc.encode()))
211
+ exp_time = datetime.fromtimestamp(dec["exp"], tz=UTC)
212
+ delta = exp_time - datetime.now(UTC)
213
+ minutes = delta.total_seconds() / 60
214
+ print(f"Token will expire in {minutes} minutes.")
215
+ print(f"Token expiration time : {exp_time.strftime('%Y-%m-%d %H:%M:%S')} UTC")
216
+ if delta < timedelta(minutes=0):
217
+ raise TokenExpiredError("Token already expired. Cannot refresh.")
218
+ elif delta > timedelta(days=days):
219
+ raise TokenTooEarlyError(
220
+ f"Too early to refresh. More than {days} day(s) until expiration.\n"
221
+ f"Use '--days' option to adjust threshold, e.g.:\n"
222
+ f" panda_auth refresh --days 10"
223
+ )
224
+
225
+ refresh_token_string = data["refresh_token"]
226
+
227
+ s, auth_config = open_id.fetch_page(open_id.auth_config_url)
228
+ if not s:
229
+ raise AuthConfigError("Failed to get Auth configuration.")
230
+
231
+ s, endpoint_config = open_id.fetch_page(auth_config["oidc_config_url"])
232
+ if not s:
233
+ raise AuthConfigError("Failed to get endpoint configuration.")
234
+
235
+ s, o = open_id.refresh_token(
236
+ endpoint_config["token_endpoint"],
237
+ auth_config["client_id"],
238
+ auth_config["client_secret"],
239
+ refresh_token_string,
240
+ )
241
+
242
+ if not s:
243
+ raise TokenRefreshError("Failed to refresh token.")
244
+
245
+ status = panda_auth_status()
246
+ if status:
247
+ exp_time = datetime.fromtimestamp(status["exp"], tz=UTC)
248
+ print(f"{'New expiration time:':23} {exp_time.strftime('%Y-%m-%d %H:%M:%S')} UTC")
249
+ print("Success to refresh token")
250
+ return status
@@ -0,0 +1,34 @@
1
+ class PandaAuthError(Exception):
2
+ """Base class for authentication errors."""
3
+
4
+ pass
5
+
6
+
7
+ class TokenNotFoundError(PandaAuthError):
8
+ """Raised when the token file is missing."""
9
+
10
+ pass
11
+
12
+
13
+ class TokenExpiredError(PandaAuthError):
14
+ """Raised when the token has already expired."""
15
+
16
+ pass
17
+
18
+
19
+ class TokenTooEarlyError(PandaAuthError):
20
+ """Raised when attempting to refresh too early."""
21
+
22
+ pass
23
+
24
+
25
+ class AuthConfigError(PandaAuthError):
26
+ """Raised when fetching the auth or endpoint configuration fails."""
27
+
28
+ pass
29
+
30
+
31
+ class TokenRefreshError(PandaAuthError):
32
+ """Raised when token refresh fails."""
33
+
34
+ pass
@@ -636,9 +636,19 @@ def add_idds_work(config, generic_workflow, idds_workflow):
636
636
  order_id_map = {}
637
637
  job_name_to_order_id_map = {}
638
638
  order_id_map_file = None
639
+ max_payloads_per_panda_job_by_label = {}
639
640
  if enable_event_service:
640
641
  enable_event_service = enable_event_service.split(",")
641
- enable_event_service = [i.strip() for i in enable_event_service]
642
+ enable_event_service_tmp = []
643
+ for es_def in enable_event_service:
644
+ if ":" in es_def:
645
+ es_label, m_payloads = es_def.split(":")
646
+ else:
647
+ es_label, m_payloads = es_def, max_payloads_per_panda_job
648
+ es_label = es_label.strip()
649
+ enable_event_service_tmp.append(es_label)
650
+ max_payloads_per_panda_job_by_label[es_label] = int(m_payloads)
651
+ enable_event_service = enable_event_service_tmp
642
652
  if enable_job_name_map:
643
653
  _, order_id_map_filename = config.search(
644
654
  "orderIdMapFilename", opt={"default": PANDA_DEFAULT_ORDER_ID_MAP_FILE}
@@ -708,6 +718,9 @@ def add_idds_work(config, generic_workflow, idds_workflow):
708
718
  work_enable_event_service = False
709
719
  if enable_event_service and job_label in enable_event_service:
710
720
  work_enable_event_service = True
721
+ max_payloads_per_panda_job_current = max_payloads_per_panda_job_by_label.get(
722
+ job_label, max_payloads_per_panda_job
723
+ )
711
724
  work, files = _make_doma_work(
712
725
  config,
713
726
  generic_workflow,
@@ -718,11 +731,12 @@ def add_idds_work(config, generic_workflow, idds_workflow):
718
731
  enable_job_name_map=enable_job_name_map,
719
732
  order_id_map_files=order_id_map_files,
720
733
  es_label=job_label,
721
- max_payloads_per_panda_job=max_payloads_per_panda_job,
734
+ max_payloads_per_panda_job=max_payloads_per_panda_job_current,
722
735
  max_wms_job_wall_time=max_wms_job_wall_time,
723
736
  remote_filename=remote_archive_filename,
724
737
  qnode_map_filename=qnode_map_filename,
725
738
  )
739
+ work.dependency_tasks = []
726
740
  name_works[work.task_name] = work
727
741
  files_to_pre_stage.update(files)
728
742
  idds_workflow.add_work(work)
@@ -751,12 +765,15 @@ def add_idds_work(config, generic_workflow, idds_workflow):
751
765
  else:
752
766
  inputname = job_to_pseudo_filename[parent_job_name]
753
767
 
768
+ parent_task_name = job_to_task[parent_job_name]
754
769
  deps.append(
755
770
  {
756
- "task": job_to_task[parent_job_name],
771
+ "task": parent_task_name,
757
772
  "inputname": inputname,
758
773
  }
759
774
  )
775
+ if parent_task_name not in work.dependency_tasks:
776
+ work.dependency_tasks.append(parent_task_name)
760
777
  if not missing_deps:
761
778
  j_name = job_to_pseudo_filename[gwjob.name]
762
779
  f_name = f"{job_label}:orderIdMap_{order_id}" if enable_job_name_map else j_name
@@ -802,12 +819,15 @@ def add_idds_work(config, generic_workflow, idds_workflow):
802
819
  else:
803
820
  inputname = job_to_pseudo_filename[parent_job_name]
804
821
 
822
+ parent_task_name = job_to_task[parent_job_name]
805
823
  deps.append(
806
824
  {
807
- "task": job_to_task[parent_job_name],
825
+ "task": parent_task_name,
808
826
  "inputname": inputname,
809
827
  }
810
828
  )
829
+ if parent_task_name not in work.dependency_tasks:
830
+ work.dependency_tasks.append(parent_task_name)
811
831
 
812
832
  work.dependency_map.append(
813
833
  {
@@ -1,2 +1,2 @@
1
1
  __all__ = ["__version__"]
2
- __version__ = "29.2025.3900"
2
+ __version__ = "29.2025.4200"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lsst-ctrl-bps-panda
3
- Version: 29.2025.3900
3
+ Version: 29.2025.4200
4
4
  Summary: PanDA plugin for lsst-ctrl-bps.
5
5
  Author-email: Rubin Observatory Data Management <dm-admin@lists.lsst.org>
6
6
  License: BSD 3-Clause License
@@ -1,15 +1,16 @@
1
1
  lsst/ctrl/bps/panda/__init__.py,sha256=oApEOBGMuIv98uQvcDf6DLVvrbD6_8p9o0m2YPTenPY,1351
2
2
  lsst/ctrl/bps/panda/cmd_line_embedder.py,sha256=_gyqWQQxM8ZtZzQbWlOdgjfgrotrwV3sFvwWXxe-jfQ,6778
3
3
  lsst/ctrl/bps/panda/constants.py,sha256=hhV1CDHW9G-Z6z2wGaAc41EMlJ-yn2NN3A8psDyjTkw,1907
4
- lsst/ctrl/bps/panda/panda_auth_drivers.py,sha256=Ff0QsrTgQYbHDCK89_Gayu_2ZC1i3RRt-Dnnx10b8G4,2558
5
- lsst/ctrl/bps/panda/panda_auth_utils.py,sha256=wb-vlB9jvabVIHKlqukE1vILO_0Q9iixE3xXyROeN2s,5093
4
+ lsst/ctrl/bps/panda/panda_auth_drivers.py,sha256=LoD-tP990ELmVks3Vxv76jm4a8j3h3cNRTNX2XtFGHk,3163
5
+ lsst/ctrl/bps/panda/panda_auth_utils.py,sha256=kJnevhvjvUegbXfAyiVcoxinONsc_TJqfK4neTmcN5k,8544
6
+ lsst/ctrl/bps/panda/panda_exceptions.py,sha256=HcOKWMuG79c16Y9j7IJbp990k4DBQ54e7haY1Fsl6XQ,629
6
7
  lsst/ctrl/bps/panda/panda_service.py,sha256=5briN6eFJ9RolV2PM5Y_OguiB-7844Pu-FSBO0QyhM8,18935
7
- lsst/ctrl/bps/panda/utils.py,sha256=_kjt3FAi4-yePkWA9X9GFJpqXEVNF4kZwsgnQotO5Qc,39998
8
- lsst/ctrl/bps/panda/version.py,sha256=qaaoasFkyU8Kx3tKT5jPh3H-bKD5Y8pAmsL3Scaq2UU,55
8
+ lsst/ctrl/bps/panda/utils.py,sha256=H_dTQSHgvr3TTav97NsWM040T7DhG_57g_CIaGtIniA,41060
9
+ lsst/ctrl/bps/panda/version.py,sha256=loN_SewEappJtRr1bfMkUAloXYiZtM4w0O03FF5yeZQ,55
9
10
  lsst/ctrl/bps/panda/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
11
  lsst/ctrl/bps/panda/cli/panda_auth.py,sha256=i54ati_HoKSlyslQRBl7QpX1w5z8MjSfHHMpT43ZeXQ,2055
11
- lsst/ctrl/bps/panda/cli/cmd/__init__.py,sha256=WVcBiZ3z9rnG4FOsYMgp1QYGwlkM9n_edpMbGDBvCrs,1393
12
- lsst/ctrl/bps/panda/cli/cmd/panda_auth_commands.py,sha256=BdLaURmUs5QIqNSDsGXHBUuqGpY-DxmTgCW5ZnERNHk,2219
12
+ lsst/ctrl/bps/panda/cli/cmd/__init__.py,sha256=QNhn-2QmXHKVUgwmtuX7wnDv3qOUuU_30juuM_2AGaE,1413
13
+ lsst/ctrl/bps/panda/cli/cmd/panda_auth_commands.py,sha256=pdNpwBh9NEaMWEoMgzGC9wfqJbiEqoANuFSxtIW4Tas,2666
13
14
  lsst/ctrl/bps/panda/conf_example/example_panda_SLAC.yaml,sha256=2ybJOm0Lmz-0QunoR-gYLptTowpr1761OKQHoGjGpVA,5855
14
15
  lsst/ctrl/bps/panda/conf_example/pipelines_check_idf.yaml,sha256=9bfRF0y4SMhlkh6OK1VEOMOG59ytv25AZSfg-Ia8YwQ,877
15
16
  lsst/ctrl/bps/panda/conf_example/test_idf.yaml,sha256=P-FLBEmKZ2o0QiR6w8GZ9AAAOPIQwQ_Z0IE5ttMRox0,399
@@ -18,12 +19,12 @@ lsst/ctrl/bps/panda/conf_example/test_usdf.yaml,sha256=WIbXCJZDaG7zYUHt7U96MUjUs
18
19
  lsst/ctrl/bps/panda/edgenode/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
20
  lsst/ctrl/bps/panda/edgenode/build_cmd_line_decoder.py,sha256=CjB_ESDKLK67QPlcZHWoJzfaqgC733ih_iIQrwYkiUo,3067
20
21
  lsst/ctrl/bps/panda/edgenode/cmd_line_decoder.py,sha256=cqPeJLA7KfB7KnPTR-ykyEoHQ-_YE17h8_EfnqWA5eA,14616
21
- lsst_ctrl_bps_panda-29.2025.3900.dist-info/licenses/COPYRIGHT,sha256=5ATATZSyXxMNKoJuCJdATg4YNm56ubTwU_hDbShxIWw,116
22
- lsst_ctrl_bps_panda-29.2025.3900.dist-info/licenses/LICENSE,sha256=pRExkS03v0MQW-neNfIcaSL6aiAnoLxYgtZoFzQ6zkM,232
23
- lsst_ctrl_bps_panda-29.2025.3900.dist-info/licenses/bsd_license.txt,sha256=7MIcv8QRX9guUtqPSBDMPz2SnZ5swI-xZMqm_VDSfxY,1606
24
- lsst_ctrl_bps_panda-29.2025.3900.dist-info/licenses/gpl-v3.0.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
25
- lsst_ctrl_bps_panda-29.2025.3900.dist-info/METADATA,sha256=3WW1eUpcxCrfpSSsY3f6kVWxjOtJGHw9F8Szaj4W_C0,2375
26
- lsst_ctrl_bps_panda-29.2025.3900.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
27
- lsst_ctrl_bps_panda-29.2025.3900.dist-info/top_level.txt,sha256=eUWiOuVVm9wwTrnAgiJT6tp6HQHXxIhj2QSZ7NYZH80,5
28
- lsst_ctrl_bps_panda-29.2025.3900.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
29
- lsst_ctrl_bps_panda-29.2025.3900.dist-info/RECORD,,
22
+ lsst_ctrl_bps_panda-29.2025.4200.dist-info/licenses/COPYRIGHT,sha256=5ATATZSyXxMNKoJuCJdATg4YNm56ubTwU_hDbShxIWw,116
23
+ lsst_ctrl_bps_panda-29.2025.4200.dist-info/licenses/LICENSE,sha256=pRExkS03v0MQW-neNfIcaSL6aiAnoLxYgtZoFzQ6zkM,232
24
+ lsst_ctrl_bps_panda-29.2025.4200.dist-info/licenses/bsd_license.txt,sha256=7MIcv8QRX9guUtqPSBDMPz2SnZ5swI-xZMqm_VDSfxY,1606
25
+ lsst_ctrl_bps_panda-29.2025.4200.dist-info/licenses/gpl-v3.0.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
26
+ lsst_ctrl_bps_panda-29.2025.4200.dist-info/METADATA,sha256=gGr7iFwLXRyxH9nump0xAWij1YLm5ralbizqJDZ_keY,2375
27
+ lsst_ctrl_bps_panda-29.2025.4200.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
28
+ lsst_ctrl_bps_panda-29.2025.4200.dist-info/top_level.txt,sha256=eUWiOuVVm9wwTrnAgiJT6tp6HQHXxIhj2QSZ7NYZH80,5
29
+ lsst_ctrl_bps_panda-29.2025.4200.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
30
+ lsst_ctrl_bps_panda-29.2025.4200.dist-info/RECORD,,