From 97e88f33a0ed44fdce8f7f5a7d4df01dd52cfd88 Mon Sep 17 00:00:00 2001
From: gpapathan87 <gpapathan@intracom-telecom.com>
Date: Mon, 12 May 2025 23:27:22 +0300
Subject: [PATCH] Refactored edge cloud API, updated configs and requirements
---
.env.sample | 5 -
Dockerfile | 4 +-
edge_cloud_management_api/__main__.py | 2 +-
edge_cloud_management_api/app.py | 2 +-
.../configs/env_config.py | 2 +-
.../controllers/app_controllers.py | 136 +++++++++++++-----
.../controllers/edge_cloud_controller.py | 38 ++++-
.../models/application_models.py | 119 ++++++---------
.../models/edge_cloud_models.py | 3 +-
.../services/pi_edge_services.py | 6 +-
requirements.txt | 2 +-
11 files changed, 188 insertions(+), 131 deletions(-)
delete mode 100644 .env.sample
diff --git a/.env.sample b/.env.sample
deleted file mode 100644
index 77929b0..0000000
--- a/.env.sample
+++ /dev/null
@@ -1,5 +0,0 @@
-MONGO_URI=mongodb://username:password@localhost:27017/sample_db
-PI_EDGE_BASE_URL=http://example.com/api
-PI_EDGE_USERNAME=username
-PI_EDGE_PASSWORD=password
-HTTP_PROXY=https://company.proxy:3128
diff --git a/Dockerfile b/Dockerfile
index 6c452eb..1744f1b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -5,7 +5,7 @@ WORKDIR /usr/src/app
COPY requirements.txt /usr/src/app/
-RUN pip install --no-cache-dir -r requirements.txt
+RUN pip install --no-cache-dir --trusted-host pypi.org --trusted-host files.pythonhosted.org -r requirements.txt
COPY . /usr/src/app
@@ -13,4 +13,4 @@ EXPOSE 8080
ENTRYPOINT ["python"]
-CMD ["-m", "edge_cloud_management_api"]
\ No newline at end of file
+CMD ["-m", "edge_cloud_management_api"]
diff --git a/edge_cloud_management_api/__main__.py b/edge_cloud_management_api/__main__.py
index 7f20b6f..41bd405 100644
--- a/edge_cloud_management_api/__main__.py
+++ b/edge_cloud_management_api/__main__.py
@@ -2,4 +2,4 @@ from edge_cloud_management_api.app import get_app_instance
if __name__ == "__main__":
app = get_app_instance()
- app.run(host="127.0.0.1", port=8080)
+ app.run(host="0.0.0.0", port=8080)
diff --git a/edge_cloud_management_api/app.py b/edge_cloud_management_api/app.py
index a97d1df..7c0fa08 100644
--- a/edge_cloud_management_api/app.py
+++ b/edge_cloud_management_api/app.py
@@ -17,4 +17,4 @@ def get_app_instance() -> FlaskApp:
if __name__ == "__main__":
app = get_app_instance()
- app.run(host="127.0.0.1", port=8080)
+ app.run(host="0.0.0.0", port=8080)
diff --git a/edge_cloud_management_api/configs/env_config.py b/edge_cloud_management_api/configs/env_config.py
index e8abd42..1be9aca 100644
--- a/edge_cloud_management_api/configs/env_config.py
+++ b/edge_cloud_management_api/configs/env_config.py
@@ -7,7 +7,7 @@ load_dotenv()
class Configuration(BaseSettings):
MONGO_URI: str = os.getenv("MONGO_URI")
- PI_EDGE_BASE_URL: str = os.getenv("PI_EDGE_BASE_URL")
+ SRM_HOST: str = os.getenv("SRM_HOST")
PI_EDGE_USERNAME: str = os.getenv("PI_EDGE_USERNAME")
PI_EDGE_PASSWORD: str = os.getenv("PI_EDGE_PASSWORD")
HTTP_PROXY: str = os.getenv("HTTP_PROXY")
diff --git a/edge_cloud_management_api/controllers/app_controllers.py b/edge_cloud_management_api/controllers/app_controllers.py
index d8ba5dc..fa7a818 100644
--- a/edge_cloud_management_api/controllers/app_controllers.py
+++ b/edge_cloud_management_api/controllers/app_controllers.py
@@ -1,10 +1,10 @@
import uuid
-from flask import jsonify
-
+from flask import jsonify, request
from pydantic import ValidationError
from edge_cloud_management_api.managers.db_manager import MongoManager
from edge_cloud_management_api.managers.log_manager import logger
-from edge_cloud_management_api.models.application_models import AppManifest
+from edge_cloud_management_api.models.application_models import AppManifest, AppZones, AppInstance
+from edge_cloud_management_api.services.pi_edge_services import PiEdgeAPIClientFactory
class NotFound404Exception(Exception):
@@ -108,49 +108,111 @@ def delete_app(appId, x_correlator=None): # noqa: E501
)
-def create_app_instance(body, app_id, x_correlator=None): # noqa: E501
- """Instantiation of an Application
+def create_app_instance():
+ logger.info("Received request to create app instance")
- Ask the Edge Cloud Platform to instantiate an application to one or several Edge Cloud Zones with an Application as an input and an Application Instance as the output. # noqa: E501
+ try:
+ # Step 1: Get request body
+ body = request.get_json()
+ logger.debug(f"Request body: {body}")
- :param body: Array of Edge Cloud Zone
- :type body: list | bytes
- :param app_id: A globally unique identifier associated with the application. Edge Cloud Provider generates this identifier when the application is submitted.
- :type app_id: dict | bytes
- :param x_correlator: Correlation id for the different services
- :type x_correlator: str
+ # Step 2: Validate body format
+ app_id = body.get('appId')
+ app_zones = body.get('appZones')
- :rtype: InlineResponse202
- """
- try:
- return {}, 202 # application instantiation accepted
- except Exception:
- logger.exception("Error while creating app instance")
- error = {
- "status": 500,
- "code": "INTERNAL",
- "message": "Internal server error.",
- }
- return error, 500
+ if not app_id or not app_zones:
+ return jsonify({"error": "Missing required fields: appId or appZones"}), 400
+
+ # Step 3: Connect to Mongo and check if app exists
+ with MongoManager() as mongo_manager:
+ app_data = mongo_manager.find_document("apps", {"_id": app_id})
+
+ if not app_data:
+ logger.warning(f"No application found with ID {app_id}")
+ return jsonify({"error": "App not found", "details": f"No application found with ID {app_id}"}), 404
+
+ logger.info(f"Application {app_id} found in database")
+
+ # Step 4: Deploy app instance using Pi-Edge client
+ pi_edge_client_factory = PiEdgeAPIClientFactory()
+ pi_edge_client = pi_edge_client_factory.create_pi_edge_api_client()
+
+ logger.info(f"Preparing to send deployment request to Pi-Edge for appId={app_id}")
+
+ deployment_payload = [{
+ "appId": app_id,
+ "appZones": app_zones
+ }]
+
+ # 🖨️ Print everything before sending
+ print("\n=== Preparing Deployment Request ===")
+ print(f"Endpoint: {pi_edge_client.base_url}/deployedServiceFunction")
+ print(f"Headers: {pi_edge_client._get_headers()}")
+ print(f"Payload: {deployment_payload}")
+ print("=== End of Deployment Request ===\n")
+
+ # 🛡️ Try sending to Pi-Edge, catch connection errors separately
+ try:
+ response = pi_edge_client.deploy_service_function(data=deployment_payload)
+ if isinstance(response, dict) and "error" in response:
+ logger.warning(f"Failed to deploy service function: {response}")
+ return jsonify({
+ "warning": "Deployment not completed (SRM service unreachable)",
+ "details": response
+ }), 202 # Still accept the request but warn
-def get_app_instance(app_id, x_correlator=None, app_instance_id=None, region=None): # noqa: E501
- """Retrieve the information of Application Instances for a given App
+ logger.info(f"Deployment response from Pi-Edge: {response}")
- Ask the Edge Cloud Provider the information of the instances for a given application # noqa: E501
+ except Exception as inner_error:
+ logger.error(f"Exception while trying to deploy to SRM: {inner_error}")
+ return jsonify({
+ "warning": "SRM backend unavailable. Deployment request was built correctly.",
+ "details": str(inner_error)
+ }), 202 # Still accept it (because your backend worked)
- :param app_id: A globally unique identifier associated with the application. Edge Cloud Provider generates this identifier when the application is submitted.
- :type app_id: dict | bytes
- :param x_correlator: Correlation id for the different services
- :type x_correlator: str
- :param app_instance_id: A globally unique identifier associated with a running instance of an application within an specific Edge Cloud Zone. Edge Cloud Provider generates this identifier.
- :type app_instance_id: dict | bytes
- :param region: Human readable name of the geographical Edge Cloud Region of the Edge Cloud. Defined by the Edge Cloud Provider.
- :type region: dict | bytes
+ return jsonify({"message": f"Application {app_id} instantiation accepted"}), 202
- :rtype: InlineResponse2001
+ 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"Unexpected error in create_app_instance: {str(e)}")
+ return jsonify({"error": "An unexpected error occurred", "details": str(e)}), 500
+
+def get_app_instance(app_id=None, x_correlator=None, app_instance_id=None, region=None):
+ """
+ Retrieve application instances from the database.
+ Supports filtering by app_id, app_instance_id, and region.
"""
- return "do some magic!"
+ try:
+ query = {}
+ if app_id:
+ query["appId"] = app_id
+ if app_instance_id:
+ query["appInstanceId"] = app_instance_id
+ if region:
+ query["edgeCloudZone.edgeCloudRegion"] = region
+
+ with MongoManager() as db:
+ instances = list(db.find_documents("appinstances", query))
+
+ if not instances:
+ return jsonify({
+ "status": 404,
+ "code": "NOT_FOUND",
+ "message": "No application instances found for the given parameters."
+ }), 404
+
+ return jsonify({"appInstanceInfo": instances}), 200
+
+ except Exception as e:
+ logger.exception("Failed to retrieve app instances")
+ return jsonify({
+ "status": 500,
+ "code": "INTERNAL",
+ "message": f"Internal server error: {str(e)}"
+ }), 500
def delete_app_instance(app_id, app_instance_id, x_correlator=None):
diff --git a/edge_cloud_management_api/controllers/edge_cloud_controller.py b/edge_cloud_management_api/controllers/edge_cloud_controller.py
index 65173b4..57292b8 100644
--- a/edge_cloud_management_api/controllers/edge_cloud_controller.py
+++ b/edge_cloud_management_api/controllers/edge_cloud_controller.py
@@ -1,6 +1,9 @@
from flask import jsonify
from pydantic import BaseModel, Field, ValidationError
from typing import List
+from edge_cloud_management_api.managers.log_manager import logger
+from edge_cloud_management_api.services.pi_edge_services import PiEdgeAPIClientFactory
+
class EdgeCloudZone(BaseModel):
@@ -25,9 +28,38 @@ class EdgeCloudQueryParams(BaseModel):
)
-def get_local_zones() -> List[EdgeCloudZone]:
- """get local Operator Platform available zones from Service Resource Manager"""
- return []
+def get_local_zones() -> list[dict]:
+ """
+ Get local Operator Platform available zones from PiEdge Service Resource Manager.
+ """
+ try:
+ pi_edge_factory = PiEdgeAPIClientFactory()
+ api_client = pi_edge_factory.create_pi_edge_api_client()
+ result = api_client.edge_cloud_zones()
+
+ if isinstance(result, dict) and "error" in result:
+ logger.error(f"PiEdge error: {result['error']}")
+ return []
+
+ zones = []
+ for node in result:
+ try:
+ zone = EdgeCloudZone(
+ edgeCloudZoneId=node["id"],
+ edgeCloudZoneName=node.get("name", "unknown"),
+ edgeCloudZoneStatus=node.get("status", "unknown"),
+ edgeCloudProvider=node.get("provider", "local-provider"),
+ edgeCloudRegion=node.get("region", "default-region")
+ )
+ zones.append(zone.model_dump())
+ except Exception as e:
+ logger.warning(f"Failed to parse node into EdgeCloudZone: {e}")
+
+ return zones
+
+ except Exception as e:
+ logger.exception("Unexpected error while retrieving local zones from PiEdge")
+ return []
def get_federated_zones() -> List[EdgeCloudZone]:
diff --git a/edge_cloud_management_api/models/application_models.py b/edge_cloud_management_api/models/application_models.py
index 723e34a..617e7de 100644
--- a/edge_cloud_management_api/models/application_models.py
+++ b/edge_cloud_management_api/models/application_models.py
@@ -1,75 +1,47 @@
-from pydantic import BaseModel, HttpUrl, Field # , UUID4
-from typing import Any, List, Optional
-from enum import Enum
+#from pydantic import BaseModel, HttpUrl, Field , UUID4
+#from typing import Any, List, Optional
+#from enum import Enum
# from ipaddress import IPv4Address, IPv6Address
+#from edge_cloud_management_api.models.edge_cloud_models import EdgeCloudZone
-# from edge_cloud_management_api.models.edge_cloud_models import EdgeCloudZone
-
-
-# Enum definitions
-
-
-# class VisibilityType(str, Enum):
-# VISIBILITY_EXTERNAL = "VISIBILITY_EXTERNAL"
-# VISIBILITY_INTERNAL = "VISIBILITY_INTERNAL"
-
-
-# class AppInstanceStatus(str, Enum):
-# ready = "ready"
-# instantiating = "instantiating"
-# failed = "failed"
-# terminating = "terminating"
-# unknown = "unknown"
+from pydantic import BaseModel, HttpUrl, Field, UUID4
+from typing import Any, List, Optional
+from enum import Enum
+from edge_cloud_management_api.models.edge_cloud_models import EdgeCloudZone # <-- you should IMPORT this properly
-# class Protocol(str, Enum):
-# TCP = "TCP"
-# UDP = "UDP"
-# ANY = "ANY"
-
-
-# Model definitions
-
-# class AccessEndpoint(BaseModel):
-# port: int # min 0
-# fqdn: Optional[str]
-# ipv4Addresses: Optional[List[IPv4Address]] # minItems: 1
-# ipv6Addresses: Optional[List[IPv6Address]] # minItems: 1
+# --- Enums ---
-# class Config:
-# schema_extra = {
-# "example": {
-# "port": 8080,
-# "fqdn": "example.com",
-# "ipv4Addresses": ["192.168.0.1"],
-# "ipv6Addresses": ["2001:db8::1"],
-# }
-# }
+class VisibilityType(str, Enum):
+ VISIBILITY_EXTERNAL = "VISIBILITY_EXTERNAL"
+ VISIBILITY_INTERNAL = "VISIBILITY_INTERNAL"
-# class ComponentEndpointInfo(BaseModel):
-# interfaceId: UUID4 # string pattern: ^[A-Za-z0-9][A-Za-z0-9_]{6,30}[A-Za-z0-9]$
-# accessPoints: AccessEndpoint
+class AppInstanceStatus(str, Enum):
+ ready = "ready"
+ instantiating = "instantiating"
+ failed = "failed"
+ terminating = "terminating"
+ unknown = "unknown"
-# class AppInstanceInfo(BaseModel):
-# appInstanceId: UUID4 # str = Field(..., regex=r"^[0-9a-fA-F-]{36}$")
-# status: AppInstanceStatus = AppInstanceStatus.unknown # [ ready, instantiating, failed, terminating, unknown ]
-# componentEndpointInfo: List[ComponentEndpointInfo]
-# kubernetesClusterRef: Optional[UUID4]
-# edgeCloudZone: EdgeCloudZone
+class Protocol(str, Enum):
+ TCP = "TCP"
+ UDP = "UDP"
+ ANY = "ANY"
+# --- Model Definitions ---
class NetworkInterface(BaseModel):
interfaceId: str = Field(..., pattern="^[A-Za-z][A-Za-z0-9_]{3,31}$")
- protocol: str # [ TCP, UDP, ANY ]
- port: int # minimum: 1, maximum: 65535
- visibilityType: str # [ VISIBILITY_EXTERNAL, VISIBILITY_INTERNAL ]
+ protocol: Protocol
+ port: int # 1-65535
+ visibilityType: VisibilityType
class ComponentSpec(BaseModel):
componentName: str
- networkInterfaces: List[NetworkInterface] # min one occurrence
+ networkInterfaces: List[NetworkInterface]
class AppRepo(BaseModel):
@@ -79,12 +51,12 @@ class AppRepo(BaseModel):
HTTP_BEARER = "HTTP_BEARER"
NONE = "NONE"
- type: str # [ PRIVATEREPO, PUBLICREPO ]
+ type: str # PRIVATEREPO or PUBLICREPO
imagePath: HttpUrl
userName: Optional[str]
- credentials: Optional[str] # maxLength: 128
+ credentials: Optional[str] # max 128 characters
authType: Optional[AppRepoAuthType]
- checksum: Optional[str] # MD5 checksum for VM and file-based images, sha256 digest for containers
+ checksum: Optional[str]
class AppManifest(BaseModel):
@@ -95,32 +67,27 @@ class AppManifest(BaseModel):
HELM = "HELM"
class OperatingSystem(BaseModel):
- architecture: str # [ x86_64, x86 ]
- family: str # [ RHEL, UBUNTU, COREOS, WINDOWS, OTHER ]
- version: str # Version of the OS # [ OS_VERSION_UBUNTU_2204_LTS, OS_VERSION_RHEL_8, OS_MS_WINDOWS_2022, OTHER ]
- license: str # License needed to activate the OS # [ OS_LICENSE_TYPE_FREE, OS_LICENSE_TYPE_ON_DEMAND, OTHER ]
-
- class RequiredResources(BaseModel):
- numCPU: int
- memory: int
- storage: int
+ architecture: str # x86_64, x86
+ family: str # UBUNTU, RHEL, COREOS, etc
+ version: str
+ license: str
- # appId: Optional[UUID4]
name: str = Field(..., pattern="^[A-Za-z][A-Za-z0-9_]{1,63}$")
appProvider: str = Field(..., pattern="^[A-Za-z][A-Za-z0-9_]{7,63}$")
- version: str # application version
+ version: str
packageType: PackageType
operatingSystem: Optional[OperatingSystem]
appRepo: AppRepo
- requiredResources: Any # Optional[RequiredResources]
+ requiredResources: Any # Could be KubernetesResources, ContainerResources, etc.
componentSpec: List[ComponentSpec]
-# class AppZones(BaseModel):
-# kubernetesClusterRef: Optional[UUID4]
-# EdgeCloudZone: EdgeCloudZone
+class AppZones(BaseModel):
+ kubernetesClusterRef: Optional[UUID4]
+ EdgeCloudZone: EdgeCloudZone
+
+class AppInstance(BaseModel):
+ appId: UUID4
+ appZones: List[AppZones]
-# class AppInstance(BaseModel):
-# appId: UUID4
-# appZones: List[AppZones]
diff --git a/edge_cloud_management_api/models/edge_cloud_models.py b/edge_cloud_management_api/models/edge_cloud_models.py
index 23c7e25..61b419f 100644
--- a/edge_cloud_management_api/models/edge_cloud_models.py
+++ b/edge_cloud_management_api/models/edge_cloud_models.py
@@ -10,8 +10,9 @@ class EdgeCloudZoneStatus(str, Enum):
class EdgeCloudZone(BaseModel):
- edgeCloudZoneId: UUID4 # Field(..., regex=r"^[0-9a-fA-F-]{36}$")
+ edgeCloudZoneId: UUID4
edgeCloudZoneName: str
edgeCloudZoneStatus: Optional[EdgeCloudZoneStatus]
edgeCloudProvider: str
edgeCloudRegion: Optional[str]
+
diff --git a/edge_cloud_management_api/services/pi_edge_services.py b/edge_cloud_management_api/services/pi_edge_services.py
index f8776dc..93ad16f 100644
--- a/edge_cloud_management_api/services/pi_edge_services.py
+++ b/edge_cloud_management_api/services/pi_edge_services.py
@@ -155,16 +155,16 @@ class PiEdgeAPIClientFactory:
"""
def __init__(self):
- self.default_base_url = config.PI_EDGE_BASE_URL
+ self.default_base_url = config.SRM_HOST
self.default_username = config.PI_EDGE_USERNAME
self.default_password = config.PI_EDGE_PASSWORD
def create_pi_edge_api_client(self, base_url=None, username=None, password=None):
"""
- Factory method to create a new PiEdgeAPIClient instance.
+ Factory method to create a new SRMAPIClient instance.
Args:
- base_url (str): The base URL for the PiEdge API. If None, the default is used.
+ base_url (str): The base URL for the SRM API. If None, the default is used.
username (str): The username for authentication. If None, the default is used.
password (str): The password for authentication. If None, the default is used.
diff --git a/requirements.txt b/requirements.txt
index 35aeb82..447a804 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,6 @@
# This file was autogenerated by uv via the following command:
# uv export
--e .
+#-e .
a2wsgi==1.10.7 \
--hash=sha256:6d7c602fb1f9cc6afc6c6d0558d3354f3c7aa281e73e6dc9e001dbfc1d9e80cf \
--hash=sha256:ce462ff7e1daac0bc57183c6f800f09a71c2a7a98ddd5cdeca149e3eabf3338e
--
GitLab