diff --git a/Documentation/CAMARAaaS-Architecture.png b/Documentation/CAMARAaaS-Architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..618a1eeb77809e3e2dd5189c61764b5277c72031 Binary files /dev/null and b/Documentation/CAMARAaaS-Architecture.png differ diff --git a/Documentation/CAMARAaaS-Workflow-OSLToday.png b/Documentation/CAMARAaaS-Workflow-OSLToday.png new file mode 100644 index 0000000000000000000000000000000000000000..1f0e3c285aaec4a12d26e0acdc606be09be2a3a8 Binary files /dev/null and b/Documentation/CAMARAaaS-Workflow-OSLToday.png differ diff --git a/Documentation/CAMARAaaS-Workflow-ServiceOperation.png b/Documentation/CAMARAaaS-Workflow-ServiceOperation.png new file mode 100644 index 0000000000000000000000000000000000000000..9a451b2ecba49978c734dd05c753737889231423 Binary files /dev/null and b/Documentation/CAMARAaaS-Workflow-ServiceOperation.png differ diff --git a/Documentation/CAMARAaaS-Workflow-ServiceOrdering.png b/Documentation/CAMARAaaS-Workflow-ServiceOrdering.png new file mode 100644 index 0000000000000000000000000000000000000000..8b10df21a6eac26d73d4ab60d3c6af464b461a9f Binary files /dev/null and b/Documentation/CAMARAaaS-Workflow-ServiceOrdering.png differ diff --git a/LICENSE b/LICENSE index 9292d7efebb89a715eee997e42fcfef865a889b1..0814be9311fa5e02586ee2f5c4b3e82529ea0b48 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2024 trantzas + Copyright 2024 Rafael Direito and Eduardo Santos Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/QoDProvisioning/.gitignore b/QoDProvisioning/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..dc410fe657d8f34f7e05f5cf2bb0a1c948ac119a --- /dev/null +++ b/QoDProvisioning/.gitignore @@ -0,0 +1,286 @@ +# Created by https://www.toptal.com/developers/gitignore/api/macos,windows,linux,python,venv,visualstudiocode +# Edit at https://www.toptal.com/developers/gitignore?templates=macos,windows,linux,python,venv,visualstudiocode + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +### venv ### +# Virtualenv +# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ +[Bb]in +[Ii]nclude +[Ll]ib +[Ll]ib64 +[Ll]ocal +[Ss]cripts +pyvenv.cfg +pip-selfcheck.json + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + + +### Helm ### +# Chart dependencies +**/charts/*.tgz +.tgz +# End of https://www.toptal.com/developers/gitignore/api/macos,windows,linux,python,venv,visualstudiocode,helm \ No newline at end of file diff --git a/QoDProvisioning/Documentation/Pictures/CAMARA-QoDProvisioning-API-Endpoints.png b/QoDProvisioning/Documentation/Pictures/CAMARA-QoDProvisioning-API-Endpoints.png new file mode 100644 index 0000000000000000000000000000000000000000..b0fee4ea886564af09d804abc4343151d9dfa1c1 Binary files /dev/null and b/QoDProvisioning/Documentation/Pictures/CAMARA-QoDProvisioning-API-Endpoints.png differ diff --git a/QoDProvisioning/Documentation/Pictures/CAMARAaaS-QoD-Prov-API-Active-Services.png b/QoDProvisioning/Documentation/Pictures/CAMARAaaS-QoD-Prov-API-Active-Services.png new file mode 100644 index 0000000000000000000000000000000000000000..0db82e2ade75e445b0d6bc15f4f3c1712ccfda21 Binary files /dev/null and b/QoDProvisioning/Documentation/Pictures/CAMARAaaS-QoD-Prov-API-Active-Services.png differ diff --git a/QoDProvisioning/Documentation/Pictures/CAMARAaaS-QoD-Prov-API-CR.png b/QoDProvisioning/Documentation/Pictures/CAMARAaaS-QoD-Prov-API-CR.png new file mode 100644 index 0000000000000000000000000000000000000000..f13ab200fc2134da69c27ded1046dc7d6415ed28 Binary files /dev/null and b/QoDProvisioning/Documentation/Pictures/CAMARAaaS-QoD-Prov-API-CR.png differ diff --git a/QoDProvisioning/Documentation/Pictures/CAMARAaaS-QoD-Prov-API-Characteristics-After-CAMARA-Invoking.png b/QoDProvisioning/Documentation/Pictures/CAMARAaaS-QoD-Prov-API-Characteristics-After-CAMARA-Invoking.png new file mode 100644 index 0000000000000000000000000000000000000000..d5a0b0f1db42fadae5c950e3f55badba479b713d Binary files /dev/null and b/QoDProvisioning/Documentation/Pictures/CAMARAaaS-QoD-Prov-API-Characteristics-After-CAMARA-Invoking.png differ diff --git a/QoDProvisioning/Documentation/Pictures/CAMARAaaS-QoD-Prov-API-Characteristics.png b/QoDProvisioning/Documentation/Pictures/CAMARAaaS-QoD-Prov-API-Characteristics.png new file mode 100644 index 0000000000000000000000000000000000000000..68b03b17eb2749f8df9cfaf44e5f6f3840e98376 Binary files /dev/null and b/QoDProvisioning/Documentation/Pictures/CAMARAaaS-QoD-Prov-API-Characteristics.png differ diff --git a/QoDProvisioning/Documentation/Pictures/CAMARAaaS-QoD-Prov-API-Ordering.png b/QoDProvisioning/Documentation/Pictures/CAMARAaaS-QoD-Prov-API-Ordering.png new file mode 100644 index 0000000000000000000000000000000000000000..1f85d2a8ce4a850ef7a896b22f3f33f5989511e8 Binary files /dev/null and b/QoDProvisioning/Documentation/Pictures/CAMARAaaS-QoD-Prov-API-Ordering.png differ diff --git a/QoDProvisioning/Documentation/Pictures/CAMARAaaS-QoD-Prov-API-Pre-Provision-Rule.png b/QoDProvisioning/Documentation/Pictures/CAMARAaaS-QoD-Prov-API-Pre-Provision-Rule.png new file mode 100644 index 0000000000000000000000000000000000000000..09bd1f6d7b6b14da0e90736be41386e8fa30e5d9 Binary files /dev/null and b/QoDProvisioning/Documentation/Pictures/CAMARAaaS-QoD-Prov-API-Pre-Provision-Rule.png differ diff --git a/QoDProvisioning/Documentation/Pictures/CAMARAaaS-QoD-Prov-API-Supervision-Rule.png b/QoDProvisioning/Documentation/Pictures/CAMARAaaS-QoD-Prov-API-Supervision-Rule.png new file mode 100644 index 0000000000000000000000000000000000000000..e08ec3ea0816e5b2ce8d8cc57854afb76ac407f6 Binary files /dev/null and b/QoDProvisioning/Documentation/Pictures/CAMARAaaS-QoD-Prov-API-Supervision-Rule.png differ diff --git a/QoDProvisioning/Documentation/Pictures/DummyOperatorService-Characteristics-After-CAMARA-Invoking.png b/QoDProvisioning/Documentation/Pictures/DummyOperatorService-Characteristics-After-CAMARA-Invoking.png new file mode 100644 index 0000000000000000000000000000000000000000..4fa0a94c30341b6f031e8c16dee8e0cabd905061 Binary files /dev/null and b/QoDProvisioning/Documentation/Pictures/DummyOperatorService-Characteristics-After-CAMARA-Invoking.png differ diff --git a/QoDProvisioning/Documentation/Pictures/DummyOperatorService-CustomResource.png b/QoDProvisioning/Documentation/Pictures/DummyOperatorService-CustomResource.png new file mode 100644 index 0000000000000000000000000000000000000000..97b9452c42aba6ac7e5e726d99e176fdb7814786 Binary files /dev/null and b/QoDProvisioning/Documentation/Pictures/DummyOperatorService-CustomResource.png differ diff --git a/QoDProvisioning/Documentation/Pictures/DummyOperatorService-Pre-Provision-Rule.png b/QoDProvisioning/Documentation/Pictures/DummyOperatorService-Pre-Provision-Rule.png new file mode 100644 index 0000000000000000000000000000000000000000..8de481239e3954ee2f33d9a75d01e2b868ec0757 Binary files /dev/null and b/QoDProvisioning/Documentation/Pictures/DummyOperatorService-Pre-Provision-Rule.png differ diff --git a/QoDProvisioning/Documentation/Pictures/DummyOperatorService-ResourceInventory.png b/QoDProvisioning/Documentation/Pictures/DummyOperatorService-ResourceInventory.png new file mode 100644 index 0000000000000000000000000000000000000000..7864d9534f868b101e55ff6d10d0298f443f61a3 Binary files /dev/null and b/QoDProvisioning/Documentation/Pictures/DummyOperatorService-ResourceInventory.png differ diff --git a/QoDProvisioning/Documentation/Pictures/DummyOperatorService-ServiceOrder.png b/QoDProvisioning/Documentation/Pictures/DummyOperatorService-ServiceOrder.png new file mode 100644 index 0000000000000000000000000000000000000000..e0054c319c533a78a906fe2222944bfd525b17ed Binary files /dev/null and b/QoDProvisioning/Documentation/Pictures/DummyOperatorService-ServiceOrder.png differ diff --git a/QoDProvisioning/Documentation/Pictures/DummyOperatorService-Supervision-Rule.png b/QoDProvisioning/Documentation/Pictures/DummyOperatorService-Supervision-Rule.png new file mode 100644 index 0000000000000000000000000000000000000000..853e3bd0a8590a31e9a142fb910f37fe7d7b672a Binary files /dev/null and b/QoDProvisioning/Documentation/Pictures/DummyOperatorService-Supervision-Rule.png differ diff --git a/QoDProvisioning/Documentation/Pictures/QoDProvisioningAPI-Docs.png b/QoDProvisioning/Documentation/Pictures/QoDProvisioningAPI-Docs.png new file mode 100644 index 0000000000000000000000000000000000000000..228fa75ab819509f16da8f20a2a7c5412bc1141d Binary files /dev/null and b/QoDProvisioning/Documentation/Pictures/QoDProvisioningAPI-Docs.png differ diff --git a/QoDProvisioning/DummyOperatorService/OSLArtifacts/DummyOperatorService-CFS-Specification.json b/QoDProvisioning/DummyOperatorService/OSLArtifacts/DummyOperatorService-CFS-Specification.json new file mode 100644 index 0000000000000000000000000000000000000000..1809856d347612527f7b64f42692dc8b1184388a --- /dev/null +++ b/QoDProvisioning/DummyOperatorService/OSLArtifacts/DummyOperatorService-CFS-Specification.json @@ -0,0 +1,205 @@ +{ + "name": "Dummy Operator Service - CFS", + "description": "Dummy Operator Service - CFS", + "lifecycleStatus": "In design", + "version": "0.1.0", + "validFor": { + "endDateTime": "2043-12-31T23:59:59.999Z", + "startDateTime": "2024-01-01T00:00:00.001Z" + }, + "isBundle": null, + "serviceSpecCharacteristic": [ + { + "name": "qodProv.operation", + "configurable": false, + "description": null, + "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": "qodProv.provisioningId", + "configurable": false, + "description": null, + "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": "qodProv.qosProfile", + "configurable": false, + "description": null, + "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": "qodProv.device.phoneNumber", + "configurable": false, + "description": null, + "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": "qodProv.device.networkAccessIdentifier", + "configurable": false, + "description": null, + "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": "qodProv.device.ipv4Address.publicAddress", + "configurable": false, + "description": null, + "extensible": null, + "maxCardinality": 1, + "minCardinality": 0, + "regex": null, + "valueType": null, + "serviceSpecCharRelationship": [], + "serviceSpecCharacteristicValue": [], + "validFor": { + "endDateTime": "2043-12-31T23:59:59.999Z", + "startDateTime": "2024-01-01T00:00:00.001Z" + } + }, + { + "name": "qodProv.device.ipv4Address.privateAddress", + "configurable": false, + "description": null, + "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": "qodProv.device.ipv4Address.publicPort", + "configurable": false, + "description": null, + "extensible": null, + "maxCardinality": 1, + "minCardinality": 0, + "regex": null, + "valueType": "INTEGER", + "serviceSpecCharRelationship": [], + "serviceSpecCharacteristicValue": [], + "validFor": { + "endDateTime": "2043-12-31T23:59:59.999Z", + "startDateTime": "2024-01-01T00:00:00.001Z" + } + }, + { + "name": "qodProv.device.ipv6Address", + "configurable": false, + "description": null, + "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": "qodProv.sink", + "configurable": false, + "description": null, + "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": "qodProv.sinkCredential.credentialType", + "configurable": false, + "description": null, + "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": "camaraResults", + "configurable": false, + "description": null, + "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/DummyOperatorService/OSLArtifacts/cr-template-pre-provision.yaml b/QoDProvisioning/DummyOperatorService/OSLArtifacts/cr-template-pre-provision.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9a9169fee5bbf724bb7a0f3503e087cf31525112 --- /dev/null +++ b/QoDProvisioning/DummyOperatorService/OSLArtifacts/cr-template-pre-provision.yaml @@ -0,0 +1,7 @@ +apiVersion: org.etsi.osl/v1 +kind: DummyOperatorService +metadata: + 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 new file mode 100644 index 0000000000000000000000000000000000000000..fc5cd6c5974a796e8f4aee0eb2bb5bc904a51c16 --- /dev/null +++ b/QoDProvisioning/DummyOperatorService/OSLArtifacts/cr-template-supervision.yaml @@ -0,0 +1,21 @@ +apiVersion: org.etsi.osl/v1 +kind: DummyOperatorService +metadata: + name: _to_be_replaced_by_osl_ + namespace: default +spec: + qodProv: + operation: "%s" + provisioningId: "%s" + device: + phoneNumber: "%s" + networkAccessIdentifier: "%s" + ipv4Address: + publicAddress: "%s" + privateAddress: "%s" + publicPort: %d + ipv6Address: "%s" + qosProfile: "%s" + sink: "%s" + sinkCredential: + credentialType: "%s" diff --git a/QoDProvisioning/DummyOperatorService/OSLArtifacts/pre_provision_rule.java b/QoDProvisioning/DummyOperatorService/OSLArtifacts/pre_provision_rule.java new file mode 100644 index 0000000000000000000000000000000000000000..529ab034cf6596cef74db2d750d0d571c76649a9 --- /dev/null +++ b/QoDProvisioning/DummyOperatorService/OSLArtifacts/pre_provision_rule.java @@ -0,0 +1,14 @@ +{ +java.util.HashMap<String,String> charvals = new java.util.HashMap<>(); +charvals.put("_CR_SPEC",String.format(""" +apiVersion: org.etsi.osl/v1 +kind: DummyOperatorService +metadata: + name: _to_be_replaced_by_osl + namespace: default +spec: + status: "%s" +""" +, "RUNNING")); +setServiceRefCharacteristicsValues("Dummy Operator Service - RFS", charvals); +} diff --git a/QoDProvisioning/DummyOperatorService/OSLArtifacts/supervision_rule.java b/QoDProvisioning/DummyOperatorService/OSLArtifacts/supervision_rule.java new file mode 100644 index 0000000000000000000000000000000000000000..cce5c806c83a7984eabddd5d05f09221bc713b61 --- /dev/null +++ b/QoDProvisioning/DummyOperatorService/OSLArtifacts/supervision_rule.java @@ -0,0 +1,28 @@ +{ +java.util.HashMap<String,String> charvals = new java.util.HashMap<>(); +charvals.put("_CR_SPEC",String.format(""" +apiVersion: org.etsi.osl/v1 +kind: DummyOperatorService +metadata: + name: _to_be_replaced_by_osl + namespace: default +spec: + qodProv: + operation: "%s" + provisioningId: "%s" + device: + phoneNumber: "%s" + networkAccessIdentifier: "%s" + ipv4Address: + publicAddress: "%s" + privateAddress: "%s" + publicPort: %d + ipv6Address: "%s" + qosProfile: "%s" + sink: "%s" + sinkCredential: + credentialType: "%s" +""" +, getCharValAsString("qodProv.operation"), getCharValAsString("qodProv.provisioningId"), getCharValAsString("qodProv.device.phoneNumber"), getCharValAsString("qodProv.device.networkAccessIdentifier"), getCharValAsString("qodProv.device.ipv4Address.publicAddress"), getCharValAsString("qodProv.device.ipv4Address.privateAddress"), getCharValNumber("qodProv.device.ipv4Address.publicPort"), getCharValAsString("qodProv.device.ipv6Address"), getCharValAsString("qodProv.qosProfile"), getCharValAsString("qodProv.sink"), getCharValAsString("qodProv.sinkCredential.credentialType"))); +setServiceRefCharacteristicsValues("Dummy Operator Service - RFS", charvals); +} \ No newline at end of file diff --git a/QoDProvisioning/DummyOperatorService/crd.yaml b/QoDProvisioning/DummyOperatorService/crd.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b6e663ff094d37f1f08d4c7d9434a0a66b4ba74c --- /dev/null +++ b/QoDProvisioning/DummyOperatorService/crd.yaml @@ -0,0 +1,70 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: dummy-operator-services.org.etsi.osl +spec: + group: org.etsi.osl + 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" + qodProv: + type: object + properties: + operation: + type: string + provisioningId: + type: string + device: + type: object + properties: + phoneNumber: + type: string + pattern: '^\+[1-9][0-9]{4,14}$' + networkAccessIdentifier: + type: string + ipv4Address: + type: object + properties: + publicAddress: + type: string + privateAddress: + type: string + publicPort: + type: integer + ipv6Address: + type: string + qosProfile: + type: string + sink: + type: string + sinkCredential: + type: object + properties: + credentialType: + type: string + camaraResults: + type: string + default: "[]" + status: + type: object + scope: Namespaced + names: + plural: dummy-operator-services + singular: dummy-operator-service + kind: DummyOperatorService + shortNames: + - dos diff --git a/QoDProvisioning/DummyOperatorService/example-cr.yaml b/QoDProvisioning/DummyOperatorService/example-cr.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e3239f5813addf57e49f51d25bff5da85c2e98e8 --- /dev/null +++ b/QoDProvisioning/DummyOperatorService/example-cr.yaml @@ -0,0 +1,22 @@ +apiVersion: org.etsi.osl/v1 +kind: DummyOperatorService +metadata: + name: example-dummy-operator-service + namespace: default +spec: + status: RUNNING + qodProv: + operation: CREATE + provisioningId: 3fa85f64-5717-4562-b3fc-2c963f66afa6 + device: + phoneNumber: "+123456789" + networkAccessIdentifier: "123456789@domain.com" + ipv4Address: + publicAddress: "203.0.113.0" + privateAddress: "192.168.1.100" + publicPort: 5000 + ipv6Address: "2001:db8::ff00:42:8329" + qosProfile: "QOS_A" + sink: "https://endpoint.example.com/sink" + sinkCredential: + credentialType: "PLAIN" diff --git a/QoDProvisioning/Makefile b/QoDProvisioning/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..b109f4892f007b6c8788d3adbcbe8bbb3d10d9f0 --- /dev/null +++ b/QoDProvisioning/Makefile @@ -0,0 +1,80 @@ + +# 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 + +delete-dummy-operator-crd: + kubectl delete -f ./DummyOperatorService/crd.yaml || true + +create-dummy-operator-cr: + kubectl apply -f ./DummyOperatorService/example-cr.yaml + +delete-dummy-operator-cr: + kubectl delete -f ./DummyOperatorService/example-cr.yaml || true + +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 0000000000000000000000000000000000000000..86d4a8c41f98aa2f669d4520d4011c3481bd3cb2 --- /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 0000000000000000000000000000000000000000..e95c62763869ed2816cdc88cc0beb8398da1520a --- /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 0000000000000000000000000000000000000000..3361aa12c55b87e19bb938b58c31bbf80956030f --- /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 0000000000000000000000000000000000000000..69156ef812cf6d86f3abb94c497451e7616741b6 --- /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 0000000000000000000000000000000000000000..0e7e2cc4a1593a441b7e14a3eace4c1f9d3e88c8 --- /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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/aux/constants.py b/QoDProvisioning/QoDProvisioningAPI/API/src/aux/constants.py new file mode 100644 index 0000000000000000000000000000000000000000..b9ec3937c2bc808b4ad9106a97670eaedfa7e6fa --- /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 0000000000000000000000000000000000000000..c9794a50034d1713831a33c7f890a71d1d571837 --- /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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 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 0000000000000000000000000000000000000000..ea22ca46a80abaa41bafa2a159a4bae3bbcfcbc9 --- /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 0000000000000000000000000000000000000000..845b013a45fe923fdb2c5c6ad4bb9bef9154ea69 --- /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 0000000000000000000000000000000000000000..78a34e012cb37e3faa3527b38c5393468a87982c --- /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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 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 0000000000000000000000000000000000000000..26705e9f1e591c3c84df07046bfc8f296823b5f9 --- /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 0000000000000000000000000000000000000000..a7d0434711e983e1dd761f04683069dc94e6a7c8 --- /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 0000000000000000000000000000000000000000..e1cc7408dba98f3e71d62569e94ad02f9f98d5ff --- /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 0000000000000000000000000000000000000000..e2a6fce312d5fc8d10e57f98494eceeec83ac676 --- /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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/QoDProvisioning/QoDProvisioningAPI/API/src/routers/osl.py b/QoDProvisioning/QoDProvisioningAPI/API/src/routers/osl.py new file mode 100644 index 0000000000000000000000000000000000000000..d4e528f52be36ebcbb98b4fc96644ec221119069 --- /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 0000000000000000000000000000000000000000..f199afbdf963a46c6dc384a65152ebeecb79bc9a --- /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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 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 0000000000000000000000000000000000000000..cf2651744bc4095aaad2e6ce5b98d8b8d2e32cc8 --- /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 0000000000000000000000000000000000000000..7c5f2fd16293d980da0f090d96e8ca1bd0b983c7 --- /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 0000000000000000000000000000000000000000..b9ad221b6e02be4d19866fff7bc9323b89f6c2dc --- /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 0000000000000000000000000000000000000000..f64ddb72d110de26e5e8f4e154d0c1bcbe5653d4 --- /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 0000000000000000000000000000000000000000..756a2faaca7a9cca45c01935720611d4b5f83667 --- /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 0000000000000000000000000000000000000000..34fc05f22140f5c7b9193f9e9f71ccfa4c1c0f6b --- /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 0000000000000000000000000000000000000000..5e0fb55f433874892894080d6c5bb5eb9d24852e --- /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 0000000000000000000000000000000000000000..c65a5d18fb7549e2f067ae1d73f7674364de07e8 --- /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 0000000000000000000000000000000000000000..e8d2a7eace397224750c4b36097b48606a22ce33 --- /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 0000000000000000000000000000000000000000..a3a283fb842b35d5bde6d9bae92ed6d4be4016fa --- /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 0000000000000000000000000000000000000000..5e4ae5cc5c8b7a09ec7ff51febea1c1204065af0 --- /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 0000000000000000000000000000000000000000..797e50ae9e212931fac209e0012674c7133b5a67 --- /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 0000000000000000000000000000000000000000..9e9d3dc84819760277f049d761f63ca52715893b --- /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 0000000000000000000000000000000000000000..08ff72b52e82ca655102354a0f77c6f9951a80d9 --- /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 0000000000000000000000000000000000000000..3f92cae01b9b8bccfaad2d2b595abf1a29669195 --- /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 0000000000000000000000000000000000000000..9a372e5d52443af03f457d95472fe36421775f99 --- /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 0000000000000000000000000000000000000000..54bbc86ded899223114c5546e4b0194e1f48fa0d --- /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 0000000000000000000000000000000000000000..1c6a3933b4e85c23bc097ff25fe60d14a47b5ff0 --- /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 0000000000000000000000000000000000000000..340e254a0d657b22aa87a0bac23c7387364b5e9c --- /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 0000000000000000000000000000000000000000..568b944d3fa627a05d711a1a6a39b3d5bbf1d557 --- /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 0000000000000000000000000000000000000000..ed91553912292b21fe41aff26869b42eb92482fa --- /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 0000000000000000000000000000000000000000..0e8a0eb36f4ca2c939201c0d54b5d82a1ea34778 --- /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 0000000000000000000000000000000000000000..a10520f894ffbdc92a57dac2834e476e429ce22a --- /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 0000000000000000000000000000000000000000..d1d7548ca0cde99f32c29ac92d9135dbf4023ea5 --- /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 0000000000000000000000000000000000000000..570aaa37835b47e1b12bba06ba0d6cb569e54901 --- /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 0000000000000000000000000000000000000000..78e0130b0647a358e64a56de9021ccf34fc9a76e --- /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 0000000000000000000000000000000000000000..fde10ea3a99f1d6d9aeb9b1724e4de7eb4f5bbb8 --- /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 0000000000000000000000000000000000000000..aa337775682453752c6c8f1bb9d6728d67843465 --- /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 0000000000000000000000000000000000000000..abd9611b1a35024db78d932768af6953874740fb --- /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 0000000000000000000000000000000000000000..d89b185eb0579dd369dd5b6faaebca9afb587153 --- /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 0000000000000000000000000000000000000000..2569eac5f6807c39b2f4227661d3f0ce53b28c35 --- /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 0000000000000000000000000000000000000000..b43b248decdcbc9686cb01a9b705b7c0a39791d0 --- /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 0000000000000000000000000000000000000000..7f9789f730583ede2f45a5055c01e48f5e1f60b8 --- /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 0000000000000000000000000000000000000000..0c8936a2ab79c91c5f54015c6673b5760f05e7c0 --- /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 diff --git a/QoDProvisioning/README.md b/QoDProvisioning/README.md new file mode 100644 index 0000000000000000000000000000000000000000..3acc181ac161e5bb1dc25df9fe1be8e4bf48ff18 --- /dev/null +++ b/QoDProvisioning/README.md @@ -0,0 +1,418 @@ + + +# Proof of Concept + +## Candidate CAMARA API - QoD Provisioning + +For this first proof of concept, we decided to rely on the [CAMARA QoD Provisioning API](https://editor.swagger.io/?url=https://raw.githubusercontent.com/camaraproject/QualityOnDemand/r1.2/code/API_definitions/qod-provisioning.yaml ). + +Such API has the following endpoints: + + + +### Mapping to TMF Service Characteristics (of the operator’s Service) + +Having chosen the candidate API, the first step is to find a way to map the possible requests to TMF Service characteristics of the operator’s service. By looking at API’s specification, it is clear that at least 3 operations are required: (i) the creation of a QoD profile, (ii) its deletion, and (iii) listing all active QoD profiles. Therefore, we can proceed with evaluating the payload required for creating a QoD Provisioning. This payload involves various fields, which can be translated to the TMF Service Characteristics: + +- *qodProv.device.phoneNumber* +- *qodProv.device.networkAccessIdentifier* +- *qodProv.device.ipv4Address.privateAddress* +- *qodProv.device.ipv4Address.publicAddress* +- *qodProv.device.ipv4Address.publicPort* +- *qodProv.device.ipv6Address* +- *qodProv.qosProfile* +- *qodProv.sink* +- *qodProv.sinkCredential.credentialType* + +In order to support interaction with OSL’s CAMARAaaS APIs, the operator service must be designed, at least, with these characteristics. + +Still, since there are various operations that can take place (CREATE and DELETE), it is also needed a characteristic to map this. Therefore, the operator’s service must also have a characteristics titled *qodProv.operation*. The DELETE operation is achieved based on a provisioning Id, and therefore another characteristics is needed: *qodProv.provisioningId.* + +Finally, it is required a characteristic to store the provisionings that were enforced by the operator’s service. We can define this characteristic as *camaraResults*. + +Therefore, for an operator’s service to be controlled by OSL’s CAMARA APIs, it needs to be designed with, at least, the following characteristics: + +- *qodProv.device.phoneNumber* +- *qodProv.device.networkAccessIdentifier* +- *qodProv.device.ipv4Address.privateAddress* +- *qodProv.device.ipv4Address.publicAddress* +- *qodProv.device.ipv4Address.publicPort* +- *qodProv.device.ipv6Address* +- *qodProv.qosProfile* +- *qodProv.sink* +- *qodProv.sinkCredential.credentialType* +- *qodProv.operation* +- *qodProv.provisioningId* +- *camaraResults* + +Additional characteristics are fully supported. Those can be custom characteristics that are required by the Operator’s Service. + +In regard to the *camaraResults* characteristic, to allow interoperability, it must store a Stringified JSON Array with the enforced QoD Provisionings. **The schema of each provisioning should be the one defined in CAMARA’s QoD Provisioning API Specification.** + +### TMF Service Characteristics of the CAMARAaaS APIs + +Considering the interactions that shall take place between the CAMARAaaS API and the Operator’s running Service and the architecture introduced before, it is clear that CAMARA APIs must interface with OSL’s Active MQ broker. Therefore, TMF Specific Service Characteristics are required to pass this information to the CAMARA APIs deployed through OSL: + +- messageBroker.address - OSL's ActiveMQ Address (e.g. 10.10.10.10) +- messageBroker.port - OSL's ActiveMQ Port +- messageBroker.username - OSL’s ActiveMQ Username +- messageBroker.password - OSL’s ActiveMQ Password + +Additionally, we also need another Service Characteristic to store the UUID of the Operator’s running Service that will be controlled through the CAMARA API: + +- serviceUnderControl.uuid + +Considering that the CAMARA API will be orchestrated by OSL, the client does not know where the API will be deployed, nor the credentials he should use to access it. Therefore, 4 additional characteristics are required. These will be automatically updated by OSL after the CAMARA API Service is deployed: + +- camaraAPI.url - URL of the CAMARA API orchestrated by this service (view-only). This field will be automatically populated when the CAMARA API starts running +- camaraAPI.username - Username of the CAMARA API orchestrated by this service (view-only). This field will be automatically populated when the CAMARA API starts running +- camaraAPI.password - Password of the CAMARA API orchestrated by this service (view-only). This field will be automatically populated when the CAMARA API starts running +- camaraAPI.status - This characteristic (view-only) will be populated with the CAMARA API status (RUNNING, NOT_RUNNING) + +Additionally, you may create a characteristic titled “*camaraAPI.results*â€, which you can use to have visibility of the QoD Provisionings processed by the API, at OSL level. Still, this characteristic is not required. + +Therefore, OSL’s CAMARA APIs must offer the following TMF Service Characteristics: + +- messageBroker.address +- messageBroker.port +- messageBroker.username +- messageBroker.password +- serviceUnderControl.uuid +- camaraAPI.url +- camaraAPI.username +- camaraAPI.password +- camaraAPI.status +- camaraAPI.results + +### Broker Connection + +This API has a *ServiceEventManager* class that communicates with OpenSlice's ActiveMQ broker through two topics: + +- `CATALOG.UPD.SERVICE`: Topic for catalog updates. +- `EVENT.SERVICE.ATTRCHANGED`: Topic for service attribute changes. + +#### CATALOG.UPD.SERVICE + +Whenever a new provisioning is created for an UE, the *ServiceEventManager*'s *update_service* method is called. This method sends a a service update message through OpenSlice's *CATALOG.UPD.SERVICE* topic. When OSL receives the request, it updates the Service with the new characteristics, which are then caught by the correspondent K8s Operator. After processing the request, the Operator adds the result to the Service-related CR *camaraResults* characteristic. + +#### EVENT.SERVICE.ATTRCHANGED + +The *ServiceEventManager* subscribes to this topic to obtain and process the update messages regarding the specified UE QoD Profile Enforcer OSL service. Whenever this service's characteristics are updated in OSL, this class catches the update message. Then, the class extracts the *camaraResults* characteristic, which contains all QoS provisionings applied to the UEs. + +These results are then processed by the *CamaraResultsProcessor* class, which updates each provisioning accordingly in the database. + + + + +## How To / Demonstration + +### 1. Dummy Operator Service Service Design +We will start by looking at the dummy operator’s service we have created to demonstrate this Add-on. This Service will be offered as simple Custom Resource. You may find its Custom Resource Definition under `/DummyOperatorService/crd.yaml` Look at the CRD fields. Please notice that these were defined according what we disclosed before. + +The first step is then to install this CRD in your kubernetes cluster. To this end, you may use the following command: `make create-dummy-operator-crd` + +After creating the CRD in your Kubernetes cluster, you may access OSL’s Resource Inventory and you will see the just created resource there. + + + +The next step is to create a RFS Service to expose this resource. To do so, you may read the [Exposing Kubernetes Operators as a Service : Offering "Calculator as a Service" through OpenSlice](https://osl.etsi.org/documentation/latest/service_design/examples/ExposingCRDs_aaS_Example_Calculator/ExposingCRDs_aaS_Example_Calculator/) documentation page. + +Regarding the RFS Service, you must set the following characteristics: + +- _CR_CHECKVAL_AVAILABLE = RUNNING +- _CR_CHECK_FIELD = spec.status + +By setting this characteristics, you will rely on the value of `spec.status` to set the service as `active`. Ideally, the operator would have implemented an Operator for this Custom Resource. However, for demonstration purposes, we will use a Supervision rule to set `spec.status` to `RUNNING` + +Then, you can proceed to create a CFS Service, which will incorporate the just created RFS Service. More information is available at: [Exposing Kubernetes Operators as a Service : Offering "Calculator as a Service" through OpenSlice](https://osl.etsi.org/documentation/latest/service_design/examples/ExposingCRDs_aaS_Example_Calculator/ExposingCRDs_aaS_Example_Calculator/). To create the CFS Service characteristics, you may use the Service Specification available at `/DummyOperatorService/OSLArtifacts/DummyOperatorService-CFS-Specification.json` . You may manually create the CFS Service, or you may onboard this Service Specification by making a POST request to *[{{url}}/tmf-api/serviceCatalogManagement/v4/serviceSpecification](https://www.notion.so/CAMARAaaS-OSL-15e11fa2ed8d80808254c87d9393cf51?pvs=21).* + +After creating the Service Specification, you should mark this Service as a Bundle. Then, go to “Service Specification Relationships†and add the RFS Service. + +Regarding the LCM Rules for the CFS Service, you should configure the following ones: + + +**[Pre-Provision Rule]** + + + + +```java +{ +java.util.HashMap<String,String> charvals = new java.util.HashMap<>(); +charvals.put("_CR_SPEC",String.format(""" +apiVersion: org.etsi.osl/v1 +kind: DummyOperatorService +metadata: + name: _to_be_replaced_by_osl + namespace: default +spec: + status: "%s" +""" +, "RUNNING")); +setServiceRefCharacteristicsValues("Dummy Operator Service - RFS", charvals); +} +``` + + +**[Supervision Rule]** + + + +```java +{ +java.util.HashMap<String,String> charvals = new java.util.HashMap<>(); +charvals.put("_CR_SPEC",String.format(""" +apiVersion: org.etsi.osl/v1 +kind: DummyOperatorService +metadata: + name: _to_be_replaced_by_osl + namespace: default +spec: + qodProv: + operation: "%s" + provisioningId: "%s" + device: + phoneNumber: "%s" + networkAccessIdentifier: "%s" + ipv4Address: + publicAddress: "%s" + privateAddress: "%s" + publicPort: %d + ipv6Address: "%s" + qosProfile: "%s" + sink: "%s" + sinkCredential: + credentialType: "%s" +""" +, getCharValAsString("qodProv.operation"), getCharValAsString("qodProv.provisioningId"), getCharValAsString("qodProv.device.phoneNumber"), getCharValAsString("qodProv.device.networkAccessIdentifier"), getCharValAsString("qodProv.device.ipv4Address.publicAddress"), getCharValAsString("qodProv.device.ipv4Address.privateAddress"), getCharValNumber("qodProv.device.ipv4Address.publicPort"), getCharValAsString("qodProv.device.ipv6Address"), getCharValAsString("qodProv.qosProfile"), getCharValAsString("qodProv.sink"), getCharValAsString("qodProv.sinkCredential.credentialType"))); +setServiceRefCharacteristicsValues("Dummy Operator Service - RFS", charvals); +} +setCharValFromStringType("camaraResults", getServiceRefPropValue("Dummy Operator Service - RFS", "serviceCharacteristicValue", "spec.camaraResults")); + +``` + +You can find the `_CR_SPEC` template used for the pre-provision rule at `/DummyOperatorService/OSLArtifacts/cr-template-pre-provision.yaml` . The `_CR_SPEC` template used for the supervision rule is available at `/DummyOperatorService/OSLArtifacts/cr-template-supervision.yaml` + +After that, you may expose this service via OSL’s Service Catalog, and order it. You do not need to configure any characteristics when ordering this Service. Confirm that the service order was completed, both RFS and CFS Services are active, and a Custom Resource of type *DummyOperatorService* was created in your Kubernetes Cluster. See images below. + + + + + +### 2. CAMARA QoD Provisioning API + +Then, we can proceed to design the CAMARAaaS QoD Provisioning API. To this end, OSL’s team has implemented CAMARA’s QoD Provisioning API, created a CRD to offer it, and developed a Kubernetes Operator to deal with its internal logic. Start by packaging the API in a docker image and pushing it to a docker repository. + +Open the file `Makefile` and update the repository to where you will push the docker image. Update the variable `REPOSITORY_HOST` . You may also choose to update the other variables, but it is not required. After this, run `make build-api-docker-image`. This command will build, tag, and push the API docker image to the repository you chose. + + +### 3. CAMARA QoD Provisioning API - Kubernetes Operator + +The previous docker image shall make available the CAMARA QoD Provisioning API. However, these APIs will be made available through Custom Resources of Type `CAMARAaaS-QoDProvisiongAPI` . Therefore, we also need a Kubernetes Operator to manage these resources. The Operator’s code can be found under `/QoDProvisioningAPI/Operator` . There, you have the source code of the Operator, as well as an Helm Chart to install it. + +Start by building and pushing the Operator’s docker image to your repository. Run `make operator-docker-image`. Then, install the operator in your Kubernetes cluster. This action will result in the creation of the Custom Resource Definition for the CAMARA QoD Provisioning API Resources, and deploy the operator in the cluster. It is this operator that will manage the CAMARA QoD Provisioning API Resources, being responsible, for instance, for the deployment a Kubernetes pod and service that expose the CAMARA QoD Provisioning API. To install the Operator, run the following command: + +```bash +helm install camaraaas-qod-prov-operator ./QoDProvisioningAPI/Operator/chart --set operator.image=<your_operator_image> --set camaraQoDAPI.image=<your_api_image> --namespace <namespace_where_the_operator_shall_be_deployed> --create-namespace +``` + +To simplify things, you may also run: `make install-operator` . After this, check if the operator is running through the `make get-operator-logs` command. + +If everything went ok, you should have a new CRD in your Kubernetes cluster. Run this command to verify if it was created: `kubectl describe crd camaraaas-qod-provisioning-apis.org.etsi.osl`. + +Before designing the service in OSL, let us first create a Custom Resource of type `CAMARAaaS-QoDProvisiongAPI` to validate that the operator is behaving according to what is expected. To this end, you may use the test custom resource available at `/QoDProvisioningAPI/Operator/test-cr.yaml` . Before creating the resource, you need to update the fields: *spec.messageBroker.address*, *spec.messageBroker.port, spec.messageBroker.username, spec.messageBroker.password*, with the values that relate with your OSL instance. Most likely, the values will be the following ones: + +- *spec.messageBroker.address: <your OSL address>* +- s*pec.messageBroker.port*: 61613 +- *spec.messageBroker.username: artemis* +- *spec.messageBroker.password: artemis* + +For now, you do not need to update the field serviceUnderControl.uuid. You may leave it as is. + +After these updates, create the Custom Resource by running the command: `make create-operator-test-cr`. + +When the Custom Resource is created, its operator will deploy the CAMARA QoD API in a pod and expose it via a K8s Node Port. The URL where the API is available is published under the CR field `spec.camaraAPI.url` (e.g.[http://10.255.28.73:32630](http://10.255.28.73:32630/)). Check this field by running `make describe-operator-test-cr`. To confirm the API is running, access *<URL>/docs*. You should see the following: + + + +If you see this page, the CAMARA QoD Provisioning API Custom Resources and their operator is working. You may delete the Custom Resource you created. Run the following command: `make delete-operator-test-cr`. + +### 4. CAMARA QoD Provisioning API - Service Design + +Now we can proceed to create a Service Specification that maps the CAMARA QoD Provisioning APICustom Resource. + +The next step is to create a RFS Service to expose this resource. To do so, you may read the [Exposing Kubernetes Operators as a Service : Offering "Calculator as a Service" through OpenSlice](https://osl.etsi.org/documentation/latest/service_design/examples/ExposingCRDs_aaS_Example_Calculator/ExposingCRDs_aaS_Example_Calculator/) documentation page. + +Regarding the RFS Service, you must set the following characteristics: + +- _CR_CHECKVAL_AVAILABLE = RUNNING +- _CR_CHECK_FIELD = spec.camaraAPI.status + +By setting this characteristics, you will rely on the value of `spec.camaraAPI.status` to set the service as `active`. The previous operator, when it deploys the CAMARA QoD Provisioning API will set that CR field to `RUNNING`. + +Then, you can proceed to create a CFS Service, which will incorporate the just created RFS Service. More information is available at: [Exposing Kubernetes Operators as a Service : Offering "Calculator as a Service" through OpenSlice](https://osl.etsi.org/documentation/latest/service_design/examples/ExposingCRDs_aaS_Example_Calculator/ExposingCRDs_aaS_Example_Calculator/). To create the CFS Service characteristics, you may use the Service Specification available at `/QoDProvisioningAPI/OSLArtifacts/CAMARAaaS-QoD-Provisioning-API-CFS-Specification.json` . You may manually create the CFS Service, or you may onboard this Service Specification by making a POST request to *[{{url}}/tmf-api/serviceCatalogManagement/v4/serviceSpecification](https://www.notion.so/CAMARAaaS-OSL-15e11fa2ed8d80808254c87d9393cf51?pvs=21).* + +After creating the Service Specification, you should mark this Service as a Bundle. Then, go to “Service Specification Relationships†and add the RFS Service. + +Regarding the LCM Rules for the CFS Service, you should configure the following ones: + +**[Pre-Provision Rule]** + + + +```java +{ +java.util.HashMap<String,String> charvals = new java.util.HashMap<>(); +charvals.put("_CR_SPEC",String.format(""" +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" +""" +, getCharValAsString("messageBroker.address"), getCharValNumber("messageBroker.port"), getCharValAsString("messageBroker.username"), getCharValAsString("messageBroker.password"), getCharValAsString("serviceUnderControl.uuid"))); +setServiceRefCharacteristicsValues("CAMARAaaS - QoD Provisioning API - RFS", charvals); +} + +``` + +**[Supervision Rule]** + + + +```java +{ +java.util.HashMap<String,String> charvals = new java.util.HashMap<>(); +charvals.put("_CR_SPEC",String.format(""" +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" +""" +, getCharValAsString("messageBroker.address"), getCharValNumber("messageBroker.port"), getCharValAsString("messageBroker.username"), getCharValAsString("messageBroker.password"), getCharValAsString("serviceUnderControl.uuid"))); +setServiceRefCharacteristicsValues("CAMARAaaS - QoD Provisioning API - RFS", charvals); +} +setCharValFromStringType("camaraAPI.status", getServiceRefPropValue("CAMARAaaS - QoD Provisioning API - RFS", "serviceCharacteristicValue", "spec.camaraAPI.status")); +setCharValFromStringType("camaraAPI.url", getServiceRefPropValue("CAMARAaaS - QoD Provisioning API - RFS", "serviceCharacteristicValue", "spec.camaraAPI.url")); +setCharValFromStringType("camaraAPI.username", getServiceRefPropValue("CAMARAaaS - QoD Provisioning API - RFS", "serviceCharacteristicValue", "spec.camaraAPI.username")); +setCharValFromStringType("camaraAPI.password", getServiceRefPropValue("CAMARAaaS - QoD Provisioning API - RFS", "serviceCharacteristicValue", "spec.camaraAPI.password")); +setCharValFromStringType("camaraAPI.results", getServiceRefPropValue("CAMARAaaS - QoD Provisioning API - RFS", "serviceCharacteristicValue", "spec.camaraAPI.results")); + +``` + +You can find the `_CR_SPEC` template used for both rules at `/QoDProvisioningAPI/OSLArtifacts/cr-template.yaml` . + +After that, you may expose this service via OSL’s Service Catalog, and order it. When you order it, you will be prompted to configure some characteristics: + +- messageBroker.address +- messageBroker.port +- messageBroker.username +- messageBroker.password +- serviceUnderControl.uuid + +In `serviceUnderControl.uuid` you should input the UUID of the Service (in the Service Inventory) that you ordered before: the one that relates with the Dummy Operator. For this tutorial, we have used the following characteristic values: + + + + +Confirm that the service order was completed, both RFS and CFS Services are active, and a Custom Resource of type CAMARAaaS-QoDProvisiongAPI was created in your Kubernetes Cluster. See images below (`kubectl describe camaraaas-qod-provisioning-apis <name> -n <namespace>`) + + + + + + +Additionally, in OSL, you may see the URL where the QoD Provisioning API is exposed. To do so, please see the characteristics of the CAMARAaaS QoD Provisioning API CFS. See image below. + + + +### 5. Validation + +Now we can test if the two services are communicating. To do so, you should create a QoD Provisioning via the API that was just deployed. You may do that, using this command: + +```bash +# You must update the url to correspond to your API instance. +curl --location 'http://10.255.28.73:31637/device-qos' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "device": { + "phoneNumber": "+987654321", + "networkAccessIdentifier": "987654321@example.org", + "ipv4Address": { + "publicAddress": "203.0.112.12", + "publicPort": 59765 + }, + "ipv6Address": "2001:db8:85a3:8d3:1319:8a2e:370:7344" + }, + "qosProfile": "QOS_PROFILE_A", + "sink": "https://endpoint.example.com/" +}' +``` + +You should have received a response similar to this one: + +```bash +{"device":{"phoneNumber":"+987654321","networkAccessIdentifier":"987654321@example.org","ipv4Address":{"publicAddress":"203.0.112.12","privateAddress":null,"publicPort":59765},"ipv6Address":"2001:db8:85a3:8d3:1319:8a2e:370:7344"},"qosProfile":"QOS_PROFILE_A","sink":"https://endpoint.example.com/","sinkCredential":{"credentialType":null},"provisioningId":"cb55f9e9-802e-4898-95f5-d1a5a2552483","startedAt":"2024-12-17T15:49:21.995399","status":"REQUESTED","statusInfo":null} +``` + +Now, if everything is working properly, the characteristics of the Dummy Operator Service you referenced should have been update. You should now see these characteristics: + + + +You may also query the QoD Provisioning API to check the status of your provisioning. + +```bash +curl --location 'http://10.255.28.73:31637/device-qos/cb55f9e9-802e-4898-95f5-d1a5a2552483' +# notice the "provisioningId":"cb55f9e9-802e-4898-95f5-d1a5a2552483" above +``` + +If you do so, you will receive the following response: + +```bash +{"device":{"phoneNumber":"+987654321","networkAccessIdentifier":"987654321@example.org","ipv4Address":{"publicAddress":"203.0.112.12","privateAddress":null,"publicPort":59765},"ipv6Address":"2001:db8:85a3:8d3:1319:8a2e:370:7344"},"qosProfile":"QOS_PROFILE_A","sink":"https://endpoint.example.com/","sinkCredential":{"credentialType":null},"provisioningId":"cb55f9e9-802e-4898-95f5-d1a5a2552483","startedAt":"2024-12-17T15:49:21.962746","status":"REQUESTED","statusInfo":null} +``` + +As there is no logic behind the Dummy Operator Service, the provisioning will remain with the status “REQUESTEDâ€. However, we can simulate that Dummy Operator Service enforced a QOS enforcement, by patching its Custom Resource: + +```bash +kubectl patch dummy-operator-services <name> -n <namespace> \ +--type='json' -p='[{"op": "replace", "path": "/spec/camaraResults", "value": "[{\"device\": {\"ipv4Address\": {\"publicAddress\": \"203.0.112.12\", \"publicPort\": 59765}, \"ipv6Address\": \"2001:db8:85a3:8d3:1319:8a2e:370:7344\", \"networkAccessIdentifier\": \"987654321@example.org\", \"phoneNumber\": \"+987654321\"}, \"provisioningId\": \"cb55f9e9-802e-4898-95f5-d1a5a2552483\", \"qosProfile\": \"QOS_PROFILE_A\", \"sink\": \"https://endpoint.example.com/\", \"sinkCredential\": {}, \"status\": \"AVAILABLE\", \"startedAt\": \"2024-12-15T11:00:00Z\"}]"}]' +``` + +When you do this, the `camaraResults` characteristic in the Dummy Operator Service will be updated to: + + + +After a while, if you check the characteristics of the CAMARAaaS QoD Provisioning API CFS, you will also see that the characteristic `camaraAPI.results` was updated. + + + +Finally, execute this request again: + +```bash +curl --location 'http://10.255.28.73:31637/device-qos/cb55f9e9-802e-4898-95f5-d1a5a2552483' +# notice the "provisioningId":"cb55f9e9-802e-4898-95f5-d1a5a2552483" above +``` + +You should receive the following response. + +```bash +{"device":{"phoneNumber":"+987654321","networkAccessIdentifier":"987654321@example.org","ipv4Address":{"publicAddress":"203.0.112.12","privateAddress":null,"publicPort":59765},"ipv6Address":"2001:db8:85a3:8d3:1319:8a2e:370:7344"},"qosProfile":"QOS_PROFILE_A","sink":"https://endpoint.example.com/","sinkCredential":{"credentialType":null},"provisioningId":"cb55f9e9-802e-4898-95f5-d1a5a2552483","startedAt":"2024-12-15T11:00:00","status":"AVAILABLE","statusInfo":null} +``` + +Notice the `"status":"AVAILABLE"` . This means the 2 services are communicating. Now, you just have to implement your own Operator Service Kubernetes Operator, and you may use OSL’s CAMARAaaS Add-on to expose it through a CAMARA API. \ No newline at end of file diff --git a/README.md b/README.md index 8c9c9670f9726154d3b4a7cad064943bc089e72b..74f4127c0ef8eed1d6b892290348739ed36407fe 100644 --- a/README.md +++ b/README.md @@ -1,93 +1,42 @@ -# org.etsi.osl.controllers.camara +# CAMARAaaS Add-on +The **CAMARA as a Service OSL Add-on** allows telecom operators and customer service providers to expose OSL services through CAMARA APIs. By doing so, it enables runtime operations, such as enforcing profiles on User Equipment (UEs) or updating 5G Network Slice characteristics, using standardized CAMARA API endpoints. Thus, this add-on enables the orchestration of CAMARA APIs, which will then be used to control the lifecyle and the operations that shall take place in an already existing OSL Service. -## Getting started +Therefore, these are the key features of this add-on: -To make it easy for you to get started with GitLab, here's a list of recommended next steps. +- **Seamless Integration**: Operators can expose their existing OSL services through CAMARA APIs, maintaining consistency with the OSL framework while offering additional accessibility. +- **Dynamic Service Control**: Allows runtime updates to characteristics of 5G-related Services, such as UE profiles or Network Slices, via CAMARA REST API calls. The updated of the characteristics of a Service can then be consumed by a Kubernetes Custom Resource that will produce an operation according to the updated characteristics. -Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! +## Architecture and Interactions -## Add your files +The add-on introduces a generic **CAMARA API Service**, which acts as a wrapper for existing (running) services. The architecture ensures: -- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files -- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: +1. **API Exposure**: CAMARA APIs are orchestrated by OSL (offered as a service) and their endpoints are exposed to the end clients. +2. **Service Mapping**: The CAMARA API Service references a running service (identified by a UUID), enabling targeted operations. The invoking of CAMARA API endpoints will results in updates in the running service’s characteristics. +3. **Operational Flow**: Updates triggered via CAMARA APIs are propagated to the operator's service through OSL0s message queue (Active MQ), ensuring synchronization of service characteristics. -``` -cd existing_repo -git remote add origin https://labs.etsi.org/rep/osl/code/addons/org.etsi.osl.controllers.camara.git -git branch -M main -git push -uf origin main -``` +This architecture is presented in the figure below: -## Integrate with your tools + -- [ ] [Set up project integrations](https://labs.etsi.org/rep/osl/code/addons/org.etsi.osl.controllers.camara/-/settings/integrations) +As already mentioned, the step “PATCH Characteristics†is achieved by send a message to OSL’s message bus. -## Collaborate with your team +For OSL’s community to get the full grasp of this architecture, we also make available a sequence diagram with all interactions that take place. -- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) -- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) -- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) -- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) -- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) + -## Test and Deploy + -Use the built-in continuous integration in GitLab. + +## Important Considerations -- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) -- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) -- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) -- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) -- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) +The OSL CAMARA as a Service Add-on depends on 2 Services: -*** +- The OSL CAMARA API Service + - Is a generic CAMARA API wrapper service implemented by the OSL team + - This implementation will be publicly offered as an Addon (Helm chart) +- A custom 5G-related Service (that shall be controlled/referenced by the CAMARA API Service): + - An OSL user must implement and provide its own 5G-related Controlling Service (following OSL design patterns) + - Implementation is custom -# Editing this README - -When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template. - -## Suggestions for a good README - -Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. - -## Name -Choose a self-explaining name for your project. - -## Description -Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. - -## Badges -On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. - -## Visuals -Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. - -## Installation -Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. - -## Usage -Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. - -## Support -Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. - -## Roadmap -If you have ideas for releases in the future, it is a good idea to list them in the README. - -## Contributing -State if you are open to contributions and what your requirements are for accepting them. - -For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. - -You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. - -## Authors and acknowledgment -Show your appreciation to those who have contributed to the project. - -## License -For open source projects, say how it is licensed. - -## Project status -If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.