diff --git a/.gitignore b/.gitignore index 48994615750efecb315c637f864759e759aaf747..605a61c66ad318e3780b642684e7486f514933fe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,12 @@ # Folders .idea venv +.venv +__pycache__/ +*.egg-info/ +.mypy_cache/ +.ruff_cache/ +.pytest_cache/ src/__pycache__/ src/models/__pycache__/ src/controllers/__pycache__/ diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 4a81844bb5fcd21d024a9f71ed5c5b34cec32026..0000000000000000000000000000000000000000 --- a/AGENTS.md +++ /dev/null @@ -1,297 +0,0 @@ -# Agent Guidelines for Federation Manager - -This document provides coding agents with essential information for working in the Federation Manager codebase. - -## Project Overview - -**Federation Manager** is a Python-based component implementing Federation Management functionality for the GSMA Operator Platform (OP). It supports dual-role architecture: -- **Partner OP Mode**: Handles external federation partner requests (without `X-Internal` header) -- **Originating OP Mode**: Handles internal service requests (with `X-Internal` and `X-Partner-API-Root` headers) - -## Build/Run Commands - -### Setup -```bash -# Create virtual environment -python -m venv venv -source venv/bin/activate # On Windows: venv\Scripts\activate - -# Install dependencies -pip install -r requirements.txt - -# Configure application -cp src/conf/config.cfg.sample src/conf/config.cfg -# Edit config.cfg with your specific settings -``` - -### Run Application -```bash -# Local development -cd src/ -python main.py - -# Using Docker Compose (recommended) -docker compose up -d - -# Build Docker image -docker build -t federation-manager . -``` - -### Testing - -**Run all tests:** -```bash -cd src/ -python test/run_all_tests.py --verbose --coverage -``` - -**Run specific test module:** -```bash -cd src/ -python -m unittest test.test_federation_management -v -``` - -**Run single test case:** -```bash -cd src/ -python -m unittest test.test_federation_management.TestFederationManagementController.test_01_create_federation -v -``` - -**Test execution order** (tests run sequentially): -1. `test_federation_management` -2. `test_availability_zone_info_synchronization` -3. `test_artefact_management` -4. `test_application_onboarding_management` -5. `test_application_deployment_management` - -**Coverage reports:** -- Terminal output with coverage percentage -- HTML report in `src/htmlcov/` directory - -### Linting -```bash -# No formal linting configured, but PEP8 style is followed -# Use flake8 or pylint manually if needed -flake8 src/ --max-line-length=120 -``` - -## Architecture - -``` -API Layer (src/api/) - ↓ -Adapter Layer (src/adapters/) - ├── tf_adapter/ (Partner OP mode - external requests) - └── fm_adapter/ (Originating OP mode - internal requests) - ↓ -Client Layer (src/clients/) - ├── fed_manager.py (Federation Manager client) - └── tf_sdk.py (Edge Cloud Platform SDK) -``` - -**Request routing** via `adapters.injector.resolve_adapter()`: -- Without `X-Internal` header → `tf_adapter` (Partner OP) -- With `X-Internal` header → `fm_adapter` (Originating OP) - -## Code Style Guidelines - -### General Python Standards -- Follow **PEP8** coding conventions -- Python **3.12+** required -- Use **snake_case** for functions and variables -- Use **PascalCase** for class names -- Maximum line length: **120 characters** (flexible) - -### Imports -```python -# Standard library imports (absolute) -from __future__ import absolute_import -import os -import sys -from configparser import ConfigParser -from datetime import date, datetime # noqa: F401 - -# Third-party imports -import connexion -from flask import abort, render_template -from flask_mongoengine import MongoEngine - -# Local application imports -from adapters.injector import resolve_adapter -from adapters.error import APIError -from models.federation_request_data import FederationRequestData # noqa: E501 -import util -``` - -**Import ordering:** -1. `from __future__` imports (if needed for compatibility) -2. Standard library imports -3. Third-party library imports -4. Local application imports - -**Import conventions:** -- Use explicit imports from packages -- Long model imports are acceptable with `# noqa: E501` to suppress line length warnings -- Use `# noqa: F401` for imports needed only for type hints - -### File Headers -All Python files must include the Apache 2.0 license header: -```python -# -------------------------------------------------------------------------- # -# 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. # -# -------------------------------------------------------------------------- # -``` - -### Type Hints -```python -# Function signatures with type hints -def create_federation(body: FederationRequestData) -> FederationResponseData: - """Creates one direction federation with partner operator platform.""" - pass - -# Model classes use swagger_types for type information -class CountryCode(Model): - def __init__(self): - self.swagger_types = {} - self.attribute_map = {} -``` - -### Docstrings -```python -def create_federation(body): # noqa: E501 - """Creates one direction federation with partner operator platform. - - # noqa: E501 - - :param body: - :type body: dict | bytes - - :rtype: FederationResponseData - """ - pass -``` - -### Configuration Management -```python -# Use ConfigParser for configuration -from configparser import ConfigParser - -CONFIG = ConfigParser() -config_file = os.environ.get("FM_CONFIG_FILE", "conf/config.cfg") -CONFIG.read(config_file) -HOST = CONFIG.get("server", "host") -PORT = int(CONFIG.get("server", "port")) -``` - -### Error Handling -```python -# Use custom APIError for API errors -from adapters.error import APIError - -try: - adapter = resolve_adapter(headers) - return adapter.federation_management.create_federation(body, bearer_token, partner_api_root) -except APIError as error: - abort(error.status_code, error.detail_error) - -# Use abort() for HTTP errors -from flask import abort - -try: - body = FederationRequestData.from_dict(connexion.request.get_json()) -except Exception as error: - abort(422, f"Federation Validation Error. Message: {error}") -``` - -### Testing Conventions -```python -# Test classes inherit from BaseTestCase -from test import BaseTestCase - -class TestFederationManagementController(BaseTestCase): - """FederationManagementController integration test stubs""" - - BaseTestCase.federation_context_id_partner = "" - BaseTestCase.token = "" - - def test_01_create_federation(self): - """Test case for create_federation in both Partner OP and Originating OP modes""" - # Test both roles - pass -``` - -**Dual role testing:** -- All integration tests must validate both Partner OP and Originating OP modes -- Use `make_request_partner_op()` for external requests (no `X-Internal` header) -- Use `make_request_originating_op()` for internal requests (with `X-Internal` header) - -### Naming Conventions -- **Functions/Methods**: `snake_case` (e.g., `create_federation`, `get_federation_details`) -- **Classes**: `PascalCase` (e.g., `FederationRequestData`, `BaseTestCase`) -- **Constants**: `UPPER_CASE` (e.g., `CONFIG`, `HOST`, `PORT`) -- **Variables**: `snake_case` (e.g., `bearer_token`, `partner_api_root`) -- **Test methods**: Prefix with `test_` and optionally number for execution order (e.g., `test_01_create_federation`) - -## Key Files and Locations - -- **Application entry**: `src/main.py` -- **API endpoints**: `src/api/` -- **Business logic**: `src/adapters/fm_adapter/` and `src/adapters/tf_adapter/` -- **Client SDKs**: `src/clients/` -- **Data models**: `src/models/` -- **Tests**: `src/test/` -- **Configuration**: `src/conf/config.cfg` -- **OpenAPI spec**: `src/swagger/swagger.yaml` - -## Development Workflow - -1. **Before making changes**: Review the dual-role architecture to understand request routing -2. **Configuration**: Always use `ConfigParser` to read from `conf/config.cfg` -3. **Adding endpoints**: Update `src/swagger/swagger.yaml` first, then implement in `src/api/` -4. **Adding business logic**: Implement in both adapters if applicable (fm_adapter and tf_adapter) -5. **Testing**: Write integration tests that validate both Partner OP and Originating OP modes -6. **Documentation**: Update docstrings and inline comments; avoid creating unnecessary markdown files - -## Common Patterns - -### Request Header Extraction -```python -bearer_token = util.get_token_from_request(connexion) -headers = dict(connexion.request.headers) -partner_api_root = headers.get("X-Partner-Api-Root") -``` - -### Adapter Resolution -```python -from adapters.injector import resolve_adapter - -adapter = resolve_adapter(headers) # Returns tf_adapter or fm_adapter based on X-Internal header -return adapter.federation_management.create_federation(body, bearer_token, partner_api_root) -``` - -### Model Deserialization -```python -body = FederationRequestData.from_dict(connexion.request.get_json()) -``` - -## Notes for AI Agents - -- **Never skip the Apache 2.0 license header** when creating new files -- **Always test both dual roles** when modifying API or adapter code -- **Configuration changes** should be documented and may require updates to `config.cfg.sample` -- **Docker builds** use `gunicorn` as the WSGI server (see `Dockerfile`) -- **Authentication** via OAuth 2.0 with Keycloak integration -- **Database**: MongoDB with MongoEngine ORM -- **API Framework**: Connexion (Flask-based) with OpenAPI/Swagger specifications diff --git a/CI_CD_SETUP.md b/CI_CD_SETUP.md deleted file mode 100644 index 0d998000ae88d8f0dd84a4bbad1793ae3eb488a8..0000000000000000000000000000000000000000 --- a/CI_CD_SETUP.md +++ /dev/null @@ -1,140 +0,0 @@ -# GitLab CI/CD Setup for Federation Manager - -This document explains how to set up and use the GitLab CI/CD pipeline for the Federation Manager project. - -## Overview - -The CI/CD pipeline includes a single stage with two different build jobs: -1. **Build (MR Validation)** - Builds Docker images for merge requests without pushing to registry -2. **Build and Push** - Builds Docker images and pushes them to the GitLab Container Registry for main branches - -## Prerequisites - -### GitLab Configuration - -1. **Container Registry**: Ensure GitLab Container Registry is enabled for your project -2. **CI/CD Variables**: Configure the following variables in GitLab (Settings > CI/CD > Variables): - - | Variable | Description | Required | - |----------|-------------|----------| - | `CI_REGISTRY` | GitLab Container Registry URL | Auto-provided | - | `CI_REGISTRY_USER` | Registry username | Auto-provided | - | `CI_REGISTRY_PASSWORD` | Registry password | Auto-provided | - -### Repository Setup - -1. Ensure your GitLab project has a Container Registry enabled -2. The pipeline uses the project's Container Registry by default -3. Docker images will be tagged with commit SHA and 'latest' - -## Pipeline Stages - -### Build Stage - -The build stage contains two different jobs depending on the trigger: - -#### MR Build (build-mr) -- **Trigger**: On merge requests -- **Actions**: - - Builds Docker image for validation - - Tags with local name: `federation-manager:$CI_COMMIT_SHORT_SHA` - - **Does NOT push to registry** (validation only) - - No registry authentication required - -#### Build and Push (build-and-push) -- **Trigger**: On main, develop branches, and tags -- **Actions**: - - Builds Docker image - - Tags with commit SHA and 'latest' - - Pushes to GitLab Container Registry - - Requires registry authentication - -## Docker Image Information - -### Image Naming Convention - -#### Registry Images (build-and-push job) -- Registry: `$CI_REGISTRY_IMAGE/federation-manager` -- Tags: - - `latest` - Latest build from main/develop branches - - `$CI_COMMIT_SHORT_SHA` - Specific commit hash - -#### Local Images (build-mr job) -- Local name: `federation-manager:$CI_COMMIT_SHORT_SHA` -- **Note**: These images are built locally for validation only and are NOT pushed to the registry - -### Base Image -- Python 3.12 official image -- Includes system dependencies: bash, build-essential, git, wget, iptables, etc. -- Exposes port 8989 -- Runs with Gunicorn (4 workers) - -## Usage - -### Triggering Builds - -1. **Automatic triggers**: - - **MR Build**: Create or update merge requests (builds for validation only) - - **Build and Push**: Push to main or develop branches, create tags - -2. **Manual triggers**: - - Go to CI/CD > Pipelines in GitLab - - Click "Run Pipeline" - - Select branch/tag - -### Build Types - -#### Merge Request Validation -- **Purpose**: Validate that Docker builds work correctly before merging -- **Benefit**: Early feedback without affecting registry or consuming unnecessary resources -- **Output**: Local Docker image (not stored in registry) -- **Time**: Faster execution (no registry operations) - -#### Production Builds -- **Purpose**: Create deployable images for main branches and releases -- **Output**: Images pushed to GitLab Container Registry -- **Accessibility**: Available for deployment and distribution - -### Accessing Docker Images - -**Note**: Only images from the `build-and-push` job are available in the registry. MR validation builds are local only. - -1. **Pull from registry** (production images only): - ```bash - docker login registry.gitlab.com - docker pull $CI_REGISTRY_IMAGE/federation-manager:latest - ``` - -2. **Use in docker-compose**: - ```yaml - services: - federation-manager: - image: registry.gitlab.com/your-group/federation-manager/federation-manager:latest - ``` - -### Monitoring Pipeline - -- **Pipeline status**: GitLab project > CI/CD > Pipelines -- **Job logs**: Click on individual jobs to view logs -- **Coverage reports**: Available in merge request widgets -- **Container registry**: GitLab project > Packages & Registries > Container Registry - -## Troubleshooting - -### Common Issues - -1. **Docker build fails**: - - Check Dockerfile syntax - - Verify all required files are present - - Check .dockerignore doesn't exclude necessary files - -2. **Registry push fails**: - - Verify Container Registry is enabled - - Check CI/CD variables are set correctly - - Ensure sufficient permissions - -3. **Build fails**: - - Check Dockerfile syntax and build context - - Verify all required files are present and not excluded by .dockerignore - - Check Python 3.12 compatibility in requirements.txt - - Ensure base image availability diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml new file mode 100644 index 0000000000000000000000000000000000000000..1083be34e12bade62dc79ac083f91c3a17d7117e --- /dev/null +++ b/docker-compose.dev.yaml @@ -0,0 +1,44 @@ +services: + postgres: + image: postgres:16-alpine + container_name: fm-postgres + environment: + POSTGRES_USER: fm + POSTGRES_PASSWORD: fm + POSTGRES_DB: fm_db + ports: + - "5433:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U fm -d fm_db"] + interval: 3s + timeout: 3s + retries: 20 + volumes: + - fm-pgdata:/var/lib/postgresql/data + + nats: + image: nats:2.10-alpine + container_name: fm-nats + command: ["-js"] + ports: + - "4222:4222" + + keycloak: + image: quay.io/keycloak/keycloak:26.1.4 + container_name: fm-keycloak + command: ["start-dev", "--import-realm"] + environment: + KC_BOOTSTRAP_ADMIN_USERNAME: admin + KC_BOOTSTRAP_ADMIN_PASSWORD: admin + ports: + - "8090:8080" + volumes: + - ./keycloak:/opt/keycloak/data/import + healthcheck: + test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/8080"] + interval: 3s + timeout: 3s + retries: 30 + +volumes: + fm-pgdata: diff --git a/docs/OPG.04-v6.0-EWBI-Federation-API-v1.4.0-oop-profile.yaml b/docs/OPG.04-v6.0-EWBI-Federation-API-v1.4.0-oop-profile.yaml new file mode 100644 index 0000000000000000000000000000000000000000..a792d8a7a7ff13f0f543437ce2fb945d72e32315 --- /dev/null +++ b/docs/OPG.04-v6.0-EWBI-Federation-API-v1.4.0-oop-profile.yaml @@ -0,0 +1,7843 @@ +# GENERATED by scripts/apply_overlay.py — do not edit. +# base: OPG.04-v6.0-EWBI-Federation-API-v1.4.0.yaml (sha256 890801d61c148762897f18ce3b88823c0d486b1defdd04227f6a65f97c62fccf) +# overlay: opg04-v1.4.0-oop-profile.overlay.yaml (version 0.1.0, 15 actions) +openapi: 3.0.3 +info: + version: 1.4.0 + title: Federation Management Service + description: "# Introduction\n---\nRESTful APIs that allow an OP to share the edge cloud resources and\ + \ capabilities securely to other partner OPs over E/WBI.\n\n---\n# API Scope\n\n---\nAPIs defined\ + \ in this version of the specification can be categorized into the following areas:\n* __FederationAPIManagement__\ + \ - Retrieves federation resources and methods a partner OP support on E/WBI\n* __FederationManagement__\ + \ - Create and manage directed federation relationship with a partner OP\n* __AvailabilityZoneInfoSynchronization__\ + \ - Management of resources of partner OP zones and status updates\n* __ArtefactManagement__ - Upload,\ + \ remove, retrieve and update application descriptors, charts and packages over E/WBI towards a partner\ + \ OP\n\n* __FileManagement__ - Upload, remove, retrieve and update application binaries over E/WBI\ + \ towards a partner OP\n* __ApplicationOnboardingManagement__ - Register, retrieve, update and remove\ + \ applications over E/WBI towards a partner OP\n* __ApplicationDeploymentManagement__ - Create, update,\ + \ retrieve and terminate application instances over E/WBI towards a partner OP\n* __AppProviderResourceManagement__\ + \ - Static resource reservation for an application provider over E/WBI for partner OP zones\n* __EdgeNodeSharing__\ + \ - Edge discovery procedures towards partner OP over E/WBI.\n* __ServiceAPIManagement__ - Service\ + \ APIs capability sharing, forwarding, notification and API context management \n* __SubscribeMonitoringInfo__\ + \ - The Originating OP subscribe for receiving the resource utilization reports periodically from\ + \ the partner OP for existing federation\n* __FaultManagement__ - The Partner OP performs the alarm\ + \ reporting and clearances to the Originating OP on existing federation\n* __EventsReporting__ - The\ + \ Partner OP notifies the detection of events as created by the Originating OP on the existing federation\n\ + * __NetworkEventsReporting__ - The Partner OP notifies the network events applied for offered network\ + \ capabilities on the existing federation\n* __ApplicationEventsReporting__ - The Partner OP notifies\ + \ the applications events of the federated applications\n* __ApplicationPolicyManagement__ - The application-level\ + \ policy requested by Originating OP for federated applications\n* __OperationPolicyManagement__ -\ + \ The operation-level policy requested by Originating OP for federated edge cloud resources\n\n---\n\ + # Definitions\n---\nThis section provides definitions of terminologies commonly referred to throughout\ + \ the API descriptions.\n\n* __Accepted Zones__ - List of partner OP zones, which the originating\ + \ OP has confirmed to use for its edge applications\n* __Anchoring__ - Partner OP capability to serve\ + \ application clients (still in their home location) from application instances running on partner\ + \ zones.\n* __Application Provider__ - An application developer, onboarding his/her edge application\ + \ on a partner operator platform (MEC).\n* __Artefact__ - Descriptor, charts or any other package\ + \ associated with the application.\n* __Availability Zone__ - Zones that partner OP can offer to share\ + \ with originating OP.\n* __Device__ - Refers to user equipment like mobile phone, tablet, IOT kit,\ + \ AR/VR device etc. In context of MEC users use these devices to access edge applications\n* __Directed\ + \ Federation__ - A Federation between two OP instances A and B, in which edge compute resources are\ + \ shared by B to A, but not from A to B.\n* __Edge Application__ - Application designed to run on\ + \ MEC edge cloud\n* __Edge Discovery Service__ - Partner OP service responsible to select most optimal\ + \ edge( within partner OP) for edge application instantiation. Edge discovery service is defined as\ + \ HTTP based API endpoint identified by a well-defined FQDN or IP.\n* __E/WBI__ - East west bound\ + \ interface.\n* __Federation__ - Relationship among member OPs who agrees to offer services and capabilities\ + \ to the application providers and end users of member OPs\n* __FederationContextId__ - Partner OP\ + \ defined string identifier representing a certain federation relationship.\n* __Federation Identifier__\ + \ - Identify an operator platform in federation context.\n* __FileId__ - An OP defined string identifier\ + \ representing a certain application image uploaded by an application provider\n* __Flavour__ - A\ + \ group of compute, network and storage resources that can be requested or granted as a single unit\n\ + * __FlavourIdentifier__ - An OP defined string identifier representing a set of compute, storage and\ + \ networking resources\n* __Home OP__ - Used in federation context to identify the OP with which the\ + \ application developers or user clients are registered.\n* __Home Routing__ - Partner OP capability\ + \ to direct roaming user client traffic towards application instances running on home OP zones.\n\ + * __Instance__ - Application process running on an edge\n* __LCM Service__ - Partner OP service responsible\ + \ for life cycle management of edge applications. LCM service is defined as HTTP based API endpoint\ + \ identified by a well-defined FQDN or IP.\n* __Offered Zones__ - Zones that partner OP offer to share\ + \ to the Originating OP based on the prior agreement and local configuration.\n* __Onboarding__ -\ + \ Submitting an application to MEC platform\n* __OP__ - Operator platform.\n* __OperatorIdentifier__\ + \ - String identifier representing the owner of MEC platform. Owner could be an enterprise, a TSP\ + \ or some other organization\n* __Originating OP__ - The OP when initiating the federation creation\ + \ request towards the partner OP is defined as the Originating OP\n* __Partner OP__ - Operator Platform\ + \ which offers its Edge Cloud capabilities to the other Operator Platforms via E/WBI.\n* __Resource__\ + \ - Compute, networking and storage resources.\n* __Resource Pool__ - A group of compute, networking\ + \ and storage resources. Application provider pre-reserve resources on partner OP zone, these resources\ + \ are reserved in terms of flavours.\n* __ZoneIdentifier__ - An OP defined string identifier representing\ + \ a certain geographical or logical area where edge resources and services are provided\n* __Zone\ + \ Confirmation__ - Procedure via which originating OP acknowledges partner OP about the partner zones\ + \ it wishes to use.\n* __User Clients__ - Lightweight client applications used to access edge applications.\ + \ Application users run these clients on their devices (UE, IOT device, AR/VR device etc)\n* __ServiceAPIManagement__\ + \ - Service APIs capability sharing, forwarding, notification and API context management\n\n---\n\ + # API Operations\n---\n\n__FederationManagement__\n* __CreateFederation__ - Creates a directed federation\ + \ relationship with a partner OP\n* __GetFederationDetails__ - Retrieves details about the federation\ + \ relationship with the partner OP. The response shall provide info about the zones offered by the\ + \ partner, partner OP network codes, information about edge discovery and LCM service etc.\n* __DeleteFederationDetails__\ + \ - Remove existing federation with the partner OP\n* __NotifyFederationUpdates__ - Call back notification\ + \ used by partner OP to update originating OP about any change in existing federation relationship\n\ + * __UpdateFederation__ - API used by the Originating OP towards the partner OP, to update the parameters\ + \ associated to the existing federation\n* __QueryFederationContext__ - The Originating OP retrieves\ + \ federationContextId from the partner OP\n* __HealthCheckFederation__ - The Originating OP sends\ + \ health check message to the partner OP to check the health of the the existing federation\n* __RenewFederation__\ + \ - The Originating OP requests the partner OP to renew the existing federation relationship\n* __GetNetworkCapabilities__\ + \ - The Originating OP requests the partner OP to share the offered network capabilities information\ + \ \n\n__AvailabilityZoneInfoSynchronization__\n* __ZoneSubscribe__ - Informs partner OP that\ + \ originating OP is willing to access the specified zones and partner OP shall reserve compute and\ + \ network resources for these zones.\n* __ZoneUnsubscribe__ - Informs partner OP that originating\ + \ OP will no longer access the specified partner OP zone.\n* __GetZoneData__ - Retrieves details about\ + \ the computation and network resources that partner OP has reserved for an partner OP zone.\n* __Notify\ + \ Zone Information__ - Call back notification used by partner OP to update originating OP about changes\ + \ in the resources reserved on a partner zone.\n\n__ArtefactManagement__\n* __UploadArtefact__ - Uploads\ + \ application artefact on partner operator platform.\n* __RemoveArtefact__ - Removes an artefact from\ + \ partner operator platform.\n* __GetArtefact__ - Retrieves details about an artefact from partner\ + \ operator platform.\n* __UploadFile__ Upload application binaries to partner operator platform\n\ + * __RemoveFile__ - Removes application binaries from partner operator platform\n* __ViewFile__ - Retrieves\ + \ details about binaries associated with an application from partner operator platform\n\n__ApplicationOnboardingManagement__\n\ + * __OnboardApplication__ - Submits an application details to a partner OP. Based on the details provided,\ + \ partner OP shall do bookkeeping, resource validation and other pre-deployment operations\n* __UpdateApplication__\ + \ - Updates partner OP about changes in application compute resource requirements, QOS Profile, associated\ + \ descriptor or change in associated components\n* __DeboardApplication__ - Removes an application\ + \ from partner OP\n* __ViewApplication__ - Retrieves application details from partner OP\n* __OnboardExistingAppNewZones__\ + \ - Make an application available on new additional zones\n* __LockUnlockApplicationZone__ - Forbid\ + \ or permit instantiation of application on a zone\n\n__Application Instance Lifecycle Management__\n\ + * __InstallApp__ - Instantiates an application on a partner OP zone.\n* __GetAppInstanceDetails__\ + \ - Retrieves an application instance details from partner OP.\n* __RemoveApp__ - Terminate an application\ + \ instance on a partner OP zone.\n* __GetAllAppInstances__ - Retrieves details about all instances\ + \ of the application running on partner OP zones.\n\n\n__AppProviderResourceManagement__\n* __CreateResourcePools__\ + \ - Reserves resources (compute, network and storage) on a partner OP zone. ISVs registered with\ + \ home OP reserves resources on a partner OP zone.\n* __UpdateISVResPool__ - Updates resources reserved\ + \ for a pool by an ISV\n* __ViewISVResPool__ - Retrieves the resource pool reserved by an ISV\n* __RemoveISVResPool__\ + \ - Deletes the resource pool reserved by an ISV\n\n\n__EdgeNodeSharing__\n*__GetCandidateZones__\ + \ - Edge discovery procedures towards partner OP over E/WBI. Originating OP request partner OP to\ + \ provide a list of candidate zones where an application instance can be created.\n\n__ServiceAPIManagement__\n\ + *__ServiceAPIRequestForwarding__ - Forward the NBI Service API requests to Partner OP over E/WBI.\n\ + *__RemoveServiceAPISession__ - Remove the existing Service API session with Partner OP over E/WBI.\n\ + *__ServiceAPIRequestForwarding__ - Retrieve Service API session context with Partner OP over E/WBI.\n\ + \n__ConsumptionReportingManagement__\n*__SubscribeForResourceConsumption__ - Originating OP Subscription\ + \ for edge resource consumption reporting by Partner OP over E/WBI.\n\n__EventManagement__\n*__SubscribeForEventNotifications__\ + \ - Originating OP Subscription for edge services related events reporting by Partner OP over E/WBI.\n\ + \n__Alarm Management__\n*__SubscribeForAlarmManagement__ - Originating OP Subscription for reporting\ + \ of alarms by Partner OP over E/WBI.\n\n__Network Capabilities Event Management__\n*__SubscribeForNetworkCapabilitiesNotifications__\ + \ - Originating OP Subscription for reporting of network events for application of network capabilities\ + \ by Partner OP over E/WBI.\n\n\n__Applications Event Notifications Management__\n*__SubscribeForApplicationEventsNotifications__\ + \ - Originating OP Subscription for reporting of application-level events by Partner OP over E/WBI.\n\ + \n\n© 2024 GSM Association.\nAll rights reserved.\n" +externalDocs: + description: GSMA, E/WBI APIs v1.4.1 + url: http://www.xxxx.com +servers: +- url: '{apiRoot}/operatorplatform/federation/v1' + variables: + apiRoot: + default: https://operatorplatform.com +security: +- oAuth2ClientCredentials: + - fed-mgmt +- notifClientCredentials: + - fed-mgmt-notif +components: + securitySchemes: + oAuth2ClientCredentials: + type: oauth2 + flows: + clientCredentials: + tokenUrl: /oauth2/token + scopes: + fed-mgmt: Access to the federation APIs + notifClientCredentials: + type: oauth2 + flows: + clientCredentials: + tokenUrl: /oauth2/token + scopes: + fed-mgmt-notif: Access to the federation notification APIs + schemas: + AppIdentifier: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,63}$ + description: Identifier used to refer to an application. + AppProviderId: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,63}$ + description: UserId of the app provider. Identifier is relevant only in context of this federation. + ArtefactId: + type: string + format: uuid + description: A globally unique identifier associated with the artefact. Originating OP generates + this identifier when artefact is submitted over NBI. + CountryCode: + type: string + description: ISO 3166-1 Alpha-2 code for the country of Partner operator + pattern: ^[A-Z]{2}$ + CPUArchType: + type: string + enum: + - ISA_X86 + - ISA_X86_64 + - ISA_ARM_64 + description: CPU Instruction Set Architecture (ISA) E.g., Intel, Arm etc. + InstanceIdentifier: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,62}[A-Za-z0-9]$ + description: Unique identifier generated by the partner OP to identify an instance of the application + on a specific zone. + InstanceState: + type: string + enum: + - PENDING + - READY + - FAILED + - TERMINATING + description: Running status of the application instance. + TransactionId: + description: A unique transaction id for this request in UUID format. It is used for tracking the + request + example: ab1d6gh5-79c2-3256-7hvb-d897549x40f7 + format: uuid + type: string + Ipv4Addr: + type: string + pattern: ^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$ + example: 198.51.100.1 + Ipv6Addr: + type: string + allOf: + - pattern: ^((:|(0?|([1-9a-f][0-9a-f]{0,3}))):)((0?|([1-9a-f][0-9a-f]{0,3})):){0,6}(:|(0?|([1-9a-f][0-9a-f]{0,3})))$ + - pattern: ^((([^:]+:){7}([^:]+))|((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?))$ + example: 2001:db8:85a3::8a2e:370:7334 + Fqdn: + type: string + FixedNetworkIds: + type: array + items: + type: string + description: List of network identifier associated with the fixed line network of the operator platform. + minItems: 1 + FederationContextId: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9-]*$ + readOnly: true + description: This identifier shall be provided by the partner OP on successful verification and + validation of the federation create request and is used by partner op to identify this newly created + federation context. Originating OP shall provide this identifier in any subsequent request towards + the partner op. + FederationIdentifier: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9-]*$ + description: Globally unique identifier allocated to an operator platform. This is valid and used + only in context of MEC federation interface. + FileId: + type: string + format: uuid + description: A globally unique identifier associated with the image file. Originating OP generates + this identifier when file is uploaded over NBI. + FileName: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,31}$ + description: Name of the image file. App provides specifies this name when image is uploaded on + originating OP over NBI. + FileDescription: + type: string + minLength: 8 + maxLength: 128 + description: Brief description about the image file. + FileVersionInfo: + type: string + description: File version information. + FlavourId: + type: string + description: An identifier to refer to a specific combination of compute resources + GeoLocation: + type: string + description: Latitude,Longitude as decimal fraction up to 4 digit precision + pattern: ^([-+]?)([\d]{1,2})((((\.)([\d]{1,4}))?(,)))(([-+]?)([\d]{1,3})((\.)([\d]{1,4}))?)$ + Mcc: + type: string + pattern: ^\d{3}$ + Mnc: + type: string + pattern: ^\d{2,3}$ + OnboardStatusInfo: + type: string + enum: + - PENDING + - ONBOARDED + - DEBOARDING + - REMOVED + - FAILED + description: Defines change in application status. This change could be related to application itself + or an application instance status + PoolName: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,30}[A-Za-z0-9]$ + description: ISV defined name of the resource pool. + PoolId: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,30}[A-Za-z0-9]$ + description: OP defined Identifier for the pool reserved for the ISV. It should be unique with an + OP. + Port: + type: integer + minimum: 0 + Status: + type: string + enum: + - FAILED + - TEMPORARY_FAILURE + - AVAILABLE + - LOCKED + - NOT_AVAILABLE + Uri: + type: string + Vcpu: + type: string + pattern: ^\d+((\.\d{1,3})|(m))?$ + description: Number of vcpus in whole, decimal up to millivcpu, or millivcpu format. + example: + whole: + value: 2 + decimal: + value: 0.5 + millivcpu: + value: 500m + VirtImageType: + type: string + enum: + - QCOW2 + - DOCKER + - OVA + description: Indicate if the file is Container image or VM image (QCOW2, OVA) + ZoneIdentifier: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9-]*$ + description: Human readable name of the zone. + FederationHealthInfo: + type: object + required: + - federationStatus + - federationStartTime + - numOfAcceptedZones + properties: + federationStatus: + $ref: '#/components/schemas/Status' + federationStartTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + numOfAcceptedZones: + type: string + numOfActiveAlarms: + type: string + numOfApplications: + type: string + FederationSupportedAPIs: + type: object + required: + - federationBaseAPI + - availabilityZoneAPI + - edgeApplicationAPI + - artefactAPI + - fileAPI + properties: + federationBaseAPI: + $ref: '#/components/schemas/FederationAPIResources' + availabilityZoneAPI: + $ref: '#/components/schemas/FederationAPIResources' + edgeApplicationAPI: + $ref: '#/components/schemas/FederationAPIResources' + artefactAPI: + $ref: '#/components/schemas/FederationAPIResources' + fileAPI: + $ref: '#/components/schemas/FederationAPIResources' + serviceAPIFederation: + $ref: '#/components/schemas/FederationAPIResources' + resourceMonitoringAPI: + $ref: '#/components/schemas/FederationAPIResources' + faultManagementAPI: + $ref: '#/components/schemas/FederationAPIResources' + eventManagementAPI: + $ref: '#/components/schemas/FederationAPIResources' + FederationAPINames: + type: string + enum: + - FEDERATION + - AVAILZONE + - ARTEFACT + - FILE + - SVSAPEFED + - RESMONITOR + - EVENTMGMT + - FAULTMGMT + HttpMethods: + type: string + enum: + - POST + - PUT + - PATCH + - DELETE + - GET + HttpResources: + type: object + required: + - href + - httpMethods + properties: + href: + $ref: '#/components/schemas/Uri' + httpMethods: + type: array + items: + $ref: '#/components/schemas/HttpMethods' + minItems: 1 + description: List of HTTP Methods supported for the given API category + FederationAPIResources: + type: object + required: + - name + - apiOperations + properties: + name: + $ref: '#/components/schemas/FederationAPINames' + apiOperations: + type: array + items: + $ref: '#/components/schemas/HttpResources' + minItems: 1 + description: List of HTTP Methods supported for the given API category + monitoringSubsType: + type: string + enum: + - edge_resource + - app_resource + - alarm + - all + description: Denotes types of edge resources, faults and events at partner OP to be reported to + Originating OP. + resourceSubscriptionInfo: + type: object + required: + - monitoringType + - subscriptionId + - dateAndTime + properties: + monitoringType: + $ref: '#/components/schemas/monitoringSubsType' + dateAndTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + subscriptionId: + type: string + format: uuid + description: Partner OP managed identifier for new subscription. + utilizationValue: + type: object + required: + - resType + - value + - unit + properties: + resType: + $ref: '#/components/schemas/resourceType' + value: + type: string + description: Whole number that represent the value of given resource type. + unit: + type: string + enum: + - Percent + - MBPS + - GB + - TB + - CORES + - SECONDS + - MINUTES + description: Indicate the resource measurement Unit + resourceType: + type: string + enum: + - CPU + - MEMORY + - DISK + - Network + - FLAVOUR + description: Indicate the type of resource + edgeResUtilizeMetrics: + type: object + required: + - edgeMetrics + - federationContextId + - sequenceNum + properties: + edgeMetrics: + type: array + items: + $ref: '#/components/schemas/edgeComputeMetrics' + minItems: 1 + description: List of edge cloud resource metrics per zone + federationContextId: + $ref: '#/components/schemas/FederationContextId' + sequenceNum: + type: integer + description: Monotonically increasing counter for sequencing resource monitoring reports + edgeComputeMetrics: + type: object + required: + - zoneId + - startTime + - endTime + - cpuUtil + - memUtil + - diskUtil + - networkUtil + - flavourUtil + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + startTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + endTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + cpuUtil: + $ref: '#/components/schemas/cpuUtilization' + memUtil: + $ref: '#/components/schemas/memUtilization' + diskUtil: + $ref: '#/components/schemas/diskUtilization' + networkUtil: + $ref: '#/components/schemas/networkUtilization' + flavourUtil: + $ref: '#/components/schemas/flavourUtilization' + memUtilization: + type: object + required: + - noOfSamples + - averageUtilization + - maxUtilization + - minUtilization + properties: + noOfSamples: + type: string + description: Number of samples used for calculating metrics. + averageUtilization: + $ref: '#/components/schemas/utilizationValue' + maxUtilization: + $ref: '#/components/schemas/utilizationValue' + minUtilization: + $ref: '#/components/schemas/utilizationValue' + effectiveUtilization: + $ref: '#/components/schemas/utilizationValue' + diskUtilization: + type: object + required: + - noOfSamples + - averageUtilization + - maxUtilization + - minUtilization + properties: + noOfSamples: + type: string + description: Number of samples used for calculating metrics. + averageUtilization: + $ref: '#/components/schemas/utilizationValue' + maxUtilization: + $ref: '#/components/schemas/utilizationValue' + minUtilization: + $ref: '#/components/schemas/utilizationValue' + effectiveUtilization: + $ref: '#/components/schemas/utilizationValue' + networkUtilization: + type: object + required: + - noOfSamples + - ingressUsage + - egressUsage + - averageThroughput + - maxThroughput + - minThroughput + properties: + noOfSamples: + type: string + description: Number of samples used for calculating metrics. + ingressUsage: + $ref: '#/components/schemas/utilizationValue' + egressUsage: + $ref: '#/components/schemas/utilizationValue' + averageThroughput: + $ref: '#/components/schemas/utilizationValue' + maxThroughput: + $ref: '#/components/schemas/utilizationValue' + minThroughput: + $ref: '#/components/schemas/utilizationValue' + flavourUtilization: + type: array + items: + $ref: '#/components/schemas/flavourMetrics' + minItems: 1 + description: List of compute flavours metrics per zone + flavourMetrics: + type: object + required: + - noOfSamples + - flavourId + - averageUtilization + - maxUtilization + - minUtilization + properties: + noOfSamples: + type: string + description: Number of samples used for calculating metrics. + flavourId: + $ref: '#/components/schemas/FlavourId' + averageUtilization: + $ref: '#/components/schemas/utilizationValue' + averageThroughput: + $ref: '#/components/schemas/utilizationValue' + maxUtilization: + $ref: '#/components/schemas/utilizationValue' + minUtilization: + $ref: '#/components/schemas/utilizationValue' + appsResUtilizeInfo: + type: object + required: + - appMetrics + - federationContextId + - sequenceNum + properties: + appMetrics: + type: array + items: + $ref: '#/components/schemas/appsResUtilizeMetrics' + minItems: 1 + description: List of edge cloud resource metrics per zone + federationContextId: + $ref: '#/components/schemas/FederationContextId' + sequenceNum: + type: integer + description: Monotonically increasing counter for sequencing app monitoring reports + appsResUtilizeMetrics: + type: object + required: + - zoneId + - startTime + - endTime + - appZoneMetrics + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + startTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + endTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + appZoneMetrics: + $ref: '#/components/schemas/appMetrics' + appMetrics: + type: array + items: + $ref: '#/components/schemas/appAggrResUtil' + minItems: 1 + description: List of edge cloud resource metrics per zone + appAggrResUtil: + type: object + required: + - appId + - appProvId + - noOfAppInstances + - appInstances + - cpuUtil + - memUtil + - diskUtil + - networkUtil + - flavourUtil + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProvId: + $ref: '#/components/schemas/AppProviderId' + noOfAppInstances: + type: integer + description: No of application instances of appId in a zone + appInstances: + type: array + items: + $ref: '#/components/schemas/InstanceIdentifier' + minItems: 1 + cpuUtil: + $ref: '#/components/schemas/cpuUtilization' + memUtil: + $ref: '#/components/schemas/memUtilization' + diskUtil: + $ref: '#/components/schemas/diskUtilization' + networkUtil: + $ref: '#/components/schemas/networkUtilization' + flavourUtil: + $ref: '#/components/schemas/flavourUtilization' + cpuUtilization: + type: object + required: + - cpuType + - noOfSamples + - averageUtilization + - maxUtilization + - minUtilization + - effectiveUtilization + properties: + cpuType: + $ref: '#/components/schemas/monitoringSubsType' + noOfSamples: + type: string + description: Number of samples used for calculating metrics. + averageUtilization: + $ref: '#/components/schemas/utilizationValue' + maxUtilization: + $ref: '#/components/schemas/utilizationValue' + minUtilization: + $ref: '#/components/schemas/utilizationValue' + effectiveUtilization: + $ref: '#/components/schemas/utilizationValue' + thresholdVal: + type: object + required: + - value + - unit + properties: + value: + type: string + unit: + type: string + enum: + - percent + - CORES + - TB + - GB + - MBPS + - GBPS + description: The unit of resources measurement e.g. number of cores, mega bits per seconds etc. + EventSubscription: + type: object + required: + - resUsageType + - periodicity + - eventListner + properties: + resUsageType: + $ref: '#/components/schemas/resourceType' + periodicity: + $ref: '#/components/schemas/periodicityInterval' + eventListner: + $ref: '#/components/schemas/Uri' + EventSubscriptionInfo: + type: object + required: + - resUsageType + - periodicity + - subscriptionId + properties: + resUsageType: + $ref: '#/components/schemas/resourceType' + periodicity: + $ref: '#/components/schemas/periodicityInterval' + subscriptionId: + type: string + format: uuid + eventCriterion: + type: object + required: + - resUsageType + - triggerCondition + - thresholdVal + - numOccurance + - monitorDuration + properties: + resUsageType: + $ref: '#/components/schemas/resourceType' + triggerCondition: + type: string + enum: + - GT + - GTE + - EQ + - LT + - LEQ + description: The condition evaluation operator to compare threashold value of a resource for + event detection. + thresholdVal: + $ref: '#/components/schemas/thresholdVal' + numOccurance: + type: integer + description: Number of times the trigger condition is detected + monitorDuration: + $ref: '#/components/schemas/periodicityInterval' + eventInfo: + type: object + required: + - eventId + - eventCriterion + properties: + eventId: + type: string + eventCriterion: + $ref: '#/components/schemas/eventCriterion' + eventTypeList: + type: array + items: + $ref: '#/components/schemas/eventCriterion' + minItems: 1 + description: List of event criterion + detectedEvent: + type: object + required: + - zoneId + - eventId + - startTime + - endTime + - numOccurance + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + eventId: + type: string + startTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + endTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + numOccurance: + type: integer + CapabilityID: + type: string + enum: + - NW_CAP_CONN_STATE_CHANGE + - NW_CAP_LOCATION_RETRIEVAL + - NW_CAP_USERPLANE_MGMT_EVENTS + - NW_CAP_DYNAMIC_QOS + description: The enumerated list of network capabilities that an OP can use for various services + via SBI-NR. + DeviceConnStatusChangeCap: + type: object + required: + - capabilityId + - maxiDetectionTime + properties: + capabilityId: + $ref: '#/components/schemas/CapabilityID' + maxiDetectionTime: + type: string + description: The maximum detection time in seconds that the OP can determine the UE change of + connectivity with the mobile network. + LocationRetrievalCap: + type: object + required: + - capabilityId + - locationType + properties: + capabilityId: + $ref: '#/components/schemas/CapabilityID' + locationType: + type: string + enum: + - CELL_LEVEL_ACCURACY + - REGISTRATION_AREA_ACCURACY + - TRACKING_AREA_ACCURACY + - GEO_LOCATION_ACCURACY + description: The enumerated list of UE location accuracy that an OP can determine via SBI-NR. + locationAccuracy: + type: string + enum: + - LAST_KNOWN_LOCATION + - CURRENT_LOCATION + - INITIAL_LOCATION + description: The enumerated list of type of network location of an UE that an OP can determine + via SBI-NR. + UserPlaneMgmtEvtCap: + type: object + required: + - capabilityId + - maxUserPlaneLatency + properties: + capabilityId: + $ref: '#/components/schemas/CapabilityID' + maxUserPlaneLatency: + type: string + description: Indicates the maximum user plane latency in units of milliseconds to decide whether + edge relocation is needed to ascertain latency remain in this range. + DynamicQoSCap: + type: object + required: + - capabilityId + - supportedQoS + properties: + capabilityId: + $ref: '#/components/schemas/CapabilityID' + supportedQoS: + type: string + description: Set of one or more 5G QoS Identifier (5QI or 4G QCI) created via concatanation + of Resource Type and 5QI values i.e., GBR1, GBR2, GBR65, NONGBR79 etc. + NetworkCapAppInfoList: + type: array + items: + required: + - appProviderId + - appId + - AppInstNetworkCapInvoked + - zoneId + properties: + appProviderId: + $ref: '#/components/schemas/AppProviderId' + appId: + $ref: '#/components/schemas/AppIdentifier' + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + appInstNetworkCapInvoked: + $ref: '#/components/schemas/AppInstNetworkCapList' + minItems: 1 + AppInstNetworkCapList: + type: object + required: + - appInstanceNwCapInfo + properties: + appInstanceNwCapInfo: + type: array + items: + type: object + required: + - appInstIdentifier + - appInstanceState + - networkCapInvoked + properties: + appInstIdentifier: + $ref: '#/components/schemas/InstanceIdentifier' + appInstanceState: + $ref: '#/components/schemas/InstanceState' + networkCapInvoked: + $ref: '#/components/schemas/NetworkCapInvoked' + minItems: 1 + NetworkCapInvoked: + type: object + required: + - networkEventId + - capabilityId + - zoneId + - detectionTime + - nwCapabilitySLI + properties: + networkEventId: + type: string + format: uuid + description: Unique identifier allocated for a network event + capabilityId: + $ref: '#/components/schemas/CapabilityID' + invocationTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + nwCapabilitySLI: + type: string + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + NetworkCapSubsInfo: + type: object + required: + - appId + - appProviderId + - capabilityId + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + capabilityId: + $ref: '#/components/schemas/CapabilityID' + NetworkEventsList: + type: array + items: + $ref: '#/components/schemas/NetworkCapInvoked' + minItems: 1 + description: List of network capabilities events detected + EventsList: + type: array + items: + $ref: '#/components/schemas/detectedEvent' + minItems: 1 + description: List of events detected + EventSubscriptionIdentifier: + type: string + format: uuid + description: Event subscription identifier allocated for enabling event reporting + EventIdentifier: + type: string + format: uuid + description: Event identifier allocated for event detected + SubscriptionIdentifier: + type: object + required: + - subsId + properties: + subsId: + type: string + format: uuid + description: Generic subscription identifier + AlarmObjectInfo: + type: object + required: + - alarmType + - alarmId + - perceivedSeverity + - probableCause + - alarmedObject + - sourceSystemId + - state + - alarmRaisedTime + properties: + alarmType: + $ref: '#/components/schemas/AlarmType' + alarmId: + $ref: '#/components/schemas/AlarmIdentifier' + perceivedSeverity: + $ref: '#/components/schemas/PerceivedSeverity' + probableCause: + $ref: '#/components/schemas/ProbableCause' + alarmedObject: + $ref: '#/components/schemas/AlarmedObject' + sourceSystemId: + $ref: '#/components/schemas/SourceSystemId' + state: + $ref: '#/components/schemas/State' + alarmRaisedTime: + $ref: '#/components/schemas/AlarmRaisedTime' + affectedService: + $ref: '#/components/schemas/AffectedService' + alarmDetails: + $ref: '#/components/schemas/AlarmDetails' + specificProblem: + $ref: '#/components/schemas/SpecificProblem' + serviceAffecting: + $ref: '#/components/schemas/ServiceAffecting' + ActiveAlarmsList: + type: array + items: + $ref: '#/components/schemas/AlarmObjectInfo' + minItems: 1 + description: List of active alarms + AlarmType: + type: object + required: + - alarmType + properties: + alarmType: + type: string + enum: + - EDGERES + - APPLICATION + - ARTEFACT + - EDGEDISC + - FEDERATION + - SECURITY + - APIFEDERATION + - FILE + description: Alarm type category + AlarmIdentifier: + type: object + required: + - alarmId + properties: + alarmId: + type: string + description: Alarm identifier to refer to an alarm instance + PerceivedSeverity: + type: object + required: + - severity + properties: + severity: + type: string + enum: + - MAJOR + - MINOR + - CRITICAL + - WARNING + - INFOMATIONAL + description: Alarm severity + ProbableCause: + type: object + required: + - cause + properties: + cause: + type: string + description: Probale cause of the alarm + AlarmedObject: + type: object + required: + - alarmId + - href + properties: + alarmId: + $ref: '#/components/schemas/AlarmIdentifier' + href: + $ref: '#/components/schemas/Uri' + SourceSystemId: + type: object + required: + - sourceSystemId + properties: + sourceSystemId: + type: string + description: Source system identity + State: + type: object + required: + - alarmState + properties: + alarmState: + type: string + enum: + - RAISED + - UPDATED + - CLEAR + description: Defines the alarm state during its life cycle (raised | updated | cleared). + AlarmRaisedTime: + type: object + required: + - alarmRaisedTime + properties: + alarmRaisedTime: + type: string + format: date-time + description: Defines the alarm raised time at source + AffectedService: + type: object + required: + - affectedService + properties: + affectedService: + type: array + items: + type: string + minItems: 1 + description: Defines the affected services e.g., edge discovery, application services, API services + etc at source + AlarmDetails: + type: object + required: + - alarmDetails + properties: + alarmDetails: + type: string + description: Detailed information of the alarm + SpecificProblem: + type: object + required: + - specificProblem + properties: + specificProblem: + type: string + description: Specific information related to the alarm + ServiceAffecting: + type: string + enum: + - true + - false + description: Specific information related to the alarm + PatchableParams: + type: string + enum: + - /perceivedSeverity + - /probableCause + - /alarmedObject + - /sourceSystemId + - /state + - /affectedService + - /alarmDetails + - /specificProblem + - /serviceAffecting + AlarmUpdateOps: + type: string + enum: + - REPLACE + description: Operations that can be performed to update the parameters of an alarm + UpdatedParam: + type: object + required: + - alarmUpdateOps + - patchableParam + - patchValue + properties: + alarmUpdateOps: + $ref: '#/components/schemas/AlarmUpdateOps' + patchableParam: + $ref: '#/components/schemas/PatchableParams' + patchValue: + type: string + description: Value to be replaced for the alarm parameter being updated + UpdatedAlarmParameters: + type: object + required: + - alarmId + - updateParams + properties: + alarmId: + $ref: '#/components/schemas/AlarmIdentifier' + updateParams: + type: array + items: + $ref: '#/components/schemas/UpdatedParam' + minItems: 1 + description: List of alarm parameters to be updated in an update operation + serviceType: + type: string + enum: + - api_federation + description: An identifier to refer to partner OP capabilities for application providers. + serviceAPINames: + type: array + items: + type: string + enum: + - QualityOnDemand + - DeviceLocation + - DeviceStatus + - SimSwap + - NumberVerification + - DeviceIdentifier + minItems: 1 + description: List of Service API capability names an OP supports and offers to other OPs "quality_on_demand", + "device_location" etc. + serviceAPINameVal: + type: string + enum: + - QualityOnDemand + - DeviceLocation + - DeviceStatus + - SimSwap + - NumberVerification + - DeviceIdentifier + description: Name of the Service API + serviceRoutingInfo: + type: array + items: + type: string + pattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))?$ + minItems: 1 + description: List of public IP addresses MNO manages for UEs to connect with public data networks + customerID: + type: string + format: uuid + description: Leading OP managed identifier associated to API Provider of the Leading OP. + txnIdentifier: + type: string + description: A API transaction identifier generated by the Partner OP for each API request + connectID: + type: string + description: An identifier generated by the Partner OP to represent the end user identity in the + Service API request. + apiContentType: + type: string + enum: + - application/json + description: Indicate the Service API body schema in JSON format + serviceAPIContent: + type: object + required: + - mediaType + - APIContent + properties: + mediaType: + $ref: '#/components/schemas/apiContentType' + APIContent: + type: object + additionalProperties: true + PlatformCaps: + type: array + items: + type: string + enum: + - homeRouting + - Anchoring + - serviceAPIs + - faultMgmt + - eventMgmt + - resourceMonitor + - networkEventMgmt + - appNotificationMgmt + - appLevelPolicyMgmt + - opsLevelPolicyMgmt + description: Home routing - Operator platform is capable of routing edge application data traffic + from its edges to user device in their home location. This is the case where user devices are + served in their home region (requesting platform region, non-roaming) but the corresponding edge + application are in operator platform edges. Anchoring - Operator platform is capable of routing + edge application traffic for roaming user devices to edge application in user device home network. + Service APIs - Capability to handle Service APIs (e.g., CAMARA APIs) from the Leading OP + expiryInterval: + type: object + required: + - numHours + - numMins + - numSecs + properties: + numHours: + type: integer + format: int32 + description: Number of Hours for Expiry (0-23) + numMins: + type: integer + format: int32 + description: Number of Minutes for Expiry (0-59) + numSecs: + type: integer + format: int32 + description: Number of Seconds for Expiry (0-59) + periodicityInterval: + type: object + required: + - numHours + - numMins + properties: + numHours: + type: integer + format: int32 + description: Number of Hours for Expiry (0-23) + numMins: + type: integer + format: int32 + description: Number of Minutes for Expiry (0-59) + periodicNotifConfig: + type: object + properties: + periodicity: + $ref: '#/components/schemas/periodicityInterval' + notificationListner: + $ref: '#/components/schemas/Uri' + targetUserContext: + type: object + required: + - connectID + - expiryDuration + properties: + connectID: + $ref: '#/components/schemas/connectID' + expiryDuration: + $ref: '#/components/schemas/expiryInterval' + serviceAPIResponse: + type: object + properties: + customerID: + $ref: '#/components/schemas/customerID' + targetUserContext: + $ref: '#/components/schemas/targetUserContext' + txnIdentifier: + $ref: '#/components/schemas/txnIdentifier' + apiResponse: + type: object + required: + - mediaType + - responseContent + properties: + mediaType: + type: string + description: May contain value e.g. "application/json". + responseContent: + type: object + additionalProperties: true + description: Result of the Service API processing, formatted according to mediaType and + defined by the Service API specification. + required: + - customerID + - txnIdentifier + anyOf: + - required: + - targetUserContext + - required: + - apiResponse + svcEventType: + type: string + enum: + - evt_timerexpiry + - evt_network + - evt_delete + serviceAPIEventDef: + type: object + required: + - NetworkEventDef + properties: + NetworkEventDef: + type: object + additionalProperties: true + serviceAPINetworkEvent: + type: object + required: + - connectID + - customerID + - EventType + properties: + connectID: + $ref: '#/components/schemas/connectID' + customerID: + $ref: '#/components/schemas/customerID' + EventType: + $ref: '#/components/schemas/svcEventType' + serviceAPIEventDef: + $ref: '#/components/schemas/serviceAPIEventDef' + expiryDuration: + $ref: '#/components/schemas/expiryInterval' + ServiceNameNB: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,62}[A-Za-z0-9]$ + description: Must be a valid RFC 1035 label name. This defines the DNS name via which the component + can be accessed over NBI. Access via serviceNameNB is restricted on specific ports. Platform + shall expose component access externally via this DNS name + ServiceNameEW: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,62}[A-Za-z0-9]$ + description: Must be a valid RFC 1035 label name. This defines the DNS name via which the component + can be accessed via peer components. Access via serviceNameEW is open on all ports. Platform + shall not expose serviceNameEW externally outside edge. + ComponentName: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,62}[A-Za-z0-9]$ + description: Must be a valid RFC 1035 label name. Component name must be unique with an application + ApplEventsSubsInfo: + type: object + required: + - appEventSubsId + - appEvtSubsStartTime + - appEvtSubsLastReportTime + - appEvtSubsNumApps + - appEvtSubsPeriodicity + properties: + appEventSubsId: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + appEvtSubsStartTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + appEvtSubsLastReportTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + appEvtSubsNumApps: + type: integer + appEvtSubsPeriodicity: + $ref: '#/components/schemas/periodicityInterval' + AppsForNotif: + type: object + required: + - appId + - appProviderId + - appZones + - appEvents + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + appZones: + $ref: '#/components/schemas/AppZones' + appEvents: + $ref: '#/components/schemas/AppEvents' + AddAppsForNotif: + type: array + items: + $ref: '#/components/schemas/AppsForNotif' + minItems: 1 + RemoveAppsForNotif: + type: array + items: + $ref: '#/components/schemas/AppsForNotif' + minItems: 1 + AppEventTypes: + type: string + enum: + - evt_type_app_relocation + - evt_type_app_session_cont + - evt_type_app_restarts + - evt_type_app_upscale + - evt_type_app_downscale + description: Application-level events + AppEvents: + type: array + items: + $ref: '#/components/schemas/AppEventTypes' + minItems: 1 + description: List of availability zones where application events are to be monitored + AppZones: + type: array + items: + $ref: '#/components/schemas/ZoneIdentifier' + minItems: 1 + description: List of availability zones where application events are to be monitored + ApplInstEventTypeInfo: + type: object + required: + - applInstEvent + - applInstEventCount + properties: + applInstEvent: + $ref: '#/components/schemas/AppEventTypes' + applInstEventCount: + type: integer + description: Number of occurances of given epplication event + ApplInstEventsContainer: + type: object + required: + - appInstanceId + - appInstEventsList + properties: + appInstanceId: + $ref: '#/components/schemas/InstanceIdentifier' + appInstEventsList: + type: array + items: + $ref: '#/components/schemas/ApplInstEventTypeInfo' + minItems: 1 + description: Application instance events list + ApplInstEventsList: + type: object + required: + - appInstanceEventsList + properties: + appInstanceEventsList: + type: array + items: + $ref: '#/components/schemas/ApplInstEventsContainer' + minItems: 1 + description: Application instance events list for one or more applications + ZoneLevelApplEventsList: + type: object + required: + - zoneId + - appsEventsList + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + appsEventsList: + type: array + items: + $ref: '#/components/schemas/ApplInstEventsList' + minItems: 1 + description: Applications instance events list in a availability zone + ApplEventsList: + type: object + required: + - appId + - appProviderId + - aggrApplEvents + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + aggrApplEvents: + type: array + items: + $ref: '#/components/schemas/ZoneLevelApplEventsList' + minItems: 1 + description: Applications instance events list in a availability zone + AggrApplEventsList: + type: object + required: + - startTime + - endTime + - aggrAppsEventsList + properties: + startTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + endTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + aggrAppsEventsList: + type: array + items: + $ref: '#/components/schemas/ApplEventsList' + minItems: 1 + description: Applications events list in a various availability zones for different application + providers + ApplPolicyIdentifier: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,63}$ + description: Application-level Policy unique identifier + ApplPolicyMetaInfo: + type: object + required: + - applPolicyTypeIdentifier + - policyVersion + properties: + applPolicyTypeIdentifier: + $ref: '#/components/schemas/ApplPolicyTypeIdentifier' + policyVersion: + type: string + description: Policy template version using Semantic Versioning 2.0.0 in MAJOR.MINOR.PATCH format + ApplPolicyTypeIdentifier: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,63}$ + description: Application-level Policy unique identifier + AppPolicyTemplate: + type: object + required: + - applPolicyName + - applPolicyMetaInfo + - applPolicyType + - applPolicyScope + - applPolicyDescription + - applPolicyRules + properties: + applPolicyName: + type: string + maxLength: 64 + description: Brief policy template name on policy objective + applPolicyMetaInfo: + $ref: '#/components/schemas/ApplPolicyMetaInfo' + applPolicyType: + $ref: '#/components/schemas/ApplPolicyType' + applPolicyScope: + $ref: '#/components/schemas/ApplPolicyScope' + applPolicyDescription: + type: string + maxLength: 256 + description: Brief policy template description on policy objective + applPolicyRules: + type: array + items: + $ref: '#/components/schemas/ApplPolicyRule' + minItems: 1 + description: Set of policy action rules for a given policy + ApplPolicyTemplateList: + type: array + items: + $ref: '#/components/schemas/AppPolicyTemplate' + minItems: 1 + description: List of Application policy templates from the Partner OP + ApplPolicyType: + type: string + enum: + - static + - dynamic + description: Policy attribute that the given policy intent to control specific resources e.g. compute + capacity expansion statically vs dynamic scaling of app instance + ApplPolicyScope: + type: string + enum: + - zonal + - global + description: Application-level Policy scope defines if a policy is a set of availability zones or + applies globally to all zones + ApplPolicyRule: + type: array + items: + $ref: '#/components/schemas/GenericPolicyRule' + minItems: 1 + description: List of Application policies + GenericPolicyRule: + type: object + required: + - ruleLHSParamType + - ruleOperator + - ruleRHSParamVal + - ruleAction + - ruleDescription + properties: + ruleLHSParamType: + $ref: '#/components/schemas/RuleLHSParamType' + ruleOperator: + $ref: '#/components/schemas/RuleOperatorType' + ruleRHSParamVal: + $ref: '#/components/schemas/RuleRHSParamVal' + ruleAction: + $ref: '#/components/schemas/RuleActionType' + ruleDescription: + type: string + maxLength: 256 + description: Brief description of the actions to be performed + RuleLHSParamType: + type: string + enum: + - AppsPolicy.App.Metadata.QoS.Latency + - AppsPolicy.App.Metadata.Compute.CPU + - AppsPolicy.App.Metadata.Compute.GPU + - AppsPolicy.App.Metadata.Location.AZ + - AppsPolicy.App.Metadata.Location.Region + - OpsPolicy.EdgeCloud.Metadata.QoS.Latency + - OpsPolicy.EdgeCloud.Metadata.Compute.CPU + - OpsPolicy.EdgeCloud.Metadata.Compute.GPU + - OpsPolicy.EdgeCloud.Metadata.Network.SRIOV + description: Resource attributes that policy will act on to determine the target pplication after + applying the policy rules + RuleRHSParamVal: + type: object + properties: + latencyRanges: + $ref: '#/components/schemas/LatencyRanges' + computeResourceProfile: + $ref: '#/components/schemas/ComputeResourceProfile' + appLocation: + type: array + items: + $ref: '#/components/schemas/AppLocation' + minItems: 1 + networkCaps: + $ref: '#/components/schemas/NetworkCaps' + description: Permitted type specific value objects for types in ruleLHSParamType + AppLocation: + type: string + enum: + - zones + - regions + description: Application Location in terms of availability zones or regions + LatencyRanges: + type: object + required: + - minLatency + - maxLatency + - unit + properties: + minLatency: + type: string + description: Minimum latency in milliseconds + maxLatency: + type: string + description: Maximum latency in milliseconds + unit: + type: string + enum: + - MS + description: Maximum latency in milliseconds + description: Latency ranges that can be experienced in the Partner OP environment + ComputeResourceProfile: + type: object + required: + - resourceSpec + properties: + resourceSpec: + $ref: '#/components/schemas/ResourceSpec' + description: Type and amount of compute resources + ResourceSpec: + type: object + required: + - resourceType + - resourceModel + - resourceCount + properties: + resourceType: + type: string + enum: + - CPU + - GPU + - FPGA + resourceModel: + type: string + enum: + - Intel-x86_64 + - Arm64 + - Nvidia + resourceCount: + type: string + description: Resource type and architecture specification + NetworkCaps: + type: object + properties: + nwAccelType: + type: string + enum: + - SRIOV + - DPDK + nwAccelSpeed: + type: string + enum: + - 1Gbps + - 10Gbps + - 100Gbps + description: Type and speed of network acceleration resources + RuleOperatorType: + type: object + properties: + StringRuleOperatorType: + $ref: '#/components/schemas/StringRuleOperatorType' + BinaryRuleOperatorType: + $ref: '#/components/schemas/BinaryRuleOperatorType' + description: Defines the logical operations that policy rule will execute on application attribute + value + BinaryRuleOperatorType: + type: string + enum: + - EQ + - LT + - GT + description: Operations that can be applied on Parameter e.g., “Binary Operation” EQ(EQual) + StringRuleOperatorType: + type: string + enum: + - EQ + - NOTEQ + description: Operations that can be applied on Parameter e.g., String Operation” EQ(EQual), NOTEQ(Not + Equal) + RuleActionType: + type: object + required: + - actionType + - actionTargetType + properties: + actionType: + $ref: '#/components/schemas/ActionType' + actionTargetType: + $ref: '#/components/schemas/RuleLHSParamType' + ActionType: + type: string + enum: + - restrict + - prefer + - priortize + - allow + - deny + description: Action to be taken once a policy rule is applied on target resource indicated by RuleLHSParamType + ApplConcretePolicy: + type: object + required: + - policyId + - policyParamLimits + properties: + policyId: + $ref: '#/components/schemas/ApplPolicyIdentifier' + policyParamLimits: + $ref: '#/components/schemas/ApplPolicyRule' + description: Application policy id and policy parameter value limits registered by the Originating + OP + AssocApplPolicies: + type: object + required: + - policyId + - appIdList + properties: + policyId: + $ref: '#/components/schemas/ApplPolicyIdentifier' + appIdList: + $ref: '#/components/schemas/AppIdLocList' + AppIdLocList: + type: object + required: + - appId + - appProvId + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProvId: + $ref: '#/components/schemas/AppProviderId' + zoneIds: + type: array + items: + $ref: '#/components/schemas/ZoneIdentifier' + minItems: 1 + RegisteredAppPolicyList: + type: array + items: + $ref: '#/components/schemas/ApplConcretePolicy' + minItems: 1 + description: Applications policies registered by the Originating OP + OpsPolicyIdentifier: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,63}$ + description: Operation-level Policy unique identifier + OpsPolicyTypeIdentifier: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,63}$ + description: Operation-level Policy template unique identifier + OpslPolicyMetaInfo: + type: object + required: + - opslPolicyTypeIdentifier + - policyVersion + properties: + opslPolicyTypeIdentifier: + $ref: '#/components/schemas/OpsPolicyTypeIdentifier' + policyVersion: + type: string + description: Policy template version using Semantic Versioning 2.0.0 in MAJOR.MINOR.PATCH format + OpsPolicyTemplateList: + type: array + items: + $ref: '#/components/schemas/OpsPolicyTemplate' + minItems: 1 + description: List of Operation policy templates from the Partner OP + OpsConcretePolicy: + type: object + required: + - policyId + - policyParamLimits + properties: + policyId: + $ref: '#/components/schemas/OpsPolicyIdentifier' + policyParamLimits: + $ref: '#/components/schemas/OpsPolicyRule' + description: Application policy id and policy parameter value limits registered by the Originating + OP + OpsPolicyTemplate: + type: object + required: + - opsPolicyName + - OpslPolicyMetaInfo + - opsPolicyType + - opsPolicyScope + - opsPolicyDescription + - opsPolicyRules + properties: + opsPolicyName: + type: string + maxLength: 64 + description: Brief policy template name on policy objective + opslPolicyMetaInfo: + $ref: '#/components/schemas/OpslPolicyMetaInfo' + opsPolicyType: + $ref: '#/components/schemas/OpsPolicyType' + opsPolicyScope: + $ref: '#/components/schemas/OpsPolicyScope' + opsPolicyDescription: + type: string + maxLength: 256 + description: Brief policy template description on policy objective + opsPolicyRules: + type: array + items: + $ref: '#/components/schemas/OpsPolicyRule' + minItems: 1 + description: Set of policy action rules for a given policy + OpsPolicyRule: + type: object + properties: + opsPolicyRule: + $ref: '#/components/schemas/GenericPolicyRule' + description: Operation policies rule defines the action to be taken against the subscribed policy + template + OpsPolicyType: + type: string + enum: + - static + - dynamic + description: Policy attribute that defines if the policy rules applies to static part of the infra + or dynamic part of the edge cloud infra + OpsPolicyScope: + type: string + enum: + - zonal + - global + description: Operation-level Policy scope defines if a policy is a set of availability zones or + applies globally to all zones + AssocOpsPolicies: + type: object + required: + - policyId + - appIdList + properties: + policyId: + $ref: '#/components/schemas/OpsPolicyIdentifier' + appIdList: + $ref: '#/components/schemas/AppIdLocList' + AvailZoneIdLocList: + type: object + required: + - zoneIds + properties: + zoneIds: + type: array + items: + $ref: '#/components/schemas/ZoneIdentifier' + minItems: 1 + RegisteredOpsPolicyList: + type: array + items: + $ref: '#/components/schemas/OpsConcretePolicy' + minItems: 1 + description: Operation policies registered by the Originating OP + AppComponentSpecs: + description: An application may consist of more than one component. Each component is associated + with a descriptor and may exposes its services externally or internally. App providers are required + to provide details about all these components, their associated descriptors and their DNS names. + type: array + items: + type: object + required: + - artefactId + properties: + serviceNameNB: + $ref: '#/components/schemas/ServiceNameNB' + serviceNameEW: + $ref: '#/components/schemas/ServiceNameEW' + componentName: + $ref: '#/components/schemas/ComponentName' + artefactId: + $ref: '#/components/schemas/ArtefactId' + minItems: 1 + AppMetaData: + description: Application metadata details + type: object + required: + - appName + - version + - accessToken + properties: + appName: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,31}$ + description: Name of the application. Application provider define a human readable name for + the application + version: + type: string + description: Version info of the application + appDescription: + type: string + minLength: 16 + maxLength: 256 + description: Brief application description provided by application provider + mobilitySupport: + $ref: '#/components/schemas/MobilitySupport' + accessToken: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{31,63}$ + description: An application Access key, to be used with UNI interface to authorize UCs Access + to a given application + category: + type: string + enum: + - IOT + - HEALTH_CARE + - GAMING + - VIRTUAL_REALITY + - SOCIALIZING + - SURVEILLANCE + - ENTERTAINMENT + - CONNECTIVITY + - PRODUCTIVITY + - SECURITY + - INDUSTRIAL + - EDUCATION + - OTHERS + description: Possible categorization of the application + AppQoSProfile: + description: Parameters corresponding to the performance constraints, tenancy details etc. + type: object + required: + - latencyConstraints + properties: + latencyConstraints: + $ref: '#/components/schemas/LatencyConstraints' + bandwidthRequired: + $ref: '#/components/schemas/BandwidthRequired' + multiUserClients: + $ref: '#/components/schemas/MultiUserClients' + noOfUsersPerAppInst: + $ref: '#/components/schemas/NoOfUsersPerAppInst' + appProvisioning: + $ref: '#/components/schemas/AppProvisioning' + EdgeAppFQDN: + type: string + description: DNS FQDN assigned to application instances in an availability zone. User Clients can + resolve the FQDN to communicate with the edge instances of the application + ClientLocation: + type: object + minProperties: 1 + properties: + geo_location: + type: string + description: Latitude, Longitude as decimal fraction up to 4 digit precision + pattern: ^([-+]?)([\d]{1,2})(((\.)(\d+)(,)))(\s*)(([-+]?)([\d]{1,3})((\.)(\d+))?)$ + rad_location: + description: Information about the 4G/5G Cell ids where the client is currently served. + type: array + items: + type: object + required: + - carrier + - mcc + - mnc + - cellId + properties: + carrier: + type: string + enum: + - 5G + - LTE + mcc: + type: integer + minimum: 1 + maximum: 999 + description: Mobile country code of the network as broadcasted in the serving cell + mnc: + type: integer + minimum: 1 + maximum: 999 + description: Mobile network code of the network as broadcasted in the serving cell + cellId: + type: integer + description: it could be a CGI (if carrier is LTE) or NCGI (if carrier is 5G). + areaCode: + type: integer + description: Routing area code or Traffic area code where client is being served. + CompEnvParams: + description: Environment variables are key value pairs that should be injected when component in + instantiated + type: object + required: + - envVarName + - envValueType + properties: + envVarName: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,30}[A-Za-z0-9]$ + description: Name of environment variable + envValueType: + type: string + enum: + - USER_DEFINED + - PLATFORM_DEFINED_DYNAMIC_PORT + - PLATFORM_DEFINED_DNS + - PLATFORM_DEFINED_IP + envVarValue: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,62}[A-Za-z0-9]$ + description: Value to be assigned to environment variable + envVarSrc: + type: string + description: Full path of parameter from componentSpec that should be used to generate the environment + value. Eg. networkResourceProfile[1]. interfaceId. + CommandLineParams: + description: List of commands and arguments that shall be invoked when the component instance is + created. This is valid only for container based deployment. + type: object + required: + - command + properties: + command: + type: array + items: + type: string + description: List of commands that application should invoke when an instance is created. + commandArgs: + type: array + items: + type: string + description: List of arguments required by the command. + DeploymentConfig: + description: Configuration used when deploying a component. May override other ComponentSpec parameters + related to deployment like restart policy, command line parameters, environment variables, etc. + type: object + required: + - configType + - contents + properties: + configType: + type: string + enum: + - DOCKER_COMPOSE + - KUBERNETES_MANIFEST + - CLOUD_INIT + - HELM_VALUES + description: Config type. + contents: + type: string + description: Contents of the configuration. + ComponentSpec: + description: Details about compute, networking and storage requirements for each component of the + application. App provider should define all information needed to instantiate the component. If + artefact is being defined at component level this section should have information just about the + component. In case the artefact is being defined at application level the section should provide + details about all the components. + type: object + required: + - componentName + - images + - numOfInstances + - restartPolicy + - computeResourceProfile + properties: + componentName: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,62}[A-Za-z0-9]$ + description: Must be a valid RFC 1035 label name. Component name must be unique with an application + images: + description: List of all images associated with the component. Images are specified using the + file identifiers. Partner OP provides these images using file upload api. + type: array + items: + $ref: '#/components/schemas/FileId' + minItems: 1 + numOfInstances: + type: integer + format: int32 + description: Number of component instances to be launched. + restartPolicy: + type: string + enum: + - RESTART_POLICY_ALWAYS + - RESTART_POLICY_NEVER + description: How the platform shall handle component failure + commandLineParams: + $ref: '#/components/schemas/CommandLineParams' + exposedInterfaces: + description: Each application component exposes some ports either for external users or for + inter component communication. Application provider is required to specify which ports are + to be exposed and the type of traffic that will flow through these ports. + type: array + items: + $ref: '#/components/schemas/InterfaceDetails' + minItems: 1 + computeResourceProfile: + $ref: '#/components/schemas/ComputeResourceInfo' + compEnvParams: + type: array + items: + $ref: '#/components/schemas/CompEnvParams' + deploymentConfig: + $ref: '#/components/schemas/DeploymentConfig' + persistentVolumes: + description: The ephemeral volume a container process may need to temporary store internal data + type: array + items: + $ref: '#/components/schemas/PersistentVolumeDetails' + minItems: 1 + ComputeResourceInfo: + type: object + required: + - cpuArchType + - numCPU + - memory + properties: + cpuArchType: + type: string + enum: + - ISA_X86_64 + - ISA_ARM_64 + description: CPU Instruction Set Architecture (ISA) E.g., Intel, Arm etc. + numCPU: + $ref: '#/components/schemas/Vcpu' + memory: + type: integer + format: int64 + description: Amount of RAM in Mbytes + diskStorage: + type: integer + format: int32 + description: Amount of disk storage in Gbytes for a given ISA type + gpu: + type: array + items: + $ref: '#/components/schemas/GpuInfo' + vpu: + type: integer + description: Number of Intel VPUs available for a given ISA type + fpga: + type: integer + description: Number of FPGAs available for a given ISA type + hugepages: + type: array + items: + $ref: '#/components/schemas/HugePage' + cpuExclusivity: + type: boolean + description: Support for exclusive CPUs + nodeDiscoveryResponse: + type: object + required: + - edgeNodes + - discoveredAppInsts + properties: + edgeNodes: + $ref: '#/components/schemas/DiscoveredEdgeNodes' + discoveredAppInsts: + $ref: '#/components/schemas/DiscoveredAppInsts' + description: Candidate availability zones and details of already running instances of the given + application + DiscoveredEdgeNodes: + type: array + items: + type: object + required: + - zoneId + - latencyServiceEndPoints + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + latencyServiceEndPoints: + $ref: '#/components/schemas/ServiceEndpoint' + minItems: 1 + description: List of candidate zones where application instance could be created. LatencyServiceEndpoint + is responsible for responding to latency measurement request from client + DiscoveredAppInsts: + type: array + items: + type: object + required: + - appId + - appProviderId + - appInstances + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + appInstances: + type: array + items: + type: object + required: + - instancesInfo + properties: + instancesInfo: + type: object + required: + - zoneId + - appProviderId + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + instanceDetails: + $ref: '#/components/schemas/InstanceDetails' + minItems: 1 + InstanceDetails: + type: array + items: + type: object + required: + - appInstanceInfo + properties: + appInstanceInfo: + type: object + required: + - instanceIdentifier + - instanceState + properties: + instanceIdentifier: + $ref: '#/components/schemas/InstanceIdentifier' + instancestate: + $ref: '#/components/schemas/InstanceState' + minItems: 1 + FederationRequestData: + type: object + required: + - initialDate + - partnerStatusLink + properties: + origOPFederationId: + $ref: '#/components/schemas/FederationIdentifier' + origOPCountryCode: + $ref: '#/components/schemas/CountryCode' + origOPMobileNetworkCodes: + $ref: '#/components/schemas/MobileNetworkIds' + origOPFixedNetworkCodes: + $ref: '#/components/schemas/FixedNetworkIds' + initialDate: + type: string + format: date-time + description: Time zone info of the federation initiated by the originating OP + partnerStatusLink: + $ref: '#/components/schemas/Uri' + FederationResponseData: + type: object + required: + - federationContextId + - platformCaps + properties: + partnerOPFederationId: + $ref: '#/components/schemas/FederationIdentifier' + partnerOPCountryCode: + $ref: '#/components/schemas/CountryCode' + federationContextId: + $ref: '#/components/schemas/FederationContextId' + edgeDiscoveryServiceEndPoint: + $ref: '#/components/schemas/ServiceEndpoint' + lcmServiceEndPoint: + $ref: '#/components/schemas/ServiceEndpoint' + partnerOPMobileNetworkCodes: + $ref: '#/components/schemas/MobileNetworkIds' + partnerOPFixedNetworkCodes: + $ref: '#/components/schemas/FixedNetworkIds' + offeredAvailabilityZones: + type: array + items: + $ref: '#/components/schemas/ZoneDetails' + minItems: 1 + description: List of zones, which the operator platform wishes to make available to developers/ISVs + of requesting operator platform. + platformCaps: + $ref: '#/components/schemas/PlatformCaps' + federationExpiryDate: + type: string + format: date-time + description: Date and Time zone info of the existing federation expiry + federationRenewalDate: + type: string + format: date-time + description: Date and Time zone info of the existing federation renewal. Shall be less than + federationExpiryDate + dateAndTimeZoneObject: + type: string + format: date-time + description: Date and Time zone info format + Flavour: + type: object + required: + - flavourId + - cpuArchType + - supportedOSTypes + - numCPU + - memorySize + - storageSize + properties: + flavourId: + $ref: '#/components/schemas/FlavourId' + cpuArchType: + $ref: '#/components/schemas/CPUArchType' + supportedOSTypes: + description: A list of operating systems which this flavour configuration can support e.g., + RHEL Linux, Ubuntu 18.04 LTS, MS Windows 2012 R2. + type: array + items: + $ref: '#/components/schemas/OSType' + minItems: 1 + numCPU: + type: integer + format: int32 + description: Number of available vCPUs + memorySize: + type: integer + format: int32 + description: Amount of RAM in Mbytes + storageSize: + type: integer + format: int32 + description: Amount of disk storage in Gbytes + gpu: + type: array + items: + $ref: '#/components/schemas/GpuInfo' + fpga: + type: integer + format: int32 + description: Number of FPGAs + vpu: + type: integer + description: Number of Intel VPUs available + hugepages: + type: array + items: + $ref: '#/components/schemas/HugePage' + cpuExclusivity: + type: boolean + description: Support for exclusive CPUs + GpuInfo: + type: object + required: + - gpuVendorType + - gpuModeName + - gpuMemory + - numGPU + properties: + gpuVendorType: + type: string + enum: + - GPU_PROVIDER_NVIDIA + - GPU_PROVIDER_AMD + description: GPU vendor name e.g. NVIDIA, AMD etc. + example: Nvidia + gpuModeName: + type: string + description: Model name corresponding to vendorType may include info e.g. for NVIDIA, model + name could be “Tesla M60”, “Tesla V100” etc. + gpuMemory: + type: integer + description: GPU memory in Mbytes + numGPU: + type: integer + description: Number of GPUs + HugePage: + type: object + required: + - pageSize + - number + properties: + pageSize: + type: string + enum: + - 2MB + - 4MB + - 1GB + description: Size of hugepage + number: + type: integer + description: Total number of huge pages + InterfaceDetails: + type: object + required: + - interfaceId + - commProtocol + - commPort + - visibilityType + properties: + interfaceId: + type: string + description: Each Port and corresponding traffic protocol exposed by the component is identified + by a name. Application client on user device requires this to uniquely identify the interface. + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,30}[A-Za-z0-9]$ + commProtocol: + type: string + enum: + - TCP + - UDP + - HTTP_HTTPS + description: Defines the IP transport communication protocol i.e., TCP, UDP or HTTP + commPort: + type: integer + format: int32 + minimum: 1 + maximum: 65535 + description: Port number exposed by the component. OP may generate a dynamic port towards the + UCs corresponding to this internal port and forward the client traffic from dynamic port to + container Port. + visibilityType: + description: Defines whether the interface is exposed to outer world or not i.e., external, + or internal. If this is set to "external", then it is exposed to external applications otherwise + it is exposed internally to edge application components within edge cloud. When exposed to + external world, an external dynamic port is assigned for UC traffic and mapped to the internal + container Port + type: string + enum: + - VISIBILITY_EXTERNAL + - VISIBILITY_INTERNAL + network: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{6,30}[A-Za-z0-9]$ + description: Name of the network. In case the application has to be associated with more than + 1 network then app provider must define the name of the network on which this interface has + to be exposed. This parameter is required only if the port has to be exposed on a specific + network other than default. + InterfaceName: + type: string + pattern: ^[a-z][a-z0-9]{3}$ + description: Interface Name. Required only if application has to be attached to a network other + than default. + InvalidParam: + type: object + properties: + param: + type: string + reason: + type: string + required: + - param + MobileNetworkIds: + type: object + properties: + mcc: + $ref: '#/components/schemas/Mcc' + mncs: + type: array + items: + $ref: '#/components/schemas/Mnc' + minItems: 1 + ObjectRepoLocation: + type: object + properties: + repoURL: + $ref: '#/components/schemas/Uri' + userName: + type: string + description: Username to access the repository + password: + type: string + description: Password to access the repository + token: + type: string + description: Authorization token to access the repository + OSType: + type: object + required: + - architecture + - distribution + - version + - license + properties: + architecture: + type: string + enum: + - x86_64 + - x86 + example: x86_64 + distribution: + type: string + enum: + - RHEL + - UBUNTU + - COREOS + - FEDORA + - WINDOWS + - OTHER + version: + type: string + enum: + - OS_VERSION_UBUNTU_2204_LTS + - OS_VERSION_RHEL_8 + - OS_VERSION_RHEL_7 + - OS_VERSION_DEBIAN_11 + - OS_VERSION_COREOS_STABLE + - OS_MS_WINDOWS_2012_R2 + - OTHER + license: + type: string + enum: + - OS_LICENSE_TYPE_FREE + - OS_LICENSE_TYPE_ON_DEMAND + - NOT_SPECIFIED + RepoType: + type: string + enum: + - PRIVATEREPO + - PUBLICREPO + - UPLOAD + description: Artefact or file repository location. PUBLICREPO is used of public URLs like GitHub, + Helm repo, docker registry etc., PRIVATEREPO is used for private repo managed by the application + developer, UPLOAD is for the case when artefact/file is uploaded from MEC web portal. OP should + pull the image from ‘repoUrl' immediately after receiving the request and then send back the response. + In case the repoURL corresponds to a docker registry, use docker v2 http api to do the pull. + ArtefactName: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,31}$ + description: Name of the artefact. + ArtefactVersionInfo: + type: string + description: Artefact version information + ArtefactDescription: + type: string + maxLength: 256 + description: Brief description of the artefact by the application provider + ArtefactVirtType: + type: string + enum: + - VM_TYPE + - CONTAINER_TYPE + ArtefactFileName: + type: string + minLength: 8 + maxLength: 32 + description: Name of the file. + ArtefactFileFormat: + type: string + enum: + - ZIP + - TAR + - TEXT + - TARGZ + description: Artefacts like Helm charts or Terraform scripts may need compressed format. + ArtefactDescriptorType: + type: string + enum: + - HELM + - TERRAFORM + - ANSIBLE + - SHELL + - COMPONENTSPEC + description: Type of descriptor present in the artefact. App provider can either define either + a Helm chart or a Terraform script or container spec. + LatencyConstraints: + type: string + enum: + - NONE + - LOW + - ULTRALOW + description: Latency requirements for the application.Allowed values (non-standardized) are none, + low and ultra-low. Ultra-Low may corresponds to range 15 - 30 msec, Low correspond to range 30 + - 50 msec. None means 51 and above + BandwidthRequired: + type: integer + format: int32 + minimum: 1 + description: Data transfer bandwidth requirement (minimum limit) for the application. It should + in Mbits/sec + MobilitySupport: + type: boolean + default: false + description: Indicates if an application is sensitive to user mobility and can be relocated. Default + is “FALSE” + MultiUserClients: + type: string + enum: + - APP_TYPE_SINGLE_USER + - APP_TYPE_MULTI_USER + description: Single user type application are designed to serve just one client. Multi user type + application is designed to serve multiple clients + NoOfUsersPerAppInst: + type: integer + default: 1 + description: Maximum no of clients that can connect to an instance of this application. This parameter + is relevant only for application of type multi user + AppProvisioning: + type: boolean + default: true + description: Define if application can be instantiated or not + AppComponents: + description: An application may consist of more than one component. Each component is associated + with a descriptor and may exposes its services externally or internally. App providers are required + to provide details about all these components, their associated descriptors and their DNS names. + type: array + items: + type: object + required: + - componentName + anyOf: + - required: + - serviceNameNB + - required: + - serviceNameEW + - required: + - artefactId + properties: + serviceNameNB: + $ref: '#/components/schemas/ServiceNameNB' + serviceNameEW: + $ref: '#/components/schemas/ServiceNameEW' + componentName: + $ref: '#/components/schemas/ComponentName' + artefactId: + $ref: '#/components/schemas/ArtefactId' + minItems: 1 + PersistentVolumeDetails: + type: object + required: + - volumeSize + - volumeMountPath + - volumeName + properties: + volumeSize: + type: string + enum: + - 10Gi + - 20Gi + - 50Gi + - 100Gi + description: size of the volume given by user (10GB, 20GB, 50 GB or 100GB) + volumeMountPath: + type: string + description: Defines the mount path of the volume + volumeName: + type: string + description: Human readable name for the volume + ephemeralType: + type: boolean + default: false + description: It indicates the ephemeral storage on the node and contents are not preserved if + containers restarts + accessMode: + type: string + enum: + - RW + - RO + default: RW + description: Values are RW (read/write) and RO (read-only)l + sharingPolicy: + type: string + enum: + - EXCLUSIVE + - SHARED + default: EXCLUSIVE + description: Exclusive or Shared. If shared, then in case of multiple containers same volume + will be shared across the containers. + ProblemDetails: + type: object + properties: + title: + type: string + description: Summary of the problem + detail: + type: string + description: Specific detail of the issue + cause: + type: string + description: Fixed string indicating cause of the issue + invalidParams: + type: array + items: + $ref: '#/components/schemas/InvalidParam' + minItems: 0 + ResourceReservationDuration: + description: Time period for which resources are to be reserved starting from now + type: object + minProperties: 1 + properties: + numOfDays: + type: integer + format: int32 + description: Number of days to be reserved + numOfMonths: + type: integer + format: int32 + description: Number of months to be reserved + numOfYears: + type: integer + format: int32 + description: Number of years to be reserved + ServiceEndpoint: + type: object + required: + - port + anyOf: + - required: + - fqdn + - required: + - ipv4Addresses + - required: + - ipv6Addresses + properties: + port: + $ref: '#/components/schemas/Port' + fqdn: + $ref: '#/components/schemas/EdgeAppFQDN' + ipv4Addresses: + type: array + items: + $ref: '#/components/schemas/Ipv4Addr' + minItems: 1 + ipv6Addresses: + type: array + items: + $ref: '#/components/schemas/Ipv6Addr' + minItems: 1 + ZoneDetails: + type: object + required: + - zoneId + - geographyDetails + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + geolocation: + $ref: '#/components/schemas/GeoLocation' + geographyDetails: + type: string + description: Details about cities or state covered by the edge. Details about the type of locality + for eg rural, urban, industrial etc. This information is defined in human readable form. + ZoneRegistrationRequestData: + type: object + required: + - acceptedAvailabilityZones + properties: + acceptedAvailabilityZones: + type: array + items: + $ref: '#/components/schemas/ZoneIdentifier' + minItems: 1 + availZoneNotifLink: + $ref: '#/components/schemas/Uri' + ZoneRegistrationResponseData: + type: object + required: + - acceptedZoneResourceInfo + properties: + acceptedZoneResourceInfo: + type: array + items: + $ref: '#/components/schemas/ZoneRegisteredData' + minItems: 1 + ZoneRegisteredData: + type: object + required: + - zoneId + - reservedComputeResources + - computeResourceQuotaLimits + - flavoursSupported + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + reservedComputeResources: + description: Resources exclusively reserved for the originator OP. + type: array + items: + $ref: '#/components/schemas/ComputeResourceInfo' + minItems: 1 + computeResourceQuotaLimits: + description: Max quota on resources partner OP allows over reserved resources. + type: array + items: + $ref: '#/components/schemas/ComputeResourceInfo' + minItems: 1 + flavoursSupported: + type: array + items: + $ref: '#/components/schemas/Flavour' + minItems: 1 + networkResources: + type: object + required: + - egressBandWidth + - dedicatedNIC + - supportSriov + - supportDPDK + properties: + egressBandWidth: + type: integer + format: int32 + description: Max dl throughput that this edge can offer. It is defined in Mbps. + dedicatedNIC: + type: integer + format: int32 + description: Number of network interface cards which can be dedicatedly assigned to application + pods on isolated networks. This includes virtual as well physical NICs + supportSriov: + type: boolean + description: If this zone support SRIOV networks or not + supportDPDK: + type: boolean + description: If this zone supports DPDK based networking. + zoneServiceLevelObjsInfo: + type: object + description: It is a measure of the actual amount of data that is being sent over a network + per unit of time and indicates máximum supported value for a zone + required: + - latencyRanges + - jitterRanges + - throughputRanges + properties: + latencyRanges: + type: object + properties: + minLatency: + type: integer + format: int32 + minimum: 1 + description: The time for data/packet to reach from UC to edge application. It represent + mínimum latency in milli seconds that may exist between UCs and edge apps in this + zone but it can be higher in actual. + maxLatency: + type: integer + format: int32 + description: The maximum limit of latency between UC and Edge App in milli seconds. + jitterRanges: + type: object + properties: + minJitter: + type: integer + format: int32 + minimum: 1 + maxJitter: + type: integer + format: int32 + description: The maximum limit of network jitter between UC and Edge App in milli seconds. + throughputRanges: + type: object + properties: + minThroughput: + type: integer + format: int32 + minimum: 1 + description: The minimum limit of network throughput between UC and Edge App in Mega + bits per seconds (Mbps). + maxThroughput: + type: integer + format: int32 + description: The maximum limit of network throughput between UC and Edge App in Mega + bits per seconds (Mbps). + responses: + '400': + description: Bad request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '412': + description: Precondition Failed + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Entity + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '501': + description: Not Implemented + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '503': + description: Service Unavailable + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '520': + description: Web Server Returned an Unknown Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + 400BadRequest: + description: Bad request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + examples: + InvalidFedParameters: + description: Sufficient parameters must be specified to allow the partner OP to validate + federation request + value: + title: Insufficient parameters + details: Incorrect values received in federation request + cause: INVALID_FED_RQST_PARAMS + 404NotFound: + description: Resource Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + examples: + FederationContextNotFound: + description: Federation context does not exist + value: + title: Federation context Id not found + details: Partner OP does not recognize the federationContextId from Originating OP + cause: INVALID_FED_CTX_ID + FederationNotFound: + description: Federation terminated parmanently + value: + title: Federation context Id not found + details: Partner OP does not recognize the federationContextId from Originating OP + cause: FED_PERMANENTLY_TERMINAT + ZoneNotFound: + description: Zone Not Found + value: + title: Requested Zone Id not found + details: Requested zone by the Originating OP does not exist with Partner OP + cause: ZONE_ID_NOT_FOUND + AppNotFound: + description: Application Not Found + value: + title: Requested Application Id not found + details: Requested Application by the Originating OP does not exist with Partner OP + cause: APP_ID_NOT_FOUND + AppInstNotFound: + description: Application Instance Not Found + value: + title: Requested App instance Id not found + details: Requested application instance by the Originating OP does not exist with Partner + OP + cause: APP_INST_NOT_FOUND + default: + description: Generic Error +paths: + /federation-resources: + get: + summary: Retrieves REST APIs supported by an OP for federation services. + operationId: GetFederationAPIs + tags: + - FederationAPIManagement + responses: + '200': + description: Federation meta-info request accepted + content: + application/json: + schema: + type: object + required: + - federationSupportedAPIs + properties: + federationSupportedAPIs: + $ref: '#/components/schemas/FederationSupportedAPIs' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /partner: + post: + summary: Creates one direction federation with partner operator platform. + operationId: CreateFederation + tags: + - FederationManagement + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/FederationRequestData' + responses: + '200': + description: Federation meta-info request accepted + content: + application/json: + schema: + $ref: '#/components/schemas/FederationResponseData' + headers: + Location: + description: 'Contains the URI of the newly created resource, according to the structure: + {apiRoot}/operatorplatform/federation/v1/partner/{federationContextId}' + required: true + schema: + type: string + Accept-Encoding: + description: Accept-Encoding, described in IETF RFC 7694 + schema: + type: string + Content-Encoding: + description: Content-Encoding, described in IETF RFC 7231 + schema: + type: string + '400': + $ref: '#/components/responses/400BadRequest' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onPartnerStatusEvent: + '{$request.body#/partnerStatusLink }': + post: + requestBody: + description: 'OP uses this callback api to notify partner OP about change in federation + status, federation metadata or offered zone details. Allowed combinations of objectType + and operationType are + + - FEDERATION - STATUS: Status specified by parameter ''federationStatus''. + + - ZONES - STATUS: Status specified by parameter ''zoneStatus''. + + - ZONES - ADD: Use parameter ''addZones'' to define add new zones + + - ZONES - REMOVE: Use parameter ''removeZones'' to define remove zones. + + - EDGE_DISCOVERY_SERVICE - UPDATE: Use parameter ''edgeDiscoverySvcEndPoint'' to specify + new endpoints + + - LCM_SERVICE - UPDATE: Use parameter ''lcmSvcEndPoint'' to specify new endpoints + + - MOBILE_NETWORK_CODES - ADD: Use parameter ''addMobileNetworkIds'' to define new mobile + network codes. + + - MOBILE_NETWORK_CODES - REMOVE: Use parameter ''removeMobileNetworkIds'' to remove + mobile network codes. + + - FIXED_NETWORK_CODES - ADD: Use parameter ''addFixedNetworkIds'' to define new fixed + network codes. + + - FIXED_NETWORK_CODES - REMOVE: Use parameter ''removeFixedNetworkIds'' to remove fixed + network codes. + + - SERVICE_APIS - ADD/REMOVE: Parameter Usage ''addServiceAPIs / removeServiceAPIs'' + to add or remove Service APIs support. + + ' + content: + application/json: + schema: + type: object + required: + - federationContextId + - objectType + - operationType + - modificationDate + properties: + federationContextId: + $ref: '#/components/schemas/FederationContextId' + objectType: + type: string + enum: + - FEDERATION + - ZONES + - EDGE_DISCOVERY_SERVICE + - LCM_SERVICE + - MOBILE_NETWORK_CODES + - FIXED_NETWORK_CODES + - SERVICE_APIS + operationType: + type: string + enum: + - STATUS + - UPDATE + - ADD + - REMOVE + edgeDiscoverySvcEndPoint: + $ref: '#/components/schemas/ServiceEndpoint' + lcmSvcEndPoint: + $ref: '#/components/schemas/ServiceEndpoint' + addMobileNetworkIds: + $ref: '#/components/schemas/MobileNetworkIds' + removeMobileNetworkIds: + $ref: '#/components/schemas/MobileNetworkIds' + addFixedNetworkIds: + $ref: '#/components/schemas/FixedNetworkIds' + removeFixedNetworkIds: + $ref: '#/components/schemas/FixedNetworkIds' + addZones: + type: array + items: + $ref: '#/components/schemas/ZoneDetails' + description: List of zones, which the operator platform wishes to make available + to developers/ISVs of requesting operator platform. + minItems: 1 + removeZones: + type: array + items: + $ref: '#/components/schemas/ZoneIdentifier' + description: List of zones, which the operator platform no longer wishes to + share. + minItems: 1 + addServiceAPIs: + $ref: '#/components/schemas/serviceAPINames' + removeServiceAPIs: + $ref: '#/components/schemas/serviceAPINames' + zoneStatus: + type: array + items: + type: object + required: + - zoneId + - status + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + status: + $ref: '#/components/schemas/Status' + minItems: 1 + federationStatus: + $ref: '#/components/schemas/Status' + modificationDate: + type: string + format: date-time + description: Date and time of the federation modification by the originating + partner OP + responses: + '204': + description: Expected response to a successful call back processing + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: + - fed-mgmt-notif + /{federationContextId}/partner: + get: + summary: Retrieves details about the federation context with the partner OP. The response shall + provide info about the zones offered by the partner, partner OP network codes, information about + edge discovery and LCM service etc. + operationId: GetFederationDetails + tags: + - FederationManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + responses: + '200': + description: Federation meta-info request accepted + content: + application/json: + schema: + type: object + properties: + edgeDiscoveryServiceEndPoint: + $ref: '#/components/schemas/ServiceEndpoint' + lcmServiceEndPoint: + $ref: '#/components/schemas/ServiceEndpoint' + allowedMobileNetworkIds: + $ref: '#/components/schemas/MobileNetworkIds' + allowedFixedNetworkIds: + $ref: '#/components/schemas/FixedNetworkIds' + offeredAvailabilityZones: + type: array + items: + $ref: '#/components/schemas/ZoneDetails' + minItems: 1 + platformCaps: + $ref: '#/components/schemas/PlatformCaps' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + patch: + summary: API used by the Originating OP towards the partner OP, to update the parameters associated + to the existing federation + operationId: UpdateFederation + tags: + - FederationManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + required: true + description: Details about changes origination OP wished to apply + content: + application/json: + schema: + type: object + required: + - objectType + - operationType + - modificationDate + properties: + objectType: + type: string + enum: + - MOBILE_NETWORK_CODES + - FIXED_NETWORK_CODES + - OPS_POLICY + - APP_POLICY + operationType: + type: string + enum: + - ADD_CODES + - REMOVE_CODES + - UPDATE_CODES + - ADD_POLICY + - REMOVE_POLICY + - UPDATE_POLICY + addMobileNetworkIds: + $ref: '#/components/schemas/MobileNetworkIds' + removeMobileNetworkIds: + $ref: '#/components/schemas/MobileNetworkIds' + addFixedNetworkIds: + $ref: '#/components/schemas/FixedNetworkIds' + removeFixedNetworkIds: + $ref: '#/components/schemas/FixedNetworkIds' + assocAppPolicies: + $ref: '#/components/schemas/AssocApplPolicies' + assocOpsPolicies: + $ref: '#/components/schemas/AssocOpsPolicies' + modificationDate: + type: string + format: date-time + description: Date and time of the federation modification by the originating partner + OP + responses: + '200': + description: Federation meta-info request accepted + content: + application/json: + schema: + type: object + properties: + edgeDiscoveryServiceEndPoint: + $ref: '#/components/schemas/ServiceEndpoint' + lcmServiceEndPoint: + $ref: '#/components/schemas/ServiceEndpoint' + allowedMobileNetworkIds: + $ref: '#/components/schemas/MobileNetworkIds' + allowedFixedNetworkIds: + $ref: '#/components/schemas/FixedNetworkIds' + offeredAvailabilityZones: + type: array + items: + $ref: '#/components/schemas/ZoneDetails' + minItems: 1 + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + delete: + summary: Remove existing federation with the partner OP + operationId: DeleteFederationDetails + tags: + - FederationManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + responses: + '200': + description: Federation removed successfully + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /fed-context-id: + get: + summary: Retrieves the existing federationContextId with partner operator platform. + operationId: GetFederationContextId + tags: + - FederationManagement + responses: + '200': + description: Federation context identifier retrieval request accepted + content: + application/json: + schema: + type: object + required: + - FederationContextId + properties: + FederationContextId: + $ref: '#/components/schemas/FederationContextId' + headers: + Location: + description: 'Contains the URI of the existing resource, according to the structure: {apiRoot}/operatorplatform/federation/v1/partner/{federationContextId}' + required: true + schema: + type: string + Accept-Encoding: + description: Accept-Encoding, described in IETF RFC 7694 + schema: + type: string + Content-Encoding: + description: Content-Encoding, described in IETF RFC 7231 + schema: + type: string + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/health: + get: + summary: Retrieves health status of the federation context with the Partner OP. + operationId: GetFederationHealth + tags: + - FederationManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + responses: + '200': + description: Federation health status information object + content: + application/json: + schema: + type: object + required: + - federationHealthStatus + properties: + federationHealthStatus: + $ref: '#/components/schemas/FederationHealthInfo' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/renew: + post: + summary: API used by the Originating OP to renew the existing federation + operationId: RenewFederation + tags: + - FederationManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + responses: + '200': + description: Federation renewal request accepted + content: + application/json: + schema: + type: object + required: + - FederationContextId + - federationRenewalDate + - federationExpiryDate + properties: + FederationContextId: + $ref: '#/components/schemas/FederationContextId' + federationRenewalDate: + $ref: '#/components/schemas/dateAndTimeZoneObject' + federationExpiryDate: + $ref: '#/components/schemas/dateAndTimeZoneObject' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/platform-caps: + get: + summary: Retrieves details about OP capabilities of the federated partner. + operationId: GetPlatformCapabilities + tags: + - FederationManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: capType + in: query + required: false + schema: + $ref: '#/components/schemas/CapabilityID' + responses: + '200': + description: Federation meta-info request accepted + content: + application/json: + schema: + type: object + anyOf: + - required: + - deviceConnStatusChangeCap + - required: + - locationRetrievalCap + - required: + - userPlaneMgmtEvtCap + - required: + - dynamicQoSCap + properties: + deviceConnStatusChangeCap: + $ref: '#/components/schemas/DeviceConnStatusChangeCap' + locationRetrievalCap: + $ref: '#/components/schemas/LocationRetrievalCap' + userPlaneMgmtEvtCap: + $ref: '#/components/schemas/UserPlaneMgmtEvtCap' + dynamicQoSCap: + $ref: '#/components/schemas/DynamicQoSCap' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/partner/service/{serviceType}: + get: + summary: Retrieves the list of Service APIs and associated information that a partner OP supports + operationId: GetServiceAPIsDetails + tags: + - FederationManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: serviceType + in: path + required: true + schema: + $ref: '#/components/schemas/serviceType' + responses: + '200': + description: List of Service APIs names and associated configuration info as supported capabilities + content: + application/json: + schema: + type: object + required: + - ServiceType + - serviceCaps + - apiRoutingInfo + properties: + serviceCaps: + $ref: '#/components/schemas/serviceAPINames' + serviceType: + $ref: '#/components/schemas/serviceType' + apiRoutingInfo: + $ref: '#/components/schemas/serviceRoutingInfo' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/zones: + get: + summary: Retrieves details about the computation and network resources that partner OP has reserved + for this zone. + operationId: GetZoneData + tags: + - AvailabilityZoneInfoSynchronization + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: zoneId + in: query + required: false + schema: + $ref: '#/components/schemas/ZoneIdentifier' + responses: + '200': + description: Zone metadata + content: + application/json: + schema: + $ref: '#/components/schemas/ZoneRegisteredData' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + post: + summary: Originating OP informs partner OP that it is willing to access the specified zones and + partner OP shall reserve compute and network resources for these zones. + operationId: ZoneSubscribe + tags: + - AvailabilityZoneInfoSynchronization + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ZoneRegistrationRequestData' + required: true + responses: + '200': + description: Zone registered successfully + content: + application/json: + schema: + $ref: '#/components/schemas/ZoneRegistrationResponseData' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onZoneResourceUpdateEvent: + '{$request.body#/availZoneNotifLink}': + post: + requestBody: + description: Notification about resource availability. + content: + application/json: + schema: + type: object + required: + - federationContextId + - zoneId + - zoneResUpdInfo + properties: + federationContextId: + $ref: '#/components/schemas/FederationIdentifier' + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + zoneResUpdInfo: + type: array + items: + type: object + minProperties: 1 + properties: + availableCompResources: + description: Resources exclusively reserved for the originator OP. + type: array + items: + $ref: '#/components/schemas/ComputeResourceInfo' + minItems: 1 + availableNetResources: + type: object + properties: + egressBandWidth: + type: integer + format: int32 + description: Max dl throughput that this edge can offer. It is defined + in Mbps. + dedicatedNIC: + type: integer + format: int32 + supportSriov: + type: boolean + description: If this zone support SRIOV networks or not + supportDPDK: + type: boolean + description: If this zone supports DPDK based networking + minProperties: 1 + responses: + '200': + description: Zone info notification acknowledged + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: + - fed-mgmt-notif + /{federationContextId}/zones/{zoneId}: + delete: + summary: Assert usage of a partner OP zone. Originating OP informs partner OP that it will no longer + access the specified zone. + operationId: ZoneUnsubscribe + tags: + - AvailabilityZoneInfoSynchronization + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + responses: + '200': + description: Zone deregistered successfully + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + get: + summary: Retrieves details about the computation and network resources that partner OP has reserved + for this zone. + operationId: GetZoneDetails + tags: + - AvailabilityZoneInfoSynchronization + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + responses: + '200': + description: Zone metadata + content: + application/json: + schema: + $ref: '#/components/schemas/ZoneRegisteredData' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/artefact: + post: + summary: Uploads application artefact on partner OP. Artefact is a zip file containing scripts and/or + packaging files like Terraform or Helm which are required to create an instance of an application. + operationId: UploadArtefact + tags: + - ArtefactManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + description: An application can consist of multiple components. App providers are allowed to define + separate artefacts for each component or they could define a consolidated artefact at application + level. + content: + multipart/form-data: + schema: + type: object + required: + - artefactId + - appProviderId + - artefactName + - artefactVersionInfo + - artefactVirtType + - artefactDescriptorType + - componentSpec + properties: + artefactId: + $ref: '#/components/schemas/ArtefactId' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + artefactName: + $ref: '#/components/schemas/ArtefactName' + artefactVersionInfo: + $ref: '#/components/schemas/ArtefactVersionInfo' + artefactDescription: + $ref: '#/components/schemas/ArtefactDescription' + artefactVirtType: + $ref: '#/components/schemas/ArtefactVirtType' + artefactFileName: + $ref: '#/components/schemas/ArtefactFileName' + artefactFileFormat: + $ref: '#/components/schemas/ArtefactFileFormat' + artefactDescriptorType: + $ref: '#/components/schemas/ArtefactDescriptorType' + repoType: + $ref: '#/components/schemas/RepoType' + artefactRepoLocation: + $ref: '#/components/schemas/ObjectRepoLocation' + artefactFile: + type: string + format: binary + description: Helm archive/Terraform archive/container spec file or Binary image associated + with an application component. + componentSpec: + type: array + items: + $ref: '#/components/schemas/ComponentSpec' + minItems: 1 + required: true + responses: + '200': + description: Artefact uploaded successfully + '202': + description: Artefact upload request accepted + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/artefact/{artefactId}: + get: + summary: Retrieves details about an artefact. + operationId: GetArtefact + tags: + - ArtefactManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: artefactId + in: path + required: true + schema: + $ref: '#/components/schemas/ArtefactId' + responses: + '200': + description: Artefact details + content: + application/json: + schema: + type: object + required: + - artefactId + - appProviderId + - artefactName + - artefactVersionInfo + - artefactVirtType + - artefactDescriptorType + properties: + artefactId: + $ref: '#/components/schemas/ArtefactId' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + artefactName: + $ref: '#/components/schemas/ArtefactName' + artefactDescription: + $ref: '#/components/schemas/ArtefactDescription' + artefactVersionInfo: + $ref: '#/components/schemas/ArtefactVersionInfo' + artefactVirtType: + $ref: '#/components/schemas/ArtefactVirtType' + artefactFileName: + $ref: '#/components/schemas/ArtefactFileName' + artefactFileFormat: + $ref: '#/components/schemas/ArtefactFileFormat' + artefactDescriptorType: + $ref: '#/components/schemas/ArtefactDescriptorType' + repoType: + $ref: '#/components/schemas/RepoType' + artefactRepoLocation: + $ref: '#/components/schemas/ObjectRepoLocation' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + delete: + summary: Removes an artefact from partner OP. + operationId: RemoveArtefact + tags: + - ArtefactManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: artefactId + in: path + required: true + schema: + $ref: '#/components/schemas/ArtefactId' + responses: + '200': + description: Artefact deletion successful + '202': + description: Artefact deletion request accepted + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/files: + post: + summary: Uploads an image file. Originating OP uses this api to onboard an application image to + partner OP. + operationId: UploadFile + tags: + - ArtefactManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + content: + multipart/form-data: + schema: + type: object + required: + - fileId + - appProviderId + - fileName + - fileVersionInfo + - fileType + - imgOSType + - imgInsSetArch + properties: + fileId: + $ref: '#/components/schemas/FileId' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + fileName: + $ref: '#/components/schemas/FileName' + fileDescription: + $ref: '#/components/schemas/FileDescription' + fileVersionInfo: + $ref: '#/components/schemas/FileVersionInfo' + fileType: + $ref: '#/components/schemas/VirtImageType' + checksum: + type: string + description: MD5 checksum for VM and file-based images, sha256 digest for containers + imgOSType: + $ref: '#/components/schemas/OSType' + imgInsSetArch: + $ref: '#/components/schemas/CPUArchType' + repoType: + $ref: '#/components/schemas/RepoType' + fileRepoLocation: + $ref: '#/components/schemas/ObjectRepoLocation' + file: + type: string + format: binary + description: Binary image associated with an application component. + required: true + responses: + '200': + description: File uploaded successfully + '202': + description: File upload request accepted + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/files/{fileId}: + delete: + summary: Removes an image file from partner OP. + operationId: RemoveFile + tags: + - ArtefactManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: fileId + in: path + required: true + schema: + $ref: '#/components/schemas/FileId' + responses: + '200': + description: Image deletion successful + '202': + description: Image deletion request accepted + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + get: + summary: View an image file from partner OP. + operationId: ViewFile + tags: + - ArtefactManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: fileId + in: path + required: true + schema: + $ref: '#/components/schemas/FileId' + responses: + '200': + description: Image details + content: + application/json: + schema: + type: object + required: + - fileId + - appProviderId + - fileName + - fileVersionInfo + - fileType + - imgOSType + - imgInsSetArch + properties: + fileId: + $ref: '#/components/schemas/FileId' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + fileName: + $ref: '#/components/schemas/FileName' + fileDescription: + $ref: '#/components/schemas/FileDescription' + fileVersionInfo: + $ref: '#/components/schemas/FileVersionInfo' + fileType: + $ref: '#/components/schemas/VirtImageType' + checksum: + type: string + description: MD5 checksum for VM and file-based images, sha256 digest for containers + imgOSType: + $ref: '#/components/schemas/OSType' + imgInsSetArch: + $ref: '#/components/schemas/CPUArchType' + repoType: + $ref: '#/components/schemas/RepoType' + fileRepoLocation: + $ref: '#/components/schemas/ObjectRepoLocation' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/application/onboarding: + post: + summary: Submits an application details to a partner OP. Based on the details provided, partner + OP shall do bookkeeping, resource validation and other pre-deployment operations. + operationId: OnboardApplication + tags: + - ApplicationOnboardingManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + required: true + description: Details about application compute resource requirements, associated artefacts, QoS + profile and regions where application shall be made available etc. + content: + application/json: + schema: + type: object + required: + - appId + - appProviderId + - appMetaData + - appQoSProfile + - appComponentSpecs + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + appDeploymentZones: + description: Details about partner OP zones where the application should be made available; This + field when specified will instruct the OP to restrict application instantiation only + on the listed zones. + type: array + items: + $ref: '#/components/schemas/ZoneIdentifier' + minItems: 1 + appMetaData: + $ref: '#/components/schemas/AppMetaData' + appQoSProfile: + $ref: '#/components/schemas/AppQoSProfile' + appComponentSpecs: + $ref: '#/components/schemas/AppComponentSpecs' + appStatusCallbackLink: + $ref: '#/components/schemas/Uri' + edgeAppFQDN: + $ref: '#/components/schemas/EdgeAppFQDN' + responses: + '200': + description: Application onboarded successfully + '202': + description: Application onboarding request accepted + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onApplicationOnboardStatusEvent: + '{$request.body#/appStatusCallbackLink}': + post: + requestBody: + description: Notification payload. + content: + application/json: + schema: + type: object + required: + - federationContextId + - appId + - statusInfo + properties: + federationContextId: + $ref: '#/components/schemas/FederationIdentifier' + appId: + $ref: '#/components/schemas/AppIdentifier' + statusInfo: + type: array + items: + type: object + required: + - zoneId + - onboardStatusInfo + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + onboardStatusInfo: + $ref: '#/components/schemas/OnboardStatusInfo' + minItems: 1 + responses: + '204': + description: Application status updated + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: + - fed-mgmt-notif + /{federationContextId}/application/onboarding/app/{appId}: + delete: + summary: Deboards the application from all zones, if any, and deletes the App. + operationId: DeleteApp + tags: + - ApplicationOnboardingManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + responses: + '200': + description: App deletion successful + '202': + description: App deletion request accepted + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + patch: + summary: Updates partner OP about changes in application compute resource requirements, QOS Profile, + associated descriptor or change in associated components + operationId: UpdateApplication + tags: + - ApplicationOnboardingManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + requestBody: + required: true + description: Details about application compute resource requirements, associated artefact and + QOS profile that needs to be updated. + content: + application/json: + schema: + type: object + minProperties: 1 + properties: + appUpdQoSProfile: + description: Parameters corresponding to the performance constraints, tenancy details + etc. + type: object + anyOf: + - required: + - latencyConstraint + - required: + - bandwidthRequired + - required: + - mobilitySupport + - required: + - multiUserClients + - required: + - appProvisioning + properties: + latencyConstraints: + $ref: '#/components/schemas/LatencyConstraints' + bandwidthRequired: + $ref: '#/components/schemas/BandwidthRequired' + mobilitySupport: + $ref: '#/components/schemas/MobilitySupport' + multiUserClients: + $ref: '#/components/schemas/MultiUserClients' + noOfUsersPerAppInst: + $ref: '#/components/schemas/NoOfUsersPerAppInst' + appProvisioning: + $ref: '#/components/schemas/AppProvisioning' + appComponents: + $ref: '#/components/schemas/AppComponents' + responses: + '200': + description: Application update successful + '202': + description: Application update request accepted + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + get: + summary: Retrieves application details from partner OP + operationId: ViewApplication + tags: + - ApplicationOnboardingManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + responses: + '200': + description: Application details + content: + application/json: + schema: + type: object + required: + - appId + - appProviderId + - appDeploymentZones + - appMetaData + - appQoSProfile + - appComponentSpecs + - onboardStatusInfo + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + appDeploymentZones: + description: Details about partner OP zones where the application should be made available; This + field when specified will instruct the OP to restrict application instantiation + only on the listed zones. + type: array + items: + type: object + required: + - countryCode + - zoneInfo + properties: + countryCode: + $ref: '#/components/schemas/CountryCode' + zoneInfo: + $ref: '#/components/schemas/ZoneIdentifier' + minItems: 1 + appMetaData: + $ref: '#/components/schemas/AppMetaData' + appQoSProfile: + $ref: '#/components/schemas/AppQoSProfile' + appComponentSpecs: + $ref: '#/components/schemas/AppComponentSpecs' + onboardStatusInfo: + $ref: '#/components/schemas/OnboardStatusInfo' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/application/onboarding/app/{appId}/zone/{zoneId}: + delete: + summary: Deboards an application from specific partner OP zones + operationId: DeboardApplication + tags: + - ApplicationOnboardingManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + responses: + '200': + description: Application deboarded successfully + '202': + description: Application deboard request accepted + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/application/onboarding/app/{appId}/additionalZones: + post: + summary: Onboards an existing application to a new zone within partner OP. + operationId: OnboardExistingAppNewZones + tags: + - ApplicationOnboardingManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + requestBody: + required: true + description: Details about new zones where application shall be made available + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ZoneIdentifier' + minItems: 1 + responses: + '200': + description: Application onboarding succussful + '202': + description: Application onboarding request accepted + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/application/onboarding/app/{appId}/zoneForbid: + post: + summary: Forbid/allow application instantiation on a partner zone + operationId: LockUnlockApplicationZone + tags: + - ApplicationOnboardingManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + requestBody: + required: true + content: + application/json: + schema: + type: array + items: + type: object + description: List of zones where application instantiation shall be forbidden or allowed. + required: + - zoneId + - forbid + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + forbid: + type: boolean + description: Value 'true' will forbid application instantiation on this zone. No + new instance of the application can be created on this zone. + minItems: 1 + responses: + '200': + description: Application forbid/permit request accepted + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/application/lcm: + post: + summary: Instantiates an application on a partner OP zone. + operationId: InstallApp + tags: + - ApplicationDeploymentManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: Idempotency-Key + in: header + required: true + schema: + $ref: '#/components/schemas/TransactionId' + requestBody: + description: Details about application and zones where application instance should be created. + It also definea call back URI which the partner OP shall use update home OP about a change in + instance status. + content: + application/json: + schema: + type: object + required: + - appId + - appProviderId + - appVersion + - zoneInfo + - appInstCallbackLink + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appVersion: + type: string + description: Version info of the application + appProviderId: + $ref: '#/components/schemas/AppProviderId' + zoneInfo: + type: object + required: + - zoneId + - flavourId + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + flavourId: + $ref: '#/components/schemas/FlavourId' + resourceConsumption: + type: string + enum: + - RESERVED_RES_SHALL + - RESERVED_RES_PREFER + - RESERVED_RES_AVOID + - RESERVED_RES_FORBID + default: RESERVED_RES_AVOID + description: Specifies if the application can be instantiated using pre-reserved + resource or not. App provider can pre-reserve a pool of compute resource on each + zone. 'RESERVED_RES_SHALL' instruct OP to use only the pre-reserved resources. + 'RESERVED_RES_PREFER' instruct to first try using pre-reserved resource, if none + available go for non-reserved resources. 'RESERVED_RES_AVOID' instruct OP not + to use pre-reserved resource if possible, it is a choice depending upon circumstances + 'RESERVED_RES_FORBID' instruct OP not to use pre-reserved resources. + resPool: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,30}[A-Za-z0-9]$ + description: Resource pool to be used for application instantiation on this zone. Valid + only if IE 'resourceConsumption' is set to 'RESERVED_RES_SHALL' or 'RESERVED_RES_PREFER' + appInstCallbackLink: + $ref: '#/components/schemas/Uri' + required: true + responses: + '202': + description: Application instance creation request accepted. + content: + application/json: + schema: + type: object + required: + - zoneId + - appInstIdentifier + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + appInstIdentifier: + $ref: '#/components/schemas/InstanceIdentifier' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onInstanceStatusEvent: + '{$request.body#/appInstCallbackLink}': + post: + requestBody: + description: Notification payload. + content: + application/json: + schema: + type: object + required: + - federationContextId + - appId + - appInstanceId + - zoneId + - appInstanceInfo + properties: + federationContextId: + $ref: '#/components/schemas/FederationIdentifier' + appId: + $ref: '#/components/schemas/AppIdentifier' + appInstanceId: + $ref: '#/components/schemas/InstanceIdentifier' + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + appInstanceInfo: + type: object + properties: + appInstanceState: + type: string + enum: + - PENDING + - READY + - FAILED + - TERMINATING + description: Running status of the application instance. + message: + type: string + description: Event information or failure message. + accesspointInfo: + description: Information about the IP and Port exposed by the OP. Application + clients shall use these access points to reach this application instance + type: array + items: + type: object + required: + - interfaceId + - accessPoints + properties: + interfaceId: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,30}[A-Za-z0-9]$ + description: This is the interface Identifier that app provider defines + when application is onboarded. + accessPoints: + $ref: '#/components/schemas/ServiceEndpoint' + minItems: 1 + minProperties: 1 + modificationDate: + type: string + format: date-time + description: Date and time of the instance state modification by partner OP. + responses: + '204': + description: Application instance state notification acknowledged + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: + - fed-mgmt-notif + /{federationContextId}/application/lcm/app/{appId}/instance/{appInstanceId}/zone/{zoneId}: + get: + summary: Retrieves an application instance details from partner OP. + operationId: GetAppInstanceDetails + tags: + - ApplicationDeploymentManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + - name: appInstanceId + in: path + required: true + schema: + $ref: '#/components/schemas/InstanceIdentifier' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + responses: + '200': + description: Application instance details + content: + application/json: + schema: + type: object + properties: + appInstanceState: + $ref: '#/components/schemas/InstanceState' + accesspointInfo: + description: Information about the IP and Port exposed by the OP. Application clients + shall use these access points to reach this application instance + type: array + items: + type: object + required: + - interfaceId + - accessPoints + properties: + interfaceId: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,30}[A-Za-z0-9]$ + description: This is the interface identifier that app provider defines when + application is onboarded. + accessPoints: + $ref: '#/components/schemas/ServiceEndpoint' + minItems: 1 + minProperties: 1 + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + delete: + summary: Terminate an application instance on a partner OP zone. + operationId: RemoveApp + tags: + - ApplicationDeploymentManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + - name: appInstanceId + in: path + required: true + schema: + $ref: '#/components/schemas/InstanceIdentifier' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + responses: + '200': + description: Application instance termination request accepted + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/application/lcm/app/{appId}/appProvider/{appProviderId}: + get: + summary: Retrieves all application instance of partner OP + operationId: GetAllAppInstances + tags: + - ApplicationDeploymentManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + - name: appProviderId + in: path + required: true + schema: + $ref: '#/components/schemas/AppProviderId' + responses: + '200': + description: Application Instance details + content: + application/json: + schema: + type: array + items: + type: object + required: + - zoneId + - appInstanceInfo + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + appInstanceInfo: + type: array + items: + type: object + required: + - appInstIdentifier + - appInstanceState + properties: + appInstIdentifier: + $ref: '#/components/schemas/InstanceIdentifier' + appInstanceState: + $ref: '#/components/schemas/InstanceState' + minItems: 1 + minItems: 1 + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/isv/resource/zone/{zoneId}/appProvider/{appProviderId}: + post: + summary: Reserves resources (compute, network and storage) on a partner OP zone. ISVs registered + with home OP reserves resources on a partner OP zone. + operationId: CreateResourcePools + tags: + - AppProviderResourceManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + - name: appProviderId + in: path + required: true + schema: + $ref: '#/components/schemas/AppProviderId' + requestBody: + content: + application/json: + schema: + type: object + required: + - resRequest + - resourceReservationCallbackLink + properties: + resRequest: + description: Compute flavours to be reserved and their time duration + type: object + required: + - poolName + - flavours + - reserveDuration + properties: + poolName: + $ref: '#/components/schemas/PoolName' + flavours: + type: array + items: + type: object + required: + - flavourId + - numFlavour + properties: + flavourId: + $ref: '#/components/schemas/FlavourId' + numFlavour: + type: integer + format: int32 + description: Total number of flavours to be reserved + minNumOfFlavours: + type: integer + format: int32 + description: If specified, indicate the minimum numbers of flavours to be + reserved up to maximum as given in “count” member. If partner OP cannot + reserve the minimum number of flavours, then the request shall fail. + minItems: 1 + reserveDuration: + $ref: '#/components/schemas/ResourceReservationDuration' + resourceReservationCallbackLink: + $ref: '#/components/schemas/Uri' + responses: + '200': + description: ISV Resource reservation request accepted + content: + application/json: + schema: + type: object + required: + - poolId + - poolName + properties: + poolName: + $ref: '#/components/schemas/PoolName' + poolId: + $ref: '#/components/schemas/PoolId' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onResourceStatusChangeEvent: + '{$request.body#/resourceReservationCallbackLink}': + post: + requestBody: + description: Notification payload. + content: + application/json: + schema: + type: object + required: + - federationContextId + - zoneId + - appProviderId + - poolId + - grantedFlavours + properties: + federationContextId: + $ref: '#/components/schemas/FederationIdentifier' + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + poolId: + $ref: '#/components/schemas/PoolId' + grantedFlavours: + type: array + items: + type: object + required: + - flavourId + - numFlavour + properties: + flavourId: + $ref: '#/components/schemas/FlavourId' + numFlavour: + type: integer + format: int32 + description: Count of flavour + minItems: 1 + responses: + '204': + description: Updated Resource reservation status updated + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: + - fed-mgmt-notif + get: + summary: Retrieves the resource pool reserved by an ISV + operationId: ViewISVResPool + tags: + - AppProviderResourceManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + - name: appProviderId + in: path + required: true + schema: + $ref: '#/components/schemas/AppProviderId' + responses: + '200': + description: Reserved Resources Details + content: + application/json: + schema: + type: array + items: + type: object + required: + - poolName + - reservedPoolId + - reservedFlavours + properties: + poolName: + $ref: '#/components/schemas/PoolName' + reservedPoolId: + $ref: '#/components/schemas/PoolId' + reservedFlavours: + type: array + items: + type: object + required: + - flavourId + - count + properties: + flavourId: + $ref: '#/components/schemas/FlavourId' + count: + type: integer + format: int32 + description: Total number of flavours reserved + minItems: 1 + reserveDuration: + $ref: '#/components/schemas/ResourceReservationDuration' + reservationTime: + type: string + format: date-time + description: Date and time when resources were reserved in UTC format + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/isv/resource/zone/{zoneId}/appProvider/{appProviderId}/pool/{poolId}: + patch: + summary: Updates resources reserved for a pool by an ISV + operationId: UpdateISVResPool + tags: + - AppProviderResourceManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + - name: appProviderId + in: path + required: true + schema: + $ref: '#/components/schemas/AppProviderId' + - name: poolId + in: path + required: true + schema: + $ref: '#/components/schemas/PoolId' + requestBody: + content: + application/json: + schema: + type: array + items: + type: object + required: + - updateType + - flavourId + - count + properties: + updateType: + type: string + enum: + - ADD + - REMOVE + - DURATION + description: Specify if resource corresponding this flavour needs to added or removed. + Field 'count' gives the final total no of such flavours that should be reserved. count + 0 means remove all the resources. + flavourId: + $ref: '#/components/schemas/FlavourId' + count: + type: integer + format: int32 + description: Total number of flavours to be reserved + reserveDuration: + $ref: '#/components/schemas/ResourceReservationDuration' + responses: + '200': + description: Resource pool updated + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + delete: + summary: Deletes the resource pool reserved by an ISV + operationId: RemoveISVResPool + tags: + - AppProviderResourceManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + - name: appProviderId + in: path + required: true + schema: + $ref: '#/components/schemas/AppProviderId' + - name: poolId + in: path + required: true + schema: + $ref: '#/components/schemas/PoolId' + responses: + '200': + description: Resource pool deleted + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/edgenodesharing/edgeDiscovery: + post: + summary: Edge discovery procedures towards partner OP over E/WBI. Originating OP request partner + OP to provide a list of candidate zones where an application instance can be created. Partner + OP applies a set of filtering criteria's to select candidate zones. + operationId: GetCandidateZones + tags: + - EdgeNodeSharing + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + content: + application/json: + schema: + type: object + required: + - appProviderId + - appId + properties: + appProviderId: + $ref: '#/components/schemas/AppProviderId' + appId: + $ref: '#/components/schemas/AppIdentifier' + edgeDiscoveryFilters: + type: object + minProperties: 1 + properties: + location: + $ref: '#/components/schemas/ClientLocation' + responses: + '200': + description: List of candidate zones + content: + application/json: + schema: + $ref: '#/components/schemas/nodeDiscoveryResponse' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + default: + $ref: '#/components/responses/default' + /{federationContextId}/apiservice/{serviceAPINameVal}: + post: + summary: Service API request forwarding to the Partner OP + operationId: APIForwarding + tags: + - ServiceAPIManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: serviceAPINameVal + in: path + required: true + schema: + $ref: '#/components/schemas/serviceAPINameVal' + requestBody: + content: + application/json: + schema: + type: object + required: + - apiServiceId + - customerID + - customerInfo + - txnIdentifier + - ServiceAPIBody + properties: + customerID: + $ref: '#/components/schemas/customerID' + txnIdentifier: + $ref: '#/components/schemas/txnIdentifier' + ServiceAPIBody: + $ref: '#/components/schemas/serviceAPIContent' + eventNotificationDest: + $ref: '#/components/schemas/Uri' + apiServiceId: + type: string + description: Named identifier of the API service, e.g. QualityOnDemand, DeviceStatus, + DeviceLocation (Table 201). + customerInfo: + type: string + description: Name identification information associated to the Application Provider + of the Leading OP (Table 201). + required: true + responses: + '200': + description: Service API request accepted + headers: + Location: + description: Contains the URI of the newly created Service API Context resource. + required: false + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/serviceAPIResponse' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + default: + $ref: '#/components/responses/default' + callbacks: + onServiceAPISessionEvent: + '{$request.body#/eventNotificationDest}': + post: + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: apiServiceId + in: path + required: true + schema: + $ref: '#/components/schemas/serviceAPINames' + requestBody: + description: Notification about network event. + content: + application/json: + schema: + type: object + required: + - txnIdentifier + - serviceAPIEvent + properties: + serviceAPIEvent: + $ref: '#/components/schemas/serviceAPINetworkEvent' + txnIdentifier: + $ref: '#/components/schemas/txnIdentifier' + responses: + '200': + description: Event info notification acknowledged + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: + - fed-mgmt-notif + /{federationContextId}/apiservice/connid/{connectID}/custid/{customerID}: + delete: + summary: Remove the Service API Session earlier created with Service API forwarding request. + operationId: RemoveServiceAPISession + tags: + - ServiceAPIManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: connectID + in: path + required: true + schema: + $ref: '#/components/schemas/connectID' + - name: customerID + in: path + required: true + schema: + $ref: '#/components/schemas/customerID' + responses: + '200': + description: Service API Session removed successfully + content: + application/json: + schema: + type: object + required: + - expiryDuration + - connectID + properties: + expiryDuration: + $ref: '#/components/schemas/expiryInterval' + connectID: + $ref: '#/components/schemas/connectID' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + get: + summary: Retrieve the Service API context information of an existing API session identified by connectID, + customerID + operationId: GetServiceAPISessionInfo + tags: + - ServiceAPIManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: connectID + in: path + required: true + schema: + $ref: '#/components/schemas/connectID' + - name: customerID + in: path + required: true + schema: + $ref: '#/components/schemas/customerID' + responses: + '200': + description: Device Auth Token validated + content: + application/json: + schema: + type: object + required: + - expiryDuration + - connectID + properties: + expiryDuration: + $ref: '#/components/schemas/expiryInterval' + connectID: + $ref: '#/components/schemas/connectID' + ServiceAPIRespBody: + $ref: '#/components/schemas/serviceAPIContent' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + default: + $ref: '#/components/responses/default' + /{federationContextId}/monioring-subscriptions: + post: + summary: Originating OP subscribe for edge cloud resource monitoring info with partner OP. + operationId: SubscribeMonitoringInfo + tags: + - ConsumptionReportingManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: monType + in: query + required: true + schema: + $ref: '#/components/schemas/monitoringSubsType' + requestBody: + content: + application/json: + schema: + type: object + properties: + periodicity: + $ref: '#/components/schemas/periodicityInterval' + resMonNotificationListner: + $ref: '#/components/schemas/Uri' + responses: + '200': + description: Subscription for resource monitoring created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/resourceSubscriptionInfo' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onPeriodicMonitoringEvent: + '{$request.body#/resMonNotificationListner}': + post: + requestBody: + description: Periodic Notification about resource monitoring info. + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/edgeResUtilizeMetrics' + - $ref: '#/components/schemas/appsResUtilizeInfo' + responses: + '200': + description: Resource monitoring info notification acknowledged + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: + - fed-mgmt-notif + /{federationContextId}/events: + post: + summary: Originating OP uses this procedure to request enabling event reporting with Partner OP. + operationId: CreateEventSubscription + tags: + - EventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + content: + application/json: + schema: + type: object + properties: + eventSubscriptionConfig: + $ref: '#/components/schemas/EventSubscription' + responses: + '200': + description: Subscription for reporting of events created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/EventSubscriptionInfo' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onEventCriterionDetectionEvent: + '{$request.body#/eventListner}': + post: + requestBody: + description: Notification about event being detected as per event criterion. + content: + application/json: + schema: + $ref: '#/components/schemas/EventsList' + responses: + '200': + description: Event report acknowledged + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: + - fed-mgmt-notif + /{federationContextId}/events/{event_subs_id}: + post: + summary: Originating OP uses this procedure to create an event criterion at Partner OP. + operationId: CreateEventCriterion + tags: + - EventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: event_subs_id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + type: object + properties: + eventCriterion: + $ref: '#/components/schemas/eventCriterion' + responses: + '200': + description: Subscription for resource monitoring created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/eventInfo' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + get: + summary: Retrieves events list with the partner OP. + operationId: GetEventsList + tags: + - EventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: event_subs_id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: event_type + in: query + required: false + schema: + type: string + enum: + - event_criterion + - event_id + responses: + '200': + description: Events criterion and detected events report request accepted + content: + application/json: + schema: + type: object + properties: + eventCriterionList: + $ref: '#/components/schemas/eventTypeList' + eventIdList: + $ref: '#/components/schemas/EventsList' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + delete: + summary: Remove existing event subscription with the partner OP + operationId: DeleteEventSubscription + tags: + - EventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: event_subs_id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + responses: + '200': + description: Event subscription removed successfully + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/events/{event_subs_id}/event-id/{eventId}: + delete: + summary: Remove existing event criterion with the partner OP + operationId: DeleteEventCriterion + tags: + - EventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: event_subs_id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: eventId + in: path + required: true + schema: + $ref: '#/components/schemas/EventIdentifier' + responses: + '200': + description: Event criterion removed successfully + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/alarms: + post: + summary: Originating OP uses this procedure to request enabling alarm reporting with Partner OP. + operationId: CreateAlarmReportingSubscription + tags: + - AlarmManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + content: + application/json: + schema: + type: object + properties: + alarmListnerCallback: + $ref: '#/components/schemas/Uri' + responses: + '200': + description: Subscription for alarm reporting created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/SubscriptionIdentifier' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onAlarmStateReportEvent: + '{$request.body#/alarmListnerCallback}': + post: + requestBody: + description: Notification about alarm management events at Partner OP. + content: + application/json: + schema: + $ref: '#/components/schemas/AlarmObjectInfo' + responses: + '200': + description: Event report acknowledged + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: + - fed-mgmt-notif + delete: + requestBody: + description: Alarm clear notification for an earlier alarm by Partner OP. + content: + application/json: + schema: + $ref: '#/components/schemas/AlarmObjectInfo' + responses: + '200': + description: Alarm clear event acknowledged + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: + - fed-mgmt-notif + patch: + requestBody: + description: Notification about alarm management events at Partner OP. + content: + application/json: + schema: + $ref: '#/components/schemas/UpdatedAlarmParameters' + responses: + '200': + description: Alarm state update report acknowledged + content: + application/json: + schema: + type: object + properties: + updatedAlarmId: + $ref: '#/components/schemas/AlarmIdentifier' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: + - fed-mgmt-notif + /{federationContextId}/events/{alarm_subs_id}: + get: + summary: Retrieves active alarms list with the partner OP. + operationId: GetAlarmsList + tags: + - AlarmManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: alarm_subs_id + in: path + required: true + schema: + $ref: '#/components/schemas/SubscriptionIdentifier' + - name: alarm_type + in: query + required: false + schema: + $ref: '#/components/schemas/AlarmType' + responses: + '200': + description: Active alarms report request accepted + content: + application/json: + schema: + type: object + properties: + activeAlarmsList: + $ref: '#/components/schemas/ActiveAlarmsList' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + delete: + summary: Remove existing alarm subscription with the partner OP + operationId: DeleteAlarmSubscription + tags: + - AlarmManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: alarm_subs_id + in: path + required: true + schema: + $ref: '#/components/schemas/SubscriptionIdentifier' + responses: + '200': + description: Alarm subscription removed successfully + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/network-caps-events: + post: + summary: Originating OP uses this procedure to request enabling network capabilities events reporting + by the Partner OP. + operationId: CreateNetworkCapsEventSubscription + tags: + - NetworkCapsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + content: + application/json: + schema: + type: object + required: + - networkCapsEventSubscriptionConfig + properties: + networkCapsEventSubscriptionConfig: + $ref: '#/components/schemas/periodicNotifConfig' + responses: + '200': + description: Subscription for notification of network events created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/periodicNotifConfig' + headers: + Location: + description: Contains the URI of the newly created resource + required: true + schema: + type: string + Accept-Encoding: + description: Accept-Encoding, described in IETF RFC 7694 + schema: + type: string + Content-Encoding: + description: Content-Encoding, described in IETF RFC 7231 + schema: + type: string + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onNetwEventDetectionEvent: + '{$request.body#/notificationListner}': + post: + requestBody: + description: Notification about events being detected as per network capabilities are + applied by Partner OP. + content: + application/json: + schema: + $ref: '#/components/schemas/NetworkCapAppInfoList' + responses: + '200': + description: Network Events report acknowledged + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: + - fed-mgmt-notif + /{federationContextId}/network-events/{nw-event-subs-id}: + post: + summary: Originating OP uses this procedure to add an intent to Partner OP to report network capability + applied by Partner OP. + operationId: CreateNetworkCapEvent + tags: + - NetworkCapsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: nw-event-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: nw-cap-id + in: query + required: true + schema: + $ref: '#/components/schemas/CapabilityID' + requestBody: + content: + application/json: + schema: + type: object + required: + - appId + - appProviderId + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + txnIdentifier: + $ref: '#/components/schemas/txnIdentifier' + responses: + '200': + description: Subscription for network event created successfully + content: + application/json: + schema: + type: object + required: + - networkCapSubsInfo + - txnIdentifier + properties: + networkCapSubsInfo: + $ref: '#/components/schemas/NetworkCapSubsInfo' + txnIdentifier: + $ref: '#/components/schemas/txnIdentifier' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + delete: + summary: Remove existing network events notification subscription with the partner OP + operationId: DeleteNwEventNotifSubscription + tags: + - NetworkCapsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: nw-event-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + responses: + '200': + description: Network Event Notification subscription removed successfully + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/network-events/{nw-event-subs-id}/nw-caps: + get: + summary: Retrieves network capabilities subscribed list with the partner OP. + operationId: GetNetworkCapsSubscribedList + tags: + - NetworkCapsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: nw-event-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: nw-event-type + in: query + required: true + schema: + type: string + responses: + '200': + description: Network capabilities subscription list returned successfully + content: + application/json: + schema: + type: object + properties: + subscribedNwCaps: + type: array + items: + $ref: '#/components/schemas/NetworkCapSubsInfo' + minItems: 1 + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + delete: + summary: Remove existing network event notification with the partner OP + operationId: DeleteNetworkCapSubscription + tags: + - NetworkCapsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: nw-event-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: nw-event-id + in: query + required: true + schema: + type: string + responses: + '200': + description: Network Event subscription removed successfully + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/appl-event-notifications: + post: + summary: Originating OP uses this procedure to Subscribe for Application's Events Notifications. + operationId: CreateApplicationEventSubscription + tags: + - ApplicationsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + content: + application/json: + schema: + type: object + required: + - applicationEventSubscriptionConfig + properties: + applEventSubscriptionConfig: + $ref: '#/components/schemas/periodicNotifConfig' + numEvtsPerNotif: + type: integer + description: The number of applications events that the Partner OP should include in + a notification + responses: + '200': + description: Subscription for notification of network events created successfully + content: + application/json: + schema: + type: object + properties: + appEventSubsId: + type: string + applEventSubscriptionConfig: + $ref: '#/components/schemas/periodicNotifConfig' + numEvtsPerNotif: + type: integer + description: The number of applications events that the Partner OP should include + in a notification + headers: + Location: + description: Contains the URI of the newly created resource + required: false + schema: + type: string + Accept-Encoding: + description: Accept-Encoding, described in IETF RFC 7694 + schema: + type: string + Content-Encoding: + description: Content-Encoding, described in IETF RFC 7231 + schema: + type: string + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onApplEventDetectionEvent: + '{$request.body#/notificationListner}': + post: + requestBody: + description: Notification about applications LCM events being detected by Partner OP. + content: + application/json: + schema: + $ref: '#/components/schemas/AggrApplEventsList' + responses: + '200': + description: Applications events notification acknowledged + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: + - fed-mgmt-notif + /{federationContextId}/appl-event-notifications/{app-notif-subs-id}: + post: + summary: Originating OP uses this procedure to add applications for reporting of application events + by Partner OP. + operationId: SubscribeApplsEvtNotif + tags: + - ApplicationsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: app-notif-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: Idempotency-Key + in: header + required: true + schema: + $ref: '#/components/schemas/TransactionId' + requestBody: + content: + application/json: + schema: + type: object + required: + - addAppsForNotif + properties: + addAppsForNotif: + $ref: '#/components/schemas/AddAppsForNotif' + responses: + '200': + description: Subscription for network event created successfully + content: + application/json: + schema: + type: object + properties: + addAppsForNotif: + $ref: '#/components/schemas/AddAppsForNotif' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + delete: + summary: Remove existing application notification subscription with the partner OP + operationId: DeleteApplNotifSubscription + tags: + - ApplicationsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: app-notif-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + responses: + '200': + description: Application Event Notifications subscription removed successfully + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + patch: + summary: Modify existing application events notification subscription with the partner OP + operationId: ModifyApplEventNotifSubscription + tags: + - ApplicationsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: app-notif-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + type: object + properties: + applEventSubscriptionConfig: + $ref: '#/components/schemas/periodicNotifConfig' + numEvtsPerNotif: + type: integer + description: The number of applications events that the Partner OP should include in + a notification + responses: + '200': + description: Event Notification subscription modified successfully + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + get: + summary: Originating OP uses this procedure to retrieve subscription meta-information about application-level + notifications. + operationId: RetrieveApplSubsMetaInfo + tags: + - ApplicationsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: app-notif-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: info-type + in: query + required: true + schema: + type: string + enum: + - subs-info + - apps-info + responses: + '200': + description: Application events Subscription information successful retrieval + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/ApplEventsSubsInfo' + - $ref: '#/components/schemas/ApplEventsSubsInfo' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/appl-event-notifications/{app-notif-subs-id}/cancel: + post: + summary: Remove applications from the reporting of application-level event notifications. + operationId: RemoveAppsEventSubscription + tags: + - ApplicationsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: app-notif-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RemoveAppsForNotif' + responses: + '200': + description: Network capabilities subscription list returned successfully + content: + application/json: + schema: + $ref: '#/components/schemas/RemoveAppsForNotif' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/appl-event-notifications/{app-notif-subs-id}/app-events: + post: + summary: Remove applications from the reporting of application-level event notifications. + operationId: RetrieveAppsEventsInfo + tags: + - ApplicationsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: app-notif-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/AppsForNotif' + minItems: 1 + responses: + '200': + description: Network capabilities subscription list returned successfully + content: + application/json: + schema: + $ref: '#/components/schemas/AggrApplEventsList' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/app-policies-subscription: + post: + summary: Originating OP uses this procedure to Subscribe for Application's policy capability at + Partner OP. + operationId: CreateApplicationPolicySubscription + tags: + - ApplicationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + responses: + '200': + description: Subscription for application policy management created successfully + content: + application/json: + schema: + type: object + properties: + applPolicySubscriptionId: + type: string + headers: + Location: + description: Contains the URI of the newly created resource + required: false + schema: + type: string + Accept-Encoding: + description: Accept-Encoding, described in IETF RFC 7694 + schema: + type: string + Content-Encoding: + description: Content-Encoding, described in IETF RFC 7231 + schema: + type: string + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/appl-policies-subscription/{appl-policy-subs-id}/app-policy-templates: + get: + summary: Originating OP uses this procedure to retrieve application policy templates from Partner + OP. + operationId: RetrieveAppPolicyTemplates + tags: + - ApplicationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appl-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: appl-policy-type + in: query + required: false + schema: + $ref: '#/components/schemas/ApplPolicyType' + responses: + '200': + description: Successfully retrieved application policy templates + content: + application/json: + schema: + type: object + properties: + applPolicyTemplateList: + $ref: '#/components/schemas/ApplPolicyTemplateList' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/appl-policies-subscription/{appl-policy-subs-id}/app-policy-registration: + post: + summary: Register an application-level policy with the partner OP + operationId: RegisterApplicationPolicy + tags: + - ApplicationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appl-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + type: object + properties: + applConcretePolicy: + $ref: '#/components/schemas/ApplConcretePolicy' + responses: + '200': + description: Application policy registered successfully + content: + application/json: + schema: + type: object + required: + - pplConcretePolicy + - policyId + properties: + pplConcretePolicy: + $ref: '#/components/schemas/ApplConcretePolicy' + policyId: + type: string + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/app-policies-subscription/{appl-policy-subs-id}: + post: + summary: Origination OP uses this procedure to apply application-level policies to federated applications + at Partner OP. + operationId: ApplyApplicationPolicy + tags: + - ApplicationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appl-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AssocApplPolicies' + responses: + '200': + description: Application Policy processed successfully + content: + application/json: + schema: + $ref: '#/components/schemas/AssocApplPolicies' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + get: + summary: Origination OP uses this procedure to retrieve application-level policies to federated + applications at Partner OP. + operationId: RetrieveApplicationPolicy + tags: + - ApplicationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appl-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: policy-search-type + in: query + required: false + schema: + type: string + enum: + - app-prov-id + - app-id + - name: policy-search-value + in: query + required: false + schema: + type: string + description: Refers to either application provider identifier or the application identifier + responses: + '200': + description: Application Policy list retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/AssocApplPolicies' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + patch: + summary: Modify application-level policy associated with federated applications with the partner + OP + operationId: ModifyApplicationPolicy + tags: + - ApplicationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appl-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AssocApplPolicies' + responses: + '200': + description: Application policies modified successfully + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/app-policies-subscription/{appl-policy-subs-id}/app-policy-cancel: + post: + summary: Remove applications from federated applications at Partner OP. + operationId: RemoveApplicationPolicies + tags: + - ApplicationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appl-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AssocApplPolicies' + responses: + '200': + description: Successfully removed application policies + content: + application/json: + schema: + $ref: '#/components/schemas/AssocApplPolicies' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/ops-policies-subscription: + post: + summary: Originating OP uses this procedure to Subscribe for Operation's policy capability at Partner + OP. + operationId: CreateOperationPolicySubscription + tags: + - OperationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + responses: + '200': + description: Subscription for operation's policy management created successfully + content: + application/json: + schema: + type: object + properties: + opslPolicySubscriptionId: + type: string + headers: + Location: + description: Contains the URI of the newly created resource + required: false + schema: + type: string + Accept-Encoding: + description: Accept-Encoding, described in IETF RFC 7694 + schema: + type: string + Content-Encoding: + description: Content-Encoding, described in IETF RFC 7231 + schema: + type: string + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/ops-policies-subscription/{ops-policy-subs-id}/ops-policy-templates: + get: + summary: Originating OP uses this procedure to retrieve operations policy templates from Partner + OP. + operationId: RetrieveOpsPolicyTemplates + tags: + - OperationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: ops-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: ops-policy-type + in: query + required: false + schema: + $ref: '#/components/schemas/OpsPolicyType' + responses: + '200': + description: Successfully retrieved operations policy templates + content: + application/json: + schema: + type: object + properties: + opsPolicyTemplateList: + $ref: '#/components/schemas/OpsPolicyTemplateList' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/ops-policies-subscription/{ops-policy-subs-id}/ops-policy-registration: + post: + summary: Register an operation-level policy with the partner OP + operationId: RegisterOperationPolicy + tags: + - OperationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: ops-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + type: object + properties: + opsConcretePolicy: + $ref: '#/components/schemas/OpsConcretePolicy' + responses: + '200': + description: Operations policy registered successfully + content: + application/json: + schema: + type: object + required: + - opsConcretePolicy + - policyId + properties: + opsConcretePolicy: + $ref: '#/components/schemas/OpsConcretePolicy' + policyId: + type: string + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/ops-policies-subscription/{ops-policy-subs-id}/policy-association: + post: + summary: Origination OP uses this procedure to apply application-level policies to federated applications + at Partner OP. + operationId: ApplyOperationPolicy + tags: + - OperationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: ops-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AssocOpsPolicies' + responses: + '200': + description: Operation Policy processed successfully + content: + application/json: + schema: + $ref: '#/components/schemas/AssocOpsPolicies' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + get: + summary: Origination OP uses this procedure to retrieve application-level policies to federated + applications at Partner OP. + operationId: RetrieveOperationPolicy + tags: + - OperationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: ops-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: policy-search-type + in: query + required: false + schema: + type: string + enum: + - zone-id + - name: policy-search-value + in: query + required: false + schema: + type: string + description: Refers to availability zone identifier + responses: + '200': + description: Operations Policy list retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/AssocOpsPolicies' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + patch: + summary: Modify operation-level policy associated with federated applications with the Partner OP + operationId: ModifyOperationPolicy + tags: + - OperationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: ops-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AssocOpsPolicies' + responses: + '200': + description: Application policies modified successfully + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/ops-policies-subscription/{ops-policy-subs-id}/ops-policy-cancel: + post: + summary: Remove applications from federated applications at Partner OP. + operationId: RemoveOperationPolicies + tags: + - OperationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: ops-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AssocOpsPolicies' + responses: + '200': + description: Successfully removed operation policies + content: + application/json: + schema: + $ref: '#/components/schemas/AssocOpsPolicies' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' diff --git a/docs/OPG.04-v6.0-EWBI-Federation-API-v1.4.0-swagger.yaml b/docs/OPG.04-v6.0-EWBI-Federation-API-v1.4.0-swagger.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f896f38aed31f446b0768f5806132d080bd48527 --- /dev/null +++ b/docs/OPG.04-v6.0-EWBI-Federation-API-v1.4.0-swagger.yaml @@ -0,0 +1,7950 @@ +openapi: 3.0.3 +info: + version: 1.4.0 + title: Federation Management Service + description: | + # Introduction + --- + RESTful APIs that allow an OP to share the edge cloud resources and capabilities securely to other partner OPs over E/WBI. + + --- + # API Scope + + --- + APIs defined in this version of the specification can be categorized into the following areas: + * __FederationAPIManagement__ - Retrieves federation resources and methods a partner OP support on E/WBI + * __FederationManagement__ - Create and manage directed federation relationship with a partner OP + * __AvailabilityZoneInfoSynchronization__ - Management of resources of partner OP zones and status updates + * __ArtefactManagement__ - Upload, remove, retrieve and update application descriptors, charts and packages over E/WBI towards a partner OP + + * __FileManagement__ - Upload, remove, retrieve and update application binaries over E/WBI towards a partner OP + * __ApplicationOnboardingManagement__ - Register, retrieve, update and remove applications over E/WBI towards a partner OP + * __ApplicationDeploymentManagement__ - Create, update, retrieve and terminate application instances over E/WBI towards a partner OP + * __AppProviderResourceManagement__ - Static resource reservation for an application provider over E/WBI for partner OP zones + * __EdgeNodeSharing__ - Edge discovery procedures towards partner OP over E/WBI. + * __ServiceAPIManagement__ - Service APIs capability sharing, forwarding, notification and API context management + * __SubscribeMonitoringInfo__ - The Originating OP subscribe for receiving the resource utilization reports periodically from the partner OP for existing federation + * __FaultManagement__ - The Partner OP performs the alarm reporting and clearances to the Originating OP on existing federation + * __EventsReporting__ - The Partner OP notifies the detection of events as created by the Originating OP on the existing federation + * __NetworkEventsReporting__ - The Partner OP notifies the network events applied for offered network capabilities on the existing federation + * __ApplicationEventsReporting__ - The Partner OP notifies the applications events of the federated applications + * __ApplicationPolicyManagement__ - The application-level policy requested by Originating OP for federated applications + * __OperationPolicyManagement__ - The operation-level policy requested by Originating OP for federated edge cloud resources + + --- + # Definitions + --- + This section provides definitions of terminologies commonly referred to throughout the API descriptions. + + * __Accepted Zones__ - List of partner OP zones, which the originating OP has confirmed to use for its edge applications + * __Anchoring__ - Partner OP capability to serve application clients (still in their home location) from application instances running on partner zones. + * __Application Provider__ - An application developer, onboarding his/her edge application on a partner operator platform (MEC). + * __Artefact__ - Descriptor, charts or any other package associated with the application. + * __Availability Zone__ - Zones that partner OP can offer to share with originating OP. + * __Device__ - Refers to user equipment like mobile phone, tablet, IOT kit, AR/VR device etc. In context of MEC users use these devices to access edge applications + * __Directed Federation__ - A Federation between two OP instances A and B, in which edge compute resources are shared by B to A, but not from A to B. + * __Edge Application__ - Application designed to run on MEC edge cloud + * __Edge Discovery Service__ - Partner OP service responsible to select most optimal edge( within partner OP) for edge application instantiation. Edge discovery service is defined as HTTP based API endpoint identified by a well-defined FQDN or IP. + * __E/WBI__ - East west bound interface. + * __Federation__ - Relationship among member OPs who agrees to offer services and capabilities to the application providers and end users of member OPs + * __FederationContextId__ - Partner OP defined string identifier representing a certain federation relationship. + * __Federation Identifier__ - Identify an operator platform in federation context. + * __FileId__ - An OP defined string identifier representing a certain application image uploaded by an application provider + * __Flavour__ - A group of compute, network and storage resources that can be requested or granted as a single unit + * __FlavourIdentifier__ - An OP defined string identifier representing a set of compute, storage and networking resources + * __Home OP__ - Used in federation context to identify the OP with which the application developers or user clients are registered. + * __Home Routing__ - Partner OP capability to direct roaming user client traffic towards application instances running on home OP zones. + * __Instance__ - Application process running on an edge + * __LCM Service__ - Partner OP service responsible for life cycle management of edge applications. LCM service is defined as HTTP based API endpoint identified by a well-defined FQDN or IP. + * __Offered Zones__ - Zones that partner OP offer to share to the Originating OP based on the prior agreement and local configuration. + * __Onboarding__ - Submitting an application to MEC platform + * __OP__ - Operator platform. + * __OperatorIdentifier__ - String identifier representing the owner of MEC platform. Owner could be an enterprise, a TSP or some other organization + * __Originating OP__ - The OP when initiating the federation creation request towards the partner OP is defined as the Originating OP + * __Partner OP__ - Operator Platform which offers its Edge Cloud capabilities to the other Operator Platforms via E/WBI. + * __Resource__ - Compute, networking and storage resources. + * __Resource Pool__ - A group of compute, networking and storage resources. Application provider pre-reserve resources on partner OP zone, these resources are reserved in terms of flavours. + * __ZoneIdentifier__ - An OP defined string identifier representing a certain geographical or logical area where edge resources and services are provided + * __Zone Confirmation__ - Procedure via which originating OP acknowledges partner OP about the partner zones it wishes to use. + * __User Clients__ - Lightweight client applications used to access edge applications. Application users run these clients on their devices (UE, IOT device, AR/VR device etc) + * __ServiceAPIManagement__ - Service APIs capability sharing, forwarding, notification and API context management + + --- + # API Operations + --- + + __FederationManagement__ + * __CreateFederation__ - Creates a directed federation relationship with a partner OP + * __GetFederationDetails__ - Retrieves details about the federation relationship with the partner OP. The response shall provide info about the zones offered by the partner, partner OP network codes, information about edge discovery and LCM service etc. + * __DeleteFederationDetails__ - Remove existing federation with the partner OP + * __NotifyFederationUpdates__ - Call back notification used by partner OP to update originating OP about any change in existing federation relationship + * __UpdateFederation__ - API used by the Originating OP towards the partner OP, to update the parameters associated to the existing federation + * __QueryFederationContext__ - The Originating OP retrieves federationContextId from the partner OP + * __HealthCheckFederation__ - The Originating OP sends health check message to the partner OP to check the health of the the existing federation + * __RenewFederation__ - The Originating OP requests the partner OP to renew the existing federation relationship + * __GetNetworkCapabilities__ - The Originating OP requests the partner OP to share the offered network capabilities information + + __AvailabilityZoneInfoSynchronization__ + * __ZoneSubscribe__ - Informs partner OP that originating OP is willing to access the specified zones and partner OP shall reserve compute and network resources for these zones. + * __ZoneUnsubscribe__ - Informs partner OP that originating OP will no longer access the specified partner OP zone. + * __GetZoneData__ - Retrieves details about the computation and network resources that partner OP has reserved for an partner OP zone. + * __Notify Zone Information__ - Call back notification used by partner OP to update originating OP about changes in the resources reserved on a partner zone. + + __ArtefactManagement__ + * __UploadArtefact__ - Uploads application artefact on partner operator platform. + * __RemoveArtefact__ - Removes an artefact from partner operator platform. + * __GetArtefact__ - Retrieves details about an artefact from partner operator platform. + * __UploadFile__ Upload application binaries to partner operator platform + * __RemoveFile__ - Removes application binaries from partner operator platform + * __ViewFile__ - Retrieves details about binaries associated with an application from partner operator platform + + __ApplicationOnboardingManagement__ + * __OnboardApplication__ - Submits an application details to a partner OP. Based on the details provided, partner OP shall do bookkeeping, resource validation and other pre-deployment operations + * __UpdateApplication__ - Updates partner OP about changes in application compute resource requirements, QOS Profile, associated descriptor or change in associated components + * __DeboardApplication__ - Removes an application from partner OP + * __ViewApplication__ - Retrieves application details from partner OP + * __OnboardExistingAppNewZones__ - Make an application available on new additional zones + * __LockUnlockApplicationZone__ - Forbid or permit instantiation of application on a zone + + __Application Instance Lifecycle Management__ + * __InstallApp__ - Instantiates an application on a partner OP zone. + * __GetAppInstanceDetails__ - Retrieves an application instance details from partner OP. + * __RemoveApp__ - Terminate an application instance on a partner OP zone. + * __GetAllAppInstances__ - Retrieves details about all instances of the application running on partner OP zones. + + + __AppProviderResourceManagement__ + * __CreateResourcePools__ - Reserves resources (compute, network and storage) on a partner OP zone. ISVs registered with home OP reserves resources on a partner OP zone. + * __UpdateISVResPool__ - Updates resources reserved for a pool by an ISV + * __ViewISVResPool__ - Retrieves the resource pool reserved by an ISV + * __RemoveISVResPool__ - Deletes the resource pool reserved by an ISV + + + __EdgeNodeSharing__ + *__GetCandidateZones__ - Edge discovery procedures towards partner OP over E/WBI. Originating OP request partner OP to provide a list of candidate zones where an application instance can be created. + + __ServiceAPIManagement__ + *__ServiceAPIRequestForwarding__ - Forward the NBI Service API requests to Partner OP over E/WBI. + *__RemoveServiceAPISession__ - Remove the existing Service API session with Partner OP over E/WBI. + *__ServiceAPIRequestForwarding__ - Retrieve Service API session context with Partner OP over E/WBI. + + __ConsumptionReportingManagement__ + *__SubscribeForResourceConsumption__ - Originating OP Subscription for edge resource consumption reporting by Partner OP over E/WBI. + + __EventManagement__ + *__SubscribeForEventNotifications__ - Originating OP Subscription for edge services related events reporting by Partner OP over E/WBI. + + __Alarm Management__ + *__SubscribeForAlarmManagement__ - Originating OP Subscription for reporting of alarms by Partner OP over E/WBI. + + __Network Capabilities Event Management__ + *__SubscribeForNetworkCapabilitiesNotifications__ - Originating OP Subscription for reporting of network events for application of network capabilities by Partner OP over E/WBI. + + + __Applications Event Notifications Management__ + *__SubscribeForApplicationEventsNotifications__ - Originating OP Subscription for reporting of application-level events by Partner OP over E/WBI. + + + © 2024 GSM Association. + All rights reserved. +externalDocs: + description: GSMA, E/WBI APIs v1.4.1 + url: http://www.xxxx.com +servers: + - url: '{apiRoot}/operatorplatform/federation/v1' + variables: + apiRoot: + default: https://operatorplatform.com +security: + - oAuth2ClientCredentials: + - fed-mgmt + - notifClientCredentials: + - fed-mgmt-notif +components: + securitySchemes: + oAuth2ClientCredentials: + type: oauth2 + flows: + clientCredentials: + tokenUrl: /oauth2/token + scopes: + fed-mgmt: Access to the federation APIs + notifClientCredentials: + type: oauth2 + flows: + clientCredentials: + tokenUrl: /oauth2/token + scopes: + fed-mgmt-notif: Access to the federation notification APIs + + schemas: + AppIdentifier: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,63}$ + description: Identifier used to refer to an application. + AppProviderId: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,63}$ + description: UserId of the app provider. Identifier is relevant only in context of this federation. + ArtefactId: + type: string + format: uuid + description: A globally unique identifier associated with the artefact. Originating OP generates this identifier when artefact is submitted over NBI. + + CountryCode: + type: string + description: ISO 3166-1 Alpha-2 code for the country of Partner operator + pattern: ^[A-Z]{2}$ + CPUArchType: + type: string + enum: + - ISA_X86 + - ISA_X86_64 + - ISA_ARM_64 + description: CPU Instruction Set Architecture (ISA) E.g., Intel, Arm etc. + + InstanceIdentifier: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,62}[A-Za-z0-9]$ + description: Unique identifier generated by the partner OP to identify an instance of the application on a specific zone. + InstanceState: + type: string + enum: + - PENDING + - READY + - FAILED + - TERMINATING + description: Running status of the application instance. + + TransactionId: + description: A unique transaction id for this request in UUID format. It is used for tracking the request + example: ab1d6gh5-79c2-3256-7hvb-d897549x40f7 + format: uuid + type: string + Ipv4Addr: + type: string + pattern: ^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$ + example: 198.51.100.1 + Ipv6Addr: + type: string + allOf: + - pattern: ^((:|(0?|([1-9a-f][0-9a-f]{0,3}))):)((0?|([1-9a-f][0-9a-f]{0,3})):){0,6}(:|(0?|([1-9a-f][0-9a-f]{0,3})))$ + - pattern: ^((([^:]+:){7}([^:]+))|((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?))$ + example: 2001:db8:85a3::8a2e:370:7334 + Fqdn: + type: string + FixedNetworkIds: + type: array + items: + type: string + description: List of network identifier associated with the fixed line network of the operator platform. + minItems: 1 + FederationContextId: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9-]*$ + readOnly: true + description: This identifier shall be provided by the partner OP on successful verification and validation of the federation create request and is used by partner op to identify this newly created federation context. Originating OP shall provide this identifier in any subsequent request towards the partner op. + FederationIdentifier: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9-]*$ + description: Globally unique identifier allocated to an operator platform. This is valid and used only in context of MEC federation interface. + FileId: + type: string + format: uuid + description: A globally unique identifier associated with the image file. Originating OP generates this identifier when file is uploaded over NBI. + + FileName: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,31}$ + description: Name of the image file. App provides specifies this name when image is uploaded on originating OP over NBI. + + FileDescription: + type: string + minLength: 8 + maxLength: 128 + description: Brief description about the image file. + + FileVersionInfo: + type: string + description: File version information. + + FlavourId: + type: string + description: An identifier to refer to a specific combination of compute resources + GeoLocation: + type: string + description: Latitude,Longitude as decimal fraction up to 4 digit precision + pattern: ^([-+]?)([\d]{1,2})((((\.)([\d]{1,4}))?(,)))(([-+]?)([\d]{1,3})((\.)([\d]{1,4}))?)$ + Mcc: + type: string + pattern: ^\d{3}$ + Mnc: + type: string + pattern: ^\d{2,3}$ + + OnboardStatusInfo: + type: string + enum: + - PENDING + - ONBOARDED + - DEBOARDING + - REMOVED + - FAILED + description: Defines change in application status. This change could be related to application itself or an application instance status + + + PoolName: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,30}[A-Za-z0-9]$ + description: ISV defined name of the resource pool. + PoolId: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,30}[A-Za-z0-9]$ + description: OP defined Identifier for the pool reserved for the ISV. It should be unique with an OP. + Port: + type: integer + minimum: 0 + Status: + type: string + enum: + - FAILED + - TEMPORARY_FAILURE + - AVAILABLE + - LOCKED + - NOT_AVAILABLE + Uri: + type: string + Vcpu: + type: string + pattern: ^\d+((\.\d{1,3})|(m))?$ + description: Number of vcpus in whole, decimal up to millivcpu, or millivcpu format. + example: + whole: + value: 2 + decimal: + value: 0.500 + millivcpu: + value: 500m + VirtImageType: + type: string + enum: + - QCOW2 + - DOCKER + - OVA + description: Indicate if the file is Container image or VM image (QCOW2, OVA) + + ZoneIdentifier: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9-]*$ + description: Human readable name of the zone. + + FederationHealthInfo: + type: object + required: + - federationStatus + - federationStartTime + - numOfAcceptedZones + properties: + federationStatus: + $ref: '#/components/schemas/State' + federationStartTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + numOfAcceptedZones: + type: string + numOfActiveAlarms: + type: string + numOfApplications: + type: string + + FederationSupportedAPIs: + type: object + required: + - federationBaseAPI + - availabilityZoneAPI + - edgeApplicationAPI + - artefactAPI + - fileAPI + properties: + federationBaseAPI: + $ref: '#/components/schemas/FederationAPIResources' + availabilityZoneAPI: + $ref: '#/components/schemas/FederationAPIResources' + edgeApplicationAPI: + $ref: '#/components/schemas/FederationAPIResources' + artefactAPI: + $ref: '#/components/schemas/FederationAPIResources' + fileAPI: + $ref: '#/components/schemas/FederationAPIResources' + serviceAPIFederation: + $ref: '#/components/schemas/FederationAPIResources' + resourceMonitoringAPI: + $ref: '#/components/schemas/FederationAPIResources' + faultManagementAPI: + $ref: '#/components/schemas/FederationAPIResources' + eventManagementAPI: + $ref: '#/components/schemas/FederationAPIResources' + + + FederationAPINames: + type: string + enum: + - FEDERATION + - AVAILZONE + - ARTEFACT + - FILE + - SVSAPEFED + - RESMONITOR + - EVENTMGMT + - FAULTMGMT + + HttpMethods: + type: string + enum: + - POST + - PUT + - PATCH + - DELETE + - GET + + HttpResources: + type: object + required: + - href + - httpMethods + properties: + href: + $ref: '#/components/schemas/Uri' + httpMethods: + type: array + items: + $ref: '#/components/schemas/HttpMethods' + minItems: 1 + description: List of HTTP Methods supported for the given API category + + FederationAPIResources: + type: object + required: + - name + - apiOperations + properties: + name: + $ref: '#/components/schemas/FederationAPINames' + apiOperations: + type: array + items: + $ref: '#/components/schemas/HttpResources' + minItems: 1 + description: List of HTTP Methods supported for the given API category + + monitoringSubsType: + type: string + enum: ["edge_resource","app_resource","alarm","all"] + description: Denotes types of edge resources, faults and events at partner OP to be reported to Originating OP. + + resourceSubscriptionInfo: + type: object + required: + - monitoringType + - subscriptionId + - dateAndTime + properties: + monitoringType: + $ref: '#/components/schemas/monitoringSubsType' + dateAndTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + subscriptionId: + type: string + format: uuid + description: Partner OP managed identifier for new subscription. + + utilizationValue: + type: object + required: + - resType + - value + - unit + properties: + resType: + $ref: '#/components/schemas/resourceType' + value: + type: string + description: Whole number that represent the value of given resource type. + unit: + type: string + enum: + - Percent + - MBPS + - GB + - TB + - CORES + - SECONDS + - MINUTES + description: Indicate the resource measurement Unit + + resourceType: + type: string + enum: + - CPU + - MEMORY + - DISK + - Network + - FLAVOUR + description: Indicate the type of resource + + edgeResUtilizeMetrics: + type: object + required: + - edgeMetrics + - federationContextId + - sequenceNum + properties: + edgeMetrics: + type: array + items: + $ref: '#/components/schemas/edgeComputeMetrics' + minItems: 1 + description: List of edge cloud resource metrics per zone + federationContextId: + $ref: '#/components/schemas/FederationContextId' + sequenceNum: + type: integer + description: Monotonically increasing counter for sequencing resource monitoring reports + + edgeComputeMetrics: + type: object + required: + - zoneId + - startTime + - endTime + - cpuUtil + - memUtil + - diskUtil + - networkUtil + - flavourUtil + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + startTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + endTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + cpuUtil: + $ref: '#/components/schemas/cpuUtilization' + memUtil: + $ref: '#/components/schemas/memUtilization' + diskUtil: + $ref: '#/components/schemas/diskUtilization' + networkUtil: + $ref: '#/components/schemas/networkUtilization' + flavourUtil: + $ref: '#/components/schemas/flavourUtilization' + + memUtilization: + type: object + required: + - noOfSamples + - averageUtilization + - maxUtilization + - minUtilization + properties: + noOfSamples: + type: string + description: Number of samples used for calculating metrics. + averageUtilization: + $ref: '#/components/schemas/utilizationValue' + maxUtilization: + $ref: '#/components/schemas/utilizationValue' + minUtilization: + $ref: '#/components/schemas/utilizationValue' + effectiveUtilization: + $ref: '#/components/schemas/utilizationValue' + + diskUtilization: + type: object + required: + - noOfSamples + - averageUtilization + - maxUtilization + - minUtilization + properties: + noOfSamples: + type: string + description: Number of samples used for calculating metrics. + averageUtilization: + $ref: '#/components/schemas/utilizationValue' + maxUtilization: + $ref: '#/components/schemas/utilizationValue' + minUtilization: + $ref: '#/components/schemas/utilizationValue' + effectiveUtilization: + $ref: '#/components/schemas/utilizationValue' + + networkUtilization: + type: object + required: + - noOfSamples + - ingressUsage + - egressUsage + - averageThroughput + - maxThroughput + - minThroughput + properties: + noOfSamples: + type: string + description: Number of samples used for calculating metrics. + ingressUsage: + $ref: '#/components/schemas/utilizationValue' + egressUsage: + $ref: '#/components/schemas/utilizationValue' + averageThroughput: + $ref: '#/components/schemas/utilizationValue' + maxThroughput: + $ref: '#/components/schemas/utilizationValue' + minThroughput: + $ref: '#/components/schemas/utilizationValue' + + flavourUtilization: + type: array + items: + $ref: '#/components/schemas/flavourMetrics' + minItems: 1 + description: List of compute flavours metrics per zone + + flavourMetrics: + type: object + required: + - noOfSamples + - flavourId + - averageUtilization + - maxUtilization + - minUtilization + properties: + noOfSamples: + type: string + description: Number of samples used for calculating metrics. + flavourId: + $ref: '#/components/schemas/FlavourId' + averageUtilization: + $ref: '#/components/schemas/utilizationValue' + averageThroughput: + $ref: '#/components/schemas/utilizationValue' + maxUtilization: + $ref: '#/components/schemas/utilizationValue' + minUtilization: + $ref: '#/components/schemas/utilizationValue' + + appsResUtilizeInfo: + type: object + required: + - appMetrics + - federationContextId + - sequenceNum + properties: + appMetrics: + type: array + items: + $ref: '#/components/schemas/appsResUtilizeMetrics' + minItems: 1 + description: List of edge cloud resource metrics per zone + federationContextId: + $ref: '#/components/schemas/FederationContextId' + sequenceNum: + type: integer + description: Monotonically increasing counter for sequencing app monitoring reports + + + appsResUtilizeMetrics: + type: object + required: + - zoneId + - startTime + - endTime + - appZoneMetrics + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + startTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + endTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + appZoneMetrics: + $ref: '#/components/schemas/appMetrics' + + appMetrics: + type: array + items: + $ref: '#/components/schemas/appAggrResUtil' + minItems: 1 + description: List of edge cloud resource metrics per zone + + appAggrResUtil: + type: object + required: + - appId + - appProvId + - noOfAppInstances + - appInstances + - cpuUtil + - memUtil + - diskUtil + - networkUtil + - flavourUtil + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProvId: + $ref: '#/components/schemas/AppProviderId' + noOfAppInstances: + type: integer + description: No of application instances of appId in a zone + appInstances: + type: array + items: + $ref: '#/components/schemas/InstanceIdentifier' + minItems: 1 + cpuUtil: + $ref: '#/components/schemas/cpuUtilization' + memUtil: + $ref: '#/components/schemas/memUtilization' + diskUtil: + $ref: '#/components/schemas/diskUtilization' + networkUtil: + $ref: '#/components/schemas/networkUtilization' + flavourUtil: + $ref: '#/components/schemas/flavourUtilization' + + + cpuUtilization: + type: object + required: + - cpuType + - noOfSamples + - averageUtilization + - maxUtilization + - minUtilization + - effectiveUtilization + properties: + cpuType: + $ref: '#/components/schemas/monitoringSubsType' + noOfSamples: + type: string + description: Number of samples used for calculating metrics. + averageUtilization: + $ref: '#/components/schemas/utilizationValue' + maxUtilization: + $ref: '#/components/schemas/utilizationValue' + minUtilization: + $ref: '#/components/schemas/utilizationValue' + effectiveUtilization: + $ref: '#/components/schemas/utilizationValue' + + thresholdVal: + type: object + required: + - value + - unit + properties: + value: + type: string + unit: + type: string + enum: + - percent + - CORES + - TB + - GB + - MBPS + - GBPS + description: The unit of resources measurement e.g. number of cores, mega bits per seconds etc. + + EventSubscription: + type: object + required: + - resUsageType + - periodicity + - eventListner + properties: + resUsageType: + $ref: '#/components/schemas/resourceType' + periodicity: + $ref: '#/components/schemas/periodicityInterval' + eventListner: + $ref: '#/components/schemas/Uri' + + EventSubscriptionInfo: + type: object + required: + - resUsageType + - periodicity + - subscriptionId + properties: + resUsageType: + $ref: '#/components/schemas/resourceType' + periodicity: + $ref: '#/components/schemas/periodicityInterval' + subscriptionId: + type: string + format: uuid + + eventCriterion: + type: object + required: + - resUsageType + - triggerCondition + - thresholdVal + - numOccurance + - monitorDuration + properties: + resUsageType: + $ref: '#/components/schemas/resourceType' + triggerCondition: + type: string + enum: + - GT + - GTE + - EQ + - LT + - LEQ + description: The condition evaluation operator to compare threashold value of a resource for event detection. + thresholdVal: + $ref: '#/components/schemas/thresholdVal' + numOccurance: + type: integer + description: Number of times the trigger condition is detected + monitorDuration: + $ref: '#/components/schemas/periodicityInterval' + + eventInfo: + type: object + required: + - eventId + - eventCriterion + properties: + eventId: + type: string + eventCriterion: + $ref: '#/components/schemas/eventCriterion' + + eventTypeList: + type: array + items: + $ref: '#/components/schemas/eventCriterion' + minItems: 1 + description: List of event criterion + + detectedEvent: + type: object + required: + - zoneId + - eventId + - startTime + - endTime + - numOccurance + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + eventId: + type: string + startTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + endTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + numOccurance: + type: integer + + CapabilityID: + type: string + enum: + - NW_CAP_CONN_STATE_CHANGE + - NW_CAP_LOCATION_RETRIEVAL + - NW_CAP_USERPLANE_MGMT_EVENTS + - NW_CAP_DYNAMIC_QOS + description: The enumerated list of network capabilities that an OP can use for various services via SBI-NR. + + DeviceConnStatusChangeCap: + type: object + required: + - capabilityId + - maxiDetectionTime + properties: + capabilityId: + $ref: '#/components/schemas/CapabilityID' + maxiDetectionTime: + type: string + description: The maximum detection time in seconds that the OP can determine the UE change of connectivity with the mobile network. + + LocationRetrievalCap: + type: object + required: + - capabilityId + - locationType + properties: + capabilityId: + $ref: '#/components/schemas/CapabilityID' + locationType: + type: string + enum: + - CELL_LEVEL_ACCURACY + - REGISTRATION_AREA_ACCURACY + - TRACKING_AREA_ACCURACY + - GEO_LOCATION_ACCURACY + description: The enumerated list of UE location accuracy that an OP can determine via SBI-NR. + locationAccuracy: + type: string + enum: + - LAST_KNOWN_LOCATION + - CURRENT_LOCATION + - INITIAL_LOCATION + description: The enumerated list of type of network location of an UE that an OP can determine via SBI-NR. + + UserPlaneMgmtEvtCap: + type: object + required: + - capabilityId + - maxUserPlaneLatency + properties: + capabilityId: + $ref: '#/components/schemas/CapabilityID' + maxUserPlaneLatency: + type: string + description: Indicates the maximum user plane latency in units of milliseconds to decide whether edge relocation is needed to ascertain latency remain in this range. + + DynamicQoSCap: + type: object + required: + - capabilityId + - supportedQoS + properties: + capabilityId: + $ref: '#/components/schemas/CapabilityID' + supportedQoS: + type: string + description: Set of one or more 5G QoS Identifier (5QI or 4G QCI) created via concatanation of Resource Type and 5QI values i.e., GBR1, GBR2, GBR65, NONGBR79 etc. + + + NetworkCapAppInfoList: + type: array + items: + required: + - appProviderId + - appId + - AppInstNetworkCapInvoked + - zoneId + properties: + appProviderId: + $ref: '#/components/schemas/AppProviderId' + appId: + $ref: '#/components/schemas/AppIdentifier' + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + appInstNetworkCapInvoked: + $ref: '#/components/schemas/AppInstNetworkCapList' + minItems: 1 + + AppInstNetworkCapList: + type: object + required: + - appInstanceNwCapInfo + properties: + appInstanceNwCapInfo: + type: array + items: + type: object + required: + - appInstIdentifier + - appInstanceState + - networkCapInvoked + properties: + appInstIdentifier: + $ref: '#/components/schemas/InstanceIdentifier' + appInstanceState: + $ref: '#/components/schemas/InstanceState' + networkCapInvoked: + $ref: '#/components/schemas/NetworkCapInvoked' + minItems: 1 + + NetworkCapInvoked: + type: object + required: + - networkEventId + - capabilityId + - zoneId + - detectionTime + - nwCapabilitySLI + properties: + networkEventId: + type: string + format: uuid + description: Unique identifier allocated for a network event + capabilityId: + $ref: '#/components/schemas/CapabilityID' + invocationTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + nwCapabilitySLI: + type: string + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + + NetworkCapSubsInfo: + type: object + required: + - appId + - appProviderId + - capabilityId + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + capabilityId: + $ref: '#/components/schemas/CapabilityID' + + + NetworkEventsList: + type: array + items: + $ref: '#/components/schemas/NetworkCapInvoked' + minItems: 1 + description: List of network capabilities events detected + + + EventsList: + type: array + items: + $ref: '#/components/schemas/detectedEvent' + minItems: 1 + description: List of events detected + + EventSubscriptionIdentifier: + type: string + format: uuid + description: Event subscription identifier allocated for enabling event reporting + + EventIdentifier: + type: string + format: uuid + description: Event identifier allocated for event detected + + SubscriptionIdentifier: + type: object + required: + - subsId + properties: + subsId: + type: string + format: uuid + description: Generic subscription identifier + + AlarmObjectInfo: + type: object + required: + - alarmType + - alarmId + - perceivedSeverity + - probableCause + - alarmedObject + - sourceSystemId + - state + - alarmRaisedTime + properties: + alarmType: + $ref: '#/components/schemas/AlarmType' + alarmId: + $ref: '#/components/schemas/AlarmIdentifier' + perceivedSeverity: + $ref: '#/components/schemas/PerceivedSeverity' + probableCause: + $ref: '#/components/schemas/ProbableCause' + alarmedObject: + $ref: '#/components/schemas/AlarmedObject' + sourceSystemId: + $ref: '#/components/schemas/SourceSystemId' + state: + $ref: '#/components/schemas/State' + alarmRaisedTime: + $ref: '#/components/schemas/AlarmRaisedTime' + affectedService: + $ref: '#/components/schemas/AffectedService' + alarmDetails: + $ref: '#/components/schemas/AlarmDetails' + specificProblem: + $ref: '#/components/schemas/SpecificProblem' + serviceAffecting: + $ref: '#/components/schemas/ServiceAffecting' + + ActiveAlarmsList: + type: array + items: + $ref: '#/components/schemas/AlarmObjectInfo' + minItems: 1 + description: List of active alarms + + AlarmType: + type: object + required: + - alarmType + properties: + alarmType: + type: string + enum: + - EDGERES + - APPLICATION + - ARTEFACT + - EDGEDISC + - FEDERATION + - SECURITY + - APIFEDERATION + - FILE + description: Alarm type category + + AlarmIdentifier: + type: object + required: + - alarmId + properties: + alarmId: + type: string + description: Alarm identifier to refer to an alarm instance + + PerceivedSeverity: + type: object + required: + - severity + properties: + severity: + type: string + enum: + - MAJOR + - MINOR + - CRITICAL + - WARNING + - INFOMATIONAL + description: Alarm severity + + ProbableCause: + type: object + required: + - cause + properties: + cause: + type: string + description: Probale cause of the alarm + + AlarmedObject: + type: object + required: + - alarmId + - href + properties: + alarmId: + $ref: '#/components/schemas/AlarmIdentifier' + href: + $ref: '#/components/schemas/Uri' + + SourceSystemId: + type: object + required: + - sourceSystemId + properties: + sourceSystemId: + type: string + description: Source system identity + + State: + type: object + required: + - alarmState + properties: + alarmState: + type: string + enum: + - RAISED + - UPDATED + - CLEAR + description: Defines the alarm state during its life cycle (raised | updated | cleared). + + AlarmRaisedTime: + type: object + required: + - alarmRaisedTime + properties: + alarmRaisedTime: + type: string + format: date-time + description: Defines the alarm raised time at source + + AffectedService: + type: object + required: + - affectedService + properties: + affectedService: + type: array + items: + type: string + minItems: 1 + description: Defines the affected services e.g., edge discovery, application services, API services etc at source + + AlarmDetails: + type: object + required: + - alarmDetails + properties: + alarmDetails: + type: string + description: Detailed information of the alarm + + SpecificProblem: + type: object + required: + - specificProblem + properties: + specificProblem: + type: string + description: Specific information related to the alarm + + ServiceAffecting: + type: string + enum: + - YES + - NO + description: Specific information related to the alarm + + PatchableParams: + type: string + enum: ["/perceivedSeverity","/probableCause","/alarmedObject","/sourceSystemId","/state","/affectedService","/alarmDetails","/specificProblem","/serviceAffecting"] + + AlarmUpdateOps: + type: string + enum: + - REPLACE + description: Operations that can be performed to update the parameters of an alarm + + UpdatedParam: + type: object + required: + - alarmUpdateOps + - patchableParam + - patchValue + properties: + alarmUpdateOps: + $ref: '#/components/schemas/AlarmUpdateOps' + patchableParam: + $ref: '#/components/schemas/PatchableParams' + patchValue: + type: string + description: Value to be replaced for the alarm parameter being updated + + UpdatedAlarmParameters: + type: object + required: + - alarmId + - updateParams + properties: + alarmId: + $ref: '#/components/schemas/AlarmIdentifier' + updateParams: + type: array + items: + $ref: '#/components/schemas/UpdatedParam' + minItems: 1 + description: List of alarm parameters to be updated in an update operation + + serviceType: + type: string + enum: ["api_federation"] + description: An identifier to refer to partner OP capabilities for application providers. + + serviceAPINames: + type: array + items: + type: string + enum: + - QualityOnDemand + - DeviceLocation + - DeviceStatus + - SimSwap + - NumberVerification + - DeviceIdentifier + minItems: 1 + description: List of Service API capability names an OP supports and offers to other OPs "quality_on_demand", "device_location" etc. + + serviceAPINameVal: + type: string + enum: + - QualityOnDemand + - DeviceLocation + - DeviceStatus + - SimSwap + - NumberVerification + - DeviceIdentifier + description: Name of the Service API + + serviceRoutingInfo: + type: array + items: + type: string + pattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))?$ + minItems: 1 + description: List of public IP addresses MNO manages for UEs to connect with public data networks + + + customerID: + type: string + format: uuid + description: Leading OP managed identifier associated to API Provider of the Leading OP. + + txnIdentifier: + type: string + description: A API transaction identifier generated by the Partner OP for each API request + + connectID: + type: string + description: An identifier generated by the Partner OP to represent the end user identity in the Service API request. + + apiContentType: + type: string + enum: + - application/json + description: Indicate the Service API body schema in JSON format + + serviceAPIContent: + type: object + required: + - mediaType + - APIContent + properties: + mediaType: + $ref: '#/components/schemas/apiContentType' + APIContent: + type: object + additionalProperties: true + description: Opaque Service API payload. The published OPG.04 artifact has an invalid placeholder reference here. + + PlatformCaps: + type: array + items: + type: string + enum: + - homeRouting + - Anchoring + - serviceAPIs + - faultMgmt + - eventMgmt + - resourceMonitor + - networkEventMgmt + - appNotificationMgmt + - appLevelPolicyMgmt + - opsLevelPolicyMgmt + description: Home routing - Operator platform is capable of routing edge application data traffic from its edges to user device in their home location. This is the case where user devices are served in their home region (requesting platform region, non-roaming) but the corresponding edge application are in operator platform edges. Anchoring - Operator platform is capable of routing edge application traffic for roaming user devices to edge application in user device home network. Service APIs - Capability to handle Service APIs (e.g., CAMARA APIs) from the Leading OP + + expiryInterval: + type: object + required: + - numHours + - numMins + - numSecs + properties: + numHours: + type: integer + format: int32 + description: Number of Hours for Expiry (0-23) + numMins: + type: integer + format: int32 + description: Number of Minutes for Expiry (0-59) + numSecs: + type: integer + format: int32 + description: Number of Seconds for Expiry (0-59) + + periodicityInterval: + type: object + required: + - numHours + - numMins + properties: + numHours: + type: integer + format: int32 + description: Number of Hours for Expiry (0-23) + numMins: + type: integer + format: int32 + description: Number of Minutes for Expiry (0-59) + + periodicNotifConfig: + type: object + properties: + periodicity: + $ref: '#/components/schemas/periodicityInterval' + notificationListner: + $ref: '#/components/schemas/Uri' + + targetUserContext: + type: object + required: + - connectID + - expiryDuration + properties: + connectID: + $ref: '#/components/schemas/connectID' + expiryDuration: + $ref: '#/components/schemas/expiryInterval' + + serviceAPIResponse: + type: object + required: + - customerID + - targetUserContext + - apiResponse + - txnIdentifier + properties: + customerID: + $ref: '#/components/schemas/customerID' + targetUserContext: + $ref: '#/components/schemas/targetUserContext' + apiResponse: + $ref: '#/components/schemas/customerID' + txnIdentifier: + $ref: '#/components/schemas/txnIdentifier' + svcEventType: + type: string + enum: + - "evt_timerexpiry" + - "evt_network" + - "evt_delete" + + + serviceAPIEventDef: + type: object + required: + - NetworkEventDef + properties: + NetworkEventDef: + type: object + additionalProperties: true + description: Opaque network-event payload. The published OPG.04 artifact has an invalid placeholder reference here. + + serviceAPINetworkEvent: + type: object + required: + - connectID + - customerID + - EventType + properties: + connectID: + $ref: '#/components/schemas/connectID' + customerID: + $ref: '#/components/schemas/customerID' + EventType: + $ref: '#/components/schemas/svcEventType' + serviceAPIEventDef: + $ref: '#/components/schemas/serviceAPIEventDef' + expiryDuration: + $ref: '#/components/schemas/expiryInterval' + + # + # STRUCTURED DATA TYPES + # + ServiceNameNB: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,62}[A-Za-z0-9]$ + description: Must be a valid RFC 1035 label name. This defines the DNS name via which the component can be accessed over NBI. Access via serviceNameNB is restricted on specific ports. Platform shall expose component access externally via this DNS name + ServiceNameEW: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,62}[A-Za-z0-9]$ + description: Must be a valid RFC 1035 label name. This defines the DNS name via which the component can be accessed via peer components. Access via serviceNameEW is open on all ports. Platform shall not expose serviceNameEW externally outside edge. + ComponentName: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,62}[A-Za-z0-9]$ + description: Must be a valid RFC 1035 label name. Component name must be unique with an application + + + ApplEventsSubsInfo: + type: object + required: + - appEventSubsId + - appEvtSubsStartTime + - appEvtSubsLastReportTime + - appEvtSubsNumApps + - appEvtSubsPeriodicity + properties: + appEventSubsId: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + appEvtSubsStartTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + appEvtSubsLastReportTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + appEvtSubsNumApps: + type: integer + appEvtSubsPeriodicity: + $ref: '#/components/schemas/periodicityInterval' + + AppsForNotif: + type: object + required: + - appId + - appProviderId + - appZones + - appEvents + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + appZones: + $ref: '#/components/schemas/AppZones' + appEvents: + $ref: '#/components/schemas/AppEvents' + + AddAppsForNotif: + type: array + items: + $ref: '#/components/schemas/AppsForNotif' + minItems: 1 + + RemoveAppsForNotif: + type: array + items: + $ref: '#/components/schemas/AppsForNotif' + minItems: 1 + + + AppEventTypes: + type: string + enum: + - evt_type_app_relocation + - evt_type_app_session_cont + - evt_type_app_restarts + - evt_type_app_upscale + - evt_type_app_downscale + description: Application-level events + + AppEvents: + type: array + items: + $ref: '#/components/schemas/AppEventTypes' + minItems: 1 + description: List of availability zones where application events are to be monitored + + AppZones: + type: array + items: + $ref: '#/components/schemas/ZoneIdentifier' + minItems: 1 + description: List of availability zones where application events are to be monitored + + ApplInstEventTypeInfo: + type: object + required: + - applInstEvent + - applInstEventCount + properties: + applInstEvent: + $ref: '#/components/schemas/AppEventTypes' + applInstEventCount: + type: integer + description: Number of occurances of given epplication event + + ApplInstEventsContainer: + type: object + required: + - appInstanceId + - appInstEventsList + properties: + appInstanceId: + $ref: '#/components/schemas/InstanceIdentifier' + appInstEventsList: + type: array + items: + $ref: '#/components/schemas/ApplInstEventTypeInfo' + minItems: 1 + description: Application instance events list + + ApplInstEventsList: + type: object + required: + - appInstanceEventsList + properties: + appInstanceEventsList: + type: array + items: + $ref: '#/components/schemas/ApplInstEventsContainer' + minItems: 1 + description: Application instance events list for one or more applications + + ZoneLevelApplEventsList: + type: object + required: + - zoneId + - appsEventsList + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + appsEventsList: + type: array + items: + $ref: '#/components/schemas/ApplInstEventsList' + minItems: 1 + description: Applications instance events list in a availability zone + + ApplEventsList: + type: object + required: + - appId + - appProviderId + - aggrApplEvents + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + aggrApplEvents: + type: array + items: + $ref: '#/components/schemas/ZoneLevelApplEventsList' + minItems: 1 + description: Applications instance events list in a availability zone + + AggrApplEventsList: + type: object + required: + - startTime + - endTime + - aggrAppsEventsList + properties: + startTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + endTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + aggrAppsEventsList: + type: array + items: + $ref: '#/components/schemas/ApplEventsList' + minItems: 1 + description: Applications events list in a various availability zones for different application providers + + ApplPolicyIdentifier: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,63}$ + description: Application-level Policy unique identifier + + ApplPolicyMetaInfo: + type: object + required: + - applPolicyTypeIdentifier + - policyVersion + properties: + applPolicyTypeIdentifier: + $ref: '#/components/schemas/ApplPolicyTypeIdentifier' + policyVersion: + type: string + description: Policy template version using Semantic Versioning 2.0.0 in MAJOR.MINOR.PATCH format + + + ApplPolicyTypeIdentifier: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,63}$ + description: Application-level Policy unique identifier + + + AppPolicyTemplate: + type: object + required: + - applPolicyName + - applPolicyMetaInfo + - applPolicyType + - applPolicyScope + - applPolicyDescription + - applPolicyRules + properties: + applPolicyName: + type: string + maxLength: 64 + description: Brief policy template name on policy objective + applPolicyMetaInfo: + $ref: '#/components/schemas/ApplPolicyMetaInfo' + applPolicyType: + $ref: '#/components/schemas/ApplPolicyType' + applPolicyScope: + $ref: '#/components/schemas/ApplPolicyScope' + applPolicyDescription: + type: string + maxLength: 256 + description: Brief policy template description on policy objective + applPolicyRules: + type: array + items: + $ref: '#/components/schemas/ApplPolicyRule' + minItems: 1 + description: Set of policy action rules for a given policy + + ApplPolicyTemplateList: + type: array + items: + $ref: '#/components/schemas/AppPolicyTemplate' + minItems: 1 + description: List of Application policy templates from the Partner OP + + ApplPolicyType: + type: string + enum: + - static + - dynamic + description: Policy attribute that the given policy intent to control specific resources e.g. compute capacity expansion statically vs dynamic scaling of app instance + + ApplPolicyScope: + type: string + enum: + - zonal + - global + description: Application-level Policy scope defines if a policy is a set of availability zones or applies globally to all zones + + + ApplPolicyRule: + type: array + items: + $ref: '#/components/schemas/GenericPolicyRule' + minItems: 1 + description: List of Application policies + + + GenericPolicyRule: + type: object + required: + - ruleLHSParamType + - ruleOperator + - ruleRHSParamVal + - ruleAction + - ruleDescription + properties: + ruleLHSParamType: + $ref: '#/components/schemas/RuleLHSParamType' + ruleOperator: + $ref: '#/components/schemas/RuleOperatorType' + ruleRHSParamVal: + $ref: '#/components/schemas/RuleRHSParamVal' + ruleAction: + $ref: '#/components/schemas/RuleActionType' + ruleDescription: + type: string + maxLength: 256 + description: Brief description of the actions to be performed + + RuleLHSParamType: + type: string + enum: + - AppsPolicy.App.Metadata.QoS.Latency + - AppsPolicy.App.Metadata.Compute.CPU + - AppsPolicy.App.Metadata.Compute.GPU + - AppsPolicy.App.Metadata.Location.AZ + - AppsPolicy.App.Metadata.Location.Region + - OpsPolicy.EdgeCloud.Metadata.QoS.Latency + - OpsPolicy.EdgeCloud.Metadata.Compute.CPU + - OpsPolicy.EdgeCloud.Metadata.Compute.GPU + - OpsPolicy.EdgeCloud.Metadata.Network.SRIOV + description: Resource attributes that policy will act on to determine the target pplication after applying the policy rules + + RuleRHSParamVal: + type: object + properties: + latencyRanges: + $ref: '#/components/schemas/LatencyRanges' + computeResourceProfile: + $ref: '#/components/schemas/ComputeResourceProfile' + appLocation: + type: array + items: + $ref: '#/components/schemas/AppLocation' + minItems: 1 + networkCaps: + $ref: '#/components/schemas/NetworkCaps' + description: Permitted type specific value objects for types in ruleLHSParamType + + AppLocation: + type: string + enum: + - zones + - regions + description: Application Location in terms of availability zones or regions + + LatencyRanges: + type: object + required: + - minLatency + - maxLatency + - unit + properties: + minLatency: + type: string + description: Minimum latency in milliseconds + maxLatency: + type: string + description: Maximum latency in milliseconds + unit: + type: string + enum: + - MS + description: Maximum latency in milliseconds + description: Latency ranges that can be experienced in the Partner OP environment + + ComputeResourceProfile: + type: object + required: + - resourceSpec + properties: + resourceSpec: + $ref: '#/components/schemas/ResourceSpec' + description: Type and amount of compute resources + + ResourceSpec: + type: object + required: + - resourceType + - resourceModel + - resourceCount + properties: + resourceType: + type: string + enum: + - CPU + - GPU + - FPGA + resourceModel: + type: string + enum: + - Intel-x86_64 + - Arm64 + - Nvidia + resourceCount: + type: string + + description: Resource type and architecture specification + + NetworkCaps: + type: object + properties: + nwAccelType: + type: string + enum: + - SRIOV + - DPDK + nwAccelSpeed: + type: string + enum: + - 1Gbps + - 10Gbps + - 100Gbps + description: Type and speed of network acceleration resources + + + RuleOperatorType: + type: object + properties: + StringRuleOperatorType: + $ref: '#/components/schemas/StringRuleOperatorType' + BinaryRuleOperatorType: + $ref: '#/components/schemas/BinaryRuleOperatorType' + description: Defines the logical operations that policy rule will execute on application attribute value + + BinaryRuleOperatorType: + type: string + enum: + - EQ + - LT + - GT + description: Operations that can be applied on Parameter e.g., “Binary Operation” EQ(EQual) + + StringRuleOperatorType: + type: string + enum: + - EQ + - NOTEQ + description: Operations that can be applied on Parameter e.g., String Operation” EQ(EQual), NOTEQ(Not Equal) + + RuleActionType: + type: object + required: + - actionType + - actionTargetType + properties: + actionType: + $ref: '#/components/schemas/ActionType' + actionTargetType: + $ref: '#/components/schemas/RuleLHSParamType' + + ActionType: + type: string + enum: + - restrict + - prefer + - priortize + - allow + - deny + description: Action to be taken once a policy rule is applied on target resource indicated by RuleLHSParamType + + ApplConcretePolicy: + type: object + required: + - policyId + - policyParamLimits + properties: + policyId: + $ref: '#/components/schemas/ApplPolicyIdentifier' + policyParamLimits: + $ref: '#/components/schemas/ApplPolicyRule' + description: Application policy id and policy parameter value limits registered by the Originating OP + + AssocApplPolicies: + type: object + required: + - policyId + - appIdList + properties: + policyId: + $ref: '#/components/schemas/ApplPolicyIdentifier' + appIdList: + $ref: '#/components/schemas/AppIdLocList' + + AppIdLocList: + type: object + required: + - appId + - appProvId + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProvId: + $ref: '#/components/schemas/AppProviderId' + zoneIds: + type: array + items: + $ref: '#/components/schemas/ZoneIdentifier' + minItems: 1 + + RegisteredAppPolicyList : + type: array + items: + $ref: '#/components/schemas/ApplConcretePolicy' + minItems: 1 + description: Applications policies registered by the Originating OP + + + OpsPolicyIdentifier: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,63}$ + description: Operation-level Policy unique identifier + + + OpsPolicyTypeIdentifier: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,63}$ + description: Operation-level Policy template unique identifier + + OpslPolicyMetaInfo: + type: object + required: + - opslPolicyTypeIdentifier + - policyVersion + properties: + opslPolicyTypeIdentifier: + $ref: '#/components/schemas/OpsPolicyTypeIdentifier' + policyVersion: + type: string + description: Policy template version using Semantic Versioning 2.0.0 in MAJOR.MINOR.PATCH format + + + OpsPolicyTemplateList: + type: array + items: + $ref: '#/components/schemas/OpsPolicyTemplate' + minItems: 1 + description: List of Operation policy templates from the Partner OP + OpsConcretePolicy: + type: object + required: + - policyId + - policyParamLimits + properties: + policyId: + $ref: '#/components/schemas/OpsPolicyIdentifier' + policyParamLimits: + $ref: '#/components/schemas/OpsPolicyRule' + description: Application policy id and policy parameter value limits registered by the Originating OP + + + OpsPolicyTemplate: + type: object + required: + - opsPolicyName + - OpslPolicyMetaInfo + - opsPolicyType + - opsPolicyScope + - opsPolicyDescription + - opsPolicyRules + properties: + opsPolicyName: + type: string + maxLength: 64 + description: Brief policy template name on policy objective + opslPolicyMetaInfo: + $ref: '#/components/schemas/OpslPolicyMetaInfo' + opsPolicyType: + $ref: '#/components/schemas/OpsPolicyType' + opsPolicyScope: + $ref: '#/components/schemas/OpsPolicyScope' + opsPolicyDescription: + type: string + maxLength: 256 + description: Brief policy template description on policy objective + opsPolicyRules: + type: array + items: + $ref: '#/components/schemas/OpsPolicyRule' + minItems: 1 + description: Set of policy action rules for a given policy + + + OpsPolicyRule: + type: object + properties: + opsPolicyRule: + $ref: '#/components/schemas/GenericPolicyRule' + description: Operation policies rule defines the action to be taken against the subscribed policy template + + + OpsPolicyType: + type: string + enum: + - static + - dynamic + description: Policy attribute that defines if the policy rules applies to static part of the infra or dynamic part of the edge cloud infra + + OpsPolicyScope: + type: string + enum: + - zonal + - global + description: Operation-level Policy scope defines if a policy is a set of availability zones or applies globally to all zones + + + AssocOpsPolicies: + type: object + required: + - policyId + - appIdList + properties: + policyId: + $ref: '#/components/schemas/OpsPolicyIdentifier' + appIdList: + $ref: '#/components/schemas/AppIdLocList' + + AvailZoneIdLocList: + type: object + required: + - zoneIds + properties: + zoneIds: + type: array + items: + $ref: '#/components/schemas/ZoneIdentifier' + minItems: 1 + + RegisteredOpsPolicyList : + type: array + items: + $ref: '#/components/schemas/OpsConcretePolicy' + minItems: 1 + description: Operation policies registered by the Originating OP + + + + AppComponentSpecs: + description: An application may consist of more than one component. Each component is associated with a descriptor and may exposes its services externally or internally. App providers are required to provide details about all these components, their associated descriptors and their DNS names. + type: array + items: + type: object + required: + - artefactId + properties: + serviceNameNB: + $ref: '#/components/schemas/ServiceNameNB' + serviceNameEW: + $ref: '#/components/schemas/ServiceNameEW' + componentName: + $ref: '#/components/schemas/ComponentName' + artefactId: + $ref: '#/components/schemas/ArtefactId' + minItems: 1 + + AppMetaData: + description: Application metadata details + type: object + required: + - appName + - version + - accessToken + properties: + appName: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,31}$ + description: Name of the application. Application provider define a human readable name for the application + version: + type: string + description: Version info of the application + appDescription: + type: string + minLength: 16 + maxLength: 256 + description: Brief application description provided by application provider + mobilitySupport: + $ref: '#/components/schemas/MobilitySupport' + accessToken: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{31,63}$ + description: An application Access key, to be used with UNI interface to authorize UCs Access to a given application + category: + type: string + enum: + - IOT + - HEALTH_CARE + - GAMING + - VIRTUAL_REALITY + - SOCIALIZING + - SURVEILLANCE + - ENTERTAINMENT + - CONNECTIVITY + - PRODUCTIVITY + - SECURITY + - INDUSTRIAL + - EDUCATION + - OTHERS + description: Possible categorization of the application + AppQoSProfile: + description: Parameters corresponding to the performance constraints, tenancy details etc. + type: object + required: + - latencyConstraints + properties: + latencyConstraints: + $ref: '#/components/schemas/LatencyConstraints' + bandwidthRequired: + $ref: '#/components/schemas/BandwidthRequired' + multiUserClients: + $ref: '#/components/schemas/MultiUserClients' + noOfUsersPerAppInst: + $ref: '#/components/schemas/NoOfUsersPerAppInst' + appProvisioning: + $ref: '#/components/schemas/AppProvisioning' + + EdgeAppFQDN: + type: string + description: DNS FQDN assigned to application instances in an availability zone. User Clients can resolve the FQDN to communicate with the edge instances of the application + + ClientLocation: + type: object + minProperties: 1 + properties: + geo_location: + type: string + description: Latitude, Longitude as decimal fraction up to 4 digit precision + pattern: ^([-+]?)([\d]{1,2})(((\.)(\d+)(,)))(\s*)(([-+]?)([\d]{1,3})((\.)(\d+))?)$ + rad_location: + description: Information about the 4G/5G Cell ids where the client is currently served. + type: array + items: + type: object + required: + - carrier + - mcc + - mnc + - cellId + properties: + carrier: + type: string + enum: + - 5G + - LTE + mcc: + type: integer + minimum: 1 + maximum: 999 + description: Mobile country code of the network as broadcasted in the serving cell + mnc: + type: integer + minimum: 1 + maximum: 999 + description: Mobile network code of the network as broadcasted in the serving cell + cellId: + type: integer + description: it could be a CGI (if carrier is LTE) or NCGI (if carrier is 5G). + areaCode: + type: integer + description: Routing area code or Traffic area code where client is being served. + CompEnvParams: + description: Environment variables are key value pairs that should be injected when component in instantiated + type: object + required: + - envVarName + - envValueType + properties: + envVarName: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,30}[A-Za-z0-9]$ + description: Name of environment variable + envValueType: + type: string + enum: + - USER_DEFINED + - PLATFORM_DEFINED_DYNAMIC_PORT + - PLATFORM_DEFINED_DNS + - PLATFORM_DEFINED_IP + envVarValue: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,62}[A-Za-z0-9]$ + description: Value to be assigned to environment variable + envVarSrc: + type: string + description: Full path of parameter from componentSpec that should be used to generate the environment value. Eg. networkResourceProfile[1]. interfaceId. + CommandLineParams: + description: List of commands and arguments that shall be invoked when the component instance is created. This is valid only for container based deployment. + type: object + required: + - command + properties: + command: + type: array + items: + type: string + description: List of commands that application should invoke when an instance is created. + commandArgs: + type: array + items: + type: string + description: List of arguments required by the command. + DeploymentConfig: + description: Configuration used when deploying a component. May override other ComponentSpec parameters related to deployment like restart policy, command line parameters, environment variables, etc. + type: object + required: + - configType + - contents + properties: + configType: + type: string + enum: + - DOCKER_COMPOSE + - KUBERNETES_MANIFEST + - CLOUD_INIT + - HELM_VALUES + description: Config type. + contents: + type: string + description: Contents of the configuration. + + ComponentSpec: + description: Details about compute, networking and storage requirements for each component of the application. App provider should define all information needed to instantiate the component. If artefact is being defined at component level this section should have information just about the component. In case the artefact is being defined at application level the section should provide details about all the components. + type: object + required: + - componentName + - images + - numOfInstances + - restartPolicy + - computeResourceProfile + properties: + componentName: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,62}[A-Za-z0-9]$ + description: Must be a valid RFC 1035 label name. Component name must be unique with an application + images: + description: List of all images associated with the component. Images are specified using the file identifiers. Partner OP provides these images using file upload api. + type: array + items: + $ref: '#/components/schemas/FileId' + minItems: 1 + numOfInstances: + type: integer + format: int32 + description: Number of component instances to be launched. + restartPolicy: + type: string + enum: + - RESTART_POLICY_ALWAYS + - RESTART_POLICY_NEVER + description: How the platform shall handle component failure + commandLineParams: + $ref: '#/components/schemas/CommandLineParams' + exposedInterfaces: + description: Each application component exposes some ports either for external users or for inter component communication. Application provider is required to specify which ports are to be exposed and the type of traffic that will flow through these ports. + type: array + items: + $ref: '#/components/schemas/InterfaceDetails' + minItems: 1 + computeResourceProfile: + $ref: '#/components/schemas/ComputeResourceInfo' + compEnvParams: + type: array + items: + $ref: '#/components/schemas/CompEnvParams' + deploymentConfig: + $ref: '#/components/schemas/DeploymentConfig' + persistentVolumes: + description: The ephemeral volume a container process may need to temporary store internal data + type: array + items: + $ref: '#/components/schemas/PersistentVolumeDetails' + minItems: 1 + ComputeResourceInfo: + type: object + required: + - cpuArchType + - numCPU + - memory + properties: + cpuArchType: + type: string + enum: + - ISA_X86_64 + - ISA_ARM_64 + description: CPU Instruction Set Architecture (ISA) E.g., Intel, Arm etc. + numCPU: + $ref: '#/components/schemas/Vcpu' + memory: + type: integer + format: int64 + description: Amount of RAM in Mbytes + diskStorage: + type: integer + format: int32 + description: Amount of disk storage in Gbytes for a given ISA type + gpu: + type: array + items: + $ref: '#/components/schemas/GpuInfo' + vpu: + type: integer + description: Number of Intel VPUs available for a given ISA type + fpga: + type: integer + description: Number of FPGAs available for a given ISA type + hugepages: + type: array + items: + $ref: '#/components/schemas/HugePage' + cpuExclusivity: + type: boolean + description: Support for exclusive CPUs + + nodeDiscoveryResponse: + type: object + required: + - edgeNodes + - discoveredAppInsts + properties: + edgeNodes: + $ref: '#/components/schemas/DiscoveredEdgeNodes' + discoveredAppInsts: + $ref: '#/components/schemas/DiscoveredAppInsts' + description: Candidate availability zones and details of already running instances of the given application + + + DiscoveredEdgeNodes: + type: array + items: + type: object + required: + - zoneId + - latencyServiceEndPoints + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + latencyServiceEndPoints: + $ref: '#/components/schemas/ServiceEndpoint' + minItems: 1 + description: List of candidate zones where application instance could be created. LatencyServiceEndpoint is responsible for responding to latency measurement request from client + + + DiscoveredAppInsts: + type: array + items: + type: object + required: + - appId + - appProviderId + - appInstances + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + appInstances: + type: array + items: + type: object + required: + - instancesInfo + properties: + instancesInfo: + type: object + required: + - zoneId + - appProviderId + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + instanceDetails: + $ref: '#/components/schemas/InstanceDetails' + minItems: 1 + + InstanceDetails: + type: array + items: + type: object + required: + - appInstanceInfo + properties: + appInstanceInfo: + type: object + required: + - instanceIdentifier + - instanceState + properties: + instanceIdentifier: + $ref: '#/components/schemas/InstanceIdentifier' + instancestate: + $ref: '#/components/schemas/InstanceState' + minItems: 1 + + + FederationRequestData: + type: object + required: + - initialDate + - partnerStatusLink + properties: + origOPFederationId: + $ref: '#/components/schemas/FederationIdentifier' + origOPCountryCode: + $ref: '#/components/schemas/CountryCode' + origOPMobileNetworkCodes: + $ref: '#/components/schemas/MobileNetworkIds' + origOPFixedNetworkCodes: + $ref: '#/components/schemas/FixedNetworkIds' + initialDate: + type: string + format: date-time + description: Time zone info of the federation initiated by the originating OP + partnerStatusLink: + $ref: '#/components/schemas/Uri' + + FederationResponseData: + type: object + required: + - federationContextId + - platformCaps + properties: + partnerOPFederationId: + $ref: '#/components/schemas/FederationIdentifier' + partnerOPCountryCode: + $ref: '#/components/schemas/CountryCode' + federationContextId: + $ref: '#/components/schemas/FederationContextId' + edgeDiscoveryServiceEndPoint: + $ref: '#/components/schemas/ServiceEndpoint' + lcmServiceEndPoint: + $ref: '#/components/schemas/ServiceEndpoint' + partnerOPMobileNetworkCodes: + $ref: '#/components/schemas/MobileNetworkIds' + partnerOPFixedNetworkCodes: + $ref: '#/components/schemas/FixedNetworkIds' + offeredAvailabilityZones: + type: array + items: + $ref: '#/components/schemas/ZoneDetails' + minItems: 1 + description: List of zones, which the operator platform wishes to make available to developers/ISVs of requesting operator platform. + platformCaps: + $ref: '#/components/schemas/PlatformCaps' + federationExpiryDate: + type: string + format: date-time + description: Date and Time zone info of the existing federation expiry + federationRenewalDate: + type: string + format: date-time + description: Date and Time zone info of the existing federation renewal. Shall be less than federationExpiryDate + + dateAndTimeZoneObject: + type: string + format: date-time + description: Date and Time zone info format + Flavour: + type: object + required: + - flavourId + - cpuArchType + - supportedOSTypes + - numCPU + - memorySize + - storageSize + properties: + flavourId: + $ref: '#/components/schemas/FlavourId' + cpuArchType: + $ref: '#/components/schemas/CPUArchType' + supportedOSTypes: + description: A list of operating systems which this flavour configuration can support e.g., RHEL Linux, Ubuntu 18.04 LTS, MS Windows 2012 R2. + type: array + items: + $ref: '#/components/schemas/OSType' + minItems: 1 + numCPU: + type: integer + format: int32 + description: Number of available vCPUs + memorySize: + type: integer + format: int32 + description: Amount of RAM in Mbytes + storageSize: + type: integer + format: int32 + description: Amount of disk storage in Gbytes + gpu: + type: array + items: + $ref: '#/components/schemas/GpuInfo' + fpga: + type: integer + format: int32 + description: Number of FPGAs + + vpu: + type: integer + description: Number of Intel VPUs available + hugepages: + type: array + items: + $ref: '#/components/schemas/HugePage' + cpuExclusivity: + type: boolean + description: Support for exclusive CPUs + GpuInfo: + type: object + required: + - gpuVendorType + - gpuModeName + - gpuMemory + - numGPU + properties: + gpuVendorType: + type: string + enum: + - GPU_PROVIDER_NVIDIA + - GPU_PROVIDER_AMD + description: GPU vendor name e.g. NVIDIA, AMD etc. + example: Nvidia + gpuModeName: + type: string + description: Model name corresponding to vendorType may include info e.g. for NVIDIA, model name could be “Tesla M60”, “Tesla V100” etc. + gpuMemory: + type: integer + description: GPU memory in Mbytes + numGPU: + type: integer + description: Number of GPUs + HugePage: + type: object + required: + - pageSize + - number + properties: + pageSize: + type: string + enum: + - 2MB + - 4MB + - 1GB + description: Size of hugepage + number: + type: integer + description: Total number of huge pages + InterfaceDetails: + type: object + required: + - interfaceId + - commProtocol + - commPort + - visibilityType + properties: + interfaceId: + type: string + description: Each Port and corresponding traffic protocol exposed by the component is identified by a name. Application client on user device requires this to uniquely identify the interface. + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,30}[A-Za-z0-9]$ + commProtocol: + type: string + enum: + - TCP + - UDP + - HTTP_HTTPS + description: Defines the IP transport communication protocol i.e., TCP, UDP or HTTP + commPort: + type: integer + format: int32 + minimum: 1 + maximum: 65535 + description: Port number exposed by the component. OP may generate a dynamic port towards the UCs corresponding to this internal port and forward the client traffic from dynamic port to container Port. + visibilityType: + description: Defines whether the interface is exposed to outer world or not i.e., external, or internal. If this is set to "external", then it is exposed to external applications otherwise it is exposed internally to edge application components within edge cloud. When exposed to external world, an external dynamic port is assigned for UC traffic and mapped to the internal container Port + type: string + enum: + - VISIBILITY_EXTERNAL + - VISIBILITY_INTERNAL + network: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{6,30}[A-Za-z0-9]$ + description: Name of the network. In case the application has to be associated with more than 1 network then app provider must define the name of the network on which this interface has to be exposed. This parameter is required only if the port has to be exposed on a specific network other than default. + InterfaceName: + type: string + pattern: ^[a-z][a-z0-9]{3}$ + description: Interface Name. Required only if application has to be attached to a network other than default. + InvalidParam: + type: object + properties: + param: + type: string + reason: + type: string + required: + - param + MobileNetworkIds: + type: object + properties: + mcc: + $ref: '#/components/schemas/Mcc' + mncs: + type: array + items: + $ref: '#/components/schemas/Mnc' + minItems: 1 + ObjectRepoLocation: + type: object + properties: + repoURL: + $ref: '#/components/schemas/Uri' + userName: + type: string + description: Username to access the repository + password: + type: string + description: Password to access the repository + token: + type: string + description: Authorization token to access the repository + OSType: + type: object + required: + - architecture + - distribution + - version + - license + properties: + architecture: + type: string + enum: + - x86_64 + - x86 + example: x86_64 + distribution: + type: string + enum: + - RHEL + - UBUNTU + - COREOS + - FEDORA + - WINDOWS + - OTHER + + version: + type: string + enum: + - OS_VERSION_UBUNTU_2204_LTS + - OS_VERSION_RHEL_8 + - OS_VERSION_RHEL_7 + - OS_VERSION_DEBIAN_11 + - OS_VERSION_COREOS_STABLE + - OS_MS_WINDOWS_2012_R2 + - OTHER + + license: + type: string + enum: + - OS_LICENSE_TYPE_FREE + - OS_LICENSE_TYPE_ON_DEMAND + - NOT_SPECIFIED + + RepoType: + type: string + enum: + - PRIVATEREPO + - PUBLICREPO + - UPLOAD + description: Artefact or file repository location. PUBLICREPO is used of public URLs like GitHub, Helm repo, docker registry etc., PRIVATEREPO is used for private repo managed by the application developer, UPLOAD is for the case when artefact/file is uploaded from MEC web portal. OP should pull the image from ‘repoUrl' immediately after receiving the request and then send back the response. In case the repoURL corresponds to a docker registry, use docker v2 http api to do the pull. + + ArtefactName: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,31}$ + description: Name of the artefact. + + ArtefactVersionInfo: + type: string + description: Artefact version information + + ArtefactDescription: + type: string + maxLength: 256 + description: Brief description of the artefact by the application provider + + ArtefactVirtType: + type: string + enum: + - VM_TYPE + - CONTAINER_TYPE + + ArtefactFileName: + type: string + minLength: 8 + maxLength: 32 + description: Name of the file. + + ArtefactFileFormat: + type: string + enum: + - ZIP + - TAR + - TEXT + - TARGZ + description: Artefacts like Helm charts or Terraform scripts may need compressed format. + + ArtefactDescriptorType: + type: string + enum: + - HELM + - TERRAFORM + - ANSIBLE + - SHELL + - COMPONENTSPEC + description: Type of descriptor present in the artefact. App provider can either define either a Helm chart or a Terraform script or container spec. + + + LatencyConstraints: + type: string + enum: + - NONE + - LOW + - ULTRALOW + description: Latency requirements for the application.Allowed values (non-standardized) are none, low and ultra-low. Ultra-Low may corresponds to range 15 - 30 msec, Low correspond to range 30 - 50 msec. None means 51 and above + + BandwidthRequired: + type: integer + format: int32 + minimum: 1 + description: Data transfer bandwidth requirement (minimum limit) for the application. It should in Mbits/sec + + MobilitySupport: + type: boolean + default: false + description: Indicates if an application is sensitive to user mobility and can be relocated. Default is “FALSE” + + MultiUserClients: + type: string + enum: + - APP_TYPE_SINGLE_USER + - APP_TYPE_MULTI_USER + description: Single user type application are designed to serve just one client. Multi user type application is designed to serve multiple clients + + NoOfUsersPerAppInst: + type: integer + default: 1 + description: Maximum no of clients that can connect to an instance of this application. This parameter is relevant only for application of type multi user + AppProvisioning: + type: boolean + default: true + description: Define if application can be instantiated or not + + AppComponents: + description: An application may consist of more than one component. Each component is associated with a descriptor and may exposes its services externally or internally. App providers are required to provide details about all these components, their associated descriptors and their DNS names. + type: array + items: + type: object + required: + - componentName + anyOf: + - required: + - serviceNameNB + - required: + - serviceNameEW + - required: + - artefactId + properties: + serviceNameNB: + $ref: '#/components/schemas/ServiceNameNB' + serviceNameEW: + $ref: '#/components/schemas/ServiceNameEW' + componentName: + $ref: '#/components/schemas/ComponentName' + artefactId: + $ref: '#/components/schemas/ArtefactId' + minItems: 1 + + + PersistentVolumeDetails: + type: object + required: + - volumeSize + - volumeMountPath + - volumeName + properties: + volumeSize: + type: string + enum: + - 10Gi + - 20Gi + - 50Gi + - 100Gi + description: size of the volume given by user (10GB, 20GB, 50 GB or 100GB) + volumeMountPath: + type: string + description: Defines the mount path of the volume + volumeName: + type: string + description: Human readable name for the volume + ephemeralType: + type: boolean + default: false + description: It indicates the ephemeral storage on the node and contents are not preserved if containers restarts + accessMode: + type: string + enum: + - RW + - RO + default: RW + description: Values are RW (read/write) and RO (read-only)l + sharingPolicy: + type: string + enum: + - EXCLUSIVE + - SHARED + default: EXCLUSIVE + description: Exclusive or Shared. If shared, then in case of multiple containers same volume will be shared across the containers. + ProblemDetails: + type: object + properties: + title: + type: string + description: Summary of the problem + detail: + type: string + description: Specific detail of the issue + cause: + type: string + description: Fixed string indicating cause of the issue + invalidParams: + type: array + items: + $ref: '#/components/schemas/InvalidParam' + minItems: 0 + ResourceReservationDuration: + description: Time period for which resources are to be reserved starting from now + type: object + minProperties: 1 + properties: + numOfDays: + type: integer + format: int32 + description: Number of days to be reserved + numOfMonths: + type: integer + format: int32 + description: Number of months to be reserved + numOfYears: + type: integer + format: int32 + description: Number of years to be reserved + ServiceEndpoint: + type: object + required: + - port + anyOf: + - required: + - fqdn + - required: + - ipv4Addresses + - required: + - ipv6Addresses + properties: + port: + $ref: '#/components/schemas/Port' + fqdn: + $ref: '#/components/schemas/EdgeAppFQDN' + ipv4Addresses: + type: array + items: + $ref: '#/components/schemas/Ipv4Addr' + minItems: 1 + ipv6Addresses: + type: array + items: + $ref: '#/components/schemas/Ipv6Addr' + minItems: 1 + ZoneDetails: + type: object + required: + - zoneId + - geographyDetails + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + geolocation: + $ref: '#/components/schemas/GeoLocation' + geographyDetails: + type: string + description: Details about cities or state covered by the edge. Details about the type of locality for eg rural, urban, industrial etc. This information is defined in human readable form. + ZoneRegistrationRequestData: + type: object + required: + - acceptedAvailabilityZones + properties: + acceptedAvailabilityZones: + type: array + items: + $ref: '#/components/schemas/ZoneIdentifier' + minItems: 1 + availZoneNotifLink: + $ref: '#/components/schemas/Uri' + ZoneRegistrationResponseData: + type: object + required: + - acceptedZoneResourceInfo + properties: + acceptedZoneResourceInfo: + type: array + items: + $ref: '#/components/schemas/ZoneRegisteredData' + + minItems: 1 + ZoneRegisteredData: + type: object + required: + - zoneId + - reservedComputeResources + - computeResourceQuotaLimits + - flavoursSupported + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + reservedComputeResources: + description: Resources exclusively reserved for the originator OP. + type: array + items: + $ref: '#/components/schemas/ComputeResourceInfo' + minItems: 1 + computeResourceQuotaLimits: + description: Max quota on resources partner OP allows over reserved resources. + type: array + items: + $ref: '#/components/schemas/ComputeResourceInfo' + minItems: 1 + flavoursSupported: + type: array + items: + $ref: '#/components/schemas/Flavour' + minItems: 1 + networkResources: + type: object + required: + - egressBandWidth + - dedicatedNIC + - supportSriov + - supportDPDK + properties: + egressBandWidth: + type: integer + format: int32 + description: Max dl throughput that this edge can offer. It is defined in Mbps. + dedicatedNIC: + type: integer + format: int32 + description: Number of network interface cards which can be dedicatedly assigned to application pods on isolated networks. This includes virtual as well physical NICs + supportSriov: + type: boolean + description: If this zone support SRIOV networks or not + supportDPDK: + type: boolean + description: If this zone supports DPDK based networking. + zoneServiceLevelObjsInfo: + type: object + description: It is a measure of the actual amount of data that is being sent over a network per unit of time and indicates máximum supported value for a zone + required: + - latencyRanges + - jitterRanges + - throughputRanges + properties: + latencyRanges: + type: object + properties: + minLatency: + type: integer + format: int32 + minimum: 1 + description: The time for data/packet to reach from UC to edge application. It represent mínimum latency in milli seconds that may exist between UCs and edge apps in this zone but it can be higher in actual. + maxLatency: + type: integer + format: int32 + description: The maximum limit of latency between UC and Edge App in milli seconds. + jitterRanges: + type: object + properties: + minJitter: + type: integer + format: int32 + minimum: 1 + maxJitter: + type: integer + format: int32 + description: The maximum limit of network jitter between UC and Edge App in milli seconds. + throughputRanges: + type: object + properties: + minThroughput: + type: integer + format: int32 + minimum: 1 + description: The minimum limit of network throughput between UC and Edge App in Mega bits per seconds (Mbps). + maxThroughput: + type: integer + format: int32 + description: The maximum limit of network throughput between UC and Edge App in Mega bits per seconds (Mbps). + + # + # HTTP responses + # + responses: + "400": + description: Bad request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "401": + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "404": + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "409": + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "412": + description: Precondition Failed + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "422": + description: Unprocessable Entity + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "500": + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "501": + description: Not Implemented + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "503": + description: Service Unavailable + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "520": + description: Web Server Returned an Unknown Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + + "400BadRequest": + description: Bad request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + examples: + InvalidFedParameters: + description: Sufficient parameters must be specified to allow the partner OP to validate federation request + value: + { + "title": "Insufficient parameters", + "details": "Incorrect values received in federation request", + "cause": "INVALID_FED_RQST_PARAMS" + } + + "404NotFound": + description: Resource Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + examples: + FederationContextNotFound: + description: Federation context does not exist + value: + { + "title": "Federation context Id not found", + "details": "Partner OP does not recognize the federationContextId from Originating OP", + "cause": "INVALID_FED_CTX_ID" + } + FederationNotFound: + description: Federation terminated parmanently + value: + { + "title": "Federation context Id not found", + "details": "Partner OP does not recognize the federationContextId from Originating OP", + "cause": "FED_PERMANENTLY_TERMINAT" + } + ZoneNotFound: + description: Zone Not Found + value: + { + "title": "Requested Zone Id not found", + "details": "Requested zone by the Originating OP does not exist with Partner OP", + "cause": "ZONE_ID_NOT_FOUND" + } + AppNotFound: + description: Application Not Found + value: + { + "title": "Requested Application Id not found", + "details": "Requested Application by the Originating OP does not exist with Partner OP", + "cause": "APP_ID_NOT_FOUND" + } + AppInstNotFound: + description: Application Instance Not Found + value: + { + "title": "Requested App instance Id not found", + "details": "Requested application instance by the Originating OP does not exist with Partner OP", + "cause": "APP_INST_NOT_FOUND" + } + + + default: + description: Generic Error +paths: + /federation-resources: + get: + summary: Retrieves REST APIs supported by an OP for federation services. + operationId: GetFederationAPIs + tags: + - FederationAPIManagement + responses: + "200": + description: Federation meta-info request accepted + content: + application/json: + schema: + type: object + required: + - federationSupportedAPIs + properties: + federationSupportedAPIs: + $ref: '#/components/schemas/FederationSupportedAPIs' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /partner: + post: + summary: Creates one direction federation with partner operator platform. + operationId: CreateFederation + tags: + - FederationManagement + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/FederationRequestData' + responses: + "200": + description: Federation meta-info request accepted + content: + application/json: + schema: + $ref: '#/components/schemas/FederationResponseData' + headers: + Location: + description: 'Contains the URI of the newly created resource, according to the structure: {apiRoot}/operatorplatform/federation/v1/partner/{federationContextId}' + required: true + schema: + type: string + Accept-Encoding: + description: Accept-Encoding, described in IETF RFC 7694 + schema: + type: string + Content-Encoding: + description: Content-Encoding, described in IETF RFC 7231 + schema: + type: string + "400": + $ref: '#/components/responses/400BadRequest' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onPartnerStatusEvent: + '{$request.body#/partnerStatusLink }': + post: + requestBody: + description: | + OP uses this callback api to notify partner OP about change in federation status, federation metadata or offered zone details. Allowed combinations of objectType and operationType are + - FEDERATION - STATUS: Status specified by parameter 'federationStatus'. + - ZONES - STATUS: Status specified by parameter 'zoneStatus'. + - ZONES - ADD: Use parameter 'addZones' to define add new zones + - ZONES - REMOVE: Use parameter 'removeZones' to define remove zones. + - EDGE_DISCOVERY_SERVICE - UPDATE: Use parameter 'edgeDiscoverySvcEndPoint' to specify new endpoints + - LCM_SERVICE - UPDATE: Use parameter 'lcmSvcEndPoint' to specify new endpoints + - MOBILE_NETWORK_CODES - ADD: Use parameter 'addMobileNetworkIds' to define new mobile network codes. + - MOBILE_NETWORK_CODES - REMOVE: Use parameter 'removeMobileNetworkIds' to remove mobile network codes. + - FIXED_NETWORK_CODES - ADD: Use parameter 'addFixedNetworkIds' to define new fixed network codes. + - FIXED_NETWORK_CODES - REMOVE: Use parameter 'removeFixedNetworkIds' to remove fixed network codes. + - SERVICE_APIS - ADD/REMOVE: Parameter Usage 'addServiceAPIs / removeServiceAPIs' to add or remove Service APIs support. + + content: + application/json: + schema: + type: object + required: + - federationContextId + - objectType + - operationType + - modificationDate + properties: + federationContextId: + $ref: '#/components/schemas/FederationContextId' + objectType: + type: string + enum: + - FEDERATION + - ZONES + - EDGE_DISCOVERY_SERVICE + - LCM_SERVICE + - MOBILE_NETWORK_CODES + - FIXED_NETWORK_CODES + - SERVICE_APIS + operationType: + type: string + enum: + - STATUS + - UPDATE + - ADD + - REMOVE + edgeDiscoverySvcEndPoint: + $ref: '#/components/schemas/ServiceEndpoint' + lcmSvcEndPoint: + $ref: '#/components/schemas/ServiceEndpoint' + addMobileNetworkIds: + $ref: '#/components/schemas/MobileNetworkIds' + removeMobileNetworkIds: + $ref: '#/components/schemas/MobileNetworkIds' + addFixedNetworkIds: + $ref: '#/components/schemas/FixedNetworkIds' + removeFixedNetworkIds: + $ref: '#/components/schemas/FixedNetworkIds' + addZones: + type: array + items: + $ref: '#/components/schemas/ZoneDetails' + description: List of zones, which the operator platform wishes to make available to developers/ISVs of requesting operator platform. + minItems: 1 + removeZones: + type: array + items: + $ref: '#/components/schemas/ZoneIdentifier' + description: List of zones, which the operator platform no longer wishes to share. + minItems: 1 + addServiceAPIs: + $ref: '#/components/schemas/serviceAPINames' + removeServiceAPIs: + $ref: '#/components/schemas/serviceAPINames' + zoneStatus: + type: array + items: + type: object + required: + - zoneId + - status + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + status: + $ref: '#/components/schemas/Status' + minItems: 1 + federationStatus: + $ref: '#/components/schemas/Status' + modificationDate: + type: string + format: date-time + description: Date and time of the federation modification by the originating partner OP + responses: + "204": + description: Expected response to a successful call back processing + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + /{federationContextId}/partner: + get: + summary: Retrieves details about the federation context with the partner OP. The response shall provide info about the zones offered by the partner, partner OP network codes, information about edge discovery and LCM service etc. + operationId: GetFederationDetails + tags: + - FederationManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + responses: + "200": + description: Federation meta-info request accepted + content: + application/json: + schema: + type: object + properties: + edgeDiscoveryServiceEndPoint: + $ref: '#/components/schemas/ServiceEndpoint' + lcmServiceEndPoint: + $ref: '#/components/schemas/ServiceEndpoint' + allowedMobileNetworkIds: + $ref: '#/components/schemas/MobileNetworkIds' + allowedFixedNetworkIds: + $ref: '#/components/schemas/FixedNetworkIds' + offeredAvailabilityZones: + type: array + items: + $ref: '#/components/schemas/ZoneDetails' + minItems: 1 + platformCaps: + $ref: '#/components/schemas/PlatformCaps' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + patch: + summary: API used by the Originating OP towards the partner OP, to update the parameters associated to the existing federation + operationId: UpdateFederation + tags: + - FederationManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + required: true + description: Details about changes origination OP wished to apply + content: + application/json: + schema: + type: object + required: + - objectType + - operationType + - modificationDate + properties: + objectType: + type: string + enum: + - MOBILE_NETWORK_CODES + - FIXED_NETWORK_CODES + - OPS_POLICY + - APP_POLICY + operationType: + type: string + enum: + - ADD_CODES + - REMOVE_CODES + - UPDATE_CODES + - ADD_POLICY + - REMOVE_POLICY + - UPDATE_POLICY + + addMobileNetworkIds: + $ref: '#/components/schemas/MobileNetworkIds' + removeMobileNetworkIds: + $ref: '#/components/schemas/MobileNetworkIds' + addFixedNetworkIds: + $ref: '#/components/schemas/FixedNetworkIds' + removeFixedNetworkIds: + $ref: '#/components/schemas/FixedNetworkIds' + assocAppPolicies: + $ref: '#/components/schemas/AssocApplPolicies' + assocOpsPolicies: + $ref: '#/components/schemas/AssocOpsPolicies' + + modificationDate: + type: string + format: date-time + description: Date and time of the federation modification by the originating partner OP + responses: + "200": + description: Federation meta-info request accepted + content: + application/json: + schema: + type: object + properties: + edgeDiscoveryServiceEndPoint: + $ref: '#/components/schemas/ServiceEndpoint' + lcmServiceEndPoint: + $ref: '#/components/schemas/ServiceEndpoint' + allowedMobileNetworkIds: + $ref: '#/components/schemas/MobileNetworkIds' + allowedFixedNetworkIds: + $ref: '#/components/schemas/FixedNetworkIds' + offeredAvailabilityZones: + type: array + items: + $ref: '#/components/schemas/ZoneDetails' + minItems: 1 + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + delete: + summary: Remove existing federation with the partner OP + operationId: DeleteFederationDetails + tags: + - FederationManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + responses: + "200": + description: Federation removed successfully + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /fed-context-id: + get: + summary: Retrieves the existing federationContextId with partner operator platform. + operationId: GetFederationContextId + tags: + - FederationManagement + responses: + "200": + description: Federation context identifier retrieval request accepted + content: + application/json: + schema: + type: object + required: + - FederationContextId + properties: + FederationContextId: + $ref: '#/components/schemas/FederationContextId' + headers: + Location: + description: 'Contains the URI of the existing resource, according to the structure: {apiRoot}/operatorplatform/federation/v1/partner/{federationContextId}' + required: true + schema: + type: string + Accept-Encoding: + description: Accept-Encoding, described in IETF RFC 7694 + schema: + type: string + Content-Encoding: + description: Content-Encoding, described in IETF RFC 7231 + schema: + type: string + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/health: + get: + summary: Retrieves health status of the federation context with the Partner OP. + operationId: GetFederationHealth + tags: + - FederationManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + responses: + "200": + description: Federation health status information object + content: + application/json: + schema: + type: object + required: + - federationHealthStatus + properties: + federationHealthStatus: + $ref: '#/components/schemas/FederationHealthInfo' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/renew: + post: + summary: API used by the Originating OP to renew the existing federation + operationId: RenewFederation + tags: + - FederationManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + responses: + "200": + description: Federation renewal request accepted + content: + application/json: + schema: + type: object + required: + - FederationContextId + - federationRenewalDate + - federationExpiryDate + properties: + FederationContextId: + $ref: '#/components/schemas/FederationContextId' + federationRenewalDate: + $ref: '#/components/schemas/dateAndTimeZoneObject' + federationExpiryDate: + $ref: '#/components/schemas/dateAndTimeZoneObject' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/platform-caps: + get: + summary: Retrieves details about OP capabilities of the federated partner. + operationId: GetPlatformCapabilities + tags: + - FederationManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: capType + in: query + required: false + schema: + $ref: '#/components/schemas/CapabilityID' + + responses: + "200": + description: Federation meta-info request accepted + content: + application/json: + schema: + type: object + anyOf: + - required: + - deviceConnStatusChangeCap + - required: + - locationRetrievalCap + - required: + - userPlaneMgmtEvtCap + - required: + - dynamicQoSCap + properties: + deviceConnStatusChangeCap: + $ref: '#/components/schemas/DeviceConnStatusChangeCap' + locationRetrievalCap: + $ref: '#/components/schemas/LocationRetrievalCap' + userPlaneMgmtEvtCap: + $ref: '#/components/schemas/UserPlaneMgmtEvtCap' + dynamicQoSCap: + $ref: '#/components/schemas/DynamicQoSCap' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + + /{federationContextId}/partner/service/{serviceType}: + get: + summary: Retrieves the list of Service APIs and associated information that a partner OP supports + operationId: GetServiceAPIsDetails + tags: + - FederationManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: serviceType + in: path + required: true + schema: + $ref: '#/components/schemas/serviceType' + responses: + '200': + description: List of Service APIs names and associated configuration info as supported capabilities + content: + application/json: + schema: + type: object + required: + - ServiceType + - serviceCaps + - apiRoutingInfo + properties: + serviceCaps: + $ref: '#/components/schemas/serviceAPINames' + serviceType: + $ref: '#/components/schemas/serviceType' + apiRoutingInfo: + $ref: '#/components/schemas/serviceRoutingInfo' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/zones: + get: + summary: Retrieves details about the computation and network resources that partner OP has reserved for this zone. + operationId: GetZoneData + tags: + - AvailabilityZoneInfoSynchronization + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: zoneId + in: query + required: false + schema: + $ref: '#/components/schemas/ZoneIdentifier' + responses: + "200": + description: Zone metadata + content: + application/json: + schema: + $ref: '#/components/schemas/ZoneRegisteredData' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + post: + summary: Originating OP informs partner OP that it is willing to access the specified zones and partner OP shall reserve compute and network resources for these zones. + operationId: ZoneSubscribe + tags: + - AvailabilityZoneInfoSynchronization + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ZoneRegistrationRequestData' + required: true + responses: + "200": + description: Zone registered successfully + content: + application/json: + schema: + $ref: '#/components/schemas/ZoneRegistrationResponseData' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onZoneResourceUpdateEvent: + '{$request.body#/availZoneNotifLink}': + post: + requestBody: + description: Notification about resource availability. + content: + application/json: + schema: + type: object + required: + - federationContextId + - zoneId + - zoneResUpdInfo + properties: + federationContextId: + $ref: '#/components/schemas/FederationIdentifier' + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + zoneResUpdInfo: + type: array + items: + type: object + minProperties: 1 + properties: + availableCompResources: + description: Resources exclusively reserved for the originator OP. + type: array + items: + $ref: '#/components/schemas/ComputeResourceInfo' + minItems: 1 + availableNetResources: + type: object + properties: + egressBandWidth: + type: integer + format: int32 + description: Max dl throughput that this edge can offer. It is defined in Mbps. + dedicatedNIC: + type: integer + format: int32 + supportSriov: + type: boolean + description: If this zone support SRIOV networks or not + supportDPDK: + type: boolean + description: If this zone supports DPDK based networking + minProperties: 1 + responses: + "200": + description: Zone info notification acknowledged + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + + /{federationContextId}/zones/{zoneId}: + delete: + summary: Assert usage of a partner OP zone. Originating OP informs partner OP that it will no longer access the specified zone. + operationId: ZoneUnsubscribe + tags: + - AvailabilityZoneInfoSynchronization + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + responses: + "200": + description: Zone deregistered successfully + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + get: + summary: Retrieves details about the computation and network resources that partner OP has reserved for this zone. + operationId: GetZoneDetails + tags: + - AvailabilityZoneInfoSynchronization + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + responses: + "200": + description: Zone metadata + content: + application/json: + schema: + $ref: '#/components/schemas/ZoneRegisteredData' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/artefact: + post: + summary: Uploads application artefact on partner OP. Artefact is a zip file containing scripts and/or packaging files like Terraform or Helm which are required to create an instance of an application. + operationId: UploadArtefact + tags: + - ArtefactManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + description: An application can consist of multiple components. App providers are allowed to define separate artefacts for each component or they could define a consolidated artefact at application level. + content: + multipart/form-data: + schema: + type: object + required: + - artefactId + - appProviderId + - artefactName + - artefactVersionInfo + - artefactVirtType + - artefactDescriptorType + - componentSpec + properties: + artefactId: + $ref: '#/components/schemas/ArtefactId' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + artefactName: + $ref: '#/components/schemas/ArtefactName' + artefactVersionInfo: + $ref: '#/components/schemas/ArtefactVersionInfo' + artefactDescription: + $ref: '#/components/schemas/ArtefactDescription' + artefactVirtType: + $ref: '#/components/schemas/ArtefactVirtType' + artefactFileName: + $ref: '#/components/schemas/ArtefactFileName' + artefactFileFormat: + $ref: '#/components/schemas/ArtefactFileFormat' + artefactDescriptorType: + $ref: '#/components/schemas/ArtefactDescriptorType' + repoType: + $ref: '#/components/schemas/RepoType' + artefactRepoLocation: + $ref: '#/components/schemas/ObjectRepoLocation' + artefactFile: + type: string + format: binary + description: Helm archive/Terraform archive/container spec file or Binary image associated with an application component. + componentSpec: + type: array + items: + $ref: '#/components/schemas/ComponentSpec' + minItems: 1 + required: true + responses: + "200": + description: Artefact uploaded successfully + "202": + description: Artefact upload request accepted + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/artefact/{artefactId}: + get: + summary: Retrieves details about an artefact. + operationId: GetArtefact + tags: + - ArtefactManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: artefactId + in: path + required: true + schema: + $ref: '#/components/schemas/ArtefactId' + responses: + "200": + description: Artefact details + content: + application/json: + schema: + type: object + required: + - artefactId + - appProviderId + - artefactName + - artefactVersionInfo + - artefactVirtType + - artefactDescriptorType + properties: + artefactId: + $ref: '#/components/schemas/ArtefactId' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + artefactName: + $ref: '#/components/schemas/ArtefactName' + artefactDescription: + $ref: '#/components/schemas/ArtefactDescription' + artefactVersionInfo: + $ref: '#/components/schemas/ArtefactVersionInfo' + artefactVirtType: + $ref: '#/components/schemas/ArtefactVirtType' + artefactFileName: + $ref: '#/components/schemas/ArtefactFileName' + artefactFileFormat: + $ref: '#/components/schemas/ArtefactFileFormat' + artefactDescriptorType: + $ref: '#/components/schemas/ArtefactDescriptorType' + repoType: + $ref: '#/components/schemas/RepoType' + artefactRepoLocation: + $ref: '#/components/schemas/ObjectRepoLocation' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + delete: + summary: Removes an artefact from partner OP. + operationId: RemoveArtefact + tags: + - ArtefactManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: artefactId + in: path + required: true + schema: + $ref: '#/components/schemas/ArtefactId' + responses: + "200": + description: Artefact deletion successful + "202": + description: Artefact deletion request accepted + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/files: + post: + summary: Uploads an image file. Originating OP uses this api to onboard an application image to partner OP. + operationId: UploadFile + tags: + - ArtefactManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + content: + multipart/form-data: + schema: + type: object + required: + - fileId + - appProviderId + - fileName + - fileVersionInfo + - fileType + - imgOSType + - imgInsSetArch + properties: + fileId: + $ref: '#/components/schemas/FileId' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + fileName: + $ref: '#/components/schemas/FileName' + fileDescription: + $ref: '#/components/schemas/FileDescription' + fileVersionInfo: + $ref: '#/components/schemas/FileVersionInfo' + fileType: + $ref: '#/components/schemas/VirtImageType' + checksum: + type: string + description: MD5 checksum for VM and file-based images, sha256 digest for containers + imgOSType: + $ref: '#/components/schemas/OSType' + imgInsSetArch: + $ref: '#/components/schemas/CPUArchType' + repoType: + $ref: '#/components/schemas/RepoType' + + fileRepoLocation: + $ref: '#/components/schemas/ObjectRepoLocation' + file: + type: string + format: binary + description: Binary image associated with an application component. + required: true + responses: + "200": + description: File uploaded successfully + "202": + description: File upload request accepted + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/files/{fileId}: + delete: + summary: Removes an image file from partner OP. + operationId: RemoveFile + tags: + - ArtefactManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: fileId + in: path + required: true + schema: + $ref: '#/components/schemas/FileId' + responses: + "200": + description: Image deletion successful + "202": + description: Image deletion request accepted + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + get: + summary: View an image file from partner OP. + operationId: ViewFile + tags: + - ArtefactManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: fileId + in: path + required: true + schema: + $ref: '#/components/schemas/FileId' + responses: + "200": + description: Image details + content: + application/json: + schema: + type: object + required: + - fileId + - appProviderId + - fileName + - fileVersionInfo + - fileType + - imgOSType + - imgInsSetArch + properties: + fileId: + $ref: '#/components/schemas/FileId' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + fileName: + $ref: '#/components/schemas/FileName' + fileDescription: + $ref: '#/components/schemas/FileDescription' + fileVersionInfo: + $ref: '#/components/schemas/FileVersionInfo' + fileType: + $ref: '#/components/schemas/VirtImageType' + checksum: + type: string + description: MD5 checksum for VM and file-based images, sha256 digest for containers + imgOSType: + $ref: '#/components/schemas/OSType' + imgInsSetArch: + $ref: '#/components/schemas/CPUArchType' + repoType: + $ref: '#/components/schemas/RepoType' + + fileRepoLocation: + $ref: '#/components/schemas/ObjectRepoLocation' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/application/onboarding: + post: + summary: Submits an application details to a partner OP. Based on the details provided, partner OP shall do bookkeeping, resource validation and other pre-deployment operations. + operationId: OnboardApplication + tags: + - ApplicationOnboardingManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + required: true + description: Details about application compute resource requirements, associated artefacts, QoS profile and regions where application shall be made available etc. + content: + application/json: + schema: + type: object + required: + - appId + - appProviderId + - appMetaData + - appQoSProfile + - appComponentSpecs + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + appDeploymentZones: + description: Details about partner OP zones where the application should be made available; This field when specified will instruct the OP to restrict application instantiation only on the listed zones. + type: array + items: + $ref: '#/components/schemas/ZoneIdentifier' + minItems: 1 + appMetaData: + $ref: '#/components/schemas/AppMetaData' + appQoSProfile: + $ref: '#/components/schemas/AppQoSProfile' + appComponentSpecs: + $ref: '#/components/schemas/AppComponentSpecs' + appStatusCallbackLink: + $ref: '#/components/schemas/Uri' + edgeAppFQDN: + $ref: '#/components/schemas/EdgeAppFQDN' + + responses: + "200": + description: Application onboarded successfully + "202": + description: Application onboarding request accepted + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onApplicationOnboardStatusEvent: + '{$request.body#/appStatusCallbackLink}': + post: + requestBody: + description: Notification payload. + content: + application/json: + schema: + type: object + required: + - federationContextId + - appId + - statusInfo + properties: + federationContextId: + $ref: '#/components/schemas/FederationIdentifier' + appId: + $ref: '#/components/schemas/AppIdentifier' + statusInfo: + type: array + items: + type: object + required: + - zoneId + - onboardStatusInfo + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + onboardStatusInfo: + $ref: '#/components/schemas/OnboardStatusInfo' + minItems: 1 + responses: + "204": + description: Application status updated + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + + /{federationContextId}/application/onboarding/app/{appId}: + delete: + summary: Deboards the application from all zones, if any, and deletes the App. + operationId: DeleteApp + tags: + - ApplicationOnboardingManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + responses: + '200': + description: App deletion successful + '202': + description: App deletion request accepted + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + patch: + summary: Updates partner OP about changes in application compute resource requirements, QOS Profile, associated descriptor or change in associated components + operationId: UpdateApplication + tags: + - ApplicationOnboardingManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + requestBody: + required: true + description: Details about application compute resource requirements, associated artefact and QOS profile that needs to be updated. + content: + application/json: + schema: + type: object + minProperties: 1 + properties: + appUpdQoSProfile: + description: Parameters corresponding to the performance constraints, tenancy details etc. + type: object + anyOf: + - required: + - latencyConstraint + - required: + - bandwidthRequired + - required: + - mobilitySupport + - required: + - multiUserClients + - required: + - appProvisioning + properties: + latencyConstraints: + $ref: '#/components/schemas/LatencyConstraints' + bandwidthRequired: + $ref: '#/components/schemas/BandwidthRequired' + mobilitySupport: + $ref: '#/components/schemas/MobilitySupport' + multiUserClients: + $ref: '#/components/schemas/MultiUserClients' + noOfUsersPerAppInst: + $ref: '#/components/schemas/NoOfUsersPerAppInst' + appProvisioning: + $ref: '#/components/schemas/AppProvisioning' + appComponents: + $ref: '#/components/schemas/AppComponents' + + responses: + "200": + description: Application update successful + "202": + description: Application update request accepted + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + get: + summary: Retrieves application details from partner OP + operationId: ViewApplication + tags: + - ApplicationOnboardingManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + responses: + "200": + description: Application details + content: + application/json: + schema: + type: object + required: + - appId + - appProviderId + - appDeploymentZones + - appMetaData + - appQoSProfile + - appComponentSpecs + - onboardStatusInfo + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + appDeploymentZones: + description: Details about partner OP zones where the application should be made available; This field when specified will instruct the OP to restrict application instantiation only on the listed zones. + type: array + items: + type: object + required: + - countryCode + - zoneInfo + properties: + countryCode: + $ref: '#/components/schemas/CountryCode' + zoneInfo: + $ref: '#/components/schemas/ZoneIdentifier' + minItems: 1 + appMetaData: + $ref: '#/components/schemas/AppMetaData' + appQoSProfile: + $ref: '#/components/schemas/AppQoSProfile' + appComponentSpecs: + $ref: '#/components/schemas/AppComponentSpecs' + onboardStatusInfo: + $ref: '#/components/schemas/OnboardStatusInfo' + + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/application/onboarding/app/{appId}/zone/{zoneId}: + delete: + summary: Deboards an application from specific partner OP zones + operationId: DeboardApplication + tags: + - ApplicationOnboardingManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + responses: + "200": + description: Application deboarded successfully + "202": + description: Application deboard request accepted + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/application/onboarding/app/{appId}/additionalZones: + post: + summary: Onboards an existing application to a new zone within partner OP. + operationId: OnboardExistingAppNewZones + tags: + - ApplicationOnboardingManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + requestBody: + required: true + description: Details about new zones where application shall be made available + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ZoneIdentifier' + minItems: 1 + responses: + "200": + description: Application onboarding succussful + "202": + description: Application onboarding request accepted + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/application/onboarding/app/{appId}/zoneForbid: + post: + summary: Forbid/allow application instantiation on a partner zone + operationId: LockUnlockApplicationZone + tags: + - ApplicationOnboardingManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + requestBody: + required: true + content: + application/json: + schema: + type: array + items: + type: object + description: List of zones where application instantiation shall be forbidden or allowed. + required: + - zoneId + - forbid + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + forbid: + type: boolean + description: Value 'true' will forbid application instantiation on this zone. No new instance of the application can be created on this zone. + minItems: 1 + responses: + "200": + description: Application forbid/permit request accepted + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/application/lcm: + post: + summary: Instantiates an application on a partner OP zone. + operationId: InstallApp + tags: + - ApplicationDeploymentManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: Idempotency-Key + in: header + required: true + schema: + $ref: '#/components/schemas/TransactionId' + + requestBody: + description: Details about application and zones where application instance should be created. It also definea call back URI which the partner OP shall use update home OP about a change in instance status. + content: + application/json: + schema: + type: object + required: + - appId + - appProviderId + - appVersion + - zoneInfo + - appInstCallbackLink + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appVersion: + type: string + description: Version info of the application + appProviderId: + $ref: '#/components/schemas/AppProviderId' + zoneInfo: + type: object + required: + - zoneId + - flavourId + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + flavourId: + $ref: '#/components/schemas/FlavourId' + resourceConsumption: + type: string + enum: + - RESERVED_RES_SHALL + - RESERVED_RES_PREFER + - RESERVED_RES_AVOID + - RESERVED_RES_FORBID + default: RESERVED_RES_AVOID + description: Specifies if the application can be instantiated using pre-reserved resource or not. App provider can pre-reserve a pool of compute resource on each zone. 'RESERVED_RES_SHALL' instruct OP to use only the pre-reserved resources. 'RESERVED_RES_PREFER' instruct to first try using pre-reserved resource, if none available go for non-reserved resources. 'RESERVED_RES_AVOID' instruct OP not to use pre-reserved resource if possible, it is a choice depending upon circumstances 'RESERVED_RES_FORBID' instruct OP not to use pre-reserved resources. + resPool: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,30}[A-Za-z0-9]$ + description: Resource pool to be used for application instantiation on this zone. Valid only if IE 'resourceConsumption' is set to 'RESERVED_RES_SHALL' or 'RESERVED_RES_PREFER' + appInstCallbackLink: + $ref: '#/components/schemas/Uri' + responses: + "202": + description: Application instance creation request accepted. + content: + application/json: + schema: + type: object + required: + - zoneId + - appInstIdentifier + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + appInstIdentifier: + $ref: '#/components/schemas/InstanceIdentifier' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onInstanceStatusEvent: + '{$request.body#/appInstCallbackLink}': + post: + requestBody: + description: Notification payload. + content: + application/json: + schema: + type: object + required: + - federationContextId + - appId + - appInstanceId + - zoneId + - appInstanceInfo + properties: + federationContextId: + $ref: '#/components/schemas/FederationIdentifier' + appId: + $ref: '#/components/schemas/AppIdentifier' + appInstanceId: + $ref: '#/components/schemas/InstanceIdentifier' + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + appInstanceInfo: + type: object + properties: + appInstanceState: + type: string + enum: + - PENDING + - READY + - FAILED + - TERMINATING + description: Running status of the application instance. + message: + type: string + description: Event information or failure message. + accesspointInfo: + description: Information about the IP and Port exposed by the OP. Application clients shall use these access points to reach this application instance + type: array + items: + type: object + required: + - interfaceId + - accessPoints + properties: + interfaceId: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,30}[A-Za-z0-9]$ + description: This is the interface Identifier that app provider defines when application is onboarded. + accessPoints: + $ref: '#/components/schemas/ServiceEndpoint' + minItems: 1 + minProperties: 1 + modificationDate: + type: string + format: date-time + description: Date and time of the instance state modification by partner OP. + responses: + "204": + description: Application instance state notification acknowledged + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + + /{federationContextId}/application/lcm/app/{appId}/instance/{appInstanceId}/zone/{zoneId}: + get: + summary: Retrieves an application instance details from partner OP. + operationId: GetAppInstanceDetails + tags: + - ApplicationDeploymentManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + - name: appInstanceId + in: path + required: true + schema: + $ref: '#/components/schemas/InstanceIdentifier' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + responses: + "200": + description: Application instance details + content: + application/json: + schema: + type: object + properties: + appInstanceState: + $ref: '#/components/schemas/InstanceState' + accesspointInfo: + description: Information about the IP and Port exposed by the OP. Application clients shall use these access points to reach this application instance + type: array + items: + type: object + required: + - interfaceId + - accessPoints + properties: + interfaceId: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,30}[A-Za-z0-9]$ + description: This is the interface identifier that app provider defines when application is onboarded. + accessPoints: + $ref: '#/components/schemas/ServiceEndpoint' + minItems: 1 + minProperties: 1 + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + delete: + summary: Terminate an application instance on a partner OP zone. + operationId: RemoveApp + tags: + - ApplicationDeploymentManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + - name: appInstanceId + in: path + required: true + schema: + $ref: '#/components/schemas/InstanceIdentifier' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + responses: + "200": + description: Application instance termination request accepted + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/application/lcm/app/{appId}/appProvider/{appProviderId}: + get: + summary: Retrieves all application instance of partner OP + operationId: GetAllAppInstances + tags: + - ApplicationDeploymentManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + - name: appProviderId + in: path + required: true + schema: + $ref: '#/components/schemas/AppProviderId' + responses: + "200": + description: Application Instance details + content: + application/json: + schema: + type: array + items: + type: object + required: + - zoneId + - appInstanceInfo + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + appInstanceInfo: + type: array + items: + type: object + required: + - appInstIdentifier + - appInstanceState + properties: + appInstIdentifier: + $ref: '#/components/schemas/InstanceIdentifier' + appInstanceState: + $ref: '#/components/schemas/InstanceState' + minItems: 1 + minItems: 1 + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/isv/resource/zone/{zoneId}/appProvider/{appProviderId}: + post: + summary: Reserves resources (compute, network and storage) on a partner OP zone. ISVs registered with home OP reserves resources on a partner OP zone. + operationId: CreateResourcePools + tags: + - AppProviderResourceManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + - name: appProviderId + in: path + required: true + schema: + $ref: '#/components/schemas/AppProviderId' + requestBody: + content: + application/json: + schema: + type: object + required: + - resRequest + - resourceReservationCallbackLink + properties: + resRequest: + description: Compute flavours to be reserved and their time duration + type: object + required: + - poolName + - flavours + - reserveDuration + properties: + poolName: + $ref: '#/components/schemas/PoolName' + flavours: + type: array + items: + type: object + required: + - flavourId + - numFlavour + properties: + flavourId: + $ref: '#/components/schemas/FlavourId' + numFlavour: + type: integer + format: int32 + description: Total number of flavours to be reserved + minNumOfFlavours: + type: integer + format: int32 + description: If specified, indicate the minimum numbers of flavours to be reserved up to maximum as given in “count” member. If partner OP cannot reserve the minimum number of flavours, then the request shall fail. + minItems: 1 + reserveDuration: + $ref: '#/components/schemas/ResourceReservationDuration' + resourceReservationCallbackLink: + $ref: '#/components/schemas/Uri' + responses: + "200": + description: ISV Resource reservation request accepted + content: + application/json: + schema: + type: object + required: + - poolId + - poolName + properties: + poolName: + $ref: '#/components/schemas/PoolName' + + poolId: + $ref: '#/components/schemas/PoolId' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onResourceStatusChangeEvent: + '{$request.body#/resourceReservationCallbackLink}': + post: + requestBody: + description: Notification payload. + content: + application/json: + schema: + type: object + required: + - federationContextId + - zoneId + - appProviderId + - poolId + - grantedFlavours + properties: + federationContextId: + $ref: '#/components/schemas/FederationIdentifier' + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + poolId: + $ref: '#/components/schemas/PoolId' + grantedFlavours: + type: array + items: + type: object + required: + - flavourId + - numFlavour + properties: + flavourId: + $ref: '#/components/schemas/FlavourId' + numFlavour: + type: integer + format: int32 + description: Count of flavour + minItems: 1 + responses: + "204": + description: Updated Resource reservation status updated + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + + get: + summary: Retrieves the resource pool reserved by an ISV + operationId: ViewISVResPool + tags: + - AppProviderResourceManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + - name: appProviderId + in: path + required: true + schema: + $ref: '#/components/schemas/AppProviderId' + responses: + "200": + description: Reserved Resources Details + content: + application/json: + schema: + type: array + items: + type: object + required: + - poolName + - reservedPoolId + - reservedFlavours + properties: + poolName: + $ref: '#/components/schemas/PoolName' + reservedPoolId: + $ref: '#/components/schemas/PoolId' + reservedFlavours: + type: array + items: + type: object + required: + - flavourId + - count + properties: + flavourId: + $ref: '#/components/schemas/FlavourId' + count: + type: integer + format: int32 + description: Total number of flavours reserved + minItems: 1 + reserveDuration: + $ref: '#/components/schemas/ResourceReservationDuration' + reservationTime: + type: string + format: date-time + description: Date and time when resources were reserved in UTC format + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/isv/resource/zone/{zoneId}/appProvider/{appProviderId}/pool/{poolId}: + patch: + summary: Updates resources reserved for a pool by an ISV + operationId: UpdateISVResPool + tags: + - AppProviderResourceManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + - name: appProviderId + in: path + required: true + schema: + $ref: '#/components/schemas/AppProviderId' + - name: poolId + in: path + required: true + schema: + $ref: '#/components/schemas/PoolId' + requestBody: + content: + application/json: + schema: + type: array + items: + type: object + required: + - updateType + - flavourId + - count + properties: + updateType: + type: string + enum: + - ADD + - REMOVE + - DURATION + description: Specify if resource corresponding this flavour needs to added or removed. Field 'count' gives the final total no of such flavours that should be reserved. count 0 means remove all the resources. + flavourId: + $ref: '#/components/schemas/FlavourId' + count: + type: integer + format: int32 + description: Total number of flavours to be reserved + reserveDuration: + $ref: '#/components/schemas/ResourceReservationDuration' + responses: + "200": + description: Resource pool updated + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + delete: + summary: Deletes the resource pool reserved by an ISV + operationId: RemoveISVResPool + tags: + - AppProviderResourceManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + - name: appProviderId + in: path + required: true + schema: + $ref: '#/components/schemas/AppProviderId' + - name: poolId + in: path + required: true + schema: + $ref: '#/components/schemas/PoolId' + responses: + "200": + description: Resource pool deleted + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/edgenodesharing/edgeDiscovery: + post: + summary: Edge discovery procedures towards partner OP over E/WBI. Originating OP request partner OP to provide a list of candidate zones where an application instance can be created. Partner OP applies a set of filtering criteria's to select candidate zones. + operationId: GetCandidateZones + tags: + - EdgeNodeSharing + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + content: + application/json: + schema: + type: object + required: + - appProviderId + - appId + properties: + appProviderId: + $ref: '#/components/schemas/AppProviderId' + appId: + $ref: '#/components/schemas/AppIdentifier' + edgeDiscoveryFilters: + type: object + minProperties: 1 + properties: + location: + $ref: '#/components/schemas/ClientLocation' + responses: + "200": + description: List of candidate zones + content: + application/json: + schema: + $ref: '#/components/schemas/nodeDiscoveryResponse' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/apiservice/{serviceAPINameVal}: + post: + summary: Service API request forwarding to the Partner OP + operationId: APIForwarding + tags: + - ServiceAPIManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + + - name: serviceAPINameVal + in: path + required: true + schema: + $ref: '#/components/schemas/serviceAPINameVal' + requestBody: + content: + application/json: + schema: + type: object + required: + - apiServiceId + - customerID + - customerInfo + - txnIdentifier + - ServiceAPIBody + properties: + customerID: + $ref: '#/components/schemas/customerID' + txnIdentifier: + $ref: '#/components/schemas/txnIdentifier' + ServiceAPIBody: + $ref: '#/components/schemas/serviceAPIContent' + eventNotificationDest: + $ref: '#/components/schemas/Uri' + responses: + '200': + description: Service API request accepted + headers: + Location: + description: Contains the URI of the newly created Service API Context resource. + required: false + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/serviceAPIResponse' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + default: + $ref: '#/components/responses/default' + callbacks: + onServiceAPISessionEvent: + '{$request.body#/eventNotificationDest}': + post: + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: apiServiceId + in: path + required: true + schema: + $ref: '#/components/schemas/serviceAPINames' + requestBody: + description: Notification about network event. + content: + application/json: + schema: + type: object + required: + - txnIdentifier + - serviceAPIEvent + properties: + serviceAPIEvent: + $ref: '#/components/schemas/serviceAPINetworkEvent' + txnIdentifier: + $ref: '#/components/schemas/txnIdentifier' + responses: + '200': + description: Event info notification acknowledged + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + + /{federationContextId}/apiservice/connid/{connectID}/custid/{customerID}: + delete: + summary: Remove the Service API Session earlier created with Service API forwarding request. + operationId: RemoveServiceAPISession + tags: + - ServiceAPIManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: connectID + in: path + required: true + schema: + $ref: '#/components/schemas/connectID' + - name: customerID + in: path + required: true + schema: + $ref: '#/components/schemas/customerID' + + responses: + '200': + description: Service API Session removed successfully + content: + application/json: + schema: + type: object + required: + - expiryDuration + - connectID + properties: + expiryDuration: + $ref: '#/components/schemas/expiryInterval' + connectID: + $ref: '#/components/schemas/connectID' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + get: + summary: Retrieve the Service API context information of an existing API session identified by connectID, customerID + operationId: GetServiceAPISessionInfo + tags: + - ServiceAPIManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: connectID + in: path + required: true + schema: + $ref: '#/components/schemas/connectID' + - name: customerID + in: path + required: true + schema: + $ref: '#/components/schemas/customerID' + + responses: + "200": + description: Device Auth Token validated + content: + application/json: + schema: + type: object + required: + - expiryDuration + - connectID + properties: + expiryDuration: + $ref: '#/components/schemas/expiryInterval' + connectID: + $ref: '#/components/schemas/connectID' + ServiceAPIRespBody: + $ref: '#/components/schemas/serviceAPIContent' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/monioring-subscriptions: + post: + summary: Originating OP subscribe for edge cloud resource monitoring info with partner OP. + operationId: SubscribeMonitoringInfo + tags: + - ConsumptionReportingManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: monType + in: query + required: true + schema: + $ref: '#/components/schemas/monitoringSubsType' + requestBody: + content: + application/json: + schema: + type: object + properties: + periodicity: + $ref: '#/components/schemas/periodicityInterval' + resMonNotificationListner: + $ref: '#/components/schemas/Uri' + + responses: + "200": + description: Subscription for resource monitoring created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/resourceSubscriptionInfo' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onPeriodicMonitoringEvent: + '{$request.body#/resMonNotificationListner}': + post: + requestBody: + description: Periodic Notification about resource monitoring info. + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/edgeResUtilizeMetrics' + - $ref: '#/components/schemas/appsResUtilizeInfo' + responses: + "200": + description: Resource monitoring info notification acknowledged + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + + /{federationContextId}/events: + post: + summary: Originating OP uses this procedure to request enabling event reporting with Partner OP. + operationId: CreateEventSubscription + tags: + - EventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + content: + application/json: + schema: + type: object + properties: + eventSubscriptionConfig: + $ref: '#/components/schemas/EventSubscription' + responses: + "200": + description: Subscription for reporting of events created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/EventSubscriptionInfo' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onEventCriterionDetectionEvent: + '{$request.body#/eventListner}': + post: + requestBody: + description: Notification about event being detected as per event criterion. + content: + application/json: + schema: + $ref: '#/components/schemas/EventsList' + responses: + "200": + description: Event report acknowledged + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + + /{federationContextId}/events/{event_subs_id}: + post: + summary: Originating OP uses this procedure to create an event criterion at Partner OP. + operationId: CreateEventCriterion + tags: + - EventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: event_subs_id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + type: object + properties: + eventCriterion: + $ref: '#/components/schemas/eventCriterion' + responses: + "200": + description: Subscription for resource monitoring created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/eventInfo' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + get: + summary: Retrieves events list with the partner OP. + operationId: GetEventsList + tags: + - EventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: event_subs_id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: event_type + in: query + required: false + schema: + type: string + enum: + - event_criterion + - event_id + responses: + "200": + description: Events criterion and detected events report request accepted + content: + application/json: + schema: + type: object + properties: + eventCriterionList: + $ref: '#/components/schemas/eventTypeList' + eventIdList: + $ref: '#/components/schemas/EventsList' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + delete: + summary: Remove existing event subscription with the partner OP + operationId: DeleteEventSubscription + tags: + - EventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: event_subs_id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + + responses: + "200": + description: Event subscription removed successfully + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/events/{event_subs_id}/event-id/{eventId}: + delete: + summary: Remove existing event criterion with the partner OP + operationId: DeleteEventCriterion + tags: + - EventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: event_subs_id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: eventId + in: path + required: true + schema: + $ref: '#/components/schemas/EventIdentifier' + responses: + "200": + description: Event criterion removed successfully + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/alarms: + post: + summary: Originating OP uses this procedure to request enabling alarm reporting with Partner OP. + operationId: CreateAlarmReportingSubscription + tags: + - AlarmManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + content: + application/json: + schema: + type: object + properties: + alarmListnerCallback: + $ref: '#/components/schemas/Uri' + + responses: + "200": + description: Subscription for alarm reporting created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/SubscriptionIdentifier' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onAlarmStateReportEvent: + '{$request.body#/alarmListnerCallback}': + post: + requestBody: + description: Notification about alarm management events at Partner OP. + content: + application/json: + schema: + $ref: '#/components/schemas/AlarmObjectInfo' + responses: + "200": + description: Event report acknowledged + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + + delete: + requestBody: + description: Alarm clear notification for an earlier alarm by Partner OP. + content: + application/json: + schema: + $ref: '#/components/schemas/AlarmObjectInfo' + responses: + "200": + description: Alarm clear event acknowledged + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + + patch: + requestBody: + description: Notification about alarm management events at Partner OP. + content: + application/json: + schema: + $ref: '#/components/schemas/UpdatedAlarmParameters' + responses: + "200": + description: Alarm state update report acknowledged + content: + application/json: + schema: + type: object + properties: + updatedAlarmId: + $ref: '#/components/schemas/AlarmIdentifier' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + + + /{federationContextId}/alarms/{alarm_subs_id}: + get: + summary: Retrieves active alarms list with the partner OP. + operationId: GetAlarmsList + tags: + - AlarmManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: alarm_subs_id + in: path + required: true + schema: + $ref: '#/components/schemas/SubscriptionIdentifier' + - name: alarm_type + in: query + required: false + schema: + $ref: '#/components/schemas/AlarmType' + responses: + "200": + description: Active alarms report request accepted + content: + application/json: + schema: + type: object + properties: + activeAlarmsList: + $ref: '#/components/schemas/ActiveAlarmsList' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + delete: + summary: Remove existing alarm subscription with the partner OP + operationId: DeleteAlarmSubscription + tags: + - AlarmManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: alarm_subs_id + in: path + required: true + schema: + $ref: '#/components/schemas/SubscriptionIdentifier' + + responses: + "200": + description: Alarm subscription removed successfully + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + + /{federationContextId}/network-caps-events: + post: + summary: Originating OP uses this procedure to request enabling network capabilities events reporting by the Partner OP. + operationId: CreateNetworkCapsEventSubscription + tags: + - NetworkCapsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + content: + application/json: + schema: + type: object + required: + - networkCapsEventSubscriptionConfig + properties: + networkCapsEventSubscriptionConfig: + $ref: '#/components/schemas/periodicNotifConfig' + responses: + "200": + description: Subscription for notification of network events created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/periodicNotifConfig' + headers: + Location: + description: 'Contains the URI of the newly created resource' + required: true + schema: + type: string + Accept-Encoding: + description: Accept-Encoding, described in IETF RFC 7694 + schema: + type: string + Content-Encoding: + description: Content-Encoding, described in IETF RFC 7231 + schema: + type: string + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onNetwEventDetectionEvent: + '{$request.body#/notificationListner}': + post: + requestBody: + description: Notification about events being detected as per network capabilities are applied by Partner OP. + content: + application/json: + schema: + $ref: '#/components/schemas/NetworkCapAppInfoList' + responses: + "200": + description: Network Events report acknowledged + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + + /{federationContextId}/network-events/{nw-event-subs-id}: + post: + summary: Originating OP uses this procedure to add an intent to Partner OP to report network capability applied by Partner OP. + operationId: CreateNetworkCapEvent + tags: + - NetworkCapsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: nw-event-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: nw-cap-id + in: query + required: true + schema: + $ref: '#/components/schemas/CapabilityID' + + requestBody: + content: + application/json: + schema: + type: object + required: + - appId + - appProviderId + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + txnIdentifier: + $ref: '#/components/schemas/txnIdentifier' + + responses: + "200": + description: Subscription for network event created successfully + content: + application/json: + schema: + type: object + required: + - networkCapSubsInfo + - txnIdentifier + properties: + networkCapSubsInfo: + $ref: '#/components/schemas/NetworkCapSubsInfo' + txnIdentifier: + $ref: '#/components/schemas/txnIdentifier' + + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + delete: + summary: Remove existing network events notification subscription with the partner OP + operationId: DeleteNwEventNotifSubscription + tags: + - NetworkCapsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: nw-event-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + responses: + "200": + description: Network Event Notification subscription removed successfully + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/network-events/{nw-event-subs-id}/nw-caps: + get: + summary: Retrieves network capabilities subscribed list with the partner OP. + operationId: GetNetworkCapsSubscribedList + tags: + - NetworkCapsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: nw-event-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: nw-event-type + in: query + required: true + schema: + type: string + responses: + "200": + description: Network capabilities subscription list returned successfully + content: + application/json: + schema: + type: object + properties: + subscribedNwCaps: + type: array + items: + $ref: '#/components/schemas/NetworkCapSubsInfo' + minItems: 1 + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + delete: + summary: Remove existing network event notification with the partner OP + operationId: DeleteNetworkCapSubscription + tags: + - NetworkCapsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: nw-event-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: nw-event-id + in: query + required: true + schema: + type: string + responses: + "200": + description: Network Event subscription removed successfully + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + + /{federationContextId}/appl-event-notifications: + post: + summary: Originating OP uses this procedure to Subscribe for Application's Events Notifications. + operationId: CreateApplicationEventSubscription + tags: + - ApplicationsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + content: + application/json: + schema: + type: object + required: + - applicationEventSubscriptionConfig + properties: + applEventSubscriptionConfig: + $ref: '#/components/schemas/periodicNotifConfig' + numEvtsPerNotif: + type: integer + description: The number of applications events that the Partner OP should include in a notification + responses: + "200": + description: Subscription for notification of network events created successfully + content: + application/json: + schema: + type: object + properties: + appEventSubsId: + type: string + applEventSubscriptionConfig: + $ref: '#/components/schemas/periodicNotifConfig' + numEvtsPerNotif: + type: integer + description: The number of applications events that the Partner OP should include in a notification + headers: + Location: + description: 'Contains the URI of the newly created resource' + required: false + schema: + type: string + Accept-Encoding: + description: Accept-Encoding, described in IETF RFC 7694 + schema: + type: string + Content-Encoding: + description: Content-Encoding, described in IETF RFC 7231 + schema: + type: string + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onApplEventDetectionEvent: + '{$request.body#/notificationListner}': + post: + requestBody: + description: Notification about applications LCM events being detected by Partner OP. + content: + application/json: + schema: + $ref: '#/components/schemas/AggrApplEventsList' + responses: + "200": + description: Applications events notification acknowledged + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + + + /{federationContextId}/appl-event-notifications/{app-notif-subs-id}: + post: + summary: Originating OP uses this procedure to add applications for reporting of application events by Partner OP. + operationId: SubscribeApplsEvtNotif + tags: + - ApplicationsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: app-notif-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: Idempotency-Key + in: header + required: true + schema: + $ref: '#/components/schemas/TransactionId' + + requestBody: + content: + application/json: + schema: + type: object + required: + - addAppsForNotif + properties: + addAppsForNotif: + $ref: '#/components/schemas/AddAppsForNotif' + + responses: + "200": + description: Subscription for network event created successfully + content: + application/json: + schema: + type: object + properties: + addAppsForNotif: + $ref: '#/components/schemas/AddAppsForNotif' + + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + delete: + summary: Remove existing application notification subscription with the partner OP + operationId: DeleteApplNotifSubscription + tags: + - ApplicationsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: app-notif-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + responses: + "200": + description: Application Event Notifications subscription removed successfully + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + patch: + summary: Modify existing application events notification subscription with the partner OP + operationId: ModifyApplEventNotifSubscription + tags: + - ApplicationsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: app-notif-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + type: object + properties: + applEventSubscriptionConfig: + $ref: '#/components/schemas/periodicNotifConfig' + numEvtsPerNotif: + type: integer + description: The number of applications events that the Partner OP should include in a notification + responses: + "200": + description: Event Notification subscription modified successfully + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + get: + summary: Originating OP uses this procedure to retrieve subscription meta-information about application-level notifications. + operationId: RetrieveApplSubsMetaInfo + tags: + - ApplicationsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: app-notif-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: info-type + in: query + required: true + schema: + type: string + enum: + - subs-info + - apps-info + + responses: + "200": + description: Application events Subscription information successful retrieval + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/ApplEventsSubsInfo' + - $ref: '#/components/schemas/ApplEventsSubsInfo' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/appl-event-notifications/{app-notif-subs-id}/cancel: + post: + summary: Remove applications from the reporting of application-level event notifications. + operationId: RemoveAppsEventSubscription + tags: + - ApplicationsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: app-notif-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RemoveAppsForNotif' + responses: + "200": + description: Network capabilities subscription list returned successfully + content: + application/json: + schema: + $ref: '#/components/schemas/RemoveAppsForNotif' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/appl-event-notifications/{app-notif-subs-id}/app-events: + post: + summary: Remove applications from the reporting of application-level event notifications. + operationId: RetrieveAppsEventsInfo + tags: + - ApplicationsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: app-notif-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/AppsForNotif' + minItems: 1 + responses: + "200": + description: Network capabilities subscription list returned successfully + content: + application/json: + schema: + $ref: '#/components/schemas/AggrApplEventsList' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + + /{federationContextId}/app-policies-subscription: + post: + summary: Originating OP uses this procedure to Subscribe for Application's policy capability at Partner OP. + operationId: CreateApplicationPolicySubscription + tags: + - ApplicationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + responses: + "200": + description: Subscription for application policy management created successfully + content: + application/json: + schema: + type: object + properties: + applPolicySubscriptionId: + type: string + headers: + Location: + description: 'Contains the URI of the newly created resource' + required: false + schema: + type: string + Accept-Encoding: + description: Accept-Encoding, described in IETF RFC 7694 + schema: + type: string + Content-Encoding: + description: Content-Encoding, described in IETF RFC 7231 + schema: + type: string + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/appl-policies-subscription/{appl-policy-subs-id}/app-policy-templates: + get: + summary: Originating OP uses this procedure to retrieve application policy templates from Partner OP. + operationId: RetrieveAppPolicyTemplates + tags: + - ApplicationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appl-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: appl-policy-type + in: query + required: false + schema: + $ref: '#/components/schemas/ApplPolicyType' + + responses: + "200": + description: Successfully retrieved application policy templates + content: + application/json: + schema: + type: object + properties: + applPolicyTemplateList: + $ref: '#/components/schemas/ApplPolicyTemplateList' + + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/appl-policies-subscription/{appl-policy-subs-id}/app-policy-registration: + post: + summary: Register an application-level policy with the partner OP + operationId: RegisterApplicationPolicy + tags: + - ApplicationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appl-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + type: object + properties: + applConcretePolicy: + $ref: '#/components/schemas/ApplConcretePolicy' + responses: + "200": + description: Application policy registered successfully + content: + application/json: + schema: + type: object + required: + - pplConcretePolicy + - policyId + properties: + pplConcretePolicy: + $ref: '#/components/schemas/ApplConcretePolicy' + policyId: + type: string + + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + + /{federationContextId}/app-policies-subscription/{appl-policy-subs-id}: + post: + summary: Origination OP uses this procedure to apply application-level policies to federated applications at Partner OP. + operationId: ApplyApplicationPolicy + tags: + - ApplicationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appl-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AssocApplPolicies' + responses: + "200": + description: Application Policy processed successfully + content: + application/json: + schema: + $ref: '#/components/schemas/AssocApplPolicies' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + get: + summary: Origination OP uses this procedure to retrieve application-level policies to federated applications at Partner OP. + operationId: RetrieveApplicationPolicy + tags: + - ApplicationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appl-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: policy-search-type + in: query + required: false + schema: + type: string + enum: + - app-prov-id + - app-id + - name: policy-search-value + in: query + required: false + schema: + type: string + description: Refers to either application provider identifier or the application identifier + + responses: + "200": + description: Application Policy list retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/AssocApplPolicies' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + patch: + summary: Modify application-level policy associated with federated applications with the partner OP + operationId: ModifyApplicationPolicy + tags: + - ApplicationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appl-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AssocApplPolicies' + responses: + "200": + description: Application policies modified successfully + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/app-policies-subscription/{appl-policy-subs-id}/app-policy-cancel: + post: + summary: Remove applications from federated applications at Partner OP. + operationId: RemoveApplicationPolicies + tags: + - ApplicationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appl-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AssocApplPolicies' + responses: + "200": + description: Successfully removed application policies + content: + application/json: + schema: + $ref: '#/components/schemas/AssocApplPolicies' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/ops-policies-subscription: + post: + summary: Originating OP uses this procedure to Subscribe for Operation's policy capability at Partner OP. + operationId: CreateOperationPolicySubscription + tags: + - OperationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + responses: + "200": + description: Subscription for operation's policy management created successfully + content: + application/json: + schema: + type: object + properties: + opslPolicySubscriptionId: + type: string + headers: + Location: + description: 'Contains the URI of the newly created resource' + required: false + schema: + type: string + Accept-Encoding: + description: Accept-Encoding, described in IETF RFC 7694 + schema: + type: string + Content-Encoding: + description: Content-Encoding, described in IETF RFC 7231 + schema: + type: string + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/ops-policies-subscription/{ops-policy-subs-id}/ops-policy-templates: + get: + summary: Originating OP uses this procedure to retrieve operations policy templates from Partner OP. + operationId: RetrieveOpsPolicyTemplates + tags: + - OperationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: ops-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: ops-policy-type + in: query + required: false + schema: + $ref: '#/components/schemas/OpsPolicyType' + + responses: + "200": + description: Successfully retrieved operations policy templates + content: + application/json: + schema: + type: object + properties: + opsPolicyTemplateList: + $ref: '#/components/schemas/OpsPolicyTemplateList' + + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/ops-policies-subscription/{ops-policy-subs-id}/ops-policy-registration: + post: + summary: Register an operation-level policy with the partner OP + operationId: RegisterOperationPolicy + tags: + - OperationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: ops-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + type: object + properties: + opsConcretePolicy: + $ref: '#/components/schemas/OpsConcretePolicy' + responses: + "200": + description: Operations policy registered successfully + content: + application/json: + schema: + type: object + required: + - opsConcretePolicy + - policyId + properties: + opsConcretePolicy: + $ref: '#/components/schemas/OpsConcretePolicy' + policyId: + type: string + + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + + /{federationContextId}/ops-policies-subscription/{ops-policy-subs-id}/policy-association: + post: + summary: Origination OP uses this procedure to apply application-level policies to federated applications at Partner OP. + operationId: ApplyOperationPolicy + tags: + - OperationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: ops-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AssocOpsPolicies' + responses: + "200": + description: Operation Policy processed successfully + content: + application/json: + schema: + $ref: '#/components/schemas/AssocOpsPolicies' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + get: + summary: Origination OP uses this procedure to retrieve application-level policies to federated applications at Partner OP. + operationId: RetrieveOperationPolicy + tags: + - OperationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: ops-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: policy-search-type + in: query + required: false + schema: + type: string + enum: + - zone-id + - name: policy-search-value + in: query + required: false + schema: + type: string + description: Refers to availability zone identifier + + responses: + "200": + description: Operations Policy list retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/AssocOpsPolicies' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + + patch: + summary: Modify operation-level policy associated with federated applications with the Partner OP + operationId: ModifyOperationPolicy + tags: + - OperationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: ops-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AssocOpsPolicies' + responses: + "200": + description: Application policies modified successfully + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/ops-policies-subscription/{ops-policy-subs-id}/ops-policy-cancel: + post: + summary: Remove applications from federated applications at Partner OP. + operationId: RemoveOperationPolicies + tags: + - OperationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: ops-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AssocOpsPolicies' + responses: + "200": + description: Successfully removed operation policies + content: + application/json: + schema: + $ref: '#/components/schemas/AssocOpsPolicies' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' diff --git a/docs/OPG.04-v6.0-EWBI-Federation-API-v1.4.0.yaml b/docs/OPG.04-v6.0-EWBI-Federation-API-v1.4.0.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ff1807bfaf9e5219114d9e07df7dcb0ad585f99e --- /dev/null +++ b/docs/OPG.04-v6.0-EWBI-Federation-API-v1.4.0.yaml @@ -0,0 +1,7946 @@ +openapi: 3.0.3 +info: + version: 1.4.0 + title: Federation Management Service + description: | + # Introduction + --- + RESTful APIs that allow an OP to share the edge cloud resources and capabilities securely to other partner OPs over E/WBI. + + --- + # API Scope + + --- + APIs defined in this version of the specification can be categorized into the following areas: + * __FederationAPIManagement__ - Retrieves federation resources and methods a partner OP support on E/WBI + * __FederationManagement__ - Create and manage directed federation relationship with a partner OP + * __AvailabilityZoneInfoSynchronization__ - Management of resources of partner OP zones and status updates + * __ArtefactManagement__ - Upload, remove, retrieve and update application descriptors, charts and packages over E/WBI towards a partner OP + + * __FileManagement__ - Upload, remove, retrieve and update application binaries over E/WBI towards a partner OP + * __ApplicationOnboardingManagement__ - Register, retrieve, update and remove applications over E/WBI towards a partner OP + * __ApplicationDeploymentManagement__ - Create, update, retrieve and terminate application instances over E/WBI towards a partner OP + * __AppProviderResourceManagement__ - Static resource reservation for an application provider over E/WBI for partner OP zones + * __EdgeNodeSharing__ - Edge discovery procedures towards partner OP over E/WBI. + * __ServiceAPIManagement__ - Service APIs capability sharing, forwarding, notification and API context management + * __SubscribeMonitoringInfo__ - The Originating OP subscribe for receiving the resource utilization reports periodically from the partner OP for existing federation + * __FaultManagement__ - The Partner OP performs the alarm reporting and clearances to the Originating OP on existing federation + * __EventsReporting__ - The Partner OP notifies the detection of events as created by the Originating OP on the existing federation + * __NetworkEventsReporting__ - The Partner OP notifies the network events applied for offered network capabilities on the existing federation + * __ApplicationEventsReporting__ - The Partner OP notifies the applications events of the federated applications + * __ApplicationPolicyManagement__ - The application-level policy requested by Originating OP for federated applications + * __OperationPolicyManagement__ - The operation-level policy requested by Originating OP for federated edge cloud resources + + --- + # Definitions + --- + This section provides definitions of terminologies commonly referred to throughout the API descriptions. + + * __Accepted Zones__ - List of partner OP zones, which the originating OP has confirmed to use for its edge applications + * __Anchoring__ - Partner OP capability to serve application clients (still in their home location) from application instances running on partner zones. + * __Application Provider__ - An application developer, onboarding his/her edge application on a partner operator platform (MEC). + * __Artefact__ - Descriptor, charts or any other package associated with the application. + * __Availability Zone__ - Zones that partner OP can offer to share with originating OP. + * __Device__ - Refers to user equipment like mobile phone, tablet, IOT kit, AR/VR device etc. In context of MEC users use these devices to access edge applications + * __Directed Federation__ - A Federation between two OP instances A and B, in which edge compute resources are shared by B to A, but not from A to B. + * __Edge Application__ - Application designed to run on MEC edge cloud + * __Edge Discovery Service__ - Partner OP service responsible to select most optimal edge( within partner OP) for edge application instantiation. Edge discovery service is defined as HTTP based API endpoint identified by a well-defined FQDN or IP. + * __E/WBI__ - East west bound interface. + * __Federation__ - Relationship among member OPs who agrees to offer services and capabilities to the application providers and end users of member OPs + * __FederationContextId__ - Partner OP defined string identifier representing a certain federation relationship. + * __Federation Identifier__ - Identify an operator platform in federation context. + * __FileId__ - An OP defined string identifier representing a certain application image uploaded by an application provider + * __Flavour__ - A group of compute, network and storage resources that can be requested or granted as a single unit + * __FlavourIdentifier__ - An OP defined string identifier representing a set of compute, storage and networking resources + * __Home OP__ - Used in federation context to identify the OP with which the application developers or user clients are registered. + * __Home Routing__ - Partner OP capability to direct roaming user client traffic towards application instances running on home OP zones. + * __Instance__ - Application process running on an edge + * __LCM Service__ - Partner OP service responsible for life cycle management of edge applications. LCM service is defined as HTTP based API endpoint identified by a well-defined FQDN or IP. + * __Offered Zones__ - Zones that partner OP offer to share to the Originating OP based on the prior agreement and local configuration. + * __Onboarding__ - Submitting an application to MEC platform + * __OP__ - Operator platform. + * __OperatorIdentifier__ - String identifier representing the owner of MEC platform. Owner could be an enterprise, a TSP or some other organization + * __Originating OP__ - The OP when initiating the federation creation request towards the partner OP is defined as the Originating OP + * __Partner OP__ - Operator Platform which offers its Edge Cloud capabilities to the other Operator Platforms via E/WBI. + * __Resource__ - Compute, networking and storage resources. + * __Resource Pool__ - A group of compute, networking and storage resources. Application provider pre-reserve resources on partner OP zone, these resources are reserved in terms of flavours. + * __ZoneIdentifier__ - An OP defined string identifier representing a certain geographical or logical area where edge resources and services are provided + * __Zone Confirmation__ - Procedure via which originating OP acknowledges partner OP about the partner zones it wishes to use. + * __User Clients__ - Lightweight client applications used to access edge applications. Application users run these clients on their devices (UE, IOT device, AR/VR device etc) + * __ServiceAPIManagement__ - Service APIs capability sharing, forwarding, notification and API context management + + --- + # API Operations + --- + + __FederationManagement__ + * __CreateFederation__ - Creates a directed federation relationship with a partner OP + * __GetFederationDetails__ - Retrieves details about the federation relationship with the partner OP. The response shall provide info about the zones offered by the partner, partner OP network codes, information about edge discovery and LCM service etc. + * __DeleteFederationDetails__ - Remove existing federation with the partner OP + * __NotifyFederationUpdates__ - Call back notification used by partner OP to update originating OP about any change in existing federation relationship + * __UpdateFederation__ - API used by the Originating OP towards the partner OP, to update the parameters associated to the existing federation + * __QueryFederationContext__ - The Originating OP retrieves federationContextId from the partner OP + * __HealthCheckFederation__ - The Originating OP sends health check message to the partner OP to check the health of the the existing federation + * __RenewFederation__ - The Originating OP requests the partner OP to renew the existing federation relationship + * __GetNetworkCapabilities__ - The Originating OP requests the partner OP to share the offered network capabilities information + + __AvailabilityZoneInfoSynchronization__ + * __ZoneSubscribe__ - Informs partner OP that originating OP is willing to access the specified zones and partner OP shall reserve compute and network resources for these zones. + * __ZoneUnsubscribe__ - Informs partner OP that originating OP will no longer access the specified partner OP zone. + * __GetZoneData__ - Retrieves details about the computation and network resources that partner OP has reserved for an partner OP zone. + * __Notify Zone Information__ - Call back notification used by partner OP to update originating OP about changes in the resources reserved on a partner zone. + + __ArtefactManagement__ + * __UploadArtefact__ - Uploads application artefact on partner operator platform. + * __RemoveArtefact__ - Removes an artefact from partner operator platform. + * __GetArtefact__ - Retrieves details about an artefact from partner operator platform. + * __UploadFile__ Upload application binaries to partner operator platform + * __RemoveFile__ - Removes application binaries from partner operator platform + * __ViewFile__ - Retrieves details about binaries associated with an application from partner operator platform + + __ApplicationOnboardingManagement__ + * __OnboardApplication__ - Submits an application details to a partner OP. Based on the details provided, partner OP shall do bookkeeping, resource validation and other pre-deployment operations + * __UpdateApplication__ - Updates partner OP about changes in application compute resource requirements, QOS Profile, associated descriptor or change in associated components + * __DeboardApplication__ - Removes an application from partner OP + * __ViewApplication__ - Retrieves application details from partner OP + * __OnboardExistingAppNewZones__ - Make an application available on new additional zones + * __LockUnlockApplicationZone__ - Forbid or permit instantiation of application on a zone + + __Application Instance Lifecycle Management__ + * __InstallApp__ - Instantiates an application on a partner OP zone. + * __GetAppInstanceDetails__ - Retrieves an application instance details from partner OP. + * __RemoveApp__ - Terminate an application instance on a partner OP zone. + * __GetAllAppInstances__ - Retrieves details about all instances of the application running on partner OP zones. + + + __AppProviderResourceManagement__ + * __CreateResourcePools__ - Reserves resources (compute, network and storage) on a partner OP zone. ISVs registered with home OP reserves resources on a partner OP zone. + * __UpdateISVResPool__ - Updates resources reserved for a pool by an ISV + * __ViewISVResPool__ - Retrieves the resource pool reserved by an ISV + * __RemoveISVResPool__ - Deletes the resource pool reserved by an ISV + + + __EdgeNodeSharing__ + *__GetCandidateZones__ - Edge discovery procedures towards partner OP over E/WBI. Originating OP request partner OP to provide a list of candidate zones where an application instance can be created. + + __ServiceAPIManagement__ + *__ServiceAPIRequestForwarding__ - Forward the NBI Service API requests to Partner OP over E/WBI. + *__RemoveServiceAPISession__ - Remove the existing Service API session with Partner OP over E/WBI. + *__ServiceAPIRequestForwarding__ - Retrieve Service API session context with Partner OP over E/WBI. + + __ConsumptionReportingManagement__ + *__SubscribeForResourceConsumption__ - Originating OP Subscription for edge resource consumption reporting by Partner OP over E/WBI. + + __EventManagement__ + *__SubscribeForEventNotifications__ - Originating OP Subscription for edge services related events reporting by Partner OP over E/WBI. + + __Alarm Management__ + *__SubscribeForAlarmManagement__ - Originating OP Subscription for reporting of alarms by Partner OP over E/WBI. + + __Network Capabilities Event Management__ + *__SubscribeForNetworkCapabilitiesNotifications__ - Originating OP Subscription for reporting of network events for application of network capabilities by Partner OP over E/WBI. + + + __Applications Event Notifications Management__ + *__SubscribeForApplicationEventsNotifications__ - Originating OP Subscription for reporting of application-level events by Partner OP over E/WBI. + + + © 2024 GSM Association. + All rights reserved. +externalDocs: + description: GSMA, E/WBI APIs v1.4.1 + url: http://www.xxxx.com +servers: + - url: '{apiRoot}/operatorplatform/federation/v1' + variables: + apiRoot: + default: https://operatorplatform.com +security: + - oAuth2ClientCredentials: + - fed-mgmt + - notifClientCredentials: + - fed-mgmt-notif +components: + securitySchemes: + oAuth2ClientCredentials: + type: oauth2 + flows: + clientCredentials: + tokenUrl: /oauth2/token + scopes: + fed-mgmt: Access to the federation APIs + notifClientCredentials: + type: oauth2 + flows: + clientCredentials: + tokenUrl: /oauth2/token + scopes: + fed-mgmt-notif: Access to the federation notification APIs + + schemas: + AppIdentifier: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,63}$ + description: Identifier used to refer to an application. + AppProviderId: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,63}$ + description: UserId of the app provider. Identifier is relevant only in context of this federation. + ArtefactId: + type: string + format: uuid + description: A globally unique identifier associated with the artefact. Originating OP generates this identifier when artefact is submitted over NBI. + + CountryCode: + type: string + description: ISO 3166-1 Alpha-2 code for the country of Partner operator + pattern: ^[A-Z]{2}$ + CPUArchType: + type: string + enum: + - ISA_X86 + - ISA_X86_64 + - ISA_ARM_64 + description: CPU Instruction Set Architecture (ISA) E.g., Intel, Arm etc. + + InstanceIdentifier: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,62}[A-Za-z0-9]$ + description: Unique identifier generated by the partner OP to identify an instance of the application on a specific zone. + InstanceState: + type: string + enum: + - PENDING + - READY + - FAILED + - TERMINATING + description: Running status of the application instance. + + TransactionId: + description: A unique transaction id for this request in UUID format. It is used for tracking the request + example: ab1d6gh5-79c2-3256-7hvb-d897549x40f7 + format: uuid + type: string + Ipv4Addr: + type: string + pattern: ^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$ + example: 198.51.100.1 + Ipv6Addr: + type: string + allOf: + - pattern: ^((:|(0?|([1-9a-f][0-9a-f]{0,3}))):)((0?|([1-9a-f][0-9a-f]{0,3})):){0,6}(:|(0?|([1-9a-f][0-9a-f]{0,3})))$ + - pattern: ^((([^:]+:){7}([^:]+))|((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?))$ + example: 2001:db8:85a3::8a2e:370:7334 + Fqdn: + type: string + FixedNetworkIds: + type: array + items: + type: string + description: List of network identifier associated with the fixed line network of the operator platform. + minItems: 1 + FederationContextId: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9-]*$ + readOnly: true + description: This identifier shall be provided by the partner OP on successful verification and validation of the federation create request and is used by partner op to identify this newly created federation context. Originating OP shall provide this identifier in any subsequent request towards the partner op. + FederationIdentifier: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9-]*$ + description: Globally unique identifier allocated to an operator platform. This is valid and used only in context of MEC federation interface. + FileId: + type: string + format: uuid + description: A globally unique identifier associated with the image file. Originating OP generates this identifier when file is uploaded over NBI. + + FileName: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,31}$ + description: Name of the image file. App provides specifies this name when image is uploaded on originating OP over NBI. + + FileDescription: + type: string + minLength: 8 + maxLength: 128 + description: Brief description about the image file. + + FileVersionInfo: + type: string + description: File version information. + + FlavourId: + type: string + description: An identifier to refer to a specific combination of compute resources + GeoLocation: + type: string + description: Latitude,Longitude as decimal fraction up to 4 digit precision + pattern: ^([-+]?)([\d]{1,2})((((\.)([\d]{1,4}))?(,)))(([-+]?)([\d]{1,3})((\.)([\d]{1,4}))?)$ + Mcc: + type: string + pattern: ^\d{3}$ + Mnc: + type: string + pattern: ^\d{2,3}$ + + OnboardStatusInfo: + type: string + enum: + - PENDING + - ONBOARDED + - DEBOARDING + - REMOVED + - FAILED + description: Defines change in application status. This change could be related to application itself or an application instance status + + + PoolName: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,30}[A-Za-z0-9]$ + description: ISV defined name of the resource pool. + PoolId: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,30}[A-Za-z0-9]$ + description: OP defined Identifier for the pool reserved for the ISV. It should be unique with an OP. + Port: + type: integer + minimum: 0 + Status: + type: string + enum: + - FAILED + - TEMPORARY_FAILURE + - AVAILABLE + - LOCKED + - NOT_AVAILABLE + Uri: + type: string + Vcpu: + type: string + pattern: ^\d+((\.\d{1,3})|(m))?$ + description: Number of vcpus in whole, decimal up to millivcpu, or millivcpu format. + example: + whole: + value: 2 + decimal: + value: 0.500 + millivcpu: + value: 500m + VirtImageType: + type: string + enum: + - QCOW2 + - DOCKER + - OVA + description: Indicate if the file is Container image or VM image (QCOW2, OVA) + + ZoneIdentifier: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9-]*$ + description: Human readable name of the zone. + + FederationHealthInfo: + type: object + required: + - federationStatus + - federationStartTime + - numOfAcceptedZones + properties: + federationStatus: + $ref: '#/components/schemas/State' + federationStartTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + numOfAcceptedZones: + type: string + numOfActiveAlarms: + type: string + numOfApplications: + type: string + + FederationSupportedAPIs: + type: object + required: + - federationBaseAPI + - availabilityZoneAPI + - edgeApplicationAPI + - artefactAPI + - fileAPI + properties: + federationBaseAPI: + $ref: '#/components/schemas/FederationAPIResources' + availabilityZoneAPI: + $ref: '#/components/schemas/FederationAPIResources' + edgeApplicationAPI: + $ref: '#/components/schemas/FederationAPIResources' + artefactAPI: + $ref: '#/components/schemas/FederationAPIResources' + fileAPI: + $ref: '#/components/schemas/FederationAPIResources' + serviceAPIFederation: + $ref: '#/components/schemas/FederationAPIResources' + resourceMonitoringAPI: + $ref: '#/components/schemas/FederationAPIResources' + faultManagementAPI: + $ref: '#/components/schemas/FederationAPIResources' + eventManagementAPI: + $ref: '#/components/schemas/FederationAPIResources' + + + FederationAPINames: + type: string + enum: + - FEDERATION + - AVAILZONE + - ARTEFACT + - FILE + - SVSAPEFED + - RESMONITOR + - EVENTMGMT + - FAULTMGMT + + HttpMethods: + type: string + enum: + - POST + - PUT + - PATCH + - DELETE + - GET + + HttpResources: + type: object + required: + - href + - httpMethods + properties: + href: + $ref: '#/components/schemas/Uri' + httpMethods: + type: array + items: + $ref: '#/components/schemas/HttpMethods' + minItems: 1 + description: List of HTTP Methods supported for the given API category + + FederationAPIResources: + type: object + required: + - name + - apiOperations + properties: + name: + $ref: '#/components/schemas/FederationAPINames' + apiOperations: + type: array + items: + $ref: '#/components/schemas/HttpResources' + minItems: 1 + description: List of HTTP Methods supported for the given API category + + monitoringSubsType: + type: string + enum: ["edge_resource","app_resource","alarm","all"] + description: Denotes types of edge resources, faults and events at partner OP to be reported to Originating OP. + + resourceSubscriptionInfo: + type: object + required: + - monitoringType + - subscriptionId + - dateAndTime + properties: + monitoringType: + $ref: '#/components/schemas/monitoringSubsType' + dateAndTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + subscriptionId: + type: string + format: uuid + description: Partner OP managed identifier for new subscription. + + utilizationValue: + type: object + required: + - resType + - value + - unit + properties: + resType: + $ref: '#/components/schemas/resourceType' + value: + type: string + description: Whole number that represent the value of given resource type. + unit: + type: string + enum: + - Percent + - MBPS + - GB + - TB + - CORES + - SECONDS + - MINUTES + description: Indicate the resource measurement Unit + + resourceType: + type: string + enum: + - CPU + - MEMORY + - DISK + - Network + - FLAVOUR + description: Indicate the type of resource + + edgeResUtilizeMetrics: + type: object + required: + - edgeMetrics + - federationContextId + - sequenceNum + properties: + edgeMetrics: + type: array + items: + $ref: '#/components/schemas/edgeComputeMetrics' + minItems: 1 + description: List of edge cloud resource metrics per zone + federationContextId: + $ref: '#/components/schemas/FederationContextId' + sequenceNum: + type: integer + description: Monotonically increasing counter for sequencing resource monitoring reports + + edgeComputeMetrics: + type: object + required: + - zoneId + - startTime + - endTime + - cpuUtil + - memUtil + - diskUtil + - networkUtil + - flavourUtil + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + startTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + endTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + cpuUtil: + $ref: '#/components/schemas/cpuUtilization' + memUtil: + $ref: '#/components/schemas/memUtilization' + diskUtil: + $ref: '#/components/schemas/diskUtilization' + networkUtil: + $ref: '#/components/schemas/networkUtilization' + flavourUtil: + $ref: '#/components/schemas/flavourUtilization' + + memUtilization: + type: object + required: + - noOfSamples + - averageUtilization + - maxUtilization + - minUtilization + properties: + noOfSamples: + type: string + description: Number of samples used for calculating metrics. + averageUtilization: + $ref: '#/components/schemas/utilizationValue' + maxUtilization: + $ref: '#/components/schemas/utilizationValue' + minUtilization: + $ref: '#/components/schemas/utilizationValue' + effectiveUtilization: + $ref: '#/components/schemas/utilizationValue' + + diskUtilization: + type: object + required: + - noOfSamples + - averageUtilization + - maxUtilization + - minUtilization + properties: + noOfSamples: + type: string + description: Number of samples used for calculating metrics. + averageUtilization: + $ref: '#/components/schemas/utilizationValue' + maxUtilization: + $ref: '#/components/schemas/utilizationValue' + minUtilization: + $ref: '#/components/schemas/utilizationValue' + effectiveUtilization: + $ref: '#/components/schemas/utilizationValue' + + networkUtilization: + type: object + required: + - noOfSamples + - ingressUsage + - egressUsage + - averageThroughput + - maxThroughput + - minThroughput + properties: + noOfSamples: + type: string + description: Number of samples used for calculating metrics. + ingressUsage: + $ref: '#/components/schemas/utilizationValue' + egressUsage: + $ref: '#/components/schemas/utilizationValue' + averageThroughput: + $ref: '#/components/schemas/utilizationValue' + maxThroughput: + $ref: '#/components/schemas/utilizationValue' + minThroughput: + $ref: '#/components/schemas/utilizationValue' + + flavourUtilization: + type: array + items: + $ref: '#/components/schemas/flavourMetrics' + minItems: 1 + description: List of compute flavours metrics per zone + + flavourMetrics: + type: object + required: + - noOfSamples + - flavourId + - averageUtilization + - maxUtilization + - minUtilization + properties: + noOfSamples: + type: string + description: Number of samples used for calculating metrics. + flavourId: + $ref: '#/components/schemas/FlavourId' + averageUtilization: + $ref: '#/components/schemas/utilizationValue' + averageThroughput: + $ref: '#/components/schemas/utilizationValue' + maxUtilization: + $ref: '#/components/schemas/utilizationValue' + minUtilization: + $ref: '#/components/schemas/utilizationValue' + + appsResUtilizeInfo: + type: object + required: + - appMetrics + - federationContextId + - sequenceNum + properties: + appMetrics: + type: array + items: + $ref: '#/components/schemas/appsResUtilizeMetrics' + minItems: 1 + description: List of edge cloud resource metrics per zone + federationContextId: + $ref: '#/components/schemas/FederationContextId' + sequenceNum: + type: integer + description: Monotonically increasing counter for sequencing app monitoring reports + + + appsResUtilizeMetrics: + type: object + required: + - zoneId + - startTime + - endTime + - appZoneMetrics + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + startTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + endTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + appZoneMetrics: + $ref: '#/components/schemas/appMetrics' + + appMetrics: + type: array + items: + $ref: '#/components/schemas/appAggrResUtil' + minItems: 1 + description: List of edge cloud resource metrics per zone + + appAggrResUtil: + type: object + required: + - appId + - appProvId + - noOfAppInstances + - appInstances + - cpuUtil + - memUtil + - diskUtil + - networkUtil + - flavourUtil + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProvId: + $ref: '#/components/schemas/AppProviderId' + noOfAppInstances: + type: integer + description: No of application instances of appId in a zone + appInstances: + type: array + items: + $ref: '#/components/schemas/InstanceIdentifier' + minItems: 1 + cpuUtil: + $ref: '#/components/schemas/cpuUtilization' + memUtil: + $ref: '#/components/schemas/memUtilization' + diskUtil: + $ref: '#/components/schemas/diskUtilization' + networkUtil: + $ref: '#/components/schemas/networkUtilization' + flavourUtil: + $ref: '#/components/schemas/flavourUtilization' + + + cpuUtilization: + type: object + required: + - cpuType + - noOfSamples + - averageUtilization + - maxUtilization + - minUtilization + - effectiveUtilization + properties: + cpuType: + $ref: '#/components/schemas/monitoringSubsType' + noOfSamples: + type: string + description: Number of samples used for calculating metrics. + averageUtilization: + $ref: '#/components/schemas/utilizationValue' + maxUtilization: + $ref: '#/components/schemas/utilizationValue' + minUtilization: + $ref: '#/components/schemas/utilizationValue' + effectiveUtilization: + $ref: '#/components/schemas/utilizationValue' + + thresholdVal: + type: object + required: + - value + - unit + properties: + value: + type: string + unit: + type: string + enum: + - percent + - CORES + - TB + - GB + - MBPS + - GBPS + description: The unit of resources measurement e.g. number of cores, mega bits per seconds etc. + + EventSubscription: + type: object + required: + - resUsageType + - periodicity + - eventListner + properties: + resUsageType: + $ref: '#/components/schemas/resourceType' + periodicity: + $ref: '#/components/schemas/periodicityInterval' + eventListner: + $ref: '#/components/schemas/Uri' + + EventSubscriptionInfo: + type: object + required: + - resUsageType + - periodicity + - subscriptionId + properties: + resUsageType: + $ref: '#/components/schemas/resourceType' + periodicity: + $ref: '#/components/schemas/periodicityInterval' + subscriptionId: + type: string + format: uuid + + eventCriterion: + type: object + required: + - resUsageType + - triggerCondition + - thresholdVal + - numOccurance + - monitorDuration + properties: + resUsageType: + $ref: '#/components/schemas/resourceType' + triggerCondition: + type: string + enum: + - GT + - GTE + - EQ + - LT + - LEQ + description: The condition evaluation operator to compare threashold value of a resource for event detection. + thresholdVal: + $ref: '#/components/schemas/thresholdVal' + numOccurance: + type: integer + description: Number of times the trigger condition is detected + monitorDuration: + $ref: '#/components/schemas/periodicityInterval' + + eventInfo: + type: object + required: + - eventId + - eventCriterion + properties: + eventId: + type: string + eventCriterion: + $ref: '#/components/schemas/eventCriterion' + + eventTypeList: + type: array + items: + $ref: '#/components/schemas/eventCriterion' + minItems: 1 + description: List of event criterion + + detectedEvent: + type: object + required: + - zoneId + - eventId + - startTime + - endTime + - numOccurance + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + eventId: + type: string + startTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + endTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + numOccurance: + type: integer + + CapabilityID: + type: string + enum: + - NW_CAP_CONN_STATE_CHANGE + - NW_CAP_LOCATION_RETRIEVAL + - NW_CAP_USERPLANE_MGMT_EVENTS + - NW_CAP_DYNAMIC_QOS + description: The enumerated list of network capabilities that an OP can use for various services via SBI-NR. + + DeviceConnStatusChangeCap: + type: object + required: + - capabilityId + - maxiDetectionTime + properties: + capabilityId: + $ref: '#/components/schemas/CapabilityID' + maxiDetectionTime: + type: string + description: The maximum detection time in seconds that the OP can determine the UE change of connectivity with the mobile network. + + LocationRetrievalCap: + type: object + required: + - capabilityId + - locationType + properties: + capabilityId: + $ref: '#/components/schemas/CapabilityID' + locationType: + type: string + enum: + - CELL_LEVEL_ACCURACY + - REGISTRATION_AREA_ACCURACY + - TRACKING_AREA_ACCURACY + - GEO_LOCATION_ACCURACY + description: The enumerated list of UE location accuracy that an OP can determine via SBI-NR. + locationAccuracy: + type: string + enum: + - LAST_KNOWN_LOCATION + - CURRENT_LOCATION + - INITIAL_LOCATION + description: The enumerated list of type of network location of an UE that an OP can determine via SBI-NR. + + UserPlaneMgmtEvtCap: + type: object + required: + - capabilityId + - maxUserPlaneLatency + properties: + capabilityId: + $ref: '#/components/schemas/CapabilityID' + maxUserPlaneLatency: + type: string + description: Indicates the maximum user plane latency in units of milliseconds to decide whether edge relocation is needed to ascertain latency remain in this range. + + DynamicQoSCap: + type: object + required: + - capabilityId + - supportedQoS + properties: + capabilityId: + $ref: '#/components/schemas/CapabilityID' + supportedQoS: + type: string + description: Set of one or more 5G QoS Identifier (5QI or 4G QCI) created via concatanation of Resource Type and 5QI values i.e., GBR1, GBR2, GBR65, NONGBR79 etc. + + + NetworkCapAppInfoList: + type: array + items: + required: + - appProviderId + - appId + - AppInstNetworkCapInvoked + - zoneId + properties: + appProviderId: + $ref: '#/components/schemas/AppProviderId' + appId: + $ref: '#/components/schemas/AppIdentifier' + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + appInstNetworkCapInvoked: + $ref: '#/components/schemas/AppInstNetworkCapList' + minItems: 1 + + AppInstNetworkCapList: + type: object + required: + - appInstanceNwCapInfo + properties: + appInstanceNwCapInfo: + type: array + items: + type: object + required: + - appInstIdentifier + - appInstanceState + - networkCapInvoked + properties: + appInstIdentifier: + $ref: '#/components/schemas/InstanceIdentifier' + appInstanceState: + $ref: '#/components/schemas/InstanceState' + networkCapInvoked: + $ref: '#/components/schemas/NetworkCapInvoked' + minItems: 1 + + NetworkCapInvoked: + type: object + required: + - networkEventId + - capabilityId + - zoneId + - detectionTime + - nwCapabilitySLI + properties: + networkEventId: + type: string + format: uuid + description: Unique identifier allocated for a network event + capabilityId: + $ref: '#/components/schemas/CapabilityID' + invocationTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + nwCapabilitySLI: + type: string + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + + NetworkCapSubsInfo: + type: object + required: + - appId + - appProviderId + - capabilityId + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + capabilityId: + $ref: '#/components/schemas/CapabilityID' + + + NetworkEventsList: + type: array + items: + $ref: '#/components/schemas/NetworkCapInvoked' + minItems: 1 + description: List of network capabilities events detected + + + EventsList: + type: array + items: + $ref: '#/components/schemas/detectedEvent' + minItems: 1 + description: List of events detected + + EventSubscriptionIdentifier: + type: string + format: uuid + description: Event subscription identifier allocated for enabling event reporting + + EventIdentifier: + type: string + format: uuid + description: Event identifier allocated for event detected + + SubscriptionIdentifier: + type: object + required: + - subsId + properties: + subsId: + type: string + format: uuid + description: Generic subscription identifier + + AlarmObjectInfo: + type: object + required: + - alarmType + - alarmId + - perceivedSeverity + - probableCause + - alarmedObject + - sourceSystemId + - state + - alarmRaisedTime + properties: + alarmType: + $ref: '#/components/schemas/AlarmType' + alarmId: + $ref: '#/components/schemas/AlarmIdentifier' + perceivedSeverity: + $ref: '#/components/schemas/PerceivedSeverity' + probableCause: + $ref: '#/components/schemas/ProbableCause' + alarmedObject: + $ref: '#/components/schemas/AlarmedObject' + sourceSystemId: + $ref: '#/components/schemas/SourceSystemId' + state: + $ref: '#/components/schemas/State' + alarmRaisedTime: + $ref: '#/components/schemas/AlarmRaisedTime' + affectedService: + $ref: '#/components/schemas/AffectedService' + alarmDetails: + $ref: '#/components/schemas/AlarmDetails' + specificProblem: + $ref: '#/components/schemas/SpecificProblem' + serviceAffecting: + $ref: '#/components/schemas/ServiceAffecting' + + ActiveAlarmsList: + type: array + items: + $ref: '#/components/schemas/AlarmObjectInfo' + minItems: 1 + description: List of active alarms + + AlarmType: + type: object + required: + - alarmType + properties: + alarmType: + type: string + enum: + - EDGERES + - APPLICATION + - ARTEFACT + - EDGEDISC + - FEDERATION + - SECURITY + - APIFEDERATION + - FILE + description: Alarm type category + + AlarmIdentifier: + type: object + required: + - alarmId + properties: + alarmId: + type: string + description: Alarm identifier to refer to an alarm instance + + PerceivedSeverity: + type: object + required: + - severity + properties: + severity: + type: string + enum: + - MAJOR + - MINOR + - CRITICAL + - WARNING + - INFOMATIONAL + description: Alarm severity + + ProbableCause: + type: object + required: + - cause + properties: + cause: + type: string + description: Probale cause of the alarm + + AlarmedObject: + type: object + required: + - alarmId + - href + properties: + alarmId: + $ref: '#/components/schemas/AlarmIdentifier' + href: + $ref: '#/components/schemas/Uri' + + SourceSystemId: + type: object + required: + - sourceSystemId + properties: + sourceSystemId: + type: string + description: Source system identity + + State: + type: object + required: + - alarmState + properties: + alarmState: + type: string + enum: + - RAISED + - UPDATED + - CLEAR + description: Defines the alarm state during its life cycle (raised | updated | cleared). + + AlarmRaisedTime: + type: object + required: + - alarmRaisedTime + properties: + alarmRaisedTime: + type: string + format: date-time + description: Defines the alarm raised time at source + + AffectedService: + type: object + required: + - affectedService + properties: + affectedService: + type: array + items: + type: string + minItems: 1 + description: Defines the affected services e.g., edge discovery, application services, API services etc at source + + AlarmDetails: + type: object + required: + - alarmDetails + properties: + alarmDetails: + type: string + description: Detailed information of the alarm + + SpecificProblem: + type: object + required: + - specificProblem + properties: + specificProblem: + type: string + description: Specific information related to the alarm + + ServiceAffecting: + type: string + enum: + - YES + - NO + description: Specific information related to the alarm + + PatchableParams: + type: string + enum: ["/perceivedSeverity","/probableCause","/alarmedObject","/sourceSystemId","/state","/affectedService","/alarmDetails","/specificProblem","/serviceAffecting"] + + AlarmUpdateOps: + type: string + enum: + - REPLACE + description: Operations that can be performed to update the parameters of an alarm + + UpdatedParam: + type: object + required: + - alarmUpdateOps + - patchableParam + - patchValue + properties: + alarmUpdateOps: + $ref: '#/components/schemas/AlarmUpdateOps' + patchableParam: + $ref: '#/components/schemas/PatchableParams' + patchValue: + type: string + description: Value to be replaced for the alarm parameter being updated + + UpdatedAlarmParameters: + type: object + required: + - alarmId + - updateParams + properties: + alarmId: + $ref: '#/components/schemas/AlarmIdentifier' + updateParams: + type: array + items: + $ref: '#/components/schemas/UpdatedParam' + minItems: 1 + description: List of alarm parameters to be updated in an update operation + + serviceType: + type: string + enum: ["api_federation"] + description: An identifier to refer to partner OP capabilities for application providers. + + serviceAPINames: + type: array + items: + type: string + enum: + - QualityOnDemand + - DeviceLocation + - DeviceStatus + - SimSwap + - NumberVerification + - DeviceIdentifier + minItems: 1 + description: List of Service API capability names an OP supports and offers to other OPs "quality_on_demand", "device_location" etc. + + serviceAPINameVal: + type: string + enum: + - QualityOnDemand + - DeviceLocation + - DeviceStatus + - SimSwap + - NumberVerification + - DeviceIdentifier + description: Name of the Service API + + serviceRoutingInfo: + type: array + items: + type: string + pattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))?$ + minItems: 1 + description: List of public IP addresses MNO manages for UEs to connect with public data networks + + + customerID: + type: string + format: uuid + description: Leading OP managed identifier associated to API Provider of the Leading OP. + + txnIdentifier: + type: string + description: A API transaction identifier generated by the Partner OP for each API request + + connectID: + type: string + description: An identifier generated by the Partner OP to represent the end user identity in the Service API request. + + apiContentType: + type: string + enum: + - application/json + description: Indicate the Service API body schema in JSON format + + serviceAPIContent: + type: object + required: + - mediaType + - APIContent + properties: + mediaType: + $ref: '#/components/schemas/apiContentType' + APIContent: + $ref: 'https://github.com/camaraproject' + + PlatformCaps: + type: array + items: + type: string + enum: + - homeRouting + - Anchoring + - serviceAPIs + - faultMgmt + - eventMgmt + - resourceMonitor + - networkEventMgmt + - appNotificationMgmt + - appLevelPolicyMgmt + - opsLevelPolicyMgmt + description: Home routing - Operator platform is capable of routing edge application data traffic from its edges to user device in their home location. This is the case where user devices are served in their home region (requesting platform region, non-roaming) but the corresponding edge application are in operator platform edges. Anchoring - Operator platform is capable of routing edge application traffic for roaming user devices to edge application in user device home network. Service APIs - Capability to handle Service APIs (e.g., CAMARA APIs) from the Leading OP + + expiryInterval: + type: object + required: + - numHours + - numMins + - numSecs + properties: + numHours: + type: integer + format: int32 + description: Number of Hours for Expiry (0-23) + numMins: + type: integer + format: int32 + description: Number of Minutes for Expiry (0-59) + numSecs: + type: integer + format: int32 + description: Number of Seconds for Expiry (0-59) + + periodicityInterval: + type: object + required: + - numHours + - numMins + properties: + numHours: + type: integer + format: int32 + description: Number of Hours for Expiry (0-23) + numMins: + type: integer + format: int32 + description: Number of Minutes for Expiry (0-59) + + periodicNotifConfig: + type: object + properties: + periodicity: + $ref: '#/components/schemas/periodicityInterval' + notificationListner: + $ref: '#/components/schemas/Uri' + + targetUserContext: + type: object + required: + - connectID + - expiryDuration + properties: + connectID: + $ref: '#/components/schemas/connectID' + expiryDuration: + $ref: '#/components/schemas/expiryInterval' + + serviceAPIResponse: + type: object + required: + - customerID + - targetUserContext + - apiResponse + - txnIdentifier + properties: + customerID: + $ref: '#/components/schemas/customerID' + targetUserContext: + $ref: '#/components/schemas/targetUserContext' + apiResponse: + $ref: '#/components/schemas/customerID' + txnIdentifier: + $ref: '#/components/schemas/txnIdentifier' + svcEventType: + type: string + enum: + - "evt_timerexpiry" + - "evt_network" + - "evt_delete" + + + serviceAPIEventDef: + type: object + required: + - NetworkEventDef + properties: + NetworkEventDef: + $ref: 'https://github.com/camaraproject' + + serviceAPINetworkEvent: + type: object + required: + - connectID + - customerID + - EventType + properties: + connectID: + $ref: '#/components/schemas/connectID' + customerID: + $ref: '#/components/schemas/customerID' + EventType: + $ref: '#/components/schemas/svcEventType' + serviceAPIEventDef: + $ref: '#/components/schemas/serviceAPIEventDef' + expiryDuration: + $ref: '#/components/schemas/expiryInterval' + + # + # STRUCTURED DATA TYPES + # + ServiceNameNB: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,62}[A-Za-z0-9]$ + description: Must be a valid RFC 1035 label name. This defines the DNS name via which the component can be accessed over NBI. Access via serviceNameNB is restricted on specific ports. Platform shall expose component access externally via this DNS name + ServiceNameEW: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,62}[A-Za-z0-9]$ + description: Must be a valid RFC 1035 label name. This defines the DNS name via which the component can be accessed via peer components. Access via serviceNameEW is open on all ports. Platform shall not expose serviceNameEW externally outside edge. + ComponentName: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,62}[A-Za-z0-9]$ + description: Must be a valid RFC 1035 label name. Component name must be unique with an application + + + ApplEventsSubsInfo: + type: object + required: + - appEventSubsId + - appEvtSubsStartTime + - appEvtSubsLastReportTime + - appEvtSubsNumApps + - appEvtSubsPeriodicity + properties: + appEventSubsId: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + appEvtSubsStartTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + appEvtSubsLastReportTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + appEvtSubsNumApps: + type: integer + appEvtSubsPeriodicity: + $ref: '#/components/schemas/periodicityInterval' + + AppsForNotif: + type: object + required: + - appId + - appProviderId + - appZones + - appEvents + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + appZones: + $ref: '#/components/schemas/AppZones' + appEvents: + $ref: '#/components/schemas/AppEvents' + + AddAppsForNotif: + type: array + items: + $ref: '#/components/schemas/AppsForNotif' + minItems: 1 + + RemoveAppsForNotif: + type: array + items: + $ref: '#/components/schemas/AppsForNotif' + minItems: 1 + + + AppEventTypes: + type: string + enum: + - evt_type_app_relocation + - evt_type_app_session_cont + - evt_type_app_restarts + - evt_type_app_upscale + - evt_type_app_downscale + description: Application-level events + + AppEvents: + type: array + items: + $ref: '#/components/schemas/AppEventTypes' + minItems: 1 + description: List of availability zones where application events are to be monitored + + AppZones: + type: array + items: + $ref: '#/components/schemas/ZoneIdentifier' + minItems: 1 + description: List of availability zones where application events are to be monitored + + ApplInstEventTypeInfo: + type: object + required: + - applInstEvent + - applInstEventCount + properties: + applInstEvent: + $ref: '#/components/schemas/AppEventTypes' + applInstEventCount: + type: integer + description: Number of occurances of given epplication event + + ApplInstEventsContainer: + type: object + required: + - appInstanceId + - appInstEventsList + properties: + appInstanceId: + $ref: '#/components/schemas/InstanceIdentifier' + appInstEventsList: + type: array + items: + $ref: '#/components/schemas/ApplInstEventTypeInfo' + minItems: 1 + description: Application instance events list + + ApplInstEventsList: + type: object + required: + - appInstanceEventsList + properties: + appInstanceEventsList: + type: array + items: + $ref: '#/components/schemas/ApplInstEventsContainer' + minItems: 1 + description: Application instance events list for one or more applications + + ZoneLevelApplEventsList: + type: object + required: + - zoneId + - appsEventsList + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + appsEventsList: + type: array + items: + $ref: '#/components/schemas/ApplInstEventsList' + minItems: 1 + description: Applications instance events list in a availability zone + + ApplEventsList: + type: object + required: + - appId + - appProviderId + - aggrApplEvents + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + aggrApplEvents: + type: array + items: + $ref: '#/components/schemas/ZoneLevelApplEventsList' + minItems: 1 + description: Applications instance events list in a availability zone + + AggrApplEventsList: + type: object + required: + - startTime + - endTime + - aggrAppsEventsList + properties: + startTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + endTime: + $ref: '#/components/schemas/dateAndTimeZoneObject' + aggrAppsEventsList: + type: array + items: + $ref: '#/components/schemas/ApplEventsList' + minItems: 1 + description: Applications events list in a various availability zones for different application providers + + ApplPolicyIdentifier: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,63}$ + description: Application-level Policy unique identifier + + ApplPolicyMetaInfo: + type: object + required: + - applPolicyTypeIdentifier + - policyVersion + properties: + applPolicyTypeIdentifier: + $ref: '#/components/schemas/ApplPolicyTypeIdentifier' + policyVersion: + type: string + description: Policy template version using Semantic Versioning 2.0.0 in MAJOR.MINOR.PATCH format + + + ApplPolicyTypeIdentifier: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,63}$ + description: Application-level Policy unique identifier + + + AppPolicyTemplate: + type: object + required: + - applPolicyName + - applPolicyMetaInfo + - applPolicyType + - applPolicyScope + - applPolicyDescription + - applPolicyRules + properties: + applPolicyName: + type: string + maxLength: 64 + description: Brief policy template name on policy objective + applPolicyMetaInfo: + $ref: '#/components/schemas/ApplPolicyMetaInfo' + applPolicyType: + $ref: '#/components/schemas/ApplPolicyType' + applPolicyScope: + $ref: '#/components/schemas/ApplPolicyScope' + applPolicyDescription: + type: string + maxLength: 256 + description: Brief policy template description on policy objective + applPolicyRules: + type: array + items: + $ref: '#/components/schemas/ApplPolicyRule' + minItems: 1 + description: Set of policy action rules for a given policy + + ApplPolicyTemplateList: + type: array + items: + $ref: '#/components/schemas/AppPolicyTemplate' + minItems: 1 + description: List of Application policy templates from the Partner OP + + ApplPolicyType: + type: string + enum: + - static + - dynamic + description: Policy attribute that the given policy intent to control specific resources e.g. compute capacity expansion statically vs dynamic scaling of app instance + + ApplPolicyScope: + type: string + enum: + - zonal + - global + description: Application-level Policy scope defines if a policy is a set of availability zones or applies globally to all zones + + + ApplPolicyRule: + type: array + items: + $ref: '#/components/schemas/GenericPolicyRule' + minItems: 1 + description: List of Application policies + + + GenericPolicyRule: + type: object + required: + - ruleLHSParamType + - ruleOperator + - ruleRHSParamVal + - ruleAction + - ruleDescription + properties: + ruleLHSParamType: + $ref: '#/components/schemas/RuleLHSParamType' + ruleOperator: + $ref: '#/components/schemas/RuleOperatorType' + ruleRHSParamVal: + $ref: '#/components/schemas/RuleRHSParamVal' + ruleAction: + $ref: '#/components/schemas/RuleActionType' + ruleDescription: + type: string + maxLength: 256 + description: Brief description of the actions to be performed + + RuleLHSParamType: + type: string + enum: + - AppsPolicy.App.Metadata.QoS.Latency + - AppsPolicy.App.Metadata.Compute.CPU + - AppsPolicy.App.Metadata.Compute.GPU + - AppsPolicy.App.Metadata.Location.AZ + - AppsPolicy.App.Metadata.Location.Region + - OpsPolicy.EdgeCloud.Metadata.QoS.Latency + - OpsPolicy.EdgeCloud.Metadata.Compute.CPU + - OpsPolicy.EdgeCloud.Metadata.Compute.GPU + - OpsPolicy.EdgeCloud.Metadata.Network.SRIOV + description: Resource attributes that policy will act on to determine the target pplication after applying the policy rules + + RuleRHSParamVal: + type: object + properties: + latencyRanges: + $ref: '#/components/schemas/LatencyRanges' + computeResourceProfile: + $ref: '#/components/schemas/ComputeResourceProfile' + appLocation: + type: array + items: + $ref: '#/components/schemas/AppLocation' + minItems: 1 + networkCaps: + $ref: '#/components/schemas/NetworkCaps' + description: Permitted type specific value objects for types in ruleLHSParamType + + AppLocation: + type: string + enum: + - zones + - regions + description: Application Location in terms of availability zones or regions + + LatencyRanges: + type: object + required: + - minLatency + - maxLatency + - unit + properties: + minLatency: + type: string + description: Minimum latency in milliseconds + maxLatency: + type: string + description: Maximum latency in milliseconds + unit: + type: string + enum: + - MS + description: Maximum latency in milliseconds + description: Latency ranges that can be experienced in the Partner OP environment + + ComputeResourceProfile: + type: object + required: + - resourceSpec + properties: + resourceSpec: + $ref: '#/components/schemas/ResourceSpec' + description: Type and amount of compute resources + + ResourceSpec: + type: object + required: + - resourceType + - resourceModel + - resourceCount + properties: + resourceType: + type: string + enum: + - CPU + - GPU + - FPGA + resourceModel: + type: string + enum: + - Intel-x86_64 + - Arm64 + - Nvidia + resourceCount: + type: string + + description: Resource type and architecture specification + + NetworkCaps: + type: object + properties: + nwAccelType: + type: string + enum: + - SRIOV + - DPDK + nwAccelSpeed: + type: string + enum: + - 1Gbps + - 10Gbps + - 100Gbps + description: Type and speed of network acceleration resources + + + RuleOperatorType: + type: object + properties: + StringRuleOperatorType: + $ref: '#/components/schemas/StringRuleOperatorType' + BinaryRuleOperatorType: + $ref: '#/components/schemas/BinaryRuleOperatorType' + description: Defines the logical operations that policy rule will execute on application attribute value + + BinaryRuleOperatorType: + type: string + enum: + - EQ + - LT + - GT + description: Operations that can be applied on Parameter e.g., “Binary Operation” EQ(EQual) + + StringRuleOperatorType: + type: string + enum: + - EQ + - NOTEQ + description: Operations that can be applied on Parameter e.g., String Operation” EQ(EQual), NOTEQ(Not Equal) + + RuleActionType: + type: object + required: + - actionType + - actionTargetType + properties: + actionType: + $ref: '#/components/schemas/ActionType' + actionTargetType: + $ref: '#/components/schemas/RuleLHSParamType' + + ActionType: + type: string + enum: + - restrict + - prefer + - priortize + - allow + - deny + description: Action to be taken once a policy rule is applied on target resource indicated by RuleLHSParamType + + ApplConcretePolicy: + type: object + required: + - policyId + - policyParamLimits + properties: + policyId: + $ref: '#/components/schemas/ApplPolicyIdentifier' + policyParamLimits: + $ref: '#/components/schemas/ApplPolicyRule' + description: Application policy id and policy parameter value limits registered by the Originating OP + + AssocApplPolicies: + type: object + required: + - policyId + - appIdList + properties: + policyId: + $ref: '#/components/schemas/ApplPolicyIdentifier' + appIdList: + $ref: '#/components/schemas/AppIdLocList' + + AppIdLocList: + type: object + required: + - appId + - appProvId + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProvId: + $ref: '#/components/schemas/AppProviderId' + zoneIds: + type: array + items: + $ref: '#/components/schemas/ZoneIdentifier' + minItems: 1 + + RegisteredAppPolicyList : + type: array + items: + $ref: '#/components/schemas/ApplConcretePolicy' + minItems: 1 + description: Applications policies registered by the Originating OP + + + OpsPolicyIdentifier: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,63}$ + description: Operation-level Policy unique identifier + + + OpsPolicyTypeIdentifier: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,63}$ + description: Operation-level Policy template unique identifier + + OpslPolicyMetaInfo: + type: object + required: + - opslPolicyTypeIdentifier + - policyVersion + properties: + opslPolicyTypeIdentifier: + $ref: '#/components/schemas/OpsPolicyTypeIdentifier' + policyVersion: + type: string + description: Policy template version using Semantic Versioning 2.0.0 in MAJOR.MINOR.PATCH format + + + OpsPolicyTemplateList: + type: array + items: + $ref: '#/components/schemas/OpsPolicyTemplate' + minItems: 1 + description: List of Operation policy templates from the Partner OP + OpsConcretePolicy: + type: object + required: + - policyId + - policyParamLimits + properties: + policyId: + $ref: '#/components/schemas/OpsPolicyIdentifier' + policyParamLimits: + $ref: '#/components/schemas/OpsPolicyRule' + description: Application policy id and policy parameter value limits registered by the Originating OP + + + OpsPolicyTemplate: + type: object + required: + - opsPolicyName + - OpslPolicyMetaInfo + - opsPolicyType + - opsPolicyScope + - opsPolicyDescription + - opsPolicyRules + properties: + opsPolicyName: + type: string + maxLength: 64 + description: Brief policy template name on policy objective + opslPolicyMetaInfo: + $ref: '#/components/schemas/OpslPolicyMetaInfo' + opsPolicyType: + $ref: '#/components/schemas/OpsPolicyType' + opsPolicyScope: + $ref: '#/components/schemas/OpsPolicyScope' + opsPolicyDescription: + type: string + maxLength: 256 + description: Brief policy template description on policy objective + opsPolicyRules: + type: array + items: + $ref: '#/components/schemas/OpsPolicyRule' + minItems: 1 + description: Set of policy action rules for a given policy + + + OpsPolicyRule: + type: object + properties: + opsPolicyRule: + $ref: '#/components/schemas/GenericPolicyRule' + description: Operation policies rule defines the action to be taken against the subscribed policy template + + + OpsPolicyType: + type: string + enum: + - static + - dynamic + description: Policy attribute that defines if the policy rules applies to static part of the infra or dynamic part of the edge cloud infra + + OpsPolicyScope: + type: string + enum: + - zonal + - global + description: Operation-level Policy scope defines if a policy is a set of availability zones or applies globally to all zones + + + AssocOpsPolicies: + type: object + required: + - policyId + - appIdList + properties: + policyId: + $ref: '#/components/schemas/OpsPolicyIdentifier' + appIdList: + $ref: '#/components/schemas/AppIdLocList' + + AvailZoneIdLocList: + type: object + required: + - zoneIds + properties: + zoneIds: + type: array + items: + $ref: '#/components/schemas/ZoneIdentifier' + minItems: 1 + + RegisteredOpsPolicyList : + type: array + items: + $ref: '#/components/schemas/OpsConcretePolicy' + minItems: 1 + description: Operation policies registered by the Originating OP + + + + AppComponentSpecs: + description: An application may consist of more than one component. Each component is associated with a descriptor and may exposes its services externally or internally. App providers are required to provide details about all these components, their associated descriptors and their DNS names. + type: array + items: + type: object + required: + - artefactId + properties: + serviceNameNB: + $ref: '#/components/schemas/ServiceNameNB' + serviceNameEW: + $ref: '#/components/schemas/ServiceNameEW' + componentName: + $ref: '#/components/schemas/ComponentName' + artefactId: + $ref: '#/components/schemas/ArtefactId' + minItems: 1 + + AppMetaData: + description: Application metadata details + type: object + required: + - appName + - version + - accessToken + properties: + appName: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,31}$ + description: Name of the application. Application provider define a human readable name for the application + version: + type: string + description: Version info of the application + appDescription: + type: string + minLength: 16 + maxLength: 256 + description: Brief application description provided by application provider + mobilitySupport: + $ref: '#/components/schemas/MobilitySupport' + accessToken: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{31,63}$ + description: An application Access key, to be used with UNI interface to authorize UCs Access to a given application + category: + type: string + enum: + - IOT + - HEALTH_CARE + - GAMING + - VIRTUAL_REALITY + - SOCIALIZING + - SURVEILLANCE + - ENTERTAINMENT + - CONNECTIVITY + - PRODUCTIVITY + - SECURITY + - INDUSTRIAL + - EDUCATION + - OTHERS + description: Possible categorization of the application + AppQoSProfile: + description: Parameters corresponding to the performance constraints, tenancy details etc. + type: object + required: + - latencyConstraints + properties: + latencyConstraints: + $ref: '#/components/schemas/LatencyConstraints' + bandwidthRequired: + $ref: '#/components/schemas/BandwidthRequired' + multiUserClients: + $ref: '#/components/schemas/MultiUserClients' + noOfUsersPerAppInst: + $ref: '#/components/schemas/NoOfUsersPerAppInst' + appProvisioning: + $ref: '#/components/schemas/AppProvisioning' + + EdgeAppFQDN: + type: string + description: DNS FQDN assigned to application instances in an availability zone. User Clients can resolve the FQDN to communicate with the edge instances of the application + + ClientLocation: + type: object + minProperties: 1 + properties: + geo_location: + type: string + description: Latitude, Longitude as decimal fraction up to 4 digit precision + pattern: ^([-+]?)([\d]{1,2})(((\.)(\d+)(,)))(\s*)(([-+]?)([\d]{1,3})((\.)(\d+))?)$ + rad_location: + description: Information about the 4G/5G Cell ids where the client is currently served. + type: array + items: + type: object + required: + - carrier + - mcc + - mnc + - cellId + properties: + carrier: + type: string + enum: + - 5G + - LTE + mcc: + type: integer + minimum: 1 + maximum: 999 + description: Mobile country code of the network as broadcasted in the serving cell + mnc: + type: integer + minimum: 1 + maximum: 999 + description: Mobile network code of the network as broadcasted in the serving cell + cellId: + type: integer + description: it could be a CGI (if carrier is LTE) or NCGI (if carrier is 5G). + areaCode: + type: integer + description: Routing area code or Traffic area code where client is being served. + CompEnvParams: + description: Environment variables are key value pairs that should be injected when component in instantiated + type: object + required: + - envVarName + - envValueType + properties: + envVarName: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,30}[A-Za-z0-9]$ + description: Name of environment variable + envValueType: + type: string + enum: + - USER_DEFINED + - PLATFORM_DEFINED_DYNAMIC_PORT + - PLATFORM_DEFINED_DNS + - PLATFORM_DEFINED_IP + envVarValue: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,62}[A-Za-z0-9]$ + description: Value to be assigned to environment variable + envVarSrc: + type: string + description: Full path of parameter from componentSpec that should be used to generate the environment value. Eg. networkResourceProfile[1]. interfaceId. + CommandLineParams: + description: List of commands and arguments that shall be invoked when the component instance is created. This is valid only for container based deployment. + type: object + required: + - command + properties: + command: + type: array + items: + type: string + description: List of commands that application should invoke when an instance is created. + commandArgs: + type: array + items: + type: string + description: List of arguments required by the command. + DeploymentConfig: + description: Configuration used when deploying a component. May override other ComponentSpec parameters related to deployment like restart policy, command line parameters, environment variables, etc. + type: object + required: + - configType + - contents + properties: + configType: + type: string + enum: + - DOCKER_COMPOSE + - KUBERNETES_MANIFEST + - CLOUD_INIT + - HELM_VALUES + description: Config type. + contents: + type: string + description: Contents of the configuration. + + ComponentSpec: + description: Details about compute, networking and storage requirements for each component of the application. App provider should define all information needed to instantiate the component. If artefact is being defined at component level this section should have information just about the component. In case the artefact is being defined at application level the section should provide details about all the components. + type: object + required: + - componentName + - images + - numOfInstances + - restartPolicy + - computeResourceProfile + properties: + componentName: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,62}[A-Za-z0-9]$ + description: Must be a valid RFC 1035 label name. Component name must be unique with an application + images: + description: List of all images associated with the component. Images are specified using the file identifiers. Partner OP provides these images using file upload api. + type: array + items: + $ref: '#/components/schemas/FileId' + minItems: 1 + numOfInstances: + type: integer + format: int32 + description: Number of component instances to be launched. + restartPolicy: + type: string + enum: + - RESTART_POLICY_ALWAYS + - RESTART_POLICY_NEVER + description: How the platform shall handle component failure + commandLineParams: + $ref: '#/components/schemas/CommandLineParams' + exposedInterfaces: + description: Each application component exposes some ports either for external users or for inter component communication. Application provider is required to specify which ports are to be exposed and the type of traffic that will flow through these ports. + type: array + items: + $ref: '#/components/schemas/InterfaceDetails' + minItems: 1 + computeResourceProfile: + $ref: '#/components/schemas/ComputeResourceInfo' + compEnvParams: + type: array + items: + $ref: '#/components/schemas/CompEnvParams' + deploymentConfig: + $ref: '#/components/schemas/DeploymentConfig' + persistentVolumes: + description: The ephemeral volume a container process may need to temporary store internal data + type: array + items: + $ref: '#/components/schemas/PersistentVolumeDetails' + minItems: 1 + ComputeResourceInfo: + type: object + required: + - cpuArchType + - numCPU + - memory + properties: + cpuArchType: + type: string + enum: + - ISA_X86_64 + - ISA_ARM_64 + description: CPU Instruction Set Architecture (ISA) E.g., Intel, Arm etc. + numCPU: + $ref: '#/components/schemas/Vcpu' + memory: + type: integer + format: int64 + description: Amount of RAM in Mbytes + diskStorage: + type: integer + format: int32 + description: Amount of disk storage in Gbytes for a given ISA type + gpu: + type: array + items: + $ref: '#/components/schemas/GpuInfo' + vpu: + type: integer + description: Number of Intel VPUs available for a given ISA type + fpga: + type: integer + description: Number of FPGAs available for a given ISA type + hugepages: + type: array + items: + $ref: '#/components/schemas/HugePage' + cpuExclusivity: + type: boolean + description: Support for exclusive CPUs + + nodeDiscoveryResponse: + type: object + required: + - edgeNodes + - discoveredAppInsts + properties: + edgeNodes: + $ref: '#/components/schemas/DiscoveredEdgeNodes' + discoveredAppInsts: + $ref: '#/components/schemas/DiscoveredAppInsts' + description: Candidate availability zones and details of already running instances of the given application + + + DiscoveredEdgeNodes: + type: array + items: + type: object + required: + - zoneId + - latencyServiceEndPoints + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + latencyServiceEndPoints: + $ref: '#/components/schemas/ServiceEndpoint' + minItems: 1 + description: List of candidate zones where application instance could be created. LatencyServiceEndpoint is responsible for responding to latency measurement request from client + + + DiscoveredAppInsts: + type: array + items: + type: object + required: + - appId + - appProviderId + - appInstances + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + appInstances: + type: array + items: + type: object + required: + - instancesInfo + properties: + instancesInfo: + type: object + required: + - zoneId + - appProviderId + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + instanceDetails: + $ref: '#/components/schemas/InstanceDetails' + minItems: 1 + + InstanceDetails: + type: array + items: + type: object + required: + - appInstanceInfo + properties: + appInstanceInfo: + type: object + required: + - instanceIdentifier + - instanceState + properties: + instanceIdentifier: + $ref: '#/components/schemas/InstanceIdentifier' + instancestate: + $ref: '#/components/schemas/InstanceState' + minItems: 1 + + + FederationRequestData: + type: object + required: + - initialDate + - partnerStatusLink + properties: + origOPFederationId: + $ref: '#/components/schemas/FederationIdentifier' + origOPCountryCode: + $ref: '#/components/schemas/CountryCode' + origOPMobileNetworkCodes: + $ref: '#/components/schemas/MobileNetworkIds' + origOPFixedNetworkCodes: + $ref: '#/components/schemas/FixedNetworkIds' + initialDate: + type: string + format: date-time + description: Time zone info of the federation initiated by the originating OP + partnerStatusLink: + $ref: '#/components/schemas/Uri' + + FederationResponseData: + type: object + required: + - federationContextId + - platformCaps + properties: + partnerOPFederationId: + $ref: '#/components/schemas/FederationIdentifier' + partnerOPCountryCode: + $ref: '#/components/schemas/CountryCode' + federationContextId: + $ref: '#/components/schemas/FederationContextId' + edgeDiscoveryServiceEndPoint: + $ref: '#/components/schemas/ServiceEndpoint' + lcmServiceEndPoint: + $ref: '#/components/schemas/ServiceEndpoint' + partnerOPMobileNetworkCodes: + $ref: '#/components/schemas/MobileNetworkIds' + partnerOPFixedNetworkCodes: + $ref: '#/components/schemas/FixedNetworkIds' + offeredAvailabilityZones: + type: array + items: + $ref: '#/components/schemas/ZoneDetails' + minItems: 1 + description: List of zones, which the operator platform wishes to make available to developers/ISVs of requesting operator platform. + platformCaps: + $ref: '#/components/schemas/PlatformCaps' + federationExpiryDate: + type: string + format: date-time + description: Date and Time zone info of the existing federation expiry + federationRenewalDate: + type: string + format: date-time + description: Date and Time zone info of the existing federation renewal. Shall be less than federationExpiryDate + + dateAndTimeZoneObject: + type: string + format: date-time + description: Date and Time zone info format + Flavour: + type: object + required: + - flavourId + - cpuArchType + - supportedOSTypes + - numCPU + - memorySize + - storageSize + properties: + flavourId: + $ref: '#/components/schemas/FlavourId' + cpuArchType: + $ref: '#/components/schemas/CPUArchType' + supportedOSTypes: + description: A list of operating systems which this flavour configuration can support e.g., RHEL Linux, Ubuntu 18.04 LTS, MS Windows 2012 R2. + type: array + items: + $ref: '#/components/schemas/OSType' + minItems: 1 + numCPU: + type: integer + format: int32 + description: Number of available vCPUs + memorySize: + type: integer + format: int32 + description: Amount of RAM in Mbytes + storageSize: + type: integer + format: int32 + description: Amount of disk storage in Gbytes + gpu: + type: array + items: + $ref: '#/components/schemas/GpuInfo' + fpga: + type: integer + format: int32 + description: Number of FPGAs + + vpu: + type: integer + description: Number of Intel VPUs available + hugepages: + type: array + items: + $ref: '#/components/schemas/HugePage' + cpuExclusivity: + type: boolean + description: Support for exclusive CPUs + GpuInfo: + type: object + required: + - gpuVendorType + - gpuModeName + - gpuMemory + - numGPU + properties: + gpuVendorType: + type: string + enum: + - GPU_PROVIDER_NVIDIA + - GPU_PROVIDER_AMD + description: GPU vendor name e.g. NVIDIA, AMD etc. + example: Nvidia + gpuModeName: + type: string + description: Model name corresponding to vendorType may include info e.g. for NVIDIA, model name could be “Tesla M60”, “Tesla V100” etc. + gpuMemory: + type: integer + description: GPU memory in Mbytes + numGPU: + type: integer + description: Number of GPUs + HugePage: + type: object + required: + - pageSize + - number + properties: + pageSize: + type: string + enum: + - 2MB + - 4MB + - 1GB + description: Size of hugepage + number: + type: integer + description: Total number of huge pages + InterfaceDetails: + type: object + required: + - interfaceId + - commProtocol + - commPort + - visibilityType + properties: + interfaceId: + type: string + description: Each Port and corresponding traffic protocol exposed by the component is identified by a name. Application client on user device requires this to uniquely identify the interface. + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,30}[A-Za-z0-9]$ + commProtocol: + type: string + enum: + - TCP + - UDP + - HTTP_HTTPS + description: Defines the IP transport communication protocol i.e., TCP, UDP or HTTP + commPort: + type: integer + format: int32 + minimum: 1 + maximum: 65535 + description: Port number exposed by the component. OP may generate a dynamic port towards the UCs corresponding to this internal port and forward the client traffic from dynamic port to container Port. + visibilityType: + description: Defines whether the interface is exposed to outer world or not i.e., external, or internal. If this is set to "external", then it is exposed to external applications otherwise it is exposed internally to edge application components within edge cloud. When exposed to external world, an external dynamic port is assigned for UC traffic and mapped to the internal container Port + type: string + enum: + - VISIBILITY_EXTERNAL + - VISIBILITY_INTERNAL + network: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{6,30}[A-Za-z0-9]$ + description: Name of the network. In case the application has to be associated with more than 1 network then app provider must define the name of the network on which this interface has to be exposed. This parameter is required only if the port has to be exposed on a specific network other than default. + InterfaceName: + type: string + pattern: ^[a-z][a-z0-9]{3}$ + description: Interface Name. Required only if application has to be attached to a network other than default. + InvalidParam: + type: object + properties: + param: + type: string + reason: + type: string + required: + - param + MobileNetworkIds: + type: object + properties: + mcc: + $ref: '#/components/schemas/Mcc' + mncs: + type: array + items: + $ref: '#/components/schemas/Mnc' + minItems: 1 + ObjectRepoLocation: + type: object + properties: + repoURL: + $ref: '#/components/schemas/Uri' + userName: + type: string + description: Username to access the repository + password: + type: string + description: Password to access the repository + token: + type: string + description: Authorization token to access the repository + OSType: + type: object + required: + - architecture + - distribution + - version + - license + properties: + architecture: + type: string + enum: + - x86_64 + - x86 + example: x86_64 + distribution: + type: string + enum: + - RHEL + - UBUNTU + - COREOS + - FEDORA + - WINDOWS + - OTHER + + version: + type: string + enum: + - OS_VERSION_UBUNTU_2204_LTS + - OS_VERSION_RHEL_8 + - OS_VERSION_RHEL_7 + - OS_VERSION_DEBIAN_11 + - OS_VERSION_COREOS_STABLE + - OS_MS_WINDOWS_2012_R2 + - OTHER + + license: + type: string + enum: + - OS_LICENSE_TYPE_FREE + - OS_LICENSE_TYPE_ON_DEMAND + - NOT_SPECIFIED + + RepoType: + type: string + enum: + - PRIVATEREPO + - PUBLICREPO + - UPLOAD + description: Artefact or file repository location. PUBLICREPO is used of public URLs like GitHub, Helm repo, docker registry etc., PRIVATEREPO is used for private repo managed by the application developer, UPLOAD is for the case when artefact/file is uploaded from MEC web portal. OP should pull the image from ‘repoUrl' immediately after receiving the request and then send back the response. In case the repoURL corresponds to a docker registry, use docker v2 http api to do the pull. + + ArtefactName: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_]{7,31}$ + description: Name of the artefact. + + ArtefactVersionInfo: + type: string + description: Artefact version information + + ArtefactDescription: + type: string + maxLength: 256 + description: Brief description of the artefact by the application provider + + ArtefactVirtType: + type: string + enum: + - VM_TYPE + - CONTAINER_TYPE + + ArtefactFileName: + type: string + minLength: 8 + maxLength: 32 + description: Name of the file. + + ArtefactFileFormat: + type: string + enum: + - ZIP + - TAR + - TEXT + - TARGZ + description: Artefacts like Helm charts or Terraform scripts may need compressed format. + + ArtefactDescriptorType: + type: string + enum: + - HELM + - TERRAFORM + - ANSIBLE + - SHELL + - COMPONENTSPEC + description: Type of descriptor present in the artefact. App provider can either define either a Helm chart or a Terraform script or container spec. + + + LatencyConstraints: + type: string + enum: + - NONE + - LOW + - ULTRALOW + description: Latency requirements for the application.Allowed values (non-standardized) are none, low and ultra-low. Ultra-Low may corresponds to range 15 - 30 msec, Low correspond to range 30 - 50 msec. None means 51 and above + + BandwidthRequired: + type: integer + format: int32 + minimum: 1 + description: Data transfer bandwidth requirement (minimum limit) for the application. It should in Mbits/sec + + MobilitySupport: + type: boolean + default: false + description: Indicates if an application is sensitive to user mobility and can be relocated. Default is “FALSE” + + MultiUserClients: + type: string + enum: + - APP_TYPE_SINGLE_USER + - APP_TYPE_MULTI_USER + description: Single user type application are designed to serve just one client. Multi user type application is designed to serve multiple clients + + NoOfUsersPerAppInst: + type: integer + default: 1 + description: Maximum no of clients that can connect to an instance of this application. This parameter is relevant only for application of type multi user + AppProvisioning: + type: boolean + default: true + description: Define if application can be instantiated or not + + AppComponents: + description: An application may consist of more than one component. Each component is associated with a descriptor and may exposes its services externally or internally. App providers are required to provide details about all these components, their associated descriptors and their DNS names. + type: array + items: + type: object + required: + - componentName + anyOf: + - required: + - serviceNameNB + - required: + - serviceNameEW + - required: + - artefactId + properties: + serviceNameNB: + $ref: '#/components/schemas/ServiceNameNB' + serviceNameEW: + $ref: '#/components/schemas/ServiceNameEW' + componentName: + $ref: '#/components/schemas/ComponentName' + artefactId: + $ref: '#/components/schemas/ArtefactId' + minItems: 1 + + + PersistentVolumeDetails: + type: object + required: + - volumeSize + - volumeMountPath + - volumeName + properties: + volumeSize: + type: string + enum: + - 10Gi + - 20Gi + - 50Gi + - 100Gi + description: size of the volume given by user (10GB, 20GB, 50 GB or 100GB) + volumeMountPath: + type: string + description: Defines the mount path of the volume + volumeName: + type: string + description: Human readable name for the volume + ephemeralType: + type: boolean + default: false + description: It indicates the ephemeral storage on the node and contents are not preserved if containers restarts + accessMode: + type: string + enum: + - RW + - RO + default: RW + description: Values are RW (read/write) and RO (read-only)l + sharingPolicy: + type: string + enum: + - EXCLUSIVE + - SHARED + default: EXCLUSIVE + description: Exclusive or Shared. If shared, then in case of multiple containers same volume will be shared across the containers. + ProblemDetails: + type: object + properties: + title: + type: string + description: Summary of the problem + detail: + type: string + description: Specific detail of the issue + cause: + type: string + description: Fixed string indicating cause of the issue + invalidParams: + type: array + items: + $ref: '#/components/schemas/InvalidParam' + minItems: 0 + ResourceReservationDuration: + description: Time period for which resources are to be reserved starting from now + type: object + minProperties: 1 + properties: + numOfDays: + type: integer + format: int32 + description: Number of days to be reserved + numOfMonths: + type: integer + format: int32 + description: Number of months to be reserved + numOfYears: + type: integer + format: int32 + description: Number of years to be reserved + ServiceEndpoint: + type: object + required: + - port + anyOf: + - required: + - fqdn + - required: + - ipv4Addresses + - required: + - ipv6Addresses + properties: + port: + $ref: '#/components/schemas/Port' + fqdn: + $ref: '#/components/schemas/EdgeAppFQDN' + ipv4Addresses: + type: array + items: + $ref: '#/components/schemas/Ipv4Addr' + minItems: 1 + ipv6Addresses: + type: array + items: + $ref: '#/components/schemas/Ipv6Addr' + minItems: 1 + ZoneDetails: + type: object + required: + - zoneId + - geographyDetails + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + geolocation: + $ref: '#/components/schemas/GeoLocation' + geographyDetails: + type: string + description: Details about cities or state covered by the edge. Details about the type of locality for eg rural, urban, industrial etc. This information is defined in human readable form. + ZoneRegistrationRequestData: + type: object + required: + - acceptedAvailabilityZones + properties: + acceptedAvailabilityZones: + type: array + items: + $ref: '#/components/schemas/ZoneIdentifier' + minItems: 1 + availZoneNotifLink: + $ref: '#/components/schemas/Uri' + ZoneRegistrationResponseData: + type: object + required: + - acceptedZoneResourceInfo + properties: + acceptedZoneResourceInfo: + type: array + items: + $ref: '#/components/schemas/ZoneRegisteredData' + + minItems: 1 + ZoneRegisteredData: + type: object + required: + - zoneId + - reservedComputeResources + - computeResourceQuotaLimits + - flavoursSupported + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + reservedComputeResources: + description: Resources exclusively reserved for the originator OP. + type: array + items: + $ref: '#/components/schemas/ComputeResourceInfo' + minItems: 1 + computeResourceQuotaLimits: + description: Max quota on resources partner OP allows over reserved resources. + type: array + items: + $ref: '#/components/schemas/ComputeResourceInfo' + minItems: 1 + flavoursSupported: + type: array + items: + $ref: '#/components/schemas/Flavour' + minItems: 1 + networkResources: + type: object + required: + - egressBandWidth + - dedicatedNIC + - supportSriov + - supportDPDK + properties: + egressBandWidth: + type: integer + format: int32 + description: Max dl throughput that this edge can offer. It is defined in Mbps. + dedicatedNIC: + type: integer + format: int32 + description: Number of network interface cards which can be dedicatedly assigned to application pods on isolated networks. This includes virtual as well physical NICs + supportSriov: + type: boolean + description: If this zone support SRIOV networks or not + supportDPDK: + type: boolean + description: If this zone supports DPDK based networking. + zoneServiceLevelObjsInfo: + type: object + description: It is a measure of the actual amount of data that is being sent over a network per unit of time and indicates máximum supported value for a zone + required: + - latencyRanges + - jitterRanges + - throughputRanges + properties: + latencyRanges: + type: object + properties: + minLatency: + type: integer + format: int32 + minimum: 1 + description: The time for data/packet to reach from UC to edge application. It represent mínimum latency in milli seconds that may exist between UCs and edge apps in this zone but it can be higher in actual. + maxLatency: + type: integer + format: int32 + description: The maximum limit of latency between UC and Edge App in milli seconds. + jitterRanges: + type: object + properties: + minJitter: + type: integer + format: int32 + minimum: 1 + maxJitter: + type: integer + format: int32 + description: The maximum limit of network jitter between UC and Edge App in milli seconds. + throughputRanges: + type: object + properties: + minThroughput: + type: integer + format: int32 + minimum: 1 + description: The minimum limit of network throughput between UC and Edge App in Mega bits per seconds (Mbps). + maxThroughput: + type: integer + format: int32 + description: The maximum limit of network throughput between UC and Edge App in Mega bits per seconds (Mbps). + + # + # HTTP responses + # + responses: + "400": + description: Bad request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "401": + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "404": + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "409": + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "412": + description: Precondition Failed + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "422": + description: Unprocessable Entity + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "500": + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "501": + description: Not Implemented + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "503": + description: Service Unavailable + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "520": + description: Web Server Returned an Unknown Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + + "400BadRequest": + description: Bad request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + examples: + InvalidFedParameters: + description: Sufficient parameters must be specified to allow the partner OP to validate federation request + value: + { + "title": "Insufficient parameters", + "details": "Incorrect values received in federation request", + "cause": "INVALID_FED_RQST_PARAMS" + } + + "404NotFound": + description: Resource Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + examples: + FederationContextNotFound: + description: Federation context does not exist + value: + { + "title": "Federation context Id not found", + "details": "Partner OP does not recognize the federationContextId from Originating OP", + "cause": "INVALID_FED_CTX_ID" + } + FederationNotFound: + description: Federation terminated parmanently + value: + { + "title": "Federation context Id not found", + "details": "Partner OP does not recognize the federationContextId from Originating OP", + "cause": "FED_PERMANENTLY_TERMINAT" + } + ZoneNotFound: + description: Zone Not Found + value: + { + "title": "Requested Zone Id not found", + "details": "Requested zone by the Originating OP does not exist with Partner OP", + "cause": "ZONE_ID_NOT_FOUND" + } + AppNotFound: + description: Application Not Found + value: + { + "title": "Requested Application Id not found", + "details": "Requested Application by the Originating OP does not exist with Partner OP", + "cause": "APP_ID_NOT_FOUND" + } + AppInstNotFound: + description: Application Instance Not Found + value: + { + "title": "Requested App instance Id not found", + "details": "Requested application instance by the Originating OP does not exist with Partner OP", + "cause": "APP_INST_NOT_FOUND" + } + + + default: + description: Generic Error +paths: + /federation-resources: + get: + summary: Retrieves REST APIs supported by an OP for federation services. + operationId: GetFederationAPIs + tags: + - FederationAPIManagement + responses: + "200": + description: Federation meta-info request accepted + content: + application/json: + schema: + type: object + required: + - federationSupportedAPIs + properties: + federationSupportedAPIs: + $ref: '#/components/schemas/FederationSupportedAPIs' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /partner: + post: + summary: Creates one direction federation with partner operator platform. + operationId: CreateFederation + tags: + - FederationManagement + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/FederationRequestData' + responses: + "200": + description: Federation meta-info request accepted + content: + application/json: + schema: + $ref: '#/components/schemas/FederationResponseData' + headers: + Location: + description: 'Contains the URI of the newly created resource, according to the structure: {apiRoot}/operatorplatform/federation/v1/partner/{federationContextId}' + required: true + schema: + type: string + Accept-Encoding: + description: Accept-Encoding, described in IETF RFC 7694 + schema: + type: string + Content-Encoding: + description: Content-Encoding, described in IETF RFC 7231 + schema: + type: string + "400": + $ref: '#/components/responses/400BadRequest' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onPartnerStatusEvent: + '{$request.body#/partnerStatusLink }': + post: + requestBody: + description: | + OP uses this callback api to notify partner OP about change in federation status, federation metadata or offered zone details. Allowed combinations of objectType and operationType are + - FEDERATION - STATUS: Status specified by parameter 'federationStatus'. + - ZONES - STATUS: Status specified by parameter 'zoneStatus'. + - ZONES - ADD: Use parameter 'addZones' to define add new zones + - ZONES - REMOVE: Use parameter 'removeZones' to define remove zones. + - EDGE_DISCOVERY_SERVICE - UPDATE: Use parameter 'edgeDiscoverySvcEndPoint' to specify new endpoints + - LCM_SERVICE - UPDATE: Use parameter 'lcmSvcEndPoint' to specify new endpoints + - MOBILE_NETWORK_CODES - ADD: Use parameter 'addMobileNetworkIds' to define new mobile network codes. + - MOBILE_NETWORK_CODES - REMOVE: Use parameter 'removeMobileNetworkIds' to remove mobile network codes. + - FIXED_NETWORK_CODES - ADD: Use parameter 'addFixedNetworkIds' to define new fixed network codes. + - FIXED_NETWORK_CODES - REMOVE: Use parameter 'removeFixedNetworkIds' to remove fixed network codes. + - SERVICE_APIS - ADD/REMOVE: Parameter Usage 'addServiceAPIs / removeServiceAPIs' to add or remove Service APIs support. + + content: + application/json: + schema: + type: object + required: + - federationContextId + - objectType + - operationType + - modificationDate + properties: + federationContextId: + $ref: '#/components/schemas/FederationContextId' + objectType: + type: string + enum: + - FEDERATION + - ZONES + - EDGE_DISCOVERY_SERVICE + - LCM_SERVICE + - MOBILE_NETWORK_CODES + - FIXED_NETWORK_CODES + - SERVICE_APIS + operationType: + type: string + enum: + - STATUS + - UPDATE + - ADD + - REMOVE + edgeDiscoverySvcEndPoint: + $ref: '#/components/schemas/ServiceEndpoint' + lcmSvcEndPoint: + $ref: '#/components/schemas/ServiceEndpoint' + addMobileNetworkIds: + $ref: '#/components/schemas/MobileNetworkIds' + removeMobileNetworkIds: + $ref: '#/components/schemas/MobileNetworkIds' + addFixedNetworkIds: + $ref: '#/components/schemas/FixedNetworkIds' + removeFixedNetworkIds: + $ref: '#/components/schemas/FixedNetworkIds' + addZones: + type: array + items: + $ref: '#/components/schemas/ZoneDetails' + description: List of zones, which the operator platform wishes to make available to developers/ISVs of requesting operator platform. + minItems: 1 + removeZones: + type: array + items: + $ref: '#/components/schemas/ZoneIdentifier' + description: List of zones, which the operator platform no longer wishes to share. + minItems: 1 + addServiceAPIs: + $ref: '#/components/schemas/serviceAPINames' + removeServiceAPIs: + $ref: '#/components/schemas/serviceAPINames' + zoneStatus: + type: array + items: + type: object + required: + - zoneId + - status + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + status: + $ref: '#/components/schemas/Status' + minItems: 1 + federationStatus: + $ref: '#/components/schemas/Status' + modificationDate: + type: string + format: date-time + description: Date and time of the federation modification by the originating partner OP + responses: + "204": + description: Expected response to a successful call back processing + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + /{federationContextId}/partner: + get: + summary: Retrieves details about the federation context with the partner OP. The response shall provide info about the zones offered by the partner, partner OP network codes, information about edge discovery and LCM service etc. + operationId: GetFederationDetails + tags: + - FederationManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + responses: + "200": + description: Federation meta-info request accepted + content: + application/json: + schema: + type: object + properties: + edgeDiscoveryServiceEndPoint: + $ref: '#/components/schemas/ServiceEndpoint' + lcmServiceEndPoint: + $ref: '#/components/schemas/ServiceEndpoint' + allowedMobileNetworkIds: + $ref: '#/components/schemas/MobileNetworkIds' + allowedFixedNetworkIds: + $ref: '#/components/schemas/FixedNetworkIds' + offeredAvailabilityZones: + type: array + items: + $ref: '#/components/schemas/ZoneDetails' + minItems: 1 + platformCaps: + $ref: '#/components/schemas/PlatformCaps' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + patch: + summary: API used by the Originating OP towards the partner OP, to update the parameters associated to the existing federation + operationId: UpdateFederation + tags: + - FederationManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + required: true + description: Details about changes origination OP wished to apply + content: + application/json: + schema: + type: object + required: + - objectType + - operationType + - modificationDate + properties: + objectType: + type: string + enum: + - MOBILE_NETWORK_CODES + - FIXED_NETWORK_CODES + - OPS_POLICY + - APP_POLICY + operationType: + type: string + enum: + - ADD_CODES + - REMOVE_CODES + - UPDATE_CODES + - ADD_POLICY + - REMOVE_POLICY + - UPDATE_POLICY + + addMobileNetworkIds: + $ref: '#/components/schemas/MobileNetworkIds' + removeMobileNetworkIds: + $ref: '#/components/schemas/MobileNetworkIds' + addFixedNetworkIds: + $ref: '#/components/schemas/FixedNetworkIds' + removeFixedNetworkIds: + $ref: '#/components/schemas/FixedNetworkIds' + assocAppPolicies: + $ref: '#/components/schemas/AssocApplPolicies' + assocOpsPolicies: + $ref: '#/components/schemas/AssocOpsPolicies' + + modificationDate: + type: string + format: date-time + description: Date and time of the federation modification by the originating partner OP + responses: + "200": + description: Federation meta-info request accepted + content: + application/json: + schema: + type: object + properties: + edgeDiscoveryServiceEndPoint: + $ref: '#/components/schemas/ServiceEndpoint' + lcmServiceEndPoint: + $ref: '#/components/schemas/ServiceEndpoint' + allowedMobileNetworkIds: + $ref: '#/components/schemas/MobileNetworkIds' + allowedFixedNetworkIds: + $ref: '#/components/schemas/FixedNetworkIds' + offeredAvailabilityZones: + type: array + items: + $ref: '#/components/schemas/ZoneDetails' + minItems: 1 + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + delete: + summary: Remove existing federation with the partner OP + operationId: DeleteFederationDetails + tags: + - FederationManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + responses: + "200": + description: Federation removed successfully + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /fed-context-id: + get: + summary: Retrieves the existing federationContextId with partner operator platform. + operationId: GetFederationContextId + tags: + - FederationManagement + responses: + "200": + description: Federation context identifier retrieval request accepted + content: + application/json: + schema: + type: object + required: + - FederationContextId + properties: + FederationContextId: + $ref: '#/components/schemas/FederationContextId' + headers: + Location: + description: 'Contains the URI of the existing resource, according to the structure: {apiRoot}/operatorplatform/federation/v1/partner/{federationContextId}' + required: true + schema: + type: string + Accept-Encoding: + description: Accept-Encoding, described in IETF RFC 7694 + schema: + type: string + Content-Encoding: + description: Content-Encoding, described in IETF RFC 7231 + schema: + type: string + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/health: + get: + summary: Retrieves health status of the federation context with the Partner OP. + operationId: GetFederationHealth + tags: + - FederationManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + responses: + "200": + description: Federation health status information object + content: + application/json: + schema: + type: object + required: + - federationHealthStatus + properties: + federationHealthStatus: + $ref: '#/components/schemas/FederationHealthInfo' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/renew: + post: + summary: API used by the Originating OP to renew the existing federation + operationId: RenewFederation + tags: + - FederationManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + responses: + "200": + description: Federation renewal request accepted + content: + application/json: + schema: + type: object + required: + - FederationContextId + - federationRenewalDate + - federationExpiryDate + properties: + FederationContextId: + $ref: '#/components/schemas/FederationContextId' + federationRenewalDate: + $ref: '#/components/schemas/dateAndTimeZoneObject' + federationExpiryDate: + $ref: '#/components/schemas/dateAndTimeZoneObject' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/platform-caps: + get: + summary: Retrieves details about OP capabilities of the federated partner. + operationId: GetPlatformCapabilities + tags: + - FederationManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: capType + in: query + required: false + schema: + $ref: '#/components/schemas/CapabilityID' + + responses: + "200": + description: Federation meta-info request accepted + content: + application/json: + schema: + type: object + anyOf: + - required: + - deviceConnStatusChangeCap + - required: + - locationRetrievalCap + - required: + - userPlaneMgmtEvtCap + - required: + - dynamicQoSCap + properties: + deviceConnStatusChangeCap: + $ref: '#/components/schemas/DeviceConnStatusChangeCap' + locationRetrievalCap: + $ref: '#/components/schemas/LocationRetrievalCap' + userPlaneMgmtEvtCap: + $ref: '#/components/schemas/UserPlaneMgmtEvtCap' + dynamicQoSCap: + $ref: '#/components/schemas/DynamicQoSCap' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + + /{federationContextId}/partner/service/{serviceType}: + get: + summary: Retrieves the list of Service APIs and associated information that a partner OP supports + operationId: GetServiceAPIsDetails + tags: + - FederationManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: serviceType + in: path + required: true + schema: + $ref: '#/components/schemas/serviceType' + responses: + '200': + description: List of Service APIs names and associated configuration info as supported capabilities + content: + application/json: + schema: + type: object + required: + - ServiceType + - serviceCaps + - apiRoutingInfo + properties: + serviceCaps: + $ref: '#/components/schemas/serviceAPINames' + serviceType: + $ref: '#/components/schemas/serviceType' + apiRoutingInfo: + $ref: '#/components/schemas/serviceRoutingInfo' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/zones: + get: + summary: Retrieves details about the computation and network resources that partner OP has reserved for this zone. + operationId: GetZoneData + tags: + - AvailabilityZoneInfoSynchronization + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: zoneId + in: query + required: false + schema: + $ref: '#/components/schemas/ZoneIdentifier' + responses: + "200": + description: Zone metadata + content: + application/json: + schema: + $ref: '#/components/schemas/ZoneRegisteredData' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + post: + summary: Originating OP informs partner OP that it is willing to access the specified zones and partner OP shall reserve compute and network resources for these zones. + operationId: ZoneSubscribe + tags: + - AvailabilityZoneInfoSynchronization + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ZoneRegistrationRequestData' + required: true + responses: + "200": + description: Zone registered successfully + content: + application/json: + schema: + $ref: '#/components/schemas/ZoneRegistrationResponseData' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onZoneResourceUpdateEvent: + '{$request.body#/availZoneNotifLink}': + post: + requestBody: + description: Notification about resource availability. + content: + application/json: + schema: + type: object + required: + - federationContextId + - zoneId + - zoneResUpdInfo + properties: + federationContextId: + $ref: '#/components/schemas/FederationIdentifier' + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + zoneResUpdInfo: + type: array + items: + type: object + minProperties: 1 + properties: + availableCompResources: + description: Resources exclusively reserved for the originator OP. + type: array + items: + $ref: '#/components/schemas/ComputeResourceInfo' + minItems: 1 + availableNetResources: + type: object + properties: + egressBandWidth: + type: integer + format: int32 + description: Max dl throughput that this edge can offer. It is defined in Mbps. + dedicatedNIC: + type: integer + format: int32 + supportSriov: + type: boolean + description: If this zone support SRIOV networks or not + supportDPDK: + type: boolean + description: If this zone supports DPDK based networking + minProperties: 1 + responses: + "200": + description: Zone info notification acknowledged + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + + /{federationContextId}/zones/{zoneId}: + delete: + summary: Assert usage of a partner OP zone. Originating OP informs partner OP that it will no longer access the specified zone. + operationId: ZoneUnsubscribe + tags: + - AvailabilityZoneInfoSynchronization + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + responses: + "200": + description: Zone deregistered successfully + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + get: + summary: Retrieves details about the computation and network resources that partner OP has reserved for this zone. + operationId: GetZoneDetails + tags: + - AvailabilityZoneInfoSynchronization + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + responses: + "200": + description: Zone metadata + content: + application/json: + schema: + $ref: '#/components/schemas/ZoneRegisteredData' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/artefact: + post: + summary: Uploads application artefact on partner OP. Artefact is a zip file containing scripts and/or packaging files like Terraform or Helm which are required to create an instance of an application. + operationId: UploadArtefact + tags: + - ArtefactManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + description: An application can consist of multiple components. App providers are allowed to define separate artefacts for each component or they could define a consolidated artefact at application level. + content: + multipart/form-data: + schema: + type: object + required: + - artefactId + - appProviderId + - artefactName + - artefactVersionInfo + - artefactVirtType + - artefactDescriptorType + - componentSpec + properties: + artefactId: + $ref: '#/components/schemas/ArtefactId' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + artefactName: + $ref: '#/components/schemas/ArtefactName' + artefactVersionInfo: + $ref: '#/components/schemas/ArtefactVersionInfo' + artefactDescription: + $ref: '#/components/schemas/ArtefactDescription' + artefactVirtType: + $ref: '#/components/schemas/ArtefactVirtType' + artefactFileName: + $ref: '#/components/schemas/ArtefactFileName' + artefactFileFormat: + $ref: '#/components/schemas/ArtefactFileFormat' + artefactDescriptorType: + $ref: '#/components/schemas/ArtefactDescriptorType' + repoType: + $ref: '#/components/schemas/RepoType' + artefactRepoLocation: + $ref: '#/components/schemas/ObjectRepoLocation' + artefactFile: + type: string + format: binary + description: Helm archive/Terraform archive/container spec file or Binary image associated with an application component. + componentSpec: + type: array + items: + $ref: '#/components/schemas/ComponentSpec' + minItems: 1 + required: true + responses: + "200": + description: Artefact uploaded successfully + "202": + description: Artefact upload request accepted + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/artefact/{artefactId}: + get: + summary: Retrieves details about an artefact. + operationId: GetArtefact + tags: + - ArtefactManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: artefactId + in: path + required: true + schema: + $ref: '#/components/schemas/ArtefactId' + responses: + "200": + description: Artefact details + content: + application/json: + schema: + type: object + required: + - artefactId + - appProviderId + - artefactName + - artefactVersionInfo + - artefactVirtType + - artefactDescriptorType + properties: + artefactId: + $ref: '#/components/schemas/ArtefactId' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + artefactName: + $ref: '#/components/schemas/ArtefactName' + artefactDescription: + $ref: '#/components/schemas/ArtefactDescription' + artefactVersionInfo: + $ref: '#/components/schemas/ArtefactVersionInfo' + artefactVirtType: + $ref: '#/components/schemas/ArtefactVirtType' + artefactFileName: + $ref: '#/components/schemas/ArtefactFileName' + artefactFileFormat: + $ref: '#/components/schemas/ArtefactFileFormat' + artefactDescriptorType: + $ref: '#/components/schemas/ArtefactDescriptorType' + repoType: + $ref: '#/components/schemas/RepoType' + artefactRepoLocation: + $ref: '#/components/schemas/ObjectRepoLocation' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + delete: + summary: Removes an artefact from partner OP. + operationId: RemoveArtefact + tags: + - ArtefactManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: artefactId + in: path + required: true + schema: + $ref: '#/components/schemas/ArtefactId' + responses: + "200": + description: Artefact deletion successful + "202": + description: Artefact deletion request accepted + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/files: + post: + summary: Uploads an image file. Originating OP uses this api to onboard an application image to partner OP. + operationId: UploadFile + tags: + - ArtefactManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + content: + multipart/form-data: + schema: + type: object + required: + - fileId + - appProviderId + - fileName + - fileVersionInfo + - fileType + - imgOSType + - imgInsSetArch + properties: + fileId: + $ref: '#/components/schemas/FileId' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + fileName: + $ref: '#/components/schemas/FileName' + fileDescription: + $ref: '#/components/schemas/FileDescription' + fileVersionInfo: + $ref: '#/components/schemas/FileVersionInfo' + fileType: + $ref: '#/components/schemas/VirtImageType' + checksum: + type: string + description: MD5 checksum for VM and file-based images, sha256 digest for containers + imgOSType: + $ref: '#/components/schemas/OSType' + imgInsSetArch: + $ref: '#/components/schemas/CPUArchType' + repoType: + $ref: '#/components/schemas/RepoType' + + fileRepoLocation: + $ref: '#/components/schemas/ObjectRepoLocation' + file: + type: string + format: binary + description: Binary image associated with an application component. + required: true + responses: + "200": + description: File uploaded successfully + "202": + description: File upload request accepted + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/files/{fileId}: + delete: + summary: Removes an image file from partner OP. + operationId: RemoveFile + tags: + - ArtefactManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: fileId + in: path + required: true + schema: + $ref: '#/components/schemas/FileId' + responses: + "200": + description: Image deletion successful + "202": + description: Image deletion request accepted + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + get: + summary: View an image file from partner OP. + operationId: ViewFile + tags: + - ArtefactManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: fileId + in: path + required: true + schema: + $ref: '#/components/schemas/FileId' + responses: + "200": + description: Image details + content: + application/json: + schema: + type: object + required: + - fileId + - appProviderId + - fileName + - fileVersionInfo + - fileType + - imgOSType + - imgInsSetArch + properties: + fileId: + $ref: '#/components/schemas/FileId' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + fileName: + $ref: '#/components/schemas/FileName' + fileDescription: + $ref: '#/components/schemas/FileDescription' + fileVersionInfo: + $ref: '#/components/schemas/FileVersionInfo' + fileType: + $ref: '#/components/schemas/VirtImageType' + checksum: + type: string + description: MD5 checksum for VM and file-based images, sha256 digest for containers + imgOSType: + $ref: '#/components/schemas/OSType' + imgInsSetArch: + $ref: '#/components/schemas/CPUArchType' + repoType: + $ref: '#/components/schemas/RepoType' + + fileRepoLocation: + $ref: '#/components/schemas/ObjectRepoLocation' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/application/onboarding: + post: + summary: Submits an application details to a partner OP. Based on the details provided, partner OP shall do bookkeeping, resource validation and other pre-deployment operations. + operationId: OnboardApplication + tags: + - ApplicationOnboardingManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + required: true + description: Details about application compute resource requirements, associated artefacts, QoS profile and regions where application shall be made available etc. + content: + application/json: + schema: + type: object + required: + - appId + - appProviderId + - appMetaData + - appQoSProfile + - appComponentSpecs + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + appDeploymentZones: + description: Details about partner OP zones where the application should be made available; This field when specified will instruct the OP to restrict application instantiation only on the listed zones. + type: array + items: + $ref: '#/components/schemas/ZoneIdentifier' + minItems: 1 + appMetaData: + $ref: '#/components/schemas/AppMetaData' + appQoSProfile: + $ref: '#/components/schemas/AppQoSProfile' + appComponentSpecs: + $ref: '#/components/schemas/AppComponentSpecs' + appStatusCallbackLink: + $ref: '#/components/schemas/Uri' + edgeAppFQDN: + $ref: '#/components/schemas/EdgeAppFQDN' + + responses: + "200": + description: Application onboarded successfully + "202": + description: Application onboarding request accepted + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onApplicationOnboardStatusEvent: + '{$request.body#/appStatusCallbackLink}': + post: + requestBody: + description: Notification payload. + content: + application/json: + schema: + type: object + required: + - federationContextId + - appId + - statusInfo + properties: + federationContextId: + $ref: '#/components/schemas/FederationIdentifier' + appId: + $ref: '#/components/schemas/AppIdentifier' + statusInfo: + type: array + items: + type: object + required: + - zoneId + - onboardStatusInfo + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + onboardStatusInfo: + $ref: '#/components/schemas/OnboardStatusInfo' + minItems: 1 + responses: + "204": + description: Application status updated + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + + /{federationContextId}/application/onboarding/app/{appId}: + delete: + summary: Deboards the application from all zones, if any, and deletes the App. + operationId: DeleteApp + tags: + - ApplicationOnboardingManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + responses: + '200': + description: App deletion successful + '202': + description: App deletion request accepted + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + patch: + summary: Updates partner OP about changes in application compute resource requirements, QOS Profile, associated descriptor or change in associated components + operationId: UpdateApplication + tags: + - ApplicationOnboardingManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + requestBody: + required: true + description: Details about application compute resource requirements, associated artefact and QOS profile that needs to be updated. + content: + application/json: + schema: + type: object + minProperties: 1 + properties: + appUpdQoSProfile: + description: Parameters corresponding to the performance constraints, tenancy details etc. + type: object + anyOf: + - required: + - latencyConstraint + - required: + - bandwidthRequired + - required: + - mobilitySupport + - required: + - multiUserClients + - required: + - appProvisioning + properties: + latencyConstraints: + $ref: '#/components/schemas/LatencyConstraints' + bandwidthRequired: + $ref: '#/components/schemas/BandwidthRequired' + mobilitySupport: + $ref: '#/components/schemas/MobilitySupport' + multiUserClients: + $ref: '#/components/schemas/MultiUserClients' + noOfUsersPerAppInst: + $ref: '#/components/schemas/NoOfUsersPerAppInst' + appProvisioning: + $ref: '#/components/schemas/AppProvisioning' + appComponents: + $ref: '#/components/schemas/AppComponents' + + responses: + "200": + description: Application update successful + "202": + description: Application update request accepted + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + get: + summary: Retrieves application details from partner OP + operationId: ViewApplication + tags: + - ApplicationOnboardingManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + responses: + "200": + description: Application details + content: + application/json: + schema: + type: object + required: + - appId + - appProviderId + - appDeploymentZones + - appMetaData + - appQoSProfile + - appComponentSpecs + - onboardStatusInfo + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + appDeploymentZones: + description: Details about partner OP zones where the application should be made available; This field when specified will instruct the OP to restrict application instantiation only on the listed zones. + type: array + items: + type: object + required: + - countryCode + - zoneInfo + properties: + countryCode: + $ref: '#/components/schemas/CountryCode' + zoneInfo: + $ref: '#/components/schemas/ZoneIdentifier' + minItems: 1 + appMetaData: + $ref: '#/components/schemas/AppMetaData' + appQoSProfile: + $ref: '#/components/schemas/AppQoSProfile' + appComponentSpecs: + $ref: '#/components/schemas/AppComponentSpecs' + onboardStatusInfo: + $ref: '#/components/schemas/OnboardStatusInfo' + + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/application/onboarding/app/{appId}/zone/{zoneId}: + delete: + summary: Deboards an application from specific partner OP zones + operationId: DeboardApplication + tags: + - ApplicationOnboardingManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + responses: + "200": + description: Application deboarded successfully + "202": + description: Application deboard request accepted + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/application/onboarding/app/{appId}/additionalZones: + post: + summary: Onboards an existing application to a new zone within partner OP. + operationId: OnboardExistingAppNewZones + tags: + - ApplicationOnboardingManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + requestBody: + required: true + description: Details about new zones where application shall be made available + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ZoneIdentifier' + minItems: 1 + responses: + "200": + description: Application onboarding succussful + "202": + description: Application onboarding request accepted + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/application/onboarding/app/{appId}/zoneForbid: + post: + summary: Forbid/allow application instantiation on a partner zone + operationId: LockUnlockApplicationZone + tags: + - ApplicationOnboardingManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + requestBody: + required: true + content: + application/json: + schema: + type: array + items: + type: object + description: List of zones where application instantiation shall be forbidden or allowed. + required: + - zoneId + - forbid + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + forbid: + type: boolean + description: Value 'true' will forbid application instantiation on this zone. No new instance of the application can be created on this zone. + minItems: 1 + responses: + "200": + description: Application forbid/permit request accepted + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/application/lcm: + post: + summary: Instantiates an application on a partner OP zone. + operationId: InstallApp + tags: + - ApplicationDeploymentManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: Idempotency-Key + in: header + required: true + schema: + $ref: '#/components/schemas/TransactionId' + + requestBody: + description: Details about application and zones where application instance should be created. It also definea call back URI which the partner OP shall use update home OP about a change in instance status. + content: + application/json: + schema: + type: object + required: + - appId + - appProviderId + - appVersion + - zoneInfo + - appInstCallbackLink + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appVersion: + type: string + description: Version info of the application + appProviderId: + $ref: '#/components/schemas/AppProviderId' + zoneInfo: + type: object + required: + - zoneId + - flavourId + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + flavourId: + $ref: '#/components/schemas/FlavourId' + resourceConsumption: + type: string + enum: + - RESERVED_RES_SHALL + - RESERVED_RES_PREFER + - RESERVED_RES_AVOID + - RESERVED_RES_FORBID + default: RESERVED_RES_AVOID + description: Specifies if the application can be instantiated using pre-reserved resource or not. App provider can pre-reserve a pool of compute resource on each zone. 'RESERVED_RES_SHALL' instruct OP to use only the pre-reserved resources. 'RESERVED_RES_PREFER' instruct to first try using pre-reserved resource, if none available go for non-reserved resources. 'RESERVED_RES_AVOID' instruct OP not to use pre-reserved resource if possible, it is a choice depending upon circumstances 'RESERVED_RES_FORBID' instruct OP not to use pre-reserved resources. + resPool: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,30}[A-Za-z0-9]$ + description: Resource pool to be used for application instantiation on this zone. Valid only if IE 'resourceConsumption' is set to 'RESERVED_RES_SHALL' or 'RESERVED_RES_PREFER' + appInstCallbackLink: + $ref: '#/components/schemas/Uri' + responses: + "202": + description: Application instance creation request accepted. + content: + application/json: + schema: + type: object + required: + - zoneId + - appInstIdentifier + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + appInstIdentifier: + $ref: '#/components/schemas/InstanceIdentifier' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onInstanceStatusEvent: + '{$request.body#/appInstCallbackLink}': + post: + requestBody: + description: Notification payload. + content: + application/json: + schema: + type: object + required: + - federationContextId + - appId + - appInstanceId + - zoneId + - appInstanceInfo + properties: + federationContextId: + $ref: '#/components/schemas/FederationIdentifier' + appId: + $ref: '#/components/schemas/AppIdentifier' + appInstanceId: + $ref: '#/components/schemas/InstanceIdentifier' + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + appInstanceInfo: + type: object + properties: + appInstanceState: + type: string + enum: + - PENDING + - READY + - FAILED + - TERMINATING + description: Running status of the application instance. + message: + type: string + description: Event information or failure message. + accesspointInfo: + description: Information about the IP and Port exposed by the OP. Application clients shall use these access points to reach this application instance + type: array + items: + type: object + required: + - interfaceId + - accessPoints + properties: + interfaceId: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,30}[A-Za-z0-9]$ + description: This is the interface Identifier that app provider defines when application is onboarded. + accessPoints: + $ref: '#/components/schemas/ServiceEndpoint' + minItems: 1 + minProperties: 1 + modificationDate: + type: string + format: date-time + description: Date and time of the instance state modification by partner OP. + responses: + "204": + description: Application instance state notification acknowledged + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + + /{federationContextId}/application/lcm/app/{appId}/instance/{appInstanceId}/zone/{zoneId}: + get: + summary: Retrieves an application instance details from partner OP. + operationId: GetAppInstanceDetails + tags: + - ApplicationDeploymentManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + - name: appInstanceId + in: path + required: true + schema: + $ref: '#/components/schemas/InstanceIdentifier' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + responses: + "200": + description: Application instance details + content: + application/json: + schema: + type: object + properties: + appInstanceState: + $ref: '#/components/schemas/InstanceState' + accesspointInfo: + description: Information about the IP and Port exposed by the OP. Application clients shall use these access points to reach this application instance + type: array + items: + type: object + required: + - interfaceId + - accessPoints + properties: + interfaceId: + type: string + pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,30}[A-Za-z0-9]$ + description: This is the interface identifier that app provider defines when application is onboarded. + accessPoints: + $ref: '#/components/schemas/ServiceEndpoint' + minItems: 1 + minProperties: 1 + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + delete: + summary: Terminate an application instance on a partner OP zone. + operationId: RemoveApp + tags: + - ApplicationDeploymentManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + - name: appInstanceId + in: path + required: true + schema: + $ref: '#/components/schemas/InstanceIdentifier' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + responses: + "200": + description: Application instance termination request accepted + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/application/lcm/app/{appId}/appProvider/{appProviderId}: + get: + summary: Retrieves all application instance of partner OP + operationId: GetAllAppInstances + tags: + - ApplicationDeploymentManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appId + in: path + required: true + schema: + $ref: '#/components/schemas/AppIdentifier' + - name: appProviderId + in: path + required: true + schema: + $ref: '#/components/schemas/AppProviderId' + responses: + "200": + description: Application Instance details + content: + application/json: + schema: + type: array + items: + type: object + required: + - zoneId + - appInstanceInfo + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + appInstanceInfo: + type: array + items: + type: object + required: + - appInstIdentifier + - appInstanceState + properties: + appInstIdentifier: + $ref: '#/components/schemas/InstanceIdentifier' + appInstanceState: + $ref: '#/components/schemas/InstanceState' + minItems: 1 + minItems: 1 + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/isv/resource/zone/{zoneId}/appProvider/{appProviderId}: + post: + summary: Reserves resources (compute, network and storage) on a partner OP zone. ISVs registered with home OP reserves resources on a partner OP zone. + operationId: CreateResourcePools + tags: + - AppProviderResourceManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + - name: appProviderId + in: path + required: true + schema: + $ref: '#/components/schemas/AppProviderId' + requestBody: + content: + application/json: + schema: + type: object + required: + - resRequest + - resourceReservationCallbackLink + properties: + resRequest: + description: Compute flavours to be reserved and their time duration + type: object + required: + - poolName + - flavours + - reserveDuration + properties: + poolName: + $ref: '#/components/schemas/PoolName' + flavours: + type: array + items: + type: object + required: + - flavourId + - numFlavour + properties: + flavourId: + $ref: '#/components/schemas/FlavourId' + numFlavour: + type: integer + format: int32 + description: Total number of flavours to be reserved + minNumOfFlavours: + type: integer + format: int32 + description: If specified, indicate the minimum numbers of flavours to be reserved up to maximum as given in “count” member. If partner OP cannot reserve the minimum number of flavours, then the request shall fail. + minItems: 1 + reserveDuration: + $ref: '#/components/schemas/ResourceReservationDuration' + resourceReservationCallbackLink: + $ref: '#/components/schemas/Uri' + responses: + "200": + description: ISV Resource reservation request accepted + content: + application/json: + schema: + type: object + required: + - poolId + - poolName + properties: + poolName: + $ref: '#/components/schemas/PoolName' + + poolId: + $ref: '#/components/schemas/PoolId' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onResourceStatusChangeEvent: + '{$request.body#/resourceReservationCallbackLink}': + post: + requestBody: + description: Notification payload. + content: + application/json: + schema: + type: object + required: + - federationContextId + - zoneId + - appProviderId + - poolId + - grantedFlavours + properties: + federationContextId: + $ref: '#/components/schemas/FederationIdentifier' + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + poolId: + $ref: '#/components/schemas/PoolId' + grantedFlavours: + type: array + items: + type: object + required: + - flavourId + - numFlavour + properties: + flavourId: + $ref: '#/components/schemas/FlavourId' + numFlavour: + type: integer + format: int32 + description: Count of flavour + minItems: 1 + responses: + "204": + description: Updated Resource reservation status updated + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + + get: + summary: Retrieves the resource pool reserved by an ISV + operationId: ViewISVResPool + tags: + - AppProviderResourceManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + - name: appProviderId + in: path + required: true + schema: + $ref: '#/components/schemas/AppProviderId' + responses: + "200": + description: Reserved Resources Details + content: + application/json: + schema: + type: array + items: + type: object + required: + - poolName + - reservedPoolId + - reservedFlavours + properties: + poolName: + $ref: '#/components/schemas/PoolName' + reservedPoolId: + $ref: '#/components/schemas/PoolId' + reservedFlavours: + type: array + items: + type: object + required: + - flavourId + - count + properties: + flavourId: + $ref: '#/components/schemas/FlavourId' + count: + type: integer + format: int32 + description: Total number of flavours reserved + minItems: 1 + reserveDuration: + $ref: '#/components/schemas/ResourceReservationDuration' + reservationTime: + type: string + format: date-time + description: Date and time when resources were reserved in UTC format + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + /{federationContextId}/isv/resource/zone/{zoneId}/appProvider/{appProviderId}/pool/{poolId}: + patch: + summary: Updates resources reserved for a pool by an ISV + operationId: UpdateISVResPool + tags: + - AppProviderResourceManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + - name: appProviderId + in: path + required: true + schema: + $ref: '#/components/schemas/AppProviderId' + - name: poolId + in: path + required: true + schema: + $ref: '#/components/schemas/PoolId' + requestBody: + content: + application/json: + schema: + type: array + items: + type: object + required: + - updateType + - flavourId + - count + properties: + updateType: + type: string + enum: + - ADD + - REMOVE + - DURATION + description: Specify if resource corresponding this flavour needs to added or removed. Field 'count' gives the final total no of such flavours that should be reserved. count 0 means remove all the resources. + flavourId: + $ref: '#/components/schemas/FlavourId' + count: + type: integer + format: int32 + description: Total number of flavours to be reserved + reserveDuration: + $ref: '#/components/schemas/ResourceReservationDuration' + responses: + "200": + description: Resource pool updated + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + delete: + summary: Deletes the resource pool reserved by an ISV + operationId: RemoveISVResPool + tags: + - AppProviderResourceManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: zoneId + in: path + required: true + schema: + $ref: '#/components/schemas/ZoneIdentifier' + - name: appProviderId + in: path + required: true + schema: + $ref: '#/components/schemas/AppProviderId' + - name: poolId + in: path + required: true + schema: + $ref: '#/components/schemas/PoolId' + responses: + "200": + description: Resource pool deleted + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/edgenodesharing/edgeDiscovery: + post: + summary: Edge discovery procedures towards partner OP over E/WBI. Originating OP request partner OP to provide a list of candidate zones where an application instance can be created. Partner OP applies a set of filtering criteria's to select candidate zones. + operationId: GetCandidateZones + tags: + - EdgeNodeSharing + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + content: + application/json: + schema: + type: object + required: + - appProviderId + - appId + properties: + appProviderId: + $ref: '#/components/schemas/AppProviderId' + appId: + $ref: '#/components/schemas/AppIdentifier' + edgeDiscoveryFilters: + type: object + minProperties: 1 + properties: + location: + $ref: '#/components/schemas/ClientLocation' + responses: + "200": + description: List of candidate zones + content: + application/json: + schema: + $ref: '#/components/schemas/nodeDiscoveryResponse' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/apiservice/{serviceAPINameVal}: + post: + summary: Service API request forwarding to the Partner OP + operationId: APIForwarding + tags: + - ServiceAPIManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + + - name: serviceAPINameVal + in: path + required: true + schema: + $ref: '#/components/schemas/serviceAPINameVal' + requestBody: + content: + application/json: + schema: + type: object + required: + - apiServiceId + - customerID + - customerInfo + - txnIdentifier + - ServiceAPIBody + properties: + customerID: + $ref: '#/components/schemas/customerID' + txnIdentifier: + $ref: '#/components/schemas/txnIdentifier' + ServiceAPIBody: + $ref: '#/components/schemas/serviceAPIContent' + eventNotificationDest: + $ref: '#/components/schemas/Uri' + responses: + '200': + description: Service API request accepted + headers: + Location: + description: Contains the URI of the newly created Service API Context resource. + required: false + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/serviceAPIResponse' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + default: + $ref: '#/components/responses/default' + callbacks: + onServiceAPISessionEvent: + '{$request.body#/eventNotificationDest}': + post: + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: apiServiceId + in: path + required: true + schema: + $ref: '#/components/schemas/serviceAPINames' + requestBody: + description: Notification about network event. + content: + application/json: + schema: + type: object + required: + - txnIdentifier + - serviceAPIEvent + properties: + serviceAPIEvent: + $ref: '#/components/schemas/serviceAPINetworkEvent' + txnIdentifier: + $ref: '#/components/schemas/txnIdentifier' + responses: + '200': + description: Event info notification acknowledged + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + + /{federationContextId}/apiservice/connid/{connectID}/custid/{customerID}: + delete: + summary: Remove the Service API Session earlier created with Service API forwarding request. + operationId: RemoveServiceAPISession + tags: + - ServiceAPIManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: connectID + in: path + required: true + schema: + $ref: '#/components/schemas/connectID' + - name: customerID + in: path + required: true + schema: + $ref: '#/components/schemas/customerID' + + responses: + '200': + description: Service API Session removed successfully + content: + application/json: + schema: + type: object + required: + - expiryDuration + - connectID + properties: + expiryDuration: + $ref: '#/components/schemas/expiryInterval' + connectID: + $ref: '#/components/schemas/connectID' + '400': + $ref: '#/components/responses/400' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404NotFound' + '409': + $ref: '#/components/responses/409' + '422': + $ref: '#/components/responses/422' + '500': + $ref: '#/components/responses/500' + '503': + $ref: '#/components/responses/503' + '520': + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + get: + summary: Retrieve the Service API context information of an existing API session identified by connectID, customerID + operationId: GetServiceAPISessionInfo + tags: + - ServiceAPIManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: connectID + in: path + required: true + schema: + $ref: '#/components/schemas/connectID' + - name: customerID + in: path + required: true + schema: + $ref: '#/components/schemas/customerID' + + responses: + "200": + description: Device Auth Token validated + content: + application/json: + schema: + type: object + required: + - expiryDuration + - connectID + properties: + expiryDuration: + $ref: '#/components/schemas/expiryInterval' + connectID: + $ref: '#/components/schemas/connectID' + ServiceAPIRespBody: + $ref: '#/components/schemas/serviceAPIContent' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/monioring-subscriptions: + post: + summary: Originating OP subscribe for edge cloud resource monitoring info with partner OP. + operationId: SubscribeMonitoringInfo + tags: + - ConsumptionReportingManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: monType + in: query + required: true + schema: + $ref: '#/components/schemas/monitoringSubsType' + requestBody: + content: + application/json: + schema: + type: object + properties: + periodicity: + $ref: '#/components/schemas/periodicityInterval' + resMonNotificationListner: + $ref: '#/components/schemas/Uri' + + responses: + "200": + description: Subscription for resource monitoring created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/resourceSubscriptionInfo' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onPeriodicMonitoringEvent: + '{$request.body#/resMonNotificationListner}': + post: + requestBody: + description: Periodic Notification about resource monitoring info. + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/edgeResUtilizeMetrics' + - $ref: '#/components/schemas/appsResUtilizeInfo' + responses: + "200": + description: Resource monitoring info notification acknowledged + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + + /{federationContextId}/events: + post: + summary: Originating OP uses this procedure to request enabling event reporting with Partner OP. + operationId: CreateEventSubscription + tags: + - EventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + content: + application/json: + schema: + type: object + properties: + eventSubscriptionConfig: + $ref: '#/components/schemas/EventSubscription' + responses: + "200": + description: Subscription for reporting of events created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/EventSubscriptionInfo' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onEventCriterionDetectionEvent: + '{$request.body#/eventListner}': + post: + requestBody: + description: Notification about event being detected as per event criterion. + content: + application/json: + schema: + $ref: '#/components/schemas/EventsList' + responses: + "200": + description: Event report acknowledged + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + + /{federationContextId}/events/{event_subs_id}: + post: + summary: Originating OP uses this procedure to create an event criterion at Partner OP. + operationId: CreateEventCriterion + tags: + - EventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: event_subs_id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + type: object + properties: + eventCriterion: + $ref: '#/components/schemas/eventCriterion' + responses: + "200": + description: Subscription for resource monitoring created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/eventInfo' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + get: + summary: Retrieves events list with the partner OP. + operationId: GetEventsList + tags: + - EventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: event_subs_id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: event_type + in: query + required: false + schema: + type: string + enum: + - event_criterion + - event_id + responses: + "200": + description: Events criterion and detected events report request accepted + content: + application/json: + schema: + type: object + properties: + eventCriterionList: + $ref: '#/components/schemas/eventTypeList' + eventIdList: + $ref: '#/components/schemas/EventsList' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + delete: + summary: Remove existing event subscription with the partner OP + operationId: DeleteEventSubscription + tags: + - EventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: event_subs_id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + + responses: + "200": + description: Event subscription removed successfully + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/events/{event_subs_id}/event-id/{eventId}: + delete: + summary: Remove existing event criterion with the partner OP + operationId: DeleteEventCriterion + tags: + - EventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: event_subs_id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: eventId + in: path + required: true + schema: + $ref: '#/components/schemas/EventIdentifier' + responses: + "200": + description: Event criterion removed successfully + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/alarms: + post: + summary: Originating OP uses this procedure to request enabling alarm reporting with Partner OP. + operationId: CreateAlarmReportingSubscription + tags: + - AlarmManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + content: + application/json: + schema: + type: object + properties: + alarmListnerCallback: + $ref: '#/components/schemas/Uri' + + responses: + "200": + description: Subscription for alarm reporting created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/SubscriptionIdentifier' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onAlarmStateReportEvent: + '{$request.body#/alarmListnerCallback}': + post: + requestBody: + description: Notification about alarm management events at Partner OP. + content: + application/json: + schema: + $ref: '#/components/schemas/AlarmObjectInfo' + responses: + "200": + description: Event report acknowledged + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + + delete: + requestBody: + description: Alarm clear notification for an earlier alarm by Partner OP. + content: + application/json: + schema: + $ref: '#/components/schemas/AlarmObjectInfo' + responses: + "200": + description: Alarm clear event acknowledged + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + + patch: + requestBody: + description: Notification about alarm management events at Partner OP. + content: + application/json: + schema: + $ref: '#/components/schemas/UpdatedAlarmParameters' + responses: + "200": + description: Alarm state update report acknowledged + content: + application/json: + schema: + type: object + properties: + updatedAlarmId: + $ref: '#/components/schemas/AlarmIdentifier' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + + + /{federationContextId}/events/{alarm_subs_id}: + get: + summary: Retrieves active alarms list with the partner OP. + operationId: GetAlarmsList + tags: + - AlarmManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: alarm_subs_id + in: path + required: true + schema: + $ref: '#/components/schemas/SubscriptionIdentifier' + - name: alarm_type + in: query + required: false + schema: + $ref: '#/components/schemas/AlarmType' + responses: + "200": + description: Active alarms report request accepted + content: + application/json: + schema: + type: object + properties: + activeAlarmsList: + $ref: '#/components/schemas/ActiveAlarmsList' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + delete: + summary: Remove existing alarm subscription with the partner OP + operationId: DeleteAlarmSubscription + tags: + - AlarmManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: alarm_subs_id + in: path + required: true + schema: + $ref: '#/components/schemas/SubscriptionIdentifier' + + responses: + "200": + description: Alarm subscription removed successfully + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + + /{federationContextId}/network-caps-events: + post: + summary: Originating OP uses this procedure to request enabling network capabilities events reporting by the Partner OP. + operationId: CreateNetworkCapsEventSubscription + tags: + - NetworkCapsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + content: + application/json: + schema: + type: object + required: + - networkCapsEventSubscriptionConfig + properties: + networkCapsEventSubscriptionConfig: + $ref: '#/components/schemas/periodicNotifConfig' + responses: + "200": + description: Subscription for notification of network events created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/periodicNotifConfig' + headers: + Location: + description: 'Contains the URI of the newly created resource' + required: true + schema: + type: string + Accept-Encoding: + description: Accept-Encoding, described in IETF RFC 7694 + schema: + type: string + Content-Encoding: + description: Content-Encoding, described in IETF RFC 7231 + schema: + type: string + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onNetwEventDetectionEvent: + '{$request.body#/notificationListner}': + post: + requestBody: + description: Notification about events being detected as per network capabilities are applied by Partner OP. + content: + application/json: + schema: + $ref: '#/components/schemas/NetworkCapAppInfoList' + responses: + "200": + description: Network Events report acknowledged + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + + /{federationContextId}/network-events/{nw-event-subs-id}: + post: + summary: Originating OP uses this procedure to add an intent to Partner OP to report network capability applied by Partner OP. + operationId: CreateNetworkCapEvent + tags: + - NetworkCapsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: nw-event-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: nw-cap-id + in: query + required: true + schema: + $ref: '#/components/schemas/CapabilityID' + + requestBody: + content: + application/json: + schema: + type: object + required: + - appId + - appProviderId + properties: + appId: + $ref: '#/components/schemas/AppIdentifier' + appProviderId: + $ref: '#/components/schemas/AppProviderId' + txnIdentifier: + $ref: '#/components/schemas/txnIdentifier' + + responses: + "200": + description: Subscription for network event created successfully + content: + application/json: + schema: + type: object + required: + - networkCapSubsInfo + - txnIdentifier + properties: + networkCapSubsInfo: + $ref: '#/components/schemas/NetworkCapSubsInfo' + txnIdentifier: + $ref: '#/components/schemas/txnIdentifier' + + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + delete: + summary: Remove existing network events notification subscription with the partner OP + operationId: DeleteNwEventNotifSubscription + tags: + - NetworkCapsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: nw-event-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + responses: + "200": + description: Network Event Notification subscription removed successfully + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/network-events/{nw-event-subs-id}/nw-caps: + get: + summary: Retrieves network capabilities subscribed list with the partner OP. + operationId: GetNetworkCapsSubscribedList + tags: + - NetworkCapsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: nw-event-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: nw-event-type + in: query + required: true + schema: + type: string + responses: + "200": + description: Network capabilities subscription list returned successfully + content: + application/json: + schema: + type: object + properties: + subscribedNwCaps: + type: array + items: + $ref: '#/components/schemas/NetworkCapSubsInfo' + minItems: 1 + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + delete: + summary: Remove existing network event notification with the partner OP + operationId: DeleteNetworkCapSubscription + tags: + - NetworkCapsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: nw-event-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: nw-event-id + in: query + required: true + schema: + type: string + responses: + "200": + description: Network Event subscription removed successfully + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + + /{federationContextId}/appl-event-notifications: + post: + summary: Originating OP uses this procedure to Subscribe for Application's Events Notifications. + operationId: CreateApplicationEventSubscription + tags: + - ApplicationsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + requestBody: + content: + application/json: + schema: + type: object + required: + - applicationEventSubscriptionConfig + properties: + applEventSubscriptionConfig: + $ref: '#/components/schemas/periodicNotifConfig' + numEvtsPerNotif: + type: integer + description: The number of applications events that the Partner OP should include in a notification + responses: + "200": + description: Subscription for notification of network events created successfully + content: + application/json: + schema: + type: object + properties: + appEventSubsId: + type: string + applEventSubscriptionConfig: + $ref: '#/components/schemas/periodicNotifConfig' + numEvtsPerNotif: + type: integer + description: The number of applications events that the Partner OP should include in a notification + headers: + Location: + description: 'Contains the URI of the newly created resource' + required: false + schema: + type: string + Accept-Encoding: + description: Accept-Encoding, described in IETF RFC 7694 + schema: + type: string + Content-Encoding: + description: Content-Encoding, described in IETF RFC 7231 + schema: + type: string + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + callbacks: + onApplEventDetectionEvent: + '{$request.body#/notificationListner}': + post: + requestBody: + description: Notification about applications LCM events being detected by Partner OP. + content: + application/json: + schema: + $ref: '#/components/schemas/AggrApplEventsList' + responses: + "200": + description: Applications events notification acknowledged + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + security: + - notifClientCredentials: [fed-mgmt-notif] + + + /{federationContextId}/appl-event-notifications/{app-notif-subs-id}: + post: + summary: Originating OP uses this procedure to add applications for reporting of application events by Partner OP. + operationId: SubscribeApplsEvtNotif + tags: + - ApplicationsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: app-notif-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: Idempotency-Key + in: header + required: true + schema: + $ref: '#/components/schemas/TransactionId' + + requestBody: + content: + application/json: + schema: + type: object + required: + - addAppsForNotif + properties: + addAppsForNotif: + $ref: '#/components/schemas/AddAppsForNotif' + + responses: + "200": + description: Subscription for network event created successfully + content: + application/json: + schema: + type: object + properties: + addAppsForNotif: + $ref: '#/components/schemas/AddAppsForNotif' + + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + delete: + summary: Remove existing application notification subscription with the partner OP + operationId: DeleteApplNotifSubscription + tags: + - ApplicationsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: app-notif-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + responses: + "200": + description: Application Event Notifications subscription removed successfully + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + patch: + summary: Modify existing application events notification subscription with the partner OP + operationId: ModifyApplEventNotifSubscription + tags: + - ApplicationsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: app-notif-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + type: object + properties: + applEventSubscriptionConfig: + $ref: '#/components/schemas/periodicNotifConfig' + numEvtsPerNotif: + type: integer + description: The number of applications events that the Partner OP should include in a notification + responses: + "200": + description: Event Notification subscription modified successfully + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + get: + summary: Originating OP uses this procedure to retrieve subscription meta-information about application-level notifications. + operationId: RetrieveApplSubsMetaInfo + tags: + - ApplicationsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: app-notif-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: info-type + in: query + required: true + schema: + type: string + enum: + - subs-info + - apps-info + + responses: + "200": + description: Application events Subscription information successful retrieval + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/ApplEventsSubsInfo' + - $ref: '#/components/schemas/ApplEventsSubsInfo' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/appl-event-notifications/{app-notif-subs-id}/cancel: + post: + summary: Remove applications from the reporting of application-level event notifications. + operationId: RemoveAppsEventSubscription + tags: + - ApplicationsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: app-notif-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RemoveAppsForNotif' + responses: + "200": + description: Network capabilities subscription list returned successfully + content: + application/json: + schema: + $ref: '#/components/schemas/RemoveAppsForNotif' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/appl-event-notifications/{app-notif-subs-id}/app-events: + post: + summary: Remove applications from the reporting of application-level event notifications. + operationId: RetrieveAppsEventsInfo + tags: + - ApplicationsEventManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: app-notif-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/AppsForNotif' + minItems: 1 + responses: + "200": + description: Network capabilities subscription list returned successfully + content: + application/json: + schema: + $ref: '#/components/schemas/AggrApplEventsList' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + + /{federationContextId}/app-policies-subscription: + post: + summary: Originating OP uses this procedure to Subscribe for Application's policy capability at Partner OP. + operationId: CreateApplicationPolicySubscription + tags: + - ApplicationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + responses: + "200": + description: Subscription for application policy management created successfully + content: + application/json: + schema: + type: object + properties: + applPolicySubscriptionId: + type: string + headers: + Location: + description: 'Contains the URI of the newly created resource' + required: false + schema: + type: string + Accept-Encoding: + description: Accept-Encoding, described in IETF RFC 7694 + schema: + type: string + Content-Encoding: + description: Content-Encoding, described in IETF RFC 7231 + schema: + type: string + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/appl-policies-subscription/{appl-policy-subs-id}/app-policy-templates: + get: + summary: Originating OP uses this procedure to retrieve application policy templates from Partner OP. + operationId: RetrieveAppPolicyTemplates + tags: + - ApplicationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appl-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: appl-policy-type + in: query + required: false + schema: + $ref: '#/components/schemas/ApplPolicyType' + + responses: + "200": + description: Successfully retrieved application policy templates + content: + application/json: + schema: + type: object + properties: + applPolicyTemplateList: + $ref: '#/components/schemas/ApplPolicyTemplateList' + + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/appl-policies-subscription/{appl-policy-subs-id}/app-policy-registration: + post: + summary: Register an application-level policy with the partner OP + operationId: RegisterApplicationPolicy + tags: + - ApplicationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appl-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + type: object + properties: + applConcretePolicy: + $ref: '#/components/schemas/ApplConcretePolicy' + responses: + "200": + description: Application policy registered successfully + content: + application/json: + schema: + type: object + required: + - pplConcretePolicy + - policyId + properties: + pplConcretePolicy: + $ref: '#/components/schemas/ApplConcretePolicy' + policyId: + type: string + + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + + /{federationContextId}/app-policies-subscription/{appl-policy-subs-id}: + post: + summary: Origination OP uses this procedure to apply application-level policies to federated applications at Partner OP. + operationId: ApplyApplicationPolicy + tags: + - ApplicationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appl-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AssocApplPolicies' + responses: + "200": + description: Application Policy processed successfully + content: + application/json: + schema: + $ref: '#/components/schemas/AssocApplPolicies' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + get: + summary: Origination OP uses this procedure to retrieve application-level policies to federated applications at Partner OP. + operationId: RetrieveApplicationPolicy + tags: + - ApplicationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appl-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: policy-search-type + in: query + required: false + schema: + type: string + enum: + - app-prov-id + - app-id + - name: policy-search-value + in: query + required: false + schema: + type: string + description: Refers to either application provider identifier or the application identifier + + responses: + "200": + description: Application Policy list retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/AssocApplPolicies' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + patch: + summary: Modify application-level policy associated with federated applications with the partner OP + operationId: ModifyApplicationPolicy + tags: + - ApplicationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appl-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AssocApplPolicies' + responses: + "200": + description: Application policies modified successfully + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/app-policies-subscription/{appl-policy-subs-id}/app-policy-cancel: + post: + summary: Remove applications from federated applications at Partner OP. + operationId: RemoveApplicationPolicies + tags: + - ApplicationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: appl-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AssocApplPolicies' + responses: + "200": + description: Successfully removed application policies + content: + application/json: + schema: + $ref: '#/components/schemas/AssocApplPolicies' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/ops-policies-subscription: + post: + summary: Originating OP uses this procedure to Subscribe for Operation's policy capability at Partner OP. + operationId: CreateOperationPolicySubscription + tags: + - OperationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + responses: + "200": + description: Subscription for operation's policy management created successfully + content: + application/json: + schema: + type: object + properties: + opslPolicySubscriptionId: + type: string + headers: + Location: + description: 'Contains the URI of the newly created resource' + required: false + schema: + type: string + Accept-Encoding: + description: Accept-Encoding, described in IETF RFC 7694 + schema: + type: string + Content-Encoding: + description: Content-Encoding, described in IETF RFC 7231 + schema: + type: string + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/ops-policies-subscription/{ops-policy-subs-id}/ops-policy-templates: + get: + summary: Originating OP uses this procedure to retrieve operations policy templates from Partner OP. + operationId: RetrieveOpsPolicyTemplates + tags: + - OperationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: ops-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: ops-policy-type + in: query + required: false + schema: + $ref: '#/components/schemas/OpsPolicyType' + + responses: + "200": + description: Successfully retrieved operations policy templates + content: + application/json: + schema: + type: object + properties: + opsPolicyTemplateList: + $ref: '#/components/schemas/OpsPolicyTemplateList' + + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/ops-policies-subscription/{ops-policy-subs-id}/ops-policy-registration: + post: + summary: Register an operation-level policy with the partner OP + operationId: RegisterOperationPolicy + tags: + - OperationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: ops-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + type: object + properties: + opsConcretePolicy: + $ref: '#/components/schemas/OpsConcretePolicy' + responses: + "200": + description: Operations policy registered successfully + content: + application/json: + schema: + type: object + required: + - opsConcretePolicy + - policyId + properties: + opsConcretePolicy: + $ref: '#/components/schemas/OpsConcretePolicy' + policyId: + type: string + + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + + /{federationContextId}/ops-policies-subscription/{ops-policy-subs-id}/policy-association: + post: + summary: Origination OP uses this procedure to apply application-level policies to federated applications at Partner OP. + operationId: ApplyOperationPolicy + tags: + - OperationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: ops-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AssocOpsPolicies' + responses: + "200": + description: Operation Policy processed successfully + content: + application/json: + schema: + $ref: '#/components/schemas/AssocOpsPolicies' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + get: + summary: Origination OP uses this procedure to retrieve application-level policies to federated applications at Partner OP. + operationId: RetrieveOperationPolicy + tags: + - OperationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: ops-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + - name: policy-search-type + in: query + required: false + schema: + type: string + enum: + - zone-id + - name: policy-search-value + in: query + required: false + schema: + type: string + description: Refers to availability zone identifier + + responses: + "200": + description: Operations Policy list retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/AssocOpsPolicies' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + + patch: + summary: Modify operation-level policy associated with federated applications with the Partner OP + operationId: ModifyOperationPolicy + tags: + - OperationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: ops-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AssocOpsPolicies' + responses: + "200": + description: Application policies modified successfully + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' + + /{federationContextId}/ops-policies-subscription/{ops-policy-subs-id}/ops-policy-cancel: + post: + summary: Remove applications from federated applications at Partner OP. + operationId: RemoveOperationPolicies + tags: + - OperationPolicyManagement + parameters: + - name: federationContextId + in: path + required: true + schema: + $ref: '#/components/schemas/FederationContextId' + - name: ops-policy-subs-id + in: path + required: true + schema: + $ref: '#/components/schemas/EventSubscriptionIdentifier' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AssocOpsPolicies' + responses: + "200": + description: Successfully removed operation policies + content: + application/json: + schema: + $ref: '#/components/schemas/AssocOpsPolicies' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "404": + $ref: '#/components/responses/404NotFound' + "409": + $ref: '#/components/responses/409' + "422": + $ref: '#/components/responses/422' + "500": + $ref: '#/components/responses/500' + "503": + $ref: '#/components/responses/503' + "520": + $ref: '#/components/responses/520' + default: + $ref: '#/components/responses/default' diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000000000000000000000000000000000..311d078ebbe40e9d66ef6806f72aa8323df0579f --- /dev/null +++ b/docs/README.md @@ -0,0 +1,13 @@ +# EWBI contract artifacts + +- `OPG.04-v6.0-EWBI-Federation-API-v1.4.0.yaml` — GSMA's published OpenAPI, vendored byte-for-byte + (sha256 `890801d6…`). Never edited. The contract checker compares FM against this file. +- `opg04-v1.4.0-oop-profile.overlay.yaml` — [OpenAPI Overlay 1.0.0](https://spec.openapis.org/overlay/v1.0.0.html) + document recording every known defect of the published artifact, each corrected from OPG.04 + v6.0 prose and cited by clause/table. This is the only place a deviation may be introduced. +- `OPG.04-v6.0-EWBI-Federation-API-v1.4.0-oop-profile.yaml` — generated result of base + overlay. + Parses in Swagger Editor and other OpenAPI tooling. Regenerate after changing the overlay: + +```bash +.venv/bin/python scripts/apply_overlay.py +``` diff --git a/docs/opg04-v1.4.0-oop-profile.overlay.yaml b/docs/opg04-v1.4.0-oop-profile.overlay.yaml new file mode 100644 index 0000000000000000000000000000000000000000..297631707fe69879d58bf1c138dd68010e03b65f --- /dev/null +++ b/docs/opg04-v1.4.0-oop-profile.overlay.yaml @@ -0,0 +1,117 @@ +overlay: 1.0.0 +info: + title: OOP profile for GSMA OPG.04 v6.0 EWBI Federation API v1.4.0 + version: 0.1.0 + description: | + Recorded defects in the published GSMA artifact, corrected from OPG.04 v6.0 prose. + The vendored artifact stays byte-identical; scripts/apply_overlay.py produces the + rendered profile. Every action cites the OPG.04 clause or table that settles it. + x-base-sha256: 890801d61c148762897f18ce3b88823c0d486b1defdd04227f6a65f97c62fccf +extends: ./OPG.04-v6.0-EWBI-Federation-API-v1.4.0.yaml +actions: + # --- Structural defects: the artifact does not parse in OpenAPI tooling --- + - target: $.components.schemas.serviceAPIContent.properties.APIContent + description: Placeholder $ref 'https://github.com/camaraproject' is not a resolvable schema. + remove: true + - target: $.components.schemas.serviceAPIContent.properties + description: >- + OPG.04 §4.2.1.6.2.1 Table 196: APIContent is the Service API body as received over the NBI, + whose schema is the CAMARA API named by serviceAPINameVal; opaque at the EWBI layer. + update: + APIContent: + type: object + additionalProperties: true + - target: $.components.schemas.serviceAPIEventDef.properties.NetworkEventDef + description: Placeholder $ref 'https://github.com/camaraproject' is not a resolvable schema. + remove: true + - target: $.components.schemas.serviceAPIEventDef.properties + description: >- + Network event payload defined by the CAMARA API in question; opaque at the EWBI layer. + update: + NetworkEventDef: + type: object + additionalProperties: true + - target: $.paths['/{federationContextId}/application/onboarding/app/{appId}/zoneForbid'].post.requestBody.content['application/json'].schema.required + description: LockUnlockApplicationZone declares required/properties on the array instead of its items. + remove: true + - target: $.paths['/{federationContextId}/application/onboarding/app/{appId}/zoneForbid'].post.requestBody.content['application/json'].schema.properties + description: See previous action. + remove: true + - target: $.paths['/{federationContextId}/application/onboarding/app/{appId}/zoneForbid'].post.requestBody.content['application/json'].schema.items + description: Same zoneId/forbid definition, moved under items where OpenAPI expects it. + update: + required: + - zoneId + - forbid + properties: + zoneId: + $ref: '#/components/schemas/ZoneIdentifier' + forbid: + type: boolean + description: Value 'true' will forbid application instantiation on this zone. No new instance of the application can be created on this zone. + + # --- Semantic defects: artifact contradicts OPG.04 prose --- + - target: $.paths['/{federationContextId}/apiservice/{serviceAPINameVal}'].post.requestBody.content['application/json'].schema.properties + description: >- + APIForwarding lists apiServiceId and customerInfo as required but defines neither. + OPG.04 §4.2.1.2 Table 186 and §4.2.1.6.3.1 Table 201 define both as String. + update: + apiServiceId: + type: string + description: Named identifier of the API service, e.g. QualityOnDemand, DeviceStatus, DeviceLocation (Table 201). + customerInfo: + type: string + description: Name identification information associated to the Application Provider of the Leading OP (Table 201). + - target: $.paths['/{federationContextId}/apiservice/{serviceAPINameVal}'].post.requestBody + description: OPG.04 §4.2.1.2 Table 186 marks the request data mandatory. + update: + required: true + - target: $.paths['/{federationContextId}/application/lcm'].post.requestBody + description: OPG.04 §4.1.4.2 Table 159 marks the InstallApp request data mandatory. + update: + required: true + - target: $.components.schemas.serviceAPIResponse.properties.apiResponse + description: apiResponse wrongly references the customerID (UUID) schema. + remove: true + - target: $.components.schemas.serviceAPIResponse.properties + description: OPG.04 §4.2.1.6.2.5 Table 200 defines apiResponse as {mediaType, responseContent}. + update: + apiResponse: + type: object + required: + - mediaType + - responseContent + properties: + mediaType: + type: string + description: May contain value e.g. "application/json". + responseContent: + type: object + additionalProperties: true + description: Result of the Service API processing, formatted according to mediaType and defined by the Service API specification. + - target: $.components.schemas.FederationHealthInfo.properties.federationStatus + description: >- + federationStatus references State, the alarm-lifecycle object {alarmState}. OPG.04 + §3.1.1.12.2.12 Table 41 defines it as the federation Status enum, and the + onPartnerStatusEvent callback in this same document already uses Status for the + identically named field. + update: + $ref: '#/components/schemas/Status' + + - target: $.components.schemas.serviceAPIResponse.required + description: >- + The artifact requires both targetUserContext and apiResponse; OPG.04 §4.2.1.6.2.2 Table 197 + makes targetUserContext conditional (session-based APIs) and apiResponse conditional + (sessionless APIs). + remove: true + - target: $.components.schemas.serviceAPIResponse + description: See previous action; at least one of the two conditional members must be present. + update: + required: + - customerID + - txnIdentifier + anyOf: + - required: + - targetUserContext + - required: + - apiResponse diff --git a/docs/running-srm.md b/docs/running-srm.md new file mode 100644 index 0000000000000000000000000000000000000000..76836d1b900ba45bcb093f00e6e986cea3e8ef18 --- /dev/null +++ b/docs/running-srm.md @@ -0,0 +1,52 @@ +# Running SRM locally for the FM ↔ SRM loop test + +`tests/integration/test_fm_srm_loop.py` drives a real deploy: FM publishes +`command.srm.service.deploy`, SRM consumes it and publishes +`event.srm.operation.completed`, and FM finalises the federation transaction on that event. +It **skips** unless SRM answers at `FM_SRM_URL` (default `http://127.0.0.1:8081`). + +## Which SRM + +`develop` is not enough: its DataBus router is a no-op. Use a branch with real command +handling, currently `origin/feat/location-retrieval-api`. + +```bash +cd ../service-resource-manager +git worktree add ../srm-loop origin/feat/location-retrieval-api +cd ../srm-loop && make install DEV=true +``` + +## Configuration + +SRM reads nested env vars (`__` delimiter). Point it at the same Postgres and NATS this +repo's `docker-compose.dev.yaml` starts, using its own database: + +```bash +docker compose -f ../federation-manager/docker-compose.dev.yaml up -d postgres nats keycloak +docker exec fm-postgres psql -U fm -d postgres -c 'CREATE DATABASE srm_db' + +export APP_NAME="Service Resource Manager" APP_VERSION=1.5.0 APP_DESCRIPTION=SRM +export POSTGRES_SETTINGS__URL="postgresql+asyncpg://fm:fm@localhost:5433/srm_db" +export POSTGRES_SETTINGS__ECHO=false POSTGRES_SETTINGS__CREATE_SCHEMA_ON_STARTUP=true +export NATS_SETTINGS__URL="nats://localhost:4222" NATS_SETTINGS__CONNECT_TIMEOUT=10 +export NATS_SETTINGS__MAX_RECONNECT_ATTEMPTS=3 NATS_SETTINGS__DRAIN_TIMEOUT=5 + +uv run uvicorn srm.main:create_app --factory --host 127.0.0.1 --port 8081 +``` + +The app is a factory (`create_app`), not a module-level `app`. Run it from its own +directory: the Sunrise SDK it imports writes a `.log/` folder into the working directory. + +## What the test asserts, and what it does not + +It asserts the transaction reaches a terminal state, not that the deployment succeeded. +With an empty catalog entry and a zone SRM has never heard of, SRM answers +`failed_before_start` and the transaction ends `failed`. That still exercises every hop. +Getting `completed` needs SRM-side data this repo does not own: deployment units on the +service specification, and the target zone present in SRM's inventory. + +## Known transport gap + +SRM subscribes with core NATS, not a JetStream durable consumer. A command published while +SRM is down is never seen, nothing is ever acked, and two SRM replicas would each run every +deploy. Raise before relying on this in a cluster. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..90a00091c3030180c9bec694185d861ee31787cf --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,61 @@ +[build-system] +requires = ["setuptools>=61"] +build-backend = "setuptools.build_meta" + +[project] +name = "federation-manager" +version = "2.0.0" +description = "Federation Manager (OOP Release 2.0)" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "asyncpg>=0.30", + "fastapi[standard]>=0.115", + "httpx>=0.27", + "nats-py>=2.6", + "pydantic-settings>=2.4", + "pyjwt[crypto]>=2.9", + "sqlalchemy[asyncio]>=2.0", +] + +[project.optional-dependencies] +dev = [ + "mypy>=1.11", + "pytest>=8.0", + "pytest-asyncio>=0.24", + "pyyaml>=6.0", + "ruff>=0.6", +] + +[tool.setuptools.packages.find] +where = ["src"] +include = ["federation_manager*"] + +[tool.ruff] +line-length = 100 +target-version = "py312" +# Legacy Connexion code under src/ is being replaced; only lint the new package. +# TODO: drop extend-exclude once the old src/ tree is removed at parity. +extend-exclude = [ + "src/adapters", "src/api", "src/clients", "src/conf", "src/deploy", + "src/models", "src/static", "src/swagger", "src/templates", "src/test", + "src/encoder.py", "src/init.py", "src/main.py", "src/type_util.py", + "src/util.py", "src/validator.py", "src/wsgi.py", +] + +[tool.ruff.lint] +select = ["E", "W", "F", "I"] + +[tool.mypy] +python_version = "3.12" +strict = true +ignore_missing_imports = true +files = ["src/federation_manager", "tests"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +asyncio_mode = "auto" +pythonpath = ["."] +markers = [ + "integration: requires a running Postgres (docker compose -f docker-compose.dev.yaml up -d)", +] diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..df8df96352d78647fc2685946c9405e22075dcca --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,22 @@ +{ + "typeshedPath": "/home/sergio/.local/share/nvim/mason/packages/pyright/node_modules/pyright/dist/typeshed-fallback", + "venvPath": ".", + "venv": ".venv", + "pythonVersion": "3.12", + "include": ["src/federation_manager", "tests"], + "exclude": [ + ".venv", + "**/__pycache__", + "src/adapters", + "src/api", + "src/clients", + "src/conf", + "src/deploy", + "src/models", + "src/static", + "src/swagger", + "src/templates", + "src/test" + ], + "extraPaths": ["src"] +} diff --git a/scripts/apply_overlay.py b/scripts/apply_overlay.py new file mode 100644 index 0000000000000000000000000000000000000000..9a5732db6f4b70e78388b8cff0342437179cd25c --- /dev/null +++ b/scripts/apply_overlay.py @@ -0,0 +1,100 @@ +import argparse +import hashlib +import re +import sys +from pathlib import Path +from typing import Any + +import yaml + +DOCS = Path(__file__).resolve().parents[1] / "docs" +BASE = DOCS / "OPG.04-v6.0-EWBI-Federation-API-v1.4.0.yaml" +OVERLAY = DOCS / "opg04-v1.4.0-oop-profile.overlay.yaml" +OUT = DOCS / "OPG.04-v6.0-EWBI-Federation-API-v1.4.0-oop-profile.yaml" + +# Singular JSONPath only: $.name, $['quoted name'], chained. +SEGMENT = re.compile(r"\.([A-Za-z_$][A-Za-z0-9_$-]*)|\['((?:[^'\\]|\\.)*)'\]") + + +def parse_target(expression: str) -> list[str]: + if not expression.startswith("$"): + raise ValueError(f"target must start with '$': {expression}") + keys: list[str] = [] + position = 1 + while position < len(expression): + match = SEGMENT.match(expression, position) + if match is None: + raise ValueError(f"unsupported JSONPath (singular member paths only): {expression}") + keys.append(match.group(1) if match.group(1) is not None else match.group(2)) + position = match.end() + if not keys: + raise ValueError(f"target selects the whole document: {expression}") + return keys + + +def merge(target: Any, update: Any) -> None: + if isinstance(target, dict) and isinstance(update, dict): + for key, value in update.items(): + if isinstance(target.get(key), dict) and isinstance(value, dict): + merge(target[key], value) + else: + target[key] = value + elif isinstance(target, list): + target.append(update) + else: + raise ValueError("update target must be an object or an array") + + +def apply(document: dict[str, Any], overlay: dict[str, Any]) -> int: + if overlay.get("overlay") != "1.0.0": + raise ValueError("only Overlay Specification 1.0.0 documents are supported") + for number, action in enumerate(overlay["actions"], start=1): + keys = parse_target(action["target"]) + parent: Any = document + for key in keys[:-1]: + parent = parent[key] + last = keys[-1] + if last not in parent: + raise KeyError(f"action {number}: {action['target']} not found in base document") + if action.get("remove", False): + del parent[last] + elif "update" in action: + merge(parent[last], action["update"]) + else: + raise ValueError(f"action {number}: needs 'update' or 'remove'") + return number + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Apply the OOP profile overlay to the vendored OPG.04 OpenAPI artifact." + ) + parser.add_argument("--base", type=Path, default=BASE) + parser.add_argument("--overlay", type=Path, default=OVERLAY) + parser.add_argument("--out", type=Path, default=OUT) + args = parser.parse_args() + + base_bytes = args.base.read_bytes() + overlay = yaml.safe_load(args.overlay.read_text(encoding="utf-8")) + expected = overlay["info"].get("x-base-sha256") + actual = hashlib.sha256(base_bytes).hexdigest() + if expected and expected != actual: + print(f"base artifact sha256 {actual} != overlay x-base-sha256 {expected}", file=sys.stderr) + return 1 + + document = yaml.safe_load(base_bytes) + applied = apply(document, overlay) + version = overlay["info"]["version"] + header = ( + "# GENERATED by scripts/apply_overlay.py — do not edit.\n" + f"# base: {args.base.name} (sha256 {actual})\n" + f"# overlay: {args.overlay.name} (version {version}, {applied} actions)\n" + ) + body = yaml.safe_dump(document, sort_keys=False, allow_unicode=True, width=100) + args.out.write_text(header + body, encoding="utf-8") + print(f"wrote {args.out} ({applied} actions applied)") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/federation_manager/__init__.py b/src/federation_manager/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8c0d5d5bb20beaaae2d684e1726eac58c9fec0ad --- /dev/null +++ b/src/federation_manager/__init__.py @@ -0,0 +1 @@ +__version__ = "2.0.0" diff --git a/src/federation_manager/adapters/__init__.py b/src/federation_manager/adapters/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/federation_manager/adapters/database/__init__.py b/src/federation_manager/adapters/database/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/federation_manager/adapters/database/agreement_repo.py b/src/federation_manager/adapters/database/agreement_repo.py new file mode 100644 index 0000000000000000000000000000000000000000..15c4cc871a088c6846e8f910ef06ccc22575142f --- /dev/null +++ b/src/federation_manager/adapters/database/agreement_repo.py @@ -0,0 +1,52 @@ +from uuid import UUID + +from sqlalchemy import and_, func, or_, select +from sqlalchemy.ext.asyncio import AsyncSession + +from federation_manager.adapters.database.tables import federation_agreements as agreements +from federation_manager.domain.models import Agreement, AppMapping + + +class PostgresAgreementRepo: + def __init__(self, session: AsyncSession) -> None: + self._session = session + + async def find_active_for_partner(self, partner_id: UUID) -> Agreement | None: + now = func.now() + currently_valid = and_( + agreements.c.valid_from <= now, + or_(agreements.c.valid_until.is_(None), agreements.c.valid_until > now), + ) + # Prefer the agreement valid right now; otherwise the newest, so the domain check + # reports "expired" against the most relevant contract. + stmt = ( + select(agreements) + .where(agreements.c.partner_op_id == partner_id, agreements.c.status == "active") + .order_by(currently_valid.desc(), agreements.c.valid_from.desc()) + .limit(1) + ) + row = (await self._session.execute(stmt)).one_or_none() + if row is None: + return None + return Agreement( + id=row.id, + partner_op_id=row.partner_op_id, + permitted_api_types={str(v) for v in row.permitted_api_types}, + permitted_zone_ids={UUID(str(v)) for v in row.permitted_zone_ids}, + app_mappings=tuple( + AppMapping( + app_id=str(entry["appId"]), + app_version=str(entry["appVersion"]), + flavour_id=str(entry["flavourId"]), + service_specification_id=UUID(str(entry["service_specification_id"])), + ) + for entry in row.service_spec_mappings.get("apps", []) + ), + api_family_mappings={ + str(k): UUID(str(v)) + for k, v in row.service_spec_mappings.get("api_families", {}).items() + }, + valid_from=row.valid_from, + valid_until=row.valid_until, + status=row.status, + ) diff --git a/src/federation_manager/adapters/database/core.py b/src/federation_manager/adapters/database/core.py new file mode 100644 index 0000000000000000000000000000000000000000..752a3a3dbe4c0237b68dbfbb69696d01ddb2ef3a --- /dev/null +++ b/src/federation_manager/adapters/database/core.py @@ -0,0 +1,21 @@ +from sqlalchemy.ext.asyncio import ( + AsyncEngine, + AsyncSession, + async_sessionmaker, + create_async_engine, +) + +from federation_manager.adapters.database.tables import metadata + + +def build_engine(url: str, echo: bool = False) -> AsyncEngine: + return create_async_engine(url, echo=echo) + + +def build_session_maker(engine: AsyncEngine) -> async_sessionmaker[AsyncSession]: + return async_sessionmaker(engine, expire_on_commit=False) + + +async def create_schema(engine: AsyncEngine) -> None: + async with engine.begin() as conn: + await conn.run_sync(metadata.create_all) diff --git a/src/federation_manager/adapters/database/federation_context_repo.py b/src/federation_manager/adapters/database/federation_context_repo.py new file mode 100644 index 0000000000000000000000000000000000000000..3fba6b51c17b60ad2ecd6bf5d6f9270f3650dded --- /dev/null +++ b/src/federation_manager/adapters/database/federation_context_repo.py @@ -0,0 +1,83 @@ +from uuid import UUID + +from sqlalchemy import Select, insert, select, update +from sqlalchemy.ext.asyncio import AsyncSession + +from federation_manager.adapters.database.tables import federation_contexts as contexts +from federation_manager.domain.models import FederationContext + + +class PostgresFederationContextRepo: + def __init__(self, session: AsyncSession) -> None: + self._session = session + + async def find_active_outbound(self, partner_id: UUID) -> FederationContext | None: + return await self._one( + select(contexts) + .where( + contexts.c.partner_op_id == partner_id, + contexts.c.direction == "outbound", + contexts.c.status == "available", + ) + .order_by(contexts.c.created_at.desc()) + .limit(1) + ) + + async def find_active_inbound(self, partner_id: UUID) -> FederationContext | None: + return await self._one( + select(contexts) + .where( + contexts.c.partner_op_id == partner_id, + contexts.c.direction == "inbound", + contexts.c.status == "available", + ) + .order_by(contexts.c.created_at.desc()) + .limit(1) + ) + + async def find_inbound( + self, partner_id: UUID, federation_context_id: str + ) -> FederationContext | None: + return await self._one( + select(contexts).where( + contexts.c.partner_op_id == partner_id, + contexts.c.direction == "inbound", + contexts.c.federation_context_id == federation_context_id, + ) + ) + + async def add(self, context: FederationContext) -> None: + await self._session.execute( + insert(contexts).values( + id=context.id, + partner_op_id=context.partner_op_id, + agreement_id=context.agreement_id, + direction=context.direction, + federation_context_id=context.federation_context_id, + status_callback_url=context.status_callback_url, + status=context.status, + created_at=context.created_at, + ) + ) + await self._session.commit() + + async def set_status(self, context_id: UUID, status: str) -> None: + await self._session.execute( + update(contexts).where(contexts.c.id == context_id).values(status=status) + ) + await self._session.commit() + + async def _one(self, stmt: Select[tuple[object, ...]]) -> FederationContext | None: + row = (await self._session.execute(stmt)).one_or_none() + if row is None: + return None + return FederationContext( + id=row.id, + partner_op_id=row.partner_op_id, + direction=row.direction, + federation_context_id=row.federation_context_id, + status=row.status, + created_at=row.created_at, + agreement_id=row.agreement_id, + status_callback_url=row.status_callback_url, + ) diff --git a/src/federation_manager/adapters/database/partner_repo.py b/src/federation_manager/adapters/database/partner_repo.py new file mode 100644 index 0000000000000000000000000000000000000000..550db285b001ae0ea40b8bf8a43c35e572733359 --- /dev/null +++ b/src/federation_manager/adapters/database/partner_repo.py @@ -0,0 +1,43 @@ +from typing import Any +from uuid import UUID + +from sqlalchemy import Select, select +from sqlalchemy.ext.asyncio import AsyncSession + +from federation_manager.adapters.database.tables import partner_ops +from federation_manager.domain.models import PartnerOP + + +class PostgresPartnerRepo: + def __init__(self, session: AsyncSession) -> None: + self._session = session + + async def find_by_oauth2_client_id(self, client_id: str) -> PartnerOP | None: + return await self._one( + select(partner_ops).where(partner_ops.c.oauth2_client_id == client_id) + ) + + async def find_by_id(self, partner_id: UUID) -> PartnerOP | None: + return await self._one(select(partner_ops).where(partner_ops.c.id == partner_id)) + + async def list_active(self) -> list[PartnerOP]: + stmt = select(partner_ops).where(partner_ops.c.status == "active") + rows = (await self._session.execute(stmt)).all() + return [self._to_partner(row) for row in rows] + + async def _one(self, stmt: Select[tuple[object, ...]]) -> PartnerOP | None: + row = (await self._session.execute(stmt)).one_or_none() + return None if row is None else self._to_partner(row) + + @staticmethod + def _to_partner(row: Any) -> PartnerOP: + return PartnerOP( + id=row.id, + mcc_mnc=row.mcc_mnc, + oauth2_client_id=row.oauth2_client_id, + status=row.status, + our_client_id=row.our_client_id, + our_client_secret_ref=row.our_client_secret_ref, + token_endpoint=row.token_endpoint, + base_url=row.base_url, + ) diff --git a/src/federation_manager/adapters/database/routing_repo.py b/src/federation_manager/adapters/database/routing_repo.py new file mode 100644 index 0000000000000000000000000000000000000000..b7056d71b78608198c14bfab29e27cb6e1586dac --- /dev/null +++ b/src/federation_manager/adapters/database/routing_repo.py @@ -0,0 +1,32 @@ +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from federation_manager.adapters.database.tables import routing_rules +from federation_manager.domain.models import RoutingRule + + +class PostgresRoutingRuleRepo: + def __init__(self, session: AsyncSession) -> None: + self._session = session + + async def list_active(self, identifier_type: str) -> list[RoutingRule]: + stmt = ( + select(routing_rules) + .where( + routing_rules.c.identifier_type == identifier_type, + routing_rules.c.is_active.is_(True), + ) + .order_by(routing_rules.c.priority, routing_rules.c.value_range) + ) + rows = (await self._session.execute(stmt)).all() + return [ + RoutingRule( + id=row.id, + partner_op_id=row.partner_op_id, + identifier_type=row.identifier_type, + value_range=row.value_range, + priority=row.priority, + is_active=row.is_active, + ) + for row in rows + ] diff --git a/src/federation_manager/adapters/database/tables.py b/src/federation_manager/adapters/database/tables.py new file mode 100644 index 0000000000000000000000000000000000000000..7eadab09d5e11c1cbf9854b7826d298833e2401b --- /dev/null +++ b/src/federation_manager/adapters/database/tables.py @@ -0,0 +1,141 @@ +from sqlalchemy import ( + CHAR, + Boolean, + Column, + DateTime, + ForeignKey, + Index, + Integer, + MetaData, + String, + Table, + Text, + UniqueConstraint, + func, + text, +) +from sqlalchemy.dialects.postgresql import JSONB +from sqlalchemy.dialects.postgresql import UUID as PGUUID + +metadata = MetaData() + +# Subset of fm_db.partner_ops (RD §K.1): columns grow as features need them. +partner_ops = Table( + "partner_ops", + metadata, + Column("id", PGUUID(as_uuid=True), primary_key=True, server_default=func.gen_random_uuid()), + Column("mcc_mnc", String(10), nullable=False, unique=True), + Column("oauth2_client_id", String(255), nullable=False, unique=True), + Column("base_url", Text, nullable=False), + Column("our_client_id", String(255)), + Column("our_client_secret_ref", Text), + Column("token_endpoint", Text), + Column("status", String(20), nullable=False, server_default="pending"), + Column("created_at", DateTime(timezone=True), nullable=False, server_default=func.now()), + Column("updated_at", DateTime(timezone=True), nullable=False, server_default=func.now()), + Index("idx_partner_ops_oauth2_client_id", "oauth2_client_id"), + Index("idx_partner_ops_status", "status"), +) + +federation_agreements = Table( + "federation_agreements", + metadata, + Column("id", PGUUID(as_uuid=True), primary_key=True, server_default=func.gen_random_uuid()), + Column("partner_op_id", PGUUID(as_uuid=True), ForeignKey("partner_ops.id"), nullable=False), + Column("permitted_api_types", JSONB, nullable=False), + Column("permitted_zone_ids", JSONB, nullable=False, server_default=text("'[]'::jsonb")), + Column("service_spec_mappings", JSONB, nullable=False, server_default=text("'{}'::jsonb")), + Column("usage_limits", JSONB, server_default=text("'{}'::jsonb")), + Column("valid_from", DateTime(timezone=True), nullable=False), + Column("valid_until", DateTime(timezone=True)), + Column("status", String(20), nullable=False, server_default="draft"), + Column("created_at", DateTime(timezone=True), nullable=False, server_default=func.now()), + Column("updated_at", DateTime(timezone=True), nullable=False, server_default=func.now()), + Index("idx_federation_agreements_partner", "partner_op_id"), + Index("idx_federation_agreements_status_validity", "status", "valid_from", "valid_until"), +) + +federation_contexts = Table( + "federation_contexts", + metadata, + Column("id", PGUUID(as_uuid=True), primary_key=True, server_default=func.gen_random_uuid()), + Column("partner_op_id", PGUUID(as_uuid=True), ForeignKey("partner_ops.id"), nullable=False), + Column("agreement_id", PGUUID(as_uuid=True), ForeignKey("federation_agreements.id")), + Column("direction", String(10), nullable=False), + Column("federation_context_id", String(255), nullable=False), + Column("status_callback_url", Text), + Column("status", String(20), nullable=False), + Column("created_at", DateTime(timezone=True), nullable=False, server_default=func.now()), + Column("updated_at", DateTime(timezone=True), nullable=False, server_default=func.now()), + UniqueConstraint( + "partner_op_id", + "direction", + "federation_context_id", + name="uq_federation_contexts_partner_direction_id", + ), +) + +routing_rules = Table( + "routing_rules", + metadata, + Column("id", PGUUID(as_uuid=True), primary_key=True, server_default=func.gen_random_uuid()), + Column("partner_op_id", PGUUID(as_uuid=True), ForeignKey("partner_ops.id"), nullable=False), + Column("identifier_type", String(20), nullable=False), + Column("value_range", String(50), nullable=False), + Column("priority", Integer, nullable=False, server_default="100"), + Column("is_active", Boolean, nullable=False, server_default=text("true")), + Column("created_at", DateTime(timezone=True), nullable=False, server_default=func.now()), + Column("updated_at", DateTime(timezone=True), nullable=False, server_default=func.now()), + UniqueConstraint("identifier_type", "value_range", "priority", name="uq_routing_rules_rule"), + Index("idx_routing_rules_type_value", "identifier_type", "value_range"), + Index("idx_routing_rules_partner", "partner_op_id"), + Index("idx_routing_rules_active", "is_active", postgresql_where=text("is_active = true")), +) + +federation_transactions = Table( + "federation_transactions", + metadata, + Column("id", PGUUID(as_uuid=True), primary_key=True, server_default=func.gen_random_uuid()), + Column("partner_op_id", PGUUID(as_uuid=True), ForeignKey("partner_ops.id"), nullable=False), + Column("agreement_id", PGUUID(as_uuid=True), ForeignKey("federation_agreements.id")), + Column("federation_context_row_id", PGUUID(as_uuid=True), ForeignKey("federation_contexts.id")), + Column("direction", String(10), nullable=False), + Column("federation_operation_id", PGUUID(as_uuid=True)), + Column("operation_id", PGUUID(as_uuid=True)), + Column("correlation_id", PGUUID(as_uuid=True)), + Column("external_txn_id", String(255)), + Column("idempotency_key", String(255)), + Column("request_fingerprint", CHAR(64)), + Column("external_resource_id", String(255)), + Column("callback_url", Text), + Column("callback_status", String(20)), + Column("callback_attempts", Integer, nullable=False, server_default="0"), + Column("api_type", String(100), nullable=False), + Column("status", String(20), nullable=False, server_default="pending"), + Column("request_summary", JSONB, nullable=False), + Column("response_summary", JSONB), + Column("error_detail", JSONB), + Column("started_at", DateTime(timezone=True), nullable=False, server_default=func.now()), + Column("completed_at", DateTime(timezone=True)), + Column("created_at", DateTime(timezone=True), nullable=False, server_default=func.now()), + Index("idx_fed_tx_partner", "partner_op_id"), + Index( + "uq_fed_tx_federation_operation_id", + "federation_operation_id", + unique=True, + postgresql_where=text("federation_operation_id IS NOT NULL"), + ), + Index("idx_fed_tx_context", "federation_context_row_id"), + Index("idx_fed_tx_operation", "operation_id"), + Index("idx_fed_tx_status", "status"), + Index("idx_fed_tx_started", "started_at"), + Index("idx_fed_tx_external_txn", "external_txn_id"), + Index( + "uq_fed_tx_idempotency_key", + "partner_op_id", + "api_type", + "idempotency_key", + unique=True, + postgresql_where=text("idempotency_key IS NOT NULL"), + ), +) diff --git a/src/federation_manager/adapters/database/transaction_repo.py b/src/federation_manager/adapters/database/transaction_repo.py new file mode 100644 index 0000000000000000000000000000000000000000..decf07a9a966eb2aaa984452905d4d44fbd24db3 --- /dev/null +++ b/src/federation_manager/adapters/database/transaction_repo.py @@ -0,0 +1,126 @@ +from datetime import datetime +from uuid import UUID + +from sqlalchemy import Select, insert, select, update +from sqlalchemy.ext.asyncio import AsyncSession + +from federation_manager.adapters.database.tables import federation_transactions as transactions +from federation_manager.domain.models import FederationTransaction + + +class PostgresTransactionRepo: + # commits per call: audit rows must survive whatever happens on the partner call + def __init__(self, session: AsyncSession) -> None: + self._session = session + + async def add(self, transaction: FederationTransaction) -> None: + await self._session.execute( + insert(transactions).values( + id=transaction.id, + partner_op_id=transaction.partner_op_id, + agreement_id=transaction.agreement_id, + federation_context_row_id=transaction.federation_context_row_id, + direction=transaction.direction, + federation_operation_id=transaction.federation_operation_id, + operation_id=transaction.operation_id, + correlation_id=transaction.correlation_id, + external_txn_id=transaction.external_txn_id, + idempotency_key=transaction.idempotency_key, + request_fingerprint=transaction.request_fingerprint, + external_resource_id=transaction.external_resource_id, + callback_url=transaction.callback_url, + callback_status=transaction.callback_status, + callback_attempts=transaction.callback_attempts, + api_type=transaction.api_type, + status=transaction.status, + request_summary=transaction.request_summary, + response_summary=transaction.response_summary, + error_detail=transaction.error_detail, + started_at=transaction.started_at, + completed_at=transaction.completed_at, + ) + ) + await self._session.commit() + + async def find_by_idempotency_key( + self, partner_id: UUID, api_type: str, idempotency_key: str + ) -> FederationTransaction | None: + return await self._one( + select(transactions).where( + transactions.c.partner_op_id == partner_id, + transactions.c.api_type == api_type, + transactions.c.idempotency_key == idempotency_key, + ) + ) + + async def find_by_operation_id(self, operation_id: UUID) -> FederationTransaction | None: + return await self._one( + select(transactions).where(transactions.c.operation_id == operation_id) + ) + + async def _one(self, stmt: Select[tuple[object, ...]]) -> FederationTransaction | None: + row = (await self._session.execute(stmt)).one_or_none() + if row is None: + return None + return FederationTransaction( + id=row.id, + partner_op_id=row.partner_op_id, + direction=row.direction, + api_type=row.api_type, + status=row.status, + request_summary=row.request_summary, + started_at=row.started_at, + agreement_id=row.agreement_id, + federation_context_row_id=row.federation_context_row_id, + external_txn_id=row.external_txn_id, + idempotency_key=row.idempotency_key, + request_fingerprint=row.request_fingerprint, + external_resource_id=row.external_resource_id, + callback_url=row.callback_url, + callback_status=row.callback_status, + callback_attempts=row.callback_attempts, + operation_id=row.operation_id, + correlation_id=row.correlation_id, + federation_operation_id=row.federation_operation_id, + response_summary=row.response_summary, + error_detail=row.error_detail, + completed_at=row.completed_at, + ) + + async def mark_in_progress(self, transaction_id: UUID) -> None: + await self._session.execute( + update(transactions) + .where(transactions.c.id == transaction_id) + .values(status="in_progress") + ) + await self._session.commit() + + async def record_callback(self, transaction_id: UUID, *, status: str) -> None: + await self._session.execute( + update(transactions) + .where(transactions.c.id == transaction_id) + .values( + callback_status=status, + callback_attempts=transactions.c.callback_attempts + 1, + ) + ) + await self._session.commit() + + async def record_outcome( + self, + transaction_id: UUID, + *, + status: str, + completed_at: datetime, + response_summary: dict[str, object] | None = None, + error_detail: dict[str, object] | None = None, + ) -> None: + values: dict[str, object] = {"status": status, "completed_at": completed_at} + if response_summary is not None: + values["response_summary"] = response_summary + if error_detail is not None: + values["error_detail"] = error_detail + await self._session.execute( + update(transactions).where(transactions.c.id == transaction_id).values(**values) + ) + await self._session.commit() diff --git a/src/federation_manager/adapters/databus/__init__.py b/src/federation_manager/adapters/databus/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/federation_manager/adapters/databus/nats_adapter.py b/src/federation_manager/adapters/databus/nats_adapter.py new file mode 100644 index 0000000000000000000000000000000000000000..f40ff8bac6fedba76e4aef2923187648634c349d --- /dev/null +++ b/src/federation_manager/adapters/databus/nats_adapter.py @@ -0,0 +1,120 @@ +import json +from collections.abc import Awaitable, Callable +from typing import Any + +import nats +from nats.aio.client import Client +from nats.aio.msg import Msg +from nats.js import JetStreamContext +from nats.js.api import AckPolicy, ConsumerConfig, RetentionPolicy, StreamConfig + +from federation_manager.contracts.srm import ( + EVENT_STREAM, + TASK_STREAM, + TASK_STREAM_MAX_AGE_SECONDS, +) + + +class NatsCommandPublisher: + def __init__(self, url: str) -> None: + self._url = url + self._nc: Client | None = None + self._js: JetStreamContext | None = None + + async def connect(self) -> None: + self._nc = await nats.connect(self._url) + self._js = self._nc.jetstream() + + async def close(self) -> None: + if self._nc is not None: + await self._nc.drain() + self._nc = None + self._js = None + + async def ensure_task_stream(self) -> None: + js = self._require_js() + config = StreamConfig( + name=TASK_STREAM, + subjects=["command.srm.>"], + retention=RetentionPolicy.WORK_QUEUE, + max_age=TASK_STREAM_MAX_AGE_SECONDS, + ) + try: + await js.stream_info(TASK_STREAM) + except Exception: + await js.add_stream(config) + else: + # an older stream may predate the age limit, and unacked commands never expire + await js.update_stream(config) + + async def publish(self, subject: str, payload: dict[str, object]) -> None: + js = self._require_js() + await js.publish(subject, json.dumps(payload).encode()) + + def _require_js(self) -> JetStreamContext: + if self._js is None: + raise RuntimeError("NATS publisher is not connected") + return self._js + + +class NatsEventConsumer: + def __init__(self, url: str, durable: str = "fm-event-worker") -> None: + self._url = url + self._durable = durable + self._nc: Client | None = None + self._js: JetStreamContext | None = None + + async def connect(self) -> None: + self._nc = await nats.connect(self._url) + self._js = self._nc.jetstream() + + async def close(self) -> None: + if self._nc is not None: + await self._nc.drain() + self._nc = None + self._js = None + + async def ensure_event_stream(self) -> None: + js = self._require_js() + try: + await js.stream_info(EVENT_STREAM) + except Exception: + await js.add_stream( + StreamConfig( + name=EVENT_STREAM, + subjects=["event.srm.>"], + retention=RetentionPolicy.LIMITS, + ) + ) + + async def subscribe( + self, subject: str, handler: Callable[[dict[str, Any]], Awaitable[None]] + ) -> None: + async def on_message(message: Msg) -> None: + try: + await handler(json.loads(message.data)) + except Exception: + # Leave it unacked so JetStream redelivers up to max_deliver. + return + await message.ack() + + name = f"{self._durable}-{subject.replace('.', '-')}" + # queue group so replicas of this FM share one durable instead of fighting over it + await self._require_js().subscribe( + subject, + queue=name, + durable=name, + stream=EVENT_STREAM, + cb=on_message, + manual_ack=True, + config=ConsumerConfig(max_deliver=3, ack_policy=AckPolicy.EXPLICIT), + ) + + async def delete_durable(self, subject: str) -> None: + name = f"{self._durable}-{subject.replace('.', '-')}" + await self._require_js().delete_consumer(EVENT_STREAM, name) + + def _require_js(self) -> JetStreamContext: + if self._js is None: + raise RuntimeError("NATS event consumer is not connected") + return self._js diff --git a/src/federation_manager/adapters/http/__init__.py b/src/federation_manager/adapters/http/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/federation_manager/adapters/http/callback_client.py b/src/federation_manager/adapters/http/callback_client.py new file mode 100644 index 0000000000000000000000000000000000000000..534c778f18f279552fdcbdf6e4ed90f3e544e68c --- /dev/null +++ b/src/federation_manager/adapters/http/callback_client.py @@ -0,0 +1,41 @@ +import httpx + +from federation_manager.domain.errors import PartnerEndpointConfigurationError +from federation_manager.domain.models import PartnerOP +from federation_manager.domain.ports import PartnerTokenProviderPort + +NOTIFICATION_SCOPE = "fed-mgmt-notif" + + +class HttpxCallbackClient: + def __init__( + self, + client: httpx.AsyncClient, + token_provider: PartnerTokenProviderPort, + allow_insecure: bool = False, + ) -> None: + self._client = client + self._token_provider = token_provider + self._allow_insecure = allow_insecure + + async def deliver(self, partner: PartnerOP, url: str, payload: dict[str, object]) -> bool: + self._check(partner, url) + token = await self._token_provider.token_for(partner, scope=NOTIFICATION_SCOPE) + try: + response = await self._client.post( + url, + headers={"Accept": "application/json", "Authorization": f"Bearer {token}"}, + json=payload, + ) + except httpx.HTTPError: + return False + return 200 <= response.status_code < 300 + + def _check(self, partner: PartnerOP, url: str) -> None: + try: + parsed = httpx.URL(url) + except httpx.InvalidURL: + raise PartnerEndpointConfigurationError(partner.id) from None + allowed = ("https", "http") if self._allow_insecure else ("https",) + if not parsed.host or parsed.scheme not in allowed: + raise PartnerEndpointConfigurationError(partner.id) diff --git a/src/federation_manager/adapters/http/ewbi_client.py b/src/federation_manager/adapters/http/ewbi_client.py new file mode 100644 index 0000000000000000000000000000000000000000..9f4632a518c207935fbb6ac9dfb37efcb855a661 --- /dev/null +++ b/src/federation_manager/adapters/http/ewbi_client.py @@ -0,0 +1,62 @@ +import httpx + +from federation_manager.domain.errors import ( + PartnerEndpointConfigurationError, + PartnerRequestFailed, +) +from federation_manager.domain.models import EwbiResponse, PartnerOP +from federation_manager.domain.ports import PartnerTokenProviderPort + + +class HttpxEwbiClient: + def __init__( + self, + client: httpx.AsyncClient, + token_provider: PartnerTokenProviderPort, + allow_insecure: bool = False, + ) -> None: + self._client = client + self._token_provider = token_provider + self._allow_insecure = allow_insecure + + async def post(self, partner: PartnerOP, path: str, payload: dict[str, object]) -> EwbiResponse: + url = self._url(partner, path) + token = await self._token_provider.token_for(partner, scope="fed-mgmt") + + try: + response = await self._client.post( + url, + headers={"Accept": "application/json", "Authorization": f"Bearer {token}"}, + json=payload, + ) + except httpx.HTTPError: + raise PartnerRequestFailed(partner.id) from None + + if not response.content: + body: object | None = None + else: + try: + body = response.json() + except ValueError: + raise PartnerRequestFailed(partner.id) from None + return EwbiResponse( + status_code=response.status_code, + body=body, + location=response.headers.get("Location"), + ) + + def _url(self, partner: PartnerOP, path: str) -> str: + if not path.startswith("/") or httpx.URL(path).is_absolute_url: + raise ValueError("EWBI path must be an absolute path without a host") + + if not partner.base_url: + raise PartnerEndpointConfigurationError(partner.id) + try: + base_url = httpx.URL(partner.base_url) + except httpx.InvalidURL: + raise PartnerEndpointConfigurationError(partner.id) from None + allowed = ("https", "http") if self._allow_insecure else ("https",) + if not base_url.host or base_url.scheme not in allowed: + raise PartnerEndpointConfigurationError(partner.id) + + return f"{partner.base_url.rstrip('/')}/{path.lstrip('/')}" diff --git a/src/federation_manager/adapters/security/__init__.py b/src/federation_manager/adapters/security/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/federation_manager/adapters/security/client_secret_token_provider.py b/src/federation_manager/adapters/security/client_secret_token_provider.py new file mode 100644 index 0000000000000000000000000000000000000000..f85c15d0f1fbf5bc2fd9d23034d2b770617da14c --- /dev/null +++ b/src/federation_manager/adapters/security/client_secret_token_provider.py @@ -0,0 +1,150 @@ +import asyncio +import time +from collections.abc import Callable +from dataclasses import dataclass +from pathlib import Path +from typing import TypeAlias, cast +from uuid import UUID + +import httpx + +from federation_manager.domain.errors import ( + PartnerTokenConfigurationError, + PartnerTokenRequestFailed, +) +from federation_manager.domain.models import PartnerOP + +_CacheKey: TypeAlias = tuple[UUID, str, str, str, str] + + +@dataclass(frozen=True) +class _CachedToken: + access_token: str + expires_at: float + + +class FileClientSecretTokenProvider: + def __init__( + self, + client: httpx.AsyncClient, + *, + refresh_skew_seconds: float = 30.0, + clock: Callable[[], float] = time.monotonic, + allow_insecure: bool = False, + ) -> None: + self._client = client + self._allow_insecure = allow_insecure + self._refresh_skew_seconds = refresh_skew_seconds + self._clock = clock + self._cache: dict[_CacheKey, _CachedToken] = {} + self._locks: dict[_CacheKey, asyncio.Lock] = {} + + async def token_for(self, partner: PartnerOP, scope: str = "fed-mgmt") -> str: + client_id = self._required(partner, "client id", partner.our_client_id) + secret_ref = self._required(partner, "client secret ref", partner.our_client_secret_ref) + token_endpoint = self._token_endpoint(partner) + cache_key = (partner.id, scope, client_id, secret_ref, token_endpoint) + + cached = self._cache.get(cache_key) + if cached is not None and self._is_usable(cached): + return cached.access_token + + lock = self._locks.setdefault(cache_key, asyncio.Lock()) + async with lock: + cached = self._cache.get(cache_key) + if cached is not None and self._is_usable(cached): + return cached.access_token + + secret = self._read_secret(partner, secret_ref) + access_token, expires_in = await self._request_token( + partner, + token_endpoint=token_endpoint, + client_id=client_id, + client_secret=secret, + scope=scope, + ) + if expires_in is not None: + self._cache[cache_key] = _CachedToken( + access_token=access_token, + expires_at=self._clock() + expires_in, + ) + return access_token + + def _is_usable(self, cached: _CachedToken) -> bool: + return self._clock() < cached.expires_at - self._refresh_skew_seconds + + @staticmethod + def _required(partner: PartnerOP, field: str, value: str | None) -> str: + if value: + return value + raise PartnerTokenConfigurationError(partner.id, field) + + def _token_endpoint(self, partner: PartnerOP) -> str: + value = self._required(partner, "token endpoint", partner.token_endpoint) + try: + endpoint = httpx.URL(value) + except httpx.InvalidURL: + raise PartnerTokenConfigurationError(partner.id, "token endpoint") from None + allowed = ("https", "http") if self._allow_insecure else ("https",) + if not endpoint.host or endpoint.scheme not in allowed: + raise PartnerTokenConfigurationError(partner.id, "token endpoint") + return value + + @staticmethod + def _read_secret(partner: PartnerOP, secret_ref: str) -> str: + try: + secret = Path(secret_ref).read_text(encoding="utf-8").rstrip("\r\n") + except (OSError, UnicodeError): + raise PartnerTokenConfigurationError(partner.id, "client secret ref") from None + if not secret: + raise PartnerTokenConfigurationError(partner.id, "client secret ref") + return secret + + async def _request_token( + self, + partner: PartnerOP, + *, + token_endpoint: str, + client_id: str, + client_secret: str, + scope: str, + ) -> tuple[str, float | None]: + try: + response = await self._client.post( + token_endpoint, + data={ + "grant_type": "client_credentials", + "client_id": client_id, + "client_secret": client_secret, + "scope": scope, + }, + ) + response.raise_for_status() + raw_payload: object = response.json() + except (httpx.HTTPError, ValueError): + raise PartnerTokenRequestFailed(partner.id) from None + + if not isinstance(raw_payload, dict): + raise PartnerTokenRequestFailed(partner.id) + payload = cast(dict[str, object], raw_payload) + + access_token = payload.get("access_token") + if not isinstance(access_token, str) or not access_token: + raise PartnerTokenRequestFailed(partner.id) + + token_type = payload.get("token_type") + if token_type is not None and ( + not isinstance(token_type, str) or token_type.lower() != "bearer" + ): + raise PartnerTokenRequestFailed(partner.id) + + expires_in = payload.get("expires_in") + if expires_in is None: + return access_token, None + if ( + isinstance(expires_in, bool) + or not isinstance(expires_in, (int, float)) + or expires_in <= 0 + ): + raise PartnerTokenRequestFailed(partner.id) + return access_token, float(expires_in) diff --git a/src/federation_manager/adapters/security/keycloak_validator.py b/src/federation_manager/adapters/security/keycloak_validator.py new file mode 100644 index 0000000000000000000000000000000000000000..779e5f6776d06b2230cc034a9e02a7855381c338 --- /dev/null +++ b/src/federation_manager/adapters/security/keycloak_validator.py @@ -0,0 +1,29 @@ +import jwt + +from federation_manager.domain.errors import AuthenticationFailed +from federation_manager.domain.models import ValidatedClaims + + +class KeycloakJwtValidator: + def __init__(self, issuer: str) -> None: + self._issuer = issuer + self._jwks_client = jwt.PyJWKClient(f"{issuer}/protocol/openid-connect/certs") + + async def validate(self, token: str) -> ValidatedClaims: + try: + signing_key = self._jwks_client.get_signing_key_from_jwt(token) + claims = jwt.decode( + token, + signing_key.key, + algorithms=["RS256"], + issuer=self._issuer, + options={"require": ["exp", "iss"], "verify_aud": False}, + ) + except jwt.PyJWTError as exc: + raise AuthenticationFailed from exc + + client_id = claims.get("azp") or claims.get("client_id") + if not client_id: + raise AuthenticationFailed + scopes = set(claims.get("scope", "").split()) + return ValidatedClaims(client_id=client_id, scopes=scopes) diff --git a/src/federation_manager/api/__init__.py b/src/federation_manager/api/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/federation_manager/api/errors.py b/src/federation_manager/api/errors.py new file mode 100644 index 0000000000000000000000000000000000000000..677ad04bdf422f130c4585034ff03c1910171ed8 --- /dev/null +++ b/src/federation_manager/api/errors.py @@ -0,0 +1,224 @@ +from typing import Any + +from fastapi import FastAPI, Request +from fastapi.responses import JSONResponse +from pydantic.json_schema import models_json_schema + +from federation_manager.contracts.ewbi import InvalidParam, ProblemDetails +from federation_manager.domain.errors import ( + AgreementExpired, + AgreementViolation, + AuthenticationFailed, + FederationAlreadyExists, + FederationContextMissing, + FederationContextUnknown, + IdempotencyKeyReused, + NoRouteMatched, + PartnerEndpointConfigurationError, + PartnerNotActive, + PartnerRequestFailed, + PartnerResponseInvalid, + PartnerTokenConfigurationError, + PartnerTokenRequestFailed, + PartnerUnknown, + problem_type, +) + +_BEARER_CHALLENGE = {"WWW-Authenticate": 'Bearer scope="fed-mgmt"'} + +_PROBLEM_STATUSES = { + 400: "Bad request", + 401: "Unauthorized", + 404: "Not Found", + 409: "Conflict", + 422: "Unprocessable Entity", + 500: "Internal Server Error", + 503: "Service Unavailable", + 520: "Web Server Returned an Unknown Error", +} + +# A "model" here would also emit application/json; a $ref needs register_problem_schemas(). +EWBI_ERROR_RESPONSES: dict[int | str, dict[str, Any]] = { + **{ + status: { + "description": description, + "content": { + "application/problem+json": { + "schema": {"$ref": "#/components/schemas/ProblemDetails"} + } + }, + } + for status, description in _PROBLEM_STATUSES.items() + }, + "default": {"description": "Generic Error"}, +} + + +def register_problem_schemas(schema: dict[str, Any]) -> dict[str, Any]: + _, defs = models_json_schema( + [(ProblemDetails, "validation"), (InvalidParam, "validation")], + ref_template="#/components/schemas/{model}", + ) + schema.setdefault("components", {}).setdefault("schemas", {}).update(defs["$defs"]) + return schema + + +def problem( + status: int, + code: str, + title: str, + detail: str, + instance: str, + headers: dict[str, str] | None = None, +) -> JSONResponse: + return JSONResponse( + status_code=status, + media_type="application/problem+json", + headers=headers, + content={ + "type": problem_type(code), + "title": title, + "status": status, + "detail": detail, + "instance": instance, + }, + ) + + +def register_exception_handlers(app: FastAPI) -> None: + # Details stay generic: nothing about our internals crosses the operator boundary. + @app.exception_handler(AuthenticationFailed) + async def _auth_failed(request: Request, exc: AuthenticationFailed) -> JSONResponse: + return problem( + 401, + "authentication-failed", + "Authentication Failed", + "Access token missing, malformed, or lacking the required scope.", + request.url.path, + headers=_BEARER_CHALLENGE, + ) + + @app.exception_handler(PartnerUnknown) + async def _partner_unknown(request: Request, exc: PartnerUnknown) -> JSONResponse: + return problem( + 401, + "partner-unknown", + "Unknown Partner", + "Presented client identity is not registered with this operator.", + request.url.path, + headers=_BEARER_CHALLENGE, + ) + + @app.exception_handler(PartnerNotActive) + async def _partner_not_active(request: Request, exc: PartnerNotActive) -> JSONResponse: + return problem( + 403, + "partner-not-active", + "Partner Not Active", + "Federation with this partner is not currently active.", + request.url.path, + ) + + @app.exception_handler(AgreementExpired) + async def _agreement_expired(request: Request, exc: AgreementExpired) -> JSONResponse: + return problem( + 403, + "agreement-expired", + "Federation Agreement Expired", + "No federation agreement with this partner is currently valid.", + request.url.path, + ) + + @app.exception_handler(AgreementViolation) + async def _agreement_violation(request: Request, exc: AgreementViolation) -> JSONResponse: + return problem( + 403, + "agreement-violation", + "Federation Agreement Violation", + "Request is outside the scope of the federation agreement with this partner.", + request.url.path, + ) + + @app.exception_handler(NoRouteMatched) + async def _no_route(request: Request, exc: NoRouteMatched) -> JSONResponse: + return problem( + 404, + "no-route", + "No Route", + "No routing rule matches the requested identifier; no partner could be resolved.", + request.url.path, + ) + + @app.exception_handler(PartnerRequestFailed) + @app.exception_handler(PartnerTokenRequestFailed) + async def _partner_unreachable(request: Request, exc: Exception) -> JSONResponse: + return problem( + 502, + "partner-unreachable", + "Partner Unreachable", + "The partner operator did not return a usable response.", + request.url.path, + ) + + @app.exception_handler(PartnerResponseInvalid) + async def _partner_response_invalid( + request: Request, exc: PartnerResponseInvalid + ) -> JSONResponse: + return problem( + 502, + "partner-response-invalid", + "Invalid Partner Response", + "The partner operator's response did not match the OPG.04 contract.", + request.url.path, + ) + + @app.exception_handler(FederationAlreadyExists) + async def _federation_exists(request: Request, exc: FederationAlreadyExists) -> JSONResponse: + return problem( + 409, + "federation-exists", + "Federation Already Exists", + "An active federation context already exists with this partner operator.", + request.url.path, + ) + + @app.exception_handler(IdempotencyKeyReused) + async def _idempotency_reused(request: Request, exc: IdempotencyKeyReused) -> JSONResponse: + return problem( + 409, + "idempotency-key-reused", + "Idempotency Key Reused", + "This Idempotency-Key was already used for a different request.", + request.url.path, + ) + + @app.exception_handler(FederationContextUnknown) + async def _context_unknown(request: Request, exc: FederationContextUnknown) -> JSONResponse: + return problem( + 404, + "federation-context-unknown", + "Unknown Federation Context", + "No federation context with this identifier exists for the calling partner.", + request.url.path, + ) + + @app.exception_handler(FederationContextMissing) + async def _context_missing(request: Request, exc: FederationContextMissing) -> JSONResponse: + return problem( + 409, + "federation-not-established", + "Federation Not Established", + "No active federation context exists with the resolved partner operator.", + request.url.path, + ) + + @app.exception_handler(PartnerEndpointConfigurationError) + @app.exception_handler(PartnerTokenConfigurationError) + async def _partner_misconfigured(request: Request, exc: Exception) -> JSONResponse: + return problem( + 500, + "internal-error", + "Internal Error", + "Outbound federation is not correctly configured for the resolved partner.", + request.url.path, + ) diff --git a/src/federation_manager/api/ewbi/__init__.py b/src/federation_manager/api/ewbi/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/federation_manager/api/ewbi/v1/__init__.py b/src/federation_manager/api/ewbi/v1/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/federation_manager/api/ewbi/v1/lcm.py b/src/federation_manager/api/ewbi/v1/lcm.py new file mode 100644 index 0000000000000000000000000000000000000000..dd45b57bead4e0b66ffb4de65b237fca2b0ea52d --- /dev/null +++ b/src/federation_manager/api/ewbi/v1/lcm.py @@ -0,0 +1,33 @@ +from typing import Annotated + +from fastapi import APIRouter, Depends, Header, Path + +from federation_manager.api.errors import EWBI_ERROR_RESPONSES +from federation_manager.api.security import get_bearer_token +from federation_manager.application.deployment import InboundDeploymentService +from federation_manager.contracts.ewbi import InstallAppRequest, InstallAppResponse +from federation_manager.dependencies import get_inbound_deployment_service +from federation_manager.domain.ewbi import EWBI_BASE_PATH + +router = APIRouter(prefix=EWBI_BASE_PATH, tags=["ApplicationDeploymentManagement"]) + +FederationContextIdPath = Annotated[str, Path(pattern=r"^[A-Za-z0-9][A-Za-z0-9-]*$")] + + +@router.post( + "/{federationContextId}/application/lcm", + operation_id="InstallApp", + status_code=202, + responses=EWBI_ERROR_RESPONSES, +) +async def install_app( + federationContextId: FederationContextIdPath, # noqa: N803 - GSMA path template name + body: InstallAppRequest, + service: Annotated[InboundDeploymentService, Depends(get_inbound_deployment_service)], + token: Annotated[str, Depends(get_bearer_token)], + idempotency_key: Annotated[str, Header(alias="Idempotency-Key", min_length=1)], +) -> InstallAppResponse: + accepted = await service.install(token, federationContextId, idempotency_key, body) + return InstallAppResponse( + zone_id=accepted.zone_id, app_inst_identifier=accepted.app_instance_identifier + ) diff --git a/src/federation_manager/api/ewbi/v1/management.py b/src/federation_manager/api/ewbi/v1/management.py new file mode 100644 index 0000000000000000000000000000000000000000..2dace4c23c8524996eabb0cec3d761c12fc2d60a --- /dev/null +++ b/src/federation_manager/api/ewbi/v1/management.py @@ -0,0 +1,90 @@ +from typing import Annotated + +from fastapi import APIRouter, Depends, Path, Response + +from federation_manager.api.errors import EWBI_ERROR_RESPONSES +from federation_manager.api.security import get_bearer_token +from federation_manager.application.authentication import PartnerAuthenticator +from federation_manager.application.federation import InboundFederationService +from federation_manager.contracts.ewbi import ( + FederationHealthInfo, + FederationHealthResponse, + FederationRequestData, + FederationResponseData, + FederationStatus, +) +from federation_manager.dependencies import ( + get_federation_context_repo, + get_inbound_federation_service, + get_partner_authenticator, +) +from federation_manager.domain.errors import FederationContextUnknown +from federation_manager.domain.ewbi import EWBI_BASE_PATH +from federation_manager.domain.ports import FederationContextRepositoryPort + +router = APIRouter(prefix=EWBI_BASE_PATH, tags=["FederationManagement"]) + +FederationContextIdPath = Annotated[str, Path(pattern=r"^[A-Za-z0-9][A-Za-z0-9-]*$")] + +_WIRE_STATUS: dict[str, FederationStatus] = { + "available": "AVAILABLE", + "locked": "LOCKED", + "not_available": "NOT_AVAILABLE", + "temporary_failure": "TEMPORARY_FAILURE", + "failed": "FAILED", +} + + +@router.get( + "/{federationContextId}/health", + operation_id="GetFederationHealth", + responses=EWBI_ERROR_RESPONSES, +) +async def get_federation_health( + federationContextId: FederationContextIdPath, # noqa: N803 - GSMA path template name + auth: Annotated[PartnerAuthenticator, Depends(get_partner_authenticator)], + contexts: Annotated[FederationContextRepositoryPort, Depends(get_federation_context_repo)], + token: Annotated[str, Depends(get_bearer_token)], +) -> FederationHealthResponse: + partner = await auth.authenticate(token) + context = await contexts.find_inbound(partner.id, federationContextId) + if context is None or context.is_terminated(): + raise FederationContextUnknown(partner.id) + return FederationHealthResponse( + federation_health_status=FederationHealthInfo( + federation_status=_WIRE_STATUS.get(context.status, "NOT_AVAILABLE"), + federation_start_time=context.created_at, + # no catalogue sync yet + num_of_accepted_zones="0", + ) + ) + + +@router.post("/partner", operation_id="CreateFederation", responses=EWBI_ERROR_RESPONSES) +async def create_federation( + body: FederationRequestData, + response: Response, + auth: Annotated[PartnerAuthenticator, Depends(get_partner_authenticator)], + service: Annotated[InboundFederationService, Depends(get_inbound_federation_service)], + token: Annotated[str, Depends(get_bearer_token)], +) -> FederationResponseData: + partner = await auth.authenticate(token) + context, accepted = await service.create(partner, body) + response.headers["Location"] = f"{EWBI_BASE_PATH}/{context.federation_context_id}/partner" + return accepted + + +@router.delete( + "/{federationContextId}/partner", + operation_id="DeleteFederationDetails", + status_code=200, + responses=EWBI_ERROR_RESPONSES, +) +async def delete_federation_details( + federationContextId: FederationContextIdPath, # noqa: N803 - GSMA path template name + auth: Annotated[PartnerAuthenticator, Depends(get_partner_authenticator)], + service: Annotated[InboundFederationService, Depends(get_inbound_federation_service)], + token: Annotated[str, Depends(get_bearer_token)], +) -> None: + partner = await auth.authenticate(token) + await service.terminate(partner, federationContextId) diff --git a/src/federation_manager/api/internal/__init__.py b/src/federation_manager/api/internal/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/federation_manager/api/internal/federation.py b/src/federation_manager/api/internal/federation.py new file mode 100644 index 0000000000000000000000000000000000000000..1b3a8c87e8409dfc73f95883ccfd6be83a7c9058 --- /dev/null +++ b/src/federation_manager/api/internal/federation.py @@ -0,0 +1,83 @@ +from ipaddress import ip_address +from re import compile as re_compile +from typing import Annotated, Any, Literal +from uuid import UUID + +from fastapi import APIRouter, Depends +from pydantic import BaseModel, ConfigDict, Field, model_validator + +from federation_manager.application.outbound import OutboundFederationService, OutboundRequest +from federation_manager.dependencies import get_outbound_federation_service + +router = APIRouter(prefix="/internal/federation", tags=["internal-federation"]) + +# mirrors domain.ewbi.SERVICE_API_NAMES; a test keeps them in sync +ApiType = Literal["device-location-retrieve", "device-status-retrieve"] + +_E164 = re_compile(r"^\+[1-9][0-9]{4,14}$") + + +class ServiceApiBody(BaseModel): + model_config = ConfigDict(extra="forbid", populate_by_name=True) + + media_type: Literal["application/json"] = Field(default="application/json", alias="mediaType") + api_content: dict[str, Any] = Field(alias="APIContent") + + +class OutboundFederationRequest(BaseModel): + model_config = ConfigDict(extra="forbid") + + api_type: ApiType + identifier_type: Literal["msisdn", "ip"] + identifier_value: str = Field(min_length=1, max_length=64) + correlation_id: UUID + customer_id: UUID + customer_info: str = Field(min_length=1, max_length=255) + txn_identifier: str = Field(min_length=1, max_length=255) + service_api_body: ServiceApiBody + event_notification_dest: str | None = None + + @model_validator(mode="after") + def _identifier_matches_type(self) -> "OutboundFederationRequest": + if self.identifier_type == "msisdn": + if not _E164.match(self.identifier_value): + raise ValueError("identifier_value must be an E.164 number (e.g. +34612345678)") + else: + try: + ip_address(self.identifier_value) + except ValueError: + raise ValueError("identifier_value must be an IPv4 or IPv6 address") from None + return self + + +class OutboundFederationResponse(BaseModel): + partner_op_id: UUID + status_code: int + body: Any = None + location: str | None = None + + +@router.post("/outbound") +async def outbound( + body: OutboundFederationRequest, + service: Annotated[OutboundFederationService, Depends(get_outbound_federation_service)], +) -> OutboundFederationResponse: + result = await service.forward( + OutboundRequest( + api_type=body.api_type, + identifier_type=body.identifier_type, + identifier_value=body.identifier_value, + correlation_id=body.correlation_id, + customer_id=body.customer_id, + customer_info=body.customer_info, + txn_identifier=body.txn_identifier, + api_content=body.service_api_body.api_content, + event_notification_dest=body.event_notification_dest, + ) + ) + return OutboundFederationResponse( + partner_op_id=result.partner_op_id, + status_code=result.status_code, + body=result.body, + location=result.location, + ) diff --git a/src/federation_manager/api/platform/__init__.py b/src/federation_manager/api/platform/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/federation_manager/api/platform/health.py b/src/federation_manager/api/platform/health.py new file mode 100644 index 0000000000000000000000000000000000000000..26859e656fb5b227001447c3ba7e33a24747f123 --- /dev/null +++ b/src/federation_manager/api/platform/health.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter + +router = APIRouter(tags=["platform"]) + + +@router.get("/healthz") +def healthz() -> dict[str, str]: + return {"status": "ok"} diff --git a/src/federation_manager/api/security.py b/src/federation_manager/api/security.py new file mode 100644 index 0000000000000000000000000000000000000000..8333ce489ea01281fa207d32096f90da57b3003c --- /dev/null +++ b/src/federation_manager/api/security.py @@ -0,0 +1,29 @@ +from typing import Annotated + +from fastapi import Depends +from fastapi.openapi.models import OAuthFlowClientCredentials, OAuthFlows +from fastapi.security import OAuth2 + +from federation_manager.core.config import get_settings +from federation_manager.domain.errors import AuthenticationFailed + +# securityScheme GSMA declares on the EWBI contract (OPG.04 v6.0 Sec. 9). +oauth2_client_credentials = OAuth2( + flows=OAuthFlows( + clientCredentials=OAuthFlowClientCredentials( + tokenUrl=f"{get_settings().keycloak_issuer}/protocol/openid-connect/token", + scopes={"fed-mgmt": "Access to the federation APIs"}, + ) + ), + scheme_name="oAuth2ClientCredentials", + auto_error=False, +) + + +def get_bearer_token( + authorization: Annotated[str | None, Depends(oauth2_client_credentials)], +) -> str: + scheme, _, token = (authorization or "").partition(" ") + if scheme.lower() != "bearer" or not token: + raise AuthenticationFailed + return token diff --git a/src/federation_manager/application/__init__.py b/src/federation_manager/application/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/federation_manager/application/authentication.py b/src/federation_manager/application/authentication.py new file mode 100644 index 0000000000000000000000000000000000000000..c8239d4869d0f4e460e9b2f2592e132716afa4ff --- /dev/null +++ b/src/federation_manager/application/authentication.py @@ -0,0 +1,22 @@ +from federation_manager.domain.errors import AuthenticationFailed, PartnerNotActive, PartnerUnknown +from federation_manager.domain.models import PartnerOP +from federation_manager.domain.ports import JwtValidatorPort, PartnerRepositoryPort + + +class PartnerAuthenticator: + def __init__( + self, partner_repo: PartnerRepositoryPort, jwt_validator: JwtValidatorPort + ) -> None: + self._partner_repo = partner_repo + self._jwt_validator = jwt_validator + + async def authenticate(self, token: str) -> PartnerOP: + claims = await self._jwt_validator.validate(token) + if not claims.has_scope("fed-mgmt"): + raise AuthenticationFailed + partner = await self._partner_repo.find_by_oauth2_client_id(claims.client_id) + if partner is None: + raise PartnerUnknown(claims.client_id) + if not partner.is_active(): + raise PartnerNotActive(partner.status) + return partner diff --git a/src/federation_manager/application/authorization.py b/src/federation_manager/application/authorization.py new file mode 100644 index 0000000000000000000000000000000000000000..bd55fc9a536a8173bc333d803e7d063d1a83b2a6 --- /dev/null +++ b/src/federation_manager/application/authorization.py @@ -0,0 +1,70 @@ +from collections.abc import Callable +from dataclasses import dataclass +from datetime import datetime, timezone +from uuid import UUID + +from federation_manager.application.authentication import PartnerAuthenticator +from federation_manager.domain.errors import AgreementExpired, AgreementViolation +from federation_manager.domain.models import Agreement, PartnerOP +from federation_manager.domain.ports import AgreementRepositoryPort + + +def _utcnow() -> datetime: + return datetime.now(timezone.utc) + + +class AgreementChecker: + def __init__( + self, + agreement_repo: AgreementRepositoryPort, + clock: Callable[[], datetime] = _utcnow, + ) -> None: + self._agreement_repo = agreement_repo + self._clock = clock + + async def require( + self, partner: PartnerOP, api_type: str, zone_id: UUID | None = None + ) -> Agreement: + agreement = await self._agreement_repo.find_active_for_partner(partner.id) + if agreement is None or not agreement.is_valid_at(self._clock()): + raise AgreementExpired + if not agreement.permits_api(api_type): + raise AgreementViolation + if zone_id is not None and not agreement.permits_zone(zone_id): + raise AgreementViolation + return agreement + + +@dataclass +class AuthorizedRequest: + partner: PartnerOP + agreement: Agreement + service_specification_id: UUID + + +class FederationAuthorizer: + def __init__( + self, + authenticator: PartnerAuthenticator, + agreement_repo: AgreementRepositoryPort, + ) -> None: + self._authenticator = authenticator + self._agreements = AgreementChecker(agreement_repo) + + async def authorize_app( + self, + token: str, + api_type: str, + app_id: str, + app_version: str, + flavour_id: str, + zone_id: UUID | None = None, + ) -> AuthorizedRequest: + partner = await self._authenticator.authenticate(token) + agreement = await self._agreements.require(partner, api_type, zone_id) + + spec_id = agreement.resolve_app_spec(app_id, app_version, flavour_id) + if spec_id is None: + raise AgreementViolation + + return AuthorizedRequest(partner, agreement, spec_id) diff --git a/src/federation_manager/application/deployment.py b/src/federation_manager/application/deployment.py new file mode 100644 index 0000000000000000000000000000000000000000..846f5d1ccaaf086a14e77640b4e323e4ee5c83cf --- /dev/null +++ b/src/federation_manager/application/deployment.py @@ -0,0 +1,154 @@ +import json +from collections.abc import Callable +from dataclasses import dataclass +from datetime import datetime, timezone +from hashlib import sha256 +from uuid import UUID, uuid4 + +from federation_manager.application.authentication import PartnerAuthenticator +from federation_manager.application.authorization import AgreementChecker +from federation_manager.contracts.ewbi import InstallAppRequest +from federation_manager.contracts.srm import ( + SUBJECT_DEPLOY, + DeployPayloadV1, + DeployTargetV1, + SrmServiceDeployV1, +) +from federation_manager.domain.errors import ( + AgreementViolation, + FederationContextUnknown, + IdempotencyKeyReused, +) +from federation_manager.domain.models import FederationTransaction +from federation_manager.domain.ports import ( + DataBusPublisherPort, + FederationContextRepositoryPort, + TransactionRepositoryPort, +) + +API_TYPE = "install-app" +INBOUND = "inbound" + + +def _utcnow() -> datetime: + return datetime.now(timezone.utc) + + +@dataclass(frozen=True) +class DeploymentAccepted: + zone_id: str + app_instance_identifier: str + + +class InboundDeploymentService: + def __init__( + self, + authenticator: PartnerAuthenticator, + contexts: FederationContextRepositoryPort, + agreements: AgreementChecker, + transactions: TransactionRepositoryPort, + publisher: DataBusPublisherPort, + *, + clock: Callable[[], datetime] = _utcnow, + id_factory: Callable[[], UUID] = uuid4, + ) -> None: + self._authenticator = authenticator + self._contexts = contexts + self._agreements = agreements + self._transactions = transactions + self._publisher = publisher + self._clock = clock + self._new_id = id_factory + + async def install( + self, + token: str, + federation_context_id: str, + idempotency_key: str, + request: InstallAppRequest, + ) -> DeploymentAccepted: + partner = await self._authenticator.authenticate(token) + context = await self._contexts.find_inbound(partner.id, federation_context_id) + if context is None or context.is_terminated(): + raise FederationContextUnknown(partner.id) + + fingerprint = _fingerprint(request) + replay = await self._transactions.find_by_idempotency_key( + partner.id, API_TYPE, idempotency_key + ) + if replay is not None: + if replay.request_fingerprint != fingerprint: + raise IdempotencyKeyReused(idempotency_key) + return DeploymentAccepted( + zone_id=str(replay.request_summary["zone_id"]), + app_instance_identifier=str(replay.external_resource_id), + ) + + zone_id = _zone_uuid(request.zone_info.zone_id) + agreement = await self._agreements.require(partner, API_TYPE, zone_id) + specification_id = agreement.resolve_app_spec( + request.app_id, request.app_version, request.zone_info.flavour_id + ) + if specification_id is None: + raise AgreementViolation + + app_instance_id = self._new_id() + operation_id = self._new_id() + correlation_id = self._new_id() + transaction = FederationTransaction( + id=self._new_id(), + partner_op_id=partner.id, + agreement_id=agreement.id, + federation_context_row_id=context.id, + direction=INBOUND, + operation_id=operation_id, + correlation_id=correlation_id, + api_type=API_TYPE, + status="pending", + idempotency_key=idempotency_key, + request_fingerprint=fingerprint, + external_resource_id=app_instance_id.hex, + callback_url=request.app_inst_callback_link, + callback_status="pending", + request_summary={ + "federation_context_id": federation_context_id, + "app_id": request.app_id, + "app_version": request.app_version, + "flavour_id": request.zone_info.flavour_id, + "zone_id": request.zone_info.zone_id, + "service_specification_id": str(specification_id), + }, + started_at=self._clock(), + ) + await self._transactions.add(transaction) + + command = SrmServiceDeployV1( + operation_id=operation_id, + correlation_id=str(correlation_id), + requested_at=self._clock(), + app_provider_id=str(partner.id), + federation_partner_ref=partner.mcc_mnc, + source="federation", + service_specification_id=specification_id, + targets=[DeployTargetV1(app_instance_id=app_instance_id, zone_id=zone_id)], + deploy=DeployPayloadV1(), + ) + await self._publisher.publish(SUBJECT_DEPLOY, command.model_dump(mode="json")) + await self._transactions.mark_in_progress(transaction.id) + + return DeploymentAccepted( + zone_id=request.zone_info.zone_id, app_instance_identifier=app_instance_id.hex + ) + + +def _fingerprint(request: InstallAppRequest) -> str: + canonical = json.dumps(request.model_dump(mode="json", by_alias=True), sort_keys=True) + return sha256(canonical.encode()).hexdigest() + + +def _zone_uuid(zone_id: str) -> UUID: + # ADR-0017: the EWBI zone identifier is SRM's zone UUID, passed through unchanged. + try: + return UUID(zone_id) + except ValueError: + raise AgreementViolation from None diff --git a/src/federation_manager/application/events.py b/src/federation_manager/application/events.py new file mode 100644 index 0000000000000000000000000000000000000000..370f9438dd8092e21c771c29e62ff7bb480c5a6b --- /dev/null +++ b/src/federation_manager/application/events.py @@ -0,0 +1,135 @@ +from collections.abc import Callable +from datetime import datetime, timezone +from typing import Any + +from federation_manager.contracts.ewbi import ( + AppInstanceInfo, + InstanceState, + InstanceStatusCallback, +) +from federation_manager.contracts.srm import CompletedInstanceV1, SrmOperationCompletedV1 +from federation_manager.domain.models import FederationTransaction +from federation_manager.domain.ports import ( + CallbackClientPort, + PartnerRepositoryPort, + TransactionRepositoryPort, +) + + +def _utcnow() -> datetime: + return datetime.now(timezone.utc) + + +class OperationCompletedConsumer: + def __init__( + self, + transactions: TransactionRepositoryPort, + partners: PartnerRepositoryPort, + callbacks: CallbackClientPort, + *, + clock: Callable[[], datetime] = _utcnow, + ) -> None: + self._transactions = transactions + self._partners = partners + self._callbacks = callbacks + self._clock = clock + + async def handle(self, payload: dict[str, Any]) -> None: + event = SrmOperationCompletedV1.model_validate(payload) + transaction = await self._transactions.find_by_operation_id(event.operation_id) + if transaction is None: + # Not ours: OEG-originated operations share the stream. + return + + instances = [ + { + "service_instance_id": str(instance.service_instance_id), + "zone_id": str(instance.zone_id), + "status": instance.status, + } + for instance in event.instances or [] + ] + await self._transactions.record_outcome( + transaction.id, + status=event.status, + completed_at=event.completed_at or self._clock(), + response_summary={"instances": instances}, + error_detail=event.error, + ) + await self._notify_partner(transaction, event) + + async def _notify_partner( + self, transaction: FederationTransaction, event: SrmOperationCompletedV1 + ) -> None: + if not transaction.callback_url: + return + partner = await self._partners.find_by_id(transaction.partner_op_id) + if partner is None: + return + + delivered = True + for body in self._callback_bodies(transaction, event): + payload = body.model_dump(mode="json", by_alias=True, exclude_none=True) + delivered &= await self._callbacks.deliver(partner, transaction.callback_url, payload) + await self._transactions.record_callback( + transaction.id, status="delivered" if delivered else "failed" + ) + + def _callback_bodies( + self, transaction: FederationTransaction, event: SrmOperationCompletedV1 + ) -> list[InstanceStatusCallback]: + summary = transaction.request_summary + context_id = str(summary.get("federation_context_id", "")) + app_id = str(summary.get("app_id", "")) + message = str(event.error.get("title")) if event.error else None + + if not event.instances: + return [ + _callback( + context_id, + app_id, + str(transaction.external_resource_id), + str(summary.get("zone_id", "")), + "FAILED", + message, + ) + ] + return [ + _callback( + context_id, + app_id, + instance.service_instance_id.hex, + str(instance.zone_id), + _instance_state(instance), + _instance_message(instance) or message, + ) + for instance in event.instances + ] + + +def _instance_state(instance: CompletedInstanceV1) -> InstanceState: + return "READY" if instance.status == "completed" else "FAILED" + + +def _instance_message(instance: CompletedInstanceV1) -> str | None: + if not instance.error: + return None + title = instance.error.get("title") + return str(title) if title is not None else None + + +def _callback( + context_id: str, + app_id: str, + instance_id: str, + zone_id: str, + state: InstanceState, + message: str | None, +) -> InstanceStatusCallback: + return InstanceStatusCallback( + federation_context_id=context_id, + app_id=app_id, + app_instance_id=instance_id, + zone_id=zone_id, + app_instance_info=AppInstanceInfo(app_instance_state=state, message=message), + ) diff --git a/src/federation_manager/application/federation.py b/src/federation_manager/application/federation.py new file mode 100644 index 0000000000000000000000000000000000000000..e206fb3493e8b90134abaff2b3759be78a3c70d2 --- /dev/null +++ b/src/federation_manager/application/federation.py @@ -0,0 +1,162 @@ +from collections.abc import Callable +from dataclasses import dataclass +from datetime import datetime, timezone +from uuid import UUID, uuid4 + +from pydantic import ValidationError + +from federation_manager.contracts.ewbi import ( + FederationRequestData, + FederationResponseData, + MobileNetworkIds, +) +from federation_manager.domain.errors import ( + FederationAlreadyExists, + FederationContextUnknown, + FederationError, + FederationEstablishmentFailed, + PartnerNotActive, + PartnerResponseInvalid, +) +from federation_manager.domain.ewbi import CREATE_FEDERATION_PATH +from federation_manager.domain.models import FederationContext, PartnerOP +from federation_manager.domain.ports import ( + EwbiClientPort, + FederationContextRepositoryPort, + PartnerRepositoryPort, +) + +OUTBOUND = "outbound" +INBOUND = "inbound" +AVAILABLE = "available" +TERMINATED = "terminated" + + +def _utcnow() -> datetime: + return datetime.now(timezone.utc) + + +@dataclass(frozen=True) +class LocalOperator: + federation_id: str + country_code: str + mcc: str + mncs: tuple[str, ...] + partner_status_link: str + platform_caps: tuple[str, ...] = ("serviceAPIs",) + + +class FederationEstablishmentService: + def __init__( + self, + partner_repo: PartnerRepositoryPort, + contexts: FederationContextRepositoryPort, + ewbi_client: EwbiClientPort, + local: LocalOperator, + *, + clock: Callable[[], datetime] = _utcnow, + id_factory: Callable[[], UUID] = uuid4, + ) -> None: + self._partner_repo = partner_repo + self._contexts = contexts + self._ewbi_client = ewbi_client + self._local = local + self._clock = clock + self._new_id = id_factory + + async def establish(self, partner: PartnerOP) -> FederationContext: + if not partner.is_active(): + raise PartnerNotActive(partner.status) + existing = await self._contexts.find_active_outbound(partner.id) + if existing is not None: + return existing + + request = FederationRequestData( + initial_date=self._clock(), + partner_status_link=self._local.partner_status_link, + orig_op_federation_id=self._local.federation_id, + orig_op_country_code=self._local.country_code, + orig_op_mobile_network_codes=MobileNetworkIds( + mcc=self._local.mcc, mncs=list(self._local.mncs) + ), + ) + response = await self._ewbi_client.post( + partner, + CREATE_FEDERATION_PATH, + request.model_dump(mode="json", by_alias=True, exclude_none=True), + ) + if not response.is_success(): + raise FederationEstablishmentFailed(partner.id, response.status_code) + try: + accepted = FederationResponseData.model_validate(response.body) + except ValidationError: + raise PartnerResponseInvalid(partner.id) from None + + context = FederationContext( + id=self._new_id(), + partner_op_id=partner.id, + direction=OUTBOUND, + federation_context_id=accepted.federation_context_id, + status=AVAILABLE, + created_at=self._clock(), + status_callback_url=self._local.partner_status_link, + ) + await self._contexts.add(context) + return context + + async def establish_missing(self) -> list[FederationContext]: + established: list[FederationContext] = [] + for partner in await self._partner_repo.list_active(): + if await self._contexts.find_active_outbound(partner.id) is not None: + continue + try: + established.append(await self.establish(partner)) + except FederationError: + # One unreachable or half-configured partner must not stop FM from starting. + continue + return established + + +class InboundFederationService: + def __init__( + self, + contexts: FederationContextRepositoryPort, + local: LocalOperator, + *, + clock: Callable[[], datetime] = _utcnow, + id_factory: Callable[[], UUID] = uuid4, + context_id_factory: Callable[[], str] = lambda: uuid4().hex, + ) -> None: + self._contexts = contexts + self._local = local + self._clock = clock + self._new_id = id_factory + self._new_context_id = context_id_factory + + async def create( + self, partner: PartnerOP, request: FederationRequestData + ) -> tuple[FederationContext, FederationResponseData]: + if await self._contexts.find_active_inbound(partner.id) is not None: + raise FederationAlreadyExists(partner.id) + + context = FederationContext( + id=self._new_id(), + partner_op_id=partner.id, + direction=INBOUND, + federation_context_id=self._new_context_id(), + status=AVAILABLE, + created_at=self._clock(), + status_callback_url=request.partner_status_link, + ) + await self._contexts.add(context) + return context, FederationResponseData( + federation_context_id=context.federation_context_id, + platform_caps=list(self._local.platform_caps), + partner_op_federation_id=self._local.federation_id, + ) + + async def terminate(self, partner: PartnerOP, federation_context_id: str) -> None: + context = await self._contexts.find_inbound(partner.id, federation_context_id) + if context is None or context.is_terminated(): + raise FederationContextUnknown(partner.id) + await self._contexts.set_status(context.id, TERMINATED) diff --git a/src/federation_manager/application/outbound.py b/src/federation_manager/application/outbound.py new file mode 100644 index 0000000000000000000000000000000000000000..64a2aabdacf000e2c80195dde4351ef238d9ba8e --- /dev/null +++ b/src/federation_manager/application/outbound.py @@ -0,0 +1,189 @@ +from collections.abc import Callable +from dataclasses import dataclass +from datetime import datetime, timezone +from typing import Any +from uuid import UUID, uuid4 + +from pydantic import ValidationError + +from federation_manager.application.authorization import AgreementChecker +from federation_manager.contracts.ewbi import ( + ApiForwardingRequest, + ServiceApiContent, + ServiceApiResponse, +) +from federation_manager.domain.errors import ( + FederationContextMissing, + NoRouteMatched, + PartnerEndpointConfigurationError, + PartnerNotActive, + PartnerRequestFailed, + PartnerResponseInvalid, + PartnerTokenConfigurationError, + PartnerTokenRequestFailed, + problem_type, +) +from federation_manager.domain.ewbi import api_forwarding_path, service_api_name +from federation_manager.domain.models import EwbiResponse, FederationTransaction +from federation_manager.domain.ports import ( + EwbiClientPort, + FederationContextRepositoryPort, + PartnerRepositoryPort, + TransactionRepositoryPort, +) +from federation_manager.domain.routing import RoutingResolver + +OUTBOUND = "outbound" + + +def _utcnow() -> datetime: + return datetime.now(timezone.utc) + + +@dataclass(frozen=True) +class OutboundRequest: + api_type: str + identifier_type: str # msisdn | ip + identifier_value: str + correlation_id: UUID + customer_id: UUID + customer_info: str + txn_identifier: str + api_content: dict[str, Any] + event_notification_dest: str | None = None + + +@dataclass(frozen=True) +class OutboundResult: + partner_op_id: UUID + status_code: int + body: object | None + location: str | None = None + + +class OutboundFederationService: + def __init__( + self, + routing: RoutingResolver, + partner_repo: PartnerRepositoryPort, + contexts: FederationContextRepositoryPort, + agreements: AgreementChecker, + transactions: TransactionRepositoryPort, + ewbi_client: EwbiClientPort, + *, + clock: Callable[[], datetime] = _utcnow, + id_factory: Callable[[], UUID] = uuid4, + ) -> None: + self._routing = routing + self._partner_repo = partner_repo + self._contexts = contexts + self._agreements = agreements + self._transactions = transactions + self._ewbi_client = ewbi_client + self._clock = clock + self._new_id = id_factory + + async def forward(self, request: OutboundRequest) -> OutboundResult: + service_api = service_api_name(request.api_type) + + resolution = await self._routing.resolve_partner( + request.identifier_type, request.identifier_value + ) + if resolution is None: + raise NoRouteMatched(request.identifier_type) + partner = await self._partner_repo.find_by_id(resolution.partner_op_id) + if partner is None: + raise NoRouteMatched(request.identifier_type) + if not partner.is_active(): + raise PartnerNotActive(partner.status) + agreement = await self._agreements.require(partner, request.api_type) + context = await self._contexts.find_active_outbound(partner.id) + if context is None: + raise FederationContextMissing(partner.id) + + path = api_forwarding_path(context.federation_context_id, service_api) + wrapper = ApiForwardingRequest( + api_service_id=service_api, + customer_id=request.customer_id, + customer_info=request.customer_info, + txn_identifier=request.txn_identifier, + service_api_body=ServiceApiContent(api_content=request.api_content), + event_notification_dest=request.event_notification_dest, + ) + + transaction = FederationTransaction( + id=self._new_id(), + partner_op_id=partner.id, + agreement_id=agreement.id, + federation_context_row_id=context.id, + direction=OUTBOUND, + correlation_id=request.correlation_id, + external_txn_id=request.txn_identifier, + api_type=request.api_type, + status="pending", + request_summary={ + "api_type": request.api_type, + "service_api": service_api, + "identifier_type": request.identifier_type, + # matched range only: the subscriber identifier is PII + "route_match": resolution.match, + }, + started_at=self._clock(), + ) + # persisted before the partner call so a crash mid-flight still leaves an audit row + await self._transactions.add(transaction) + + try: + response = await self._ewbi_client.post( + partner, path, wrapper.model_dump(mode="json", by_alias=True, exclude_none=True) + ) + except (PartnerRequestFailed, PartnerTokenRequestFailed): + await self._fail(transaction.id, {"type": problem_type("partner-unreachable")}) + raise + except (PartnerEndpointConfigurationError, PartnerTokenConfigurationError): + await self._fail(transaction.id, {"type": problem_type("internal-error")}) + raise + + if not response.is_success(): + await self._record_partner_error(transaction.id, response) + return OutboundResult( + partner.id, response.status_code, response.body, response.location + ) + + try: + forwarded = ServiceApiResponse.model_validate(response.body) + except ValidationError: + await self._fail(transaction.id, {"type": problem_type("partner-response-invalid")}) + raise PartnerResponseInvalid(partner.id) from None + + body = forwarded.api_response.response_content if forwarded.api_response else None + await self._transactions.record_outcome( + transaction.id, + status="completed", + completed_at=self._clock(), + response_summary={ + "status_code": response.status_code, + "session": forwarded.target_user_context is not None, + }, + ) + return OutboundResult(partner.id, response.status_code, body, response.location) + + async def _record_partner_error(self, transaction_id: UUID, response: EwbiResponse) -> None: + error_detail: dict[str, object] = {"status_code": response.status_code} + if isinstance(response.body, dict): + error_detail["problem"] = response.body + await self._transactions.record_outcome( + transaction_id, + status="failed", + completed_at=self._clock(), + response_summary={"status_code": response.status_code}, + error_detail=error_detail, + ) + + async def _fail(self, transaction_id: UUID, error_detail: dict[str, object]) -> None: + await self._transactions.record_outcome( + transaction_id, + status="failed", + completed_at=self._clock(), + error_detail=error_detail, + ) diff --git a/src/federation_manager/contracts/__init__.py b/src/federation_manager/contracts/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/federation_manager/contracts/ewbi.py b/src/federation_manager/contracts/ewbi.py new file mode 100644 index 0000000000000000000000000000000000000000..58703f2a6e8e7bc693f892979be7bc0417c1a1d1 --- /dev/null +++ b/src/federation_manager/contracts/ewbi.py @@ -0,0 +1,260 @@ +# Vendored from GSMA OPG.04 v6.0 EWBI Federation API v1.4.0 plus +# docs/opg04-v1.4.0-oop-profile.overlay.yaml; the published artifact wins. +from datetime import datetime +from typing import Any, Literal, Self +from uuid import UUID + +from pydantic import BaseModel, ConfigDict, Field, model_validator + + +class ServiceApiContent(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + media_type: Literal["application/json"] = Field( + default="application/json", validation_alias="mediaType", serialization_alias="mediaType" + ) + api_content: dict[str, Any] = Field( + validation_alias="APIContent", serialization_alias="APIContent" + ) + + +class ApiForwardingRequest(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + api_service_id: str = Field(validation_alias="apiServiceId", serialization_alias="apiServiceId") + customer_id: UUID = Field(validation_alias="customerID", serialization_alias="customerID") + customer_info: str = Field(validation_alias="customerInfo", serialization_alias="customerInfo") + txn_identifier: str = Field( + validation_alias="txnIdentifier", serialization_alias="txnIdentifier" + ) + service_api_body: ServiceApiContent = Field( + validation_alias="ServiceAPIBody", serialization_alias="ServiceAPIBody" + ) + event_notification_dest: str | None = Field( + default=None, + validation_alias="eventNotificationDest", + serialization_alias="eventNotificationDest", + ) + + +class ExpiryInterval(BaseModel): + model_config = ConfigDict(extra="ignore", populate_by_name=True) + + num_hours: int = Field(validation_alias="numHours", serialization_alias="numHours") + num_mins: int = Field(validation_alias="numMins", serialization_alias="numMins") + num_secs: int = Field(validation_alias="numSecs", serialization_alias="numSecs") + + +class TargetUserContext(BaseModel): + model_config = ConfigDict(extra="ignore", populate_by_name=True) + + connect_id: str = Field(validation_alias="connectID", serialization_alias="connectID") + expiry_duration: ExpiryInterval = Field( + validation_alias="expiryDuration", serialization_alias="expiryDuration" + ) + + +class ApiResponseBody(BaseModel): + model_config = ConfigDict(extra="ignore", populate_by_name=True) + + media_type: str = Field(validation_alias="mediaType", serialization_alias="mediaType") + response_content: dict[str, Any] = Field( + validation_alias="responseContent", serialization_alias="responseContent" + ) + + +class ServiceApiResponse(BaseModel): + model_config = ConfigDict(extra="ignore", populate_by_name=True) + + customer_id: UUID = Field(validation_alias="customerID", serialization_alias="customerID") + txn_identifier: str = Field( + validation_alias="txnIdentifier", serialization_alias="txnIdentifier" + ) + target_user_context: TargetUserContext | None = Field( + default=None, validation_alias="targetUserContext", serialization_alias="targetUserContext" + ) + api_response: ApiResponseBody | None = Field( + default=None, validation_alias="apiResponse", serialization_alias="apiResponse" + ) + + @model_validator(mode="after") + def _requires_one_conditional_member(self) -> Self: + if self.target_user_context is None and self.api_response is None: + raise ValueError("serviceAPIResponse needs targetUserContext or apiResponse") + return self + + +class MobileNetworkIds(BaseModel): + model_config = ConfigDict(extra="ignore") + + mcc: str + mncs: list[str] = Field(min_length=1) + + +class FederationRequestData(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + initial_date: datetime = Field( + validation_alias="initialDate", serialization_alias="initialDate" + ) + partner_status_link: str = Field( + validation_alias="partnerStatusLink", serialization_alias="partnerStatusLink" + ) + orig_op_federation_id: str | None = Field( + default=None, + validation_alias="origOPFederationId", + serialization_alias="origOPFederationId", + ) + orig_op_country_code: str | None = Field( + default=None, + validation_alias="origOPCountryCode", + serialization_alias="origOPCountryCode", + ) + orig_op_mobile_network_codes: MobileNetworkIds | None = Field( + default=None, + validation_alias="origOPMobileNetworkCodes", + serialization_alias="origOPMobileNetworkCodes", + ) + + +class FederationResponseData(BaseModel): + model_config = ConfigDict(extra="ignore", populate_by_name=True) + + federation_context_id: str = Field( + validation_alias="federationContextId", serialization_alias="federationContextId" + ) + platform_caps: list[str] = Field( + validation_alias="platformCaps", serialization_alias="platformCaps" + ) + partner_op_federation_id: str | None = Field( + default=None, + validation_alias="partnerOPFederationId", + serialization_alias="partnerOPFederationId", + ) + offered_availability_zones: list[dict[str, Any]] | None = Field( + default=None, + validation_alias="offeredAvailabilityZones", + serialization_alias="offeredAvailabilityZones", + ) + federation_expiry_date: datetime | None = Field( + default=None, + validation_alias="federationExpiryDate", + serialization_alias="federationExpiryDate", + ) + federation_renewal_date: datetime | None = Field( + default=None, + validation_alias="federationRenewalDate", + serialization_alias="federationRenewalDate", + ) + + +FederationStatus = Literal["FAILED", "TEMPORARY_FAILURE", "AVAILABLE", "LOCKED", "NOT_AVAILABLE"] + + +class FederationHealthInfo(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + federation_status: FederationStatus = Field( + validation_alias="federationStatus", serialization_alias="federationStatus" + ) + federation_start_time: datetime = Field( + validation_alias="federationStartTime", serialization_alias="federationStartTime" + ) + num_of_accepted_zones: str = Field( + validation_alias="numOfAcceptedZones", serialization_alias="numOfAcceptedZones" + ) + num_of_active_alarms: str | None = Field( + default=None, + validation_alias="numOfActiveAlarms", + serialization_alias="numOfActiveAlarms", + ) + num_of_applications: str | None = Field( + default=None, + validation_alias="numOfApplications", + serialization_alias="numOfApplications", + ) + + +class FederationHealthResponse(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + federation_health_status: FederationHealthInfo = Field( + validation_alias="federationHealthStatus", serialization_alias="federationHealthStatus" + ) + + +class InvalidParam(BaseModel): + param: str + reason: str | None = None + + +class ProblemDetails(BaseModel): + title: str | None = None + detail: str | None = None + cause: str | None = None + invalid_params: list[InvalidParam] | None = Field( + default=None, validation_alias="invalidParams", serialization_alias="invalidParams" + ) + + +class ZoneInfo(BaseModel): + model_config = ConfigDict(extra="ignore", populate_by_name=True) + + zone_id: str = Field(validation_alias="zoneId", serialization_alias="zoneId") + flavour_id: str = Field(validation_alias="flavourId", serialization_alias="flavourId") + resource_consumption: str | None = Field( + default=None, + validation_alias="resourceConsumption", + serialization_alias="resourceConsumption", + ) + + +class InstallAppRequest(BaseModel): + model_config = ConfigDict(extra="ignore", populate_by_name=True) + + app_id: str = Field(validation_alias="appId", serialization_alias="appId") + app_version: str = Field(validation_alias="appVersion", serialization_alias="appVersion") + app_provider_id: str = Field( + validation_alias="appProviderId", serialization_alias="appProviderId" + ) + zone_info: ZoneInfo = Field(validation_alias="zoneInfo", serialization_alias="zoneInfo") + app_inst_callback_link: str = Field( + validation_alias="appInstCallbackLink", serialization_alias="appInstCallbackLink" + ) + + +class InstallAppResponse(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + zone_id: str = Field(validation_alias="zoneId", serialization_alias="zoneId") + app_inst_identifier: str = Field( + validation_alias="appInstIdentifier", serialization_alias="appInstIdentifier" + ) + + +InstanceState = Literal["PENDING", "READY", "FAILED", "TERMINATING"] + + +class AppInstanceInfo(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + app_instance_state: InstanceState = Field( + validation_alias="appInstanceState", serialization_alias="appInstanceState" + ) + message: str | None = None + + +class InstanceStatusCallback(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + federation_context_id: str = Field( + validation_alias="federationContextId", serialization_alias="federationContextId" + ) + app_id: str = Field(validation_alias="appId", serialization_alias="appId") + app_instance_id: str = Field( + validation_alias="appInstanceId", serialization_alias="appInstanceId" + ) + zone_id: str = Field(validation_alias="zoneId", serialization_alias="zoneId") + app_instance_info: AppInstanceInfo = Field( + validation_alias="appInstanceInfo", serialization_alias="appInstanceInfo" + ) diff --git a/src/federation_manager/contracts/srm.py b/src/federation_manager/contracts/srm.py new file mode 100644 index 0000000000000000000000000000000000000000..c9e824af5ed2d0a0495d37f270b5375ab996bfb3 --- /dev/null +++ b/src/federation_manager/contracts/srm.py @@ -0,0 +1,90 @@ +# Vendored from srm/interface-contract.md §B/§C — hand-synced; SRM's spec wins. +from datetime import datetime +from typing import Literal +from uuid import UUID + +from pydantic import BaseModel, ConfigDict, Field + +Source = Literal["nbi_camara", "nbi_tmf", "operator_portal", "federation"] + +TASK_STREAM = "OOP_TASKS" +TASK_STREAM_MAX_AGE_SECONDS = 24 * 60 * 60 +EVENT_STREAM = "OOP_EVENTS" +SUBJECT_OPERATION_COMPLETED = "event.srm.operation.completed" +SUBJECT_DEPLOY = "command.srm.service.deploy" +SUBJECT_TERMINATE = "command.srm.service.terminate" + + +class CommandEnvelopeV1(BaseModel): + schema_version: str = "1.0" + operation_id: UUID + correlation_id: str + requested_at: datetime + app_provider_id: str + federation_partner_ref: str | None = None + source: Source + + +class DeployTargetV1(BaseModel): + app_instance_id: UUID + zone_id: UUID | None = None + domain_id: UUID | None = None + + +class DeployPayloadV1(BaseModel): + instance_name: str | None = None + placement_constraints: dict[str, object] | None = None + + +class SrmServiceDeployV1(CommandEnvelopeV1): + service_specification_id: UUID + targets: list[DeployTargetV1] = Field(min_length=1) + deploy: DeployPayloadV1 + + +class TerminatePayloadV1(BaseModel): + grace_period_seconds: int = 0 + + +class SrmServiceTerminateV1(CommandEnvelopeV1): + service_instance_id: UUID + service_specification_id: UUID | None = None + terminate: TerminatePayloadV1 = Field(default_factory=TerminatePayloadV1) + + +class SrmOperationStatusV1(BaseModel): + model_config = ConfigDict(extra="ignore") + + schema_version: str + operation_id: UUID + service_order_id: UUID | None = None + service_instance_id: UUID | None = None + capability: str | None = None + state: Literal["accepted", "failed_before_start", "completed", "failed", "in_progress"] + metadata: dict[str, object] | None = None + correlation_id: str + emitted_at: datetime + + +class CompletedInstanceV1(BaseModel): + model_config = ConfigDict(extra="ignore") + + service_instance_id: UUID + zone_id: UUID + status: Literal["completed", "failed"] + external_ref: str | None = None + error: dict[str, object] | None = None + + +class SrmOperationCompletedV1(BaseModel): + model_config = ConfigDict(extra="ignore") + + schema_version: str + operation_id: UUID + status: Literal["completed", "partially_completed", "failed"] + service_order_id: UUID | None = None + instances: list[CompletedInstanceV1] | None = None + metadata: dict[str, object] | None = None + error: dict[str, object] | None = None + correlation_id: str + completed_at: datetime diff --git a/src/federation_manager/core/__init__.py b/src/federation_manager/core/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/federation_manager/core/config.py b/src/federation_manager/core/config.py new file mode 100644 index 0000000000000000000000000000000000000000..7f8e393a8e2b744d876655072decc041b191012f --- /dev/null +++ b/src/federation_manager/core/config.py @@ -0,0 +1,30 @@ +from functools import lru_cache + +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + model_config = SettingsConfigDict(env_prefix="FM_", env_file=".env", extra="ignore") + + postgres_url: str = "postgresql+asyncpg://fm:fm@localhost:5433/fm_db" + postgres_echo: bool = False + nats_url: str = "nats://localhost:4222" + event_consumer_durable: str = "fm-event-worker" + keycloak_issuer: str = "http://localhost:8090/realms/federation" + + # Our own OPG.04 identity, sent on outbound CreateFederation. + federation_id: str = "oop-local" + country_code: str = "ES" + mcc: str = "214" + mncs: tuple[str, ...] = ("07",) + partner_status_link: str = "https://localhost/operatorplatform/federation/v1/partner-status" + platform_caps: tuple[str, ...] = ("serviceAPIs",) + # Local stacks have no TLS. Never enable outside development. + allow_insecure_partner_endpoints: bool = False + # ADR-0043 bootstrap path: federate with every active partner that has no outbound context. + bootstrap_federation: bool = False + + +@lru_cache +def get_settings() -> Settings: + return Settings() diff --git a/src/federation_manager/dependencies.py b/src/federation_manager/dependencies.py new file mode 100644 index 0000000000000000000000000000000000000000..4400200584dc8755c43be9fbe7c5737574cd0210 --- /dev/null +++ b/src/federation_manager/dependencies.py @@ -0,0 +1,144 @@ +from collections.abc import AsyncIterator +from typing import Annotated + +from fastapi import Depends, Request +from sqlalchemy.ext.asyncio import AsyncSession + +from federation_manager.adapters.database.agreement_repo import PostgresAgreementRepo +from federation_manager.adapters.database.federation_context_repo import ( + PostgresFederationContextRepo, +) +from federation_manager.adapters.database.partner_repo import PostgresPartnerRepo +from federation_manager.adapters.database.routing_repo import PostgresRoutingRuleRepo +from federation_manager.adapters.database.transaction_repo import PostgresTransactionRepo +from federation_manager.application.authentication import PartnerAuthenticator +from federation_manager.application.authorization import AgreementChecker +from federation_manager.application.deployment import InboundDeploymentService +from federation_manager.application.federation import InboundFederationService, LocalOperator +from federation_manager.application.outbound import OutboundFederationService +from federation_manager.core.config import get_settings +from federation_manager.domain.ports import ( + AgreementRepositoryPort, + DataBusPublisherPort, + EwbiClientPort, + FederationContextRepositoryPort, + JwtValidatorPort, + PartnerRepositoryPort, + PartnerTokenProviderPort, + RoutingRuleRepositoryPort, + TransactionRepositoryPort, +) +from federation_manager.domain.routing import RoutingResolver + + +async def get_session(request: Request) -> AsyncIterator[AsyncSession]: + async with request.app.state.session_maker() as session: + yield session + + +def get_partner_repo( + session: Annotated[AsyncSession, Depends(get_session)], +) -> PartnerRepositoryPort: + return PostgresPartnerRepo(session) + + +def get_agreement_repo( + session: Annotated[AsyncSession, Depends(get_session)], +) -> AgreementRepositoryPort: + return PostgresAgreementRepo(session) + + +def get_federation_context_repo( + session: Annotated[AsyncSession, Depends(get_session)], +) -> FederationContextRepositoryPort: + return PostgresFederationContextRepo(session) + + +def get_routing_rule_repo( + session: Annotated[AsyncSession, Depends(get_session)], +) -> RoutingRuleRepositoryPort: + return PostgresRoutingRuleRepo(session) + + +def get_transaction_repo( + session: Annotated[AsyncSession, Depends(get_session)], +) -> TransactionRepositoryPort: + return PostgresTransactionRepo(session) + + +def get_jwt_validator(request: Request) -> JwtValidatorPort: + validator: JwtValidatorPort = request.app.state.jwt_validator + return validator + + +def get_partner_token_provider(request: Request) -> PartnerTokenProviderPort: + provider: PartnerTokenProviderPort = request.app.state.partner_token_provider + return provider + + +def get_ewbi_client(request: Request) -> EwbiClientPort: + client: EwbiClientPort = request.app.state.ewbi_client + return client + + +def get_partner_authenticator( + repo: Annotated[PartnerRepositoryPort, Depends(get_partner_repo)], + jwt_validator: Annotated[JwtValidatorPort, Depends(get_jwt_validator)], +) -> PartnerAuthenticator: + return PartnerAuthenticator(repo, jwt_validator) + + +def get_outbound_federation_service( + routing_repo: Annotated[RoutingRuleRepositoryPort, Depends(get_routing_rule_repo)], + partner_repo: Annotated[PartnerRepositoryPort, Depends(get_partner_repo)], + context_repo: Annotated[FederationContextRepositoryPort, Depends(get_federation_context_repo)], + agreement_repo: Annotated[AgreementRepositoryPort, Depends(get_agreement_repo)], + transaction_repo: Annotated[TransactionRepositoryPort, Depends(get_transaction_repo)], + ewbi_client: Annotated[EwbiClientPort, Depends(get_ewbi_client)], +) -> OutboundFederationService: + return OutboundFederationService( + RoutingResolver(routing_repo), + partner_repo, + context_repo, + AgreementChecker(agreement_repo), + transaction_repo, + ewbi_client, + ) + + +def get_inbound_federation_service( + context_repo: Annotated[FederationContextRepositoryPort, Depends(get_federation_context_repo)], +) -> InboundFederationService: + settings = get_settings() + return InboundFederationService( + context_repo, + LocalOperator( + federation_id=settings.federation_id, + country_code=settings.country_code, + mcc=settings.mcc, + mncs=settings.mncs, + partner_status_link=settings.partner_status_link, + platform_caps=settings.platform_caps, + ), + ) + + +def get_command_publisher(request: Request) -> DataBusPublisherPort: + publisher: DataBusPublisherPort = request.app.state.command_publisher + return publisher + + +def get_inbound_deployment_service( + authenticator: Annotated[PartnerAuthenticator, Depends(get_partner_authenticator)], + context_repo: Annotated[FederationContextRepositoryPort, Depends(get_federation_context_repo)], + agreement_repo: Annotated[AgreementRepositoryPort, Depends(get_agreement_repo)], + transaction_repo: Annotated[TransactionRepositoryPort, Depends(get_transaction_repo)], + publisher: Annotated[DataBusPublisherPort, Depends(get_command_publisher)], +) -> InboundDeploymentService: + return InboundDeploymentService( + authenticator, + context_repo, + AgreementChecker(agreement_repo), + transaction_repo, + publisher, + ) diff --git a/src/federation_manager/domain/__init__.py b/src/federation_manager/domain/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/federation_manager/domain/errors.py b/src/federation_manager/domain/errors.py new file mode 100644 index 0000000000000000000000000000000000000000..eb1f72a36f6de4ac62eb556e4df5f7fc4afa0606 --- /dev/null +++ b/src/federation_manager/domain/errors.py @@ -0,0 +1,109 @@ +from uuid import UUID + +PROBLEM_TYPE_BASE = "urn:oop:ewbi:error:" + + +def problem_type(code: str) -> str: + return f"{PROBLEM_TYPE_BASE}{code}" + + +class FederationError(Exception): + pass + + +class AuthenticationFailed(FederationError): + pass + + +class PartnerUnknown(FederationError): + def __init__(self, identifier: str) -> None: + super().__init__(f"no partner registered for {identifier}") + self.identifier = identifier + + +class PartnerNotActive(FederationError): + def __init__(self, status: str) -> None: + super().__init__(f"partner status is {status}") + self.status = status + + +class PartnerTokenConfigurationError(FederationError): + def __init__(self, partner_id: UUID, field: str) -> None: + super().__init__(f"partner {partner_id} has invalid outbound OAuth2 {field}") + self.partner_id = partner_id + self.field = field + + +class PartnerTokenRequestFailed(FederationError): + def __init__(self, partner_id: UUID) -> None: + super().__init__(f"failed to obtain access token for partner {partner_id}") + self.partner_id = partner_id + + +class PartnerEndpointConfigurationError(FederationError): + def __init__(self, partner_id: UUID) -> None: + super().__init__(f"partner {partner_id} has invalid EWBI endpoint configuration") + self.partner_id = partner_id + + +class PartnerRequestFailed(FederationError): + def __init__(self, partner_id: UUID) -> None: + super().__init__(f"EWBI request to partner {partner_id} failed") + self.partner_id = partner_id + + +class AgreementExpired(FederationError): + pass + + +class AgreementViolation(FederationError): + pass + + +class FederationContextMissing(FederationError): + def __init__(self, partner_id: UUID) -> None: + super().__init__(f"no active outbound federation context with partner {partner_id}") + self.partner_id = partner_id + + +class FederationEstablishmentFailed(FederationError): + def __init__(self, partner_id: UUID, status_code: int) -> None: + super().__init__(f"partner {partner_id} rejected federation setup with {status_code}") + self.partner_id = partner_id + self.status_code = status_code + + +class PartnerResponseInvalid(FederationError): + def __init__(self, partner_id: UUID) -> None: + super().__init__(f"partner {partner_id} returned a malformed EWBI response") + self.partner_id = partner_id + + +class FederationAlreadyExists(FederationError): + def __init__(self, partner_id: UUID) -> None: + super().__init__(f"federation with partner {partner_id} already exists") + self.partner_id = partner_id + + +class FederationContextUnknown(FederationError): + def __init__(self, partner_id: UUID) -> None: + super().__init__(f"federation context is not known for partner {partner_id}") + self.partner_id = partner_id + + +class IdempotencyKeyReused(FederationError): + def __init__(self, idempotency_key: str) -> None: + super().__init__(f"idempotency key {idempotency_key} was reused with a different request") + self.idempotency_key = idempotency_key + + +class NoRouteMatched(FederationError): + def __init__(self, identifier_type: str) -> None: + super().__init__(f"no routing rule matches the {identifier_type} identifier") + self.identifier_type = identifier_type + + +class UnsupportedApiType(FederationError): + def __init__(self, api_type: str) -> None: + super().__init__(f"api type {api_type!r} has no EWBI service path") + self.api_type = api_type diff --git a/src/federation_manager/domain/ewbi.py b/src/federation_manager/domain/ewbi.py new file mode 100644 index 0000000000000000000000000000000000000000..1b9d6f754db560a32cb74e143e64ee0c50eb506c --- /dev/null +++ b/src/federation_manager/domain/ewbi.py @@ -0,0 +1,27 @@ +from urllib.parse import quote + +from federation_manager.domain.errors import UnsupportedApiType + +EWBI_BASE_PATH = "/operatorplatform/federation/v1" + +# api_type -> OPG.04 serviceAPINameVal. Only the subscriber-keyed query families reach the +# outbound hand-off; zone-based deploys go SRM -> FM (ADR-0044). +SERVICE_API_NAMES: dict[str, str] = { + "device-location-retrieve": "DeviceLocation", + "device-status-retrieve": "DeviceStatus", +} + + +CREATE_FEDERATION_PATH = f"{EWBI_BASE_PATH}/partner" + + +def service_api_name(api_type: str) -> str: + try: + return SERVICE_API_NAMES[api_type] + except KeyError: + raise UnsupportedApiType(api_type) from None + + +def api_forwarding_path(federation_context_id: str, service_api: str) -> str: + # The context id is issued by the partner; encode it so it cannot escape its path segment. + return f"{EWBI_BASE_PATH}/{quote(federation_context_id, safe='')}/apiservice/{service_api}" diff --git a/src/federation_manager/domain/models.py b/src/federation_manager/domain/models.py new file mode 100644 index 0000000000000000000000000000000000000000..b4ae8c10000657245110e2d98aab97f2f7c2f1b4 --- /dev/null +++ b/src/federation_manager/domain/models.py @@ -0,0 +1,139 @@ +from dataclasses import dataclass +from datetime import datetime +from uuid import UUID + + +@dataclass +class PartnerOP: + id: UUID + mcc_mnc: str + oauth2_client_id: str # Keycloak `federation` realm client id (ADR-0042) + status: str # pending | active | suspended | decommissioned + our_client_id: str | None = None + our_client_secret_ref: str | None = None + token_endpoint: str | None = None + base_url: str | None = None + + def is_active(self) -> bool: + return self.status == "active" + + +@dataclass(frozen=True) +class AppMapping: + app_id: str + app_version: str + flavour_id: str + service_specification_id: UUID + + +@dataclass +class Agreement: + id: UUID + partner_op_id: UUID + permitted_api_types: set[str] + permitted_zone_ids: set[UUID] + # both resolve a partner request to a local service_specification_id (ADR-0018) + app_mappings: tuple[AppMapping, ...] + api_family_mappings: dict[str, UUID] + valid_from: datetime + valid_until: datetime | None + status: str # draft | active | suspended | expired + + def is_valid_at(self, now: datetime) -> bool: + # Expiry is checked against the clock, independent of status (RD §N step 6): + # an agreement can still be status=active yet past valid_until. + if now < self.valid_from: + return False + return self.valid_until is None or now < self.valid_until + + def permits_api(self, api_type: str) -> bool: + return api_type in self.permitted_api_types + + def permits_zone(self, zone_id: UUID) -> bool: + return zone_id in self.permitted_zone_ids + + def resolve_app_spec(self, app_id: str, app_version: str, flavour_id: str) -> UUID | None: + for mapping in self.app_mappings: + if (mapping.app_id, mapping.app_version, mapping.flavour_id) == ( + app_id, + app_version, + flavour_id, + ): + return mapping.service_specification_id + return None + + def resolve_api_spec(self, api_type: str) -> UUID | None: + return self.api_family_mappings.get(api_type) + + +@dataclass +class ValidatedClaims: + client_id: str + scopes: set[str] + + def has_scope(self, scope: str) -> bool: + return scope in self.scopes + + +@dataclass(frozen=True) +class EwbiResponse: + status_code: int + body: object | None + location: str | None = None + + def is_success(self) -> bool: + return 200 <= self.status_code < 300 + + +@dataclass(frozen=True) +class RoutingRule: + id: UUID + partner_op_id: UUID + identifier_type: str # msisdn_prefix | ip_cidr + value_range: str # "+34" or "203.0.113.0/24" + priority: int = 100 # lower wins among equally specific matches + is_active: bool = True + + +@dataclass(frozen=True) +class FederationContext: + id: UUID + partner_op_id: UUID + direction: str # inbound: we issued the context id | outbound: the partner issued it + federation_context_id: str # opaque OPG.04 FederationContextId, not necessarily a UUID + status: str # available | locked | not_available | temporary_failure | failed | terminated + created_at: datetime + agreement_id: UUID | None = None + status_callback_url: str | None = None + + def is_active(self) -> bool: + return self.status == "available" + + def is_terminated(self) -> bool: + return self.status == "terminated" + + +@dataclass +class FederationTransaction: + id: UUID + partner_op_id: UUID + direction: str # inbound | outbound + api_type: str + status: str # pending | in_progress | completed | partially_completed | failed + request_summary: dict[str, object] + started_at: datetime + agreement_id: UUID | None = None + federation_context_row_id: UUID | None = None + external_txn_id: str | None = None + idempotency_key: str | None = None + request_fingerprint: str | None = None + external_resource_id: str | None = None + callback_url: str | None = None + callback_status: str | None = None + callback_attempts: int = 0 # OPG.04 txnIdentifier / apiTxnId when the operation has one + federation_operation_id: UUID | None = None + operation_id: UUID | None = None # never returned to partners + correlation_id: UUID | None = None # never returned to partners + response_summary: dict[str, object] | None = None + error_detail: dict[str, object] | None = None + completed_at: datetime | None = None diff --git a/src/federation_manager/domain/ports.py b/src/federation_manager/domain/ports.py new file mode 100644 index 0000000000000000000000000000000000000000..430f8c16fa43b46111b81d6d827950881fedc81a --- /dev/null +++ b/src/federation_manager/domain/ports.py @@ -0,0 +1,89 @@ +from datetime import datetime +from typing import Protocol +from uuid import UUID + +from federation_manager.domain.models import ( + Agreement, + EwbiResponse, + FederationContext, + FederationTransaction, + PartnerOP, + RoutingRule, + ValidatedClaims, +) + + +class PartnerRepositoryPort(Protocol): + async def find_by_oauth2_client_id(self, client_id: str) -> PartnerOP | None: ... + + async def find_by_id(self, partner_id: UUID) -> PartnerOP | None: ... + + async def list_active(self) -> list[PartnerOP]: ... + + +class JwtValidatorPort(Protocol): + async def validate(self, token: str) -> ValidatedClaims: ... + + +class PartnerTokenProviderPort(Protocol): + async def token_for(self, partner: PartnerOP, scope: str = "fed-mgmt") -> str: ... + + +class EwbiClientPort(Protocol): + async def post( + self, partner: PartnerOP, path: str, payload: dict[str, object] + ) -> EwbiResponse: ... + + +class CallbackClientPort(Protocol): + async def deliver(self, partner: PartnerOP, url: str, payload: dict[str, object]) -> bool: ... + + +class AgreementRepositoryPort(Protocol): + async def find_active_for_partner(self, partner_id: UUID) -> Agreement | None: ... + + +class FederationContextRepositoryPort(Protocol): + async def find_active_outbound(self, partner_id: UUID) -> FederationContext | None: ... + + async def find_inbound( + self, partner_id: UUID, federation_context_id: str + ) -> FederationContext | None: ... + + async def find_active_inbound(self, partner_id: UUID) -> FederationContext | None: ... + + async def add(self, context: FederationContext) -> None: ... + + async def set_status(self, context_id: UUID, status: str) -> None: ... + + +class RoutingRuleRepositoryPort(Protocol): + async def list_active(self, identifier_type: str) -> list[RoutingRule]: ... + + +class TransactionRepositoryPort(Protocol): + async def add(self, transaction: FederationTransaction) -> None: ... + + async def find_by_idempotency_key( + self, partner_id: UUID, api_type: str, idempotency_key: str + ) -> FederationTransaction | None: ... + + async def find_by_operation_id(self, operation_id: UUID) -> FederationTransaction | None: ... + + async def mark_in_progress(self, transaction_id: UUID) -> None: ... + + async def record_callback(self, transaction_id: UUID, *, status: str) -> None: ... + + async def record_outcome( + self, + transaction_id: UUID, + *, + status: str, + completed_at: datetime, + response_summary: dict[str, object] | None = None, + error_detail: dict[str, object] | None = None, + ) -> None: ... + + +class DataBusPublisherPort(Protocol): + async def publish(self, subject: str, payload: dict[str, object]) -> None: ... diff --git a/src/federation_manager/domain/routing.py b/src/federation_manager/domain/routing.py new file mode 100644 index 0000000000000000000000000000000000000000..f822263016a458858daa7d89fa2e395b735032b3 --- /dev/null +++ b/src/federation_manager/domain/routing.py @@ -0,0 +1,69 @@ +from dataclasses import dataclass +from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network, ip_address, ip_network +from uuid import UUID + +from federation_manager.domain.models import RoutingRule +from federation_manager.domain.ports import RoutingRuleRepositoryPort + +MSISDN_PREFIX = "msisdn_prefix" +IP_CIDR = "ip_cidr" + +RULE_TYPE_FOR_IDENTIFIER: dict[str, str] = {"msisdn": MSISDN_PREFIX, "ip": IP_CIDR} + + +@dataclass(frozen=True) +class PartnerResolution: + partner_op_id: UUID + rule_id: UUID + match: str # the value_range that matched, e.g. "+34" or "203.0.113.0/24" + + +class RoutingResolver: + def __init__(self, routing_repo: RoutingRuleRepositoryPort) -> None: + self._routing_repo = routing_repo + + async def resolve_partner( + self, identifier_type: str, identifier_value: str + ) -> PartnerResolution | None: + rule_type = RULE_TYPE_FOR_IDENTIFIER.get(identifier_type) + if rule_type is None: + return None + rules = await self._routing_repo.list_active(rule_type) + if rule_type == MSISDN_PREFIX: + candidates = _msisdn_candidates(rules, identifier_value) + else: + candidates = _ip_candidates(rules, identifier_value) + if not candidates: + return None + # Longest match wins; priority only breaks ties between equally specific rules. + _, rule = min(candidates, key=lambda c: (-c[0], c[1].priority, str(c[1].id))) + return PartnerResolution( + partner_op_id=rule.partner_op_id, rule_id=rule.id, match=rule.value_range + ) + + +def _msisdn_candidates(rules: list[RoutingRule], msisdn: str) -> list[tuple[int, RoutingRule]]: + return [ + (len(rule.value_range), rule) + for rule in rules + if rule.is_active and rule.value_range and msisdn.startswith(rule.value_range) + ] + + +def _ip_candidates(rules: list[RoutingRule], value: str) -> list[tuple[int, RoutingRule]]: + try: + address: IPv4Address | IPv6Address = ip_address(value) + except ValueError: + return [] + candidates: list[tuple[int, RoutingRule]] = [] + for rule in rules: + if not rule.is_active: + continue + try: + network: IPv4Network | IPv6Network = ip_network(rule.value_range, strict=False) + except ValueError: + continue # malformed rule must not take the whole routing table down + if network.version != address.version or address not in network: + continue + candidates.append((network.prefixlen, rule)) + return candidates diff --git a/src/federation_manager/main.py b/src/federation_manager/main.py new file mode 100644 index 0000000000000000000000000000000000000000..9e99b2c0fc9634c1ff41c4ffd16aff043bf494c0 --- /dev/null +++ b/src/federation_manager/main.py @@ -0,0 +1,140 @@ +from collections.abc import AsyncIterator, Awaitable, Callable +from contextlib import asynccontextmanager +from typing import Any + +import httpx +from fastapi import FastAPI +from starlette.types import Lifespan + +from federation_manager import __version__ +from federation_manager.adapters.database.core import ( + build_engine, + build_session_maker, + create_schema, +) +from federation_manager.adapters.database.federation_context_repo import ( + PostgresFederationContextRepo, +) +from federation_manager.adapters.database.partner_repo import PostgresPartnerRepo +from federation_manager.adapters.database.transaction_repo import PostgresTransactionRepo +from federation_manager.adapters.databus.nats_adapter import ( + NatsCommandPublisher, + NatsEventConsumer, +) +from federation_manager.adapters.http.callback_client import HttpxCallbackClient +from federation_manager.adapters.http.ewbi_client import HttpxEwbiClient +from federation_manager.adapters.security.client_secret_token_provider import ( + FileClientSecretTokenProvider, +) +from federation_manager.adapters.security.keycloak_validator import KeycloakJwtValidator +from federation_manager.api.errors import register_exception_handlers, register_problem_schemas +from federation_manager.api.ewbi.v1.lcm import router as ewbi_lcm_router +from federation_manager.api.ewbi.v1.management import router as ewbi_management_router +from federation_manager.api.internal.federation import router as internal_federation_router +from federation_manager.api.platform.health import router as health_router +from federation_manager.application.events import OperationCompletedConsumer +from federation_manager.application.federation import ( + FederationEstablishmentService, + LocalOperator, +) +from federation_manager.contracts.srm import SUBJECT_OPERATION_COMPLETED +from federation_manager.core.config import Settings, get_settings +from federation_manager.domain.ports import EwbiClientPort + + +@asynccontextmanager +async def default_lifespan(app: FastAPI) -> AsyncIterator[None]: + settings = get_settings() + engine = build_engine(settings.postgres_url, echo=settings.postgres_echo) + await create_schema(engine) + app.state.session_maker = build_session_maker(engine) + app.state.jwt_validator = KeycloakJwtValidator(settings.keycloak_issuer) + publisher = NatsCommandPublisher(settings.nats_url) + await publisher.connect() + await publisher.ensure_task_stream() + app.state.command_publisher = publisher + events = NatsEventConsumer(settings.nats_url, settings.event_consumer_durable) + await events.connect() + await events.ensure_event_stream() + async with httpx.AsyncClient(timeout=httpx.Timeout(10.0, connect=5.0)) as http_client: + token_provider = FileClientSecretTokenProvider( + http_client, allow_insecure=settings.allow_insecure_partner_endpoints + ) + app.state.partner_token_provider = token_provider + ewbi_client = HttpxEwbiClient( + http_client, token_provider, allow_insecure=settings.allow_insecure_partner_endpoints + ) + app.state.ewbi_client = ewbi_client + app.state.callback_client = HttpxCallbackClient( + http_client, token_provider, allow_insecure=settings.allow_insecure_partner_endpoints + ) + # subscribe last: the handler needs the clients created above + await events.subscribe(SUBJECT_OPERATION_COMPLETED, _completed_handler(app)) + if settings.bootstrap_federation: + await _bootstrap_federation(app, settings, ewbi_client) + try: + yield + finally: + await events.close() + await publisher.close() + await engine.dispose() + + +async def _bootstrap_federation( + app: FastAPI, settings: Settings, ewbi_client: EwbiClientPort +) -> None: + async with app.state.session_maker() as session: + service = FederationEstablishmentService( + PostgresPartnerRepo(session), + PostgresFederationContextRepo(session), + ewbi_client, + LocalOperator( + federation_id=settings.federation_id, + country_code=settings.country_code, + mcc=settings.mcc, + mncs=settings.mncs, + partner_status_link=settings.partner_status_link, + ), + ) + await service.establish_missing() + + +def _completed_handler(app: FastAPI) -> Callable[[dict[str, Any]], Awaitable[None]]: + async def handle(payload: dict[str, Any]) -> None: + async with app.state.session_maker() as session: + await OperationCompletedConsumer( + PostgresTransactionRepo(session), + PostgresPartnerRepo(session), + app.state.callback_client, + ).handle(payload) + + return handle + + +def _openapi_with_problem_schemas(app: FastAPI) -> Callable[[], dict[str, Any]]: + generate = app.openapi + + def openapi() -> dict[str, Any]: + if app.openapi_schema is None: + app.openapi_schema = register_problem_schemas(generate()) + return app.openapi_schema + + return openapi + + +def create_app(lifespan: Lifespan[FastAPI] | None = None) -> FastAPI: + app = FastAPI( + title="Federation Manager", + version=__version__, + lifespan=lifespan or default_lifespan, + ) + register_exception_handlers(app) + app.openapi = _openapi_with_problem_schemas(app) # type: ignore[method-assign] + app.include_router(health_router) + app.include_router(ewbi_management_router) + app.include_router(ewbi_lcm_router) + app.include_router(internal_federation_router) + return app + + +app = create_app() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/fakes.py b/tests/fakes.py new file mode 100644 index 0000000000000000000000000000000000000000..2fa8b8be7d6bca9cf195d8cf4b469e3c3485f5fd --- /dev/null +++ b/tests/fakes.py @@ -0,0 +1,196 @@ +from dataclasses import replace +from datetime import datetime +from uuid import UUID + +from federation_manager.domain.errors import AuthenticationFailed +from federation_manager.domain.models import ( + Agreement, + EwbiResponse, + FederationContext, + FederationTransaction, + PartnerOP, + RoutingRule, + ValidatedClaims, +) + + +class InMemoryPartnerRepo: + def __init__(self, partners: list[PartnerOP]) -> None: + self._by_client_id = {p.oauth2_client_id: p for p in partners} + self._by_id = {p.id: p for p in partners} + + async def find_by_oauth2_client_id(self, client_id: str) -> PartnerOP | None: + return self._by_client_id.get(client_id) + + async def find_by_id(self, partner_id: UUID) -> PartnerOP | None: + return self._by_id.get(partner_id) + + async def list_active(self) -> list[PartnerOP]: + return [p for p in self._by_id.values() if p.is_active()] + + +class FakeJwtValidator: + def __init__(self, tokens: dict[str, ValidatedClaims]) -> None: + self._tokens = tokens + + async def validate(self, token: str) -> ValidatedClaims: + claims = self._tokens.get(token) + if claims is None: + raise AuthenticationFailed + return claims + + +class InMemoryAgreementRepo: + def __init__(self, agreements: list[Agreement]) -> None: + self._active_by_partner: dict[UUID, Agreement] = {} + for agreement in agreements: + if agreement.status == "active": + self._active_by_partner[agreement.partner_op_id] = agreement + + async def find_active_for_partner(self, partner_id: UUID) -> Agreement | None: + return self._active_by_partner.get(partner_id) + + +class InMemoryFederationContextRepo: + def __init__(self, contexts: list[FederationContext] | None = None) -> None: + self.contexts = contexts if contexts is not None else [] + + async def add(self, context: FederationContext) -> None: + self.contexts.append(context) + + async def set_status(self, context_id: UUID, status: str) -> None: + for index, context in enumerate(self.contexts): + if context.id == context_id: + self.contexts[index] = replace(context, status=status) + return + raise KeyError(context_id) + + async def find_active_outbound(self, partner_id: UUID) -> FederationContext | None: + for context in self.contexts: + if ( + context.partner_op_id == partner_id + and context.direction == "outbound" + and context.is_active() + ): + return context + return None + + async def find_active_inbound(self, partner_id: UUID) -> FederationContext | None: + for context in self.contexts: + if ( + context.partner_op_id == partner_id + and context.direction == "inbound" + and context.is_active() + ): + return context + return None + + async def find_inbound( + self, partner_id: UUID, federation_context_id: str + ) -> FederationContext | None: + for context in self.contexts: + if ( + context.partner_op_id == partner_id + and context.direction == "inbound" + and context.federation_context_id == federation_context_id + ): + return context + return None + + +class InMemoryRoutingRuleRepo: + def __init__(self, rules: list[RoutingRule]) -> None: + self._rules = rules + + async def list_active(self, identifier_type: str) -> list[RoutingRule]: + return [r for r in self._rules if r.identifier_type == identifier_type and r.is_active] + + +class InMemoryTransactionRepo: + def __init__(self) -> None: + self.transactions: dict[UUID, FederationTransaction] = {} + + async def add(self, transaction: FederationTransaction) -> None: + self.transactions[transaction.id] = transaction + + async def find_by_idempotency_key( + self, partner_id: UUID, api_type: str, idempotency_key: str + ) -> FederationTransaction | None: + for transaction in self.transactions.values(): + if (transaction.partner_op_id, transaction.api_type, transaction.idempotency_key) == ( + partner_id, + api_type, + idempotency_key, + ): + return transaction + return None + + async def find_by_operation_id(self, operation_id: UUID) -> FederationTransaction | None: + for transaction in self.transactions.values(): + if transaction.operation_id == operation_id: + return transaction + return None + + async def mark_in_progress(self, transaction_id: UUID) -> None: + self.transactions[transaction_id].status = "in_progress" + + async def record_callback(self, transaction_id: UUID, *, status: str) -> None: + transaction = self.transactions[transaction_id] + transaction.callback_status = status + transaction.callback_attempts += 1 + + async def record_outcome( + self, + transaction_id: UUID, + *, + status: str, + completed_at: datetime, + response_summary: dict[str, object] | None = None, + error_detail: dict[str, object] | None = None, + ) -> None: + transaction = self.transactions[transaction_id] + transaction.status = status + transaction.completed_at = completed_at + if response_summary is not None: + transaction.response_summary = response_summary + if error_detail is not None: + transaction.error_detail = error_detail + + def single(self) -> FederationTransaction: + assert len(self.transactions) == 1, self.transactions + return next(iter(self.transactions.values())) + + +class RecordingCallbackClient: + def __init__(self, succeeds: bool = True) -> None: + self.succeeds = succeeds + self.delivered: list[tuple[UUID, str, dict[str, object]]] = [] + + async def deliver(self, partner: PartnerOP, url: str, payload: dict[str, object]) -> bool: + self.delivered.append((partner.id, url, payload)) + return self.succeeds + + +class RecordingCommandPublisher: + def __init__(self) -> None: + self.published: list[tuple[str, dict[str, object]]] = [] + + async def publish(self, subject: str, payload: dict[str, object]) -> None: + self.published.append((subject, payload)) + + +class FakeEwbiClient: + def __init__( + self, + response: EwbiResponse | None = None, + error: Exception | None = None, + ) -> None: + self._response = response or EwbiResponse(status_code=200, body={"ok": True}) + self._error = error + self.calls: list[tuple[UUID, str, dict[str, object]]] = [] + + async def post(self, partner: PartnerOP, path: str, payload: dict[str, object]) -> EwbiResponse: + self.calls.append((partner.id, path, payload)) + if self._error is not None: + raise self._error + return self._response diff --git a/tests/fixtures/keycloak/federation-realm.json b/tests/fixtures/keycloak/federation-realm.json new file mode 100644 index 0000000000000000000000000000000000000000..8b0bddaa9572e186a18d6b0ca63c5b2bea52c338 --- /dev/null +++ b/tests/fixtures/keycloak/federation-realm.json @@ -0,0 +1,24 @@ +{ + "realm": "federation", + "enabled": true, + "clientScopes": [ + { + "name": "fed-mgmt", + "protocol": "openid-connect", + "description": "Access to the E/WBI federation management APIs (GSMA OPG.04 Sec. 9)" + } + ], + "clients": [ + { + "clientId": "test-partner", + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "test-partner-secret", + "publicClient": false, + "serviceAccountsEnabled": true, + "standardFlowEnabled": false, + "directAccessGrantsEnabled": false, + "defaultClientScopes": ["fed-mgmt"] + } + ] +} diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/integration/test_federation_context_repo.py b/tests/integration/test_federation_context_repo.py new file mode 100644 index 0000000000000000000000000000000000000000..858f183823c1d0552f983820c1d4ed56469be732 --- /dev/null +++ b/tests/integration/test_federation_context_repo.py @@ -0,0 +1,164 @@ +import os +from datetime import datetime, timezone +from uuid import uuid4 + +import pytest +from sqlalchemy import delete, insert, select + +from federation_manager.adapters.database.core import ( + build_engine, + build_session_maker, + create_schema, +) +from federation_manager.adapters.database.federation_context_repo import ( + PostgresFederationContextRepo, +) +from federation_manager.adapters.database.partner_repo import PostgresPartnerRepo +from federation_manager.adapters.database.tables import ( + federation_agreements, + federation_contexts, + federation_transactions, + partner_ops, +) +from federation_manager.adapters.database.transaction_repo import PostgresTransactionRepo +from federation_manager.domain.models import FederationContext, FederationTransaction +from federation_manager.domain.ports import FederationContextRepositoryPort + +pytestmark = pytest.mark.integration + +URL = os.getenv("FM_POSTGRES_URL", "postgresql+asyncpg://fm:fm@localhost:5433/fm_db") + + +async def test_context_repo_round_trips_through_postgres() -> None: + engine = build_engine(URL) + await create_schema(engine) + session_maker = build_session_maker(engine) + + partner_id, other_id = uuid4(), uuid4() + agreement_id = uuid4() + outbound_row_id = uuid4() + async with session_maker() as session: + for pid in (partner_id, other_id): + await session.execute( + insert(partner_ops).values( + id=pid, + mcc_mnc=uuid4().hex[:10], + oauth2_client_id=f"partner-{uuid4().hex[:8]}", + base_url="https://partner.example", + status="active", + ) + ) + await session.execute( + insert(federation_agreements).values( + id=agreement_id, + partner_op_id=partner_id, + permitted_api_types=["device-location-retrieve"], + valid_from=datetime.now(timezone.utc), + status="active", + ) + ) + # multi-row insert: every dict must carry the same keys or later extras are dropped + rows = [ + (uuid4(), partner_id, None, "outbound", "ctx-old", None, "terminated"), + ( + outbound_row_id, + partner_id, + agreement_id, + "outbound", + "ctx-out", + "https://us.example/partner-status", + "available", + ), + (uuid4(), partner_id, None, "inbound", "ctx-in", None, "available"), + (uuid4(), other_id, None, "inbound", "ctx-in", None, "locked"), + ] + await session.execute( + insert(federation_contexts).values( + [ + { + "id": row_id, + "partner_op_id": partner, + "agreement_id": agreement, + "direction": direction, + "federation_context_id": context_id, + "status_callback_url": callback, + "status": status, + } + for row_id, partner, agreement, direction, context_id, callback, status in rows + ] + ) + ) + await session.commit() + + repo: FederationContextRepositoryPort = PostgresFederationContextRepo(session) + outbound = await repo.find_active_outbound(partner_id) + assert outbound is not None + assert outbound.id == outbound_row_id + assert outbound.federation_context_id == "ctx-out" + assert outbound.agreement_id == agreement_id + assert outbound.status_callback_url == "https://us.example/partner-status" + assert await repo.find_active_outbound(other_id) is None + + inbound = await repo.find_inbound(partner_id, "ctx-in") + assert inbound is not None and inbound.is_active() + other_inbound = await repo.find_inbound(other_id, "ctx-in") + assert other_inbound is not None and not other_inbound.is_active() + assert await repo.find_inbound(partner_id, "ctx-out") is None + + added = FederationContext( + id=uuid4(), + partner_op_id=other_id, + direction="outbound", + federation_context_id=f"ctx-{uuid4().hex[:8]}", + status="available", + created_at=datetime.now(timezone.utc), + status_callback_url="https://us.example/partner-status", + ) + await repo.add(added) + stored = await repo.find_active_outbound(other_id) + assert stored is not None + assert stored.id == added.id + assert stored.federation_context_id == added.federation_context_id + assert stored.status_callback_url == added.status_callback_url + + active_partners = {p.id for p in await PostgresPartnerRepo(session).list_active()} + assert {partner_id, other_id} <= active_partners + + tx = FederationTransaction( + id=uuid4(), + partner_op_id=partner_id, + agreement_id=agreement_id, + federation_context_row_id=outbound.id, + direction="outbound", + external_txn_id="txn-42", + api_type="device-location-retrieve", + status="pending", + request_summary={}, + started_at=datetime.now(timezone.utc), + ) + await PostgresTransactionRepo(session).add(tx) + row = ( + await session.execute( + select(federation_transactions).where(federation_transactions.c.id == tx.id) + ) + ).one() + assert row.federation_context_row_id == outbound.id + assert row.external_txn_id == "txn-42" + + await session.execute( + delete(federation_transactions).where(federation_transactions.c.id == tx.id) + ) + await session.execute( + delete(federation_contexts).where( + federation_contexts.c.partner_op_id.in_([partner_id, other_id]) + ) + ) + await session.execute( + delete(federation_agreements).where(federation_agreements.c.id == agreement_id) + ) + await session.execute( + delete(partner_ops).where(partner_ops.c.id.in_([partner_id, other_id])) + ) + await session.commit() + + await engine.dispose() diff --git a/tests/integration/test_fm_srm_loop.py b/tests/integration/test_fm_srm_loop.py new file mode 100644 index 0000000000000000000000000000000000000000..f6c3fe1e5429520c8e1d59ba499ab536998e2a65 --- /dev/null +++ b/tests/integration/test_fm_srm_loop.py @@ -0,0 +1,285 @@ +import asyncio +import os +import socket +import subprocess +import sys +import time +from collections.abc import Iterator +from contextlib import suppress +from datetime import datetime, timezone +from uuid import UUID, uuid4 + +import httpx +import pytest +from sqlalchemy import delete, insert, select +from sqlalchemy.ext.asyncio import AsyncSession + +from federation_manager.adapters.database.core import ( + build_engine, + build_session_maker, + create_schema, +) +from federation_manager.adapters.database.tables import ( + federation_agreements, + federation_contexts, + federation_transactions, + partner_ops, +) +from federation_manager.adapters.databus.nats_adapter import NatsEventConsumer +from federation_manager.contracts.srm import SUBJECT_OPERATION_COMPLETED + +pytestmark = pytest.mark.integration + +FM_DB = os.getenv("FM_POSTGRES_URL", "postgresql+asyncpg://fm:fm@localhost:5433/fm_db") +ISSUER = os.getenv("FM_KEYCLOAK_ISSUER", "http://localhost:8090/realms/federation") +NATS = os.getenv("FM_NATS_URL", "nats://localhost:4222") +SRM = os.getenv("FM_SRM_URL", "http://127.0.0.1:8081") +CLIENT_ID, CLIENT_SECRET = "originating-op-1", "dd7vNwFqjNpYwaghlEwMbw10g0klWDHb" +CONTEXT_ID = "fed-ctx-srm-loop" +APP_ID, APP_VERSION, FLAVOUR = "videoAnalytics", "1.2.0", "small" +TERMINAL = {"completed", "partially_completed", "failed"} + + +def _free_port() -> int: + with socket.socket() as s: + s.bind(("127.0.0.1", 0)) + port: int = s.getsockname()[1] + return port + + +def _require_srm() -> None: + try: + if httpx.get(f"{SRM}/healthz", timeout=2.0).status_code != 200: + raise httpx.HTTPError("unhealthy") + except httpx.HTTPError: + pytest.skip(f"SRM not reachable at {SRM}; see docs/running-srm.md") + + +def _create_specification() -> UUID: + specification_id = uuid4() + response = httpx.post( + f"{SRM}/internal/catalog/service-specifications", + json={ + "service_specification": { + "id": str(specification_id), + "app_provider_id": "partner-tenant", + "ref": f"fed-loop-{specification_id.hex[:10]}", + "name": "Federated loop test app", + "version": APP_VERSION, + }, + "service_deployment_units": [], + "service_capability_requirements": [], + }, + timeout=10.0, + ) + response.raise_for_status() + return specification_id + + +def _token() -> str: + response = httpx.post( + f"{ISSUER}/protocol/openid-connect/token", + data={ + "grant_type": "client_credentials", + "client_id": CLIENT_ID, + "client_secret": CLIENT_SECRET, + "scope": "fed-mgmt", + }, + timeout=10.0, + ) + response.raise_for_status() + token: str = response.json()["access_token"] + return token + + +async def _seed(specification_id: UUID, partner_id: UUID, zone_id: UUID) -> None: + engine = build_engine(FM_DB) + await create_schema(engine) + async with build_session_maker(engine)() as session: + # oauth2_client_id is unique: drop anything an interrupted run left behind + stale = ( + ( + await session.execute( + select(partner_ops.c.id).where(partner_ops.c.oauth2_client_id == CLIENT_ID) + ) + ) + .scalars() + .all() + ) + for previous in stale: + await _delete_partner(session, previous) + await session.commit() + + agreement_id = uuid4() + await session.execute( + insert(partner_ops).values( + id=partner_id, + mcc_mnc=uuid4().hex[:10], + oauth2_client_id=CLIENT_ID, + base_url="http://127.0.0.1:9", + status="active", + ) + ) + await session.execute( + insert(federation_agreements).values( + id=agreement_id, + partner_op_id=partner_id, + permitted_api_types=["install-app"], + permitted_zone_ids=[str(zone_id)], + service_spec_mappings={ + "apps": [ + { + "appId": APP_ID, + "appVersion": APP_VERSION, + "flavourId": FLAVOUR, + "service_specification_id": str(specification_id), + } + ], + "api_families": {}, + }, + valid_from=datetime(2026, 1, 1, tzinfo=timezone.utc), + status="active", + ) + ) + await session.execute( + insert(federation_contexts).values( + id=uuid4(), + partner_op_id=partner_id, + agreement_id=agreement_id, + direction="inbound", + federation_context_id=CONTEXT_ID, + status="available", + created_at=datetime.now(timezone.utc), + ) + ) + await session.commit() + await engine.dispose() + + +async def _delete_partner(session: AsyncSession, partner_id: UUID) -> None: + await session.execute( + delete(federation_transactions).where(federation_transactions.c.partner_op_id == partner_id) + ) + await session.execute( + delete(federation_contexts).where(federation_contexts.c.partner_op_id == partner_id) + ) + await session.execute( + delete(federation_agreements).where(federation_agreements.c.partner_op_id == partner_id) + ) + await session.execute(delete(partner_ops).where(partner_ops.c.id == partner_id)) + + +async def _drop_consumer(durable: str) -> None: + # durables outlive the process that made them and pile up on OOP_EVENTS + consumer = NatsEventConsumer(NATS, durable) + await consumer.connect() + with suppress(Exception): + await consumer.delete_durable(SUBJECT_OPERATION_COMPLETED) + await consumer.close() + + +async def _cleanup(partner_id: UUID) -> None: + engine = build_engine(FM_DB) + async with build_session_maker(engine)() as session: + await _delete_partner(session, partner_id) + await session.commit() + await engine.dispose() + + +async def _transaction_status(partner_id: UUID) -> tuple[str, dict[str, object] | None]: + engine = build_engine(FM_DB) + async with build_session_maker(engine)() as session: + row = ( + await session.execute( + select(federation_transactions).where( + federation_transactions.c.partner_op_id == partner_id + ) + ) + ).one() + await engine.dispose() + return row.status, row.response_summary + + +@pytest.fixture +def federated_stack() -> Iterator[tuple[int, UUID, UUID]]: + _require_srm() + specification_id = _create_specification() + partner_id, zone_id = uuid4(), uuid4() + asyncio.run(_seed(specification_id, partner_id, zone_id)) + + port = _free_port() + durable = f"fm-loop-{partner_id.hex[:8]}" + process = subprocess.Popen( + [ + sys.executable, + "-m", + "uvicorn", + "federation_manager.main:app", + "--host", + "127.0.0.1", + "--port", + str(port), + "--log-level", + "warning", + ], + env={ + **os.environ, + "FM_POSTGRES_URL": FM_DB, + "FM_KEYCLOAK_ISSUER": ISSUER, + "FM_NATS_URL": NATS, + "FM_EVENT_CONSUMER_DURABLE": durable, + }, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + deadline = time.monotonic() + 40 + try: + while time.monotonic() < deadline: + if process.poll() is not None: + output = process.stdout.read().decode() if process.stdout else "" + raise AssertionError(f"FM exited early:\n{output}") + try: + if httpx.get(f"http://127.0.0.1:{port}/healthz", timeout=1.0).status_code == 200: + break + except httpx.HTTPError: + time.sleep(0.3) + else: + raise AssertionError("FM did not become ready") + yield port, partner_id, zone_id + finally: + process.kill() + process.wait(timeout=10) + asyncio.run(_cleanup(partner_id)) + asyncio.run(_drop_consumer(durable)) + + +def test_install_app_reaches_srm_and_the_completion_comes_back( + federated_stack: tuple[int, UUID, UUID], +) -> None: + port, partner_id, zone_id = federated_stack + + accepted = httpx.post( + f"http://127.0.0.1:{port}/operatorplatform/federation/v1/{CONTEXT_ID}/application/lcm", + headers={"Authorization": f"Bearer {_token()}", "Idempotency-Key": uuid4().hex}, + json={ + "appId": APP_ID, + "appVersion": APP_VERSION, + "appProviderId": "partnerProvider", + "zoneInfo": {"zoneId": str(zone_id), "flavourId": FLAVOUR}, + "appInstCallbackLink": "https://partner.example/instances/callback", + }, + timeout=10.0, + ) + + assert accepted.status_code == 202 + assert accepted.json()["zoneId"] == str(zone_id) + + # SRM consumes the command and publishes event.srm.operation.completed; FM finalises on it. + deadline = time.monotonic() + 25 + status, summary = asyncio.run(_transaction_status(partner_id)) + while status not in TERMINAL and time.monotonic() < deadline: + time.sleep(0.5) + status, summary = asyncio.run(_transaction_status(partner_id)) + + assert status in TERMINAL, f"transaction never finalised, still {status}" + assert summary is not None diff --git a/tests/integration/test_nats_publisher.py b/tests/integration/test_nats_publisher.py new file mode 100644 index 0000000000000000000000000000000000000000..fd72b655e35abef9c83e4f9ce31d0911f1b4e481 --- /dev/null +++ b/tests/integration/test_nats_publisher.py @@ -0,0 +1,76 @@ +import json +import os +from contextlib import suppress +from datetime import datetime, timezone +from uuid import uuid4 + +import pytest +from nats.js.api import RetentionPolicy + +from federation_manager.adapters.databus.nats_adapter import NatsCommandPublisher +from federation_manager.contracts.srm import ( + SUBJECT_DEPLOY, + TASK_STREAM, + TASK_STREAM_MAX_AGE_SECONDS, + DeployPayloadV1, + DeployTargetV1, + SrmServiceDeployV1, +) + +pytestmark = pytest.mark.integration + +URL = os.getenv("FM_NATS_URL", "nats://localhost:4222") + + +async def test_deploy_command_round_trips_through_jetstream() -> None: + publisher = NatsCommandPublisher(URL) + await publisher.connect() + await publisher.ensure_task_stream() + + op_id, zone, spec = uuid4(), uuid4(), uuid4() + cmd = SrmServiceDeployV1( + operation_id=op_id, + correlation_id="corr-1", + requested_at=datetime.now(timezone.utc), + app_provider_id=str(uuid4()), + federation_partner_ref="214-07", + source="federation", + service_specification_id=spec, + targets=[DeployTargetV1(app_instance_id=uuid4(), zone_id=zone)], + deploy=DeployPayloadV1(instance_name="video-es"), + ) + + # work-queue retention only drops acked messages, so start from a known-empty subject + await publisher._require_js()._jsm.purge_stream(TASK_STREAM, subject=SUBJECT_DEPLOY) + await publisher.publish(SUBJECT_DEPLOY, cmd.model_dump(mode="json")) + + # OOP_TASKS is a work queue: one consumer per subject, so reuse and drop a fixed durable. + js = publisher._require_js() + durable = "fm-test-deploy" + try: + sub = await js.pull_subscribe(SUBJECT_DEPLOY, durable=durable, stream=TASK_STREAM) + msgs = await sub.fetch(1, timeout=5) + await msgs[0].ack() + + received = SrmServiceDeployV1.model_validate(json.loads(msgs[0].data)) + assert received.operation_id == op_id + assert received.source == "federation" + assert received.targets[0].zone_id == zone + assert received.service_specification_id == spec + finally: + with suppress(Exception): + await js.delete_consumer(TASK_STREAM, durable) + await publisher.close() + + +async def test_task_stream_carries_the_platform_age_limit() -> None: + publisher = NatsCommandPublisher(URL) + await publisher.connect() + await publisher.ensure_task_stream() + + info = await publisher._require_js()._jsm.stream_info(TASK_STREAM) + + assert info.config.max_age == TASK_STREAM_MAX_AGE_SECONDS + assert info.config.retention == RetentionPolicy.WORK_QUEUE + + await publisher.close() diff --git a/tests/integration/test_outbound_repos.py b/tests/integration/test_outbound_repos.py new file mode 100644 index 0000000000000000000000000000000000000000..a506187062b3bb7ec1ce1485975ea56d0de543d5 --- /dev/null +++ b/tests/integration/test_outbound_repos.py @@ -0,0 +1,272 @@ +import os +from datetime import datetime, timedelta, timezone +from uuid import UUID, uuid4 + +import pytest +from sqlalchemy import delete, insert, select +from sqlalchemy.exc import IntegrityError + +from federation_manager.adapters.database.agreement_repo import PostgresAgreementRepo +from federation_manager.adapters.database.core import ( + build_engine, + build_session_maker, + create_schema, +) +from federation_manager.adapters.database.partner_repo import PostgresPartnerRepo +from federation_manager.adapters.database.routing_repo import PostgresRoutingRuleRepo +from federation_manager.adapters.database.tables import ( + federation_agreements, + federation_transactions, + partner_ops, + routing_rules, +) +from federation_manager.adapters.database.transaction_repo import PostgresTransactionRepo +from federation_manager.domain.models import FederationTransaction +from federation_manager.domain.ports import ( + AgreementRepositoryPort, + PartnerRepositoryPort, + RoutingRuleRepositoryPort, + TransactionRepositoryPort, +) +from federation_manager.domain.routing import IP_CIDR, MSISDN_PREFIX, RoutingResolver + +pytestmark = pytest.mark.integration + +URL = os.getenv("FM_POSTGRES_URL", "postgresql+asyncpg://fm:fm@localhost:5433/fm_db") +NOW = datetime.now(timezone.utc) + + +async def _insert_partner(session: object, partner_id: object) -> str: + client_id = f"partner-{uuid4().hex[:8]}" + await session.execute( # type: ignore[attr-defined] + insert(partner_ops).values( + id=partner_id, + mcc_mnc=uuid4().hex[:10], + oauth2_client_id=client_id, + base_url="https://partner.example", + status="active", + ) + ) + return client_id + + +async def test_outbound_repos_round_trip_through_postgres() -> None: + engine = build_engine(URL) + await create_schema(engine) + session_maker = build_session_maker(engine) + + partner_id, other_partner_id = uuid4(), uuid4() + spec_id, zone_id = uuid4(), uuid4() + # Unique per run: routing_rules has UNIQUE(identifier_type, value_range, priority). + prefix = f"+999{uuid4().int % 10**6:06d}" + cidr_octet = uuid4().int % 200 + 1 + cidr = f"10.{cidr_octet}.0.0/16" + narrow_cidr = f"10.{cidr_octet}.5.0/24" + + async with session_maker() as session: + await _insert_partner(session, partner_id) + await _insert_partner(session, other_partner_id) + expired_id, current_id = uuid4(), uuid4() + await session.execute( + insert(federation_agreements).values( + [ + { + "id": expired_id, + "partner_op_id": partner_id, + "permitted_api_types": ["edge-cloud-deploy"], + "permitted_zone_ids": [], + "service_spec_mappings": {"apps": [], "api_families": {}}, + "valid_from": NOW - timedelta(days=365), + "valid_until": NOW - timedelta(days=1), + "status": "active", + }, + { + "id": current_id, + "partner_op_id": partner_id, + "permitted_api_types": ["device-location-retrieve"], + "permitted_zone_ids": [str(zone_id)], + "service_spec_mappings": { + "apps": [ + { + "appId": "partner-app", + "appVersion": "1.2.0", + "flavourId": "small", + "service_specification_id": str(spec_id), + } + ], + "api_families": {"device-location-retrieve": str(spec_id)}, + }, + "valid_from": NOW - timedelta(days=2), + "valid_until": None, + "status": "active", + }, + ] + ) + ) + rules_to_insert = [ + (other_partner_id, MSISDN_PREFIX, prefix, True), + (partner_id, MSISDN_PREFIX, prefix + "61", True), + (partner_id, MSISDN_PREFIX, prefix + "6", False), + (other_partner_id, IP_CIDR, cidr, True), + (partner_id, IP_CIDR, narrow_cidr, True), + ] + await session.execute( + insert(routing_rules).values( + [ + { + "partner_op_id": partner, + "identifier_type": kind, + "value_range": value, + "is_active": active, + } + for partner, kind, value, active in rules_to_insert + ] + ) + ) + await session.commit() + + partners: PartnerRepositoryPort = PostgresPartnerRepo(session) + partner = await partners.find_by_id(partner_id) + assert partner is not None and partner.id == partner_id + assert await partners.find_by_id(uuid4()) is None + + agreements: AgreementRepositoryPort = PostgresAgreementRepo(session) + agreement = await agreements.find_active_for_partner(partner_id) + assert agreement is not None + assert ( + agreement.id == current_id + ) # the currently valid one wins over the older, expired one + assert agreement.permitted_api_types == {"device-location-retrieve"} + assert agreement.permitted_zone_ids == {zone_id} + assert agreement.resolve_app_spec("partner-app", "1.2.0", "small") == spec_id + assert agreement.resolve_app_spec("partner-app", "9.9.9", "small") is None + assert agreement.resolve_api_spec("device-location-retrieve") == spec_id + assert agreement.valid_until is None + assert agreement.is_valid_at(NOW) + assert await agreements.find_active_for_partner(other_partner_id) is None + + rules: RoutingRuleRepositoryPort = PostgresRoutingRuleRepo(session) + resolver = RoutingResolver(rules) + by_msisdn = await resolver.resolve_partner("msisdn", prefix + "612345") + assert by_msisdn is not None + assert by_msisdn.partner_op_id == partner_id + assert by_msisdn.match == prefix + "61" + broad = await resolver.resolve_partner("msisdn", prefix + "712345") + assert broad is not None and broad.partner_op_id == other_partner_id + by_ip = await resolver.resolve_partner("ip", f"10.{cidr_octet}.5.9") + assert by_ip is not None and by_ip.partner_op_id == partner_id + active_ranges = {r.value_range for r in await rules.list_active(MSISDN_PREFIX)} + assert prefix + "6" not in active_ranges + assert {prefix, prefix + "61"} <= active_ranges + + transactions: TransactionRepositoryPort = PostgresTransactionRepo(session) + tx = FederationTransaction( + id=uuid4(), + partner_op_id=partner_id, + agreement_id=current_id, + direction="outbound", + correlation_id=uuid4(), + api_type="device-location-retrieve", + status="pending", + request_summary={"route_match": prefix + "61"}, + started_at=NOW, + ) + await transactions.add(tx) + await transactions.record_outcome( + tx.id, + status="failed", + completed_at=NOW + timedelta(seconds=1), + response_summary={"status_code": 403}, + error_detail={"status_code": 403, "problem": {"type": "x"}}, + ) + row = ( + await session.execute( + select(federation_transactions).where(federation_transactions.c.id == tx.id) + ) + ).one() + assert row.status == "failed" + assert row.direction == "outbound" + assert row.correlation_id == tx.correlation_id + assert row.federation_operation_id is None + assert row.external_txn_id is None + assert row.response_summary == {"status_code": 403} + assert row.error_detail == {"status_code": 403, "problem": {"type": "x"}} + assert row.completed_at == NOW + timedelta(seconds=1) + + await session.execute( + delete(federation_transactions).where(federation_transactions.c.id == tx.id) + ) + await session.execute( + delete(routing_rules).where( + routing_rules.c.partner_op_id.in_([partner_id, other_partner_id]) + ) + ) + await session.execute( + delete(federation_agreements).where(federation_agreements.c.partner_op_id == partner_id) + ) + await session.execute( + delete(partner_ops).where(partner_ops.c.id.in_([partner_id, other_partner_id])) + ) + await session.commit() + + await engine.dispose() + + +async def test_idempotency_key_is_unique_per_partner_and_api_type() -> None: + engine = build_engine(URL) + await create_schema(engine) + session_maker = build_session_maker(engine) + + partner_id = uuid4() + async with session_maker() as session: + await _insert_partner(session, partner_id) + await session.commit() + + transactions = PostgresTransactionRepo(session) + first = _lcm_transaction(partner_id, key="idem-1") + await transactions.add(first) + + await transactions.add(_lcm_transaction(partner_id, key="idem-2")) + await transactions.add(_lcm_transaction(partner_id, key=None)) + await transactions.add(_lcm_transaction(partner_id, key=None)) + + with pytest.raises(IntegrityError): + await transactions.add(_lcm_transaction(partner_id, key="idem-1")) + await session.rollback() + + stored = ( + await session.execute( + select(federation_transactions).where(federation_transactions.c.id == first.id) + ) + ).one() + assert stored.idempotency_key == "idem-1" + assert stored.request_fingerprint == "a" * 64 + assert stored.external_resource_id == "appInst-1" + assert stored.callback_url == "https://partner.example/callback" + assert stored.callback_attempts == 0 + + await session.execute( + delete(federation_transactions).where( + federation_transactions.c.partner_op_id == partner_id + ) + ) + await session.execute(delete(partner_ops).where(partner_ops.c.id == partner_id)) + await session.commit() + + await engine.dispose() + + +def _lcm_transaction(partner_id: UUID, key: str | None) -> FederationTransaction: + return FederationTransaction( + id=uuid4(), + partner_op_id=partner_id, + direction="inbound", + api_type="install-app", + status="pending", + request_summary={}, + started_at=NOW, + idempotency_key=key, + request_fingerprint="a" * 64, + external_resource_id="appInst-1", + callback_url="https://partner.example/callback", + ) diff --git a/tests/integration/test_partner_repo.py b/tests/integration/test_partner_repo.py new file mode 100644 index 0000000000000000000000000000000000000000..7d36e6580de04643614688593f937b7d017fbf58 --- /dev/null +++ b/tests/integration/test_partner_repo.py @@ -0,0 +1,102 @@ +import os +from uuid import uuid4 + +import pytest +from sqlalchemy import delete, insert + +from federation_manager.adapters.database.core import ( + build_engine, + build_session_maker, + create_schema, +) +from federation_manager.adapters.database.partner_repo import PostgresPartnerRepo +from federation_manager.adapters.database.tables import partner_ops +from federation_manager.application.authentication import PartnerAuthenticator +from federation_manager.domain.errors import PartnerNotActive +from federation_manager.domain.models import ValidatedClaims +from federation_manager.domain.ports import PartnerRepositoryPort +from tests.fakes import FakeJwtValidator + +pytestmark = pytest.mark.integration + +URL = os.getenv("FM_POSTGRES_URL", "postgresql+asyncpg://fm:fm@localhost:5433/fm_db") +TOKEN = "token-partner-a" + + +def _validator(client_id: str) -> FakeJwtValidator: + return FakeJwtValidator({TOKEN: ValidatedClaims(client_id=client_id, scopes={"fed-mgmt"})}) + + +async def test_repo_reads_partner_from_postgres() -> None: + engine = build_engine(URL) + await create_schema(engine) + session_maker = build_session_maker(engine) + + client_id = f"partner-{uuid4().hex[:8]}" + secret_ref = f"/run/secrets/partners/{client_id}" + async with session_maker() as session: + await session.execute( + insert(partner_ops).values( + id=uuid4(), + mcc_mnc=uuid4().hex[:10], + oauth2_client_id=client_id, + base_url="https://partner.example", + our_client_id="our-fm", + our_client_secret_ref=secret_ref, + token_endpoint="https://partner.example/oauth2/token", + status="active", + ) + ) + await session.commit() + + repo: PartnerRepositoryPort = PostgresPartnerRepo(session) + partner = await repo.find_by_oauth2_client_id(client_id) + + assert partner is not None + assert partner.oauth2_client_id == client_id + assert partner.base_url == "https://partner.example" + assert partner.our_client_id == "our-fm" + assert partner.our_client_secret_ref == secret_ref + assert partner.token_endpoint == "https://partner.example/oauth2/token" + assert partner.is_active() + + auth = PartnerAuthenticator(repo, _validator(client_id)) + assert await auth.authenticate(TOKEN) is not None + assert await repo.find_by_oauth2_client_id("nope") is None + + await session.execute( + delete(partner_ops).where(partner_ops.c.oauth2_client_id == client_id) + ) + await session.commit() + + await engine.dispose() + + +async def test_suspended_partner_rejected_against_postgres() -> None: + engine = build_engine(URL) + await create_schema(engine) + session_maker = build_session_maker(engine) + + client_id = f"partner-{uuid4().hex[:8]}" + async with session_maker() as session: + await session.execute( + insert(partner_ops).values( + id=uuid4(), + mcc_mnc=uuid4().hex[:10], + oauth2_client_id=client_id, + base_url="https://partner.example", + status="suspended", + ) + ) + await session.commit() + + auth = PartnerAuthenticator(PostgresPartnerRepo(session), _validator(client_id)) + with pytest.raises(PartnerNotActive): + await auth.authenticate(TOKEN) + + await session.execute( + delete(partner_ops).where(partner_ops.c.oauth2_client_id == client_id) + ) + await session.commit() + + await engine.dispose() diff --git a/tests/integration/test_two_stack_federation.py b/tests/integration/test_two_stack_federation.py new file mode 100644 index 0000000000000000000000000000000000000000..755b5f114423e84ff44851e0f08ae47bdf72b77b --- /dev/null +++ b/tests/integration/test_two_stack_federation.py @@ -0,0 +1,272 @@ +import asyncio +import os +import socket +import subprocess +import sys +import time +from collections.abc import Iterator +from contextlib import suppress +from dataclasses import dataclass +from pathlib import Path +from uuid import UUID, uuid4 + +import httpx +import pytest +from sqlalchemy import delete, insert +from sqlalchemy.ext.asyncio import create_async_engine + +from federation_manager.adapters.database.core import ( + build_engine, + build_session_maker, + create_schema, +) +from federation_manager.adapters.database.federation_context_repo import ( + PostgresFederationContextRepo, +) +from federation_manager.adapters.database.tables import federation_contexts, partner_ops +from federation_manager.adapters.databus.nats_adapter import NatsEventConsumer +from federation_manager.contracts.srm import SUBJECT_OPERATION_COMPLETED +from federation_manager.domain.models import FederationContext + +pytestmark = pytest.mark.integration + +PG_ROOT = os.getenv("FM_POSTGRES_ROOT", "postgresql+asyncpg://fm:fm@localhost:5433") +DB_A, DB_B = "fm_db", "fm_db_b" +NATS = os.getenv("FM_NATS_URL", "nats://localhost:4222") +ISSUER = os.getenv("FM_KEYCLOAK_ISSUER", "http://localhost:8090/realms/federation") +TOKEN_ENDPOINT = f"{ISSUER}/protocol/openid-connect/token" +CLIENT_A, SECRET_A = "originating-op-1", "dd7vNwFqjNpYwaghlEwMbw10g0klWDHb" +CLIENT_B, SECRET_B = "originating-op-2", "2mhznERfWclLDuVojY77Lp4Qd2r4e8Ms" + + +@dataclass(frozen=True) +class Stacks: + port_a: int + port_b: int + partner_a: UUID + partner_b: UUID + + +def _free_port() -> int: + with socket.socket() as s: + s.bind(("127.0.0.1", 0)) + port: int = s.getsockname()[1] + return port + + +def _require(url: str, what: str) -> None: + try: + httpx.get(url, timeout=2.0) + except httpx.HTTPError: + pytest.skip( + f"{what} not reachable at {url}; run docker compose -f docker-compose.dev.yaml up -d" + ) + + +async def _create_database(name: str) -> None: + engine = create_async_engine(f"{PG_ROOT}/postgres", isolation_level="AUTOCOMMIT") + async with engine.connect() as conn: + exists = await conn.exec_driver_sql(f"SELECT 1 FROM pg_database WHERE datname = '{name}'") + if exists.first() is None: + await conn.exec_driver_sql(f'CREATE DATABASE "{name}"') + await engine.dispose() + + +def _start_fm(port: int, env: dict[str, str]) -> subprocess.Popen[bytes]: + process = subprocess.Popen( + [ + sys.executable, + "-m", + "uvicorn", + "federation_manager.main:app", + "--host", + "127.0.0.1", + "--port", + str(port), + "--log-level", + "warning", + ], + env={**os.environ, **env}, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + deadline = time.monotonic() + 40 + while time.monotonic() < deadline: + if process.poll() is not None: + output = process.stdout.read().decode() if process.stdout else "" + raise AssertionError(f"FM on port {port} exited early:\n{output}") + try: + if httpx.get(f"http://127.0.0.1:{port}/healthz", timeout=1.0).status_code == 200: + return process + except httpx.HTTPError: + time.sleep(0.3) + process.kill() + raise AssertionError(f"FM on port {port} did not become ready") + + +def _fm_env(database: str, port: int, federation_id: str, **extra: str) -> dict[str, str]: + return { + "FM_POSTGRES_URL": f"{PG_ROOT}/{database}", + "FM_KEYCLOAK_ISSUER": ISSUER, + "FM_FEDERATION_ID": federation_id, + "FM_COUNTRY_CODE": "ES", + "FM_MCC": "214", + "FM_MNCS": '["07"]', + "FM_PARTNER_STATUS_LINK": f"http://127.0.0.1:{port}/operatorplatform/federation/v1/partner-status", + "FM_ALLOW_INSECURE_PARTNER_ENDPOINTS": "true", + "FM_EVENT_CONSUMER_DURABLE": f"fm-event-worker-{federation_id}", + **extra, + } + + +def _access_token(client_id: str, secret: str) -> str: + response = httpx.post( + TOKEN_ENDPOINT, + data={ + "grant_type": "client_credentials", + "client_id": client_id, + "client_secret": secret, + "scope": "fed-mgmt", + }, + timeout=10.0, + ) + response.raise_for_status() + token: str = response.json()["access_token"] + return token + + +@pytest.fixture +def stacks(tmp_path: Path) -> Iterator[Stacks]: + _require(f"{ISSUER}/.well-known/openid-configuration", "Keycloak") + + asyncio.run(_create_database(DB_B)) + + port_a, port_b = _free_port(), _free_port() + secret_file = tmp_path / "partner-b-secret" + secret_file.write_text(SECRET_A, encoding="utf-8") + + partner_b_id, partner_a_id = uuid4(), uuid4() + mcc_mnc_b, mcc_mnc_a = uuid4().hex[:10], uuid4().hex[:10] + + async def seed() -> None: + for database, values in ( + ( + DB_A, + { + "id": partner_b_id, + "mcc_mnc": mcc_mnc_b, + "oauth2_client_id": CLIENT_B, + "base_url": f"http://127.0.0.1:{port_b}", + "our_client_id": CLIENT_A, + "our_client_secret_ref": str(secret_file), + "token_endpoint": TOKEN_ENDPOINT, + "status": "active", + }, + ), + ( + DB_B, + { + "id": partner_a_id, + "mcc_mnc": mcc_mnc_a, + "oauth2_client_id": CLIENT_A, + "base_url": f"http://127.0.0.1:{port_a}", + "status": "active", + }, + ), + ): + engine = build_engine(f"{PG_ROOT}/{database}") + await create_schema(engine) + async with build_session_maker(engine)() as session: + await session.execute(insert(partner_ops).values(**values)) + await session.commit() + await engine.dispose() + + asyncio.run(seed()) + + processes = [] + try: + processes.append(_start_fm(port_b, _fm_env(DB_B, port_b, "op-b"))) + processes.append( + _start_fm( + port_a, + _fm_env(DB_A, port_a, "op-a", FM_BOOTSTRAP_FEDERATION="true"), + ) + ) + yield Stacks(port_a=port_a, port_b=port_b, partner_a=partner_a_id, partner_b=partner_b_id) + finally: + for process in processes: + process.kill() + process.wait(timeout=10) + + async def drop_consumers() -> None: + for federation_id in ("op-a", "op-b"): + consumer = NatsEventConsumer(NATS, f"fm-event-worker-{federation_id}") + await consumer.connect() + with suppress(Exception): + await consumer.delete_durable(SUBJECT_OPERATION_COMPLETED) + await consumer.close() + + asyncio.run(drop_consumers()) + + async def cleanup() -> None: + for database, partner_id in ((DB_A, partner_b_id), (DB_B, partner_a_id)): + engine = build_engine(f"{PG_ROOT}/{database}") + async with build_session_maker(engine)() as session: + await session.execute( + delete(federation_contexts).where( + federation_contexts.c.partner_op_id == partner_id + ) + ) + await session.execute(delete(partner_ops).where(partner_ops.c.id == partner_id)) + await session.commit() + await engine.dispose() + + asyncio.run(cleanup()) + + +async def _context(database: str, partner_id: UUID, direction: str) -> FederationContext | None: + engine = build_engine(f"{PG_ROOT}/{database}") + async with build_session_maker(engine)() as session: + repo = PostgresFederationContextRepo(session) + context = await ( + repo.find_active_outbound(partner_id) + if direction == "outbound" + else repo.find_active_inbound(partner_id) + ) + await engine.dispose() + return context + + +def test_two_stacks_federate_over_real_oauth2(stacks: Stacks) -> None: + outbound = asyncio.run(_context(DB_A, stacks.partner_b, "outbound")) + inbound = asyncio.run(_context(DB_B, stacks.partner_a, "inbound")) + + assert outbound is not None, "FM-A stored no outbound context; bootstrap federation failed" + assert inbound is not None, "FM-B stored no inbound context" + assert outbound.federation_context_id == inbound.federation_context_id + assert outbound.status == "available" + assert inbound.status == "available" + + context_id = outbound.federation_context_id + health = httpx.get( + f"http://127.0.0.1:{stacks.port_b}/operatorplatform/federation/v1/{context_id}/health", + headers={"Authorization": f"Bearer {_access_token(CLIENT_A, SECRET_A)}"}, + timeout=10.0, + ) + + assert health.status_code == 200 + assert health.json()["federationHealthStatus"]["federationStatus"] == "AVAILABLE" + + +def test_partner_b_rejects_an_unknown_client(stacks: Stacks) -> None: + outbound = asyncio.run(_context(DB_A, stacks.partner_b, "outbound")) + assert outbound is not None + context_id = outbound.federation_context_id + health = httpx.get( + f"http://127.0.0.1:{stacks.port_b}/operatorplatform/federation/v1/{context_id}/health", + headers={"Authorization": f"Bearer {_access_token(CLIENT_B, SECRET_B)}"}, + timeout=10.0, + ) + + assert health.status_code in (401, 404) + assert "federationHealthStatus" not in health.text diff --git a/tests/test_authentication.py b/tests/test_authentication.py new file mode 100644 index 0000000000000000000000000000000000000000..4666b0d315c8762103759f28f248581516ea9cf8 --- /dev/null +++ b/tests/test_authentication.py @@ -0,0 +1,61 @@ +from uuid import uuid4 + +import pytest + +from federation_manager.application.authentication import PartnerAuthenticator +from federation_manager.domain.errors import ( + AuthenticationFailed, + PartnerNotActive, + PartnerUnknown, +) +from federation_manager.domain.models import PartnerOP, ValidatedClaims +from tests.fakes import FakeJwtValidator, InMemoryPartnerRepo + +CLIENT_ID = "partner-a" +TOKEN = "token-partner-a" + + +def _partner(status: str = "active") -> PartnerOP: + return PartnerOP(id=uuid4(), mcc_mnc="214-07", oauth2_client_id=CLIENT_ID, status=status) + + +def _authenticator(partner: PartnerOP, scopes: set[str] | None = None) -> PartnerAuthenticator: + claims = ValidatedClaims(client_id=CLIENT_ID, scopes=scopes or {"fed-mgmt"}) + return PartnerAuthenticator(InMemoryPartnerRepo([partner]), FakeJwtValidator({TOKEN: claims})) + + +async def test_authenticates_known_active_partner() -> None: + partner = _partner() + + assert await _authenticator(partner).authenticate(TOKEN) is partner + + +async def test_invalid_token_is_rejected() -> None: + auth = _authenticator(_partner()) + + with pytest.raises(AuthenticationFailed): + await auth.authenticate("not-a-real-token") + + +async def test_token_without_fed_mgmt_scope_is_rejected() -> None: + auth = _authenticator(_partner(), scopes={"some-other-scope"}) + + with pytest.raises(AuthenticationFailed): + await auth.authenticate(TOKEN) + + +async def test_unknown_client_id_is_rejected() -> None: + claims = ValidatedClaims(client_id="partner-nobody", scopes={"fed-mgmt"}) + auth = PartnerAuthenticator( + InMemoryPartnerRepo([_partner()]), FakeJwtValidator({TOKEN: claims}) + ) + + with pytest.raises(PartnerUnknown): + await auth.authenticate(TOKEN) + + +async def test_suspended_partner_is_rejected() -> None: + auth = _authenticator(_partner(status="suspended")) + + with pytest.raises(PartnerNotActive): + await auth.authenticate(TOKEN) diff --git a/tests/test_authorization.py b/tests/test_authorization.py new file mode 100644 index 0000000000000000000000000000000000000000..651d1943af829a5e7105bd147c5ac3b2b8421373 --- /dev/null +++ b/tests/test_authorization.py @@ -0,0 +1,119 @@ +from datetime import datetime, timezone +from uuid import UUID, uuid4 + +import pytest + +from federation_manager.application.authentication import PartnerAuthenticator +from federation_manager.application.authorization import FederationAuthorizer +from federation_manager.domain.errors import AgreementExpired, AgreementViolation +from federation_manager.domain.models import Agreement, AppMapping, PartnerOP, ValidatedClaims +from tests.fakes import FakeJwtValidator, InMemoryAgreementRepo, InMemoryPartnerRepo + +CLIENT_ID = "partner-a" +TOKEN = "token-partner-a" +APP_ID = "partner-app" +APP_VERSION = "1.2.0" +FLAVOUR = "small" + + +def _partner() -> PartnerOP: + return PartnerOP(id=uuid4(), mcc_mnc="214-07", oauth2_client_id=CLIENT_ID, status="active") + + +def _app_mapping(app_id: str, spec: UUID | None = None) -> AppMapping: + return AppMapping( + app_id=app_id, + app_version=APP_VERSION, + flavour_id=FLAVOUR, + service_specification_id=spec or uuid4(), + ) + + +def _agreement(partner_id: object, **over: object) -> Agreement: + zone = uuid4() + base: dict[str, object] = { + "id": uuid4(), + "partner_op_id": partner_id, + "permitted_api_types": {"edge-cloud-deploy"}, + "permitted_zone_ids": {zone}, + "app_mappings": (_app_mapping(APP_ID),), + "api_family_mappings": {}, + "valid_from": datetime(2026, 1, 1, tzinfo=timezone.utc), + "valid_until": datetime(2099, 1, 1, tzinfo=timezone.utc), + "status": "active", + } + base.update(over) + return Agreement(**base) # type: ignore[arg-type] + + +def _authenticator(partner: PartnerOP) -> PartnerAuthenticator: + claims = ValidatedClaims(client_id=CLIENT_ID, scopes={"fed-mgmt"}) + return PartnerAuthenticator(InMemoryPartnerRepo([partner]), FakeJwtValidator({TOKEN: claims})) + + +def _authorizer(partner: PartnerOP, agreement: Agreement) -> FederationAuthorizer: + return FederationAuthorizer(_authenticator(partner), InMemoryAgreementRepo([agreement])) + + +async def test_authorizes_valid_request_and_resolves_spec() -> None: + partner = _partner() + zone = uuid4() + spec = uuid4() + agreement = _agreement( + partner.id, permitted_zone_ids={zone}, app_mappings=(_app_mapping(APP_ID, spec),) + ) + + result = await _authorizer(partner, agreement).authorize_app( + TOKEN, "edge-cloud-deploy", APP_ID, APP_VERSION, FLAVOUR, zone_id=zone + ) + + assert result.partner is partner + assert result.service_specification_id == spec + + +async def test_expired_agreement_rejected() -> None: + partner = _partner() + agreement = _agreement(partner.id, valid_until=datetime(2025, 1, 1, tzinfo=timezone.utc)) + + with pytest.raises(AgreementExpired): + await _authorizer(partner, agreement).authorize_app( + TOKEN, "edge-cloud-deploy", APP_ID, APP_VERSION, FLAVOUR + ) + + +async def test_no_active_agreement_rejected() -> None: + partner = _partner() + authorizer = FederationAuthorizer(_authenticator(partner), InMemoryAgreementRepo([])) + + with pytest.raises(AgreementExpired): + await authorizer.authorize_app(TOKEN, "edge-cloud-deploy", APP_ID, APP_VERSION, FLAVOUR) + + +async def test_api_not_permitted_rejected() -> None: + partner = _partner() + agreement = _agreement(partner.id, permitted_api_types={"device-location"}) + + with pytest.raises(AgreementViolation): + await _authorizer(partner, agreement).authorize_app( + TOKEN, "edge-cloud-deploy", APP_ID, APP_VERSION, FLAVOUR + ) + + +async def test_zone_not_permitted_rejected() -> None: + partner = _partner() + agreement = _agreement(partner.id, permitted_zone_ids={uuid4()}) + + with pytest.raises(AgreementViolation): + await _authorizer(partner, agreement).authorize_app( + TOKEN, "edge-cloud-deploy", APP_ID, APP_VERSION, FLAVOUR, zone_id=uuid4() + ) + + +async def test_unmapped_app_rejected() -> None: + partner = _partner() + agreement = _agreement(partner.id, app_mappings=(_app_mapping("other-app"),)) + + with pytest.raises(AgreementViolation): + await _authorizer(partner, agreement).authorize_app( + TOKEN, "edge-cloud-deploy", APP_ID, APP_VERSION, FLAVOUR + ) diff --git a/tests/test_client_secret_token_provider.py b/tests/test_client_secret_token_provider.py new file mode 100644 index 0000000000000000000000000000000000000000..b5c4d019ca924df2b4dc707d598b7be461a69487 --- /dev/null +++ b/tests/test_client_secret_token_provider.py @@ -0,0 +1,201 @@ +import asyncio +from pathlib import Path +from urllib.parse import parse_qs +from uuid import uuid4 + +import httpx +import pytest + +from federation_manager.adapters.security.client_secret_token_provider import ( + FileClientSecretTokenProvider, +) +from federation_manager.domain.errors import ( + PartnerTokenConfigurationError, + PartnerTokenRequestFailed, +) +from federation_manager.domain.models import PartnerOP +from federation_manager.domain.ports import PartnerTokenProviderPort + + +class FakeClock: + def __init__(self) -> None: + self.now = 0.0 + + def __call__(self) -> float: + return self.now + + def advance(self, seconds: float) -> None: + self.now += seconds + + +def _partner(secret_path: Path, **overrides: object) -> PartnerOP: + values: dict[str, object] = { + "id": uuid4(), + "mcc_mnc": "214-07", + "oauth2_client_id": "partner-a", + "status": "active", + "our_client_id": "our-fm", + "our_client_secret_ref": str(secret_path), + "token_endpoint": "https://partner.example/oauth2/token", + } + values.update(overrides) + return PartnerOP(**values) # type: ignore[arg-type] + + +async def test_fetches_token_with_secret_from_file(tmp_path: Path) -> None: + secret_path = tmp_path / "partner-a" + secret_path.write_text("secret-value\n", encoding="utf-8") + request_data: dict[str, list[str]] = {} + + def handler(request: httpx.Request) -> httpx.Response: + request_data.update(parse_qs(request.content.decode())) + assert str(request.url) == "https://partner.example/oauth2/token" + return httpx.Response( + 200, + json={"access_token": "access-token", "token_type": "Bearer", "expires_in": 300}, + ) + + async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client: + provider: PartnerTokenProviderPort = FileClientSecretTokenProvider(client) + token = await provider.token_for(_partner(secret_path)) + + assert token == "access-token" + assert request_data == { + "grant_type": ["client_credentials"], + "client_id": ["our-fm"], + "client_secret": ["secret-value"], + "scope": ["fed-mgmt"], + } + + +async def test_caches_token_until_refresh_window(tmp_path: Path) -> None: + secret_path = tmp_path / "partner-a" + secret_path.write_text("secret", encoding="utf-8") + calls = 0 + clock = FakeClock() + + def handler(request: httpx.Request) -> httpx.Response: + nonlocal calls + calls += 1 + return httpx.Response(200, json={"access_token": f"token-{calls}", "expires_in": 120}) + + async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client: + provider = FileClientSecretTokenProvider(client, clock=clock) + partner = _partner(secret_path) + + assert await provider.token_for(partner) == "token-1" + clock.advance(89) + assert await provider.token_for(partner) == "token-1" + clock.advance(2) + assert await provider.token_for(partner) == "token-2" + + assert calls == 2 + + +async def test_rereads_rotated_secret_when_token_refreshes(tmp_path: Path) -> None: + secret_path = tmp_path / "partner-a" + secret_path.write_text("old-secret", encoding="utf-8") + seen_secrets: list[str] = [] + clock = FakeClock() + + def handler(request: httpx.Request) -> httpx.Response: + secret = parse_qs(request.content.decode())["client_secret"][0] + seen_secrets.append(secret) + return httpx.Response( + 200, + json={"access_token": f"token-for-{secret}", "expires_in": 120}, + ) + + async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client: + provider = FileClientSecretTokenProvider(client, clock=clock) + partner = _partner(secret_path) + + assert await provider.token_for(partner) == "token-for-old-secret" + secret_path.write_text("new-secret", encoding="utf-8") + clock.advance(91) + assert await provider.token_for(partner) == "token-for-new-secret" + + assert seen_secrets == ["old-secret", "new-secret"] + + +async def test_concurrent_requests_share_one_token_fetch(tmp_path: Path) -> None: + secret_path = tmp_path / "partner-a" + secret_path.write_text("secret", encoding="utf-8") + calls = 0 + + async def handler(request: httpx.Request) -> httpx.Response: + nonlocal calls + calls += 1 + await asyncio.sleep(0) + return httpx.Response(200, json={"access_token": "shared", "expires_in": 300}) + + async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client: + provider = FileClientSecretTokenProvider(client) + partner = _partner(secret_path) + tokens = await asyncio.gather(*(provider.token_for(partner) for _ in range(10))) + + assert tokens == ["shared"] * 10 + assert calls == 1 + + +async def test_missing_outbound_configuration_fails_before_request(tmp_path: Path) -> None: + calls = 0 + + def handler(request: httpx.Request) -> httpx.Response: + nonlocal calls + calls += 1 + return httpx.Response(500) + + async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client: + provider = FileClientSecretTokenProvider(client) + with pytest.raises(PartnerTokenConfigurationError) as error: + await provider.token_for(_partner(tmp_path / "unused", token_endpoint=None)) + + assert error.value.field == "token endpoint" + assert calls == 0 + + +async def test_refuses_to_send_secret_over_http(tmp_path: Path) -> None: + secret_path = tmp_path / "partner-a" + secret_path.write_text("secret", encoding="utf-8") + + def handler(request: httpx.Request) -> httpx.Response: + raise AssertionError("transport must not be called") + + async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client: + provider = FileClientSecretTokenProvider(client) + with pytest.raises(PartnerTokenConfigurationError): + await provider.token_for( + _partner(secret_path, token_endpoint="http://partner.example/oauth2/token") + ) + + +async def test_token_endpoint_failure_does_not_leak_secret(tmp_path: Path) -> None: + secret_path = tmp_path / "partner-a" + secret_path.write_text("do-not-leak", encoding="utf-8") + + def handler(request: httpx.Request) -> httpx.Response: + return httpx.Response(401, json={"error": "invalid_client", "secret": "do-not-leak"}) + + async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client: + provider = FileClientSecretTokenProvider(client) + with pytest.raises(PartnerTokenRequestFailed) as error: + await provider.token_for(_partner(secret_path)) + + assert "do-not-leak" not in str(error.value) + + +async def test_http_token_endpoint_is_allowed_only_when_explicitly_enabled(tmp_path: Path) -> None: + secret_path = tmp_path / "partner-a" + secret_path.write_text("secret", encoding="utf-8") + + def handler(request: httpx.Request) -> httpx.Response: + return httpx.Response(200, json={"access_token": "token", "expires_in": 300}) + + partner = _partner(secret_path, token_endpoint="http://localhost:8090/token") + async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client: + with pytest.raises(PartnerTokenConfigurationError): + await FileClientSecretTokenProvider(client).token_for(partner) + + permissive = FileClientSecretTokenProvider(client, allow_insecure=True) + assert await permissive.token_for(partner) == "token" diff --git a/tests/test_create_federation.py b/tests/test_create_federation.py new file mode 100644 index 0000000000000000000000000000000000000000..63ba1b5d79c80e9fad955f3616f6eab026b4c178 --- /dev/null +++ b/tests/test_create_federation.py @@ -0,0 +1,233 @@ +from collections.abc import AsyncIterator +from contextlib import asynccontextmanager +from dataclasses import replace +from datetime import datetime, timezone +from typing import Any +from uuid import uuid4 + +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from federation_manager.dependencies import ( + get_federation_context_repo, + get_jwt_validator, + get_partner_repo, +) +from federation_manager.domain.models import FederationContext, PartnerOP, ValidatedClaims +from federation_manager.main import create_app +from tests.fakes import FakeJwtValidator, InMemoryFederationContextRepo, InMemoryPartnerRepo + +CLIENT_ID = "partner-a" +TOKEN = "token-partner-a" +STRANGER_TOKEN = "token-partner-nobody" +URL = "/operatorplatform/federation/v1/partner" +STATUS_LINK = "https://partner.example/operatorplatform/federation/v1/partner-status" + + +@asynccontextmanager +async def _no_infra(app: FastAPI) -> AsyncIterator[None]: + yield + + +def _bearer(token: str) -> dict[str, str]: + return {"Authorization": f"Bearer {token}"} + + +def _client( + partner_status: str = "active", existing: list[FederationContext] | None = None +) -> tuple[TestClient, PartnerOP, InMemoryFederationContextRepo]: + partner = PartnerOP( + id=uuid4(), mcc_mnc="214-07", oauth2_client_id=CLIENT_ID, status=partner_status + ) + contexts = InMemoryFederationContextRepo(existing or []) + validator = FakeJwtValidator( + { + TOKEN: ValidatedClaims(client_id=CLIENT_ID, scopes={"fed-mgmt"}), + STRANGER_TOKEN: ValidatedClaims(client_id="partner-nobody", scopes={"fed-mgmt"}), + } + ) + app = create_app(lifespan=_no_infra) + app.dependency_overrides[get_partner_repo] = lambda: InMemoryPartnerRepo([partner]) + app.dependency_overrides[get_jwt_validator] = lambda: validator + app.dependency_overrides[get_federation_context_repo] = lambda: contexts + return TestClient(app), partner, contexts + + +def _body(**over: Any) -> dict[str, Any]: + body: dict[str, Any] = { + "initialDate": "2026-09-08T12:00:00Z", + "partnerStatusLink": STATUS_LINK, + "origOPFederationId": "partner-op", + "origOPCountryCode": "ES", + "origOPMobileNetworkCodes": {"mcc": "214", "mncs": ["07"]}, + } + body.update(over) + return body + + +def _inbound(partner: PartnerOP, status: str = "available") -> FederationContext: + return FederationContext( + id=uuid4(), + partner_op_id=partner.id, + direction="inbound", + federation_context_id="ctx-existing", + status=status, + created_at=datetime(2026, 3, 1, tzinfo=timezone.utc), + ) + + +def test_creates_a_federation_context_for_the_calling_partner() -> None: + client, partner, contexts = _client() + + r = client.post(URL, json=_body(), headers=_bearer(TOKEN)) + + assert r.status_code == 200 + body = r.json() + assert body["platformCaps"] == ["serviceAPIs"] + assert body["partnerOPFederationId"] == "oop-local" + context_id = body["federationContextId"] + assert context_id + assert r.headers["Location"] == f"/operatorplatform/federation/v1/{context_id}/partner" + + stored = contexts.contexts[0] + assert stored.partner_op_id == partner.id + assert stored.federation_context_id == context_id + assert stored.direction == "inbound" + assert stored.status_callback_url == STATUS_LINK + + +def test_the_new_context_is_immediately_usable_for_health() -> None: + client, _, _ = _client() + + context_id = client.post(URL, json=_body(), headers=_bearer(TOKEN)).json()[ + "federationContextId" + ] + health = client.get( + f"/operatorplatform/federation/v1/{context_id}/health", headers=_bearer(TOKEN) + ) + + assert health.status_code == 200 + assert health.json()["federationHealthStatus"]["federationStatus"] == "AVAILABLE" + + +def test_second_federation_with_the_same_partner_is_409() -> None: + client, partner, _ = _client() + client.post(URL, json=_body(), headers=_bearer(TOKEN)) + + r = client.post(URL, json=_body(), headers=_bearer(TOKEN)) + + assert r.status_code == 409 + assert r.json()["type"] == "urn:oop:ewbi:error:federation-exists" + + +def test_a_terminated_federation_can_be_re_established() -> None: + client, partner, contexts = _client() + contexts.contexts.append(_inbound(partner, status="terminated")) + + r = client.post(URL, json=_body(), headers=_bearer(TOKEN)) + + assert r.status_code == 200 + assert len(contexts.contexts) == 2 + + +def test_request_without_mandatory_fields_is_422() -> None: + client, _, _ = _client() + + for bad in ({"partnerStatusLink": STATUS_LINK}, {"initialDate": "2026-09-08T12:00:00Z"}): + r = client.post(URL, json=bad, headers=_bearer(TOKEN)) + assert r.status_code == 422, bad + + +def _delete_url(context_id: str) -> str: + return f"/operatorplatform/federation/v1/{context_id}/partner" + + +def test_delete_terminates_the_federation() -> None: + client, partner, contexts = _client() + context_id = client.post(URL, json=_body(), headers=_bearer(TOKEN)).json()[ + "federationContextId" + ] + + r = client.delete(_delete_url(context_id), headers=_bearer(TOKEN)) + + assert r.status_code == 200 + assert contexts.contexts[0].status == "terminated" + + +def test_health_on_a_terminated_federation_is_404() -> None: + client, _, _ = _client() + context_id = client.post(URL, json=_body(), headers=_bearer(TOKEN)).json()[ + "federationContextId" + ] + client.delete(_delete_url(context_id), headers=_bearer(TOKEN)) + + health = client.get( + f"/operatorplatform/federation/v1/{context_id}/health", headers=_bearer(TOKEN) + ) + + assert health.status_code == 404 + assert health.json()["type"] == "urn:oop:ewbi:error:federation-context-unknown" + + +def test_deleting_twice_is_404() -> None: + client, _, _ = _client() + context_id = client.post(URL, json=_body(), headers=_bearer(TOKEN)).json()[ + "federationContextId" + ] + client.delete(_delete_url(context_id), headers=_bearer(TOKEN)) + + r = client.delete(_delete_url(context_id), headers=_bearer(TOKEN)) + + assert r.status_code == 404 + + +def test_terminating_frees_the_partner_to_federate_again() -> None: + client, _, contexts = _client() + first = client.post(URL, json=_body(), headers=_bearer(TOKEN)).json()["federationContextId"] + client.delete(_delete_url(first), headers=_bearer(TOKEN)) + + second = client.post(URL, json=_body(), headers=_bearer(TOKEN)) + + assert second.status_code == 200 + assert second.json()["federationContextId"] != first + assert len(contexts.contexts) == 2 + + +def test_another_partner_cannot_delete_our_federation() -> None: + client, _, contexts = _client() + context_id = client.post(URL, json=_body(), headers=_bearer(TOKEN)).json()[ + "federationContextId" + ] + + r = client.delete(_delete_url(context_id), headers=_bearer(STRANGER_TOKEN)) + + assert r.status_code == 401 + assert contexts.contexts[0].status == "available" + + +def test_health_still_reports_a_locked_federation() -> None: + client, partner, contexts = _client() + client.post(URL, json=_body(), headers=_bearer(TOKEN)) + stored = contexts.contexts[0] + contexts.contexts[0] = replace(stored, status="locked") + + health = client.get( + f"/operatorplatform/federation/v1/{stored.federation_context_id}/health", + headers=_bearer(TOKEN), + ) + + assert health.status_code == 200 + assert health.json()["federationHealthStatus"]["federationStatus"] == "LOCKED" + + +def test_a_locked_federation_can_still_be_deleted() -> None: + client, _, contexts = _client() + context_id = client.post(URL, json=_body(), headers=_bearer(TOKEN)).json()[ + "federationContextId" + ] + contexts.contexts[0] = replace(contexts.contexts[0], status="locked") + + r = client.delete(_delete_url(context_id), headers=_bearer(TOKEN)) + + assert r.status_code == 200 + assert contexts.contexts[0].status == "terminated" diff --git a/tests/test_domain.py b/tests/test_domain.py new file mode 100644 index 0000000000000000000000000000000000000000..4cbf70ad258ee486cc9eb39d232f40b24316e49d --- /dev/null +++ b/tests/test_domain.py @@ -0,0 +1,79 @@ +from datetime import datetime, timezone +from uuid import UUID, uuid4 + +from federation_manager.domain.models import Agreement, AppMapping, PartnerOP + + +def _dt(y: int, m: int, d: int) -> datetime: + return datetime(y, m, d, tzinfo=timezone.utc) + + +def _app_mapping(app_id: str, spec: UUID | None = None) -> AppMapping: + return AppMapping( + app_id=app_id, + app_version="1.2.0", + flavour_id="small", + service_specification_id=spec or uuid4(), + ) + + +def _agreement(**over: object) -> Agreement: + zone = uuid4() + base: dict[str, object] = { + "id": uuid4(), + "partner_op_id": uuid4(), + "permitted_api_types": {"edge-cloud-deploy"}, + "permitted_zone_ids": {zone}, + "app_mappings": (_app_mapping("partner-app"),), + "api_family_mappings": {}, + "valid_from": _dt(2026, 1, 1), + "valid_until": _dt(2027, 1, 1), + "status": "active", + } + base.update(over) + return Agreement(**base) # type: ignore[arg-type] + + +def test_partner_is_active() -> None: + assert PartnerOP(uuid4(), "214-07", "partner-a", "active").is_active() + assert not PartnerOP(uuid4(), "214-07", "partner-a", "suspended").is_active() + + +def test_valid_at_boundaries() -> None: + a = _agreement() + assert not a.is_valid_at(_dt(2025, 12, 31)) # before valid_from + assert a.is_valid_at(_dt(2026, 6, 1)) # inside + assert not a.is_valid_at(_dt(2027, 1, 1)) # valid_until is exclusive + assert not a.is_valid_at(_dt(2027, 6, 1)) # after + + +def test_valid_until_none_is_indefinite() -> None: + a = _agreement(valid_until=None) + assert a.is_valid_at(_dt(2099, 1, 1)) + + +def test_permits_api_and_zone() -> None: + zone = uuid4() + a = _agreement(permitted_api_types={"edge-cloud-deploy"}, permitted_zone_ids={zone}) + assert a.permits_api("edge-cloud-deploy") + assert not a.permits_api("edge-cloud-terminate") + assert a.permits_zone(zone) + assert not a.permits_zone(uuid4()) + + +def test_resolve_app_spec_needs_the_whole_triple() -> None: + spec = uuid4() + a = _agreement(app_mappings=(_app_mapping("partner-app", spec),)) + + assert a.resolve_app_spec("partner-app", "1.2.0", "small") == spec + assert a.resolve_app_spec("partner-app", "9.9.9", "small") is None + assert a.resolve_app_spec("partner-app", "1.2.0", "large") is None + assert a.resolve_app_spec("unknown-app", "1.2.0", "small") is None + + +def test_resolve_api_spec() -> None: + spec = uuid4() + a = _agreement(api_family_mappings={"device-location-retrieve": spec}) + + assert a.resolve_api_spec("device-location-retrieve") == spec + assert a.resolve_api_spec("device-status-retrieve") is None diff --git a/tests/test_ewbi_auth.py b/tests/test_ewbi_auth.py new file mode 100644 index 0000000000000000000000000000000000000000..1a89a9ff0b418ad8e7a8f332c872f5a0f733afd3 --- /dev/null +++ b/tests/test_ewbi_auth.py @@ -0,0 +1,176 @@ +from collections.abc import AsyncIterator +from contextlib import asynccontextmanager +from datetime import datetime, timezone +from typing import Any +from uuid import UUID, uuid4 + +import pytest +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from federation_manager.dependencies import ( + get_agreement_repo, + get_command_publisher, + get_federation_context_repo, + get_jwt_validator, + get_partner_repo, + get_transaction_repo, +) +from federation_manager.domain.models import ( + Agreement, + AppMapping, + FederationContext, + PartnerOP, + ValidatedClaims, +) +from federation_manager.main import create_app +from tests.fakes import ( + FakeJwtValidator, + InMemoryAgreementRepo, + InMemoryFederationContextRepo, + InMemoryPartnerRepo, + InMemoryTransactionRepo, + RecordingCommandPublisher, +) + +CLIENT_ID = "partner-a" +TOKEN = "token-partner-a" +STRANGER_TOKEN = "token-partner-nobody" +CONTEXT_ID = "fed-ctx-1" +BASE = "/operatorplatform/federation/v1" +ZONE_ID = str(uuid4()) +SPEC_ID = uuid4() + +_CREATE_FEDERATION_BODY = { + "initialDate": "2026-09-08T12:00:00Z", + "partnerStatusLink": "https://partner.example/partner-status", +} +_INSTALL_APP_BODY = { + "appId": "videoAnalytics", + "appVersion": "1.2.0", + "appProviderId": "partnerProvider", + "zoneInfo": {"zoneId": ZONE_ID, "flavourId": "small"}, + "appInstCallbackLink": "https://partner.example/cb", +} + +# Every partner-facing EWBI route, with a body valid enough that authentication is what fails. +ROUTES = [ + ("GET", f"{BASE}/{CONTEXT_ID}/health", None, {}), + ("POST", f"{BASE}/partner", _CREATE_FEDERATION_BODY, {}), + ("DELETE", f"{BASE}/{CONTEXT_ID}/partner", None, {}), + ( + "POST", + f"{BASE}/{CONTEXT_ID}/application/lcm", + _INSTALL_APP_BODY, + {"Idempotency-Key": "idem-1"}, + ), +] +ROUTE_IDS = [f"{method} {path}" for method, path, _, _ in ROUTES] + + +@asynccontextmanager +async def _no_infra(app: FastAPI) -> AsyncIterator[None]: + yield + + +def _client(partner_status: str = "active") -> TestClient: + partner = PartnerOP( + id=uuid4(), mcc_mnc="214-07", oauth2_client_id=CLIENT_ID, status=partner_status + ) + context = FederationContext( + id=uuid4(), + partner_op_id=partner.id, + direction="inbound", + federation_context_id=CONTEXT_ID, + status="available", + created_at=datetime(2026, 3, 1, tzinfo=timezone.utc), + ) + agreement = Agreement( + id=uuid4(), + partner_op_id=partner.id, + permitted_api_types={"install-app"}, + permitted_zone_ids={UUID(ZONE_ID)}, + app_mappings=( + AppMapping( + app_id="videoAnalytics", + app_version="1.2.0", + flavour_id="small", + service_specification_id=SPEC_ID, + ), + ), + api_family_mappings={}, + valid_from=datetime(2026, 1, 1, tzinfo=timezone.utc), + valid_until=None, + status="active", + ) + validator = FakeJwtValidator( + { + TOKEN: ValidatedClaims(client_id=CLIENT_ID, scopes={"fed-mgmt"}), + STRANGER_TOKEN: ValidatedClaims(client_id="partner-nobody", scopes={"fed-mgmt"}), + } + ) + + app = create_app(lifespan=_no_infra) + app.dependency_overrides[get_partner_repo] = lambda: InMemoryPartnerRepo([partner]) + app.dependency_overrides[get_jwt_validator] = lambda: validator + app.dependency_overrides[get_federation_context_repo] = lambda: InMemoryFederationContextRepo( + [context] + ) + app.dependency_overrides[get_agreement_repo] = lambda: InMemoryAgreementRepo([agreement]) + app.dependency_overrides[get_transaction_repo] = lambda: InMemoryTransactionRepo() + app.dependency_overrides[get_command_publisher] = lambda: RecordingCommandPublisher() + return TestClient(app) + + +def _call( + client: TestClient, + route: tuple[str, str, dict[str, Any] | None, dict[str, str]], + token: str | None, +) -> Any: + method, path, body, headers = route + request_headers = dict(headers) + if token is not None: + request_headers["Authorization"] = f"Bearer {token}" + return client.request(method, path, json=body, headers=request_headers) + + +@pytest.mark.parametrize("route", ROUTES, ids=ROUTE_IDS) +def test_route_rejects_an_anonymous_caller( + route: tuple[str, str, dict[str, Any] | None, dict[str, str]], +) -> None: + response = _call(_client(), route, token=None) + + assert response.status_code == 401 + assert response.json()["type"] == "urn:oop:ewbi:error:authentication-failed" + assert response.headers["WWW-Authenticate"] == 'Bearer scope="fed-mgmt"' + + +@pytest.mark.parametrize("route", ROUTES, ids=ROUTE_IDS) +def test_route_rejects_an_unverifiable_token( + route: tuple[str, str, dict[str, Any] | None, dict[str, str]], +) -> None: + response = _call(_client(), route, token="forged") + + assert response.status_code == 401 + assert response.json()["type"] == "urn:oop:ewbi:error:authentication-failed" + + +@pytest.mark.parametrize("route", ROUTES, ids=ROUTE_IDS) +def test_route_rejects_an_unregistered_partner( + route: tuple[str, str, dict[str, Any] | None, dict[str, str]], +) -> None: + response = _call(_client(), route, token=STRANGER_TOKEN) + + assert response.status_code == 401 + assert response.json()["type"] == "urn:oop:ewbi:error:partner-unknown" + assert "partner-nobody" not in response.text + + +@pytest.mark.parametrize("route", ROUTES, ids=ROUTE_IDS) +def test_route_rejects_a_suspended_partner( + route: tuple[str, str, dict[str, Any] | None, dict[str, str]], +) -> None: + response = _call(_client(partner_status="suspended"), route, token=TOKEN) + + assert response.status_code == 403 + assert response.json()["type"] == "urn:oop:ewbi:error:partner-not-active" diff --git a/tests/test_ewbi_client.py b/tests/test_ewbi_client.py new file mode 100644 index 0000000000000000000000000000000000000000..76b813424f8d9f8c6479de6b662c62d9aed43cea --- /dev/null +++ b/tests/test_ewbi_client.py @@ -0,0 +1,148 @@ +import json +from uuid import UUID, uuid4 + +import httpx +import pytest + +from federation_manager.adapters.http.ewbi_client import HttpxEwbiClient +from federation_manager.domain.errors import ( + PartnerEndpointConfigurationError, + PartnerRequestFailed, +) +from federation_manager.domain.models import PartnerOP +from federation_manager.domain.ports import EwbiClientPort + + +class FakeTokenProvider: + def __init__(self, token: str = "partner-access-token") -> None: + self.token = token + self.calls: list[tuple[UUID, str]] = [] + + async def token_for(self, partner: PartnerOP, scope: str = "fed-mgmt") -> str: + self.calls.append((partner.id, scope)) + return self.token + + +def _partner(base_url: str | None = "https://partner.example") -> PartnerOP: + return PartnerOP( + id=uuid4(), + mcc_mnc="214-07", + oauth2_client_id="partner-a", + status="active", + base_url=base_url, + ) + + +async def test_returns_location_header_from_partner() -> None: + def handler(request: httpx.Request) -> httpx.Response: + return httpx.Response( + 200, json={"ok": True}, headers={"Location": "https://partner.example/session/1"} + ) + + async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as http_client: + client = HttpxEwbiClient(http_client, FakeTokenProvider()) + response = await client.post( + _partner(), "/operatorplatform/federation/v1/ctx-1/apiservice/QualityOnDemand", {} + ) + + assert response.location == "https://partner.example/session/1" + + +async def test_posts_payload_with_bearer_token_and_only_standard_headers() -> None: + partner = _partner() + token_provider = FakeTokenProvider() + + def handler(request: httpx.Request) -> httpx.Response: + assert ( + str(request.url) + == "https://partner.example/operatorplatform/federation/v1/ctx-1/apiservice/DeviceLocation" + ) + assert request.headers["Authorization"] == "Bearer partner-access-token" + assert request.headers["Accept"] == "application/json" + assert request.headers["Content-Type"] == "application/json" + assert not [name for name in request.headers if name.lower().startswith("x-")] + assert json.loads(request.content) == {"device": {"phoneNumber": "+34612345678"}} + return httpx.Response(202, json={"status": "accepted"}) + + async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as http_client: + client: EwbiClientPort = HttpxEwbiClient(http_client, token_provider) + response = await client.post( + partner, + "/operatorplatform/federation/v1/ctx-1/apiservice/DeviceLocation", + {"device": {"phoneNumber": "+34612345678"}}, + ) + + assert response.status_code == 202 + assert response.body == {"status": "accepted"} + assert token_provider.calls == [(partner.id, "fed-mgmt")] + + +async def test_returns_partner_http_error_as_response() -> None: + def handler(request: httpx.Request) -> httpx.Response: + return httpx.Response(403, json={"type": "agreement-violation"}) + + async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as http_client: + client = HttpxEwbiClient(http_client, FakeTokenProvider()) + response = await client.post( + _partner(), "/operatorplatform/federation/v1/ctx-1/apiservice/DeviceStatus", {} + ) + + assert response.status_code == 403 + assert response.body == {"type": "agreement-violation"} + + +async def test_rejects_invalid_partner_endpoint_before_fetching_token() -> None: + token_provider = FakeTokenProvider() + + def handler(request: httpx.Request) -> httpx.Response: + raise AssertionError("transport must not be called") + + async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as http_client: + client = HttpxEwbiClient(http_client, token_provider) + with pytest.raises(PartnerEndpointConfigurationError): + await client.post( + _partner("http://partner.example"), + "/operatorplatform/federation/v1/ctx-1/apiservice/DeviceStatus", + {}, + ) + + assert token_provider.calls == [] + + +async def test_transport_failure_becomes_safe_domain_error() -> None: + def handler(request: httpx.Request) -> httpx.Response: + raise httpx.ConnectError("internal connection detail", request=request) + + async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as http_client: + client = HttpxEwbiClient(http_client, FakeTokenProvider()) + with pytest.raises(PartnerRequestFailed) as error: + await client.post( + _partner(), "/operatorplatform/federation/v1/ctx-1/apiservice/DeviceStatus", {} + ) + + assert "internal connection detail" not in str(error.value) + + +async def test_non_json_partner_response_is_rejected() -> None: + def handler(request: httpx.Request) -> httpx.Response: + return httpx.Response(200, text="not-json") + + async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as http_client: + client = HttpxEwbiClient(http_client, FakeTokenProvider()) + with pytest.raises(PartnerRequestFailed): + await client.post( + _partner(), "/operatorplatform/federation/v1/ctx-1/apiservice/DeviceStatus", {} + ) + + +async def test_http_partner_base_url_is_allowed_only_when_explicitly_enabled() -> None: + def handler(request: httpx.Request) -> httpx.Response: + return httpx.Response(200, json={"ok": True}) + + partner = _partner("http://partner.internal:8082") + async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as http_client: + with pytest.raises(PartnerEndpointConfigurationError): + await HttpxEwbiClient(http_client, FakeTokenProvider()).post(partner, "/x", {}) + + permissive = HttpxEwbiClient(http_client, FakeTokenProvider(), allow_insecure=True) + assert (await permissive.post(partner, "/x", {})).status_code == 200 diff --git a/tests/test_federation_establishment.py b/tests/test_federation_establishment.py new file mode 100644 index 0000000000000000000000000000000000000000..319d3ed8700d8375d08b3ece7b7e435830ada11b --- /dev/null +++ b/tests/test_federation_establishment.py @@ -0,0 +1,185 @@ +from datetime import datetime, timezone +from typing import Any +from uuid import uuid4 + +import pytest + +from federation_manager.application.federation import ( + FederationEstablishmentService, + LocalOperator, +) +from federation_manager.domain.errors import ( + FederationEstablishmentFailed, + PartnerNotActive, + PartnerRequestFailed, + PartnerResponseInvalid, +) +from federation_manager.domain.models import EwbiResponse, FederationContext, PartnerOP +from tests.fakes import FakeEwbiClient, InMemoryFederationContextRepo, InMemoryPartnerRepo + +NOW = datetime(2026, 9, 8, 12, 0, tzinfo=timezone.utc) +CONTEXT_ID = "fed-ctx-partner-a" +LOCAL = LocalOperator( + federation_id="oop-i2cat", + country_code="ES", + mcc="214", + mncs=("07",), + partner_status_link="https://us.example/operatorplatform/federation/v1/partner-status", +) + + +def _partner(status: str = "active") -> PartnerOP: + return PartnerOP( + id=uuid4(), + mcc_mnc="214-07", + oauth2_client_id=f"partner-{uuid4().hex[:6]}", + status=status, + base_url="https://partner.example", + ) + + +def _accepted(**over: Any) -> dict[str, Any]: + body: dict[str, Any] = { + "federationContextId": CONTEXT_ID, + "platformCaps": ["serviceAPIs", "eventMgmt"], + "partnerOPFederationId": "partner-op", + } + body.update(over) + return body + + +def _existing(partner: PartnerOP, status: str = "available") -> FederationContext: + return FederationContext( + id=uuid4(), + partner_op_id=partner.id, + direction="outbound", + federation_context_id="fed-ctx-existing", + status=status, + created_at=NOW, + ) + + +def _service( + partners: list[PartnerOP], + contexts: list[FederationContext] | None = None, + ewbi: FakeEwbiClient | None = None, +) -> tuple[FederationEstablishmentService, FakeEwbiClient, InMemoryFederationContextRepo]: + ewbi = ewbi or FakeEwbiClient(EwbiResponse(200, _accepted())) + repo = InMemoryFederationContextRepo(contexts or []) + service = FederationEstablishmentService( + InMemoryPartnerRepo(partners), repo, ewbi, LOCAL, clock=lambda: NOW + ) + return service, ewbi, repo + + +async def test_creates_federation_and_stores_the_partner_issued_context() -> None: + partner = _partner() + service, ewbi, contexts = _service([partner]) + + context = await service.establish(partner) + + partner_id, path, payload = ewbi.calls[0] + assert partner_id == partner.id + assert path == "/operatorplatform/federation/v1/partner" + assert payload == { + "initialDate": "2026-09-08T12:00:00Z", + "partnerStatusLink": LOCAL.partner_status_link, + "origOPFederationId": "oop-i2cat", + "origOPCountryCode": "ES", + "origOPMobileNetworkCodes": {"mcc": "214", "mncs": ["07"]}, + } + + assert context.federation_context_id == CONTEXT_ID + assert context.direction == "outbound" + assert context.is_active() + assert context.status_callback_url == LOCAL.partner_status_link + assert await contexts.find_active_outbound(partner.id) is context + + +async def test_establishing_twice_reuses_the_existing_context() -> None: + partner = _partner() + existing = _existing(partner) + service, ewbi, _ = _service([partner], [existing]) + + assert await service.establish(partner) is existing + assert ewbi.calls == [] + + +async def test_terminated_context_does_not_block_a_new_federation() -> None: + partner = _partner() + service, ewbi, _ = _service([partner], [_existing(partner, status="terminated")]) + + context = await service.establish(partner) + + assert context.federation_context_id == CONTEXT_ID + assert len(ewbi.calls) == 1 + + +async def test_inactive_partner_is_rejected_before_the_call() -> None: + partner = _partner(status="suspended") + service, ewbi, _ = _service([partner]) + + with pytest.raises(PartnerNotActive): + await service.establish(partner) + + assert ewbi.calls == [] + + +async def test_partner_rejection_is_reported_and_nothing_is_stored() -> None: + partner = _partner() + service, _, contexts = _service( + [partner], ewbi=FakeEwbiClient(EwbiResponse(409, {"cause": "ALREADY_EXISTS"})) + ) + + with pytest.raises(FederationEstablishmentFailed) as error: + await service.establish(partner) + + assert error.value.status_code == 409 + assert await contexts.find_active_outbound(partner.id) is None + + +async def test_response_without_a_context_id_is_rejected() -> None: + partner = _partner() + body = _accepted() + del body["federationContextId"] + service, _, contexts = _service([partner], ewbi=FakeEwbiClient(EwbiResponse(200, body))) + + with pytest.raises(PartnerResponseInvalid): + await service.establish(partner) + + assert await contexts.find_active_outbound(partner.id) is None + + +async def test_bootstrap_federates_only_active_partners_without_a_context() -> None: + federated = _partner() + fresh = _partner() + suspended = _partner(status="suspended") + service, ewbi, _ = _service([federated, fresh, suspended], [_existing(federated)]) + + established = await service.establish_missing() + + assert [c.partner_op_id for c in established] == [fresh.id] + assert [call[0] for call in ewbi.calls] == [fresh.id] + + +async def test_bootstrap_skips_partners_that_fail_and_keeps_going() -> None: + broken = _partner() + healthy = _partner() + + class FailingForOne(FakeEwbiClient): + async def post( + self, partner: PartnerOP, path: str, payload: dict[str, object] + ) -> EwbiResponse: + if partner.id == broken.id: + raise PartnerRequestFailed(partner.id) + return await super().post(partner, path, payload) + + service, _, contexts = _service( + [broken, healthy], ewbi=FailingForOne(EwbiResponse(200, _accepted())) + ) + + established = await service.establish_missing() + + assert [c.partner_op_id for c in established] == [healthy.id] + assert await contexts.find_active_outbound(broken.id) is None + assert await contexts.find_active_outbound(healthy.id) is not None diff --git a/tests/test_federation_health.py b/tests/test_federation_health.py new file mode 100644 index 0000000000000000000000000000000000000000..78df03c6858d83b682456521904da50603c14dae --- /dev/null +++ b/tests/test_federation_health.py @@ -0,0 +1,101 @@ +from collections.abc import AsyncIterator +from contextlib import asynccontextmanager +from datetime import datetime, timezone +from uuid import uuid4 + +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from federation_manager.dependencies import ( + get_federation_context_repo, + get_jwt_validator, + get_partner_repo, +) +from federation_manager.domain.models import FederationContext, PartnerOP, ValidatedClaims +from federation_manager.main import create_app +from tests.fakes import FakeJwtValidator, InMemoryFederationContextRepo, InMemoryPartnerRepo + +CLIENT_ID = "partner-a" +OTHER_CLIENT_ID = "partner-b" +TOKEN = "token-partner-a" +OTHER_TOKEN = "token-partner-b" +STRANGER_TOKEN = "token-partner-nobody" +CONTEXT_ID = "fed-ctx-1" +URL = f"/operatorplatform/federation/v1/{CONTEXT_ID}/health" +STARTED = datetime(2026, 3, 1, 9, 30, tzinfo=timezone.utc) + + +@asynccontextmanager +async def _no_infra(app: FastAPI) -> AsyncIterator[None]: + yield + + +def _bearer(token: str) -> dict[str, str]: + return {"Authorization": f"Bearer {token}"} + + +def _client(partner_status: str = "active", context_status: str = "available") -> TestClient: + partner = PartnerOP( + id=uuid4(), mcc_mnc="214-07", oauth2_client_id=CLIENT_ID, status=partner_status + ) + other = PartnerOP( + id=uuid4(), mcc_mnc="234-15", oauth2_client_id=OTHER_CLIENT_ID, status="active" + ) + context = FederationContext( + id=uuid4(), + partner_op_id=partner.id, + direction="inbound", + federation_context_id=CONTEXT_ID, + status=context_status, + created_at=STARTED, + ) + validator = FakeJwtValidator( + { + TOKEN: ValidatedClaims(client_id=CLIENT_ID, scopes={"fed-mgmt"}), + OTHER_TOKEN: ValidatedClaims(client_id=OTHER_CLIENT_ID, scopes={"fed-mgmt"}), + STRANGER_TOKEN: ValidatedClaims(client_id="partner-nobody", scopes={"fed-mgmt"}), + } + ) + app = create_app(lifespan=_no_infra) + app.dependency_overrides[get_partner_repo] = lambda: InMemoryPartnerRepo([partner, other]) + app.dependency_overrides[get_jwt_validator] = lambda: validator + app.dependency_overrides[get_federation_context_repo] = lambda: InMemoryFederationContextRepo( + [context] + ) + return TestClient(app) + + +def test_reports_health_for_an_established_federation() -> None: + r = _client().get(URL, headers=_bearer(TOKEN)) + + assert r.status_code == 200 + assert r.json() == { + "federationHealthStatus": { + "federationStatus": "AVAILABLE", + "federationStartTime": "2026-03-01T09:30:00Z", + "numOfAcceptedZones": "0", + "numOfActiveAlarms": None, + "numOfApplications": None, + } + } + + +def test_context_status_is_reported_in_gsma_vocabulary() -> None: + r = _client(context_status="locked").get(URL, headers=_bearer(TOKEN)) + + assert r.json()["federationHealthStatus"]["federationStatus"] == "LOCKED" + + +def test_unknown_context_is_404() -> None: + r = _client().get("/operatorplatform/federation/v1/other-ctx/health", headers=_bearer(TOKEN)) + + assert r.status_code == 404 + assert r.json()["type"] == "urn:oop:ewbi:error:federation-context-unknown" + + +def test_another_partners_context_is_not_visible() -> None: + r = _client().get(URL, headers=_bearer(OTHER_TOKEN)) + + assert r.status_code == 404 + assert r.json()["type"] == "urn:oop:ewbi:error:federation-context-unknown" + assert "federationHealthStatus" not in r.text diff --git a/tests/test_install_app.py b/tests/test_install_app.py new file mode 100644 index 0000000000000000000000000000000000000000..8854c740ab81d1e3f93fad202cd72fb859e9b451 --- /dev/null +++ b/tests/test_install_app.py @@ -0,0 +1,227 @@ +from collections.abc import AsyncIterator +from contextlib import asynccontextmanager +from datetime import datetime, timezone +from typing import Any, cast +from uuid import UUID, uuid4 + +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from federation_manager.contracts.srm import SUBJECT_DEPLOY +from federation_manager.dependencies import ( + get_agreement_repo, + get_command_publisher, + get_federation_context_repo, + get_jwt_validator, + get_partner_repo, + get_transaction_repo, +) +from federation_manager.domain.models import ( + Agreement, + AppMapping, + FederationContext, + PartnerOP, + ValidatedClaims, +) +from federation_manager.main import create_app +from tests.fakes import ( + FakeJwtValidator, + InMemoryAgreementRepo, + InMemoryFederationContextRepo, + InMemoryPartnerRepo, + InMemoryTransactionRepo, + RecordingCommandPublisher, +) + +CLIENT_ID = "partner-a" +TOKEN = "token-partner-a" +CONTEXT_ID = "fed-ctx-1" +URL = f"/operatorplatform/federation/v1/{CONTEXT_ID}/application/lcm" +APP_ID = "videoAnalytics" +APP_VERSION = "1.2.0" +FLAVOUR = "small" +ZONE_ID = str(uuid4()) +SPEC_ID = uuid4() +CALLBACK = "https://partner.example/instances/callback" + + +@asynccontextmanager +async def _no_infra(app: FastAPI) -> AsyncIterator[None]: + yield + + +def _bearer(token: str = TOKEN) -> dict[str, str]: + return {"Authorization": f"Bearer {token}"} + + +def _headers(key: str = "idem-1") -> dict[str, str]: + return {**_bearer(), "Idempotency-Key": key} + + +def _agreement(partner: PartnerOP, **over: Any) -> Agreement: + values: dict[str, Any] = { + "id": uuid4(), + "partner_op_id": partner.id, + "permitted_api_types": {"install-app"}, + "permitted_zone_ids": {UUID(ZONE_ID)}, + "app_mappings": ( + AppMapping( + app_id=APP_ID, + app_version=APP_VERSION, + flavour_id=FLAVOUR, + service_specification_id=SPEC_ID, + ), + ), + "api_family_mappings": {}, + "valid_from": datetime(2026, 1, 1, tzinfo=timezone.utc), + "valid_until": None, + "status": "active", + } + values.update(over) + return Agreement(**values) + + +def _client( + agreement_overrides: dict[str, Any] | None = None, + context_status: str = "available", +) -> tuple[TestClient, InMemoryTransactionRepo, RecordingCommandPublisher, PartnerOP]: + partner = PartnerOP(id=uuid4(), mcc_mnc="214-07", oauth2_client_id=CLIENT_ID, status="active") + context = FederationContext( + id=uuid4(), + partner_op_id=partner.id, + direction="inbound", + federation_context_id=CONTEXT_ID, + status=context_status, + created_at=datetime(2026, 3, 1, tzinfo=timezone.utc), + ) + agreement = _agreement(partner, **(agreement_overrides or {})) + transactions = InMemoryTransactionRepo() + publisher = RecordingCommandPublisher() + + app = create_app(lifespan=_no_infra) + app.dependency_overrides[get_partner_repo] = lambda: InMemoryPartnerRepo([partner]) + app.dependency_overrides[get_jwt_validator] = lambda: FakeJwtValidator( + {TOKEN: ValidatedClaims(client_id=CLIENT_ID, scopes={"fed-mgmt"})} + ) + app.dependency_overrides[get_federation_context_repo] = lambda: InMemoryFederationContextRepo( + [context] + ) + app.dependency_overrides[get_agreement_repo] = lambda: InMemoryAgreementRepo([agreement]) + app.dependency_overrides[get_transaction_repo] = lambda: transactions + app.dependency_overrides[get_command_publisher] = lambda: publisher + return TestClient(app), transactions, publisher, partner + + +def _body(**over: Any) -> dict[str, Any]: + body: dict[str, Any] = { + "appId": APP_ID, + "appVersion": APP_VERSION, + "appProviderId": "partnerProvider", + "zoneInfo": {"zoneId": ZONE_ID, "flavourId": FLAVOUR}, + "appInstCallbackLink": CALLBACK, + } + body.update(over) + return body + + +def test_accepts_a_deploy_and_publishes_the_srm_command() -> None: + client, transactions, publisher, partner = _client() + + r = client.post(URL, json=_body(), headers=_headers()) + + assert r.status_code == 202 + body = r.json() + assert body["zoneId"] == ZONE_ID + instance_id = body["appInstIdentifier"] + assert len(instance_id) == 32 + + subject, command = publisher.published[0] + targets = cast(list[dict[str, str]], command["targets"]) + assert subject == SUBJECT_DEPLOY + assert command["source"] == "federation" + assert command["app_provider_id"] == str(partner.id) + assert command["federation_partner_ref"] == "214-07" + assert command["service_specification_id"] == str(SPEC_ID) + assert targets[0]["zone_id"] == ZONE_ID + assert UUID(targets[0]["app_instance_id"]).hex == instance_id + + tx = transactions.single() + assert tx.status == "in_progress" + assert tx.direction == "inbound" + assert tx.api_type == "install-app" + assert tx.idempotency_key == "idem-1" + assert tx.external_resource_id == instance_id + assert tx.callback_url == CALLBACK + assert tx.operation_id == UUID(cast(str, command["operation_id"])) + + +def test_replaying_the_same_idempotency_key_returns_the_same_instance() -> None: + client, transactions, publisher, _ = _client() + + first = client.post(URL, json=_body(), headers=_headers()) + second = client.post(URL, json=_body(), headers=_headers()) + + assert second.status_code == 202 + assert second.json() == first.json() + assert len(publisher.published) == 1 + assert len(transactions.transactions) == 1 + + +def test_reusing_a_key_with_a_different_request_is_409() -> None: + client, _, publisher, _ = _client() + client.post(URL, json=_body(), headers=_headers()) + + r = client.post(URL, json=_body(appVersion="9.9.9"), headers=_headers()) + + assert r.status_code == 409 + assert r.json()["type"] == "urn:oop:ewbi:error:idempotency-key-reused" + assert len(publisher.published) == 1 + + +def test_unmapped_app_version_is_403_and_publishes_nothing() -> None: + client, transactions, publisher, _ = _client() + + r = client.post(URL, json=_body(appVersion="9.9.9"), headers=_headers()) + + assert r.status_code == 403 + assert r.json()["type"] == "urn:oop:ewbi:error:agreement-violation" + assert publisher.published == [] + assert transactions.transactions == {} + + +def test_zone_outside_the_agreement_is_403() -> None: + client, _, publisher, _ = _client(agreement_overrides={"permitted_zone_ids": {uuid4()}}) + + r = client.post(URL, json=_body(), headers=_headers()) + + assert r.status_code == 403 + assert publisher.published == [] + + +def test_unknown_federation_context_is_404() -> None: + client, _, publisher, _ = _client(context_status="terminated") + + r = client.post(URL, json=_body(), headers=_headers()) + + assert r.status_code == 404 + assert publisher.published == [] + + +def test_missing_idempotency_key_is_422() -> None: + client, _, publisher, _ = _client() + + r = client.post(URL, json=_body(), headers=_bearer()) + + assert r.status_code == 422 + assert publisher.published == [] + + +def test_incomplete_body_is_422() -> None: + client, _, publisher, _ = _client() + + for missing in ("appId", "appVersion", "appProviderId", "zoneInfo", "appInstCallbackLink"): + body = _body() + del body[missing] + assert client.post(URL, json=body, headers=_headers()).status_code == 422, missing + + assert publisher.published == [] diff --git a/tests/test_internal_federation.py b/tests/test_internal_federation.py new file mode 100644 index 0000000000000000000000000000000000000000..934f4f6b11225c408bcedd034db3273ab9450405 --- /dev/null +++ b/tests/test_internal_federation.py @@ -0,0 +1,299 @@ +from collections.abc import AsyncIterator +from contextlib import asynccontextmanager +from datetime import datetime, timezone +from typing import Any, get_args +from uuid import uuid4 + +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from federation_manager.api.internal.federation import ApiType +from federation_manager.dependencies import ( + get_agreement_repo, + get_ewbi_client, + get_federation_context_repo, + get_partner_repo, + get_routing_rule_repo, + get_transaction_repo, +) +from federation_manager.domain.errors import PartnerRequestFailed, PartnerTokenRequestFailed +from federation_manager.domain.ewbi import SERVICE_API_NAMES +from federation_manager.domain.models import ( + Agreement, + EwbiResponse, + FederationContext, + PartnerOP, + RoutingRule, +) +from federation_manager.domain.routing import IP_CIDR, MSISDN_PREFIX +from federation_manager.main import create_app +from tests.fakes import ( + FakeEwbiClient, + InMemoryAgreementRepo, + InMemoryFederationContextRepo, + InMemoryPartnerRepo, + InMemoryRoutingRuleRepo, + InMemoryTransactionRepo, +) + +URL = "/internal/federation/outbound" +SECRET_REF = "/run/secrets/partners/partner-a" +BASE_URL = "https://partner-a.internal.example" +CONTEXT_ID = "fed-ctx-1" +LOCATION_CONTENT = {"lastLocationTime": "2026-09-08T12:00:00Z"} + + +@asynccontextmanager +async def _no_infra(app: FastAPI) -> AsyncIterator[None]: + yield + + +def _partner(status: str = "active") -> PartnerOP: + return PartnerOP( + id=uuid4(), + mcc_mnc="214-07", + oauth2_client_id="partner-a", + status=status, + our_client_id="our-fm", + our_client_secret_ref=SECRET_REF, + token_endpoint=f"{BASE_URL}/oauth2/token", + base_url=BASE_URL, + ) + + +def _agreement(partner: PartnerOP, api_types: set[str] | None = None) -> Agreement: + return Agreement( + id=uuid4(), + partner_op_id=partner.id, + permitted_api_types=api_types or {"device-location-retrieve"}, + permitted_zone_ids=set(), + app_mappings=(), + api_family_mappings={}, + valid_from=datetime(2026, 1, 1, tzinfo=timezone.utc), + valid_until=None, + status="active", + ) + + +def _context(partner: PartnerOP) -> FederationContext: + return FederationContext( + id=uuid4(), + partner_op_id=partner.id, + direction="outbound", + federation_context_id=CONTEXT_ID, + status="available", + created_at=datetime(2026, 3, 1, tzinfo=timezone.utc), + ) + + +def _rules(partner: PartnerOP) -> list[RoutingRule]: + return [ + RoutingRule( + id=uuid4(), + partner_op_id=partner.id, + identifier_type=MSISDN_PREFIX, + value_range="+34", + ), + RoutingRule( + id=uuid4(), + partner_op_id=partner.id, + identifier_type=IP_CIDR, + value_range="203.0.113.0/24", + ), + ] + + +def _ok_response() -> EwbiResponse: + return EwbiResponse( + 200, + { + "customerID": str(uuid4()), + "txnIdentifier": "oeg-transaction-123", + "apiResponse": {"mediaType": "application/json", "responseContent": LOCATION_CONTENT}, + }, + location="https://partner.example/session/1", + ) + + +def _client( + partner: PartnerOP | None = None, + agreement: Agreement | None = None, + rules: list[RoutingRule] | None = None, + ewbi: FakeEwbiClient | None = None, + with_context: bool = True, +) -> tuple[TestClient, InMemoryTransactionRepo, FakeEwbiClient]: + partner = partner or _partner() + agreement = agreement or _agreement(partner) + rules = _rules(partner) if rules is None else rules + ewbi = ewbi or FakeEwbiClient(_ok_response()) + transactions = InMemoryTransactionRepo() + contexts = [_context(partner)] if with_context else [] + + app = create_app(lifespan=_no_infra) + app.dependency_overrides[get_partner_repo] = lambda: InMemoryPartnerRepo([partner]) + app.dependency_overrides[get_agreement_repo] = lambda: InMemoryAgreementRepo([agreement]) + app.dependency_overrides[get_federation_context_repo] = lambda: InMemoryFederationContextRepo( + contexts + ) + app.dependency_overrides[get_routing_rule_repo] = lambda: InMemoryRoutingRuleRepo(rules) + app.dependency_overrides[get_transaction_repo] = lambda: transactions + app.dependency_overrides[get_ewbi_client] = lambda: ewbi + return TestClient(app), transactions, ewbi + + +def _body(**over: object) -> dict[str, Any]: + body: dict[str, Any] = { + "api_type": "device-location-retrieve", + "identifier_type": "msisdn", + "identifier_value": "+34612345678", + "correlation_id": str(uuid4()), + "customer_id": str(uuid4()), + "customer_info": "ACME Corp", + "txn_identifier": "oeg-transaction-123", + "service_api_body": { + "mediaType": "application/json", + "APIContent": {"device": {"phoneNumber": "+34612345678"}}, + }, + } + body.update(over) + return body + + +def test_forwards_and_unwraps_the_partner_response() -> None: + partner = _partner() + client, transactions, ewbi = _client(partner) + + r = client.post(URL, json=_body()) + + assert r.status_code == 200 + assert r.json() == { + "partner_op_id": str(partner.id), + "status_code": 200, + "body": LOCATION_CONTENT, + "location": "https://partner.example/session/1", + } + assert ewbi.calls[0][1] == ( + f"/operatorplatform/federation/v1/{CONTEXT_ID}/apiservice/DeviceLocation" + ) + assert transactions.single().status == "completed" + + +def test_ip_identifier_routes_by_cidr() -> None: + partner = _partner() + client, _, ewbi = _client(partner) + + r = client.post(URL, json=_body(identifier_type="ip", identifier_value="203.0.113.9")) + + assert r.status_code == 200 + assert r.json()["partner_op_id"] == str(partner.id) + assert len(ewbi.calls) == 1 + + +def test_partner_error_status_is_passed_through_inside_200() -> None: + problem = {"title": "Forbidden", "cause": "AGREEMENT_VIOLATION"} + client, transactions, _ = _client(ewbi=FakeEwbiClient(EwbiResponse(403, problem))) + + r = client.post(URL, json=_body()) + + assert r.status_code == 200 + assert r.json()["status_code"] == 403 + assert r.json()["body"] == problem + assert transactions.single().status == "failed" + + +def test_no_route_is_404_problem() -> None: + client, transactions, _ = _client(rules=[]) + + r = client.post(URL, json=_body()) + + assert r.status_code == 404 + assert r.headers["content-type"] == "application/problem+json" + body = r.json() + assert body["type"] == "urn:oop:ewbi:error:no-route" + assert body["status"] == 404 + assert body["instance"] == URL + assert transactions.transactions == {} + + +def test_missing_federation_context_is_409() -> None: + client, _, ewbi = _client(with_context=False) + + r = client.post(URL, json=_body()) + + assert r.status_code == 409 + assert r.json()["type"] == "urn:oop:ewbi:error:federation-not-established" + assert ewbi.calls == [] + + +def test_expired_agreement_is_403_expired() -> None: + partner = _partner() + agreement = _agreement(partner) + agreement.valid_until = datetime(2026, 1, 2, tzinfo=timezone.utc) + client, _, _ = _client(partner, agreement) + + r = client.post(URL, json=_body()) + + assert r.status_code == 403 + assert r.json()["type"] == "urn:oop:ewbi:error:agreement-expired" + + +def test_unreachable_partner_is_502() -> None: + partner = _partner() + for error in (PartnerRequestFailed(partner.id), PartnerTokenRequestFailed(partner.id)): + client, transactions, _ = _client(partner, ewbi=FakeEwbiClient(error=error)) + + r = client.post(URL, json=_body()) + + assert r.status_code == 502 + assert r.json()["type"] == "urn:oop:ewbi:error:partner-unreachable" + assert transactions.single().status == "failed" + + +def test_malformed_partner_response_is_502() -> None: + incomplete = {"customerID": str(uuid4()), "txnIdentifier": "oeg-transaction-123"} + client, transactions, _ = _client(ewbi=FakeEwbiClient(EwbiResponse(200, incomplete))) + + r = client.post(URL, json=_body()) + + assert r.status_code == 502 + assert r.json()["type"] == "urn:oop:ewbi:error:partner-response-invalid" + assert transactions.single().status == "failed" + + +def test_error_responses_leak_no_partner_configuration() -> None: + partner = _partner() + client, _, _ = _client(partner, ewbi=FakeEwbiClient(error=PartnerRequestFailed(partner.id))) + + r = client.post(URL, json=_body()) + + assert BASE_URL not in r.text + assert SECRET_REF not in r.text + assert str(partner.id) not in r.text + assert CONTEXT_ID not in r.text + + +def test_malformed_requests_are_422() -> None: + client, _, ewbi = _client() + + bad_bodies = [ + _body(identifier_value="34612345678"), + _body(identifier_type="ip", identifier_value="203.0.113.999"), + _body(api_type="edge-cloud-deploy"), + _body(identifier_type="imsi"), + _body(correlation_id="not-a-uuid"), + _body(customer_id="not-a-uuid"), + _body(customer_info=""), + _body(txn_identifier=""), + _body(service_api_body={"mediaType": "application/xml", "APIContent": {}}), + _body(service_api_body={"mediaType": "application/json"}), + _body(extra="field"), + ] + for bad in bad_bodies: + r = client.post(URL, json=bad) + assert r.status_code == 422, bad + + assert ewbi.calls == [] + + +def test_api_type_literal_matches_domain_service_apis() -> None: + assert set(get_args(ApiType)) == set(SERVICE_API_NAMES) diff --git a/tests/test_operation_completed.py b/tests/test_operation_completed.py new file mode 100644 index 0000000000000000000000000000000000000000..08f643203256b81679f48305ee38423282393c8f --- /dev/null +++ b/tests/test_operation_completed.py @@ -0,0 +1,247 @@ +from datetime import datetime, timezone +from typing import Any, cast +from uuid import uuid4 + +import pytest +from pydantic import ValidationError + +from federation_manager.application.events import OperationCompletedConsumer +from federation_manager.domain.models import FederationTransaction, PartnerOP +from tests.fakes import InMemoryPartnerRepo, InMemoryTransactionRepo, RecordingCallbackClient + +NOW = datetime(2026, 9, 8, 12, 0, tzinfo=timezone.utc) +COMPLETED_AT = datetime(2026, 9, 8, 12, 5, tzinfo=timezone.utc) + + +PARTNER = PartnerOP(id=uuid4(), mcc_mnc="214-07", oauth2_client_id="partner-a", status="active") +CALLBACK = "https://partner.example/instances/callback" + + +def _consumer( + transactions: InMemoryTransactionRepo, callbacks: RecordingCallbackClient | None = None +) -> OperationCompletedConsumer: + return OperationCompletedConsumer( + transactions, InMemoryPartnerRepo([PARTNER]), callbacks or RecordingCallbackClient() + ) + + +def _transaction(operation_id: Any, callback_url: str | None = None) -> FederationTransaction: + return FederationTransaction( + id=uuid4(), + partner_op_id=PARTNER.id, + direction="inbound", + api_type="install-app", + status="in_progress", + started_at=NOW, + operation_id=operation_id, + callback_url=callback_url, + external_resource_id=uuid4().hex, + request_summary={}, + ) + + +def _event(operation_id: Any, **over: Any) -> dict[str, Any]: + event: dict[str, Any] = { + "schema_version": "1.0", + "operation_id": str(operation_id), + "status": "completed", + "service_order_id": str(uuid4()), + "instances": [ + { + "service_instance_id": str(uuid4()), + "zone_id": str(uuid4()), + "status": "completed", + } + ], + "correlation_id": str(uuid4()), + "completed_at": COMPLETED_AT.isoformat(), + } + event.update(over) + return event + + +async def test_completion_finalises_the_matching_transaction() -> None: + operation_id = uuid4() + transactions = InMemoryTransactionRepo() + await transactions.add(_transaction(operation_id)) + event = _event(operation_id) + + await _consumer(transactions).handle(event) + + tx = transactions.single() + assert tx.status == "completed" + assert tx.completed_at == COMPLETED_AT + assert tx.response_summary is not None + instances = tx.response_summary["instances"] + assert instances == [ + { + "service_instance_id": event["instances"][0]["service_instance_id"], + "zone_id": event["instances"][0]["zone_id"], + "status": "completed", + } + ] + + +async def test_partial_and_failed_outcomes_are_recorded() -> None: + for status in ("partially_completed", "failed"): + operation_id = uuid4() + transactions = InMemoryTransactionRepo() + await transactions.add(_transaction(operation_id)) + + await _consumer(transactions).handle(_event(operation_id, status=status)) + + assert transactions.single().status == status + + +async def test_failure_detail_is_kept_for_dispute_resolution() -> None: + operation_id = uuid4() + transactions = InMemoryTransactionRepo() + await transactions.add(_transaction(operation_id)) + problem = {"title": "Deploy service failed.", "status": 500} + + await _consumer(transactions).handle( + _event(operation_id, status="failed", instances=[], error=problem) + ) + + tx = transactions.single() + assert tx.status == "failed" + assert tx.error_detail == problem + + +async def test_events_for_other_components_are_ignored() -> None: + transactions = InMemoryTransactionRepo() + await transactions.add(_transaction(uuid4())) + + await _consumer(transactions).handle(_event(uuid4())) + + assert transactions.single().status == "in_progress" + + +async def test_a_malformed_event_is_rejected_so_it_is_redelivered() -> None: + transactions = InMemoryTransactionRepo() + + with pytest.raises(ValidationError): + await _consumer(transactions).handle({"schema_version": "1.0"}) + + +CONTEXT_ID = "fed-ctx-1" +APP_ID = "videoAnalytics" +ZONE_ID = str(uuid4()) + + +def _lcm_transaction(operation_id: Any, instance_id: str) -> FederationTransaction: + transaction = _transaction(operation_id, callback_url=CALLBACK) + transaction.external_resource_id = instance_id + transaction.request_summary = { + "federation_context_id": CONTEXT_ID, + "app_id": APP_ID, + "zone_id": ZONE_ID, + } + return transaction + + +async def test_a_successful_deploy_tells_the_partner_the_instance_is_ready() -> None: + operation_id, instance_id = uuid4(), uuid4() + transactions = InMemoryTransactionRepo() + await transactions.add(_lcm_transaction(operation_id, instance_id.hex)) + callbacks = RecordingCallbackClient() + event = _event( + operation_id, + instances=[ + { + "service_instance_id": str(instance_id), + "zone_id": ZONE_ID, + "status": "completed", + } + ], + ) + + await _consumer(transactions, callbacks).handle(event) + + partner_id, url, payload = callbacks.delivered[0] + assert partner_id == PARTNER.id + assert url == CALLBACK + assert payload == { + "federationContextId": CONTEXT_ID, + "appId": APP_ID, + "appInstanceId": instance_id.hex, + "zoneId": ZONE_ID, + "appInstanceInfo": {"appInstanceState": "READY"}, + } + assert transactions.single().callback_status == "delivered" + assert transactions.single().callback_attempts == 1 + + +async def test_a_failed_deploy_reports_failed_with_the_reason() -> None: + operation_id = uuid4() + transactions = InMemoryTransactionRepo() + await transactions.add(_lcm_transaction(operation_id, uuid4().hex)) + callbacks = RecordingCallbackClient() + + await _consumer(transactions, callbacks).handle( + _event( + operation_id, + status="failed", + instances=[], + error={"title": "Deploy service failed before start.", "status": 400}, + ) + ) + + _, _, payload = callbacks.delivered[0] + info = payload["appInstanceInfo"] + assert info == { + "appInstanceState": "FAILED", + "message": "Deploy service failed before start.", + } + assert payload["zoneId"] == ZONE_ID + + +async def test_each_instance_gets_its_own_callback() -> None: + operation_id = uuid4() + first, second = uuid4(), uuid4() + transactions = InMemoryTransactionRepo() + await transactions.add(_lcm_transaction(operation_id, first.hex)) + callbacks = RecordingCallbackClient() + + await _consumer(transactions, callbacks).handle( + _event( + operation_id, + status="partially_completed", + instances=[ + {"service_instance_id": str(first), "zone_id": ZONE_ID, "status": "completed"}, + {"service_instance_id": str(second), "zone_id": ZONE_ID, "status": "failed"}, + ], + ) + ) + + states = [ + cast(dict[str, str], payload["appInstanceInfo"])["appInstanceState"] + for _, _, payload in callbacks.delivered + ] + assert states == ["READY", "FAILED"] + + +async def test_a_rejected_callback_is_recorded_as_failed() -> None: + operation_id = uuid4() + transactions = InMemoryTransactionRepo() + await transactions.add(_lcm_transaction(operation_id, uuid4().hex)) + callbacks = RecordingCallbackClient(succeeds=False) + + await _consumer(transactions, callbacks).handle(_event(operation_id)) + + tx = transactions.single() + assert tx.status == "completed" + assert tx.callback_status == "failed" + assert tx.callback_attempts == 1 + + +async def test_no_callback_url_means_no_delivery() -> None: + operation_id = uuid4() + transactions = InMemoryTransactionRepo() + await transactions.add(_transaction(operation_id)) + callbacks = RecordingCallbackClient() + + await _consumer(transactions, callbacks).handle(_event(operation_id)) + + assert callbacks.delivered == [] + assert transactions.single().callback_status is None diff --git a/tests/test_outbound_service.py b/tests/test_outbound_service.py new file mode 100644 index 0000000000000000000000000000000000000000..882b07db6247552e3c2258102b7ae9ebc90a8bcc --- /dev/null +++ b/tests/test_outbound_service.py @@ -0,0 +1,386 @@ +import json +from datetime import datetime, timezone +from typing import Any +from uuid import UUID, uuid4 + +import pytest + +from federation_manager.application.authorization import AgreementChecker +from federation_manager.application.outbound import OutboundFederationService, OutboundRequest +from federation_manager.domain.errors import ( + AgreementExpired, + AgreementViolation, + FederationContextMissing, + NoRouteMatched, + PartnerEndpointConfigurationError, + PartnerNotActive, + PartnerRequestFailed, + PartnerResponseInvalid, + UnsupportedApiType, +) +from federation_manager.domain.models import ( + Agreement, + EwbiResponse, + FederationContext, + PartnerOP, + RoutingRule, +) +from federation_manager.domain.routing import MSISDN_PREFIX, RoutingResolver +from tests.fakes import ( + FakeEwbiClient, + InMemoryAgreementRepo, + InMemoryFederationContextRepo, + InMemoryPartnerRepo, + InMemoryRoutingRuleRepo, + InMemoryTransactionRepo, +) + +MSISDN = "+34612345678" +API_TYPE = "device-location-retrieve" +CONTEXT_ID = "fed-ctx-telefonica" +TXN = "oeg-transaction-123" +NOW = datetime(2026, 9, 8, 12, 0, tzinfo=timezone.utc) +LOCATION_CONTENT = {"lastLocationTime": "2026-09-08T12:00:00Z"} + + +def _partner(status: str = "active") -> PartnerOP: + return PartnerOP( + id=uuid4(), + mcc_mnc="214-07", + oauth2_client_id="partner-a", + status=status, + base_url="https://partner.example", + ) + + +def _agreement(partner: PartnerOP, **over: object) -> Agreement: + base: dict[str, object] = { + "id": uuid4(), + "partner_op_id": partner.id, + "permitted_api_types": {API_TYPE, "device-status-retrieve"}, + "permitted_zone_ids": set(), + "app_mappings": (), + "api_family_mappings": {}, + "valid_from": datetime(2026, 1, 1, tzinfo=timezone.utc), + "valid_until": None, + "status": "active", + } + base.update(over) + return Agreement(**base) # type: ignore[arg-type] + + +def _context(partner: PartnerOP, status: str = "available") -> FederationContext: + return FederationContext( + id=uuid4(), + partner_op_id=partner.id, + direction="outbound", + federation_context_id=CONTEXT_ID, + status=status, + created_at=NOW, + ) + + +def _rule(partner: PartnerOP) -> RoutingRule: + return RoutingRule( + id=uuid4(), + partner_op_id=partner.id, + identifier_type=MSISDN_PREFIX, + value_range="+34", + ) + + +def _service_api_response(**over: Any) -> dict[str, Any]: + body: dict[str, Any] = { + "customerID": str(uuid4()), + "txnIdentifier": TXN, + "apiResponse": {"mediaType": "application/json", "responseContent": LOCATION_CONTENT}, + } + body.update(over) + return body + + +def _request(api_type: str = API_TYPE, correlation_id: UUID | None = None) -> OutboundRequest: + return OutboundRequest( + api_type=api_type, + identifier_type="msisdn", + identifier_value=MSISDN, + correlation_id=correlation_id or uuid4(), + customer_id=uuid4(), + customer_info="ACME Corp", + txn_identifier=TXN, + api_content={"device": {"phoneNumber": MSISDN}}, + ) + + +class Harness: + def __init__( + self, + partner: PartnerOP, + agreement: Agreement | None, + context: FederationContext | None, + rules: list[RoutingRule], + ewbi: FakeEwbiClient, + ) -> None: + self.ewbi = ewbi + self.transactions = InMemoryTransactionRepo() + self.service = OutboundFederationService( + RoutingResolver(InMemoryRoutingRuleRepo(rules)), + InMemoryPartnerRepo([partner]), + InMemoryFederationContextRepo([context] if context else []), + AgreementChecker( + InMemoryAgreementRepo([agreement] if agreement else []), clock=lambda: NOW + ), + self.transactions, + ewbi, + clock=lambda: NOW, + ) + + +def _harness( + partner: PartnerOP | None = None, + agreement: Agreement | None = None, + context: FederationContext | None = None, + rules: list[RoutingRule] | None = None, + ewbi: FakeEwbiClient | None = None, + with_agreement: bool = True, + with_context: bool = True, +) -> Harness: + partner = partner or _partner() + if agreement is None and with_agreement: + agreement = _agreement(partner) + if context is None and with_context: + context = _context(partner) + rules = [_rule(partner)] if rules is None else rules + ewbi = ewbi or FakeEwbiClient(EwbiResponse(200, _service_api_response())) + return Harness(partner, agreement, context, rules, ewbi) + + +async def test_forwards_an_opg04_apiforwarding_wrapper_to_the_partner_context() -> None: + partner = _partner() + agreement = _agreement(partner) + context = _context(partner) + ewbi = FakeEwbiClient( + EwbiResponse(200, _service_api_response(), location="https://partner.example/session/1") + ) + h = _harness(partner, agreement, context, ewbi=ewbi) + oeg_correlation = uuid4() + request = _request(correlation_id=oeg_correlation) + + result = await h.service.forward(request) + + partner_id, path, payload = ewbi.calls[0] + assert partner_id == partner.id + assert path == f"/operatorplatform/federation/v1/{CONTEXT_ID}/apiservice/DeviceLocation" + assert payload == { + "apiServiceId": "DeviceLocation", + "customerID": str(request.customer_id), + "customerInfo": "ACME Corp", + "txnIdentifier": TXN, + "ServiceAPIBody": { + "mediaType": "application/json", + "APIContent": {"device": {"phoneNumber": MSISDN}}, + }, + } + assert str(oeg_correlation) not in json.dumps(payload) + + assert result.partner_op_id == partner.id + assert result.status_code == 200 + assert result.body == LOCATION_CONTENT + assert result.location == "https://partner.example/session/1" + + +async def test_records_a_completed_transaction_against_context_and_agreement() -> None: + partner = _partner() + agreement = _agreement(partner) + context = _context(partner) + h = _harness(partner, agreement, context) + oeg_correlation = uuid4() + + await h.service.forward(_request(correlation_id=oeg_correlation)) + + tx = h.transactions.single() + assert tx.direction == "outbound" + assert tx.status == "completed" + assert tx.partner_op_id == partner.id + assert tx.agreement_id == agreement.id + assert tx.federation_context_row_id == context.id + assert tx.correlation_id == oeg_correlation + assert tx.external_txn_id == TXN + assert tx.api_type == API_TYPE + assert tx.started_at == NOW + assert tx.completed_at == NOW + assert tx.response_summary == {"status_code": 200, "session": False} + assert tx.error_detail is None + assert tx.request_summary["route_match"] == "+34" + assert tx.request_summary["service_api"] == "DeviceLocation" + assert MSISDN not in str(tx.request_summary) + + +async def test_session_response_without_api_response_is_accepted() -> None: + body = _service_api_response( + targetUserContext={ + "connectID": "conn-1", + "expiryDuration": {"numHours": 1, "numMins": 0, "numSecs": 0}, + } + ) + del body["apiResponse"] + h = _harness(ewbi=FakeEwbiClient(EwbiResponse(200, body))) + + result = await h.service.forward(_request()) + + assert result.body is None + assert h.transactions.single().response_summary == {"status_code": 200, "session": True} + + +async def test_event_notification_dest_is_forwarded_only_when_given() -> None: + h = _harness() + request = _request() + + await h.service.forward(request) + assert "eventNotificationDest" not in h.ewbi.calls[0][2] + + with_dest = OutboundRequest( + api_type=request.api_type, + identifier_type=request.identifier_type, + identifier_value=request.identifier_value, + correlation_id=request.correlation_id, + customer_id=request.customer_id, + customer_info=request.customer_info, + txn_identifier=request.txn_identifier, + api_content=request.api_content, + event_notification_dest="https://us.example/events", + ) + await h.service.forward(with_dest) + assert h.ewbi.calls[1][2]["eventNotificationDest"] == "https://us.example/events" + + +async def test_no_matching_route_is_rejected_before_any_side_effect() -> None: + h = _harness(rules=[]) + + with pytest.raises(NoRouteMatched): + await h.service.forward(_request()) + + assert h.transactions.transactions == {} + assert h.ewbi.calls == [] + + +async def test_rule_pointing_at_unknown_partner_is_no_route() -> None: + partner = _partner() + dangling = RoutingRule( + id=uuid4(), partner_op_id=uuid4(), identifier_type=MSISDN_PREFIX, value_range="+34" + ) + h = _harness(partner, rules=[dangling]) + + with pytest.raises(NoRouteMatched): + await h.service.forward(_request()) + + +async def test_suspended_partner_is_rejected() -> None: + partner = _partner(status="suspended") + h = _harness(partner, _agreement(partner), _context(partner)) + + with pytest.raises(PartnerNotActive): + await h.service.forward(_request()) + + assert h.transactions.transactions == {} + assert h.ewbi.calls == [] + + +async def test_missing_or_expired_agreement_is_rejected() -> None: + partner = _partner() + expired = _agreement(partner, valid_until=datetime(2026, 6, 1, tzinfo=timezone.utc)) + + with pytest.raises(AgreementExpired): + await _harness(partner, with_agreement=False).service.forward(_request()) + with pytest.raises(AgreementExpired): + await _harness(partner, expired).service.forward(_request()) + + +async def test_api_type_outside_agreement_is_rejected() -> None: + partner = _partner() + h = _harness(partner, _agreement(partner, permitted_api_types={"device-status-retrieve"})) + + with pytest.raises(AgreementViolation): + await h.service.forward(_request()) + + assert h.transactions.transactions == {} + assert h.ewbi.calls == [] + + +async def test_missing_federation_context_is_rejected_before_the_partner_call() -> None: + h = _harness(with_context=False) + + with pytest.raises(FederationContextMissing): + await h.service.forward(_request()) + + assert h.transactions.transactions == {} + assert h.ewbi.calls == [] + + +async def test_terminated_context_is_not_used() -> None: + partner = _partner() + h = _harness(partner, context=_context(partner, status="terminated")) + + with pytest.raises(FederationContextMissing): + await h.service.forward(_request()) + + +async def test_unsupported_api_type_fails_before_any_io() -> None: + h = _harness() + + with pytest.raises(UnsupportedApiType): + await h.service.forward(_request(api_type="edge-cloud-deploy")) + + assert h.transactions.transactions == {} + + +async def test_partner_transport_failure_marks_transaction_failed_and_propagates() -> None: + partner = _partner() + ewbi = FakeEwbiClient(error=PartnerRequestFailed(partner.id)) + h = _harness(partner, ewbi=ewbi) + + with pytest.raises(PartnerRequestFailed): + await h.service.forward(_request()) + + tx = h.transactions.single() + assert tx.status == "failed" + assert tx.completed_at == NOW + assert tx.error_detail == {"type": "urn:oop:ewbi:error:partner-unreachable"} + assert tx.response_summary is None + + +async def test_partner_misconfiguration_marks_transaction_failed_and_propagates() -> None: + partner = _partner() + ewbi = FakeEwbiClient(error=PartnerEndpointConfigurationError(partner.id)) + h = _harness(partner, ewbi=ewbi) + + with pytest.raises(PartnerEndpointConfigurationError): + await h.service.forward(_request()) + + assert h.transactions.single().error_detail == {"type": "urn:oop:ewbi:error:internal-error"} + + +async def test_malformed_partner_response_is_rejected() -> None: + incomplete = {"customerID": str(uuid4()), "txnIdentifier": TXN} + h = _harness(ewbi=FakeEwbiClient(EwbiResponse(200, incomplete))) + + with pytest.raises(PartnerResponseInvalid): + await h.service.forward(_request()) + + tx = h.transactions.single() + assert tx.status == "failed" + assert tx.error_detail == {"type": "urn:oop:ewbi:error:partner-response-invalid"} + + +async def test_partner_problem_response_is_returned_and_audited_as_failed() -> None: + problem = {"title": "Agreement violation", "cause": "AGREEMENT_VIOLATION"} + h = _harness(ewbi=FakeEwbiClient(EwbiResponse(403, problem))) + + result = await h.service.forward(_request()) + + assert result.status_code == 403 + assert result.body == problem + tx = h.transactions.single() + assert tx.status == "failed" + assert tx.response_summary == {"status_code": 403} + assert tx.error_detail == {"status_code": 403, "problem": problem} diff --git a/tests/test_ports.py b/tests/test_ports.py new file mode 100644 index 0000000000000000000000000000000000000000..a609adf661d60c83f1513da470f8b04fb03131c5 --- /dev/null +++ b/tests/test_ports.py @@ -0,0 +1,45 @@ +from datetime import datetime, timezone +from uuid import UUID, uuid4 + +from federation_manager.domain.models import FederationContext, PartnerOP +from federation_manager.domain.ports import FederationContextRepositoryPort, PartnerRepositoryPort +from tests.fakes import InMemoryFederationContextRepo, InMemoryPartnerRepo + + +async def test_fake_satisfies_port_and_finds_partner() -> None: + partner = PartnerOP(id=uuid4(), mcc_mnc="214-07", oauth2_client_id="partner-a", status="active") + repo: PartnerRepositoryPort = InMemoryPartnerRepo([partner]) + + assert await repo.find_by_oauth2_client_id("partner-a") is partner + assert await repo.find_by_oauth2_client_id("unknown") is None + + +def _context( + partner: UUID, direction: str, context_id: str, status: str = "available" +) -> FederationContext: + return FederationContext( + id=uuid4(), + partner_op_id=partner, + direction=direction, + federation_context_id=context_id, + status=status, + created_at=datetime(2026, 3, 1, tzinfo=timezone.utc), + ) + + +async def test_fake_context_repo_resolves_outbound_and_inbound_contexts() -> None: + partner, other = uuid4(), uuid4() + terminated = _context(partner, "outbound", "ctx-old", status="terminated") + outbound = _context(partner, "outbound", "ctx-out") + inbound = _context(partner, "inbound", "ctx-in") + other_inbound = _context(other, "inbound", "ctx-in") + repo: FederationContextRepositoryPort = InMemoryFederationContextRepo( + [terminated, outbound, inbound, other_inbound] + ) + + assert await repo.find_active_outbound(partner) is outbound + assert await repo.find_active_outbound(other) is None + assert await repo.find_inbound(partner, "ctx-in") is inbound + assert await repo.find_inbound(other, "ctx-in") is other_inbound + assert await repo.find_inbound(partner, "ctx-out") is None + assert not terminated.is_active() diff --git a/tests/test_routing.py b/tests/test_routing.py new file mode 100644 index 0000000000000000000000000000000000000000..2599056bd357e17dbb1f76b3884019809abc78c3 --- /dev/null +++ b/tests/test_routing.py @@ -0,0 +1,149 @@ +from uuid import UUID, uuid4 + +from federation_manager.domain.ewbi import SERVICE_API_NAMES, api_forwarding_path +from federation_manager.domain.models import RoutingRule +from federation_manager.domain.routing import IP_CIDR, MSISDN_PREFIX, RoutingResolver +from tests.fakes import InMemoryRoutingRuleRepo + + +def _rule( + partner: UUID, + value_range: str, + identifier_type: str = MSISDN_PREFIX, + priority: int = 100, + is_active: bool = True, +) -> RoutingRule: + return RoutingRule( + id=uuid4(), + partner_op_id=partner, + identifier_type=identifier_type, + value_range=value_range, + priority=priority, + is_active=is_active, + ) + + +def _resolver(*rules: RoutingRule) -> RoutingResolver: + return RoutingResolver(InMemoryRoutingRuleRepo(list(rules))) + + +async def test_longest_msisdn_prefix_wins() -> None: + spain, sub_range = uuid4(), uuid4() + resolver = _resolver(_rule(spain, "+34"), _rule(sub_range, "+3461")) + + result = await resolver.resolve_partner("msisdn", "+34612345678") + + assert result is not None + assert result.partner_op_id == sub_range + assert result.match == "+3461" + + +async def test_exact_prefix_match_and_no_match() -> None: + spain = uuid4() + resolver = _resolver(_rule(spain, "+34")) + + assert (await resolver.resolve_partner("msisdn", "+34")) is not None + assert await resolver.resolve_partner("msisdn", "+33612345678") is None + assert await resolver.resolve_partner("msisdn", "34612345678") is None + + +async def test_priority_breaks_ties_between_equally_specific_rules() -> None: + preferred, fallback = uuid4(), uuid4() + resolver = _resolver( + _rule(fallback, "+34", priority=200), + _rule(preferred, "+34", priority=10), + ) + + result = await resolver.resolve_partner("msisdn", "+34612345678") + + assert result is not None + assert result.partner_op_id == preferred + + +async def test_more_specific_rule_beats_higher_priority_broad_rule() -> None: + broad, specific = uuid4(), uuid4() + resolver = _resolver( + _rule(broad, "+34", priority=1), + _rule(specific, "+346", priority=100), + ) + + result = await resolver.resolve_partner("msisdn", "+34612345678") + + assert result is not None + assert result.partner_op_id == specific + + +async def test_inactive_rules_are_ignored() -> None: + resolver = _resolver(_rule(uuid4(), "+34", is_active=False)) + + assert await resolver.resolve_partner("msisdn", "+34612345678") is None + + +async def test_unknown_identifier_type_resolves_nothing() -> None: + resolver = _resolver(_rule(uuid4(), "+34")) + + assert await resolver.resolve_partner("imsi", "214070000000000") is None + + +async def test_msisdn_rules_do_not_match_ip_lookups() -> None: + resolver = _resolver(_rule(uuid4(), "+34")) + + assert await resolver.resolve_partner("ip", "203.0.113.7") is None + + +async def test_most_specific_cidr_wins() -> None: + wide, narrow = uuid4(), uuid4() + resolver = _resolver( + _rule(wide, "203.0.113.0/24", IP_CIDR), + _rule(narrow, "203.0.113.0/28", IP_CIDR), + ) + + result = await resolver.resolve_partner("ip", "203.0.113.7") + + assert result is not None + assert result.partner_op_id == narrow + assert result.match == "203.0.113.0/28" + + outside_narrow = await resolver.resolve_partner("ip", "203.0.113.200") + assert outside_narrow is not None + assert outside_narrow.partner_op_id == wide + + +async def test_ipv6_cidr_matches_and_version_mismatch_is_ignored() -> None: + v6_partner = uuid4() + resolver = _resolver( + _rule(uuid4(), "203.0.113.0/24", IP_CIDR), + _rule(v6_partner, "2001:db8::/32", IP_CIDR), + ) + + result = await resolver.resolve_partner("ip", "2001:db8::1") + + assert result is not None + assert result.partner_op_id == v6_partner + assert await resolver.resolve_partner("ip", "2001:db9::1") is None + + +async def test_malformed_rule_or_identifier_does_not_break_routing() -> None: + good = uuid4() + resolver = _resolver( + _rule(uuid4(), "not-a-cidr", IP_CIDR), + _rule(good, "203.0.113.0/24", IP_CIDR), + ) + + result = await resolver.resolve_partner("ip", "203.0.113.7") + + assert result is not None + assert result.partner_op_id == good + assert await resolver.resolve_partner("ip", "not-an-ip") is None + + +def test_api_forwarding_path_is_canonical_and_context_scoped() -> None: + for api_type, service_api in SERVICE_API_NAMES.items(): + path = api_forwarding_path("fed-ctx-1", service_api) + assert path == f"/operatorplatform/federation/v1/fed-ctx-1/apiservice/{service_api}", ( + api_type + ) + + +def test_api_forwarding_path_encodes_the_partner_supplied_context_id() -> None: + assert api_forwarding_path("a/b", "DeviceLocation").endswith("/a%2Fb/apiservice/DeviceLocation")