-
Notifications
You must be signed in to change notification settings - Fork 180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(robot-server): Implement storage for "enable/disable error recovery" setting #16333
Changes from all commits
4a2b8e0
e989b9b
0f5c348
778546d
929a95f
55797be
6505218
8015844
8b33cad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# noqa: D100 | ||
|
||
|
||
import sqlalchemy | ||
|
||
from robot_server.persistence.tables import boolean_setting_table, BooleanSettingKey | ||
|
||
|
||
class ErrorRecoverySettingStore: | ||
"""Persistently stores settings related to error recovery.""" | ||
|
||
def __init__(self, sql_engine: sqlalchemy.engine.Engine) -> None: | ||
self._sql_engine = sql_engine | ||
|
||
def get_is_disabled(self) -> bool | None: | ||
"""Get the value of the "error recovery enabled" setting. | ||
|
||
`None` is the default, i.e. it's never been explicitly set one way or the other. | ||
""" | ||
with self._sql_engine.begin() as transaction: | ||
return transaction.execute( | ||
sqlalchemy.select(boolean_setting_table.c.value).where( | ||
boolean_setting_table.c.key | ||
== BooleanSettingKey.DISABLE_ERROR_RECOVERY | ||
) | ||
).scalar_one_or_none() | ||
|
||
def set_is_disabled(self, is_disabled: bool | None) -> None: | ||
"""Set the value of the "error recovery enabled" setting. | ||
|
||
`None` means revert to the default. | ||
""" | ||
with self._sql_engine.begin() as transaction: | ||
transaction.execute( | ||
sqlalchemy.delete(boolean_setting_table).where( | ||
boolean_setting_table.c.key | ||
== BooleanSettingKey.DISABLE_ERROR_RECOVERY | ||
) | ||
) | ||
if is_disabled is not None: | ||
transaction.execute( | ||
sqlalchemy.insert(boolean_setting_table).values( | ||
key=BooleanSettingKey.DISABLE_ERROR_RECOVERY, | ||
value=is_disabled, | ||
) | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,23 +30,13 @@ def _get_corrupt_persistence_dir() -> Path: | |
async def _assert_reset_was_successful( | ||
robot_client: RobotClient, persistence_directory: Path | ||
) -> None: | ||
# It should have no protocols. | ||
# We really want to check that the server's persistence directory has been wiped | ||
# clean, but testing that directly would rely on internal implementation details | ||
# of file layout and tend to be brittle. | ||
# As an approximation, just check that there are no protocols or runs left. | ||
assert (await robot_client.get_protocols()).json()["data"] == [] | ||
|
||
# It should have no runs. | ||
assert (await robot_client.get_runs()).json()["data"] == [] | ||
|
||
# There should be no files except for robot_server.db | ||
# and an empty protocols/ directory. | ||
all_files_and_directories = set(persistence_directory.glob("**/*")) | ||
expected_files_and_directories = { | ||
persistence_directory / "robot_server.db", | ||
persistence_directory / "7", | ||
persistence_directory / "7" / "protocols", | ||
persistence_directory / "7" / "robot_server.db", | ||
} | ||
assert all_files_and_directories == expected_files_and_directories | ||
|
||
Comment on lines
-33
to
-49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've removed the tests that check the filesystem to make sure that the protocol files, etc., have been removed. This test breaks every time we add a new persistence subdirectory or adjust the files inside it, and I don't personally find the test helpful these days. Is this a good idea? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No in general, maybe yes for this PR. We really want this testing to exist because we have messed up integrations before. What I'd recommend is to figure out a way to not make it rely on brittle implementation details, by maybe presenting a contract about what files go where. |
||
|
||
async def _wait_until_initialization_failed(robot_client: RobotClient) -> None: | ||
"""Wait until the server returns an "initialization failed" health status.""" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
"""Tests for error_recovery_setting_store.""" | ||
|
||
|
||
from robot_server.runs.error_recovery_setting_store import ErrorRecoverySettingStore | ||
|
||
import pytest | ||
import sqlalchemy | ||
|
||
|
||
@pytest.fixture | ||
def subject( | ||
sql_engine: sqlalchemy.engine.Engine, | ||
) -> ErrorRecoverySettingStore: | ||
"""Return a test subject.""" | ||
return ErrorRecoverySettingStore(sql_engine=sql_engine) | ||
|
||
|
||
def test_error_recovery_setting_store(subject: ErrorRecoverySettingStore) -> None: | ||
"""Test `ErrorRecoverySettingStore`.""" | ||
assert subject.get_is_disabled() is None | ||
|
||
subject.set_is_disabled(is_disabled=False) | ||
assert subject.get_is_disabled() is False | ||
|
||
subject.set_is_disabled(is_disabled=True) | ||
assert subject.get_is_disabled() is True | ||
|
||
subject.set_is_disabled(is_disabled=None) | ||
assert subject.get_is_disabled() is None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we're abandoning subdirectory 7 and replacing the 6->7 migration with 6->7.1. We've talked about this before, but this is the first time we're actually doing it. The downside is that we'll lose the past few weeks of run history on the in-office robots that are on the
edge
branch.Alternatives:
7/robot_server.db
in-place to add the new table. I didn't do this because I think it would set up a surprising precedent of modifying the database outside of a migration.edge
-only DB changes like this requiring migrations, because I want to keep them as not-annoying as possible so people aren't scared to make improvements onedge
.