Skip to content
Snippets Groups Projects
Connection.py 6.79 KiB
Newer Older
  • Learn to ignore specific revisions
  • Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    # Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
    #
    # 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 re
    from sqlalchemy.dialects.postgresql import insert
    from sqlalchemy.engine import Engine
    from sqlalchemy.exc import IntegrityError
    from sqlalchemy.orm import Session, sessionmaker
    from sqlalchemy_cockroachdb import run_transaction
    from typing import Dict, List, Optional, Tuple
    from common.proto.context_pb2 import Connection, ConnectionId, ConnectionIdList, ConnectionList, ServiceId
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    from common.method_wrappers.ServiceExceptions import NotFoundException
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    from common.tools.grpc.Tools import grpc_message_to_json_string
    from common.tools.object_factory.Connection import json_connection_id
    from .models.ConnectionModel import ConnectionEndPointModel, ConnectionModel, ConnectionSubServiceModel
    from .uuids.Connection import connection_get_uuid
    from .uuids.EndPoint import endpoint_get_uuid
    from .uuids.Service import service_get_uuid
    
    def connection_list_ids(db_engine : Engine, request : ServiceId) -> ConnectionIdList:
        _,service_uuid = service_get_uuid(request, allow_random=False)
        def callback(session : Session) -> List[Dict]:
            obj_list : List[ConnectionModel] = session.query(ConnectionModel).filter_by(service_uuid=service_uuid).all()
            #.options(selectinload(ContextModel.connection)).filter_by(context_uuid=context_uuid).one_or_none()
            return [obj.dump_id() for obj in obj_list]
        return ConnectionIdList(connection_ids=run_transaction(sessionmaker(bind=db_engine), callback))
    
    def connection_list_objs(db_engine : Engine, request : ServiceId) -> ConnectionList:
        _,service_uuid = service_get_uuid(request, allow_random=False)
        def callback(session : Session) -> List[Dict]:
            obj_list : List[ConnectionModel] = session.query(ConnectionModel).filter_by(service_uuid=service_uuid).all()
            #.options(selectinload(ContextModel.connection)).filter_by(context_uuid=context_uuid).one_or_none()
            return [obj.dump() for obj in obj_list]
        return ConnectionList(connections=run_transaction(sessionmaker(bind=db_engine), callback))
    
    def connection_get(db_engine : Engine, request : ConnectionId) -> Connection:
        connection_uuid = connection_get_uuid(request, allow_random=False)
        def callback(session : Session) -> Optional[Dict]:
            obj : Optional[ConnectionModel] = session.query(ConnectionModel)\
                .filter_by(connection_uuid=connection_uuid).one_or_none()
            return None if obj is None else obj.dump()
        obj = run_transaction(sessionmaker(bind=db_engine), callback)
        if obj is None:
            raise NotFoundException('Connection', request.connection_uuid.uuid, extra_details=[
                'connection_uuid generated was: {:s}'.format(connection_uuid),
            ])
        return Connection(**obj)
    
    def connection_set(db_engine : Engine, request : Connection) -> Tuple[ConnectionId, bool]:
        connection_uuid = connection_get_uuid(request.connection_id, allow_random=True)
        _,service_uuid = service_get_uuid(request.service_id, allow_random=False)
        settings = grpc_message_to_json_string(request.settings),
    
        connection_data = [{
            'connection_uuid': connection_uuid,
            'service_uuid'   : service_uuid,
            'settings'       : settings,
        }]
    
        connection_endpoints_data : List[Dict] = list()
        for position,endpoint_id in enumerate(request.path_hops_endpoint_ids):
            _, _, endpoint_uuid = endpoint_get_uuid(endpoint_id, allow_random=False)
            connection_endpoints_data.append({
                'connection_uuid': connection_uuid,
                'endpoint_uuid'  : endpoint_uuid,
                'position'       : position,
            })
    
        connection_subservices_data : List[Dict] = list()
        for i,service_id in enumerate(request.sub_service_ids):
            _, service_uuid = service_get_uuid(service_id, allow_random=False)
            connection_subservices_data.append({
                'connection_uuid': connection_uuid,
                'subservice_uuid': service_uuid,
            })
    
        def callback(session : Session) -> None:
            stmt = insert(ConnectionModel).values(connection_data)
            stmt = stmt.on_conflict_do_update(
                index_elements=[ConnectionModel.connection_uuid],
                set_=dict(settings = stmt.excluded.settings)
            )
            session.execute(stmt)
    
            if len(connection_endpoints_data) > 0:
                stmt = insert(ConnectionEndPointModel).values(connection_endpoints_data)
                stmt = stmt.on_conflict_do_nothing(
                    index_elements=[ConnectionEndPointModel.connection_uuid, ConnectionEndPointModel.endpoint_uuid]
                )
                try:
                    session.execute(stmt)
                except IntegrityError as e:
                    str_args = ''.join(e.args).replace('\n', ' ')
                    pattern_fkv = \
                        r'\(psycopg2.errors.ForeignKeyViolation\) '\
                        r'insert on table \"([^\"]+)\" violates foreign key constraint '\
                        r'.+DETAIL\:  Key \([^\)]+\)\=\([\'\"]*([^\)\'\"]+)[\'\"]*\) is not present in table \"([^\"]+)\"'
                    m_fkv = re.match(pattern_fkv, str_args)
                    if m_fkv is not None:
                        insert_table, primary_key, origin_table = m_fkv.groups()
                        raise NotFoundException(origin_table, primary_key, extra_details=[
                            'while inserting in table "{:s}"'.format(insert_table)
                        ]) from e
                    else:
                        raise
    
            if len(connection_subservices_data) > 0:
                stmt = insert(ConnectionSubServiceModel).values(connection_subservices_data)
                stmt = stmt.on_conflict_do_nothing(
                    index_elements=[ConnectionSubServiceModel.connection_uuid, ConnectionSubServiceModel.subservice_uuid]
                )
                session.execute(stmt)
    
        run_transaction(sessionmaker(bind=db_engine), callback)
        updated = False # TODO: improve and check if created/updated
        return ConnectionId(**json_connection_id(connection_uuid)),updated
    
    def connection_delete(db_engine : Engine, request : ConnectionId) -> bool:
        connection_uuid = connection_get_uuid(request, allow_random=False)
        def callback(session : Session) -> bool:
            num_deleted = session.query(ConnectionModel).filter_by(connection_uuid=connection_uuid).delete()
            return num_deleted > 0
        return run_transaction(sessionmaker(bind=db_engine), callback)