diff --git a/scripts/run_tests_locally-context.sh b/scripts/run_tests_locally-context.sh index 9e5ac4b92b5d55509173b23f0896cb108bdd3a1e..a9e601208aa9259219708a5e1ca770232e44faa6 100755 --- a/scripts/run_tests_locally-context.sh +++ b/scripts/run_tests_locally-context.sh @@ -22,6 +22,7 @@ RCFILE=$PROJECTDIR/coverage/.coveragerc K8S_NAMESPACE="tf-dev" K8S_HOSTNAME="kubernetes-master" +kubectl --namespace $K8S_NAMESPACE expose deployment contextservice --port=6379 --name=redis-tests --type=NodePort export REDIS_SERVICE_HOST=$(kubectl get node $K8S_HOSTNAME -o 'jsonpath={.status.addresses[?(@.type=="InternalIP")].address}') export REDIS_SERVICE_PORT=$(kubectl get service redis-tests --namespace $K8S_NAMESPACE -o 'jsonpath={.spec.ports[?(@.port==6379)].nodePort}') diff --git a/src/context/service/database/ConfigModel.py b/src/context/service/database/ConfigModel.py index e36fc58cfa2a42425004f6c9766457c5d1c53896..bb2a37467ce3ad451bd29f824a5092ec1ad43cee 100644 --- a/src/context/service/database/ConfigModel.py +++ b/src/context/service/database/ConfigModel.py @@ -41,6 +41,11 @@ grpc_to_enum__config_action = functools.partial( class ConfigModel(Model): # pylint: disable=abstract-method pk = PrimaryKeyField() + def delete(self) -> None: + db_config_rule_pks = self.references(ConfigRuleModel) + for pk,_ in db_config_rule_pks: ConfigRuleModel(self.database, pk).delete() + super().delete() + def dump(self) -> List[Dict]: db_config_rule_pks = self.references(ConfigRuleModel) config_rules = [ConfigRuleModel(self.database, pk).dump(include_position=True) for pk,_ in db_config_rule_pks] diff --git a/src/context/service/database/ConnectionModel.py b/src/context/service/database/ConnectionModel.py index 1d3f093727fc59b06b13e9f39636bbd9da3b010a..4cbed43a40f3538633216f09060f8a2483fe5e1f 100644 --- a/src/context/service/database/ConnectionModel.py +++ b/src/context/service/database/ConnectionModel.py @@ -32,6 +32,11 @@ LOGGER = logging.getLogger(__name__) class PathModel(Model): # pylint: disable=abstract-method pk = PrimaryKeyField() + def delete(self) -> None: + for db_path_hop_pk,_ in self.references(PathHopModel): + PathHopModel(self.database, db_path_hop_pk).delete() + super().delete() + def dump(self) -> List[Dict]: db_path_hop_pks = self.references(PathHopModel) path_hops = [PathHopModel(self.database, pk).dump(include_position=True) for pk,_ in db_path_hop_pks] @@ -56,6 +61,18 @@ class ConnectionModel(Model): service_fk = ForeignKeyField(ServiceModel, required=False) path_fk = ForeignKeyField(PathModel, required=True) + def delete(self) -> None: + # pylint: disable=import-outside-toplevel + from .RelationModels import ConnectionSubServiceModel + + # Do not remove sub-services automatically. They are supported by real services, so Service component should + # deal with the correct removal workflow to deconfigure the devices. + for db_connection_sub_service_pk,_ in self.references(ConnectionSubServiceModel): + ConnectionSubServiceModel(self.database, db_connection_sub_service_pk).delete() + + super().delete() + PathModel(self.database, self.path_fk).delete() + def dump_id(self) -> Dict: return { 'connection_uuid': {'uuid': self.connection_uuid}, @@ -108,7 +125,7 @@ def set_path( str_path_key = connection_uuid if len(path_name) == 0 else key_to_str([connection_uuid, path_name], separator=':') result : Tuple[PathModel, bool] = get_or_create_object(database, PathModel, str_path_key) - db_path, created = result + db_path, created = result # pylint: disable=unused-variable db_path_hop_pks : Set[str] = set(map(operator.itemgetter(0), db_path.references(PathHopModel))) db_objects : List[Tuple[Union[PathModel, PathHopModel], bool]] = [db_path] @@ -127,7 +144,7 @@ def set_path( db_endpoint : EndPointModel = get_object(database, EndPointModel, str_endpoint_key) result : Tuple[PathHopModel, bool] = set_path_hop(database, db_path, position, db_endpoint) - db_path_hop, updated = result + db_path_hop, updated = result # pylint: disable=unused-variable db_objects.append(db_path_hop) db_path_hop_pks.discard(db_path_hop.instance_key) diff --git a/src/context/service/database/ConstraintModel.py b/src/context/service/database/ConstraintModel.py index 61310dd57c710d84e500c25bb83e6f59ce78fb94..a35ec250d8a62a8a2534e9f27ddecac801db6eff 100644 --- a/src/context/service/database/ConstraintModel.py +++ b/src/context/service/database/ConstraintModel.py @@ -13,11 +13,13 @@ # limitations under the License. import logging, operator -from typing import Dict, List, Tuple, Type, Union +from enum import Enum +from typing import Dict, List, Optional, Tuple, Type, Union from common.orm.Database import Database -from common.orm.HighLevel import get_or_create_object, update_or_create_object +from common.orm.HighLevel import get_object, 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.EnumeratedField import EnumeratedField from common.orm.fields.FloatField import FloatField from common.orm.fields.ForeignKeyField import ForeignKeyField from common.orm.fields.IntegerField import IntegerField @@ -34,101 +36,123 @@ LOGGER = logging.getLogger(__name__) class ConstraintsModel(Model): # pylint: disable=abstract-method pk = PrimaryKeyField() + def delete(self) -> None: + db_constraint_pks = self.references(ConstraintModel) + for pk,_ in db_constraint_pks: ConstraintModel(self.database, pk).delete() + super().delete() + 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] -class ConstraintModel(Model): # pylint: disable=abstract-method - 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 +class ConstraintCustomModel(Model): # pylint: disable=abstract-method 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, - }, - } - result.update(super().dump(include_position=include_position)) - return result - -class ConstraintEndpointLocationModel(ConstraintModel): # pylint: disable=abstract-method + def dump(self) -> Dict: # pylint: disable=arguments-differ + return {'custom': {'constraint_type': self.constraint_type, 'constraint_value': self.constraint_value}} + +Union_ConstraintEndpoint = Union[ + 'ConstraintEndpointLocationGpsPositionModel', 'ConstraintEndpointLocationRegionModel', + 'ConstraintEndpointPriorityModel' +] +def dump_endpoint_id(endpoint_constraint : Union_ConstraintEndpoint): + db_endpoints_pks = list(endpoint_constraint.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(endpoint_constraint.database, db_endpoints_pks[0]) + return db_endpoint.dump_id() + +class ConstraintEndpointLocationRegionModel(Model): # 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 + def dump(self) -> Dict: # pylint: disable=arguments-differ + return {'endpoint_location': {'endpoint_id': dump_endpoint_id(self), 'region': self.region}} -class ConstraintEndpointLocationGpsPositionModel(ConstraintEndpointLocationModel): # pylint: disable=abstract-method +class ConstraintEndpointLocationGpsPositionModel(Model): # pylint: disable=abstract-method + endpoint_fk = ForeignKeyField(EndPointModel) 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 + def dump(self) -> Dict: # pylint: disable=arguments-differ + gps_position = {'latitude': self.latitude, 'longitude': self.longitude} + return {'endpoint_location': {'endpoint_id': dump_endpoint_id(self), 'gps_position': gps_position}} -class ConstraintEndpointPriorityModel(ConstraintModel): # pylint: disable=abstract-method +class ConstraintEndpointPriorityModel(Model): # 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 + def dump(self) -> Dict: # pylint: disable=arguments-differ + return {'endpoint_priority': {'endpoint_id': dump_endpoint_id(self), 'priority': self.priority}} -class ConstraintSlaAvailabilityModel(ConstraintModel): # pylint: disable=abstract-method +class ConstraintSlaAvailabilityModel(Model): # pylint: disable=abstract-method num_disjoint_paths = IntegerField(required=True, min_value=1) all_active = BooleanField(required=True) + def dump(self) -> Dict: # pylint: disable=arguments-differ + return {'sla_availability': {'num_disjoint_paths': self.num_disjoint_paths, 'all_active': self.all_active}} + +# enum values should match name of field in ConstraintModel +class ConstraintKindEnum(Enum): + CUSTOM = 'custom' + ENDPOINT_LOCATION_REGION = 'ep_loc_region' + ENDPOINT_LOCATION_GPSPOSITION = 'ep_loc_gpspos' + ENDPOINT_PRIORITY = 'ep_priority' + SLA_AVAILABILITY = 'sla_avail' + +Union_SpecificConstraint = Union[ + ConstraintCustomModel, ConstraintEndpointLocationRegionModel, ConstraintEndpointLocationGpsPositionModel, + ConstraintEndpointPriorityModel, ConstraintSlaAvailabilityModel, +] + +class ConstraintModel(Model): # pylint: disable=abstract-method + pk = PrimaryKeyField() + constraints_fk = ForeignKeyField(ConstraintsModel) + kind = EnumeratedField(ConstraintKindEnum) + position = IntegerField(min_value=0, required=True) + constraint_custom_fk = ForeignKeyField(ConstraintCustomModel, required=False) + constraint_ep_loc_region_fk = ForeignKeyField(ConstraintEndpointLocationRegionModel, required=False) + constraint_ep_loc_gpspos_fk = ForeignKeyField(ConstraintEndpointLocationGpsPositionModel, required=False) + constraint_ep_priority_fk = ForeignKeyField(ConstraintEndpointPriorityModel, required=False) + constraint_sla_avail_fk = ForeignKeyField(ConstraintSlaAvailabilityModel, required=False) + + def delete(self) -> None: + field_name = 'constraint_{:s}_fk'.format(str(self.kind.value)) + specific_fk_value : Optional[ForeignKeyField] = getattr(self, field_name, None) + if specific_fk_value is None: + raise Exception('Unable to find constraint key for field_name({:s})'.format(field_name)) + specific_fk_class = getattr(ConstraintModel, field_name, None) + foreign_model_class : Model = specific_fk_class.foreign_model + super().delete() + get_object(self.database, foreign_model_class, str(specific_fk_value)).delete() + 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)) + field_name = 'constraint_{:s}_fk'.format(str(self.kind.value)) + specific_fk_value : Optional[ForeignKeyField] = getattr(self, field_name, None) + if specific_fk_value is None: + raise Exception('Unable to find constraint key for field_name({:s})'.format(field_name)) + specific_fk_class = getattr(ConstraintModel, field_name, None) + foreign_model_class : Model = specific_fk_class.foreign_model + constraint : Union_SpecificConstraint = get_object(self.database, foreign_model_class, str(specific_fk_value)) + result = constraint.dump() + if include_position: result['position'] = self.position return result -def parse_constraint_custom(database : Database, grpc_constraint) -> Tuple[Type, str, Dict]: +Tuple_ConstraintSpecs = Tuple[Type, str, Dict, ConstraintKindEnum] +def parse_constraint_custom(database : Database, grpc_constraint) -> Tuple_ConstraintSpecs: 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 + return constraint_class, str_constraint_id, constraint_data, ConstraintKindEnum.CUSTOM -def parse_constraint_endpoint_location(database : Database, grpc_constraint) -> Tuple[Type, str, Dict]: +def parse_constraint_endpoint_location(database : Database, grpc_constraint) -> Tuple_ConstraintSpecs: grpc_endpoint_id = grpc_constraint.endpoint_location.endpoint_id str_endpoint_key, db_endpoint = get_endpoint(database, grpc_endpoint_id) @@ -140,16 +164,17 @@ def parse_constraint_endpoint_location(database : Database, grpc_constraint) -> if location_kind == 'region': constraint_class = ConstraintEndpointLocationRegionModel constraint_data.update({'region': grpc_location.region}) + return constraint_class, str_constraint_id, constraint_data, ConstraintKindEnum.ENDPOINT_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}) + return constraint_class, str_constraint_id, constraint_data, ConstraintKindEnum.ENDPOINT_LOCATION_GPSPOSITION 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]: +def parse_constraint_endpoint_priority(database : Database, grpc_constraint) -> Tuple_ConstraintSpecs: grpc_endpoint_id = grpc_constraint.endpoint_priority.endpoint_id str_endpoint_key, db_endpoint = get_endpoint(database, grpc_endpoint_id) @@ -158,16 +183,16 @@ def parse_constraint_endpoint_priority(database : Database, grpc_constraint) -> priority = grpc_constraint.endpoint_priority.priority constraint_data = {'endpoint_fk': db_endpoint, 'priority': priority} - return constraint_class, str_constraint_id, constraint_data + return constraint_class, str_constraint_id, constraint_data, ConstraintKindEnum.ENDPOINT_PRIORITY -def parse_constraint_sla_availability(database : Database, grpc_constraint) -> Tuple[Type, str, Dict]: +def parse_constraint_sla_availability(database : Database, grpc_constraint) -> Tuple_ConstraintSpecs: 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 + return constraint_class, str_constraint_id, constraint_data, ConstraintKindEnum.SLA_AVAILABILITY CONSTRAINT_PARSERS = { 'custom' : parse_constraint_custom, @@ -176,25 +201,39 @@ CONSTRAINT_PARSERS = { 'sla_availability' : parse_constraint_sla_availability, } +Union_ConstraintModel = Union[ + ConstraintCustomModel, ConstraintEndpointLocationGpsPositionModel, ConstraintEndpointLocationRegionModel, + ConstraintEndpointPriorityModel, ConstraintSlaAvailabilityModel +] + def set_constraint( - database : Database, db_constraints : ConstraintsModel, grpc_constraint, position : int -) -> Tuple[Constraint, bool]: - constraint_kind = str(grpc_constraint.WhichOneof('constraint')) + database : Database, db_constraints : ConstraintsModel, grpc_constraint : Constraint, position : int +) -> Tuple[Union_ConstraintModel, bool]: + grpc_constraint_kind = str(grpc_constraint.WhichOneof('constraint')) - parser = CONSTRAINT_PARSERS.get(constraint_kind) + parser = CONSTRAINT_PARSERS.get(grpc_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) + grpc_constraint_kind, grpc_message_to_json_string(grpc_constraint))) - str_constraint_key_hash = fast_hasher(':'.join([constraint_kind, str_constraint_id])) + # create specific constraint + constraint_class, str_constraint_id, constraint_data, constraint_kind = parser(database, grpc_constraint) + str_constraint_key_hash = fast_hasher(':'.join([constraint_kind.value, str_constraint_id])) 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[Union_ConstraintModel, bool] = update_or_create_object( + database, constraint_class, str_constraint_key, constraint_data) + db_specific_constraint, updated = result + # create generic constraint + constraint_fk_field_name = 'constraint_{:s}_fk'.format(constraint_kind.value) + constraint_data = { + 'constraints_fk': db_constraints, 'position': position, 'kind': constraint_kind, + constraint_fk_field_name: db_specific_constraint + } result : Tuple[ConstraintModel, bool] = update_or_create_object( - database, constraint_class, str_constraint_key, constraint_data) + database, ConstraintModel, str_constraint_key, constraint_data) db_constraint, updated = result + return db_constraint, updated def set_constraints( @@ -208,7 +247,8 @@ def set_constraints( 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) + result : Tuple[ConstraintModel, bool] = set_constraint( + database, db_constraints, grpc_constraint, position) db_constraint, updated = result db_objects.append((db_constraint, updated)) diff --git a/src/context/service/database/DeviceModel.py b/src/context/service/database/DeviceModel.py index 0f0201190542397a34b68fa217706c904606ead3..0d42326793b44473d8aef3da2c3e9ce8464bd1c4 100644 --- a/src/context/service/database/DeviceModel.py +++ b/src/context/service/database/DeviceModel.py @@ -54,6 +54,24 @@ class DeviceModel(Model): device_config_fk = ForeignKeyField(ConfigModel) device_operational_status = EnumeratedField(ORM_DeviceOperationalStatusEnum, required=True) + def delete(self) -> None: + # pylint: disable=import-outside-toplevel + from .EndPointModel import EndPointModel + from .RelationModels import TopologyDeviceModel + + for db_endpoint_pk,_ in self.references(EndPointModel): + EndPointModel(self.database, db_endpoint_pk).delete() + + for db_topology_device_pk,_ in self.references(TopologyDeviceModel): + TopologyDeviceModel(self.database, db_topology_device_pk).delete() + + for db_driver_pk,_ in self.references(DriverModel): + DriverModel(self.database, db_driver_pk).delete() + + super().delete() + + ConfigModel(self.database, self.device_config_fk).delete() + def dump_id(self) -> Dict: return {'device_uuid': {'uuid': self.device_uuid}} diff --git a/src/context/service/database/EndPointModel.py b/src/context/service/database/EndPointModel.py index 29ac43db9ae48e8369591b690af3cadf77335334..aeef91b654dfaaaaf14d53f625126632b7303741 100644 --- a/src/context/service/database/EndPointModel.py +++ b/src/context/service/database/EndPointModel.py @@ -36,6 +36,11 @@ class EndPointModel(Model): endpoint_uuid = StringField(required=True, allow_empty=False) endpoint_type = StringField() + def delete(self) -> None: + for db_kpi_sample_type_pk,_ in self.references(KpiSampleTypeModel): + KpiSampleTypeModel(self.database, db_kpi_sample_type_pk).delete() + super().delete() + def dump_id(self) -> Dict: device_id = DeviceModel(self.database, self.device_fk).dump_id() result = { diff --git a/src/context/service/database/LinkModel.py b/src/context/service/database/LinkModel.py index 742044b9758df297413ad2d0318520c825e8b738..8f1d971c3127371e0d9a1a401d885a02269bd8dd 100644 --- a/src/context/service/database/LinkModel.py +++ b/src/context/service/database/LinkModel.py @@ -25,6 +25,18 @@ class LinkModel(Model): pk = PrimaryKeyField() link_uuid = StringField(required=True, allow_empty=False) + def delete(self) -> None: + #pylint: disable=import-outside-toplevel + from .RelationModels import LinkEndPointModel, TopologyLinkModel + + for db_link_endpoint_pk,_ in self.references(LinkEndPointModel): + LinkEndPointModel(self.database, db_link_endpoint_pk).delete() + + for db_topology_link_pk,_ in self.references(TopologyLinkModel): + TopologyLinkModel(self.database, db_topology_link_pk).delete() + + super().delete() + def dump_id(self) -> Dict: return {'link_uuid': {'uuid': self.link_uuid}} diff --git a/src/context/service/database/ServiceModel.py b/src/context/service/database/ServiceModel.py index cf756af60a8178a9ae2fda2a5fa5ddeebc73912c..8b32d1cc9eeec248d1097f972df93dbd2c0882fa 100644 --- a/src/context/service/database/ServiceModel.py +++ b/src/context/service/database/ServiceModel.py @@ -56,6 +56,18 @@ class ServiceModel(Model): service_status = EnumeratedField(ORM_ServiceStatusEnum, required=True) service_config_fk = ForeignKeyField(ConfigModel) + def delete(self) -> None: + #pylint: disable=import-outside-toplevel + from .RelationModels import ServiceEndPointModel + + for db_service_endpoint_pk,_ in self.references(ServiceEndPointModel): + ServiceEndPointModel(self.database, db_service_endpoint_pk).delete() + + super().delete() + + ConfigModel(self.database, self.service_config_fk).delete() + ConstraintsModel(self.database, self.service_constraints_fk).delete() + def dump_id(self) -> Dict: context_id = ContextModel(self.database, self.context_fk).dump_id() return { diff --git a/src/context/service/database/SliceModel.py b/src/context/service/database/SliceModel.py index a185afed80aff870d7c7b93adee032261effa731..bc00ada43758c9c5ffefbb88a87134aa46fbd73a 100644 --- a/src/context/service/database/SliceModel.py +++ b/src/context/service/database/SliceModel.py @@ -47,6 +47,24 @@ class SliceModel(Model): slice_status = EnumeratedField(ORM_SliceStatusEnum, required=True) slice_config_fk = ForeignKeyField(ConfigModel) + def delete(self) -> None: + # pylint: disable=import-outside-toplevel + from .RelationModels import SliceEndPointModel, SliceServiceModel, SliceSubSliceModel + + for db_slice_endpoint_pk,_ in self.references(SliceEndPointModel): + SliceEndPointModel(self.database, db_slice_endpoint_pk).delete() + + for db_slice_service_pk,_ in self.references(SliceServiceModel): + SliceServiceModel(self.database, db_slice_service_pk).delete() + + for db_slice_subslice_pk,_ in self.references(SliceSubSliceModel): + SliceSubSliceModel(self.database, db_slice_subslice_pk).delete() + + super().delete() + + ConfigModel(self.database, self.slice_config_fk).delete() + ConstraintsModel(self.database, self.slice_constraints_fk).delete() + def dump_id(self) -> Dict: context_id = ContextModel(self.database, self.context_fk).dump_id() return { diff --git a/src/context/service/grpc_server/ContextServiceServicerImpl.py b/src/context/service/grpc_server/ContextServiceServicerImpl.py index 012de343f4f944913c40a882fc6c676d70816628..4c8f957ecb70765cbd36032fca7bfacc27f9b5ae 100644 --- a/src/context/service/grpc_server/ContextServiceServicerImpl.py +++ b/src/context/service/grpc_server/ContextServiceServicerImpl.py @@ -31,23 +31,24 @@ from common.proto.context_pb2 import ( from common.proto.context_pb2_grpc import ContextServiceServicer from common.rpc_method_wrapper.Decorator import create_metrics, safe_and_metered_rpc_method from common.rpc_method_wrapper.ServiceExceptions import InvalidArgumentException -from context.service.database.ConfigModel import ConfigModel, ConfigRuleModel, grpc_config_rules_to_raw, update_config -from context.service.database.ConnectionModel import ConnectionModel, PathHopModel, PathModel, set_path -from context.service.database.ConstraintModel import ConstraintModel, ConstraintsModel, set_constraints +from context.service.database.ConfigModel import grpc_config_rules_to_raw, update_config +from context.service.database.ConnectionModel import ConnectionModel, set_path +from context.service.database.ConstraintModel import set_constraints from context.service.database.ContextModel import ContextModel -from context.service.database.DeviceModel import ( - DeviceModel, DriverModel, grpc_to_enum__device_operational_status, set_drivers) -from context.service.database.EndPointModel import EndPointModel, KpiSampleTypeModel, set_kpi_sample_types +from context.service.database.DeviceModel import DeviceModel, grpc_to_enum__device_operational_status, set_drivers +from context.service.database.EndPointModel import EndPointModel, set_kpi_sample_types from context.service.database.Events import notify_event from context.service.database.LinkModel import LinkModel from context.service.database.RelationModels import ( - ConnectionSubServiceModel, LinkEndPointModel, ServiceEndPointModel, SliceEndPointModel, SliceServiceModel, SliceSubSliceModel, TopologyDeviceModel, TopologyLinkModel) + ConnectionSubServiceModel, LinkEndPointModel, ServiceEndPointModel, SliceEndPointModel, SliceServiceModel, + SliceSubSliceModel, TopologyDeviceModel, TopologyLinkModel) from context.service.database.ServiceModel import ( ServiceModel, grpc_to_enum__service_status, grpc_to_enum__service_type) from context.service.database.SliceModel import SliceModel, grpc_to_enum__slice_status from context.service.database.TopologyModel import TopologyModel from .Constants import ( - CONSUME_TIMEOUT, TOPIC_CONNECTION, TOPIC_CONTEXT, TOPIC_DEVICE, TOPIC_LINK, TOPIC_SERVICE, TOPIC_SLICE, TOPIC_TOPOLOGY) + CONSUME_TIMEOUT, TOPIC_CONNECTION, TOPIC_CONTEXT, TOPIC_DEVICE, TOPIC_LINK, TOPIC_SERVICE, TOPIC_SLICE, + TOPIC_TOPOLOGY) LOGGER = logging.getLogger(__name__) @@ -336,25 +337,7 @@ class ContextServiceServicerImpl(ContextServiceServicer): if not found: return Empty() dict_device_id = db_device.dump_id() - - for db_endpoint_pk,_ in db_device.references(EndPointModel): - db_endpoint = EndPointModel(self.database, db_endpoint_pk) - for db_kpi_sample_type_pk,_ in db_endpoint.references(KpiSampleTypeModel): - KpiSampleTypeModel(self.database, db_kpi_sample_type_pk).delete() - db_endpoint.delete() - - for db_topology_device_pk,_ in db_device.references(TopologyDeviceModel): - TopologyDeviceModel(self.database, db_topology_device_pk).delete() - - for db_driver_pk,_ in db_device.references(DriverModel): - DriverModel(self.database, db_driver_pk).delete() - - db_config = ConfigModel(self.database, db_device.device_config_fk) - for db_config_rule_pk,_ in db_config.references(ConfigRuleModel): - ConfigRuleModel(self.database, db_config_rule_pk).delete() - db_device.delete() - db_config.delete() event_type = EventTypeEnum.EVENTTYPE_REMOVE notify_event(self.messagebroker, TOPIC_DEVICE, event_type, {'device_id': dict_device_id}) @@ -443,14 +426,8 @@ class ContextServiceServicerImpl(ContextServiceServicer): if not found: return Empty() dict_link_id = db_link.dump_id() - - for db_link_endpoint_pk,_ in db_link.references(LinkEndPointModel): - LinkEndPointModel(self.database, db_link_endpoint_pk).delete() - - for db_topology_link_pk,_ in db_link.references(TopologyLinkModel): - TopologyLinkModel(self.database, db_topology_link_pk).delete() - db_link.delete() + event_type = EventTypeEnum.EVENTTYPE_REMOVE notify_event(self.messagebroker, TOPIC_LINK, event_type, {'link_id': dict_link_id}) return Empty() @@ -557,21 +534,7 @@ class ContextServiceServicerImpl(ContextServiceServicer): if not found: return Empty() dict_service_id = db_service.dump_id() - - for db_service_endpoint_pk,_ in db_service.references(ServiceEndPointModel): - ServiceEndPointModel(self.database, db_service_endpoint_pk).delete() - - db_config = ConfigModel(self.database, db_service.service_config_fk) - for db_config_rule_pk,_ in db_config.references(ConfigRuleModel): - ConfigRuleModel(self.database, db_config_rule_pk).delete() - - db_constraints = ConstraintsModel(self.database, db_service.service_constraints_fk) - for db_constraint_pk,_ in db_constraints.references(ConstraintModel): - ConstraintModel(self.database, db_constraint_pk).delete() - db_service.delete() - db_config.delete() - db_constraints.delete() event_type = EventTypeEnum.EVENTTYPE_REMOVE notify_event(self.messagebroker, TOPIC_SERVICE, event_type, {'service_id': dict_service_id}) @@ -703,27 +666,7 @@ class ContextServiceServicerImpl(ContextServiceServicer): if not found: return Empty() dict_slice_id = db_slice.dump_id() - - for db_slice_endpoint_pk,_ in db_slice.references(SliceEndPointModel): - SliceEndPointModel(self.database, db_slice_endpoint_pk).delete() - - db_config = ConfigModel(self.database, db_slice.slice_config_fk) - for db_config_rule_pk,_ in db_config.references(ConfigRuleModel): - ConfigRuleModel(self.database, db_config_rule_pk).delete() - - db_constraints = ConstraintsModel(self.database, db_slice.slice_constraints_fk) - for db_constraint_pk,_ in db_constraints.references(ConstraintModel): - ConstraintModel(self.database, db_constraint_pk).delete() - - for db_slice_service_pk,_ in db_slice.references(SliceServiceModel): - SliceServiceModel(self.database, db_slice_service_pk).delete() - - for db_slice_subslice_pk,_ in db_slice.references(SliceSubSliceModel): - SliceSubSliceModel(self.database, db_slice_subslice_pk).delete() - db_slice.delete() - db_config.delete() - db_constraints.delete() event_type = EventTypeEnum.EVENTTYPE_REMOVE notify_event(self.messagebroker, TOPIC_SLICE, event_type, {'slice_id': dict_slice_id}) @@ -808,20 +751,7 @@ class ContextServiceServicerImpl(ContextServiceServicer): if not found: return Empty() dict_connection_id = db_connection.dump_id() - - db_path = PathModel(self.database, db_connection.path_fk) - for db_path_hop_pk,_ in db_path.references(PathHopModel): - PathHopModel(self.database, db_path_hop_pk).delete() - - # Do not remove sub-services automatically. They are supported by real services, so Service component should - # deal with the correct removal workflow to deconfigure the devices. - for db_connection_sub_service_pk,_ in db_connection.references(ConnectionSubServiceModel): - db_connection_sub_service : ConnectionSubServiceModel = get_object( - self.database, ConnectionSubServiceModel, db_connection_sub_service_pk) - db_connection_sub_service.delete() - db_connection.delete() - db_path.delete() event_type = EventTypeEnum.EVENTTYPE_REMOVE notify_event(self.messagebroker, TOPIC_CONNECTION, event_type, {'connection_id': dict_connection_id}) diff --git a/src/context/tests/test_unitary.py b/src/context/tests/test_unitary.py index efdd1f8b96a09b31b7d144b676ddd355152fc003..b46c9468c56974be5c987dbbc284daae337d3c7b 100644 --- a/src/context/tests/test_unitary.py +++ b/src/context/tests/test_unitary.py @@ -846,7 +846,7 @@ def test_grpc_service( for db_entry in db_entries: LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover LOGGER.info('-----------------------------------------------------------') - assert len(db_entries) == 84 + assert len(db_entries) == 89 # ----- Get when the object exists --------------------------------------------------------------------------------- response = context_client_grpc.GetService(ServiceId(**SERVICE_R1_R2_ID)) @@ -1042,7 +1042,7 @@ def test_grpc_connection( for db_entry in db_entries: LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover LOGGER.info('-----------------------------------------------------------') - assert len(db_entries) == 137 + assert len(db_entries) == 150 # ----- Create the object ------------------------------------------------------------------------------------------ with pytest.raises(grpc.RpcError) as e: @@ -1082,7 +1082,7 @@ def test_grpc_connection( for db_entry in db_entries: LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover LOGGER.info('-----------------------------------------------------------') - assert len(db_entries) == 153 + assert len(db_entries) == 166 # ----- Get when the object exists --------------------------------------------------------------------------------- response = context_client_grpc.GetConnection(ConnectionId(**CONNECTION_R1_R3_ID))