From 5412bd3a8846a7a52305b7fb73271c41d76ac003 Mon Sep 17 00:00:00 2001 From: direito <rdireito@av.it.pt> Date: Tue, 17 Dec 2024 16:49:26 +0000 Subject: [PATCH] Fixed CRs + API Changes --- .../cr-template-pre-provision.yaml | 2 +- .../OSLArtifacts/cr-template-supervision.yaml | 2 +- QoDProvisioning/Makefile | 60 + .../QoDProvisioningAPI/API/Dockerfile | 31 + .../QoDProvisioningAPI/API/Makefile | 37 + .../API/docker-compose.yaml | 21 + .../QoDProvisioningAPI/API/openapi.yaml | 2051 +++++++++++++++++ .../QoDProvisioningAPI/API/requirements.txt | 39 + .../QoDProvisioningAPI/API/src/__init__.py | 0 .../API/src/aux/constants.py | 12 + .../QoDProvisioningAPI/API/src/aux/mappers.py | 73 + .../src/aux/service_event_manager/__init__.py | 0 .../camara_results_processor.py | 81 + .../service_event_manager.py | 155 ++ .../QoDProvisioningAPI/API/src/config.py | 67 + .../API/src/database/__init__.py | 0 .../API/src/database/base_models.py | 59 + .../API/src/database/crud.py | 454 ++++ .../QoDProvisioningAPI/API/src/database/db.py | 53 + .../QoDProvisioningAPI/API/src/main.py | 117 + .../API/src/routers/__init__.py | 0 .../QoDProvisioningAPI/API/src/routers/osl.py | 69 + .../src/routers/qod_provisioning_router.py | 364 +++ .../API/src/schemas/__init__.py | 0 .../src/schemas/access_token_credential.py | 112 + .../API/src/schemas/base_provisioning_info.py | 110 + .../API/src/schemas/cloud_event.py | 138 ++ .../API/src/schemas/create_provisioning.py | 117 + .../API/src/schemas/device.py | 121 + .../API/src/schemas/device_ipv4_addr.py | 95 + .../API/src/schemas/error_info.py | 94 + .../API/src/schemas/event_status_changed.py | 132 ++ .../event_status_changed_all_of_data.py | 96 + .../API/src/schemas/extra_models.py | 8 + .../API/src/schemas/plain_credential.py | 102 + .../API/src/schemas/provisioning_info.py | 128 + .../src/schemas/refresh_token_credential.py | 116 + .../retrieve_provisioning_by_device.py | 94 + .../API/src/schemas/sink_credential.py | 114 + .../API/src/schemas/status.py | 46 + .../API/src/schemas/status_changed.py | 45 + .../API/src/schemas/status_info.py | 45 + ...oD-Provisioning-API-CFS-Specification.json | 173 ++ .../OSLArtifacts/cr-template.yaml | 12 + .../QoDProvisioningAPI/Operator/Dockerfile | 11 + .../Operator/chart/.helmignore | 23 + .../Operator/chart/Chart.yaml | 21 + .../Operator/chart/templates/_helpers.tpl | 62 + .../Operator/chart/templates/crd.yaml | 82 + .../Operator/chart/templates/deployment.yaml | 43 + .../chart/templates/operator-role.yaml | 68 + .../Operator/chart/values.yaml | 16 + .../Operator/src/__init__.py | 9 + .../Operator/src/camaraaas_cr_handler.py | 284 +++ .../QoDProvisioningAPI/Operator/src/config.py | 58 + .../Operator/src/k8s_operator.py | 79 + .../Operator/src/requirements.txt | 75 + .../QoDProvisioningAPI/Operator/test-cr.yaml | 12 + 58 files changed, 6486 insertions(+), 2 deletions(-) create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/Dockerfile create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/Makefile create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/docker-compose.yaml create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/openapi.yaml create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/requirements.txt create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/__init__.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/aux/constants.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/aux/mappers.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/aux/service_event_manager/__init__.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/aux/service_event_manager/camara_results_processor.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/aux/service_event_manager/service_event_manager.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/config.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/database/__init__.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/database/base_models.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/database/crud.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/database/db.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/main.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/routers/__init__.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/routers/osl.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/routers/qod_provisioning_router.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/schemas/__init__.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/schemas/access_token_credential.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/schemas/base_provisioning_info.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/schemas/cloud_event.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/schemas/create_provisioning.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/schemas/device.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/schemas/device_ipv4_addr.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/schemas/error_info.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/schemas/event_status_changed.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/schemas/event_status_changed_all_of_data.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/schemas/extra_models.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/schemas/plain_credential.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/schemas/provisioning_info.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/schemas/refresh_token_credential.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/schemas/retrieve_provisioning_by_device.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/schemas/sink_credential.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/schemas/status.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/schemas/status_changed.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/API/src/schemas/status_info.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/OSLArtifacts/CAMARAaaS-QoD-Provisioning-API-CFS-Specification.json create mode 100644 QoDProvisioning/QoDProvisioningAPI/OSLArtifacts/cr-template.yaml create mode 100644 QoDProvisioning/QoDProvisioningAPI/Operator/Dockerfile create mode 100644 QoDProvisioning/QoDProvisioningAPI/Operator/chart/.helmignore create mode 100644 QoDProvisioning/QoDProvisioningAPI/Operator/chart/Chart.yaml create mode 100644 QoDProvisioning/QoDProvisioningAPI/Operator/chart/templates/_helpers.tpl create mode 100644 QoDProvisioning/QoDProvisioningAPI/Operator/chart/templates/crd.yaml create mode 100644 QoDProvisioning/QoDProvisioningAPI/Operator/chart/templates/deployment.yaml create mode 100644 QoDProvisioning/QoDProvisioningAPI/Operator/chart/templates/operator-role.yaml create mode 100644 QoDProvisioning/QoDProvisioningAPI/Operator/chart/values.yaml create mode 100644 QoDProvisioning/QoDProvisioningAPI/Operator/src/__init__.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/Operator/src/camaraaas_cr_handler.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/Operator/src/config.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/Operator/src/k8s_operator.py create mode 100644 QoDProvisioning/QoDProvisioningAPI/Operator/src/requirements.txt create mode 100644 QoDProvisioning/QoDProvisioningAPI/Operator/test-cr.yaml diff --git a/QoDProvisioning/DummyOperatorService/OSLArtifacts/cr-template-pre-provision.yaml b/QoDProvisioning/DummyOperatorService/OSLArtifacts/cr-template-pre-provision.yaml index 03fc05b..9a9169f 100644 --- a/QoDProvisioning/DummyOperatorService/OSLArtifacts/cr-template-pre-provision.yaml +++ b/QoDProvisioning/DummyOperatorService/OSLArtifacts/cr-template-pre-provision.yaml @@ -1,7 +1,7 @@ apiVersion: org.etsi.osl/v1 kind: DummyOperatorService metadata: - name: _to_be_replaced_by_osl + name: _to_be_replaced_by_osl_ namespace: default spec: status: "%s" \ No newline at end of file diff --git a/QoDProvisioning/DummyOperatorService/OSLArtifacts/cr-template-supervision.yaml b/QoDProvisioning/DummyOperatorService/OSLArtifacts/cr-template-supervision.yaml index 90b3c46..fc5cd6c 100644 --- a/QoDProvisioning/DummyOperatorService/OSLArtifacts/cr-template-supervision.yaml +++ b/QoDProvisioning/DummyOperatorService/OSLArtifacts/cr-template-supervision.yaml @@ -1,7 +1,7 @@ apiVersion: org.etsi.osl/v1 kind: DummyOperatorService metadata: - name: _to_be_replaced_by_osl + name: _to_be_replaced_by_osl_ namespace: default spec: qodProv: diff --git a/QoDProvisioning/Makefile b/QoDProvisioning/Makefile index 0a1a348..b109f48 100644 --- a/QoDProvisioning/Makefile +++ b/QoDProvisioning/Makefile @@ -1,4 +1,22 @@ +# VARIABLES + +# API Docker Image +API_DOCKER_IMAGE_LOCAL_NAME = osl-camaraaas-qod-provisioning-api +API_DOCKER_IMAGE_LOCAL_TAG = latest +API_DOCKER_IMAGE_NAME_ON_REPOSITORY = osl-camaraaas-qod-provisioning-api +API_DOCKER_IMAGE_TAG_ON_REPOSITORY = latest + +# Operator Docker Image +OPERATOR_DOCKER_IMAGE_LOCAL_NAME = osl-camaraaas-qod-provisioning-api-op +OPERATOR_DOCKER_IMAGE_LOCAL_TAG = latest +OPERATOR_DOCKER_IMAGE_NAME_ON_REPOSITORY = osl-camaraaas-qod-provisioning-api-op +OPERATOR_DOCKER_IMAGE_TAG_ON_REPOSITORY = latest + +# VARIABLES TO UPDATE +REPOSITORY_HOST = atnog-harbor.av.it.pt/camaraaas +OPERATOR_NAMESPACE = osl-camara-controllers + # Dummy Operator Service create-dummy-operator-crd: kubectl apply -f ./DummyOperatorService/crd.yaml @@ -16,5 +34,47 @@ describe-dummy-operator-cr: kubectl describe dos example-dummy-operator-service || true +# CAMARAaaS QoD Provisioning API +build-api-docker-image: + docker build -t $(API_DOCKER_IMAGE_LOCAL_NAME):$(API_DOCKER_IMAGE_LOCAL_TAG) ./QoDProvisioningAPI/API + +tag-api-docker-image: + docker tag $(API_DOCKER_IMAGE_LOCAL_NAME):$(API_DOCKER_IMAGE_LOCAL_TAG) $(REPOSITORY_HOST)/$(API_DOCKER_IMAGE_NAME_ON_REPOSITORY):$(API_DOCKER_IMAGE_TAG_ON_REPOSITORY) + +push-api-docker-image: + docker push $(REPOSITORY_HOST)/$(API_DOCKER_IMAGE_NAME_ON_REPOSITORY):$(API_DOCKER_IMAGE_TAG_ON_REPOSITORY) + +api-docker-image: build-api-docker-image tag-api-docker-image push-api-docker-image + + +# CAMARAaaS QoD Provisioning API Operator +build-operator-docker-image: + docker build -t $(OPERATOR_DOCKER_IMAGE_LOCAL_NAME):$(OPERATOR_DOCKER_IMAGE_LOCAL_TAG) ./QoDProvisioningAPI/Operator + +tag-operator-docker-image: + docker tag $(OPERATOR_DOCKER_IMAGE_LOCAL_NAME):$(OPERATOR_DOCKER_IMAGE_LOCAL_TAG) $(REPOSITORY_HOST)/$(OPERATOR_DOCKER_IMAGE_NAME_ON_REPOSITORY):$(OPERATOR_DOCKER_IMAGE_TAG_ON_REPOSITORY) + +push-operator-docker-image: + docker push $(REPOSITORY_HOST)/$(OPERATOR_DOCKER_IMAGE_NAME_ON_REPOSITORY):$(OPERATOR_DOCKER_IMAGE_TAG_ON_REPOSITORY) + +operator-docker-image: build-operator-docker-image tag-operator-docker-image push-operator-docker-image + + +install-operator: + helm install camaraaas-qod-prov-operator ./QoDProvisioningAPI/Operator/chart --set operator.image="$(REPOSITORY_HOST)/$(OPERATOR_DOCKER_IMAGE_NAME_ON_REPOSITORY):$(OPERATOR_DOCKER_IMAGE_TAG_ON_REPOSITORY)" --set camaraQoDAPI.image="$(REPOSITORY_HOST)/$(API_DOCKER_IMAGE_NAME_ON_REPOSITORY):$(API_DOCKER_IMAGE_TAG_ON_REPOSITORY)" --namespace $(OPERATOR_NAMESPACE) --create-namespace + +get-operator-logs: + kubectl logs -f $$(kubectl get pods -n $(OPERATOR_NAMESPACE) -l app=camaraaas-qod-provisioning-api-op -o jsonpath="{.items[0].metadata.name}") -n $(OPERATOR_NAMESPACE) + +uninstall-operator: + helm uninstall camaraaas-qod-prov-operator -n $(OPERATOR_NAMESPACE) + + +create-operator-test-cr: + kubectl apply -f ./QoDProvisioningAPI/Operator/test-cr.yaml +describe-operator-test-cr: + kubectl describe camaraaas-qod-provisioning-apis.org.etsi.osl test-qod-provisioning +delete-operator-test-cr: + kubectl delete -f ./QoDProvisioningAPI/Operator/test-cr.yaml diff --git a/QoDProvisioning/QoDProvisioningAPI/API/Dockerfile b/QoDProvisioning/QoDProvisioningAPI/API/Dockerfile new file mode 100644 index 0000000..86d4a8c --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/Dockerfile @@ -0,0 +1,31 @@ +# Use an official Python runtime as a parent image +FROM python:3.9-slim + +# Set the working directory to /app +WORKDIR /app + +# Install system dependencies and SQLite CLI +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + sqlite3 \ + gcc \ + libsqlite3-dev && \ + rm -rf /var/lib/apt/lists/* + +# Copy the requirements file into the container +COPY requirements.txt . + +# Install Python dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Copy the application code into the container +COPY src/ /app/ + +# Create the data directory for the SQLite database +RUN mkdir -p /data + +# Set environment variables +ENV SQLITE_DB_PATH=/data/sqlite.db + +# Set the command to run the application +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/QoDProvisioning/QoDProvisioningAPI/API/Makefile b/QoDProvisioning/QoDProvisioningAPI/API/Makefile new file mode 100644 index 0000000..e95c627 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/Makefile @@ -0,0 +1,37 @@ + +# Variables +DOCKER_REPOSITORY_IMAGE_NAME = camaraaas-qod-provisioning-api +DOCKER_REPOSITORY_TAG = latest +REPOSITORY_HOST = atnog-harbor.av.it.pt/camaraaas + +SERVICE_UUID=6b6b2f37-b232-4a6a-b9bf-55f96dbdb773 + +# API +init: + python3 -m venv venv && . venv/bin/activate && pip install -r requirements.txt && deactivate + +run: + . venv/bin/activate && SQLITE_DB_PATH=~/sqlite.db BROKER_ADDRESS=10.255.28.137 BROKER_PORT=61613 BROKER_USERNAME=artemis BROKER_PASSWORD=artemis SERVICE_UUID=$(SERVICE_UUID) PYTHONPATH=src uvicorn src.main:app --host 0.0.0.0 --port 8000 --reload --log-level info && deactivate + +delete-db: + rm -f ~/sqlite.db + + +# DOCKER +docker-build: + docker build -t $(DOCKER_REPOSITORY_IMAGE_NAME):$(DOCKER_REPOSITORY_TAG) . + +docker-tag: + docker tag $(DOCKER_REPOSITORY_IMAGE_NAME):$(DOCKER_REPOSITORY_TAG) $(REPOSITORY_HOST)/$(DOCKER_REPOSITORY_IMAGE_NAME):$(DOCKER_REPOSITORY_TAG) + +docker-push: + docker push $(REPOSITORY_HOST)/$(DOCKER_REPOSITORY_IMAGE_NAME):$(DOCKER_REPOSITORY_TAG) + +docker-clean: + docker image prune -f + +docker-remove: + docker rmi $(LOCAL_IMAGE_NAME):$(LOCAL_TAG) + +docker: docker-build docker-tag docker-push + diff --git a/QoDProvisioning/QoDProvisioningAPI/API/docker-compose.yaml b/QoDProvisioning/QoDProvisioningAPI/API/docker-compose.yaml new file mode 100644 index 0000000..3361aa1 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/docker-compose.yaml @@ -0,0 +1,21 @@ +version: '3.6' +services: + service: + build: + context: . + ports: + - "8000:8000" + command: uvicorn main:app --host 0.0.0.0 --port 8000 --log-level info + environment: + - SQLITE_DB_PATH=/data/sqlite.db + - BROKER_ADDRESS=10.255.28.137 + - BROKER_PORT=61613 + - BROKER_USERNAME=artemis + - BROKER_PASSWORD=artemis + - SERVICE_UUID=0726c0c2-9fc8-4593-b2e0-8740764ae365 + volumes: + - sqlite_data:/data + +volumes: + sqlite_data: + driver: local diff --git a/QoDProvisioning/QoDProvisioningAPI/API/openapi.yaml b/QoDProvisioning/QoDProvisioningAPI/API/openapi.yaml new file mode 100644 index 0000000..69156ef --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/openapi.yaml @@ -0,0 +1,2051 @@ +openapi: 3.0.3 +info: + description: | + The Quality-On-Demand (QoD) Provisioning API offers a programmable interface for developers to request the assignment of a certain QoS Profile to a certain device, indefinitely. + + This API sets up the configuration in the network so the requested QoS profile is applied to an specified device, at any time while the provisioning is available. The device traffic will be treated with a certain QoS profile by the network whenever the device is connected to the network, until the provisioning is deleted. + + + # Relevant terms and definitions + + * **QoS profiles and QoS profile labels**: + Latency, throughput or priority requirements of the application mapped to relevant QoS profile values. The set of QoS Profiles that a network operator is offering may be retrieved via the `qos-profiles` API (cf. https://github.com/camaraproject/QualityOnDemand/) or will be agreed during the onboarding with the API service provider. + + * **Identifier for the device**: + At least one identifier for the device (user equipment) out of four options: IPv4 address, IPv6 address, Phone number, or Network Access Identifier assigned by the network operator for the device, at the request time. After the provisioning request is accepted, the device may get different IP addresses, but the provisioning will still apply to the device that was identified during the request process. Note: Network Access Identifier is defined for future use and will not be supported with v0.1 of the API. + + * **Notification URL and token**: + Developers may provide a callback URL (`sink`) on which notifications about all status change events (eg. provisioning termination) can be received from the service provider. This is an optional parameter. The notification will be sent as a CloudEvent compliant message. If `sink` is included, it is RECOMMENDED for the client to provide as well the `sinkCredential` property to protect the notification endpoint. In the current version,`sinkCredential.credentialType` MUST be set to `ACCESSTOKEN` if provided. + + # Resources and Operations overview + The API defines four operations: + + - An operation to setup a new QoD provisioning for a given device. + - An operation to get the information about a specific QoD provisioning, identified by its `provisioningId`. + - An operation to get the QoD provisioning for a given device. + - An operation to terminate a QoD provisioning, identified by its `provisioningId`. + + # Authorization and Authentication + + [Camara Security and Interoperability Profile](https://github.com/camaraproject/IdentityAndConsentManagement/blob/main/documentation/CAMARA-Security-Interoperability.md) provides details on how a client requests an access token. + + Which specific authorization flows are to be used will be determined during onboarding process, happening between the API Client and the Telco Operator exposing the API, taking into account the declared purpose for accessing the API, while also being subject to the prevailing legal framework dictated by local legislation. + + It is important to remark that in cases where personal user data is processed by the API, and users can exercise their rights through mechanisms such as opt-in and/or opt-out, the use of 3-legged access tokens becomes mandatory. This measure ensures that the API remains in strict compliance with user privacy preferences and regulatory obligations, upholding the principles of transparency and user-centric data control. + + # Identifying a device from the access token + + This specification defines the `device` object field as optional in API requests, specifically in cases where the API is accessed using a 3-legged access token, and the device can be uniquely identified by the token. This approach simplifies API usage for API consumers by relying on the device information associated with the access token used to invoke the API. + + ## Handling of device information: + + ### Optional device object for 3-legged tokens: + + - When using a 3-legged access token, the device associated with the access token must be considered as the device for the API request. This means that the device object is not required in the request, and if included it must identify the same device, therefore **it is recommended NOT to include it in these scenarios** to simplify the API usage and avoid additional validations. + + ### Validation mechanism: + + - The server will extract the device identification from the access token, if available. + - If the API request additionally includes a `device` object when using a 3-legged access token, the API will validate that the device identifier provided matches the one associated with the access token. + - If there is a mismatch, the API will respond with a 403 - INVALID_TOKEN_CONTEXT error, indicating that the device information in the request does not match the token. + + ### Error handling for unidentifiable devices: + + - If the `device` object is not included in the request and the device information cannot be derived from the 3-legged access token, the server will return a 422 `UNIDENTIFIABLE_DEVICE` error. + + ### Restrictions for tokens without an associated authenticated identifier: + + - For scenarios which do not have a single device identifier associated to the token during the authentication flow, e.g. 2-legged access tokens, the `device` object MUST be provided in the API request. This ensures that the device identification is explicit and valid for each API call made with these tokens. + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + title: QoD Provisioning API + version: 0.1.0 + x-camara-commonalities: 0.4.0 +externalDocs: + description: Project documentation at CAMARA + url: https://github.com/camaraproject/QualityOnDemand +servers: +- url: "{apiRoot}/qod-provisioning/v0.1" + variables: + apiRoot: + default: http://localhost:9091 + description: "API root, defined by the service provider, e.g. `api.example.com`\ + \ or `api.example.com/somepath`" +tags: +- description: Manage the permanent provisioning of QoD + name: QoD Provisioning +paths: + /device-qos: + post: + callbacks: + notifications: + '{$request.body#/sink}': + post: + description: | + Important: this endpoint is to be implemented by the API consumer. + The QoD server will call this endpoint whenever any QoD provisioning change related event occurs. + Currently only `PROVISIONING_STATUS_CHANGED` event is defined. + operationId: postProvisioningNotification + parameters: + - description: Correlation id for the different services + explode: false + in: header + name: x-correlator + required: false + schema: + type: string + style: simple + requestBody: + content: + application/cloudevents+json: + examples: + PROVISIONING_STATUS_CHANGED_EXAMPLE: + $ref: '#/components/examples/PROVISIONING_STATUS_CHANGED_EXAMPLE' + schema: + $ref: '#/components/schemas/CloudEvent' + required: true + responses: + "204": + description: Successful notification + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "400": + content: + application/json: + examples: + GENERIC_400_INVALID_ARGUMENT: + description: Invalid Argument. Generic Syntax Exception + value: + status: 400 + code: INVALID_ARGUMENT + message: "Client specified an invalid argument, request\ + \ body or query param." + GENERIC_400_OUT_OF_RANGE: + description: Out of Range. Specific Syntax Exception used + when a given field has a pre-defined range or a invalid + filter criteria combination is requested + value: + status: 400 + code: OUT_OF_RANGE + message: Client specified an invalid range. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Bad Request + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "401": + content: + application/json: + examples: + GENERIC_401_UNAUTHENTICATED: + description: Request cannot be authenticated + value: + status: 401 + code: UNAUTHENTICATED + message: "Request not authenticated due to missing, invalid,\ + \ or expired credentials." + GENERIC_401_AUTHENTICATION_REQUIRED: + description: "New authentication is needed, authentication\ + \ is no longer valid" + value: + status: 401 + code: AUTHENTICATION_REQUIRED + message: New authentication is required. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Unauthorized + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "403": + content: + application/json: + examples: + GENERIC_403_PERMISSION_DENIED: + description: Permission denied. OAuth2 token access does + not have the required scope or when the user fails operational + security + value: + status: 403 + code: PERMISSION_DENIED + message: Client does not have sufficient permissions to + perform this action. + GENERIC_403_INVALID_TOKEN_CONTEXT: + description: Reflect some inconsistency between information + in some field of the API and the related OAuth2 Token + value: + status: 403 + code: INVALID_TOKEN_CONTEXT + message: "{{field}} is not consistent with access token." + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Forbidden + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "410": + content: + application/json: + examples: + GENERIC_410_GONE: + description: Use in notifications flow to allow API Consumer + to indicate that its callback is no longer available + value: + status: 410 + code: GONE + message: Access to the target resource is no longer available. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Gone + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "429": + content: + application/json: + examples: + GENERIC_429_QUOTA_EXCEEDED: + description: Request is rejected due to exceeding a business + quota limit + value: + status: 429 + code: QUOTA_EXCEEDED + message: Either out of resource quota or reaching rate + limiting. + GENERIC_429_TOO_MANY_REQUESTS: + description: API Server request limit is overpassed + value: + status: 429 + code: TOO_MANY_REQUESTS + message: Either out of resource quota or reaching rate + limiting. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Too Many Requests + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "500": + content: + application/json: + examples: + GENERIC_500_INTERNAL: + description: Problem in Server side. Regular Server Exception + value: + status: 500 + code: INTERNAL + message: Unknown server error. Typically a server bug. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Internal server error + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "503": + content: + application/json: + examples: + GENERIC_503_UNAVAILABLE: + description: Service is not available. Temporary situation + usually related to maintenance process in the server side + value: + status: 503 + code: UNAVAILABLE + message: Service Unavailable. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Service unavailable + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + security: + - {} + - notificationsBearerAuth: [] + summary: Provisioning notifications callback + x-callback-request: true + description: | + Triggers a new provisioning in the operator to assign certain QoS Profile to certain device. + + - If the provisioning is completed synchronously, the response will be 201 with `status` = `AVAILABLE`. + - If the provisioning request is accepted but not yet completed, the response will be 201 with `status` = `REQUESTED`. + - If the operator determines synchronously that the provisioning request cannot be fulfilled, the response will be 201 with `status` = `UNAVAILABLE`. + + - If the request includes the `sink` and `sinkCredential` properties, the client will receive a `status-changed` event with the outcome of the process. The event will be sent also for synchronous operations. + + **NOTES:** + - When the provisioning status becomes `UNAVAILABLE`, the QoD provisioning resource is not immediately released, but will get deleted automatically, at earliest 360 seconds after. + + This behavior allows clients which are not receiving notification events but are polling, to get the provisioning status information. Before a client can attempt to create a new QoD provisioning for the same device, they must release the provisioning resources with an explicit `delete` operation if not yet automatically deleted. + - The access token may be either 2-legged or 3-legged. + - If a 3-legged access token which is associated with a device is used, it is recommended NOT to include the `device` parameter in the request (see "Handling of device information" within the API description for details). + - If a 2-legged access token is used, the device parameter must be provided and identify a device. + operationId: createProvisioning + parameters: + - description: Correlation id for the different services + explode: false + in: header + name: x-correlator + required: false + schema: + type: string + style: simple + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CreateProvisioning' + description: Parameters to create a new provisioning + required: true + responses: + "201": + content: + application/json: + schema: + $ref: '#/components/schemas/ProvisioningInfo' + description: Provisioning created + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "400": + content: + application/json: + examples: + GENERIC_400_INVALID_ARGUMENT: + description: Invalid Argument. Generic Syntax Exception + value: + status: 400 + code: INVALID_ARGUMENT + message: "Client specified an invalid argument, request body or\ + \ query param." + GENERIC_400_OUT_OF_RANGE: + description: Out of Range. Specific Syntax Exception used when a + given field has a pre-defined range or a invalid filter criteria + combination is requested + value: + status: 400 + code: OUT_OF_RANGE + message: Client specified an invalid range. + GENERIC_400_INVALID_CREDENTIAL: + value: + status: 400 + code: INVALID_CREDENTIAL + message: Only Access token is supported + GENERIC_400_INVALID_TOKEN: + value: + status: 400 + code: INVALID_TOKEN + message: Only bearer token is supported + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Bad Request with additional errors for implicit notifications + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "401": + content: + application/json: + examples: + GENERIC_401_UNAUTHENTICATED: + description: Request cannot be authenticated + value: + status: 401 + code: UNAUTHENTICATED + message: "Request not authenticated due to missing, invalid, or\ + \ expired credentials." + GENERIC_401_AUTHENTICATION_REQUIRED: + description: "New authentication is needed, authentication is no\ + \ longer valid" + value: + status: 401 + code: AUTHENTICATION_REQUIRED + message: New authentication is required. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Unauthorized + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "403": + content: + application/json: + examples: + GENERIC_403_PERMISSION_DENIED: + description: Permission denied. OAuth2 token access does not have + the required scope or when the user fails operational security + value: + status: 403 + code: PERMISSION_DENIED + message: Client does not have sufficient permissions to perform + this action. + GENERIC_403_INVALID_TOKEN_CONTEXT: + description: Reflect some inconsistency between information in some + field of the API and the related OAuth2 Token + value: + status: 403 + code: INVALID_TOKEN_CONTEXT + message: "{{field}} is not consistent with access token." + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Forbidden + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "404": + content: + application/json: + examples: + GENERIC_404_NOT_FOUND: + description: Resource is not found + value: + status: 404 + code: NOT_FOUND + message: The specified resource is not found. + GENERIC_404_DEVICE_NOT_FOUND: + description: Device identifier not found + value: + status: 404 + code: DEVICE_NOT_FOUND + message: Device identifier not found. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Not found + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "409": + content: + application/json: + examples: + PROVISIONING_409_CONFLICT: + description: The requested provisioning conflicts with an existing + one + value: + status: 409 + code: CONFLICT + message: There is another existing provisioning for the same device + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Provisioning conflict + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "422": + content: + application/json: + examples: + GENERIC_422_UNPROCESSABLE_ENTITY: + description: The request was well-formed but was unable to be processed + due to semantic errors or not applicable values. This is the generic + error code for 422 responses. + value: + status: 422 + code: UNPROCESSABLE_ENTITY + message: "Value not acceptable: ..." + GENERIC_422_DEVICE_IDENTIFIERS_MISMATCH: + description: Inconsistency between device identifiers not pointing + to the same device + value: + status: 422 + code: DEVICE_IDENTIFIERS_MISMATCH + message: Provided device identifiers are not consistent. + GENERIC_422_DEVICE_NOT_APPLICABLE: + description: Service is not available for the provided device + value: + status: 422 + code: DEVICE_NOT_APPLICABLE + message: The service is not available for the provided device. + GENERIC_422_UNSUPPORTED_DEVICE_IDENTIFIERS: + description: Message may list the supported device identifiers + value: + status: 422 + code: UNSUPPORTED_DEVICE_IDENTIFIERS + message: "Supported device supported are: ..." + GENERIC_422_UNIDENTIFIABLE_DEVICE: + description: Service is not available for the provided device + value: + status: 422 + code: UNIDENTIFIABLE_DEVICE + message: The device cannot be identified. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Unprocessable entity + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "429": + content: + application/json: + examples: + GENERIC_429_QUOTA_EXCEEDED: + description: Request is rejected due to exceeding a business quota + limit + value: + status: 429 + code: QUOTA_EXCEEDED + message: Either out of resource quota or reaching rate limiting. + GENERIC_429_TOO_MANY_REQUESTS: + description: API Server request limit is overpassed + value: + status: 429 + code: TOO_MANY_REQUESTS + message: Either out of resource quota or reaching rate limiting. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Too Many Requests + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "500": + content: + application/json: + examples: + GENERIC_500_INTERNAL: + description: Problem in Server side. Regular Server Exception + value: + status: 500 + code: INTERNAL + message: Unknown server error. Typically a server bug. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Internal server error + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "503": + content: + application/json: + examples: + GENERIC_503_UNAVAILABLE: + description: Service is not available. Temporary situation usually + related to maintenance process in the server side + value: + status: 503 + code: UNAVAILABLE + message: Service Unavailable. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Service unavailable + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + security: + - openId: + - qod-provisioning:device-qos:create + summary: Sets a new provisioning of QoS for a device + tags: + - QoD Provisioning + /device-qos/{provisioningId}: + delete: + description: | + Release resources related to QoS provisioning. + + If the notification callback is provided and the provisioning status was `AVAILABLE`, when the deletion is completed, the client will receive in addition to the response a `PROVISIONING_STATUS_CHANGED` event with + - `status` as `UNAVAILABLE` and + - `statusInfo` as `DELETE_REQUESTED` + There will be no notification event if the `status` was already `UNAVAILABLE`. + + **NOTES:** + - The access token may be either 2-legged or 3-legged. + - If a 3-legged access token is used, the end user (and device) associated with the QoD provisioning must also be associated with the access token. + - The QoD provisioning must have been created by the same API client given in the access token. + operationId: deleteProvisioning + parameters: + - description: Provisioning ID that was obtained from the createProvision operation + explode: false + in: path + name: provisioningId + required: true + schema: + $ref: '#/components/schemas/ProvisioningId' + style: simple + - description: Correlation id for the different services + explode: false + in: header + name: x-correlator + required: false + schema: + type: string + style: simple + responses: + "204": + description: Provisioning deleted + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "202": + content: + application/json: + schema: + $ref: '#/components/schemas/ProvisioningInfo' + description: Deletion request accepted to be processed. It applies for an + async deletion process. `status` in the response will be `AVAILABLE` with + `statusInfo` set to `DELETE_REQUESTED`. + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "400": + content: + application/json: + examples: + GENERIC_400_INVALID_ARGUMENT: + description: Invalid Argument. Generic Syntax Exception + value: + status: 400 + code: INVALID_ARGUMENT + message: "Client specified an invalid argument, request body or\ + \ query param." + GENERIC_400_OUT_OF_RANGE: + description: Out of Range. Specific Syntax Exception used when a + given field has a pre-defined range or a invalid filter criteria + combination is requested + value: + status: 400 + code: OUT_OF_RANGE + message: Client specified an invalid range. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Bad Request + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "401": + content: + application/json: + examples: + GENERIC_401_UNAUTHENTICATED: + description: Request cannot be authenticated + value: + status: 401 + code: UNAUTHENTICATED + message: "Request not authenticated due to missing, invalid, or\ + \ expired credentials." + GENERIC_401_AUTHENTICATION_REQUIRED: + description: "New authentication is needed, authentication is no\ + \ longer valid" + value: + status: 401 + code: AUTHENTICATION_REQUIRED + message: New authentication is required. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Unauthorized + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "403": + content: + application/json: + examples: + GENERIC_403_PERMISSION_DENIED: + description: Permission denied. OAuth2 token access does not have + the required scope or when the user fails operational security + value: + status: 403 + code: PERMISSION_DENIED + message: Client does not have sufficient permissions to perform + this action. + GENERIC_403_INVALID_TOKEN_CONTEXT: + description: Reflect some inconsistency between information in some + field of the API and the related OAuth2 Token + value: + status: 403 + code: INVALID_TOKEN_CONTEXT + message: "{{field}} is not consistent with access token." + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Forbidden + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "404": + content: + application/json: + examples: + GENERIC_404_NOT_FOUND: + description: Resource is not found + value: + status: 404 + code: NOT_FOUND + message: The specified resource is not found. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Not found + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "429": + content: + application/json: + examples: + GENERIC_429_QUOTA_EXCEEDED: + description: Request is rejected due to exceeding a business quota + limit + value: + status: 429 + code: QUOTA_EXCEEDED + message: Either out of resource quota or reaching rate limiting. + GENERIC_429_TOO_MANY_REQUESTS: + description: API Server request limit is overpassed + value: + status: 429 + code: TOO_MANY_REQUESTS + message: Either out of resource quota or reaching rate limiting. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Too Many Requests + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "500": + content: + application/json: + examples: + GENERIC_500_INTERNAL: + description: Problem in Server side. Regular Server Exception + value: + status: 500 + code: INTERNAL + message: Unknown server error. Typically a server bug. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Internal server error + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "503": + content: + application/json: + examples: + GENERIC_503_UNAVAILABLE: + description: Service is not available. Temporary situation usually + related to maintenance process in the server side + value: + status: 503 + code: UNAVAILABLE + message: Service Unavailable. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Service unavailable + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + security: + - openId: + - qod-provisioning:device-qos:delete + summary: Deletes a QoD provisioning + tags: + - QoD Provisioning + get: + description: | + Querying for QoD provisioning resource information details + + **NOTES:** + - The access token may be either 2-legged or 3-legged. + - If a 3-legged access token is used, the end user (and device) associated with the QoD provisioning must also be associated with the access token. + - The QoD provisioning must have been created by the same API client given in the access token. + operationId: getProvisioningById + parameters: + - description: Provisioning ID that was obtained from the createProvision operation + explode: false + in: path + name: provisioningId + required: true + schema: + $ref: '#/components/schemas/ProvisioningId' + style: simple + - description: Correlation id for the different services + explode: false + in: header + name: x-correlator + required: false + schema: + type: string + style: simple + responses: + "200": + content: + application/json: + examples: + PROVISIONING_AVAILABLE: + $ref: '#/components/examples/PROVISIONING_AVAILABLE' + PROVISIONING_UNAVAILABLE: + $ref: '#/components/examples/PROVISIONING_UNAVAILABLE' + PROVISIONING_AVAILABLE_WITHOUT_DEVICE: + $ref: '#/components/examples/PROVISIONING_AVAILABLE_WITHOUT_DEVICE' + schema: + $ref: '#/components/schemas/ProvisioningInfo' + description: Returns information about certain provisioning + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "400": + content: + application/json: + examples: + GENERIC_400_INVALID_ARGUMENT: + description: Invalid Argument. Generic Syntax Exception + value: + status: 400 + code: INVALID_ARGUMENT + message: "Client specified an invalid argument, request body or\ + \ query param." + GENERIC_400_OUT_OF_RANGE: + description: Out of Range. Specific Syntax Exception used when a + given field has a pre-defined range or a invalid filter criteria + combination is requested + value: + status: 400 + code: OUT_OF_RANGE + message: Client specified an invalid range. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Bad Request + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "401": + content: + application/json: + examples: + GENERIC_401_UNAUTHENTICATED: + description: Request cannot be authenticated + value: + status: 401 + code: UNAUTHENTICATED + message: "Request not authenticated due to missing, invalid, or\ + \ expired credentials." + GENERIC_401_AUTHENTICATION_REQUIRED: + description: "New authentication is needed, authentication is no\ + \ longer valid" + value: + status: 401 + code: AUTHENTICATION_REQUIRED + message: New authentication is required. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Unauthorized + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "403": + content: + application/json: + examples: + GENERIC_403_PERMISSION_DENIED: + description: Permission denied. OAuth2 token access does not have + the required scope or when the user fails operational security + value: + status: 403 + code: PERMISSION_DENIED + message: Client does not have sufficient permissions to perform + this action. + GENERIC_403_INVALID_TOKEN_CONTEXT: + description: Reflect some inconsistency between information in some + field of the API and the related OAuth2 Token + value: + status: 403 + code: INVALID_TOKEN_CONTEXT + message: "{{field}} is not consistent with access token." + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Forbidden + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "404": + content: + application/json: + examples: + GENERIC_404_NOT_FOUND: + description: Resource is not found + value: + status: 404 + code: NOT_FOUND + message: The specified resource is not found. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Not found + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "429": + content: + application/json: + examples: + GENERIC_429_QUOTA_EXCEEDED: + description: Request is rejected due to exceeding a business quota + limit + value: + status: 429 + code: QUOTA_EXCEEDED + message: Either out of resource quota or reaching rate limiting. + GENERIC_429_TOO_MANY_REQUESTS: + description: API Server request limit is overpassed + value: + status: 429 + code: TOO_MANY_REQUESTS + message: Either out of resource quota or reaching rate limiting. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Too Many Requests + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "500": + content: + application/json: + examples: + GENERIC_500_INTERNAL: + description: Problem in Server side. Regular Server Exception + value: + status: 500 + code: INTERNAL + message: Unknown server error. Typically a server bug. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Internal server error + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "503": + content: + application/json: + examples: + GENERIC_503_UNAVAILABLE: + description: Service is not available. Temporary situation usually + related to maintenance process in the server side + value: + status: 503 + code: UNAVAILABLE + message: Service Unavailable. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Service unavailable + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + security: + - openId: + - qod-provisioning:device-qos:read + summary: Get QoD provisioning information + tags: + - QoD Provisioning + /retrieve-device-qos: + post: + description: | + Retrieves the QoD provisioning for a device. + + **NOTES:** + - The access token may be either 2-legged or 3-legged. + - If a 3-legged access token is used, the end user (and device) associated with the QoD provisioning must also be associated with the access token. In this case it is recommended NOT to include the `device` parameter in the request (see "Handling of device information" within the API description for details). + - If a 2-legged access token is used, the device parameter must be provided and identify a device. + - The QoD provisioning must have been created by the same API client given in the access token. + - If no provisioning is found for the device, an error response 404 is returned with code "NOT_FOUND". + operationId: retrieveProvisioningByDevice + parameters: + - description: Correlation id for the different services + explode: false + in: header + name: x-correlator + required: false + schema: + type: string + style: simple + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RetrieveProvisioningByDevice' + description: Parameters to retrieve a provisioning by device + required: true + responses: + "200": + content: + application/json: + examples: + PROVISIONING_AVAILABLE: + $ref: '#/components/examples/PROVISIONING_AVAILABLE' + PROVISIONING_AVAILABLE_WITHOUT_DEVICE: + $ref: '#/components/examples/PROVISIONING_AVAILABLE_WITHOUT_DEVICE' + schema: + $ref: '#/components/schemas/ProvisioningInfo' + description: Returns information about QoS provisioning for the device. + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "400": + content: + application/json: + examples: + GENERIC_400_INVALID_ARGUMENT: + description: Invalid Argument. Generic Syntax Exception + value: + status: 400 + code: INVALID_ARGUMENT + message: "Client specified an invalid argument, request body or\ + \ query param." + GENERIC_400_OUT_OF_RANGE: + description: Out of Range. Specific Syntax Exception used when a + given field has a pre-defined range or a invalid filter criteria + combination is requested + value: + status: 400 + code: OUT_OF_RANGE + message: Client specified an invalid range. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Bad Request + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "401": + content: + application/json: + examples: + GENERIC_401_UNAUTHENTICATED: + description: Request cannot be authenticated + value: + status: 401 + code: UNAUTHENTICATED + message: "Request not authenticated due to missing, invalid, or\ + \ expired credentials." + GENERIC_401_AUTHENTICATION_REQUIRED: + description: "New authentication is needed, authentication is no\ + \ longer valid" + value: + status: 401 + code: AUTHENTICATION_REQUIRED + message: New authentication is required. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Unauthorized + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "403": + content: + application/json: + examples: + GENERIC_403_PERMISSION_DENIED: + description: Permission denied. OAuth2 token access does not have + the required scope or when the user fails operational security + value: + status: 403 + code: PERMISSION_DENIED + message: Client does not have sufficient permissions to perform + this action. + GENERIC_403_INVALID_TOKEN_CONTEXT: + description: Reflect some inconsistency between information in some + field of the API and the related OAuth2 Token + value: + status: 403 + code: INVALID_TOKEN_CONTEXT + message: "{{field}} is not consistent with access token." + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Forbidden + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "404": + content: + application/json: + examples: + GENERIC_404_NOT_FOUND: + description: Resource is not found + value: + status: 404 + code: NOT_FOUND + message: The specified resource is not found. + GENERIC_404_DEVICE_NOT_FOUND: + description: Device identifier not found + value: + status: 404 + code: DEVICE_NOT_FOUND + message: Device identifier not found. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Not found + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "422": + content: + application/json: + examples: + GENERIC_422_UNPROCESSABLE_ENTITY: + description: The request was well-formed but was unable to be processed + due to semantic errors or not applicable values. This is the generic + error code for 422 responses. + value: + status: 422 + code: UNPROCESSABLE_ENTITY + message: "Value not acceptable: ..." + GENERIC_422_DEVICE_IDENTIFIERS_MISMATCH: + description: Inconsistency between device identifiers not pointing + to the same device + value: + status: 422 + code: DEVICE_IDENTIFIERS_MISMATCH + message: Provided device identifiers are not consistent. + GENERIC_422_DEVICE_NOT_APPLICABLE: + description: Service is not available for the provided device + value: + status: 422 + code: DEVICE_NOT_APPLICABLE + message: The service is not available for the provided device. + GENERIC_422_UNSUPPORTED_DEVICE_IDENTIFIERS: + description: Message may list the supported device identifiers + value: + status: 422 + code: UNSUPPORTED_DEVICE_IDENTIFIERS + message: "Supported device supported are: ..." + GENERIC_422_UNIDENTIFIABLE_DEVICE: + description: Service is not available for the provided device + value: + status: 422 + code: UNIDENTIFIABLE_DEVICE + message: The device cannot be identified. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Unprocessable entity + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "429": + content: + application/json: + examples: + GENERIC_429_QUOTA_EXCEEDED: + description: Request is rejected due to exceeding a business quota + limit + value: + status: 429 + code: QUOTA_EXCEEDED + message: Either out of resource quota or reaching rate limiting. + GENERIC_429_TOO_MANY_REQUESTS: + description: API Server request limit is overpassed + value: + status: 429 + code: TOO_MANY_REQUESTS + message: Either out of resource quota or reaching rate limiting. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Too Many Requests + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "500": + content: + application/json: + examples: + GENERIC_500_INTERNAL: + description: Problem in Server side. Regular Server Exception + value: + status: 500 + code: INTERNAL + message: Unknown server error. Typically a server bug. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Internal server error + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + "503": + content: + application/json: + examples: + GENERIC_503_UNAVAILABLE: + description: Service is not available. Temporary situation usually + related to maintenance process in the server side + value: + status: 503 + code: UNAVAILABLE + message: Service Unavailable. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Service unavailable + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + security: + - openId: + - qod-provisioning:device-qos:read-by-device + summary: Gets the QoD provisioning for a device + tags: + - QoD Provisioning +components: + examples: + PROVISIONING_AVAILABLE: + description: The provisioning has become available + summary: QoD provisioning status is available + value: + device: + phoneNumber: "+123456789" + qosProfile: QOS_L + sink: https://application-server.com/callback + sinkCredential: + credentialType: ACCESSTOKEN + accessToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c + accessTokenExpiresUtc: 2024-12-01T12:00:00Z + accessTokenType: bearer + provisioningId: 3fa85f64-5717-4562-b3fc-2c963f66afa6 + startedAt: 2024-05-12T17:32:01Z + status: AVAILABLE + PROVISIONING_UNAVAILABLE: + description: The provisioning could not be created or is not active anymore + summary: QoD provisioning status is unavailable + value: + duration: 86400 + device: + ipv4Address: + publicAddress: 203.0.113.0 + publicPort: 59765 + qosProfile: QOS_L + sink: https://application-server.com/callback + sinkCredential: + credentialType: ACCESSTOKEN + accessToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c + accessTokenExpiresUtc: 2024-12-01T12:00:00Z + accessTokenType: bearer + provisioningId: 3fa85f64-5717-4562-b3fc-2c963f66afa6 + startedAt: 2024-05-12T17:32:01Z + status: UNAVAILABLE + statusInfo: NETWORK_TERMINATED + PROVISIONING_AVAILABLE_WITHOUT_DEVICE: + description: Device is optional in responses and must not be provided if it + was not provided in the request + summary: QoD provisioning status is available but no device information is provided + value: + qosProfile: QOS_M + sink: https://application-server.com/callback + sinkCredential: + credentialType: ACCESSTOKEN + accessToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c + accessTokenExpiresUtc: 2024-12-01T12:00:00Z + accessTokenType: bearer + provisioningId: 3fa85f64-5717-4562-b3fc-2c963f66afa6 + startedAt: 2024-05-12T17:32:01Z + status: AVAILABLE + PROVISIONING_STATUS_CHANGED_EXAMPLE: + description: Provisioning status changed + summary: Cloud event example for QoD provisioning status change to UNAVAILABLE + due to NETWORK_TERMINATED + value: + id: 83a0d986-0866-4f38-b8c0-fc65bfcda452 + source: https://api.example.com/qod-provisioning/v0.1/device-qos/123e4567-e89b-12d3-a456-426614174000 + specversion: "1.0" + type: org.camaraproject.qod-provisioning.v0.status-changed + time: 2021-12-12T00:00:00Z + data: + provisioningId: 123e4567-e89b-12d3-a456-426614174000 + status: UNAVAILABLE + statusInfo: NETWORK_TERMINATED + headers: + x-correlator: + description: Correlation id for the different services + explode: false + schema: + type: string + style: simple + parameters: + provisioningId: + description: Provisioning ID that was obtained from the createProvision operation + explode: false + in: path + name: provisioningId + required: true + schema: + $ref: '#/components/schemas/ProvisioningId' + style: simple + x-correlator: + description: Correlation id for the different services + explode: false + in: header + name: x-correlator + required: false + schema: + type: string + style: simple + responses: + Generic400: + content: + application/json: + examples: + GENERIC_400_INVALID_ARGUMENT: + description: Invalid Argument. Generic Syntax Exception + value: + status: 400 + code: INVALID_ARGUMENT + message: "Client specified an invalid argument, request body or query\ + \ param." + GENERIC_400_OUT_OF_RANGE: + description: Out of Range. Specific Syntax Exception used when a given + field has a pre-defined range or a invalid filter criteria combination + is requested + value: + status: 400 + code: OUT_OF_RANGE + message: Client specified an invalid range. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Bad Request + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + CreateProvisioning400: + content: + application/json: + examples: + GENERIC_400_INVALID_ARGUMENT: + description: Invalid Argument. Generic Syntax Exception + value: + status: 400 + code: INVALID_ARGUMENT + message: "Client specified an invalid argument, request body or query\ + \ param." + GENERIC_400_OUT_OF_RANGE: + description: Out of Range. Specific Syntax Exception used when a given + field has a pre-defined range or a invalid filter criteria combination + is requested + value: + status: 400 + code: OUT_OF_RANGE + message: Client specified an invalid range. + GENERIC_400_INVALID_CREDENTIAL: + value: + status: 400 + code: INVALID_CREDENTIAL + message: Only Access token is supported + GENERIC_400_INVALID_TOKEN: + value: + status: 400 + code: INVALID_TOKEN + message: Only bearer token is supported + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Bad Request with additional errors for implicit notifications + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + Generic401: + content: + application/json: + examples: + GENERIC_401_UNAUTHENTICATED: + description: Request cannot be authenticated + value: + status: 401 + code: UNAUTHENTICATED + message: "Request not authenticated due to missing, invalid, or expired\ + \ credentials." + GENERIC_401_AUTHENTICATION_REQUIRED: + description: "New authentication is needed, authentication is no longer\ + \ valid" + value: + status: 401 + code: AUTHENTICATION_REQUIRED + message: New authentication is required. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Unauthorized + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + Generic403: + content: + application/json: + examples: + GENERIC_403_PERMISSION_DENIED: + description: Permission denied. OAuth2 token access does not have the + required scope or when the user fails operational security + value: + status: 403 + code: PERMISSION_DENIED + message: Client does not have sufficient permissions to perform this + action. + GENERIC_403_INVALID_TOKEN_CONTEXT: + description: Reflect some inconsistency between information in some + field of the API and the related OAuth2 Token + value: + status: 403 + code: INVALID_TOKEN_CONTEXT + message: "{{field}} is not consistent with access token." + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Forbidden + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + Generic404: + content: + application/json: + examples: + GENERIC_404_NOT_FOUND: + description: Resource is not found + value: + status: 404 + code: NOT_FOUND + message: The specified resource is not found. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Not found + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + GenericDevice404: + content: + application/json: + examples: + GENERIC_404_NOT_FOUND: + description: Resource is not found + value: + status: 404 + code: NOT_FOUND + message: The specified resource is not found. + GENERIC_404_DEVICE_NOT_FOUND: + description: Device identifier not found + value: + status: 404 + code: DEVICE_NOT_FOUND + message: Device identifier not found. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Not found + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + ProvisioningConflict409: + content: + application/json: + examples: + PROVISIONING_409_CONFLICT: + description: The requested provisioning conflicts with an existing one + value: + status: 409 + code: CONFLICT + message: There is another existing provisioning for the same device + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Provisioning conflict + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + Generic410: + content: + application/json: + examples: + GENERIC_410_GONE: + description: Use in notifications flow to allow API Consumer to indicate + that its callback is no longer available + value: + status: 410 + code: GONE + message: Access to the target resource is no longer available. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Gone + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + Generic422: + content: + application/json: + examples: + GENERIC_422_UNPROCESSABLE_ENTITY: + description: The request was well-formed but was unable to be processed + due to semantic errors or not applicable values. This is the generic + error code for 422 responses. + value: + status: 422 + code: UNPROCESSABLE_ENTITY + message: "Value not acceptable: ..." + GENERIC_422_DEVICE_IDENTIFIERS_MISMATCH: + description: Inconsistency between device identifiers not pointing to + the same device + value: + status: 422 + code: DEVICE_IDENTIFIERS_MISMATCH + message: Provided device identifiers are not consistent. + GENERIC_422_DEVICE_NOT_APPLICABLE: + description: Service is not available for the provided device + value: + status: 422 + code: DEVICE_NOT_APPLICABLE + message: The service is not available for the provided device. + GENERIC_422_UNSUPPORTED_DEVICE_IDENTIFIERS: + description: Message may list the supported device identifiers + value: + status: 422 + code: UNSUPPORTED_DEVICE_IDENTIFIERS + message: "Supported device supported are: ..." + GENERIC_422_UNIDENTIFIABLE_DEVICE: + description: Service is not available for the provided device + value: + status: 422 + code: UNIDENTIFIABLE_DEVICE + message: The device cannot be identified. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Unprocessable entity + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + Generic429: + content: + application/json: + examples: + GENERIC_429_QUOTA_EXCEEDED: + description: Request is rejected due to exceeding a business quota limit + value: + status: 429 + code: QUOTA_EXCEEDED + message: Either out of resource quota or reaching rate limiting. + GENERIC_429_TOO_MANY_REQUESTS: + description: API Server request limit is overpassed + value: + status: 429 + code: TOO_MANY_REQUESTS + message: Either out of resource quota or reaching rate limiting. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Too Many Requests + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + Generic500: + content: + application/json: + examples: + GENERIC_500_INTERNAL: + description: Problem in Server side. Regular Server Exception + value: + status: 500 + code: INTERNAL + message: Unknown server error. Typically a server bug. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Internal server error + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + Generic503: + content: + application/json: + examples: + GENERIC_503_UNAVAILABLE: + description: Service is not available. Temporary situation usually related + to maintenance process in the server side + value: + status: 503 + code: UNAVAILABLE + message: Service Unavailable. + schema: + $ref: '#/components/schemas/ErrorInfo' + description: Service unavailable + headers: + x-correlator: + $ref: '#/components/headers/x-correlator' + schemas: + ProvisioningId: + description: Provisioning Identifier in UUID format + format: uuid + title: ProvisioningId + type: string + BaseProvisioningInfo: + description: Common attributes of a QoD provisioning + properties: + device: + $ref: '#/components/schemas/Device' + qosProfile: + description: | + A unique name for identifying a specific QoS profile. + This may follow different formats depending on the service providers implementation. + Some options addresses: + - A UUID style string + - Support for predefined profiles QOS_S, QOS_M, QOS_L, and QOS_E + - A searchable descriptive name + The set of QoS Profiles that an operator is offering can be retrieved by means of the [QoS Profile API](link TBC). + example: QCI_1_voice + format: string + maxLength: 256 + minLength: 3 + pattern: "^[a-zA-Z0-9_.-]+$" + title: qosProfile + type: string + sink: + description: "The address to which events shall be delivered, using the\ + \ HTTP protocol." + example: https://endpoint.example.com/sink + format: url + title: sink + type: string + sinkCredential: + $ref: '#/components/schemas/SinkCredential' + required: + - qosProfile + title: BaseProvisioningInfo + type: object + ProvisioningInfo: + allOf: + - $ref: '#/components/schemas/BaseProvisioningInfo' + - properties: + provisioningId: + $ref: '#/components/schemas/ProvisioningId' + startedAt: + description: Date and time when the provisioning became "AVAILABLE". Not + to be returned when `status` is "REQUESTED". Format must follow RFC + 3339 and must indicate time zone (UTC or local). + example: 2024-06-01T12:00:00Z + format: date-time + type: string + status: + $ref: '#/components/schemas/Status' + statusInfo: + $ref: '#/components/schemas/StatusInfo' + required: + - provisioningId + - status + type: object + description: | + Provisioning related information returned in responses. + Optional device object only to be returned if provided in createProvisioning. If more than one type of device identifier was provided, only one identifier will be returned (at implementation choice and with the original value provided in createProvisioning). + Please note that IP addresses of devices can change and get reused, so the original values may no longer identify the same device. They identified the device at the time of QoD provisioning creation. + example: + qosProfile: QCI_1_voice + statusInfo: NETWORK_TERMINATED + sink: https://endpoint.example.com/sink + provisioningId: null + startedAt: 2024-06-01T12:00:00Z + sinkCredential: + credentialType: PLAIN + device: + phoneNumber: "+123456789" + ipv6Address: 2001:db8:85a3:8d3:1319:8a2e:370:7344 + ipv4Address: + publicAddress: 203.0.113.0 + publicPort: 59765 + networkAccessIdentifier: 123456789@domain.com + status: REQUESTED + title: ProvisioningInfo + CreateProvisioning: + allOf: + - $ref: '#/components/schemas/BaseProvisioningInfo' + description: Attributes to request a new QoD provisioning + example: + qosProfile: QCI_1_voice + sink: https://endpoint.example.com/sink + sinkCredential: + credentialType: PLAIN + device: + phoneNumber: "+123456789" + ipv6Address: 2001:db8:85a3:8d3:1319:8a2e:370:7344 + ipv4Address: + publicAddress: 203.0.113.0 + publicPort: 59765 + networkAccessIdentifier: 123456789@domain.com + RetrieveProvisioningByDevice: + description: Attributes to look for QoD provisioning + example: + device: + phoneNumber: "+123456789" + ipv6Address: 2001:db8:85a3:8d3:1319:8a2e:370:7344 + ipv4Address: + publicAddress: 203.0.113.0 + publicPort: 59765 + networkAccessIdentifier: 123456789@domain.com + properties: + device: + $ref: '#/components/schemas/Device' + title: RetrieveProvisioningByDevice + type: object + SinkCredential: + description: A sink credential provides authentication or authorization information + necessary to enable delivery of events to a target. + discriminator: + mapping: + PLAIN: '#/components/schemas/PlainCredential' + ACCESSTOKEN: '#/components/schemas/AccessTokenCredential' + REFRESHTOKEN: '#/components/schemas/RefreshTokenCredential' + propertyName: credentialType + example: + credentialType: PLAIN + properties: + credentialType: + description: The type of the credential. + enum: + - PLAIN + - ACCESSTOKEN + - REFRESHTOKEN + title: credentialType + type: string + required: + - credentialType + title: SinkCredential + type: object + PlainCredential: + allOf: + - $ref: '#/components/schemas/SinkCredential' + - properties: + identifier: + description: The identifier might be an account or username. + type: string + secret: + description: The secret might be a password or passphrase. + type: string + required: + - identifier + - secret + type: object + description: A plain credential as a combination of an identifier and a secret. + type: object + AccessTokenCredential: + allOf: + - $ref: '#/components/schemas/SinkCredential' + - properties: + accessToken: + description: REQUIRED. An access token is a previously acquired token + granting access to the target resource. + type: string + accessTokenExpiresUtc: + description: REQUIRED. An absolute UTC instant at which the token shall + be considered expired. + format: date-time + type: string + accessTokenType: + description: "REQUIRED. Type of the access token (See [OAuth 2.0](https://tools.ietf.org/html/rfc6749#section-7.1))." + enum: + - bearer + type: string + required: + - accessToken + - accessTokenExpiresUtc + - accessTokenType + type: object + description: An access token credential. + type: object + RefreshTokenCredential: + allOf: + - $ref: '#/components/schemas/SinkCredential' + - properties: + accessToken: + description: REQUIRED. An access token is a previously acquired token + granting access to the target resource. + type: string + accessTokenExpiresUtc: + description: REQUIRED. An absolute UTC instant at which the token shall + be considered expired. + format: date-time + type: string + accessTokenType: + description: "REQUIRED. Type of the access token (See [OAuth 2.0](https://tools.ietf.org/html/rfc6749#section-7.1))." + enum: + - bearer + type: string + refreshToken: + description: REQUIRED. An refresh token credential used to acquire access + tokens. + type: string + refreshTokenEndpoint: + description: REQUIRED. A URL at which the refresh token can be traded + for an access token. + format: uri + type: string + type: object + description: An access token credential with a refresh token. + required: + - accessToken + - accessTokenExpiresUtc + - accessTokenType + - refreshToken + - refreshTokenEndpoint + type: object + Port: + description: TCP or UDP port number + maximum: 65535 + minimum: 0 + type: integer + QosProfileName: + description: | + A unique name for identifying a specific QoS profile. + This may follow different formats depending on the service providers implementation. + Some options addresses: + - A UUID style string + - Support for predefined profiles QOS_S, QOS_M, QOS_L, and QOS_E + - A searchable descriptive name + The set of QoS Profiles that an operator is offering can be retrieved by means of the [QoS Profile API](link TBC). + example: QCI_1_voice + format: string + maxLength: 256 + minLength: 3 + pattern: "^[a-zA-Z0-9_.-]+$" + title: qosProfile + type: string + CloudEvent: + description: Event compliant with the CloudEvents specification + discriminator: + mapping: + org.camaraproject.qod-provisioning.v0.status-changed: '#/components/schemas/EventStatusChanged' + propertyName: type + properties: + id: + description: "Identifier of this event, that must be unique in the source\ + \ context." + title: id + type: string + source: + description: Identifies the context in which an event happened in the specific + Provider Implementation. + format: uri-reference + title: source + type: string + type: + description: The type of the event. + enum: + - org.camaraproject.qod-provisioning.v0.status-changed + title: type + type: string + specversion: + description: Version of the specification to which this event conforms (must + be 1.0 if it conforms to cloudevents 1.0.2 version) + enum: + - "1.0" + title: specversion + type: string + datacontenttype: + description: "media-type that describes the event payload encoding, must\ + \ be \"application/json\" for CAMARA APIs" + enum: + - application/json + title: datacontenttype + type: string + data: + description: "Event notification details payload, which depends on the event\ + \ type" + title: data + type: object + time: + description: | + Timestamp of when the occurrence happened. It must follow RFC 3339 + format: date-time + title: time + type: string + required: + - id + - source + - specversion + - time + - type + title: CloudEvent + EventStatusChanged: + allOf: + - $ref: '#/components/schemas/CloudEvent' + - properties: + data: + $ref: '#/components/schemas/EventStatusChanged_allOf_data' + required: + - data + type: object + description: Event to notify a QoD provisioning status change + StatusInfo: + description: | + Reason for the new `status`: + * `NETWORK_TERMINATED` - Network terminated the QoD provisioning + * `DELETE_REQUESTED`- User requested the deletion of the QoD provisioning + enum: + - NETWORK_TERMINATED + - DELETE_REQUESTED + title: StatusInfo + type: string + Device: + description: | + End-user equipment able to connect to the network. Examples of devices include smartphones or IoT sensors/actuators. + + The developer can choose to provide the below specified device identifiers: + + * `ipv4Address` + * `ipv6Address` + * `phoneNumber` + * `networkAccessIdentifier` + + NOTE1: the network operator might support only a subset of these options. The API invoker can provide multiple identifiers to be compatible across different network operators. In this case the identifiers MUST belong to the same device. + NOTE2: for the Commonalities release v0.4, we are enforcing that the networkAccessIdentifier is only part of the schema for future-proofing, and CAMARA does not currently allow its use. After the CAMARA meta-release work is concluded and the relevant issues are resolved, its use will need to be explicitly documented in the guidelines. + example: + phoneNumber: "+123456789" + ipv6Address: 2001:db8:85a3:8d3:1319:8a2e:370:7344 + ipv4Address: + publicAddress: 203.0.113.0 + publicPort: 59765 + networkAccessIdentifier: 123456789@domain.com + minProperties: 1 + properties: + phoneNumber: + description: "A public identifier addressing a telephone subscription. In\ + \ mobile networks it corresponds to the MSISDN (Mobile Station International\ + \ Subscriber Directory Number). In order to be globally unique it has\ + \ to be formatted in international format, according to E.164 standard,\ + \ prefixed with '+'." + example: "+123456789" + pattern: "^\\+[1-9][0-9]{4,14}$" + title: PhoneNumber + type: string + networkAccessIdentifier: + description: "A public identifier addressing a subscription in a mobile\ + \ network. In 3GPP terminology, it corresponds to the GPSI formatted with\ + \ the External Identifier ({Local Identifier}@{Domain Identifier}). Unlike\ + \ the telephone number, the network access identifier is not subjected\ + \ to portability ruling in force, and is individually managed by each\ + \ operator." + example: 123456789@domain.com + title: NetworkAccessIdentifier + type: string + ipv4Address: + $ref: '#/components/schemas/DeviceIpv4Addr' + ipv6Address: + description: | + The device should be identified by the observed IPv6 address, or by any single IPv6 address from within the subnet allocated to the device (e.g. adding ::0 to the /64 prefix). + example: 2001:db8:85a3:8d3:1319:8a2e:370:7344 + format: ipv6 + title: DeviceIpv6Address + type: string + title: Device + type: object + NetworkAccessIdentifier: + description: "A public identifier addressing a subscription in a mobile network.\ + \ In 3GPP terminology, it corresponds to the GPSI formatted with the External\ + \ Identifier ({Local Identifier}@{Domain Identifier}). Unlike the telephone\ + \ number, the network access identifier is not subjected to portability ruling\ + \ in force, and is individually managed by each operator." + example: 123456789@domain.com + title: NetworkAccessIdentifier + type: string + PhoneNumber: + description: "A public identifier addressing a telephone subscription. In mobile\ + \ networks it corresponds to the MSISDN (Mobile Station International Subscriber\ + \ Directory Number). In order to be globally unique it has to be formatted\ + \ in international format, according to E.164 standard, prefixed with '+'." + example: "+123456789" + pattern: "^\\+[1-9][0-9]{4,14}$" + title: PhoneNumber + type: string + DeviceIpv4Addr: + anyOf: [] + description: | + The device should be identified by either the public (observed) IP address and port as seen by the application server, or the private (local) and any public (observed) IP addresses in use by the device (this information can be obtained by various means, for example from some DNS servers). + + If the allocated and observed IP addresses are the same (i.e. NAT is not in use) then the same address should be specified for both publicAddress and privateAddress. + + If NAT64 is in use, the device should be identified by its publicAddress and publicPort, or separately by its allocated IPv6 address (field ipv6Address of the Device object) + + In all cases, publicAddress must be specified, along with at least one of either privateAddress or publicPort, dependent upon which is known. In general, mobile devices cannot be identified by their public IPv4 address alone. + example: + publicAddress: 203.0.113.0 + publicPort: 59765 + nullable: true + properties: + publicAddress: + description: A single IPv4 address with no subnet mask + example: 203.0.113.0 + format: ipv4 + type: string + privateAddress: + description: A single IPv4 address with no subnet mask + example: 203.0.113.0 + format: ipv4 + type: string + publicPort: + description: TCP or UDP port number + maximum: 65535 + minimum: 0 + type: integer + title: DeviceIpv4Addr + type: object + SingleIpv4Addr: + description: A single IPv4 address with no subnet mask + example: 203.0.113.0 + format: ipv4 + type: string + DeviceIpv6Address: + description: | + The device should be identified by the observed IPv6 address, or by any single IPv6 address from within the subnet allocated to the device (e.g. adding ::0 to the /64 prefix). + example: 2001:db8:85a3:8d3:1319:8a2e:370:7344 + format: ipv6 + title: DeviceIpv6Address + type: string + Status: + description: | + The current status of the requested QoD provisioning. The status can be one of the following: + * `REQUESTED` - QoD provisioning has been requested but is still being processed. + * `AVAILABLE` - The requested QoS profile has been provisioned to the device, and is active. + * `UNAVAILABLE` - The QoD provisioning request has been processed but is not active. `statusInfo` may provide additional information about the reason for the unavailability. + enum: + - REQUESTED + - AVAILABLE + - UNAVAILABLE + title: Status + type: string + StatusChanged: + description: | + The current status of a requested or previously available QoD provisioning. Applicable values in the event are: + * `AVAILABLE` - The requested QoS profile has been provisioned to the device, and is active. + * `UNAVAILABLE` - A requested or previously available QoD provisioning is now unavailable. `statusInfo` may provide additional information about the reason for the unavailability. + enum: + - AVAILABLE + - UNAVAILABLE + title: StatusChanged + type: string + ErrorInfo: + description: Common schema for errors + example: + code: code + message: message + status: 0 + properties: + status: + description: HTTP status code returned along with this error response + title: status + type: integer + code: + description: Code given to this error + title: code + type: string + message: + description: Detailed error description + title: message + type: string + required: + - code + - message + - status + title: ErrorInfo + type: object + EventStatusChanged_allOf_data: + description: Event details depending on the event type + properties: + provisioningId: + description: Provisioning Identifier in UUID format + format: uuid + title: ProvisioningId + type: string + status: + $ref: '#/components/schemas/StatusChanged' + statusInfo: + $ref: '#/components/schemas/StatusInfo' + required: + - provisioningId + - qosStatus + title: EventStatusChanged_allOf_data + type: object + securitySchemes: + openId: + description: OpenID Connect authentication + openIdConnectUrl: https://example.com/.well-known/openid-configuration + type: openIdConnect + notificationsBearerAuth: + bearerFormat: "{$request.body#/sinkCredential.credentialType}" + description: Bearer authentication for notifications + scheme: bearer + type: http diff --git a/QoDProvisioning/QoDProvisioningAPI/API/requirements.txt b/QoDProvisioning/QoDProvisioningAPI/API/requirements.txt new file mode 100644 index 0000000..0e7e2cc --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/requirements.txt @@ -0,0 +1,39 @@ +aiofiles==23.1.0 +aniso8601==7.0.0 +async-exit-stack==1.0.1 +async-generator==1.10 +certifi==2024.7.4 +chardet==4.0.0 +click==7.1.2 +dnspython==2.6.1 +email-validator==2.0.0 +fastapi==0.115.5 +graphene==2.1.8 +graphql-core==2.3.2 +graphql-relay==2.0.1 +h11==0.12.0 +httptools>=0.3.0,<0.7.0 +httpx==0.24.1 +idna==3.7 +itsdangerous==1.1.0 +Jinja2==3.1.4 +MarkupSafe==2.0.1 +orjson==3.9.15 +promise==2.3 +pydantic>=2 +python-dotenv==0.17.1 +python-multipart==0.0.7 +PyYAML>=5.4.1,<6.1.0 +requests==2.32.0 +Rx==1.6.1 +starlette==0.40.0 +typing-extensions==4.8.0 +ujson==4.0.2 +urllib3==1.26.19 +uvicorn==0.13.4 +uvloop==0.19.0 +watchgod==0.7 +websockets==10.0 +pysqlite3==0.5.4 +sqlalchemy==1.4.46 +stomp.py==8.2.0 \ No newline at end of file diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/__init__.py b/QoDProvisioning/QoDProvisioningAPI/API/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/aux/constants.py b/QoDProvisioning/QoDProvisioningAPI/API/src/aux/constants.py new file mode 100644 index 0000000..b9ec393 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/aux/constants.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# @Authors: +# Eduardo Santos (eduardosantoshf@av.it.pt) +# Rafael Direito (rdireito@av.it.pt) +# @Organization: +# Instituto de Telecomunicações, Aveiro (ITAv) +# Aveiro, Portugal +# @Date: +# December 2024 + +class Constants(): + processed_camara_results = [] \ No newline at end of file diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/aux/mappers.py b/QoDProvisioning/QoDProvisioningAPI/API/src/aux/mappers.py new file mode 100644 index 0000000..c9794a5 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/aux/mappers.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +# @Authors: +# Eduardo Santos (eduardosantoshf@av.it.pt) +# Rafael Direito (rdireito@av.it.pt) +# @Organization: +# Instituto de Telecomunicações, Aveiro (ITAv) +# Aveiro, Portugal +# @Date: +# December 2024 + +from database.base_models import Provisioning, Device + +def map_device_to_dict(device: Device) -> dict: + return { + "phone_number": device.phone_number, + "network_access_identifier": device.network_access_identifier, + "ipv4_address": { + "public_address": device.ipv4_public_address, + "private_address": device.ipv4_private_address, + "public_port": device.ipv4_public_port + }, + "ipv6_address": device.ipv6_address + } + +def map_service_characteristics(provisioning, operation): + characteristics = [ + { + "name": "qodProv.device.phoneNumber", + "value": {"value": provisioning.device.phone_number or ""} + }, + { + "name": "qodProv.device.networkAccessIdentifier", + "value": {"value": provisioning.device.network_access_identifier \ + or ""} + }, + { + "name": "qodProv.device.ipv4Address.publicAddress", + "value": {"value": provisioning.device.ipv4_public_address or ""} + }, + { + "name": "qodProv.device.ipv4Address.privateAddress", + "value": {"value": provisioning.device.ipv4_private_address or ""} + }, + { + "name": "qodProv.device.ipv4Address.publicPort", + "value": {"value": provisioning.device.ipv4_public_port or ""} + }, + { + "name": "qodProv.device.ipv6Address", + "value": {"value": provisioning.device.ipv6_address or ""} + }, + { + "name": "qodProv.qosProfile", + "value": {"value": provisioning.qos_profile} + }, + { + "name": "qodProv.operation", + "value": {"value": operation} + }, + { + "name": "qodProv.provisioningId", + "value": {"value": provisioning.id} + }, + { + "name": "qodProv.sink", + "value": {"value": provisioning.sink or ""} + }, + { + "name": "qodProv.sinkCredential.credentialType", + "value": {"value": provisioning.sink_credential or ""} + } + ] + return characteristics \ No newline at end of file diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/aux/service_event_manager/__init__.py b/QoDProvisioning/QoDProvisioningAPI/API/src/aux/service_event_manager/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/aux/service_event_manager/camara_results_processor.py b/QoDProvisioning/QoDProvisioningAPI/API/src/aux/service_event_manager/camara_results_processor.py new file mode 100644 index 0000000..ea22ca4 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/aux/service_event_manager/camara_results_processor.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +# @Authors: +# Eduardo Santos (eduardosantoshf@av.it.pt) +# Rafael Direito (rdireito@av.it.pt) +# @Organization: +# Instituto de Telecomunicações, Aveiro (ITAv) +# Aveiro, Portugal +# @Date: +# December 2024 + +import asyncio + +from aux.service_event_manager.service_event_manager import ServiceEventManager +from config import Config +import json +from database import crud +from database.db import get_db +from aux.constants import Constants +# Set up logging +logger = Config.setup_logging() + +class CamaraResultsProcessor: + """Handles processing of camara results from the queue.""" + + def __init__(self, queue): + self.queue = queue + self.db_session = next(get_db()) + + async def process_results(self): + """Continuously processes results from the queue.""" + try: + results = None + # Enter the infinite loop to process subsequent results + while True: + results_str = await self.queue.get() + try: + results = json.loads(results_str) + except Exception as e: + logger.error( + f"Could not parse Camara results. Reason: {e}" + ) + logger.info( + f"Amounf of processed CAMARA Results: {len(results)}." + ) + logger.debug(f"Processed camaraResults: {results}") + + self.update_provisionings(results) + except asyncio.CancelledError: + logger.info("CamaraResultsProcessor stopped gracefully.") + except Exception as e: + logger.error(f"Error processing camara results: {e}", exc_info=True) + + def update_provisionings(self, current_results): + Constants.processed_camara_results = [] + for result in current_results: + try: + prov_id = result["provisioningId"] + prov_status = result["status"] + prov_timestamp = result["startedAt"] + crud.update_provisioning_by_id( + self.db_session, + prov_id, + prov_status, + prov_timestamp + + ) + # Deal with camara-current-results endpoint + if "sinkCredential" in result: + if "credentialType" in result["sinkCredential"] \ + and result["sinkCredential"]["credentialType"] not in \ + ['PLAIN', 'ACCESSTOKEN', 'REFRESHTOKEN']: + result["sinkCredential"]["credentialType"] = None + else: + result["sinkCredential"] = { + "credentialType": None + } + Constants.processed_camara_results.append(result) + except Exception as e: + logger.error( + f"Could not process CAMARA Result: {result}. Reason: {e}" + ) \ No newline at end of file diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/aux/service_event_manager/service_event_manager.py b/QoDProvisioning/QoDProvisioningAPI/API/src/aux/service_event_manager/service_event_manager.py new file mode 100644 index 0000000..845b013 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/aux/service_event_manager/service_event_manager.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- +# @Authors: +# Eduardo Santos (eduardosantoshf@av.it.pt) +# Rafael Direito (rdireito@av.it.pt) +# @Organization: +# Instituto de Telecomunicações, Aveiro (ITAv) +# Aveiro, Portugal +# @Date: +# December 2024 + +import stomp +import json +import time +import os +import asyncio +from functools import wraps +from config import Config + +# Set up logging +logger = Config.setup_logging() + +def check_subscribe_connection(method): + @wraps(method) + def wrapper(cls, *args, **kwargs): + if not cls.connection or not cls.connection.is_connected(): + logger.warning("Connection not active. Reconnecting...") + cls.connection = stomp.Connection( + [(cls.broker_address, cls.broker_port)], + heartbeats=(15000, 15000) + ) + cls.connection.connect( + cls.broker_username, + cls.broker_password, + wait=True + ) + if cls.connection and cls.connection.is_connected(): + logger.info("Connection is active.") + return method(cls, *args, **kwargs) + return wrapper + +class ServiceEventManager: + """Manages event subscriptions and service updates using STOMP.""" + + # Validate the environment variables before using them + Config.validate() + + camara_results_queue = None + camara_results_lock = None + connection = None + + @classmethod + def initialize(cls): + cls.broker_address = Config.broker_address + cls.broker_port = Config.broker_port + cls.broker_username = Config.broker_username + cls.broker_password = Config.broker_password + cls.service_uuid = Config.service_uuid + cls.catalog_upd_service = Config.catalog_upd_service + cls.event_service_attrchanged = Config.event_service_attrchanged + + # Initialize shared resources + cls.camara_results_queue = asyncio.Queue() + cls.camara_results_lock = asyncio.Lock() + + + + + @classmethod + @check_subscribe_connection + def subscribe_to_events(cls): + """Subscribe to the events topic.""" + + loop = asyncio.get_event_loop() + + def run_listener(): + cls.connection.set_listener('', cls.MyListener(loop)) + cls.connection.subscribe( + destination=cls.event_service_attrchanged, + id=1 + ) + + logger.info( + f"Subscribed to {cls.event_service_attrchanged}. " + f"Waiting for messages..." + ) + + # Run the listener in a separate thread + import threading + listener_thread = threading.Thread(target=run_listener, daemon=True) + listener_thread.start() + + @classmethod + def update_service(cls, update_payload): + """Send a service update to the specified destination.""" + try: + headers = { + "serviceid": cls.service_uuid, + "triggerServiceActionQueue": True + } + + # Connect to STOMP broker and send the message + conn = stomp.Connection([(cls.broker_address, cls.broker_port)]) + conn.connect( + cls.broker_username, + cls.broker_password, + wait=True + ) + + logger.info(f"Sending update to {cls.catalog_upd_service}...") + conn.send( + destination=cls.catalog_upd_service, + body=json.dumps(update_payload), + headers=headers + ) + logger.info("Update sent successfully.") + + conn.disconnect() + except Exception as e: + logger.error(f"Cannot update Service: {cls.service_uuid}: {str(e)}") + + class MyListener(stomp.ConnectionListener): + """Custom listener to handle incoming messages from the STOMP broker.""" + + def __init__(self, loop): + super().__init__() + self.loop = loop + + def get_camara_results(self, service_info): + for charact in service_info.get("serviceCharacteristic"): + if charact.get("name") == "camaraResults": + return charact.get("value").get("value") + + def on_message(self, frame): + """Handle received message frames.""" + + # Attempt to parse the body as JSON + try: + message = json.loads(frame.body) + service_info = message.get("event").get("service") + + camara_results = None + if service_info.get("uuid") == ServiceEventManager.service_uuid: + camara_results = self.get_camara_results(service_info) + + # Add the result to the async queue + if camara_results: + asyncio.run_coroutine_threadsafe( + ServiceEventManager.camara_results_queue.put( + camara_results + ), + self.loop + ) + + except json.JSONDecodeError: + logger.info('Received message is not valid JSON.') \ No newline at end of file diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/config.py b/QoDProvisioning/QoDProvisioningAPI/API/src/config.py new file mode 100644 index 0000000..78a34e0 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/config.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +# @Authors: +# Eduardo Santos (eduardosantoshf@av.it.pt) +# Rafael Direito (rdireito@av.it.pt) +# @Organization: +# Instituto de Telecomunicações, Aveiro (ITAv) +# Aveiro, Portugal +# @Date: +# December 2024 + +import os +import sys +import logging + +class Config(): + + broker_address = os.getenv('BROKER_ADDRESS') + broker_port = os.getenv('BROKER_PORT') + broker_username = os.getenv('BROKER_USERNAME') + broker_password = os.getenv('BROKER_PASSWORD') + service_uuid = os.getenv('SERVICE_UUID') + log_level = os.getenv('LOG_LEVEL', "INFO") + db_path = os.getenv("SQLITE_DB_PATH", "/data/sqlite.db") + + # Broker topics + catalog_upd_service = "CATALOG.UPD.SERVICE" + event_service_attrchanged = "EVENT.SERVICE.ATTRCHANGED" + + logger = None + + @classmethod + def validate(cls): + missing_envs = [] + + for var in ['broker_address', 'broker_port', 'broker_username', + 'broker_password', 'service_uuid']: + if getattr(cls, var) is None: + missing_envs.append(var.upper()) + + if missing_envs: + raise EnvironmentError( + f"Missing required environment variables: {', '.join(missing_envs)}" + ) + + print("All required environment variables are set.") + + @classmethod + def setup_logging(cls): + if cls.logger is None: + log_level = getattr(logging, cls.log_level.upper()) + + # Create a logger + cls.logger = logging.getLogger() + cls.logger.setLevel(log_level) + + # Create a stream handler that outputs to stdout + handler = logging.StreamHandler(sys.stdout) + handler.setLevel(log_level) + + # Create a formatter and add it to the handler + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + handler.setFormatter(formatter) + + # Add the handler to the logger + cls.logger.addHandler(handler) + + return cls.logger \ No newline at end of file diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/database/__init__.py b/QoDProvisioning/QoDProvisioningAPI/API/src/database/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/database/base_models.py b/QoDProvisioning/QoDProvisioningAPI/API/src/database/base_models.py new file mode 100644 index 0000000..26705e9 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/database/base_models.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# @Authors: +# Eduardo Santos (eduardosantoshf@av.it.pt) +# Rafael Direito (rdireito@av.it.pt) +# @Organization: +# Instituto de Telecomunicações, Aveiro (ITAv) +# Aveiro, Portugal +# @Date: +# December 2024 + +from sqlalchemy import ( + Column, + String, + Integer, + DateTime, + ForeignKey, + Enum as SAEnum, +) +from sqlalchemy.orm import relationship, validates +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.ext.declarative import declarative_base +from database.db import Base, engine +from schemas.status import Status +from schemas.status_info import StatusInfo +from enum import Enum as PyEnum +import uuid +from datetime import datetime +import re + +class Device(Base): + __tablename__ = 'device' + __table_args__ = {"extend_existing": True} + + id = Column(Integer, primary_key=True) + phone_number = Column(String, nullable=True) + network_access_identifier = Column(String, nullable=True) + ipv4_public_address = Column(String, nullable=True) + ipv4_private_address = Column(String, nullable=True) + ipv4_public_port = Column(Integer, nullable=True) + ipv6_address = Column(String, nullable=True) + + provisioning = relationship('Provisioning', back_populates='device') + + +class Provisioning(Base): + __tablename__ = 'provisioning' + __table_args__ = {"extend_existing": True} + + id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4())) + qos_profile = Column(String(256), nullable=False) + sink = Column(String, nullable=True) + device_id = Column(Integer, ForeignKey('device.id'), nullable=True) + sink_credential = Column(String, nullable=True, default=None) + started_at = Column(DateTime, default=datetime.utcnow) + status = Column(SAEnum(Status), nullable=False, default=Status.REQUESTED) + status_info = Column(SAEnum(StatusInfo), nullable=True) + + device = relationship('Device', back_populates='provisioning') + diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/database/crud.py b/QoDProvisioning/QoDProvisioningAPI/API/src/database/crud.py new file mode 100644 index 0000000..a7d0434 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/database/crud.py @@ -0,0 +1,454 @@ +# -*- coding: utf-8 -*- +# @Authors: +# Eduardo Santos (eduardosantoshf@av.it.pt) +# Rafael Direito (rdireito@av.it.pt) +# @Organization: +# Instituto de Telecomunicações, Aveiro (ITAv) +# Aveiro, Portugal +# @Date: +# December 2024 + +from sqlalchemy.orm import Session +from database.db import SessionLocal +from sqlalchemy.exc import SQLAlchemyError +from typing import List +from datetime import datetime +from fastapi import HTTPException + +from schemas.create_provisioning import CreateProvisioning +from database.base_models import Provisioning, Device +from schemas.provisioning_info import ProvisioningInfo +from schemas.status import Status +from schemas.status_changed import StatusChanged +from schemas.status_info import StatusInfo +from schemas.retrieve_provisioning_by_device import RetrieveProvisioningByDevice +from config import Config + +# Set up logging +logger = Config.setup_logging() + + +def retrieve_fields_to_check(device: Device) -> list: + """ + Retrieves the list of fields to check for a given device. + + Args: + device: The device object that contains the fields to be checked. + + Returns: + A list of tuples, each containing the field name and its + corresponding value. + """ + return [ + ("phone_number", device.phone_number), + ( + "ipv4_public_address", + device.ipv4_address.public_address if device.ipv4_address else None + ), + ( + "ipv4_private_address", + device.ipv4_address.private_address if device.ipv4_address else None + ), + ( + "ipv4_public_port", + device.ipv4_address.public_port if device.ipv4_address else None + ), + ("ipv6_address", device.ipv6_address), + ("network_access_identifier", device.network_access_identifier) + ] + + +def find_existing_device(db: Session, fields_to_check: list) -> Device: + """ + Find an existing device based on the fields provided. + + Args: + db: Database session. + fields_to_check: List of tuples with field names and values. + + Returns: + The existing device if found, None otherwise. + """ + for field, value in fields_to_check: + if value: # Only search if the field has a value + existing_device = db.query(Device).filter_by(**{field: value})\ + .first() + + if existing_device: + logger.debug(f"Existing device found: {existing_device}") + + return existing_device + return None + +def validate_device_fields(create_provisioning): + """ + Validates the fields in the device object and assigns them to variables. + + Args: + create_provisioning: The provisioning object containing the + device to validate. + + Returns: + A dictionary containing the validated fields. + """ + device = create_provisioning.device + + # Validate phone_number and network_access_identifier + phone_number = device.phone_number if device.phone_number else None + network_access_identifier = device.network_access_identifier \ + if device.network_access_identifier else None + + # Validate ipv4_address and its subfields + ipv4_address = device.ipv4_address + if ipv4_address: + ipv4_public_address = ipv4_address.public_address \ + if ipv4_address.public_address else None + ipv4_private_address = ipv4_address.private_address \ + if ipv4_address.private_address else None + ipv4_public_port = ipv4_address.public_port \ + if ipv4_address.public_port else None + else: + ipv4_public_address = None + ipv4_private_address = None + ipv4_public_port = None + + # Validate ipv6_address + ipv6_address = device.ipv6_address if device.ipv6_address else None + + # Return all the validated fields in a dictionary + return { + 'phone_number': phone_number, + 'network_access_identifier': network_access_identifier, + 'ipv4_public_address': ipv4_public_address, + 'ipv4_private_address': ipv4_private_address, + 'ipv4_public_port': ipv4_public_port, + 'ipv6_address': ipv6_address + } + + +def create_provisioning( + db: Session, create_provisioning: CreateProvisioning + ) -> Provisioning: + """ + Creates a new provisioning in the database. + + Args: + db: Database session. + create_provisioning: The data needed to create the provisioning. + + Returns: + The created Provisioning object. + """ + try: + logger.debug(f"Received provisioning data: {create_provisioning}\n") + + device = create_provisioning.device + + fields_to_check = retrieve_fields_to_check(device) + + # Find an existing device if any field matches + existing_device = find_existing_device(db, fields_to_check) + + # If device exists, check for field differences + if existing_device: + # Compare provided fields with the existing device fields + differences = [ + (field, value, getattr(existing_device, field) != value) + for field, value in fields_to_check + ] + + # Check if there's at least one difference, + # and whether a new field is being added + new_field_added = False + for field, value, differs in differences: + if differs: + existing_value = getattr(existing_device, field) + # Field doesn't exist in the existing device + if existing_value is None: + logger.debug( + f"Adding new field {field} to existing device." + ) + setattr(existing_device, field, value) + new_field_added = True + else: + # If any field differs, raise a conflict + logger.debug( + f"Device already exists, but fields differ: {field}" + ) + raise HTTPException( + status_code=409, + detail="Device already exists, but fields differ." + ) + + # If no differences found, reuse the existing device + new_device = existing_device + else: + # Validate fields + validated_fields = validate_device_fields(create_provisioning) + + # Create a new device instance using the validated fields + new_device = Device( + phone_number=validated_fields['phone_number'], + network_access_identifier=validated_fields[ + 'network_access_identifier' + ], + ipv4_public_address=validated_fields['ipv4_public_address'], + ipv4_private_address=validated_fields['ipv4_private_address'], + ipv4_public_port=validated_fields['ipv4_public_port'], + ipv6_address=validated_fields['ipv6_address'] + ) + + # Add the new device to the session + db.add(new_device) + db.commit() + db.refresh(new_device) + + # Create a new provisioning instance + new_provisioning = Provisioning( + qos_profile=create_provisioning.qos_profile, + sink=create_provisioning.sink, + device_id=new_device.id, + sink_credential= \ + create_provisioning.sink_credential.credential_type + if create_provisioning.sink_credential + else None + ) + + # Add the new provisioning to the session + db.add(new_provisioning) + db.commit() + db.refresh(new_provisioning) + + return new_provisioning + + except SQLAlchemyError as e: + db.rollback() + logger.error(f"Error creating provisioning: {e}") + raise ValueError(f"Error creating provisioning: {e}") + +def get_all_provisionings(db: Session, provisioning_id: str) -> Provisioning: + """ + Retrieves all provisioning records from the database. + + Args: + db: Database session. + provisioning_id: The ID of the provisioning to query. Although it's + passed, it is not used in the query, and all provisionings are returned. + + Returns: + A list of all provisioning records. + """ + return db.query(Provisioning).all() + +def update_provisioning_by_id( + db: Session, provisioning_id: str, provisioning_status: str, + provisioning_timestamp: str) -> tuple[Provisioning, Device]: + """ + Updates the status and timestamp of a provisioning record by its ID. + + Args: + db: Database session. + provisioning_id: The ID of the provisioning to update. + provisioning_status: The new status for the provisioning. + provisioning_timestamp: The timestamp when the provisioning started. + + Returns: + A tuple containing the updated Provisioning object and the associated + Device object. + + Raises: + HTTPException: If the fields of the existing device differ during an + update. + """ + provisioning, device = get_provisioning_by_id(db, provisioning_id) + if provisioning: + provisioning.started_at = datetime.fromisoformat( + provisioning_timestamp.replace("Z", "+00:00") + ) + provisioning.status = provisioning_status + db.commit() + db.refresh(provisioning) + logger.debug( + f"Updated provisioning with id={provisioning_id} " + f"to status={provisioning_status}" + ) + return provisioning, device + + + +def get_provisioning_by_id( + db: Session, provisioning_id: str + ) -> tuple[Provisioning, Device]: + """ + Fetch a provisioning by its ID. + + Args: + db: Database session. + provisioning_id: The ID of the provisioning. + + Returns: + The ProvisioningInfo object or None if not found. + """ + try: + logger.debug(f"Received provisioning ID: {provisioning_id}\n") + + # Check if the provisioning exists + provisioning = db.query(Provisioning).filter_by(id=provisioning_id)\ + .first() + + if provisioning: + device = db.query(Device).filter_by(id=provisioning.device_id)\ + .first() + + return provisioning, device + + else: + logger.debug(f"Provisioning with ID {provisioning_id} not found.\n") + raise HTTPException( + status_code=404, + detail=f"Provisioning with ID {provisioning_id} not found." + ) + + except SQLAlchemyError as e: + db.rollback() + logger.error(f"Error fetching provisioning by ID: {e}") + raise ValueError(f"Error fetching provisioning by ID: {e}") + + +def get_provisioning_by_device( + db: Session, + retrieve_provisioning_by_device: RetrieveProvisioningByDevice + ) -> tuple[Provisioning, Device]: + """ + Fetch a provisioning by device ID. + + Args: + db: Database session. + retrieve_provisioning_by_device: The data needed to retrieve the + provisioning. + + Returns: + The ProvisioningInfo object or None if not found. + """ + from fastapi import HTTPException + + try: + logger.debug( + f"Received retrieve provisioning by device data: " + f"{retrieve_provisioning_by_device}\n" + ) + + # Validate if any field to search for is provided + device = retrieve_provisioning_by_device.device + if not ( + device.phone_number or + (device.ipv4_address and device.ipv4_address.public_address) or + (device.ipv4_address and device.ipv4_address.private_address) or + (device.ipv4_address and device.ipv4_address.public_port) or + device.ipv6_address or + device.network_access_identifier + ): + raise HTTPException( + status_code=400, + detail="No search fields provided to retrieve the device." + ) + + fields_to_check = retrieve_fields_to_check(device) + + # Iterate through the fields to check for the provided values and query the DB + for field, value in fields_to_check: + if value: # Only search if the field has a value + existing_device = db.query(Device).filter_by(**{field: value})\ + .first() + + logger.debug( + f"Existing device found for field {field}: " + f"{existing_device}" + ) + + if existing_device: # Stop searching as soon as we find a match + break + + # If a device was found, we need to check all fields to ensure they match + if existing_device: + # Compare all fields to check if any field differs + differences = [] + for field, value in fields_to_check: + if value and getattr(existing_device, field) != value: + differences.append((field, value)) + + # If any field differs, raise a conflict (not found) + if differences: + logger.debug(f"Device fields differ: {differences}") + raise HTTPException( + status_code=404, + detail="Device found, but fields differ." + ) + + logger.debug( + "Device found and fields match, proceeding with provisioning." + ) + provisioning = db.query(Provisioning)\ + .filter_by(device_id=existing_device.id).first() + + if provisioning: + return provisioning, existing_device + else: + logger.debug("Device not found.\n") + raise HTTPException(status_code=404, detail="Device not found.") + + except SQLAlchemyError as e: + db.rollback() + logger.error(f"Error fetching provisioning by device: {e}") + raise ValueError(f"Error fetching provisioning by device: {e}") + + +def delete_provisioning( + db: Session, provisioning_id: str + ) -> tuple[Provisioning, Device]: + """ + Deletes a provisioning (marks it as unavailable or removes it). + + Args: + db: Database session. + provisioning_id: The ID of the provisioning to delete. + """ + try: + logger.debug( + f"Received to-be-deleted provisioning's id: {provisioning_id}\n" + ) + + # Check if the provisioning exists + provisioning = db.query(Provisioning).filter_by(id=provisioning_id)\ + .first() + + if not provisioning: + logger.debug(f"Provisioning with ID {provisioning_id} not found.\n") + raise HTTPException( + status_code=404, + detail=f"Provisioning with ID {provisioning_id} not found." + ) + + # Check if the device already exists + related_device = db.query(Device).filter_by(id=provisioning.device_id)\ + .first() + + if related_device: + db.delete(provisioning) + db.commit() + + logger.debug( + f"Provisioning with ID {provisioning_id} has been deleted.\n" + ) + + return provisioning, related_device + + else: + logger.debug( + f"Provisioning with ID {provisioning_id} not found.\n" + ) + + except SQLAlchemyError as e: + db.rollback() + logger.error(f"Error deleting provisioning: {e}") + raise ValueError(f"Error deleting provisioning: {e}") \ No newline at end of file diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/database/db.py b/QoDProvisioning/QoDProvisioningAPI/API/src/database/db.py new file mode 100644 index 0000000..e1cc740 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/database/db.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# @Authors: +# Eduardo Santos (eduardosantoshf@av.it.pt) +# Rafael Direito (rdireito@av.it.pt) +# @Organization: +# Instituto de Telecomunicações, Aveiro (ITAv) +# Aveiro, Portugal +# @Date: +# December 2024 + +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import Session +import os +from config import Config + +import logging + +logger = Config.setup_logging() + +sqlalchemy_logger = logging.getLogger("sqlalchemy.engine") +sqlalchemy_logger.setLevel("WARNING") + +for handler in logger.handlers: + sqlalchemy_logger.addHandler(handler) + +sqlalchemy_logger.propagate = True + +# SQLite database URL +SQLALCHEMY_DATABASE_URL = f"sqlite:///{Config.db_path}" + +# Create the SQLAlchemy engine +engine = create_engine( + SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} +) + +# Session and Base for models +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) +Base = declarative_base() + +# Initialize the database +def init_db(): + import database.base_models + Base.metadata.create_all(bind=engine) + +# Dependency for database session +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/main.py b/QoDProvisioning/QoDProvisioningAPI/API/src/main.py new file mode 100644 index 0000000..e2a6fce --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/main.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +# @Authors: +# Eduardo Santos (eduardosantoshf@av.it.pt) +# Rafael Direito (rdireito@av.it.pt) +# @Organization: +# Instituto de Telecomunicações, Aveiro (ITAv) +# Aveiro, Portugal +# @Date: +# December 2024 + +""" + QoD Provisioning API + + The Quality-On-Demand (QoD) Provisioning API offers a programmable interface for developers to request the assignment of a certain QoS Profile to a certain device, indefinitely. This API sets up the configuration in the network so the requested QoS profile is applied to an specified device, at any time while the provisioning is available. The device traffic will be treated with a certain QoS profile by the network whenever the device is connected to the network, until the provisioning is deleted. # Relevant terms and definitions * **QoS profiles and QoS profile labels**: Latency, throughput or priority requirements of the application mapped to relevant QoS profile values. The set of QoS Profiles that a network operator is offering may be retrieved via the `qos-profiles` API (cf. https://github.com/camaraproject/QualityOnDemand/) or will be agreed during the onboarding with the API service provider. * **Identifier for the device**: At least one identifier for the device (user equipment) out of four options: IPv4 address, IPv6 address, Phone number, or Network Access Identifier assigned by the network operator for the device, at the request time. After the provisioning request is accepted, the device may get different IP addresses, but the provisioning will still apply to the device that was identified during the request process. Note: Network Access Identifier is defined for future use and will not be supported with v0.1 of the API. * **Notification URL and token**: Developers may provide a callback URL (`sink`) on which notifications about all status change events (eg. provisioning termination) can be received from the service provider. This is an optional parameter. The notification will be sent as a CloudEvent compliant message. If `sink` is included, it is RECOMMENDED for the client to provide as well the `sinkCredential` property to protect the notification endpoint. In the current version,`sinkCredential.credentialType` MUST be set to `ACCESSTOKEN` if provided. # Resources and Operations overview The API defines four operations: - An operation to setup a new QoD provisioning for a given device. - An operation to get the information about a specific QoD provisioning, identified by its `provisioningId`. - An operation to get the QoD provisioning for a given device. - An operation to terminate a QoD provisioning, identified by its `provisioningId`. # Authorization and Authentication [Camara Security and Interoperability Profile](https://github.com/camaraproject/IdentityAndConsentManagement/blob/main/documentation/CAMARA-Security-Interoperability.md) provides details on how a client requests an access token. Which specific authorization flows are to be used will be determined during onboarding process, happening between the API Client and the Telco Operator exposing the API, taking into account the declared purpose for accessing the API, while also being subject to the prevailing legal framework dictated by local legislation. It is important to remark that in cases where personal user data is processed by the API, and users can exercise their rights through mechanisms such as opt-in and/or opt-out, the use of 3-legged access tokens becomes mandatory. This measure ensures that the API remains in strict compliance with user privacy preferences and regulatory obligations, upholding the principles of transparency and user-centric data control. # Identifying a device from the access token This specification defines the `device` object field as optional in API requests, specifically in cases where the API is accessed using a 3-legged access token, and the device can be uniquely identified by the token. This approach simplifies API usage for API consumers by relying on the device information associated with the access token used to invoke the API. ## Handling of device information: ### Optional device object for 3-legged tokens: - When using a 3-legged access token, the device associated with the access token must be considered as the device for the API request. This means that the device object is not required in the request, and if included it must identify the same device, therefore **it is recommended NOT to include it in these scenarios** to simplify the API usage and avoid additional validations. ### Validation mechanism: - The server will extract the device identification from the access token, if available. - If the API request additionally includes a `device` object when using a 3-legged access token, the API will validate that the device identifier provided matches the one associated with the access token. - If there is a mismatch, the API will respond with a 403 - INVALID_TOKEN_CONTEXT error, indicating that the device information in the request does not match the token. ### Error handling for unidentifiable devices: - If the `device` object is not included in the request and the device information cannot be derived from the 3-legged access token, the server will return a 422 `UNIDENTIFIABLE_DEVICE` error. ### Restrictions for tokens without an associated authenticated identifier: - For scenarios which do not have a single device identifier associated to the token during the authentication flow, e.g. 2-legged access tokens, the `device` object MUST be provided in the API request. This ensures that the device identification is explicit and valid for each API call made with these tokens. + + The version of the OpenAPI document: 0.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + +import asyncio +import json + +from fastapi import FastAPI +from sqlalchemy.orm import Session + +from routers.qod_provisioning_router import router as QoDProvisioningApiRouter +from routers.osl import router as OSLRouter + +from database.db import init_db, get_db +from aux.service_event_manager.service_event_manager import ServiceEventManager +from aux.service_event_manager.camara_results_processor import CamaraResultsProcessor +from config import Config + +# Set up logging +logger = Config.setup_logging() + +app = FastAPI( + title="QoD Provisioning API", + description=( + "The Quality-On-Demand (QoD) Provisioning API offers a programmable " + "interface for developers to request the assignment of a certain QoS " + "Profile to a certain device, indefinitely.\n\n" + + "This API sets up the configuration in the network so the requested QoS profile is applied to a specified device, at any time while the provisioning is available. The device traffic will be treated with a certain QoS profile by the network whenever the device is connected to the network, until the provisioning is deleted.\n\n" + + "## Relevant terms and definitions\n\n" + + "* **QoS profiles and QoS profile labels**: Latency, throughput or priority requirements of the application mapped to relevant QoS profile values. The set of QoS Profiles that a network operator is offering may be retrieved via the `qos-profiles` API (cf. https://github.com/camaraproject/QualityOnDemand/) or will be agreed during the onboarding with the API service provider.\n\n" + + "* **Identifier for the device**: At least one identifier for the device (user equipment) out of four options: IPv4 address, IPv6 address, Phone number, or Network Access Identifier assigned by the network operator for the device, at the request time. After the provisioning request is accepted, the device may get different IP addresses, but the provisioning will still apply to the device that was identified during the request process. Note: Network Access Identifier is defined for future use and will not be supported with v0.1 of the API.\n\n" + + "* **Notification URL and token**: Developers may provide a callback URL (`sink`) on which notifications about all status change events (eg. provisioning termination) can be received from the service provider. This is an optional parameter. The notification will be sent as a CloudEvent compliant message. If `sink` is included, it is RECOMMENDED for the client to provide as well the `sinkCredential` property to protect the notification endpoint. In the current version, `sinkCredential.credentialType` MUST be set to `ACCESSTOKEN` if provided.\n\n" + + "## Resources and Operations overview\n\n" + + "The API defines four operations:\n\n" + + "- An operation to setup a new QoD provisioning for a given device.\n\n" + "- An operation to get the information about a specific QoD provisioning, identified by its `provisioningId`.\n\n" + "- An operation to get the QoD provisioning for a given device.\n\n" + "- An operation to terminate a QoD provisioning, identified by its `provisioningId`.\n\n" + + "## Authorization and Authentication\n\n" + + "[Camara Security and Interoperability Profile](https://github.com/camaraproject/IdentityAndConsentManagement/blob/main/documentation/CAMARA-Security-Interoperability.md) provides details on how a client requests an access token.\n\n" + + "Which specific authorization flows are to be used will be determined during onboarding process, happening between the API Client and the Telco Operator exposing the API, taking into account the declared purpose for accessing the API, while also being subject to the prevailing legal framework dictated by local legislation. It is important to remark that in cases where personal user data is processed by the API, and users can exercise their rights through mechanisms such as opt-in and/or opt-out, the use of 3-legged access tokens becomes mandatory. This measure ensures that the API remains in strict compliance with user privacy preferences and regulatory obligations, upholding the principles of transparency and user-centric data control.\n\n" + + "## Identifying a device from the access token\n\n" + + "This specification defines the `device` object field as optional in API requests, specifically in cases where the API is accessed using a 3-legged access token, and the device can be uniquely identified by the token. This approach simplifies API usage for API consumers by relying on the device information associated with the access token used to invoke the API.\n\n" + + "### Handling of device information:\n\n" + + "#### Optional device object for 3-legged tokens:\n\n" + + "- When using a 3-legged access token, the device associated with the access token must be considered as the device for the API request. This means that the device object is not required in the request, and if included it must identify the same device, therefore **it is recommended NOT to include it in these scenarios** to simplify the API usage and avoid additional validations.\n\n" + + "#### Validation mechanism:\n\n" + + "- The server will extract the device identification from the access token, if available.\n\n" + "- If the API request additionally includes a `device` object when using a 3-legged access token, the API will validate that the device identifier provided matches the one associated with the access token.\n\n" + "- If there is a mismatch, the API will respond with a 403 - INVALID_TOKEN_CONTEXT error, indicating that the device information in the request does not match the token.\n\n" + + "#### Error handling for unidentifiable devices:\n\n" + + "- If the `device` object is not included in the request and the device information cannot be derived from the 3-legged access token, the server will return a 422 `UNIDENTIFIABLE_DEVICE` error.\n\n" + + "#### Restrictions for tokens without an associated authenticated identifier:\n\n" + + "- For scenarios which do not have a single device identifier associated to the token during the authentication flow, e.g. 2-legged access tokens, the `device` object MUST be provided in the API request. This ensures that the device identification is explicit and valid for each API call made with these tokens.\n\n" + ), + version="0.1.0", +) + +app.include_router(QoDProvisioningApiRouter) +app.include_router(OSLRouter) + +@app.on_event("startup") +async def startup_event(): + """ + Event triggered when the application starts. + Initializes the database tables. + """ + init_db() + + # Initialize the ServiceEventManager and subscribe to OSL events topic + ServiceEventManager.initialize() + ServiceEventManager.subscribe_to_events() + + # Initialize the CamaraResultsProcessor with the queue and start processing + camara_processor = CamaraResultsProcessor( + ServiceEventManager.camara_results_queue + ) + asyncio.create_task(camara_processor.process_results()) diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/routers/__init__.py b/QoDProvisioning/QoDProvisioningAPI/API/src/routers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/routers/osl.py b/QoDProvisioning/QoDProvisioningAPI/API/src/routers/osl.py new file mode 100644 index 0000000..d4e528f --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/routers/osl.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +# @Authors: +# Eduardo Santos (eduardosantoshf@av.it.pt) +# Rafael Direito (rdireito@av.it.pt) +# @Organization: +# Instituto de Telecomunicações, Aveiro (ITAv) +# Aveiro, Portugal +# @Date: +# December 2024 + +from typing import Dict, List # noqa: F401 +import importlib +import pkgutil + +from fastapi import ( # noqa: F401 + APIRouter, + Body, + Cookie, + Depends, + Form, + Header, + HTTPException, + Path, + Query, + Response, + Security, + status, +) + +from pydantic import Field, StrictStr +from typing import Any, Optional +from typing_extensions import Annotated +from sqlalchemy.orm import Session +from database.db import get_db +from fastapi import HTTPException, Depends +from schemas.create_provisioning import CreateProvisioning +from schemas.error_info import ErrorInfo +from schemas.provisioning_info import ProvisioningInfo +from schemas.retrieve_provisioning_by_device import RetrieveProvisioningByDevice +from schemas.status import Status +from schemas.status_info import StatusInfo +from database import crud +from aux import mappers +from datetime import datetime +import logging +from aux.service_event_manager.service_event_manager import ServiceEventManager +import json +from config import Config +from aux.constants import Constants +# Set up logging +logger = Config.setup_logging() + +router = APIRouter() + + +@router.get( + "/osl/current-camara-results", + tags=["OSL"], + summary=( + "This endpoint is only used when this service is deployed " + "with OSL. It is used to get a list of the camaraResults " + "processed by the API" + ), + response_model_by_alias=True, + status_code=200 +) +async def current_camara_results() -> List[ProvisioningInfo]: + return Constants.processed_camara_results + diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/routers/qod_provisioning_router.py b/QoDProvisioning/QoDProvisioningAPI/API/src/routers/qod_provisioning_router.py new file mode 100644 index 0000000..f199afb --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/routers/qod_provisioning_router.py @@ -0,0 +1,364 @@ +# -*- coding: utf-8 -*- +# @Authors: +# Eduardo Santos (eduardosantoshf@av.it.pt) +# Rafael Direito (rdireito@av.it.pt) +# @Organization: +# Instituto de Telecomunicações, Aveiro (ITAv) +# Aveiro, Portugal +# @Date: +# December 2024 + +from typing import Dict, List # noqa: F401 +import importlib +import pkgutil + +from fastapi import ( # noqa: F401 + APIRouter, + Body, + Cookie, + Depends, + Form, + Header, + HTTPException, + Path, + Query, + Response, + Security, + status, +) + +from schemas.extra_models import TokenModel # noqa: F401 +from pydantic import Field, StrictStr +from typing import Any, Optional +from typing_extensions import Annotated +from sqlalchemy.orm import Session +from database.db import get_db +from fastapi import HTTPException, Depends +from schemas.create_provisioning import CreateProvisioning +from schemas.error_info import ErrorInfo +from schemas.provisioning_info import ProvisioningInfo +from schemas.retrieve_provisioning_by_device import RetrieveProvisioningByDevice +from schemas.status import Status +from schemas.status_info import StatusInfo +from database import crud +from aux import mappers +from datetime import datetime +import logging +from aux.service_event_manager.service_event_manager import ServiceEventManager +import json +from config import Config + +# Set up logging +logger = Config.setup_logging() + +router = APIRouter() + + +@router.post( + "/device-qos", + responses={ + 201: {"model": ProvisioningInfo, "description": "Provisioning created"}, + 400: {"model": ErrorInfo, "description": + "Bad Request with additional errors for implicit notifications"}, + 401: {"model": ErrorInfo, "description": "Unauthorized"}, + 403: {"model": ErrorInfo, "description": "Forbidden"}, + 404: {"model": ErrorInfo, "description": "Not found"}, + 409: {"model": ErrorInfo, "description": "Provisioning conflict"}, + 422: {"model": ErrorInfo, "description": "Unprocessable entity"}, + 429: {"model": ErrorInfo, "description": "Too Many Requests"}, + 500: {"model": ErrorInfo, "description": "Internal server error"}, + 503: {"model": ErrorInfo, "description": "Service unavailable"}, + }, + tags=["QoD Provisioning"], + summary="Sets a new provisioning of QoS for a device", + response_model_by_alias=True, + status_code=201 # Default status code for successful creation +) +async def create_provisioning( + create_provisioning: CreateProvisioning, + x_correlator: Annotated[ + Optional[StrictStr], + Field(description="Correlation id for the different services") + ] = Header(None, description="Correlation id for the different services"), + db_session: Session = Depends(get_db) +) -> ProvisioningInfo: + try: + # Call the CRUD function to create the provisioning in the database + new_provisioning = crud.create_provisioning( + db_session, + create_provisioning + ) + + ServiceEventManager.update_service({ + "serviceCharacteristic": mappers.map_service_characteristics( + new_provisioning, + "CREATE" + ) + }) + + return ProvisioningInfo( + provisioning_id=new_provisioning.id, + device=mappers.map_device_to_dict(new_provisioning.device), + qos_profile=new_provisioning.qos_profile, + sink=new_provisioning.sink, + sink_credential={ + "credential_type": new_provisioning.sink_credential + }, + started_at=datetime.utcnow(), + status=new_provisioning.status, + status_info=new_provisioning.status_info + ) + + except HTTPException: + raise + except Exception as e: + # If an error occurs, roll back and raise an HTTPException + raise HTTPException(status_code=500, detail=str(e)) + + +@router.delete( + "/device-qos/{provisioningId}", + responses={ + 204: {"description": "Provisioning deleted"}, + 202: { + "model": ProvisioningInfo, + "description": ( + "Deletion request accepted to be processed. " + "It applies for an async deletion process. " + "`status` in the response will be `AVAILABLE` " + "with `statusInfo` set to `DELETE_REQUESTED`." + ) + }, + 400: {"model": ErrorInfo, "description": "Bad Request"}, + 401: {"model": ErrorInfo, "description": "Unauthorized"}, + 403: {"model": ErrorInfo, "description": "Forbidden"}, + 404: {"model": ErrorInfo, "description": "Not found"}, + 429: {"model": ErrorInfo, "description": "Too Many Requests"}, + 500: {"model": ErrorInfo, "description": "Internal server error"}, + 503: {"model": ErrorInfo, "description": "Service unavailable"}, + }, + tags=["QoD Provisioning"], + summary="Deletes a QoD provisioning", + response_model_by_alias=True, +) +async def delete_provisioning( + provisioningId: Annotated[ + StrictStr, + Field(description=( + "Provisioning ID that was obtained from the createProvision " + "operation" + )) + ] = Path(..., description=( + "Provisioning ID that was obtained from the createProvision " + "operation" + )), + x_correlator: Annotated[ + Optional[StrictStr], + Field(description="Correlation id for the different services") + ] = Header(None, description="Correlation id for the different services"), + db_session: Session = Depends(get_db), +) -> ProvisioningInfo: + """ + Release resources related to QoS provisioning. + If the notification callback is provided and the provisioning status was + `AVAILABLE`, when the deletion is completed, the client will + receive in addition to the response a + `PROVISIONING_STATUS_CHANGED` event with - `status` + as `UNAVAILABLE` and - `statusInfo` as + `DELETE_REQUESTED` There will be no notification event if the + `status` was already `UNAVAILABLE`. + **NOTES:** - The access token may be either 2-legged or 3-legged. - + If a 3-legged access token is used, the end user (and device) associated + with the QoD provisioning must also be associated with the access token. - + The QoD provisioning must have been created by the same API client given in + the access token. + """ + try: + # Call the CRUD function to create the provisioning in the database + provisioning, related_device = crud.delete_provisioning( + db_session, provisioningId + ) + + ServiceEventManager.update_service({ + "serviceCharacteristic": mappers.map_service_characteristics( + provisioning, + "DELETE" + ) + }) + + deleted_provisioning = ProvisioningInfo( + provisioning_id=str(provisioning.id), + device=mappers.map_device_to_dict(related_device), + qos_profile=provisioning.qos_profile, + sink=provisioning.sink, + sink_credential={ + "credential_type": provisioning.sink_credential + }, + started_at=provisioning.started_at, + status=provisioning.status, + status_info=StatusInfo.DELETE_REQUESTED + ) + + return deleted_provisioning + + except HTTPException: + # Allow 404 and other HTTPExceptions to propagate without modification + raise + + except Exception as e: + # If an error occurs, roll back and raise an HTTPException + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get( + "/device-qos/{provisioningId}", + responses={ + 200: { + "model": ProvisioningInfo, + "description": "Returns information about certain provisioning" + }, + 400: {"model": ErrorInfo, "description": "Bad Request"}, + 401: {"model": ErrorInfo, "description": "Unauthorized"}, + 403: {"model": ErrorInfo, "description": "Forbidden"}, + 404: {"model": ErrorInfo, "description": "Not found"}, + 429: {"model": ErrorInfo, "description": "Too Many Requests"}, + 500: {"model": ErrorInfo, "description": "Internal server error"}, + 503: {"model": ErrorInfo, "description": "Service unavailable"}, + }, + tags=["QoD Provisioning"], + summary="Get QoD provisioning information", + response_model_by_alias=True, +) + +async def get_provisioning_by_id( + provisioningId: Annotated[ + StrictStr, + Field(description=( + "Provisioning ID that was obtained from the createProvision " + "operation" + )) + ] = Path(..., description=( + "Provisioning ID that was obtained from the createProvision operation" + )), + x_correlator: Annotated[ + Optional[StrictStr], + Field(description="Correlation id for the different services") + ] = Header(None, description="Correlation id for the different services"), + db_session: Session = Depends(get_db) +) -> ProvisioningInfo: + try: + # Call the CRUD function to create the provisioning in the database + provisioning, device = crud.get_provisioning_by_id( + db_session, provisioningId + ) + + if provisioning.status_info: + provisioning_status_info = provisioning.status_info + else: + provisioning_status_info = None + + retrieved_provisioning = ProvisioningInfo( + provisioning_id=str(provisioning.id), + device=mappers.map_device_to_dict(device), + qos_profile=provisioning.qos_profile, + sink=provisioning.sink, + sink_credential={ + "credential_type": provisioning.sink_credential + }, + started_at=provisioning.started_at, + status=provisioning.status, + status_info=provisioning_status_info + ) + + return retrieved_provisioning + + except HTTPException: + # Allow 404 and other HTTPExceptions to propagate without modification + raise + + except Exception as e: + # If an error occurs, roll back and raise an HTTPException + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post( + "/retrieve-device-qos", + responses={ + 200: { + "model": ProvisioningInfo, + "description": ( + "Returns information about QoS provisioning for the device." + ) + }, + 400: {"model": ErrorInfo, "description": "Bad Request"}, + 401: {"model": ErrorInfo, "description": "Unauthorized"}, + 403: {"model": ErrorInfo, "description": "Forbidden"}, + 404: {"model": ErrorInfo, "description": "Not found"}, + 422: {"model": ErrorInfo, "description": "Unprocessable entity"}, + 429: {"model": ErrorInfo, "description": "Too Many Requests"}, + 500: {"model": ErrorInfo, "description": "Internal server error"}, + 503: {"model": ErrorInfo, "description": "Service unavailable"}, + }, + tags=["QoD Provisioning"], + summary="Gets the QoD provisioning for a device", + response_model_by_alias=True, +) +async def retrieve_provisioning_by_device( + retrieve_provisioning_by_device: Annotated[ + RetrieveProvisioningByDevice, + Field(description="Parameters to retrieve a provisioning by device") + ] = Body( + None, description="Parameters to retrieve a provisioning by device" + ), + x_correlator: Annotated[ + Optional[StrictStr], + Field(description="Correlation id for the different services") + ] = Header(None, description="Correlation id for the different services"), + db_session: Session = Depends(get_db) +) -> ProvisioningInfo: + """ + Retrieves the QoD provisioning for a device. **NOTES:** - The access token + may be either 2-legged or 3-legged. - If a 3-legged access token is used, + the end user (and device) associated with the QoD provisioning must also be + associated with the access token. In this case it is recommended NOT to + include the `device` parameter in the request (see \" + Handling of device information\" within the API description for + details). - If a 2-legged access token is used, the device parameter must + be provided and identify a device. - The QoD provisioning must have been + created by the same API client given in the access token. - If no + provisioning is found for the device, an error response 404 is returned with + code \"NOT_FOUND\". + """ + try: + # Call the CRUD function to create the provisioning in the database + provisioning, existing_device = crud.get_provisioning_by_device( + db_session, retrieve_provisioning_by_device + ) + + if provisioning.status_info: + provisioning_status_info = provisioning.status_info + else: + provisioning_status_info = None + + device_provisioning_info = ProvisioningInfo( + provisioning_id=str(provisioning.id), + device=mappers.map_device_to_dict(existing_device), + qos_profile=provisioning.qos_profile, + sink=provisioning.sink, + sink_credential={ + "credential_type": provisioning.sink_credential + }, + started_at=provisioning.started_at, + status=provisioning.status, + status_info=provisioning_status_info + ) + + return device_provisioning_info + + except HTTPException: + # Allow 404 and other HTTPExceptions to propagate without modification + raise + + except Exception as e: + # If an error occurs, roll back and raise an HTTPException + raise HTTPException(status_code=500, detail=str(e)) diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/__init__.py b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/access_token_credential.py b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/access_token_credential.py new file mode 100644 index 0000000..cf26517 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/access_token_credential.py @@ -0,0 +1,112 @@ +# coding: utf-8 + +""" + QoD Provisioning API + + The Quality-On-Demand (QoD) Provisioning API offers a programmable interface for developers to request the assignment of a certain QoS Profile to a certain device, indefinitely. This API sets up the configuration in the network so the requested QoS profile is applied to an specified device, at any time while the provisioning is available. The device traffic will be treated with a certain QoS profile by the network whenever the device is connected to the network, until the provisioning is deleted. # Relevant terms and definitions * **QoS profiles and QoS profile labels**: Latency, throughput or priority requirements of the application mapped to relevant QoS profile values. The set of QoS Profiles that a network operator is offering may be retrieved via the `qos-profiles` API (cf. https://github.com/camaraproject/QualityOnDemand/) or will be agreed during the onboarding with the API service provider. * **Identifier for the device**: At least one identifier for the device (user equipment) out of four options: IPv4 address, IPv6 address, Phone number, or Network Access Identifier assigned by the network operator for the device, at the request time. After the provisioning request is accepted, the device may get different IP addresses, but the provisioning will still apply to the device that was identified during the request process. Note: Network Access Identifier is defined for future use and will not be supported with v0.1 of the API. * **Notification URL and token**: Developers may provide a callback URL (`sink`) on which notifications about all status change events (eg. provisioning termination) can be received from the service provider. This is an optional parameter. The notification will be sent as a CloudEvent compliant message. If `sink` is included, it is RECOMMENDED for the client to provide as well the `sinkCredential` property to protect the notification endpoint. In the current version,`sinkCredential.credentialType` MUST be set to `ACCESSTOKEN` if provided. # Resources and Operations overview The API defines four operations: - An operation to setup a new QoD provisioning for a given device. - An operation to get the information about a specific QoD provisioning, identified by its `provisioningId`. - An operation to get the QoD provisioning for a given device. - An operation to terminate a QoD provisioning, identified by its `provisioningId`. # Authorization and Authentication [Camara Security and Interoperability Profile](https://github.com/camaraproject/IdentityAndConsentManagement/blob/main/documentation/CAMARA-Security-Interoperability.md) provides details on how a client requests an access token. Which specific authorization flows are to be used will be determined during onboarding process, happening between the API Client and the Telco Operator exposing the API, taking into account the declared purpose for accessing the API, while also being subject to the prevailing legal framework dictated by local legislation. It is important to remark that in cases where personal user data is processed by the API, and users can exercise their rights through mechanisms such as opt-in and/or opt-out, the use of 3-legged access tokens becomes mandatory. This measure ensures that the API remains in strict compliance with user privacy preferences and regulatory obligations, upholding the principles of transparency and user-centric data control. # Identifying a device from the access token This specification defines the `device` object field as optional in API requests, specifically in cases where the API is accessed using a 3-legged access token, and the device can be uniquely identified by the token. This approach simplifies API usage for API consumers by relying on the device information associated with the access token used to invoke the API. ## Handling of device information: ### Optional device object for 3-legged tokens: - When using a 3-legged access token, the device associated with the access token must be considered as the device for the API request. This means that the device object is not required in the request, and if included it must identify the same device, therefore **it is recommended NOT to include it in these scenarios** to simplify the API usage and avoid additional validations. ### Validation mechanism: - The server will extract the device identification from the access token, if available. - If the API request additionally includes a `device` object when using a 3-legged access token, the API will validate that the device identifier provided matches the one associated with the access token. - If there is a mismatch, the API will respond with a 403 - INVALID_TOKEN_CONTEXT error, indicating that the device information in the request does not match the token. ### Error handling for unidentifiable devices: - If the `device` object is not included in the request and the device information cannot be derived from the 3-legged access token, the server will return a 422 `UNIDENTIFIABLE_DEVICE` error. ### Restrictions for tokens without an associated authenticated identifier: - For scenarios which do not have a single device identifier associated to the token during the authentication flow, e.g. 2-legged access tokens, the `device` object MUST be provided in the API request. This ensures that the device identification is explicit and valid for each API call made with these tokens. + + The version of the OpenAPI document: 0.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + + + +from datetime import datetime +from pydantic import ConfigDict, Field, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List +from schemas.sink_credential import SinkCredential +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class AccessTokenCredential(SinkCredential): + """ + An access token credential. + """ # noqa: E501 + credential_type: StrictStr = Field(description="The type of the credential.", alias="credentialType") + access_token: StrictStr = Field(description="REQUIRED. An access token is a previously acquired token granting access to the target resource.", alias="accessToken") + access_token_expires_utc: datetime = Field(description="REQUIRED. An absolute UTC instant at which the token shall be considered expired.", alias="accessTokenExpiresUtc") + access_token_type: StrictStr = Field(description="REQUIRED. Type of the access token (See [OAuth 2.0](https://tools.ietf.org/html/rfc6749#section-7.1)).", alias="accessTokenType") + __properties: ClassVar[List[str]] = ["credentialType", "accessToken", "accessTokenExpiresUtc", "accessTokenType"] + + @field_validator('credential_type') + def credential_type_validate_enum(cls, value): + """Validates the enum""" + if value not in ('PLAIN', 'ACCESSTOKEN', 'REFRESHTOKEN',): + raise ValueError("must be one of enum values ('PLAIN', 'ACCESSTOKEN', 'REFRESHTOKEN')") + return value + + @field_validator('access_token_type') + def access_token_type_validate_enum(cls, value): + """Validates the enum""" + if value not in ('bearer',): + raise ValueError("must be one of enum values ('bearer')") + return value + + model_config = { + "populate_by_name": True, + "validate_assignment": True, + "protected_namespaces": (), + } + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of AccessTokenCredential from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Self: + """Create an instance of AccessTokenCredential from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "credentialType": obj.get("credentialType"), + "accessToken": obj.get("accessToken"), + "accessTokenExpiresUtc": obj.get("accessTokenExpiresUtc"), + "accessTokenType": obj.get("accessTokenType") + }) + return _obj + + diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/base_provisioning_info.py b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/base_provisioning_info.py new file mode 100644 index 0000000..7c5f2fd --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/base_provisioning_info.py @@ -0,0 +1,110 @@ +# coding: utf-8 + +""" + QoD Provisioning API + + The Quality-On-Demand (QoD) Provisioning API offers a programmable interface for developers to request the assignment of a certain QoS Profile to a certain device, indefinitely. This API sets up the configuration in the network so the requested QoS profile is applied to an specified device, at any time while the provisioning is available. The device traffic will be treated with a certain QoS profile by the network whenever the device is connected to the network, until the provisioning is deleted. # Relevant terms and definitions * **QoS profiles and QoS profile labels**: Latency, throughput or priority requirements of the application mapped to relevant QoS profile values. The set of QoS Profiles that a network operator is offering may be retrieved via the `qos-profiles` API (cf. https://github.com/camaraproject/QualityOnDemand/) or will be agreed during the onboarding with the API service provider. * **Identifier for the device**: At least one identifier for the device (user equipment) out of four options: IPv4 address, IPv6 address, Phone number, or Network Access Identifier assigned by the network operator for the device, at the request time. After the provisioning request is accepted, the device may get different IP addresses, but the provisioning will still apply to the device that was identified during the request process. Note: Network Access Identifier is defined for future use and will not be supported with v0.1 of the API. * **Notification URL and token**: Developers may provide a callback URL (`sink`) on which notifications about all status change events (eg. provisioning termination) can be received from the service provider. This is an optional parameter. The notification will be sent as a CloudEvent compliant message. If `sink` is included, it is RECOMMENDED for the client to provide as well the `sinkCredential` property to protect the notification endpoint. In the current version,`sinkCredential.credentialType` MUST be set to `ACCESSTOKEN` if provided. # Resources and Operations overview The API defines four operations: - An operation to setup a new QoD provisioning for a given device. - An operation to get the information about a specific QoD provisioning, identified by its `provisioningId`. - An operation to get the QoD provisioning for a given device. - An operation to terminate a QoD provisioning, identified by its `provisioningId`. # Authorization and Authentication [Camara Security and Interoperability Profile](https://github.com/camaraproject/IdentityAndConsentManagement/blob/main/documentation/CAMARA-Security-Interoperability.md) provides details on how a client requests an access token. Which specific authorization flows are to be used will be determined during onboarding process, happening between the API Client and the Telco Operator exposing the API, taking into account the declared purpose for accessing the API, while also being subject to the prevailing legal framework dictated by local legislation. It is important to remark that in cases where personal user data is processed by the API, and users can exercise their rights through mechanisms such as opt-in and/or opt-out, the use of 3-legged access tokens becomes mandatory. This measure ensures that the API remains in strict compliance with user privacy preferences and regulatory obligations, upholding the principles of transparency and user-centric data control. # Identifying a device from the access token This specification defines the `device` object field as optional in API requests, specifically in cases where the API is accessed using a 3-legged access token, and the device can be uniquely identified by the token. This approach simplifies API usage for API consumers by relying on the device information associated with the access token used to invoke the API. ## Handling of device information: ### Optional device object for 3-legged tokens: - When using a 3-legged access token, the device associated with the access token must be considered as the device for the API request. This means that the device object is not required in the request, and if included it must identify the same device, therefore **it is recommended NOT to include it in these scenarios** to simplify the API usage and avoid additional validations. ### Validation mechanism: - The server will extract the device identification from the access token, if available. - If the API request additionally includes a `device` object when using a 3-legged access token, the API will validate that the device identifier provided matches the one associated with the access token. - If there is a mismatch, the API will respond with a 403 - INVALID_TOKEN_CONTEXT error, indicating that the device information in the request does not match the token. ### Error handling for unidentifiable devices: - If the `device` object is not included in the request and the device information cannot be derived from the 3-legged access token, the server will return a 422 `UNIDENTIFIABLE_DEVICE` error. ### Restrictions for tokens without an associated authenticated identifier: - For scenarios which do not have a single device identifier associated to the token during the authentication flow, e.g. 2-legged access tokens, the `device` object MUST be provided in the API request. This ensures that the device identification is explicit and valid for each API call made with these tokens. + + The version of the OpenAPI document: 0.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + +from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List, Optional +from typing_extensions import Annotated +from schemas.device import Device +from schemas.sink_credential import SinkCredential +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class BaseProvisioningInfo(BaseModel): + """ + Common attributes of a QoD provisioning + """ # noqa: E501 + device: Optional[Device] = None + qos_profile: Annotated[str, Field(min_length=3, strict=True, max_length=256)] = Field(description="A unique name for identifying a specific QoS profile. This may follow different formats depending on the service providers implementation. Some options addresses: - A UUID style string - Support for predefined profiles QOS_S, QOS_M, QOS_L, and QOS_E - A searchable descriptive name The set of QoS Profiles that an operator is offering can be retrieved by means of the [QoS Profile API](link TBC). ", alias="qosProfile") + sink: Optional[StrictStr] = Field(default=None, description="The address to which events shall be delivered, using the HTTP protocol.") + sink_credential: Optional[SinkCredential] = Field(default=None, alias="sinkCredential") + __properties: ClassVar[List[str]] = ["device", "qosProfile", "sink", "sinkCredential"] + + @field_validator('qos_profile') + def qos_profile_validate_regular_expression(cls, value): + """Validates the regular expression""" + if not re.match(r"^[a-zA-Z0-9_.-]+$", value): + raise ValueError(r"must validate the regular expression /^[a-zA-Z0-9_.-]+$/") + return value + + model_config = { + "populate_by_name": True, + "validate_assignment": True, + "protected_namespaces": (), + } + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of BaseProvisioningInfo from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of device + if self.device: + _dict['device'] = self.device.to_dict() + # override the default output from pydantic by calling `to_dict()` of sink_credential + if self.sink_credential: + _dict['sinkCredential'] = self.sink_credential.to_dict() + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Self: + """Create an instance of BaseProvisioningInfo from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "device": Device.from_dict(obj.get("device")) if obj.get("device") is not None else None, + "qosProfile": obj.get("qosProfile"), + "sink": obj.get("sink"), + "sinkCredential": SinkCredential.from_dict(obj.get("sinkCredential")) if obj.get("sinkCredential") is not None else None + }) + return _obj + + diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/cloud_event.py b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/cloud_event.py new file mode 100644 index 0000000..b9ad221 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/cloud_event.py @@ -0,0 +1,138 @@ +# coding: utf-8 + +""" + QoD Provisioning API + + The Quality-On-Demand (QoD) Provisioning API offers a programmable interface for developers to request the assignment of a certain QoS Profile to a certain device, indefinitely. This API sets up the configuration in the network so the requested QoS profile is applied to an specified device, at any time while the provisioning is available. The device traffic will be treated with a certain QoS profile by the network whenever the device is connected to the network, until the provisioning is deleted. # Relevant terms and definitions * **QoS profiles and QoS profile labels**: Latency, throughput or priority requirements of the application mapped to relevant QoS profile values. The set of QoS Profiles that a network operator is offering may be retrieved via the `qos-profiles` API (cf. https://github.com/camaraproject/QualityOnDemand/) or will be agreed during the onboarding with the API service provider. * **Identifier for the device**: At least one identifier for the device (user equipment) out of four options: IPv4 address, IPv6 address, Phone number, or Network Access Identifier assigned by the network operator for the device, at the request time. After the provisioning request is accepted, the device may get different IP addresses, but the provisioning will still apply to the device that was identified during the request process. Note: Network Access Identifier is defined for future use and will not be supported with v0.1 of the API. * **Notification URL and token**: Developers may provide a callback URL (`sink`) on which notifications about all status change events (eg. provisioning termination) can be received from the service provider. This is an optional parameter. The notification will be sent as a CloudEvent compliant message. If `sink` is included, it is RECOMMENDED for the client to provide as well the `sinkCredential` property to protect the notification endpoint. In the current version,`sinkCredential.credentialType` MUST be set to `ACCESSTOKEN` if provided. # Resources and Operations overview The API defines four operations: - An operation to setup a new QoD provisioning for a given device. - An operation to get the information about a specific QoD provisioning, identified by its `provisioningId`. - An operation to get the QoD provisioning for a given device. - An operation to terminate a QoD provisioning, identified by its `provisioningId`. # Authorization and Authentication [Camara Security and Interoperability Profile](https://github.com/camaraproject/IdentityAndConsentManagement/blob/main/documentation/CAMARA-Security-Interoperability.md) provides details on how a client requests an access token. Which specific authorization flows are to be used will be determined during onboarding process, happening between the API Client and the Telco Operator exposing the API, taking into account the declared purpose for accessing the API, while also being subject to the prevailing legal framework dictated by local legislation. It is important to remark that in cases where personal user data is processed by the API, and users can exercise their rights through mechanisms such as opt-in and/or opt-out, the use of 3-legged access tokens becomes mandatory. This measure ensures that the API remains in strict compliance with user privacy preferences and regulatory obligations, upholding the principles of transparency and user-centric data control. # Identifying a device from the access token This specification defines the `device` object field as optional in API requests, specifically in cases where the API is accessed using a 3-legged access token, and the device can be uniquely identified by the token. This approach simplifies API usage for API consumers by relying on the device information associated with the access token used to invoke the API. ## Handling of device information: ### Optional device object for 3-legged tokens: - When using a 3-legged access token, the device associated with the access token must be considered as the device for the API request. This means that the device object is not required in the request, and if included it must identify the same device, therefore **it is recommended NOT to include it in these scenarios** to simplify the API usage and avoid additional validations. ### Validation mechanism: - The server will extract the device identification from the access token, if available. - If the API request additionally includes a `device` object when using a 3-legged access token, the API will validate that the device identifier provided matches the one associated with the access token. - If there is a mismatch, the API will respond with a 403 - INVALID_TOKEN_CONTEXT error, indicating that the device information in the request does not match the token. ### Error handling for unidentifiable devices: - If the `device` object is not included in the request and the device information cannot be derived from the 3-legged access token, the server will return a 422 `UNIDENTIFIABLE_DEVICE` error. ### Restrictions for tokens without an associated authenticated identifier: - For scenarios which do not have a single device identifier associated to the token during the authentication flow, e.g. 2-legged access tokens, the `device` object MUST be provided in the API request. This ensures that the device identification is explicit and valid for each API call made with these tokens. + + The version of the OpenAPI document: 0.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + + + +from datetime import datetime +from importlib import import_module +from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List, Optional, Union +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class CloudEvent(BaseModel): + """ + Event compliant with the CloudEvents specification + """ # noqa: E501 + id: StrictStr = Field(description="Identifier of this event, that must be unique in the source context.") + source: StrictStr = Field(description="Identifies the context in which an event happened in the specific Provider Implementation.") + type: StrictStr = Field(description="The type of the event.") + specversion: StrictStr = Field(description="Version of the specification to which this event conforms (must be 1.0 if it conforms to cloudevents 1.0.2 version)") + datacontenttype: Optional[StrictStr] = Field(default=None, description="media-type that describes the event payload encoding, must be \"application/json\" for CAMARA APIs") + data: Optional[Dict[str, Any]] = Field(default=None, description="Event notification details payload, which depends on the event type") + time: datetime = Field(description="Timestamp of when the occurrence happened. It must follow RFC 3339 ") + __properties: ClassVar[List[str]] = ["id", "source", "type", "specversion", "datacontenttype", "data", "time"] + + @field_validator('type') + def type_validate_enum(cls, value): + """Validates the enum""" + if value not in ('org.camaraproject.qod-provisioning.v0.status-changed',): + raise ValueError("must be one of enum values ('org.camaraproject.qod-provisioning.v0.status-changed')") + return value + + @field_validator('specversion') + def specversion_validate_enum(cls, value): + """Validates the enum""" + if value not in ('1.0',): + raise ValueError("must be one of enum values ('1.0')") + return value + + @field_validator('datacontenttype') + def datacontenttype_validate_enum(cls, value): + """Validates the enum""" + if value is None: + return value + + if value not in ('application/json',): + raise ValueError("must be one of enum values ('application/json')") + return value + + model_config = { + "populate_by_name": True, + "validate_assignment": True, + "protected_namespaces": (), + } + + + # JSON field name that stores the object type + __discriminator_property_name: ClassVar[List[str]] = 'type' + + # discriminator mappings + __discriminator_value_class_map: ClassVar[Dict[str, str]] = { + 'org.camaraproject.qod-provisioning.v0.status-changed': 'EventStatusChanged' + } + + @classmethod + def get_discriminator_value(cls, obj: Dict) -> str: + """Returns the discriminator value (object type) of the data""" + discriminator_value = obj[cls.__discriminator_property_name] + if discriminator_value: + return cls.__discriminator_value_class_map.get(discriminator_value) + else: + return None + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Union[Self]: + """Create an instance of CloudEvent from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Union[Self]: + """Create an instance of CloudEvent from a dict""" + # look up the object type based on discriminator mapping + object_type = cls.get_discriminator_value(obj) + if object_type: + klass = globals()[object_type] + return klass.from_dict(obj) + else: + raise ValueError("CloudEvent failed to lookup discriminator value from " + + json.dumps(obj) + ". Discriminator property name: " + cls.__discriminator_property_name + + ", mapping: " + json.dumps(cls.__discriminator_value_class_map)) + + diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/create_provisioning.py b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/create_provisioning.py new file mode 100644 index 0000000..f64ddb7 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/create_provisioning.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +# @Author: Eduardo Santos +# @Date: 2024-11-28 10:13:05 +# @Last Modified by: Eduardo Santos +# @Last Modified time: 2024-11-28 17:29:05 +# coding: utf-8 + +""" + QoD Provisioning API + + The Quality-On-Demand (QoD) Provisioning API offers a programmable interface for developers to request the assignment of a certain QoS Profile to a certain device, indefinitely. This API sets up the configuration in the network so the requested QoS profile is applied to an specified device, at any time while the provisioning is available. The device traffic will be treated with a certain QoS profile by the network whenever the device is connected to the network, until the provisioning is deleted. # Relevant terms and definitions * **QoS profiles and QoS profile labels**: Latency, throughput or priority requirements of the application mapped to relevant QoS profile values. The set of QoS Profiles that a network operator is offering may be retrieved via the `qos-profiles` API (cf. https://github.com/camaraproject/QualityOnDemand/) or will be agreed during the onboarding with the API service provider. * **Identifier for the device**: At least one identifier for the device (user equipment) out of four options: IPv4 address, IPv6 address, Phone number, or Network Access Identifier assigned by the network operator for the device, at the request time. After the provisioning request is accepted, the device may get different IP addresses, but the provisioning will still apply to the device that was identified during the request process. Note: Network Access Identifier is defined for future use and will not be supported with v0.1 of the API. * **Notification URL and token**: Developers may provide a callback URL (`sink`) on which notifications about all status change events (eg. provisioning termination) can be received from the service provider. This is an optional parameter. The notification will be sent as a CloudEvent compliant message. If `sink` is included, it is RECOMMENDED for the client to provide as well the `sinkCredential` property to protect the notification endpoint. In the current version,`sinkCredential.credentialType` MUST be set to `ACCESSTOKEN` if provided. # Resources and Operations overview The API defines four operations: - An operation to setup a new QoD provisioning for a given device. - An operation to get the information about a specific QoD provisioning, identified by its `provisioningId`. - An operation to get the QoD provisioning for a given device. - An operation to terminate a QoD provisioning, identified by its `provisioningId`. # Authorization and Authentication [Camara Security and Interoperability Profile](https://github.com/camaraproject/IdentityAndConsentManagement/blob/main/documentation/CAMARA-Security-Interoperability.md) provides details on how a client requests an access token. Which specific authorization flows are to be used will be determined during onboarding process, happening between the API Client and the Telco Operator exposing the API, taking into account the declared purpose for accessing the API, while also being subject to the prevailing legal framework dictated by local legislation. It is important to remark that in cases where personal user data is processed by the API, and users can exercise their rights through mechanisms such as opt-in and/or opt-out, the use of 3-legged access tokens becomes mandatory. This measure ensures that the API remains in strict compliance with user privacy preferences and regulatory obligations, upholding the principles of transparency and user-centric data control. # Identifying a device from the access token This specification defines the `device` object field as optional in API requests, specifically in cases where the API is accessed using a 3-legged access token, and the device can be uniquely identified by the token. This approach simplifies API usage for API consumers by relying on the device information associated with the access token used to invoke the API. ## Handling of device information: ### Optional device object for 3-legged tokens: - When using a 3-legged access token, the device associated with the access token must be considered as the device for the API request. This means that the device object is not required in the request, and if included it must identify the same device, therefore **it is recommended NOT to include it in these scenarios** to simplify the API usage and avoid additional validations. ### Validation mechanism: - The server will extract the device identification from the access token, if available. - If the API request additionally includes a `device` object when using a 3-legged access token, the API will validate that the device identifier provided matches the one associated with the access token. - If there is a mismatch, the API will respond with a 403 - INVALID_TOKEN_CONTEXT error, indicating that the device information in the request does not match the token. ### Error handling for unidentifiable devices: - If the `device` object is not included in the request and the device information cannot be derived from the 3-legged access token, the server will return a 422 `UNIDENTIFIABLE_DEVICE` error. ### Restrictions for tokens without an associated authenticated identifier: - For scenarios which do not have a single device identifier associated to the token during the authentication flow, e.g. 2-legged access tokens, the `device` object MUST be provided in the API request. This ensures that the device identification is explicit and valid for each API call made with these tokens. + + The version of the OpenAPI document: 0.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + + + +from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List, Optional +from typing_extensions import Annotated +from schemas.device import Device +from schemas.sink_credential import SinkCredential +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class CreateProvisioning(BaseModel): + """ + Attributes to request a new QoD provisioning + """ # noqa: E501 + device: Optional[Device] = None + qos_profile: Annotated[str, Field(min_length=3, strict=True, max_length=256)] = Field(description="A unique name for identifying a specific QoS profile. This may follow different formats depending on the service providers implementation. Some options addresses: - A UUID style string - Support for predefined profiles QOS_S, QOS_M, QOS_L, and QOS_E - A searchable descriptive name The set of QoS Profiles that an operator is offering can be retrieved by means of the [QoS Profile API](link TBC). ", alias="qosProfile") + sink: Optional[StrictStr] = Field(default=None, description="The address to which events shall be delivered, using the HTTP protocol.") + sink_credential: Optional[SinkCredential] = Field(default=None, alias="sinkCredential") + __properties: ClassVar[List[str]] = ["device", "qosProfile", "sink", "sinkCredential"] + + @field_validator('qos_profile') + def qos_profile_validate_regular_expression(cls, value): + """Validates the regular expression""" + if not re.match(r"^[a-zA-Z0-9_.-]+$", value): + raise ValueError(r"must validate the regular expression /^[a-zA-Z0-9_.-]+$/") + return value + + model_config = { + "populate_by_name": True, + "validate_assignment": True, + "protected_namespaces": (), + } + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of CreateProvisioning from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of device + if self.device: + _dict['device'] = self.device.to_dict() + # override the default output from pydantic by calling `to_dict()` of sink_credential + if self.sink_credential: + _dict['sinkCredential'] = self.sink_credential.to_dict() + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Self: + """Create an instance of CreateProvisioning from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "device": Device.from_dict(obj.get("device")) if obj.get("device") is not None else None, + "qosProfile": obj.get("qosProfile"), + "sink": obj.get("sink"), + "sinkCredential": SinkCredential.from_dict(obj.get("sinkCredential")) if obj.get("sinkCredential") is not None else None + }) + return _obj + + diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/device.py b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/device.py new file mode 100644 index 0000000..756a2fa --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/device.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +# @Author: Eduardo Santos +# @Date: 2024-11-28 10:13:05 +# @Last Modified by: Eduardo Santos +# @Last Modified time: 2024-11-28 12:55:53 +# coding: utf-8 + +""" + QoD Provisioning API + + The Quality-On-Demand (QoD) Provisioning API offers a programmable interface for developers to request the assignment of a certain QoS Profile to a certain device, indefinitely. This API sets up the configuration in the network so the requested QoS profile is applied to an specified device, at any time while the provisioning is available. The device traffic will be treated with a certain QoS profile by the network whenever the device is connected to the network, until the provisioning is deleted. # Relevant terms and definitions * **QoS profiles and QoS profile labels**: Latency, throughput or priority requirements of the application mapped to relevant QoS profile values. The set of QoS Profiles that a network operator is offering may be retrieved via the `qos-profiles` API (cf. https://github.com/camaraproject/QualityOnDemand/) or will be agreed during the onboarding with the API service provider. * **Identifier for the device**: At least one identifier for the device (user equipment) out of four options: IPv4 address, IPv6 address, Phone number, or Network Access Identifier assigned by the network operator for the device, at the request time. After the provisioning request is accepted, the device may get different IP addresses, but the provisioning will still apply to the device that was identified during the request process. Note: Network Access Identifier is defined for future use and will not be supported with v0.1 of the API. * **Notification URL and token**: Developers may provide a callback URL (`sink`) on which notifications about all status change events (eg. provisioning termination) can be received from the service provider. This is an optional parameter. The notification will be sent as a CloudEvent compliant message. If `sink` is included, it is RECOMMENDED for the client to provide as well the `sinkCredential` property to protect the notification endpoint. In the current version,`sinkCredential.credentialType` MUST be set to `ACCESSTOKEN` if provided. # Resources and Operations overview The API defines four operations: - An operation to setup a new QoD provisioning for a given device. - An operation to get the information about a specific QoD provisioning, identified by its `provisioningId`. - An operation to get the QoD provisioning for a given device. - An operation to terminate a QoD provisioning, identified by its `provisioningId`. # Authorization and Authentication [Camara Security and Interoperability Profile](https://github.com/camaraproject/IdentityAndConsentManagement/blob/main/documentation/CAMARA-Security-Interoperability.md) provides details on how a client requests an access token. Which specific authorization flows are to be used will be determined during onboarding process, happening between the API Client and the Telco Operator exposing the API, taking into account the declared purpose for accessing the API, while also being subject to the prevailing legal framework dictated by local legislation. It is important to remark that in cases where personal user data is processed by the API, and users can exercise their rights through mechanisms such as opt-in and/or opt-out, the use of 3-legged access tokens becomes mandatory. This measure ensures that the API remains in strict compliance with user privacy preferences and regulatory obligations, upholding the principles of transparency and user-centric data control. # Identifying a device from the access token This specification defines the `device` object field as optional in API requests, specifically in cases where the API is accessed using a 3-legged access token, and the device can be uniquely identified by the token. This approach simplifies API usage for API consumers by relying on the device information associated with the access token used to invoke the API. ## Handling of device information: ### Optional device object for 3-legged tokens: - When using a 3-legged access token, the device associated with the access token must be considered as the device for the API request. This means that the device object is not required in the request, and if included it must identify the same device, therefore **it is recommended NOT to include it in these scenarios** to simplify the API usage and avoid additional validations. ### Validation mechanism: - The server will extract the device identification from the access token, if available. - If the API request additionally includes a `device` object when using a 3-legged access token, the API will validate that the device identifier provided matches the one associated with the access token. - If there is a mismatch, the API will respond with a 403 - INVALID_TOKEN_CONTEXT error, indicating that the device information in the request does not match the token. ### Error handling for unidentifiable devices: - If the `device` object is not included in the request and the device information cannot be derived from the 3-legged access token, the server will return a 422 `UNIDENTIFIABLE_DEVICE` error. ### Restrictions for tokens without an associated authenticated identifier: - For scenarios which do not have a single device identifier associated to the token during the authentication flow, e.g. 2-legged access tokens, the `device` object MUST be provided in the API request. This ensures that the device identification is explicit and valid for each API call made with these tokens. + + The version of the OpenAPI document: 0.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + + + +from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List, Optional +from typing_extensions import Annotated +from schemas.device_ipv4_addr import DeviceIpv4Addr +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class Device(BaseModel): + """ + End-user equipment able to connect to the network. Examples of devices include smartphones or IoT sensors/actuators. The developer can choose to provide the below specified device identifiers: * `ipv4Address` * `ipv6Address` * `phoneNumber` * `networkAccessIdentifier` NOTE1: the network operator might support only a subset of these options. The API invoker can provide multiple identifiers to be compatible across different network operators. In this case the identifiers MUST belong to the same device. NOTE2: for the Commonalities release v0.4, we are enforcing that the networkAccessIdentifier is only part of the schema for future-proofing, and CAMARA does not currently allow its use. After the CAMARA meta-release work is concluded and the relevant issues are resolved, its use will need to be explicitly documented in the guidelines. + """ # noqa: E501 + phone_number: Optional[Annotated[str, Field(strict=True)]] = Field(default=None, description="A public identifier addressing a telephone subscription. In mobile networks it corresponds to the MSISDN (Mobile Station International Subscriber Directory Number). In order to be globally unique it has to be formatted in international format, according to E.164 standard, prefixed with '+'.", alias="phoneNumber") + network_access_identifier: Optional[StrictStr] = Field(default=None, description="A public identifier addressing a subscription in a mobile network. In 3GPP terminology, it corresponds to the GPSI formatted with the External Identifier ({Local Identifier}@{Domain Identifier}). Unlike the telephone number, the network access identifier is not subjected to portability ruling in force, and is individually managed by each operator.", alias="networkAccessIdentifier") + ipv4_address: Optional[DeviceIpv4Addr] = Field(default=None, alias="ipv4Address") + ipv6_address: Optional[StrictStr] = Field(default=None, description="The device should be identified by the observed IPv6 address, or by any single IPv6 address from within the subnet allocated to the device (e.g. adding ::0 to the /64 prefix). ", alias="ipv6Address") + __properties: ClassVar[List[str]] = ["phoneNumber", "networkAccessIdentifier", "ipv4Address", "ipv6Address"] + + @field_validator('phone_number') + def phone_number_validate_regular_expression(cls, value): + """Validates the regular expression""" + if value is None: + return value + + if not re.match(r"^\+[1-9][0-9]{4,14}$", value): + raise ValueError(r"must validate the regular expression /^\+[1-9][0-9]{4,14}$/") + return value + + model_config = { + "populate_by_name": True, + "validate_assignment": True, + "protected_namespaces": (), + } + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of Device from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of ipv4_address + if self.ipv4_address: + _dict['ipv4Address'] = self.ipv4_address.to_dict() + # set to None if ipv4_address (nullable) is None + # and model_fields_set contains the field + if self.ipv4_address is None and "ipv4_address" in self.model_fields_set: + _dict['ipv4Address'] = None + + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Self: + """Create an instance of Device from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "phoneNumber": obj.get("phoneNumber"), + "networkAccessIdentifier": obj.get("networkAccessIdentifier"), + "ipv4Address": DeviceIpv4Addr.from_dict(obj.get("ipv4Address")) if obj.get("ipv4Address") is not None else None, + "ipv6Address": obj.get("ipv6Address") + }) + return _obj + + diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/device_ipv4_addr.py b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/device_ipv4_addr.py new file mode 100644 index 0000000..34fc05f --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/device_ipv4_addr.py @@ -0,0 +1,95 @@ +# coding: utf-8 + +""" + QoD Provisioning API + + The Quality-On-Demand (QoD) Provisioning API offers a programmable interface for developers to request the assignment of a certain QoS Profile to a certain device, indefinitely. This API sets up the configuration in the network so the requested QoS profile is applied to an specified device, at any time while the provisioning is available. The device traffic will be treated with a certain QoS profile by the network whenever the device is connected to the network, until the provisioning is deleted. # Relevant terms and definitions * **QoS profiles and QoS profile labels**: Latency, throughput or priority requirements of the application mapped to relevant QoS profile values. The set of QoS Profiles that a network operator is offering may be retrieved via the `qos-profiles` API (cf. https://github.com/camaraproject/QualityOnDemand/) or will be agreed during the onboarding with the API service provider. * **Identifier for the device**: At least one identifier for the device (user equipment) out of four options: IPv4 address, IPv6 address, Phone number, or Network Access Identifier assigned by the network operator for the device, at the request time. After the provisioning request is accepted, the device may get different IP addresses, but the provisioning will still apply to the device that was identified during the request process. Note: Network Access Identifier is defined for future use and will not be supported with v0.1 of the API. * **Notification URL and token**: Developers may provide a callback URL (`sink`) on which notifications about all status change events (eg. provisioning termination) can be received from the service provider. This is an optional parameter. The notification will be sent as a CloudEvent compliant message. If `sink` is included, it is RECOMMENDED for the client to provide as well the `sinkCredential` property to protect the notification endpoint. In the current version,`sinkCredential.credentialType` MUST be set to `ACCESSTOKEN` if provided. # Resources and Operations overview The API defines four operations: - An operation to setup a new QoD provisioning for a given device. - An operation to get the information about a specific QoD provisioning, identified by its `provisioningId`. - An operation to get the QoD provisioning for a given device. - An operation to terminate a QoD provisioning, identified by its `provisioningId`. # Authorization and Authentication [Camara Security and Interoperability Profile](https://github.com/camaraproject/IdentityAndConsentManagement/blob/main/documentation/CAMARA-Security-Interoperability.md) provides details on how a client requests an access token. Which specific authorization flows are to be used will be determined during onboarding process, happening between the API Client and the Telco Operator exposing the API, taking into account the declared purpose for accessing the API, while also being subject to the prevailing legal framework dictated by local legislation. It is important to remark that in cases where personal user data is processed by the API, and users can exercise their rights through mechanisms such as opt-in and/or opt-out, the use of 3-legged access tokens becomes mandatory. This measure ensures that the API remains in strict compliance with user privacy preferences and regulatory obligations, upholding the principles of transparency and user-centric data control. # Identifying a device from the access token This specification defines the `device` object field as optional in API requests, specifically in cases where the API is accessed using a 3-legged access token, and the device can be uniquely identified by the token. This approach simplifies API usage for API consumers by relying on the device information associated with the access token used to invoke the API. ## Handling of device information: ### Optional device object for 3-legged tokens: - When using a 3-legged access token, the device associated with the access token must be considered as the device for the API request. This means that the device object is not required in the request, and if included it must identify the same device, therefore **it is recommended NOT to include it in these scenarios** to simplify the API usage and avoid additional validations. ### Validation mechanism: - The server will extract the device identification from the access token, if available. - If the API request additionally includes a `device` object when using a 3-legged access token, the API will validate that the device identifier provided matches the one associated with the access token. - If there is a mismatch, the API will respond with a 403 - INVALID_TOKEN_CONTEXT error, indicating that the device information in the request does not match the token. ### Error handling for unidentifiable devices: - If the `device` object is not included in the request and the device information cannot be derived from the 3-legged access token, the server will return a 422 `UNIDENTIFIABLE_DEVICE` error. ### Restrictions for tokens without an associated authenticated identifier: - For scenarios which do not have a single device identifier associated to the token during the authentication flow, e.g. 2-legged access tokens, the `device` object MUST be provided in the API request. This ensures that the device identification is explicit and valid for each API call made with these tokens. + + The version of the OpenAPI document: 0.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + + + +from pydantic import BaseModel, ConfigDict, Field, StrictStr +from typing import Any, ClassVar, Dict, List, Optional +from typing_extensions import Annotated +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class DeviceIpv4Addr(BaseModel): + """ + The device should be identified by either the public (observed) IP address and port as seen by the application server, or the private (local) and any public (observed) IP addresses in use by the device (this information can be obtained by various means, for example from some DNS servers). If the allocated and observed IP addresses are the same (i.e. NAT is not in use) then the same address should be specified for both publicAddress and privateAddress. If NAT64 is in use, the device should be identified by its publicAddress and publicPort, or separately by its allocated IPv6 address (field ipv6Address of the Device object) In all cases, publicAddress must be specified, along with at least one of either privateAddress or publicPort, dependent upon which is known. In general, mobile devices cannot be identified by their public IPv4 address alone. + """ # noqa: E501 + public_address: Optional[StrictStr] = Field(default=None, description="A single IPv4 address with no subnet mask", alias="publicAddress") + private_address: Optional[StrictStr] = Field(default=None, description="A single IPv4 address with no subnet mask", alias="privateAddress") + public_port: Optional[Annotated[int, Field(le=65535, strict=True, ge=0)]] = Field(default=None, description="TCP or UDP port number", alias="publicPort") + __properties: ClassVar[List[str]] = ["publicAddress", "privateAddress", "publicPort"] + + model_config = { + "populate_by_name": True, + "validate_assignment": True, + "protected_namespaces": (), + } + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of DeviceIpv4Addr from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Self: + """Create an instance of DeviceIpv4Addr from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "publicAddress": obj.get("publicAddress"), + "privateAddress": obj.get("privateAddress"), + "publicPort": obj.get("publicPort") + }) + return _obj + + diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/error_info.py b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/error_info.py new file mode 100644 index 0000000..5e0fb55 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/error_info.py @@ -0,0 +1,94 @@ +# coding: utf-8 + +""" + QoD Provisioning API + + The Quality-On-Demand (QoD) Provisioning API offers a programmable interface for developers to request the assignment of a certain QoS Profile to a certain device, indefinitely. This API sets up the configuration in the network so the requested QoS profile is applied to an specified device, at any time while the provisioning is available. The device traffic will be treated with a certain QoS profile by the network whenever the device is connected to the network, until the provisioning is deleted. # Relevant terms and definitions * **QoS profiles and QoS profile labels**: Latency, throughput or priority requirements of the application mapped to relevant QoS profile values. The set of QoS Profiles that a network operator is offering may be retrieved via the `qos-profiles` API (cf. https://github.com/camaraproject/QualityOnDemand/) or will be agreed during the onboarding with the API service provider. * **Identifier for the device**: At least one identifier for the device (user equipment) out of four options: IPv4 address, IPv6 address, Phone number, or Network Access Identifier assigned by the network operator for the device, at the request time. After the provisioning request is accepted, the device may get different IP addresses, but the provisioning will still apply to the device that was identified during the request process. Note: Network Access Identifier is defined for future use and will not be supported with v0.1 of the API. * **Notification URL and token**: Developers may provide a callback URL (`sink`) on which notifications about all status change events (eg. provisioning termination) can be received from the service provider. This is an optional parameter. The notification will be sent as a CloudEvent compliant message. If `sink` is included, it is RECOMMENDED for the client to provide as well the `sinkCredential` property to protect the notification endpoint. In the current version,`sinkCredential.credentialType` MUST be set to `ACCESSTOKEN` if provided. # Resources and Operations overview The API defines four operations: - An operation to setup a new QoD provisioning for a given device. - An operation to get the information about a specific QoD provisioning, identified by its `provisioningId`. - An operation to get the QoD provisioning for a given device. - An operation to terminate a QoD provisioning, identified by its `provisioningId`. # Authorization and Authentication [Camara Security and Interoperability Profile](https://github.com/camaraproject/IdentityAndConsentManagement/blob/main/documentation/CAMARA-Security-Interoperability.md) provides details on how a client requests an access token. Which specific authorization flows are to be used will be determined during onboarding process, happening between the API Client and the Telco Operator exposing the API, taking into account the declared purpose for accessing the API, while also being subject to the prevailing legal framework dictated by local legislation. It is important to remark that in cases where personal user data is processed by the API, and users can exercise their rights through mechanisms such as opt-in and/or opt-out, the use of 3-legged access tokens becomes mandatory. This measure ensures that the API remains in strict compliance with user privacy preferences and regulatory obligations, upholding the principles of transparency and user-centric data control. # Identifying a device from the access token This specification defines the `device` object field as optional in API requests, specifically in cases where the API is accessed using a 3-legged access token, and the device can be uniquely identified by the token. This approach simplifies API usage for API consumers by relying on the device information associated with the access token used to invoke the API. ## Handling of device information: ### Optional device object for 3-legged tokens: - When using a 3-legged access token, the device associated with the access token must be considered as the device for the API request. This means that the device object is not required in the request, and if included it must identify the same device, therefore **it is recommended NOT to include it in these scenarios** to simplify the API usage and avoid additional validations. ### Validation mechanism: - The server will extract the device identification from the access token, if available. - If the API request additionally includes a `device` object when using a 3-legged access token, the API will validate that the device identifier provided matches the one associated with the access token. - If there is a mismatch, the API will respond with a 403 - INVALID_TOKEN_CONTEXT error, indicating that the device information in the request does not match the token. ### Error handling for unidentifiable devices: - If the `device` object is not included in the request and the device information cannot be derived from the 3-legged access token, the server will return a 422 `UNIDENTIFIABLE_DEVICE` error. ### Restrictions for tokens without an associated authenticated identifier: - For scenarios which do not have a single device identifier associated to the token during the authentication flow, e.g. 2-legged access tokens, the `device` object MUST be provided in the API request. This ensures that the device identification is explicit and valid for each API call made with these tokens. + + The version of the OpenAPI document: 0.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + + + +from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr +from typing import Any, ClassVar, Dict, List +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class ErrorInfo(BaseModel): + """ + Common schema for errors + """ # noqa: E501 + status: StrictInt = Field(description="HTTP status code returned along with this error response") + code: StrictStr = Field(description="Code given to this error") + message: StrictStr = Field(description="Detailed error description") + __properties: ClassVar[List[str]] = ["status", "code", "message"] + + model_config = { + "populate_by_name": True, + "validate_assignment": True, + "protected_namespaces": (), + } + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of ErrorInfo from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Self: + """Create an instance of ErrorInfo from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "status": obj.get("status"), + "code": obj.get("code"), + "message": obj.get("message") + }) + return _obj + + diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/event_status_changed.py b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/event_status_changed.py new file mode 100644 index 0000000..c65a5d1 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/event_status_changed.py @@ -0,0 +1,132 @@ +# coding: utf-8 + +""" + QoD Provisioning API + + The Quality-On-Demand (QoD) Provisioning API offers a programmable interface for developers to request the assignment of a certain QoS Profile to a certain device, indefinitely. This API sets up the configuration in the network so the requested QoS profile is applied to an specified device, at any time while the provisioning is available. The device traffic will be treated with a certain QoS profile by the network whenever the device is connected to the network, until the provisioning is deleted. # Relevant terms and definitions * **QoS profiles and QoS profile labels**: Latency, throughput or priority requirements of the application mapped to relevant QoS profile values. The set of QoS Profiles that a network operator is offering may be retrieved via the `qos-profiles` API (cf. https://github.com/camaraproject/QualityOnDemand/) or will be agreed during the onboarding with the API service provider. * **Identifier for the device**: At least one identifier for the device (user equipment) out of four options: IPv4 address, IPv6 address, Phone number, or Network Access Identifier assigned by the network operator for the device, at the request time. After the provisioning request is accepted, the device may get different IP addresses, but the provisioning will still apply to the device that was identified during the request process. Note: Network Access Identifier is defined for future use and will not be supported with v0.1 of the API. * **Notification URL and token**: Developers may provide a callback URL (`sink`) on which notifications about all status change events (eg. provisioning termination) can be received from the service provider. This is an optional parameter. The notification will be sent as a CloudEvent compliant message. If `sink` is included, it is RECOMMENDED for the client to provide as well the `sinkCredential` property to protect the notification endpoint. In the current version,`sinkCredential.credentialType` MUST be set to `ACCESSTOKEN` if provided. # Resources and Operations overview The API defines four operations: - An operation to setup a new QoD provisioning for a given device. - An operation to get the information about a specific QoD provisioning, identified by its `provisioningId`. - An operation to get the QoD provisioning for a given device. - An operation to terminate a QoD provisioning, identified by its `provisioningId`. # Authorization and Authentication [Camara Security and Interoperability Profile](https://github.com/camaraproject/IdentityAndConsentManagement/blob/main/documentation/CAMARA-Security-Interoperability.md) provides details on how a client requests an access token. Which specific authorization flows are to be used will be determined during onboarding process, happening between the API Client and the Telco Operator exposing the API, taking into account the declared purpose for accessing the API, while also being subject to the prevailing legal framework dictated by local legislation. It is important to remark that in cases where personal user data is processed by the API, and users can exercise their rights through mechanisms such as opt-in and/or opt-out, the use of 3-legged access tokens becomes mandatory. This measure ensures that the API remains in strict compliance with user privacy preferences and regulatory obligations, upholding the principles of transparency and user-centric data control. # Identifying a device from the access token This specification defines the `device` object field as optional in API requests, specifically in cases where the API is accessed using a 3-legged access token, and the device can be uniquely identified by the token. This approach simplifies API usage for API consumers by relying on the device information associated with the access token used to invoke the API. ## Handling of device information: ### Optional device object for 3-legged tokens: - When using a 3-legged access token, the device associated with the access token must be considered as the device for the API request. This means that the device object is not required in the request, and if included it must identify the same device, therefore **it is recommended NOT to include it in these scenarios** to simplify the API usage and avoid additional validations. ### Validation mechanism: - The server will extract the device identification from the access token, if available. - If the API request additionally includes a `device` object when using a 3-legged access token, the API will validate that the device identifier provided matches the one associated with the access token. - If there is a mismatch, the API will respond with a 403 - INVALID_TOKEN_CONTEXT error, indicating that the device information in the request does not match the token. ### Error handling for unidentifiable devices: - If the `device` object is not included in the request and the device information cannot be derived from the 3-legged access token, the server will return a 422 `UNIDENTIFIABLE_DEVICE` error. ### Restrictions for tokens without an associated authenticated identifier: - For scenarios which do not have a single device identifier associated to the token during the authentication flow, e.g. 2-legged access tokens, the `device` object MUST be provided in the API request. This ensures that the device identification is explicit and valid for each API call made with these tokens. + + The version of the OpenAPI document: 0.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + + + +from datetime import datetime +from pydantic import ConfigDict, Field, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List, Optional +from schemas.cloud_event import CloudEvent +from schemas.event_status_changed_all_of_data import EventStatusChangedAllOfData +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class EventStatusChanged(CloudEvent): + """ + Event to notify a QoD provisioning status change + """ # noqa: E501 + id: StrictStr = Field(description="Identifier of this event, that must be unique in the source context.") + source: StrictStr = Field(description="Identifies the context in which an event happened in the specific Provider Implementation.") + type: StrictStr = Field(description="The type of the event.") + specversion: StrictStr = Field(description="Version of the specification to which this event conforms (must be 1.0 if it conforms to cloudevents 1.0.2 version)") + datacontenttype: Optional[StrictStr] = Field(default=None, description="media-type that describes the event payload encoding, must be \"application/json\" for CAMARA APIs") + data: EventStatusChangedAllOfData + time: datetime = Field(description="Timestamp of when the occurrence happened. It must follow RFC 3339 ") + __properties: ClassVar[List[str]] = ["id", "source", "type", "specversion", "datacontenttype", "data", "time"] + + @field_validator('type') + def type_validate_enum(cls, value): + """Validates the enum""" + if value not in ('org.camaraproject.qod-provisioning.v0.status-changed',): + raise ValueError("must be one of enum values ('org.camaraproject.qod-provisioning.v0.status-changed')") + return value + + @field_validator('specversion') + def specversion_validate_enum(cls, value): + """Validates the enum""" + if value not in ('1.0',): + raise ValueError("must be one of enum values ('1.0')") + return value + + @field_validator('datacontenttype') + def datacontenttype_validate_enum(cls, value): + """Validates the enum""" + if value is None: + return value + + if value not in ('application/json',): + raise ValueError("must be one of enum values ('application/json')") + return value + + model_config = { + "populate_by_name": True, + "validate_assignment": True, + "protected_namespaces": (), + } + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of EventStatusChanged from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of data + if self.data: + _dict['data'] = self.data.to_dict() + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Self: + """Create an instance of EventStatusChanged from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "id": obj.get("id"), + "source": obj.get("source"), + "type": obj.get("type"), + "specversion": obj.get("specversion"), + "datacontenttype": obj.get("datacontenttype"), + "data": EventStatusChangedAllOfData.from_dict(obj.get("data")) if obj.get("data") is not None else None, + "time": obj.get("time") + }) + return _obj + + diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/event_status_changed_all_of_data.py b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/event_status_changed_all_of_data.py new file mode 100644 index 0000000..e8d2a7e --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/event_status_changed_all_of_data.py @@ -0,0 +1,96 @@ +# coding: utf-8 + +""" + QoD Provisioning API + + The Quality-On-Demand (QoD) Provisioning API offers a programmable interface for developers to request the assignment of a certain QoS Profile to a certain device, indefinitely. This API sets up the configuration in the network so the requested QoS profile is applied to an specified device, at any time while the provisioning is available. The device traffic will be treated with a certain QoS profile by the network whenever the device is connected to the network, until the provisioning is deleted. # Relevant terms and definitions * **QoS profiles and QoS profile labels**: Latency, throughput or priority requirements of the application mapped to relevant QoS profile values. The set of QoS Profiles that a network operator is offering may be retrieved via the `qos-profiles` API (cf. https://github.com/camaraproject/QualityOnDemand/) or will be agreed during the onboarding with the API service provider. * **Identifier for the device**: At least one identifier for the device (user equipment) out of four options: IPv4 address, IPv6 address, Phone number, or Network Access Identifier assigned by the network operator for the device, at the request time. After the provisioning request is accepted, the device may get different IP addresses, but the provisioning will still apply to the device that was identified during the request process. Note: Network Access Identifier is defined for future use and will not be supported with v0.1 of the API. * **Notification URL and token**: Developers may provide a callback URL (`sink`) on which notifications about all status change events (eg. provisioning termination) can be received from the service provider. This is an optional parameter. The notification will be sent as a CloudEvent compliant message. If `sink` is included, it is RECOMMENDED for the client to provide as well the `sinkCredential` property to protect the notification endpoint. In the current version,`sinkCredential.credentialType` MUST be set to `ACCESSTOKEN` if provided. # Resources and Operations overview The API defines four operations: - An operation to setup a new QoD provisioning for a given device. - An operation to get the information about a specific QoD provisioning, identified by its `provisioningId`. - An operation to get the QoD provisioning for a given device. - An operation to terminate a QoD provisioning, identified by its `provisioningId`. # Authorization and Authentication [Camara Security and Interoperability Profile](https://github.com/camaraproject/IdentityAndConsentManagement/blob/main/documentation/CAMARA-Security-Interoperability.md) provides details on how a client requests an access token. Which specific authorization flows are to be used will be determined during onboarding process, happening between the API Client and the Telco Operator exposing the API, taking into account the declared purpose for accessing the API, while also being subject to the prevailing legal framework dictated by local legislation. It is important to remark that in cases where personal user data is processed by the API, and users can exercise their rights through mechanisms such as opt-in and/or opt-out, the use of 3-legged access tokens becomes mandatory. This measure ensures that the API remains in strict compliance with user privacy preferences and regulatory obligations, upholding the principles of transparency and user-centric data control. # Identifying a device from the access token This specification defines the `device` object field as optional in API requests, specifically in cases where the API is accessed using a 3-legged access token, and the device can be uniquely identified by the token. This approach simplifies API usage for API consumers by relying on the device information associated with the access token used to invoke the API. ## Handling of device information: ### Optional device object for 3-legged tokens: - When using a 3-legged access token, the device associated with the access token must be considered as the device for the API request. This means that the device object is not required in the request, and if included it must identify the same device, therefore **it is recommended NOT to include it in these scenarios** to simplify the API usage and avoid additional validations. ### Validation mechanism: - The server will extract the device identification from the access token, if available. - If the API request additionally includes a `device` object when using a 3-legged access token, the API will validate that the device identifier provided matches the one associated with the access token. - If there is a mismatch, the API will respond with a 403 - INVALID_TOKEN_CONTEXT error, indicating that the device information in the request does not match the token. ### Error handling for unidentifiable devices: - If the `device` object is not included in the request and the device information cannot be derived from the 3-legged access token, the server will return a 422 `UNIDENTIFIABLE_DEVICE` error. ### Restrictions for tokens without an associated authenticated identifier: - For scenarios which do not have a single device identifier associated to the token during the authentication flow, e.g. 2-legged access tokens, the `device` object MUST be provided in the API request. This ensures that the device identification is explicit and valid for each API call made with these tokens. + + The version of the OpenAPI document: 0.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + + + +from pydantic import BaseModel, ConfigDict, Field, StrictStr +from typing import Any, ClassVar, Dict, List, Optional +from schemas.status_changed import StatusChanged +from schemas.status_info import StatusInfo +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class EventStatusChangedAllOfData(BaseModel): + """ + Event details depending on the event type + """ # noqa: E501 + provisioning_id: StrictStr = Field(description="Provisioning Identifier in UUID format", alias="provisioningId") + status: Optional[StatusChanged] = None + status_info: Optional[StatusInfo] = Field(default=None, alias="statusInfo") + __properties: ClassVar[List[str]] = ["provisioningId", "status", "statusInfo"] + + model_config = { + "populate_by_name": True, + "validate_assignment": True, + "protected_namespaces": (), + } + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of EventStatusChangedAllOfData from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Self: + """Create an instance of EventStatusChangedAllOfData from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "provisioningId": obj.get("provisioningId"), + "status": obj.get("status"), + "statusInfo": obj.get("statusInfo") + }) + return _obj + + diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/extra_models.py b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/extra_models.py new file mode 100644 index 0000000..a3a283f --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/extra_models.py @@ -0,0 +1,8 @@ +# coding: utf-8 + +from pydantic import BaseModel + +class TokenModel(BaseModel): + """Defines a token model.""" + + sub: str diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/plain_credential.py b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/plain_credential.py new file mode 100644 index 0000000..5e4ae5c --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/plain_credential.py @@ -0,0 +1,102 @@ +# coding: utf-8 + +""" + QoD Provisioning API + + The Quality-On-Demand (QoD) Provisioning API offers a programmable interface for developers to request the assignment of a certain QoS Profile to a certain device, indefinitely. This API sets up the configuration in the network so the requested QoS profile is applied to an specified device, at any time while the provisioning is available. The device traffic will be treated with a certain QoS profile by the network whenever the device is connected to the network, until the provisioning is deleted. # Relevant terms and definitions * **QoS profiles and QoS profile labels**: Latency, throughput or priority requirements of the application mapped to relevant QoS profile values. The set of QoS Profiles that a network operator is offering may be retrieved via the `qos-profiles` API (cf. https://github.com/camaraproject/QualityOnDemand/) or will be agreed during the onboarding with the API service provider. * **Identifier for the device**: At least one identifier for the device (user equipment) out of four options: IPv4 address, IPv6 address, Phone number, or Network Access Identifier assigned by the network operator for the device, at the request time. After the provisioning request is accepted, the device may get different IP addresses, but the provisioning will still apply to the device that was identified during the request process. Note: Network Access Identifier is defined for future use and will not be supported with v0.1 of the API. * **Notification URL and token**: Developers may provide a callback URL (`sink`) on which notifications about all status change events (eg. provisioning termination) can be received from the service provider. This is an optional parameter. The notification will be sent as a CloudEvent compliant message. If `sink` is included, it is RECOMMENDED for the client to provide as well the `sinkCredential` property to protect the notification endpoint. In the current version,`sinkCredential.credentialType` MUST be set to `ACCESSTOKEN` if provided. # Resources and Operations overview The API defines four operations: - An operation to setup a new QoD provisioning for a given device. - An operation to get the information about a specific QoD provisioning, identified by its `provisioningId`. - An operation to get the QoD provisioning for a given device. - An operation to terminate a QoD provisioning, identified by its `provisioningId`. # Authorization and Authentication [Camara Security and Interoperability Profile](https://github.com/camaraproject/IdentityAndConsentManagement/blob/main/documentation/CAMARA-Security-Interoperability.md) provides details on how a client requests an access token. Which specific authorization flows are to be used will be determined during onboarding process, happening between the API Client and the Telco Operator exposing the API, taking into account the declared purpose for accessing the API, while also being subject to the prevailing legal framework dictated by local legislation. It is important to remark that in cases where personal user data is processed by the API, and users can exercise their rights through mechanisms such as opt-in and/or opt-out, the use of 3-legged access tokens becomes mandatory. This measure ensures that the API remains in strict compliance with user privacy preferences and regulatory obligations, upholding the principles of transparency and user-centric data control. # Identifying a device from the access token This specification defines the `device` object field as optional in API requests, specifically in cases where the API is accessed using a 3-legged access token, and the device can be uniquely identified by the token. This approach simplifies API usage for API consumers by relying on the device information associated with the access token used to invoke the API. ## Handling of device information: ### Optional device object for 3-legged tokens: - When using a 3-legged access token, the device associated with the access token must be considered as the device for the API request. This means that the device object is not required in the request, and if included it must identify the same device, therefore **it is recommended NOT to include it in these scenarios** to simplify the API usage and avoid additional validations. ### Validation mechanism: - The server will extract the device identification from the access token, if available. - If the API request additionally includes a `device` object when using a 3-legged access token, the API will validate that the device identifier provided matches the one associated with the access token. - If there is a mismatch, the API will respond with a 403 - INVALID_TOKEN_CONTEXT error, indicating that the device information in the request does not match the token. ### Error handling for unidentifiable devices: - If the `device` object is not included in the request and the device information cannot be derived from the 3-legged access token, the server will return a 422 `UNIDENTIFIABLE_DEVICE` error. ### Restrictions for tokens without an associated authenticated identifier: - For scenarios which do not have a single device identifier associated to the token during the authentication flow, e.g. 2-legged access tokens, the `device` object MUST be provided in the API request. This ensures that the device identification is explicit and valid for each API call made with these tokens. + + The version of the OpenAPI document: 0.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + + + +from pydantic import ConfigDict, Field, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List +from schemas.sink_credential import SinkCredential +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class PlainCredential(SinkCredential): + """ + A plain credential as a combination of an identifier and a secret. + """ # noqa: E501 + credential_type: StrictStr = Field(description="The type of the credential.", alias="credentialType") + identifier: StrictStr = Field(description="The identifier might be an account or username.") + secret: StrictStr = Field(description="The secret might be a password or passphrase.") + __properties: ClassVar[List[str]] = ["credentialType", "identifier", "secret"] + + @field_validator('credential_type') + def credential_type_validate_enum(cls, value): + """Validates the enum""" + if value not in ('PLAIN', 'ACCESSTOKEN', 'REFRESHTOKEN',): + raise ValueError("must be one of enum values ('PLAIN', 'ACCESSTOKEN', 'REFRESHTOKEN')") + return value + + model_config = { + "populate_by_name": True, + "validate_assignment": True, + "protected_namespaces": (), + } + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of PlainCredential from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Self: + """Create an instance of PlainCredential from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "credentialType": obj.get("credentialType"), + "identifier": obj.get("identifier"), + "secret": obj.get("secret") + }) + return _obj + + diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/provisioning_info.py b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/provisioning_info.py new file mode 100644 index 0000000..797e50a --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/provisioning_info.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- +# @Author: Eduardo Santos +# @Date: 2024-11-28 10:13:05 +# @Last Modified by: Eduardo Santos +# @Last Modified time: 2024-11-28 17:07:23 +# coding: utf-8 + +""" + QoD Provisioning API + + The Quality-On-Demand (QoD) Provisioning API offers a programmable interface for developers to request the assignment of a certain QoS Profile to a certain device, indefinitely. This API sets up the configuration in the network so the requested QoS profile is applied to an specified device, at any time while the provisioning is available. The device traffic will be treated with a certain QoS profile by the network whenever the device is connected to the network, until the provisioning is deleted. # Relevant terms and definitions * **QoS profiles and QoS profile labels**: Latency, throughput or priority requirements of the application mapped to relevant QoS profile values. The set of QoS Profiles that a network operator is offering may be retrieved via the `qos-profiles` API (cf. https://github.com/camaraproject/QualityOnDemand/) or will be agreed during the onboarding with the API service provider. * **Identifier for the device**: At least one identifier for the device (user equipment) out of four options: IPv4 address, IPv6 address, Phone number, or Network Access Identifier assigned by the network operator for the device, at the request time. After the provisioning request is accepted, the device may get different IP addresses, but the provisioning will still apply to the device that was identified during the request process. Note: Network Access Identifier is defined for future use and will not be supported with v0.1 of the API. * **Notification URL and token**: Developers may provide a callback URL (`sink`) on which notifications about all status change events (eg. provisioning termination) can be received from the service provider. This is an optional parameter. The notification will be sent as a CloudEvent compliant message. If `sink` is included, it is RECOMMENDED for the client to provide as well the `sinkCredential` property to protect the notification endpoint. In the current version,`sinkCredential.credentialType` MUST be set to `ACCESSTOKEN` if provided. # Resources and Operations overview The API defines four operations: - An operation to setup a new QoD provisioning for a given device. - An operation to get the information about a specific QoD provisioning, identified by its `provisioningId`. - An operation to get the QoD provisioning for a given device. - An operation to terminate a QoD provisioning, identified by its `provisioningId`. # Authorization and Authentication [Camara Security and Interoperability Profile](https://github.com/camaraproject/IdentityAndConsentManagement/blob/main/documentation/CAMARA-Security-Interoperability.md) provides details on how a client requests an access token. Which specific authorization flows are to be used will be determined during onboarding process, happening between the API Client and the Telco Operator exposing the API, taking into account the declared purpose for accessing the API, while also being subject to the prevailing legal framework dictated by local legislation. It is important to remark that in cases where personal user data is processed by the API, and users can exercise their rights through mechanisms such as opt-in and/or opt-out, the use of 3-legged access tokens becomes mandatory. This measure ensures that the API remains in strict compliance with user privacy preferences and regulatory obligations, upholding the principles of transparency and user-centric data control. # Identifying a device from the access token This specification defines the `device` object field as optional in API requests, specifically in cases where the API is accessed using a 3-legged access token, and the device can be uniquely identified by the token. This approach simplifies API usage for API consumers by relying on the device information associated with the access token used to invoke the API. ## Handling of device information: ### Optional device object for 3-legged tokens: - When using a 3-legged access token, the device associated with the access token must be considered as the device for the API request. This means that the device object is not required in the request, and if included it must identify the same device, therefore **it is recommended NOT to include it in these scenarios** to simplify the API usage and avoid additional validations. ### Validation mechanism: - The server will extract the device identification from the access token, if available. - If the API request additionally includes a `device` object when using a 3-legged access token, the API will validate that the device identifier provided matches the one associated with the access token. - If there is a mismatch, the API will respond with a 403 - INVALID_TOKEN_CONTEXT error, indicating that the device information in the request does not match the token. ### Error handling for unidentifiable devices: - If the `device` object is not included in the request and the device information cannot be derived from the 3-legged access token, the server will return a 422 `UNIDENTIFIABLE_DEVICE` error. ### Restrictions for tokens without an associated authenticated identifier: - For scenarios which do not have a single device identifier associated to the token during the authentication flow, e.g. 2-legged access tokens, the `device` object MUST be provided in the API request. This ensures that the device identification is explicit and valid for each API call made with these tokens. + + The version of the OpenAPI document: 0.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + + + +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List, Optional +from typing_extensions import Annotated +from schemas.device import Device +from schemas.sink_credential import SinkCredential +from schemas.status import Status +from schemas.status_info import StatusInfo +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class ProvisioningInfo(BaseModel): + """ + Provisioning related information returned in responses. Optional device object only to be returned if provided in createProvisioning. If more than one type of device identifier was provided, only one identifier will be returned (at implementation choice and with the original value provided in createProvisioning). Please note that IP addresses of devices can change and get reused, so the original values may no longer identify the same device. They identified the device at the time of QoD provisioning creation. + """ # noqa: E501 + device: Optional[Device] = None + qos_profile: Annotated[str, Field(min_length=3, strict=True, max_length=256)] = Field(description="A unique name for identifying a specific QoS profile. This may follow different formats depending on the service providers implementation. Some options addresses: - A UUID style string - Support for predefined profiles QOS_S, QOS_M, QOS_L, and QOS_E - A searchable descriptive name The set of QoS Profiles that an operator is offering can be retrieved by means of the [QoS Profile API](link TBC). ", alias="qosProfile") + sink: Optional[StrictStr] = Field(default=None, description="The address to which events shall be delivered, using the HTTP protocol.") + sink_credential: Optional[SinkCredential] = Field(default=None, alias="sinkCredential") + provisioning_id: StrictStr = Field(description="Provisioning Identifier in UUID format", alias="provisioningId") + started_at: Optional[datetime] = Field(default=None, description="Date and time when the provisioning became \"AVAILABLE\". Not to be returned when `status` is \"REQUESTED\". Format must follow RFC 3339 and must indicate time zone (UTC or local).", alias="startedAt") + status: Status + status_info: Optional[StatusInfo] = Field(default=None, alias="statusInfo") + __properties: ClassVar[List[str]] = ["device", "qosProfile", "sink", "sinkCredential", "provisioningId", "startedAt", "status", "statusInfo"] + + @field_validator('qos_profile') + def qos_profile_validate_regular_expression(cls, value): + """Validates the regular expression""" + if not re.match(r"^[a-zA-Z0-9_.-]+$", value): + raise ValueError(r"must validate the regular expression /^[a-zA-Z0-9_.-]+$/") + return value + + model_config = { + "populate_by_name": True, + "validate_assignment": True, + "protected_namespaces": (), + } + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of ProvisioningInfo from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of device + if self.device: + _dict['device'] = self.device.to_dict() + # override the default output from pydantic by calling `to_dict()` of sink_credential + if self.sink_credential: + _dict['sinkCredential'] = self.sink_credential.to_dict() + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Self: + """Create an instance of ProvisioningInfo from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "device": Device.from_dict(obj.get("device")) if obj.get("device") is not None else None, + "qosProfile": obj.get("qosProfile"), + "sink": obj.get("sink"), + "sinkCredential": SinkCredential.from_dict(obj.get("sinkCredential")) if obj.get("sinkCredential") is not None else None, + "provisioningId": obj.get("provisioningId"), + "startedAt": obj.get("startedAt"), + "status": obj.get("status"), + "statusInfo": obj.get("statusInfo") + }) + return _obj + + diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/refresh_token_credential.py b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/refresh_token_credential.py new file mode 100644 index 0000000..9e9d3dc --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/refresh_token_credential.py @@ -0,0 +1,116 @@ +# coding: utf-8 + +""" + QoD Provisioning API + + The Quality-On-Demand (QoD) Provisioning API offers a programmable interface for developers to request the assignment of a certain QoS Profile to a certain device, indefinitely. This API sets up the configuration in the network so the requested QoS profile is applied to an specified device, at any time while the provisioning is available. The device traffic will be treated with a certain QoS profile by the network whenever the device is connected to the network, until the provisioning is deleted. # Relevant terms and definitions * **QoS profiles and QoS profile labels**: Latency, throughput or priority requirements of the application mapped to relevant QoS profile values. The set of QoS Profiles that a network operator is offering may be retrieved via the `qos-profiles` API (cf. https://github.com/camaraproject/QualityOnDemand/) or will be agreed during the onboarding with the API service provider. * **Identifier for the device**: At least one identifier for the device (user equipment) out of four options: IPv4 address, IPv6 address, Phone number, or Network Access Identifier assigned by the network operator for the device, at the request time. After the provisioning request is accepted, the device may get different IP addresses, but the provisioning will still apply to the device that was identified during the request process. Note: Network Access Identifier is defined for future use and will not be supported with v0.1 of the API. * **Notification URL and token**: Developers may provide a callback URL (`sink`) on which notifications about all status change events (eg. provisioning termination) can be received from the service provider. This is an optional parameter. The notification will be sent as a CloudEvent compliant message. If `sink` is included, it is RECOMMENDED for the client to provide as well the `sinkCredential` property to protect the notification endpoint. In the current version,`sinkCredential.credentialType` MUST be set to `ACCESSTOKEN` if provided. # Resources and Operations overview The API defines four operations: - An operation to setup a new QoD provisioning for a given device. - An operation to get the information about a specific QoD provisioning, identified by its `provisioningId`. - An operation to get the QoD provisioning for a given device. - An operation to terminate a QoD provisioning, identified by its `provisioningId`. # Authorization and Authentication [Camara Security and Interoperability Profile](https://github.com/camaraproject/IdentityAndConsentManagement/blob/main/documentation/CAMARA-Security-Interoperability.md) provides details on how a client requests an access token. Which specific authorization flows are to be used will be determined during onboarding process, happening between the API Client and the Telco Operator exposing the API, taking into account the declared purpose for accessing the API, while also being subject to the prevailing legal framework dictated by local legislation. It is important to remark that in cases where personal user data is processed by the API, and users can exercise their rights through mechanisms such as opt-in and/or opt-out, the use of 3-legged access tokens becomes mandatory. This measure ensures that the API remains in strict compliance with user privacy preferences and regulatory obligations, upholding the principles of transparency and user-centric data control. # Identifying a device from the access token This specification defines the `device` object field as optional in API requests, specifically in cases where the API is accessed using a 3-legged access token, and the device can be uniquely identified by the token. This approach simplifies API usage for API consumers by relying on the device information associated with the access token used to invoke the API. ## Handling of device information: ### Optional device object for 3-legged tokens: - When using a 3-legged access token, the device associated with the access token must be considered as the device for the API request. This means that the device object is not required in the request, and if included it must identify the same device, therefore **it is recommended NOT to include it in these scenarios** to simplify the API usage and avoid additional validations. ### Validation mechanism: - The server will extract the device identification from the access token, if available. - If the API request additionally includes a `device` object when using a 3-legged access token, the API will validate that the device identifier provided matches the one associated with the access token. - If there is a mismatch, the API will respond with a 403 - INVALID_TOKEN_CONTEXT error, indicating that the device information in the request does not match the token. ### Error handling for unidentifiable devices: - If the `device` object is not included in the request and the device information cannot be derived from the 3-legged access token, the server will return a 422 `UNIDENTIFIABLE_DEVICE` error. ### Restrictions for tokens without an associated authenticated identifier: - For scenarios which do not have a single device identifier associated to the token during the authentication flow, e.g. 2-legged access tokens, the `device` object MUST be provided in the API request. This ensures that the device identification is explicit and valid for each API call made with these tokens. + + The version of the OpenAPI document: 0.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + + + +from datetime import datetime +from pydantic import ConfigDict, Field, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List +from schemas.sink_credential import SinkCredential +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class RefreshTokenCredential(SinkCredential): + """ + An access token credential with a refresh token. + """ # noqa: E501 + credential_type: StrictStr = Field(description="The type of the credential.", alias="credentialType") + access_token: StrictStr = Field(description="REQUIRED. An access token is a previously acquired token granting access to the target resource.", alias="accessToken") + access_token_expires_utc: datetime = Field(description="REQUIRED. An absolute UTC instant at which the token shall be considered expired.", alias="accessTokenExpiresUtc") + access_token_type: StrictStr = Field(description="REQUIRED. Type of the access token (See [OAuth 2.0](https://tools.ietf.org/html/rfc6749#section-7.1)).", alias="accessTokenType") + refresh_token: StrictStr = Field(description="REQUIRED. An refresh token credential used to acquire access tokens.", alias="refreshToken") + refresh_token_endpoint: StrictStr = Field(description="REQUIRED. A URL at which the refresh token can be traded for an access token.", alias="refreshTokenEndpoint") + __properties: ClassVar[List[str]] = ["credentialType", "accessToken", "accessTokenExpiresUtc", "accessTokenType", "refreshToken", "refreshTokenEndpoint"] + + @field_validator('credential_type') + def credential_type_validate_enum(cls, value): + """Validates the enum""" + if value not in ('PLAIN', 'ACCESSTOKEN', 'REFRESHTOKEN',): + raise ValueError("must be one of enum values ('PLAIN', 'ACCESSTOKEN', 'REFRESHTOKEN')") + return value + + @field_validator('access_token_type') + def access_token_type_validate_enum(cls, value): + """Validates the enum""" + if value not in ('bearer',): + raise ValueError("must be one of enum values ('bearer')") + return value + + model_config = { + "populate_by_name": True, + "validate_assignment": True, + "protected_namespaces": (), + } + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of RefreshTokenCredential from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Self: + """Create an instance of RefreshTokenCredential from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "credentialType": obj.get("credentialType"), + "accessToken": obj.get("accessToken"), + "accessTokenExpiresUtc": obj.get("accessTokenExpiresUtc"), + "accessTokenType": obj.get("accessTokenType"), + "refreshToken": obj.get("refreshToken"), + "refreshTokenEndpoint": obj.get("refreshTokenEndpoint") + }) + return _obj + + diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/retrieve_provisioning_by_device.py b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/retrieve_provisioning_by_device.py new file mode 100644 index 0000000..08ff72b --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/retrieve_provisioning_by_device.py @@ -0,0 +1,94 @@ +# coding: utf-8 + +""" + QoD Provisioning API + + The Quality-On-Demand (QoD) Provisioning API offers a programmable interface for developers to request the assignment of a certain QoS Profile to a certain device, indefinitely. This API sets up the configuration in the network so the requested QoS profile is applied to an specified device, at any time while the provisioning is available. The device traffic will be treated with a certain QoS profile by the network whenever the device is connected to the network, until the provisioning is deleted. # Relevant terms and definitions * **QoS profiles and QoS profile labels**: Latency, throughput or priority requirements of the application mapped to relevant QoS profile values. The set of QoS Profiles that a network operator is offering may be retrieved via the `qos-profiles` API (cf. https://github.com/camaraproject/QualityOnDemand/) or will be agreed during the onboarding with the API service provider. * **Identifier for the device**: At least one identifier for the device (user equipment) out of four options: IPv4 address, IPv6 address, Phone number, or Network Access Identifier assigned by the network operator for the device, at the request time. After the provisioning request is accepted, the device may get different IP addresses, but the provisioning will still apply to the device that was identified during the request process. Note: Network Access Identifier is defined for future use and will not be supported with v0.1 of the API. * **Notification URL and token**: Developers may provide a callback URL (`sink`) on which notifications about all status change events (eg. provisioning termination) can be received from the service provider. This is an optional parameter. The notification will be sent as a CloudEvent compliant message. If `sink` is included, it is RECOMMENDED for the client to provide as well the `sinkCredential` property to protect the notification endpoint. In the current version,`sinkCredential.credentialType` MUST be set to `ACCESSTOKEN` if provided. # Resources and Operations overview The API defines four operations: - An operation to setup a new QoD provisioning for a given device. - An operation to get the information about a specific QoD provisioning, identified by its `provisioningId`. - An operation to get the QoD provisioning for a given device. - An operation to terminate a QoD provisioning, identified by its `provisioningId`. # Authorization and Authentication [Camara Security and Interoperability Profile](https://github.com/camaraproject/IdentityAndConsentManagement/blob/main/documentation/CAMARA-Security-Interoperability.md) provides details on how a client requests an access token. Which specific authorization flows are to be used will be determined during onboarding process, happening between the API Client and the Telco Operator exposing the API, taking into account the declared purpose for accessing the API, while also being subject to the prevailing legal framework dictated by local legislation. It is important to remark that in cases where personal user data is processed by the API, and users can exercise their rights through mechanisms such as opt-in and/or opt-out, the use of 3-legged access tokens becomes mandatory. This measure ensures that the API remains in strict compliance with user privacy preferences and regulatory obligations, upholding the principles of transparency and user-centric data control. # Identifying a device from the access token This specification defines the `device` object field as optional in API requests, specifically in cases where the API is accessed using a 3-legged access token, and the device can be uniquely identified by the token. This approach simplifies API usage for API consumers by relying on the device information associated with the access token used to invoke the API. ## Handling of device information: ### Optional device object for 3-legged tokens: - When using a 3-legged access token, the device associated with the access token must be considered as the device for the API request. This means that the device object is not required in the request, and if included it must identify the same device, therefore **it is recommended NOT to include it in these scenarios** to simplify the API usage and avoid additional validations. ### Validation mechanism: - The server will extract the device identification from the access token, if available. - If the API request additionally includes a `device` object when using a 3-legged access token, the API will validate that the device identifier provided matches the one associated with the access token. - If there is a mismatch, the API will respond with a 403 - INVALID_TOKEN_CONTEXT error, indicating that the device information in the request does not match the token. ### Error handling for unidentifiable devices: - If the `device` object is not included in the request and the device information cannot be derived from the 3-legged access token, the server will return a 422 `UNIDENTIFIABLE_DEVICE` error. ### Restrictions for tokens without an associated authenticated identifier: - For scenarios which do not have a single device identifier associated to the token during the authentication flow, e.g. 2-legged access tokens, the `device` object MUST be provided in the API request. This ensures that the device identification is explicit and valid for each API call made with these tokens. + + The version of the OpenAPI document: 0.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + + + +from pydantic import BaseModel, ConfigDict +from typing import Any, ClassVar, Dict, List, Optional +from schemas.device import Device +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class RetrieveProvisioningByDevice(BaseModel): + """ + Attributes to look for QoD provisioning + """ # noqa: E501 + device: Optional[Device] = None + __properties: ClassVar[List[str]] = ["device"] + + model_config = { + "populate_by_name": True, + "validate_assignment": True, + "protected_namespaces": (), + } + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of RetrieveProvisioningByDevice from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of device + if self.device: + _dict['device'] = self.device.to_dict() + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Self: + """Create an instance of RetrieveProvisioningByDevice from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "device": Device.from_dict(obj.get("device")) if obj.get("device") is not None else None + }) + return _obj + + diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/sink_credential.py b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/sink_credential.py new file mode 100644 index 0000000..3f92cae --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/sink_credential.py @@ -0,0 +1,114 @@ +# coding: utf-8 + +""" + QoD Provisioning API + + The Quality-On-Demand (QoD) Provisioning API offers a programmable interface for developers to request the assignment of a certain QoS Profile to a certain device, indefinitely. This API sets up the configuration in the network so the requested QoS profile is applied to an specified device, at any time while the provisioning is available. The device traffic will be treated with a certain QoS profile by the network whenever the device is connected to the network, until the provisioning is deleted. # Relevant terms and definitions * **QoS profiles and QoS profile labels**: Latency, throughput or priority requirements of the application mapped to relevant QoS profile values. The set of QoS Profiles that a network operator is offering may be retrieved via the `qos-profiles` API (cf. https://github.com/camaraproject/QualityOnDemand/) or will be agreed during the onboarding with the API service provider. * **Identifier for the device**: At least one identifier for the device (user equipment) out of four options: IPv4 address, IPv6 address, Phone number, or Network Access Identifier assigned by the network operator for the device, at the request time. After the provisioning request is accepted, the device may get different IP addresses, but the provisioning will still apply to the device that was identified during the request process. Note: Network Access Identifier is defined for future use and will not be supported with v0.1 of the API. * **Notification URL and token**: Developers may provide a callback URL (`sink`) on which notifications about all status change events (eg. provisioning termination) can be received from the service provider. This is an optional parameter. The notification will be sent as a CloudEvent compliant message. If `sink` is included, it is RECOMMENDED for the client to provide as well the `sinkCredential` property to protect the notification endpoint. In the current version,`sinkCredential.credentialType` MUST be set to `ACCESSTOKEN` if provided. # Resources and Operations overview The API defines four operations: - An operation to setup a new QoD provisioning for a given device. - An operation to get the information about a specific QoD provisioning, identified by its `provisioningId`. - An operation to get the QoD provisioning for a given device. - An operation to terminate a QoD provisioning, identified by its `provisioningId`. # Authorization and Authentication [Camara Security and Interoperability Profile](https://github.com/camaraproject/IdentityAndConsentManagement/blob/main/documentation/CAMARA-Security-Interoperability.md) provides details on how a client requests an access token. Which specific authorization flows are to be used will be determined during onboarding process, happening between the API Client and the Telco Operator exposing the API, taking into account the declared purpose for accessing the API, while also being subject to the prevailing legal framework dictated by local legislation. It is important to remark that in cases where personal user data is processed by the API, and users can exercise their rights through mechanisms such as opt-in and/or opt-out, the use of 3-legged access tokens becomes mandatory. This measure ensures that the API remains in strict compliance with user privacy preferences and regulatory obligations, upholding the principles of transparency and user-centric data control. # Identifying a device from the access token This specification defines the `device` object field as optional in API requests, specifically in cases where the API is accessed using a 3-legged access token, and the device can be uniquely identified by the token. This approach simplifies API usage for API consumers by relying on the device information associated with the access token used to invoke the API. ## Handling of device information: ### Optional device object for 3-legged tokens: - When using a 3-legged access token, the device associated with the access token must be considered as the device for the API request. This means that the device object is not required in the request, and if included it must identify the same device, therefore **it is recommended NOT to include it in these scenarios** to simplify the API usage and avoid additional validations. ### Validation mechanism: - The server will extract the device identification from the access token, if available. - If the API request additionally includes a `device` object when using a 3-legged access token, the API will validate that the device identifier provided matches the one associated with the access token. - If there is a mismatch, the API will respond with a 403 - INVALID_TOKEN_CONTEXT error, indicating that the device information in the request does not match the token. ### Error handling for unidentifiable devices: - If the `device` object is not included in the request and the device information cannot be derived from the 3-legged access token, the server will return a 422 `UNIDENTIFIABLE_DEVICE` error. ### Restrictions for tokens without an associated authenticated identifier: - For scenarios which do not have a single device identifier associated to the token during the authentication flow, e.g. 2-legged access tokens, the `device` object MUST be provided in the API request. This ensures that the device identification is explicit and valid for each API call made with these tokens. + + The version of the OpenAPI document: 0.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + + + +from importlib import import_module +from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List, Union +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class SinkCredential(BaseModel): + """ + A sink credential provides authentication or authorization information necessary to enable delivery of events to a target. + """ # noqa: E501 + credential_type: Optional[StrictStr] = Field(description="The type of the credential.", alias="credentialType") + __properties: ClassVar[List[str]] = ["credentialType"] + + @field_validator('credential_type') + def credential_type_validate_enum(cls, value): + """Validates the enum""" + if value not in ('PLAIN', 'ACCESSTOKEN', 'REFRESHTOKEN', None): + raise ValueError("must be one of enum values ('PLAIN', 'ACCESSTOKEN', 'REFRESHTOKEN')") + return value + + model_config = { + "populate_by_name": True, + "validate_assignment": True, + "protected_namespaces": (), + } + + + # JSON field name that stores the object type + __discriminator_property_name: ClassVar[List[str]] = 'credentialType' + + # discriminator mappings + __discriminator_value_class_map: ClassVar[Dict[str, str]] = { + 'ACCESSTOKEN': 'AccessTokenCredential','PLAIN': 'PlainCredential','REFRESHTOKEN': 'RefreshTokenCredential' + } + + @classmethod + def get_discriminator_value(cls, obj: Dict) -> str: + """Returns the discriminator value (object type) of the data""" + discriminator_value = obj[cls.__discriminator_property_name] + if discriminator_value: + return cls.__discriminator_value_class_map.get(discriminator_value) + else: + return None + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Union[Self, Self, Self]: + """Create an instance of SinkCredential from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Union[Self, Self, Self]: + """Create an instance of SinkCredential from a dict""" + # look up the object type based on discriminator mapping + object_type = cls.get_discriminator_value(obj) + if object_type: + klass = globals()[object_type] + return klass.from_dict(obj) + else: + raise ValueError("SinkCredential failed to lookup discriminator value from " + + json.dumps(obj) + ". Discriminator property name: " + cls.__discriminator_property_name + + ", mapping: " + json.dumps(cls.__discriminator_value_class_map)) + + diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/status.py b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/status.py new file mode 100644 index 0000000..9a372e5 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/status.py @@ -0,0 +1,46 @@ +# coding: utf-8 + +""" + QoD Provisioning API + + The Quality-On-Demand (QoD) Provisioning API offers a programmable interface for developers to request the assignment of a certain QoS Profile to a certain device, indefinitely. This API sets up the configuration in the network so the requested QoS profile is applied to an specified device, at any time while the provisioning is available. The device traffic will be treated with a certain QoS profile by the network whenever the device is connected to the network, until the provisioning is deleted. # Relevant terms and definitions * **QoS profiles and QoS profile labels**: Latency, throughput or priority requirements of the application mapped to relevant QoS profile values. The set of QoS Profiles that a network operator is offering may be retrieved via the `qos-profiles` API (cf. https://github.com/camaraproject/QualityOnDemand/) or will be agreed during the onboarding with the API service provider. * **Identifier for the device**: At least one identifier for the device (user equipment) out of four options: IPv4 address, IPv6 address, Phone number, or Network Access Identifier assigned by the network operator for the device, at the request time. After the provisioning request is accepted, the device may get different IP addresses, but the provisioning will still apply to the device that was identified during the request process. Note: Network Access Identifier is defined for future use and will not be supported with v0.1 of the API. * **Notification URL and token**: Developers may provide a callback URL (`sink`) on which notifications about all status change events (eg. provisioning termination) can be received from the service provider. This is an optional parameter. The notification will be sent as a CloudEvent compliant message. If `sink` is included, it is RECOMMENDED for the client to provide as well the `sinkCredential` property to protect the notification endpoint. In the current version,`sinkCredential.credentialType` MUST be set to `ACCESSTOKEN` if provided. # Resources and Operations overview The API defines four operations: - An operation to setup a new QoD provisioning for a given device. - An operation to get the information about a specific QoD provisioning, identified by its `provisioningId`. - An operation to get the QoD provisioning for a given device. - An operation to terminate a QoD provisioning, identified by its `provisioningId`. # Authorization and Authentication [Camara Security and Interoperability Profile](https://github.com/camaraproject/IdentityAndConsentManagement/blob/main/documentation/CAMARA-Security-Interoperability.md) provides details on how a client requests an access token. Which specific authorization flows are to be used will be determined during onboarding process, happening between the API Client and the Telco Operator exposing the API, taking into account the declared purpose for accessing the API, while also being subject to the prevailing legal framework dictated by local legislation. It is important to remark that in cases where personal user data is processed by the API, and users can exercise their rights through mechanisms such as opt-in and/or opt-out, the use of 3-legged access tokens becomes mandatory. This measure ensures that the API remains in strict compliance with user privacy preferences and regulatory obligations, upholding the principles of transparency and user-centric data control. # Identifying a device from the access token This specification defines the `device` object field as optional in API requests, specifically in cases where the API is accessed using a 3-legged access token, and the device can be uniquely identified by the token. This approach simplifies API usage for API consumers by relying on the device information associated with the access token used to invoke the API. ## Handling of device information: ### Optional device object for 3-legged tokens: - When using a 3-legged access token, the device associated with the access token must be considered as the device for the API request. This means that the device object is not required in the request, and if included it must identify the same device, therefore **it is recommended NOT to include it in these scenarios** to simplify the API usage and avoid additional validations. ### Validation mechanism: - The server will extract the device identification from the access token, if available. - If the API request additionally includes a `device` object when using a 3-legged access token, the API will validate that the device identifier provided matches the one associated with the access token. - If there is a mismatch, the API will respond with a 403 - INVALID_TOKEN_CONTEXT error, indicating that the device information in the request does not match the token. ### Error handling for unidentifiable devices: - If the `device` object is not included in the request and the device information cannot be derived from the 3-legged access token, the server will return a 422 `UNIDENTIFIABLE_DEVICE` error. ### Restrictions for tokens without an associated authenticated identifier: - For scenarios which do not have a single device identifier associated to the token during the authentication flow, e.g. 2-legged access tokens, the `device` object MUST be provided in the API request. This ensures that the device identification is explicit and valid for each API call made with these tokens. + + The version of the OpenAPI document: 0.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import json +import pprint +import re # noqa: F401 +from enum import Enum + + + +try: + from typing import Self +except ImportError: + from typing_extensions import Self + + +class Status(str, Enum): + """ + The current status of the requested QoD provisioning. The status can be one of the following: * `REQUESTED` - QoD provisioning has been requested but is still being processed. * `AVAILABLE` - The requested QoS profile has been provisioned to the device, and is active. * `UNAVAILABLE` - The QoD provisioning request has been processed but is not active. `statusInfo` may provide additional information about the reason for the unavailability. + """ + + """ + allowed enum values + """ + REQUESTED = 'REQUESTED' + AVAILABLE = 'AVAILABLE' + UNAVAILABLE = 'UNAVAILABLE' + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of Status from a JSON string""" + return cls(json.loads(json_str)) + + diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/status_changed.py b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/status_changed.py new file mode 100644 index 0000000..54bbc86 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/status_changed.py @@ -0,0 +1,45 @@ +# coding: utf-8 + +""" + QoD Provisioning API + + The Quality-On-Demand (QoD) Provisioning API offers a programmable interface for developers to request the assignment of a certain QoS Profile to a certain device, indefinitely. This API sets up the configuration in the network so the requested QoS profile is applied to an specified device, at any time while the provisioning is available. The device traffic will be treated with a certain QoS profile by the network whenever the device is connected to the network, until the provisioning is deleted. # Relevant terms and definitions * **QoS profiles and QoS profile labels**: Latency, throughput or priority requirements of the application mapped to relevant QoS profile values. The set of QoS Profiles that a network operator is offering may be retrieved via the `qos-profiles` API (cf. https://github.com/camaraproject/QualityOnDemand/) or will be agreed during the onboarding with the API service provider. * **Identifier for the device**: At least one identifier for the device (user equipment) out of four options: IPv4 address, IPv6 address, Phone number, or Network Access Identifier assigned by the network operator for the device, at the request time. After the provisioning request is accepted, the device may get different IP addresses, but the provisioning will still apply to the device that was identified during the request process. Note: Network Access Identifier is defined for future use and will not be supported with v0.1 of the API. * **Notification URL and token**: Developers may provide a callback URL (`sink`) on which notifications about all status change events (eg. provisioning termination) can be received from the service provider. This is an optional parameter. The notification will be sent as a CloudEvent compliant message. If `sink` is included, it is RECOMMENDED for the client to provide as well the `sinkCredential` property to protect the notification endpoint. In the current version,`sinkCredential.credentialType` MUST be set to `ACCESSTOKEN` if provided. # Resources and Operations overview The API defines four operations: - An operation to setup a new QoD provisioning for a given device. - An operation to get the information about a specific QoD provisioning, identified by its `provisioningId`. - An operation to get the QoD provisioning for a given device. - An operation to terminate a QoD provisioning, identified by its `provisioningId`. # Authorization and Authentication [Camara Security and Interoperability Profile](https://github.com/camaraproject/IdentityAndConsentManagement/blob/main/documentation/CAMARA-Security-Interoperability.md) provides details on how a client requests an access token. Which specific authorization flows are to be used will be determined during onboarding process, happening between the API Client and the Telco Operator exposing the API, taking into account the declared purpose for accessing the API, while also being subject to the prevailing legal framework dictated by local legislation. It is important to remark that in cases where personal user data is processed by the API, and users can exercise their rights through mechanisms such as opt-in and/or opt-out, the use of 3-legged access tokens becomes mandatory. This measure ensures that the API remains in strict compliance with user privacy preferences and regulatory obligations, upholding the principles of transparency and user-centric data control. # Identifying a device from the access token This specification defines the `device` object field as optional in API requests, specifically in cases where the API is accessed using a 3-legged access token, and the device can be uniquely identified by the token. This approach simplifies API usage for API consumers by relying on the device information associated with the access token used to invoke the API. ## Handling of device information: ### Optional device object for 3-legged tokens: - When using a 3-legged access token, the device associated with the access token must be considered as the device for the API request. This means that the device object is not required in the request, and if included it must identify the same device, therefore **it is recommended NOT to include it in these scenarios** to simplify the API usage and avoid additional validations. ### Validation mechanism: - The server will extract the device identification from the access token, if available. - If the API request additionally includes a `device` object when using a 3-legged access token, the API will validate that the device identifier provided matches the one associated with the access token. - If there is a mismatch, the API will respond with a 403 - INVALID_TOKEN_CONTEXT error, indicating that the device information in the request does not match the token. ### Error handling for unidentifiable devices: - If the `device` object is not included in the request and the device information cannot be derived from the 3-legged access token, the server will return a 422 `UNIDENTIFIABLE_DEVICE` error. ### Restrictions for tokens without an associated authenticated identifier: - For scenarios which do not have a single device identifier associated to the token during the authentication flow, e.g. 2-legged access tokens, the `device` object MUST be provided in the API request. This ensures that the device identification is explicit and valid for each API call made with these tokens. + + The version of the OpenAPI document: 0.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import json +import pprint +import re # noqa: F401 +from enum import Enum + + + +try: + from typing import Self +except ImportError: + from typing_extensions import Self + + +class StatusChanged(str, Enum): + """ + The current status of a requested or previously available QoD provisioning. Applicable values in the event are: * `AVAILABLE` - The requested QoS profile has been provisioned to the device, and is active. * `UNAVAILABLE` - A requested or previously available QoD provisioning is now unavailable. `statusInfo` may provide additional information about the reason for the unavailability. + """ + + """ + allowed enum values + """ + AVAILABLE = 'AVAILABLE' + UNAVAILABLE = 'UNAVAILABLE' + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of StatusChanged from a JSON string""" + return cls(json.loads(json_str)) + + diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/status_info.py b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/status_info.py new file mode 100644 index 0000000..1c6a393 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/API/src/schemas/status_info.py @@ -0,0 +1,45 @@ +# coding: utf-8 + +""" + QoD Provisioning API + + The Quality-On-Demand (QoD) Provisioning API offers a programmable interface for developers to request the assignment of a certain QoS Profile to a certain device, indefinitely. This API sets up the configuration in the network so the requested QoS profile is applied to an specified device, at any time while the provisioning is available. The device traffic will be treated with a certain QoS profile by the network whenever the device is connected to the network, until the provisioning is deleted. # Relevant terms and definitions * **QoS profiles and QoS profile labels**: Latency, throughput or priority requirements of the application mapped to relevant QoS profile values. The set of QoS Profiles that a network operator is offering may be retrieved via the `qos-profiles` API (cf. https://github.com/camaraproject/QualityOnDemand/) or will be agreed during the onboarding with the API service provider. * **Identifier for the device**: At least one identifier for the device (user equipment) out of four options: IPv4 address, IPv6 address, Phone number, or Network Access Identifier assigned by the network operator for the device, at the request time. After the provisioning request is accepted, the device may get different IP addresses, but the provisioning will still apply to the device that was identified during the request process. Note: Network Access Identifier is defined for future use and will not be supported with v0.1 of the API. * **Notification URL and token**: Developers may provide a callback URL (`sink`) on which notifications about all status change events (eg. provisioning termination) can be received from the service provider. This is an optional parameter. The notification will be sent as a CloudEvent compliant message. If `sink` is included, it is RECOMMENDED for the client to provide as well the `sinkCredential` property to protect the notification endpoint. In the current version,`sinkCredential.credentialType` MUST be set to `ACCESSTOKEN` if provided. # Resources and Operations overview The API defines four operations: - An operation to setup a new QoD provisioning for a given device. - An operation to get the information about a specific QoD provisioning, identified by its `provisioningId`. - An operation to get the QoD provisioning for a given device. - An operation to terminate a QoD provisioning, identified by its `provisioningId`. # Authorization and Authentication [Camara Security and Interoperability Profile](https://github.com/camaraproject/IdentityAndConsentManagement/blob/main/documentation/CAMARA-Security-Interoperability.md) provides details on how a client requests an access token. Which specific authorization flows are to be used will be determined during onboarding process, happening between the API Client and the Telco Operator exposing the API, taking into account the declared purpose for accessing the API, while also being subject to the prevailing legal framework dictated by local legislation. It is important to remark that in cases where personal user data is processed by the API, and users can exercise their rights through mechanisms such as opt-in and/or opt-out, the use of 3-legged access tokens becomes mandatory. This measure ensures that the API remains in strict compliance with user privacy preferences and regulatory obligations, upholding the principles of transparency and user-centric data control. # Identifying a device from the access token This specification defines the `device` object field as optional in API requests, specifically in cases where the API is accessed using a 3-legged access token, and the device can be uniquely identified by the token. This approach simplifies API usage for API consumers by relying on the device information associated with the access token used to invoke the API. ## Handling of device information: ### Optional device object for 3-legged tokens: - When using a 3-legged access token, the device associated with the access token must be considered as the device for the API request. This means that the device object is not required in the request, and if included it must identify the same device, therefore **it is recommended NOT to include it in these scenarios** to simplify the API usage and avoid additional validations. ### Validation mechanism: - The server will extract the device identification from the access token, if available. - If the API request additionally includes a `device` object when using a 3-legged access token, the API will validate that the device identifier provided matches the one associated with the access token. - If there is a mismatch, the API will respond with a 403 - INVALID_TOKEN_CONTEXT error, indicating that the device information in the request does not match the token. ### Error handling for unidentifiable devices: - If the `device` object is not included in the request and the device information cannot be derived from the 3-legged access token, the server will return a 422 `UNIDENTIFIABLE_DEVICE` error. ### Restrictions for tokens without an associated authenticated identifier: - For scenarios which do not have a single device identifier associated to the token during the authentication flow, e.g. 2-legged access tokens, the `device` object MUST be provided in the API request. This ensures that the device identification is explicit and valid for each API call made with these tokens. + + The version of the OpenAPI document: 0.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import json +import pprint +import re # noqa: F401 +from enum import Enum + + + +try: + from typing import Self +except ImportError: + from typing_extensions import Self + + +class StatusInfo(str, Enum): + """ + Reason for the new `status`: * `NETWORK_TERMINATED` - Network terminated the QoD provisioning * `DELETE_REQUESTED`- User requested the deletion of the QoD provisioning + """ + + """ + allowed enum values + """ + NETWORK_TERMINATED = 'NETWORK_TERMINATED' + DELETE_REQUESTED = 'DELETE_REQUESTED' + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of StatusInfo from a JSON string""" + return cls(json.loads(json_str)) + + diff --git a/QoDProvisioning/QoDProvisioningAPI/OSLArtifacts/CAMARAaaS-QoD-Provisioning-API-CFS-Specification.json b/QoDProvisioning/QoDProvisioningAPI/OSLArtifacts/CAMARAaaS-QoD-Provisioning-API-CFS-Specification.json new file mode 100644 index 0000000..340e254 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/OSLArtifacts/CAMARAaaS-QoD-Provisioning-API-CFS-Specification.json @@ -0,0 +1,173 @@ +{ + "isBundle": true, + "description": "CAMARAaaS - QoD Provisioning API -CFS", + "lifecycleStatus": "In design", + "name": "CAMARAaaS - QoD Provisioning API - CFS", + "validFor": { + "endDateTime": "2043-12-31T23:59:59.999Z", + "startDateTime": "2024-01-01T00:00:00.001Z" + }, + "version": "0.1.0", + "serviceSpecCharacteristic": [ + { + "name": "messageBroker.address", + "configurable": true, + "description": "OSL's ActiveMQ Address (e.g. 10.10.10.10)", + "extensible": null, + "maxCardinality": 1, + "minCardinality": 1, + "regex": null, + "valueType": "TEXT", + "serviceSpecCharRelationship": [], + "serviceSpecCharacteristicValue": [], + "validFor": { + "endDateTime": "2043-12-31T23:59:59.999Z", + "startDateTime": "2024-01-01T00:00:00.001Z" + } + }, + { + "name": "messageBroker.port", + "configurable": true, + "description": "OSL's ActiveMQ Port", + "extensible": null, + "maxCardinality": 1, + "minCardinality": 1, + "regex": null, + "valueType": "INTEGER", + "serviceSpecCharRelationship": [], + "serviceSpecCharacteristicValue": [], + "validFor": { + "endDateTime": "2043-12-31T23:59:59.999Z", + "startDateTime": "2024-01-01T00:00:00.001Z" + } + }, + { + "name": "messageBroker.username", + "configurable": true, + "description": "OSL's ActiveMQ Username", + "extensible": null, + "maxCardinality": 1, + "minCardinality": 0, + "regex": null, + "valueType": "TEXT", + "serviceSpecCharRelationship": [], + "serviceSpecCharacteristicValue": [], + "validFor": { + "endDateTime": "2043-12-31T23:59:59.999Z", + "startDateTime": "2024-01-01T00:00:00.001Z" + } + }, + { + "name": "messageBroker.password", + "configurable": true, + "description": "OSL's ActiveMQ Password", + "extensible": null, + "maxCardinality": 1, + "minCardinality": 0, + "regex": null, + "valueType": "TEXT", + "serviceSpecCharRelationship": [], + "serviceSpecCharacteristicValue": [], + "validFor": { + "endDateTime": "2043-12-31T23:59:59.999Z", + "startDateTime": "2024-01-01T00:00:00.001Z" + } + }, + { + "name": "serviceUnderControl.uuid", + "configurable": true, + "description": "UUID of the running Service (in the Inventory) that will be controlled through the CAMARAaaS QoD Provisioning API", + "extensible": null, + "maxCardinality": 1, + "minCardinality": 1, + "regex": null, + "valueType": "TEXT", + "serviceSpecCharRelationship": [], + "serviceSpecCharacteristicValue": [], + "validFor": { + "endDateTime": "2043-12-31T23:59:59.999Z", + "startDateTime": "2024-01-01T00:00:00.001Z" + } + }, + { + "name": "camaraAPI.url", + "configurable": false, + "description": "URL of the CAMARA API orchestrated by this service (view-only). This field will be automatically populated when the CAMARA API starts running", + "extensible": null, + "maxCardinality": 1, + "minCardinality": 1, + "regex": null, + "valueType": "TEXT", + "serviceSpecCharRelationship": [], + "serviceSpecCharacteristicValue": [], + "validFor": { + "endDateTime": "2043-12-31T23:59:59.999Z", + "startDateTime": "2024-01-01T00:00:00.001Z" + } + }, + { + "name": "camaraAPI.username", + "configurable": false, + "description": "Username of the CAMARA API orchestrated by this service (view-only). This field will be automatically populated when the CAMARA API starts running", + "extensible": null, + "maxCardinality": 1, + "minCardinality": 1, + "regex": null, + "valueType": "TEXT", + "serviceSpecCharRelationship": [], + "serviceSpecCharacteristicValue": [], + "validFor": { + "endDateTime": "2043-12-31T23:59:59.999Z", + "startDateTime": "2024-01-01T00:00:00.001Z" + } + }, + { + "name": "camaraAPI.password", + "configurable": false, + "description": "Password of the CAMARA API orchestrated by this service (view-only). This field will be automatically populated when the CAMARA API starts running", + "extensible": null, + "maxCardinality": 1, + "minCardinality": 1, + "regex": null, + "valueType": "TEXT", + "serviceSpecCharRelationship": [], + "serviceSpecCharacteristicValue": [], + "validFor": { + "endDateTime": "2043-12-31T23:59:59.999Z", + "startDateTime": "2024-01-01T00:00:00.001Z" + } + }, + { + "name": "camaraAPI.status", + "configurable": false, + "description": "This characteristic (view-only) will be populated with the CAMARA API status (RUNNING, NOT_RUNNING)", + "extensible": null, + "maxCardinality": 1, + "minCardinality": 0, + "regex": null, + "valueType": "TEXT", + "serviceSpecCharRelationship": [], + "serviceSpecCharacteristicValue": [], + "validFor": { + "endDateTime": "2043-12-31T23:59:59.999Z", + "startDateTime": "2024-01-01T00:00:00.001Z" + } + }, + { + "name": "camaraAPI.results", + "configurable": false, + "description": "This characteristic (view-only) will be populated with the CAMARA API Processed Results", + "extensible": null, + "maxCardinality": 1, + "minCardinality": 0, + "regex": null, + "valueType": "TEXT", + "serviceSpecCharRelationship": [], + "serviceSpecCharacteristicValue": [], + "validFor": { + "endDateTime": "2043-12-31T23:59:59.999Z", + "startDateTime": "2024-01-01T00:00:00.001Z" + } + } + ] +} \ No newline at end of file diff --git a/QoDProvisioning/QoDProvisioningAPI/OSLArtifacts/cr-template.yaml b/QoDProvisioning/QoDProvisioningAPI/OSLArtifacts/cr-template.yaml new file mode 100644 index 0000000..568b944 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/OSLArtifacts/cr-template.yaml @@ -0,0 +1,12 @@ +apiVersion: org.etsi.osl/v1 +kind: CAMARAaaS-QoDProvisiongAPI +metadata: + name: _to_be_replaced_by_osl_ +spec: + messageBroker: + address: "%s" + port: %d + username: "%s" + password: "%s" + serviceUnderControl: + uuid: "%s" \ No newline at end of file diff --git a/QoDProvisioning/QoDProvisioningAPI/Operator/Dockerfile b/QoDProvisioning/QoDProvisioningAPI/Operator/Dockerfile new file mode 100644 index 0000000..ed91553 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/Operator/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.11-slim + +RUN apt-get update && apt-get install -y \ + libpq-dev gcc python3-dev && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /opt/app + +COPY ./src /opt/app +RUN pip3 install -r requirements.txt + diff --git a/QoDProvisioning/QoDProvisioningAPI/Operator/chart/.helmignore b/QoDProvisioning/QoDProvisioningAPI/Operator/chart/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/Operator/chart/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/QoDProvisioning/QoDProvisioningAPI/Operator/chart/Chart.yaml b/QoDProvisioning/QoDProvisioningAPI/Operator/chart/Chart.yaml new file mode 100644 index 0000000..a10520f --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/Operator/chart/Chart.yaml @@ -0,0 +1,21 @@ +apiVersion: v2 +name: camaraaas-qod-provisioning-api-op-chart +description: OSL's CAMARAaaS QoD Provisioning API Operator Chart +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "0.1.0" diff --git a/QoDProvisioning/QoDProvisioningAPI/Operator/chart/templates/_helpers.tpl b/QoDProvisioning/QoDProvisioningAPI/Operator/chart/templates/_helpers.tpl new file mode 100644 index 0000000..d1d7548 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/Operator/chart/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "camaraaas-qod-provisioning-api-op-chart.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "camaraaas-qod-provisioning-api-op-chart.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "camaraaas-qod-provisioning-api-op-chart.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "camaraaas-qod-provisioning-api-op-chart.labels" -}} +helm.sh/chart: {{ include "camaraaas-qod-provisioning-api-op-chart.chart" . }} +{{ include "camaraaas-qod-provisioning-api-op-chart.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "camaraaas-qod-provisioning-api-op-chart.selectorLabels" -}} +app.kubernetes.io/name: {{ include "camaraaas-qod-provisioning-api-op-chart.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "camaraaas-qod-provisioning-api-op-chart.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "camaraaas-qod-provisioning-api-op-chart.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/QoDProvisioning/QoDProvisioningAPI/Operator/chart/templates/crd.yaml b/QoDProvisioning/QoDProvisioningAPI/Operator/chart/templates/crd.yaml new file mode 100644 index 0000000..570aaa3 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/Operator/chart/templates/crd.yaml @@ -0,0 +1,82 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: {{ .Values.customResource.plural }}.{{ .Values.customResource.group }} + labels: + {{- include "camaraaas-qod-provisioning-api-op-chart.labels" . | nindent 4 }} +spec: + group: {{ quote .Values.customResource.group }} + names: + plural: {{ quote .Values.customResource.plural }} + singular: {{ quote .Values.customResource.singular }} + kind: {{ quote .Values.customResource.kind }} + shortNames: {{ toYaml .Values.customResource.shortNames | nindent 6 }} + scope: Namespaced + versions: + - name: v1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + status: + type: string + default: NOT_RUNNING + enum: + - "RUNNING" + - "NOT_RUNNING" + messageBroker: + type: object + properties: + address: + type: string + port: + type: integer + # Todo: The username and password should come from a K8s Secret + username: + type: string + password: + type: string + required: + - address + - port + - username + - password + + serviceUnderControl: + type: object + properties: + uuid: + type: string + format: uuid + required: + - uuid + + camaraAPI: + type: object + properties: + status: + type: string + default: "NOT_RUNNING" + enum: + - "RUNNING" + - "NOT_RUNNING" + url: + type: string + default: Yet To Be Configured + username: + type: string + default: Yet To Be Configured + password: + type: string + default: Yet To Be Configured + results: + type: string + default: "" + required: + - serviceUnderControl + - messageBroker \ No newline at end of file diff --git a/QoDProvisioning/QoDProvisioningAPI/Operator/chart/templates/deployment.yaml b/QoDProvisioning/QoDProvisioningAPI/Operator/chart/templates/deployment.yaml new file mode 100644 index 0000000..78e0130 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/Operator/chart/templates/deployment.yaml @@ -0,0 +1,43 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: '{{ include "camaraaas-qod-provisioning-api-op-chart.fullname" . }}-deployment' + namespace: {{ .Release.Namespace }} + labels: + {{- include "camaraaas-qod-provisioning-api-op-chart.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.operator.replicas }} + selector: + matchLabels: + app: camaraaas-qod-provisioning-api-op + {{- include "camaraaas-qod-provisioning-api-op-chart.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + app: camaraaas-qod-provisioning-api-op + {{- include "camaraaas-qod-provisioning-api-op-chart.selectorLabels" . | nindent 8 }} + spec: + containers: + - command: + - python + - k8s_operator.py + env: + - name: LOG_LEVEL + value: {{ quote .Values.logLevel }} + - name: CR_GROUP + value: {{ quote .Values.customResource.group }} + - name: CR_VERSION + value: {{ quote .Values.customResource.version }} + - name: CR_PLURAL + value: {{ quote .Values.customResource.plural }} + - name: CAMARA_API_DOCKER_IMAGE + value: {{ quote .Values.camaraQoDAPI.image }} + - name: CAMARA_API_DOCKER_IMAGE_PORT + value: {{ quote .Values.camaraQoDAPI.port }} + - name: KUBERNETES_CLUSTER_DOMAIN + value: {{ quote .Values.kubernetesClusterDomain }} + image: '{{ .Values.operator.image }}' + name: camaraaas-qod-provisioning-api-op-container + resources: {} + restartPolicy: Always + serviceAccountName: '{{ include "camaraaas-qod-provisioning-api-op-chart.fullname" . }}-svc-account' \ No newline at end of file diff --git a/QoDProvisioning/QoDProvisioningAPI/Operator/chart/templates/operator-role.yaml b/QoDProvisioning/QoDProvisioningAPI/Operator/chart/templates/operator-role.yaml new file mode 100644 index 0000000..fde10ea --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/Operator/chart/templates/operator-role.yaml @@ -0,0 +1,68 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: '{{ include "camaraaas-qod-provisioning-api-op-chart.fullname" . }}-svc-account' + namespace: {{ .Release.Namespace }} + labels: + {{- include "camaraaas-qod-provisioning-api-op-chart.labels" . | nindent 4 }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: '{{ include "camaraaas-qod-provisioning-api-op-chart.fullname" . }}-cluster-role' + namespace: {{ .Release.Namespace }} + labels: + {{- include "camaraaas-qod-provisioning-api-op-chart.labels" . | nindent 4 }} +rules: +- apiGroups: + - "" + resources: + - events + verbs: + - get + - list + - watch + - patch + - create + - update +- apiGroups: + - "" + resources: + - pods + - services + - nodes + verbs: + - get + - list + - watch + - patch + - create + - update + - delete +- apiGroups: + - {{ .Values.customResource.group }} + resources: + - {{ .Values.customResource.plural }} + verbs: + - get + - list + - watch + - patch + - create + - update + - delete +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: '{{ include "camaraaas-qod-provisioning-api-op-chart.fullname" . }}-cluster-role-binding' + labels: + {{- include "camaraaas-qod-provisioning-api-op-chart.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: '{{ include "camaraaas-qod-provisioning-api-op-chart.fullname" . }}-cluster-role' +subjects: +- kind: ServiceAccount + name: '{{ include "camaraaas-qod-provisioning-api-op-chart.fullname" . }}-svc-account' + namespace: '{{ .Release.Namespace }}' \ No newline at end of file diff --git a/QoDProvisioning/QoDProvisioningAPI/Operator/chart/values.yaml b/QoDProvisioning/QoDProvisioningAPI/Operator/chart/values.yaml new file mode 100644 index 0000000..aa33777 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/Operator/chart/values.yaml @@ -0,0 +1,16 @@ +kubernetesClusterDomain: cluster.local +operator: + image: harbor.etsi.org/osl/osl-camaraaas-qod-provisioning-api-op:latest + replicas: 1 +customResource: + group: org.etsi.osl + singular: camaraaas-qod-provisioning-api + plural: camaraaas-qod-provisioning-apis + kind: CAMARAaaS-QoDProvisiongAPI + shortNames: + - qod-provisioning-api + version: v1 +camaraQoDAPI: + image: harbor.etsi.org/osl/osl-camaraaas-qod-provisioning-api:latest + port: 8000 +logLevel: INFO \ No newline at end of file diff --git a/QoDProvisioning/QoDProvisioningAPI/Operator/src/__init__.py b/QoDProvisioning/QoDProvisioningAPI/Operator/src/__init__.py new file mode 100644 index 0000000..abd9611 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/Operator/src/__init__.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# @Authors: +# Eduardo Santos (eduardosantoshf@av.it.pt) +# Rafael Direito (rdireito@av.it.pt) +# @Organization: +# Instituto de Telecomunicações, Aveiro (ITAv) +# Aveiro, Portugal +# @Date: +# December 2024 \ No newline at end of file diff --git a/QoDProvisioning/QoDProvisioningAPI/Operator/src/camaraaas_cr_handler.py b/QoDProvisioning/QoDProvisioningAPI/Operator/src/camaraaas_cr_handler.py new file mode 100644 index 0000000..d89b185 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/Operator/src/camaraaas_cr_handler.py @@ -0,0 +1,284 @@ +# -*- coding: utf-8 -*- +# @Authors: +# Eduardo Santos (eduardosantoshf@av.it.pt) +# Rafael Direito (rdireito@av.it.pt) +# @Organization: +# Instituto de Telecomunicações, Aveiro (ITAv) +# Aveiro, Portugal +# @Date: +# December 2024 +import kopf +from kubernetes import client, config, watch +from kubernetes.client import CoreV1Api +from kubernetes.client import CustomObjectsApi +import requests +import json +from config import Config + +# Set up logging +logger = Config.setup_logging() + +class CAMARAaaSQoDProvisioningAPICRHandler: + + def __init__( + self, + custom_objects_api: CustomObjectsApi, + core_api: CoreV1Api + ): + self.custom_objects_api = custom_objects_api + self.core_api = core_api + + def process_camaraaas_qod_prov_api( + self, event: str, spec: dict, metadata: dict + ) -> None: + + cr_name = metadata['name'] + cr_namespace = metadata['namespace'] + cr_uuid = metadata['uid'] + + if event == "ADD": + logger.info( + f"A resource with group: {Config.cr_group}, " + f"version: {Config.cr_version}, plural: {Config.cr_plural} " + f"was CREATED. This resource is named '{cr_name}' and was " + f"deployed in namespace '{cr_namespace}'. Will now create a " + "a pod and a service to offer this resource" + ) + self._deploy_CAMARAaaS(cr_uuid, cr_name, cr_namespace, spec) + + elif event == "UPDATE": + logger.info( + f"A resource with group: {Config.cr_group}, " + f"version: {Config.cr_version}, plural: {Config.cr_plural} " + f"was UPDATED. This resource is named '{cr_name}' and was " + f"deployed in namespace '{cr_namespace}'. " + f"Resource: {spec}." + ) + + elif event == "DELETE": + logger.info( + f"A resource with group: {Config.cr_group}, " + f"version: {Config.cr_version}, plural: {Config.cr_plural} " + f"was DELETED. This resource was named '{cr_name}' and was " + f"deployed in namespace '{cr_namespace}'. Will now delete its " + "artifacts" + ) + self._delete_CAMARAaaS(cr_uuid, cr_name, cr_namespace) + + + def update_camara_results(self, spec, metadata): + if "camaraAPI" in spec: + try: + response = requests.get( + f"{spec['camaraAPI']['url']}/osl/current-camara-results" + ) + # Validate the HTTP response status + if response.status_code == 200: + results = response.json() + logger.info(f"Processed CAMARA Results: {results}") + self._process_obtained_camara_results( + metadata['namespace'], + metadata['name'], + results + ) + else: + logger.error( + "Could not obtain processed CAMARA Results " + f"for the API available at {spec['camaraAPI']['url']}. " + f"Got HTTP Status Code {response.status_code}. " + f"Response Text: {response.text}" + ) + except Exception as e: + logger.error( + "Could not obtain processed CAMARA Results " + f"for the API available at {spec['camaraAPI']['url']}. " + f"Reason: {e}." + ) + + + def _deploy_CAMARAaaS(self, cr_uuid, cr_name, cr_namespace, spec): + pod_name = f"camara-{cr_uuid}-pod" + service_name = f"camara-{cr_uuid}-service" + app_label = f"camara-{cr_uuid}-app" + + # 1. Deploy the Pod + pod_manifest = { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": pod_name, + "labels": {"app": app_label} + }, + "spec": { + "containers": [{ + "name": "app-container", + "image": f"{Config.camara_api_docker_image}", + "imagePullPolicy": "Always", + "ports": [ + {"containerPort": Config.camara_api_docker_image_port} + ], + "env": [ + { + "name": "BROKER_ADDRESS", + "value": spec["messageBroker"]["address"] + }, + { + "name": "BROKER_PORT", + "value": str(spec["messageBroker"]["port"]) + }, + { + "name": "BROKER_USERNAME", + "value": spec["messageBroker"]["username"] + }, + { + "name": "BROKER_PASSWORD", + "value": spec["messageBroker"]["password"] + }, + { + "name": "SERVICE_UUID", + "value": spec["serviceUnderControl"]["uuid"] + } + ] + }] + } + } + self.core_api.create_namespaced_pod( + namespace=cr_namespace, body=pod_manifest + ) + + # 2. Create a NodePort Service + service_manifest = { + "apiVersion": "v1", + "kind": "Service", + "metadata": {"name": service_name}, + "spec": { + "selector": {"app": app_label}, + "ports": [ + { + "port": Config.camara_api_docker_image_port, + "targetPort": Config.camara_api_docker_image_port + } + ], + "type": "NodePort" + } + } + service = self.core_api.create_namespaced_service( + namespace=cr_namespace, + body=service_manifest + ) + + # 3. Determine the random NodePort assigned + node_port = service.spec.ports[0].node_port + nodes = self.core_api.list_node().items + if not nodes: + raise kopf.PermanentError("No nodes found in the cluster.") + node_ip = next( + ( + addr.address + for addr in nodes[0].status.addresses + if addr.type == "InternalIP" + ), None + ) + + if not node_ip: + raise kopf.PermanentError("No internal node IP found.") + + url = f"http://{node_ip}:{node_port}" + + logger.info(f"CAMARA QoD Provisioning API deployed at: {url}") + self._process_successful_deployment(cr_namespace, cr_name, url) + + + def _delete_CAMARAaaS(self, cr_uuid, cr_name, cr_namespace): + pod_name = f"camara-{cr_uuid}-pod" + service_name = f"camara-{cr_uuid}-service" + app_label = f"camara-{cr_uuid}-app" + + # 1. Delete the Pod + try: + self.core_api.delete_namespaced_pod( + name=pod_name, + namespace=cr_namespace + ) + logger.info(f"Pod {pod_name} deleted successfully.") + except client.exceptions.ApiException as e: + if e.status == 404: + logger.info(f"Pod {pod_name} not found. Skipping deletion.") + else: + raise + + # 2. Delete the Service + try: + self.core_api.delete_namespaced_service( + name=service_name, + namespace=cr_namespace + ) + logger.info(f"Service {service_name} deleted successfully.") + except client.exceptions.ApiException as e: + if e.status == 404: + logger.info(f"Service {service_name} not found.") + else: + raise + + + def _process_successful_deployment(self, namespace, name, url): + patch = { + "spec": { + "camaraAPI": { + "status": "RUNNING", + "url": url, + "username": "Not Applicable", + "password": "Not Applicable" + } + } + } + + try: + # Apply the patch to update 'spec.data2' of the custom resource + self.custom_objects_api.patch_namespaced_custom_object( + group=Config.cr_group, + version=Config.cr_version, + namespace=namespace, + plural=Config.cr_plural, + name=name, + body=patch + ) + logger.info( + f"Updated 'spec.camaraAPI' for {name} in " + f"{namespace} to {patch}") + + except client.exceptions.ApiException as e: + logger.error( + "Exception when updating 'spec.camaraAPI' " + f"in custom resource: {e}") + + + def _process_obtained_camara_results(self, namespace, name, results): + patch = { + "spec": { + "camaraAPI": { + "results": json.dumps(results) + } + } + } + + try: + # Apply the patch to update 'spec.data2' of the custom resource + self.custom_objects_api.patch_namespaced_custom_object( + group=Config.cr_group, + version=Config.cr_version, + namespace=namespace, + plural=Config.cr_plural, + name=name, + body=patch + ) + logger.info( + f"Updated 'spec.camaraAPI.results' for {name} in " + f"{namespace} to {patch}") + + except client.exceptions.ApiException as e: + logger.error( + "Exception when updating 'spec.camaraAPI.results' " + f"in custom resource: {e}") + + diff --git a/QoDProvisioning/QoDProvisioningAPI/Operator/src/config.py b/QoDProvisioning/QoDProvisioningAPI/Operator/src/config.py new file mode 100644 index 0000000..2569eac --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/Operator/src/config.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# @Authors: +# Eduardo Santos (eduardosantoshf@av.it.pt) +# Rafael Direito (rdireito@av.it.pt) +# @Organization: +# Instituto de Telecomunicações, Aveiro (ITAv) +# Aveiro, Portugal +# @Date: +# December 2024 +import logging +import os +import sys + +class Config(): + # Set up custom resources info + cr_group = os.getenv('CR_GROUP') + cr_version = os.getenv('CR_VERSION') + cr_plural = os.getenv('CR_PLURAL') + + # CAMARA API to be deployed + camara_api_docker_image = os.getenv('CAMARA_API_DOCKER_IMAGE') + camara_api_docker_image_port = int( + os.getenv('CAMARA_API_DOCKER_IMAGE_PORT') + ) + + logger = None + + # Logging + @classmethod + def setup_logging(cls): + if cls.logger is None: + + log_level = os.getenv('LOG_LEVEL', 'INFO') + log_level = getattr(logging, log_level.upper()) + + # Create a logger + cls.logger = logging.getLogger() + cls.logger.setLevel(log_level) + + # Create a stream handler that outputs to stdout + handler = logging.StreamHandler(sys.stdout) + handler.setLevel(log_level) + + # Create a formatter and add it to the handler + formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + handler.setFormatter(formatter) + + # Add the handler to the logger + cls.logger.addHandler(handler) + + return cls.logger + + @staticmethod + def cluster(): + """Name of the cluster""" + return os.getenv('CLUSTER_NAME') \ No newline at end of file diff --git a/QoDProvisioning/QoDProvisioningAPI/Operator/src/k8s_operator.py b/QoDProvisioning/QoDProvisioningAPI/Operator/src/k8s_operator.py new file mode 100644 index 0000000..b43b248 --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/Operator/src/k8s_operator.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# @Authors: +# Eduardo Santos (eduardosantoshf@av.it.pt) +# Rafael Direito (rdireito@av.it.pt) +# @Organization: +# Instituto de Telecomunicações, Aveiro (ITAv) +# Aveiro, Portugal +# @Date: +# December 2024 +import kopf +import json +from kubernetes import client, config, watch +from kubernetes.client import CoreV1Api +from kubernetes.client.models.v1_pod import V1Pod +from kubernetes.client.models.v1_container_status import V1ContainerStatus +import time + +from config import Config +from camaraaas_cr_handler import CAMARAaaSQoDProvisioningAPICRHandler + +logger = Config.setup_logging() + +cluster = Config.cluster() + +def kubeconfig() -> client.CoreV1Api: + """This is for using the kubeconfig to auth with the k8s api + with the first try it will try to use the in-cluster config + (so for in cluster use). If it cannot find an incluster because + it is running locally, it will use your local config. + """ + try: + config.load_incluster_config() + logger.info("Loaded in-cluster configuration.") + except config.ConfigException as e: + logger.warning( + "Failed to load in-cluster config, attempting local kubeconfig." + ) + try: + config.load_kube_config(context=cluster) + logger.info(f"Loaded kubeconfig file with context {cluster}.") + except config.ConfigException as e: + logger.error( + "Failed to load kubeconfig: ensure your kubeconfig is valid." + ) + raise + + api = client.CoreV1Api() + return api + +@kopf.on.create(Config.cr_group, Config.cr_version, Config.cr_plural) +def on_create_camaraaas_qod_prov_api( + spec, meta, logger, **kwargs + ): + camaraaas_handler.process_camaraaas_qod_prov_api("ADD", spec, meta) + +@kopf.on.update(Config.cr_group, Config.cr_version, Config.cr_plural) +def on_update_camaraaas_qod_prov_api( + spec, old, new, diff, meta, logger, **kwargs + ): + camaraaas_handler.process_camaraaas_qod_prov_api("UPDATE", spec, meta) + +@kopf.on.delete(Config.cr_group, Config.cr_version, Config.cr_plural) +def on_delete_camaraaas_qod_prov_api( + spec, old, new, diff, meta, logger, **kwargs + ): + camaraaas_handler.process_camaraaas_qod_prov_api("DELETE", spec, meta) + +@kopf.timer(Config.cr_group, Config.cr_version, Config.cr_plural, interval=15) +def periodic_operation(spec, meta, status, namespace, logger, **kwargs): + camaraaas_handler.update_camara_results(spec, meta) + +def main(): + kopf.run() + +if __name__ == '__main__': + v1 = kubeconfig() + custom_api = client.CustomObjectsApi() + camaraaas_handler = CAMARAaaSQoDProvisioningAPICRHandler(custom_api, v1) + main() \ No newline at end of file diff --git a/QoDProvisioning/QoDProvisioningAPI/Operator/src/requirements.txt b/QoDProvisioning/QoDProvisioningAPI/Operator/src/requirements.txt new file mode 100644 index 0000000..7f9789f --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/Operator/src/requirements.txt @@ -0,0 +1,75 @@ +absl-py==2.0.0 +aiohttp==3.9.1 +aiosignal==1.3.1 +annotated-types==0.6.0 +anyio==3.7.1 +astunparse==1.6.3 +attrs==23.1.0 +azure-core==1.29.5 +azure-data-tables==12.4.4 +azure-storage-blob==12.19.0 +azure-storage-queue==12.8.0 +cachetools==5.3.2 +certifi==2023.7.22 +cffi==1.16.0 +charset-normalizer==3.3.0 +click==8.1.7 +cryptography==41.0.5 +fastapi==0.104.1 +flatbuffers==23.5.26 +frozenlist==1.4.0 +gast==0.5.4 +google-auth==2.24.0 +google-auth-oauthlib==1.2.0 +google-pasta==0.2.0 +grpcio==1.60.0 +h11==0.14.0 +h5py==3.10.0 +httpcore==1.0.2 +httpx==0.25.1 +idna==3.4 +iso8601==2.1.0 +isodate==0.6.1 +jsonpatch==1.33 +jsonpointer==2.4 +keras==2.15.0 +kopf==1.36.2 +kubernetes==28.1.0 +libclang==16.0.6 +Markdown==3.5.1 +MarkupSafe==2.1.3 +ml-dtypes==0.2.0 +multidict==6.0.4 +numpy==1.26.1 +oauthlib==3.2.2 +opt-einsum==3.3.0 +packaging==23.2 +pandas==2.1.1 +protobuf==4.23.4 +psycopg2-binary==2.9.9 +pyasn1==0.5.1 +pyasn1-modules==0.3.0 +pycparser==2.21 +pydantic==2.4.2 +pydantic_core==2.10.1 +python-dateutil==2.8.2 +python-json-logger==2.0.7 +python-multipart==0.0.6 +pytz==2023.3.post1 +requests==2.31.0 +requests-oauthlib==1.3.1 +rsa==4.9 +six==1.16.0 +sniffio==1.3.0 +starlette==0.27.0 +tensorboard==2.15.1 +termcolor==2.4.0 +typing_extensions==4.8.0 +tzdata==2023.3 +urllib3==1.26.18 +uvicorn==0.23.2 +websocket-client==1.7.0 +Werkzeug==3.0.1 +wrapt==1.14.1 +yarl==1.9.2 +asyncio==3.4.3 diff --git a/QoDProvisioning/QoDProvisioningAPI/Operator/test-cr.yaml b/QoDProvisioning/QoDProvisioningAPI/Operator/test-cr.yaml new file mode 100644 index 0000000..0c8936a --- /dev/null +++ b/QoDProvisioning/QoDProvisioningAPI/Operator/test-cr.yaml @@ -0,0 +1,12 @@ +apiVersion: org.etsi.osl/v1 +kind: CAMARAaaS-QoDProvisiongAPI +metadata: + name: test-qod-provisioning +spec: + messageBroker: + address: "10.255.28.137" + port: 61613 + username: "artemis" + password: "artemis" + serviceUnderControl: + uuid: "6b6b2f37-b232-4a6a-b9bf-55f96dbdb773" \ No newline at end of file -- GitLab