Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/changes/DM-50619.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added get_status method to PanDAService class for quick checking of run status.
41 changes: 41 additions & 0 deletions python/lsst/ctrl/bps/panda/panda_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,47 @@ def run_submission_checks(self):
if status != 0:
raise RuntimeError(message)

def get_status(
self,
wms_workflow_id=None,
hist=0,
is_global=False,
):
# Docstring inherited from BaseWmsService.get_status.

idds_client = get_idds_client(self.config)
ret = idds_client.get_requests(request_id=wms_workflow_id, with_detail=False)
_LOG.debug("PanDA get workflow status returned = %s", str(ret))

request_status = ret[0]
if request_status != 0:
state = WmsStates.UNKNOWN
message = f"Error getting workflow status for id {wms_workflow_id}: ret = {ret}"
else:
tasks = ret[1][1]
if not tasks:
state = WmsStates.UNKNOWN
message = f"No records found for workflow id '{wms_workflow_id}'. Hint: double check the id"
elif not isinstance(tasks[0], dict):
state = WmsStates.UNKNOWN
message = f"Error getting workflow status for id {wms_workflow_id}: ret = {ret}"
else:
message = ""
head = tasks[0]
workflow_status = head["status"]["attributes"]["_name_"]
if workflow_status in ["Finished"]:
state = WmsStates.SUCCEEDED
elif workflow_status in ["Failed", "Expired", "SubFinished"]:
state = WmsStates.FAILED
elif workflow_status in ["Cancelled"]:
state = WmsStates.DELETED
elif workflow_status in ["Suspended"]:
state = WmsStates.HELD
else:
state = WmsStates.RUNNING
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a workflow is running, i.e. some tasks have not finished, its status in iDDS is Transforming. The status is mapped to "RUNNING" correctly here in this "else" block.


return state, message


class PandaBpsWmsWorkflow(BaseWmsWorkflow):
"""A single Panda based workflow.
Expand Down
194 changes: 194 additions & 0 deletions tests/test_panda_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
# This file is part of ctrl_bps_panda.
#
# Developed for the LSST Data Management System.
# This product includes software developed by the LSST Project
# (https://www.lsst.org).
# See the COPYRIGHT file at the top-level directory of this distribution
# for details of code ownership.
#
# This software is dual licensed under the GNU General Public License and also
# under a 3-clause BSD license. Recipients may choose which of these licenses
# to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
# respectively. If you choose the GPL option then the following text applies
# (but note that there is still no warranty even if you opt for BSD instead):
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

"""Unit tests for ctrl_bps_panda panda_service module."""

import logging
import unittest

from idds.common.constants import WorkStatus

from lsst.ctrl.bps import BpsConfig, WmsStates
from lsst.ctrl.bps.panda import panda_service

_LOG = logging.getLogger(__name__)


class MockClient:
"""Mock idds client."""

def __init__(self):
_LOG.debug("Called mock client init")

def get_requests(self, request_id, with_detail):
_LOG.debug("Called mock client get_requests with %s", request_id)
status = None
match request_id:
case "1000": # GetRequestsFailure
requests = (1, "PanDA error message")
case "1001":
status = WorkStatus.Finished
case "1002":
status = WorkStatus.SubFinished
case "1003":
status = WorkStatus.Failed
case "1004":
status = WorkStatus.Cancelled
case "1005":
status = WorkStatus.Suspended
case "1006":
status = WorkStatus.Running
case "1007":
status = WorkStatus.Transforming
case "1008":
requests = (0, [True, [False, "An unknown IDDS exception occurred."]])
case _: # Unknown ID
requests = (0, [True, []])

if status:
workflow_name = "FAKE_WORKFLOW_NAME_20250515T213417Z"
requests = (
0,
[
True,
[
{
"name": workflow_name,
"request_id": request_id,
"status": {
"attributes": {
"_value_": status.value,
"_name_": status.name,
"_sort_order_": status.value,
}
},
}
],
],
)

return requests


class TestPanDAService(unittest.TestCase):
"""Test PanDAService class methods."""

def setUp(self):
config = BpsConfig({}, wms_service_class_fqn="lsst.ctrl.bps.panda.PanDAService")
self.service = panda_service.PanDAService(config)

@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
def testGetStatusGetRequestsFailure(self, mock_get):
mock_get.return_value = MockClient()
status, message = self.service.get_status("1000")

self.assertEqual(status, WmsStates.UNKNOWN)
self.assertEqual(
message, "Error getting workflow status for id 1000: ret = (1, 'PanDA error message')"
)

@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
def testGetStatusUnknownID(self, mock_get):
mock_get.return_value = MockClient()
status, message = self.service.get_status("9999")

self.assertEqual(status, WmsStates.UNKNOWN)
self.assertEqual(message, "No records found for workflow id '9999'. Hint: double check the id")

@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
def testGetStatusFinished(self, mock_get):
mock_get.return_value = MockClient()
status, message = self.service.get_status("1001")

self.assertEqual(status, WmsStates.SUCCEEDED)
self.assertEqual(message, "")

@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
def testGetStatusSubFinished(self, mock_get):
mock_get.return_value = MockClient()
status, message = self.service.get_status("1002")

self.assertEqual(status, WmsStates.FAILED)
self.assertEqual(message, "")

@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
def testGetStatusFailed(self, mock_get):
mock_get.return_value = MockClient()
status, message = self.service.get_status("1003")

self.assertEqual(status, WmsStates.FAILED)
self.assertEqual(message, "")

@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
def testGetStatusCancelled(self, mock_get):
mock_get.return_value = MockClient()
status, message = self.service.get_status("1004")

self.assertEqual(status, WmsStates.DELETED)
self.assertEqual(message, "")

@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
def testGetStatusSuspended(self, mock_get):
mock_get.return_value = MockClient()
status, message = self.service.get_status("1005")

self.assertEqual(status, WmsStates.HELD)
self.assertEqual(message, "")

@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
def testGetStatusRunning(self, mock_get):
mock_get.return_value = MockClient()
status, message = self.service.get_status("1006")

self.assertEqual(status, WmsStates.RUNNING)
self.assertEqual(message, "")

@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
def testGetStatusTransforming(self, mock_get):
mock_get.return_value = MockClient()
status, message = self.service.get_status("1007")

self.assertEqual(status, WmsStates.RUNNING)
self.assertEqual(message, "")

@unittest.mock.patch("lsst.ctrl.bps.panda.panda_service.get_idds_client")
def testGetStatusUnknownIDDSException(self, mock_get):
# Test example unknown IDDS exception similar to what occurs
# if give path to ctrl_bps_panda's get_status.
mock_get.return_value = MockClient()
status, message = self.service.get_status("1008")

self.assertEqual(status, WmsStates.UNKNOWN)
self.assertEqual(
message,
"Error getting workflow status for id 1008: ret = "
"(0, [True, [False, 'An unknown IDDS exception occurred.']])",
)


if __name__ == "__main__":
unittest.main()

Check warning on line 194 in tests/test_panda_service.py

View check run for this annotation

Codecov / codecov/patch

tests/test_panda_service.py#L194

Added line #L194 was not covered by tests
Loading