Skip to content
Snippets Groups Projects
Constraint.py 7.63 KiB
Newer Older
# Copyright 2022-2024 ETSI OSG/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.

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
from sqlalchemy.orm import Session
from typing import Dict, List, Optional, Set
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
from common.proto.context_pb2 import Constraint
from common.tools.grpc.Tools import grpc_message_to_json_string
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
from .models.ConstraintModel import ConstraintKindEnum, ServiceConstraintModel, SliceConstraintModel
from .models.enums.ConstraintAction import ORM_ConstraintActionEnum, grpc_to_enum__constraint_action
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
from .uuids._Builder import get_uuid_from_string
from .uuids.EndPoint import endpoint_get_uuid
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
LOGGER = logging.getLogger(__name__)

Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
def compose_constraints_data(
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
    constraints : List[Constraint], now : datetime.datetime,
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
    service_uuid : Optional[str] = None, slice_uuid : Optional[str] = None
) -> List[Dict]:
    dict_constraints : List[Dict] = list()
    for position,constraint in enumerate(constraints):
        str_kind = constraint.WhichOneof('constraint')
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
        kind = ConstraintKindEnum._member_map_.get(str_kind.upper()) # pylint: disable=no-member
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
        dict_constraint = {
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
            'position'  : position,
            'kind'      : kind,
            'action'    : grpc_to_enum__constraint_action(constraint.action),
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
            'data'      : grpc_message_to_json_string(getattr(constraint, 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
        if service_uuid is not None:
            dict_constraint['service_uuid'] = service_uuid
            parent_kind,parent_uuid = 'service',service_uuid
        elif slice_uuid is not None:
            dict_constraint['slice_uuid'] = slice_uuid
            parent_kind,parent_uuid = 'slice',slice_uuid
        else:
            MSG = 'Parent for Constraint({:s}) cannot be identified '+\
                  '(service_uuid={:s}, slice_uuid={:s})'
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
            str_constraint = grpc_message_to_json_string(constraint)
            raise Exception(MSG.format(str_constraint, str(service_uuid), str(slice_uuid)))

        constraint_name = None
        if kind == ConstraintKindEnum.CUSTOM:
            constraint_name = '{:s}:{:s}:{:s}'.format(parent_kind, kind.value, constraint.custom.constraint_type)
        elif kind == ConstraintKindEnum.ENDPOINT_LOCATION:
            _, _, endpoint_uuid = endpoint_get_uuid(constraint.endpoint_location.endpoint_id, allow_random=False)
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
            location_kind = constraint.endpoint_location.location.WhichOneof('location')
            constraint_name = '{:s}:{:s}:{:s}:{:s}'.format(parent_kind, kind.value, endpoint_uuid, location_kind)
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
        elif kind == ConstraintKindEnum.ENDPOINT_PRIORITY:
            _, _, endpoint_uuid = endpoint_get_uuid(constraint.endpoint_priority.endpoint_id, allow_random=False)
            constraint_name = '{:s}:{:s}:{:s}'.format(parent_kind, kind.value, endpoint_uuid)
        elif kind in {
            ConstraintKindEnum.SCHEDULE, ConstraintKindEnum.SLA_CAPACITY, ConstraintKindEnum.SLA_LATENCY,
            ConstraintKindEnum.SLA_AVAILABILITY, ConstraintKindEnum.SLA_ISOLATION, ConstraintKindEnum.EXCLUSIONS,
            ConstraintKindEnum.QOS_PROFILE
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
        }:
            constraint_name = '{:s}:{:s}:'.format(parent_kind, kind.value)
        else:
            MSG = 'Name for Constraint({:s}) cannot be inferred '+\
                  '(service_uuid={:s}, slice_uuid={:s})'
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
            str_constraint = grpc_message_to_json_string(constraint)
            raise Exception(MSG.format(str_constraint, str(service_uuid), str(slice_uuid)))

        constraint_uuid = get_uuid_from_string(constraint_name, prefix_for_name=parent_uuid)
        dict_constraint['constraint_uuid'] = constraint_uuid

Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
        dict_constraints.append(dict_constraint)
    return dict_constraints

def upsert_constraints(
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
    session : Session, constraints : List[Dict], is_delete : bool = False,
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
    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 service_uuid is not None and slice_uuid is None:
        klass = ServiceConstraintModel
    elif service_uuid is None and slice_uuid is not None:
        klass = SliceConstraintModel
    else:
        MSG = 'DataModel cannot be identified (service_uuid={:s}, slice_uuid={:s})'
        raise Exception(MSG.format(str(service_uuid), str(slice_uuid)))
    uuids_to_delete : Set[str] = set()
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
    uuids_to_upsert : Dict[str, int] = dict()
    rules_to_upsert : List[Dict] = list()
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
    for constraint in constraints:
        constraint_uuid = constraint['constraint_uuid']
        constraint_action = constraint['action']
        if is_delete or constraint_action == ORM_ConstraintActionEnum.DELETE:
            uuids_to_delete.add(constraint_uuid)
        elif constraint_action == ORM_ConstraintActionEnum.SET:
            position = uuids_to_upsert.get(constraint_uuid)
            if position is None:
                # if not added, add it
                rules_to_upsert.append(constraint)
                uuids_to_upsert[constraint_uuid] = len(rules_to_upsert) - 1
            else:
                # if already added, update occurrence
                rules_to_upsert[position] = constraint
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
        else:
            MSG = 'Action for ConstraintRule({:s}) is not supported (service_uuid={:s}, slice_uuid={:s})'
            LOGGER.warning(MSG.format(str(constraint), str(service_uuid), str(slice_uuid)))
            # raise Exception(MSG.format(str_constraint, str(service_uuid), str(slice_uuid)))
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed

    delete_affected = False
    if len(uuids_to_delete) > 0:
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
        stmt = delete(klass)
        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.constraint_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)))
        constraint_deletes = session.execute(stmt)
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
        #LOGGER.warning('constraint_deletes.rowcount={:s}'.format(str(constraint_deletes.rowcount)))
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
        delete_affected = int(constraint_deletes.rowcount) > 0
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed

Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
    upsert_affected = False
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
    if not is_delete and len(constraints) > 0:
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
        stmt = insert(klass).values(constraints)
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.constraint_uuid],
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
            set_=dict(
                position   = stmt.excluded.position,
                action     = stmt.excluded.action,
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
                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
        constraint_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 constraint_updates])
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed

Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
    return delete_affected or upsert_affected