From d03919be0562757f11086a783bc506a4411ef91d Mon Sep 17 00:00:00 2001
From: gifrerenom <lluis.gifre@cttc.es>
Date: Tue, 10 Jan 2023 16:31:45 +0000
Subject: [PATCH] Context component:

- moved relational models to associated classes
- migrated support for slice subslices
- added filters to prevent upsert when there is nothing to update
---
 src/context/service/database/ConfigRule.py    |  3 +-
 src/context/service/database/Constraint.py    |  3 +-
 src/context/service/database/Device.py        |  2 +-
 src/context/service/database/Link.py          |  4 +-
 src/context/service/database/Service.py       |  3 +-
 src/context/service/database/Slice.py         | 60 ++++++-------
 .../database/models/ConnectionModel.py        | 16 ++++
 .../service/database/models/LinkModel.py      | 11 ++-
 .../service/database/models/RelationModels.py | 86 -------------------
 .../service/database/models/ServiceModel.py   |  9 ++
 .../service/database/models/SliceModel.py     | 33 ++++++-
 .../service/database/models/TopologyModel.py  | 18 ++++
 12 files changed, 121 insertions(+), 127 deletions(-)
 delete mode 100644 src/context/service/database/models/RelationModels.py

diff --git a/src/context/service/database/ConfigRule.py b/src/context/service/database/ConfigRule.py
index af1dd1ec5..05dda20aa 100644
--- a/src/context/service/database/ConfigRule.py
+++ b/src/context/service/database/ConfigRule.py
@@ -52,7 +52,8 @@ def upsert_config_rules(
     if service_uuid is not None: stmt = stmt.where(ConfigRuleModel.service_uuid == service_uuid)
     if slice_uuid   is not None: stmt = stmt.where(ConfigRuleModel.slice_uuid   == slice_uuid  )
     session.execute(stmt)
-    session.execute(insert(ConfigRuleModel).values(config_rules))
+    if len(config_rules) > 0:
+        session.execute(insert(ConfigRuleModel).values(config_rules))
 
 
 #Union_SpecificConfigRule = Union[
diff --git a/src/context/service/database/Constraint.py b/src/context/service/database/Constraint.py
index 5c94d13c0..f79159a35 100644
--- a/src/context/service/database/Constraint.py
+++ b/src/context/service/database/Constraint.py
@@ -47,7 +47,8 @@ def upsert_constraints(
     if service_uuid is not None: stmt = stmt.where(ConstraintModel.service_uuid == service_uuid)
     if slice_uuid   is not None: stmt = stmt.where(ConstraintModel.slice_uuid   == slice_uuid  )
     session.execute(stmt)
-    session.execute(insert(ConstraintModel).values(constraints))
+    if len(constraints) > 0:
+        session.execute(insert(ConstraintModel).values(constraints))
 
 #    def set_constraint(self, db_constraints: ConstraintsModel, grpc_constraint: Constraint, position: int
 #    ) -> Tuple[Union_ConstraintModel, bool]:
diff --git a/src/context/service/database/Device.py b/src/context/service/database/Device.py
index 8899b5a12..acb1603c6 100644
--- a/src/context/service/database/Device.py
+++ b/src/context/service/database/Device.py
@@ -23,7 +23,7 @@ from common.tools.object_factory.Device import json_device_id
 from context.service.database.ConfigRule import compose_config_rules_data, upsert_config_rules
 from .models.DeviceModel import DeviceModel
 from .models.EndPointModel import EndPointModel
-from .models.RelationModels import TopologyDeviceModel
+from .models.TopologyModel import TopologyDeviceModel
 from .models.enums.DeviceDriver import grpc_to_enum__device_driver
 from .models.enums.DeviceOperationalStatus import grpc_to_enum__device_operational_status
 from .models.enums.KpiSampleType import grpc_to_enum__kpi_sample_type
diff --git a/src/context/service/database/Link.py b/src/context/service/database/Link.py
index 7032a2138..a2b4e3035 100644
--- a/src/context/service/database/Link.py
+++ b/src/context/service/database/Link.py
@@ -20,8 +20,8 @@ from typing import Dict, List, Optional, Set, Tuple
 from common.proto.context_pb2 import Link, LinkId, LinkIdList, LinkList
 from common.rpc_method_wrapper.ServiceExceptions import NotFoundException
 from common.tools.object_factory.Link import json_link_id
-from .models.LinkModel import LinkModel
-from .models.RelationModels import LinkEndPointModel, TopologyLinkModel
+from .models.LinkModel import LinkModel, LinkEndPointModel
+from .models.TopologyModel import TopologyLinkModel
 from .uuids.EndPoint import endpoint_get_uuid
 from .uuids.Link import link_get_uuid
 
diff --git a/src/context/service/database/Service.py b/src/context/service/database/Service.py
index 0230bc4d5..c926c2540 100644
--- a/src/context/service/database/Service.py
+++ b/src/context/service/database/Service.py
@@ -25,8 +25,7 @@ from context.service.database.ConfigRule import compose_config_rules_data, upser
 from context.service.database.Constraint import compose_constraints_data, upsert_constraints
 from .models.enums.ServiceStatus import grpc_to_enum__service_status
 from .models.enums.ServiceType import grpc_to_enum__service_type
-from .models.RelationModels import ServiceEndPointModel
-from .models.ServiceModel import ServiceModel
+from .models.ServiceModel import ServiceModel, ServiceEndPointModel
 from .uuids.Context import context_get_uuid
 from .uuids.EndPoint import endpoint_get_uuid
 from .uuids.Service import service_get_uuid
diff --git a/src/context/service/database/Slice.py b/src/context/service/database/Slice.py
index 318923555..00b2fd24b 100644
--- a/src/context/service/database/Slice.py
+++ b/src/context/service/database/Slice.py
@@ -25,8 +25,7 @@ from common.tools.object_factory.Slice import json_slice_id
 from context.service.database.ConfigRule import compose_config_rules_data, upsert_config_rules
 from context.service.database.Constraint import compose_constraints_data, upsert_constraints
 from .models.enums.SliceStatus import grpc_to_enum__slice_status
-from .models.RelationModels import SliceEndPointModel, SliceServiceModel #, SliceSubSliceModel
-from .models.SliceModel import SliceModel
+from .models.SliceModel import SliceModel, SliceEndPointModel, SliceServiceModel, SliceSubSliceModel
 from .uuids.Context import context_get_uuid
 from .uuids.EndPoint import endpoint_get_uuid
 from .uuids.Service import service_get_uuid
@@ -96,13 +95,13 @@ def slice_set(db_engine : Engine, request : Slice) -> Tuple[SliceId, bool]:
             'service_uuid': service_uuid,
         })
 
-    #slice_subslices_data : List[Dict] = list()
-    #for i,subslice_id in enumerate(request.slice_subslice_ids):
-    #    _, subslice_uuid = slice_get_uuid(subslice_id, allow_random=False)
-    #    slice_subslices_data.append({
-    #        'slice_uuid'   : slice_uuid,
-    #        'subslice_uuid': subslice_uuid,
-    #    })
+    slice_subslices_data : List[Dict] = list()
+    for i,subslice_id in enumerate(request.slice_subslice_ids):
+        _, subslice_uuid = slice_get_uuid(subslice_id, allow_random=False)
+        slice_subslices_data.append({
+            'slice_uuid'   : slice_uuid,
+            'subslice_uuid': subslice_uuid,
+        })
 
     constraints = compose_constraints_data(request.slice_constraints, slice_uuid=slice_uuid)
     config_rules = compose_config_rules_data(request.slice_config.config_rules, slice_uuid=slice_uuid)
@@ -129,23 +128,26 @@ def slice_set(db_engine : Engine, request : Slice) -> Tuple[SliceId, bool]:
         )
         session.execute(stmt)
 
-        stmt = insert(SliceEndPointModel).values(slice_endpoints_data)
-        stmt = stmt.on_conflict_do_nothing(
-            index_elements=[SliceEndPointModel.slice_uuid, SliceEndPointModel.endpoint_uuid]
-        )
-        session.execute(stmt)
+        if len(slice_endpoints_data) > 0:
+            stmt = insert(SliceEndPointModel).values(slice_endpoints_data)
+            stmt = stmt.on_conflict_do_nothing(
+                index_elements=[SliceEndPointModel.slice_uuid, SliceEndPointModel.endpoint_uuid]
+            )
+            session.execute(stmt)
 
-        stmt = insert(SliceServiceModel).values(slice_services_data)
-        stmt = stmt.on_conflict_do_nothing(
-            index_elements=[SliceServiceModel.slice_uuid, SliceServiceModel.service_uuid]
-        )
-        session.execute(stmt)
+        if len(slice_services_data) > 0:
+            stmt = insert(SliceServiceModel).values(slice_services_data)
+            stmt = stmt.on_conflict_do_nothing(
+                index_elements=[SliceServiceModel.slice_uuid, SliceServiceModel.service_uuid]
+            )
+            session.execute(stmt)
 
-        #stmt = insert(SliceSubSliceModel).values(slice_subslices_data)
-        #stmt = stmt.on_conflict_do_nothing(
-        #    index_elements=[SliceSubSliceModel.slice_uuid, SliceSubSliceModel.subslice_uuid]
-        #)
-        #session.execute(stmt)
+        if len(slice_subslices_data) > 0:
+            stmt = insert(SliceSubSliceModel).values(slice_subslices_data)
+            stmt = stmt.on_conflict_do_nothing(
+                index_elements=[SliceSubSliceModel.slice_uuid, SliceSubSliceModel.subslice_uuid]
+            )
+            session.execute(stmt)
 
         upsert_constraints(session, constraints, slice_uuid=slice_uuid)
         upsert_config_rules(session, config_rules, slice_uuid=slice_uuid)
@@ -193,11 +195,11 @@ def slice_unset(db_engine : Engine, request : Slice) -> Tuple[SliceId, bool]:
                 SliceServiceModel.slice_uuid == slice_uuid,
                 SliceServiceModel.service_uuid.in_(slice_service_uuids)
             )).delete()
-        #num_deletes += session.query(SliceSubSliceModel)\
-        #    .filter_by(and_(
-        #        SliceSubSliceModel.slice_uuid == slice_uuid,
-        #        SliceSubSliceModel.subslice_uuid.in_(slice_subslice_uuids)
-        #    )).delete()
+        num_deletes += session.query(SliceSubSliceModel)\
+            .filter_by(and_(
+                SliceSubSliceModel.slice_uuid == slice_uuid,
+                SliceSubSliceModel.subslice_uuid.in_(slice_subslice_uuids)
+            )).delete()
         num_deletes += session.query(SliceEndPointModel)\
             .filter_by(and_(
                 SliceEndPointModel.slice_uuid == slice_uuid,
diff --git a/src/context/service/database/models/ConnectionModel.py b/src/context/service/database/models/ConnectionModel.py
index 546fb7a80..19cafc59b 100644
--- a/src/context/service/database/models/ConnectionModel.py
+++ b/src/context/service/database/models/ConnectionModel.py
@@ -25,6 +25,11 @@ from common.proto.context_pb2 import EndPointId
 from .EndPointModel import EndPointModel
 from .ServiceModel import ServiceModel
 
+from sqlalchemy import Column, ForeignKey #, ForeignKeyConstraint
+#from sqlalchemy.dialects.postgresql import UUID
+from sqlalchemy.orm import relationship
+from ._Base import _Base
+
 def remove_dict_key(dictionary : Dict, key : str):
     dictionary.pop(key, None)
     return dictionary
@@ -111,6 +116,17 @@ class ConnectionModel(Model):
         if include_sub_service_ids: result['sub_service_ids'] = self.dump_sub_service_ids()
         return result
 
+
+
+
+# class ConnectionSubServiceModel(Model):
+#     pk = PrimaryKeyField()
+#     connection_fk = ForeignKeyField(ConnectionModel)
+#     sub_service_fk = ForeignKeyField(ServiceModel)
+
+
+
+
 def set_path_hop(
         database : Database, db_path : PathModel, position : int, db_endpoint : EndPointModel
     ) -> Tuple[PathHopModel, bool]:
diff --git a/src/context/service/database/models/LinkModel.py b/src/context/service/database/models/LinkModel.py
index fd4f80c16..950f48763 100644
--- a/src/context/service/database/models/LinkModel.py
+++ b/src/context/service/database/models/LinkModel.py
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 from typing import Dict
-from sqlalchemy import Column, String
+from sqlalchemy import Column, ForeignKey, String
 from sqlalchemy.dialects.postgresql import UUID
 from sqlalchemy.orm import relationship
 from ._Base import _Base
@@ -39,3 +39,12 @@ class LinkModel(_Base):
                 for link_endpoint in self.link_endpoints
             ],
         }
+
+class LinkEndPointModel(_Base):
+    __tablename__ = 'link_endpoint'
+
+    link_uuid     = Column(ForeignKey('link.link_uuid',         ondelete='CASCADE' ), primary_key=True)
+    endpoint_uuid = Column(ForeignKey('endpoint.endpoint_uuid', ondelete='RESTRICT'), primary_key=True)
+
+    link     = relationship('LinkModel',     back_populates='link_endpoints', lazy='joined')
+    endpoint = relationship('EndPointModel', lazy='joined') # back_populates='link_endpoints'
diff --git a/src/context/service/database/models/RelationModels.py b/src/context/service/database/models/RelationModels.py
deleted file mode 100644
index 468b14519..000000000
--- a/src/context/service/database/models/RelationModels.py
+++ /dev/null
@@ -1,86 +0,0 @@
-# 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.
-
-from sqlalchemy import Column, ForeignKey #, ForeignKeyConstraint
-#from sqlalchemy.dialects.postgresql import UUID
-from sqlalchemy.orm import relationship
-from ._Base import _Base
-
-# class ConnectionSubServiceModel(Model):
-#     pk = PrimaryKeyField()
-#     connection_fk = ForeignKeyField(ConnectionModel)
-#     sub_service_fk = ForeignKeyField(ServiceModel)
-
-class LinkEndPointModel(_Base):
-    __tablename__ = 'link_endpoint'
-
-    link_uuid     = Column(ForeignKey('link.link_uuid',         ondelete='CASCADE' ), primary_key=True)
-    endpoint_uuid = Column(ForeignKey('endpoint.endpoint_uuid', ondelete='RESTRICT'), primary_key=True)
-
-    link     = relationship('LinkModel',     back_populates='link_endpoints', lazy='joined')
-    endpoint = relationship('EndPointModel', lazy='joined') # back_populates='link_endpoints'
-
-class ServiceEndPointModel(_Base):
-    __tablename__ = 'service_endpoint'
-
-    service_uuid  = Column(ForeignKey('service.service_uuid',   ondelete='CASCADE' ), primary_key=True)
-    endpoint_uuid = Column(ForeignKey('endpoint.endpoint_uuid', ondelete='RESTRICT'), primary_key=True)
-
-    service  = relationship('ServiceModel',  back_populates='service_endpoints', lazy='joined')
-    endpoint = relationship('EndPointModel', lazy='joined') # back_populates='service_endpoints'
-
-class SliceEndPointModel(_Base):
-    __tablename__ = 'slice_endpoint'
-
-    slice_uuid    = Column(ForeignKey('slice.slice_uuid',       ondelete='CASCADE' ), primary_key=True)
-    endpoint_uuid = Column(ForeignKey('endpoint.endpoint_uuid', ondelete='RESTRICT'), primary_key=True)
-
-    slice    = relationship('SliceModel', back_populates='slice_endpoints', lazy='joined')
-    endpoint = relationship('EndPointModel', lazy='joined') # back_populates='slice_endpoints'
-
-class SliceServiceModel(_Base):
-    __tablename__ = 'slice_service'
-
-    slice_uuid   = Column(ForeignKey('slice.slice_uuid',     ondelete='CASCADE' ), primary_key=True)
-    service_uuid = Column(ForeignKey('service.service_uuid', ondelete='RESTRICT'), primary_key=True)
-
-    slice   = relationship('SliceModel', back_populates='slice_services', lazy='joined')
-    service = relationship('ServiceModel', lazy='joined') # back_populates='slice_services'
-
-#class SliceSubSliceModel(_Base):
-#    __tablename__ = 'slice_subslice'
-#
-#    slice_uuid    = Column(ForeignKey('slice.slice_uuid', ondelete='CASCADE' ), primary_key=True)
-#    subslice_uuid = Column(ForeignKey('slice.slice_uuid', ondelete='RESTRICT'), primary_key=True)
-#
-#    slice    = relationship('SliceModel', foreign_keys=[slice_uuid],    lazy='joined') #back_populates='slice_subslices'
-#    subslice = relationship('SliceModel', foreign_keys=[subslice_uuid], lazy='joined') #back_populates='slice_subslices'
-
-class TopologyDeviceModel(_Base):
-    __tablename__ = 'topology_device'
-
-    topology_uuid = Column(ForeignKey('topology.topology_uuid', ondelete='RESTRICT'), primary_key=True)
-    device_uuid   = Column(ForeignKey('device.device_uuid',     ondelete='CASCADE' ), primary_key=True)
-
-    #topology = relationship('TopologyModel', lazy='joined') # back_populates='topology_devices'
-    device   = relationship('DeviceModel',   lazy='joined') # back_populates='topology_devices'
-
-class TopologyLinkModel(_Base):
-    __tablename__ = 'topology_link'
-
-    topology_uuid = Column(ForeignKey('topology.topology_uuid', ondelete='RESTRICT'), primary_key=True)
-    link_uuid     = Column(ForeignKey('link.link_uuid',         ondelete='CASCADE' ), primary_key=True)
-
-    #topology = relationship('TopologyModel', lazy='joined') # back_populates='topology_links'
-    link     = relationship('LinkModel',     lazy='joined') # back_populates='topology_links'
diff --git a/src/context/service/database/models/ServiceModel.py b/src/context/service/database/models/ServiceModel.py
index b08043844..e1e57f4c7 100644
--- a/src/context/service/database/models/ServiceModel.py
+++ b/src/context/service/database/models/ServiceModel.py
@@ -60,3 +60,12 @@ class ServiceModel(_Base):
                 for config_rule in sorted(self.config_rules, key=operator.attrgetter('position'))
             ]},
         }
+
+class ServiceEndPointModel(_Base):
+    __tablename__ = 'service_endpoint'
+
+    service_uuid  = Column(ForeignKey('service.service_uuid',   ondelete='CASCADE' ), primary_key=True)
+    endpoint_uuid = Column(ForeignKey('endpoint.endpoint_uuid', ondelete='RESTRICT'), primary_key=True)
+
+    service  = relationship('ServiceModel',  back_populates='service_endpoints', lazy='joined')
+    endpoint = relationship('EndPointModel', lazy='joined') # back_populates='service_endpoints'
diff --git a/src/context/service/database/models/SliceModel.py b/src/context/service/database/models/SliceModel.py
index ef2b64962..d3dff51e1 100644
--- a/src/context/service/database/models/SliceModel.py
+++ b/src/context/service/database/models/SliceModel.py
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 import operator
-from sqlalchemy import Column, Enum, ForeignKey, String
+from sqlalchemy import Column, Enum, ForeignKey, String, Table
 from sqlalchemy.dialects.postgresql import UUID
 from sqlalchemy.orm import relationship
 from typing import Dict
@@ -33,7 +33,8 @@ class SliceModel(_Base):
     context         = relationship('ContextModel', back_populates='slices')
     slice_endpoints = relationship('SliceEndPointModel') # lazy='joined', back_populates='slice'
     slice_services  = relationship('SliceServiceModel') # lazy='joined', back_populates='slice'
-    #slice_subslices = relationship('SliceSubSliceModel') # lazy='joined', back_populates='slice'
+    slice_subslices = relationship(
+        'SliceSubSliceModel', primaryjoin='slice.c.slice_uuid == slice_subslice.c.slice_uuid')
     constraints     = relationship('ConstraintModel', passive_deletes=True) # lazy='joined', back_populates='slice'
     config_rules    = relationship('ConfigRuleModel', passive_deletes=True) # lazy='joined', back_populates='slice'
 
@@ -65,11 +66,35 @@ class SliceModel(_Base):
                 for slice_service in self.slice_services
             ],
             'slice_subslice_ids': [
-                #slice_subslice.subslice.dump_id()
-                #for slice_subslice in self.slice_subslices
+                slice_subslice.subslice.dump_id()
+                for slice_subslice in self.slice_subslices
             ],
             'slice_owner': {
                 'owner_uuid': {'uuid': self.slice_owner_uuid},
                 'owner_string': self.slice_owner_string
             }
         }
+
+class SliceEndPointModel(_Base):
+    __tablename__ = 'slice_endpoint'
+
+    slice_uuid    = Column(ForeignKey('slice.slice_uuid',       ondelete='CASCADE' ), primary_key=True)
+    endpoint_uuid = Column(ForeignKey('endpoint.endpoint_uuid', ondelete='RESTRICT'), primary_key=True)
+
+    slice    = relationship('SliceModel', back_populates='slice_endpoints', lazy='joined')
+    endpoint = relationship('EndPointModel', lazy='joined') # back_populates='slice_endpoints'
+
+class SliceServiceModel(_Base):
+    __tablename__ = 'slice_service'
+
+    slice_uuid   = Column(ForeignKey('slice.slice_uuid',     ondelete='CASCADE' ), primary_key=True)
+    service_uuid = Column(ForeignKey('service.service_uuid', ondelete='RESTRICT'), primary_key=True)
+
+    slice   = relationship('SliceModel', back_populates='slice_services', lazy='joined')
+    service = relationship('ServiceModel', lazy='joined') # back_populates='slice_services'
+
+class SliceSubSliceModel(_Base):
+    __tablename__ = 'slice_subslice'
+
+    slice_uuid    = Column(ForeignKey('slice.slice_uuid', ondelete='CASCADE' ), primary_key=True)
+    subslice_uuid = Column(ForeignKey('slice.slice_uuid', ondelete='RESTRICT'), primary_key=True)
diff --git a/src/context/service/database/models/TopologyModel.py b/src/context/service/database/models/TopologyModel.py
index 8c59bf58a..ef1ae0be8 100644
--- a/src/context/service/database/models/TopologyModel.py
+++ b/src/context/service/database/models/TopologyModel.py
@@ -42,3 +42,21 @@ class TopologyModel(_Base):
             'device_ids' : [{'device_uuid': {'uuid': td.device_uuid}} for td in self.topology_devices],
             'link_ids'   : [{'link_uuid'  : {'uuid': tl.link_uuid  }} for tl in self.topology_links  ],
         }
+
+class TopologyDeviceModel(_Base):
+    __tablename__ = 'topology_device'
+
+    topology_uuid = Column(ForeignKey('topology.topology_uuid', ondelete='RESTRICT'), primary_key=True)
+    device_uuid   = Column(ForeignKey('device.device_uuid',     ondelete='CASCADE' ), primary_key=True)
+
+    #topology = relationship('TopologyModel', lazy='joined') # back_populates='topology_devices'
+    device   = relationship('DeviceModel',   lazy='joined') # back_populates='topology_devices'
+
+class TopologyLinkModel(_Base):
+    __tablename__ = 'topology_link'
+
+    topology_uuid = Column(ForeignKey('topology.topology_uuid', ondelete='RESTRICT'), primary_key=True)
+    link_uuid     = Column(ForeignKey('link.link_uuid',         ondelete='CASCADE' ), primary_key=True)
+
+    #topology = relationship('TopologyModel', lazy='joined') # back_populates='topology_links'
+    link     = relationship('LinkModel',     lazy='joined') # back_populates='topology_links'
-- 
GitLab