-
Notifications
You must be signed in to change notification settings - Fork 5
Add retry, error handling for agamemnon and blueapi requests #1690
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
base: main
Are you sure you want to change the base?
Changes from all commits
7c4eace
7b50b00
c758979
13b7f0c
b27cb72
4760f41
b0cabc4
1d5cfd8
73af0fa
b8a9613
d7b9f9b
d7231ab
bc282e7
80cad0a
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 |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| import json | ||
| import os | ||
| import re | ||
| import time | ||
| from collections.abc import Sequence | ||
| from enum import StrEnum | ||
| from os import path | ||
|
|
@@ -9,7 +10,9 @@ | |
| import requests | ||
| from dodal.utils import get_beamline_name | ||
| from pydantic import BaseModel | ||
| from requests import ConnectionError, HTTPError, Response, Timeout | ||
|
|
||
| from mx_bluesky.common.external_interaction.alerting import get_alerting_service | ||
| from mx_bluesky.common.parameters.components import ( | ||
| WithVisit, | ||
| ) | ||
|
|
@@ -25,6 +28,7 @@ | |
| PinTypeParam, | ||
| SingleSamplePinTypeParam, | ||
| ) | ||
| from mx_bluesky.hyperion.plan_runner import PlanError | ||
| from mx_bluesky.hyperion.external_interaction.config_server import ( | ||
| get_hyperion_feature_settings, | ||
| ) | ||
|
|
@@ -35,6 +39,9 @@ | |
| MULTIPIN_REGEX = rf"^{MULTIPIN_PREFIX}_(\d+)x(\d+(?:\.\d+)?)\+(\d+(?:\.\d+)?)$" | ||
| MX_GENERAL_ROOT_REGEX = r"^/dls/(?P<beamline>[^/]+)/data/[^/]*/(?P<visit>[^/]+)(?:/|$)" | ||
|
|
||
| MAX_TRIES = 3 | ||
| RETRY_INITIAL_DELAY_S = 2 | ||
|
|
||
|
|
||
| class _InstructionType(StrEnum): | ||
| WAIT = "wait" | ||
|
|
@@ -46,7 +53,10 @@ def create_parameters_from_agamemnon() -> Sequence[BaseModel]: | |
| mx-bluesky instructions. | ||
| Returns: | ||
| The generated sequence of mx-bluesky parameters, or empty list if | ||
| no instructions.""" | ||
| no instructions. | ||
| Raises: | ||
| PlanError: if the instructions could not be fetched from Agamemnon | ||
| """ | ||
| beamline_name = get_beamline_name("i03") | ||
| agamemnon_instruction = _get_next_instruction(beamline_name) | ||
| if agamemnon_instruction: | ||
|
|
@@ -75,11 +85,46 @@ def _instruction_and_data(agamemnon_instruction: dict) -> tuple[str, Any]: | |
|
|
||
|
|
||
| def _get_parameters_from_url(url: str) -> dict: | ||
| response = requests.get(url, headers={"Accept": "application/json"}) | ||
| response.raise_for_status() | ||
| tries, delay = MAX_TRIES, RETRY_INITIAL_DELAY_S | ||
|
Contributor
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. really - could these not have been done on separate lines -
Contributor
Author
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. python gives you tuple assignment, it's the python way. They are related values so it makes sense to me. |
||
| while tries > 0: | ||
| tries -= 1 | ||
| try: | ||
| response = requests.get(url, headers={"Accept": "application/json"}) | ||
| try: | ||
| response.raise_for_status() | ||
| break | ||
| except HTTPError as e: | ||
| if _is_server_error(response): | ||
| LOGGER.warning( | ||
| f"Agamemnon returned server error status {response.status_code}, retries left {tries}: {str(e)}" | ||
| ) | ||
| else: | ||
| msg = f"Agamemnon returned unexpected HTTP response status code {response.status_code}" | ||
| get_alerting_service().raise_error_alert(msg, {}) | ||
| raise PlanError(msg) from e | ||
| except ConnectionError as e: | ||
| LOGGER.warning( | ||
| f"Connection error attempting to connect to agamemnon, retries left {tries}", | ||
| exc_info=e, | ||
| ) | ||
| except Timeout: | ||
| LOGGER.warning( | ||
| f"Timed out attempting to connect to agamemnon, retries left {tries}" | ||
| ) | ||
| if tries: | ||
|
CoePaul marked this conversation as resolved.
|
||
| time.sleep(delay) # noqa | ||
| delay *= 2 | ||
|
CoePaul marked this conversation as resolved.
|
||
| else: | ||
| msg = f"Unable to fetch instruction from agamemnon after {MAX_TRIES} attempts, ending UDC." | ||
|
rtuck99 marked this conversation as resolved.
|
||
| get_alerting_service().raise_error_alert(msg, {}) | ||
| raise PlanError(msg) | ||
| return json.loads(response.content) | ||
|
|
||
|
|
||
| def _is_server_error(response: Response): | ||
| return 500 <= response.status_code < 600 | ||
|
|
||
|
|
||
| def _get_pin_type_from_agamemnon_collect_parameters( | ||
| collect_parameters: dict, | ||
| ) -> PinTypeParam: | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
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 sentence would be shorter in the form
"Alert beamline staff and EHC controllers ... support."
( We can see from the method name it achieves this by raising something )
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 want to retain the raise an alert phrasing - it's something that might have more meaning in future - various alerting paradigms such as AlertManager have the concept of alerts as things that can be raised and continue alerting until a subsequent action is taken.
Just using "alert" as a verb is weaker and could just mean sending a message, as opposed to something more substantial.
In the long run we will probably plug in to such systems via the alerting backend.