diff --git a/README.md b/README.md index 1a071387ee472251c9bcd85e7c638ba3edee66b4..0fef83e8bb95089c947ee5b3098697efc85ca0eb 100644 --- a/README.md +++ b/README.md @@ -124,4 +124,4 @@ _{ "kubernetesClusterRef": "", "name": "nginx-test", "status": "unknown" -}_ +}_ \ No newline at end of file diff --git a/service-resource-manager-implementation/Dockerfile b/service-resource-manager-implementation/Dockerfile index 3ef607e633e5bee1414a48e8ea42a5d40da694f5..3c60ffdf1f8ca998ed2335a1285a2921889e2964 100644 --- a/service-resource-manager-implementation/Dockerfile +++ b/service-resource-manager-implementation/Dockerfile @@ -1,39 +1,18 @@ -FROM python:3.9-alpine - -#RUN apk add git +FROM python:3.12-alpine RUN mkdir -p /usr/src/app WORKDIR /usr/src/app -#RUN apk add --no-cache --virtual .build-deps gcc musl-dev - -#RUN apk update && apk add python3-dev \ -# gcc \ -# libc-dev - - -#THIS SOLVED THE ISSUE WITH CFFI: building wheel for cffi (setup.py) finished with status 'error'! -#RUN apk add --no-cache libffi-dev build-base -# COPY requirements.txt /usr/src/app/ -#RUN pip3 install connexion - -#ENV EMP_STORAGE_DRIVER mongo -#ENV EMP_STORAGE_URI mongodb://203.0.113.8:27017 -# -#ENV PIP_ROOT_USER_ACTION=ignore -# ENV PYTHONUNBUFFERED=1 -#RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python -#RUN python3 -m ensurepip -#RUN pip3 install --no-cache --upgrade pip setuptools +RUN python3 -m venv .venv +RUN source .venv/bin/activate RUN pip3 install --upgrade pip -RUN pip3 install wheel +RUN pip3 install wheel --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host=files.pythonhosted.org -#RUN pip3 install --no-cache --upgrade setuptools RUN pip3 install --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host=files.pythonhosted.org --no-cache-dir -r requirements.txt COPY . /usr/src/app diff --git a/service-resource-manager-implementation/requirements.txt b/service-resource-manager-implementation/requirements.txt index c211cb227197cbdfc57b599bb63b0bdb0a65471b..9ea2d853568823883678e0f01b492b8d83f9fea6 100644 --- a/service-resource-manager-implementation/requirements.txt +++ b/service-resource-manager-implementation/requirements.txt @@ -1,36 +1,8 @@ -#connexion >= 2.6.0 -#connexion[swagger-ui] >= 2.6.0 -#python_dateutil == 2.6.0 -#setuptools >= 21.0.0 -#swagger-ui-bundle >= 0.0.2 -#requests -#pymongo -#logging -#traceback -#pprint - -#connexion >= 2.6.0 -#connexion == 2.5.0 used in python 3.5 running server for dev -#connexion #for test +connexion<3.0.0 connexion[swagger-ui] -python_dateutil == 2.6.0 setuptools >= 21.0.0 -#setuptools==50.3.2 -pymongo==3.12.0 -#git+https://github.com/kubernetes-client/python.git -requests==2.25.1 -#kubernetes==17.17.0 -kubernetes==18.20.0 - -python-jose[cryptography] -cffi==1.15.1 -#bcrypt -#bcrypt==3.1.7 #used in python 3.5 running server for dev - +requests==2.32.4 psycopg2-binary -#psycopg2==2.7.7 #used in python 3.5 running server for dev - -#not used! -#pandas==0.24.2 -paramiko>=2.12.0 -urllib3 +urllib3 +pydantic-extra-types==2.10.3 +sunrise6g-opensdk==1.0.2.post3 \ No newline at end of file diff --git a/service-resource-manager-implementation/src/__main__.py b/service-resource-manager-implementation/src/__main__.py index 2b50d2b87a6d02fedc8f2c61c819e42a94657343..828513a8859ae0c1e40d7cfc95ef8ff1c5c0ed56 100644 --- a/service-resource-manager-implementation/src/__main__.py +++ b/service-resource-manager-implementation/src/__main__.py @@ -1,21 +1,18 @@ #!/usr/bin/env python3 import connexion - import logging -import sys -import os +import src.encoder as encoder +from json import JSONEncoder -from src import encoder import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) def main(): - global driver logging.basicConfig(level=logging.INFO) app = connexion.App(__name__, specification_dir='./swagger/') - app.app.json_encoder = encoder.JSONEncoder - app.add_api('swagger.yaml', strict_validation=True, arguments={'title': 'π-edge Controller API'}, pythonic_params=True) + app.app.json_encoder = JSONEncoder + app.add_api('swagger.yaml', strict_validation=True, arguments={'title': 'Service Resource Manager Controller API'}, pythonic_params=True) app.run(port=8080) diff --git a/service-resource-manager-implementation/src/clients/__init__.py b/service-resource-manager-implementation/src/clients/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/service-resource-manager-implementation/src/clients/common/__init__.py b/service-resource-manager-implementation/src/clients/common/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/service-resource-manager-implementation/src/clients/common/sdk.py b/service-resource-manager-implementation/src/clients/common/sdk.py deleted file mode 100644 index 207094693d784718b39af50da2f63fbcc2f033f8..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/clients/common/sdk.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- coding: utf-8 -*- -## -# Copyright 2025-present by Software Networks Area, i2CAT. -# All rights reserved. -# -# This file is part of the Open SDK -# -# Contributors: -# - Adrián Pino Martínez (adrian.pino@i2cat.net) -## -from typing import Dict - -from src.common.sdk_factory import SdkFactory - - -class Sdk: - @staticmethod - def create_clients_from( - client_specs: Dict[str, Dict[str, str]], - ) -> Dict[str, object]: - """ - Create and return a dictionary of instantiated edgecloud/network/o-ran clients - based on the provided specifications. - - Args: - client_specs (dict): A dictionary where each key is the client's domain (e.g., 'edgecloud', 'network'), - and each value is a dictionary containing: - - 'client_name' (str): The specific name of the client (e.g., 'i2edge', 'open5gs'). - - 'base_url' (str): The base URL for the client's API. - Additional parameters like 'scs_as_id' may also be included. - - Returns: - dict: A dictionary where keys are the 'client_name' (str) and values are - the instantiated client objects. - - Example: - >>> from src.common.universal_client_catalog import UniversalCatalogClient - >>> - >>> client_specs_example = { - >>> 'edgecloud': { - >>> 'client_name': 'i2edge', - >>> 'base_url': 'http://ip_edge_cloud:port', - >>> 'additionalEdgeCloudParamater1': 'example' - >>> }, - >>> 'network': { - >>> 'client_name': 'open5gs', - >>> 'base_url': 'http://ip_network:port', - >>> 'additionalNetworkParamater1': 'example' - >>> } - >>> } - >>> - >>> clients = UniversalCatalogClient.create_clients(client_specs_example) - >>> edgecloud_client = clients.get("edgecloud") - >>> network_client = clients.get("network") - >>> - >>> edgecloud_client.get_edge_cloud_zones() - >>> network_client.get_qod_session(session_id="example_session_id") - """ - sdk_client = SdkFactory() - clients = {} - - for domain, config in client_specs.items(): - client_name = config["client_name"] - base_url = config["base_url"] - - # Support of additional paramaters for specific clients - kwargs = { - k: v for k, v in config.items() if k not in ("client_name", "base_url") - } - - client = sdk_client.instantiate_and_retrieve_clients( - domain, client_name, base_url, **kwargs - ) - clients[domain] = client - - return clients diff --git a/service-resource-manager-implementation/src/clients/common/sdk_factory.py b/service-resource-manager-implementation/src/clients/common/sdk_factory.py deleted file mode 100644 index f67bd08a21550d5f8c269df5bb2e4baa059597fe..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/clients/common/sdk_factory.py +++ /dev/null @@ -1,78 +0,0 @@ -# -*- coding: utf-8 -*- -## -# Copyright 2025-present by Software Networks Area, i2CAT. -# All rights reserved. -# -# This file is part of the Open SDK -# -# Contributors: -# - Adrián Pino Martínez (adrian.pino@i2cat.net) -## -from src.edgecloud.clients.aeros.client import EdgeApplicationManager as AerosClient -from src.edgecloud.clients.i2edge.client import EdgeApplicationManager as I2EdgeClient -from src.edgecloud.clients.piedge.client import EdgeApplicationManager as PiEdgeClient -# from src.network.clients.oai.client import NetworkManager as OaiCoreClient -# from src.network.clients.open5gcore.client import NetworkManager as Open5GCoreClient -# from src.network.clients.open5gs.client import NetworkManager as Open5GSClient - - -def _edgecloud_factory(client_name: str, base_url: str, **kwargs): - edge_cloud_factory = { - "aeros": lambda url, **kw: AerosClient(base_url=url, **kw), - "i2edge": lambda url: I2EdgeClient(base_url=url), - "piedge": lambda url, **kw: PiEdgeClient(base_url=url, **kw) - } - try: - return edge_cloud_factory[client_name](base_url, **kwargs) - except KeyError: - raise ValueError( - f"Invalid edgecloud client '{client_name}'. Available: {list(edge_cloud_factory)}" - ) - - -# def _network_factory(client_name: str, base_url: str, **kwargs): -# if "scs_as_id" not in kwargs: -# raise ValueError("Missing required 'scs_as_id' for network clients.") -# scs_as_id = kwargs.pop("scs_as_id") - -# network_factory = { -# "open5gs": lambda url, scs_id, **kw: Open5GSClient( -# base_url=url, scs_as_id=scs_id, **kw -# ), -# "oai": lambda url, scs_id, **kw: OaiCoreClient( -# base_url=url, scs_as_id=scs_id, **kw -# ), -# "open5gcore": lambda url, scs_id, **kw: Open5GCoreClient( -# base_url=url, scs_as_id=scs_id, **kw -# ), -# } -# try: -# return network_factory[client_name](base_url, scs_as_id, **kwargs) -# except KeyError: -# raise ValueError( -# f"Invalid network client '{client_name}'. Available: {list(network_factory)}" -# ) - - -# def _oran_factory(client_name: str, base_url: str): -# # TODO - - -class SdkFactory: - _domain_factories = { - "edgecloud": _edgecloud_factory - # "network": _network_factory, - # "oran": _oran_factory, - } - - @classmethod - def instantiate_and_retrieve_clients( - cls, domain: str, client_name: str, base_url: str, **kwargs - ): - try: - catalog = cls._domain_factories[domain] - except KeyError: - raise ValueError( - f"Unsupported domain '{domain}'. Supported: {list(cls._domain_factories)}" - ) - return catalog(client_name, base_url, **kwargs) diff --git a/service-resource-manager-implementation/src/clients/edgecloud/.env b/service-resource-manager-implementation/src/clients/edgecloud/.env deleted file mode 100644 index 8bcda46a98889d9c56cfbeb8de896ae6adb29d58..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/clients/edgecloud/.env +++ /dev/null @@ -1,7 +0,0 @@ -# #### Logging #### -# LOG_LEVEL="debug" -# LOG_FILE="edgecloud.log" - -#### EdgeCloud #### -# EDGE_CLOUD="i2edge" -EDGE_CLOUD_URL=http://192.168.123.86:30769 diff --git a/service-resource-manager-implementation/src/clients/edgecloud/__init__.py b/service-resource-manager-implementation/src/clients/edgecloud/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/service-resource-manager-implementation/src/clients/edgecloud/clients/__init__.py b/service-resource-manager-implementation/src/clients/edgecloud/clients/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/service-resource-manager-implementation/src/clients/edgecloud/clients/aeros/__init__.py b/service-resource-manager-implementation/src/clients/edgecloud/clients/aeros/__init__.py deleted file mode 100644 index 0ea3493dd8e379ce69ff2567e2b747cf57839679..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/clients/edgecloud/clients/aeros/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -""" -aerOS client - This module provides a client for interacting with the aerOS REST API. - It includes methods for onboarding/deploying applications, - and querying aerOS continuum entities - aerOS domain is exposed as zones - aerOS services and service components are exposed as applications - Client is initialized with a base URL for the aerOS API - and an access token for authentication. -""" - -from src.edgecloud.clients.aeros import config -from src.logger import setup_logger - -logger = setup_logger(__name__, is_debug=True, file_name=config.LOG_FILE) - -# TODO: The following should only appear in case aerOS client is used -# Currently even if another client is used, the logs appear -# logger.info("aerOS client initialized") -# logger.debug("aerOS API URL: %s", config.aerOS_API_URL) -# logger.debug("aerOS access token: %s", config.aerOS_ACCESS_TOKEN) -# logger.debug("aerOS debug mode: %s", config.DEBUG) -# logger.debug("aerOS log file: %s", config.LOG_FILE) diff --git a/service-resource-manager-implementation/src/clients/edgecloud/clients/aeros/client.py b/service-resource-manager-implementation/src/clients/edgecloud/clients/aeros/client.py deleted file mode 100644 index 1e8742c59402a171541db10bcdd08434f54b71b0..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/clients/edgecloud/clients/aeros/client.py +++ /dev/null @@ -1,263 +0,0 @@ -## -# This file is part of the Open SDK -# -# Contributors: -# - Vasilis Pitsilis (vpitsilis@dat.demokritos.gr, vpitsilis@iit.demokritos.gr) -# - Andreas Sakellaropoulos (asakellaropoulos@iit.demokritos.gr) -## -from typing import Any, Dict, List, Optional - -from src.edgecloud.clients.aeros import config -from src.edgecloud.clients.aeros.continuum_client import ContinuumClient -from src.edgecloud.core.edgecloud_interface import EdgeCloudManagementInterface -from src.logger import setup_logger - - -class EdgeApplicationManager(EdgeCloudManagementInterface): - """ - aerOS Continuum Client - FIXME: Handle None responses from continuum client - """ - - def __init__(self, base_url: str, **kwargs): - self.base_url = base_url - self.logger = setup_logger(__name__, is_debug=True, file_name=config.LOG_FILE) - - # Overwrite config values if provided via kwargs - if "aerOS_API_URL" in kwargs: - config.aerOS_API_URL = kwargs["aerOS_API_URL"] - if "aerOS_ACCESS_TOKEN" in kwargs: - config.aerOS_ACCESS_TOKEN = kwargs["aerOS_ACCESS_TOKEN"] - if "aerOS_HLO_TOKEN" in kwargs: - config.aerOS_HLO_TOKEN = kwargs["aerOS_HLO_TOKEN"] - - if not config.aerOS_API_URL: - raise ValueError("Missing 'aerOS_API_URL'") - if not config.aerOS_ACCESS_TOKEN: - raise ValueError("Missing 'aerOS_ACCESS_TOKEN'") - if not config.aerOS_HLO_TOKEN: - raise ValueError("Missing 'aerOS_HLO_TOKEN'") - - def onboard_app(self, app_manifest: Dict) -> Dict: - # HLO-FE POST with TOSCA and app_id (service_id) - service_id = app_manifest.get("serviceId") - tosca_str = app_manifest.get("tosca") - aeros_client = ContinuumClient(self.base_url) - onboard_response = aeros_client.onboard_service( - service_id=service_id, tosca_str=tosca_str - ) - return {"appId": onboard_response["serviceId"]} - - def get_all_onboarded_apps(self) -> List[Dict]: - aeros_client = ContinuumClient(self.base_url) - ngsild_params = "type=Service&format=simplified" - aeros_apps = aeros_client.query_entities(ngsild_params) - return [ - {"appId": service["id"], "name": service["name"]} for service in aeros_apps - ] - # return [{"appId": "1234-5678", "name": "TestApp"}] - - def get_onboarded_app(self, app_id: str) -> Dict: - aeros_client = ContinuumClient(self.base_url) - ngsild_params = "format=simplified" - aeros_app = aeros_client.query_entity(app_id, ngsild_params) - return {"appId": aeros_app["id"], "name": aeros_app["name"]} - - def delete_onboarded_app(self, app_id: str) -> None: - print(f"Deleting application: {app_id}") - # TBD: Purge from continuum (make all ngsil-ld calls for servieId connected entities) - # Should check if undeployed first - - def deploy_app(self, app_id: str, app_zones: List[Dict]) -> Dict: - # HLO-FE PUT with app_id (service_id) - aeros_client = ContinuumClient(self.base_url) - deploy_response = aeros_client.deploy_service(app_id) - return {"appInstanceId": deploy_response["serviceId"]} - - def get_all_deployed_apps( - self, - app_id: Optional[str] = None, - app_instance_id: Optional[str] = None, - region: Optional[str] = None, - ) -> List[Dict]: - # FIXME: Get services in deployed state - aeros_client = ContinuumClient(self.base_url) - ngsild_params = 'type=Service&format=simplified&q=actionType=="DEPLOYED"' - if app_id: - ngsild_params += f'&q=service=="{app_id}"' - aeros_apps = aeros_client.query_entities(ngsild_params) - return [ - { - "appInstanceId": service["id"], - "status": - # scomponent["serviceComponentStatus"].split(":")[-1].lower() - service["actionType"], - } - for service in aeros_apps - ] - # return [{"appInstanceId": "abcd-efgh", "status": "ready"}] - - # def get_all_deployed_apps(self, - # app_id: Optional[str] = None, - # app_instance_id: Optional[str] = None, - # region: Optional[str] = None) -> List[Dict]: - # # FIXME: Get services in deployed state - # aeros_client = ContinuumClient(self.base_url) - # ngsild_params = "type=ServiceComponent&format=simplified" - # if app_id: - # ngsild_params += f'&q=service=="{app_id}"' - # aeros_apps = aeros_client.query_entities(ngsild_params) - # return [{ - # "appInstanceId": - # scomponent["id"], - # "status": - # scomponent["serviceComponentStatus"].split(":")[-1].lower() - # } for scomponent in aeros_apps] - # # return [{"appInstanceId": "abcd-efgh", "status": "ready"}] - - def undeploy_app(self, app_instance_id: str) -> None: - # HLO-FE DELETE with app_id (service_id) - aeros_client = ContinuumClient(self.base_url) - _ = aeros_client.undeploy_service(app_instance_id) - - def get_edge_cloud_zones( - self, region: Optional[str] = None, status: Optional[str] = None - ) -> List[Dict]: - aeros_client = ContinuumClient(self.base_url) - ngsild_params = "type=Domain&format=simplified" - aeros_domains = aeros_client.query_entities(ngsild_params) - return [ - { - "edgeCloudZoneId": domain["id"], - "status": domain["domainStatus"].split(":")[-1].lower(), - } - for domain in aeros_domains - ] - - # return [{"edgeCloudZoneId": "zone-1", "status": "active"}] - - def get_edge_cloud_zones_details( - self, zone_id: str, flavour_id: Optional[str] = None - ) -> Dict: - """ - Get details of a specific edge cloud zone. - :param zone_id: The ID of the edge cloud zone - :param flavour_id: Optional flavour ID to filter the results - :return: Details of the edge cloud zone - """ - # Minimal mocked response based on required fields of 'ZoneRegisteredData' in GSMA OPG E/WBI API - # return { - # "zoneId": - # zone_id, - # "reservedComputeResources": [{ - # "cpuArchType": "ISA_X86_64", - # "numCPU": "4", - # "memory": 8192, - # }], - # "computeResourceQuotaLimits": [{ - # "cpuArchType": "ISA_X86_64", - # "numCPU": "8", - # "memory": 16384, - # }], - # "flavoursSupported": [{ - # "flavourId": - # "medium-x86", - # "cpuArchType": - # "ISA_X86_64", - # "supportedOSTypes": [{ - # "architecture": "x86_64", - # "distribution": "UBUNTU", - # "version": "OS_VERSION_UBUNTU_2204_LTS", - # "license": "OS_LICENSE_TYPE_FREE", - # }], - # "numCPU": - # 4, - # "memorySize": - # 8192, - # "storageSize": - # 100, - # }], - # # - # } - aeros_client = ContinuumClient(self.base_url) - ngsild_params = ( - f'format=simplified&type=InfrastructureElement&q=domain=="{zone_id}"' - ) - self.logger.debug( - "Querying infrastructure elements for zone %s with params: %s", - zone_id, - ngsild_params, - ) - # Query the infrastructure elements for the specified zonese - aeros_domain_ies = aeros_client.query_entities(ngsild_params) - # Transform the infrastructure elements into the required format - # and return the details of the edge cloud zone - response = self.transform_infrastructure_elements( - domain_ies=aeros_domain_ies, domain=zone_id - ) - self.logger.debug("Transformed response: %s", response) - # Return the transformed response - return response - - def transform_infrastructure_elements( - self, domain_ies: List[Dict[str, Any]], domain: str - ) -> Dict[str, Any]: - """ - Transform the infrastructure elements into a format suitable for the - edge cloud zone details. - :param domain_ies: List of infrastructure elements - :param domain: The ID of the edge cloud zone - :return: Transformed details of the edge cloud zone - """ - total_cpu = 0 - total_ram = 0 - total_disk = 0 - total_available_ram = 0 - total_available_disk = 0 - - flavours_supported = [] - - for element in domain_ies: - total_cpu += element.get("cpuCores", 0) - total_ram += element.get("ramCapacity", 0) - total_available_ram += element.get("availableRam", 0) - total_disk += element.get("diskCapacity", 0) - total_available_disk += element.get("availableDisk", 0) - - # Create a flavour per machine - flavour = { - "flavourId": f"{element.get('hostname')}-{element.get('containerTechnology')}", - "cpuArchType": f"{element.get('cpuArchitecture')}", - "supportedOSTypes": [ - { - "architecture": f"{element.get('cpuArchitecture')}", - "distribution": f"{element.get('operatingSystem')}", # assume - "version": "OS_VERSION_UBUNTU_2204_LTS", - "license": "OS_LICENSE_TYPE_FREE", - } - ], - "numCPU": element.get("cpuCores", 0), - "memorySize": element.get("ramCapacity", 0), - "storageSize": element.get("diskCapacity", 0), - } - flavours_supported.append(flavour) - - result = { - "zoneId": domain, - "reservedComputeResources": [ - { - "cpuArchType": "ISA_X86_64", - "numCPU": str(total_cpu), - "memory": total_ram, - } - ], - "computeResourceQuotaLimits": [ - { - "cpuArchType": "ISA_X86_64", - "numCPU": str(total_cpu * 2), # Assume quota is 2x total? - "memory": total_ram * 2, - } - ], - "flavoursSupported": flavours_supported, - } - return result diff --git a/service-resource-manager-implementation/src/clients/edgecloud/clients/aeros/config.py b/service-resource-manager-implementation/src/clients/edgecloud/clients/aeros/config.py deleted file mode 100644 index 794cba555d3d862e4fa4bfb431ae11ba05afdbb6..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/clients/edgecloud/clients/aeros/config.py +++ /dev/null @@ -1,27 +0,0 @@ -## -# This file is part of the Open SDK -# -# Contributors: -# - Vasilis Pitsilis (vpitsilis@dat.demokritos.gr, vpitsilis@iit.demokritos.gr) -# - Andreas Sakellaropoulos (asakellaropoulos@iit.demokritos.gr) -## -""" -aerOS access configuration -Access tokens need to be provided in environment variables. -""" -# import os - -# aerOS_API_URL = os.environ.get("aerOS_API_URL") -aerOS_API_URL = "harcoded_api" -if not aerOS_API_URL: - raise ValueError("Environment variable 'aerOS_API_URL' is not set.") -# aerOS_ACCESS_TOKEN = os.environ.get("aerOS_ACCESS_TOKEN") -aerOS_ACCESS_TOKEN = "harcoded_access_token" -if not aerOS_ACCESS_TOKEN: - raise ValueError("Environment variable 'aerOS_ACCESS_TOKEN' is not set.") -# aerOS_HLO_TOKEN = os.environ.get("aerOS_HLO_TOKEN") -aerOS_HLO_TOKEN = "harcoded_hlo_token" -if not aerOS_HLO_TOKEN: - raise ValueError("Environment variable 'aerOS_HLO_TOKEN' is not set.") -DEBUG = False -LOG_FILE = ".log/aeros_client.log" diff --git a/service-resource-manager-implementation/src/clients/edgecloud/clients/aeros/continuum_client.py b/service-resource-manager-implementation/src/clients/edgecloud/clients/aeros/continuum_client.py deleted file mode 100644 index 064ad6e595b65e46a25654b2afcba468249b69a7..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/clients/edgecloud/clients/aeros/continuum_client.py +++ /dev/null @@ -1,170 +0,0 @@ -## -# This file is part of the Open SDK -# -# Contributors: -# - Vasilis Pitsilis (vpitsilis@dat.demokritos.gr, vpitsilis@iit.demokritos.gr) -# - Andreas Sakellaropoulos (asakellaropoulos@iit.demokritos.gr) -## -""" -aerOS REST API Client - This client is used to interact with the aerOS REST API. -""" - -import requests - -from src.edgecloud.clients.aeros import config -from src.edgecloud.clients.aeros.utils import catch_requests_exceptions -from src.logger import setup_logger - - -class ContinuumClient: - """ - Client to aerOS ngsi-ld based continuum exposure - """ - - def __init__(self, base_url: str = None): - """ - :param base_url: the base url of the aerOS API - """ - if base_url is None: - self.api_url = config.aerOS_API_URL - else: - self.api_url = base_url - self.logger = setup_logger(__name__, is_debug=True, file_name=config.LOG_FILE) - self.m2m_cb_token = config.aerOS_ACCESS_TOKEN - self.hlo_token = config.aerOS_HLO_TOKEN - self.headers = { - "Content-Type": "application/json", - "Accept": "application/json", - "aerOS": "true", - "Authorization": f"Bearer {self.m2m_cb_token}", - } - self.hlo_headers = { - "Content-Type": "application/json", - "Accept": "application/json", - "aerOS": "true", - "Authorization": f"Bearer {self.hlo_token}", - } - self.hlo_onboard_headers = { - "Content-Type": "application/yaml", - "Authorization": f"Bearer {self.hlo_token}", - } - - @catch_requests_exceptions - def query_entity(self, entity_id, ngsild_params) -> dict: - """ - Query entity with ngsi-ld params - :input - @param entity_id: the id of the queried entity - @param ngsi-ld: the query params - :output - ngsi-ld object - """ - entity_url = f"{self.api_url}/entities/{entity_id}?{ngsild_params}" - response = requests.get(entity_url, headers=self.headers, timeout=15) - if response is None: - return None - else: - if config.DEBUG: - self.logger.debug("Query entity URL: %s", entity_url) - self.logger.debug( - "Query entity response: %s %s", response.status_code, response.text - ) - return response.json() - - @catch_requests_exceptions - def query_entities(self, ngsild_params): - """ - Query entities with ngsi-ld params - :input - @param ngsi-ld: the query params - :output - ngsi-ld object - """ - entities_url = f"{self.api_url}/entities?{ngsild_params}" - response = requests.get(entities_url, headers=self.headers, timeout=15) - if response is None: - return None - # else: - # if config.DEBUG: - # self.logger.debug("Query entities URL: %s", entities_url) - # self.logger.debug("Query entities response: %s %s", - # response.status_code, response.text) - return response.json() - - @catch_requests_exceptions - def deploy_service(self, service_id: str) -> dict: - """ - Re-allocate (deploy) service on aerOS continuum - :input - @param service_id: the id of the service to be re-allocated - :output - the re-allocated service json object - """ - re_allocate_url = f"{self.api_url}/hlo_fe/services/{service_id}" - response = requests.put(re_allocate_url, headers=self.hlo_headers, timeout=15) - if response is None: - return None - else: - if config.DEBUG: - self.logger.debug("Re-allocate service URL: %s", re_allocate_url) - self.logger.debug( - "Re-allocate service response: %s %s", - response.status_code, - response.text, - ) - return response.json() - - @catch_requests_exceptions - def undeploy_service(self, service_id: str) -> dict: - """ - Undeploy service - :input - @param service_id: the id of the service to be undeployed - :output - the undeployed service json object - """ - undeploy_url = f"{self.api_url}/hlo_fe/services/{service_id}" - response = requests.delete(undeploy_url, headers=self.hlo_headers, timeout=15) - if response is None: - return None - else: - if config.DEBUG: - self.logger.debug("Re-allocate service URL: %s", undeploy_url) - self.logger.debug( - "Undeploy service response: %s %s", - response.status_code, - response.text, - ) - return response.json() - - @catch_requests_exceptions - def onboard_service(self, service_id: str, tosca_str: str) -> dict: - """ - Onboard (& deploy) service on aerOS continuum - :input - @param service_id: the id of the service to onboarded (& deployed) - @param tosca_str: the tosca whith all orchestration information - :output - the allocated service json object - """ - onboard_url = f"{self.api_url}/hlo_fe/services/{service_id}" - if config.DEBUG: - self.logger.debug("Onboard service URL: %s", onboard_url) - self.logger.debug( - "Onboard service request body (TOSCA-YAML): %s", tosca_str - ) - response = requests.post( - onboard_url, data=tosca_str, headers=self.hlo_onboard_headers, timeout=15 - ) - if response is None: - return None - else: - if config.DEBUG: - self.logger.debug("Onboard service URL: %s", onboard_url) - self.logger.debug( - "Onboard service response: %s %s", - response.status_code, - response.text, - ) - return response.json() diff --git a/service-resource-manager-implementation/src/clients/edgecloud/clients/aeros/utils.py b/service-resource-manager-implementation/src/clients/edgecloud/clients/aeros/utils.py deleted file mode 100644 index d4f5cf586bdbcc2d6c1699053f3516b489aa60d6..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/clients/edgecloud/clients/aeros/utils.py +++ /dev/null @@ -1,43 +0,0 @@ -## -# This file is part of the Open SDK -# -# Contributors: -# - Vasilis Pitsilis (vpitsilis@dat.demokritos.gr, vpitsilis@iit.demokritos.gr) -# - Andreas Sakellaropoulos (asakellaropoulos@iit.demokritos.gr) -## -""" -Docstring -""" -from requests.exceptions import HTTPError, RequestException, Timeout - -import src.edgecloud.clients.aeros.config as config -from src.logger import setup_logger - - -def catch_requests_exceptions(func): - """ - Docstring - """ - logger = setup_logger(__name__, is_debug=True, file_name=config.LOG_FILE) - - def wrapper(*args, **kwargs): - try: - result = func(*args, **kwargs) - return result - except HTTPError as e: - logger.info("4xx or 5xx: %s \n", {e}) - return None # raise our custom exception or log, etc. - except ConnectionError as e: - logger.info( - "Raised for connection-related issues (e.g., DNS resolution failure, network issues): %s \n", - {e}, - ) - return None # raise our custom exception or log, etc. - except Timeout as e: - logger.info("Timeout occured: %s \n", {e}) - return None # raise our custom exception or log, etc. - except RequestException as e: - logger.info("Request failed: %s \n", {e}) - return None # raise our custom exception or log, etc. - - return wrapper diff --git a/service-resource-manager-implementation/src/clients/edgecloud/clients/errors.py b/service-resource-manager-implementation/src/clients/edgecloud/clients/errors.py deleted file mode 100644 index 97b14bc5bcbda2aa494602a56ac188f11ddc5e2b..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/clients/edgecloud/clients/errors.py +++ /dev/null @@ -1,5 +0,0 @@ -# -*- coding: utf-8 -*- - - -class EdgeCloudPlatformError(Exception): - pass diff --git a/service-resource-manager-implementation/src/clients/edgecloud/clients/i2edge/__init__.py b/service-resource-manager-implementation/src/clients/edgecloud/clients/i2edge/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/service-resource-manager-implementation/src/clients/edgecloud/clients/i2edge/client.py b/service-resource-manager-implementation/src/clients/edgecloud/clients/i2edge/client.py deleted file mode 100644 index 20807dffd04316583a73e467610baeea0622b1db..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/clients/edgecloud/clients/i2edge/client.py +++ /dev/null @@ -1,241 +0,0 @@ -# -*- coding: utf-8 -*- -## -# Copyright 2025-present by Software Networks Area, i2CAT. -# All rights reserved. -# -# This file is part of the Open SDK -# -# Contributors: -# - Adrián Pino Martínez (adrian.pino@i2cat.net) -# - Sergio Giménez (sergio.gimenez@i2cat.net) -## -from typing import Dict, List, Optional - -from src import logger -from src.edgecloud.core.edgecloud_interface import EdgeCloudManagementInterface - -from . import schemas -from .common import ( - I2EdgeError, - i2edge_delete, - i2edge_get, - i2edge_post, - i2edge_post_multiform_data, -) - -log = logger.get_logger(__name__) - - -class EdgeApplicationManager(EdgeCloudManagementInterface): - def __init__(self, base_url: str): - self.base_url = base_url - - def get_edge_cloud_zones( - self, region: Optional[str] = None, status: Optional[str] = None - ) -> List[dict]: - url = "{}/zones/list".format(self.base_url) - params = {} - try: - response = i2edge_get(url, params=params) - log.info("Availability zones retrieved successfully") - return response - except I2EdgeError as e: - raise e - - def get_edge_cloud_zones_details( - self, zone_id: str, flavour_id: Optional[str] = None - ) -> Dict: - url = "{}zone/{}".format(self.base_url, zone_id) - params = {} - try: - response = i2edge_get(url, params=params) - log.info("Availability zone details retrieved successfully") - return response - except I2EdgeError as e: - raise e - - def _create_artefact( - self, - artefact_id: str, - artefact_name: str, - repo_name: str, - repo_type: str, - repo_url: str, - password: Optional[str] = None, - token: Optional[str] = None, - user_name: Optional[str] = None, - ): - repo_type = schemas.RepoType(repo_type) - url = "{}/artefact".format(self.base_url) - payload = schemas.ArtefactOnboarding( - artefact_id=artefact_id, - name=artefact_name, - repo_password=password, - repo_name=repo_name, - repo_type=repo_type, - repo_url=repo_url, - repo_token=token, - repo_user_name=user_name, - ) - try: - i2edge_post_multiform_data(url, payload) - log.info("Artifact added successfully") - except I2EdgeError as e: - raise e - - def _get_artefact(self, artefact_id: str) -> Dict: - url = "{}/artefact/{}".format(self.base_url, artefact_id) - try: - response = i2edge_get(url, artefact_id) - log.info("Artifact retrieved successfully") - return response - except I2EdgeError as e: - raise e - - def _get_all_artefacts(self) -> List[Dict]: - url = "{}/artefact".format(self.base_url) - try: - response = i2edge_get(url, {}) - log.info("Artifacts retrieved successfully") - return response - except I2EdgeError as e: - raise e - - def _delete_artefact(self, artefact_id: str): - url = "{}/artefact".format(self.base_url) - try: - i2edge_delete(url, artefact_id) - log.info("Artifact deleted successfully") - except I2EdgeError as e: - raise e - - def onboard_app(self, app_manifest: Dict) -> Dict: - try: - app_id = app_manifest["appId"] - artefact_id = app_id - - app_component_spec = schemas.AppComponentSpec(artefactId=artefact_id) - data = schemas.ApplicationOnboardingData( - app_id=app_id, appComponentSpecs=[app_component_spec] - ) - payload = schemas.ApplicationOnboardingRequest(profile_data=data) - url = "{}/application/onboarding".format(self.base_url) - i2edge_post(url, payload) - except I2EdgeError as e: - raise e - except KeyError as e: - raise I2EdgeError("Missing required field in app_manifest: {}".format(e)) - - def delete_onboarded_app(self, app_id: str) -> None: - url = "{}/application/onboarding".format(self.base_url) - try: - i2edge_delete(url, app_id) - except I2EdgeError as e: - raise e - - def get_onboarded_app(self, app_id: str) -> Dict: - url = "{}/application/onboarding/{}".format(self.base_url, app_id) - try: - response = i2edge_get(url, app_id) - return response - except I2EdgeError as e: - raise e - - def get_all_onboarded_apps(self) -> List[Dict]: - url = "{}/applications/onboarding".format(self.base_url) - params = {} - try: - response = i2edge_get(url, params) - return response - except I2EdgeError as e: - raise e - - def _select_best_flavour_for_app(self, zone_id) -> str: - """ - Selects the best flavour for the specified app requirements in a given zone. - """ - # list_of_flavours = self.get_edge_cloud_zones_details(zone_id) - # - # TODO - Harcoded - flavourId = "67f3a0b0e3184a85952e174d" - return flavourId - - def deploy_app(self, app_id: str, app_zones: List[Dict]) -> Dict: - appId = app_id - app = self.get_onboarded_app(appId) - profile_data = app["profile_data"] - appProviderId = profile_data["appProviderId"] - appVersion = profile_data["appMetaData"]["version"] - # TODO: Iterate in the list; deploy the app in all zones - zone_info = app_zones[0]["EdgeCloudZone"] - zone_id = zone_info["edgeCloudZoneId"] - flavourId = self._select_best_flavour_for_app(zone_id=zone_id) - app_deploy_data = schemas.AppDeployData( - appId=appId, - appProviderId=appProviderId, - appVersion=appVersion, - zoneInfo=schemas.ZoneInfo(flavourId=flavourId, zoneId=zone_id), - ) - url = "{}/app/".format(self.base_url) - payload = schemas.AppDeploy(app_deploy_data=app_deploy_data) - try: - response = i2edge_post(url, payload) - log.info("App deployed successfully") - print(response) - return response - except I2EdgeError as e: - raise e - - def get_all_deployed_apps(self) -> List[Dict]: - url = "{}/app/".format(self.base_url) - params = {} - try: - response = i2edge_get(url, params=params) - log.info("All app instances retrieved successfully") - return response - except I2EdgeError as e: - raise e - - def get_deployed_app(self, app_id, zone_id) -> List[Dict]: - # Logic: Get all onboarded apps and filter the one where release_name == artifact name - - # Step 1) Extract "app_name" from the onboarded app using the "app_id" - onboarded_app = self.get_onboarded_app(app_id) - if not onboarded_app: - raise ValueError(f"No onboarded app found with ID: {app_id}") - - try: - app_name = onboarded_app["profile_data"]["appMetaData"]["appName"] - except KeyError as e: - raise ValueError(f"Onboarded app missing required field: {e}") - - # Step 2) Retrieve all deployed apps and filter the one(s) where release_name == app_name - deployed_apps = self.get_all_deployed_apps() - if not deployed_apps: - return [] - - # Filter apps where release_name matches our app_name and zone matches - for app_instance_name in deployed_apps: - if ( - app_instance_name.get("release_name") == app_name - and app_instance_name.get("zone_id") == zone_id - ): - return app_instance_name - return None - - url = "{}/app/{}/{}".format(self.base_url, zone_id, app_instance_name) - params = {} - try: - response = i2edge_get(url, params=params) - log.info("App instance retrieved successfully") - return response - except I2EdgeError as e: - raise e - - def undeploy_app(self, app_instance_id: str) -> None: - url = "{}/app".format(self.base_url) - try: - i2edge_delete(url, app_instance_id) - log.info("App instance deleted successfully") - except I2EdgeError as e: - raise e diff --git a/service-resource-manager-implementation/src/clients/edgecloud/clients/i2edge/common.py b/service-resource-manager-implementation/src/clients/edgecloud/clients/i2edge/common.py deleted file mode 100644 index d4cda498109a73e4cc01f5e475efbcd7ce7bee1d..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/clients/edgecloud/clients/i2edge/common.py +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -## -# Copyright 2025-present by Software Networks Area, i2CAT. -# All rights reserved. -# -# This file is part of the Open SDK -# -# Contributors: -# - Sergio Giménez (sergio.gimenez@i2cat.net) -## -import json -from typing import Optional - -import requests -from pydantic import BaseModel - -from src import logger -from src.edgecloud.clients.errors import EdgeCloudPlatformError - -log = logger.get_logger(__name__) - - -class I2EdgeError(EdgeCloudPlatformError): - pass - - -class I2EdgeErrorResponse(BaseModel): - message: str - detail: dict - - -def get_error_message_from(response: requests.Response) -> str: - try: - error_response = I2EdgeErrorResponse(**response.json()) - return error_response.message - except Exception as e: - log.error("Failed to parse error response from i2edge: {}".format(e)) - return response.text - - -def i2edge_post(url: str, model_payload: BaseModel) -> dict: - headers = { - "Content-Type": "application/json", - "accept": "application/json", - } - json_payload = json.dumps(model_payload.model_dump(mode="json")) - try: - response = requests.post(url, data=json_payload, headers=headers) - response.raise_for_status() - return response.json() - except requests.exceptions.HTTPError as e: - i2edge_err_msg = get_error_message_from(response) - err_msg = "Failed to deploy app: {}. Detail: {}".format(i2edge_err_msg, e) - log.error(err_msg) - raise I2EdgeError(err_msg) - - -def i2edge_post_multiform_data(url: str, model_payload: BaseModel) -> dict: - headers = { - "accept": "application/json", - } - payload_dict = model_payload.model_dump(mode="json") - payload_in_str = {k: str(v) for k, v in payload_dict.items()} - try: - response = requests.post(url, data=payload_in_str, headers=headers) - response.raise_for_status() - return response.json() - except requests.exceptions.HTTPError as e: - i2edge_err_msg = get_error_message_from(response) - err_msg = "Failed to deploy app: {}. Detail: {}".format(i2edge_err_msg, e) - log.error(err_msg) - raise I2EdgeError(err_msg) - - -def i2edge_delete(url: str, id: str) -> dict: - headers = {"accept": "application/json"} - try: - query = "{}/{}".format(url, id) - response = requests.delete(query, headers=headers) - response.raise_for_status() - return response.json() - except requests.exceptions.HTTPError as e: - i2edge_err_msg = get_error_message_from(response) - err_msg = "Failed to undeploy app: {}. Detail: {}".format(i2edge_err_msg, e) - log.error(err_msg) - raise I2EdgeError(err_msg) - - -def i2edge_get(url: str, params: Optional[dict]): - headers = {"accept": "application/json"} - try: - response = requests.get(url, params=params, headers=headers) - response.raise_for_status() - return response.json() - except requests.exceptions.HTTPError as e: - i2edge_err_msg = get_error_message_from(response) - err_msg = "Failed to get apps: {}. Detail: {}".format(i2edge_err_msg, e) - log.error(err_msg) - raise I2EdgeError(err_msg) diff --git a/service-resource-manager-implementation/src/clients/edgecloud/clients/i2edge/schemas.py b/service-resource-manager-implementation/src/clients/edgecloud/clients/i2edge/schemas.py deleted file mode 100644 index c0d522fce675000b4a5bd132eab911d38ed72616..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/clients/edgecloud/clients/i2edge/schemas.py +++ /dev/null @@ -1,167 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -## -# Copyright 2025-present by Software Networks Area, i2CAT. -# All rights reserved. -# -# This file is part of the Open SDK -# -# Contributors: -# - Sergio Giménez (sergio.gimenez@i2cat.net) -# - César Cajas (cesar.cajas@i2cat.net) -## -from enum import Enum -from typing import List, Optional - -from pydantic import BaseModel, ConfigDict, Field, field_validator - - -class ZoneInfo(BaseModel): - flavourId: str - zoneId: str - - -class AppParameters(BaseModel): - namespace: Optional[str] = None - - -class AppDeployData(BaseModel): - appId: str - appProviderId: str - appVersion: str - zoneInfo: ZoneInfo - - -class AppDeploy(BaseModel): - app_deploy_data: AppDeployData - app_parameters: Optional[AppParameters] = Field(default=AppParameters()) - - -# Artefact - - -class RepoType(str, Enum): - UPLOAD = "UPLOAD" - PUBLICREPO = "PUBLICREPO" - PRIVATEREPO = "PRIVATEREPO" - - -class ArtefactOnboarding(BaseModel): - artefact_id: str - name: str - # chart: Optional[bytes] = Field(default=None) # XXX AFAIK not supported by CAMARA. - repo_password: Optional[str] = None - repo_name: Optional[str] = None - repo_type: RepoType - repo_url: Optional[str] = None - repo_token: Optional[str] = None - repo_user_name: Optional[str] = None - model_config = ConfigDict(use_enum_values=True) - - -# Application Onboarding - -# XXX Leaving default values since i2edge only cares about appid and artifactid, at least for now. - - -class AppComponentSpec(BaseModel): - artefactId: str - componentName: str = Field(default="default_component") - serviceNameEW: str = Field(default="default_ew_service") - serviceNameNB: str = Field(default="default_nb_service") - - -class AppMetaData(BaseModel): - appDescription: str = Field(default="Default app description") - appName: str = Field(default="Default App") - category: str = Field(default="DEFAULT") - mobilitySupport: bool = Field(default=False) - version: str = Field(default="1.0") - - -class AppQoSProfile(BaseModel): - appProvisioning: bool = Field(default=True) - bandwidthRequired: int = Field(default=1) - latencyConstraints: str = Field(default="NONE") - multiUserClients: str = Field(default="APP_TYPE_SINGLE_USER") - noOfUsersPerAppInst: int = Field(default=1) - - -class ApplicationOnboardingData(BaseModel): - appComponentSpecs: List[AppComponentSpec] - appDeploymentZones: List[str] = Field(default=["default_zone"]) - app_id: str - appMetaData: AppMetaData = Field(default_factory=AppMetaData) - appProviderId: str = Field(default="default_provider") - appQoSProfile: AppQoSProfile = Field(default_factory=AppQoSProfile) - appStatusCallbackLink: Optional[str] = None - - -class ApplicationOnboardingRequest(BaseModel): - profile_data: ApplicationOnboardingData - - -# Flavour - - -class GPU(BaseModel): - gpuMemory: int = Field(default=0, description="GPU memory in MB") - gpuModeName: str = Field(default="", description="GPU mode name") - gpuVendorType: str = Field( - default="GPU_PROVIDER_NVIDIA", description="GPU vendor type" - ) - numGPU: int = Field(..., description="Number of GPUs") - - -class Hugepages(BaseModel): - number: int = Field(default=0, description="Number of hugepages") - pageSize: str = Field(default="2MB", description="Size of hugepages") - - -class SupportedOSTypes(BaseModel): - architecture: str = Field(default="x86_64", description="OS architecture") - distribution: str = Field(default="RHEL", description="OS distribution") - license: str = Field(default="OS_LICENSE_TYPE_FREE", description="OS license type") - version: str = Field(default="OS_VERSION_UBUNTU_2204_LTS", description="OS version") - - -class FlavourSupported(BaseModel): - cpuArchType: str = Field(default="ISA_X86", description="CPU architecture type") - cpuExclusivity: bool = Field(default=True, description="CPU exclusivity") - fpga: int = Field(default=0, description="Number of FPGAs") - gpu: Optional[List[GPU]] = Field(default=None, description="List of GPUs") - hugepages: List[Hugepages] = Field( - default_factory=lambda: [Hugepages()], description="List of hugepages" - ) - memorySize: str = Field(..., description="Memory size (e.g., '1024MB' or '2GB')") - numCPU: int = Field(..., description="Number of CPUs") - storageSize: int = Field(default=0, description="Storage size in GB") - supportedOSTypes: List[SupportedOSTypes] = Field( - default_factory=lambda: [SupportedOSTypes()], - description="List of supported OS types", - ) - vpu: int = Field(default=0, description="Number of VPUs") - - @field_validator("memorySize") - @classmethod - def validate_memory_size(cls, v): - if not (v.endswith("MB") or v.endswith("GB")): - raise ValueError("memorySize must end with MB or GB") - try: - int(v[:-2]) - except ValueError: - raise ValueError("memorySize must be a number followed by MB or GB") - return v - - -class Flavour(BaseModel): - flavour_supported: FlavourSupported - - -# EdgeCloud Zones - - -class Zone(BaseModel): - geographyDetails: str - geolocation: str - zoneId: str diff --git a/service-resource-manager-implementation/src/clients/edgecloud/clients/i2edge/utils.py b/service-resource-manager-implementation/src/clients/edgecloud/clients/i2edge/utils.py deleted file mode 100644 index bee5e94e2c0604119e43d6ae46811807ce64cd63..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/clients/edgecloud/clients/i2edge/utils.py +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -## -# Copyright 2025-present by Software Networks Area, i2CAT. -# All rights reserved. -# -# This file is part of the Open SDK -# -# Contributors: -# - Sergio Giménez (sergio.gimenez@i2cat.net) -# - César Cajas (cesar.cajas@i2cat.net) -## -import uuid -from typing import Optional, Union -from uuid import UUID - -from src.edgecloud import logger -from src.edgecloud.api.routers.lcm.schemas import RequiredResources -from src.edgecloud.core import utils as core_utils - -from .client import I2EdgeClient -from .common import I2EdgeError - -log = logger.get_logger(__name__) - - -def generate_namespace_name_from(app_id: str, app_instance_id: str) -> str: - max_length = 63 - combined_name = "{}-{}".format(app_id, app_instance_id) - if len(combined_name) > max_length: - combined_name = combined_name[:max_length] - return combined_name - - -def generate_unique_id() -> UUID: - return uuid.uuid4() - - -def instantiate_app_with( - camara_app_id: UUID, - zone_id: str, - required_resources: RequiredResources, - i2edge: I2EdgeClient, -) -> tuple[str, str]: - memory_size_str = "{}GB".format(required_resources.memory + 1) - num_gpus = core_utils.get_num_gpus_from(required_resources) - try: - flavour_id = i2edge.create_flavour( - zone_id=zone_id, - memory_size=memory_size_str, - num_cpu=required_resources.numCPU, - num_gpus=num_gpus, - ) - i2edge_instance_id = generate_unique_id() - application_k8s_namespace = generate_namespace_name_from( - str(camara_app_id), str(i2edge_instance_id) - ) - i2edge.deploy_app( - appId=str(camara_app_id), - zoneId=zone_id, - flavourId=flavour_id, - namespace=application_k8s_namespace, - ) - return flavour_id, application_k8s_namespace - except I2EdgeError as e: - err_msg = "Error instantiating app {} in zone {}".format(camara_app_id, zone_id) - log.error("{}. Detailed error: {}".format(err_msg, e)) - raise e - - -def onboard_app_with( - application_id: UUID, - artefact_id: UUID, - app_name: str, - app_version: Optional[str], # TODO pass this to i2edge - repo_type: str, - app_repo: str, - user_name: Optional[str], - password: Optional[str], - token: Optional[str], - i2edge: I2EdgeClient, -): - try: - # TODO Come back to handle errors when onboarding and perform rollbacks - i2edge.create_artefact( - artefact_id=str(artefact_id), - artefact_name=app_name, - repo_name=app_name, - repo_type=repo_type, - repo_url=app_repo, - user_name=user_name, - password=password, - token=token, - ) - - i2edge.onboard_app(app_id=str(application_id), artefact_id=str(application_id)) - except I2EdgeError as e: - err_msg = "Error onboarding app {} in i2edge".format(app_name) - log.error("{}. Detailed error: {}".format(err_msg, e)) - raise e - - -def delete_app_instance_by( - namespace: str, flavour_id: str, zone_id: str, i2edge: I2EdgeClient -): - i2edge_app_instance_name = get_app_name_from(namespace, i2edge) - if i2edge_app_instance_name is None: - err_msg = "Couldn't retrieve app instance from I2Edge." - log.error(err_msg) - raise I2EdgeError(err_msg) - i2edge.undeploy_app(i2edge_app_instance_name) - i2edge.delete_flavour(flavour_id=str(flavour_id), zone_id=zone_id) - - -def get_app_name_from(namespace: str, i2edge: I2EdgeClient) -> Union[str, None]: - try: - response = i2edge.get_all_deployed_apps() - for deployment in response: - if deployment.get("bodytosend", {}).get("namespace") == namespace: - return deployment.get("name") - return None - except I2EdgeError as e: - err_msg = "Error getting app name for namespace {}".format(namespace) - log.error("{}. Detailed error: {}".format(err_msg, e)) - raise e - - -def delete_app_by(app_id: UUID, artefact_id: UUID, i2edge: I2EdgeClient): - try: - i2edge.delete_onboarded_app(app_id=str(app_id)) - i2edge.delete_artefact(artefact_id=str(artefact_id)) - except I2EdgeError as e: - err_msg = "Error deleting app {}".format(app_id) - log.error("{}. Detailed error: {}".format(err_msg, e)) - raise e - - -def get_edgecloud_zones(i2edge: I2EdgeClient) -> list[str]: - try: - zone_ids = [] - response = i2edge.get_zones_list() - for zone in response: - zone_id = zone.get("zoneId") - if zone_id is not None: - zone_ids.append(zone_id) - return zone_ids - - except I2EdgeError as e: - err_msg = "Error getting zones from i2edge" - log.error("{}. Detailed error: {}".format(err_msg, e)) - raise e diff --git a/service-resource-manager-implementation/src/clients/edgecloud/clients/piedge/__init__.py b/service-resource-manager-implementation/src/clients/edgecloud/clients/piedge/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/service-resource-manager-implementation/src/clients/edgecloud/clients/piedge/client.py b/service-resource-manager-implementation/src/clients/edgecloud/clients/piedge/client.py deleted file mode 100644 index b84271341851c55428cf56fe488956fc01382413..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/clients/edgecloud/clients/piedge/client.py +++ /dev/null @@ -1,158 +0,0 @@ -# Mocked API for testing purposes -from typing import Dict, List, Optional -import os -import logging -import requests -from src.clients.edgecloud.core.edgecloud_interface import EdgeCloudManagementInterface -from src.clients.edgecloud.clients.piedge.lib.utils.connector_db import ConnectorDB -from src.clients.edgecloud.clients.piedge.lib.utils.kubernetes_connector import KubernetesConnector -from src.clients.edgecloud.clients.piedge.lib.models.service_function_registration_request import ServiceFunctionRegistrationRequest -from src.clients.edgecloud.clients.piedge.lib.models.deploy_service_function import DeployServiceFunction -from src.clients.edgecloud.clients.piedge.lib.models.app_manifest import AppManifest -from src.clients.edgecloud.clients.piedge.lib.core.piedge_encoder import deploy_service_function - - -class EdgeApplicationManager(EdgeCloudManagementInterface): - - def __init__(self, base_url: str, **kwargs): - self.kubernetes_host = base_url - self.edge_cloud_provider = kwargs.get('PLATFORM_PROVIDER') - kubernetes_token = kwargs.get('KUBERNETES_MASTER_TOKEN') - kubernetes_port = kwargs.get('KUBERNETES_MASTER_PORT') - storage_uri = kwargs.get('EMP_STORAGE_URI') - username = kwargs.get('KUBERNETES_USERNAME') - if self.kubernetes_host is not None: - self.k8s_connector = KubernetesConnector(ip=self.kubernetes_host, port=kubernetes_port, token=kubernetes_token, username=username) - if storage_uri is not None: - self.connector_db = ConnectorDB(storage_uri) - - - def onboard_app(self, app_manifest: AppManifest) -> Dict: - print(f"Submitting application: {app_manifest}") - logging.info('Extracting variables from payload...') - app_name = app_manifest.get('name') - image = app_manifest.get('appRepo').get('imagePath') - package_type = app_manifest.get('packageType') - network_interfaces = app_manifest.get('componentSpec')[0].get('networkInterfaces') - ports = [] - for ni in network_interfaces: - ports.append(ni.get('port')) - insert_doc = ServiceFunctionRegistrationRequest(service_function_image=image, service_function_name=app_name, service_function_type=package_type, application_ports=ports) - result = self.connector_db.insert_document_service_function(insert_doc.to_dict()) - if type(result) is str: - return result - return {'appId': str(result.inserted_id)} - - def get_all_onboarded_apps(self) -> List[Dict]: - logging.info('Retrieving all registered apps from database...') - db_list = self.connector_db.get_documents_from_collection(collection_input="service_functions") - app_list = [] - for sf in db_list: - app_list.append(self.__transform_to_camara(sf)) - return app_list - # return [{"appId": "1234-5678", "name": "TestApp"}] - - def get_onboarded_app(self, app_id: str) -> Dict: - logging.info('Searching for registered app with ID: '+ app_id+' in database...') - app = self.connector_db.get_documents_from_collection("service_functions", input_type="_id", input_value=app_id) - if len(app)>0: - return self.__transform_to_camara(app[0]) - else: - return [] - - def delete_onboarded_app(self, app_id: str) -> None: - logging.info('Deleting registered app with ID: '+ app_id+' from database...') - result, code = self.connector_db.delete_document_service_function(_id=app_id) - print(f"Removing application metadata: {app_id}") - # return result, code - - def deploy_app(self, app_id: str, app_zones: List[Dict]) -> Dict: - logging.info('Searching for registered app with ID: '+ app_id+' in database...') - app = self.connector_db.get_documents_from_collection("service_functions", input_type="_id", input_value=app_id) - # success_response = [] - result = None - if len(app)<1: - return 'Application with ID: '+ app_id+' not found', 404 - if app is not None: - for zone in app_zones: - sf = DeployServiceFunction(service_function_name=app[0].get('name'), - service_function_instance_name=app[0].get('name')+'-'+zone.get('EdgeCloudZone').get('edgeCloudZoneName'), - location=zone.get('edgeCloudZoneName')) - result = deploy_service_function(service_function=sf, connector_db=self.connector_db, kubernetes_connector=self.k8s_connector) - logging.info(result) - # success_response.append(result) - return {"appInstanceId": result} - - - def get_all_deployed_apps(self, app_id: Optional[str] = None, app_instance_id: Optional[str] = None, region: Optional[str] = None) -> List[Dict]: - logging.info('Retreiving all deployed apps in the edge cloud platform') - deployments = self.k8s_connector.get_deployed_service_functions(self.connector_db) - response = [] - for deployment in deployments: - item = {} - item['appInstanceId'] = deployment.get('uid') - item['status'] = deployment.get('status') - item['componentEndpointInfo'] = {} - item['kubernetesClusterRef'] = "" - item['edgeCloudZone'] = {} - response.append(item) - return response - # return [{"appInstanceId": "abcd-efgh", "status": "ready"}] - - def undeploy_app(self, app_instance_id: str) -> None: - logging.info('Searching for deployed app with ID: '+ app_instance_id+' in database...') - print(f"Deleting app instance: {app_instance_id}") - sfs=self.k8s_connector.get_deployed_service_functions(self.connector_db) - response = 'App instance with ID ['+app_instance_id+'] not found' - for service_fun in sfs: - if service_fun["uid"]==app_instance_id: - self.k8s_connector.delete_service_function(self.connector_db, service_fun['service_function_instance_name']) - response = 'App instance with ID ['+app_instance_id+'] successfully removed' - break - return response - - - def get_edge_cloud_zones(self, region: Optional[str] = None, status: Optional[str] = None) -> List[Dict]: - - nodes_response = self.k8s_connector.get_PoPs() - zone_list =[] - - for node in nodes_response: - zone = {} - zone['edgeCloudZoneId'] = node.get('uid') - zone['edgeCloudZoneName'] = node.get('name') - zone['edgeCloudZoneStatus'] = node.get('status') - zone['edgeCloudProvider'] = self.edge_cloud_provider - zone['edgeCloudRegion'] = node.get('location') - zone_list.append(zone) - return zone_list - - def get_edge_cloud_zones_details(self, zone_id: str, flavour_id: Optional[str] = None ) -> Dict: - node_details = self.k8s_connector.get_node_details(zone_id) - node_details = {} - labels = node_details.get('metadata').get('labels') - status = node_details.get('status') - arch_type = labels.get('beta.kubernetes.io/arch') - computeResourceQuotaLimits = [{'cpuArchType': arch_type, 'numCPU': status.get('capacity').get('cpu'), 'memory': int(status.get('capacity').get('memory'))/(1024*1024)}] - reservedComputeResources = [{'cpuArchType': arch_type, 'numCPU': status.get('allocatable').get('cpu'), 'memory': int(status.get('allocatable').get('memory'))/(1024*1024)}] - flavoursSupported = [] - node_details['computeResourceQuotaLimits'] = computeResourceQuotaLimits - node_details['reservedComputeResources'] = reservedComputeResources - node_details['flavoursSupported'] = flavoursSupported - node_details['zoneId'] = zone_id - return node_details - - - def __transform_to_camara(self, app_data): - app = {} - app['appId'] = app_data.get('_id') - app['name'] = app_data.get('name') - app['packageType'] = app_data.get('type') - appRepo = {'imagePath': app_data.get('image')} - app['appRepo'] = appRepo - networkInterfaces = [] - for port in app_data.get('application_ports'): - port_spec = {'protocol': 'TCP', 'port': port} - networkInterfaces.append(port_spec) - app['componentSpec'] = [{'componentName': app_data.get('name'), 'networkInterfaces': networkInterfaces}] - return app diff --git a/service-resource-manager-implementation/src/clients/edgecloud/core/__init__.py b/service-resource-manager-implementation/src/clients/edgecloud/core/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/service-resource-manager-implementation/src/clients/edgecloud/core/edgecloud_interface.py b/service-resource-manager-implementation/src/clients/edgecloud/core/edgecloud_interface.py deleted file mode 100644 index 7dd2c6ffa1be04a5c1e445c0a76c0dd43ef42aea..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/clients/edgecloud/core/edgecloud_interface.py +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -## -# Copyright 2025-present by Software Networks Area, i2CAT. -# All rights reserved. -# -# This file is part of the Open SDK -# -# Contributors: -# - Adrián Pino Martínez (adrian.pino@i2cat.net) -## -from abc import ABC, abstractmethod -from typing import Dict, List, Optional - - -class EdgeCloudManagementInterface(ABC): - """ - Abstract Base Class for Edge Application Management. - """ - - @abstractmethod - def onboard_app(self, app_manifest: Dict) -> Dict: - """ - Onboards an app, submitting application metadata - to the Edge Cloud Provider. - - :param app_manifest: Application metadata in dictionary format. - :return: Dictionary containing created application details. - """ - pass - - @abstractmethod - def get_all_onboarded_apps(self) -> List[Dict]: - """ - Retrieves a list of onboarded applications. - - :return: List of application metadata dictionaries. - """ - pass - - @abstractmethod - def get_onboarded_app(self, app_id: str) -> Dict: - """ - Retrieves information of a specific onboarded application. - - :param app_id: Unique identifier of the application. - :return: Dictionary with application details. - """ - pass - - @abstractmethod - def delete_onboarded_app(self, app_id: str) -> None: - """ - Deletes an application onboarded from the Edge Cloud Provider. - - :param app_id: Unique identifier of the application. - """ - pass - - @abstractmethod - def deploy_app(self, app_id: str, app_zones: List[Dict]) -> Dict: - """ - Requests the instantiation of an application instance. - - :param app_id: Unique identifier of the application. - :param app_zones: List of Edge Cloud Zones where the app should be - instantiated. - :return: Dictionary with instance details. - """ - pass - - @abstractmethod - def get_all_deployed_apps( - self, - app_id: Optional[str] = None, - app_instance_id: Optional[str] = None, - region: Optional[str] = None, - ) -> List[Dict]: - """ - Retrieves information of application instances. - - :param app_id: Filter by application ID. - :param app_instance_id: Filter by instance ID. - :param region: Filter by Edge Cloud region. - :return: List of application instance details. - """ - pass - - @abstractmethod - def undeploy_app(self, app_instance_id: str) -> None: - """ - Terminates a specific application instance. - - :param app_instance_id: Unique identifier of the application instance. - """ - pass - - @abstractmethod - def get_edge_cloud_zones( - self, region: Optional[str] = None, status: Optional[str] = None - ) -> List[Dict]: - """ - Retrieves a list of available Edge Cloud Zones. - - :param region: Filter by geographical region. - :param status: Filter by status (active, inactive, unknown). - :return: List of Edge Cloud Zones. - """ - pass - - @abstractmethod - def get_edge_cloud_zones_details( - self, federation_context_id: str, zone_id: str - ) -> Dict: - """ - Retrieves details of a specific Edge Cloud Zone reserved - for the specified zone by the partner OP. - - :param federation_context_id: Identifier of the federation context. - :param zone_id: Unique identifier of the Edge Cloud Zone. - :return: Dictionary with Edge Cloud Zone details. - """ - pass diff --git a/service-resource-manager-implementation/src/clients/logger.py b/service-resource-manager-implementation/src/clients/logger.py deleted file mode 100644 index 14c3f6b6df27fd6b97301268179b64e458a8b1a7..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/clients/logger.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -## -# Copyright 2025-present by Software Networks Area, i2CAT. -# All rights reserved. -# -# This file is part of the Open SDK -# -# Contributors: -# - Sergio Giménez (sergio.gimenez@i2cat.net) -## -import logging -import sys -from pathlib import Path - -from colorlog import ColoredFormatter - -APP_LOGGER_NAME = "edgecloud" -COLORED_FORMATERR = ( - "%(log_color)s%(levelname)s%(reset)s | " - "[%(log_color)s%(name)s%(reset)s:%(log_color)s%(lineno)d%(reset)s] " - "%(log_color)s%(message)s%(reset)s" -) -FILE_FORMATTER = "[%(asctime)s] {%(name)s: %(lineno)d} %(levelname)s - %(message)s" - - -def setup_logger(logger_name=APP_LOGGER_NAME, is_debug=True, file_name=None): - logger = logging.getLogger(logger_name) - logger.setLevel(logging.DEBUG if is_debug else logging.INFO) - - colored_formatter = ColoredFormatter(COLORED_FORMATERR) - sh = logging.StreamHandler(sys.stdout) - sh.setFormatter(colored_formatter) - logger.handlers.clear() - logger.addHandler(sh) - - if file_name: - log_path = Path(file_name) - log_path.parent.mkdir(parents=True, exist_ok=True) - fh = logging.FileHandler(file_name) - fh.setFormatter(logging.Formatter(FILE_FORMATTER)) - logger.addHandler(fh) - - return logger - - -def get_logger(module_name): - return logging.getLogger(APP_LOGGER_NAME).getChild(module_name) diff --git a/service-resource-manager-implementation/src/clients/network/__init__.py b/service-resource-manager-implementation/src/clients/network/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/service-resource-manager-implementation/src/clients/network/clients/__init__.py b/service-resource-manager-implementation/src/clients/network/clients/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/service-resource-manager-implementation/src/clients/network/clients/errors.py b/service-resource-manager-implementation/src/clients/network/clients/errors.py deleted file mode 100644 index 6497bd8a40e274e50ab88a15dc704c1b47265b14..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/clients/network/clients/errors.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -class NetworkPlatformError(Exception): - pass diff --git a/service-resource-manager-implementation/src/clients/network/clients/oai/__init__.py b/service-resource-manager-implementation/src/clients/network/clients/oai/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/service-resource-manager-implementation/src/clients/network/clients/oai/client.py b/service-resource-manager-implementation/src/clients/network/clients/oai/client.py deleted file mode 100644 index a1d4766c0ff9ee30dbf564359a0cef1de26b7abb..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/clients/network/clients/oai/client.py +++ /dev/null @@ -1,139 +0,0 @@ -## -# Copyright (c) 2025 Netsoft Group, EURECOM. -# All rights reserved. -# -# This file is part of the Open SDK -# -# Contributors: -# - Giulio Carota (giulio.carota@eurecom.fr) -## - - -from src import logger -from src.network.core.network_interface import NetworkManagementInterface -from src.network.core.schemas import ( - AsSessionWithQoSSubscription, - CreateSession, - CreateTrafficInfluence, - FlowInfo, - Snssai, - TrafficInfluSub, -) - -log = logger.get_logger(__name__) -supportedQos = ["qos-e", "qos-s", "qos-m", "qos-l"] - - -class NetworkManager(NetworkManagementInterface): - def __init__(self, base_url: str, scs_as_id: str = None): - """ - Initialize Network Client for OAI Core Network - The currently supported features are: - - QoD - - Traffic Influence - """ - try: - super().__init__() - self.base_url = base_url - self.scs_as_id = scs_as_id - log.info( - f"Initialized OaiNefClient with base_url: {self.base_url} and scs_as_id: {self.scs_as_id}" - ) - - except Exception as e: - log.error(f"Failed to initialize OaiNefClient: {e}") - raise e - - def core_specific_qod_validation(self, session_info: CreateSession): - """ - Validates core-specific parameters for the session creation. - - args: - session_info: The session information to validate. - - raises: - ValidationError: If the session information does not meet core-specific requirements. - """ - if session_info.qosProfile.root not in supportedQos: - raise OaiValidationError( - f"QoS profile {session_info.qosProfile} not supported by OAI, supported profiles are {supportedQos}" - ) - - if session_info.device is None or session_info.device.ipv4Address is None: - raise OaiValidationError("OAI requires UE IPv4 Address to activate QoS") - - if session_info.applicationServer.ipv4Address is None: - raise OaiValidationError("OAI requires App IPv4 Address to activate QoS") - return - - def add_core_specific_qod_parameters( - self, - session_info: CreateSession, - subscription: AsSessionWithQoSSubscription, - ) -> None: - device_ip = _retrieve_ue_ipv4(session_info) - server_ip = _retrieve_app_ipv4(session_info) - - # build flow descriptor in oai format using device ip and server ip - flow_descriptor = f"permit out ip from {device_ip}/32 to {server_ip}/32" - _add_qod_flow_descriptor(subscription, flow_descriptor) - _add_qod_snssai(subscription, 1, "FFFFFF") - subscription.dnn = "oai" - - def add_core_specific_ti_parameters( - self, - traffic_influence_info: CreateTrafficInfluence, - subscription: TrafficInfluSub, - ): - # todo oai add dnn, ssnai, afServiceId - subscription.dnn = "oai" - subscription.add_snssai(1, "FFFFFF") - subscription.afServiceId = self.scs_as_id - - def core_specific_traffic_influence_validation( - self, traffic_influence_info: CreateTrafficInfluence - ) -> None: - """ - Validates core-specific parameters for the session creation. - - args: - session_info: The session information to validate. - - raises: - ValidationError: If the session information does not meet core-specific requirements. - """ - # Placeholder for core-specific validation logic - # This method should be overridden by subclasses if needed - - if ( - traffic_influence_info.device is None - or traffic_influence_info.device.ipv4Address is None - ): - raise OaiValidationError( - "OAI requires UE IPv4 Address to activate Traffic Influence" - ) - - -def _retrieve_ue_ipv4(session_info: CreateSession): - return session_info.device.ipv4Address.root.privateAddress - - -def _retrieve_app_ipv4(session_info: CreateSession): - return session_info.applicationServer.ipv4Address - - -def _add_qod_flow_descriptor( - qos_sub: AsSessionWithQoSSubscription, flow_desriptor: str -): - qos_sub.flowInfo = list() - qos_sub.flowInfo.append( - FlowInfo(flowId=len(qos_sub.flowInfo) + 1, flowDescriptions=[flow_desriptor]) - ) - - -def _add_qod_snssai(qos_sub: AsSessionWithQoSSubscription, sst: int, sd: str = None): - qos_sub.snssai = Snssai(sst=sst, sd=sd) - - -class OaiValidationError(Exception): - pass diff --git a/service-resource-manager-implementation/src/clients/network/clients/open5gcore/__init__.py b/service-resource-manager-implementation/src/clients/network/clients/open5gcore/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/service-resource-manager-implementation/src/clients/network/clients/open5gcore/client.py b/service-resource-manager-implementation/src/clients/network/clients/open5gcore/client.py deleted file mode 100644 index 2d9d397a6802324ec64447be31226038a2f744ef..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/clients/network/clients/open5gcore/client.py +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 -*- -from src import logger -from src.network.core.network_interface import NetworkManagementInterface - -log = logger.get_logger(__name__) - - -# TODO: Define any specific parameters or methods needed for Open5GCore -# In case any functionality is not implemented, raise NotImplementedError - - -class NetworkManager(NetworkManagementInterface): - def __init__(self, base_url: str, scs_as_id: str): - pass diff --git a/service-resource-manager-implementation/src/clients/network/clients/open5gs/__init__.py b/service-resource-manager-implementation/src/clients/network/clients/open5gs/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/service-resource-manager-implementation/src/clients/network/clients/open5gs/client.py b/service-resource-manager-implementation/src/clients/network/clients/open5gs/client.py deleted file mode 100644 index fbc7f6be69a653227e9b7734c5ae2e49660c8daa..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/clients/network/clients/open5gs/client.py +++ /dev/null @@ -1,62 +0,0 @@ -# -*- coding: utf-8 -*- -from pydantic import ValidationError - -from src import logger -from src.network.core.network_interface import NetworkManagementInterface, build_flows - -from ...core import schemas - -log = logger.get_logger(__name__) - -flow_id_mapping = {"qos-e": 3, "qos-s": 4, "qos-m": 5, "qos-l": 6} - - -class NetworkManager(NetworkManagementInterface): - """ - This client implements the NetworkManagementInterface and translates the - CAMARA APIs into specific HTTP requests understandable by the Open5GS NEF API. - - Invloved partners and their roles in this implementation: - - I2CAT: Responsible for the CAMARA QoD API and its mapping to the - 3GPP AsSessionWithQoS API exposed by Open5GS NEF. - - NCSRD: Responsible for the CAMARA Location API and its mapping to the - 3GPP Monitoring Event API exposed Open5GS NEF. - """ - - def __init__(self, base_url: str, scs_as_id): - """ - Initializes the Open5GS Client. - """ - try: - self.base_url = base_url - self.scs_as_id = scs_as_id - log.info( - f"Initialized Open5GSClient with base_url: {self.base_url} " - f"and scs_as_id: {self.scs_as_id}" - ) - except Exception as e: - log.error(f"Failed to initialize Open5GSClient: {e}") - raise e - - def core_specific_qod_validation(self, session_info: schemas.CreateSession): - if session_info.qosProfile.root not in flow_id_mapping.keys(): - raise ValidationError( - f"Open5Gs only supports these qos-profiles: {', '.join(flow_id_mapping.keys())}" - ) - - def add_core_specific_qod_parameters( - self, - session_info: schemas.CreateSession, - subscription: schemas.AsSessionWithQoSSubscription, - ) -> None: - subscription.supportedFeatures = schemas.SupportedFeatures("003C") - flow_id = flow_id_mapping[session_info.qosProfile.root] - subscription.flowInfo = build_flows(flow_id, session_info) - - -# Note: -# As this class is inheriting from NetworkManagementInterface, it is -# expected to implement all the abstract methods defined in that interface. -# -# In case this network adapter doesn't support a specific method, it should -# be marked as NotImplementedError. diff --git a/service-resource-manager-implementation/src/clients/network/core/__init__.py b/service-resource-manager-implementation/src/clients/network/core/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/service-resource-manager-implementation/src/clients/network/core/common.py b/service-resource-manager-implementation/src/clients/network/core/common.py deleted file mode 100644 index ace91da7ce5b2dc9d10aba7878731f762793f6a1..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/clients/network/core/common.py +++ /dev/null @@ -1,94 +0,0 @@ -# -*- coding: utf-8 -*- -# Common utilities (errors, HTTP helpers) used by the core network interface (network_interface.py). - -import requests -from pydantic import BaseModel - -from src import logger - -log = logger.get_logger(__name__) - - -def _make_request(method: str, url: str, data=None): - try: - headers = None - if method == "POST" or method == "PUT": - headers = { - "Content-Type": "application/json", - "accept": "application/json", - } - elif method == "GET": - headers = { - "accept": "application/json", - } - response = requests.request(method, url, headers=headers, data=data) - response.raise_for_status() - if response.content: - return response.json() - except requests.exceptions.HTTPError as e: - raise CoreHttpError(e) from e - except requests.exceptions.ConnectionError as e: - raise CoreHttpError("connection error") from e - - -# QoD methods -def as_session_with_qos_post( - base_url: str, scs_as_id: str, model_payload: BaseModel -) -> dict: - data = model_payload.model_dump_json(exclude_none=True, by_alias=True) - url = as_session_with_qos_build_url(base_url, scs_as_id) - return _make_request("POST", url, data=data) - - -def as_session_with_qos_get(base_url: str, scs_as_id: str, session_id: str) -> dict: - url = as_session_with_qos_build_url(base_url, scs_as_id, session_id) - return _make_request("GET", url) - - -def as_session_with_qos_delete(base_url: str, scs_as_id: str, session_id: str): - url = as_session_with_qos_build_url(base_url, scs_as_id, session_id) - return _make_request("DELETE", url) - - -def as_session_with_qos_build_url( - base_url: str, scs_as_id: str, session_id: str = None -): - url = f"{base_url}/3gpp-as-session-with-qos/v1/{scs_as_id}/subscriptions" - if session_id is not None and len(session_id) > 0: - return f"{url}/{session_id}" - else: - return url - - -# Traffic Influence Methods -def traffic_influence_post( - base_url: str, scs_as_id: str, model_payload: BaseModel -) -> dict: - data = model_payload.model_dump_json(exclude_none=True) - url = traffic_influence_build_url(base_url, scs_as_id) - return _make_request("POST", url, data=data) - - -def traffic_influence_delete(base_url: str, scs_as_id: str, session_id: str): - url = traffic_influence_build_url(base_url, scs_as_id, session_id) - return _make_request("DELETE", url) - - -def traffic_influence_put( - base_url: str, scs_as_id: str, session_id: str, model_payload: BaseModel -) -> dict: - data = model_payload.model_dump_json(exclude_none=True) - url = traffic_influence_build_url(base_url, scs_as_id, session_id) - return _make_request("PUT", url, data=data) - - -def traffic_influence_build_url(base_url: str, scs_as_id: str, session_id: str = None): - url = f"{base_url}/3gpp-traffic-influence/v1/{scs_as_id}/subscriptions" - if session_id is not None and len(session_id) > 0: - return f"{url}/{session_id}" - else: - return url - - -class CoreHttpError(Exception): - pass diff --git a/service-resource-manager-implementation/src/clients/network/core/network_interface.py b/service-resource-manager-implementation/src/clients/network/core/network_interface.py deleted file mode 100644 index 2fedbcf0ba50a4706de31029fbcb7677dbd46448..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/clients/network/core/network_interface.py +++ /dev/null @@ -1,295 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -## -# Copyright 2025-present by Software Networks Area, i2CAT. -# All rights reserved. -# -# This file is part of the Open SDK -# -# Contributors: -# - Reza Mosahebfard (reza.mosahebfard@i2cat.net) -# - Ferran Cañellas (ferran.canellas@i2cat.net) -## -from abc import ABC -from itertools import product -from typing import Dict - -from src import logger -from src.network.core import common, schemas - -log = logger.get_logger(__name__) - - -def flatten_port_spec(ports_spec: schemas.PortsSpec | None) -> list[str]: - has_ports = False - has_ranges = False - flat_ports = [] - if ports_spec and ports_spec.ports: - has_ports = True - flat_ports.extend([str(port) for port in ports_spec.ports]) - if ports_spec and ports_spec.ranges: - has_ranges = True - flat_ports.extend( - [f"{range.from_.root}-{range.to.root}" for range in ports_spec.ranges] - ) - if not has_ports and not has_ranges: - flat_ports.append("0-65535") - return flat_ports - - -def build_flows( - flow_id: int, - session_info: schemas.CreateSession, -) -> list[schemas.FlowInfo]: - device_ports = flatten_port_spec(session_info.devicePorts) - server_ports = flatten_port_spec(session_info.applicationServerPorts) - ports_combis = list(product(device_ports, server_ports)) - - device_ip = session_info.device.ipv4Address or session_info.device.ipv6Address - if isinstance(device_ip, schemas.DeviceIpv6Address): - device_ip = device_ip.root - else: # IPv4 - device_ip = ( - device_ip.root.publicAddress.root or device_ip.root.privateAddress.root - ) - device_ip = str(device_ip) - server_ip = ( - session_info.applicationServer.ipv4Address - or session_info.applicationServer.ipv6Address - ) - server_ip = server_ip.root - flow_descrs = [] - for device_port, server_port in ports_combis: - flow_descrs.append( - f"permit in ip from {device_ip} {device_port} to {server_ip} {server_port}" - ) - flow_descrs.append( - f"permit out ip from {server_ip} {server_port} to {device_ip} {device_port}" - ) - flows = [ - schemas.FlowInfo(flowId=flow_id, flowDescriptions=[", ".join(flow_descrs)]) - ] - return flows - - -class NetworkManagementInterface(ABC): - """ - Abstract Base Class for Network Resource Management. - - This interface defines the standard methods that all - Network Clients (Open5GS, OAI, Open5GCore) must implement. - - Partners implementing a new network client should inherit from this class - and provide concrete implementations for all abstract methods relevant - to their specific NEF capabilities. - """ - - base_url: str - scs_as_id: str - - def add_core_specific_qod_parameters( - self, - session_info: schemas.CreateSession, - subscription: schemas.AsSessionWithQoSSubscription, - ): - """ - Placeholder for adding core-specific parameters to the subscription. - This method should be overridden by subclasses to implement specific logic. - """ - pass - - def add_core_specific_ti_parameters( - self, - traffic_influence_info: schemas.CreateTrafficInfluence, - subscription: schemas.TrafficInfluSub, - ): - """ - Placeholder for adding core-specific parameters to the subscription. - This method should be overridden by subclasses to implement specific logic. - """ - pass - - def core_specific_qod_validation(self, session_info: schemas.CreateSession) -> None: - """ - Validates core-specific parameters for the session creation. - - args: - session_info: The session information to validate. - - raises: - ValidationError: If the session information does not meet core-specific requirements. - """ - # Placeholder for core-specific validation logic - # This method should be overridden by subclasses if needed - pass - - def core_specific_traffic_influence_validation( - self, traffic_influence_info: schemas.CreateTrafficInfluence - ) -> None: - """ - Validates core-specific parameters for the session creation. - - args: - session_info: The session information to validate. - - raises: - ValidationError: If the session information does not meet core-specific requirements. - """ - # Placeholder for core-specific validation logic - # This method should be overridden by subclasses if needed - pass - - def _build_qod_subscription( - self, session_info: Dict - ) -> schemas.AsSessionWithQoSSubscription: - valid_session_info = schemas.CreateSession.model_validate(session_info) - device_ipv4 = None - if valid_session_info.device.ipv4Address: - device_ipv4 = valid_session_info.device.ipv4Address.root.publicAddress.root - - self.core_specific_qod_validation(valid_session_info) - subscription = schemas.AsSessionWithQoSSubscription( - notificationDestination=str(valid_session_info.sink), - qosReference=valid_session_info.qosProfile.root, - ueIpv4Addr=device_ipv4, - ueIpv6Addr=valid_session_info.device.ipv6Address, - usageThreshold=schemas.UsageThreshold(duration=valid_session_info.duration), - ) - self.add_core_specific_qod_parameters(valid_session_info, subscription) - return subscription - - def _build_ti_subscription(self, traffic_influence_info: Dict): - traffic_influence_data = schemas.CreateTrafficInfluence.model_validate( - traffic_influence_info - ) - self.core_specific_traffic_influence_validation(traffic_influence_data) - - device_ip = traffic_influence_data.retrieve_ue_ipv4() - server_ip = ( - traffic_influence_data.appInstanceId - ) # assume that the instance id corresponds to its IPv4 address - sink_url = traffic_influence_data.notificationUri - edge_zone = traffic_influence_data.edgeCloudZoneId - - # build flow descriptor in oai format using device ip and server ip - flow_descriptor = f"permit out ip from {device_ip}/32 to {server_ip}/32" - - subscription = schemas.TrafficInfluSub( - afAppId=traffic_influence_data.appId, - ipv4Addr=str(device_ip), - notificationDestination=sink_url, - ) - subscription.add_flow_descriptor(flow_desriptor=flow_descriptor) - subscription.add_traffic_route(dnai=edge_zone) - - self.add_core_specific_ti_parameters(traffic_influence_data, subscription) - return subscription - - def create_qod_session(self, session_info: Dict) -> Dict: - """ - Creates a QoS session based on CAMARA QoD API input. - - args: - session_info: Dictionary containing session details conforming to - the CAMARA QoD session creation parameters. - - returns: - dictionary containing the created session details, including its ID. - """ - subscription = self._build_qod_subscription(session_info) - return common.as_session_with_qos_post( - self.base_url, self.scs_as_id, subscription - ) - - def get_qod_session(self, session_id: str) -> Dict: - """ - Retrieves details of a specific Quality on Demand (QoS) session. - - args: - session_id: The unique identifier of the QoS session. - - returns: - Dictionary containing the details of the requested QoS session. - """ - session = common.as_session_with_qos_get( - self.base_url, self.scs_as_id, session_id=session_id - ) - log.info(f"QoD session retrived successfully [id={session_id}]") - return session - - def delete_qod_session(self, session_id: str) -> None: - """ - Deletes a specific Quality on Demand (QoS) session. - - args: - session_id: The unique identifier of the QoS session to delete. - - returns: - None - """ - common.as_session_with_qos_delete( - self.base_url, self.scs_as_id, session_id=session_id - ) - log.info(f"QoD session deleted successfully [id={session_id}]") - - def create_traffic_influence_resource(self, traffic_influence_info: Dict) -> Dict: - """ - Creates a Traffic Influence resource based on CAMARA TI API input. - - args: - traffic_influence_info: Dictionary containing traffic influence details conforming to - the CAMARA TI resource creation parameters. - - returns: - dictionary containing the created traffic influence resource details, including its ID. - """ - - subscription = self._build_ti_subscription(traffic_influence_info) - response = common.traffic_influence_post( - self.base_url, self.scs_as_id, subscription - ) - - # retrieve the NEF resource id - if "self" in response.keys(): - subscription_id = response["self"] - else: - subscription_id = None - - traffic_influence_info["trafficInfluenceID"] = subscription_id - return traffic_influence_info - - def put_traffic_influence_resource( - self, resource_id: str, traffic_influence_info: Dict - ) -> Dict: - """ - Retrieves details of a specific Traffic Influence resource. - - args: - resource_id: The unique identifier of the Traffic Influence resource. - - returns: - Dictionary containing the details of the requested Traffic Influence resource. - """ - subscription = self._build_ti_subscription(traffic_influence_info) - common.traffic_influence_put( - self.base_url, self.scs_as_id, resource_id, subscription - ) - - traffic_influence_info.trafficInfluenceID = resource_id - return traffic_influence_info - - def delete_traffic_influence_resource(self, resource_id: str) -> None: - """ - Deletes a specific Traffic Influence resource. - - args: - resource_id: The unique identifier of the Traffic Influence resource to delete. - - returns: - None - """ - common.traffic_influence_delete(self.base_url, self.scs_as_id, resource_id) - return - - # Placeholder for other CAMARA APIs (e.g., Traffic Influence, - # Location-retrieval, etc.) diff --git a/service-resource-manager-implementation/src/clients/network/core/schemas.py b/service-resource-manager-implementation/src/clients/network/core/schemas.py deleted file mode 100644 index ac8dc116b6cf6d7134d1fa0e8b064953fbe44ed5..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/clients/network/core/schemas.py +++ /dev/null @@ -1,430 +0,0 @@ -# -*- coding: utf-8 -*- -# This file defines the Pydantic models that represent the data structures (schemas) -# for the requests sent to and responses received from the Open5GS NEF API, -# specifically focusing on the APIs needed to support CAMARA QoD. - -import ipaddress -from enum import Enum -from ipaddress import IPv4Address, IPv6Address -from typing import Annotated - -from pydantic import AnyUrl, BaseModel, ConfigDict, Field, NonNegativeInt, RootModel -from pydantic_extra_types.mac_address import MacAddress - - -class FlowDirection(Enum): - """ - DOWNLINK: The corresponding filter applies for traffic to the UE. - UPLINK: The corresponding filter applies for traffic from the UE. - BIDIRECTIONAL: The corresponding filter applies for traffic both to and from the UE. - UNSPECIFIED: The corresponding filter applies for traffic to the UE (downlink), but - has no specific direction declared. The service data flow detection shall apply the - filter for uplink traffic as if the filter was bidirectional. The PCF shall not use - the value UNSPECIFIED in filters created by the network in NW-initiated procedures. - The PCF shall only include the value UNSPECIFIED in filters in UE-initiated - procedures if the same value is received from the SMF. - """ - - DOWNLINK = "DOWNLINK" - UPLINK = "UPLINK" - BIDIRECTIONAL = "BIDIRECTIONAL" - UNSPECIFIED = "UNSPECIFIED" - - -class RequestedQosMonitoringParameter(Enum): - DOWNLINK = "DOWNLINK" - UPLINK = "UPLINK" - ROUND_TRIP = "ROUND_TRIP" - - -class ReportingFrequency(Enum): - EVENT_TRIGGERED = "EVENT_TRIGGERED" - PERIODIC = "PERIODIC" - SESSION_RELEASE = "SESSION_RELEASE" - - -Uinteger = Annotated[int, Field(ge=0)] - - -class DurationSec(RootModel[NonNegativeInt]): - root: NonNegativeInt = Field( - ..., - description="Unsigned integer identifying a period of time in units of \ - seconds.", - ) - - -class Volume(RootModel[NonNegativeInt]): - root: NonNegativeInt = Field( - ..., description="Unsigned integer identifying a volume in units of bytes." - ) - - -class SupportedFeatures(RootModel[str]): - root: str = Field( - ..., - pattern=r"^[A-Fa-f0-9]*$", - description="Hexadecimal string representing supported features.", - ) - - -class Link(RootModel[str]): - root: str = Field( - ..., - description="String formatted according to IETF RFC 3986 identifying a \ - referenced resource.", - ) - - -class FlowDescriptionModel(RootModel[str]): - root: str = Field(..., description="Defines a packet filter of an IP flow.") - - -class EthFlowDescription(BaseModel): - destMacAddr: MacAddress | None = None - ethType: str - fDesc: FlowDescriptionModel | None = None - fDir: FlowDirection | None = None - sourceMacAddr: MacAddress | None = None - vlanTags: list[str] | None = Field(None, max_length=2, min_length=1) - srcMacAddrEnd: MacAddress | None = None - destMacAddrEnd: MacAddress | None = None - - -class UsageThreshold(BaseModel): - duration: DurationSec | None = None - totalVolume: Volume | None = None - downlinkVolume: Volume | None = None - uplinkVolume: Volume | None = None - - -class SponsorInformation(BaseModel): - sponsorId: str = Field(..., description="It indicates Sponsor ID.") - aspId: str = Field(..., description="It indicates Application Service Provider ID.") - - -class QosMonitoringInformationModel(BaseModel): - reqQosMonParams: list[RequestedQosMonitoringParameter] | None = Field( - None, min_length=1 - ) - repFreqs: list[ReportingFrequency] | None = Field(None, min_length=1) - repThreshDl: Uinteger | None = None - repThreshUl: Uinteger | None = None - repThreshRp: Uinteger | None = None - waitTime: int | None = None - repPeriod: int | None = None - - -class FlowInfo(BaseModel): - flowId: int = Field(..., description="Indicates the IP flow.") - flowDescriptions: list[str] | None = Field( - None, - description="Indicates the packet filters of the IP flow. Refer to subclause \ - 5.3.8 of 3GPP TS 29.214 for encoding. It shall contain UL and/or DL IP \ - flow description.", - max_length=2, - min_length=1, - ) - - -class Snssai(BaseModel): - sst: int = Field(default=1) - sd: str = Field(default="FFFFFF") - - -class AsSessionWithQoSSubscription(BaseModel): - model_config = ConfigDict(serialize_by_alias=True) - self_: Link | None = Field(None, alias="self") - supportedFeatures: SupportedFeatures | None = None - notificationDestination: Link - flowInfo: list[FlowInfo] | None = Field( - None, description="Describe the data flow which requires QoS.", min_length=1 - ) - ethFlowInfo: list[EthFlowDescription] | None = Field( - None, description="Identifies Ethernet packet flows.", min_length=1 - ) - qosReference: str | None = Field( - None, description="Identifies a pre-defined QoS information" - ) - altQoSReferences: list[str] | None = Field( - None, - description="Identifies an ordered list of pre-defined QoS information. The \ - lower the index of the array for a given entry, the higher the priority.", - min_length=1, - ) - ueIpv4Addr: ipaddress.IPv4Address | None = None - ueIpv6Addr: ipaddress.IPv6Address | None = None - macAddr: MacAddress | None = None - usageThreshold: UsageThreshold | None = None - sponsorInfo: SponsorInformation | None = None - qosMonInfo: QosMonitoringInformationModel | None = None - - -class SourceTrafficFilters(BaseModel): - sourcePort: int - - -class DestinationTrafficFilters(BaseModel): - destinationPort: int - destinationProtocol: str - - -class TrafficRoute(BaseModel): - dnai: str - - -class TrafficInfluSub(BaseModel): # Replace with a meaningful name - afServiceId: str | None = None - afAppId: str - dnn: str | None = None - snssai: Snssai | None = None - trafficFilters: list[FlowInfo] | None = Field( - None, - description="Describe the data flow which requires Traffic Influence.", - min_length=1, - ) - ipv4Addr: str | None = None - ipv6Addr: str | None = None - - notificationDestination: str - trafficRoutes: list[TrafficRoute] | None = Field( - None, - description="Describe the list of DNAIs to reach the destination", - min_length=1, - ) - suppFeat: str | None = None - - def add_flow_descriptor(self, flow_desriptor: str): - self.trafficFilters = list() - self.trafficFilters.append( - FlowInfo( - flowId=len(self.trafficFilters) + 1, flowDescriptions=[flow_desriptor] - ) - ) - - def add_traffic_route(self, dnai: str): - self.trafficRoutes = list() - self.trafficRoutes.append(TrafficRoute(dnai=dnai)) - - def add_snssai(self, sst: int, sd: str = None): - self.snssai = Snssai(sst=sst, sd=sd) - - -############################################################### -############################################################### -# CAMARA Models - - -class PhoneNumber(RootModel[str]): - root: Annotated[ - str, - Field( - 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 '+'.", - examples=["+123456789"], - pattern="^\\+[1-9][0-9]{4,14}$", - ), - ] - - -class NetworkAccessIdentifier(RootModel[str]): - root: Annotated[ - str, - Field( - 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.", - examples=["123456789@domain.com"], - ), - ] - - -class SingleIpv4Addr(RootModel[IPv4Address]): - root: Annotated[ - IPv4Address, - Field( - description="A single IPv4 address with no subnet mask", - examples=["203.0.113.0"], - ), - ] - - -class Port(RootModel[int]): - root: Annotated[int, Field(description="TCP or UDP port number", ge=0, le=65535)] - - -class DeviceIpv4Addr1(BaseModel): - publicAddress: SingleIpv4Addr - privateAddress: SingleIpv4Addr - publicPort: Port | None = None - - -class DeviceIpv4Addr2(BaseModel): - publicAddress: SingleIpv4Addr - privateAddress: SingleIpv4Addr | None = None - publicPort: Port - - -class DeviceIpv4Addr(RootModel[DeviceIpv4Addr1 | DeviceIpv4Addr2]): - root: Annotated[ - DeviceIpv4Addr1 | DeviceIpv4Addr2, - Field( - 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).\n\nIf 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.\n\nIf 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)\n\nIn 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.\n", - examples=[{"publicAddress": "203.0.113.0", "publicPort": 59765}], - ), - ] - - -class DeviceIpv6Address(RootModel[IPv6Address]): - root: Annotated[ - IPv6Address, - Field( - 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).\n\nThe session shall apply to all IP flows between the device subnet and the specified application server, unless further restricted by the optional parameters devicePorts or applicationServerPorts.\n", - examples=["2001:db8:85a3:8d3:1319:8a2e:370:7344"], - ), - ] - - -class Device(BaseModel): - phoneNumber: PhoneNumber | None = None - networkAccessIdentifier: NetworkAccessIdentifier | None = None - ipv4Address: DeviceIpv4Addr | None = None - ipv6Address: DeviceIpv6Address | None = None - - -class ApplicationServerIpv4Address(RootModel[str]): - root: Annotated[ - str, - Field( - description="IPv4 address may be specified in form
as:\n - address - an IPv4 number in dotted-quad form 1.2.3.4. Only this exact IP number will match the flow control rule.\n - address/mask - an IP number as above with a mask width of the form 1.2.3.4/24.\n In this case, all IP numbers from 1.2.3.0 to 1.2.3.255 will match. The bit width MUST be valid for the IP version.\n", - examples=["198.51.100.0/24"], - ), - ] - - -class ApplicationServerIpv6Address(RootModel[str]): - root: Annotated[ - str, - Field( - description="IPv6 address may be specified in form
as:\n - address - The /128 subnet is optional for single addresses:\n - 2001:db8:85a3:8d3:1319:8a2e:370:7344\n - 2001:db8:85a3:8d3:1319:8a2e:370:7344/128\n - address/mask - an IP v6 number with a mask:\n - 2001:db8:85a3:8d3::0/64\n - 2001:db8:85a3:8d3::/64\n", - examples=["2001:db8:85a3:8d3:1319:8a2e:370:7344"], - ), - ] - - -class ApplicationServer(BaseModel): - ipv4Address: ApplicationServerIpv4Address | None = None - ipv6Address: ApplicationServerIpv6Address | None = None - - -class Range(BaseModel): - from_: Annotated[Port, Field(alias="from")] - to: Port - - -class PortsSpec(BaseModel): - ranges: Annotated[ - list[Range] | None, Field(description="Range of TCP or UDP ports", min_length=1) - ] = None - ports: Annotated[ - list[Port] | None, Field(description="Array of TCP or UDP ports", min_length=1) - ] = None - - -class QosProfileName(RootModel[str]): - root: Annotated[ - str, - Field( - description="A unique name for identifying a specific QoS profile.\nThis may follow different formats depending on the API provider implementation.\nSome options addresses:\n - A UUID style string\n - Support for predefined profiles QOS_S, QOS_M, QOS_L, and QOS_E\n - A searchable descriptive name\nThe set of QoS Profiles that an API provider is offering may be retrieved by means of the QoS Profile API (qos-profile) or agreed on onboarding time.\n", - examples=["voice"], - max_length=256, - min_length=3, - pattern="^[a-zA-Z0-9_.-]+$", - ), - ] - - -class CredentialType(Enum): - PLAIN = "PLAIN" - ACCESSTOKEN = "ACCESSTOKEN" - REFRESHTOKEN = "REFRESHTOKEN" - - -class SinkCredential(BaseModel): - credentialType: Annotated[ - CredentialType, - Field( - description="The type of the credential.\nNote: Type of the credential - MUST be set to ACCESSTOKEN for now\n" - ), - ] - - -class NotificationSink(BaseModel): - sink: str | None - sinkCredential: SinkCredential | None - - -class BaseSessionInfo(BaseModel): - device: Device | None = None - applicationServer: ApplicationServer - devicePorts: Annotated[ - PortsSpec | None, - Field( - description="The ports used locally by the device for flows to which the requested QoS profile should apply. If omitted, then the qosProfile will apply to all flows between the device and the specified application server address and ports" - ), - ] = None - applicationServerPorts: Annotated[ - PortsSpec | None, - Field( - description="A list of single ports or port ranges on the application server" - ), - ] = None - qosProfile: QosProfileName - sink: Annotated[ - AnyUrl | None, - Field( - description="The address to which events about all status changes of the session (e.g. session termination) shall be delivered using the selected protocol.", - examples=["https://endpoint.example.com/sink"], - ), - ] = None - sinkCredential: Annotated[ - SinkCredential | None, - Field( - description="A sink credential provides authentication or authorization information necessary to enable delivery of events to a target." - ), - ] = None - - -class CreateSession(BaseSessionInfo): - duration: Annotated[ - int, - Field( - description="Requested session duration in seconds. Value may be explicitly limited for the QoS profile, as specified in the Qos Profile (see qos-profile API). Implementations can grant the requested session duration or set a different duration, based on network policies or conditions.\n", - examples=[3600], - ge=1, - ), - ] - - -class CreateTrafficInfluence(BaseModel): - trafficInfluenceID: str | None = None - apiConsumerId: str | None = None - appId: str - appInstanceId: str - edgeCloudRegion: str | None = None - edgeCloudZoneId: str | None = None - sourceTrafficFilters: SourceTrafficFilters | None = None - destinationTrafficFilters: DestinationTrafficFilters | None = None - notificationUri: str | None = None - notificationAuthToken: str | None = None - device: Device - notificationSink: NotificationSink | None = None - - def retrieve_ue_ipv4(self): - if self.device is not None and self.device.ipv4Address is not None: - return self.device.ipv4Address.root.privateAddress.root - else: - raise KeyError("device.ipv4Address.publicAddress") - - def add_ue_ipv4(self, ipv4: str): - if self.device is None: - self.device = Device() - if self.device.ipv4Address is None: - self.device.ipv4Address = DeviceIpv4Addr(publicAddress=ipv4) diff --git a/service-resource-manager-implementation/src/clients/o-ran/__init__.py b/service-resource-manager-implementation/src/clients/o-ran/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/service-resource-manager-implementation/src/clients/o-ran/clients/__init__.py b/service-resource-manager-implementation/src/clients/o-ran/clients/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/service-resource-manager-implementation/src/clients/o-ran/clients/juniper-ric/__init__.py b/service-resource-manager-implementation/src/clients/o-ran/clients/juniper-ric/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/service-resource-manager-implementation/src/clients/o-ran/clients/juniper-ric/client.py b/service-resource-manager-implementation/src/clients/o-ran/clients/juniper-ric/client.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/service-resource-manager-implementation/src/clients/o-ran/core/__init__.py b/service-resource-manager-implementation/src/clients/o-ran/core/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/service-resource-manager-implementation/src/clients/o-ran/core/o-ran_interface.py b/service-resource-manager-implementation/src/clients/o-ran/core/o-ran_interface.py deleted file mode 100644 index 464090415c47109523e91779d4f40e19495c9cf1..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/clients/o-ran/core/o-ran_interface.py +++ /dev/null @@ -1 +0,0 @@ -# TODO diff --git a/service-resource-manager-implementation/src/controllers/app_instance_controller.py b/service-resource-manager-implementation/src/controllers/app_instance_controller.py index fac58559a835a49df86f666d4453ed9022a04857..40ccb008afa662ea9ea0777b1c84deaa7b138986 100644 --- a/service-resource-manager-implementation/src/controllers/app_instance_controller.py +++ b/service-resource-manager-implementation/src/controllers/app_instance_controller.py @@ -1,6 +1,5 @@ from os import environ import logging -from src.adapters.kubernetes_adapter import submit_helm_chart, app_deploy import connexion logger = logging.getLogger(__name__) diff --git a/service-resource-manager-implementation/src/controllers/edge_cloud_management_controller.py b/service-resource-manager-implementation/src/controllers/edge_cloud_management_controller.py index 70f7f15402e493523a5dc61e165d2ba9dd2e9b20..0194906540ff75faa5c8db68da85cab5216c1ace 100644 --- a/service-resource-manager-implementation/src/controllers/edge_cloud_management_controller.py +++ b/service-resource-manager-implementation/src/controllers/edge_cloud_management_controller.py @@ -1,109 +1,77 @@ import connexion -import six import logging -import os +from os import environ +from sunrise6g_opensdk import Sdk as sdkclient +import sys logger=logging.getLogger(__name__) +logging.basicConfig(level=logging.DEBUG) -adapter_name = os.environ['EDGE_CLOUD_ADAPTER_NAME'] -adapter_base_url = os.environ['ADAPTER_BASE_URL'] -adapter = None +if environ['EDGE_CLOUD_ADAPTER_NAME'] is not None: + edgecloud_adapter_name = environ['EDGE_CLOUD_ADAPTER_NAME'] + edgecloud_adapter_base_url = environ['ADAPTER_BASE_URL'] + edgecloud_adapter_specs = {'client_name': edgecloud_adapter_name, 'base_url': edgecloud_adapter_base_url} + edgecloud_adapter_specs.update(environ) + print('Creating edge cloud adapter with env: ', edgecloud_adapter_specs) + adapters = sdkclient.create_adapters_from(adapter_specs={'edgecloud': edgecloud_adapter_specs}) + edgecloud_adapter = adapters.get("edgecloud") +# else: +# logging.error('Edge Cloud adapter has not been specified! Aborting...') +# sys.exit() -if adapter_name=='aeros': - from src.clients.edgecloud.clients.aeros.client import EdgeApplicationManager - adapter = EdgeApplicationManager(base_url=adapter_base_url) -elif adapter_name=='i2edge': - from src.clients.edgecloud.clients.i2edge.client import EdgeApplicationManager - adapter = EdgeApplicationManager(base_url=adapter_base_url) -elif adapter_name=='kubernetes': - from src.clients.edgecloud.clients.piedge.client import EdgeApplicationManager - adapter = EdgeApplicationManager(base_url=adapter_base_url, **os.environ) def deregister_service_function(service_function_id: str): # noqa: E501 """Deregister service. - # noqa: E501 - - :param service_function_name: Returns a specific service function from the catalogue. - :type service_function_name: str - - :rtype: None - - + :param service_function_name: Removed app metadata from the catalogue. """ try: - status_deregistration, code = adapter.delete_onboarded_app(service_function_id) - return status_deregistration, code + code = edgecloud_adapter.delete_onboarded_app(service_function_id) + return code except Exception as ce_: raise Exception("An exception occurred :", ce_) def get_service_function(service_function_id: str): # noqa: E501 - """Returns a specific service function from the catalogue. - - # noqa: E501 - - :param service_function_id: Returns a specific service function from the catalogue. - :type service_function_id: str - - :rtype: AppsResponseApps + """Returns a specific app from the catalogue. """ try: - service_function = adapter.get_onboarded_app(service_function_id) + service_function = edgecloud_adapter.get_onboarded_app(service_function_id) return service_function except Exception as ce_: raise Exception("An exception occurred :", ce_) def get_service_functions(): # noqa: E501 - """Returns service functions from the catalogue. - - # noqa: E501 - - - :rtype: AppsResponse + """Returns all apps from the catalogue. """ try: - service_functions = adapter.get_all_onboarded_apps() + service_functions = edgecloud_adapter.get_all_onboarded_apps() return service_functions except Exception as ce_: raise Exception("An exception occurred :", ce_) def register_service_function(body=None): # noqa: E501 - """Register Service. - - # noqa: E501 - - :param body: Registration method to save service function into database - :type body: dict | bytes - - :rtype: None + """Registers app to the database """ if connexion.request.is_json: insert_doc = connexion.request.get_json() try: - return adapter.onboard_app(insert_doc) + return edgecloud_adapter.onboard_app(insert_doc) except Exception as ce_: return ce_ def delete_deployed_service_function(app_id: str): # noqa: E501 - """Deletes a deployed Service function. - - # noqa: E501 - - :param deployed_service_function_name: Represents a service function from the running deployments. - :type deployed_service_function_name: str - - :rtype: None + """Undeployes app """ response = None try: - response = adapter.undeploy_app(app_id) + response = edgecloud_adapter.undeploy_app(app_id) # return response except Exception as ce_: @@ -130,7 +98,7 @@ def deploy_service_function(): # noqa: E501 try: # body = DeployApp.from_dict(connexion.request.get_json()) body = connexion.request.get_json() - response = adapter.deploy_app(app_id=body.get("appId"), app_zones=body.get("appZones")) + response = edgecloud_adapter.deploy_app(body) # body = DeployServiceFunction.from_dict(connexion.request.get_json()) # response = piedge_encoder.deploy_service_function(body) return response @@ -154,7 +122,7 @@ def get_deployed_service_functions(): # noqa: E501 # role = user_authentication.check_role() # if role is not None and role == "admin": try: - response = adapter.get_all_deployed_apps() + response = edgecloud_adapter.get_all_deployed_apps() return response except Exception as ce_: logger.error(ce_) @@ -174,7 +142,7 @@ def get_deployed_service_function(app_id: str): # noqa: E501 # role = user_authentication.check_role() # if role is not None and role == "admin": try: - response = adapter.get_deployed_app(app_id=app_id) + response = edgecloud_adapter.get_deployed_app(app_id=app_id) return response except Exception as ce_: logger.error(ce_) @@ -185,10 +153,23 @@ def get_nodes(): # noqa: E501 # noqa: E501 + :rtype: NodesResponse + """ + # try: + response = edgecloud_adapter.get_edge_cloud_zones() + return response + # except Exception as ce_: + # logger.info(ce_) + +def node_details(node_id: str): # noqa: E501 + """Returns the edge nodes status. + + # noqa: E501 + :rtype: NodesResponse """ try: - response = adapter.get_edge_cloud_zones() + response = edgecloud_adapter.get_edge_cloud_zones_details(zone_id=node_id) return response except Exception as ce_: - logger.info(ce_) \ No newline at end of file + logger.info(ce_) \ No newline at end of file diff --git a/service-resource-manager-implementation/src/controllers/network_functions_controller.py b/service-resource-manager-implementation/src/controllers/network_functions_controller.py index 57a441f282ac691675f709bbf0fdda6565c2bd5f..ced3527c8e7d71a6fb092c95476aa555d9c8b266 100644 --- a/service-resource-manager-implementation/src/controllers/network_functions_controller.py +++ b/service-resource-manager-implementation/src/controllers/network_functions_controller.py @@ -1,61 +1,67 @@ from os import environ import logging import connexion +import sys +from sunrise6g_opensdk.common.sdk import Sdk as sdkclient logger = logging.getLogger(__name__) logging.basicConfig(level=logging.DEBUG) +if environ.get('NETWORK_ADAPTER_NAME') is not None: + network_adapter_name = environ.get('NETWORK_ADAPTER_NAME') + adapter_base_url = environ.get('NETWORK_ADAPTER_BASE_URL') + scs_as_id = environ.get('SCS_AS_ID') + network_adapter_specs = {'client_name': network_adapter_name, 'base_url': adapter_base_url, 'scs_as_id': scs_as_id} + network_adapter_specs.update(environ) + print('Creating network adapter with env: ', network_adapter_specs) + adapters = sdkclient.create_adapters_from(adapter_specs={'network': network_adapter_specs}) + network_adapter = adapters.get("network") +# else: +# logging.error('Network adapter has not been specified! Aborting...') +# sys.exit() -network_client = environ.get('NETWORK_CLIENT') -adapter = None - -if network_client is not None: - if network_client=='oai': - from src.clients.network.clients.oai.client import NetworkManager - adapter = NetworkManager() - elif network_client=='open5gcore': - from src.clients.network.clients.open5gcore.client import NetworkManager - adapter = NetworkManager() - else: - from src.clients.network.clients.open5gs.client import NetworkManager - adapter = NetworkManager() - - -def create_qod_session(body): +def create_qod_session(): + if connexion.request.is_json: try: - response = adapter.create_qod_session(body) + response = network_adapter.create_qod_session(connexion.request.get_json()) return response except Exception as ce_: logger.error(ce_) - return ce_ + return ce_ else: return 'ERROR: Could not read JSON payload.', 400 -def get_qod_session(id: str): +def get_qod_session(session_id: str): + try: - response = adapter.get_qod_session(id) - return response + response = network_adapter.get_qod_session(id) + return {'status': 200, 'session': response.json()} except Exception as ce_: - logger.error(ce_) - return ce_ + logger.error(ce_) + return ce_ -def delete_qod_session(id: str): +def delete_qod_session(session_id: str): + try: - response = adapter.delete_qod_session(id) - return 'QoD successfully removed' + response = network_adapter.delete_qod_session(id) + return response except Exception as ce_: - logger.error(ce_) - return ce_ + logger.error(ce_) + return ce_ def create_traffic_influence_resource(body): + #TODO pass def delete_traffic_influence_resource(id: str): + #TODO pass def get_traffic_influence_resource(id: str): + #TODO pass def get_all_traffic_influence_resources(): + #TODO pass \ No newline at end of file diff --git a/service-resource-manager-implementation/src/controllers/nodes_controller.py b/service-resource-manager-implementation/src/controllers/nodes_controller.py index 0e1d81e8ed6425f44bd19e682e21b12a7386acf3..c7713c8626d2f0ab715b970ec9c984d9cf4a6ccc 100644 --- a/service-resource-manager-implementation/src/controllers/nodes_controller.py +++ b/service-resource-manager-implementation/src/controllers/nodes_controller.py @@ -1,9 +1,4 @@ -import connexion -import six -import time -from src.core import piedge_encoder import logging -# from src.__main__ import driver import os @@ -13,13 +8,13 @@ adapter_name = os.environ['EDGE_CLOUD_ADAPTER_NAME'] adapter = None if adapter_name=='aeros': - from src.clients.edgecloud.clients.aeros.client import EdgeApplicationManager + from src.adapters.edgecloud.adapters.aeros.client import EdgeApplicationManager adapter = EdgeApplicationManager() elif adapter_name=='i2edge': - from src.clients.edgecloud.clients.i2edge.client import EdgeApplicationManager + from src.adapters.edgecloud.adapters.i2edge.client import EdgeApplicationManager adapter = EdgeApplicationManager() elif adapter_name=='piedge': - from src.clients.edgecloud.clients.piedge.client import EdgeApplicationManager + from src.adapters.edgecloud.adapters.kubernetes.client import EdgeApplicationManager adapter = EdgeApplicationManager() @@ -35,3 +30,16 @@ def get_nodes(): # noqa: E501 return response except Exception as ce_: logger.info(ce_) + +def node_details(node_id: str): # noqa: E501 + """Returns the edge nodes status. + + # noqa: E501 + + :rtype: NodesResponse + """ + try: + response = adapter.get_edge_cloud_zones_details(node_id=node_id) + return response + except Exception as ce_: + logger.info(ce_) diff --git a/service-resource-manager-implementation/src/controllers/operations_controller.py b/service-resource-manager-implementation/src/controllers/operations_controller.py index aec2bf67cb2130b870e0a9d29fbcf7690f7b5931..528d97bde59269facefbd7be47228e3238b4c902 100644 --- a/service-resource-manager-implementation/src/controllers/operations_controller.py +++ b/service-resource-manager-implementation/src/controllers/operations_controller.py @@ -1,42 +1,44 @@ -import paramiko +# import paramiko import logging from src.models.helm_install_model import HelmInstall from os import environ import connexion -master_node_password=environ["KUBERNETES_MASTER_PASSWORD"].strip() -master_node_hostname=environ["KUBERNETES_MASTER_HOSTNAME"].strip() -master_node_ip=environ["KUBERNETES_MASTER_IP"].strip() -master_node_port=environ["KUBERNETES_MASTER_PORT"].strip() +# master_node_password=environ.get("KUBERNETES_MASTER_PASSWORD").strip() +# master_node_hostname=environ.get("KUBERNETES_MASTER_HOSTNAME").strip() +# master_node_ip=environ.get("KUBERNETES_MASTER_IP").strip() +# master_node_port=environ.get("KUBERNETES_MASTER_PORT").strip() def install_helm_chart(helm=None): - logging.info('Installing helm chart') - if connexion.request.is_json: - try: - # logging.info(connexion.request.get_json()) - helm =connexion.request.get_json() - ssh=paramiko.SSHClient() - ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - ssh.connect(master_node_ip,22, username='dlaskaratos', password=master_node_password) - creds_string='' - if helm.get('repo_password') is not None and helm.get('repo_username') is not None: - creds_string=' --username '+helm['repo_username']+' --password '+helm['repo_password'] - stdin, stdout, stderr= ssh.exec_command('echo | sudo helm install '+helm['deployment_name']+' '+helm['uri']+creds_string) - stdout.channel.set_combine_stderr(True) - lines=stdout.readlines() - return lines - except Exception as e: - logging.error(e) - return e.__cause__ - else: - return 'Error installing helm chart' + pass +# logging.info('Installing helm chart') +# if connexion.request.is_json: +# try: +# # logging.info(connexion.request.get_json()) +# helm =connexion.request.get_json() +# ssh=paramiko.SSHClient() +# ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +# ssh.connect(master_node_ip,22, username='dlaskaratos', password=master_node_password) +# creds_string='' +# if helm.get('repo_password') is not None and helm.get('repo_username') is not None: +# creds_string=' --username '+helm['repo_username']+' --password '+helm['repo_password'] +# stdin, stdout, stderr= ssh.exec_command('echo | sudo helm install '+helm['deployment_name']+' '+helm['uri']+creds_string) +# stdout.channel.set_combine_stderr(True) +# lines=stdout.readlines() +# return lines +# except Exception as e: +# logging.error(e) +# return e.__cause__ +# else: +# return 'Error installing helm chart' def uninstall_helm_chart(name: str): - logging.info('Uninstalling helm chart') - ssh=paramiko.SSHClient() - ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - ssh.connect(master_node_ip,22, master_node_hostname,master_node_password) - stdin, stdout, stderr= ssh.exec_command('echo | sudo helm uninstall '+name) - stdout.channel.set_combine_stderr(True) - lines=stdout.readlines() - return lines + pass +# logging.info('Uninstalling helm chart') +# ssh=paramiko.SSHClient() +# ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +# ssh.connect(master_node_ip,22, master_node_hostname,master_node_password) +# stdin, stdout, stderr= ssh.exec_command('echo | sudo helm uninstall '+name) +# stdout.channel.set_combine_stderr(True) +# lines=stdout.readlines() +# return lines diff --git a/service-resource-manager-implementation/src/core/__init__.py b/service-resource-manager-implementation/src/core/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/service-resource-manager-implementation/src/core/piedge_encoder.py b/service-resource-manager-implementation/src/core/piedge_encoder.py deleted file mode 100644 index 9f6f8dd932b3fd46944331e0c2e312a863b797e8..0000000000000000000000000000000000000000 --- a/service-resource-manager-implementation/src/core/piedge_encoder.py +++ /dev/null @@ -1,325 +0,0 @@ -import logging -import traceback -import connexion -import six -import json -import sys -import os - -# from src.models.service_function_registration_request import ServiceFunctionRegistrationRequest # noqa: E501 -from src.models.deploy_service_function import DeployServiceFunction # noqa: E501 -from src.core import paas_handler -#from src.utils import connector_db -from src.utils import kubernetes_connector, connector_db, auxiliary_functions, nodes_monitoring - -driver=os.environ['DRIVER'].strip() - - -def deploy_chain(chain_input): - for app_ in chain_input["chain_paas_services_order"]: - for app_details in chain_input["apps"]: - if app_ == app_details["paas_input_name"]: - - app_details["paas_input_name"]= chain_input["chain_service_name"] + "-" + app_ - response = deploy_service_function(app_details) - - break - return "Chain deployed successfully" - - -def deploy_service_function(service_function: DeployServiceFunction, paas_name=None): # noqa: E501 - - # descriptor_paas_input["scaling_type"]="minimize_cost" - # print(descriptor_paas_input) - # we need to create the descriptor_paas_ needed for deployment - # search if app exists in the catalogue - - - - ser_function_ = connector_db.get_documents_from_collection("service_functions", input_type="name", - input_value=service_function.service_function_name) - if not ser_function_: - return "The given service function does not exist in the catalogue" - - - # search if node exists in the node catalogue - # if service_function.location is not None: - # node_ = connector_db.get_documents_from_collection("points_of_presence", input_type="location", - # input_value=service_function.location) - # if not node_: - # return "The given location does not exist in the node catalogue" - - final_deploy_descriptor = {} - # final_deploy_descriptor["name"]=app_[0]["name"] - - # deployed_name= app_[0]["name"] + "emp"+ descriptor_paas_input["paas_input_name"] - if paas_name is not None: - final_deploy_descriptor["paas_name"] = paas_name - - deployed_name = service_function.service_function_instance_name - - - deployed_name= auxiliary_functions.prepare_name(deployed_name, driver) - - final_deploy_descriptor["name"] = deployed_name - - - final_deploy_descriptor["count-min"] = 1 if service_function.count_min is None else service_function.count_min - final_deploy_descriptor["count-max"] = 1 if service_function.count_max is None else service_function.count_max - - if final_deploy_descriptor["count-min"]>final_deploy_descriptor["count-max"]: - final_deploy_descriptor["count-min"]=final_deploy_descriptor["count-max"] - - if service_function.location is not None: - final_deploy_descriptor["location"] = service_function.location - - containers = [] - con_ = {} - con_["image"] = ser_function_[0]["image"] - - if "privileged" in ser_function_[0]: - - con_["privileged"]=ser_function_[0]["privileged"] - - - #con_["imagePullPolicy"] = "Always" - #ports - - application_ports = ser_function_[0].get("application_ports") - con_["application_ports"] = application_ports - - if service_function.all_node_ports is not None: - - if service_function.all_node_ports==False and service_function.node_ports is None: - return "Please provide the application ports in the field exposed_ports or all_node_ports==true" - - if service_function.all_node_ports: - con_["exposed_ports"] = application_ports - else: - - exposed_ports = auxiliary_functions.return_equal_ignore_order(application_ports, - service_function.node_ports) - if exposed_ports: - con_["exposed_ports"] = exposed_ports - # application_ports = ser_function_[0]["application_ports"] - # con_["application_ports"] = application_ports - # containers.append(con_) - else: - if service_function.node_ports is not None: - exposed_ports = auxiliary_functions.return_equal_ignore_order(application_ports, - service_function.node_ports) - if exposed_ports: - - con_["exposed_ports"] = exposed_ports - containers.append(con_) - - - final_deploy_descriptor["containers"] = containers - #final_deploy_descriptor["restartPolicy"] = "Always" - - #check volumes!! - req_volumes = [] - if "required_volumes" in ser_function_[0]: - if ser_function_[0].get("required_volumes") is not None: - for required_volumes in ser_function_[0]["required_volumes"]: - req_volumes.append(required_volumes["name"]) - vol_mount = [] - volume_input = [] - - - if service_function.volume_mounts is not None: - for volume_mounts in service_function.volume_mounts: - - vo_in = {} - - vo_in["name"] = volume_mounts.name - vo_in["storage"] = volume_mounts.storage - volume_input.append(vo_in) - vol_mount.append(volume_mounts.name) - if (len(vol_mount) != len(req_volumes)): - return "The selected service function requires " + str(len(req_volumes)) +" volume/ volumes " - else: - if ser_function_[0].get("required_volumes") is not None: - - result = auxiliary_functions.equal_ignore_order(req_volumes, vol_mount) - - if result is False: - return "The selected service function requires " + str(len(req_volumes)) +" volume/ volumes. Please check volume names" - else: - volumes=[] - for vol in ser_function_[0]["required_volumes"]: - for vol_re in service_function.volume_mounts: - vol_={} - if vol["name"]==vol_re.name: - vol_["name"]=vol_re.name - vol_["storage"]=vol_re.storage - vol_["path"]=vol["path"] - if "hostpath" in vol: - vol_["hostpath"] = vol["hostpath"] - volumes.append(vol_) - final_deploy_descriptor["volumes"] = volumes - - #check env parameters: - req_env_parameters = [] - - - if "required_env_parameters" in ser_function_[0]: - if ser_function_[0].get("required_env_parameters") is not None: - for required_env_parameters in ser_function_[0]["required_env_parameters"]: - req_env_parameters.append(required_env_parameters["name"]) - env_names = [] - env_input = [] - if service_function.env_parameters is not None: - for env_parameters in service_function.env_parameters: - env_in = {} - - env_in["name"] = env_parameters.name - if env_parameters.value is not None: - env_in["value"] = env_parameters.value - elif env_parameters.value_ref is not None: - env_in["value_ref"] = env_parameters.value_ref - env_input.append(env_in) - env_names.append(env_parameters.name) - if (len(env_names) != len(req_env_parameters)): - return "The selected service function requires " + str(len(req_env_parameters)) + " env parameters" - else: - if ser_function_[0].get("required_env_parameters") is not None: - - result = auxiliary_functions.equal_ignore_order(req_env_parameters, env_names) - - if result is False: - return "The selected service function requires " + str( - len(req_env_parameters)) + " env parameters. Please check names of env parameters" - else: - #EnvParameters to dict - paremeters = [] - for reqenv in ser_function_[0]["required_env_parameters"]: - for env_in in service_function.env_parameters: - reqenv_ = {} - if reqenv["name"] == env_in.name: - reqenv_["name"] = env_in.name - if env_in.value is not None: - reqenv_["value"] = env_in.value - elif env_in.value_ref is not None: - reqenv_["value_ref"] = env_in.value_ref - paremeters.append(reqenv_) - final_deploy_descriptor["env_parameters"] = paremeters - - - #check autoscaling policies - if "autoscaling_policies" in ser_function_[0]: - if ser_function_[0].get("autoscaling_policies") is not None: - if service_function.autoscaling_metric is not None: - for scaling_method in ser_function_[0]["autoscaling_policies"]: - if service_function.autoscaling_policy is not None: - if scaling_method["policy"] == service_function.autoscaling_policy: - for metric in scaling_method["monitoring_metrics"]: - - if metric["metric"] == service_function.autoscaling_metric: - scaling_metric_ = [] - scaling_metric_.append(metric) - final_deploy_descriptor["autoscaling_policies"] = scaling_metric_ - break - - ##################START##################### TODO!!!!!!!!!!!!!!!!!!!!1 - - # #Get deployed apps to check if app exist (if yes use patch methods) - # deployed_apps = kubernetes_connector.get_deployed_apps() - # - # - # exists_flag = False - # for deployed_app in deployed_apps: - # if "appname" in deployed_app: - # if final_deploy_descriptor["name"] == deployed_app["appname"]: - # exists_flag = True - # break - - exists_flag=False - ##################END##################### - - - if exists_flag: - response = kubernetes_connector.patch_service_function(final_deploy_descriptor) - else: - - response = kubernetes_connector.deploy_service_function(final_deploy_descriptor) - # insert it to mongo db - deployed_service_function_db = {} - deployed_service_function_db["service_function_name"] = ser_function_[0]["name"] - if service_function.location is not None: - deployed_service_function_db["location"] = service_function.location - deployed_service_function_db["instance_name"] = deployed_name - - if service_function.autoscaling_policy is not None: - deployed_service_function_db["autoscaling_policy"] = service_function.autoscaling_policy - - if "volumes" in final_deploy_descriptor: - deployed_service_function_db["volumes"] = final_deploy_descriptor["volumes"] - if "env_parameters" in final_deploy_descriptor: - deployed_service_function_db["env_parameters"] = final_deploy_descriptor["env_parameters"] - - if service_function.monitoring_services: - monitor_url = nodes_monitoring.create_monitoring_for_service_function(service_function) - deployed_service_function_db["monitoring_service_URL"] = monitor_url - - if "paas_name" in final_deploy_descriptor: - deployed_service_function_db["paas_name"] = final_deploy_descriptor["paas_name"] - - if "Conflict" not in response: - - if "location" not in deployed_service_function_db: - deployed_service_function_db["location"]= "Node is selected by the K8s scheduler" - connector_db.insert_document_deployed_service_function(document=deployed_service_function_db) - - return response - # return "PaaS deployed successfully" - # except Exception as ce_: - - # logging.error(traceback.format_exc()) - # # logging.error("ERROR NAME: ", fname) - # # logging.error("ERROR INFO: ", exc_tb.tb_lineno) - # return ("An exception occurred :", ce_) - - -def initiliaze_edge_nodes(): - try: - - nodes = kubernetes_connector.get_PoPs() - kubernetes_connector.create_immediate_storageclass() - # write it to mongodb - nodes_mon=[] - - nodes_num=0 - for node in nodes: - nodes_num=nodes_num+1 - node_ = {} - node_["name"] = node.name - node_["location"] = node.location - node_["_id"] = node.id - node_["serial"] = node.serial - node_["node_type"] = node.node_type - #create monitoring url - # http://146.124.106.230:3000/d/piedge-k8smaster/k8smaster-node?orgId=1&refresh=1m - #node_["stats_url"]="http://146.124.106.230:3000/d/piedge-k8smaster/k8smaster-node?orgId=1&refresh=1m" - - mon_url=nodes_monitoring.create_monitoring_infra_per_node(node_,nodes_num) - # print('MON_URL: '+mon_url) - node_["nodeUsageMonitoringURL"]=mon_url - # print(node_) - connector_db.insert_document_nodes(node_) - nodes_mon.append(node_) - - # #Creating storageclass for each node - Will be mostly used for migrating stateful applications. - # for node in nodes: - # kubernetes_connector.create_node_storageclass(node) - - # #creates storage class with immediate volume binding mode - will be used for pvc migration - # for node in nodes: - # kubernetes_connector.create_immediate_storageclass(node) - - - nodes_monitoring.create_monitoring_for_all_infra(nodes_mon) - return "Nodes initialized" - except Exception as ce_: - logging.error(traceback.format_exc()) - raise Exception("An exception occurred :", ce_) diff --git a/service-resource-manager-implementation/src/encoder.py b/service-resource-manager-implementation/src/encoder.py index 3451299f175dc744a47583a6c4dcba24231cdbd2..1de7e669b817565d8ed1cf32f7f6289d432adb92 100644 --- a/service-resource-manager-implementation/src/encoder.py +++ b/service-resource-manager-implementation/src/encoder.py @@ -1,10 +1,9 @@ -from connexion.apps.flask_app import FlaskJSONEncoder +from connexion.jsonifier import Jsonifier import six from src.models.base_model_ import Model - -class JSONEncoder(FlaskJSONEncoder): +class JSONEncoder(Jsonifier): include_nulls = False def default(self, o): @@ -17,4 +16,4 @@ class JSONEncoder(FlaskJSONEncoder): attr = o.attribute_map[attr] dikt[attr] = value return dikt - return FlaskJSONEncoder.default(self, o) + return Jsonifier.default(self, o) diff --git a/service-resource-manager-implementation/src/swagger/swagger.yaml b/service-resource-manager-implementation/src/swagger/swagger.yaml index 18edf2a32bccfdc496c619f2518844509b8baa3f..46550ce3c4146a29a6b57e7377f6e3dd258d60bc 100644 --- a/service-resource-manager-implementation/src/swagger/swagger.yaml +++ b/service-resource-manager-implementation/src/swagger/swagger.yaml @@ -2,7 +2,7 @@ openapi: 3.0.0 info: title: SRM Controller API description: | - API exposed by SRM for "PaaS" - based interaction with NFV MANO. + API exposed by SRM for for CAMARA-deefined operations. termsOfService: http://swagger.io/terms/ contact: email: dlaskaratos@intracom-telecom.com @@ -14,26 +14,31 @@ externalDocs: description: Find out more about Swagger url: http://swagger.io servers: -- url: http://vitrualserver:8080/piedge-connector/2.0.0 +- url: http://vitrualserver:8080/srm/1.0.0 paths: -# /authentication: -# post: -# tags: -# - Login -# summary: Login with a username and password. -# operationId: authentication_login -# requestBody: -# description: Registration method to login -# content: -# application/json: -# schema: -# $ref: '#/components/schemas/LoginRegistrationRequest' -# responses: -# "200": -# description: A JSON Web Token (JWT). -# "401": -# description: Incorrect username or password. -# x-openapi-router-controller: src.controllers.login_controller + /node/{node_id}: + get: + tags: + - Nodes + summary: Get Node details by Node identifier + operationId: node_details + parameters: + - name: node_id + in: path + description: Gets node details + required: true + style: simple + explode: false + schema: + type: string + responses: + "200": + description: Node details retrieved + "405": + description: Method not allowed + "404": + description: Node does not exist + x-openapi-router-controller: src.controllers.edge_cloud_management_controller /helm: post: tags: @@ -311,14 +316,14 @@ paths: "200": description: Session created. x-openapi-router-controller: src.controllers.network_functions_controller - /sessions/{id}: + /sessions/{sessionId}: get: tags: - Quality on Demand Functions summary: Retrieve details of a QoD Session operationId: get_qod_session parameters: - - name: id + - name: sessionId in: path description: Represents a QoD Session. required: true @@ -340,7 +345,7 @@ paths: summary: Remove QoD Session operationId: delete_qod_session parameters: - - name: id + - name: sessionId in: path description: Represents a QoD Session. required: true diff --git a/srm-deployment.yaml b/srm-deployment.yaml index 74461cd85d79cfe4b86d9857034badfa2d22db35..4df2ea7966148d35c289cd5223971ac18f1f97db 100644 --- a/srm-deployment.yaml +++ b/srm-deployment.yaml @@ -1,3 +1,9 @@ +kind: Namespace +apiVersion: v1 +metadata: + name: sunrise6g + labels: + name: sunrise6g --- apiVersion: apps/v1 kind: Deployment @@ -73,44 +79,23 @@ metadata: io.kompose.service: srm name: srm spec: - type: NodePort + type: ClusterIP ports: - name: "8080" - nodePort: 32415 port: 8080 - targetPort: 8080 selector: io.kompose.service: srmcontroller status: loadBalancer: {} --- -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: srm-ingress - annotations: - #traefik.ingress.kubernetes.io/router.entrypoints: web -spec: - ingressClassName: nginx - rules: - - http: - paths: - - path: /srm - pathType: Prefix - backend: - service: - name: srm - port: - number: 8080 ---- kind: PersistentVolume apiVersion: v1 metadata: name: mongodb-pv-volume # Sets PV's name labels: type: local # Sets PV's type to local - app: mongopiedge + app: mongosrm spec: storageClassName: manual capacity: @@ -144,13 +129,13 @@ metadata: kompose.version: 1.26.0 (40646f47) creationTimestamp: null labels: - io.kompose.service: mongopiedge - name: mongopiedge + io.kompose.service: mongosrm + name: mongosrm spec: replicas: 1 selector: matchLabels: - io.kompose.service: mongopiedge + io.kompose.service: mongosrm strategy: type: Recreate template: @@ -161,11 +146,11 @@ spec: creationTimestamp: null labels: #io.kompose.network/netEMPkub: "true" - io.kompose.service: mongopiedge + io.kompose.service: mongosrm spec: containers: - image: mongo - name: mongopiedge + name: mongosrm ports: - containerPort: 27017 resources: {} @@ -187,8 +172,8 @@ metadata: kompose.version: 1.26.0 (40646f47) creationTimestamp: null labels: - io.kompose.service: mongopiedge - name: mongopiedge + io.kompose.service: mongosrm + name: mongosrm spec: type: ClusterIP ports: @@ -196,132 +181,9 @@ spec: port: 27017 targetPort: 27017 selector: - io.kompose.service: mongopiedge -status: - loadBalancer: {} ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - annotations: - kompose.cmd: kompose convert - kompose.version: 1.26.0 (40646f47) - creationTimestamp: null - labels: - io.kompose.service: oegcontroller - name: oegcontroller -spec: - replicas: 1 - selector: - matchLabels: - io.kompose.service: oegcontroller - strategy: {} - template: - metadata: - annotations: - kompose.cmd: kompose convert - kompose.version: 1.26.0 (40646f47) - creationTimestamp: null - labels: - io.kompose.service: oegcontroller - spec: - containers: - - env: - - name: MONGO_URI - value: mongodb://oegmongo/sample_db?authSource=admin - - name: SRM_HOST - value: http://srm:8080/piedge-connector/2.0.0 - - name: FEDERATION_MANAGER_HOST - value: http://federation-manager:8989 - #- name: PI_EDGE_USERNAME - # value: username - #- name: PI_EDGE_PASSWORD - # value: password - #- name: HTTP_PROXY - # value: http://proxy - image: ghcr.io/sunriseopenoperatorplatform/oeg/oeg - name: oegcontroller - ports: - - containerPort: 8080 - resources: {} - imagePullPolicy: Always - restartPolicy: Always - -status: {} ---- -apiVersion: v1 -kind: Service -metadata: - annotations: - kompose.cmd: kompose convert - kompose.version: 1.26.0 (40646f47) - creationTimestamp: null - labels: - io.kompose.service: oeg - name: oeg -spec: - type: NodePort - ports: - - name: "8080" - nodePort: 32414 - port: 8080 - targetPort: 8080 - selector: - io.kompose.service: oegcontroller + io.kompose.service: mongosrm status: loadBalancer: {} - ---- -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: oeg-ingress - annotations: - #traefik.ingress.kubernetes.io/router.entrypoints: web -spec: - ingressClassName: nginx - rules: - - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: oeg - port: - number: 8080 ---- -kind: PersistentVolume -apiVersion: v1 -metadata: - name: oegmongodb-pv-volume # Sets PV's name - labels: - type: local # Sets PV's type to local - app: oegmongo -spec: - storageClassName: manual - capacity: - storage: 200Mi # Sets PV Volume - accessModes: - - ReadWriteOnce - hostPath: - path: "/mnt/data/mongodb_oeg" ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - creationTimestamp: null - labels: - io.kompose.service: oegmongo - name: oeg-mongo-db -spec: - storageClassName: manual - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 200Mi -status: {} --- apiVersion: apps/v1 kind: Deployment @@ -356,15 +218,7 @@ spec: ports: - containerPort: 27017 resources: {} - volumeMounts: - - mountPath: /data/db - name: mongo-db restartPolicy: Always - volumes: - - name: mongo-db - persistentVolumeClaim: - claimName: oeg-mongo-db -status: {} --- apiVersion: v1 kind: Service @@ -386,264 +240,71 @@ spec: io.kompose.service: oegmongo status: loadBalancer: {} ---- + --- apiVersion: apps/v1 kind: Deployment metadata: - name: artefact-manager -spec: - replicas: 1 - selector: - matchLabels: - app: artefact-manager - template: - metadata: - labels: - app: artefact-manager - spec: - containers: - - name: artefact-manager - image: ghcr.io/sunriseopenoperatorplatform/artefactmanager:0.5 - ports: - - containerPort: 8000 - env: - - name: PYTHONPATH - value: "/app" ---- -apiVersion: v1 -kind: Service -metadata: - name: artefact-manager-service -spec: - type: ClusterIP - selector: - app: artefact-manager - ports: - - protocol: TCP - port: 8000 - targetPort: 8000 ---- -kind: Secret -apiVersion: v1 -metadata: - name: federation-manager-config -data: - config.cfg: >- - W2tleWNsb2FrXQpjbGllbnQxX2lkID0gb3JpZ2luYXRpbmctb3AtMQpjbGllbnQxX3NlY3JldCA9IGRkN3ZOd0Zxak5wWXdhZ2hsRXdNYncxMGcwa2xXREhiCmNsaWVudDJfaWQgPSBvcmlnaW5hdGluZy1vcC0yCmNsaWVudDJfc2VjcmV0ID0gMm1oem5FUmZXY2xMRHVWb2pZNzdMcDRRZDJyNGU4TXMKc2NvcGUgPSBmZWQtbWdtdAoKW3NlcnZlcl0KaG9zdCA9IDEyNy4wLjAuMQpwb3J0ID0gODk4OQpwcmVmaXggPSBhcGkKdmVyc2lvbiA9IHYxLjAKcHJvdG9jb2wgPSBodHRwCgpbbW9uZ29kYl0KaG9zdCA9IG1vbmdvZGIubW9uZ29kYi5zdmMuY2x1c3Rlci5sb2NhbApwb3J0ID0gMjcwMTcKCltpMmVkZ2VdCmhvc3QgPSAxOTIuMTY4LjEyMy4yMzcKcG9ydCA9IDMwNzYwCgpbb3BfZGF0YV0KcGFydG5lck9QRmVkZXJhdGlvbklkID0gaTJjYXQKcGFydG5lck9QQ291bnRyeUNvZGUgPSBFUwpwYXJ0bmVyT1BNb2JpbGVOZXR3b3JrQ29kZV9NQ0MgPSAwMDEKcGFydG5lck9QTW9iaWxlTmV0d29ya0NvZGVfTU5DID0gMDEKcGFydG5lck9QRml4ZWROZXR3b3JrQ29kZSA9IDM0CnBsYXRmb3JtQ2FwcyA9IGhvbWVSb3V0aW5nCmVkZ2VEaXNjb3ZlcnlTZXJ2aWNlRW5kUG9pbnRfcG9ydCA9CmVkZ2VEaXNjb3ZlcnlTZXJ2aWNlRW5kUG9pbnRfZnFkbiA9IGRpc2NvdmVyeS5vcGVyYXRvcjEuY29tCmVkZ2VEaXNjb3ZlcnlTZXJ2aWNlRW5kUG9pbnRfaXB2NEFkZHJlc3NlcyA9CmVkZ2VEaXNjb3ZlcnlTZXJ2aWNlRW5kUG9pbnRfaXB2NkFkZHJlc3NlcyA9CmxjbVNlcnZpY2VFbmRQb2ludF9wb3J0ID0gODk4OQpsY21TZXJ2aWNlRW5kUG9pbnRfZnFkbiA9CmxjbVNlcnZpY2VFbmRQb2ludF9pcHY0QWRkcmVzc2VzID0gMTI3LjAuMC4xCmxjbVNlcnZpY2VFbmRQb2ludF9pcHY2QWRkcmVzc2VzID0KCltwYXJ0bmVyX29wXQojIERlZmluZXMgdGhlIHJvbGUgb2YgdGhlIEZlZGVyYXRpb24gTWFuYWdlcgpwYXJ0bmVyX29wX2hvc3QgPSAxMjcuMC4wLjEKcGFydG5lcl9vcF9zZXJ2ZXIgPSAvb3BlcmF0b3JwbGF0Zm9ybS9mZWRlcmF0aW9uL3YxCnBhcnRuZXJfb3BfcG9ydCA9IDg5OTAKI3JvbGUgPSBvcmlnaW5hdGluZ19vcApyb2xlID0gcGFydG5lcl9vcA== -type: Opaque ---- -kind: Deployment -apiVersion: apps/v1 -metadata: - labels: - app: federation-manager - name: federation-manager -spec: - replicas: 1 - selector: - matchLabels: - app: federation-manager - template: - metadata: - labels: - app: federation-manager - spec: - containers: - - name: federation-manager - image: ghcr.io/sunriseopenoperatorplatform/federation-manager:0.0.1 - imagePullPolicy: Always - volumeMounts: - - name: config - readOnly: false - mountPath: /usr/app/src/conf/ - ports: - - containerPort: 8989 - protocol: TCP - resources: - requests: - cpu: "2" - memory: "4Gi" - limits: - cpu: "4" - memory: "6Gi" - imagePullSecrets: - - name: federation-manager-regcred - volumes: - - name: config - secret: - secretName: federation-manager-config - defaultMode: 420 ---- -kind: Service -apiVersion: v1 -metadata: + annotations: + kompose.cmd: kompose convert + kompose.version: 1.26.0 (40646f47) + creationTimestamp: null labels: - app: federation-manager - name: federation-manager -spec: - type: ClusterIP - ports: - - name: http - port: 8989 - protocol: TCP - targetPort: 8989 - selector: - app: federation-manager ---- -kind: ConfigMap -apiVersion: v1 -metadata: - name: keycloak-config -data: - realm-import.json: | - { - "realm": "federation", - "enabled": true, - "clientScopes" : [ - { - "id" : "439d9c71-8a8a-469c-9280-058016000cc2", - "name" : "fed-mgmt", - "protocol": "openid-connect", - "description" : "fed-mgmt" - } - ], - "clients": [ - { - "clientId": "originating-op-1", - "enabled": true, - "clientAuthenticatorType": "client-secret", - "secret": "dd7vNwFqjNpYwaghlEwMbw10g0klWDHb", - "redirectUris": ["http://localhost:8080/*"], - "publicClient": false, - "directAccessGrantsEnabled": true, - "serviceAccountsEnabled": true, - "defaultClientScopes": ["fed-mgmt"], - "webOrigins": ["*"] - } - ] - } ---- -kind: Deployment -apiVersion: apps/v1 -metadata: - name: keycloak + io.kompose.service: oegcontroller + name: oegcontroller spec: replicas: 1 selector: matchLabels: - app: keycloak + io.kompose.service: oegcontroller + strategy: {} template: metadata: + annotations: + kompose.cmd: kompose convert + kompose.version: 1.26.0 (40646f47) + creationTimestamp: null labels: - app: keycloak + io.kompose.service: oegcontroller spec: containers: - - name: keycloak - image: quay.io/keycloak/keycloak:26.1.4 + - env: + - name: MONGO_URI + value: mongodb://oegmongo/sample_db?authSource=admin + - name: SRM_HOST + value: http://srm:8080/srm/1.0.0 + - name: FEDERATION_MANAGER_HOST + value: http://federation-manager:8989 + image: ghcr.io/sunriseopenoperatorplatform/oeg/oeg + name: oegcontroller ports: - containerPort: 8080 - args: [ "start-dev", "--import-realm" ] - env: - - name: KC_BOOTSTRAP_ADMIN_USERNAME - value: admin - - name: KC_BOOTSTRAP_ADMIN_PASSWORD - value: admin - - name: KC_IMPORT - value: /opt/keycloak/data/import/realm-import.json - volumeMounts: - - name: realm-import - mountPath: /opt/keycloak/data/import/ - volumes: - - name: realm-import - configMap: - name: keycloak-config + resources: {} + imagePullPolicy: Always + restartPolicy: Always + +status: {} --- -kind: Service apiVersion: v1 +kind: Service metadata: - name: keycloak + annotations: + kompose.cmd: kompose convert + kompose.version: 1.26.0 (40646f47) + creationTimestamp: null + labels: + io.kompose.service: oeg + name: oeg spec: type: NodePort ports: - - protocol: TCP + - name: "8080" + nodePort: 32414 port: 8080 targetPort: 8080 - nodePort: 30081 - selector: - app: keycloak ---- -kind: PersistentVolume -apiVersion: v1 -metadata: - name: mongodb -spec: - capacity: - storage: 1Gi - hostPath: - path: /tmp/db - accessModes: - - ReadWriteOnce - persistentVolumeReclaimPolicy: Retain ---- -kind: PersistentVolumeClaim -apiVersion: v1 -metadata: - name: mongodb -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi - volumeName: mongodb ---- -kind: Deployment -apiVersion: apps/v1 -metadata: - name: mongodb -spec: - replicas: 1 - selector: - matchLabels: - app: mongodb - template: - metadata: - labels: - app: mongodb - spec: - volumes: - - name: storage - persistentVolumeClaim: - claimName: mongodb - containers: - - name: mongodb - image: 'mongo:7.0' - ports: - - containerPort: 27017 - protocol: TCP - env: - - name: MONGO_INITDB_DATABASE - value: federation-manager - - name: MONGODB_DATA_DIR - value: /data/db - - name: MONDODB_LOG_DIR - value: /dev/null - volumeMounts: - - name: storage - mountPath: /data/db - imagePullPolicy: IfNotPresent ---- -kind: Service -apiVersion: v1 -metadata: - name: mongodb -spec: - type: NodePort - ports: - - protocol: TCP - port: 27017 - targetPort: 27017 - nodePort: 30017 selector: - app: mongodb \ No newline at end of file + io.kompose.service: oegcontroller +status: + loadBalancer: {} + + diff --git a/sunrise6g-deployment.yaml b/sunrise6g-deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..27d8a87565517c7ba523cbee3da10e91e676b959 --- /dev/null +++ b/sunrise6g-deployment.yaml @@ -0,0 +1,193 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + kompose.cmd: kompose convert + kompose.version: 1.26.0 (40646f47) + creationTimestamp: null + labels: + io.kompose.service: srmcontroller + name: srmcontroller +spec: + replicas: 1 + selector: + matchLabels: + io.kompose.service: srmcontroller + strategy: {} + template: + metadata: + annotations: + kompose.cmd: kompose convert + kompose.version: 1.26.0 (40646f47) + creationTimestamp: null + labels: + io.kompose.service: srmcontroller + spec: + containers: + - env: + - name: KUBERNETES_MASTER_IP + value: k3d-sunriseop-server-0 + - name: KUBERNETES_MASTER_PORT + value: "6443" + - name: KUBERNETES_USERNAME + value: cluster-admin + - name: K8S_NAMESPACE + value: sunrise6g + - name: EMP_STORAGE_URI + value: mongodb://mongopiedge:27017 + - name: KUBERNETES_MASTER_TOKEN + value: eyJhbGciOiJSUzI1NiIsImtpZCI6IkRRS3VMNktkc1BOYk5ZeDhfSnFvVmJQdkJ6em1FODhPeHNIMHFya3JEQzgifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6InNybS1zZWNyZXQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiY2x1c3Rlci1hZG1pbiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImU1MjUxZjhiLWY2ODItNDU0Ni1hOTgxLWNlNTk0YTg2NmZiNCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmNsdXN0ZXItYWRtaW4ifQ.rnZyHFEE1ywceWqcio0UKQrp5GdfVGQOCXxx3RJpb_vvDj65GvNwN0VgA_anOlzj8kKJ9JQjWrA7an2k-5w0ycjeu8Ei_5Z0dvgRSpvKc4O5kCHddOB1kJl480hKWtZqgL0Vi6YbOziFGqvPd8hxHSTquxUgXEN2BStqII8MpVEK8z8iU2pJE5CNIaukGBozjlgc1Vb6HiEU4_UhlqG61uO6ReRVrzaYa4T1j4Zvvx1JN8t2HYcuv50QlHPrEAfW2F3ed0SBbb_X8AT0pGJrVas_uqZgMcN1j5BLO51RNmCY27ADHwCbj8HWuiHhyuLKQxYw8yKB-iMNQmq2fk3ezw + - name: ARTIFACT_MANAGER_ADDRESS + value: http://artefact-manager-service:8000 + - name: EDGE_CLOUD_ADAPTER_NAME + value: kubernetes + - name: PLATFORM_PROVIDER + value: ISI + image: ghcr.io/sunriseopenoperatorplatform/srm/srm:1.0.0 + name: srmcontroller + ports: + - containerPort: 8080 + resources: {} + imagePullPolicy: Always + restartPolicy: Always +status: {} + +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + kompose.cmd: kompose convert + kompose.version: 1.26.0 (40646f47) + creationTimestamp: null + labels: + io.kompose.service: srm + name: srm +spec: + type: NodePort + ports: + - name: "8080" + nodePort: 32415 + port: 8080 + targetPort: 8080 + selector: + io.kompose.service: srmcontroller +status: + loadBalancer: {} + +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: srm-ingress + annotations: + #traefik.ingress.kubernetes.io/router.entrypoints: web +spec: + ingressClassName: nginx + rules: + - http: + paths: + - path: /srm + pathType: Prefix + backend: + service: + name: srm + port: + number: 8080 +--- +kind: PersistentVolume +apiVersion: v1 +metadata: + name: mongodb-pv-volume # Sets PV's name + labels: + type: local # Sets PV's type to local + app: mongopiedge +spec: + storageClassName: manual + capacity: + storage: 200Mi # Sets PV Volume + accessModes: + - ReadWriteOnce + hostPath: + path: "/mnt/data/mongodb_srm" +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + creationTimestamp: null + labels: + io.kompose.service: mongo-db + name: mongo-db +spec: + storageClassName: manual + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 200Mi +status: {} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + kompose.cmd: kompose convert + kompose.version: 1.26.0 (40646f47) + creationTimestamp: null + labels: + io.kompose.service: mongopiedge + name: mongopiedge +spec: + replicas: 1 + selector: + matchLabels: + io.kompose.service: mongopiedge + strategy: + type: Recreate + template: + metadata: + annotations: + kompose.cmd: kompose convert + kompose.version: 1.26.0 (40646f47) + creationTimestamp: null + labels: + #io.kompose.network/netEMPkub: "true" + io.kompose.service: mongopiedge + spec: + containers: + - image: mongo + name: mongopiedge + ports: + - containerPort: 27017 + resources: {} + volumeMounts: + - mountPath: /data/db + name: mongo-db + restartPolicy: Always + volumes: + - name: mongo-db + persistentVolumeClaim: + claimName: mongo-db +status: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + kompose.cmd: kompose convert + kompose.version: 1.26.0 (40646f47) + creationTimestamp: null + labels: + io.kompose.service: mongopiedge + name: mongopiedge +spec: + type: ClusterIP + ports: + - name: "27017" + port: 27017 + targetPort: 27017 + selector: + io.kompose.service: mongopiedge +status: + loadBalancer: {}