-
Notifications
You must be signed in to change notification settings - Fork 3
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
Proposal for restore_label_and_restart
functionality
#87
Conversation
The approach looks bang on. I think the important details in resurrecting a label like this are:
This does all of the above. I'd suggest maybe we want to check that the label is We'll need to expose this in the API. The status codes/error messages might take some work to get right to communicate to callers the right details about what is happening, but the existing API functions should be a good starting point, if not already enough. One thing I can imagine being a little surprising here is that elsewhere we always merge def restore_label(app: App, label: LabelRef, *, replacement_metadata: Metadata) -> Metadata:
... I think the other functions here return the Do you think we should communicate that we're restarting the label in the state machine? The function here could be |
😄
Good shout, I agree
FWIW, the But I've no issue with making the param name more explicit.
Yeah I was just trying to be consistent with what the
I think that's a good idea, yeah. It makes it explicit that it's not going to open in the same state as before, or whatever |
restore_label
functionalityrestore_label_and_restart
functionality
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.
Thanks for putting this together.
Have we considered what the relationship between creating labels, reinstating labels and getting the lists of labels is? Would it make sense to have a way to enumerate the labels which are available for reinstating? (I'm not actually sure we do, just raising it as something to consider)
routemaster/server/endpoints.py
Outdated
@@ -157,11 +157,21 @@ def create_label(state_machine_name, label_name): | |||
try: | |||
initial_state_name = \ | |||
app.config.state_machines[state_machine_name].states[0].name | |||
metadata = state_machine.create_label(app, label, initial_metadata) | |||
if 'restore_label_and_restart' in data: |
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.
Have we considered moving this to a separate endpoint? Creating a label is a considerably different operation to reinstating a deleted one and has rather different failure modes.
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.
I did consider it, but in my mind creating and restoring aren't really that different. Either way you're saying "I want this label to be a thing that I can track the state of", it's just in one case you have to also say "and I know it was previously a thing that I deleted, it's fine"
Happy to make it a POST /{label}/restore
or PUT /{label}
with deleted=false
or something, if people disagree, though. I don't have hugely strong feelings.
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.
I think I agree with Peter that making this a separate API would be better...
- A failure means quite a different thing
- It's a destructive operation as we delete the original metadata (where both create and delete aren't really destructive)
- We still need the
restore_label_and_restart
, so we're still needing to track that the label previously existed in the client.
On the last point, I think that forcing the client to track this is probably desirable – if two run-throughs are entirely independent at the application level then the label IDs should probably be different, whereas in this case styleme needs to be kinda careful about this, so making restoration more explicit will probably be handy.
POST /{label}/restore
sounds good to me.
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.
POST /{label}/restore sounds good to me.
Cool beans - I'm split it out into that 🙂
It's a destructive operation as we delete the original metadata (where both create and delete aren't really destructive)
Just to be clear, the delete operation is destructive. It deletes the metadata. The restore operation is overwriting what is guaranteed to be an empty dict.
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.
Just to be clear, the delete operation is destructive. It deletes the metadata.
Oh! I had forgotten about that!
I think it's still good that we do a full replacement on restore anyway for safety, and I think it's still worth splitting out the API, but that's good to know. Sorry for the confusion!
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.
d559606 🙂
I agree, that sounds like a good idea. I think it might make sense to hold off on that feature though, just purely on the basis of:
|
routemaster/server/endpoints.py
Outdated
@@ -157,11 +157,21 @@ def create_label(state_machine_name, label_name): | |||
try: | |||
initial_state_name = \ | |||
app.config.state_machines[state_machine_name].states[0].name | |||
metadata = state_machine.create_label(app, label, initial_metadata) | |||
if 'restore_label_and_restart' in data: |
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.
I think I agree with Peter that making this a separate API would be better...
- A failure means quite a different thing
- It's a destructive operation as we delete the original metadata (where both create and delete aren't really destructive)
- We still need the
restore_label_and_restart
, so we're still needing to track that the label previously existed in the client.
On the last point, I think that forcing the client to track this is probably desirable – if two run-throughs are entirely independent at the application level then the label IDs should probably be different, whereas in this case styleme needs to be kinda careful about this, so making restoration more explicit will probably be handy.
POST /{label}/restore
sounds good to me.
@@ -326,6 +326,42 @@ def test_create_label_409_for_deleted_label(client, create_label): | |||
assert response.status_code == 409 | |||
|
|||
|
|||
def test_restore_label(app, client, create_label, delete_label): | |||
create_label('foo', 'test_machine', {}) |
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.
Just to test thoroughly, I'd put something in the metadata here so that we're testing that metadata isn't merged in the API, but actually replaced.
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.
routemaster/state_machine/api.py
Outdated
label: LabelRef, | ||
replacement_metadata: Metadata, | ||
) -> Metadata: | ||
"""Restores a label that was previously deleted.""" |
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.
I'd probably extend this with a bit about why we're not merging the metadata like in the other API functions, but rather replacing entirely.
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.
routemaster/state_machine/api.py
Outdated
|
||
# Record the label as having been restored and add its initial metadata | ||
if not row.deleted: | ||
raise AssertionError(f"Label {label} is not marked as deleted!") |
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.
This could maybe be LabelAlreadyExists
or a new exception. Same with the one below as well.
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.
Agreed that we don't need the listing of deleted labels for now. |
@danpalmer is there a release process for this repo? Or am I good to just merge the branch, and be done with it? |
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.
Thanks for putting this together. I think the bulk of the changes here are great.
I'm concerned that the new restore_label_and_restart
function isn't going to correctly restart the label on its journey through the state machine though. If that's intentional, I think we should add either a comment or something to the docstring about why that's the case.
Regarding the release process -- there are some docs in this repo at docs/releasing.md.
def _create_and_delete( | ||
name: str, | ||
state_machine_name: str, | ||
metadata: dict = {}, |
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.
Please could we type the metadata value more correctly? dict
isn't really a valid type annotation (we should prefer a generic Dict
in general) and in this case I think we have a Metadata
type somewhere?
@@ -167,6 +167,51 @@ def create_label(state_machine_name, label_name): | |||
abort(409, msg) | |||
|
|||
|
|||
@server.route( | |||
'/state-machines/<state_machine_name>/labels/<label_name>/restore_and_restart', # noqa |
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.
Please could we pin down this noqa
with a clarification of specifically which error we're silencing? Just as we would prefer not to blanket catch exceptions, we should prefer not to blanket silence errors.
In case you're not aware, the syntax is like: noqa: E123
.
initial state machine location. | ||
|
||
Returns: | ||
- 201 Created: if the label is successfully restored and restarted |
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.
- 201 Created: if the label is successfully restored and restarted | |
- 201 Created: if the label is successfully restored and restarted. |
assert label.deleted is False | ||
|
||
|
||
def test_restore_unknown_label(client): |
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.
Nice tests, would be good to maybe also cover the case of an unrecognised state machine? (I think we do that explicitly for the other endpoints; if we don't then this is less important).
content_type='application/json', | ||
) | ||
|
||
assert response.status_code == 404 |
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.
Maybe also assert that a label of the given name was not created? (This would help clarify the difference between this endpoint and create_label
)
old_state=None, | ||
new_state=state_machine.states[0].name, | ||
)) | ||
|
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.
In order to restart the label within the state machine, I think we also need to process_transitions
here, just like we do in create_label
. Is that not the case?
label = app.session.query(Label).one() | ||
assert label.metadata == label_metadata | ||
assert label.deleted is False | ||
|
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.
Maybe also assert the state that the label ends up in? (see also my question in state_machine/api.py
about whether we should process the initial state transition of the label)
Currently there is no API to allow restoring a label, once deleted.
This is PR adds in an API to allow restoring a label, if it's been deleted, restarting the label to the initial state machine state, and taking the metadata specified