Loading .env.example +5 −0 Changes for .env.example: 5 added lines, 0 removed lines. Original line number Diff line number Diff line APP_NAME="Service Resource Manager" APP_DESCRIPTION="Service Resource Manager is the brains of the ETSI SDG Open Operator Platform - OpenOP" APP_VERSION="1.5.0" INSTANCES_MONITOR_INTERVAL=10.0 POSTGRES_SETTINGS__URL = "postgresql+asyncpg://postgres:postgres@localhost:5432/srm" POSTGRES_SETTINGS__ECHO = true Loading @@ -10,3 +11,7 @@ NATS_SETTINGS__URL = "nats://localhost:4222" NATS_SETTINGS__CONNECT_TIMEOUT = 10 NATS_SETTINGS__MAX_RECONNECT_ATTEMPTS = 3 NATS_SETTINGS__DRAIN_TIMEOUT = 30 FEDERATION_MANAGER_SETTINGS__BASE_URL = "http://localhost:8082" FEDERATION_MANAGER_SETTINGS__TIMEOUT = 0.5 FEDERATION_MANAGER_SETTINGS__ENABLED = true .gitignore +1 −0 Changes for .gitignore: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ wheels/ # AI software tools .claude CLAUDE.md .agents .tox/ htmlcov/ Loading Dockerfile +2 −0 Changes for Dockerfile: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -17,6 +17,8 @@ RUN uv sync --locked FROM python:3.12-slim AS runtime COPY --from=alpine/helm:3.18.6 /usr/bin/helm /usr/local/bin/helm WORKDIR /app COPY --from=builder /app/.venv /app/.venv Loading pyproject.toml +10 −1 Changes for pyproject.toml: 10 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -18,10 +18,11 @@ dependencies = [ "pydantic-settings>=2.13.1", "python-dotenv>=1.2.2", "structlog>=25.5.0", "sunrise6g-opensdk==2.0.0", "sqlalchemy>=2.0.48", "asyncpg>=0.31.0", "nats-py>=2.10.0", "openop-tfsdk==2.0.0rc2", "apscheduler<4.0.0", ] [project.optional-dependencies] Loading @@ -37,6 +38,14 @@ dev = [ "ruff>=0.15.6", ] [tool.uv.sources] openop-tfsdk = { index = "etsi-tf-sdk" } [[tool.uv.index]] name = "etsi-tf-sdk" url = "https://labs.etsi.org/rep/api/v4/projects/396/packages/pypi/simple" explicit = true [tool.ruff] line-length = 100 target-version = "py312" Loading src/srm/adapters/background_tasks/instances_monitor.py 0 → 100644 +67 −0 Changes for src/srm/adapters/background_tasks/instances_monitor.py: 67 added lines, 0 removed lines. Original line number Diff line number Diff line """Implementation of Service and Capability Instances monitoring adapter using APScheduler package. """ import structlog from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.triggers.interval import IntervalTrigger from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker from srm.adapters.database.repos.runtime_inventory import ( SqlCapabilityInstanceRepository, SqlServiceInstanceRepository, SqlServiceOrderRepository, ) from srm.application.use_cases.instance_monitor import CapabilityInstanceMonitorUseCase from srm.domain.ports.background_tasks.instances_monitor import InstancesMonitorPort from srm.domain.ports.databus.publisher import DataBusPublisher from srm.domain.ports.transformation_functions import ControlPathRegistry logger: structlog.BoundLogger = structlog.getLogger(__name__) class APSchedulerInstancesMonitor(InstancesMonitorPort): def __init__( self, session_maker: async_sessionmaker[AsyncSession], control_path_registry: ControlPathRegistry, publisher: DataBusPublisher, ) -> None: self._session_maker = session_maker self._control_path_registry = control_path_registry self._publisher = publisher self._scheduler = AsyncIOScheduler() def register_scheduler( self, interval: float, ) -> None: self._scheduler.add_job( self.update_statuses, IntervalTrigger(seconds=interval), id="instance_monitor" ) def start(self) -> None: self._scheduler.start() def shutdown(self) -> None: self._scheduler.shutdown(wait=True) async def update_statuses(self) -> None: async with self._session_maker() as session: try: use_case = CapabilityInstanceMonitorUseCase( control_path_registry=self._control_path_registry, capability_instances=SqlCapabilityInstanceRepository(session=session), service_instances=SqlServiceInstanceRepository(session=session), service_orders=SqlServiceOrderRepository(session=session), publisher=self._publisher, ) await use_case.execute() await session.commit() except Exception as exc: logger.exception( "instances_monitor_tick_failed", error=str(exc), error_type=type(exc).__name__, ) await session.rollback() Loading
.env.example +5 −0 Changes for .env.example: 5 added lines, 0 removed lines. Original line number Diff line number Diff line APP_NAME="Service Resource Manager" APP_DESCRIPTION="Service Resource Manager is the brains of the ETSI SDG Open Operator Platform - OpenOP" APP_VERSION="1.5.0" INSTANCES_MONITOR_INTERVAL=10.0 POSTGRES_SETTINGS__URL = "postgresql+asyncpg://postgres:postgres@localhost:5432/srm" POSTGRES_SETTINGS__ECHO = true Loading @@ -10,3 +11,7 @@ NATS_SETTINGS__URL = "nats://localhost:4222" NATS_SETTINGS__CONNECT_TIMEOUT = 10 NATS_SETTINGS__MAX_RECONNECT_ATTEMPTS = 3 NATS_SETTINGS__DRAIN_TIMEOUT = 30 FEDERATION_MANAGER_SETTINGS__BASE_URL = "http://localhost:8082" FEDERATION_MANAGER_SETTINGS__TIMEOUT = 0.5 FEDERATION_MANAGER_SETTINGS__ENABLED = true
.gitignore +1 −0 Changes for .gitignore: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ wheels/ # AI software tools .claude CLAUDE.md .agents .tox/ htmlcov/ Loading
Dockerfile +2 −0 Changes for Dockerfile: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -17,6 +17,8 @@ RUN uv sync --locked FROM python:3.12-slim AS runtime COPY --from=alpine/helm:3.18.6 /usr/bin/helm /usr/local/bin/helm WORKDIR /app COPY --from=builder /app/.venv /app/.venv Loading
pyproject.toml +10 −1 Changes for pyproject.toml: 10 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -18,10 +18,11 @@ dependencies = [ "pydantic-settings>=2.13.1", "python-dotenv>=1.2.2", "structlog>=25.5.0", "sunrise6g-opensdk==2.0.0", "sqlalchemy>=2.0.48", "asyncpg>=0.31.0", "nats-py>=2.10.0", "openop-tfsdk==2.0.0rc2", "apscheduler<4.0.0", ] [project.optional-dependencies] Loading @@ -37,6 +38,14 @@ dev = [ "ruff>=0.15.6", ] [tool.uv.sources] openop-tfsdk = { index = "etsi-tf-sdk" } [[tool.uv.index]] name = "etsi-tf-sdk" url = "https://labs.etsi.org/rep/api/v4/projects/396/packages/pypi/simple" explicit = true [tool.ruff] line-length = 100 target-version = "py312" Loading
src/srm/adapters/background_tasks/instances_monitor.py 0 → 100644 +67 −0 Changes for src/srm/adapters/background_tasks/instances_monitor.py: 67 added lines, 0 removed lines. Original line number Diff line number Diff line """Implementation of Service and Capability Instances monitoring adapter using APScheduler package. """ import structlog from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.triggers.interval import IntervalTrigger from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker from srm.adapters.database.repos.runtime_inventory import ( SqlCapabilityInstanceRepository, SqlServiceInstanceRepository, SqlServiceOrderRepository, ) from srm.application.use_cases.instance_monitor import CapabilityInstanceMonitorUseCase from srm.domain.ports.background_tasks.instances_monitor import InstancesMonitorPort from srm.domain.ports.databus.publisher import DataBusPublisher from srm.domain.ports.transformation_functions import ControlPathRegistry logger: structlog.BoundLogger = structlog.getLogger(__name__) class APSchedulerInstancesMonitor(InstancesMonitorPort): def __init__( self, session_maker: async_sessionmaker[AsyncSession], control_path_registry: ControlPathRegistry, publisher: DataBusPublisher, ) -> None: self._session_maker = session_maker self._control_path_registry = control_path_registry self._publisher = publisher self._scheduler = AsyncIOScheduler() def register_scheduler( self, interval: float, ) -> None: self._scheduler.add_job( self.update_statuses, IntervalTrigger(seconds=interval), id="instance_monitor" ) def start(self) -> None: self._scheduler.start() def shutdown(self) -> None: self._scheduler.shutdown(wait=True) async def update_statuses(self) -> None: async with self._session_maker() as session: try: use_case = CapabilityInstanceMonitorUseCase( control_path_registry=self._control_path_registry, capability_instances=SqlCapabilityInstanceRepository(session=session), service_instances=SqlServiceInstanceRepository(session=session), service_orders=SqlServiceOrderRepository(session=session), publisher=self._publisher, ) await use_case.execute() await session.commit() except Exception as exc: logger.exception( "instances_monitor_tick_failed", error=str(exc), error_type=type(exc).__name__, ) await session.rollback()