Loading edge_cloud_management_api/controllers/app_controllers.py +187 −39 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ from edge_cloud_management_api.services.edge_cloud_services import PiEdgeAPIClie 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 import json factory = FederationManagerClientFactory() federation_client = factory.create_federation_client() Loading Loading @@ -91,62 +92,209 @@ 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") if not app_id or not app_zones: return jsonify({ "error": "Missing required fields: appId, appZones" }), 400 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 zone = get_zone( app_zones[0] .get("EdgeCloudZone", {}) .get("edgeCloudZoneId") ) logger.info(f"Attempting to get zone for: {app_zones[0].get('EdgeCloudZone').get('edgeCloudZoneId')}") zone = get_zone(app_zones[0].get('EdgeCloudZone').get('edgeCloudZoneId')) logger.info(f"Retrieved zone: {zone}") # ============================================================ # PARTNER DEPLOYMENT (Federation path) # ============================================================ if zone.get("isLocal") == "false": if zone is None: logger.error("get_zone returned None!") return jsonify({"error": "Zone not found"}), 404 # ============================================================ # Step 1: Retrieve application metadata from SRM # ============================================================ app_response = pi_edge_client.get_app(appId=app_id) appData = app_response.get("appManifest") is_local = zone.get('isLocal') logger.info(f"Zone isLocal: {is_local}") if not appData: return jsonify({ "error": "Application manifest not found", "appId": app_id }), 404 if is_local == 'false': # Step 1: retrieve app metadata appData = pi_edge_client.get_app(appId=app_id).get('appManifest') # ... rest of federation logic ... return deploy_app_response # ============================================================ # Step 2: Compose GSMA artefact payload # artefactId == appId (INTENTIONAL) # ============================================================ artefact_id = app_id artefact = { "artefactId": artefact_id, "artefactName": artefact_id, "appProviderId": appData.get("appProvider"), "artefactVersionInfo": "22.3.9", "artefactDescription": appData.get("description", ""), "artefactVirtType": "CONTAINER_TYPE", "artefactDescriptorType": "HELM", "artefactDescriptorTypeR": "HELM", "artefactFileName": f"{appData.get('name')}.tar", "artefactFileFormat": "TAR", "repoType": "PUBLICREPO", "artefactRepoLocation": { "repoURL": "https://charts.bitnami.com/bitnami", "userName": "", "password": "", "token": "" }, "componentSpec": [ { "componentName": "nginx", "images": ["bitnami/nginx:1.29.4"], "numOfInstances": 1, "restartPolicy": "RESTART_POLICY_ALWAYS", "computeResourceProfile": { "cpuArchType": "ISA_X86_64", "numCPU": "1", "memory": 1024, "diskStorage": 10240 } } ] } fed_token = get_fed( zone.get("fedContextId") ).get("token") print("\n========== OEG → FM ARTEFACT PAYLOAD ==========") print(json.dumps(artefact, indent=2)) print("================================================\n") artefact_body, artefact_status = federation_client.create_artefact( artefact=artefact, federation_context_id=zone.get("fedContextId"), token=fed_token ) logger.info(f"Preparing to send deployment request to SRM for appId={app_id}") # Idempotency: duplicate artefact = success if artefact_status == 422 and "duplicate key" in str(artefact_body): logger.info("Artefact already exists in FM, continuing") artefact_status = 200 response = pi_edge_client.deploy_service_function(data=body) logger.info(f"Type of response from SRM: {type(response)}") logger.info(f"Response from SRM: {response}") if artefact_status not in (200, 409): return jsonify({ "error": "Artefact creation failed", "fm_response": artefact_body }), artefact_status # ============================================================ # Step 4: Onboard application at partner OP # ============================================================ onboard_app = { "appId": app_id, "appProviderId": appData.get("appProvider"), "appDeploymentZones": [ { "edgeCloudZoneId": zone.get("edgeCloudZoneId"), "edgeCloudProvider": zone.get("edgeCloudProvider") } ], "appMetaData": { "appName": appData.get("name"), "version": appData.get("version") or "1.0.0", "appDescription": appData.get( "description", "Federated application" ), "mobilitySupport": False, "accessToken": "dummy-access-token", "category": "IOT" }, "appQoSProfile": { "latencyConstraints": "NONE", "bandwidthRequired": 1, "multiUserClients": "APP_TYPE_SINGLE_USER", "noOfUsersPerAppInst": 1, "appProvisioning": True }, "appComponentSpecs": [ { "serviceNameNB": appData.get("name"), "serviceNameEW": appData.get("name"), "componentName": appData.get("name"), "artefactId": artefact_id } ], "appStatusCallbackLink": "http://oeg/api/status" } federation_client.onboard_application( federation_context_id=zone.get("fedContextId"), body=onboard_app, token=fed_token ) if response is None: logger.error("SRM returned None!") return jsonify({"error": "SRM returned no response"}), 500 # Step 5 intentionally skipped return jsonify({ "message": "Application onboarded successfully (partner OP)", "appId": app_id }), 202 # ============================================================ # LOCAL DEPLOYMENT (SRM path) # ============================================================ logger.info(f"Proceeding with LOCAL deployment for appId={app_id}") try: logger.debug("Sending deployment request to SRM") response = pi_edge_client.deploy_service_function(data=body) if isinstance(response, dict) and "error" in response: logger.warning(f"Failed to deploy service function: {response}") logger.warning( "SRM returned an error, deployment not completed" ) return jsonify({ "warning": "Deployment not completed (SRM service unreachable)", "warning": "Deployment request accepted but not completed", "details": response }), 202 logger.info(f"Deployment response from SRM: {response}") return response logger.info("Local deployment request successfully sent to SRM") return jsonify({ "message": "Application deployed locally", "appId": app_id, "response": response }), 202 except ValidationError as e: logger.error(f"Validation error: {str(e)}") return jsonify({"error": "Validation error", "details": str(e)}), 400 except Exception as e: logger.error(f"SRM deployment failed: {str(e)}") return jsonify({ "warning": "SRM backend unavailable", "details": str(e) }), 202 except Exception as e: logger.error(f"Unexpected error in create_app_instance:{str(e)}") return jsonify({"error": "An unexpected error occurred", "details": str(e)}), 500 logger.exception("Unexpected error in create_app_instance") return jsonify({ "error": "Unexpected error", "details": str(e) }), 500 def get_app_instance(app_id=None, x_correlator=None, app_instance_id=None, region=None): Loading edge_cloud_management_api/services/federation_services.py +16 −4 Original line number Diff line number Diff line Loading @@ -238,14 +238,26 @@ class FederationManagerClient: '''---ARTEFACT API---''' def create_artefact(self, artefact: dict, federation_context_id, token: str): def create_artefact(self, artefact: dict, federation_context_id: str, 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 response = requests.post( url, headers=self._get_headers(token), json=artefact, timeout=120 ) try: body = response.json() except ValueError: body = response.text return body, response.status_code except Exception as e: logger.error(f"Create artefact unexpected error: {e}") return {"error": str(e), "status_code": 500} return {"error": str(e)}, 500 class FederationManagerClientFactory: def __init__(self): Loading Loading
edge_cloud_management_api/controllers/app_controllers.py +187 −39 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ from edge_cloud_management_api.services.edge_cloud_services import PiEdgeAPIClie 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 import json factory = FederationManagerClientFactory() federation_client = factory.create_federation_client() Loading Loading @@ -91,62 +92,209 @@ 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") if not app_id or not app_zones: return jsonify({ "error": "Missing required fields: appId, appZones" }), 400 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 zone = get_zone( app_zones[0] .get("EdgeCloudZone", {}) .get("edgeCloudZoneId") ) logger.info(f"Attempting to get zone for: {app_zones[0].get('EdgeCloudZone').get('edgeCloudZoneId')}") zone = get_zone(app_zones[0].get('EdgeCloudZone').get('edgeCloudZoneId')) logger.info(f"Retrieved zone: {zone}") # ============================================================ # PARTNER DEPLOYMENT (Federation path) # ============================================================ if zone.get("isLocal") == "false": if zone is None: logger.error("get_zone returned None!") return jsonify({"error": "Zone not found"}), 404 # ============================================================ # Step 1: Retrieve application metadata from SRM # ============================================================ app_response = pi_edge_client.get_app(appId=app_id) appData = app_response.get("appManifest") is_local = zone.get('isLocal') logger.info(f"Zone isLocal: {is_local}") if not appData: return jsonify({ "error": "Application manifest not found", "appId": app_id }), 404 if is_local == 'false': # Step 1: retrieve app metadata appData = pi_edge_client.get_app(appId=app_id).get('appManifest') # ... rest of federation logic ... return deploy_app_response # ============================================================ # Step 2: Compose GSMA artefact payload # artefactId == appId (INTENTIONAL) # ============================================================ artefact_id = app_id artefact = { "artefactId": artefact_id, "artefactName": artefact_id, "appProviderId": appData.get("appProvider"), "artefactVersionInfo": "22.3.9", "artefactDescription": appData.get("description", ""), "artefactVirtType": "CONTAINER_TYPE", "artefactDescriptorType": "HELM", "artefactDescriptorTypeR": "HELM", "artefactFileName": f"{appData.get('name')}.tar", "artefactFileFormat": "TAR", "repoType": "PUBLICREPO", "artefactRepoLocation": { "repoURL": "https://charts.bitnami.com/bitnami", "userName": "", "password": "", "token": "" }, "componentSpec": [ { "componentName": "nginx", "images": ["bitnami/nginx:1.29.4"], "numOfInstances": 1, "restartPolicy": "RESTART_POLICY_ALWAYS", "computeResourceProfile": { "cpuArchType": "ISA_X86_64", "numCPU": "1", "memory": 1024, "diskStorage": 10240 } } ] } fed_token = get_fed( zone.get("fedContextId") ).get("token") print("\n========== OEG → FM ARTEFACT PAYLOAD ==========") print(json.dumps(artefact, indent=2)) print("================================================\n") artefact_body, artefact_status = federation_client.create_artefact( artefact=artefact, federation_context_id=zone.get("fedContextId"), token=fed_token ) logger.info(f"Preparing to send deployment request to SRM for appId={app_id}") # Idempotency: duplicate artefact = success if artefact_status == 422 and "duplicate key" in str(artefact_body): logger.info("Artefact already exists in FM, continuing") artefact_status = 200 response = pi_edge_client.deploy_service_function(data=body) logger.info(f"Type of response from SRM: {type(response)}") logger.info(f"Response from SRM: {response}") if artefact_status not in (200, 409): return jsonify({ "error": "Artefact creation failed", "fm_response": artefact_body }), artefact_status # ============================================================ # Step 4: Onboard application at partner OP # ============================================================ onboard_app = { "appId": app_id, "appProviderId": appData.get("appProvider"), "appDeploymentZones": [ { "edgeCloudZoneId": zone.get("edgeCloudZoneId"), "edgeCloudProvider": zone.get("edgeCloudProvider") } ], "appMetaData": { "appName": appData.get("name"), "version": appData.get("version") or "1.0.0", "appDescription": appData.get( "description", "Federated application" ), "mobilitySupport": False, "accessToken": "dummy-access-token", "category": "IOT" }, "appQoSProfile": { "latencyConstraints": "NONE", "bandwidthRequired": 1, "multiUserClients": "APP_TYPE_SINGLE_USER", "noOfUsersPerAppInst": 1, "appProvisioning": True }, "appComponentSpecs": [ { "serviceNameNB": appData.get("name"), "serviceNameEW": appData.get("name"), "componentName": appData.get("name"), "artefactId": artefact_id } ], "appStatusCallbackLink": "http://oeg/api/status" } federation_client.onboard_application( federation_context_id=zone.get("fedContextId"), body=onboard_app, token=fed_token ) if response is None: logger.error("SRM returned None!") return jsonify({"error": "SRM returned no response"}), 500 # Step 5 intentionally skipped return jsonify({ "message": "Application onboarded successfully (partner OP)", "appId": app_id }), 202 # ============================================================ # LOCAL DEPLOYMENT (SRM path) # ============================================================ logger.info(f"Proceeding with LOCAL deployment for appId={app_id}") try: logger.debug("Sending deployment request to SRM") response = pi_edge_client.deploy_service_function(data=body) if isinstance(response, dict) and "error" in response: logger.warning(f"Failed to deploy service function: {response}") logger.warning( "SRM returned an error, deployment not completed" ) return jsonify({ "warning": "Deployment not completed (SRM service unreachable)", "warning": "Deployment request accepted but not completed", "details": response }), 202 logger.info(f"Deployment response from SRM: {response}") return response logger.info("Local deployment request successfully sent to SRM") return jsonify({ "message": "Application deployed locally", "appId": app_id, "response": response }), 202 except ValidationError as e: logger.error(f"Validation error: {str(e)}") return jsonify({"error": "Validation error", "details": str(e)}), 400 except Exception as e: logger.error(f"SRM deployment failed: {str(e)}") return jsonify({ "warning": "SRM backend unavailable", "details": str(e) }), 202 except Exception as e: logger.error(f"Unexpected error in create_app_instance:{str(e)}") return jsonify({"error": "An unexpected error occurred", "details": str(e)}), 500 logger.exception("Unexpected error in create_app_instance") return jsonify({ "error": "Unexpected error", "details": str(e) }), 500 def get_app_instance(app_id=None, x_correlator=None, app_instance_id=None, region=None): Loading
edge_cloud_management_api/services/federation_services.py +16 −4 Original line number Diff line number Diff line Loading @@ -238,14 +238,26 @@ class FederationManagerClient: '''---ARTEFACT API---''' def create_artefact(self, artefact: dict, federation_context_id, token: str): def create_artefact(self, artefact: dict, federation_context_id: str, 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 response = requests.post( url, headers=self._get_headers(token), json=artefact, timeout=120 ) try: body = response.json() except ValueError: body = response.text return body, response.status_code except Exception as e: logger.error(f"Create artefact unexpected error: {e}") return {"error": str(e), "status_code": 500} return {"error": str(e)}, 500 class FederationManagerClientFactory: def __init__(self): Loading