Commit d31d7751 authored by Adriana Fernández-Fernández's avatar Adriana Fernández-Fernández
Browse files

Adapts artefact management API

parent c14054a1
Loading
Loading
Loading
Loading
+354 −0
Original line number Diff line number Diff line
# -------------------------------------------------------------------------- #
# Copyright 2025-present, Federation Manager, by Software Networks, i2CAT    #
#                                                                            #
# Licensed under the Apache License, Version 2.0 (the "License"); you may    #
# not use this file except in compliance with the License. You may obtain    #
# a copy of the License at                                                   #
#                                                                            #
# http://www.apache.org/licenses/LICENSE-2.0                                 #
#                                                                            #
# Unless required by applicable law or agreed to in writing, software        #
# distributed under the License is distributed on an "AS IS" BASIS,          #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #
# See the License for the specific language governing permissions and        #
# limitations under the License.                                             #
# -------------------------------------------------------------------------- #
import json
from mongoengine.errors import ValidationError

from models.mongo_document import OriginatingOperatorPlatformOriginatingOP
from models.mongo_document import OriginatingArtefactManagementOriginatingOP
from adapters.error import APIError
from clients import fed_manager as fm_client


def verify_required_header(partner_api_root):
    if not partner_api_root:
        raise APIError(400, "X-Partner-API-Root header is missing")


def get_artefact(federation_context_id, artefact_id, bearer_token, partner_api_root):  # noqa: E501
    """Retrieves details about an artefact.

     # noqa: E501

    :param federation_context_id:
    :type federation_context_id: dict | bytes
    :param artefact_id:
    :type artefact_id: dict | bytes

    :rtype: InlineResponse2005
    """

    # Verify partner API root is provided
    verify_required_header(partner_api_root)

    try:
        # Find federation at originating OP
        originating_op_objects = OriginatingOperatorPlatformOriginatingOP.objects(id=federation_context_id)
        if not originating_op_objects:
            raise APIError(404, "Federation not found")
        originating_op_instance = originating_op_objects[0]

        # Find artefact at partner
        response_get = fm_client.get_artefact(originating_op_instance.partner_federation_id, artefact_id, bearer_token,
                                              partner_api_root)
        
        # Check if the response contains an error from the partner API
        if "error" in response_get and "status_code" in response_get:
            status_code = response_get["status_code"]
            error_message = response_get["error"]
            raise APIError(status_code, f"Partner API error: {error_message}")
        elif "artefactId" in response_get:
            if find_artefact_at_orig_op(federation_context_id, artefact_id):
                return response_get
            else:
                raise APIError(409, "Artefact exist at partner operator but not in originating operator")
        else:
            if find_artefact_at_orig_op(federation_context_id, artefact_id):
                raise APIError(409, "Artefact exist in originating operator but not in partner operator")
            else:
                raise APIError(404, "Artefact not Found")
    except ValidationError:
        raise APIError(400, f"Invalid federation context ID or artefact ID format: {federation_context_id}, {artefact_id}")
    except APIError:
        raise  # Re-raise APIError as-is
    except Exception as error:
        raise APIError(500, f"Error while getting Artefact. Reason: {error}")


def remove_artefact(federation_context_id, artefact_id, bearer_token, partner_api_root):  # noqa: E501
    """Removes an artefact from partner OP.

     # noqa: E501

    :param federation_context_id:
    :type federation_context_id: dict | bytes
    :param artefact_id:
    :type artefact_id: dict | bytes

    :rtype: None
    """

    # Verify partner API root is provided
    verify_required_header(partner_api_root)

    try:
        # Find federation at originating OP
        originating_op_objects = OriginatingOperatorPlatformOriginatingOP.objects(id=federation_context_id)
        if not originating_op_objects:
            raise APIError(404, "Federation not found")
        originating_op_instance = originating_op_objects[0]

        # Find artefact at partner
        response_get = fm_client.get_artefact(originating_op_instance.partner_federation_id, artefact_id, bearer_token,
                                              partner_api_root)
        
        # Check if the response contains an error from the partner API
        if "error" in response_get and "status_code" in response_get:
            status_code = response_get["status_code"]
            error_message = response_get["error"]
            raise APIError(status_code, f"Partner API error: {error_message}")
        elif "artefactId" in response_get:
            originating_am_objects = find_artefact_at_orig_op(federation_context_id, artefact_id)
            if originating_am_objects:
                # Delete artefact from partner
                response_data = fm_client.delete_artefact(originating_op_instance.partner_federation_id, artefact_id,
                                                          bearer_token, partner_api_root)
                
                # Check if the response contains an error from the partner API
                if "error" in response_data and "status_code" in response_data:
                    status_code = response_data["status_code"]
                    error_message = response_data["error"]
                    raise APIError(status_code, f"Partner API error: {error_message}")
                elif "deletion" in response_data:
                    # If artefact has been removed, remove artefact from originating
                    originating_am_objects.delete()
                    return response_data, 200
                else:
                    raise APIError(422, f"Unexpected response from partner API: {response_data}")
            else:
                raise APIError(409, "Artefact exist at partner operator but not in originating operator")
        else:
            if find_artefact_at_orig_op(federation_context_id, artefact_id):
                raise APIError(409, "Artefact exist in originating operator but not in partner operator")
            else:
                raise APIError(404, "Artefact not Found")
    except ValidationError:
        raise APIError(400, f"Invalid federation context ID or artefact ID format: {federation_context_id}, {artefact_id}")
    except APIError:
        raise  # Re-raise APIError as-is
    except Exception as error:
        raise APIError(500, f"Error while deleting Artefact. Reason: {error}")


def upload_artefact(body, federation_context_id, bearer_token, partner_api_root):  # noqa: E501
    """Uploads application artefact on partner OP. Artefact is a zip file containing scripts and/or packaging files like Terraform or Helm which are required to create an instance of an application.

     # noqa: E501

    :param body:
    :type body: dict | bytes
    :param federation_context_id:
    :type federation_context_id: dict | bytes

    :rtype: None
    """

    # Verify partner API root is provided
    verify_required_header(partner_api_root)

    try:
        # Find federation at originating OP
        originating_op_objects = OriginatingOperatorPlatformOriginatingOP.objects(id=federation_context_id)
        if not originating_op_objects:
            raise APIError(404, "Federation not found")
        originating_op_instance = originating_op_objects[0]
        
        # Find artefact at partner
        response_get = fm_client.get_artefact(originating_op_instance.partner_federation_id, body.artefact_id,
                                              bearer_token, partner_api_root)
        
        # Check if the response contains an error from the partner API
        if "error" in response_get and "status_code" in response_get:
            # If it's a 404, that means artefact doesn't exist at partner, which is what we want for upload
            if response_get["status_code"] != 404:
                status_code = response_get["status_code"]
                error_message = response_get["error"]
                raise APIError(status_code, f"Partner API error: {error_message}")
            # 404 means artefact doesn't exist at partner, continue with upload logic
            if find_artefact_at_orig_op(federation_context_id, body.artefact_id):
                raise APIError(409, "Artefact exist in Originating Operator but not in Partner Operator")
            else:
                # Create artefact in partner
                response_data = fm_client.create_artefact(originating_op_instance.partner_federation_id, body,
                                                          bearer_token, partner_api_root)
                
                # Check if the response contains an error from the partner API
                if "error" in response_data and "status_code" in response_data:
                    status_code = response_data["status_code"]
                    error_message = response_data["error"]
                    raise APIError(status_code, f"Partner API error: {error_message}")
                elif "uploaded" in response_data:
                    # Create artefact in originating
                    create_artefact_originating_op(federation_context_id, originating_op_instance.partner_federation_id,
                                                   body)
                    return response_data, 200
                else:
                    raise APIError(422, f"Unexpected response from partner API: {response_data}")
        elif "artefactId" in response_get:
            if find_artefact_at_orig_op(federation_context_id, body.artefact_id):
                raise APIError(409, "Artefact already exists")
            else:
                raise APIError(409, "Artefact exist in Partner Operator but not in Originating Operator")
        else:
            if find_artefact_at_orig_op(federation_context_id, body.artefact_id):
                raise APIError(409, "Artefact exist in Originating Operator but not in Partner Operator")
            else:
                # Create artefact in partner
                response_data = fm_client.create_artefact(originating_op_instance.partner_federation_id, body,
                                                          bearer_token, partner_api_root)
                
                # Check if the response contains an error from the partner API
                if "error" in response_data and "status_code" in response_data:
                    status_code = response_data["status_code"]
                    error_message = response_data["error"]
                    raise APIError(status_code, f"Partner API error: {error_message}")
                elif "uploaded" in response_data:
                    # Create artefact in originating
                    create_artefact_originating_op(federation_context_id, originating_op_instance.partner_federation_id,
                                                   body)
                    return response_data, 200
                else:
                    raise APIError(422, f"Unexpected response from partner API: {response_data}")
    except ValidationError:
        raise APIError(400, f"Invalid federation context ID format: {federation_context_id}")
    except APIError:
        raise  # Re-raise APIError as-is
    except Exception as error:
        raise APIError(500, f"Error while creating Artefact. Reason: {error}")


def find_artefact_at_orig_op(federation_id, artefact_id):
    originating_am_objects = OriginatingArtefactManagementOriginatingOP.objects(
        orig_am_federation_context_id=federation_id,
        orig_am_artefact_id=artefact_id)

    return originating_am_objects


def create_artefact_originating_op(federation_id, partner_federation_id, body):
    component_spec_list = []
    for c in body.component_spec:

        exposed_interfaces_list = []
        for e in c.exposed_interfaces or []:
            data_ei = {
                "orig_ei_interface_id": e.interface_id,
                "orig_ei_comm_protocol": e.comm_protocol,
                "orig_ei_comm_port": e.comm_port,
                "orig_ei_visibility_type": e.visibility_type,
                "orig_ei_network": e.network,
                "orig_ei_interface_name": e.interface_name
            }
            exposed_interfaces_list.append(data_ei)

        gpu_list = []
        for g in c.compute_resource_profile.gpu or []:
            data_gpu = {
                "orig_g_gpu_vendor_type": g.gpu_vendor_type,
                "orig_g_gpu_mode_name": g.gpu_mode_name,
                "orig_g_gpu_memory": g.gpu_memory,
                "orig_g_num_gpu": g.num_gpu,
            }
            gpu_list.append(data_gpu)

        huge_pages_list = []
        for h in c.compute_resource_profile.hugepages or []:
            data_hp = {
                "orig_h_page_size": h.page_size,
                "orig_h_number": h.number
            }
            huge_pages_list.append(data_hp)

        comp_env_params_list = []
        for cep in c.comp_env_params or []:
            data_comp = {
                "orig_cep_env_var_name": cep.env_var_name,
                "orig_cep_env_value_type": cep.env_value_type,
                "orig_cep_env_var_value": cep.env_var_value,
                "orig_cep_env_var_src": cep.env_var_src
            }
            comp_env_params_list.append(data_comp)

        pv_list = []
        for v in c.persistent_volumes or []:
            data_pv = {
                "orig_pv_volume_size": v.volume_size,
                "orig_pv_volume_mounth_path": v.volume_mount_path,
                "orig_pv_volume_name": v.volume_name,
                "orig_pv_ephemeral_type": v.ephemeral_type,
                "orig_pv_access_mode": v.access_mode,
                "orig_pv_sharing_policy": v.sharing_policy
            }
            pv_list.append(data_pv)

        command_line_params_command = None
        command_line_params_command_args = None
        if c.command_line_params:
            command_line_params_command = c.command_line_params.command
            command_line_params_command_args = c.command_line_params.command_args

        deployment_config_config_type = None
        deployment_config_contents = None
        if c.deployment_config:
            deployment_config_config_type = c.deployment_config.config_type
            deployment_config_contents = c.deployment_config.contents

        data_ce = {
            "orig_ce_component_name": c.component_name,
            "orig_ce_component_spec_images": c.images,
            "orig_ce_component_spec_num_of_instances": c.num_of_instances,
            "orig_ce_component_spec_restart_policy": c.restart_policy,
            "orig_ce_component_spec_command_line_params_command": command_line_params_command,
            "orig_ce_component_spec_command_line_params_command_args": command_line_params_command_args,
            "orig_ce_component_spec_exposed_interfaces": exposed_interfaces_list,
            "orig_ce_component_spec_compute_resource_profile_cpuarchtype": c.compute_resource_profile.cpu_arch_type,
            "orig_ce_component_spec_compute_resource_profile_numcpu": c.compute_resource_profile.num_cpu,
            "orig_ce_component_spec_compute_resource_profile_memory": c.compute_resource_profile.memory,
            "orig_ce_component_spec_compute_resource_profile_diskstorage": c.compute_resource_profile.disk_storage,
            "orig_ce_component_spec_compute_resource_profile_gpu": gpu_list,
            "orig_ce_component_spec_compute_resource_profile_vpu": c.compute_resource_profile.vpu,
            "orig_ce_component_spec_compute_resource_profile_fpga": c.compute_resource_profile.fpga,
            "orig_ce_component_spec_compute_resource_profile_hugepages": huge_pages_list,
            "orig_ce_component_spec_compute_resource_profile_cpuexclusivity": c.compute_resource_profile.cpu_exclusivity,
            "orig_ce_component_spec_comp_env_params": comp_env_params_list,
            "orig_ce_component_spec_deployment_config_config_type": deployment_config_config_type,
            "orig_ce_component_spec_deployment_config_contents": deployment_config_contents,
            "orig_ce_component_spec_persistent_volumes": pv_list
        }
        component_spec_list.append(data_ce)

    artefact_data = {
        "orig_am_federation_context_id": federation_id,
        "orig_am_artefact_id": body.artefact_id,
        "orig_am_app_provider_id": body.app_provider_id,
        "orig_am_artefact_name": body.artefact_name,
        "orig_am_artefact_version_info": body.artefact_version_info,
        "orig_am_artefact_description": body.artefact_description,
        "orig_am_artefact_virt_type": body.artefact_virt_type,
        "orig_am_artefact_filename": body.artefact_file_name,
        "orig_am_artefact_file_format": body.artefact_file_format,
        "orig_am_artefact_descriptor_type": body.artefact_descriptor_type,
        "orig_am_repo_type": body.repo_type,
        "orig_am_artefact_repo_location_repo_url": body.artefact_repo_location.repo_url,
        "orig_am_artefact_repo_location_user_name": body.artefact_repo_location.user_name,
        "orig_am_artefact_repo_location_password": body.artefact_repo_location.password,
        "orig_am_artefact_repo_location_token": body.artefact_repo_location.token,
        "orig_am_artefact_file": "",
        "orig_am_component_spec": json.dumps(component_spec_list),
        "partner_federation_id": partner_federation_id
    }
    # Create a new MongoEngine document and save it to MongoDB
    new_artefact = OriginatingArtefactManagementOriginatingOP(**artefact_data)
    new_artefact.save()
+444 −0

File added.

Preview size limit exceeded, changes collapsed.

+127 −0
Original line number Diff line number Diff line
# -------------------------------------------------------------------------- #
# Copyright 2025-present, Federation Manager, by Software Networks, i2CAT    #
#                                                                            #
# Licensed under the Apache License, Version 2.0 (the "License"); you may    #
# not use this file except in compliance with the License. You may obtain    #
# a copy of the License at                                                   #
#                                                                            #
# http://www.apache.org/licenses/LICENSE-2.0                                 #
#                                                                            #
# Unless required by applicable law or agreed to in writing, software        #
# distributed under the License is distributed on an "AS IS" BASIS,          #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #
# See the License for the specific language governing permissions and        #
# limitations under the License.                                             #
# -------------------------------------------------------------------------- #
from flask import abort
from adapters.error import APIError
from adapters.injector import resolve_adapter
from models.federation_context_id_artefact_body import FederationContextIdArtefactBody
import connexion
import util


def get_artefact(federation_context_id, artefact_id):  # noqa: E501
    """Retrieves details about an artefact.

     # noqa: E501

    :param federation_context_id:
    :type federation_context_id: dict | bytes
    :param artefact_id:
    :type artefact_id: dict | bytes

    :rtype: InlineResponse2005
    """

    # Extract the token and the headers
    bearer_token = util.get_token_from_request(connexion)
    headers = dict(connexion.request.headers)
    partner_api_root = headers.get("X-Partner-Api-Root")

    try:
        adapter = resolve_adapter(headers)
        return adapter.artefact_management.get_artefact(federation_context_id, artefact_id, bearer_token,
                                                        partner_api_root)
    except APIError as error:
        abort(error.status_code, error.detail_error)


def remove_artefact(federation_context_id, artefact_id):  # noqa: E501
    """Removes an artefact from partner OP.

     # noqa: E501

    :param federation_context_id:
    :type federation_context_id: dict | bytes
    :param artefact_id:
    :type artefact_id: dict | bytes

    :rtype: None
    """

    # Extract the token and the headers
    bearer_token = util.get_token_from_request(connexion)
    headers = dict(connexion.request.headers)
    partner_api_root = headers.get("X-Partner-Api-Root")

    try:
        adapter = resolve_adapter(headers)
        return adapter.artefact_management.remove_artefact(federation_context_id, artefact_id, bearer_token,
                                                           partner_api_root)
    except APIError as error:
        abort(error.status_code, error.detail_error)


def upload_artefact(body, federation_context_id):  # noqa: E501
    """Uploads application artefact on partner OP. Artefact is a zip file containing scripts and/or packaging files like Terraform or Helm which are required to create an instance of an application.

     # noqa: E501

    :param artefact_id:
    :type artefact_id: dict | bytes
    :param app_provider_id:
    :type app_provider_id: dict | bytes
    :param artefact_name:
    :type artefact_name: str
    :param artefact_version_info:
    :type artefact_version_info: str
    :param artefact_description:
    :type artefact_description: str
    :param artefact_virt_type:
    :type artefact_virt_type: str
    :param artefact_file_name:
    :type artefact_file_name: str
    :param artefact_file_format:
    :type artefact_file_format: str
    :param artefact_descriptor_type:
    :type artefact_descriptor_type: str
    :param repo_type:
    :type repo_type: str
    :param artefact_repo_location:
    :type artefact_repo_location: dict | bytes
    :param artefact_file:
    :type artefact_file: strstr
    :param component_spec:
    :type component_spec: list | bytes
    :param federation_context_id:
    :type federation_context_id: dict | bytes

    :rtype: None
    """

    # Extract the token and the headers
    bearer_token = util.get_token_from_request(connexion)
    headers = dict(connexion.request.headers)
    partner_api_root = headers.get("X-Partner-Api-Root")

    try:
        body = FederationContextIdArtefactBody.from_dict(connexion.request.get_json())
    except Exception as error:
        raise APIError(422, f"Artefact Validation Error. Message: {error}")

    try:
        adapter = resolve_adapter(headers)
        return adapter.artefact_management.upload_artefact(body, federation_context_id, bearer_token, partner_api_root)
    except APIError as error:
        abort(error.status_code, error.detail_error)
+0 −821

File deleted.

Preview size limit exceeded, changes collapsed.