Commit 82daf6fc authored by Sergio Gimenez's avatar Sergio Gimenez
Browse files

Merge branch 'chore/retire-legacy-connexion-stack' into 'develop'

retire the legacy Connexion stack and ship the FastAPI service

See merge request !12
parents d57354ee 93be71bf
Loading
Loading
Loading
Loading
Loading
+12 −62
Original line number Diff line number Diff line
# Git files
.git
.gitignore
.gitlab-ci.yml

# Documentation
README.md
CONTRIBUTING.md
LICENSE
docs/

# Docker files
Dockerfile
docker-compose.yaml
docker-compose.dev.yaml
.dockerignore

# Python cache and virtual environments
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
ENV/
env.bak/
venv.bak/
.venv/
src/__pycache__/
src/models/__pycache__/
src/controllers/__pycache__/
src/clients/__pycache__/
src/test/__pycache__/
src/adapters/__pycache__/
src/api/__pycache__/
src/adapters/tf_adapter/__pycache__/
src/adapters/fm_adapter/__pycache__/
venv/
*.egg-info/

# Testing and coverage
.coverage
tests/
.pytest_cache/
.mypy_cache/
.ruff_cache/
.coverage
htmlcov/
.tox/
.nox/
coverage.xml
*.cover
.hypothesis/

# IDE and editor files
.vscode/
.idea/
*.swp
*.swo
*~

# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# Logs
*.log
logs/

# Temporary files
tmp/
temp/
*.tmp

# Node modules (if any)
node_modules/
npm-debug.log*

# Configuration files that shouldn't be in the image
src/conf/config.cfg
.log/
*.env
.env.local
.env.development
.env.test
.env.production

# Log files and directories
*.tgz
src/.log/

# Build artifacts
.env.*
tmp/
dist/
build/
+29 −0
Original line number Diff line number Diff line
@@ -2,8 +2,37 @@
# This pipeline builds and pushes Docker images for the federation-manager service

stages:
  - check
  - build

# Lint, type-check and test on every push and merge request (issue #10).
check:
  stage: check
  image: python:3.12
  tags:
    - docker
  services:
    - name: postgres:16-alpine
      alias: postgres
    - name: nats:2.10-alpine
      alias: nats
      command: ["-js"]
  variables:
    POSTGRES_USER: fm
    POSTGRES_PASSWORD: fm
    POSTGRES_DB: fm_db
    FM_POSTGRES_URL: "postgresql+asyncpg://fm:fm@postgres:5432/fm_db"
    FM_NATS_URL: "nats://nats:4222"
  before_script:
    - python -m pip install --upgrade pip
    - pip install -e ".[dev]"
  script:
    - ruff format --check src/federation_manager tests
    - ruff check src/federation_manager tests
    - mypy
    # Keycloak is not available here, so the tests needing it are deselected.
    - pytest -q -k "not two_stack"

variables:
  IMAGE_NAME: federation-manager
  REGISTRY_IMAGE: $CI_REGISTRY_IMAGE/$IMAGE_NAME
+12 −27
Original line number Diff line number Diff line
@@ -14,38 +14,23 @@
# limitations under the License.                                             #
# -------------------------------------------------------------------------- #

#########################################################
#                                                       #
#   Dockerfile for creating a container image for the   #
#   federation-manager                                  #
#                                                       #
#########################################################

FROM python:3.12
WORKDIR /usr/app
RUN apt-get update && apt-get install -y \
  bash \
  build-essential \
  git \
  wget \
  iptables \
  libcurl4-openssl-dev \
  libssl-dev \
  && rm -rf /var/lib/apt/lists/*
# Copy application code
COPY . . 
FROM python:3.12-slim AS build
WORKDIR /build
ARG PIP_INDEX_URL
ARG PIP_EXTRA_INDEX_URL
ARG PIP_TRUSTED_HOST
# Create pip.conf for indexes
RUN mkdir -p /root/.config/pip && \
    echo "[global]" > /root/.config/pip/pip.conf && \
    if [ -n "$PIP_INDEX_URL" ]; then echo "index-url = ${PIP_INDEX_URL}" >> /root/.config/pip/pip.conf; fi && \
    if [ -n "$PIP_EXTRA_INDEX_URL" ]; then echo "extra-index-url = ${PIP_EXTRA_INDEX_URL}" >> /root/.config/pip/pip.conf; fi && \
    if [ -n "$PIP_TRUSTED_HOST" ]; then echo "trusted-host = ${PIP_TRUSTED_HOST}" >> /root/.config/pip/pip.conf; fi
# Install Python dependencies
RUN python -m pip install --no-cache-dir -r requirements.txt
WORKDIR /usr/app/src/
EXPOSE 8989
# Set Gunicorn as the entrypoint
ENTRYPOINT ["gunicorn", "wsgi:app", "--bind", "0.0.0.0:8989", "--workers", "4", "--log-level", "debug", "--timeout", "1000"]
COPY pyproject.toml README.md ./
COPY src/ ./src/
RUN python -m pip install --no-cache-dir --prefix=/install .

FROM python:3.12-slim
COPY --from=build /install /usr/local
RUN useradd --create-home --uid 10001 fm
USER fm
EXPOSE 8082
ENTRYPOINT ["uvicorn", "federation_manager.main:app", "--host", "0.0.0.0", "--port", "8082"]

Dockerfile.dev

deleted100644 → 0
+0 −57
Original line number Diff line number Diff line
# -------------------------------------------------------------------------- #
# Copyright 2025-present, Federation Manager, by Software Networks, i2CAT    #
#                                                                            #
# Licensed under the Apache License, Version 2.0 (the "License"); you may    #
# not use this file except in compliance with the License. You may obtain    #
# a copy of the License at                                                   #
#                                                                            #
# http://www.apache.org/licenses/LICENSE-2.0                                 #
#                                                                            #
# Unless required by applicable law or agreed to in writing, software        #
# distributed under the License is distributed on an "AS IS" BASIS,          #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #
# See the License for the specific language governing permissions and        #
# limitations under the License.                                             #
# -------------------------------------------------------------------------- #

#########################################################
#                                                       #
#   Dev Dockerfile for creating a container image      #
#   for the federation-manager with local TF-SDK       #
#                                                       #
#########################################################

FROM python:3.12
WORKDIR /usr/app
RUN apt-get update && apt-get install -y \
    bash \
    build-essential \
    git \
    wget \
    iptables \
    libcurl4-openssl-dev \
    libssl-dev \
    && rm -rf /var/lib/apt/lists/*

# Copy local TF-SDK first
COPY ../tf-sdk /tmp/tf-sdk

# Copy application code
COPY . . 


# Install local TF-SDK instead of the published version
RUN pip install --no-cache-dir /tmp/tf-sdk

# Install remaining Python dependencies (TF-SDK will be skipped since already installed)
RUN python -m pip install --no-cache-dir -r requirements.txt \
  --extra-index-url https://labs.etsi.org/rep/api/v4/projects/396/packages/pypi/simple \
  --trusted-host labs.etsi.org

# Clean up
RUN rm -rf /tmp/tf-sdk

WORKDIR /usr/app/src/
EXPOSE 8989
# Set Gunicorn as the entrypoint
ENTRYPOINT ["gunicorn", "wsgi:app", "--bind", "0.0.0.0:8989", "--workers", "4", "--log-level", "debug", "--timeout", "1000"]
+49 −181
Original line number Diff line number Diff line
@@ -2,7 +2,7 @@

**Federation Manager** is an open-source Python component implementing the *Federation Management* functionality of the 
**Operator Platform (OP)**, as defined by the [GSMA Operator Platform Group (OPG)](https://www.gsma.com/solutions-and-impact/technologies/networks/gsma_resources/gsma-operator-platform-group-september-2024-publications/). 
This project aligns with the EWBI specifications outlined in the GSMA OPG document *"East-Westbound Interface APIs, (Version 4.0)"*.
This project implements the EWBI specification published by the GSMA OPG as *"East-Westbound Interface APIs"* (**OPG.04 v6.0**), against the OpenAPI contract distributed with that PRD (`OPG.04_EWBI_Federation_API_v1.4.0.yaml`, vendored under `docs/`).

## Description

@@ -39,8 +39,8 @@ experiences across markets.

## Compatibility

- Based on GSMA OPG Specifications: **OPG.02-v6.0***Operator Platform: Requirements and Architecture* and 
**OPG.04-v4.0***East-Westbound Interface APIs*.
- Based on GSMA OPG specifications: **OPG.02 v10.0***Operator Platform: Requirements and Architecture* and
**OPG.04 v6.0***East-Westbound Interface APIs*.
- Designed to work with the Operator Platform core components and peer Federation Managers.

## Getting Started
@@ -49,205 +49,73 @@ experiences across markets.

- **Python 3.12** or higher
- **Docker** and **Docker Compose**
- **Git**

### Installation
### Run the stack

1. **Clone the repository:**
```bash
   git clone <repository-url>
   cd federation-manager
docker compose up --build
```

2. **Set up configuration:**
   ```bash
   cp src/conf/config.cfg.sample src/conf/config.cfg
   # Edit config.cfg with your specific settings
   ```

3. **Choose your deployment method:**

#### Option A: Docker Compose (Recommended)
   ```bash
   docker compose up -d
   ```
   This starts:
   - Federation Manager (port 8990)
   - MongoDB (port 27017)
   - Keycloak (port 8080)

#### Option B: Local Development
   ```bash
   # Create virtual environment
   python -m venv venv
   source venv/bin/activate  # On Windows: venv\Scripts\activate
   
   # Install dependencies
   pip install -r requirements.txt
   
   # Start services
   cd src/
   python main.py
   ```

### Quick Start
That starts Federation Manager on **:8082**, plus PostgreSQL, NATS JetStream and Keycloak.

1. **Access the API documentation:**
   - Swagger UI: `http://localhost:8990/ui/`
   - OpenAPI spec: `http://localhost:8990/openapi.yaml`

2. **Authentication:**
   - Keycloak Admin Console: `http://localhost:8080/admin/`
   - Default credentials: `admin/admin`

3. **Test the API:**
   ```bash
   curl -X GET http://localhost:8990/operatorplatform/federation/v1/health
   ```

## Testing

The Federation Manager includes a comprehensive test suite that validates the **dual role architecture**:

### Dual Role Testing
The Federation Manager operates in two modes:
- **Partner OP Mode**: Handles external requests (without `X-Internal` header)
- **Originating OP Mode**: Handles internal requests (with `X-Internal` and `X-Partner-API-Root` headers)

### Running Tests

1. **Run all tests:**
```bash
   cd src/
   python test/run_all_tests.py --verbose --coverage
   ```

2. **Test execution order:**
   - `federation_management`
   - `availability_zone_info_synchronization`
   - `artefact_management`
   - `application_onboarding_management`
   - `application_deployment_management`

3. **Coverage reports:**
   - Terminal output with coverage percentage
   - HTML report in `src/htmlcov/` directory

### Test Architecture

The test suite validates the complete stack:
```
API → Adapter → Client → External System
curl http://localhost:8082/healthz
```

Each test runs for both operational modes, ensuring:
- ✅ Partner OP functionality (external federation partners)
- ✅ Originating OP functionality (internal service requests)
- ✅ End-to-end integration across all components

## Development

### CI/CD Pipeline

The project includes GitLab CI/CD configuration (`.gitlab-ci.yml`) with:
### Local development

- **Build Stage**: Creates and pushes Docker images to GitLab Container Registry

For detailed CI/CD setup instructions, see [CI_CD_SETUP.md](CI_CD_SETUP.md).

### Docker Development

1. **Build the image:**
```bash
   docker build -t federation-manager .
python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
docker compose -f docker-compose.dev.yaml up -d postgres nats keycloak
uvicorn federation_manager.main:app --reload --port 8082
```

2. **Run with custom configuration:**
   ```bash
   docker run -p 8989:8989 \
     -v ./src/conf/config.cfg:/usr/app/src/conf/config.cfg \
     federation-manager
   ```
### Tests

3. **Development with docker-compose:**
```bash
   docker-compose -f docker-compose.yaml up --build
pytest                      # everything
pytest -m "not integration" # unit tests only, no infrastructure needed
ruff check src tests && mypy
```

### Environment Variables

Key configuration options (set in `src/conf/config.cfg`):
Integration tests need the dev stack above. The FM ↔ SRM loop test additionally needs a running
SRM and skips without one; see `docs/running-srm.md`.

- **Server**: Host, port, and API settings
- **Database**: MongoDB connection parameters
- **Keycloak**: Authentication service configuration
- **Logging**: Log levels and output configuration
## Configuration

## Deployment
Settings are environment variables with the `FM_` prefix, read by `federation_manager.core.config`.

### Production Deployment
| Variable | Default | Purpose |
|---|---|---|
| `FM_POSTGRES_URL` | `postgresql+asyncpg://fm:fm@localhost:5433/fm_db` | `fm_db` connection |
| `FM_NATS_URL` | `nats://localhost:4222` | DataBus |
| `FM_KEYCLOAK_ISSUER` | `http://localhost:8090/realms/federation` | Inbound token issuer |
| `FM_FEDERATION_ID`, `FM_COUNTRY_CODE`, `FM_MCC`, `FM_MNCS` | — | Our OPG.04 identity, sent on outbound `CreateFederation` |
| `FM_PARTNER_STATUS_LINK` | — | Callback URL advertised to partners |
| `FM_PLATFORM_CAPS` | `["serviceAPIs"]` | Capabilities advertised on federation setup |
| `FM_BOOTSTRAP_FEDERATION` | `false` | Federate with every active partner at startup (ADR-0043) |
| `FM_EVENT_CONSUMER_DURABLE` | `fm-event-worker` | JetStream durable name; must differ per deployment |
| `FM_ALLOW_INSECURE_PARTNER_ENDPOINTS` | `false` | Permit plain-HTTP partner endpoints. **Development only** |

1. **Using Docker Compose:**
   ```bash
   # Pull latest images
   docker-compose pull
## API

   # Start services
   docker-compose up -d
The partner-facing surface is GSMA OPG.04 v6.0, served under `/operatorplatform/federation/v1`.
Generated OpenAPI is at `/openapi.json`, with Swagger UI at `/docs`.

   # Monitor logs
   docker-compose logs -f federation-manager
   ```
`docs/` holds the vendored GSMA artifact, an OpenAPI Overlay recording its known defects, and the
generated profile used for contract checks. Regenerate with:

2. **Using Kubernetes:**
```bash
   kubectl apply -f src/deploy/
python scripts/apply_overlay.py
```

## API Documentation

### Swagger UI
Interactive API documentation is available at:
- **Local**: `http://localhost:8990/ui/`
- **API Spec**: `http://localhost:8990/openapi.yaml`

### Key Endpoints

1. **Federation Management**:
   - `POST /partner` - Create federation context
   - `GET /{federationContextId}` - Get federation details
   - `DELETE /{federationContextId}` - Terminate federation

2. **Zone Management**:
   - `POST /{federationContextId}/zones` - Register availability zones
   - `GET /{federationContextId}/zones` - List zones

3. **Application Management**:
   - `POST /{federationContextId}/artefact` - Upload application artefacts
   - `POST /{federationContextId}/applicationonboarding` - Onboard applications
   - `POST /{federationContextId}/applicationlcm` - Manage application lifecycle

### Authentication

All API endpoints require OAuth 2.0 Bearer tokens from Keycloak:

```bash
# Get access token
curl -X POST http://localhost:8080/realms/federation/protocol/openid-connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=<client_id>" \
  -d "client_secret=<client_secret>"

# Use token in API calls
curl -X GET http://localhost:8990/operatorplatform/federation/v1/health \
  -H "Authorization: Bearer <access_token>"
```
Internal, non-partner endpoints live under `/internal/`; they are not exposed on the EWBI path.

## Contributing

Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
See [CONTRIBUTING.md](CONTRIBUTING.md).

## License

This project is licensed under the [Apache 2.0 License](LICENSE).
Apache License 2.0. See [LICENSE](LICENSE).
Loading