diff --git a/src/context/service/ContextServiceServicerImpl.py b/src/context/service/ContextServiceServicerImpl.py index 95cda2c29d23c16f60889386efd4340b6ac72b76..5c956585997476d579cc0e14e0ad5eac3bb6c2b6 100644 --- a/src/context/service/ContextServiceServicerImpl.py +++ b/src/context/service/ContextServiceServicerImpl.py @@ -165,28 +165,29 @@ class ContextServiceServicerImpl(ContextServiceServicer, ContextPolicyServiceSer @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def ListLinkIds(self, request : Empty, context : grpc.ServicerContext) -> LinkIdList: - return link_list_ids(self.db_engine) + return LinkIdList(link_ids=link_list_ids(self.db_engine)) @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def ListLinks(self, request : Empty, context : grpc.ServicerContext) -> LinkList: - return link_list_objs(self.db_engine) + return LinkList(links=link_list_objs(self.db_engine)) @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def GetLink(self, request : LinkId, context : grpc.ServicerContext) -> Link: - return link_get(self.db_engine, request) + return Link(**link_get(self.db_engine, request)) @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def SetLink(self, request : Link, context : grpc.ServicerContext) -> LinkId: - link_id,updated = link_set(self.db_engine, request) # pylint: disable=unused-variable - #event_type = EventTypeEnum.EVENTTYPE_UPDATE if updated else EventTypeEnum.EVENTTYPE_CREATE - #notify_event(self.messagebroker, TOPIC_LINK, event_type, {'link_id': link_id}) - return link_id + link_id,updated = link_set(self.db_engine, request) + event_type = EventTypeEnum.EVENTTYPE_UPDATE if updated else EventTypeEnum.EVENTTYPE_CREATE + notify_event(self.messagebroker, TOPIC_LINK, event_type, {'link_id': link_id}) + return LinkId(**link_id) @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def RemoveLink(self, request : LinkId, context : grpc.ServicerContext) -> Empty: - deleted = link_delete(self.db_engine, request) # pylint: disable=unused-variable - #if deleted: - # notify_event(self.messagebroker, TOPIC_LINK, EventTypeEnum.EVENTTYPE_REMOVE, {'link_id': request}) + link_id,deleted = link_delete(self.db_engine, request) + if deleted: + event_type = EventTypeEnum.EVENTTYPE_REMOVE + notify_event(self.messagebroker, TOPIC_LINK, event_type, {'link_id': link_id}) return Empty() @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) diff --git a/src/context/service/database/ConfigRule.py b/src/context/service/database/ConfigRule.py index f64e273bfdbd1b309f4b1398309a31b1296b18ec..5f701386fa2087934088894383348ce9d00f53ad 100644 --- a/src/context/service/database/ConfigRule.py +++ b/src/context/service/database/ConfigRule.py @@ -59,7 +59,7 @@ def upsert_config_rules( if slice_uuid is not None: stmt = stmt.where(ConfigRuleModel.slice_uuid == slice_uuid ) session.execute(stmt) - updated = False + configrule_updates = [] if len(config_rules) > 0: stmt = insert(ConfigRuleModel).values(config_rules) #stmt = stmt.on_conflict_do_update( @@ -69,11 +69,9 @@ def upsert_config_rules( # ) #) stmt = stmt.returning(ConfigRuleModel.created_at, ConfigRuleModel.updated_at) - config_rule_updates = session.execute(stmt).fetchall() - LOGGER.warning('config_rule_updates = {:s}'.format(str(config_rule_updates))) - # TODO: updated = ... + configrule_updates = session.execute(stmt).fetchall() - return updated + return configrule_updates #Union_SpecificConfigRule = Union[ # ConfigRuleCustomModel, ConfigRuleAclModel diff --git a/src/context/service/database/Device.py b/src/context/service/database/Device.py index 68369ac9df6f1c3bb8707452641a9d18148c6504..e40c28e699af539c8886986d18ed0aa477e00842 100644 --- a/src/context/service/database/Device.py +++ b/src/context/service/database/Device.py @@ -148,13 +148,14 @@ def device_set(db_engine : Engine, request : Device) -> Tuple[Dict, bool]: ) stmt = stmt.returning(EndPointModel.created_at, EndPointModel.updated_at) endpoint_updates = session.execute(stmt).fetchall() - LOGGER.warning('endpoint_updates = {:s}'.format(str(endpoint_updates))) + updated = updated or any([(updated_at > created_at) for created_at,updated_at in endpoint_updates]) session.execute(insert(TopologyDeviceModel).values(related_topologies).on_conflict_do_nothing( index_elements=[TopologyDeviceModel.topology_uuid, TopologyDeviceModel.device_uuid] )) - configrules_updated = upsert_config_rules(session, config_rules, device_uuid=device_uuid) + configrule_updates = upsert_config_rules(session, config_rules, device_uuid=device_uuid) + updated = updated or any([(updated_at > created_at) for created_at,updated_at in configrule_updates]) return updated diff --git a/src/context/service/database/Link.py b/src/context/service/database/Link.py index c21dd6714a1e5f732757162987f84dd40a0ec5fe..2621e73dc62d70751c8040078968dbf4b2ad78d3 100644 --- a/src/context/service/database/Link.py +++ b/src/context/service/database/Link.py @@ -12,12 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +import datetime, logging from sqlalchemy.dialects.postgresql import insert from sqlalchemy.engine import Engine from sqlalchemy.orm import Session, sessionmaker from sqlalchemy_cockroachdb import run_transaction from typing import Dict, List, Optional, Set, Tuple -from common.proto.context_pb2 import Link, LinkId, LinkIdList, LinkList +from common.proto.context_pb2 import Link, LinkId from common.method_wrappers.ServiceExceptions import NotFoundException from common.tools.object_factory.Link import json_link_id from .models.LinkModel import LinkModel, LinkEndPointModel @@ -25,21 +26,21 @@ from .models.TopologyModel import TopologyLinkModel from .uuids.EndPoint import endpoint_get_uuid from .uuids.Link import link_get_uuid -def link_list_ids(db_engine : Engine) -> LinkIdList: +LOGGER = logging.getLogger(__name__) + +def link_list_ids(db_engine : Engine) -> List[Dict]: def callback(session : Session) -> List[Dict]: obj_list : List[LinkModel] = session.query(LinkModel).all() - #.options(selectinload(LinkModel.topology)).filter_by(context_uuid=context_uuid).one_or_none() return [obj.dump_id() for obj in obj_list] - return LinkIdList(link_ids=run_transaction(sessionmaker(bind=db_engine), callback)) + return run_transaction(sessionmaker(bind=db_engine), callback) -def link_list_objs(db_engine : Engine) -> LinkList: +def link_list_objs(db_engine : Engine) -> List[Dict]: def callback(session : Session) -> List[Dict]: obj_list : List[LinkModel] = session.query(LinkModel).all() - #.options(selectinload(LinkModel.topology)).filter_by(context_uuid=context_uuid).one_or_none() return [obj.dump() for obj in obj_list] - return LinkList(links=run_transaction(sessionmaker(bind=db_engine), callback)) + return run_transaction(sessionmaker(bind=db_engine), callback) -def link_get(db_engine : Engine, request : LinkId) -> Link: +def link_get(db_engine : Engine, request : LinkId) -> Dict: link_uuid = link_get_uuid(request, allow_random=False) def callback(session : Session) -> Optional[Dict]: obj : Optional[LinkModel] = session.query(LinkModel).filter_by(link_uuid=link_uuid).one_or_none() @@ -50,14 +51,16 @@ def link_get(db_engine : Engine, request : LinkId) -> Link: raise NotFoundException('Link', raw_link_uuid, extra_details=[ 'link_uuid generated was: {:s}'.format(link_uuid) ]) - return Link(**obj) + return obj -def link_set(db_engine : Engine, request : Link) -> Tuple[LinkId, bool]: +def link_set(db_engine : Engine, request : Link) -> Tuple[Dict, bool]: raw_link_uuid = request.link_id.link_uuid.uuid raw_link_name = request.name link_name = raw_link_uuid if len(raw_link_name) == 0 else raw_link_name link_uuid = link_get_uuid(request.link_id, link_name=link_name, allow_random=True) + now = datetime.datetime.utcnow() + topology_uuids : Set[str] = set() related_topologies : List[Dict] = list() link_endpoints_data : List[Dict] = list() @@ -73,23 +76,31 @@ def link_set(db_engine : Engine, request : Link) -> Tuple[LinkId, bool]: if endpoint_topology_uuid not in topology_uuids: related_topologies.append({ 'topology_uuid': endpoint_topology_uuid, - 'link_uuid': link_uuid, + 'link_uuid' : link_uuid, }) topology_uuids.add(endpoint_topology_uuid) link_data = [{ - 'link_uuid': link_uuid, - 'link_name': link_name, + 'link_uuid' : link_uuid, + 'link_name' : link_name, + 'created_at': now, + 'updated_at': now, }] - def callback(session : Session) -> None: + def callback(session : Session) -> bool: stmt = insert(LinkModel).values(link_data) stmt = stmt.on_conflict_do_update( index_elements=[LinkModel.link_uuid], - set_=dict(link_name = stmt.excluded.link_name) + set_=dict( + link_name = stmt.excluded.link_name, + updated_at = stmt.excluded.updated_at, + ) ) - session.execute(stmt) + stmt = stmt.returning(LinkModel.created_at, LinkModel.updated_at) + created_at,updated_at = session.execute(stmt).fetchone() + updated = updated_at > created_at + # TODO: manage add/remove of endpoints; manage changes in relations with topology stmt = insert(LinkEndPointModel).values(link_endpoints_data) stmt = stmt.on_conflict_do_nothing( index_elements=[LinkEndPointModel.link_uuid, LinkEndPointModel.endpoint_uuid] @@ -100,13 +111,15 @@ def link_set(db_engine : Engine, request : Link) -> Tuple[LinkId, bool]: index_elements=[TopologyLinkModel.topology_uuid, TopologyLinkModel.link_uuid] )) - run_transaction(sessionmaker(bind=db_engine), callback) - updated = False # TODO: improve and check if created/updated - return LinkId(**json_link_id(link_uuid)),updated + return updated -def link_delete(db_engine : Engine, request : LinkId) -> bool: + updated = run_transaction(sessionmaker(bind=db_engine), callback) + return json_link_id(link_uuid),updated + +def link_delete(db_engine : Engine, request : LinkId) -> Tuple[Dict, bool]: link_uuid = link_get_uuid(request, allow_random=False) def callback(session : Session) -> bool: num_deleted = session.query(LinkModel).filter_by(link_uuid=link_uuid).delete() return num_deleted > 0 - return run_transaction(sessionmaker(bind=db_engine), callback) + deleted = run_transaction(sessionmaker(bind=db_engine), callback) + return json_link_id(link_uuid),deleted diff --git a/src/context/service/database/models/LinkModel.py b/src/context/service/database/models/LinkModel.py index ecad019726eff8b82d4ea842d7b7596da5277e98..a13f61bf319d1298c34a64f6832399fe7ad350b8 100644 --- a/src/context/service/database/models/LinkModel.py +++ b/src/context/service/database/models/LinkModel.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from sqlalchemy import Column, ForeignKey, String +from sqlalchemy import Column, DateTime, ForeignKey, String from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship from typing import Dict @@ -23,6 +23,8 @@ class LinkModel(_Base): link_uuid = Column(UUID(as_uuid=False), primary_key=True) link_name = Column(String, nullable=False) + created_at = Column(DateTime) + updated_at = Column(DateTime) #topology_links = relationship('TopologyLinkModel', back_populates='link') link_endpoints = relationship('LinkEndPointModel') # lazy='joined', back_populates='link' diff --git a/src/context/tests/test_link.py b/src/context/tests/test_link.py index ec767f1c9bba56fe3d1f3ece4ef7b00b0951a9be..5167c41b8412183a1aa9eb929b91137afacd4532 100644 --- a/src/context/tests/test_link.py +++ b/src/context/tests/test_link.py @@ -12,10 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import copy, grpc, pytest -from common.proto.context_pb2 import Context, ContextId, Device, DeviceId, Empty, Link, LinkId, Topology, TopologyId +import copy, grpc, pytest, time +from common.proto.context_pb2 import ( + Context, ContextEvent, ContextId, Device, DeviceEvent, DeviceId, Empty, EventTypeEnum, Link, LinkEvent, LinkId, + Topology, TopologyEvent, TopologyId) from context.client.ContextClient import ContextClient -#from context.client.EventsCollector import EventsCollector +from context.client.EventsCollector import EventsCollector from context.service.database.uuids.Link import link_get_uuid from .Objects import ( CONTEXT, CONTEXT_ID, DEVICE_R1, DEVICE_R1_ID, DEVICE_R2, DEVICE_R2_ID, LINK_R1_R2, LINK_R1_R2_ID, LINK_R1_R2_NAME, @@ -25,12 +27,13 @@ from .Objects import ( def test_link(context_client : ContextClient) -> None: # ----- Initialize the EventsCollector ----------------------------------------------------------------------------- - #events_collector = EventsCollector( - # context_client, log_events_received=True, - # activate_context_collector = False, activate_topology_collector = False, activate_device_collector = False, - # activate_link_collector = True, activate_service_collector = False, activate_slice_collector = False, - # activate_connection_collector = False) - #events_collector.start() + events_collector = EventsCollector( + context_client, log_events_received=True, + activate_context_collector = True, activate_topology_collector = True, activate_device_collector = True, + activate_link_collector = True, activate_service_collector = False, activate_slice_collector = False, + activate_connection_collector = False) + events_collector.start() + time.sleep(3) # ----- Prepare dependencies for the test and capture related events ----------------------------------------------- response = context_client.SetContext(Context(**CONTEXT)) @@ -45,20 +48,20 @@ def test_link(context_client : ContextClient) -> None: response = context_client.SetDevice(Device(**DEVICE_R2)) device_r2_uuid = response.device_uuid.uuid - # events = events_collector.get_events(block=True, count=4) - # assert isinstance(events[0], ContextEvent) - # assert events[0].event.event_type == EventTypeEnum.EVENTTYPE_CREATE - # assert events[0].context_id.context_uuid.uuid == context_uuid - # assert isinstance(events[1], TopologyEvent) - # assert events[1].event.event_type == EventTypeEnum.EVENTTYPE_CREATE - # assert events[1].topology_id.context_id.context_uuid.uuid == context_uuid - # assert events[1].topology_id.topology_uuid.uuid == topology_uuid - # assert isinstance(events[2], DeviceEvent) - # assert events[2].event.event_type == EventTypeEnum.EVENTTYPE_CREATE - # assert events[2].device_id.device_uuid.uuid == device_r1_uuid - # assert isinstance(events[3], DeviceEvent) - # assert events[3].event.event_type == EventTypeEnum.EVENTTYPE_CREATE - # assert events[3].device_id.device_uuid.uuid == device_r2_uuid + events = events_collector.get_events(block=True, count=4, timeout=1.0) + assert isinstance(events[0], ContextEvent) + assert events[0].event.event_type == EventTypeEnum.EVENTTYPE_CREATE + assert events[0].context_id.context_uuid.uuid == context_uuid + assert isinstance(events[1], TopologyEvent) + assert events[1].event.event_type == EventTypeEnum.EVENTTYPE_CREATE + assert events[1].topology_id.context_id.context_uuid.uuid == context_uuid + assert events[1].topology_id.topology_uuid.uuid == topology_uuid + assert isinstance(events[2], DeviceEvent) + assert events[2].event.event_type == EventTypeEnum.EVENTTYPE_CREATE + assert events[2].device_id.device_uuid.uuid == device_r1_uuid + assert isinstance(events[3], DeviceEvent) + assert events[3].event.event_type == EventTypeEnum.EVENTTYPE_CREATE + assert events[3].device_id.device_uuid.uuid == device_r2_uuid # ----- Get when the object does not exist ------------------------------------------------------------------------- link_id = LinkId(**LINK_R1_R2_ID) @@ -81,10 +84,10 @@ def test_link(context_client : ContextClient) -> None: assert response.link_uuid.uuid == link_uuid # ----- Check create event ----------------------------------------------------------------------------------------- - #event = events_collector.get_event(block=True) - #assert isinstance(event, LinkEvent) - #assert event.event.event_type == EventTypeEnum.EVENTTYPE_CREATE - #assert event.link_id.link_uuid.uuid == link_uuid + event = events_collector.get_event(block=True, timeout=1.0) + assert isinstance(event, LinkEvent) + assert event.event.event_type == EventTypeEnum.EVENTTYPE_CREATE + assert event.link_id.link_uuid.uuid == link_uuid # ----- Get when the object exists --------------------------------------------------------------------------------- response = context_client.GetLink(LinkId(**LINK_R1_R2_ID)) @@ -111,10 +114,10 @@ def test_link(context_client : ContextClient) -> None: assert response.link_uuid.uuid == link_uuid # ----- Check update event ----------------------------------------------------------------------------------------- - #event = events_collector.get_event(block=True) - #assert isinstance(event, LinkEvent) - #assert event.event.event_type == EventTypeEnum.EVENTTYPE_UPDATE - #assert event.link_id.link_uuid.uuid == link_uuid + event = events_collector.get_event(block=True, timeout=1.0) + assert isinstance(event, LinkEvent) + assert event.event.event_type == EventTypeEnum.EVENTTYPE_UPDATE + assert event.link_id.link_uuid.uuid == link_uuid # ----- Get when the object is modified ---------------------------------------------------------------------------- response = context_client.GetLink(LinkId(**LINK_R1_R2_ID)) @@ -133,20 +136,6 @@ def test_link(context_client : ContextClient) -> None: assert response.links[0].name == new_link_name assert len(response.links[0].link_endpoint_ids) == 2 - # ----- Create object relation ------------------------------------------------------------------------------------- - #TOPOLOGY_WITH_LINK = copy.deepcopy(TOPOLOGY) - #TOPOLOGY_WITH_LINK['link_ids'].append(LINK_R1_R2_ID) - #response = context_client.SetTopology(Topology(**TOPOLOGY_WITH_LINK)) - #assert response.context_id.context_uuid.uuid == context_uuid - #assert response.topology_uuid.uuid == topology_uuid - - # ----- Check update event ----------------------------------------------------------------------------------------- - #event = events_collector.get_event(block=True) - #assert isinstance(event, TopologyEvent) - #assert event.event.event_type == EventTypeEnum.EVENTTYPE_UPDATE - #assert response.context_id.context_uuid.uuid == context_uuid - #assert response.topology_uuid.uuid == topology_uuid - # ----- Check relation was created --------------------------------------------------------------------------------- response = context_client.GetTopology(TopologyId(**TOPOLOGY_ID)) assert response.topology_id.context_id.context_uuid.uuid == context_uuid @@ -161,10 +150,10 @@ def test_link(context_client : ContextClient) -> None: context_client.RemoveLink(LinkId(**LINK_R1_R2_ID)) # ----- Check remove event ----------------------------------------------------------------------------------------- - #event = events_collector.get_event(block=True) - #assert isinstance(event, LinkEvent) - #assert event.event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - #assert event.link_id.link_uuid.uuid == link_uuid + event = events_collector.get_event(block=True, timeout=1.0) + assert isinstance(event, LinkEvent) + assert event.event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + assert event.link_id.link_uuid.uuid == link_uuid # ----- List after deleting the object ----------------------------------------------------------------------------- response = context_client.ListLinkIds(Empty()) @@ -187,20 +176,20 @@ def test_link(context_client : ContextClient) -> None: context_client.RemoveTopology(TopologyId(**TOPOLOGY_ID)) context_client.RemoveContext(ContextId(**CONTEXT_ID)) - #events = events_collector.get_events(block=True, count=4) - #assert isinstance(events[0], DeviceEvent) - #assert events[0].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - #assert events[0].device_id.device_uuid.uuid == device_r1_uuid - #assert isinstance(events[1], DeviceEvent) - #assert events[1].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - #assert events[1].device_id.device_uuid.uuid == device_r2_uuid - #assert isinstance(events[2], TopologyEvent) - #assert events[2].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - #assert events[2].topology_id.context_id.context_uuid.uuid == context_uuid - #assert events[2].topology_id.topology_uuid.uuid == topology_uuid - #assert isinstance(events[3], ContextEvent) - #assert events[3].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - #assert events[3].context_id.context_uuid.uuid == context_uuid + events = events_collector.get_events(block=True, count=4, timeout=1.0) + assert isinstance(events[0], DeviceEvent) + assert events[0].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + assert events[0].device_id.device_uuid.uuid == device_r1_uuid + assert isinstance(events[1], DeviceEvent) + assert events[1].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + assert events[1].device_id.device_uuid.uuid == device_r2_uuid + assert isinstance(events[2], TopologyEvent) + assert events[2].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + assert events[2].topology_id.context_id.context_uuid.uuid == context_uuid + assert events[2].topology_id.topology_uuid.uuid == topology_uuid + assert isinstance(events[3], ContextEvent) + assert events[3].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + assert events[3].context_id.context_uuid.uuid == context_uuid # ----- Stop the EventsCollector ----------------------------------------------------------------------------------- - #events_collector.stop() + events_collector.stop()