Skip to content
Snippets Groups Projects
ConfigRule.py 8.69 KiB
Newer Older
  • Learn to ignore specific revisions
  • # Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/)
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    #
    # 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.
    
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    import datetime, json, logging
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    from sqlalchemy import delete
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    #from sqlalchemy.dialects import postgresql
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    from sqlalchemy.dialects.postgresql import insert
    
    Mohammad Ismaeel's avatar
    Mohammad Ismaeel committed
    from sqlalchemy.engine import Engine
    
    from sqlalchemy.orm import Session
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    from typing import Dict, List, Optional, Set
    
    from common.proto.context_pb2 import ConfigRule, ServiceConfigRule, Empty
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    from common.tools.grpc.Tools import grpc_message_to_json_string
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    from .models.enums.ConfigAction import ORM_ConfigActionEnum, grpc_to_enum__config_action
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    from .models.ConfigRuleModel import (
        ConfigRuleKindEnum, DeviceConfigRuleModel, ServiceConfigRuleModel, SliceConfigRuleModel)
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    from .uuids._Builder import get_uuid_from_string
    from .uuids.EndPoint import endpoint_get_uuid
    
    Mohammad Ismaeel's avatar
    Mohammad Ismaeel committed
    from sqlalchemy_cockroachdb import run_transaction
    
    from sqlalchemy.orm import Session, sessionmaker
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    LOGGER = logging.getLogger(__name__)
    
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    def compose_config_rules_data(
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
        config_rules : List[ConfigRule], now : datetime.datetime,
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
        device_uuid : Optional[str] = None, service_uuid : Optional[str] = None, slice_uuid : Optional[str] = None
    ) -> List[Dict]:
        dict_config_rules : List[Dict] = list()
        for position,config_rule in enumerate(config_rules):
            str_kind = config_rule.WhichOneof('config_rule')
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            kind = ConfigRuleKindEnum._member_map_.get(str_kind.upper()) # pylint: disable=no-member
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            dict_config_rule = {
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
                'position'  : position,
                'kind'      : kind,
                'action'    : grpc_to_enum__config_action(config_rule.action),
                'data'      : grpc_message_to_json_string(getattr(config_rule, str_kind, {})),
                'created_at': now,
                'updated_at': now,
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            }
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            parent_kind,parent_uuid = '',None
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            if device_uuid is not None:
                dict_config_rule['device_uuid'] = device_uuid
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
                parent_kind,parent_uuid = 'device',device_uuid
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            elif service_uuid is not None:
                dict_config_rule['service_uuid'] = service_uuid
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
                parent_kind,parent_uuid = 'service',service_uuid
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            elif slice_uuid is not None:
                dict_config_rule['slice_uuid'] = slice_uuid
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
                parent_kind,parent_uuid = 'slice',slice_uuid
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            else:
                MSG = 'Parent for ConfigRule({:s}) cannot be identified '+\
                      '(device_uuid={:s}, service_uuid={:s}, slice_uuid={:s})'
                str_config_rule = grpc_message_to_json_string(config_rule)
                raise Exception(MSG.format(str_config_rule, str(device_uuid), str(service_uuid), str(slice_uuid)))
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            configrule_name = None
            if kind == ConfigRuleKindEnum.CUSTOM:
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
                configrule_name = '{:s}:{:s}:{:s}'.format(parent_kind, kind.value, config_rule.custom.resource_key)
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            elif kind == ConfigRuleKindEnum.ACL:
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
                _, _, endpoint_uuid = endpoint_get_uuid(config_rule.acl.endpoint_id, allow_random=False)
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
                rule_set_name = config_rule.acl.rule_set.name
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
                configrule_name = '{:s}:{:s}:{:s}:{:s}'.format(parent_kind, kind.value, endpoint_uuid, rule_set_name)
    
    Leandro Campos's avatar
    Leandro Campos committed
            elif kind == ConfigRuleKindEnum.PON_ACCESS:
                _, _, endpoint_uuid = endpoint_get_uuid(config_rule.pon_access.endpoint_id, allow_random=False)
                rule_set_name = config_rule.pon_access.rule_set.name
                configrule_name = '{:s}:{:s}:{:s}:{:s}'.format(parent_kind, kind.value, endpoint_uuid, rule_set_name)
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            else:
                MSG = 'Name for ConfigRule({:s}) cannot be inferred '+\
                      '(device_uuid={:s}, service_uuid={:s}, slice_uuid={:s})'
                str_config_rule = grpc_message_to_json_string(config_rule)
                raise Exception(MSG.format(str_config_rule, str(device_uuid), str(service_uuid), str(slice_uuid)))
    
            configrule_uuid = get_uuid_from_string(configrule_name, prefix_for_name=parent_uuid)
            dict_config_rule['configrule_uuid'] = configrule_uuid
    
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            dict_config_rules.append(dict_config_rule)
        return dict_config_rules
    
    def upsert_config_rules(
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
        session : Session, config_rules : List[Dict], is_delete : bool = False,
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
        device_uuid : Optional[str] = None, service_uuid : Optional[str] = None, slice_uuid : Optional[str] = None,
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    ) -> bool:
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
        if device_uuid is not None and service_uuid is None and slice_uuid is None:
            klass = DeviceConfigRuleModel
        elif device_uuid is None and service_uuid is not None and slice_uuid is None:
            klass = ServiceConfigRuleModel
        elif device_uuid is None and service_uuid is None and slice_uuid is not None:
            klass = SliceConfigRuleModel
        else:
            MSG = 'DataModel cannot be identified (device_uuid={:s}, service_uuid={:s}, slice_uuid={:s})'
            raise Exception(MSG.format(str(device_uuid), str(service_uuid), str(slice_uuid)))
    
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
        uuids_to_delete : Set[str] = set()
        uuids_to_upsert : Dict[str, int] = dict()
        rules_to_upsert : List[Dict] = list()
        for config_rule in config_rules:
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            configrule_uuid = config_rule['configrule_uuid']
            configrule_action = config_rule['action']
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            if is_delete or configrule_action == ORM_ConfigActionEnum.DELETE:
                uuids_to_delete.add(configrule_uuid)
            elif configrule_action == ORM_ConfigActionEnum.SET:
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
                position = uuids_to_upsert.get(configrule_uuid)
                if position is None:
                    # if not added, add it
                    rules_to_upsert.append(config_rule)
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
                    uuids_to_upsert[configrule_uuid] = len(rules_to_upsert) - 1
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
                else:
                    # if already added, update occurrence
                    rules_to_upsert[position] = config_rule
            else:
                MSG = 'Action for ConfigRule({:s}) is not supported '+\
                      '(device_uuid={:s}, service_uuid={:s}, slice_uuid={:s})'
                str_config_rule = json.dumps(config_rule)
                raise Exception(MSG.format(str_config_rule, str(device_uuid), str(service_uuid), str(slice_uuid)))
    
        delete_affected = False
        if len(uuids_to_delete) > 0:
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            stmt = delete(klass)
            if device_uuid  is not None: stmt = stmt.where(klass.device_uuid  == device_uuid )
            if service_uuid is not None: stmt = stmt.where(klass.service_uuid == service_uuid)
            if slice_uuid   is not None: stmt = stmt.where(klass.slice_uuid   == slice_uuid  )
            stmt = stmt.where(klass.configrule_uuid.in_(uuids_to_delete))
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            #str_stmt = stmt.compile(dialect=postgresql.dialect(), compile_kwargs={"literal_binds": True})
            #LOGGER.warning('delete stmt={:s}'.format(str(str_stmt)))
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            configrule_deletes = session.execute(stmt)
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            #LOGGER.warning('configrule_deletes.rowcount={:s}'.format(str(configrule_deletes.rowcount)))
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            delete_affected = int(configrule_deletes.rowcount) > 0
    
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
        upsert_affected = False
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
        if len(rules_to_upsert) > 0:
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            stmt = insert(klass).values(rules_to_upsert)
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            stmt = stmt.on_conflict_do_update(
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
                index_elements=[klass.configrule_uuid],
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
                set_=dict(
                    position   = stmt.excluded.position,
                    action     = stmt.excluded.action,
                    data       = stmt.excluded.data,
                    updated_at = stmt.excluded.updated_at,
                )
            )
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            stmt = stmt.returning(klass.created_at, klass.updated_at)
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            #str_stmt = stmt.compile(dialect=postgresql.dialect(), compile_kwargs={"literal_binds": True})
            #LOGGER.warning('upsert stmt={:s}'.format(str(str_stmt)))
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            configrule_updates = session.execute(stmt).fetchall()
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            upsert_affected = any([(updated_at > created_at) for created_at,updated_at in configrule_updates])
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
        return delete_affected or upsert_affected
    
    def delete_config_rule(db_engine : Engine, request : ServiceConfigRule):
        config_rule = request.configrule_custom
        service_id  = request.service_id
        parent_uuid = service_id.service_uuid.uuid
        configrule_name = 'service:custom:{:s}'.format( config_rule.resource_key)
        configrule_uuid = get_uuid_from_string(configrule_name, prefix_for_name=parent_uuid)
        def callback(session : Session) -> bool:
            num_deleted = session.query(ServiceConfigRuleModel).filter_by(configrule_uuid=configrule_uuid).delete()
            return num_deleted > 0
        deleted = run_transaction(sessionmaker(bind=db_engine), callback)
        return Empty()