Skip to content
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(launcher): add new API endpoint /v1/launcher/time-limit and update LauncherDialog #2012

Merged
merged 9 commits into from
May 23, 2024
Merged
25 changes: 24 additions & 1 deletion antarest/core/config.py
Original file line number Diff line number Diff line change
@@ -308,7 +308,7 @@ def _autodetect_nb_cores(cls) -> Dict[str, int]:

class InvalidConfigurationError(Exception):
"""
Exception raised when an attempt is made to retrieve the number of cores
Exception raised when an attempt is made to retrieve a property
of a launcher that doesn't exist in the configuration.
"""

@@ -371,6 +371,29 @@ def get_nb_cores(self, launcher: str) -> "NbCoresConfig":
raise InvalidConfigurationError(launcher)
return launcher_config.nb_cores

def get_time_limit(self, launcher: str) -> int:
"""
Retrieve the time limit for a job of the given launcher: "local" or "slurm".
If "default" is specified, retrieve the configuration of the default launcher.

Args:
launcher: type of launcher "local", "slurm" or "default".

Returns:
Time limit for a job of the given launcher.

Raises:
InvalidConfigurationError: Exception raised when an attempt is made to retrieve
a property of a launcher that doesn't exist in the configuration.
"""
config_map = {"local": self.local, "slurm": self.slurm}
config_map["default"] = config_map[self.default]
launcher_config = config_map.get(launcher)
if launcher_config is None:
raise InvalidConfigurationError(launcher)
# Default to 172800 (48 hours) for local launcher.
return getattr(launcher_config, "default_time_limit", 172800)


@dataclass(frozen=True)
class LoggingConfig:
84 changes: 46 additions & 38 deletions antarest/launcher/web.py
Original file line number Diff line number Diff line change
@@ -41,6 +41,25 @@ def __init__(self, solver: str) -> None:
)


LauncherQuery = Query(
"default",
examples={
"Default launcher": {
"description": "Default solver (auto-detected)",
"value": "default",
},
"SLURM launcher": {
"description": "SLURM solver configuration",
"value": "slurm",
},
"Local launcher": {
"description": "Local solver configuration",
"value": "local",
},
},
)


def create_launcher_api(service: LauncherService, config: Config) -> APIRouter:
bp = APIRouter(prefix="/v1/launcher")

@@ -214,25 +233,7 @@ def get_load() -> LauncherLoadDTO:
summary="Get list of supported solver versions",
response_model=List[str],
)
def get_solver_versions(
solver: str = Query(
"default",
examples={
"Default solver": {
"description": "Get the solver versions of the default configuration",
"value": "default",
},
"SLURM solver": {
"description": "Get the solver versions of the SLURM server if available",
"value": "slurm",
},
"Local solver": {
"description": "Get the solver versions of the Local server if available",
"value": "local",
},
},
),
) -> List[str]:
def get_solver_versions(solver: str = LauncherQuery) -> List[str]:
"""
Get list of supported solver versions defined in the configuration.

@@ -251,25 +252,7 @@ def get_solver_versions(
summary="Retrieving Min, Default, and Max Core Count",
response_model=Dict[str, int],
)
def get_nb_cores(
launcher: str = Query(
"default",
examples={
"Default launcher": {
"description": "Min, Default, and Max Core Count",
"value": "default",
},
"SLURM launcher": {
"description": "Min, Default, and Max Core Count",
"value": "slurm",
},
"Local launcher": {
"description": "Min, Default, and Max Core Count",
"value": "local",
},
},
),
) -> Dict[str, int]:
def get_nb_cores(launcher: str = LauncherQuery) -> Dict[str, int]:
"""
Retrieve the numer of cores of the launcher.

@@ -288,4 +271,29 @@ def get_nb_cores(
except InvalidConfigurationError:
raise UnknownSolverConfig(launcher)

# noinspection SpellCheckingInspection
@bp.get(
"/time-limit",
tags=[APITag.launcher],
summary="Retrieve the time limit for a job (in seconds)",
)
def get_time_limit(launcher: str = LauncherQuery) -> int:
"""
Retrieve the time limit for a job (in seconds) of the given launcher: "local" or "slurm".

If a jobs exceed this time limit, SLURM kills the job and it is considered failed.

Args:
- `launcher`: name of the configuration to read: "slurm" or "local".
If "default" is specified, retrieve the configuration of the default launcher.

Returns:
- time limit in seconds
"""
logger.info(f"Fetching the time limit for the '{launcher}' configuration")
try:
return service.config.launcher.get_time_limit(launcher)
except InvalidConfigurationError:
raise UnknownSolverConfig(launcher)

return bp
54 changes: 54 additions & 0 deletions tests/integration/launcher_blueprint/test_launcher_local.py
Original file line number Diff line number Diff line change
@@ -68,3 +68,57 @@ def test_get_launcher_nb_cores(
"description": "Unknown solver configuration: 'unknown'",
"exception": "UnknownSolverConfig",
}

def test_get_time_limit(
self,
client: TestClient,
user_access_token: str,
) -> None:
nb_cores_expected = 3600
res = client.get(
"/v1/launcher/time-limit",
headers={"Authorization": f"Bearer {user_access_token}"},
)
res.raise_for_status()
actual = res.json()
assert actual == nb_cores_expected

res = client.get(
"/v1/launcher/time-limit?launcher=default",
headers={"Authorization": f"Bearer {user_access_token}"},
)
res.raise_for_status()
actual = res.json()
assert actual == nb_cores_expected

res = client.get(
"/v1/launcher/time-limit?launcher=local",
headers={"Authorization": f"Bearer {user_access_token}"},
)
res.raise_for_status()
actual = res.json()
assert actual == nb_cores_expected

# Check that the endpoint raise an exception when the "slurm" launcher is requested.
res = client.get(
"/v1/launcher/time-limit?launcher=slurm",
headers={"Authorization": f"Bearer {user_access_token}"},
)
assert res.status_code == http.HTTPStatus.UNPROCESSABLE_ENTITY, res.json()
actual = res.json()
assert actual == {
"description": "Unknown solver configuration: 'slurm'",
"exception": "UnknownSolverConfig",
}

# Check that the endpoint raise an exception when an unknown launcher is requested.
res = client.get(
"/v1/launcher/time-limit?launcher=unknown",
headers={"Authorization": f"Bearer {user_access_token}"},
)
assert res.status_code == http.HTTPStatus.UNPROCESSABLE_ENTITY, res.json()
actual = res.json()
assert actual == {
"description": "Unknown solver configuration: 'unknown'",
"exception": "UnknownSolverConfig",
}
1 change: 1 addition & 0 deletions webapp/public/locales/en/main.json
Original file line number Diff line number Diff line change
@@ -564,6 +564,7 @@
"study.error.listOutputs": "Failed to retrieve output list",
"study.error.launcherVersions": "Failed to retrieve launcher versions",
"study.error.launcherCores": "Failed to retrieve launcher number of cores",
"study.error.launcherTimeLimit": "Failed to retrieve launcher time limit",
"study.error.fetchComments": "Failed to fetch comments",
"study.error.commentsNotSaved": "Comments not saved",
"study.error.studyIdCopy": "Failed to copy study ID",
1 change: 1 addition & 0 deletions webapp/public/locales/fr/main.json
Original file line number Diff line number Diff line change
@@ -564,6 +564,7 @@
"study.error.listOutputs": "Échec de la récupération des sorties",
"study.error.launcherVersions": "Échec lors de la récupération des versions du launcher",
"study.error.launcherCores": "Échec lors de la récupération du nombre de cœurs du launcher",
"study.error.launcherTimeLimit": "Échec lors de la récupération de la limite de temps du launcher",
"study.error.fetchComments": "Échec lors de la récupération des commentaires",
"study.error.commentsNotSaved": "Erreur lors de l'enregistrement des commentaires",
"study.error.studyIdCopy": "Erreur lors de la copie de l'identifiant de l'étude",
Loading

Unchanged files with check annotations Beta

);
};
const onChange = async (currentName: string) => {

Check warning on line 28 in webapp/src/components/App/Data/DataPropsView.tsx

GitHub Actions / npm-test (ubuntu-20.04)

Async arrow function has no 'await' expression
if (currentName !== "") {
const f = filter(currentName);
setFilteredDatas(f);
setOpenModal(true);
};
const handleDelete = async (id: string) => {

Check warning on line 61 in webapp/src/components/App/Data/index.tsx

GitHub Actions / npm-test (ubuntu-20.04)

Async arrow function has no 'await' expression
setIdForDeletion(id);
setOpenConfirmationModal(true);
};
}
};
const onMatrixClick = async (id: string) => {

Check warning on line 100 in webapp/src/components/App/Data/index.tsx

GitHub Actions / npm-test (ubuntu-20.04)

Async arrow function has no 'await' expression
if (selectedItem) {
const tmp = dataList.find((o) => o.id === selectedItem);
if (tmp) {
}
};
const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => {

Check warning on line 30 in webapp/src/components/App/Singlestudy/Commands/Edition/DraggableCommands/CommandImportButton.tsx

GitHub Actions / npm-test (ubuntu-20.04)

Async arrow function has no 'await' expression
e.preventDefault();
const reader = new FileReader();
reader.onload = async (ev: ProgressEvent<FileReader>) => {

Check warning on line 33 in webapp/src/components/App/Singlestudy/Commands/Edition/DraggableCommands/CommandImportButton.tsx

GitHub Actions / npm-test (ubuntu-20.04)

Async arrow function has no 'await' expression
try {
if (ev.target) {
const text = ev.target.result;
}
};
const onDelete = async (index: number) => {

Check warning on line 121 in webapp/src/components/App/Singlestudy/Commands/Edition/index.tsx

GitHub Actions / npm-test (ubuntu-20.04)

Async arrow function has no 'await' expression
setOpenDeleteCommandDialog(index);
};
const handleSubmitSuccessful = async (
data: SubmitHandlerPlus<typeof defaultValues>,
variantId: string,
) => {

Check warning on line 50 in webapp/src/components/App/Singlestudy/HomeView/InformationView/CreateVariantDialog.tsx

GitHub Actions / npm-test (ubuntu-20.04)

Async arrow function has no 'await' expression
onClose();
navigate(`/studies/${variantId}`);
};
import ConfirmationDialog from "../../../../../common/dialogs/ConfirmationDialog";
import LinearProgressWithLabel from "../../../../../common/LinearProgressWithLabel";
export const ColorStatus = {

Check warning on line 33 in webapp/src/components/App/Singlestudy/HomeView/InformationView/LauncherHistory/JobStepper.tsx

GitHub Actions / npm-test (ubuntu-20.04)

Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
running: "warning.main",
pending: "grey.400",
success: "success.main",
const buildTree = async (
node: StudyTree,
childrenTree: VariantTree,
): Promise<void> => {

Check warning on line 55 in webapp/src/components/App/Singlestudy/HomeView/StudyTreeView/utils.ts

GitHub Actions / npm-test (ubuntu-20.04)

Async arrow function has no 'await' expression
if ((childrenTree.children || []).length === 0) {
node.drawOptions.depth = 1;
node.drawOptions.nbAllChildrens = 0;
const handleSubmit = async (
data: SubmitHandlerPlus<typeof defaultValues>,
) => {

Check warning on line 42 in webapp/src/components/App/Singlestudy/UpgradeDialog.tsx

GitHub Actions / npm-test (ubuntu-20.04)

Async arrow function has no 'await' expression
return upgradeStudy(study.id, data.values.version).then(onClose);
};