Commit 7e8f8517 authored by Konstantinos Togias's avatar Konstantinos Togias
Browse files

docs: align TF-SDK guides and package metadata with Release 2

Describe canonical SRM requests, resolved control paths and separate topology
discovery. Replace legacy README workflows with released contract support and
current setup and verification instructions.

Add the adapter guide and request-path diagram, clarify QoD status and Helm
failure limits, and update the package summary and author contact.
parent 7769a9ad
Loading
Loading
Loading
Loading
Loading
+159 −173
Changes for README.md: 159 added lines, 173 removed lines.
Original line number Diff line number Diff line
<a href="https://labs.etsi.org/rep/oop/code/tf-sdk/-/pipelines" title="Pipeline Status">
  <img src="https://labs.etsi.org/rep/oop/code/tf-sdk/badges/main/pipeline.svg" alt="pipeline status">
</a>
</a>
<a href="https://labs.etsi.org/rep/oop/code/tf-sdk/-/blob/main/LICENSE" title="License">
  <img src="https://img.shields.io/badge/License-Apache%202.0-green.svg?style=plastic" alt="license">
</a>

# Transformation Function SDK (TF-SDK)

`openop-tfsdk` provides southbound execution and topology discovery for OpenOP.
It runs as a Python library inside the Service Resource Manager (SRM). SRM calls
it with canonical request models and an already-resolved target. Each adapter
translates that operation into calls to its configured backend and returns a
normalized result or typed failure.

Public CAMARA API handling belongs to OEG. SRM owns placement, lifecycle
decisions, the authoritative inventory and reconciliation. Cross-operator
federation and GSMA E/WBI belong to the Federation Manager (FM). The TF-SDK
execution boundary accepts SRM's normalized contract models, not raw CAMARA or
GSMA API payloads. The SDK has no standalone service, HTTP API or Pod.

This README describes the **Release 2.0.0 implementation**, distributed as
`openop-tfsdk==2.0.0` and imported as `openop_tfsdk`.

## What it does

- Execute workload lifecycle operations on Kubernetes using Helm, for container
  images and Helm charts.
- Execute the network capabilities advertised by the selected adapter.
- Read declared zones, domains, capabilities and control-path bindings from the
  bootstrap file, without probing backends.
- Derive supported actions and runtime kinds from the adapter's
  `capability_surface`.
- Return backend resource identifiers, observed state, workload endpoints and
  typed errors for SRM to record and handle.

## How SRM uses the SDK

1. At startup, SRM loads `BootstrapTopology` from the operator's bootstrap file.
   Its discovery methods return normalized topology. Runtime connection
   configuration is exposed separately.
2. SRM builds one adapter instance per control path with
   `build_adapter(adapter_type, config)`. Construction and capability discovery
   perform no backend I/O.
3. For an operation, SRM selects the target and control path, builds the
   canonical request and invokes an action advertised for that adapter.
4. The adapter checks `target.control_path_ref`, calls the configured backend
   and returns a normalized result or typed error.
5. SRM stores the returned `external_id` and supplies it for later lifecycle
   actions or runtime-state reads. The adapter holds connection configuration;
   SRM holds orchestration state.

| Boundary | Release interface |
| --- | --- |
| Execution | `activate`, `update`, `terminate`, `read_runtime_state`, `query`, as offered by each adapter |
| Topology discovery | `get_zones`, `get_domains`, `get_capabilities`, `get_control_path_bindings` |
| Adapter construction | `build_adapter(adapter_type, infrastructure)` |
| Executable capability declaration | `capability_surface(adapter_kind, capability_kind, config=None)` |

See [Contract adapter behavior](docs/ADAPTERS.md) for the request flows, SRM
execution-port names, backend calls, results and errors. The
[Release 2 specification][spec] defines the architecture; the
[support matrix][matrix] records implementation and verification limits.

## Release 2.0.0 contract support

| Adapter kind | Capability kinds | Runtime kinds / release status |
| --- | --- | --- |
| `kubernetes` | `workload` | `helm`, `container` |
| `oai` | `qod_session`, `location_context`, `traffic_influence` | EURECOM NEF client |
| `open5gs` | `qod_session`, `location_context` | Open5GS NEF client |
| `open5gcore` | `qod_session` | Open5GCore NEF client |
| `i2edge`, `i2cat_ric` | None advertised | Registered; retained legacy clients |
| `aeros`, `juniper_ric` | None | No registered contract adapter |

Inspect an adapter's offered operations without constructing it or contacting
the backend:

# TF SDK

Open source SDK to abstract CAMARA/GSMA Transformation Functions (TFs) for Edge Cloud platforms, 5G network cores and O-RAN solutions.

## Features

- Abstract CAMARA NBI & GSMA E/WBI Transformation Functions (TFs)
- Unified Python SDK for interacting with Edge Cloud platforms, 5G Core solutions, and O-RAN controllers.
- Modular and extensible adapter structure


---

## API & Platform Support Matrix

### CAMARA APIs

| API Name                  | Version |
|---------------------------|---------|
| Edge Application Management | [v0.9.3-wip (commit: 79aa595)](https://github.com/camaraproject/EdgeCloud/blob/79aa5951d64391422ea5b861ab617a9540386092/code/API_definitions/Edge-Application-Management.yaml) |
| Quality-on-Demand         | [v1.0.0](https://raw.githubusercontent.com/camaraproject/QualityOnDemand/refs/tags/r2.2/code/API_definitions/quality-on-demand.yaml) |
| Location Retrieval        | [v0.4.0](https://raw.githubusercontent.com/camaraproject/DeviceLocation/refs/tags/r2.2/code/API_definitions/location-retrieval.yaml) |
| Traffic Influence         | [v0.8.1](https://raw.githubusercontent.com/camaraproject/EdgeCloud/v0.8.1/code/API_definitions/Traffic_Influence.yaml) |

### Federation APIs (E/WBI GSMA OPG)

| API Name                  | Version |
|---------------------------|---------|
|  Federation Management Service | [1.2.0](https://www.gsma.com/solutions-and-impact/technologies/networks/wp-content/uploads/2023/07/OPG.04-v3.0-GSMA-Operator-Platform-Group-East-Westbound-Interface-APIs-Version-3.0.pdf)

### EdgeCloud Platforms

| Platform   | Supported     |
|------------|------------|
| Kubernetes | ✅  |
| i2Edge     | ✅  |
| aerOS      | ✅  |

### Network Adapters

| Platform     | NEF Version | QoD | Location Retrieval | Traffic Influence |
|--------------|-------------|-----|---------------------|--------------------|
| Open5GS      | [v1.2.3](https://www.3gpp.org/ftp/Specs/archive/29_series/29.122/29122-hc0.zip) TS 29.122 (v17.12.0) | ✅  | ✅                  |                  |
| Open5GCore   | [v1.2.3](https://www.3gpp.org/ftp/Specs/archive/29_series/29.122/29122-hc0.zip) TS 29.122 (v17.12.0) | ✅  |                   |                  |
| OAI          | [v1.2.3](https://www.3gpp.org/ftp/Specs/archive/29_series/29.122/29122-hc0.zip) TS 29.122 (v17.12.0) | ✅  |                   | ✅                 |

### O-RAN Adapters

| Platform   | Supported     |
|------------|------------|
| Juniper O-RAN controller | WIP  |
| i2CAT O-RAN controller     | ✅  |


### O-RAN Platforms
```python
from openop_tfsdk.contract.adapters import capability_surface

| Platform         | Status  | CAMARA QoD |
|------------------|---------|------------|
| i2CAT RIC & NEF  |    ✅   |    ✅     |
| Juniper          |    ❌   |    ❌     |
actions, runtime_kinds = capability_surface("kubernetes", "workload")
print([action.value for action in actions])
# ['activate', 'update', 'terminate', 'read_runtime_state']
print(runtime_kinds)
# ['helm', 'container']
```

An unsupported capability of a registered adapter returns `([], [])`. An
unregistered adapter kind raises `UnknownAdapterKind`. Retained legacy methods
do not establish Release 2 contract support.

---
The [support matrix][matrix] lists actions, field mappings, backend requirements
and lab coverage separately. Implemented support does not establish complete
public API compatibility or live qualification with every backend. Public API
versions and federation workflows belong to the OEG/FM documentation and
[platform specification][platform-spec].

## How to Use
## Installation and development

### Option 1: Install via PyPI
Use Python **3.10–3.13**. In a platform deployment, SRM installs the SDK through
its locked dependencies. The published release is available from the
[ETSI GitLab package registry][packages]. See [the release procedure](ci/RELEASE.md)
for package publication and SRM handoff.

For end users:

```bash
pip install openop-tfsdk
```

### Option 2: Development Mode

If you plan to modify the SDK:
To inspect and experiment with the released source in an isolated environment:

```bash
git clone https://labs.etsi.org/rep/oop/code/tf-sdk.git
cd tf-sdk
git checkout v2.0.0
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install -e .
```

### Basic Usage

You can use the SDK by simply specifying the adapters to be used. E.g. Edge Cloud Platform: i2Edge, 5G core: Open5Gs

Example available in [`/examples/example.py`](examples/example.py)

```python
python3 -m examples.example
. .venv/bin/activate
python -m pip install -e . pytest
```

---

## How to Contribute

We welcome contributions to the TF SDK!

To get started:

1. Fork the repository and create a branch from `main`.
2. Add your changes in the appropriate adapter directory.
3. Write or update tests for your changes.
4. Ensure all tests and pre-commit checks pass.
5. Submit a pull request with a clear description.

Please follow our full [Contributing Guidelines](docs/CONTRIBUTING.md) for further details.
Use a contribution branch for changes. The release tag is the comparison
baseline. Runtime dependencies are declared in `pyproject.toml`; the hashed
CI/development dependencies are in `ci/requirements.lock`.

---
The contract models are in
[`src/openop_tfsdk/contract/execution_models.py`](src/openop_tfsdk/contract/execution_models.py).
Contract adapter implementations and the registry are in
[`src/openop_tfsdk/contract/adapters/`](src/openop_tfsdk/contract/adapters/).
The lower-level domain clients remain implementation details behind those
adapters. [`examples/example.py`](examples/example.py) demonstrates the legacy
domain-family factory and is not a Release 2 contract usage example.

## Example Workflow #1: App deployment over Kubernetes
## Contributing and verification

```mermaid
sequenceDiagram
title Application Deployment using the TF SDK
Implement or extend the normalized contract adapter and its capability surface,
with tests for the supported actions, result/error mapping and control-path
isolation. Extend SRM integration when introducing a new capability or operation;
public API changes also require the corresponding OEG/FM work.

actor AP as Application Vertical Provider
box Module implementing CAMARA APIs
    participant API as CAMARA Edge Application Management API
    participant SDK as TF SDK
end
participant K8s as Kubernetes
Run focused offline contract tests during development:

note over SDK: [Config] Edge Cloud platform: Kubernetes, IP, Port
API ->> SDK: from openop_tfsdk import Sdk as sdkclient
API ->> SDK: sdkclient.create_adapters_from(configuration)
API ->> SDK: edgecloud_client = adapters.get("edgecloud")
SDK ->> SDK: SDK initialized and ready to be used
note over AP,API: Platform ready to receive CAMARA calls
AP ->> API: POST /app (APP_ONBOARD_MANIFEST)
API ->> SDK: edgecloud_client.onboard_app(APP_ONBOARD_MANIFEST)
SDK ->> K8s: Equivalent dedicated endpoint
AP ->> API: POST /appinstances (APP_ID, APP_ZONES)
API ->> SDK: edgecloud_client.deploy_app(APP_ID, APP_ZONES)
SDK ->> K8s: Equivalent dedicated endpoint
```

## Example Workflow #2: QoS Session Creation over Open5Gs

```mermaid
sequenceDiagram
title QoS Session Creation over Open5GS

actor AP as Application Vertical Provider
box Module implementing CAMARA APIs
    participant API as CAMARA QoS Management API
    participant SDK as TF SDK
end
participant NEF as NEF
participant 5GS as Open5GS

note over SDK: [Config] Network core: Open5Gs, IP, Port
API ->> SDK: from openop_tfsdk import Sdk as sdkclient
API ->> SDK: sdkclient.create_adapters_from(configuration)
API ->> SDK: network_client = adapters.get("network")
SDK ->> SDK: SDK initialized and ready to be used
note over AP,API: Platform ready to receive CAMARA calls
AP ->> API: POST /sessions (QOS_SESSION_REQUEST)
API ->> SDK: network_client.create_qos_session(QOS_SESSION_REQUEST)
SDK ->> NEF: Equivalent endpoint
NEF ->> 5GS: QoS session creation
```bash
python -m pytest tests/contract -q
```
---

## Roadmap for the TF SDK 2nd release
For the locked repository checks, install the CI dependencies and build before
running the wheel-based tests:

- [ ] Add support to GSMA OPG.02 TFs
- [x] Include JUNIPER O-RAN adapter

---
```bash
python -m pip install --require-hashes -r ci/requirements.lock
python ci/verify.py lint
python ci/verify.py build
python ci/verify.py tests
```

## License
See the [contribution guide](docs/CONTRIBUTING.md),
[CI configuration](.gitlab-ci.yml) and [verification runner](ci/verify.py) for
the repository process and additional checks. Offline tests and package checks
do not replace backend or platform E2E acceptance; record the exact inputs and
scope of any live verification in the [support matrix][matrix].

Apache 2.0 License – see [`LICENSE`](LICENSE) file for details.
# Kubernetes control-path connections
## Kubernetes control-path connections

Configure each adapter explicitly. File mode requires an absolute mounted file,
its context and a namespace:

```python
from openop_tfsdk.contract.adapters import build_adapter

adapter = build_adapter("kubernetes", {
    "control_path_ref": "k8s-athens",
    "kubeconfig_path": "/etc/openop/kubeconfigs/athens.yaml",
@@ -237,11 +208,11 @@ is implemented in the SDK.
Every contract execution adapter requires a nonempty `control_path_ref` in its
construction config. SRM derives it from the registry entry key; the operator
declares that identity once in the bootstrap control-path ref. Direct SDK
callers must also supply it. Missing construction identity raises ValidationError.
callers must also supply it. Missing construction identity raises `ValidationError`.

Every implemented lifecycle, query and runtime-state method checks the resolved
request's `target.control_path_ref` before loading credentials or contacting a
backend. A mismatch raises non-retryable InvalidTargetRef. This applies to
backend. A mismatch raises non-retryable `InvalidTargetRef`. This applies to
direct concrete adapter calls as well as factory construction. The check uses
an opaque identity; topology relationships and placement remain in SRM. It
does not establish authorization or the provenance of an arbitrary backend ID.
@@ -259,7 +230,9 @@ Duplicate/missing refs, malformed entries, unknown kinds and unresolved binding
refs fail locally. Discovery does not construct execution adapters or resolve
credentials. Keep runtime config out of normalized responses and persisted
topology. The deployment owns mounts; the SDK does not parse a secret URI.
See `tests/contract/bootstrap_example.yaml` for an explicit file-mode example.
See [the bootstrap example](tests/contract/bootstrap_example.yaml) for an explicit
file-mode example. Its declarations are filtered through each adapter's capability
surface; a declared capability with no advertised actions is not executable.

## OAI QoD provider profiles

@@ -285,22 +258,24 @@ are opaque provider names: they reach the NEF unchanged, with no inferred aliase
or QoS policy mapping. Configure the actual names and policies offered by that
NEF; the example does not assert a universal OAI default.

Omitting qos_profiles applies no local allowlist: syntactically valid profile
Omitting `qos_profiles` applies no local allowlist: syntactically valid profile
identifiers reach the NEF unchanged. The NEF decides whether a profile exists
and can be applied. There are no built-in provider profile names. An explicit
null, empty, duplicate or malformed list fails adapter construction with
ValidationError. Requests outside a configured list fail before HTTP. Each
`null`, empty, duplicate or malformed list fails adapter construction with
`ValidationError`. Requests outside a configured list fail before HTTP. Each
control path keeps its own optional restriction. Backend refusals remain typed
execution failures even for profiles allowed locally. The
existing notification destination requirement still applies; configuring profiles
does not implement a receiver or establish data-plane QoS guarantees.

Network DomainExecutionFailed errors include the existing allowlisted backend
status/reason diagnostics in details when available. HTTP400 carries
Network `DomainExecutionFailed` errors include the existing allowlisted backend
status/reason diagnostics in `details` when available. HTTP 400 carries
`{"backend_status": 400, "reason": "bad_request"}`; transport errors carry a safe
reason. Raw response bodies, URLs and exception text are not copied. Error
classes, retryable flags and wrapped HTTP 404 classification are unchanged.
SRM's existing bridge can preserve these details without a new contract field.
The [support matrix's diagnostic limits][diagnostics] distinguish these safe
details from the logging behavior of retained legacy clients.

## Helm workload endpoints

@@ -310,14 +285,25 @@ only its NodePort Services in the configured namespace through the same explicit
control-path connection. Service names need not equal the Helm release name.

Declare external interfaces in the deployment unit's resource requirements and
configure the control path's reachable endpoint_host. An interface matches the
observed Service's numeric targetPort and protocol; omitted targetPort defaults
configure the control path's reachable `endpoint_host`. An interface matches the
observed Service's numeric `targetPort` and protocol; omitted `targetPort` defaults
to the Service port. Each interface must have exactly one matching allocated
NodePort across the release. The returned port is the allocated nodePort, not
the application's listening port. Service names are read from the release; ambiguous matches are not selected.
NodePort across the release. The returned port is the allocated `nodePort`, not
the application's listening port. Service names are read from the release;
ambiguous matches are not selected.

Missing, ambiguous or unavailable observation preserves ACTIVE and the installed
release handle, with endpoints absent and a sanitized warning. Named targetPort,
Missing, ambiguous or unavailable observation preserves `active` and the installed
release handle, with endpoints absent and a sanitized warning. Named `targetPort`,
Ingress, LoadBalancer and automatic endpoint refresh on update/runtime reads are
outside this bounded observation path. A DNS endpoint_host also needs the
outside this bounded observation path. A DNS `endpoint_host` also needs the
consumer's address-resolution support before it can serve an IPv4-only TI flow.

## License

Apache 2.0 License — see [LICENSE](LICENSE).

[spec]: https://labs.etsi.org/rep/oop/architecture/-/tree/v2.0.0-release/tf-sdk
[platform-spec]: https://labs.etsi.org/rep/oop/architecture/-/blob/v2.0.0-release/platform/architecture.md
[matrix]: https://labs.etsi.org/rep/oop/deploy/ansible/-/blob/v2.0.0-release/docs/releases/2.0.0/tf-sdk-support-matrix.md
[diagnostics]: https://labs.etsi.org/rep/oop/deploy/ansible/-/blob/v2.0.0-release/docs/releases/2.0.0/tf-sdk-support-matrix.md#8-errors-and-diagnostic-limits
[packages]: https://labs.etsi.org/rep/oop/code/tf-sdk/-/packages

docs/ADAPTERS.md

0 → 100644
+284 −0

File added.

Preview size limit exceeded, changes collapsed.

+11 −0

File added.

Preview size limit exceeded, changes collapsed.

+2 −2
Changes for pyproject.toml: 2 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "openop-tfsdk"
version = "2.0.0"
description = "Open source SDK to abstract CAMARA/GSMA Transformation Functions (TFs) for Edge Cloud platforms, 5G network cores and Open RAN solutions."
description = "Southbound execution and topology discovery library for the OpenOP Service Resource Manager."
keywords = [
  "Federation",
  "Transformation Functions",
@@ -24,7 +24,7 @@ keywords = [
  "O-RAN",
]
authors = [
  { name="Adrian Pino", email="adrian.pino@i2cat.net" }
  { name="Konstantinos Togias", email="ktogias@isi.gr" }
]
readme = "README.md"
requires-python = ">=3.10,<3.14"