Skip to content

Commit 17389a3

Browse files
Merge pull request #89 from lsst/tickets/DM-50619
DM-50619: Add get_status method to PanDAService class.
2 parents 74a5378 + bbde2b8 commit 17389a3

3 files changed

Lines changed: 236 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: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,47 @@ 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 getting workflow status for id {wms_workflow_id}: ret = {ret}"
398+
else:
399+
tasks = ret[1][1]
400+
if not tasks:
401+
state = WmsStates.UNKNOWN
402+
message = f"No records found for workflow id '{wms_workflow_id}'. Hint: double check the id"
403+
elif not isinstance(tasks[0], dict):
404+
state = WmsStates.UNKNOWN
405+
message = f"Error getting workflow status for id {wms_workflow_id}: ret = {ret}"
406+
else:
407+
message = ""
408+
head = tasks[0]
409+
workflow_status = head["status"]["attributes"]["_name_"]
410+
if workflow_status in ["Finished"]:
411+
state = WmsStates.SUCCEEDED
412+
elif workflow_status in ["Failed", "Expired", "SubFinished"]:
413+
state = WmsStates.FAILED
414+
elif workflow_status in ["Cancelled"]:
415+
state = WmsStates.DELETED
416+
elif workflow_status in ["Suspended"]:
417+
state = WmsStates.HELD
418+
else:
419+
state = WmsStates.RUNNING
420+
421+
return state, message
422+
382423

383424
class PandaBpsWmsWorkflow(BaseWmsWorkflow):
384425
"""A single Panda based workflow.

tests/test_panda_service.py

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
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 "1008":
68+
requests = (0, [True, [False, "An unknown IDDS exception occurred."]])
69+
case _: # Unknown ID
70+
requests = (0, [True, []])
71+
72+
if status:
73+
workflow_name = "FAKE_WORKFLOW_NAME_20250515T213417Z"
74+
requests = (
75+
0,
76+
[
77+
True,
78+
[
79+
{
80+
"name": workflow_name,
81+
"request_id": request_id,
82+
"status": {
83+
"attributes": {
84+
"_value_": status.value,
85+
"_name_": status.name,
86+
"_sort_order_": status.value,
87+
}
88+
},
89+
}
90+
],
91+
],
92+
)
93+
94+
return requests
95+
96+
97+
class TestPanDAService(unittest.TestCase):
98+
"""Test PanDAService class methods."""
99+
100+
def setUp(self):
101+
config = BpsConfig({}, wms_service_class_fqn="lsst.ctrl.bps.panda.PanDAService")
102+
self.service = panda_service.PanDAService(config)
103+
104+
@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
105+
def testGetStatusGetRequestsFailure(self, mock_get):
106+
mock_get.return_value = MockClient()
107+
status, message = self.service.get_status("1000")
108+
109+
self.assertEqual(status, WmsStates.UNKNOWN)
110+
self.assertEqual(
111+
message, "Error getting workflow status for id 1000: ret = (1, 'PanDA error message')"
112+
)
113+
114+
@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
115+
def testGetStatusUnknownID(self, mock_get):
116+
mock_get.return_value = MockClient()
117+
status, message = self.service.get_status("9999")
118+
119+
self.assertEqual(status, WmsStates.UNKNOWN)
120+
self.assertEqual(message, "No records found for workflow id '9999'. Hint: double check the id")
121+
122+
@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
123+
def testGetStatusFinished(self, mock_get):
124+
mock_get.return_value = MockClient()
125+
status, message = self.service.get_status("1001")
126+
127+
self.assertEqual(status, WmsStates.SUCCEEDED)
128+
self.assertEqual(message, "")
129+
130+
@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
131+
def testGetStatusSubFinished(self, mock_get):
132+
mock_get.return_value = MockClient()
133+
status, message = self.service.get_status("1002")
134+
135+
self.assertEqual(status, WmsStates.FAILED)
136+
self.assertEqual(message, "")
137+
138+
@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
139+
def testGetStatusFailed(self, mock_get):
140+
mock_get.return_value = MockClient()
141+
status, message = self.service.get_status("1003")
142+
143+
self.assertEqual(status, WmsStates.FAILED)
144+
self.assertEqual(message, "")
145+
146+
@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
147+
def testGetStatusCancelled(self, mock_get):
148+
mock_get.return_value = MockClient()
149+
status, message = self.service.get_status("1004")
150+
151+
self.assertEqual(status, WmsStates.DELETED)
152+
self.assertEqual(message, "")
153+
154+
@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
155+
def testGetStatusSuspended(self, mock_get):
156+
mock_get.return_value = MockClient()
157+
status, message = self.service.get_status("1005")
158+
159+
self.assertEqual(status, WmsStates.HELD)
160+
self.assertEqual(message, "")
161+
162+
@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
163+
def testGetStatusRunning(self, mock_get):
164+
mock_get.return_value = MockClient()
165+
status, message = self.service.get_status("1006")
166+
167+
self.assertEqual(status, WmsStates.RUNNING)
168+
self.assertEqual(message, "")
169+
170+
@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
171+
def testGetStatusTransforming(self, mock_get):
172+
mock_get.return_value = MockClient()
173+
status, message = self.service.get_status("1007")
174+
175+
self.assertEqual(status, WmsStates.RUNNING)
176+
self.assertEqual(message, "")
177+
178+
@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
179+
def testGetStatusUnknownIDDSException(self, mock_get):
180+
# Test example unknown IDDS exception similar to what occurs
181+
# if give path to ctrl_bps_panda's get_status.
182+
mock_get.return_value = MockClient()
183+
status, message = self.service.get_status("1008")
184+
185+
self.assertEqual(status, WmsStates.UNKNOWN)
186+
self.assertEqual(
187+
message,
188+
"Error getting workflow status for id 1008: ret = "
189+
"(0, [True, [False, 'An unknown IDDS exception occurred.']])",
190+
)
191+
192+
193+
if __name__ == "__main__":
194+
unittest.main()

0 commit comments

Comments
 (0)