Skip to content

Commit 8280c39

Browse files
committed
Add get_status method to PanDAService class.
1 parent 74a5378 commit 8280c39

3 files changed

Lines changed: 215 additions & 0 deletions

File tree

doc/changes/DM-50619.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added get_status method to PanDAService class for quick checking of run status.

python/lsst/ctrl/bps/panda/panda_service.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,44 @@ def run_submission_checks(self):
379379
if status != 0:
380380
raise RuntimeError(message)
381381

382+
def get_status(
383+
self,
384+
wms_workflow_id=None,
385+
hist=0,
386+
is_global=False,
387+
):
388+
# Docstring inherited from BaseWmsService.get_status.
389+
390+
idds_client = get_idds_client(self.config)
391+
ret = idds_client.get_requests(request_id=wms_workflow_id, with_detail=False)
392+
_LOG.debug("PanDA get workflow status returned = %s", str(ret))
393+
394+
request_status = ret[0]
395+
if request_status != 0:
396+
state = WmsStates.UNKNOWN
397+
message = f"Error to get workflow status: {ret} for id: {wms_workflow_id}"
398+
else:
399+
tasks = ret[1][1]
400+
if not tasks:
401+
message = f"No records found for workflow id '{wms_workflow_id}'. Hint: double check the id"
402+
state = WmsStates.UNKNOWN
403+
else:
404+
message = ""
405+
head = tasks[0]
406+
workflow_status = head["status"]["attributes"]["_name_"]
407+
if workflow_status in ["Finished"]:
408+
state = WmsStates.SUCCEEDED
409+
elif workflow_status in ["Failed", "Expired", "SubFinished"]:
410+
state = WmsStates.FAILED
411+
elif workflow_status in ["Cancelled"]:
412+
state = WmsStates.DELETED
413+
elif workflow_status in ["Suspended"]:
414+
state = WmsStates.HELD
415+
else:
416+
state = WmsStates.RUNNING
417+
418+
return state, message
419+
382420

383421
class PandaBpsWmsWorkflow(BaseWmsWorkflow):
384422
"""A single Panda based workflow.

tests/test_panda_service.py

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
# This file is part of ctrl_bps_panda.
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 ctrl_bps_panda panda_service module."""
29+
30+
import logging
31+
import unittest
32+
33+
from idds.common.constants import WorkStatus
34+
35+
from lsst.ctrl.bps import BpsConfig, WmsStates
36+
from lsst.ctrl.bps.panda import panda_service
37+
38+
_LOG = logging.getLogger(__name__)
39+
40+
41+
class MockClient:
42+
"""Mock idds client."""
43+
44+
def __init__(self):
45+
_LOG.debug("Called mock client init")
46+
47+
def get_requests(self, request_id, with_detail):
48+
_LOG.debug("Called mock client get_requests with %s", request_id)
49+
status = None
50+
match request_id:
51+
case "1000": # GetRequestsFailure
52+
requests = (1, "PanDA error message")
53+
case "1001":
54+
status = WorkStatus.Finished
55+
case "1002":
56+
status = WorkStatus.SubFinished
57+
case "1003":
58+
status = WorkStatus.Failed
59+
case "1004":
60+
status = WorkStatus.Cancelled
61+
case "1005":
62+
status = WorkStatus.Suspended
63+
case "1006":
64+
status = WorkStatus.Running
65+
case "1007":
66+
status = WorkStatus.Transforming
67+
case _: # Unknown ID
68+
requests = (0, [True, []])
69+
70+
if status:
71+
workflow_name = "FAKE_WORKFLOW_NAME_20250515T213417Z"
72+
requests = (
73+
0,
74+
[
75+
True,
76+
[
77+
{
78+
"name": workflow_name,
79+
"request_id": request_id,
80+
"status": {
81+
"attributes": {
82+
"_value_": status.value,
83+
"_name_": status.name,
84+
"_sort_order_": status.value,
85+
}
86+
},
87+
}
88+
],
89+
],
90+
)
91+
92+
return requests
93+
94+
95+
class TestPanDAService(unittest.TestCase):
96+
"""Test PanDAService class methods."""
97+
98+
def setUp(self):
99+
config = BpsConfig({}, wms_service_class_fqn="lsst.ctrl.bps.panda.PanDAService")
100+
self.service = panda_service.PanDAService(config)
101+
102+
@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
103+
def testGetStatusGetRequestsFailure(self, mock_get):
104+
mock_get.return_value = MockClient()
105+
status, message = self.service.get_status("1000")
106+
107+
self.assertEqual(status, WmsStates.UNKNOWN)
108+
self.assertEqual(message, "Error to get workflow status: (1, 'PanDA error message') for id: 1000")
109+
110+
@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
111+
def testGetStatusUnknownID(self, mock_get):
112+
mock_get.return_value = MockClient()
113+
status, message = self.service.get_status("9999")
114+
115+
self.assertEqual(status, WmsStates.UNKNOWN)
116+
self.assertEqual(message, "No records found for workflow id '9999'. Hint: double check the id")
117+
118+
@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
119+
def testGetStatusFinished(self, mock_get):
120+
mock_get.return_value = MockClient()
121+
status, message = self.service.get_status("1001")
122+
123+
self.assertEqual(status, WmsStates.SUCCEEDED)
124+
self.assertEqual(message, "")
125+
126+
@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
127+
def testGetStatusSubFinished(self, mock_get):
128+
mock_get.return_value = MockClient()
129+
status, message = self.service.get_status("1002")
130+
131+
self.assertEqual(status, WmsStates.FAILED)
132+
self.assertEqual(message, "")
133+
134+
@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
135+
def testGetStatusFailed(self, mock_get):
136+
mock_get.return_value = MockClient()
137+
status, message = self.service.get_status("1003")
138+
139+
self.assertEqual(status, WmsStates.FAILED)
140+
self.assertEqual(message, "")
141+
142+
@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
143+
def testGetStatusCancelled(self, mock_get):
144+
mock_get.return_value = MockClient()
145+
status, message = self.service.get_status("1004")
146+
147+
self.assertEqual(status, WmsStates.DELETED)
148+
self.assertEqual(message, "")
149+
150+
@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
151+
def testGetStatusSuspended(self, mock_get):
152+
mock_get.return_value = MockClient()
153+
status, message = self.service.get_status("1005")
154+
155+
self.assertEqual(status, WmsStates.HELD)
156+
self.assertEqual(message, "")
157+
158+
@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
159+
def testGetStatusRunning(self, mock_get):
160+
mock_get.return_value = MockClient()
161+
status, message = self.service.get_status("1006")
162+
163+
self.assertEqual(status, WmsStates.RUNNING)
164+
self.assertEqual(message, "")
165+
166+
@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
167+
def testGetStatusTransforming(self, mock_get):
168+
mock_get.return_value = MockClient()
169+
status, message = self.service.get_status("1007")
170+
171+
self.assertEqual(status, WmsStates.RUNNING)
172+
self.assertEqual(message, "")
173+
174+
175+
if __name__ == "__main__":
176+
unittest.main()

0 commit comments

Comments
 (0)