Commit 03f6ef54 authored by Stavros-Anastasios Charismiadis's avatar Stavros-Anastasios Charismiadis
Browse files

Add interconnection procedure in DELETE method of Publish API

parent aa7c6098
Loading
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -27,6 +27,10 @@ class AuthManager(Resource):

        auth_context = cert_col.find_one({"id":apf_id})

        # Service APIs published by an interconnected CCF have no certificate entry
        if auth_context is None:
            return

        if "services" in auth_context["resources"]:
            if service_id in auth_context["resources"]["services"]:
                auth_context["resources"]["services"].remove(service_id)
+102 −3
Original line number Diff line number Diff line
@@ -22,6 +22,9 @@ import encoder
TOTAL_FEATURES = 10
SUPPORTED_FEATURES_HEX = "120"

# Seconds allowed for a single request towards a peer CCF
INTERCONNECTION_TIMEOUT = 30

publisher_ops = Publisher()


@@ -189,10 +192,10 @@ class PublishServiceOperations(Resource):
                )

            # 1. Check shareableInfo 
            if serviceapidescription.shareable_info.is_shareable: 
            if serviceapidescription.shareable_info and serviceapidescription.shareable_info.is_shareable: 
                interconnected_col = self.db.get_col_by_name(self.db.interconnected)
                # 2. Iterate through capifProvDoms
                for dom in serviceapidescription.shareable_info.capif_prov_doms:   
                for dom in serviceapidescription.shareable_info.capif_prov_doms or []:   
                    # 3. For each domain, check if there is record in the "interconnected" table
                    interconnected_ccf = interconnected_col.find_one({"dst_prov_dom": dom}) 
                    # 4. If yes, make a publish request with CCF certificate
@@ -298,6 +301,90 @@ class PublishServiceOperations(Resource):
            current_app.logger.error(exception + "::" + str(e))
            return internal_server_error(detail=exception, cause=str(e))

    def unpublish_from_interconnected_ccfs(self, api_name, capif_prov_doms):
        """Unpublish a shared service API from every interconnected CCF holding a copy.

        A peer assigns its own api_id to the copy it stores, and that id is never kept
        locally, so it is resolved by api_name out of the service APIs this CCF published
        there. Returns an error response if a peer could not be reached or refused the
        removal, None if every copy is gone.
        """
        interconnected_col = self.db.get_col_by_name(self.db.interconnected)
        config_col = self.db.get_col_by_name(self.db.capif_configuration)
        config = config_col.find_one({}, {"_id": 0})
        ccf_id = config['ccf_id']

        headers = {'accept': 'application/json'}
        certs = ('certs/server.crt', 'certs/server.key')

        for dom in capif_prov_doms:
            interconnected_ccf = interconnected_col.find_one({"dst_prov_dom": dom})
            if interconnected_ccf is None:
                continue

            url = 'https://{}/published-apis/v1/{}/service-apis'.format(dom, ccf_id)

            try:
                response = requests.request("GET", url, headers=headers, cert=certs,
                                            verify='certs/ca.crt',
                                            timeout=INTERCONNECTION_TIMEOUT)
            except requests.exceptions.RequestException as exc:
                current_app.logger.error(
                    "Interconnection: listing service apis on {} failed: {}".format(dom, str(exc)))
                return internal_server_error(
                    detail="Could not reach interconnected CCF {}".format(dom),
                    cause="Service API is still published on an interconnected CCF")

            try:
                shared_apis = response.json()
            except ValueError:
                shared_apis = None

            if response.status_code != 200 or not isinstance(shared_apis, list):
                current_app.logger.error(
                    "Interconnection: {} answered the service api listing with status {}".format(
                        dom, response.status_code))
                return internal_server_error(
                    detail="Interconnected CCF {} did not return its published service apis".format(dom),
                    cause="Service API is still published on an interconnected CCF")

            # The listing is serialized in camel case by the peer
            shared_api_id = next(
                (shared_api.get("apiId") for shared_api in shared_apis
                 if isinstance(shared_api, dict) and shared_api.get("apiName") == api_name),
                None)

            if shared_api_id is None:
                current_app.logger.warning(
                    "Interconnection: service api {} is not published on {}".format(api_name, dom))
                continue

            try:
                response = requests.request("DELETE", '{}/{}'.format(url, shared_api_id),
                                            headers=headers, cert=certs,
                                            verify='certs/ca.crt',
                                            timeout=INTERCONNECTION_TIMEOUT)
            except requests.exceptions.RequestException as exc:
                current_app.logger.error(
                    "Interconnection: unpublish from {} failed: {}".format(dom, str(exc)))
                return internal_server_error(
                    detail="Could not reach interconnected CCF {}".format(dom),
                    cause="Service API is still published on an interconnected CCF")

            # 404 means the peer already dropped its copy
            if response.status_code not in (204, 404):
                current_app.logger.error(
                    "Interconnection: {} refused to unpublish service api {} with status {}".format(
                        dom, shared_api_id, response.status_code))
                return internal_server_error(
                    detail="Interconnected CCF {} refused to unpublish the service api".format(dom),
                    cause="Service API is still published on an interconnected CCF")

            current_app.logger.debug(
                "Interconnection: service api {} unpublished from {}".format(api_name, dom))

        return None

    def delete_serviceapidescription(self, service_api_id, apf_id):

        mycol = self.db.get_col_by_name(self.db.service_api_descriptions)
@@ -327,8 +414,20 @@ class PublishServiceOperations(Resource):
                    detail="Service API not existing",
                    cause="Service API id not found")

            # The copies held by interconnected CCFs go first, so that a peer that cannot
            # be reached leaves the service api published here instead of orphaned there
            shareable_info = serviceapidescription_dict.get("shareable_info") or {}
            if shareable_info.get("is_shareable"):
                interconnection_error = self.unpublish_from_interconnected_ccfs(
                    serviceapidescription_dict.get("api_name"),
                    shareable_info.get("capif_prov_doms") or [])

                if interconnection_error is not None:
                    return interconnection_error

            mycol.delete_one(my_query)

            if "APF" in apf_id:
                self.auth_manager.remove_auth_service(service_api_id, apf_id)

            current_app.logger.info("Removed service from database")