Scheduled maintenance on Saturday, 27 September 2025, from 07:00 AM to 4:00 PM GMT (09:00 AM to 6:00 PM CEST) - some services may be unavailable -

Skip to content
Snippets Groups Projects
ConstraintModel.py 10.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • # 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.
    
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    import logging, operator
    
    from typing import Dict, List, Tuple, Type, Union
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    from common.orm.Database import Database
    from common.orm.HighLevel import get_or_create_object, update_or_create_object
    from common.orm.backend.Tools import key_to_str
    
    from common.orm.fields.BooleanField import BooleanField
    from common.orm.fields.FloatField import FloatField
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    from common.orm.fields.ForeignKeyField import ForeignKeyField
    from common.orm.fields.IntegerField import IntegerField
    from common.orm.fields.PrimaryKeyField import PrimaryKeyField
    from common.orm.fields.StringField import StringField
    from common.orm.model.Model import Model
    
    from common.proto.context_pb2 import Constraint
    from common.tools.grpc.Tools import grpc_message_to_json_string
    
    from .EndPointModel import EndPointModel, get_endpoint
    from .Tools import fast_hasher, remove_dict_key
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    
    LOGGER = logging.getLogger(__name__)
    
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    class ConstraintsModel(Model): # pylint: disable=abstract-method
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
        pk = PrimaryKeyField()
    
        def dump(self) -> List[Dict]:
            db_constraint_pks = self.references(ConstraintModel)
            constraints = [ConstraintModel(self.database, pk).dump(include_position=True) for pk,_ in db_constraint_pks]
            constraints = sorted(constraints, key=operator.itemgetter('position'))
            return [remove_dict_key(constraint, 'position') for constraint in constraints]
    
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    class ConstraintModel(Model): # pylint: disable=abstract-method
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
        pk = PrimaryKeyField()
        constraints_fk = ForeignKeyField(ConstraintsModel)
        position = IntegerField(min_value=0, required=True)
    
    
        def dump(self, include_position=True) -> Dict: # pylint: disable=arguments-differ
            result = {}
            if include_position: result['position'] = self.position
            return result
    
    class ConstraintCustomModel(ConstraintModel): # pylint: disable=abstract-method
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
        constraint_type = StringField(required=True, allow_empty=False)
        constraint_value = StringField(required=True, allow_empty=False)
    
        def dump(self, include_position=True) -> Dict: # pylint: disable=arguments-differ
            result = {
    
                'custom': {
                    'constraint_type': self.constraint_type,
                    'constraint_value': self.constraint_value,
                },
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            }
    
            result.update(super().dump(include_position=include_position))
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            return result
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    
    
    class ConstraintEndpointLocationModel(ConstraintModel): # pylint: disable=abstract-method
        endpoint_fk = ForeignKeyField(EndPointModel)
    
        def dump(self, include_position=True) -> Dict: # pylint: disable=arguments-differ
            db_endpoints_pks = list(self.references(EndPointModel))
            num_endpoints = len(db_endpoints_pks)
            if num_endpoints != 1:
                raise Exception('Wrong number({:d}) of associated Endpoints with constraint'.format(num_endpoints))
            db_endpoint = EndPointModel(self.database, db_endpoints_pks[0])
            result = {'endpoint_id': db_endpoint.dump_id()}
            result.update(super().dump(include_position=include_position))
            return result
    
    class ConstraintEndpointLocationRegionModel(ConstraintEndpointLocationModel): # pylint: disable=abstract-method
        region = StringField(required=True, allow_empty=False)
    
        def dump(self, include_position=True) -> Dict: # pylint: disable=arguments-differ
            result = super().dump(include_position=include_position)
            result['endpoint_location'].setdefault('region', {}).setdefault('region', self.region)
            return result
    
    class ConstraintEndpointLocationGpsPositionModel(ConstraintEndpointLocationModel): # pylint: disable=abstract-method
        latitude = FloatField(required=True, min_value=-90.0, max_value=90.0)
        longitude = FloatField(required=True, min_value=-180.0, max_value=180.0)
    
        def dump(self, include_position=True) -> Dict: # pylint: disable=arguments-differ
            result = super().dump(include_position=include_position)
            gps_position = result['endpoint_location'].setdefault('gps_position', {})
            gps_position['latitude' ] = self.latitude
            gps_position['longitude'] = self.longitude
            return result
    
    class ConstraintEndpointPriorityModel(ConstraintModel): # pylint: disable=abstract-method
        endpoint_fk = ForeignKeyField(EndPointModel)
        priority = FloatField(required=True)
    
        def dump(self, include_position=True) -> Dict: # pylint: disable=arguments-differ
            db_endpoints_pks = list(self.references(EndPointModel))
            num_endpoints = len(db_endpoints_pks)
            if num_endpoints != 1:
                raise Exception('Wrong number({:d}) of associated Endpoints with constraint'.format(num_endpoints))
            db_endpoint = EndPointModel(self.database, db_endpoints_pks[0])
            result = {'endpoint_id': db_endpoint.dump_id(), 'priority': self.priority}
            result.update(super().dump(include_position=include_position))
            return result
    
    class ConstraintSlaAvailabilityModel(ConstraintModel): # pylint: disable=abstract-method
        num_disjoint_paths = IntegerField(required=True, min_value=1)
        all_active = BooleanField(required=True)
    
        def dump(self, include_position=True) -> Dict: # pylint: disable=arguments-differ
            result = {'num_disjoint_paths': self.num_disjoint_paths, 'all_active': self.all_active}
            result.update(super().dump(include_position=include_position))
            return result
    
    def parse_constraint_custom(database : Database, grpc_constraint) -> Tuple[Type, str, Dict]:
        constraint_class = ConstraintCustomModel
        str_constraint_id = grpc_constraint.custom.constraint_type
        constraint_data = {
            'constraint_type' : grpc_constraint.custom.constraint_type,
            'constraint_value': grpc_constraint.custom.constraint_value,
        }
        return constraint_class, str_constraint_id, constraint_data
    
    def parse_constraint_endpoint_location(database : Database, grpc_constraint) -> Tuple[Type, str, Dict]:
        grpc_endpoint_id = grpc_constraint.endpoint_location.endpoint_id
        str_endpoint_key, db_endpoint = get_endpoint(database, grpc_endpoint_id)
    
        str_constraint_id = str_endpoint_key
        constraint_data = {'endpoint_fk': db_endpoint}
    
        grpc_location = grpc_constraint.endpoint_location.location
        location_kind = str(grpc_location.WhichOneof('location'))
        if location_kind == 'region':
            constraint_class = ConstraintEndpointLocationRegionModel
            constraint_data.update({'region': grpc_location.region})
        elif location_kind == 'gps_position':
            constraint_class = ConstraintEndpointLocationGpsPositionModel
            gps_position = grpc_location.gps_position
            constraint_data.update({'latitude': gps_position.latitude, 'longitude': gps_position.longitude})
        else:
            MSG = 'Location kind {:s} in Constraint of kind endpoint_location is not implemented: {:s}'
            raise NotImplementedError(MSG.format(location_kind, grpc_message_to_json_string(grpc_constraint)))
        return constraint_class, str_constraint_id, constraint_data
    
    def parse_constraint_endpoint_priority(database : Database, grpc_constraint) -> Tuple[Type, str, Dict]:
        grpc_endpoint_id = grpc_constraint.endpoint_priority.endpoint_id
        str_endpoint_key, db_endpoint = get_endpoint(database, grpc_endpoint_id)
    
        constraint_class = ConstraintEndpointPriorityModel
        str_constraint_id = str_endpoint_key
        priority = grpc_constraint.endpoint_priority.priority
        constraint_data = {'endpoint_fk': db_endpoint, 'priority': priority}
    
        return constraint_class, str_constraint_id, constraint_data
    
    def parse_constraint_sla_availability(database : Database, grpc_constraint) -> Tuple[Type, str, Dict]:
        constraint_class = ConstraintSlaAvailabilityModel
        str_constraint_id = ''
        constraint_data = {
            'num_disjoint_paths' : grpc_constraint.sla_availability.num_disjoint_paths,
            'all_active': grpc_constraint.sla_availability.all_active,
        }
        return constraint_class, str_constraint_id, constraint_data
    
    CONSTRAINT_PARSERS = {
        'custom'            : parse_constraint_custom,
        'endpoint_location' : parse_constraint_endpoint_location,
        'endpoint_priority' : parse_constraint_endpoint_priority,
        'sla_availability'  : parse_constraint_sla_availability,
    }
    
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    def set_constraint(
        database : Database, db_constraints : ConstraintsModel, grpc_constraint, position : int
    
    ) -> Tuple[Constraint, bool]:
    
        constraint_kind = str(grpc_constraint.WhichOneof('constraint'))
    
        parser = CONSTRAINT_PARSERS.get(constraint_kind)
        if parser is None:
            raise NotImplementedError('Constraint of kind {:s} is not implemented: {:s}'.format(
                constraint_kind, grpc_message_to_json_string(grpc_constraint)))
    
        constraint_class, str_constraint_id, constraint_data = parser(database, grpc_constraint)
    
        str_constraint_key_hash = fast_hasher(':'.join([constraint_kind, str_constraint_id]))
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
        str_constraint_key = key_to_str([db_constraints.pk, str_constraint_key_hash], separator=':')
    
        constraint_data.update({'constraints_fk': db_constraints, 'position': position})
    
        result : Tuple[ConstraintModel, bool] = update_or_create_object(
            database, constraint_class, str_constraint_key, constraint_data)
        db_constraint, updated = result
        return db_constraint, updated
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    
    def set_constraints(
        database : Database, db_parent_pk : str, constraints_name : str, grpc_constraints
    
    ) -> List[Tuple[Union[ConstraintsModel, ConstraintModel], bool]]:
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    
        str_constraints_key = key_to_str([db_parent_pk, constraints_name], separator=':')
        result : Tuple[ConstraintsModel, bool] = get_or_create_object(database, ConstraintsModel, str_constraints_key)
        db_constraints, created = result
    
        db_objects = [(db_constraints, created)]
    
        for position,grpc_constraint in enumerate(grpc_constraints):
            result : Tuple[ConstraintModel, bool] = set_constraint(database, db_constraints, grpc_constraint, position)
            db_constraint, updated = result
            db_objects.append((db_constraint, updated))
    
        return db_objects