Skip to content

Commit

Permalink
Merge pull request #1948 from DSD-DBS/configurable-cronjob
Browse files Browse the repository at this point in the history
feat: Customize schedule and tz of nightly pipelines via global config
  • Loading branch information
MoritzWeber0 authored Nov 1, 2024
2 parents b7fbb2b + 6424741 commit d25c6f0
Show file tree
Hide file tree
Showing 12 changed files with 128 additions and 3 deletions.
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ repos:
- typer
- types-lxml
- cryptography
- types-croniter
- repo: local
hooks:
- id: pylint
Expand Down
6 changes: 5 additions & 1 deletion backend/capellacollab/projects/toolmodels/backups/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
)
from capellacollab.projects.users import models as projects_users_models
from capellacollab.sessions import operators
from capellacollab.settings.configuration import core as configuration_core
from capellacollab.settings.modelsources.t4c.instance.repositories import (
interface as t4c_repository_interface,
)
Expand Down Expand Up @@ -99,6 +100,8 @@ def create_backup(
exceptions.PipelineOperation.CREATE
)

pipeline_config = configuration_core.get_global_configuration(db).pipelines

if body.run_nightly:
if not toolmodel.version_id:
raise toolmodels_exceptions.VersionIdNotSetError(toolmodel.id)
Expand All @@ -117,7 +120,8 @@ def create_backup(
labels=core.get_pipeline_labels(toolmodel),
tool_resources=toolmodel.tool.config.resources,
command="backup",
schedule="0 3 * * *",
schedule=pipeline_config.cron,
timezone=pipeline_config.timezone,
)
else:
reference = operators.get_operator()._generate_id()
Expand Down
4 changes: 3 additions & 1 deletion backend/capellacollab/sessions/operators/k8s.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,16 +275,18 @@ def create_cronjob(
tool_resources: tools_models.Resources,
environment: dict[str, str | None],
schedule="* * * * *",
timezone="UTC",
timeout=18000,
) -> str:
_id = self._generate_id()

cronjob: client.V1CronJob = client.V1CronJob(
kind="CronJob",
api_version="batch/v1",
metadata=client.V1ObjectMeta(name=_id),
metadata=client.V1ObjectMeta(name=_id, labels=labels),
spec=client.V1CronJobSpec(
schedule=schedule,
time_zone=timezone,
job_template=client.V1JobTemplateSpec(
metadata=client.V1ObjectMeta(labels=labels),
spec=self._create_job_spec(
Expand Down
38 changes: 38 additions & 0 deletions backend/capellacollab/settings/configuration/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import abc
import enum
import typing as t
import zoneinfo

import pydantic
from croniter import croniter
from sqlalchemy import orm

from capellacollab import core
Expand Down Expand Up @@ -166,6 +168,38 @@ class ConfigurationBase(core_pydantic.BaseModelStrict, abc.ABC):
_name: t.ClassVar[str]


class PipelineConfiguration(core_pydantic.BaseModelStrict):
cron: str = pydantic.Field(
default="0 3 * * *",
description=(
"Cron for nightly backup. Only applies to newly created pipelines."
),
)
timezone: str = pydantic.Field(
default="UTC",
description="Timezone for the cron expression.",
)

@pydantic.field_validator("cron")
@classmethod
def validate_cron(cls, v: str) -> str:
if croniter.is_valid(v):
return v

raise ValueError("Cron doesn't have a valid syntax.")

@pydantic.field_validator("timezone")
@classmethod
def validate_timezone(cls, v: str) -> str:
if v in zoneinfo.available_timezones():
return v

raise ValueError(
"Timezone is not valid. A list of timezones can be found at"
" https://en.wikipedia.org/wiki/List_of_tz_database_time_zones"
)


class GlobalConfiguration(ConfigurationBase):
"""Global application configuration."""

Expand All @@ -183,6 +217,10 @@ class GlobalConfiguration(ConfigurationBase):
default_factory=FeedbackConfiguration
)

pipelines: PipelineConfiguration = pydantic.Field(
default_factory=PipelineConfiguration
)


# All subclasses of ConfigurationBase are automatically registered using this dict.
NAME_TO_MODEL_TYPE_MAPPING: dict[str, t.Type[ConfigurationBase]] = {
Expand Down
8 changes: 7 additions & 1 deletion backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ dependencies = [
"lxml",
"valkey[libvalkey]",
"cryptography",
"croniter",
]

[project.urls]
Expand All @@ -69,6 +70,7 @@ dev = [
"pytest-cov",
"aioresponses",
"types-lxml",
"types-croniter",
]

[tool.black]
Expand Down Expand Up @@ -223,7 +225,11 @@ extension-pkg-whitelist = "pydantic" # https://github.com/pydantic/pydantic/issu

[tool.pylint.master]
init-import = "yes"
load-plugins = ["pylint.extensions.bad_builtin", "pylint.extensions.mccabe", "pylint_pytest"]
load-plugins = [
"pylint.extensions.bad_builtin",
"pylint.extensions.mccabe",
"pylint_pytest",
]
extension-pkg-allow-list = ["lxml.etree"]

[tool.pylint.similarities]
Expand Down
18 changes: 18 additions & 0 deletions backend/tests/settings/test_global_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,21 @@ def test_navbar_is_updated(
"href": "https://example.com",
"role": "user",
}


@pytest.mark.usefixtures("admin")
def test_global_configuration_invalid_pipelines(
client: testclient.TestClient,
):
response = client.put(
"/api/v1/settings/configurations/global",
json={"pipelines": {"cron": "invalid", "timezone": "Berlin"}},
)

assert response.status_code == 422

detail = response.json()["detail"]
assert detail[0]["type"] == "value_error"
assert detail[0]["loc"] == ["body", "pipelines", "cron"]
assert detail[1]["type"] == "value_error"
assert detail[1]["loc"] == ["body", "pipelines", "timezone"]
2 changes: 2 additions & 0 deletions frontend/src/app/openapi/.openapi-generator/FILES

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions frontend/src/app/openapi/model/global-configuration-input.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions frontend/src/app/openapi/model/global-configuration-output.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions frontend/src/app/openapi/model/models.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions frontend/src/app/openapi/model/pipeline-configuration-input.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions frontend/src/app/openapi/model/pipeline-configuration-output.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit d25c6f0

Please sign in to comment.