Commit fbc47301 authored by Paris Stentoumis's avatar Paris Stentoumis
Browse files

refactor: SRM release 2.0.0

 
Replace the Connexion + MongoDB service-resource-manager-implementation
tree with src/srm: FastAPI, SQLAlchemy/Postgres, NATS, and a clear hexagonal architecture.
 
Persistence
- Migrate durable state from MongoDB to PostgreSQL
- Async SQLAlchemy repos for catalog, topology, and runtime inventory
 
Messaging
- Introduce NATS as the platform databus (publisher, durable subscriber,
  connection manager)
- Command/event envelopes with schema_version and federation-context
  validation
- Async handlers for deploy/terminate and network-capability activate/
  deactivate, with answers published back on the bus
 
Sync vs async operations
- Synchronous northbound REST (/internal and health) for queries and
  service-specification CRUD (immediate HTTP response, RFC 7807 errors)
- Asynchronous southbound lifecycle via NATS commands; long-running
  work (deploy, terminate, capability activate/deactivate) is accepted
  on the bus and completed with follow-up events (including FM operation
  completed where federation applies)
- OpenOp TF-SDK wired as the control-path adapter factory for sync
  control-plane calls inside those async flows (service and network
  capability lifecycle, location retrieval, traffic influence)
 
Other develop capabilities
- Service specification catalog APIs and catalog update events
- Traffic Influence and Location Retrieval
- Control-path bootstrap aligned with TF-SDK 2.0.0
- CI qualification for TF-SDK consumer builds
parent d1f8bbd5
Loading
Loading
Loading
Loading

.env.example

0 → 100644
+20 −0
Changes for .env.example: 20 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
POSTGRES_SETTINGS__CREATE_SCHEMA_ON_STARTUP = true

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

# One complete SDK bootstrap; leave unset for an empty topology/health-only startup.
# BOOTSTRAP_TOPOLOGY_PATH=/etc/srm/topology.yaml

.gitignore

0 → 100644
+32 −0
Changes for .gitignore: 32 added lines, 0 removed lines.
Original line number Diff line number Diff line
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info

# Virtual environments
.venv

# environment
.env

# code editors
.vscode/

# AI software tools
.claude
CLAUDE.md
.agents

.tox/
htmlcov/
.mypy_cache/
.pytest_cache/
.ruff_cache/
.idea/
.coverage
.python_version
coverage.xml
.cache
+117 −22
Changes for .gitlab-ci.yml: 117 added lines, 22 removed lines.
Original line number Diff line number Diff line
workflow:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "pipeline" && $SDK_PROJECT_ID == "396"'
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_PIPELINE_SOURCE == "push" && $CI_OPEN_MERGE_REQUESTS'
      when: never
    - when: always

default:
  image: python:3.12-slim
  cache:
    paths:
      - .cache/uv
  before_script:
    - cd "$CI_PROJECT_DIR/${BACKEND_DIR:-.}"
    - pip install uv
    - export UV_SYSTEM_CERTS=1
    - uv sync --locked --extra dev
    - . .venv/bin/activate

stages:
  - build
  - push
  - type
  - architecture
  - lint
  - format
  - test
  - build-and-push

variables:
  IMAGE_NAME: $CI_REGISTRY/$CI_PROJECT_PATH
  IMAGE_TAG: latest
  UV_CACHE_DIR: "$CI_PROJECT_DIR/.cache/uv"

type:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "pipeline" && $SDK_PROJECT_ID == "396"'
      when: never
    - when: on_success
  stage: type
  script:
    - mypy src/ tests/

architecture:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "pipeline" && $SDK_PROJECT_ID == "396"'
      when: never
    - when: on_success
  stage: architecture
  script:
    - lint-imports

lint:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "pipeline" && $SDK_PROJECT_ID == "396"'
      when: never
    - when: on_success
  stage: lint
  script:
    - ruff check src/ tests/

format:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "pipeline" && $SDK_PROJECT_ID == "396"'
      when: never
    - when: on_success
  stage: format
  script:
    - ruff format --check src/ tests/

test:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "pipeline" && $SDK_PROJECT_ID == "396"'
      when: never
    - when: on_success
  stage: test
  variables:
    DOCKER_HOST: tcp://docker:2375
    DOCKER_TLS_CERTDIR: ""
  services:
    - docker:dind
  script:
    - pytest --cov=src/srm --cov-branch --cov-report=term-missing
  coverage: '/TOTAL.+ ([0-9]{1,3}(?:\.[0-9]+)?%)/'

build-and-push:
  stage: build-and-push
  image: docker:cli
  variables:
    DOCKER_HOST: tcp://docker:2375
    DOCKER_TLS_CERTDIR: ""
  services:
    - docker:dind
  before_script:
    - docker info

build:
  tags: 
    - "shell"
  stage: build
  script:
    - docker build -t $IMAGE_NAME:$IMAGE_TAG .
  only:
    - main

push:
  tags: 
    - "shell"
  stage: push
    - export TEST_IMAGE_TAG="ci-${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA}"
    - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" "$CI_REGISTRY" --password-stdin
    - docker buildx build --provenance=false --network=host -t "$CI_REGISTRY_IMAGE:$TEST_IMAGE_TAG" --push .
    - docker logout "$CI_REGISTRY"
  rules:
    - if: '$CI_PIPELINE_SOURCE == "pipeline" && $SDK_PROJECT_ID == "396"'
      when: never
    - if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "develop"'

sdk-consumer:
  stage: test
  image: python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1
  timeout: 15 minutes
  variables:
    TMPDIR: "$CI_PROJECT_DIR/.cache/sdk-tmp"
    PYTHONDONTWRITEBYTECODE: "1"
  rules:
    - if: '$CI_PIPELINE_SOURCE == "pipeline" && $SDK_PROJECT_ID == "396"'
    - when: never
  before_script:
    - mkdir -p "$TMPDIR"
    - pip install uv==0.11.26
    - uv sync --locked --extra dev --no-install-project
  script:
    - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" $CI_REGISTRY --password-stdin
    - docker tag $IMAGE_NAME:$IMAGE_TAG $CI_REGISTRY_IMAGE:$IMAGE_TAG
    - docker push $CI_REGISTRY_IMAGE:$IMAGE_TAG
  only:
    - main
    - python ci/sdk_consumer.py
  artifacts:
    when: always
    paths:
      - .cache/sdk-consumer/qualification.json
    reports:
      junit: .cache/sdk-consumer/junit.xml
    expire_in: 90 days
+38 −0
Changes for .pre-commit-config.yaml: 38 added lines, 0 removed lines.
Original line number Diff line number Diff line
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
  rev: v6.0.0
  hooks:
    - id: check-yaml
    - id: check-toml
    - id: end-of-file-fixer
    - id: trailing-whitespace
      args: [--markdown-linebreak-ext=md]

- repo: https://github.com/astral-sh/ruff-pre-commit
  # Ruff version.
  rev: v0.15.6
  hooks:
    # Run the linter.
    - id: ruff-check
    # Run the formatter.
    - id: ruff-format

- repo: https://github.com/pre-commit/mirrors-mypy
  rev: v1.19.1  # Use the sha / tag you want to point at
  hooks:
    - id: mypy
      files: ^src/srm/|^tests/
      additional_dependencies:
        - "asgi-lifespan>=2.1.0"
        - "docker>=7.0.0"
        - "fastapi[standard]>=0.135.1"
        - "httpx>=0.27.0"
        - "nats-py>=2.10.0"
        - "pydantic-settings>=2.13.1"
        - "pytest>=9.0.2"
        - "pytest-asyncio>=0.24"
        - "python-dotenv>=1.2.2"
        - "sqlalchemy>=2.0.48"
        - "structlog>=25.5.0"
        - "testcontainers[postgres]>=4.13.2"
        - "types-PyYAML>=6.0.12"

Dockerfile

0 → 100644
+34 −0
Changes for Dockerfile: 34 added lines, 0 removed lines.
Original line number Diff line number Diff line
# syntax=docker/dockerfile:1

FROM python:3.12-slim AS builder
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PIP_NO_CACHE_DIR=1 \
    PIP_DEFAULT_TIMEOUT=120

COPY pyproject.toml ./
COPY uv.lock ./

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
COPY src/ ./src/

ENV PATH="/app/.venv/bin:$PATH"
ENV PYTHONPATH="/app/src"
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1

EXPOSE 8080

CMD ["uvicorn", "srm.main:create_app", "--host", "0.0.0.0", "--port", "8080"]
Loading