Skip to content

Commit

Permalink
feat(robot-server): exclude quick-transfer protocol runs when auto de…
Browse files Browse the repository at this point in the history
…leting runs.
  • Loading branch information
vegano1 committed Jul 7, 2024
1 parent 16f00c6 commit 927931a
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 19 deletions.
4 changes: 4 additions & 0 deletions robot-server/robot_server/runs/dependencies.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Run router dependency-injection wire-up."""
from fastapi import Depends, status
from robot_server.protocols.dependencies import get_protocol_store
from robot_server.protocols.protocol_store import ProtocolStore
from sqlalchemy.engine import Engine as SQLEngine

from opentrons_shared_data.robot.dev_types import RobotType
Expand Down Expand Up @@ -153,9 +155,11 @@ async def get_run_data_manager(

async def get_run_auto_deleter(
run_store: RunStore = Depends(get_run_store),
protocol_store: ProtocolStore = Depends(get_protocol_store),
) -> RunAutoDeleter:
"""Get an `AutoDeleter` to delete old runs."""
return RunAutoDeleter(
run_store=run_store,
protocol_store=protocol_store,
deletion_planner=RunDeletionPlanner(maximum_runs=get_settings().maximum_runs),
)
9 changes: 8 additions & 1 deletion robot-server/robot_server/runs/router/base_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from opentrons_shared_data.errors import ErrorCodes

from robot_server.errors.error_responses import ErrorDetails, ErrorBody
from robot_server.protocols.protocol_models import ProtocolKind
from robot_server.service.dependencies import get_current_time, get_unique_id
from robot_server.robot.control.dependencies import require_estop_in_good_state

Expand Down Expand Up @@ -180,7 +181,13 @@ async def create_run(
# TODO(mc, 2022-05-13): move inside `RunDataManager` or return data
# to pass to `RunDataManager.create`. Right now, runs may be deleted
# even if a new create is unable to succeed due to a conflict
run_auto_deleter.make_room_for_new_run()
kind = None
if (
protocol_resource
and protocol_resource.protocol_kind == ProtocolKind.QUICK_TRANSFER
):
kind = ProtocolKind.QUICK_TRANSFER
run_auto_deleter.make_room_for_new_run(exclude_kind=kind)

try:
run_data = await run_data_manager.create(
Expand Down
21 changes: 19 additions & 2 deletions robot-server/robot_server/runs/run_auto_deleter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@


from logging import getLogger
from typing import Optional

from robot_server.deletion_planner import RunDeletionPlanner
from robot_server.protocols.protocol_models import ProtocolKind
from robot_server.protocols.protocol_store import ProtocolStore
from .run_store import RunStore


Expand All @@ -14,13 +17,27 @@ class RunAutoDeleter: # noqa: D101
def __init__(
self,
run_store: RunStore,
protocol_store: ProtocolStore,
deletion_planner: RunDeletionPlanner,
) -> None:
self._run_store = run_store
self._protocol_store = protocol_store
self._deletion_planner = deletion_planner

def make_room_for_new_run(self) -> None: # noqa: D102
run_ids = [r.run_id for r in self._run_store.get_all()]
def make_room_for_new_run( # noqa: D102
self, exclude_kind: Optional[ProtocolKind] = None
) -> None:
_log.warning(f"ALL_PROTOCOLS: {self._protocol_store.get_all()}")
excluded_protocols = [
p.protocol_id
for p in self._protocol_store.get_all()
if p.protocol_kind == exclude_kind
]
run_ids = [
r.run_id
for r in self._run_store.get_all()
if r.protocol_id not in excluded_protocols
]

run_ids_to_delete = self._deletion_planner.plan_for_new_run(
existing_runs=run_ids,
Expand Down
102 changes: 86 additions & 16 deletions robot-server/tests/runs/test_run_auto_deleter.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,21 @@
import pytest
from decoy import Decoy

from opentrons.protocol_reader import ProtocolSource

from robot_server.deletion_planner import RunDeletionPlanner
from robot_server.protocols.protocol_models import ProtocolKind
from robot_server.protocols.protocol_store import ProtocolResource, ProtocolStore
from robot_server.runs.run_auto_deleter import RunAutoDeleter
from robot_server.runs.run_store import RunStore, RunResource, BadRunResource


def _make_dummy_run_resource(run_id: str) -> RunResource:
def _make_dummy_run_resource(run_id: str, protocol_id: str) -> RunResource:
"""Return a RunResource with the given ID."""
return RunResource(
ok=True,
run_id=run_id,
protocol_id=None,
protocol_id=protocol_id,
created_at=datetime.min,
actions=[],
)
Expand All @@ -27,35 +31,101 @@ def _make_dummy_run_resource(run_id: str) -> RunResource:
def test_make_room_for_new_run(decoy: Decoy, caplog: pytest.LogCaptureFixture) -> None:
"""It should get a deletion plan and enact it on the store."""
mock_run_store = decoy.mock(cls=RunStore)
mock_deletion_planner = decoy.mock(cls=RunDeletionPlanner)
mock_protocol_store = decoy.mock(cls=ProtocolStore)
mock_protocol_source = decoy.mock(cls=ProtocolSource)

subject = RunAutoDeleter(
run_store=mock_run_store,
deletion_planner=mock_deletion_planner,
protocol_store=mock_protocol_store,
deletion_planner=RunDeletionPlanner(1),
)

run_resources: List[Union[RunResource, BadRunResource]] = [
_make_dummy_run_resource("run-id-1"),
_make_dummy_run_resource("run-id-2"),
_make_dummy_run_resource("run-id-3"),
protocol_resources: List[ProtocolResource] = [
ProtocolResource(
protocol_id=f"protocol-id-{idx}",
created_at=datetime.min,
source=mock_protocol_source,
protocol_key=None,
protocol_kind=ProtocolKind.STANDARD,
)
for idx in range(1, 4)
]

deletion_plan = set(["run-id-4", "run-id-5"])
run_resources: List[Union[RunResource, BadRunResource]] = [
_make_dummy_run_resource("run-id-1", "protocol-id-1"),
_make_dummy_run_resource("run-id-2", "protocol-id-2"),
_make_dummy_run_resource("run-id-3", "protocol-id-3"),
]

decoy.when(mock_protocol_store.get_all()).then_return(protocol_resources)
decoy.when(mock_run_store.get_all()).then_return(run_resources)
decoy.when(
mock_deletion_planner.plan_for_new_run(
existing_runs=["run-id-1", "run-id-2", "run-id-3"]
)
).then_return(deletion_plan)

# Run the subject, capturing log messages at least as severe as INFO.
with caplog.at_level(logging.INFO):
subject.make_room_for_new_run()

decoy.verify(mock_run_store.remove(run_id="run-id-1"))
decoy.verify(mock_run_store.remove(run_id="run-id-2"))
decoy.verify(mock_run_store.remove(run_id="run-id-3"))

# It should log the runs that it deleted.
assert "run-id-1" in caplog.text
assert "run-id-2" in caplog.text
assert "run-id-3" in caplog.text


def test_ignore_quick_transfer_protocol_runs(
decoy: Decoy,
caplog: pytest.LogCaptureFixture,
) -> None:
"""It should delete all runs but the specified protocol kind."""
mock_run_store = decoy.mock(cls=RunStore)
mock_protocol_store = decoy.mock(cls=ProtocolStore)
mock_protocol_source = decoy.mock(cls=ProtocolSource)

subject = RunAutoDeleter(
run_store=mock_run_store,
protocol_store=mock_protocol_store,
deletion_planner=RunDeletionPlanner(2),
)

protocol_resources: List[ProtocolResource] = [
ProtocolResource(
protocol_id=f"protocol-id-{idx}",
created_at=datetime.min,
source=mock_protocol_source,
protocol_key=None,
protocol_kind=ProtocolKind.STANDARD
if idx not in [2, 5]
else ProtocolKind.QUICK_TRANSFER,
)
for idx in range(1, 6)
]

run_resources: List[Union[RunResource, BadRunResource]] = [
_make_dummy_run_resource("run-id-1", "protocol-id-1"),
_make_dummy_run_resource("run-id-2", "protocol-id-2"),
_make_dummy_run_resource("run-id-3", "protocol-id-3"),
_make_dummy_run_resource("run-id-4", "protocol-id-4"),
_make_dummy_run_resource("run-id-5", "protocol-id-5"),
_make_dummy_run_resource("run-id-6", "protocol-id-6"),
]

decoy.when(mock_protocol_store.get_all()).then_return(protocol_resources)
decoy.when(mock_run_store.get_all()).then_return(run_resources)

# Run the subject, capturing log messages at least as severe as INFO.
with caplog.at_level(logging.INFO):
subject.make_room_for_new_run(exclude_kind=ProtocolKind.QUICK_TRANSFER)

decoy.verify(mock_run_store.remove(run_id="run-id-1"))
decoy.verify(mock_run_store.remove(run_id="run-id-3"))
decoy.verify(mock_run_store.remove(run_id="run-id-4"))
decoy.verify(mock_run_store.remove(run_id="run-id-5"))

# It should log the runs that it deleted.
assert "run-id-1" in caplog.text
assert "run-id-3" in caplog.text
assert "run-id-4" in caplog.text
assert "run-id-5" in caplog.text
# Make sure we did not delete quick-transfer protocols
assert "run-id-2" not in caplog.text
assert "run-id-5" not in caplog.text

0 comments on commit 927931a

Please sign in to comment.