From 006a3f47c1973fe268c2d0f580bebb87194342ff Mon Sep 17 00:00:00 2001 From: Laskaratos Dimitris Date: Fri, 17 Oct 2025 12:52:20 +0300 Subject: [PATCH 1/2] Added deployment file --- deploy/oeg.yaml | 179 ++++++++++++++++++ docker-compose.yaml | 20 -- .../configs/env_config.py | 2 + .../controllers/app_controllers.py | 8 +- .../federation_manager_controller.py | 48 +++-- .../network_functions_controller.py | 14 +- .../services/edge_cloud_services.py | 94 +++++---- .../services/federation_services.py | 10 +- .../services/storage_service.py | 33 +++- .../specification/openapi.yaml | 73 ++++--- 10 files changed, 356 insertions(+), 125 deletions(-) create mode 100644 deploy/oeg.yaml delete mode 100644 docker-compose.yaml diff --git a/deploy/oeg.yaml b/deploy/oeg.yaml new file mode 100644 index 0000000..c34f664 --- /dev/null +++ b/deploy/oeg.yaml @@ -0,0 +1,179 @@ +--- +kind: PersistentVolume +apiVersion: v1 +metadata: + name: mongodb-oeg-pv-volume # Sets PV's name + labels: + type: local # Sets PV's type to local + app: oegmongo +spec: + storageClassName: manual + capacity: + storage: 50Mi # Sets PV Volume + accessModes: + - ReadWriteOnce + hostPath: + path: "/mnt/data/mongodb_oeg" +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + creationTimestamp: null + labels: + io.kompose.service: mongo-oeg + name: mongo-oeg +spec: + storageClassName: manual + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 50Mi +status: {} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + kompose.cmd: kompose convert + kompose.version: 1.26.0 (40646f47) + creationTimestamp: null + labels: + io.kompose.service: oegmongo + name: oegmongo +spec: + replicas: 1 + selector: + matchLabels: + io.kompose.service: oegmongo + 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: oegmongo + spec: + containers: + - image: mongo + name: oegmongo + ports: + - containerPort: 27017 + resources: {} + volumeMounts: + - mountPath: /data/db + name: mongo-db + restartPolicy: Always + volumes: + - name: mongo-db + persistentVolumeClaim: + claimName: mongo-oeg +status: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + kompose.cmd: kompose convert + kompose.version: 1.26.0 (40646f47) + creationTimestamp: null + labels: + io.kompose.service: oegmongo + name: oegmongo +spec: + type: ClusterIP + ports: + - name: "27017" + port: 27017 + targetPort: 27017 + selector: + io.kompose.service: oegmongo +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:27017 + - name: SRM_HOST + value: http://srm:8080/srm/1.0.0 + - name: FEDERATION_MANAGER_HOST + value: http://federation-manager.federation-manager.svc.cluster.local:8989/operatorplatform/federation/v1 + - name: PARTNER_API_ROOT + value: http://10.8.0.1:31002 + - name: TOKEN_ENDPOINT + value: http://federation-manager.federation-manager.svc.cluster.local:8080/realms/federation/protocol/openid-connect/token + image: ghcr.io/sunriseopenoperatorplatform/oeg/oeg:1.0.1 + name: oegcontroller + ports: + - containerPort: 8080 + resources: {} + imagePullPolicy: Always + restartPolicy: Always + +status: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: oeg + namespace: sunrise6g +spec: + type: ClusterIP + selector: + io.kompose.service: oegcontroller + ports: + - name: http + port: 80 + targetPort: 8080 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: oegcontroller-ingress + namespace: sunrise6g + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: web + # traefik.ingress.kubernetes.io/rewrite-target: /$2 +spec: + ingressClassName: traefik + rules: + - host: isiath.duckdns.org + http: + paths: + - path: /oeg + pathType: Prefix + backend: + service: + name: oeg + port: + number: 80 \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index d4b2c0c..0000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,20 +0,0 @@ -version: '3.1' - -services: - mongo: - image: mongo - restart: always - environment: - MONGO_INITDB_ROOT_USERNAME: root - MONGO_INITDB_ROOT_PASSWORD: example - - # cloud-management-api: - # image: cloud-management-api - # restart: always - # ports: - # - 8080:8080 - # environment: - # ME_CONFIG_MONGODB_ADMINUSERNAME: root - # ME_CONFIG_MONGODB_ADMINPASSWORD: example - # ME_CONFIG_MONGODB_URL: mongodb://root:example@mongo:27017/ - # ME_CONFIG_BASICAUTH: false diff --git a/edge_cloud_management_api/configs/env_config.py b/edge_cloud_management_api/configs/env_config.py index 87c5878..b8e709d 100644 --- a/edge_cloud_management_api/configs/env_config.py +++ b/edge_cloud_management_api/configs/env_config.py @@ -12,6 +12,8 @@ class Configuration(BaseSettings): PI_EDGE_PASSWORD: str = os.getenv("PI_EDGE_PASSWORD") HTTP_PROXY: str = os.getenv("HTTP_PROXY") FEDERATION_MANAGER_HOST=os.getenv("FEDERATION_MANAGER_HOST") + TOKEN_ENDPOINT = os.getenv('TOKEN_ENDPOINT') + PARTNER_API_ROOT = os.getenv('PARTNER_API_ROOT') config = Configuration() diff --git a/edge_cloud_management_api/controllers/app_controllers.py b/edge_cloud_management_api/controllers/app_controllers.py index b180ca0..cceaa4b 100644 --- a/edge_cloud_management_api/controllers/app_controllers.py +++ b/edge_cloud_management_api/controllers/app_controllers.py @@ -88,19 +88,19 @@ def delete_app(appId, x_correlator=None): def create_app_instance(): logger.info("Received request to create app instance") - try: + try: body = request.get_json() logger.debug(f"Request body: {body}") app_id = body.get("appId") - edge_zone_id = body.get("edgeCloudZoneId") + app_zones = body.get("appZones") # k8s_ref = body.get("kubernetesClusterRef") - if not app_id or not edge_zone_id : + if not app_id or not app_zones : return jsonify({"error": "Missing required fields: appId, edgeCloudZoneId, or kubernetesCLusterRef"}), 400 - local_zone = get_zone(edge_zone_id) + local_zone = get_zone(app_zones[0].get('EdgeCloudZone').get('edgeCloudZoneId')) if not local_zone: # TODO: apply federation logic return 'Zone belongs to federated partner OP. Switching to Federation Manager.' diff --git a/edge_cloud_management_api/controllers/federation_manager_controller.py b/edge_cloud_management_api/controllers/federation_manager_controller.py index ac5de2f..ac1bf59 100644 --- a/edge_cloud_management_api/controllers/federation_manager_controller.py +++ b/edge_cloud_management_api/controllers/federation_manager_controller.py @@ -4,9 +4,19 @@ import connexion from requests.exceptions import Timeout, ConnectionError from edge_cloud_management_api.managers.log_manager import logger import requests +from edge_cloud_management_api.configs.env_config import config +from edge_cloud_management_api.services.storage_service import insert_federation, get_fed, get_all_feds from edge_cloud_management_api.services.federation_services import FederationManagerClientFactory +token_headers = {'Authorization': 'Basic b3JpZ2luYXRpbmctb3AtMTpkZDd2TndGcWpOcFl3YWdobEV3TWJ3MTBnMGtsV0RIYg==', + 'Content-Type': 'application/x-www-form-urlencoded' + } +data = {'grant_type': 'client_credentials', + 'scope': 'fed-mgmt'} +TOKEN_ENDPOINT = config.TOKEN_ENDPOINT + + # Factory pattern factory = FederationManagerClientFactory() federation_client = factory.create_federation_client() @@ -16,29 +26,42 @@ def create_federation(): """POST /partner - Create federation with partner OP.""" body = request.get_json() - token = __get_token() + token = requests.post(TOKEN_ENDPOINT, headers=token_headers, data=data).json().get('access_token') response, code = federation_client.post_partner(body, token) + fed = {'_id': response.get('federationContextId'), 'token': token} + if code==200: + insert_federation(fed) return response, code def get_federation(federationContextId): """GET /{federationContextId}/partner - Get federation info.""" - - token = __get_token() - response, code = federation_client.get_partner(federationContextId, token) - return response, code + fed = get_fed(federationContextId) + if not fed: + return 'Federation not found', 404 + else: + token = fed.get('token') + response, code = federation_client.get_partner(federationContextId, token) + return response, code def delete_federation(federationContextId): """DELETE /{federationContextId}/partner - Delete federation.""" - - token = __get_token() - response, code = federation_client.delete_partner(federationContextId, token) - return response, code + fed = get_fed(federationContextId) + if not fed: + return 'Federation not found', 404 + else: + token = fed.get('token') + response, code = federation_client.delete_partner(federationContextId, token) + return response, code def get_federation_context_ids(): """GET /fed-context-id - Fetch federationContextId(s).""" - token = __get_token() - response, code = federation_client.get_federation_context_ids(token) - return response, code + feds = get_all_feds() + if not feds: + return 'Federation not found', 404 + else: + token = feds[len(feds)-1].get('token') + response, code = federation_client.get_federation_context_ids(token) + return response, code def onboard_application_to_partner(federationContextId): @@ -85,5 +108,6 @@ def remove_zone_sync(federationContextId, zoneId): def __get_token(): bearer = connexion.request.headers['Authorization'] token = bearer.split()[1] + # __token = requests.post(TOKEN_ENDPOINT, headers=token_headers, data=data).json().get('access_token') return token diff --git a/edge_cloud_management_api/controllers/network_functions_controller.py b/edge_cloud_management_api/controllers/network_functions_controller.py index 5750ff5..965c6b9 100644 --- a/edge_cloud_management_api/controllers/network_functions_controller.py +++ b/edge_cloud_management_api/controllers/network_functions_controller.py @@ -15,7 +15,7 @@ def create_qod_session(body: dict): pi_edge_factory = PiEdgeAPIClientFactory() api_client = pi_edge_factory.create_pi_edge_api_client() response = api_client.create_qod_session(body) - return response.json() + return response except ValidationError as e: return jsonify({"error": "Invalid input", "details": e.errors()}), 400 @@ -38,8 +38,8 @@ def delete_qod_session(sessionId: str): pi_edge_factory = PiEdgeAPIClientFactory() api_client = pi_edge_factory.create_pi_edge_api_client() response = api_client.delete_qod_session(sessionId=sessionId) - - return response.text + + return response except ValidationError as e: return jsonify({"error": "Invalid input", "details": e.errors()}), 400 @@ -68,7 +68,7 @@ def get_qod_session(sessionId: str): # jsonify({"appId": str(document_id)}), # 201, # ) - return response.json() + return response except ValidationError as e: return jsonify({"error": "Invalid input", "details": e.errors()}), 400 @@ -84,7 +84,7 @@ def create_traffic_influence_resource(body: dict): pi_edge_factory = PiEdgeAPIClientFactory() api_client = pi_edge_factory.create_pi_edge_api_client() response = api_client.create_traffic_influence_resource(body) - return response.json() + return response except ValidationError as e: return jsonify({"error": "Invalid input", "details": e.errors()}), 400 @@ -99,7 +99,7 @@ def get_traffic_influence_resource(id: str): pi_edge_factory = PiEdgeAPIClientFactory() api_client = pi_edge_factory.create_pi_edge_api_client() response = api_client.get_traffic_influence_resource(id) - return response.json() + return response except ValidationError as e: return jsonify({"error": "Invalid input", "details": e.errors()}), 400 @@ -115,7 +115,7 @@ def delete_traffic_influence_resource(id: str): pi_edge_factory = PiEdgeAPIClientFactory() api_client = pi_edge_factory.create_pi_edge_api_client() response = api_client.delete_traffic_influence_resource(id) - return response.text + return response except ValidationError as e: return jsonify({"error": "Invalid input", "details": e.errors()}), 400 diff --git a/edge_cloud_management_api/services/edge_cloud_services.py b/edge_cloud_management_api/services/edge_cloud_services.py index 4463b6e..eae8660 100644 --- a/edge_cloud_management_api/services/edge_cloud_services.py +++ b/edge_cloud_management_api/services/edge_cloud_services.py @@ -262,81 +262,77 @@ class PiEdgeAPIClient: url = f"{self.base_url}/sessions" request_headers = self._get_headers() - try: - response = requests.post(url, json=body, headers=request_headers,verify=False) - response.raise_for_status() - return response - except Exception as e: - logger.info(e.args) - return e.args + response = requests.post(url, json=body, headers=request_headers,verify=False) + response.raise_for_status() + if response.status_code==200: + return response.json() + elif response.status_code==500: + return response.content + + def get_qod_session(self, sessionId: str): url = f"{self.base_url}/sessions/"+sessionId request_headers = self._get_headers() - try: - response = requests.get(url, headers=request_headers,verify=False) - response.raise_for_status() - return response - except Exception as e: - logger.info(e.args) - return e.args + response = requests.get(url, headers=request_headers,verify=False) + response.raise_for_status() + if response.status_code==200: + return response.json() + elif response.status_code==500: + return response.content + def delete_qod_session(self, sessionId: str): url = f"{self.base_url}/sessions/"+sessionId request_headers = self._get_headers() - try: - response = requests.delete(url, headers=request_headers,verify=False) - response.raise_for_status() - return response - except Exception as e: - logger.info(e.args) - return e.args + response = requests.delete(url, headers=request_headers,verify=False) + response.raise_for_status() + if response.status_code==200: + return response.text + elif response.status_code==500: + return response.content def create_traffic_influence_resource(self, body_dict): url = f"{self.base_url}/traffic-influences" request_headers = self._get_headers() - try: - response = requests.post(url, json=body_dict, headers=request_headers,verify=False) - response.raise_for_status() - return response - except Exception as e: - logger.info(e.args) - return e.args + response = requests.post(url, json=body_dict, headers=request_headers,verify=False) + response.raise_for_status() + if response.status_code==200: + return response.json() + elif response.status_code==500: + return response.content def delete_traffic_influence_resource(self, id: str): url = f"{self.base_url}/traffic-influences/"+id request_headers = self._get_headers() - try: - response = requests.delete(url, headers=request_headers,verify=False) - response.raise_for_status() - return response - except Exception as e: - logger.info(e.args) - return e.args + response = requests.delete(url, headers=request_headers,verify=False) + response.raise_for_status() + if response.status_code==200: + return response.json() + elif response.status_code==500: + return response.content def get_traffic_influence_resource(self, id: str): url = f"{self.base_url}/traffic-influences/"+id request_headers = self._get_headers() - try: - response = requests.get(url, headers=request_headers,verify=False) - response.raise_for_status() - return response - except Exception as e: - logger.info(e.args) - return e.args + response = requests.get(url, headers=request_headers,verify=False) + response.raise_for_status() + if response.status_code==200: + return response.json() + elif response.status_code==500: + return response.content def get_all_traffic_influence_resources(self): url = f"{self.base_url}/traffic-influences/" request_headers = self._get_headers() - try: - response = requests.get(url, headers=request_headers,verify=False) - response.raise_for_status() - return response - except Exception as e: - logger.info(e.args) - return e.args + response = requests.get(url, headers=request_headers,verify=False) + response.raise_for_status() + if response.status_code==200: + return response.json() + elif response.status_code==500: + return response.content class PiEdgeAPIClientFactory: """ diff --git a/edge_cloud_management_api/services/federation_services.py b/edge_cloud_management_api/services/federation_services.py index c3cde66..5b56c85 100644 --- a/edge_cloud_management_api/services/federation_services.py +++ b/edge_cloud_management_api/services/federation_services.py @@ -4,16 +4,20 @@ from requests.exceptions import Timeout, ConnectionError from edge_cloud_management_api.configs.env_config import config from edge_cloud_management_api.managers.log_manager import logger from edge_cloud_management_api.services.edge_cloud_services import PiEdgeAPIClientFactory +from edge_cloud_management_api.services.storage_service import delete_fed class FederationManagerClient: def __init__(self, base_url=None): self.base_url = base_url or config.FEDERATION_MANAGER_HOST + self.partner_root = config.PARTNER_API_ROOT def _get_headers(self, token): headers = {} if token is not None: headers['Authorization'] = 'Bearer '+token + headers['X-Partner-API-Root'] = self.partner_root + headers['X-Internal']='true' headers['Content-Type'] = 'application/json' headers['Accept'] = 'application/json' return headers @@ -22,9 +26,12 @@ class FederationManagerClient: def post_partner(self, data: dict, token: str): url = f"{self.base_url}/partner" + headers=self._get_headers(token) + try: - response = requests.post(url, json=data, headers=self._get_headers(token), timeout=10) + response = requests.post(url, json=data, headers=headers, timeout=20) response.raise_for_status() + print(response.json()) return response.json(), 200 except Timeout: logger.error("POST /partner timed out") @@ -63,6 +70,7 @@ class FederationManagerClient: try: response = requests.delete(url, headers=self._get_headers(token), timeout=10) if response.content: + delete_fed(federation_context_id) return response.json(), 200 return {"status": response.status_code} except Timeout: diff --git a/edge_cloud_management_api/services/storage_service.py b/edge_cloud_management_api/services/storage_service.py index 38d80b5..d7782f2 100644 --- a/edge_cloud_management_api/services/storage_service.py +++ b/edge_cloud_management_api/services/storage_service.py @@ -17,4 +17,35 @@ def get_zone(zone_id: str): mydbmongo = myclient[mydb_mongo] col = mydbmongo[collection] zone = col.find_one({'_id': zone_id}) - return zone \ No newline at end of file + return zone + +def insert_federation(fed: dict): + collection = 'federations' + myclient = pymongo.MongoClient(storage_url) + mydbmongo = myclient[mydb_mongo] + col = mydbmongo[collection] + col.insert_one(fed) + +def get_fed(fed_context_id: str): + collection = 'federations' + myclient = pymongo.MongoClient(storage_url) + mydbmongo = myclient[mydb_mongo] + col = mydbmongo[collection] + fed = col.find_one({'_id': fed_context_id}) + return fed + +def get_all_feds(): + collection = 'federations' + myclient = pymongo.MongoClient(storage_url) + mydbmongo = myclient[mydb_mongo] + col = mydbmongo[collection] + feds = col.find() + return feds.to_list() + +def delete_fed(fed_context_id: str): + collection = 'federations' + myclient = pymongo.MongoClient(storage_url) + mydbmongo = myclient[mydb_mongo] + col = mydbmongo[collection] + col.delete_one({'_id': fed_context_id}) + diff --git a/edge_cloud_management_api/specification/openapi.yaml b/edge_cloud_management_api/specification/openapi.yaml index 19c177b..ddcbcac 100644 --- a/edge_cloud_management_api/specification/openapi.yaml +++ b/edge_cloud_management_api/specification/openapi.yaml @@ -439,18 +439,21 @@ paths: schema: type: object required: - - name + # - name - appId - - edgeCloudZoneId + - appZones + # - edgeCloudZoneId properties: - name: - $ref: '#/components/schemas/AppInstanceName' + # name: + # $ref: '#/components/schemas/AppInstanceName' appId: $ref: "#/components/schemas/AppId" - edgeCloudZoneId: - $ref: "#/components/schemas/EdgeCloudZoneId" - kubernetesClusterRef: - $ref: "#/components/schemas/KubernetesClusterRef" + # edgeCloudZoneId: + # $ref: "#/components/schemas/EdgeCloudZoneId" + # kubernetesClusterRef: + # $ref: "#/components/schemas/KubernetesClusterRef" + appZones: + $ref: "#/components/schemas/EdgeCloudZones" required: true responses: "202": @@ -815,9 +818,11 @@ paths: tags: - FederationManagement summary: Creates one direction federation with partner operator platform. - security: - - oAuth2ClientCredentials: - - fed-mgmt + # security: + # - oAuth2ClientCredentials: + # - fed-mgmt + # security: + # - jwt: [ ] operationId: edge_cloud_management_api.controllers.federation_manager_controller.create_federation requestBody: @@ -1046,9 +1051,8 @@ paths: \ The response shall provide info about the zones offered by the partner,\ \ partner OP network codes, information about edge discovery and LCM service\ \ etc." - security: - - oAuth2ClientCredentials: - - fed-mgmt + # security: + # - jwt: [ ] operationId: edge_cloud_management_api.controllers.federation_manager_controller.get_federation parameters: - name: federationContextId @@ -1087,9 +1091,8 @@ paths: tags: - FederationManagement summary: Remove existing federation with the partner OP - security: - - oAuth2ClientCredentials: - - fed-mgmt + # security: + # - jwt: [ ] operationId: edge_cloud_management_api.controllers.federation_manager_controller.delete_federation parameters: - name: federationContextId @@ -1125,9 +1128,8 @@ paths: tags: - FederationManagement summary: Retrieves the existing federationContextId with partner operator platform. - security: - - oAuth2ClientCredentials: - - fed-mgmt + # security: + # - jwt: [ ] operationId: edge_cloud_management_api.controllers.federation_manager_controller.get_federation_context_ids responses: "200": @@ -1175,16 +1177,17 @@ components: # scheme: bearer # bearerFormat: JWT # x-bearerInfoFunc: edge_cloud_management_api.controllers.security_controller.decode_token - securitySchemes: - oAuth2ClientCredentials: - type: oauth2 - flows: - clientCredentials: - tokenUrl: http://isiath.duckdns.org:8081//realms/federation/protocol/openid-connect/token - scopes: - fed-mgmt: Access to the federation APIs - x-tokenInfoFunc: edge_cloud_management_api.controllers.security_controller.check_oAuth2ClientCredentials - x-scopeValidateFunc: edge_cloud_management_api.controllers.security_controller.validate_scope_oAuth2ClientCredentials + # x-bearerInfoFunc: edge_cloud_management_api.controllers.security_controller.decode_token + # securitySchemes: + # oAuth2ClientCredentials: + # type: oauth2 + # flows: + # clientCredentials: + # tokenUrl: http://federation-manager.federation-manager.svc.cluster.local:8080/realms/federation/protocol/openid-connect/token + # scopes: + # fed-mgmt: Access to the federation APIs + # x-tokenInfoFunc: edge_cloud_management_api.controllers.security_controller.check_oAuth2ClientCredentials + # x-scopeValidateFunc: edge_cloud_management_api.controllers.security_controller.validate_scope_oAuth2ClientCredentials parameters: x-correlator: @@ -1780,13 +1783,21 @@ components: EdgeCloudZones: type: array items: - $ref: "#/components/schemas/EdgeCloudZone" + $ref: "#/components/schemas/ZoneObject" minItems: 1 description: | A collection of Edge Cloud Zones where the Application Provider can instantiate an Application Instance. additionalProperties: false + ZoneObject: + type: object + required: + - EdgeCloudZone + properties: + EdgeCloudZone: + $ref: "#/components/schemas/EdgeCloudZone" + EdgeCloudZoneId: type: string format: uuid -- GitLab From ba67f754363ded90a87b8adccb51d22e54b847be Mon Sep 17 00:00:00 2001 From: Laskaratos Dimitris Date: Thu, 23 Oct 2025 16:45:33 +0300 Subject: [PATCH 2/2] Added partner app deployment code --- .../controllers/app_controllers.py | 79 ++++++++++++++++--- .../controllers/edge_cloud_controller.py | 5 ++ .../federation_manager_controller.py | 15 ++++ .../services/federation_services.py | 23 +++++- .../services/storage_service.py | 7 ++ 5 files changed, 116 insertions(+), 13 deletions(-) diff --git a/edge_cloud_management_api/controllers/app_controllers.py b/edge_cloud_management_api/controllers/app_controllers.py index cceaa4b..41d5a8a 100644 --- a/edge_cloud_management_api/controllers/app_controllers.py +++ b/edge_cloud_management_api/controllers/app_controllers.py @@ -2,9 +2,12 @@ from flask import jsonify, request from pydantic import ValidationError from edge_cloud_management_api.managers.log_manager import logger from edge_cloud_management_api.services.edge_cloud_services import PiEdgeAPIClientFactory +from edge_cloud_management_api.services.federation_services import FederationManagerClientFactory from edge_cloud_management_api.services.storage_service import get_zone +from edge_cloud_management_api.services.storage_service import get_fed - +factory = FederationManagerClientFactory() +federation_client = factory.create_federation_client() class NotFound404Exception(Exception): pass @@ -87,29 +90,83 @@ def delete_app(appId, x_correlator=None): def create_app_instance(): logger.info("Received request to create app instance") - try: - body = request.get_json() logger.debug(f"Request body: {body}") app_id = body.get("appId") app_zones = body.get("appZones") - # k8s_ref = body.get("kubernetesClusterRef") + pi_edge_client_factory = PiEdgeAPIClientFactory() + pi_edge_client = pi_edge_client_factory.create_pi_edge_api_client() if not app_id or not app_zones : return jsonify({"error": "Missing required fields: appId, edgeCloudZoneId, or kubernetesCLusterRef"}), 400 - local_zone = get_zone(app_zones[0].get('EdgeCloudZone').get('edgeCloudZoneId')) - if not local_zone: - # TODO: apply federation logic - return 'Zone belongs to federated partner OP. Switching to Federation Manager.' + zone = get_zone(app_zones[0].get('EdgeCloudZone').get('edgeCloudZoneId')) + if zone.get('isLocal')=='false': + # Step 1: retrieve app metadata + appData = pi_edge_client.get_app(appId=app_id).get('appManifest') + #Step 2: compose GSMA artefact payload + artefact = {} + artefact['artefactId'] = app_id + artefact['appProviderId'] = appData.get('appProvider') + artefact['artefactName'] = appData.get('name') + artefact['artefactVersionInfo'] = appData.get('version') + artefact['artefactDescription'] = '' + repoInfo = appData.get('appRepo') + artefact['repoType'] = repoInfo.get('type') + artefact['artefactRepoLocation'] = {'repoURL': repoInfo.get('imagePath'), 'userName': repoInfo.get('userName'), 'password': repoInfo.get('credentials'), 'token': ''} + exposedInterfaces = [] + networkInterfaces = appData.get('componentSpec')[0].get('networkInterfaces') + for ni in networkInterfaces: + interface = {'interfaceId': '', 'commProtocol': ni.get('protocol'), 'commPort': ni.get('port'), 'visibilityType': ni.get('visibilityType'), 'network': '', 'InterfaceName': ''} + exposedInterfaces.append(interface) + artefact['componentSpec'] = [ + { + 'componentName': appData.get('name'), + 'numOfInstances': 0, + 'restartPolicy': 'RESTART_POLICY_ALWAYS', + 'exposedInterfaces': exposedInterfaces, + 'compEnvParams': [], + 'persistentVolumes': [] + } + ] + # Step 3: Send artefact to local fed manager + fed_token = get_fed(zone.get('fedContextId')).get('token') + create_artefact_response = federation_client.create_artefact(artefact=artefact, federation_context_id=zone.get('fedContextId'), token=fed_token) + # Step 4: Onboard app + if create_artefact_response.status_code == 200 or create_artefact_response.status_code ==409: + # Step 5: Create GSM onboard app payload + onboard_app = {} + onboard_app['appId'] = app_id + onboard_app['appProviderId'] = appData.get('appProvider') + onboard_app['appDeploymentZones'] = [] + appMetaData = {} + appMetaData['appName'] = appData.get('name') + appMetaData['version'] = appData.get('version') + onboard_app['appMetaData'] = appMetaData + onboard_app['appComponentSpecs'] = [{'serviceNameNB': appData.get('name'), + 'serviceNameEW': appData.get('name'), + 'componentName': appData.get('name'), + 'artefactId': app_id + } + ] + # Step 6: Onboard app at partner + onboard_app_response = federation_client.onboard_application(federation_context_id=zone.get('fedContextId'), body=onboard_app, token=fed_token) + if onboard_app_response.status_code==200: + # Step 7: Construct GSMA deployment payload + deploy_app = {} + deploy_app['appId'] = app_id + deploy_app['appVersion'] = appData.get('version') + deploy_app['appProviderId'] = appData.get('appProvider') + deploy_app['zoneInfo'] = {'zoneId': zone.get('edgeCloudZoneId')} + # Step 8: Deploy app at partner + deploy_app_response = federation_client.deploy_app_partner(federation_context_id=zone.get('fedContextId'), body=deploy_app, token = fed_token) + return deploy_app_response + logger.info(f"Preparing to send deployment request to SRM for appId={app_id}") - pi_edge_client_factory = PiEdgeAPIClientFactory() - pi_edge_client = pi_edge_client_factory.create_pi_edge_api_client() - print("\n === Preparing Deployment Request ===") print(f" Endpoint: {pi_edge_client.base_url}/deployedServiceFunction") print(f" Headers: {pi_edge_client._get_headers()}") diff --git a/edge_cloud_management_api/controllers/edge_cloud_controller.py b/edge_cloud_management_api/controllers/edge_cloud_controller.py index 2c070ed..96fdc6c 100644 --- a/edge_cloud_management_api/controllers/edge_cloud_controller.py +++ b/edge_cloud_management_api/controllers/edge_cloud_controller.py @@ -5,6 +5,7 @@ from edge_cloud_management_api.configs.env_config import config from edge_cloud_management_api.managers.log_manager import logger from edge_cloud_management_api.services.edge_cloud_services import PiEdgeAPIClientFactory from edge_cloud_management_api.services.storage_service import insert_zones +from edge_cloud_management_api.services.federation_services import FederationManagerClientFactory try: @@ -13,10 +14,14 @@ try: zones = api_client.edge_cloud_zones() for zone in zones: zone['_id'] = zone.get('edgeCloudZoneId') + zone['isLocal'] = 'true' insert_zones(zones) except Exception as e: logger.error(e.args) +factory = FederationManagerClientFactory() +federation_client = factory.create_federation_client() + class EdgeCloudZone(BaseModel): edgeCloudZoneId: str = Field(..., description="Unique identifier of the Edge Cloud Zone") edgeCloudZoneName: str = Field(..., description="Name of the Edge Cloud Zone") diff --git a/edge_cloud_management_api/controllers/federation_manager_controller.py b/edge_cloud_management_api/controllers/federation_manager_controller.py index ac1bf59..8dc579f 100644 --- a/edge_cloud_management_api/controllers/federation_manager_controller.py +++ b/edge_cloud_management_api/controllers/federation_manager_controller.py @@ -5,6 +5,7 @@ from requests.exceptions import Timeout, ConnectionError from edge_cloud_management_api.managers.log_manager import logger import requests from edge_cloud_management_api.configs.env_config import config +from edge_cloud_management_api.services.storage_service import insert_zones from edge_cloud_management_api.services.storage_service import insert_federation, get_fed, get_all_feds from edge_cloud_management_api.services.federation_services import FederationManagerClientFactory @@ -30,6 +31,20 @@ def create_federation(): response, code = federation_client.post_partner(body, token) fed = {'_id': response.get('federationContextId'), 'token': token} if code==200: + provider = response.get('partnerOPFederationId') + av_zones = response.get('offeredAvailabilityZones') + zones_to_insert = [] + for zone in av_zones: + inserted_item = {'_id': zone.get('zoneId'), + 'edgeCloudProvider': provider, + 'edgeCloudZoneId': zone.get('zoneId'), + 'edgeCloudZoneName': zone.get('geographyDetails'), + 'edgeCloudZoneStatus': 'unknown', + 'isLocal': 'false', + 'fedContextId': response.get('federationContextId') + } + zones_to_insert.append(inserted_item) + insert_zones(zones_to_insert) insert_federation(fed) return response, code diff --git a/edge_cloud_management_api/services/federation_services.py b/edge_cloud_management_api/services/federation_services.py index 5b56c85..3c136e3 100644 --- a/edge_cloud_management_api/services/federation_services.py +++ b/edge_cloud_management_api/services/federation_services.py @@ -4,7 +4,7 @@ from requests.exceptions import Timeout, ConnectionError from edge_cloud_management_api.configs.env_config import config from edge_cloud_management_api.managers.log_manager import logger from edge_cloud_management_api.services.edge_cloud_services import PiEdgeAPIClientFactory -from edge_cloud_management_api.services.storage_service import delete_fed +from edge_cloud_management_api.services.storage_service import delete_fed, delete_partner_zones class FederationManagerClient: def __init__(self, base_url=None): @@ -71,6 +71,7 @@ class FederationManagerClient: response = requests.delete(url, headers=self._get_headers(token), timeout=10) if response.content: delete_fed(federation_context_id) + delete_partner_zones() return response.json(), 200 return {"status": response.status_code} except Timeout: @@ -110,7 +111,7 @@ class FederationManagerClient: def onboard_application(self, federation_context_id: str, body: dict, token: str): url = f"{self.base_url}/{federation_context_id}/application/onboarding" try: - response = requests.post(url, headers=self._get_headers(), json=body, timeout=10) + response = requests.post(url, headers=self._get_headers(token), json=body, timeout=10) response.raise_for_status() return response.json() except Timeout: @@ -167,6 +168,14 @@ class FederationManagerClient: '''---PARTNER APP DEPLOYMENT---''' + def deploy_app_partner(self, federation_context_id: str, body: dict, token: str): + url = f"{self.base_url}/{federation_context_id}/application/lcm" + try: + response = requests.post(url, headers=self._get_headers(token), json=body, timeout=10) + return response + except Exception as e: + logger.error(f"DELETE onboarding app unexpected error: {e}") + return {"error": str(e), "status_code": 500} '''---AVAILABILITY ZONE INFO SYNCHRONIZATION---''' @@ -227,6 +236,16 @@ class FederationManagerClient: logger.error(f"Remove Zone sync unexpected error: {e}") return {"error": str(e), "status_code": 500} + '''---ARTEFACT API---''' + + def create_artefact(self, artefact: dict, federation_context_id, token: str): + url = f"{self.base_url}/{federation_context_id}/artefact" + try: + response = requests.post(url, headers=self._get_headers(token), json=artefact, timeout=10) + return response + except Exception as e: + logger.error(f"Create artefact unexpected error: {e}") + return {"error": str(e), "status_code": 500} class FederationManagerClientFactory: def __init__(self): diff --git a/edge_cloud_management_api/services/storage_service.py b/edge_cloud_management_api/services/storage_service.py index d7782f2..b7721ae 100644 --- a/edge_cloud_management_api/services/storage_service.py +++ b/edge_cloud_management_api/services/storage_service.py @@ -19,6 +19,13 @@ def get_zone(zone_id: str): zone = col.find_one({'_id': zone_id}) return zone +def delete_partner_zones(): + collection = "zones" + myclient = pymongo.MongoClient(storage_url) + mydbmongo = myclient[mydb_mongo] + col = mydbmongo[collection] + col.delete_many({'isLocal': 'false'}) + def insert_federation(fed: dict): collection = 'federations' myclient = pymongo.MongoClient(storage_url) -- GitLab