Commit cb9452d8 authored by Sergio Gimenez's avatar Sergio Gimenez
Browse files

Add SRM FM internal endpoints

parent 50957afa
Loading
Loading
Loading
Loading
+372 −0
Original line number Diff line number Diff line
import connexion
import logging

from requests import Response as RequestsResponse

from src.controllers.edge_cloud_management_controller import edgecloud_adapter


logger = logging.getLogger(__name__)


def _normalize_adapter_response(response):
    if isinstance(response, RequestsResponse):
        try:
            data = response.json()
        except ValueError:
            data = response.text
        return data, response.status_code
    return response


def _call_first_available(target, *method_names):
    for method_name in method_names:
        method = getattr(target, method_name, None)
        if method:
            return method
    raise AttributeError(f"None of these methods exist on {type(target).__name__}: {method_names}")


def _repo_type_to_package_type(repo_data):
    descriptor_type = (repo_data.get("artefactDescriptorType") or "").upper()
    virt_type = (repo_data.get("artefactVirtType") or "").upper()
    if descriptor_type == "HELM":
        return "HELM"
    if virt_type == "VM_TYPE":
        return "VM"
    return "CONTAINER"


def _repo_credentials(repo_location):
    if not isinstance(repo_location, dict):
        return {}
    return {
        "repoURL": repo_location.get("repoURL"),
        "userName": repo_location.get("userName") or "",
        "password": repo_location.get("password") or "",
        "token": repo_location.get("token") or "",
    }


def _build_app_repo_from_artefact(artefact_data):
    repo_location = _repo_credentials(artefact_data.get("artefactRepoLocation"))
    repo_url = repo_location.get("repoURL")
    image_path = repo_url

    if repo_url and artefact_data.get("artefactName") and artefact_data.get("artefactVersionInfo"):
        version = artefact_data.get("artefactVersionInfo")
        if repo_url.endswith("/"):
            image_path = f"{repo_url}{artefact_data.get('artefactName')}:{version}"
        elif "/" in repo_url and not repo_url.endswith(f":{version}"):
            image_path = f"{repo_url.rstrip('/')}/{artefact_data.get('artefactName')}:{version}"

    return {
        "imagePath": image_path,
        "repoURL": repo_url,
        "userName": repo_location.get("userName"),
        "credentials": repo_location.get("password") or repo_location.get("token"),
    }


def _normalize_component_specs(component_specs):
    normalized_specs = []
    for component in component_specs or []:
        normalized_component = dict(component)
        network_interfaces = []
        for interface in component.get("exposedInterfaces") or []:
            port = interface.get("commPort")
            if port is None:
                continue
            network_interfaces.append(
                {
                    "port": port,
                    "protocol": (interface.get("commProtocol") or "TCP").upper(),
                    "visibilityType": interface.get("visibilityType") or "VISIBILITY_EXTERNAL",
                }
            )
        if network_interfaces:
            normalized_component["networkInterfaces"] = network_interfaces
        normalized_specs.append(normalized_component)
    return normalized_specs


def _build_manifest_from_gsma_onboarding(request_data):
    component_specs = _normalize_component_specs(request_data.get("appComponentSpecs") or [])
    artefact_id = None
    if component_specs:
        artefact_id = component_specs[0].get("artefactId")

    artefact_data = None
    if artefact_id:
        get_artefact_method = _call_first_available(
            edgecloud_adapter,
            "get_artefact",
            "get_artefact_gsma",
        )
        artefact_response = _normalize_adapter_response(get_artefact_method(artefact_id))
        if isinstance(artefact_response, tuple):
            artefact_data, status = artefact_response
            if status != 200:
                return artefact_data, status

    app_meta_data = request_data.get("appMetaData") or {}
    manifest = {
        "appId": request_data.get("appId"),
        "name": app_meta_data.get("appName") or request_data.get("appId"),
        "appName": app_meta_data.get("appName") or request_data.get("appId"),
        "version": app_meta_data.get("version") or "1.0.0",
        "appDescription": app_meta_data.get("appDescription") or "",
        "appProvider": request_data.get("appProviderId"),
        "packageType": _repo_type_to_package_type(artefact_data or {}),
        "appComponentSpecs": component_specs,
        "appDeploymentZones": [
            {"zoneId": zone_id} for zone_id in request_data.get("appDeploymentZones") or []
        ],
    }

    if artefact_data:
        manifest["appRepo"] = _build_app_repo_from_artefact(artefact_data)

    return manifest, 200


def _normalize_onboarding_response(response):
    normalized = _normalize_adapter_response(response)
    if isinstance(normalized, tuple):
        data, status = normalized
        if status == 201:
            return data, 200
        return data, status
    return normalized


def _normalize_delete_response(response):
    normalized = _normalize_adapter_response(response)
    if isinstance(normalized, tuple):
        data, status = normalized
        if status == 204:
            return "deleted", 200
        return data, status
    return normalized


def _build_deploy_request_from_gsma(request_data):
    zone_info = request_data.get("zoneInfo") or {}
    zone_id = zone_info.get("zoneId")
    app_zones = []
    if zone_id:
        app_zones.append({"zoneId": zone_id})
    return request_data.get("appId"), app_zones


def _build_zone_summary(zone):
    zone_id = zone.get("zoneId") or zone.get("edgeCloudZoneId")
    return {
        "zoneId": zone_id,
        "edgeCloudRegion": zone.get("edgeCloudRegion", "unknown"),
    }


def _build_zone_details(zone_id, zone):
    return {
        "zoneId": zone_id,
        "edgeCloudRegion": zone.get("edgeCloudRegion", "unknown"),
        "reservedComputeResources": [],
        "computeResourceQuotaLimits": [],
        "flavoursSupported": [
            {
                "flavourId": zone_id,
                "numCPU": 0,
                "memorySize": 0,
                "storageSize": 0,
                "supportedOSTypes": ["OTHER"],
                "cpuArchType": "OTHER",
                "gpu": [],
                "fpga": 0,
                "vpu": 0,
                "hugepages": [],
                "cpuExclusivity": False,
            }
        ],
        "networkResources": {},
        "zoneServiceLevelObjsInfo": {},
    }


def _extract_onboarded_app(response):
    normalized = _normalize_adapter_response(response)
    if isinstance(normalized, tuple):
        data, status = normalized
        if isinstance(data, dict) and "appManifest" in data:
            return data["appManifest"], status
        return data, status
    return normalized


def _extract_deployed_app(response):
    normalized = _normalize_adapter_response(response)
    if isinstance(normalized, tuple):
        data, status = normalized
        if isinstance(data, dict) and "appInstance" in data:
            return data["appInstance"], status
        return data, status
    return normalized


def get_zones_list():
    response = edgecloud_adapter.get_edge_cloud_zones()
    normalized = _normalize_adapter_response(response)
    if isinstance(normalized, tuple):
        data, status = normalized
        if isinstance(data, list):
            data = [_build_zone_summary(zone) for zone in data]
        return data, status
    return normalized


def get_zones():
    response = edgecloud_adapter.get_edge_cloud_zones()
    normalized = _normalize_adapter_response(response)
    if isinstance(normalized, tuple):
        data, status = normalized
        if isinstance(data, list):
            data = [
                _build_zone_details(zone.get("zoneId") or zone.get("edgeCloudZoneId"), zone)
                for zone in data
            ]
        return data, status
    return normalized


def get_zone(zone_id: str):
    response = edgecloud_adapter.get_edge_cloud_zones()
    normalized = _normalize_adapter_response(response)
    if isinstance(normalized, tuple):
        data, status = normalized
        if status != 200:
            return data, status
        for zone in data:
            current_zone_id = zone.get("zoneId") or zone.get("edgeCloudZoneId")
            if current_zone_id == zone_id:
                return _build_zone_details(zone_id, zone), 200
        return {"error": "Zone not found"}, 404
    return normalized


def create_artefact():
    if not connexion.request.is_json:
        return {"error": "Wrong request schema"}, 400
    create_artefact_method = _call_first_available(
        edgecloud_adapter,
        "create_artefact",
        "create_artefact_gsma",
    )
    response = create_artefact_method(connexion.request.get_json())
    return _normalize_adapter_response(response)


def delete_artefact(artefact_id: str):
    delete_artefact_method = _call_first_available(
        edgecloud_adapter,
        "delete_artefact",
        "delete_artefact_gsma",
    )
    response = delete_artefact_method(artefact_id)
    return _normalize_delete_response(response)


def create_onboarding():
    if not connexion.request.is_json:
        return {"error": "Wrong request schema"}, 400
    manifest, status = _build_manifest_from_gsma_onboarding(connexion.request.get_json())
    if status != 200:
        return manifest, status
    response = edgecloud_adapter.onboard_app(manifest)
    return _normalize_onboarding_response(response)


def get_onboarding(app_id: str):
    response = edgecloud_adapter.get_onboarded_app(app_id)
    return _extract_onboarded_app(response)


def update_onboarding(app_id: str):
    if not connexion.request.is_json:
        return {"error": "Wrong request schema"}, 400
    request_data = connexion.request.get_json()
    response = edgecloud_adapter.get_onboarded_app(app_id)
    normalized = _extract_onboarded_app(response)
    if isinstance(normalized, tuple):
        data, status = normalized
        if status != 200:
            return data, status
        if isinstance(data, dict):
            if request_data.get("appUpdQoSProfile"):
                data["appUpdQoSProfile"] = request_data.get("appUpdQoSProfile")
            if request_data.get("appComponentSpecs"):
                data["appComponentSpecs"] = _normalize_component_specs(request_data.get("appComponentSpecs"))
            patch_method = _call_first_available(
                edgecloud_adapter,
                "patch_onboarded_app",
                "patch_onboarded_app_gsma",
            )
            patch_response = patch_method(app_id, data)
            normalized_patch = _normalize_adapter_response(patch_response)
            if isinstance(normalized_patch, tuple):
                patch_data, patch_status = normalized_patch
                return patch_data, patch_status
            return normalized_patch
    return normalized


def delete_onboarding(app_id: str):
    response = edgecloud_adapter.delete_onboarded_app(app_id)
    return _normalize_delete_response(response)


def deploy_app():
    if not connexion.request.is_json:
        return {"error": "Wrong request schema"}, 400
    request_data = connexion.request.get_json()
    app_id, app_zones = _build_deploy_request_from_gsma(request_data)
    response = edgecloud_adapter.deploy_app(app_id, app_zones)
    normalized = _extract_deployed_app(response)
    if isinstance(normalized, tuple):
        data, status = normalized
        if status >= 400:
            return data, status
        if isinstance(data, dict):
            return {
                "appInstIdentifier": data.get("appInstanceId"),
                "zoneID": data.get("edgeCloudZoneId"),
            }, status
    return normalized


def get_deployed_app(app_id: str, app_instance_id: str, zone_id: str):
    response = edgecloud_adapter.get_deployed_app(app_instance_id)
    normalized = _extract_deployed_app(response)
    if isinstance(normalized, tuple):
        data, status = normalized
        if status != 200:
            return data, status
        if data.get("appId") != app_id or data.get("edgeCloudZoneId") != zone_id:
            return {"error": "App instance not found"}, 404
        return {
            "appInstanceState": data.get("appInstanceState"),
            "accesspointInfo": data.get("accesspointInfo", []),
        }, status
    return normalized


def undeploy_app(app_id: str, app_instance_id: str, zone_id: str):
    response = edgecloud_adapter.get_deployed_app(app_instance_id)
    normalized = _extract_deployed_app(response)
    if isinstance(normalized, tuple):
        data, status = normalized
        if status != 200:
            return data, status
        if data.get("appId") != app_id or data.get("edgeCloudZoneId") != zone_id:
            return {"error": "App instance not found"}, 404
    response = edgecloud_adapter.undeploy_app(app_instance_id)
    return _normalize_delete_response(response)
+187 −0
Original line number Diff line number Diff line
@@ -16,6 +16,193 @@ externalDocs:
servers:
- url: http://vitrualserver:8080/srm/1.0.0
paths:
  /internal/fm/zones/list:
    get:
      tags:
      - FM Internal
      summary: Returns GSMA zone list for FM
      operationId: get_zones_list
      responses:
        "200":
          description: Zone list retrieved
      x-openapi-router-controller: src.controllers.fm_internal_controller
  /internal/fm/zones:
    get:
      tags:
      - FM Internal
      summary: Returns GSMA zone details for FM
      operationId: get_zones
      responses:
        "200":
          description: Zone details retrieved
      x-openapi-router-controller: src.controllers.fm_internal_controller
  /internal/fm/zones/{zone_id}:
    get:
      tags:
      - FM Internal
      summary: Returns GSMA zone detail by id for FM
      operationId: get_zone
      parameters:
      - name: zone_id
        in: path
        required: true
        schema:
          type: string
      responses:
        "200":
          description: Zone detail retrieved
      x-openapi-router-controller: src.controllers.fm_internal_controller
  /internal/fm/artefacts:
    post:
      tags:
      - FM Internal
      summary: Create artefact through GSMA adapter
      operationId: create_artefact
      responses:
        "200":
          description: Artefact created
        "201":
          description: Artefact created
      x-openapi-router-controller: src.controllers.fm_internal_controller
  /internal/fm/artefacts/{artefact_id}:
    delete:
      tags:
      - FM Internal
      summary: Delete artefact through GSMA adapter
      operationId: delete_artefact
      parameters:
      - name: artefact_id
        in: path
        required: true
        schema:
          type: string
      responses:
        "200":
          description: Artefact deleted
        "204":
          description: Artefact deleted
      x-openapi-router-controller: src.controllers.fm_internal_controller
  /internal/fm/onboardings:
    post:
      tags:
      - FM Internal
      summary: Create onboarding through GSMA adapter
      operationId: create_onboarding
      responses:
        "200":
          description: Onboarding created
      x-openapi-router-controller: src.controllers.fm_internal_controller
  /internal/fm/onboardings/{app_id}:
    get:
      tags:
      - FM Internal
      summary: Get onboarding through GSMA adapter
      operationId: get_onboarding
      parameters:
      - name: app_id
        in: path
        required: true
        schema:
          type: string
      responses:
        "200":
          description: Onboarding retrieved
      x-openapi-router-controller: src.controllers.fm_internal_controller
    patch:
      tags:
      - FM Internal
      summary: Update onboarding through GSMA adapter
      operationId: update_onboarding
      parameters:
      - name: app_id
        in: path
        required: true
        schema:
          type: string
      responses:
        "200":
          description: Onboarding updated
      x-openapi-router-controller: src.controllers.fm_internal_controller
    delete:
      tags:
      - FM Internal
      summary: Delete onboarding through GSMA adapter
      operationId: delete_onboarding
      parameters:
      - name: app_id
        in: path
        required: true
        schema:
          type: string
      responses:
        "200":
          description: Onboarding deleted
        "204":
          description: Onboarding deleted
      x-openapi-router-controller: src.controllers.fm_internal_controller
  /internal/fm/deployments:
    post:
      tags:
      - FM Internal
      summary: Deploy app through GSMA adapter
      operationId: deploy_app
      responses:
        "202":
          description: Deployment accepted
      x-openapi-router-controller: src.controllers.fm_internal_controller
  /internal/fm/deployments/{app_id}/instances/{app_instance_id}/zones/{zone_id}:
    get:
      tags:
      - FM Internal
      summary: Get deployed app through GSMA adapter
      operationId: get_deployed_app
      parameters:
      - name: app_id
        in: path
        required: true
        schema:
          type: string
      - name: app_instance_id
        in: path
        required: true
        schema:
          type: string
      - name: zone_id
        in: path
        required: true
        schema:
          type: string
      responses:
        "200":
          description: Deployment retrieved
      x-openapi-router-controller: src.controllers.fm_internal_controller
    delete:
      tags:
      - FM Internal
      summary: Delete deployed app through GSMA adapter
      operationId: undeploy_app
      parameters:
      - name: app_id
        in: path
        required: true
        schema:
          type: string
      - name: app_instance_id
        in: path
        required: true
        schema:
          type: string
      - name: zone_id
        in: path
        required: true
        schema:
          type: string
      responses:
        "200":
          description: Deployment deleted
        "204":
          description: Deployment deleted
      x-openapi-router-controller: src.controllers.fm_internal_controller
  /node/{node_id}:
    get:
      tags: