diff --git a/scripts/run_tests_locally-context.sh b/scripts/run_tests_locally-context.sh index 61f8cee916374b228ca1bcee8810bd299837a2f5..5b6c53aa8a137c3ba3ca75d061a83e0c9810a6b4 100755 --- a/scripts/run_tests_locally-context.sh +++ b/scripts/run_tests_locally-context.sh @@ -44,8 +44,9 @@ export PYTHONPATH=/home/tfs/tfs-ctrl/src #coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose --maxfail=1 \ # context/tests/test_unitary.py -# --log-level=INFO -o log_cli=true -pytest --verbose --maxfail=1 --durations=0 \ - context/tests/test_unitary.py +# --log-level=INFO -o log_cli=true --durations=0 +pytest --verbose --maxfail=1 \ + context/tests/test_unitary.py \ + context/tests/test_hasher.py #kubectl --namespace $TFS_K8S_NAMESPACE delete service redis-tests diff --git a/src/context/requirements.in b/src/context/requirements.in index 6c68d692d6745e93f23fac8ca04be492a262365f..f5d5ccbe2cf5d8f85649f85f00f6c9fa9b21cfc4 100644 --- a/src/context/requirements.in +++ b/src/context/requirements.in @@ -1,8 +1,10 @@ Flask==2.1.3 Flask-RESTful==0.3.9 psycopg2-binary==2.9.3 +pytest-depends==1.0.1 redis==4.1.2 requests==2.27.1 SQLAlchemy==1.4.40 sqlalchemy-cockroachdb==1.4.3 SQLAlchemy-Utils==0.38.3 +prettytable==3.5.0 diff --git a/src/context/service/ChangeFeedExample.py b/src/context/service/ChangeFeedExample.py new file mode 100644 index 0000000000000000000000000000000000000000..2bd46b546f1194ea8a109ee08a9d5c2907f3001d --- /dev/null +++ b/src/context/service/ChangeFeedExample.py @@ -0,0 +1,34 @@ +# 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. + + + @safe_and_metered_rpc_method(METRICS, LOGGER) + def GetContextEvents(self, request : Empty, context : grpc.ServicerContext) -> Iterator[ContextEvent]: + pass + #for message in self.messagebroker.consume({TOPIC_CONTEXT}, consume_timeout=CONSUME_TIMEOUT): + # yield ContextEvent(**json.loads(message.content)) + #cf = ChangeFeedClient() + #ready = cf.initialize() + #if not ready: raise OperationFailedException('Initialize ChangeFeed') + #for timestamp, _, primary_key, is_delete, after in cf.get_changes('context'): + # if is_delete: + # event_type = EventTypeEnum.EVENTTYPE_REMOVE + # else: + # is_create = (timestamp - after.get('created_at')) < 1.0 + # event_type = EventTypeEnum.EVENTTYPE_CREATE if is_create else EventTypeEnum.EVENTTYPE_UPDATE + # event = { + # 'event': {'timestamp': {'timestamp': timestamp}, 'event_type': event_type}, + # 'context_id': json_context_id(primary_key[0]), + # } + # yield ContextEvent(**event) diff --git a/src/context/service/Constants.py b/src/context/service/Constants.py index 9d7c886c725d22308f33dc274234ad17f595633d..25790fe2925cc18003cd3d7df057e9c2923803fb 100644 --- a/src/context/service/Constants.py +++ b/src/context/service/Constants.py @@ -14,12 +14,16 @@ TOPIC_CONNECTION = 'connection' TOPIC_CONTEXT = 'context' -TOPIC_TOPOLOGY = 'topology' TOPIC_DEVICE = 'device' TOPIC_LINK = 'link' +TOPIC_POLICY = 'policy' TOPIC_SERVICE = 'service' TOPIC_SLICE = 'slice' +TOPIC_TOPOLOGY = 'topology' -TOPICS = {TOPIC_CONNECTION, TOPIC_CONTEXT, TOPIC_TOPOLOGY, TOPIC_DEVICE, TOPIC_LINK, TOPIC_SERVICE, TOPIC_SLICE} +TOPICS = { + TOPIC_CONNECTION, TOPIC_CONTEXT, TOPIC_DEVICE, TOPIC_LINK, + TOPIC_POLICY, TOPIC_SERVICE, TOPIC_SLICE, TOPIC_TOPOLOGY +} CONSUME_TIMEOUT = 0.5 # seconds diff --git a/src/context/service/ContextServiceServicerImpl.py b/src/context/service/ContextServiceServicerImpl.py index 2661f25c12a5797a1bf11ec51551569a36c5c50f..5075d8889fd217623a76955ba0a786a8f5366c01 100644 --- a/src/context/service/ContextServiceServicerImpl.py +++ b/src/context/service/ContextServiceServicerImpl.py @@ -16,8 +16,8 @@ import grpc, json, logging, operator, sqlalchemy, threading, time, uuid from sqlalchemy.orm import Session, contains_eager, selectinload, sessionmaker from sqlalchemy.dialects.postgresql import UUID, insert -from sqlalchemy_cockroachdb import run_transaction from typing import Dict, Iterator, List, Optional, Set, Tuple, Union + from common.message_broker.MessageBroker import MessageBroker #from common.orm.backend.Tools import key_to_str from common.proto.context_pb2 import ( @@ -37,6 +37,10 @@ from common.tools.object_factory.Context import json_context_id from common.rpc_method_wrapper.Decorator import create_metrics, safe_and_metered_rpc_method from common.rpc_method_wrapper.ServiceExceptions import ( InvalidArgumentException, NotFoundException, OperationFailedException) +from context.service.database.methods.Context import context_delete, context_get, context_list_ids, context_list_objs, context_set +from context.service.database.methods.Device import device_delete, device_get, device_list_ids, device_list_objs, device_set +from context.service.database.methods.Link import link_delete, link_get, link_list_ids, link_list_objs, link_set +from context.service.database.methods.Topology import topology_delete, topology_get, topology_list_ids, topology_list_objs, topology_set #from common.tools.grpc.Tools import grpc_message_to_json, grpc_message_to_json_string #from context.service.Database import Database #from context.service.database.ConfigModel import ( @@ -44,24 +48,24 @@ from common.rpc_method_wrapper.ServiceExceptions import ( #from context.service.database.ConnectionModel import ConnectionModel, set_path #from context.service.database.ConstraintModel import ( # ConstraintModel, ConstraintsModel, Union_ConstraintModel, CONSTRAINT_PARSERS, set_constraints) -from context.service.database.ContextModel import ContextModel -from context.service.database.DeviceModel import ( - DeviceModel, grpc_to_enum__device_operational_status, grpc_to_enum__device_driver) -from context.service.database.EndPointModel import EndPointModel, grpc_to_enum__kpi_sample_type +#from context.service.database.models.ContextModel import ContextModel +#from context.service.database.models.DeviceModel import ( +# DeviceModel, grpc_to_enum__device_operational_status, grpc_to_enum__device_driver) +#from context.service.database.models.EndPointModel import EndPointModel, grpc_to_enum__kpi_sample_type #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.PolicyRuleModel import PolicyRuleModel -from context.service.database.RelationModels import TopologyDeviceModel +#from context.service.database.RelationModels import TopologyDeviceModel # ConnectionSubServiceModel, LinkEndPointModel, ServiceEndPointModel, SliceEndPointModel, SliceServiceModel, # SliceSubSliceModel, 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) +#from context.service.database.TopologyModel import TopologyModel +from .Constants import ( + CONSUME_TIMEOUT, TOPIC_CONNECTION, TOPIC_CONTEXT, TOPIC_DEVICE, TOPIC_LINK, TOPIC_POLICY, TOPIC_SERVICE, + TOPIC_SLICE, TOPIC_TOPOLOGY) #from .ChangeFeedClient import ChangeFeedClient LOGGER = logging.getLogger(__name__) @@ -84,508 +88,148 @@ class ContextServiceServicerImpl(ContextServiceServicer, ContextPolicyServiceSer def __init__(self, db_engine : sqlalchemy.engine.Engine, messagebroker : MessageBroker) -> None: LOGGER.debug('Creating Servicer...') self.db_engine = db_engine - #self.lock = threading.Lock() - #session = sessionmaker(bind=db_engine, expire_on_commit=False) - #self.session = session - #self.database = Database(session) self.messagebroker = messagebroker LOGGER.debug('Servicer Created') + def _get_metrics(self): return METRICS + + # ----- Context ---------------------------------------------------------------------------------------------------- @safe_and_metered_rpc_method(METRICS, LOGGER) def ListContextIds(self, request : Empty, context : grpc.ServicerContext) -> ContextIdList: - def callback(session : Session) -> List[Dict]: - obj_list : List[ContextModel] = session.query(ContextModel).all() - #.options(selectinload(ContextModel.topology)).filter_by(context_uuid=context_uuid).one_or_none() - return [obj.dump_id() for obj in obj_list] - return ContextIdList(context_ids=run_transaction(sessionmaker(bind=self.db_engine), callback)) + return context_list_ids(self.db_engine) @safe_and_metered_rpc_method(METRICS, LOGGER) def ListContexts(self, request : Empty, context : grpc.ServicerContext) -> ContextList: - def callback(session : Session) -> List[Dict]: - obj_list : List[ContextModel] = session.query(ContextModel).all() - #.options(selectinload(ContextModel.topology)).filter_by(context_uuid=context_uuid).one_or_none() - return [obj.dump() for obj in obj_list] - return ContextList(contexts=run_transaction(sessionmaker(bind=self.db_engine), callback)) + return context_list_objs(self.db_engine) @safe_and_metered_rpc_method(METRICS, LOGGER) def GetContext(self, request : ContextId, context : grpc.ServicerContext) -> Context: - context_uuid = request.context_uuid.uuid - def callback(session : Session) -> Optional[Dict]: - obj : Optional[ContextModel] = session.query(ContextModel)\ - .filter_by(context_uuid=context_uuid).one_or_none() - return None if obj is None else obj.dump() - obj = run_transaction(sessionmaker(bind=self.db_engine), callback) - if obj is None: raise NotFoundException(ContextModel.__name__.replace('Model', ''), context_uuid) - return Context(**obj) + return context_get(self.db_engine, request) @safe_and_metered_rpc_method(METRICS, LOGGER) def SetContext(self, request : Context, context : grpc.ServicerContext) -> ContextId: - context_uuid = request.context_id.context_uuid.uuid - context_name = request.name - - for i, topology_id in enumerate(request.topology_ids): - topology_context_uuid = topology_id.context_id.context_uuid.uuid - if topology_context_uuid != context_uuid: - raise InvalidArgumentException( - 'request.topology_ids[{:d}].context_id.context_uuid.uuid'.format(i), topology_context_uuid, - ['should be == {:s}({:s})'.format('request.context_id.context_uuid.uuid', context_uuid)]) - - for i, service_id in enumerate(request.service_ids): - service_context_uuid = service_id.context_id.context_uuid.uuid - if service_context_uuid != context_uuid: - raise InvalidArgumentException( - 'request.service_ids[{:d}].context_id.context_uuid.uuid'.format(i), service_context_uuid, - ['should be == {:s}({:s})'.format('request.context_id.context_uuid.uuid', context_uuid)]) - - for i, slice_id in enumerate(request.slice_ids): - slice_context_uuid = slice_id.context_id.context_uuid.uuid - if slice_context_uuid != context_uuid: - raise InvalidArgumentException( - 'request.slice_ids[{:d}].context_id.context_uuid.uuid'.format(i), slice_context_uuid, - ['should be == {:s}({:s})'.format('request.context_id.context_uuid.uuid', context_uuid)]) - - def callback(session : Session) -> Tuple[Optional[Dict], bool]: - obj : Optional[ContextModel] = session.query(ContextModel).with_for_update()\ - .filter_by(context_uuid=context_uuid).one_or_none() - is_update = obj is not None - if is_update: - obj.context_name = context_name - session.merge(obj) - else: - session.add(ContextModel(context_uuid=context_uuid, context_name=context_name, created_at=time.time())) - obj : Optional[ContextModel] = session.query(ContextModel)\ - .filter_by(context_uuid=context_uuid).one_or_none() - return (None if obj is None else obj.dump_id()), is_update - - obj_id,updated = run_transaction(sessionmaker(bind=self.db_engine), callback) - if obj_id is None: raise NotFoundException(ContextModel.__name__.replace('Model', ''), context_uuid) - + updated = context_set(self.db_engine, request) #event_type = EventTypeEnum.EVENTTYPE_UPDATE if updated else EventTypeEnum.EVENTTYPE_CREATE - #notify_event(self.messagebroker, TOPIC_CONTEXT, event_type, {'context_id': obj_id}) - return ContextId(**obj_id) + #notify_event(self.messagebroker, TOPIC_CONTEXT, event_type, {'context_id': request.context_id}) + return request.context_id @safe_and_metered_rpc_method(METRICS, LOGGER) def RemoveContext(self, request : ContextId, context : grpc.ServicerContext) -> Empty: - context_uuid = request.context_uuid.uuid - - def callback(session : Session) -> bool: - num_deleted = session.query(ContextModel).filter_by(context_uuid=context_uuid).delete() - return num_deleted > 0 - - deleted = run_transaction(sessionmaker(bind=self.db_engine), callback) + deleted = context_delete(self.db_engine, request) #if deleted: # notify_event(self.messagebroker, TOPIC_CONTEXT, EventTypeEnum.EVENTTYPE_REMOVE, {'context_id': request}) return Empty() @safe_and_metered_rpc_method(METRICS, LOGGER) def GetContextEvents(self, request : Empty, context : grpc.ServicerContext) -> Iterator[ContextEvent]: - pass - #for message in self.messagebroker.consume({TOPIC_CONTEXT}, consume_timeout=CONSUME_TIMEOUT): - # yield ContextEvent(**json.loads(message.content)) - #cf = ChangeFeedClient() - #ready = cf.initialize() - #if not ready: raise OperationFailedException('Initialize ChangeFeed') - #for timestamp, _, primary_key, is_delete, after in cf.get_changes('context'): - # if is_delete: - # event_type = EventTypeEnum.EVENTTYPE_REMOVE - # else: - # is_create = (timestamp - after.get('created_at')) < 1.0 - # event_type = EventTypeEnum.EVENTTYPE_CREATE if is_create else EventTypeEnum.EVENTTYPE_UPDATE - # event = { - # 'event': {'timestamp': {'timestamp': timestamp}, 'event_type': event_type}, - # 'context_id': json_context_id(primary_key[0]), - # } - # yield ContextEvent(**event) + for message in self.messagebroker.consume({TOPIC_CONTEXT}, consume_timeout=CONSUME_TIMEOUT): + yield ContextEvent(**json.loads(message.content)) + # ----- Topology --------------------------------------------------------------------------------------------------- @safe_and_metered_rpc_method(METRICS, LOGGER) def ListTopologyIds(self, request : ContextId, context : grpc.ServicerContext) -> TopologyIdList: - context_uuid = request.context_uuid.uuid - def callback(session : Session) -> List[Dict]: - obj_list : List[TopologyModel] = session.query(TopologyModel).filter_by(context_uuid=context_uuid).all() - #.options(selectinload(ContextModel.topology)).filter_by(context_uuid=context_uuid).one_or_none() - return [obj.dump_id() for obj in obj_list] - return TopologyIdList(topology_ids=run_transaction(sessionmaker(bind=self.db_engine), callback)) + return topology_list_ids(self.db_engine, request) @safe_and_metered_rpc_method(METRICS, LOGGER) def ListTopologies(self, request : ContextId, context : grpc.ServicerContext) -> TopologyList: - context_uuid = request.context_uuid.uuid - def callback(session : Session) -> List[Dict]: - obj_list : List[TopologyModel] = session.query(TopologyModel).filter_by(context_uuid=context_uuid).all() - #.options(selectinload(ContextModel.topology)).filter_by(context_uuid=context_uuid).one_or_none() - return [obj.dump() for obj in obj_list] - return TopologyList(topologies=run_transaction(sessionmaker(bind=self.db_engine), callback)) + return topology_list_objs(self.db_engine, request) @safe_and_metered_rpc_method(METRICS, LOGGER) def GetTopology(self, request : TopologyId, context : grpc.ServicerContext) -> Topology: - context_uuid = request.context_id.context_uuid.uuid - topology_uuid = request.topology_uuid.uuid - - def callback(session : Session) -> Optional[Dict]: - obj : Optional[TopologyModel] = session.query(TopologyModel)\ - .filter_by(context_uuid=context_uuid, topology_uuid=topology_uuid).one_or_none() - return None if obj is None else obj.dump() - obj = run_transaction(sessionmaker(bind=self.db_engine), callback) - if obj is None: - obj_uuid = '{:s}/{:s}'.format(context_uuid, topology_uuid) - raise NotFoundException(TopologyModel.__name__.replace('Model', ''), obj_uuid) - return Topology(**obj) + return topology_get(self.db_engine, request) @safe_and_metered_rpc_method(METRICS, LOGGER) def SetTopology(self, request : Topology, context : grpc.ServicerContext) -> TopologyId: - context_uuid = request.topology_id.context_id.context_uuid.uuid - topology_uuid = request.topology_id.topology_uuid.uuid - topology_name = request.name - - devices_to_add : List[str] = [ - {'context_uuid': context_uuid, 'topology_uuid': topology_uuid, 'device_uuid': device_id.device_uuid.uuid} - for device_id in request.device_ids - ] - links_to_add : List[str] = [ - {'context_uuid': context_uuid, 'topology_uuid': topology_uuid, 'link_uuid': link_id.link_uuid.uuid} - for link_id in request.link_ids - ] - print('devices_to_add', devices_to_add) - - def callback(session : Session) -> Tuple[Optional[Dict], bool]: - topology_data = [{ - 'context_uuid' : context_uuid, - 'topology_uuid': topology_uuid, - 'topology_name': topology_name, - 'created_at' : time.time(), - }] - stmt = insert(TopologyModel).values(topology_data) - stmt = stmt.on_conflict_do_update( - index_elements=[TopologyModel.context_uuid, TopologyModel.topology_uuid], - set_=dict(topology_name = stmt.excluded.topology_name) - ) - session.execute(stmt) - - if len(devices_to_add) > 0: - session.execute(insert(TopologyDeviceModel).values(devices_to_add).on_conflict_do_nothing( - index_elements=[ - TopologyDeviceModel.context_uuid, TopologyDeviceModel.topology_uuid, - TopologyDeviceModel.device_uuid - ] - )) - - #if len(link_to_add) > 0: - # session.execute(insert(TopologyLinkModel).values(link_to_add).on_conflict_do_nothing( - # index_elements=[ - # TopologyLinkModel.context_uuid, TopologyLinkModel.topology_uuid, - # TopologyLinkModel.link_uuid - # ] - # )) - - is_update = True - obj : Optional[TopologyModel] = session.query(TopologyModel)\ - .filter_by(context_uuid=context_uuid, topology_uuid=topology_uuid).one_or_none() - return (None if obj is None else obj.dump_id()), is_update - - obj_id,updated = run_transaction(sessionmaker(bind=self.db_engine), callback) - if obj_id is None: raise NotFoundException(ContextModel.__name__.replace('Model', ''), context_uuid) - + updated = topology_set(self.db_engine, request) #event_type = EventTypeEnum.EVENTTYPE_UPDATE if updated else EventTypeEnum.EVENTTYPE_CREATE - #notify_event(self.messagebroker, TOPIC_TOPOLOGY, event_type, {'topology_id': obj_id}) - return TopologyId(**obj_id) + #notify_event(self.messagebroker, TOPIC_TOPOLOGY, event_type, {'topology_id': request.topology_id}) + return request.topology_id @safe_and_metered_rpc_method(METRICS, LOGGER) def RemoveTopology(self, request : TopologyId, context : grpc.ServicerContext) -> Empty: - context_uuid = request.context_id.context_uuid.uuid - topology_uuid = request.topology_uuid.uuid - - def callback(session : Session) -> bool: - num_deleted = session.query(TopologyModel)\ - .filter_by(context_uuid=context_uuid, topology_uuid=topology_uuid).delete() - return num_deleted > 0 - - deleted = run_transaction(sessionmaker(bind=self.db_engine), callback) + deleted = topology_delete(self.db_engine, request) #if deleted: # notify_event(self.messagebroker, TOPIC_TOPOLOGY, EventTypeEnum.EVENTTYPE_REMOVE, {'topology_id': request}) return Empty() @safe_and_metered_rpc_method(METRICS, LOGGER) def GetTopologyEvents(self, request : Empty, context : grpc.ServicerContext) -> Iterator[TopologyEvent]: - pass - #for message in self.messagebroker.consume({TOPIC_TOPOLOGY}, consume_timeout=CONSUME_TIMEOUT): - # yield TopologyEvent(**json.loads(message.content)) + for message in self.messagebroker.consume({TOPIC_TOPOLOGY}, consume_timeout=CONSUME_TIMEOUT): + yield TopologyEvent(**json.loads(message.content)) + # ----- Device ----------------------------------------------------------------------------------------------------- @safe_and_metered_rpc_method(METRICS, LOGGER) def ListDeviceIds(self, request : Empty, context : grpc.ServicerContext) -> DeviceIdList: - def callback(session : Session) -> List[Dict]: - obj_list : List[DeviceModel] = session.query(DeviceModel).all() - #.options(selectinload(DeviceModel.topology)).filter_by(context_uuid=context_uuid).one_or_none() - return [obj.dump_id() for obj in obj_list] - return DeviceIdList(device_ids=run_transaction(sessionmaker(bind=self.db_engine), callback)) + return device_list_ids(self.db_engine) @safe_and_metered_rpc_method(METRICS, LOGGER) def ListDevices(self, request : Empty, context : grpc.ServicerContext) -> DeviceList: - def callback(session : Session) -> List[Dict]: - obj_list : List[DeviceModel] = session.query(DeviceModel).all() - #.options(selectinload(DeviceModel.topology)).filter_by(context_uuid=context_uuid).one_or_none() - return [obj.dump() for obj in obj_list] - return DeviceList(devices=run_transaction(sessionmaker(bind=self.db_engine), callback)) + return device_list_objs(self.db_engine) @safe_and_metered_rpc_method(METRICS, LOGGER) def GetDevice(self, request : ContextId, context : grpc.ServicerContext) -> Device: - device_uuid = request.device_uuid.uuid - def callback(session : Session) -> Optional[Dict]: - obj : Optional[DeviceModel] = session.query(DeviceModel)\ - .filter_by(device_uuid=device_uuid).one_or_none() - return None if obj is None else obj.dump() - obj = run_transaction(sessionmaker(bind=self.db_engine), callback) - if obj is None: raise NotFoundException(DeviceModel.__name__.replace('Model', ''), device_uuid) - return Device(**obj) + return device_get(self.db_engine, request) @safe_and_metered_rpc_method(METRICS, LOGGER) def SetDevice(self, request : Device, context : grpc.ServicerContext) -> DeviceId: - device_uuid = request.device_id.device_uuid.uuid - device_name = request.name - device_type = request.device_type - oper_status = grpc_to_enum__device_operational_status(request.device_operational_status) - device_drivers = [grpc_to_enum__device_driver(d) for d in request.device_drivers] - - related_topology_uuids : Set[Tuple[str, str]] = set() - endpoints_data : List[Dict] = list() - for i, endpoint in enumerate(request.device_endpoints): - endpoint_device_uuid = endpoint.endpoint_id.device_id.device_uuid.uuid - if len(endpoint_device_uuid) == 0: endpoint_device_uuid = device_uuid - if device_uuid != endpoint_device_uuid: - raise InvalidArgumentException( - 'request.device_endpoints[{:d}].device_id.device_uuid.uuid'.format(i), endpoint_device_uuid, - ['should be == {:s}({:s})'.format('request.device_id.device_uuid.uuid', device_uuid)]) - - endpoint_context_uuid = endpoint.endpoint_id.topology_id.context_id.context_uuid.uuid - endpoint_topology_uuid = endpoint.endpoint_id.topology_id.topology_uuid.uuid - - kpi_sample_types = [grpc_to_enum__kpi_sample_type(kst) for kst in endpoint.kpi_sample_types] + updated = device_set(self.db_engine, request) + #event_type = EventTypeEnum.EVENTTYPE_UPDATE if updated else EventTypeEnum.EVENTTYPE_CREATE + #notify_event(self.messagebroker, TOPIC_DEVICE, event_type, {'device_id': request.device_id}) + return request.device_id - endpoints_data.append({ - 'context_uuid' : endpoint_context_uuid, - 'topology_uuid' : endpoint_topology_uuid, - 'device_uuid' : endpoint_device_uuid, - 'endpoint_uuid' : endpoint.endpoint_id.endpoint_uuid.uuid, - 'endpoint_type' : endpoint.endpoint_type, - 'kpi_sample_types': kpi_sample_types, - }) + @safe_and_metered_rpc_method(METRICS, LOGGER) + def RemoveDevice(self, request : DeviceId, context : grpc.ServicerContext) -> Empty: + deleted = device_delete(self.db_engine, request) + #if deleted: + # notify_event(self.messagebroker, TOPIC_DEVICE, EventTypeEnum.EVENTTYPE_REMOVE, {'device_id': request}) + return Empty() - if len(endpoint_context_uuid) > 0 and len(endpoint_topology_uuid) > 0: - related_topology_uuids.add({ - 'context_uuid': endpoint_context_uuid, - 'topology_uuid': endpoint_topology_uuid, - 'device_uuid': endpoint_device_uuid, - }) + @safe_and_metered_rpc_method(METRICS, LOGGER) + def GetDeviceEvents(self, request : Empty, context : grpc.ServicerContext) -> Iterator[DeviceEvent]: + for message in self.messagebroker.consume({TOPIC_DEVICE}, consume_timeout=CONSUME_TIMEOUT): + yield DeviceEvent(**json.loads(message.content)) - def callback(session : Session) -> Tuple[Optional[Dict], bool]: - obj : Optional[DeviceModel] = session.query(DeviceModel).with_for_update()\ - .filter_by(device_uuid=device_uuid).one_or_none() - is_update = obj is not None - if is_update: - obj.device_name = device_name - obj.device_type = device_type - obj.device_operational_status = oper_status - obj.device_drivers = device_drivers - session.merge(obj) - else: - session.add(DeviceModel( - device_uuid=device_uuid, device_name=device_name, device_type=device_type, - device_operational_status=oper_status, device_drivers=device_drivers, created_at=time.time())) - obj : Optional[DeviceModel] = session.query(DeviceModel)\ - .filter_by(device_uuid=device_uuid).one_or_none() - stmt = insert(EndPointModel).values(endpoints_data) - stmt = stmt.on_conflict_do_update( - index_elements=[ - EndPointModel.context_uuid, EndPointModel.topology_uuid, EndPointModel.device_uuid, - EndPointModel.endpoint_uuid - ], - set_=dict( - endpoint_type = stmt.excluded.endpoint_type, - kpi_sample_types = stmt.excluded.kpi_sample_types, - ) - ) - session.execute(stmt) + # ----- Link ------------------------------------------------------------------------------------------------------- - session.execute(insert(TopologyDeviceModel).values(list(related_topology_uuids)).on_conflict_do_nothing( - index_elements=[ - TopologyDeviceModel.context_uuid, TopologyDeviceModel.topology_uuid, - TopologyDeviceModel.device_uuid - ] - )) + @safe_and_metered_rpc_method(METRICS, LOGGER) + def ListLinkIds(self, request : Empty, context : grpc.ServicerContext) -> LinkIdList: + return link_list_ids(self.db_engine) - return (None if obj is None else obj.dump_id()), is_update + @safe_and_metered_rpc_method(METRICS, LOGGER) + def ListLinks(self, request : Empty, context : grpc.ServicerContext) -> LinkList: + return link_list_objs(self.db_engine) - obj_id,updated = run_transaction(sessionmaker(bind=self.db_engine), callback) - if obj_id is None: raise NotFoundException(DeviceModel.__name__.replace('Model', ''), device_uuid) + @safe_and_metered_rpc_method(METRICS, LOGGER) + def GetLink(self, request : LinkId, context : grpc.ServicerContext) -> Link: + return link_get(self.db_engine, request) + @safe_and_metered_rpc_method(METRICS, LOGGER) + def SetLink(self, request : Link, context : grpc.ServicerContext) -> LinkId: + updated = link_set(self.db_engine, request) #event_type = EventTypeEnum.EVENTTYPE_UPDATE if updated else EventTypeEnum.EVENTTYPE_CREATE - #notify_event(self.messagebroker, TOPIC_DEVICE, event_type, {'device_id': obj_id}) - return DeviceId(**obj_id) - -# with self.session() as session: -# config_rules = grpc_config_rules_to_raw(request.device_config.config_rules) -# running_config_result = self.update_config(session, device_uuid, 'device', config_rules) -# db_running_config = running_config_result[0][0] -# config_uuid = db_running_config.config_uuid -# running_config_rules = update_config( -# self.database, device_uuid, 'device', request.device_config.config_rules) -# db_running_config = running_config_rules[0][0] -# -# new_obj = DeviceModel(**{ -# 'device_uuid' : device_uuid, -# 'device_type' : request.device_type, -# 'device_operational_status' : grpc_to_enum__device_operational_status(request.device_operational_status), -# 'device_config_uuid' : config_uuid, -# }) -# result: Tuple[DeviceModel, bool] = self.database.create_or_update(new_obj) -# db_device, updated = result -# -# self.set_drivers(db_device, request.device_drivers) -# -# + #notify_event(self.messagebroker, TOPIC_LINK, event_type, {'link_id': request.link_id}) + return request.link_id @safe_and_metered_rpc_method(METRICS, LOGGER) - def RemoveDevice(self, request : DeviceId, context : grpc.ServicerContext) -> Empty: - device_uuid = request.device_uuid.uuid - def callback(session : Session) -> bool: - session.query(TopologyDeviceModel).filter_by(device_uuid=device_uuid).delete() - num_deleted = session.query(DeviceModel).filter_by(device_uuid=device_uuid).delete() - #db_device = session.query(DeviceModel).filter_by(device_uuid=device_uuid).one_or_none() - #session.query(ConfigRuleModel).filter_by(config_uuid=db_device.device_config_uuid).delete() - #session.query(ConfigModel).filter_by(config_uuid=db_device.device_config_uuid).delete() - #session.query(DeviceModel).filter_by(device_uuid=device_uuid).delete() - return num_deleted > 0 - deleted = run_transaction(sessionmaker(bind=self.db_engine), callback) + def RemoveLink(self, request : LinkId, context : grpc.ServicerContext) -> Empty: + deleted = link_delete(self.db_engine, request) #if deleted: - # notify_event(self.messagebroker, TOPIC_DEVICE, EventTypeEnum.EVENTTYPE_REMOVE, {'device_id': request}) + # notify_event(self.messagebroker, TOPIC_LINK, event_type, {'link_id': dict_link_id}) return Empty() @safe_and_metered_rpc_method(METRICS, LOGGER) - def GetDeviceEvents(self, request : Empty, context : grpc.ServicerContext) -> Iterator[DeviceEvent]: - pass - #for message in self.messagebroker.consume({TOPIC_DEVICE}, consume_timeout=CONSUME_TIMEOUT): - # yield DeviceEvent(**json.loads(message.content)) + def GetLinkEvents(self, request : Empty, context : grpc.ServicerContext) -> Iterator[LinkEvent]: + for message in self.messagebroker.consume({TOPIC_LINK}, consume_timeout=CONSUME_TIMEOUT): + yield LinkEvent(**json.loads(message.content)) -# # ----- Link ------------------------------------------------------------------------------------------------------- -# -# @safe_and_metered_rpc_method(METRICS, LOGGER) -# def ListLinkIds(self, request : Empty, context : grpc.ServicerContext) -> LinkIdList: -# with self.session() as session: -# result = session.query(LinkModel).all() -# return LinkIdList(link_ids=[db_link.dump_id() for db_link in result]) -# -# -# @safe_and_metered_rpc_method(METRICS, LOGGER) -# def ListLinks(self, request : Empty, context : grpc.ServicerContext) -> LinkList: -# with self.session() as session: -# link_list = LinkList() -# -# db_links = session.query(LinkModel).all() -# -# for db_link in db_links: -# link_uuid = db_link.link_uuid -# filt = {'link_uuid': link_uuid} -# link_endpoints = session.query(LinkEndPointModel).filter_by(**filt).all() -# if link_endpoints: -# eps = [] -# for lep in link_endpoints: -# filt = {'endpoint_uuid': lep.endpoint_uuid} -# eps.append(session.query(EndPointModel).filter_by(**filt).one()) -# link_list.links.append(Link(**db_link.dump(eps))) -# -# return link_list -# -# @safe_and_metered_rpc_method(METRICS, LOGGER) -# def GetLink(self, request : LinkId, context : grpc.ServicerContext) -> Link: -# link_uuid = request.link_uuid.uuid -# with self.session() as session: -# result = session.query(LinkModel).filter(LinkModel.link_uuid == link_uuid).one_or_none() -# if not result: -# raise NotFoundException(LinkModel.__name__.replace('Model', ''), link_uuid) -# -# filt = {'link_uuid': link_uuid} -# link_endpoints = session.query(LinkEndPointModel).filter_by(**filt).all() -# if link_endpoints: -# eps = [] -# for lep in link_endpoints: -# filt = {'endpoint_uuid': lep.endpoint_uuid} -# eps.append(session.query(EndPointModel).filter_by(**filt).one()) -# return Link(**result.dump(eps)) -# -# rd = result.dump() -# rt = Link(**rd) -# -# return rt -# -# -# -# @safe_and_metered_rpc_method(METRICS, LOGGER) -# def SetLink(self, request : Link, context : grpc.ServicerContext) -> LinkId: -# link_uuid = request.link_id.link_uuid.uuid -# -# new_link = LinkModel(**{ -# 'link_uuid': link_uuid -# }) -# result: Tuple[LinkModel, bool] = self.database.create_or_update(new_link) -# db_link, updated = result -# -# for endpoint_id in request.link_endpoint_ids: -# endpoint_uuid = endpoint_id.endpoint_uuid.uuid -# endpoint_device_uuid = endpoint_id.device_id.device_uuid.uuid -# endpoint_topology_uuid = endpoint_id.topology_id.topology_uuid.uuid -# endpoint_topology_context_uuid = endpoint_id.topology_id.context_id.context_uuid.uuid -# -# -# db_topology = None -# if len(endpoint_topology_context_uuid) > 0 and len(endpoint_topology_uuid) > 0: -# db_topology: TopologyModel = self.database.get_object(TopologyModel, endpoint_topology_uuid) -# # check device is in topology -# self.database.get_object(TopologyDeviceModel, endpoint_device_uuid) -# -# -# link_endpoint = LinkEndPointModel(link_uuid=link_uuid, endpoint_uuid=endpoint_uuid) -# result: Tuple[LinkEndPointModel, bool] = self.database.create_or_update(link_endpoint) -# -# if db_topology is not None: -# topology_link = TopologyLinkModel(topology_uuid=endpoint_topology_uuid, link_uuid=link_uuid) -# result: Tuple[TopologyLinkModel, bool] = self.database.create_or_update(topology_link) -# -# event_type = EventTypeEnum.EVENTTYPE_UPDATE if updated else EventTypeEnum.EVENTTYPE_CREATE -# dict_link_id = db_link.dump_id() -# notify_event(self.messagebroker, TOPIC_LINK, event_type, {'link_id': dict_link_id}) -# return LinkId(**dict_link_id) -# -# @safe_and_metered_rpc_method(METRICS, LOGGER) -# def RemoveLink(self, request : LinkId, context : grpc.ServicerContext) -> Empty: -# with self.session() as session: -# link_uuid = request.link_uuid.uuid -# -# session.query(TopologyLinkModel).filter_by(link_uuid=link_uuid).delete() -# session.query(LinkEndPointModel).filter_by(link_uuid=link_uuid).delete() -# -# result = session.query(LinkModel).filter_by(link_uuid=link_uuid).one_or_none() -# if not result: -# return Empty() -# dict_link_id = result.dump_id() -# -# session.query(LinkModel).filter_by(link_uuid=link_uuid).delete() -# session.commit() -# event_type = EventTypeEnum.EVENTTYPE_REMOVE -# notify_event(self.messagebroker, TOPIC_LINK, event_type, {'link_id': dict_link_id}) -# return Empty() -# -## @safe_and_metered_rpc_method(METRICS, LOGGER) -## def GetLinkEvents(self, request : Empty, context : grpc.ServicerContext) -> Iterator[LinkEvent]: -## for message in self.messagebroker.consume({TOPIC_LINK}, consume_timeout=CONSUME_TIMEOUT): -## yield LinkEvent(**json.loads(message.content)) -# -# # # ----- Service ---------------------------------------------------------------------------------------------------- # # @safe_and_metered_rpc_method(METRICS, LOGGER) @@ -810,13 +454,13 @@ class ContextServiceServicerImpl(ContextServiceServicer, ContextPolicyServiceSer # event_type = EventTypeEnum.EVENTTYPE_REMOVE # notify_event(self.messagebroker, TOPIC_SERVICE, event_type, {'service_id': dict_service_id}) # return Empty() -# -## @safe_and_metered_rpc_method(METRICS, LOGGER) -## def GetServiceEvents(self, request : Empty, context : grpc.ServicerContext) -> Iterator[ServiceEvent]: -## for message in self.messagebroker.consume({TOPIC_SERVICE}, consume_timeout=CONSUME_TIMEOUT): -## yield ServiceEvent(**json.loads(message.content)) -# -# + + @safe_and_metered_rpc_method(METRICS, LOGGER) + def GetServiceEvents(self, request : Empty, context : grpc.ServicerContext) -> Iterator[ServiceEvent]: + for message in self.messagebroker.consume({TOPIC_SERVICE}, consume_timeout=CONSUME_TIMEOUT): + yield ServiceEvent(**json.loads(message.content)) + + # # ----- Slice ---------------------------------------------------------------------------------------------------- # # @safe_and_metered_rpc_method(METRICS, LOGGER) @@ -993,13 +637,13 @@ class ContextServiceServicerImpl(ContextServiceServicer, ContextPolicyServiceSer # event_type = EventTypeEnum.EVENTTYPE_REMOVE # notify_event(self.messagebroker, TOPIC_SLICE, event_type, {'slice_id': dict_slice_id}) # return Empty() -# -## @safe_and_metered_rpc_method(METRICS, LOGGER) -## def GetSliceEvents(self, request : Empty, context : grpc.ServicerContext) -> Iterator[SliceEvent]: -## for message in self.messagebroker.consume({TOPIC_SLICE}, consume_timeout=CONSUME_TIMEOUT): -## yield SliceEvent(**json.loads(message.content)) -# -# + + @safe_and_metered_rpc_method(METRICS, LOGGER) + def GetSliceEvents(self, request : Empty, context : grpc.ServicerContext) -> Iterator[SliceEvent]: + for message in self.messagebroker.consume({TOPIC_SLICE}, consume_timeout=CONSUME_TIMEOUT): + yield SliceEvent(**json.loads(message.content)) + + # # ----- Connection ------------------------------------------------------------------------------------------------- # # @safe_and_metered_rpc_method(METRICS, LOGGER) @@ -1082,13 +726,13 @@ class ContextServiceServicerImpl(ContextServiceServicer, ContextPolicyServiceSer # event_type = EventTypeEnum.EVENTTYPE_REMOVE # notify_event(self.messagebroker, TOPIC_CONNECTION, event_type, {'connection_id': dict_connection_id}) # return Empty() -# -## @safe_and_metered_rpc_method(METRICS, LOGGER) -## def GetConnectionEvents(self, request : Empty, context : grpc.ServicerContext) -> Iterator[ConnectionEvent]: -## for message in self.messagebroker.consume({TOPIC_CONNECTION}, consume_timeout=CONSUME_TIMEOUT): -## yield ConnectionEvent(**json.loads(message.content)) -# -# + + @safe_and_metered_rpc_method(METRICS, LOGGER) + def GetConnectionEvents(self, request : Empty, context : grpc.ServicerContext) -> Iterator[ConnectionEvent]: + for message in self.messagebroker.consume({TOPIC_CONNECTION}, consume_timeout=CONSUME_TIMEOUT): + yield ConnectionEvent(**json.loads(message.content)) + + # # ----- Policy ----------------------------------------------------------------------------------------------------- # # @safe_and_metered_rpc_method(METRICS, LOGGER) @@ -1140,4 +784,3 @@ class ContextServiceServicerImpl(ContextServiceServicer, ContextPolicyServiceSer # #event_type = EventTypeEnum.EVENTTYPE_REMOVE # #notify_event(self.messagebroker, TOPIC_POLICY, event_type, {"policy_id": dict_policy_id}) # return Empty() -# \ No newline at end of file diff --git a/src/context/service/Database.py b/src/context/service/Database.py index 03598a97fb5df6b1cb6951449d8df2d3a360e0ff..edb903a104070f407e2511a1b947a38ad4d7ed34 100644 --- a/src/context/service/Database.py +++ b/src/context/service/Database.py @@ -2,7 +2,7 @@ import logging from sqlalchemy import MetaData from sqlalchemy.orm import Session #, joinedload from typing import Tuple #, List -from context.service.database._Base import _Base +from context.service.database.models._Base import _Base #from common.orm.backend.Tools import key_to_str from common.rpc_method_wrapper.ServiceExceptions import NotFoundException diff --git a/src/context/service/Engine.py b/src/context/service/Engine.py index ec4702f271ecec2659f7c227e9540db8a1c03e26..151f33751b0a04657d01ab4b8369f1dd6cc4b2c5 100644 --- a/src/context/service/Engine.py +++ b/src/context/service/Engine.py @@ -18,6 +18,7 @@ from common.Settings import get_setting LOGGER = logging.getLogger(__name__) APP_NAME = 'tfs' +ECHO = False # true: dump SQL commands and transactions executed class Engine: @staticmethod @@ -26,7 +27,7 @@ class Engine: try: engine = sqlalchemy.create_engine( - crdb_uri, connect_args={'application_name': APP_NAME}, echo=True, future=True) + crdb_uri, connect_args={'application_name': APP_NAME}, echo=ECHO, future=True) except: # pylint: disable=bare-except LOGGER.exception('Failed to connect to database: {:s}'.format(crdb_uri)) return None diff --git a/src/context/service/database/Events.py b/src/context/service/Events.py similarity index 100% rename from src/context/service/database/Events.py rename to src/context/service/Events.py diff --git a/src/context/service/_old_code/_test_restapi.py b/src/context/service/_old_code/_test_restapi.py new file mode 100644 index 0000000000000000000000000000000000000000..82a8bca4005eb1ff575293a473c7cebeb5f69a76 --- /dev/null +++ b/src/context/service/_old_code/_test_restapi.py @@ -0,0 +1,31 @@ +# 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. + +import logging +#from context.service._old_code.Populate import populate +#from context.service.rest_server.RestServer import RestServer +#from context.service.rest_server.Resources import RESOURCES + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +#def do_rest_request(url : str): +# base_url = get_service_baseurl_http(ServiceNameEnum.CONTEXT) +# request_url = 'http://{:s}:{:s}{:s}{:s}'.format(str(LOCAL_HOST), str(HTTP_PORT), str(base_url), url) +# LOGGER.warning('Request: GET {:s}'.format(str(request_url))) +# reply = requests.get(request_url) +# LOGGER.warning('Reply: {:s}'.format(str(reply.text))) +# assert reply.status_code == 200, 'Reply failed with code {}'.format(reply.status_code) +# return reply.json() + diff --git a/src/context/service/_old_code/test_unitary.py b/src/context/service/_old_code/test_unitary.py index 04e054aad022a916d443feac8916b7eb436cafe2..5a0dcb9c18f4693a40fc3b48ed0297f5902e5d79 100644 --- a/src/context/service/_old_code/test_unitary.py +++ b/src/context/service/_old_code/test_unitary.py @@ -34,7 +34,7 @@ from common.type_checkers.Assertions import ( validate_topology_ids) from context.client.ContextClient import ContextClient from context.client.EventsCollector import EventsCollector -from context.service.database.Tools import ( +from context.service.database.tools.Tools import ( FASTHASHER_DATA_ACCEPTED_FORMAT, FASTHASHER_ITEM_ACCEPTED_FORMAT, fast_hasher) from context.service.grpc_server.ContextService import ContextService from context.service._old_code.Populate import populate @@ -43,7 +43,7 @@ from context.service.rest_server.Resources import RESOURCES from requests import Session from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker -from context.service.database._Base import Base +from context.service.database.models._Base import Base from .Objects import ( CONNECTION_R1_R3, CONNECTION_R1_R3_ID, CONNECTION_R1_R3_UUID, CONTEXT, CONTEXT_ID, DEVICE_R1, DEVICE_R1_ID, diff --git a/src/context/service/database/ConfigModel.py b/src/context/service/database/ConfigModel.py deleted file mode 100644 index d36622e765b6011c0ac49ef382888438e9139979..0000000000000000000000000000000000000000 --- a/src/context/service/database/ConfigModel.py +++ /dev/null @@ -1,278 +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. -import enum -import functools, logging, operator -from typing import Dict, List, Optional, Tuple, Union -from common.orm.backend.Tools import key_to_str -from common.proto.context_pb2 import ConfigActionEnum -from common.tools.grpc.Tools import grpc_message_to_json_string -from sqlalchemy import Column, ForeignKey, INTEGER, CheckConstraint, Enum, String -from sqlalchemy.dialects.postgresql import UUID, ARRAY -from context.service.database._Base import _Base -from sqlalchemy.orm import relationship -from context.service.Database import Database - - -import functools, json, logging, operator -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_object, get_or_create_object, update_or_create_object -from common.orm.backend.Tools import key_to_str -from common.orm.fields.EnumeratedField import EnumeratedField -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 ConfigActionEnum, ConfigRule -from common.tools.grpc.Tools import grpc_message_to_json_string -#from .EndPointModel import EndPointModel, get_endpoint -from .Tools import fast_hasher, grpc_to_enum, remove_dict_key - -LOGGER = logging.getLogger(__name__) - -class ORM_ConfigActionEnum(enum.Enum): - UNDEFINED = ConfigActionEnum.CONFIGACTION_UNDEFINED - SET = ConfigActionEnum.CONFIGACTION_SET - DELETE = ConfigActionEnum.CONFIGACTION_DELETE - -grpc_to_enum__config_action = functools.partial( - grpc_to_enum, ConfigActionEnum, ORM_ConfigActionEnum) - -class ConfigModel(Base): # pylint: disable=abstract-method - __tablename__ = 'Config' - config_uuid = Column(UUID(as_uuid=False), primary_key=True) - - # Relationships - config_rule = relationship("ConfigRuleModel", cascade="all,delete", back_populates="config", lazy='joined') - - def dump(self) -> List[Dict]: - config_rules = [] - for a in self.config_rule: - asdf = a.dump() - config_rules.append(asdf) - return [remove_dict_key(config_rule, 'position') for config_rule in config_rules] - - @staticmethod - def main_pk_name(): - return 'config_uuid' - -class ConfigRuleModel(Base): # pylint: disable=abstract-method - __tablename__ = 'ConfigRule' - config_rule_uuid = Column(UUID(as_uuid=False), primary_key=True) - config_uuid = Column(UUID(as_uuid=False), ForeignKey("Config.config_uuid", ondelete='CASCADE'), primary_key=True) - - action = Column(Enum(ORM_ConfigActionEnum, create_constraint=True, native_enum=True), nullable=False) - position = Column(INTEGER, nullable=False) - key = Column(String, nullable=False) - value = Column(String, nullable=False) - - __table_args__ = ( - CheckConstraint(position >= 0, name='check_position_value'), - {} - ) - - # Relationships - config = relationship("ConfigModel", passive_deletes=True, back_populates="config_rule") -class ConfigRuleCustomModel(Model): # pylint: disable=abstract-method - key = StringField(required=True, allow_empty=False) - value = StringField(required=True, allow_empty=False) - - def dump(self) -> Dict: # pylint: disable=arguments-differ - return {'custom': {'resource_key': self.key, 'resource_value': self.value}} - -class ConfigRuleAclModel(Model): # pylint: disable=abstract-method - # TODO: improve definition of fields in ConfigRuleAclModel - # To simplify, endpoint encoded as JSON-string directly; otherwise causes circular dependencies - #endpoint_fk = ForeignKeyField(EndPointModel) - endpoint_id = StringField(required=True, allow_empty=False) - # To simplify, ACL rule is encoded as a JSON-string directly - acl_data = StringField(required=True, allow_empty=False) - - def dump(self) -> Dict: # pylint: disable=arguments-differ - #json_endpoint_id = EndPointModel(self.database, self.endpoint_fk).dump_id() - json_endpoint_id = json.loads(self.endpoint_id) - json_acl_rule_set = json.loads(self.acl_data) - return {'acl': {'endpoint_id': json_endpoint_id, 'rule_set': json_acl_rule_set}} - -# enum values should match name of field in ConfigRuleModel -class ConfigRuleKindEnum(Enum): - CUSTOM = 'custom' - ACL = 'acl' - -Union_SpecificConfigRule = Union[ - ConfigRuleCustomModel, ConfigRuleAclModel -] - -class ConfigRuleModel(Model): # pylint: disable=abstract-method - pk = PrimaryKeyField() - config_fk = ForeignKeyField(ConfigModel) - kind = EnumeratedField(ConfigRuleKindEnum) - position = IntegerField(min_value=0, required=True) - action = EnumeratedField(ORM_ConfigActionEnum, required=True) - config_rule_custom_fk = ForeignKeyField(ConfigRuleCustomModel, required=False) - config_rule_acl_fk = ForeignKeyField(ConfigRuleAclModel, required=False) - - def delete(self) -> None: - field_name = 'config_rule_{: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 config_rule key for field_name({:s})'.format(field_name)) - specific_fk_class = getattr(ConfigRuleModel, 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 - field_name = 'config_rule_{: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 config_rule key for field_name({:s})'.format(field_name)) - specific_fk_class = getattr(ConfigRuleModel, field_name, None) - foreign_model_class : Model = specific_fk_class.foreign_model - config_rule : Union_SpecificConfigRule = get_object(self.database, foreign_model_class, str(specific_fk_value)) - result = config_rule.dump() - result['action'] = self.action.value - if include_position: result['position'] = self.position - return result - - @staticmethod - def main_pk_name(): - return 'config_rule_uuid' - -def set_config_rule( - database : Database, db_config : ConfigModel, position : int, resource_key : str, resource_value : str, -): # -> Tuple[ConfigRuleModel, bool]: - - str_rule_key_hash = fast_hasher(resource_key) - str_config_rule_key = key_to_str([db_config.config_uuid, str_rule_key_hash], separator=':') - - data = {'config_fk': db_config, 'position': position, 'action': ORM_ConfigActionEnum.SET, 'key': resource_key, - 'value': resource_value} - to_add = ConfigRuleModel(**data) - - result = database.create_or_update(to_add) - return result -Tuple_ConfigRuleSpecs = Tuple[Type, str, Dict, ConfigRuleKindEnum] - -def parse_config_rule_custom(database : Database, grpc_config_rule) -> Tuple_ConfigRuleSpecs: - config_rule_class = ConfigRuleCustomModel - str_config_rule_id = grpc_config_rule.custom.resource_key - config_rule_data = { - 'key' : grpc_config_rule.custom.resource_key, - 'value': grpc_config_rule.custom.resource_value, - } - return config_rule_class, str_config_rule_id, config_rule_data, ConfigRuleKindEnum.CUSTOM - -def parse_config_rule_acl(database : Database, grpc_config_rule) -> Tuple_ConfigRuleSpecs: - config_rule_class = ConfigRuleAclModel - grpc_endpoint_id = grpc_config_rule.acl.endpoint_id - grpc_rule_set = grpc_config_rule.acl.rule_set - device_uuid = grpc_endpoint_id.device_id.device_uuid.uuid - endpoint_uuid = grpc_endpoint_id.endpoint_uuid.uuid - str_endpoint_key = '/'.join([device_uuid, endpoint_uuid]) - #str_endpoint_key, db_endpoint = get_endpoint(database, grpc_endpoint_id) - str_config_rule_id = ':'.join([str_endpoint_key, grpc_rule_set.name]) - config_rule_data = { - #'endpoint_fk': db_endpoint, - 'endpoint_id': grpc_message_to_json_string(grpc_endpoint_id), - 'acl_data': grpc_message_to_json_string(grpc_rule_set), - } - return config_rule_class, str_config_rule_id, config_rule_data, ConfigRuleKindEnum.ACL - -CONFIGRULE_PARSERS = { - 'custom': parse_config_rule_custom, - 'acl' : parse_config_rule_acl, -} - -Union_ConfigRuleModel = Union[ - ConfigRuleCustomModel, ConfigRuleAclModel, -] - -def set_config_rule( - database : Database, db_config : ConfigModel, grpc_config_rule : ConfigRule, position : int -) -> Tuple[Union_ConfigRuleModel, bool]: - grpc_config_rule_kind = str(grpc_config_rule.WhichOneof('config_rule')) - parser = CONFIGRULE_PARSERS.get(grpc_config_rule_kind) - if parser is None: - raise NotImplementedError('ConfigRule of kind {:s} is not implemented: {:s}'.format( - grpc_config_rule_kind, grpc_message_to_json_string(grpc_config_rule))) - - # create specific ConfigRule - config_rule_class, str_config_rule_id, config_rule_data, config_rule_kind = parser(database, grpc_config_rule) - str_config_rule_key_hash = fast_hasher(':'.join([config_rule_kind.value, str_config_rule_id])) - str_config_rule_key = key_to_str([db_config.pk, str_config_rule_key_hash], separator=':') - result : Tuple[Union_ConfigRuleModel, bool] = update_or_create_object( - database, config_rule_class, str_config_rule_key, config_rule_data) - db_specific_config_rule, updated = result - - # create generic ConfigRule - config_rule_fk_field_name = 'config_rule_{:s}_fk'.format(config_rule_kind.value) - config_rule_data = { - 'config_fk': db_config, 'kind': config_rule_kind, 'position': position, - 'action': ORM_ConfigActionEnum.SET, - config_rule_fk_field_name: db_specific_config_rule - } - result : Tuple[ConfigRuleModel, bool] = update_or_create_object( - database, ConfigRuleModel, str_config_rule_key, config_rule_data) - db_config_rule, updated = result - - return db_config_rule, updated - -def delete_config_rule( - database : Database, db_config : ConfigModel, grpc_config_rule : ConfigRule -) -> None: - grpc_config_rule_kind = str(grpc_config_rule.WhichOneof('config_rule')) - parser = CONFIGRULE_PARSERS.get(grpc_config_rule_kind) - if parser is None: - raise NotImplementedError('ConfigRule of kind {:s} is not implemented: {:s}'.format( - grpc_config_rule_kind, grpc_message_to_json_string(grpc_config_rule))) - - # delete generic config rules; self deletes specific config rule - _, str_config_rule_id, _, config_rule_kind = parser(database, grpc_config_rule) - str_config_rule_key_hash = fast_hasher(':'.join([config_rule_kind.value, str_config_rule_id])) - str_config_rule_key = key_to_str([db_config.pk, str_config_rule_key_hash], separator=':') - db_config_rule : Optional[ConfigRuleModel] = get_object( - database, ConfigRuleModel, str_config_rule_key, raise_if_not_found=False) - if db_config_rule is None: return - db_config_rule.delete() - -def update_config( - database : Database, db_parent_pk : str, config_name : str, grpc_config_rules -) -> List[Tuple[Union[ConfigModel, ConfigRuleModel], bool]]: - - str_config_key = key_to_str([config_name, db_parent_pk], separator=':') - result : Tuple[ConfigModel, bool] = get_or_create_object(database, ConfigModel, str_config_key) - db_config, created = result - - db_objects = [(db_config, created)] - - for position,grpc_config_rule in enumerate(grpc_config_rules): - action = grpc_to_enum__config_action(grpc_config_rule.action) - - if action == ORM_ConfigActionEnum.SET: - result : Tuple[ConfigRuleModel, bool] = set_config_rule( - database, db_config, grpc_config_rule, position) - db_config_rule, updated = result - db_objects.append((db_config_rule, updated)) - elif action == ORM_ConfigActionEnum.DELETE: - delete_config_rule(database, db_config, grpc_config_rule) - else: - msg = 'Unsupported Action({:s}) for ConfigRule({:s})' - str_action = str(ConfigActionEnum.Name(action)) - str_config_rule = grpc_message_to_json_string(grpc_config_rule) - raise AttributeError(msg.format(str_action, str_config_rule)) - - return db_objects diff --git a/src/context/service/database/DeviceModel.py b/src/context/service/database/DeviceModel.py deleted file mode 100644 index 5c9e27e06ded6a4a3d31a0d356fd8621cd930dc2..0000000000000000000000000000000000000000 --- a/src/context/service/database/DeviceModel.py +++ /dev/null @@ -1,198 +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. - -import enum -import functools, logging -#import uuid -from typing import Dict #, List -#from common.orm.Database import Database -#from common.orm.backend.Tools import key_to_str -from common.proto.context_pb2 import DeviceDriverEnum, DeviceOperationalStatusEnum -from sqlalchemy import Column, Float, ForeignKey, String, Enum -from sqlalchemy.dialects.postgresql import UUID, ARRAY -from sqlalchemy.orm import relationship -from context.service.database._Base import _Base -from .Tools import grpc_to_enum - -LOGGER = logging.getLogger(__name__) - -class ORM_DeviceDriverEnum(enum.Enum): - UNDEFINED = DeviceDriverEnum.DEVICEDRIVER_UNDEFINED - OPENCONFIG = DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG - TRANSPORT_API = DeviceDriverEnum.DEVICEDRIVER_TRANSPORT_API - P4 = DeviceDriverEnum.DEVICEDRIVER_P4 - IETF_NETWORK_TOPOLOGY = DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY - ONF_TR_352 = DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352 - XR = DeviceDriverEnum.DEVICEDRIVER_XR - -grpc_to_enum__device_driver = functools.partial( - grpc_to_enum, DeviceDriverEnum, ORM_DeviceDriverEnum) - -class ORM_DeviceOperationalStatusEnum(enum.Enum): - UNDEFINED = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_UNDEFINED - DISABLED = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_DISABLED - ENABLED = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED - -grpc_to_enum__device_operational_status = functools.partial( - grpc_to_enum, DeviceOperationalStatusEnum, ORM_DeviceOperationalStatusEnum) - -class DeviceModel(_Base): - __tablename__ = 'device' - device_uuid = Column(UUID(as_uuid=False), primary_key=True) - device_name = Column(String(), nullable=False) - device_type = Column(String(), nullable=False) - #device_config_uuid = Column(UUID(as_uuid=False), ForeignKey('config.config_uuid', ondelete='CASCADE')) - device_operational_status = Column(Enum(ORM_DeviceOperationalStatusEnum)) - device_drivers = Column(ARRAY(Enum(ORM_DeviceDriverEnum), dimensions=1)) - created_at = Column(Float) - - # Relationships - topology_device = relationship('TopologyDeviceModel', back_populates='devices') - #device_config = relationship("ConfigModel", passive_deletes=True, lazy="joined") - endpoints = relationship('EndPointModel', passive_deletes=True, back_populates='device') - - def dump_id(self) -> Dict: - return {'device_uuid': {'uuid': self.device_uuid}} - - def dump(self) -> Dict: - return { - 'device_id' : self.dump_id(), - 'name' : self.device_name, - 'device_type' : self.device_type, - 'device_operational_status': self.device_operational_status.value, - 'device_drivers' : [d.value for d in self.device_drivers], - #'device_config' : {'config_rules': self.device_config.dump()}, - #'device_endpoints' : [ep.dump() for ep in self.endpoints], - } - -#def set_drivers(database : Database, db_device : DeviceModel, grpc_device_drivers): -# db_device_pk = db_device.device_uuid -# for driver in grpc_device_drivers: -# orm_driver = grpc_to_enum__device_driver(driver) -# str_device_driver_key = key_to_str([db_device_pk, orm_driver.name]) -# db_device_driver = DriverModel(database, str_device_driver_key) -# db_device_driver.device_fk = db_device -# db_device_driver.driver = orm_driver -# db_device_driver.save() - -# def set_kpi_sample_types(self, db_endpoint: EndPointModel, grpc_endpoint_kpi_sample_types): -# db_endpoint_pk = db_endpoint.endpoint_uuid -# for kpi_sample_type in grpc_endpoint_kpi_sample_types: -# orm_kpi_sample_type = grpc_to_enum__kpi_sample_type(kpi_sample_type) -# # str_endpoint_kpi_sample_type_key = key_to_str([db_endpoint_pk, orm_kpi_sample_type.name]) -# data = {'endpoint_uuid': db_endpoint_pk, -# 'kpi_sample_type': orm_kpi_sample_type.name, -# 'kpi_uuid': str(uuid.uuid4())} -# db_endpoint_kpi_sample_type = KpiSampleTypeModel(**data) -# self.database.create(db_endpoint_kpi_sample_type) - -# def set_drivers(self, db_device: DeviceModel, grpc_device_drivers): -# db_device_pk = db_device.device_uuid -# for driver in grpc_device_drivers: -# orm_driver = grpc_to_enum__device_driver(driver) -# str_device_driver_key = key_to_str([db_device_pk, orm_driver.name]) -# driver_config = { -# # "driver_uuid": str(uuid.uuid4()), -# "device_uuid": db_device_pk, -# "driver": orm_driver.name -# } -# db_device_driver = DriverModel(**driver_config) -# db_device_driver.device_fk = db_device -# db_device_driver.driver = orm_driver -# -# self.database.create_or_update(db_device_driver) - -# def update_config( -# self, session, db_parent_pk: str, config_name: str, -# raw_config_rules: List[Tuple[ORM_ConfigActionEnum, str, str]] -# ) -> List[Tuple[Union[ConfigModel, ConfigRuleModel], bool]]: -# -# created = False -# -# db_config = session.query(ConfigModel).filter_by(**{ConfigModel.main_pk_name(): db_parent_pk}).one_or_none() -# if not db_config: -# db_config = ConfigModel() -# setattr(db_config, ConfigModel.main_pk_name(), db_parent_pk) -# session.add(db_config) -# session.commit() -# created = True -# -# LOGGER.info('UPDATED-CONFIG: {}'.format(db_config.dump())) -# -# db_objects: List[Tuple[Union[ConfigModel, ConfigRuleModel], bool]] = [(db_config, created)] -# -# for position, (action, resource_key, resource_value) in enumerate(raw_config_rules): -# if action == ORM_ConfigActionEnum.SET: -# result : Tuple[ConfigRuleModel, bool] = self.set_config_rule( -# db_config, position, resource_key, resource_value) -# db_config_rule, updated = result -# db_objects.append((db_config_rule, updated)) -# elif action == ORM_ConfigActionEnum.DELETE: -# self.delete_config_rule(db_config, resource_key) -# else: -# msg = 'Unsupported action({:s}) for resource_key({:s})/resource_value({:s})' -# raise AttributeError( -# msg.format(str(ConfigActionEnum.Name(action)), str(resource_key), str(resource_value))) -# -# return db_objects -# -# def set_config_rule(self, db_config: ConfigModel, position: int, resource_key: str, resource_value: str, -# ): # -> Tuple[ConfigRuleModel, bool]: -# -# from src.context.service.database.Tools import fast_hasher -# str_rule_key_hash = fast_hasher(resource_key) -# str_config_rule_key = key_to_str([db_config.config_uuid, str_rule_key_hash], separator=':') -# pk = str(uuid.uuid5(uuid.UUID('9566448d-e950-425e-b2ae-7ead656c7e47'), str_config_rule_key)) -# data = {'config_rule_uuid': pk, 'config_uuid': db_config.config_uuid, 'position': position, -# 'action': ORM_ConfigActionEnum.SET, 'key': resource_key, 'value': resource_value} -# to_add = ConfigRuleModel(**data) -# -# result, updated = self.database.create_or_update(to_add) -# return result, updated -# -# def delete_config_rule( -# self, db_config: ConfigModel, resource_key: str -# ) -> None: -# -# from src.context.service.database.Tools import fast_hasher -# str_rule_key_hash = fast_hasher(resource_key) -# str_config_rule_key = key_to_str([db_config.pk, str_rule_key_hash], separator=':') -# -# db_config_rule = self.database.get_object(ConfigRuleModel, str_config_rule_key, raise_if_not_found=False) -# -# if db_config_rule is None: -# return -# db_config_rule.delete() -# -# def delete_all_config_rules(self, db_config: ConfigModel) -> None: -# -# db_config_rule_pks = db_config.references(ConfigRuleModel) -# for pk, _ in db_config_rule_pks: ConfigRuleModel(self.database, pk).delete() -# -# """ -# for position, (action, resource_key, resource_value) in enumerate(raw_config_rules): -# if action == ORM_ConfigActionEnum.SET: -# result: Tuple[ConfigRuleModel, bool] = set_config_rule( -# database, db_config, position, resource_key, resource_value) -# db_config_rule, updated = result -# db_objects.append((db_config_rule, updated)) -# elif action == ORM_ConfigActionEnum.DELETE: -# delete_config_rule(database, db_config, resource_key) -# else: -# msg = 'Unsupported action({:s}) for resource_key({:s})/resource_value({:s})' -# raise AttributeError( -# msg.format(str(ConfigActionEnum.Name(action)), str(resource_key), str(resource_value))) -# -# return db_objects -# """ diff --git a/src/context/service/database/LinkModel.py b/src/context/service/database/LinkModel.py deleted file mode 100644 index 6b768d1b7ba8e4eca5e35ec774d40f2d66aeac63..0000000000000000000000000000000000000000 --- a/src/context/service/database/LinkModel.py +++ /dev/null @@ -1,52 +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. - -import logging, operator -from typing import Dict, List -from sqlalchemy import Column, ForeignKey -from sqlalchemy.dialects.postgresql import UUID -from context.service.database._Base import Base -from sqlalchemy.orm import relationship - -LOGGER = logging.getLogger(__name__) - -class LinkModel(Base): - __tablename__ = 'Link' - link_uuid = Column(UUID(as_uuid=False), primary_key=True, unique=True) - - @staticmethod - def main_pk_name(): - return 'link_uuid' - - def dump_id(self) -> Dict: - return {'link_uuid': {'uuid': self.link_uuid}} - - def dump_endpoint_ids(self) -> List[Dict]: - return [endpoint.dump_id() for endpoint in self.endpoints] - - def dump(self, endpoints=None) -> Dict: - result = { - 'link_id': self.dump_id() - } - if endpoints: - result['link_endpoint_ids'] = [] - for endpoint in endpoints: - dump = endpoint.dump_id() - LOGGER.info(dump) - result['link_endpoint_ids'].append(dump) - - LOGGER.info(result['link_endpoint_ids']) - - LOGGER.info(result) - return result diff --git a/src/context/service/database/methods/Context.py b/src/context/service/database/methods/Context.py new file mode 100644 index 0000000000000000000000000000000000000000..8f1c2ee23ef853d450343cf4fdedd320b5a12bac --- /dev/null +++ b/src/context/service/database/methods/Context.py @@ -0,0 +1,95 @@ +# 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. + +import time +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 +from common.proto.context_pb2 import Context, ContextId, ContextIdList, ContextList +from common.rpc_method_wrapper.ServiceExceptions import InvalidArgumentException, NotFoundException +from context.service.database.models.ContextModel import ContextModel + +def context_list_ids(db_engine : Engine) -> ContextIdList: + def callback(session : Session) -> List[Dict]: + obj_list : List[ContextModel] = session.query(ContextModel).all() + #.options(selectinload(ContextModel.topology)).filter_by(context_uuid=context_uuid).one_or_none() + return [obj.dump_id() for obj in obj_list] + return ContextIdList(context_ids=run_transaction(sessionmaker(bind=db_engine), callback)) + +def context_list_objs(db_engine : Engine) -> ContextList: + def callback(session : Session) -> List[Dict]: + obj_list : List[ContextModel] = session.query(ContextModel).all() + #.options(selectinload(ContextModel.topology)).filter_by(context_uuid=context_uuid).one_or_none() + return [obj.dump() for obj in obj_list] + return ContextList(contexts=run_transaction(sessionmaker(bind=db_engine), callback)) + +def context_get(db_engine : Engine, request : ContextId) -> Context: + context_uuid = request.context_uuid.uuid + def callback(session : Session) -> Optional[Dict]: + obj : Optional[ContextModel] = session.query(ContextModel)\ + .filter_by(context_uuid=context_uuid).one_or_none() + return None if obj is None else obj.dump() + obj = run_transaction(sessionmaker(bind=db_engine), callback) + if obj is None: raise NotFoundException('Context', context_uuid) + return Context(**obj) + +def context_set(db_engine : Engine, request : Context) -> bool: + context_uuid = request.context_id.context_uuid.uuid + context_name = request.name + + for i, topology_id in enumerate(request.topology_ids): + topology_context_uuid = topology_id.context_id.context_uuid.uuid + if topology_context_uuid != context_uuid: + raise InvalidArgumentException( + 'request.topology_ids[{:d}].context_id.context_uuid.uuid'.format(i), topology_context_uuid, + ['should be == {:s}({:s})'.format('request.context_id.context_uuid.uuid', context_uuid)]) + + for i, service_id in enumerate(request.service_ids): + service_context_uuid = service_id.context_id.context_uuid.uuid + if service_context_uuid != context_uuid: + raise InvalidArgumentException( + 'request.service_ids[{:d}].context_id.context_uuid.uuid'.format(i), service_context_uuid, + ['should be == {:s}({:s})'.format('request.context_id.context_uuid.uuid', context_uuid)]) + + for i, slice_id in enumerate(request.slice_ids): + slice_context_uuid = slice_id.context_id.context_uuid.uuid + if slice_context_uuid != context_uuid: + raise InvalidArgumentException( + 'request.slice_ids[{:d}].context_id.context_uuid.uuid'.format(i), slice_context_uuid, + ['should be == {:s}({:s})'.format('request.context_id.context_uuid.uuid', context_uuid)]) + + def callback(session : Session) -> None: + context_data = [{ + 'context_uuid': context_uuid, + 'context_name': context_name, + 'created_at' : time.time(), + }] + stmt = insert(ContextModel).values(context_data) + stmt = stmt.on_conflict_do_update( + index_elements=[ContextModel.context_uuid], + set_=dict(context_name = stmt.excluded.context_name) + ) + session.execute(stmt) + + run_transaction(sessionmaker(bind=db_engine), callback) + return False # TODO: improve and check if created/updated + +def context_delete(db_engine : Engine, request : ContextId) -> bool: + context_uuid = request.context_uuid.uuid + def callback(session : Session) -> bool: + num_deleted = session.query(ContextModel).filter_by(context_uuid=context_uuid).delete() + return num_deleted > 0 + return run_transaction(sessionmaker(bind=db_engine), callback) diff --git a/src/context/service/database/methods/Device.py b/src/context/service/database/methods/Device.py new file mode 100644 index 0000000000000000000000000000000000000000..e7dc3dadb224fafbeb8b459f4ac5a1a70f844874 --- /dev/null +++ b/src/context/service/database/methods/Device.py @@ -0,0 +1,296 @@ +# 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. + +import time +from sqlalchemy import delete +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 Device, DeviceId, DeviceIdList, DeviceList +from common.rpc_method_wrapper.ServiceExceptions import InvalidArgumentException, NotFoundException +from common.tools.grpc.Tools import grpc_message_to_json_string +from context.service.database.models.ConfigRuleModel import ConfigRuleKindEnum, ConfigRuleModel +from context.service.database.models.DeviceModel import DeviceModel +from context.service.database.models.EndPointModel import EndPointModel +from context.service.database.models.RelationModels import TopologyDeviceModel +from context.service.database.models.enums.ConfigAction import grpc_to_enum__config_action +from context.service.database.models.enums.DeviceDriver import grpc_to_enum__device_driver +from context.service.database.models.enums.DeviceOperationalStatus import grpc_to_enum__device_operational_status +from context.service.database.models.enums.KpiSampleType import grpc_to_enum__kpi_sample_type + +def device_list_ids(db_engine : Engine) -> DeviceIdList: + def callback(session : Session) -> List[Dict]: + obj_list : List[DeviceModel] = session.query(DeviceModel).all() + #.options(selectinload(DeviceModel.topology)).filter_by(context_uuid=context_uuid).one_or_none() + return [obj.dump_id() for obj in obj_list] + return DeviceIdList(device_ids=run_transaction(sessionmaker(bind=db_engine), callback)) + +def device_list_objs(db_engine : Engine) -> DeviceList: + def callback(session : Session) -> List[Dict]: + obj_list : List[DeviceModel] = session.query(DeviceModel).all() + #.options(selectinload(DeviceModel.topology)).filter_by(context_uuid=context_uuid).one_or_none() + return [obj.dump() for obj in obj_list] + return DeviceList(devices=run_transaction(sessionmaker(bind=db_engine), callback)) + +def device_get(db_engine : Engine, request : DeviceId) -> Device: + device_uuid = request.device_uuid.uuid + def callback(session : Session) -> Optional[Dict]: + obj : Optional[DeviceModel] = session.query(DeviceModel)\ + .filter_by(device_uuid=device_uuid).one_or_none() + return None if obj is None else obj.dump() + obj = run_transaction(sessionmaker(bind=db_engine), callback) + if obj is None: raise NotFoundException('Device', device_uuid) + return Device(**obj) + +def device_set(db_engine : Engine, request : Device) -> bool: + device_uuid = request.device_id.device_uuid.uuid + device_name = request.name + device_type = request.device_type + oper_status = grpc_to_enum__device_operational_status(request.device_operational_status) + device_drivers = [grpc_to_enum__device_driver(d) for d in request.device_drivers] + + topology_keys : Set[Tuple[str, str]] = set() + related_topologies : List[Dict] = list() + endpoints_data : List[Dict] = list() + for i, endpoint in enumerate(request.device_endpoints): + endpoint_device_uuid = endpoint.endpoint_id.device_id.device_uuid.uuid + if len(endpoint_device_uuid) == 0: endpoint_device_uuid = device_uuid + if device_uuid != endpoint_device_uuid: + raise InvalidArgumentException( + 'request.device_endpoints[{:d}].device_id.device_uuid.uuid'.format(i), endpoint_device_uuid, + ['should be == {:s}({:s})'.format('request.device_id.device_uuid.uuid', device_uuid)]) + + endpoint_context_uuid = endpoint.endpoint_id.topology_id.context_id.context_uuid.uuid + endpoint_topology_uuid = endpoint.endpoint_id.topology_id.topology_uuid.uuid + + kpi_sample_types = [grpc_to_enum__kpi_sample_type(kst) for kst in endpoint.kpi_sample_types] + + endpoints_data.append({ + 'context_uuid' : endpoint_context_uuid, + 'topology_uuid' : endpoint_topology_uuid, + 'device_uuid' : endpoint_device_uuid, + 'endpoint_uuid' : endpoint.endpoint_id.endpoint_uuid.uuid, + 'endpoint_type' : endpoint.endpoint_type, + 'kpi_sample_types': kpi_sample_types, + }) + + if len(endpoint_context_uuid) > 0 and len(endpoint_topology_uuid) > 0: + topology_key = (endpoint_context_uuid, endpoint_topology_uuid) + if topology_key not in topology_keys: + related_topologies.append({ + 'context_uuid': endpoint_context_uuid, + 'topology_uuid': endpoint_topology_uuid, + 'device_uuid': endpoint_device_uuid, + }) + topology_keys.add(topology_key) + + config_rules : List[Dict] = list() + for position,config_rule in enumerate(request.device_config.config_rules): + str_kind = config_rule.WhichOneof('config_rule') + config_rules.append({ + 'device_uuid': device_uuid, + 'kind' : ConfigRuleKindEnum._member_map_.get(str_kind.upper()), # pylint: disable=no-member + 'action' : grpc_to_enum__config_action(config_rule.action), + 'position' : position, + 'data' : grpc_message_to_json_string(getattr(config_rule, str_kind, {})), + }) + + def callback(session : Session) -> None: + obj : Optional[DeviceModel] = session.query(DeviceModel).with_for_update()\ + .filter_by(device_uuid=device_uuid).one_or_none() + is_update = obj is not None + if is_update: + obj.device_name = device_name + obj.device_type = device_type + obj.device_operational_status = oper_status + obj.device_drivers = device_drivers + session.merge(obj) + else: + session.add(DeviceModel( + device_uuid=device_uuid, device_name=device_name, device_type=device_type, + device_operational_status=oper_status, device_drivers=device_drivers, created_at=time.time())) + obj : Optional[DeviceModel] = session.query(DeviceModel)\ + .filter_by(device_uuid=device_uuid).one_or_none() + + stmt = insert(EndPointModel).values(endpoints_data) + stmt = stmt.on_conflict_do_update( + index_elements=[ + EndPointModel.context_uuid, EndPointModel.topology_uuid, EndPointModel.device_uuid, + EndPointModel.endpoint_uuid + ], + set_=dict( + endpoint_type = stmt.excluded.endpoint_type, + kpi_sample_types = stmt.excluded.kpi_sample_types, + ) + ) + session.execute(stmt) + + session.execute(insert(TopologyDeviceModel).values(related_topologies).on_conflict_do_nothing( + index_elements=[ + TopologyDeviceModel.context_uuid, TopologyDeviceModel.topology_uuid, + TopologyDeviceModel.device_uuid + ] + )) + + session.execute(delete(ConfigRuleModel).where(ConfigRuleModel.device_uuid == device_uuid)) + session.execute(insert(ConfigRuleModel).values(config_rules)) + + run_transaction(sessionmaker(bind=db_engine), callback) + return False # TODO: improve and check if created/updated + +def device_delete(db_engine : Engine, request : DeviceId) -> bool: + device_uuid = request.device_uuid.uuid + def callback(session : Session) -> bool: + session.query(TopologyDeviceModel).filter_by(device_uuid=device_uuid).delete() + num_deleted = session.query(DeviceModel).filter_by(device_uuid=device_uuid).delete() + #db_device = session.query(DeviceModel).filter_by(device_uuid=device_uuid).one_or_none() + #session.query(ConfigRuleModel).filter_by(config_uuid=db_device.device_config_uuid).delete() + #session.query(ConfigModel).filter_by(config_uuid=db_device.device_config_uuid).delete() + #session.query(DeviceModel).filter_by(device_uuid=device_uuid).delete() + return num_deleted > 0 + return run_transaction(sessionmaker(bind=db_engine), callback) + + + + +#Union_SpecificConfigRule = Union[ +# ConfigRuleCustomModel, ConfigRuleAclModel +#] +# +#def set_config_rule( +# database : Database, db_config : ConfigModel, position : int, resource_key : str, resource_value : str, +#): # -> Tuple[ConfigRuleModel, bool]: +# +# str_rule_key_hash = fast_hasher(resource_key) +# str_config_rule_key = key_to_str([db_config.config_uuid, str_rule_key_hash], separator=':') +# +# data = {'config_fk': db_config, 'position': position, 'action': ORM_ConfigActionEnum.SET, 'key': resource_key, +# 'value': resource_value} +# to_add = ConfigRuleModel(**data) +# +# result = database.create_or_update(to_add) +# return result +#Tuple_ConfigRuleSpecs = Tuple[Type, str, Dict, ConfigRuleKindEnum] +# +#def parse_config_rule_custom(database : Database, grpc_config_rule) -> Tuple_ConfigRuleSpecs: +# config_rule_class = ConfigRuleCustomModel +# str_config_rule_id = grpc_config_rule.custom.resource_key +# config_rule_data = { +# 'key' : grpc_config_rule.custom.resource_key, +# 'value': grpc_config_rule.custom.resource_value, +# } +# return config_rule_class, str_config_rule_id, config_rule_data, ConfigRuleKindEnum.CUSTOM +# +#def parse_config_rule_acl(database : Database, grpc_config_rule) -> Tuple_ConfigRuleSpecs: +# config_rule_class = ConfigRuleAclModel +# grpc_endpoint_id = grpc_config_rule.acl.endpoint_id +# grpc_rule_set = grpc_config_rule.acl.rule_set +# device_uuid = grpc_endpoint_id.device_id.device_uuid.uuid +# endpoint_uuid = grpc_endpoint_id.endpoint_uuid.uuid +# str_endpoint_key = '/'.join([device_uuid, endpoint_uuid]) +# #str_endpoint_key, db_endpoint = get_endpoint(database, grpc_endpoint_id) +# str_config_rule_id = ':'.join([str_endpoint_key, grpc_rule_set.name]) +# config_rule_data = { +# #'endpoint_fk': db_endpoint, +# 'endpoint_id': grpc_message_to_json_string(grpc_endpoint_id), +# 'acl_data': grpc_message_to_json_string(grpc_rule_set), +# } +# return config_rule_class, str_config_rule_id, config_rule_data, ConfigRuleKindEnum.ACL +# +#CONFIGRULE_PARSERS = { +# 'custom': parse_config_rule_custom, +# 'acl' : parse_config_rule_acl, +#} +# +#Union_ConfigRuleModel = Union[ +# ConfigRuleCustomModel, ConfigRuleAclModel, +#] +# +#def set_config_rule( +# database : Database, db_config : ConfigModel, grpc_config_rule : ConfigRule, position : int +#) -> Tuple[Union_ConfigRuleModel, bool]: +# grpc_config_rule_kind = str(grpc_config_rule.WhichOneof('config_rule')) +# parser = CONFIGRULE_PARSERS.get(grpc_config_rule_kind) +# if parser is None: +# raise NotImplementedError('ConfigRule of kind {:s} is not implemented: {:s}'.format( +# grpc_config_rule_kind, grpc_message_to_json_string(grpc_config_rule))) +# +# # create specific ConfigRule +# config_rule_class, str_config_rule_id, config_rule_data, config_rule_kind = parser(database, grpc_config_rule) +# str_config_rule_key_hash = fast_hasher(':'.join([config_rule_kind.value, str_config_rule_id])) +# str_config_rule_key = key_to_str([db_config.pk, str_config_rule_key_hash], separator=':') +# result : Tuple[Union_ConfigRuleModel, bool] = update_or_create_object( +# database, config_rule_class, str_config_rule_key, config_rule_data) +# db_specific_config_rule, updated = result +# +# # create generic ConfigRule +# config_rule_fk_field_name = 'config_rule_{:s}_fk'.format(config_rule_kind.value) +# config_rule_data = { +# 'config_fk': db_config, 'kind': config_rule_kind, 'position': position, +# 'action': ORM_ConfigActionEnum.SET, +# config_rule_fk_field_name: db_specific_config_rule +# } +# result : Tuple[ConfigRuleModel, bool] = update_or_create_object( +# database, ConfigRuleModel, str_config_rule_key, config_rule_data) +# db_config_rule, updated = result +# +# return db_config_rule, updated +# +#def delete_config_rule( +# database : Database, db_config : ConfigModel, grpc_config_rule : ConfigRule +#) -> None: +# grpc_config_rule_kind = str(grpc_config_rule.WhichOneof('config_rule')) +# parser = CONFIGRULE_PARSERS.get(grpc_config_rule_kind) +# if parser is None: +# raise NotImplementedError('ConfigRule of kind {:s} is not implemented: {:s}'.format( +# grpc_config_rule_kind, grpc_message_to_json_string(grpc_config_rule))) +# +# # delete generic config rules; self deletes specific config rule +# _, str_config_rule_id, _, config_rule_kind = parser(database, grpc_config_rule) +# str_config_rule_key_hash = fast_hasher(':'.join([config_rule_kind.value, str_config_rule_id])) +# str_config_rule_key = key_to_str([db_config.pk, str_config_rule_key_hash], separator=':') +# db_config_rule : Optional[ConfigRuleModel] = get_object( +# database, ConfigRuleModel, str_config_rule_key, raise_if_not_found=False) +# if db_config_rule is None: return +# db_config_rule.delete() +# +#def update_config( +# database : Database, db_parent_pk : str, config_name : str, grpc_config_rules +#) -> List[Tuple[Union[ConfigModel, ConfigRuleModel], bool]]: +# +# str_config_key = key_to_str([config_name, db_parent_pk], separator=':') +# result : Tuple[ConfigModel, bool] = get_or_create_object(database, ConfigModel, str_config_key) +# db_config, created = result +# +# db_objects = [(db_config, created)] +# +# for position,grpc_config_rule in enumerate(grpc_config_rules): +# action = grpc_to_enum__config_action(grpc_config_rule.action) +# +# if action == ORM_ConfigActionEnum.SET: +# result : Tuple[ConfigRuleModel, bool] = set_config_rule( +# database, db_config, grpc_config_rule, position) +# db_config_rule, updated = result +# db_objects.append((db_config_rule, updated)) +# elif action == ORM_ConfigActionEnum.DELETE: +# delete_config_rule(database, db_config, grpc_config_rule) +# else: +# msg = 'Unsupported Action({:s}) for ConfigRule({:s})' +# str_action = str(ConfigActionEnum.Name(action)) +# str_config_rule = grpc_message_to_json_string(grpc_config_rule) +# raise AttributeError(msg.format(str_action, str_config_rule)) +# +# return db_objects diff --git a/src/context/service/database/methods/Link.py b/src/context/service/database/methods/Link.py new file mode 100644 index 0000000000000000000000000000000000000000..b98578c226e7b5810c15bf3d783038d6975a334c --- /dev/null +++ b/src/context/service/database/methods/Link.py @@ -0,0 +1,120 @@ +# 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. + +import time +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.rpc_method_wrapper.ServiceExceptions import NotFoundException +from context.service.database.models.LinkModel import LinkModel +from context.service.database.models.RelationModels import LinkEndPointModel, TopologyLinkModel + +def link_list_ids(db_engine : Engine) -> LinkIdList: + 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)) + +def link_list_objs(db_engine : Engine) -> LinkList: + 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)) + +def link_get(db_engine : Engine, request : LinkId) -> Link: + link_uuid = request.link_uuid.uuid + def callback(session : Session) -> Optional[Dict]: + obj : Optional[LinkModel] = session.query(LinkModel)\ + .filter_by(link_uuid=link_uuid).one_or_none() + return None if obj is None else obj.dump() + obj = run_transaction(sessionmaker(bind=db_engine), callback) + if obj is None: raise NotFoundException('Link', link_uuid) + return Link(**obj) + +def link_set(db_engine : Engine, request : Link) -> bool: + link_uuid = request.link_id.link_uuid.uuid + link_name = request.name + + topology_keys : Set[Tuple[str, str]] = set() + related_topologies : List[Dict] = list() + link_endpoints_data : List[Dict] = list() + for endpoint_id in request.link_endpoint_ids: + context_uuid = endpoint_id.topology_id.context_id.context_uuid.uuid + topology_uuid = endpoint_id.topology_id.topology_uuid.uuid + device_uuid = endpoint_id.device_id.device_uuid.uuid + endpoint_uuid = endpoint_id.endpoint_uuid.uuid + + link_endpoints_data.append({ + 'link_uuid' : link_uuid, + 'context_uuid' : context_uuid, + 'topology_uuid': topology_uuid, + 'device_uuid' : device_uuid, + 'endpoint_uuid': endpoint_uuid, + }) + + if len(context_uuid) > 0 and len(topology_uuid) > 0: + topology_key = (context_uuid, topology_uuid) + if topology_key not in topology_keys: + related_topologies.append({ + 'context_uuid': context_uuid, + 'topology_uuid': topology_uuid, + 'link_uuid': link_uuid, + }) + topology_keys.add(topology_key) + + def callback(session : Session) -> None: + obj : Optional[LinkModel] = session.query(LinkModel).with_for_update()\ + .filter_by(link_uuid=link_uuid).one_or_none() + is_update = obj is not None + if is_update: + obj.link_name = link_name + session.merge(obj) + else: + session.add(LinkModel(link_uuid=link_uuid, link_name=link_name, created_at=time.time())) + obj : Optional[LinkModel] = session.query(LinkModel)\ + .filter_by(link_uuid=link_uuid).one_or_none() + + stmt = insert(LinkEndPointModel).values(link_endpoints_data) + stmt = stmt.on_conflict_do_nothing( + index_elements=[ + LinkEndPointModel.link_uuid, LinkEndPointModel.context_uuid, LinkEndPointModel.topology_uuid, + LinkEndPointModel.device_uuid, LinkEndPointModel.endpoint_uuid + ], + ) + session.execute(stmt) + + session.execute(insert(TopologyLinkModel).values(related_topologies).on_conflict_do_nothing( + index_elements=[ + TopologyLinkModel.context_uuid, TopologyLinkModel.topology_uuid, + TopologyLinkModel.link_uuid + ] + )) + run_transaction(sessionmaker(bind=db_engine), callback) + return False # TODO: improve and check if created/updated + +def link_delete(db_engine : Engine, request : LinkId) -> bool: + link_uuid = request.link_uuid.uuid + def callback(session : Session) -> bool: + session.query(TopologyLinkModel).filter_by(link_uuid=link_uuid).delete() + session.query(LinkEndPointModel).filter_by(link_uuid=link_uuid).delete() + num_deleted = session.query(LinkModel).filter_by(link_uuid=link_uuid).delete() + #db_link = session.query(LinkModel).filter_by(link_uuid=link_uuid).one_or_none() + #session.query(LinkModel).filter_by(link_uuid=link_uuid).delete() + return num_deleted > 0 + return run_transaction(sessionmaker(bind=db_engine), callback) diff --git a/src/context/service/database/methods/Topology.py b/src/context/service/database/methods/Topology.py new file mode 100644 index 0000000000000000000000000000000000000000..f9449e0c39050544ae9f5bba6b9f73e6913cde42 --- /dev/null +++ b/src/context/service/database/methods/Topology.py @@ -0,0 +1,123 @@ +# 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. + +import time +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 +from common.proto.context_pb2 import ContextId, Topology, TopologyId, TopologyIdList, TopologyList +from common.rpc_method_wrapper.ServiceExceptions import NotFoundException +from context.service.database.models.RelationModels import TopologyDeviceModel +from context.service.database.models.TopologyModel import TopologyModel + +def topology_list_ids(db_engine : Engine, request : ContextId) -> TopologyIdList: + context_uuid = request.context_uuid.uuid + def callback(session : Session) -> List[Dict]: + obj_list : List[TopologyModel] = session.query(TopologyModel).filter_by(context_uuid=context_uuid).all() + #.options(selectinload(ContextModel.topology)).filter_by(context_uuid=context_uuid).one_or_none() + return [obj.dump_id() for obj in obj_list] + return TopologyIdList(topology_ids=run_transaction(sessionmaker(bind=db_engine), callback)) + +def topology_list_objs(db_engine : Engine, request : ContextId) -> TopologyList: + context_uuid = request.context_uuid.uuid + def callback(session : Session) -> List[Dict]: + obj_list : List[TopologyModel] = session.query(TopologyModel).filter_by(context_uuid=context_uuid).all() + #.options(selectinload(ContextModel.topology)).filter_by(context_uuid=context_uuid).one_or_none() + return [obj.dump() for obj in obj_list] + return TopologyList(topologies=run_transaction(sessionmaker(bind=db_engine), callback)) + +def topology_get(db_engine : Engine, request : TopologyId) -> Topology: + context_uuid = request.context_id.context_uuid.uuid + topology_uuid = request.topology_uuid.uuid + + def callback(session : Session) -> Optional[Dict]: + obj : Optional[TopologyModel] = session.query(TopologyModel)\ + .filter_by(context_uuid=context_uuid, topology_uuid=topology_uuid).one_or_none() + return None if obj is None else obj.dump() + obj = run_transaction(sessionmaker(bind=db_engine), callback) + if obj is None: + obj_uuid = '{:s}/{:s}'.format(context_uuid, topology_uuid) + raise NotFoundException('Topology', obj_uuid) + return Topology(**obj) + +def topology_set(db_engine : Engine, request : Topology) -> bool: + context_uuid = request.topology_id.context_id.context_uuid.uuid + topology_uuid = request.topology_id.topology_uuid.uuid + topology_name = request.name + + device_uuids : Set[str] = set() + devices_to_add : List[Dict] = list() + for device_id in request.device_ids: + device_uuid = device_id.device_uuid.uuid + if device_uuid in device_uuids: continue + devices_to_add.append({ + 'context_uuid': context_uuid, 'topology_uuid': topology_uuid, 'device_uuid': device_uuid + }) + device_uuids.add(device_uuid) + + link_uuids : Set[str] = set() + links_to_add : List[Dict] = list() + for link_id in request.link_ids: + link_uuid = link_id.link_uuid.uuid + if link_uuid in link_uuids: continue + links_to_add.append({ + 'context_uuid': context_uuid, 'topology_uuid': topology_uuid, 'link_uuid': link_uuid + }) + link_uuids.add(link_uuid) + + def callback(session : Session) -> None: + topology_data = [{ + 'context_uuid' : context_uuid, + 'topology_uuid': topology_uuid, + 'topology_name': topology_name, + 'created_at' : time.time(), + }] + stmt = insert(TopologyModel).values(topology_data) + stmt = stmt.on_conflict_do_update( + index_elements=[TopologyModel.context_uuid, TopologyModel.topology_uuid], + set_=dict(topology_name = stmt.excluded.topology_name) + ) + session.execute(stmt) + + if len(devices_to_add) > 0: + session.execute(insert(TopologyDeviceModel).values(devices_to_add).on_conflict_do_nothing( + index_elements=[ + TopologyDeviceModel.context_uuid, TopologyDeviceModel.topology_uuid, + TopologyDeviceModel.device_uuid + ] + )) + + #if len(link_to_add) > 0: + # session.execute(insert(TopologyLinkModel).values(links_to_add).on_conflict_do_nothing( + # index_elements=[ + # TopologyLinkModel.context_uuid, TopologyLinkModel.topology_uuid, + # TopologyLinkModel.link_uuid + # ] + # )) + + run_transaction(sessionmaker(bind=db_engine), callback) + return False # TODO: improve and check if created/updated + +def topology_delete(db_engine : Engine, request : TopologyId) -> bool: + context_uuid = request.context_id.context_uuid.uuid + topology_uuid = request.topology_uuid.uuid + + def callback(session : Session) -> bool: + num_deleted = session.query(TopologyModel)\ + .filter_by(context_uuid=context_uuid, topology_uuid=topology_uuid).delete() + return num_deleted > 0 + + return run_transaction(sessionmaker(bind=db_engine), callback) diff --git a/src/context/service/database/methods/__init__.py b/src/context/service/database/methods/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..9953c820575d42fa88351cc8de022d880ba96e6a --- /dev/null +++ b/src/context/service/database/methods/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/src/context/service/database/models/ConfigRuleModel.py b/src/context/service/database/models/ConfigRuleModel.py new file mode 100644 index 0000000000000000000000000000000000000000..d5a37eed2cfac39ebb4b72906e1e70249e22b365 --- /dev/null +++ b/src/context/service/database/models/ConfigRuleModel.py @@ -0,0 +1,44 @@ +# 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. + +import enum, json +from sqlalchemy import Column, ForeignKey, INTEGER, CheckConstraint, Enum, String, text +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import relationship +from typing import Dict +from .enums.ConfigAction import ORM_ConfigActionEnum +from ._Base import _Base + +# enum values should match name of field in ConfigRuleModel +class ConfigRuleKindEnum(enum.Enum): + CUSTOM = 'custom' + ACL = 'acl' + +class ConfigRuleModel(_Base): + __tablename__ = 'config_rule' + device_uuid = Column(UUID(as_uuid=False), ForeignKey('device.device_uuid', ondelete='CASCADE'), primary_key=True) + rule_uuid = Column(UUID(as_uuid=False), primary_key=True, server_default=text('uuid_generate_v4()')) + kind = Column(Enum(ConfigRuleKindEnum)) + action = Column(Enum(ORM_ConfigActionEnum)) + position = Column(INTEGER, nullable=False) + data = Column(String, nullable=False) + + __table_args__ = ( + CheckConstraint(position >= 0, name='check_position_value'), + ) + + device = relationship('DeviceModel', back_populates='config_rules') + + def dump(self) -> Dict: + return {self.kind.value: json.loads(self.data)} diff --git a/src/context/service/database/ConnectionModel.py b/src/context/service/database/models/ConnectionModel.py similarity index 97% rename from src/context/service/database/ConnectionModel.py rename to src/context/service/database/models/ConnectionModel.py index e780ccb681101919dd7065ab6424dcb9033f521b..546fb7a80ee42a524e8950c8d5d3363a09ee6710 100644 --- a/src/context/service/database/ConnectionModel.py +++ b/src/context/service/database/models/ConnectionModel.py @@ -24,19 +24,21 @@ from common.orm.HighLevel import get_object, get_or_create_object, get_related_o from common.proto.context_pb2 import EndPointId from .EndPointModel import EndPointModel from .ServiceModel import ServiceModel -from .Tools import remove_dict_key +def remove_dict_key(dictionary : Dict, key : str): + dictionary.pop(key, None) + return dictionary from sqlalchemy import Column, Enum, ForeignKey, Integer, CheckConstraint from typing import Dict, List from common.orm.HighLevel import get_related_objects from common.proto.context_pb2 import ServiceStatusEnum, ServiceTypeEnum -from .ConfigModel import ConfigModel +from .ConfigRuleModel import ConfigModel from .ConstraintModel import ConstraintsModel -from .ContextModel import ContextModel +from .models.ContextModel import ContextModel from .Tools import grpc_to_enum from sqlalchemy.dialects.postgresql import UUID -from context.service.database._Base import Base +from context.service.database.models._Base import Base import enum LOGGER = logging.getLogger(__name__) diff --git a/src/context/service/database/ConstraintModel.py b/src/context/service/database/models/ConstraintModel.py similarity index 98% rename from src/context/service/database/ConstraintModel.py rename to src/context/service/database/models/ConstraintModel.py index 30d9003009f2f804a168a3b709bb15565dae96ed..d616c3a7fe86adfccd37faa39fef737a4e21dfeb 100644 --- a/src/context/service/database/ConstraintModel.py +++ b/src/context/service/database/models/ConstraintModel.py @@ -19,14 +19,17 @@ from common.orm.backend.Tools import key_to_str from common.proto.context_pb2 import Constraint from common.tools.grpc.Tools import grpc_message_to_json_string from .EndPointModel import EndPointModel -from .Tools import fast_hasher, remove_dict_key +from .Tools import fast_hasher from sqlalchemy import Column, ForeignKey, String, Float, CheckConstraint, Integer, Boolean, Enum from sqlalchemy.dialects.postgresql import UUID -from context.service.database._Base import Base +from context.service.database.models._Base import Base import enum LOGGER = logging.getLogger(__name__) +def remove_dict_key(dictionary : Dict, key : str): + dictionary.pop(key, None) + return dictionary class ConstraintsModel(Base): # pylint: disable=abstract-method __tablename__ = 'Constraints' diff --git a/src/context/service/database/ContextModel.py b/src/context/service/database/models/ContextModel.py similarity index 86% rename from src/context/service/database/ContextModel.py rename to src/context/service/database/models/ContextModel.py index ae8cf995f67cba1708e1bad6d941ef2c976eb67c..a5ddeb59686356e211192fc18390df8cc54834bc 100644 --- a/src/context/service/database/ContextModel.py +++ b/src/context/service/database/models/ContextModel.py @@ -24,9 +24,9 @@ class ContextModel(_Base): context_name = Column(String(), nullable=False) created_at = Column(Float) - topology = relationship('TopologyModel', back_populates='context') - #service = relationship('ServiceModel', back_populates='context') - #slice = relationship('SliceModel', back_populates='context') + topologies = relationship('TopologyModel', back_populates='context') + #services = relationship('ServiceModel', back_populates='context') + #slices = relationship('SliceModel', back_populates='context') def dump_id(self) -> Dict: return {'context_uuid': {'uuid': self.context_uuid}} @@ -38,7 +38,7 @@ class ContextModel(_Base): return { 'context_id' : self.dump_id(), 'name' : self.context_name, - 'topology_ids': [obj.dump_id() for obj in self.topology], - #'service_ids' : [obj.dump_id() for obj in self.service ], - #'slice_ids' : [obj.dump_id() for obj in self.slice ], + 'topology_ids': [obj.dump_id() for obj in self.topologies], + #'service_ids' : [obj.dump_id() for obj in self.services ], + #'slice_ids' : [obj.dump_id() for obj in self.slices ], } diff --git a/src/context/service/database/models/DeviceModel.py b/src/context/service/database/models/DeviceModel.py new file mode 100644 index 0000000000000000000000000000000000000000..fb585348216a294f4e92d92fb1d10dac4c9cb57d --- /dev/null +++ b/src/context/service/database/models/DeviceModel.py @@ -0,0 +1,52 @@ +# 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. + +import operator +from typing import Dict +from sqlalchemy import Column, Float, String, Enum +from sqlalchemy.dialects.postgresql import UUID, ARRAY +from sqlalchemy.orm import relationship +from ._Base import _Base +from .enums.DeviceDriver import ORM_DeviceDriverEnum +from .enums.DeviceOperationalStatus import ORM_DeviceOperationalStatusEnum + +class DeviceModel(_Base): + __tablename__ = 'device' + device_uuid = Column(UUID(as_uuid=False), primary_key=True) + device_name = Column(String, nullable=False) + device_type = Column(String, nullable=False) + device_operational_status = Column(Enum(ORM_DeviceOperationalStatusEnum)) + device_drivers = Column(ARRAY(Enum(ORM_DeviceDriverEnum), dimensions=1)) + created_at = Column(Float) + + topology_devices = relationship('TopologyDeviceModel', back_populates='device') + config_rules = relationship('ConfigRuleModel', passive_deletes=True, back_populates='device', lazy='joined') + endpoints = relationship('EndPointModel', passive_deletes=True, back_populates='device', lazy='joined') + + def dump_id(self) -> Dict: + return {'device_uuid': {'uuid': self.device_uuid}} + + def dump(self) -> Dict: + return { + 'device_id' : self.dump_id(), + 'name' : self.device_name, + 'device_type' : self.device_type, + 'device_operational_status': self.device_operational_status.value, + 'device_drivers' : [driver.value for driver in self.device_drivers], + 'device_config' : {'config_rules': [ + config_rule.dump() + for config_rule in sorted(self.config_rules, key=operator.attrgetter('position')) + ]}, + 'device_endpoints' : [endpoint.dump() for endpoint in self.endpoints], + } diff --git a/src/context/service/database/EndPointModel.py b/src/context/service/database/models/EndPointModel.py similarity index 82% rename from src/context/service/database/EndPointModel.py rename to src/context/service/database/models/EndPointModel.py index a8d3c2c699db8f54024508edc376c519509f0c03..b7e4c9fe38d59bc6fab02f8cfa7338eb1ce16c41 100644 --- a/src/context/service/database/EndPointModel.py +++ b/src/context/service/database/models/EndPointModel.py @@ -12,24 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import enum, functools from typing import Dict from sqlalchemy import Column, String, Enum, ForeignKeyConstraint from sqlalchemy.dialects.postgresql import ARRAY, UUID from sqlalchemy.orm import relationship -from common.proto.kpi_sample_types_pb2 import KpiSampleType +from .enums.KpiSampleType import ORM_KpiSampleTypeEnum from ._Base import _Base -from .Tools import grpc_to_enum - -class ORM_KpiSampleTypeEnum(enum.Enum): - UNKNOWN = KpiSampleType.KPISAMPLETYPE_UNKNOWN - PACKETS_TRANSMITTED = KpiSampleType.KPISAMPLETYPE_PACKETS_TRANSMITTED - PACKETS_RECEIVED = KpiSampleType.KPISAMPLETYPE_PACKETS_RECEIVED - BYTES_TRANSMITTED = KpiSampleType.KPISAMPLETYPE_BYTES_TRANSMITTED - BYTES_RECEIVED = KpiSampleType.KPISAMPLETYPE_BYTES_RECEIVED - -grpc_to_enum__kpi_sample_type = functools.partial( - grpc_to_enum, KpiSampleType, ORM_KpiSampleTypeEnum) class EndPointModel(_Base): __tablename__ = 'endpoint' @@ -51,8 +39,9 @@ class EndPointModel(_Base): ondelete='CASCADE'), ) - topology = relationship('TopologyModel', back_populates='endpoints') - device = relationship('DeviceModel', back_populates='endpoints') + topology = relationship('TopologyModel', back_populates='endpoints') + device = relationship('DeviceModel', back_populates='endpoints') + link_endpoints = relationship('LinkEndPointModel', back_populates='endpoint') def dump_id(self) -> Dict: result = { diff --git a/src/context/service/database/models/LinkModel.py b/src/context/service/database/models/LinkModel.py new file mode 100644 index 0000000000000000000000000000000000000000..df173f52726f0f9d28fd350c17abed83bea0b876 --- /dev/null +++ b/src/context/service/database/models/LinkModel.py @@ -0,0 +1,41 @@ +# 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 typing import Dict +from sqlalchemy import Column, Float, String +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import relationship +from ._Base import _Base + +class LinkModel(_Base): + __tablename__ = 'link' + link_uuid = Column(UUID(as_uuid=False), primary_key=True) + link_name = Column(String, nullable=False) + created_at = Column(Float) + + topology_links = relationship('TopologyLinkModel', back_populates='link') + link_endpoints = relationship('LinkEndPointModel', back_populates='link') #, lazy='joined') + + def dump_id(self) -> Dict: + return {'link_uuid': {'uuid': self.link_uuid}} + + def dump(self) -> Dict: + return { + 'link_id' : self.dump_id(), + 'name' : self.link_name, + 'link_endpoint_ids': [ + link_endpoint.endpoint.dump_id() + for link_endpoint in self.link_endpoints + ], + } diff --git a/src/context/service/database/PolicyRuleModel.py b/src/context/service/database/models/PolicyRuleModel.py similarity index 100% rename from src/context/service/database/PolicyRuleModel.py rename to src/context/service/database/models/PolicyRuleModel.py diff --git a/src/context/service/database/RelationModels.py b/src/context/service/database/models/RelationModels.py similarity index 57% rename from src/context/service/database/RelationModels.py rename to src/context/service/database/models/RelationModels.py index bcf85d005d6e0c11cb77ea567b1bf28fa47bdc1a..6cc4ff86cf061efd516a2bad59e0ad0e5965c796 100644 --- a/src/context/service/database/RelationModels.py +++ b/src/context/service/database/models/RelationModels.py @@ -16,7 +16,7 @@ import logging from sqlalchemy import Column, ForeignKey, ForeignKeyConstraint from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship -from context.service.database._Base import _Base +from context.service.database.models._Base import _Base LOGGER = logging.getLogger(__name__) @@ -24,27 +24,43 @@ LOGGER = logging.getLogger(__name__) # pk = PrimaryKeyField() # connection_fk = ForeignKeyField(ConnectionModel) # sub_service_fk = ForeignKeyField(ServiceModel) -# -#class LinkEndPointModel(Base): -# __tablename__ = 'LinkEndPoint' -# # uuid = Column(UUID(as_uuid=False), primary_key=True, unique=True) + + # link_uuid = Column(UUID(as_uuid=False), ForeignKey("Link.link_uuid")) # endpoint_uuid = Column(UUID(as_uuid=False), ForeignKey("EndPoint.endpoint_uuid"), primary_key=True) -# -# @staticmethod -# def main_pk_name(): -# return 'endpoint_uuid' -# + +class LinkEndPointModel(_Base): + __tablename__ = 'link_endpoint' + link_uuid = Column(UUID(as_uuid=False), primary_key=True) + context_uuid = Column(UUID(as_uuid=False), primary_key=True) + topology_uuid = Column(UUID(as_uuid=False), primary_key=True) + device_uuid = Column(UUID(as_uuid=False), primary_key=True) + endpoint_uuid = Column(UUID(as_uuid=False), primary_key=True) + + link = relationship('LinkModel', back_populates='link_endpoints', lazy='joined') + endpoint = relationship('EndPointModel', back_populates='link_endpoints', lazy='joined') + + __table_args__ = ( + ForeignKeyConstraint( + ['link_uuid'], + ['link.link_uuid'], + ondelete='CASCADE'), + ForeignKeyConstraint( + ['context_uuid', 'topology_uuid', 'device_uuid', 'endpoint_uuid'], + ['endpoint.context_uuid', 'endpoint.topology_uuid', 'endpoint.device_uuid', 'endpoint.endpoint_uuid'], + ondelete='CASCADE'), + ) + # class ServiceEndPointModel(Model): # pk = PrimaryKeyField() # service_fk = ForeignKeyField(ServiceModel) # endpoint_fk = ForeignKeyField(EndPointModel) -# + # class SliceEndPointModel(Model): # pk = PrimaryKeyField() # slice_fk = ForeignKeyField(SliceModel) # endpoint_fk = ForeignKeyField(EndPointModel) -# + # class SliceServiceModel(Model): # pk = PrimaryKeyField() # slice_fk = ForeignKeyField(SliceModel) @@ -54,7 +70,7 @@ LOGGER = logging.getLogger(__name__) # link_uuid = Column(UUID(as_uuid=False), ForeignKey("Link.link_uuid")) # endpoint_uuid = Column(UUID(as_uuid=False), ForeignKey("EndPoint.endpoint_uuid")) #del) -# + # class SliceSubSliceModel(Model): # pk = PrimaryKeyField() # slice_fk = ForeignKeyField(SliceModel) @@ -66,8 +82,8 @@ class TopologyDeviceModel(_Base): topology_uuid = Column(UUID(as_uuid=False), primary_key=True) device_uuid = Column(UUID(as_uuid=False), primary_key=True) - topologies = relationship('TopologyModel', back_populates='topology_device') - devices = relationship('DeviceModel', back_populates='topology_device') + topology = relationship('TopologyModel', back_populates='topology_devices', lazy='joined') + device = relationship('DeviceModel', back_populates='topology_devices', lazy='joined') __table_args__ = ( ForeignKeyConstraint( @@ -80,7 +96,22 @@ class TopologyDeviceModel(_Base): ondelete='CASCADE'), ) -#class TopologyLinkModel(Base): -# __tablename__ = 'TopologyLink' -# topology_uuid = Column(UUID(as_uuid=False), ForeignKey("Topology.topology_uuid")) -# link_uuid = Column(UUID(as_uuid=False), ForeignKey("Link.link_uuid"), primary_key=True) +class TopologyLinkModel(_Base): + __tablename__ = 'topology_link' + context_uuid = Column(UUID(as_uuid=False), primary_key=True) + topology_uuid = Column(UUID(as_uuid=False), primary_key=True) + link_uuid = Column(UUID(as_uuid=False), primary_key=True) + + topology = relationship('TopologyModel', back_populates='topology_links', lazy='joined') + link = relationship('LinkModel', back_populates='topology_links', lazy='joined') + + __table_args__ = ( + ForeignKeyConstraint( + ['context_uuid', 'topology_uuid'], + ['topology.context_uuid', 'topology.topology_uuid'], + ondelete='CASCADE'), + ForeignKeyConstraint( + ['link_uuid'], + ['link.link_uuid'], + ondelete='CASCADE'), + ) diff --git a/src/context/service/database/ServiceModel.py b/src/context/service/database/models/ServiceModel.py similarity index 97% rename from src/context/service/database/ServiceModel.py rename to src/context/service/database/models/ServiceModel.py index 20e10ddd5a9ecb5b62e22c4c082b3b3144a5a509..c06baca32c785e47fe295699d3cdd2026c0c389b 100644 --- a/src/context/service/database/ServiceModel.py +++ b/src/context/service/database/models/ServiceModel.py @@ -17,12 +17,12 @@ from sqlalchemy import Column, Enum, ForeignKey from typing import Dict, List from common.orm.HighLevel import get_related_objects from common.proto.context_pb2 import ServiceStatusEnum, ServiceTypeEnum -from .ConfigModel import ConfigModel +from .ConfigRuleModel import ConfigModel from .ConstraintModel import ConstraintsModel -from .ContextModel import ContextModel +from .models.ContextModel import ContextModel from .Tools import grpc_to_enum from sqlalchemy.dialects.postgresql import UUID -from context.service.database._Base import Base +from context.service.database.models._Base import Base import enum LOGGER = logging.getLogger(__name__) diff --git a/src/context/service/database/SliceModel.py b/src/context/service/database/models/SliceModel.py similarity index 98% rename from src/context/service/database/SliceModel.py rename to src/context/service/database/models/SliceModel.py index 74bb60b401f656fdcfec8b0466019f87a8f1b41e..2b03e61222cd8d36836bdacd921d0f0f359827b0 100644 --- a/src/context/service/database/SliceModel.py +++ b/src/context/service/database/models/SliceModel.py @@ -22,9 +22,9 @@ from common.orm.fields.StringField import StringField from common.orm.model.Model import Model from common.orm.HighLevel import get_related_objects from common.proto.context_pb2 import SliceStatusEnum -from .ConfigModel import ConfigModel +from .ConfigRuleModel import ConfigModel from .ConstraintModel import ConstraintsModel -from .ContextModel import ContextModel +from .models.ContextModel import ContextModel from .Tools import grpc_to_enum LOGGER = logging.getLogger(__name__) diff --git a/src/context/service/database/TopologyModel.py b/src/context/service/database/models/TopologyModel.py similarity index 77% rename from src/context/service/database/TopologyModel.py rename to src/context/service/database/models/TopologyModel.py index 57fe1b3475aa643eba881324f0fc3c05dad74578..95f7a63502a68e8b92b70a29c7b71cf229f108bf 100644 --- a/src/context/service/database/TopologyModel.py +++ b/src/context/service/database/models/TopologyModel.py @@ -26,10 +26,10 @@ class TopologyModel(_Base): created_at = Column(Float) # Relationships - context = relationship('ContextModel', back_populates='topology') - topology_device = relationship('TopologyDeviceModel', back_populates='topologies') - #topology_link = relationship('TopologyLinkModel', back_populates='topology') - endpoints = relationship('EndPointModel', back_populates='topology') + context = relationship('ContextModel', back_populates='topologies') + topology_devices = relationship('TopologyDeviceModel', back_populates='topology') + topology_links = relationship('TopologyLinkModel', back_populates='topology') + endpoints = relationship('EndPointModel', back_populates='topology') def dump_id(self) -> Dict: return { @@ -41,6 +41,6 @@ class TopologyModel(_Base): return { 'topology_id': self.dump_id(), 'name' : self.topology_name, - 'device_ids' : [{'device_uuid': {'uuid': td.device_uuid}} for td in self.topology_device], - #'link_ids' : [{'link_uuid' : {'uuid': td.link_uuid }} for td in self.topology_link ], + 'device_ids' : [{'device_uuid': {'uuid': td.device_uuid}} for td in self.topology_devices], + 'link_ids' : [{'link_uuid' : {'uuid': td.link_uuid }} for td in self.topology_links ], } diff --git a/src/context/service/database/_Base.py b/src/context/service/database/models/_Base.py similarity index 100% rename from src/context/service/database/_Base.py rename to src/context/service/database/models/_Base.py diff --git a/src/context/service/database/models/__init__.py b/src/context/service/database/models/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..9953c820575d42fa88351cc8de022d880ba96e6a --- /dev/null +++ b/src/context/service/database/models/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/src/context/service/database/models/enums/ConfigAction.py b/src/context/service/database/models/enums/ConfigAction.py new file mode 100644 index 0000000000000000000000000000000000000000..6bbcdea99c02823e982d19d1ad8d12c77f17dbdb --- /dev/null +++ b/src/context/service/database/models/enums/ConfigAction.py @@ -0,0 +1,25 @@ +# 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. + +import enum, functools +from common.proto.context_pb2 import ConfigActionEnum +from ._GrpcToEnum import grpc_to_enum + +class ORM_ConfigActionEnum(enum.Enum): + UNDEFINED = ConfigActionEnum.CONFIGACTION_UNDEFINED + SET = ConfigActionEnum.CONFIGACTION_SET + DELETE = ConfigActionEnum.CONFIGACTION_DELETE + +grpc_to_enum__config_action = functools.partial( + grpc_to_enum, ConfigActionEnum, ORM_ConfigActionEnum) diff --git a/src/context/service/database/models/enums/DeviceDriver.py b/src/context/service/database/models/enums/DeviceDriver.py new file mode 100644 index 0000000000000000000000000000000000000000..21338ddb8dc0de111e889a7041a65a6fa0219cfd --- /dev/null +++ b/src/context/service/database/models/enums/DeviceDriver.py @@ -0,0 +1,29 @@ +# 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. + +import enum, functools +from common.proto.context_pb2 import DeviceDriverEnum +from ._GrpcToEnum import grpc_to_enum + +class ORM_DeviceDriverEnum(enum.Enum): + UNDEFINED = DeviceDriverEnum.DEVICEDRIVER_UNDEFINED + OPENCONFIG = DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG + TRANSPORT_API = DeviceDriverEnum.DEVICEDRIVER_TRANSPORT_API + P4 = DeviceDriverEnum.DEVICEDRIVER_P4 + IETF_NETWORK_TOPOLOGY = DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY + ONF_TR_352 = DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352 + XR = DeviceDriverEnum.DEVICEDRIVER_XR + +grpc_to_enum__device_driver = functools.partial( + grpc_to_enum, DeviceDriverEnum, ORM_DeviceDriverEnum) diff --git a/src/context/service/database/models/enums/DeviceOperationalStatus.py b/src/context/service/database/models/enums/DeviceOperationalStatus.py new file mode 100644 index 0000000000000000000000000000000000000000..2bfe60779101a7021a40c00c6ee91cf28e2304ca --- /dev/null +++ b/src/context/service/database/models/enums/DeviceOperationalStatus.py @@ -0,0 +1,25 @@ +# 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. + +import enum, functools +from common.proto.context_pb2 import DeviceOperationalStatusEnum +from ._GrpcToEnum import grpc_to_enum + +class ORM_DeviceOperationalStatusEnum(enum.Enum): + UNDEFINED = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_UNDEFINED + DISABLED = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_DISABLED + ENABLED = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED + +grpc_to_enum__device_operational_status = functools.partial( + grpc_to_enum, DeviceOperationalStatusEnum, ORM_DeviceOperationalStatusEnum) diff --git a/src/context/service/database/models/enums/KpiSampleType.py b/src/context/service/database/models/enums/KpiSampleType.py new file mode 100644 index 0000000000000000000000000000000000000000..4126e90b2373a1d720b6a35e8e122be10d3d74e5 --- /dev/null +++ b/src/context/service/database/models/enums/KpiSampleType.py @@ -0,0 +1,27 @@ +# 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. + +import enum, functools +from common.proto.kpi_sample_types_pb2 import KpiSampleType +from ._GrpcToEnum import grpc_to_enum + +class ORM_KpiSampleTypeEnum(enum.Enum): + UNKNOWN = KpiSampleType.KPISAMPLETYPE_UNKNOWN + PACKETS_TRANSMITTED = KpiSampleType.KPISAMPLETYPE_PACKETS_TRANSMITTED + PACKETS_RECEIVED = KpiSampleType.KPISAMPLETYPE_PACKETS_RECEIVED + BYTES_TRANSMITTED = KpiSampleType.KPISAMPLETYPE_BYTES_TRANSMITTED + BYTES_RECEIVED = KpiSampleType.KPISAMPLETYPE_BYTES_RECEIVED + +grpc_to_enum__kpi_sample_type = functools.partial( + grpc_to_enum, KpiSampleType, ORM_KpiSampleTypeEnum) diff --git a/src/context/service/database/models/enums/_GrpcToEnum.py b/src/context/service/database/models/enums/_GrpcToEnum.py new file mode 100644 index 0000000000000000000000000000000000000000..df70399f98692adf12d1baabd6af25007b6e524f --- /dev/null +++ b/src/context/service/database/models/enums/_GrpcToEnum.py @@ -0,0 +1,32 @@ +# 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. + +import re +from enum import Enum + +# Enumeration classes are redundant with gRPC classes, but gRPC does not provide a programmatical method to retrieve +# the values it expects from strings containing the desired value symbol or its integer value, so a kind of mapping is +# required. Besides, ORM Models expect Enum classes in EnumeratedFields; we create specific and conveniently defined +# Enum classes to serve both purposes. + +def grpc_to_enum(grpc_enum_class, orm_enum_class : Enum, grpc_enum_value): + grpc_enum_name = grpc_enum_class.Name(grpc_enum_value) + grpc_enum_prefix = orm_enum_class.__name__.upper() + #grpc_enum_prefix = re.sub(r'^ORM_(.+)$', r'\1', grpc_enum_prefix) + #grpc_enum_prefix = re.sub(r'^(.+)ENUM$', r'\1', grpc_enum_prefix) + #grpc_enum_prefix = grpc_enum_prefix + '_' + grpc_enum_prefix = re.sub(r'^ORM_(.+)ENUM$', r'\1_', grpc_enum_prefix) + orm_enum_name = grpc_enum_name.replace(grpc_enum_prefix, '') + orm_enum_value = orm_enum_class._member_map_.get(orm_enum_name) + return orm_enum_value diff --git a/src/context/service/database/models/enums/__init__.py b/src/context/service/database/models/enums/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..9953c820575d42fa88351cc8de022d880ba96e6a --- /dev/null +++ b/src/context/service/database/models/enums/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/src/context/service/database/Tools.py b/src/context/service/database/tools/FastHasher.py similarity index 63% rename from src/context/service/database/Tools.py rename to src/context/service/database/tools/FastHasher.py index 44a5aa2647c79daa0d69c9b6a2d617d9bcb2f476..6632a1c794ed3be8533486614993bbe7a88650cb 100644 --- a/src/context/service/database/Tools.py +++ b/src/context/service/database/tools/FastHasher.py @@ -12,31 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import hashlib, re -from enum import Enum -from typing import Dict, List, Tuple, Union -import logging -# Convenient helper function to remove dictionary items in dict/list/set comprehensions. -LOGGER = logging.getLogger(__name__) - -def remove_dict_key(dictionary : Dict, key : str): - dictionary.pop(key, None) - return dictionary - -# Enumeration classes are redundant with gRPC classes, but gRPC does not provide a programmatical method to retrieve -# the values it expects from strings containing the desired value symbol or its integer value, so a kind of mapping is -# required. Besides, ORM Models expect Enum classes in EnumeratedFields; we create specific and conveniently defined -# Enum classes to serve both purposes. - -def grpc_to_enum(grpc_enum_class, orm_enum_class : Enum, grpc_enum_value): - grpc_enum_name = grpc_enum_class.Name(grpc_enum_value) - grpc_enum_prefix = orm_enum_class.__name__.upper() - grpc_enum_prefix = re.sub(r'^ORM_(.+)$', r'\1', grpc_enum_prefix) - grpc_enum_prefix = re.sub(r'^(.+)ENUM$', r'\1', grpc_enum_prefix) - grpc_enum_prefix = grpc_enum_prefix + '_' - orm_enum_name = grpc_enum_name.replace(grpc_enum_prefix, '') - orm_enum_value = orm_enum_class._member_map_.get(orm_enum_name) # pylint: disable=protected-access - return orm_enum_value +import hashlib +from typing import List, Tuple, Union # For some models, it is convenient to produce a string hash for fast comparisons of existence or modification. Method # fast_hasher computes configurable length (between 1 and 64 byte) hashes and retrieves them in hex representation. diff --git a/src/context/service/database/tools/__init__.py b/src/context/service/database/tools/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..9953c820575d42fa88351cc8de022d880ba96e6a --- /dev/null +++ b/src/context/service/database/tools/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/src/context/tests/_test_connection.py b/src/context/tests/_test_connection.py new file mode 100644 index 0000000000000000000000000000000000000000..b6060df68bddb610fa0540c0ddab30cbf4f005c2 --- /dev/null +++ b/src/context/tests/_test_connection.py @@ -0,0 +1,280 @@ +# 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. + +import copy, grpc, pytest +from typing import Tuple +from common.Constants import DEFAULT_CONTEXT_UUID, DEFAULT_TOPOLOGY_UUID +from common.proto.context_pb2 import ( + Connection, ConnectionEvent, ConnectionId, Context, ContextEvent, ContextId, Device, DeviceEvent, DeviceId, + EventTypeEnum, Service, ServiceEvent, ServiceId, Topology, TopologyEvent, TopologyId) +from context.client.ContextClient import ContextClient +from context.client.EventsCollector import EventsCollector +from .Objects import ( + CONNECTION_R1_R3, CONNECTION_R1_R3_ID, CONNECTION_R1_R3_UUID, CONTEXT, CONTEXT_ID, DEVICE_R1, DEVICE_R1_ID, + DEVICE_R1_UUID, DEVICE_R2, DEVICE_R2_ID, DEVICE_R2_UUID, DEVICE_R3, DEVICE_R3_ID, DEVICE_R3_UUID, SERVICE_R1_R2, + SERVICE_R1_R2_ID, SERVICE_R1_R2_UUID, SERVICE_R1_R3, SERVICE_R1_R3_ID, SERVICE_R1_R3_UUID, SERVICE_R2_R3, + SERVICE_R2_R3_ID, SERVICE_R2_R3_UUID, TOPOLOGY, TOPOLOGY_ID) + +def grpc_connection( + context_client_grpc : ContextClient, # pylint: disable=redefined-outer-name + context_db_mb : Tuple[Database, MessageBroker]): # pylint: disable=redefined-outer-name + Session = context_db_mb[0] + + database = Database(Session) + + # ----- Clean the database ----------------------------------------------------------------------------------------- + database.clear() + + # ----- Initialize the EventsCollector ----------------------------------------------------------------------------- + events_collector = EventsCollector(context_client_grpc) + events_collector.start() + + # ----- Prepare dependencies for the test and capture related events ----------------------------------------------- + response = context_client_grpc.SetContext(Context(**CONTEXT)) + assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + response = context_client_grpc.SetTopology(Topology(**TOPOLOGY)) + assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID + + response = context_client_grpc.SetDevice(Device(**DEVICE_R1)) + assert response.device_uuid.uuid == DEVICE_R1_UUID + + response = context_client_grpc.SetDevice(Device(**DEVICE_R2)) + assert response.device_uuid.uuid == DEVICE_R2_UUID + + response = context_client_grpc.SetDevice(Device(**DEVICE_R3)) + assert response.device_uuid.uuid == DEVICE_R3_UUID + + response = context_client_grpc.SetService(Service(**SERVICE_R1_R2)) + assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.service_uuid.uuid == SERVICE_R1_R2_UUID + + CONTEXT_WITH_SERVICE = copy.deepcopy(CONTEXT) + CONTEXT_WITH_SERVICE['service_ids'].append(SERVICE_R1_R2_ID) + response = context_client_grpc.SetContext(Context(**CONTEXT_WITH_SERVICE)) + assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + response = context_client_grpc.SetService(Service(**SERVICE_R2_R3)) + assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.service_uuid.uuid == SERVICE_R2_R3_UUID + + CONTEXT_WITH_SERVICE = copy.deepcopy(CONTEXT) + CONTEXT_WITH_SERVICE['service_ids'].append(SERVICE_R2_R3_ID) + response = context_client_grpc.SetContext(Context(**CONTEXT_WITH_SERVICE)) + assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + response = context_client_grpc.SetService(Service(**SERVICE_R1_R3)) + assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.service_uuid.uuid == SERVICE_R1_R3_UUID + + CONTEXT_WITH_SERVICE = copy.deepcopy(CONTEXT) + CONTEXT_WITH_SERVICE['service_ids'].append(SERVICE_R1_R3_ID) + response = context_client_grpc.SetContext(Context(**CONTEXT_WITH_SERVICE)) + assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + events = events_collector.get_events(block=True, count=11) + + assert isinstance(events[0], ContextEvent) + assert events[0].event.event_type == EventTypeEnum.EVENTTYPE_CREATE + assert events[0].context_id.context_uuid.uuid == DEFAULT_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 == DEFAULT_CONTEXT_UUID + assert events[1].topology_id.topology_uuid.uuid == DEFAULT_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 + + assert isinstance(events[4], DeviceEvent) + assert events[4].event.event_type == EventTypeEnum.EVENTTYPE_CREATE + assert events[4].device_id.device_uuid.uuid == DEVICE_R3_UUID + + assert isinstance(events[5], ServiceEvent) + assert events[5].event.event_type == EventTypeEnum.EVENTTYPE_CREATE + assert events[5].service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert events[5].service_id.service_uuid.uuid == SERVICE_R1_R2_UUID + + assert isinstance(events[6], ContextEvent) + assert events[6].event.event_type == EventTypeEnum.EVENTTYPE_UPDATE + assert events[6].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + assert isinstance(events[7], ServiceEvent) + assert events[7].event.event_type == EventTypeEnum.EVENTTYPE_CREATE + assert events[7].service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert events[7].service_id.service_uuid.uuid == SERVICE_R2_R3_UUID + + assert isinstance(events[8], ContextEvent) + assert events[8].event.event_type == EventTypeEnum.EVENTTYPE_UPDATE + assert events[8].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + assert isinstance(events[9], ServiceEvent) + assert events[9].event.event_type == EventTypeEnum.EVENTTYPE_CREATE + assert events[9].service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert events[9].service_id.service_uuid.uuid == SERVICE_R1_R3_UUID + + assert isinstance(events[10], ContextEvent) + assert events[10].event.event_type == EventTypeEnum.EVENTTYPE_UPDATE + assert events[10].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + # ----- Get when the object does not exist ------------------------------------------------------------------------- + with pytest.raises(grpc.RpcError) as e: + context_client_grpc.GetConnection(ConnectionId(**CONNECTION_R1_R3_ID)) + assert e.value.code() == grpc.StatusCode.NOT_FOUND + assert e.value.details() == 'Connection({:s}) not found'.format(CONNECTION_R1_R3_UUID) + + # ----- List when the object does not exist ------------------------------------------------------------------------ + response = context_client_grpc.ListConnectionIds(ServiceId(**SERVICE_R1_R3_ID)) + assert len(response.connection_ids) == 0 + + response = context_client_grpc.ListConnections(ServiceId(**SERVICE_R1_R3_ID)) + assert len(response.connections) == 0 + + # ----- Dump state of database before create the object ------------------------------------------------------------ + db_entries = context_database.dump() + LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) + for db_entry in db_entries: + LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover + LOGGER.info('-----------------------------------------------------------') + assert len(db_entries) == 187 + + # ----- Create the object ------------------------------------------------------------------------------------------ + with pytest.raises(grpc.RpcError) as e: + WRONG_CONNECTION = copy.deepcopy(CONNECTION_R1_R3) + WRONG_CONNECTION['path_hops_endpoint_ids'][0]\ + ['topology_id']['context_id']['context_uuid']['uuid'] = 'wrong-context-uuid' + context_client_grpc.SetConnection(Connection(**WRONG_CONNECTION)) + assert e.value.code() == grpc.StatusCode.NOT_FOUND + # TODO: should we check that all endpoints belong to same topology? + # TODO: should we check that endpoints form links over the topology? + msg = 'EndPoint({:s}/{:s}:wrong-context-uuid/{:s}) not found'.format( + DEVICE_R1_UUID, WRONG_CONNECTION['path_hops_endpoint_ids'][0]['endpoint_uuid']['uuid'], DEFAULT_TOPOLOGY_UUID) + assert e.value.details() == msg + + response = context_client_grpc.SetConnection(Connection(**CONNECTION_R1_R3)) + assert response.connection_uuid.uuid == CONNECTION_R1_R3_UUID + + # ----- Check create event ----------------------------------------------------------------------------------------- + event = events_collector.get_event(block=True) + assert isinstance(event, ConnectionEvent) + assert event.event.event_type == EventTypeEnum.EVENTTYPE_CREATE + assert event.connection_id.connection_uuid.uuid == CONNECTION_R1_R3_UUID + + # ----- Update the object ------------------------------------------------------------------------------------------ + response = context_client_grpc.SetConnection(Connection(**CONNECTION_R1_R3)) + assert response.connection_uuid.uuid == CONNECTION_R1_R3_UUID + + # ----- Check update event ----------------------------------------------------------------------------------------- + event = events_collector.get_event(block=True) + assert isinstance(event, ConnectionEvent) + assert event.event.event_type == EventTypeEnum.EVENTTYPE_UPDATE + assert event.connection_id.connection_uuid.uuid == CONNECTION_R1_R3_UUID + + # ----- Dump state of database after create/update the object ------------------------------------------------------ + db_entries = context_database.dump() + LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) + for db_entry in db_entries: + LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover + LOGGER.info('-----------------------------------------------------------') + assert len(db_entries) == 203 + + # ----- Get when the object exists --------------------------------------------------------------------------------- + response = context_client_grpc.GetConnection(ConnectionId(**CONNECTION_R1_R3_ID)) + assert response.connection_id.connection_uuid.uuid == CONNECTION_R1_R3_UUID + assert response.service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.service_id.service_uuid.uuid == SERVICE_R1_R3_UUID + assert len(response.path_hops_endpoint_ids) == 6 + assert len(response.sub_service_ids) == 2 + + # ----- List when the object exists -------------------------------------------------------------------------------- + response = context_client_grpc.ListConnectionIds(ServiceId(**SERVICE_R1_R3_ID)) + assert len(response.connection_ids) == 1 + assert response.connection_ids[0].connection_uuid.uuid == CONNECTION_R1_R3_UUID + + response = context_client_grpc.ListConnections(ServiceId(**SERVICE_R1_R3_ID)) + assert len(response.connections) == 1 + assert response.connections[0].connection_id.connection_uuid.uuid == CONNECTION_R1_R3_UUID + assert len(response.connections[0].path_hops_endpoint_ids) == 6 + assert len(response.connections[0].sub_service_ids) == 2 + + # ----- Remove the object ------------------------------------------------------------------------------------------ + context_client_grpc.RemoveConnection(ConnectionId(**CONNECTION_R1_R3_ID)) + context_client_grpc.RemoveService(ServiceId(**SERVICE_R1_R3_ID)) + context_client_grpc.RemoveService(ServiceId(**SERVICE_R2_R3_ID)) + context_client_grpc.RemoveService(ServiceId(**SERVICE_R1_R2_ID)) + context_client_grpc.RemoveDevice(DeviceId(**DEVICE_R1_ID)) + context_client_grpc.RemoveDevice(DeviceId(**DEVICE_R2_ID)) + context_client_grpc.RemoveDevice(DeviceId(**DEVICE_R3_ID)) + context_client_grpc.RemoveTopology(TopologyId(**TOPOLOGY_ID)) + context_client_grpc.RemoveContext(ContextId(**CONTEXT_ID)) + + # ----- Check remove event ----------------------------------------------------------------------------------------- + events = events_collector.get_events(block=True, count=9) + + assert isinstance(events[0], ConnectionEvent) + assert events[0].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + assert events[0].connection_id.connection_uuid.uuid == CONNECTION_R1_R3_UUID + + assert isinstance(events[1], ServiceEvent) + assert events[1].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + assert events[1].service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert events[1].service_id.service_uuid.uuid == SERVICE_R1_R3_UUID + + assert isinstance(events[2], ServiceEvent) + assert events[2].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + assert events[2].service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert events[2].service_id.service_uuid.uuid == SERVICE_R2_R3_UUID + + assert isinstance(events[3], ServiceEvent) + assert events[3].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + assert events[3].service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert events[3].service_id.service_uuid.uuid == SERVICE_R1_R2_UUID + + assert isinstance(events[4], DeviceEvent) + assert events[4].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + assert events[4].device_id.device_uuid.uuid == DEVICE_R1_UUID + + assert isinstance(events[5], DeviceEvent) + assert events[5].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + assert events[5].device_id.device_uuid.uuid == DEVICE_R2_UUID + + assert isinstance(events[6], DeviceEvent) + assert events[6].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + assert events[6].device_id.device_uuid.uuid == DEVICE_R3_UUID + + assert isinstance(events[7], TopologyEvent) + assert events[7].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + assert events[7].topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert events[7].topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID + + assert isinstance(events[8], ContextEvent) + assert events[8].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + assert events[8].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + # ----- Stop the EventsCollector ----------------------------------------------------------------------------------- + events_collector.stop() + + # ----- Dump state of database after remove the object ------------------------------------------------------------- + db_entries = context_database.dump() + LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) + for db_entry in db_entries: + LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover + LOGGER.info('-----------------------------------------------------------') + assert len(db_entries) == 0 diff --git a/src/context/tests/_test_context.py b/src/context/tests/_test_context.py new file mode 100644 index 0000000000000000000000000000000000000000..ef67d39d79a8fb175ba7da8ca093f39262dd2390 --- /dev/null +++ b/src/context/tests/_test_context.py @@ -0,0 +1,160 @@ +# 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. + +import copy, grpc, pytest, uuid +from common.Constants import DEFAULT_CONTEXT_UUID +from common.proto.context_pb2 import Context, ContextId, Empty +from common.tools.object_factory.Context import json_context_id +from common.tools.object_factory.Service import json_service_id +from common.tools.object_factory.Slice import json_slice_id +from common.tools.object_factory.Topology import json_topology_id +from context.client.ContextClient import ContextClient +#from context.client.EventsCollector import EventsCollector +from .Objects import CONTEXT, CONTEXT_ID + +def grpc_context(context_client_grpc : ContextClient) -> None: + + # ----- Initialize the EventsCollector ----------------------------------------------------------------------------- + #events_collector = EventsCollector( + # context_client_grpc, log_events_received=True, + # activate_context_collector = True, activate_topology_collector = False, activate_device_collector = False, + # activate_link_collector = False, activate_service_collector = False, activate_slice_collector = False, + # activate_connection_collector = False) + #events_collector.start() + + # ----- Get when the object does not exist ------------------------------------------------------------------------- + with pytest.raises(grpc.RpcError) as e: + context_client_grpc.GetContext(ContextId(**CONTEXT_ID)) + assert e.value.code() == grpc.StatusCode.NOT_FOUND + assert e.value.details() == 'Context({:s}) not found'.format(DEFAULT_CONTEXT_UUID) + + # ----- List when the object does not exist ------------------------------------------------------------------------ + response = context_client_grpc.ListContextIds(Empty()) + assert len(response.context_ids) == 0 + + response = context_client_grpc.ListContexts(Empty()) + assert len(response.contexts) == 0 + + # ----- Create the object ------------------------------------------------------------------------------------------ + response = context_client_grpc.SetContext(Context(**CONTEXT)) + assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + wrong_context_uuid = str(uuid.uuid4()) + wrong_context_id = json_context_id(wrong_context_uuid) + with pytest.raises(grpc.RpcError) as e: + WRONG_CONTEXT = copy.deepcopy(CONTEXT) + WRONG_CONTEXT['topology_ids'].append(json_topology_id(str(uuid.uuid4()), context_id=wrong_context_id)) + context_client_grpc.SetContext(Context(**WRONG_CONTEXT)) + assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT + msg = 'request.topology_ids[0].context_id.context_uuid.uuid({}) is invalid; '\ + 'should be == request.context_id.context_uuid.uuid({})'.format(wrong_context_uuid, DEFAULT_CONTEXT_UUID) + assert e.value.details() == msg + + with pytest.raises(grpc.RpcError) as e: + WRONG_CONTEXT = copy.deepcopy(CONTEXT) + WRONG_CONTEXT['service_ids'].append(json_service_id(str(uuid.uuid4()), context_id=wrong_context_id)) + context_client_grpc.SetContext(Context(**WRONG_CONTEXT)) + assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT + msg = 'request.service_ids[0].context_id.context_uuid.uuid({}) is invalid; '\ + 'should be == request.context_id.context_uuid.uuid({})'.format(wrong_context_uuid, DEFAULT_CONTEXT_UUID) + assert e.value.details() == msg + + with pytest.raises(grpc.RpcError) as e: + WRONG_CONTEXT = copy.deepcopy(CONTEXT) + WRONG_CONTEXT['slice_ids'].append(json_slice_id(str(uuid.uuid4()), context_id=wrong_context_id)) + context_client_grpc.SetContext(Context(**WRONG_CONTEXT)) + assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT + msg = 'request.slice_ids[0].context_id.context_uuid.uuid({}) is invalid; '\ + 'should be == request.context_id.context_uuid.uuid({})'.format(wrong_context_uuid, DEFAULT_CONTEXT_UUID) + assert e.value.details() == msg + + # ----- Check create event ----------------------------------------------------------------------------------------- + #event = events_collector.get_event(block=True, timeout=10.0) + #assert isinstance(event, ContextEvent) + #assert event.event.event_type == EventTypeEnum.EVENTTYPE_CREATE + #assert event.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + # ----- Get when the object exists --------------------------------------------------------------------------------- + response = context_client_grpc.GetContext(ContextId(**CONTEXT_ID)) + assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.name == '' + assert len(response.topology_ids) == 0 + assert len(response.service_ids) == 0 + assert len(response.slice_ids) == 0 + + # ----- List when the object exists -------------------------------------------------------------------------------- + response = context_client_grpc.ListContextIds(Empty()) + assert len(response.context_ids) == 1 + assert response.context_ids[0].context_uuid.uuid == DEFAULT_CONTEXT_UUID + + response = context_client_grpc.ListContexts(Empty()) + assert len(response.contexts) == 1 + assert response.contexts[0].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.contexts[0].name == '' + assert len(response.contexts[0].topology_ids) == 0 + assert len(response.contexts[0].service_ids) == 0 + assert len(response.contexts[0].slice_ids) == 0 + + # ----- Update the object ------------------------------------------------------------------------------------------ + new_context_name = 'new' + CONTEXT_WITH_NAME = copy.deepcopy(CONTEXT) + CONTEXT_WITH_NAME['name'] = new_context_name + response = context_client_grpc.SetContext(Context(**CONTEXT_WITH_NAME)) + assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + # ----- Check update event ----------------------------------------------------------------------------------------- + #event = events_collector.get_event(block=True, timeout=10.0) + #assert isinstance(event, ContextEvent) + #assert event.event.event_type == EventTypeEnum.EVENTTYPE_UPDATE + #assert event.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + # ----- Get when the object is modified ---------------------------------------------------------------------------- + response = context_client_grpc.GetContext(ContextId(**CONTEXT_ID)) + assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.name == new_context_name + assert len(response.topology_ids) == 0 + assert len(response.service_ids) == 0 + assert len(response.slice_ids) == 0 + + # ----- List when the object is modified --------------------------------------------------------------------------- + response = context_client_grpc.ListContextIds(Empty()) + assert len(response.context_ids) == 1 + assert response.context_ids[0].context_uuid.uuid == DEFAULT_CONTEXT_UUID + + response = context_client_grpc.ListContexts(Empty()) + assert len(response.contexts) == 1 + assert response.contexts[0].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.contexts[0].name == new_context_name + assert len(response.contexts[0].topology_ids) == 0 + assert len(response.contexts[0].service_ids) == 0 + assert len(response.contexts[0].slice_ids) == 0 + + # ----- Remove the object ------------------------------------------------------------------------------------------ + context_client_grpc.RemoveContext(ContextId(**CONTEXT_ID)) + + # ----- Check remove event ----------------------------------------------------------------------------------------- + #event = events_collector.get_event(block=True, timeout=10.0) + #assert isinstance(event, ContextEvent) + #assert event.event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + #assert event.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + # ----- List after deleting the object ----------------------------------------------------------------------------- + response = context_client_grpc.ListContextIds(Empty()) + assert len(response.context_ids) == 0 + + response = context_client_grpc.ListContexts(Empty()) + assert len(response.contexts) == 0 + + # ----- Stop the EventsCollector ----------------------------------------------------------------------------------- + #events_collector.stop() diff --git a/src/context/tests/_test_device.py b/src/context/tests/_test_device.py new file mode 100644 index 0000000000000000000000000000000000000000..20760a9612cf57739dda44736e184bccebbc776f --- /dev/null +++ b/src/context/tests/_test_device.py @@ -0,0 +1,199 @@ +# 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. + +import copy, grpc, pytest +from common.Constants import DEFAULT_CONTEXT_UUID, DEFAULT_TOPOLOGY_UUID +from common.proto.context_pb2 import ( + Context, ContextId, Device, DeviceDriverEnum, DeviceId, DeviceOperationalStatusEnum, Empty, Topology, TopologyId) +from context.client.ContextClient import ContextClient +#from context.client.EventsCollector import EventsCollector +from .Objects import CONTEXT, CONTEXT_ID, DEVICE_R1, DEVICE_R1_ID, DEVICE_R1_UUID, TOPOLOGY, TOPOLOGY_ID + +def grpc_device(context_client_grpc : ContextClient) -> None: + + # ----- Initialize the EventsCollector ----------------------------------------------------------------------------- + #events_collector = EventsCollector( + # context_client_grpc, log_events_received=True, + # activate_context_collector = False, activate_topology_collector = False, activate_device_collector = True, + # activate_link_collector = False, activate_service_collector = False, activate_slice_collector = False, + # activate_connection_collector = False) + #events_collector.start() + + # ----- Prepare dependencies for the test and capture related events ----------------------------------------------- + response = context_client_grpc.SetContext(Context(**CONTEXT)) + assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + response = context_client_grpc.SetTopology(Topology(**TOPOLOGY)) + assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID + + #events = events_collector.get_events(block=True, count=2) + #assert isinstance(events[0], ContextEvent) + #assert events[0].event.event_type == EventTypeEnum.EVENTTYPE_CREATE + #assert events[0].context_id.context_uuid.uuid == DEFAULT_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 == DEFAULT_CONTEXT_UUID + #assert events[1].topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID + + # ----- Get when the object does not exist ------------------------------------------------------------------------- + with pytest.raises(grpc.RpcError) as e: + context_client_grpc.GetDevice(DeviceId(**DEVICE_R1_ID)) + assert e.value.code() == grpc.StatusCode.NOT_FOUND + assert e.value.details() == 'Device({:s}) not found'.format(DEVICE_R1_UUID) + + # ----- List when the object does not exist ------------------------------------------------------------------------ + response = context_client_grpc.ListDeviceIds(Empty()) + assert len(response.device_ids) == 0 + + response = context_client_grpc.ListDevices(Empty()) + assert len(response.devices) == 0 + + # ----- Create the object ------------------------------------------------------------------------------------------ + with pytest.raises(grpc.RpcError) as e: + WRONG_DEVICE = copy.deepcopy(DEVICE_R1) + WRONG_DEVICE_UUID = '3f03c76d-31fb-47f5-9c1d-bc6b6bfa2d08' + WRONG_DEVICE['device_endpoints'][0]['endpoint_id']['device_id']['device_uuid']['uuid'] = WRONG_DEVICE_UUID + context_client_grpc.SetDevice(Device(**WRONG_DEVICE)) + assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT + msg = 'request.device_endpoints[0].device_id.device_uuid.uuid({}) is invalid; '\ + 'should be == request.device_id.device_uuid.uuid({})'.format(WRONG_DEVICE_UUID, DEVICE_R1_UUID) + assert e.value.details() == msg + + response = context_client_grpc.SetDevice(Device(**DEVICE_R1)) + assert response.device_uuid.uuid == DEVICE_R1_UUID + + # ----- Check create event ----------------------------------------------------------------------------------------- + # event = events_collector.get_event(block=True) + # assert isinstance(event, DeviceEvent) + # assert event.event.event_type == EventTypeEnum.EVENTTYPE_CREATE + # assert event.device_id.device_uuid.uuid == DEVICE_R1_UUID + + # ----- Get when the object exists --------------------------------------------------------------------------------- + response = context_client_grpc.GetDevice(DeviceId(**DEVICE_R1_ID)) + assert response.device_id.device_uuid.uuid == DEVICE_R1_UUID + assert response.name == '' + assert response.device_type == 'packet-router' + assert len(response.device_config.config_rules) == 3 + assert response.device_operational_status == DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_DISABLED + assert len(response.device_drivers) == 1 + assert DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG in response.device_drivers + assert len(response.device_endpoints) == 3 + + # ----- List when the object exists -------------------------------------------------------------------------------- + response = context_client_grpc.ListDeviceIds(Empty()) + assert len(response.device_ids) == 1 + assert response.device_ids[0].device_uuid.uuid == DEVICE_R1_UUID + + response = context_client_grpc.ListDevices(Empty()) + assert len(response.devices) == 1 + assert response.devices[0].device_id.device_uuid.uuid == DEVICE_R1_UUID + assert response.devices[0].name == '' + assert response.devices[0].device_type == 'packet-router' + assert len(response.devices[0].device_config.config_rules) == 3 + assert response.devices[0].device_operational_status == DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_DISABLED + assert len(response.devices[0].device_drivers) == 1 + assert DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG in response.devices[0].device_drivers + assert len(response.devices[0].device_endpoints) == 3 + + # ----- Update the object ------------------------------------------------------------------------------------------ + new_device_name = 'r1' + new_device_driver = DeviceDriverEnum.DEVICEDRIVER_UNDEFINED + DEVICE_UPDATED = copy.deepcopy(DEVICE_R1) + DEVICE_UPDATED['name'] = new_device_name + DEVICE_UPDATED['device_operational_status'] = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED + DEVICE_UPDATED['device_drivers'].append(new_device_driver) + response = context_client_grpc.SetDevice(Device(**DEVICE_UPDATED)) + assert response.device_uuid.uuid == DEVICE_R1_UUID + + # ----- Check update event ----------------------------------------------------------------------------------------- + # event = events_collector.get_event(block=True) + # assert isinstance(event, DeviceEvent) + # assert event.event.event_type == EventTypeEnum.EVENTTYPE_UPDATE + # assert event.device_id.device_uuid.uuid == DEVICE_R1_UUID + + # ----- Get when the object is modified ---------------------------------------------------------------------------- + response = context_client_grpc.GetDevice(DeviceId(**DEVICE_R1_ID)) + assert response.device_id.device_uuid.uuid == DEVICE_R1_UUID + assert response.name == new_device_name + assert response.device_type == 'packet-router' + assert len(response.device_config.config_rules) == 3 + assert response.device_operational_status == DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED + assert len(response.device_drivers) == 2 + assert DeviceDriverEnum.DEVICEDRIVER_UNDEFINED in response.device_drivers + assert DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG in response.device_drivers + assert len(response.device_endpoints) == 3 + + # ----- List when the object is modified --------------------------------------------------------------------------- + response = context_client_grpc.ListDeviceIds(Empty()) + assert len(response.device_ids) == 1 + assert response.device_ids[0].device_uuid.uuid == DEVICE_R1_UUID + + response = context_client_grpc.ListDevices(Empty()) + assert len(response.devices) == 1 + assert response.devices[0].device_id.device_uuid.uuid == DEVICE_R1_UUID + assert response.devices[0].name == new_device_name + assert response.devices[0].device_type == 'packet-router' + assert len(response.devices[0].device_config.config_rules) == 3 + assert response.devices[0].device_operational_status == DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED + assert len(response.devices[0].device_drivers) == 2 + assert DeviceDriverEnum.DEVICEDRIVER_UNDEFINED in response.devices[0].device_drivers + assert DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG in response.devices[0].device_drivers + assert len(response.devices[0].device_endpoints) == 3 + + # ----- Create object relation ------------------------------------------------------------------------------------- + TOPOLOGY_WITH_DEVICE = copy.deepcopy(TOPOLOGY) + TOPOLOGY_WITH_DEVICE['device_ids'].append(DEVICE_R1_ID) + response = context_client_grpc.SetTopology(Topology(**TOPOLOGY_WITH_DEVICE)) + assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.topology_uuid.uuid == DEFAULT_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 == DEFAULT_CONTEXT_UUID + # assert response.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID + + # ----- Check relation was created --------------------------------------------------------------------------------- + response = context_client_grpc.GetTopology(TopologyId(**TOPOLOGY_ID)) + assert response.topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID + assert len(response.device_ids) == 1 + assert response.device_ids[0].device_uuid.uuid == DEVICE_R1_UUID + assert len(response.link_ids) == 0 + + # ----- Remove the object ------------------------------------------------------------------------------------------ + context_client_grpc.RemoveDevice(DeviceId(**DEVICE_R1_ID)) + context_client_grpc.RemoveTopology(TopologyId(**TOPOLOGY_ID)) + context_client_grpc.RemoveContext(ContextId(**CONTEXT_ID)) + + # ----- Check remove event ----------------------------------------------------------------------------------------- + # events = events_collector.get_events(block=True, count=3) + + # 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], TopologyEvent) + # assert events[1].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + # assert events[1].topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + # assert events[1].topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID + + # assert isinstance(events[2], ContextEvent) + # assert events[2].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + # assert events[2].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + # ----- Stop the EventsCollector ----------------------------------------------------------------------------------- + #events_collector.stop() diff --git a/src/context/tests/_test_link.py b/src/context/tests/_test_link.py new file mode 100644 index 0000000000000000000000000000000000000000..d493f23d7d64e29ae4fbfc987bd09fb4d54c3aac --- /dev/null +++ b/src/context/tests/_test_link.py @@ -0,0 +1,189 @@ +# 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. + +import copy, grpc, pytest +from common.Constants import DEFAULT_CONTEXT_UUID, DEFAULT_TOPOLOGY_UUID +from common.proto.context_pb2 import Context, ContextId, Device, DeviceId, Empty, Link, LinkId, Topology, TopologyId +from context.client.ContextClient import ContextClient +#from context.client.EventsCollector import EventsCollector +from .Objects import ( + CONTEXT, CONTEXT_ID, DEVICE_R1, DEVICE_R1_ID, DEVICE_R1_UUID, DEVICE_R2, DEVICE_R2_ID, DEVICE_R2_UUID, LINK_R1_R2, + LINK_R1_R2_ID, LINK_R1_R2_UUID, TOPOLOGY, TOPOLOGY_ID) + +def grpc_link(context_client_grpc: ContextClient) -> None: + + # ----- Initialize the EventsCollector ----------------------------------------------------------------------------- + #events_collector = EventsCollector( + # context_client_grpc, 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() + + # ----- Prepare dependencies for the test and capture related events ----------------------------------------------- + response = context_client_grpc.SetContext(Context(**CONTEXT)) + assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + response = context_client_grpc.SetTopology(Topology(**TOPOLOGY)) + assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID + + response = context_client_grpc.SetDevice(Device(**DEVICE_R1)) + assert response.device_uuid.uuid == DEVICE_R1_UUID + + response = context_client_grpc.SetDevice(Device(**DEVICE_R2)) + assert response.device_uuid.uuid == DEVICE_R2_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 == DEFAULT_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 == DEFAULT_CONTEXT_UUID + # assert events[1].topology_id.topology_uuid.uuid == DEFAULT_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 ------------------------------------------------------------------------- + with pytest.raises(grpc.RpcError) as e: + context_client_grpc.GetLink(LinkId(**LINK_R1_R2_ID)) + assert e.value.code() == grpc.StatusCode.NOT_FOUND + assert e.value.details() == 'Link({:s}) not found'.format(LINK_R1_R2_UUID) + + # ----- List when the object does not exist ------------------------------------------------------------------------ + response = context_client_grpc.ListLinkIds(Empty()) + assert len(response.link_ids) == 0 + + response = context_client_grpc.ListLinks(Empty()) + assert len(response.links) == 0 + + # ----- Create the object ------------------------------------------------------------------------------------------ + response = context_client_grpc.SetLink(Link(**LINK_R1_R2)) + assert response.link_uuid.uuid == LINK_R1_R2_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_R1_R2_UUID + + # ----- Get when the object exists --------------------------------------------------------------------------------- + response = context_client_grpc.GetLink(LinkId(**LINK_R1_R2_ID)) + assert response.link_id.link_uuid.uuid == LINK_R1_R2_UUID + assert response.name == '' + assert len(response.link_endpoint_ids) == 2 + + # ----- List when the object exists -------------------------------------------------------------------------------- + response = context_client_grpc.ListLinkIds(Empty()) + assert len(response.link_ids) == 1 + assert response.link_ids[0].link_uuid.uuid == LINK_R1_R2_UUID + + response = context_client_grpc.ListLinks(Empty()) + assert len(response.links) == 1 + assert response.links[0].link_id.link_uuid.uuid == LINK_R1_R2_UUID + assert response.links[0].name == '' + assert len(response.links[0].link_endpoint_ids) == 2 + + # ----- Update the object ------------------------------------------------------------------------------------------ + new_link_name = 'l1' + LINK_UPDATED = copy.deepcopy(LINK_R1_R2) + LINK_UPDATED['name'] = new_link_name + response = context_client_grpc.SetLink(Link(**LINK_UPDATED)) + assert response.link_uuid.uuid == LINK_R1_R2_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_R1_R2_UUID + + # ----- Get when the object is modified ---------------------------------------------------------------------------- + response = context_client_grpc.GetLink(LinkId(**LINK_R1_R2_ID)) + assert response.link_id.link_uuid.uuid == LINK_R1_R2_UUID + assert response.name == new_link_name + assert len(response.link_endpoint_ids) == 2 + + # ----- List when the object is modified --------------------------------------------------------------------------- + response = context_client_grpc.ListLinkIds(Empty()) + assert len(response.link_ids) == 1 + assert response.link_ids[0].link_uuid.uuid == LINK_R1_R2_UUID + + response = context_client_grpc.ListLinks(Empty()) + assert len(response.links) == 1 + assert response.links[0].link_id.link_uuid.uuid == LINK_R1_R2_UUID + 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_grpc.SetTopology(Topology(**TOPOLOGY_WITH_LINK)) + assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.topology_uuid.uuid == DEFAULT_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 == DEFAULT_CONTEXT_UUID + # assert response.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID + + # ----- Check relation was created --------------------------------------------------------------------------------- + response = context_client_grpc.GetTopology(TopologyId(**TOPOLOGY_ID)) + assert response.topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID + assert len(response.device_ids) == 2 + assert response.device_ids[0].device_uuid.uuid in {DEVICE_R1_UUID, DEVICE_R2_UUID} + assert response.device_ids[1].device_uuid.uuid in {DEVICE_R1_UUID, DEVICE_R2_UUID} + assert len(response.link_ids) == 1 + assert response.link_ids[0].link_uuid.uuid == LINK_R1_R2_UUID + + # ----- Remove the object ------------------------------------------------------------------------------------------ + #context_client_grpc.RemoveLink(LinkId(**LINK_R1_R2_ID)) + #context_client_grpc.RemoveDevice(DeviceId(**DEVICE_R1_ID)) + #context_client_grpc.RemoveDevice(DeviceId(**DEVICE_R2_ID)) + #context_client_grpc.RemoveTopology(TopologyId(**TOPOLOGY_ID)) + #context_client_grpc.RemoveContext(ContextId(**CONTEXT_ID)) + + # ----- Check remove event ----------------------------------------------------------------------------------------- + # events = events_collector.get_events(block=True, count=5) + # + # assert isinstance(events[0], LinkEvent) + # assert events[0].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + # assert events[0].link_id.link_uuid.uuid == LINK_R1_R2_UUID + # + # assert isinstance(events[1], DeviceEvent) + # assert events[1].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + # assert events[1].device_id.device_uuid.uuid == DEVICE_R1_UUID + # + # assert isinstance(events[2], DeviceEvent) + # assert events[2].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + # assert events[2].device_id.device_uuid.uuid == DEVICE_R2_UUID + # + # assert isinstance(events[3], TopologyEvent) + # assert events[3].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + # assert events[3].topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + # assert events[3].topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID + # + # assert isinstance(events[4], ContextEvent) + # assert events[4].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + # assert events[4].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + # ----- Stop the EventsCollector ----------------------------------------------------------------------------------- + #events_collector.stop() diff --git a/src/context/tests/_test_policy.py b/src/context/tests/_test_policy.py new file mode 100644 index 0000000000000000000000000000000000000000..e416575f774bc6fdc3bcc3c7c5e003dba994e639 --- /dev/null +++ b/src/context/tests/_test_policy.py @@ -0,0 +1,114 @@ +# 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. + +import grpc, pytest +from typing import Tuple +from common.proto.context_pb2 import Empty +from common.proto.policy_pb2 import PolicyRuleId, PolicyRule +from context.client.ContextClient import ContextClient +#from context.client.EventsCollector import EventsCollector +from .Objects import POLICY_RULE, POLICY_RULE_ID, POLICY_RULE_UUID + +def grpc_policy( + context_client_grpc : ContextClient, # pylint: disable=redefined-outer-name + context_db_mb : Tuple[Database, MessageBroker]): # pylint: disable=redefined-outer-name + context_database = context_db_mb[0] + + # ----- Clean the database ----------------------------------------------------------------------------------------- + context_database.clear_all() + + # ----- Initialize the EventsCollector ----------------------------------------------------------------------------- + #events_collector = EventsCollector(context_client_grpc) + #events_collector.start() + + # ----- Get when the object does not exist ------------------------------------------------------------------------- + POLICY_ID = 'no-uuid' + DEFAULT_POLICY_ID = {'uuid': {'uuid': POLICY_ID}} + + with pytest.raises(grpc.RpcError) as e: + context_client_grpc.GetPolicyRule(PolicyRuleId(**DEFAULT_POLICY_ID)) + + assert e.value.code() == grpc.StatusCode.NOT_FOUND + assert e.value.details() == 'PolicyRule({:s}) not found'.format(POLICY_ID) + + # ----- List when the object does not exist ------------------------------------------------------------------------ + response = context_client_grpc.ListPolicyRuleIds(Empty()) + assert len(response.policyRuleIdList) == 0 + + response = context_client_grpc.ListPolicyRules(Empty()) + assert len(response.policyRules) == 0 + + # ----- Dump state of database before create the object ------------------------------------------------------------ + db_entries = context_database.dump() + LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) + for db_entry in db_entries: + LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover + LOGGER.info('-----------------------------------------------------------') + assert len(db_entries) == 0 + + # ----- Create the object ------------------------------------------------------------------------------------------ + response = context_client_grpc.SetPolicyRule(PolicyRule(**POLICY_RULE)) + assert response.uuid.uuid == POLICY_RULE_UUID + + # ----- Check create event ----------------------------------------------------------------------------------------- + # events = events_collector.get_events(block=True, count=1) + # assert isinstance(events[0], PolicyEvent) + # assert events[0].event.event_type == EventTypeEnum.EVENTTYPE_CREATE + # assert events[0].policy_id.uuid.uuid == POLICY_RULE_UUID + + # ----- Update the object ------------------------------------------------------------------------------------------ + response = context_client_grpc.SetPolicyRule(PolicyRule(**POLICY_RULE)) + assert response.uuid.uuid == POLICY_RULE_UUID + + # ----- Dump state of database after create/update the object ------------------------------------------------------ + db_entries = context_database.dump() + LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) + for db_entry in db_entries: + LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover + LOGGER.info('-----------------------------------------------------------') + assert len(db_entries) == 2 + + # ----- Get when the object exists --------------------------------------------------------------------------------- + response = context_client_grpc.GetPolicyRule(PolicyRuleId(**POLICY_RULE_ID)) + assert response.device.policyRuleBasic.policyRuleId.uuid.uuid == POLICY_RULE_UUID + + # ----- List when the object exists -------------------------------------------------------------------------------- + response = context_client_grpc.ListPolicyRuleIds(Empty()) + assert len(response.policyRuleIdList) == 1 + assert response.policyRuleIdList[0].uuid.uuid == POLICY_RULE_UUID + + response = context_client_grpc.ListPolicyRules(Empty()) + assert len(response.policyRules) == 1 + + # ----- Remove the object ------------------------------------------------------------------------------------------ + context_client_grpc.RemovePolicyRule(PolicyRuleId(**POLICY_RULE_ID)) + + # ----- Check remove event ----------------------------------------------------------------------------------------- + # events = events_collector.get_events(block=True, count=2) + + # assert isinstance(events[0], PolicyEvent) + # assert events[0].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + # assert events[0].policy_id.uuid.uuid == POLICY_RULE_UUID + + + # ----- Stop the EventsCollector ----------------------------------------------------------------------------------- + # events_collector.stop() + + # ----- Dump state of database after remove the object ------------------------------------------------------------- + db_entries = context_database.dump() + LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) + for db_entry in db_entries: + LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover + LOGGER.info('-----------------------------------------------------------') + assert len(db_entries) == 0 diff --git a/src/context/tests/_test_service.py b/src/context/tests/_test_service.py new file mode 100644 index 0000000000000000000000000000000000000000..88ece2ba90be93e133ed6699024e399a92045a7b --- /dev/null +++ b/src/context/tests/_test_service.py @@ -0,0 +1,214 @@ +# 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. + +import copy, grpc, pytest +from typing import Tuple +from common.Constants import DEFAULT_CONTEXT_UUID, DEFAULT_TOPOLOGY_UUID +from common.proto.context_pb2 import ( + Context, ContextEvent, ContextId, Device, DeviceEvent, DeviceId, EventTypeEnum, Service, ServiceEvent, ServiceId, + ServiceStatusEnum, ServiceTypeEnum, Topology, TopologyEvent, TopologyId) +from context.client.ContextClient import ContextClient +from context.client.EventsCollector import EventsCollector +from .Objects import ( + CONTEXT, CONTEXT_ID, DEVICE_R1, DEVICE_R1_ID, DEVICE_R1_UUID, DEVICE_R2, DEVICE_R2_ID, DEVICE_R2_UUID, + SERVICE_R1_R2, SERVICE_R1_R2_ID, SERVICE_R1_R2_UUID, TOPOLOGY, TOPOLOGY_ID) + +def grpc_service( + context_client_grpc : ContextClient, # pylint: disable=redefined-outer-name + context_db_mb : Tuple[Database, MessageBroker]): # pylint: disable=redefined-outer-name + Session = context_db_mb[0] + # ----- Clean the database ----------------------------------------------------------------------------------------- + database = Database(Session) + database.clear() + + # ----- Initialize the EventsCollector ----------------------------------------------------------------------------- + events_collector = EventsCollector(context_client_grpc) + events_collector.start() + + # ----- Prepare dependencies for the test and capture related events ----------------------------------------------- + response = context_client_grpc.SetContext(Context(**CONTEXT)) + assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + response = context_client_grpc.SetTopology(Topology(**TOPOLOGY)) + assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID + + response = context_client_grpc.SetDevice(Device(**DEVICE_R1)) + assert response.device_uuid.uuid == DEVICE_R1_UUID + + response = context_client_grpc.SetDevice(Device(**DEVICE_R2)) + assert response.device_uuid.uuid == DEVICE_R2_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 == DEFAULT_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 == DEFAULT_CONTEXT_UUID + # assert events[1].topology_id.topology_uuid.uuid == DEFAULT_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 + LOGGER.info('----------------') + + # ----- Get when the object does not exist ------------------------------------------------------------------------- + with pytest.raises(grpc.RpcError) as e: + context_client_grpc.GetService(ServiceId(**SERVICE_R1_R2_ID)) + assert e.value.code() == grpc.StatusCode.NOT_FOUND + assert e.value.details() == 'Service({:s}) not found'.format(SERVICE_R1_R2_UUID) + LOGGER.info('----------------') + + # ----- List when the object does not exist ------------------------------------------------------------------------ + response = context_client_grpc.ListServiceIds(ContextId(**CONTEXT_ID)) + assert len(response.service_ids) == 0 + LOGGER.info('----------------') + + response = context_client_grpc.ListServices(ContextId(**CONTEXT_ID)) + assert len(response.services) == 0 + LOGGER.info('----------------') + + # ----- Dump state of database before create the object ------------------------------------------------------------ + db_entries = database.dump_all() + LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) + for db_entry in db_entries: + LOGGER.info(db_entry) + LOGGER.info('-----------------------------------------------------------') + assert len(db_entries) == 80 + + # ----- Create the object ------------------------------------------------------------------------------------------ + with pytest.raises(grpc.RpcError) as e: + WRONG_SERVICE = copy.deepcopy(SERVICE_R1_R2) + WRONG_SERVICE['service_endpoint_ids'][0]\ + ['topology_id']['context_id']['context_uuid']['uuid'] = 'ca1ea172-728f-441d-972c-feeae8c9bffc' + context_client_grpc.SetService(Service(**WRONG_SERVICE)) + assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT + msg = 'request.service_endpoint_ids[0].topology_id.context_id.context_uuid.uuid(ca1ea172-728f-441d-972c-feeae8c9bffc) is invalid; '\ + 'should be == request.service_id.context_id.context_uuid.uuid({:s})'.format(DEFAULT_CONTEXT_UUID) + assert e.value.details() == msg + + response = context_client_grpc.SetService(Service(**SERVICE_R1_R2)) + assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.service_uuid.uuid == SERVICE_R1_R2_UUID + + CONTEXT_WITH_SERVICE = copy.deepcopy(CONTEXT) + CONTEXT_WITH_SERVICE['service_ids'].append(SERVICE_R1_R2_ID) + response = context_client_grpc.SetContext(Context(**CONTEXT_WITH_SERVICE)) + assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + # ----- Check create event ----------------------------------------------------------------------------------------- + events = events_collector.get_events(block=True, count=2) + + assert isinstance(events[0], ServiceEvent) + assert events[0].event.event_type == EventTypeEnum.EVENTTYPE_CREATE + assert events[0].service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert events[0].service_id.service_uuid.uuid == SERVICE_R1_R2_UUID + + assert isinstance(events[1], ContextEvent) + assert events[1].event.event_type == EventTypeEnum.EVENTTYPE_UPDATE + assert events[1].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + # ----- Update the object ------------------------------------------------------------------------------------------ + response = context_client_grpc.SetService(Service(**SERVICE_R1_R2)) + assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.service_uuid.uuid == SERVICE_R1_R2_UUID + + # ----- Check update event ----------------------------------------------------------------------------------------- + event = events_collector.get_event(block=True) + assert isinstance(event, ServiceEvent) + assert event.event.event_type == EventTypeEnum.EVENTTYPE_UPDATE + assert event.service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert event.service_id.service_uuid.uuid == SERVICE_R1_R2_UUID + + # ----- Dump state of database after create/update the object ------------------------------------------------------ + db_entries = context_database.dump() + LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) + for db_entry in db_entries: + LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover + LOGGER.info('-----------------------------------------------------------') + assert len(db_entries) == 108 + + # ----- Get when the object exists --------------------------------------------------------------------------------- + response = context_client_grpc.GetService(ServiceId(**SERVICE_R1_R2_ID)) + assert response.service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.service_id.service_uuid.uuid == SERVICE_R1_R2_UUID + assert response.service_type == ServiceTypeEnum.SERVICETYPE_L3NM + assert len(response.service_endpoint_ids) == 2 + assert len(response.service_constraints) == 2 + assert response.service_status.service_status == ServiceStatusEnum.SERVICESTATUS_PLANNED + assert len(response.service_config.config_rules) == 3 + + # ----- List when the object exists -------------------------------------------------------------------------------- + response = context_client_grpc.ListServiceIds(ContextId(**CONTEXT_ID)) + assert len(response.service_ids) == 1 + assert response.service_ids[0].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.service_ids[0].service_uuid.uuid == SERVICE_R1_R2_UUID + + response = context_client_grpc.ListServices(ContextId(**CONTEXT_ID)) + assert len(response.services) == 1 + assert response.services[0].service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.services[0].service_id.service_uuid.uuid == SERVICE_R1_R2_UUID + assert response.services[0].service_type == ServiceTypeEnum.SERVICETYPE_L3NM + assert len(response.services[0].service_endpoint_ids) == 2 + assert len(response.services[0].service_constraints) == 2 + assert response.services[0].service_status.service_status == ServiceStatusEnum.SERVICESTATUS_PLANNED + assert len(response.services[0].service_config.config_rules) == 3 + + # ----- Remove the object ------------------------------------------------------------------------------------------ + context_client_grpc.RemoveService(ServiceId(**SERVICE_R1_R2_ID)) + context_client_grpc.RemoveDevice(DeviceId(**DEVICE_R1_ID)) + context_client_grpc.RemoveDevice(DeviceId(**DEVICE_R2_ID)) + context_client_grpc.RemoveTopology(TopologyId(**TOPOLOGY_ID)) + context_client_grpc.RemoveContext(ContextId(**CONTEXT_ID)) + + # ----- Check remove event ----------------------------------------------------------------------------------------- + events = events_collector.get_events(block=True, count=5) + + assert isinstance(events[0], ServiceEvent) + assert events[0].service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert events[0].service_id.service_uuid.uuid == SERVICE_R1_R2_UUID + + assert isinstance(events[1], DeviceEvent) + assert events[1].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + assert events[1].device_id.device_uuid.uuid == DEVICE_R1_UUID + + assert isinstance(events[2], DeviceEvent) + assert events[2].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + assert events[2].device_id.device_uuid.uuid == DEVICE_R2_UUID + + assert isinstance(events[3], TopologyEvent) + assert events[3].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + assert events[3].topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert events[3].topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID + + assert isinstance(events[4], ContextEvent) + assert events[4].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + assert events[4].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + # ----- Stop the EventsCollector ----------------------------------------------------------------------------------- + events_collector.stop() + + # ----- Dump state of database after remove the object ------------------------------------------------------------- + db_entries = context_database.dump() + LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) + for db_entry in db_entries: + LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover + LOGGER.info('-----------------------------------------------------------') + assert len(db_entries) == 0 diff --git a/src/context/tests/_test_slice.py b/src/context/tests/_test_slice.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/context/tests/_test_topology.py b/src/context/tests/_test_topology.py new file mode 100644 index 0000000000000000000000000000000000000000..9774d972fc78c0688766ed4b8ad6ff68038a1c89 --- /dev/null +++ b/src/context/tests/_test_topology.py @@ -0,0 +1,166 @@ +# 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. + +import copy, grpc, pytest +from common.Constants import DEFAULT_CONTEXT_UUID, DEFAULT_TOPOLOGY_UUID +from common.proto.context_pb2 import Context, ContextId, Topology, TopologyId +from context.client.ContextClient import ContextClient +#from context.client.EventsCollector import EventsCollector +from .Objects import CONTEXT, CONTEXT_ID, TOPOLOGY, TOPOLOGY_ID + +def grpc_topology(context_client_grpc : ContextClient) -> None: + + # ----- Initialize the EventsCollector ----------------------------------------------------------------------------- + #events_collector = EventsCollector( + # context_client_grpc, log_events_received=True, + # activate_context_collector = False, activate_topology_collector = True, activate_device_collector = False, + # activate_link_collector = False, activate_service_collector = False, activate_slice_collector = False, + # activate_connection_collector = False) + #events_collector.start() + + # ----- Prepare dependencies for the test and capture related events ----------------------------------------------- + response = context_client_grpc.SetContext(Context(**CONTEXT)) + assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID + # event = events_collector.get_event(block=True) + # assert isinstance(event, ContextEvent) + # assert event.event.event_type == EventTypeEnum.EVENTTYPE_CREATE + # assert event.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + # ----- Get when the object does not exist ------------------------------------------------------------------------- + with pytest.raises(grpc.RpcError) as e: + context_client_grpc.GetTopology(TopologyId(**TOPOLOGY_ID)) + assert e.value.code() == grpc.StatusCode.NOT_FOUND + assert e.value.details() == 'Topology({:s}/{:s}) not found'.format(DEFAULT_CONTEXT_UUID, DEFAULT_TOPOLOGY_UUID) + + # ----- List when the object does not exist ------------------------------------------------------------------------ + response = context_client_grpc.ListTopologyIds(ContextId(**CONTEXT_ID)) + assert len(response.topology_ids) == 0 + + response = context_client_grpc.ListTopologies(ContextId(**CONTEXT_ID)) + assert len(response.topologies) == 0 + + # ----- Create the object ------------------------------------------------------------------------------------------ + response = context_client_grpc.SetTopology(Topology(**TOPOLOGY)) + assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID + + #CONTEXT_WITH_TOPOLOGY = copy.deepcopy(CONTEXT) + #CONTEXT_WITH_TOPOLOGY['topology_ids'].append(TOPOLOGY_ID) + #response = context_client_grpc.SetContext(Context(**CONTEXT_WITH_TOPOLOGY)) + #assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + # ----- Check create event ----------------------------------------------------------------------------------------- + #events = events_collector.get_events(block=True, count=2) + #assert isinstance(events[0], TopologyEvent) + #assert events[0].event.event_type == EventTypeEnum.EVENTTYPE_CREATE + #assert events[0].topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + #assert events[0].topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID + #assert isinstance(events[1], ContextEvent) + #assert events[1].event.event_type == EventTypeEnum.EVENTTYPE_UPDATE + #assert events[1].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + # ----- Get when the object exists --------------------------------------------------------------------------------- + response = context_client_grpc.GetContext(ContextId(**CONTEXT_ID)) + assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.name == '' + assert len(response.topology_ids) == 1 + assert response.topology_ids[0].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.topology_ids[0].topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID + assert len(response.service_ids) == 0 + assert len(response.slice_ids) == 0 + + response = context_client_grpc.GetTopology(TopologyId(**TOPOLOGY_ID)) + assert response.topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID + assert response.name == '' + assert len(response.device_ids) == 0 + assert len(response.link_ids) == 0 + + # ----- List when the object exists -------------------------------------------------------------------------------- + response = context_client_grpc.ListTopologyIds(ContextId(**CONTEXT_ID)) + assert len(response.topology_ids) == 1 + assert response.topology_ids[0].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.topology_ids[0].topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID + + response = context_client_grpc.ListTopologies(ContextId(**CONTEXT_ID)) + assert len(response.topologies) == 1 + assert response.topologies[0].topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.topologies[0].topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID + assert response.topologies[0].name == '' + assert len(response.topologies[0].device_ids) == 0 + assert len(response.topologies[0].link_ids) == 0 + + # ----- Update the object ------------------------------------------------------------------------------------------ + new_topology_name = 'new' + TOPOLOGY_WITH_NAME = copy.deepcopy(TOPOLOGY) + TOPOLOGY_WITH_NAME['name'] = new_topology_name + response = context_client_grpc.SetTopology(Topology(**TOPOLOGY_WITH_NAME)) + assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.topology_uuid.uuid == DEFAULT_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 event.topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + #assert event.topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID + + # ----- Get when the object is modified ---------------------------------------------------------------------------- + response = context_client_grpc.GetTopology(TopologyId(**TOPOLOGY_ID)) + assert response.topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID + assert response.name == new_topology_name + assert len(response.device_ids) == 0 + assert len(response.link_ids) == 0 + + # ----- List when the object is modified --------------------------------------------------------------------------- + response = context_client_grpc.ListTopologyIds(ContextId(**CONTEXT_ID)) + assert len(response.topology_ids) == 1 + assert response.topology_ids[0].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.topology_ids[0].topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID + + response = context_client_grpc.ListTopologies(ContextId(**CONTEXT_ID)) + assert len(response.topologies) == 1 + assert response.topologies[0].topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.topologies[0].topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID + assert response.topologies[0].name == new_topology_name + assert len(response.topologies[0].device_ids) == 0 + assert len(response.topologies[0].link_ids) == 0 + + # ----- Remove the object ------------------------------------------------------------------------------------------ + context_client_grpc.RemoveTopology(TopologyId(**TOPOLOGY_ID)) + + # ----- Check remove event ----------------------------------------------------------------------------------------- + #event = events_collector.get_event(block=True) + #assert isinstance(event, TopologyEvent) + #assert event.event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + #assert event.topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + #assert event.topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID + + # ----- List after deleting the object ----------------------------------------------------------------------------- + response = context_client_grpc.ListTopologyIds(ContextId(**CONTEXT_ID)) + assert len(response.topology_ids) == 0 + + response = context_client_grpc.ListTopologies(ContextId(**CONTEXT_ID)) + assert len(response.topologies) == 0 + + # ----- Clean dependencies used in the test and capture related events --------------------------------------------- + context_client_grpc.RemoveContext(ContextId(**CONTEXT_ID)) + #event = events_collector.get_event(block=True) + #assert isinstance(event, ContextEvent) + #assert event.event.event_type == EventTypeEnum.EVENTTYPE_REMOVE + #assert event.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + + # ----- Stop the EventsCollector ----------------------------------------------------------------------------------- + #events_collector.stop() diff --git a/src/context/tests/conftest.py b/src/context/tests/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..cf56ed9af2798a42191266e58f373422a3374257 --- /dev/null +++ b/src/context/tests/conftest.py @@ -0,0 +1,153 @@ +# 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. + +import json, os, pytest, sqlalchemy +from _pytest.config import Config +from _pytest.terminal import TerminalReporter +from prettytable import PrettyTable +from typing import Any, Dict, List, Tuple +from common.Constants import ServiceNameEnum +from common.Settings import ( + ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, ENVVAR_SUFIX_SERVICE_PORT_HTTP, get_env_var_name, + get_service_port_grpc, get_service_port_http) +from common.message_broker.Factory import get_messagebroker_backend, BackendEnum as MessageBrokerBackendEnum +from common.message_broker.MessageBroker import MessageBroker +from context.client.ContextClient import ContextClient +from context.service.ContextService import ContextService +from context.service.Database import Database +from context.service.Engine import Engine +from context.service.database.models._Base import rebuild_database +#from context.service._old_code.Populate import populate +#from context.service.rest_server.RestServer import RestServer +#from context.service.rest_server.Resources import RESOURCES + + +LOCAL_HOST = '127.0.0.1' +GRPC_PORT = 10000 + int(get_service_port_grpc(ServiceNameEnum.CONTEXT)) # avoid privileged ports +HTTP_PORT = 10000 + int(get_service_port_http(ServiceNameEnum.CONTEXT)) # avoid privileged ports + +os.environ[get_env_var_name(ServiceNameEnum.CONTEXT, ENVVAR_SUFIX_SERVICE_HOST )] = str(LOCAL_HOST) +os.environ[get_env_var_name(ServiceNameEnum.CONTEXT, ENVVAR_SUFIX_SERVICE_PORT_GRPC)] = str(GRPC_PORT) +os.environ[get_env_var_name(ServiceNameEnum.CONTEXT, ENVVAR_SUFIX_SERVICE_PORT_HTTP)] = str(HTTP_PORT) + +#DEFAULT_REDIS_SERVICE_HOST = LOCAL_HOST +#DEFAULT_REDIS_SERVICE_PORT = 6379 +#DEFAULT_REDIS_DATABASE_ID = 0 + +#REDIS_CONFIG = { +# 'REDIS_SERVICE_HOST': os.environ.get('REDIS_SERVICE_HOST', DEFAULT_REDIS_SERVICE_HOST), +# 'REDIS_SERVICE_PORT': os.environ.get('REDIS_SERVICE_PORT', DEFAULT_REDIS_SERVICE_PORT), +# 'REDIS_DATABASE_ID' : os.environ.get('REDIS_DATABASE_ID', DEFAULT_REDIS_DATABASE_ID ), +#} + +#SCENARIOS = [ +# ('db:cockroach_mb:inmemory', None, {}, None, {}), +# ('all_inmemory', DatabaseBackendEnum.INMEMORY, {}, MessageBrokerBackendEnum.INMEMORY, {} ) +# ('all_redis', DatabaseBackendEnum.REDIS, REDIS_CONFIG, MessageBrokerBackendEnum.REDIS, REDIS_CONFIG), +#] + +#@pytest.fixture(scope='session', ids=[str(scenario[0]) for scenario in SCENARIOS], params=SCENARIOS) +@pytest.fixture(scope='session') +def context_db_mb(request) -> Tuple[sqlalchemy.engine.Engine, MessageBroker]: # pylint: disable=unused-argument + #name,db_session,mb_backend,mb_settings = request.param + #msg = 'Running scenario {:s} db_session={:s}, mb_backend={:s}, mb_settings={:s}...' + #LOGGER.info(msg.format(str(name), str(db_session), str(mb_backend.value), str(mb_settings))) + + _db_engine = Engine.get_engine() + Engine.drop_database(_db_engine) + Engine.create_database(_db_engine) + rebuild_database(_db_engine) + + _msg_broker = MessageBroker(get_messagebroker_backend(backend=MessageBrokerBackendEnum.INMEMORY)) + yield _db_engine, _msg_broker + _msg_broker.terminate() + +RAW_METRICS = None + +@pytest.fixture(scope='session') +def context_service_grpc(context_db_mb : Tuple[Database, MessageBroker]): # pylint: disable=redefined-outer-name + global RAW_METRICS # pylint: disable=global-statement + _service = ContextService(context_db_mb[0], context_db_mb[1]) + RAW_METRICS = _service.context_servicer._get_metrics() + _service.start() + yield _service + _service.stop() + +#@pytest.fixture(scope='session') +#def context_service_rest(context_db_mb : Tuple[Database, MessageBroker]): # pylint: disable=redefined-outer-name +# database = context_db_mb[0] +# _rest_server = RestServer() +# for endpoint_name, resource_class, resource_url in RESOURCES: +# _rest_server.add_resource(resource_class, resource_url, endpoint=endpoint_name, resource_class_args=(database,)) +# _rest_server.start() +# time.sleep(1) # bring time for the server to start +# yield _rest_server +# _rest_server.shutdown() +# _rest_server.join() + +@pytest.fixture(scope='session') +def context_client_grpc( + context_service_grpc : ContextService # pylint: disable=redefined-outer-name,unused-argument +): + _client = ContextClient() + yield _client + _client.close() + +@pytest.hookimpl(hookwrapper=True) +def pytest_terminal_summary( + terminalreporter : TerminalReporter, exitstatus : int, config : Config # pylint: disable=unused-argument +): + yield + + method_to_metric_fields : Dict[str, Dict[str, Dict[str, Any]]]= dict() + for raw_metric_name,raw_metric_data in RAW_METRICS.items(): + if '_COUNTER_' in raw_metric_name: + method_name,metric_name = raw_metric_name.split('_COUNTER_') + elif '_HISTOGRAM_' in raw_metric_name: + method_name,metric_name = raw_metric_name.split('_HISTOGRAM_') + else: + raise Exception('Unsupported metric: {:s}'.format(raw_metric_name)) + metric_data = method_to_metric_fields.setdefault(method_name, dict()).setdefault(metric_name, dict()) + for field_name,labels,value,_,_ in raw_metric_data._child_samples(): + if len(labels) > 0: field_name = '{:s}:{:s}'.format(field_name, json.dumps(labels, sort_keys=True)) + metric_data[field_name] = value + #print('method_to_metric_fields', method_to_metric_fields) + + def sort_stats_key(item : List) -> float: + str_duration = str(item[0]) + if str_duration == '---': return 0.0 + return float(str_duration.replace(' ms', '')) + + field_names = ['Method', 'Started', 'Completed', 'Failed', 'avg(Duration)'] + pt_stats = PrettyTable(field_names=field_names, sortby='avg(Duration)', sort_key=sort_stats_key, reversesort=True) + for f in ['Method']: pt_stats.align[f] = 'l' + for f in ['Started', 'Completed', 'Failed', 'avg(Duration)']: pt_stats.align[f] = 'r' + + for method_name,metrics in method_to_metric_fields.items(): + counter_started_value = int(metrics['STARTED']['_total']) + if counter_started_value == 0: + #pt_stats.add_row([method_name, '---', '---', '---', '---']) + continue + counter_completed_value = int(metrics['COMPLETED']['_total']) + counter_failed_value = int(metrics['FAILED']['_total']) + duration_count_value = float(metrics['DURATION']['_count']) + duration_sum_value = float(metrics['DURATION']['_sum']) + duration_avg_value = duration_sum_value/duration_count_value + pt_stats.add_row([ + method_name, str(counter_started_value), str(counter_completed_value), str(counter_failed_value), + '{:.3f} ms'.format(1000.0 * duration_avg_value), + ]) + print('') + print('Performance Results:') + print(pt_stats.get_string()) diff --git a/src/context/tests/test_hasher.py b/src/context/tests/test_hasher.py new file mode 100644 index 0000000000000000000000000000000000000000..f9a52f5d0222160ea434bc326e49e8a0f80ea622 --- /dev/null +++ b/src/context/tests/test_hasher.py @@ -0,0 +1,47 @@ +# 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. + +import logging, pytest +from context.service.database.tools.FastHasher import ( + FASTHASHER_DATA_ACCEPTED_FORMAT, FASTHASHER_ITEM_ACCEPTED_FORMAT, fast_hasher) + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + + +# ----- Test misc. Context internal tools ------------------------------------------------------------------------------ + +def test_tools_fast_string_hasher(): + with pytest.raises(TypeError) as e: + fast_hasher(27) + assert str(e.value) == "data(27) must be " + FASTHASHER_DATA_ACCEPTED_FORMAT + ", found <class 'int'>" + + with pytest.raises(TypeError) as e: + fast_hasher({27}) + assert str(e.value) == "data({27}) must be " + FASTHASHER_DATA_ACCEPTED_FORMAT + ", found <class 'set'>" + + with pytest.raises(TypeError) as e: + fast_hasher({'27'}) + assert str(e.value) == "data({'27'}) must be " + FASTHASHER_DATA_ACCEPTED_FORMAT + ", found <class 'set'>" + + with pytest.raises(TypeError) as e: + fast_hasher([27]) + assert str(e.value) == "data[0](27) must be " + FASTHASHER_ITEM_ACCEPTED_FORMAT + ", found <class 'int'>" + + fast_hasher('hello-world') + fast_hasher('hello-world'.encode('UTF-8')) + fast_hasher(['hello', 'world']) + fast_hasher(('hello', 'world')) + fast_hasher(['hello'.encode('UTF-8'), 'world'.encode('UTF-8')]) + fast_hasher(('hello'.encode('UTF-8'), 'world'.encode('UTF-8'))) diff --git a/src/context/tests/test_unitary.py b/src/context/tests/test_unitary.py index c85042d2cd71285c98015a3a8872cf59d4cf7683..6845036bd6daa6ac1c595bca388ef0ff53f98f56 100644 --- a/src/context/tests/test_unitary.py +++ b/src/context/tests/test_unitary.py @@ -12,1348 +12,44 @@ # See the License for the specific language governing permissions and # limitations under the License. -# pylint: disable=too-many-lines -import copy, grpc, logging, os, pytest, requests, sqlalchemy, time, urllib, uuid -from typing import Tuple -from common.Constants import DEFAULT_CONTEXT_UUID, DEFAULT_TOPOLOGY_UUID, ServiceNameEnum -from common.Settings import ( - ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, ENVVAR_SUFIX_SERVICE_PORT_HTTP, get_env_var_name, - get_service_baseurl_http, get_service_port_grpc, get_service_port_http) -from context.service.Database import Database -from common.message_broker.Factory import get_messagebroker_backend, BackendEnum as MessageBrokerBackendEnum -from common.message_broker.MessageBroker import MessageBroker -from common.proto.context_pb2 import ( - Connection, ConnectionEvent, ConnectionId, Context, ContextEvent, ContextId, Device, DeviceDriverEnum, DeviceEvent, DeviceId, - DeviceOperationalStatusEnum, Empty, EventTypeEnum, Link, LinkEvent, LinkId, Service, ServiceEvent, ServiceId, - ServiceStatusEnum, ServiceTypeEnum, Topology, TopologyEvent, TopologyId) -from common.proto.policy_pb2 import (PolicyRuleIdList, PolicyRuleId, PolicyRuleList, PolicyRule) -from common.tools.object_factory.Context import json_context, json_context_id -from common.tools.object_factory.Service import json_service_id -from common.tools.object_factory.Slice import json_slice_id -from common.tools.object_factory.Topology import json_topology_id -from common.type_checkers.Assertions import ( - validate_connection, validate_connection_ids, validate_connections, validate_context, validate_context_ids, - validate_contexts, validate_device, validate_device_ids, validate_devices, validate_link, validate_link_ids, - validate_links, validate_service, validate_service_ids, validate_services, validate_topologies, validate_topology, - validate_topology_ids) +import pytest from context.client.ContextClient import ContextClient -from context.client.EventsCollector import EventsCollector -from context.service.database.Tools import ( - FASTHASHER_DATA_ACCEPTED_FORMAT, FASTHASHER_ITEM_ACCEPTED_FORMAT, fast_hasher) -from context.service.ContextService import ContextService -#from context.service._old_code.Populate import populate -#from context.service.rest_server.RestServer import RestServer -#from context.service.rest_server.Resources import RESOURCES -from requests import Session -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from context.service.database._Base import _Base -from common.Settings import get_setting -from context.service.Engine import Engine -from context.service.database._Base import rebuild_database - -from .Objects import ( - CONNECTION_R1_R3, CONNECTION_R1_R3_ID, CONNECTION_R1_R3_UUID, CONTEXT, CONTEXT_ID, DEVICE_R1, DEVICE_R1_ID, - DEVICE_R1_UUID, DEVICE_R2, DEVICE_R2_ID, DEVICE_R2_UUID, DEVICE_R3, DEVICE_R3_ID, DEVICE_R3_UUID, LINK_R1_R2, - LINK_R1_R2_ID, LINK_R1_R2_UUID, SERVICE_R1_R2, SERVICE_R1_R2_ID, SERVICE_R1_R2_UUID, SERVICE_R1_R3, - SERVICE_R1_R3_ID, SERVICE_R1_R3_UUID, SERVICE_R2_R3, SERVICE_R2_R3_ID, SERVICE_R2_R3_UUID, TOPOLOGY, TOPOLOGY_ID, - POLICY_RULE, POLICY_RULE_ID, POLICY_RULE_UUID) - -LOGGER = logging.getLogger(__name__) -LOGGER.setLevel(logging.DEBUG) - -LOCAL_HOST = '127.0.0.1' -GRPC_PORT = 10000 + int(get_service_port_grpc(ServiceNameEnum.CONTEXT)) # avoid privileged ports -HTTP_PORT = 10000 + int(get_service_port_http(ServiceNameEnum.CONTEXT)) # avoid privileged ports - -os.environ[get_env_var_name(ServiceNameEnum.CONTEXT, ENVVAR_SUFIX_SERVICE_HOST )] = str(LOCAL_HOST) -os.environ[get_env_var_name(ServiceNameEnum.CONTEXT, ENVVAR_SUFIX_SERVICE_PORT_GRPC)] = str(GRPC_PORT) -os.environ[get_env_var_name(ServiceNameEnum.CONTEXT, ENVVAR_SUFIX_SERVICE_PORT_HTTP)] = str(HTTP_PORT) - -#DEFAULT_REDIS_SERVICE_HOST = LOCAL_HOST -#DEFAULT_REDIS_SERVICE_PORT = 6379 -#DEFAULT_REDIS_DATABASE_ID = 0 - -#REDIS_CONFIG = { -# 'REDIS_SERVICE_HOST': os.environ.get('REDIS_SERVICE_HOST', DEFAULT_REDIS_SERVICE_HOST), -# 'REDIS_SERVICE_PORT': os.environ.get('REDIS_SERVICE_PORT', DEFAULT_REDIS_SERVICE_PORT), -# 'REDIS_DATABASE_ID' : os.environ.get('REDIS_DATABASE_ID', DEFAULT_REDIS_DATABASE_ID ), -#} - -#SCENARIOS = [ -# ('db:cockroach_mb:inmemory', None, {}, None, {}), -# ('all_inmemory', DatabaseBackendEnum.INMEMORY, {}, MessageBrokerBackendEnum.INMEMORY, {} ) -# ('all_redis', DatabaseBackendEnum.REDIS, REDIS_CONFIG, MessageBrokerBackendEnum.REDIS, REDIS_CONFIG), -#] - -#@pytest.fixture(scope='session', ids=[str(scenario[0]) for scenario in SCENARIOS], params=SCENARIOS) -@pytest.fixture(scope='session') -def context_db_mb(request) -> Tuple[Session, MessageBroker]: - #name,db_session,mb_backend,mb_settings = request.param - #msg = 'Running scenario {:s} db_session={:s}, mb_backend={:s}, mb_settings={:s}...' - #LOGGER.info(msg.format(str(name), str(db_session), str(mb_backend.value), str(mb_settings))) - - _db_engine = Engine.get_engine() - Engine.drop_database(_db_engine) - Engine.create_database(_db_engine) - rebuild_database(_db_engine) - - _msg_broker = MessageBroker(get_messagebroker_backend(backend=MessageBrokerBackendEnum.INMEMORY)) - yield _db_engine, _msg_broker - _msg_broker.terminate() - -@pytest.fixture(scope='session') -def context_service_grpc(context_db_mb : Tuple[Database, MessageBroker]): # pylint: disable=redefined-outer-name - _service = ContextService(context_db_mb[0], context_db_mb[1]) - _service.start() - yield _service - _service.stop() - -#@pytest.fixture(scope='session') -#def context_service_rest(context_db_mb : Tuple[Database, MessageBroker]): # pylint: disable=redefined-outer-name -# database = context_db_mb[0] -# _rest_server = RestServer() -# for endpoint_name, resource_class, resource_url in RESOURCES: -# _rest_server.add_resource(resource_class, resource_url, endpoint=endpoint_name, resource_class_args=(database,)) -# _rest_server.start() -# time.sleep(1) # bring time for the server to start -# yield _rest_server -# _rest_server.shutdown() -# _rest_server.join() - -@pytest.fixture(scope='session') -def context_client_grpc(context_service_grpc : ContextService): # pylint: disable=redefined-outer-name - _client = ContextClient() - yield _client - _client.close() - -#def do_rest_request(url : str): -# base_url = get_service_baseurl_http(ServiceNameEnum.CONTEXT) -# request_url = 'http://{:s}:{:s}{:s}{:s}'.format(str(LOCAL_HOST), str(HTTP_PORT), str(base_url), url) -# LOGGER.warning('Request: GET {:s}'.format(str(request_url))) -# reply = requests.get(request_url) -# LOGGER.warning('Reply: {:s}'.format(str(reply.text))) -# assert reply.status_code == 200, 'Reply failed with code {}'.format(reply.status_code) -# return reply.json() - -# pylint: disable=redefined-outer-name, unused-argument -def test_grpc_initialize(context_client_grpc : ContextClient) -> None: - # dummy method used to initialize fixtures, database, message broker, etc. - pass - -# ----- Test gRPC methods ---------------------------------------------------------------------------------------------- - -def test_grpc_context(context_client_grpc : ContextClient) -> None: # pylint: disable=redefined-outer-name - - # ----- Initialize the EventsCollector ----------------------------------------------------------------------------- - #events_collector = EventsCollector( - # context_client_grpc, log_events_received=True, - # activate_context_collector = True, activate_topology_collector = False, activate_device_collector = False, - # activate_link_collector = False, activate_service_collector = False, activate_slice_collector = False, - # activate_connection_collector = False) - #events_collector.start() - - # ----- Get when the object does not exist ------------------------------------------------------------------------- - with pytest.raises(grpc.RpcError) as e: - context_client_grpc.GetContext(ContextId(**CONTEXT_ID)) - assert e.value.code() == grpc.StatusCode.NOT_FOUND - assert e.value.details() == 'Context({:s}) not found'.format(DEFAULT_CONTEXT_UUID) - - # ----- List when the object does not exist ------------------------------------------------------------------------ - response = context_client_grpc.ListContextIds(Empty()) - assert len(response.context_ids) == 0 - - response = context_client_grpc.ListContexts(Empty()) - assert len(response.contexts) == 0 - - # ----- Create the object ------------------------------------------------------------------------------------------ - response = context_client_grpc.SetContext(Context(**CONTEXT)) - assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - wrong_context_uuid = str(uuid.uuid4()) - wrong_context_id = json_context_id(wrong_context_uuid) - with pytest.raises(grpc.RpcError) as e: - WRONG_CONTEXT = copy.deepcopy(CONTEXT) - WRONG_CONTEXT['topology_ids'].append(json_topology_id(str(uuid.uuid4()), context_id=wrong_context_id)) - context_client_grpc.SetContext(Context(**WRONG_CONTEXT)) - assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT - msg = 'request.topology_ids[0].context_id.context_uuid.uuid({}) is invalid; '\ - 'should be == request.context_id.context_uuid.uuid({})'.format(wrong_context_uuid, DEFAULT_CONTEXT_UUID) - assert e.value.details() == msg - - with pytest.raises(grpc.RpcError) as e: - WRONG_CONTEXT = copy.deepcopy(CONTEXT) - WRONG_CONTEXT['service_ids'].append(json_service_id(str(uuid.uuid4()), context_id=wrong_context_id)) - context_client_grpc.SetContext(Context(**WRONG_CONTEXT)) - assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT - msg = 'request.service_ids[0].context_id.context_uuid.uuid({}) is invalid; '\ - 'should be == request.context_id.context_uuid.uuid({})'.format(wrong_context_uuid, DEFAULT_CONTEXT_UUID) - assert e.value.details() == msg - - with pytest.raises(grpc.RpcError) as e: - WRONG_CONTEXT = copy.deepcopy(CONTEXT) - WRONG_CONTEXT['slice_ids'].append(json_slice_id(str(uuid.uuid4()), context_id=wrong_context_id)) - context_client_grpc.SetContext(Context(**WRONG_CONTEXT)) - assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT - msg = 'request.slice_ids[0].context_id.context_uuid.uuid({}) is invalid; '\ - 'should be == request.context_id.context_uuid.uuid({})'.format(wrong_context_uuid, DEFAULT_CONTEXT_UUID) - assert e.value.details() == msg - - # ----- Check create event ----------------------------------------------------------------------------------------- - #event = events_collector.get_event(block=True, timeout=10.0) - #assert isinstance(event, ContextEvent) - #assert event.event.event_type == EventTypeEnum.EVENTTYPE_CREATE - #assert event.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - # ----- Get when the object exists --------------------------------------------------------------------------------- - response = context_client_grpc.GetContext(ContextId(**CONTEXT_ID)) - assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.name == '' - assert len(response.topology_ids) == 0 - assert len(response.service_ids) == 0 - assert len(response.slice_ids) == 0 - - # ----- List when the object exists -------------------------------------------------------------------------------- - response = context_client_grpc.ListContextIds(Empty()) - assert len(response.context_ids) == 1 - assert response.context_ids[0].context_uuid.uuid == DEFAULT_CONTEXT_UUID - - response = context_client_grpc.ListContexts(Empty()) - assert len(response.contexts) == 1 - assert response.contexts[0].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.contexts[0].name == '' - assert len(response.contexts[0].topology_ids) == 0 - assert len(response.contexts[0].service_ids) == 0 - assert len(response.contexts[0].slice_ids) == 0 - - # ----- Update the object ------------------------------------------------------------------------------------------ - new_context_name = 'new' - CONTEXT_WITH_NAME = copy.deepcopy(CONTEXT) - CONTEXT_WITH_NAME['name'] = new_context_name - response = context_client_grpc.SetContext(Context(**CONTEXT_WITH_NAME)) - assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - # ----- Check update event ----------------------------------------------------------------------------------------- - #event = events_collector.get_event(block=True, timeout=10.0) - #assert isinstance(event, ContextEvent) - #assert event.event.event_type == EventTypeEnum.EVENTTYPE_UPDATE - #assert event.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - # ----- Get when the object is modified ---------------------------------------------------------------------------- - response = context_client_grpc.GetContext(ContextId(**CONTEXT_ID)) - assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.name == new_context_name - assert len(response.topology_ids) == 0 - assert len(response.service_ids) == 0 - assert len(response.slice_ids) == 0 - - # ----- List when the object is modified --------------------------------------------------------------------------- - response = context_client_grpc.ListContextIds(Empty()) - assert len(response.context_ids) == 1 - assert response.context_ids[0].context_uuid.uuid == DEFAULT_CONTEXT_UUID - - response = context_client_grpc.ListContexts(Empty()) - assert len(response.contexts) == 1 - assert response.contexts[0].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.contexts[0].name == new_context_name - assert len(response.contexts[0].topology_ids) == 0 - assert len(response.contexts[0].service_ids) == 0 - assert len(response.contexts[0].slice_ids) == 0 - - # ----- Remove the object ------------------------------------------------------------------------------------------ - context_client_grpc.RemoveContext(ContextId(**CONTEXT_ID)) - - # ----- Check remove event ----------------------------------------------------------------------------------------- - #event = events_collector.get_event(block=True, timeout=10.0) - #assert isinstance(event, ContextEvent) - #assert event.event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - #assert event.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - # ----- List after deleting the object ----------------------------------------------------------------------------- - response = context_client_grpc.ListContextIds(Empty()) - assert len(response.context_ids) == 0 - - response = context_client_grpc.ListContexts(Empty()) - assert len(response.contexts) == 0 - - # ----- Stop the EventsCollector ----------------------------------------------------------------------------------- - #events_collector.stop() - - -def test_grpc_topology(context_client_grpc : ContextClient) -> None: # pylint: disable=redefined-outer-name - - # ----- Initialize the EventsCollector ----------------------------------------------------------------------------- - #events_collector = EventsCollector( - # context_client_grpc, log_events_received=True, - # activate_context_collector = False, activate_topology_collector = True, activate_device_collector = False, - # activate_link_collector = False, activate_service_collector = False, activate_slice_collector = False, - # activate_connection_collector = False) - #events_collector.start() - - # ----- Prepare dependencies for the test and capture related events ----------------------------------------------- - response = context_client_grpc.SetContext(Context(**CONTEXT)) - assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID - # event = events_collector.get_event(block=True) - # assert isinstance(event, ContextEvent) - # assert event.event.event_type == EventTypeEnum.EVENTTYPE_CREATE - # assert event.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - # ----- Get when the object does not exist ------------------------------------------------------------------------- - with pytest.raises(grpc.RpcError) as e: - context_client_grpc.GetTopology(TopologyId(**TOPOLOGY_ID)) - assert e.value.code() == grpc.StatusCode.NOT_FOUND - assert e.value.details() == 'Topology({:s}/{:s}) not found'.format(DEFAULT_CONTEXT_UUID, DEFAULT_TOPOLOGY_UUID) - - # ----- List when the object does not exist ------------------------------------------------------------------------ - response = context_client_grpc.ListTopologyIds(ContextId(**CONTEXT_ID)) - assert len(response.topology_ids) == 0 - - response = context_client_grpc.ListTopologies(ContextId(**CONTEXT_ID)) - assert len(response.topologies) == 0 - - # ----- Create the object ------------------------------------------------------------------------------------------ - response = context_client_grpc.SetTopology(Topology(**TOPOLOGY)) - assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID - - #CONTEXT_WITH_TOPOLOGY = copy.deepcopy(CONTEXT) - #CONTEXT_WITH_TOPOLOGY['topology_ids'].append(TOPOLOGY_ID) - #response = context_client_grpc.SetContext(Context(**CONTEXT_WITH_TOPOLOGY)) - #assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - # ----- Check create event ----------------------------------------------------------------------------------------- - #events = events_collector.get_events(block=True, count=2) - #assert isinstance(events[0], TopologyEvent) - #assert events[0].event.event_type == EventTypeEnum.EVENTTYPE_CREATE - #assert events[0].topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - #assert events[0].topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID - #assert isinstance(events[1], ContextEvent) - #assert events[1].event.event_type == EventTypeEnum.EVENTTYPE_UPDATE - #assert events[1].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - # ----- Get when the object exists --------------------------------------------------------------------------------- - response = context_client_grpc.GetContext(ContextId(**CONTEXT_ID)) - assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.name == '' - assert len(response.topology_ids) == 1 - assert response.topology_ids[0].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.topology_ids[0].topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID - assert len(response.service_ids) == 0 - assert len(response.slice_ids) == 0 - - response = context_client_grpc.GetTopology(TopologyId(**TOPOLOGY_ID)) - assert response.topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID - assert response.name == '' - assert len(response.device_ids) == 0 - assert len(response.link_ids) == 0 - - # ----- List when the object exists -------------------------------------------------------------------------------- - response = context_client_grpc.ListTopologyIds(ContextId(**CONTEXT_ID)) - assert len(response.topology_ids) == 1 - assert response.topology_ids[0].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.topology_ids[0].topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID - - response = context_client_grpc.ListTopologies(ContextId(**CONTEXT_ID)) - assert len(response.topologies) == 1 - assert response.topologies[0].topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.topologies[0].topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID - assert response.topologies[0].name == '' - assert len(response.topologies[0].device_ids) == 0 - assert len(response.topologies[0].link_ids) == 0 - - # ----- Update the object ------------------------------------------------------------------------------------------ - new_topology_name = 'new' - TOPOLOGY_WITH_NAME = copy.deepcopy(TOPOLOGY) - TOPOLOGY_WITH_NAME['name'] = new_topology_name - response = context_client_grpc.SetTopology(Topology(**TOPOLOGY_WITH_NAME)) - assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.topology_uuid.uuid == DEFAULT_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 event.topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - #assert event.topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID - - # ----- Get when the object is modified ---------------------------------------------------------------------------- - response = context_client_grpc.GetTopology(TopologyId(**TOPOLOGY_ID)) - assert response.topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID - assert response.name == new_topology_name - assert len(response.device_ids) == 0 - assert len(response.link_ids) == 0 - - # ----- List when the object is modified --------------------------------------------------------------------------- - response = context_client_grpc.ListTopologyIds(ContextId(**CONTEXT_ID)) - assert len(response.topology_ids) == 1 - assert response.topology_ids[0].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.topology_ids[0].topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID - - response = context_client_grpc.ListTopologies(ContextId(**CONTEXT_ID)) - assert len(response.topologies) == 1 - assert response.topologies[0].topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.topologies[0].topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID - assert response.topologies[0].name == new_topology_name - assert len(response.topologies[0].device_ids) == 0 - assert len(response.topologies[0].link_ids) == 0 - - # ----- Remove the object ------------------------------------------------------------------------------------------ - context_client_grpc.RemoveTopology(TopologyId(**TOPOLOGY_ID)) - - # ----- Check remove event ----------------------------------------------------------------------------------------- - #event = events_collector.get_event(block=True) - #assert isinstance(event, TopologyEvent) - #assert event.event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - #assert event.topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - #assert event.topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID - - # ----- List after deleting the object ----------------------------------------------------------------------------- - response = context_client_grpc.ListTopologyIds(ContextId(**CONTEXT_ID)) - assert len(response.topology_ids) == 0 - - response = context_client_grpc.ListTopologies(ContextId(**CONTEXT_ID)) - assert len(response.topologies) == 0 - - # ----- Clean dependencies used in the test and capture related events --------------------------------------------- - context_client_grpc.RemoveContext(ContextId(**CONTEXT_ID)) - #event = events_collector.get_event(block=True) - #assert isinstance(event, ContextEvent) - #assert event.event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - #assert event.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - # ----- Stop the EventsCollector ----------------------------------------------------------------------------------- - #events_collector.stop() - -def test_grpc_device(context_client_grpc : ContextClient) -> None: # pylint: disable=redefined-outer-name - - # ----- Initialize the EventsCollector ----------------------------------------------------------------------------- - #events_collector = EventsCollector( - # context_client_grpc, log_events_received=True, - # activate_context_collector = False, activate_topology_collector = False, activate_device_collector = True, - # activate_link_collector = False, activate_service_collector = False, activate_slice_collector = False, - # activate_connection_collector = False) - #events_collector.start() - - # ----- Prepare dependencies for the test and capture related events ----------------------------------------------- - response = context_client_grpc.SetContext(Context(**CONTEXT)) - assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - response = context_client_grpc.SetTopology(Topology(**TOPOLOGY)) - assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID - - #events = events_collector.get_events(block=True, count=2) - #assert isinstance(events[0], ContextEvent) - #assert events[0].event.event_type == EventTypeEnum.EVENTTYPE_CREATE - #assert events[0].context_id.context_uuid.uuid == DEFAULT_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 == DEFAULT_CONTEXT_UUID - #assert events[1].topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID - - # ----- Get when the object does not exist ------------------------------------------------------------------------- - with pytest.raises(grpc.RpcError) as e: - context_client_grpc.GetDevice(DeviceId(**DEVICE_R1_ID)) - assert e.value.code() == grpc.StatusCode.NOT_FOUND - assert e.value.details() == 'Device({:s}) not found'.format(DEVICE_R1_UUID) - - # ----- List when the object does not exist ------------------------------------------------------------------------ - response = context_client_grpc.ListDeviceIds(Empty()) - assert len(response.device_ids) == 0 - - response = context_client_grpc.ListDevices(Empty()) - assert len(response.devices) == 0 - - # ----- Create the object ------------------------------------------------------------------------------------------ - with pytest.raises(grpc.RpcError) as e: - WRONG_DEVICE = copy.deepcopy(DEVICE_R1) - WRONG_DEVICE_UUID = '3f03c76d-31fb-47f5-9c1d-bc6b6bfa2d08' - WRONG_DEVICE['device_endpoints'][0]['endpoint_id']['device_id']['device_uuid']['uuid'] = WRONG_DEVICE_UUID - context_client_grpc.SetDevice(Device(**WRONG_DEVICE)) - assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT - msg = 'request.device_endpoints[0].device_id.device_uuid.uuid({}) is invalid; '\ - 'should be == request.device_id.device_uuid.uuid({})'.format(WRONG_DEVICE_UUID, DEVICE_R1_UUID) - assert e.value.details() == msg - - response = context_client_grpc.SetDevice(Device(**DEVICE_R1)) - assert response.device_uuid.uuid == DEVICE_R1_UUID - - # ----- Check create event ----------------------------------------------------------------------------------------- - # event = events_collector.get_event(block=True) - # assert isinstance(event, DeviceEvent) - # assert event.event.event_type == EventTypeEnum.EVENTTYPE_CREATE - # assert event.device_id.device_uuid.uuid == DEVICE_R1_UUID - - # ----- Get when the object exists --------------------------------------------------------------------------------- - response = context_client_grpc.GetDevice(DeviceId(**DEVICE_R1_ID)) - assert response.device_id.device_uuid.uuid == DEVICE_R1_UUID - assert response.name == '' - assert response.device_type == 'packet-router' - #assert len(response.device_config.config_rules) == 3 - assert response.device_operational_status == DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_DISABLED - assert len(response.device_drivers) == 1 - assert DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG in response.device_drivers - #assert len(response.device_endpoints) == 3 - - # ----- List when the object exists -------------------------------------------------------------------------------- - response = context_client_grpc.ListDeviceIds(Empty()) - assert len(response.device_ids) == 1 - assert response.device_ids[0].device_uuid.uuid == DEVICE_R1_UUID - - response = context_client_grpc.ListDevices(Empty()) - assert len(response.devices) == 1 - assert response.devices[0].device_id.device_uuid.uuid == DEVICE_R1_UUID - assert response.devices[0].name == '' - assert response.devices[0].device_type == 'packet-router' - #assert len(response.devices[0].device_config.config_rules) == 3 - assert response.devices[0].device_operational_status == DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_DISABLED - assert len(response.devices[0].device_drivers) == 1 - assert DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG in response.devices[0].device_drivers - #assert len(response.devices[0].device_endpoints) == 3 - - # ----- Update the object ------------------------------------------------------------------------------------------ - new_device_name = 'r1' - new_device_driver = DeviceDriverEnum.DEVICEDRIVER_UNDEFINED - DEVICE_UPDATED = copy.deepcopy(DEVICE_R1) - DEVICE_UPDATED['name'] = new_device_name - DEVICE_UPDATED['device_operational_status'] = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED - DEVICE_UPDATED['device_drivers'].append(new_device_driver) - response = context_client_grpc.SetDevice(Device(**DEVICE_UPDATED)) - assert response.device_uuid.uuid == DEVICE_R1_UUID - - # ----- Check update event ----------------------------------------------------------------------------------------- - # event = events_collector.get_event(block=True) - # assert isinstance(event, DeviceEvent) - # assert event.event.event_type == EventTypeEnum.EVENTTYPE_UPDATE - # assert event.device_id.device_uuid.uuid == DEVICE_R1_UUID - - # ----- Get when the object is modified ---------------------------------------------------------------------------- - response = context_client_grpc.GetDevice(DeviceId(**DEVICE_R1_ID)) - assert response.device_id.device_uuid.uuid == DEVICE_R1_UUID - assert response.name == 'r1' - assert response.device_type == 'packet-router' - #assert len(response.device_config.config_rules) == 3 - assert response.device_operational_status == DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED - assert len(response.device_drivers) == 2 - assert DeviceDriverEnum.DEVICEDRIVER_UNDEFINED in response.device_drivers - assert DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG in response.device_drivers - #assert len(response.device_endpoints) == 3 - - # ----- List when the object is modified --------------------------------------------------------------------------- - response = context_client_grpc.ListDeviceIds(Empty()) - assert len(response.device_ids) == 1 - assert response.device_ids[0].device_uuid.uuid == DEVICE_R1_UUID - - response = context_client_grpc.ListDevices(Empty()) - assert len(response.devices) == 1 - assert response.devices[0].device_id.device_uuid.uuid == DEVICE_R1_UUID - assert response.devices[0].name == 'r1' - assert response.devices[0].device_type == 'packet-router' - #assert len(response.devices[0].device_config.config_rules) == 3 - assert response.devices[0].device_operational_status == DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED - assert len(response.devices[0].device_drivers) == 2 - assert DeviceDriverEnum.DEVICEDRIVER_UNDEFINED in response.devices[0].device_drivers - assert DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG in response.devices[0].device_drivers - #assert len(response.devices[0].device_endpoints) == 3 - - # ----- Create object relation ------------------------------------------------------------------------------------- - TOPOLOGY_WITH_DEVICE = copy.deepcopy(TOPOLOGY) - TOPOLOGY_WITH_DEVICE['device_ids'].append(DEVICE_R1_ID) - response = context_client_grpc.SetTopology(Topology(**TOPOLOGY_WITH_DEVICE)) - assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.topology_uuid.uuid == DEFAULT_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 == DEFAULT_CONTEXT_UUID - # assert response.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID - - # ----- Check relation was created --------------------------------------------------------------------------------- - response = context_client_grpc.GetTopology(TopologyId(**TOPOLOGY_ID)) - assert response.topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID - assert len(response.device_ids) == 1 - assert response.device_ids[0].device_uuid.uuid == DEVICE_R1_UUID - assert len(response.link_ids) == 0 - - # ----- Remove the object ------------------------------------------------------------------------------------------ - context_client_grpc.RemoveDevice(DeviceId(**DEVICE_R1_ID)) - context_client_grpc.RemoveTopology(TopologyId(**TOPOLOGY_ID)) - context_client_grpc.RemoveContext(ContextId(**CONTEXT_ID)) - - # ----- Check remove event ----------------------------------------------------------------------------------------- - # events = events_collector.get_events(block=True, count=3) - - # 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], TopologyEvent) - # assert events[1].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - # assert events[1].topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - # assert events[1].topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID - - # assert isinstance(events[2], ContextEvent) - # assert events[2].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - # assert events[2].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - # ----- Stop the EventsCollector ----------------------------------------------------------------------------------- - # events_collector.stop() - - -""" -def test_grpc_link( - context_client_grpc: ContextClient, # pylint: disable=redefined-outer-name - context_db_mb: Tuple[Session, MessageBroker]): # pylint: disable=redefined-outer-name - session = context_db_mb[0] - - database = Database(session) - - # ----- Clean the database ----------------------------------------------------------------------------------------- - database.clear() - - # ----- Initialize the EventsCollector ----------------------------------------------------------------------------- - events_collector = EventsCollector(context_client_grpc) - events_collector.start() - - # ----- Prepare dependencies for the test and capture related events ----------------------------------------------- - response = context_client_grpc.SetContext(Context(**CONTEXT)) - assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - response = context_client_grpc.SetTopology(Topology(**TOPOLOGY)) - assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID - - response = context_client_grpc.SetDevice(Device(**DEVICE_R1)) - assert response.device_uuid.uuid == DEVICE_R1_UUID - - response = context_client_grpc.SetDevice(Device(**DEVICE_R2)) - assert response.device_uuid.uuid == DEVICE_R2_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 == DEFAULT_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 == DEFAULT_CONTEXT_UUID - # assert events[1].topology_id.topology_uuid.uuid == DEFAULT_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 ------------------------------------------------------------------------- - with pytest.raises(grpc.RpcError) as e: - context_client_grpc.GetLink(LinkId(**LINK_R1_R2_ID)) - assert e.value.code() == grpc.StatusCode.NOT_FOUND - assert e.value.details() == 'Link({:s}) not found'.format(LINK_R1_R2_UUID) - - # ----- List when the object does not exist ------------------------------------------------------------------------ - response = context_client_grpc.ListLinkIds(Empty()) - assert len(response.link_ids) == 0 - - response = context_client_grpc.ListLinks(Empty()) - assert len(response.links) == 0 - - # ----- Dump state of database before create the object ------------------------------------------------------------ - db_entries = database.dump_all() - LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) - for db_entry in db_entries: - LOGGER.info(db_entry) - LOGGER.info('-----------------------------------------------------------') - assert len(db_entries) == 80 - - # ----- Create the object ------------------------------------------------------------------------------------------ - response = context_client_grpc.SetLink(Link(**LINK_R1_R2)) - assert response.link_uuid.uuid == LINK_R1_R2_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_R1_R2_UUID - - # ----- Update the object ------------------------------------------------------------------------------------------ - response = context_client_grpc.SetLink(Link(**LINK_R1_R2)) - assert response.link_uuid.uuid == LINK_R1_R2_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_R1_R2_UUID - - # ----- Dump state of database after create/update the object ------------------------------------------------------ - db_entries = database.dump_all() - LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) - for db_entry in db_entries: - LOGGER.info(db_entry) - LOGGER.info('-----------------------------------------------------------') - assert len(db_entries) == 88 - - # ----- Get when the object exists --------------------------------------------------------------------------------- - response = context_client_grpc.GetLink(LinkId(**LINK_R1_R2_ID)) - assert response.link_id.link_uuid.uuid == LINK_R1_R2_UUID - assert len(response.link_endpoint_ids) == 2 - - # ----- List when the object exists -------------------------------------------------------------------------------- - response = context_client_grpc.ListLinkIds(Empty()) - assert len(response.link_ids) == 1 - assert response.link_ids[0].link_uuid.uuid == LINK_R1_R2_UUID - - response = context_client_grpc.ListLinks(Empty()) - assert len(response.links) == 1 - assert response.links[0].link_id.link_uuid.uuid == LINK_R1_R2_UUID - - 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_grpc.SetTopology(Topology(**TOPOLOGY_WITH_LINK)) - assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.topology_uuid.uuid == DEFAULT_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 == DEFAULT_CONTEXT_UUID - # assert response.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID - - # ----- Check relation was created --------------------------------------------------------------------------------- - response = context_client_grpc.GetTopology(TopologyId(**TOPOLOGY_ID)) - assert response.topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID - assert len(response.device_ids) == 2 - # assert response.device_ids[0].device_uuid.uuid == DEVICE_R1_UUID - # assert response.device_ids[1].device_uuid.uuid == DEVICE_R2_UUID - assert len(response.link_ids) == 1 - assert response.link_ids[0].link_uuid.uuid == LINK_R1_R2_UUID - - db_entries = database.dump_all() - LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) - for db_entry in db_entries: - LOGGER.info(db_entry) - LOGGER.info('-----------------------------------------------------------') - assert len(db_entries) == 88 - - # ----- Remove the object ------------------------------------------------------------------------------------------ - context_client_grpc.RemoveLink(LinkId(**LINK_R1_R2_ID)) - context_client_grpc.RemoveDevice(DeviceId(**DEVICE_R1_ID)) - context_client_grpc.RemoveDevice(DeviceId(**DEVICE_R2_ID)) - context_client_grpc.RemoveTopology(TopologyId(**TOPOLOGY_ID)) - context_client_grpc.RemoveContext(ContextId(**CONTEXT_ID)) - - # ----- Check remove event ----------------------------------------------------------------------------------------- - # events = events_collector.get_events(block=True, count=5) - # - # assert isinstance(events[0], LinkEvent) - # assert events[0].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - # assert events[0].link_id.link_uuid.uuid == LINK_R1_R2_UUID - # - # assert isinstance(events[1], DeviceEvent) - # assert events[1].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - # assert events[1].device_id.device_uuid.uuid == DEVICE_R1_UUID - # - # assert isinstance(events[2], DeviceEvent) - # assert events[2].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - # assert events[2].device_id.device_uuid.uuid == DEVICE_R2_UUID - # - # assert isinstance(events[3], TopologyEvent) - # assert events[3].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - # assert events[3].topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - # assert events[3].topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID - # - # assert isinstance(events[4], ContextEvent) - # assert events[4].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - # assert events[4].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - # ----- Stop the EventsCollector ----------------------------------------------------------------------------------- - events_collector.stop() - - # ----- Dump state of database after remove the object ------------------------------------------------------------- - db_entries = database.dump_all() - LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) - for db_entry in db_entries: - LOGGER.info(db_entry) - LOGGER.info('-----------------------------------------------------------') - assert len(db_entries) == 0 -""" - -""" -def test_grpc_service( - context_client_grpc : ContextClient, # pylint: disable=redefined-outer-name - context_db_mb : Tuple[Database, MessageBroker]): # pylint: disable=redefined-outer-name - Session = context_db_mb[0] - # ----- Clean the database ----------------------------------------------------------------------------------------- - database = Database(Session) - database.clear() - - # ----- Initialize the EventsCollector ----------------------------------------------------------------------------- - events_collector = EventsCollector(context_client_grpc) - events_collector.start() - - # ----- Prepare dependencies for the test and capture related events ----------------------------------------------- - response = context_client_grpc.SetContext(Context(**CONTEXT)) - assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - response = context_client_grpc.SetTopology(Topology(**TOPOLOGY)) - assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID - - response = context_client_grpc.SetDevice(Device(**DEVICE_R1)) - assert response.device_uuid.uuid == DEVICE_R1_UUID - - response = context_client_grpc.SetDevice(Device(**DEVICE_R2)) - assert response.device_uuid.uuid == DEVICE_R2_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 == DEFAULT_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 == DEFAULT_CONTEXT_UUID - # assert events[1].topology_id.topology_uuid.uuid == DEFAULT_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 - LOGGER.info('----------------') - - # ----- Get when the object does not exist ------------------------------------------------------------------------- - with pytest.raises(grpc.RpcError) as e: - context_client_grpc.GetService(ServiceId(**SERVICE_R1_R2_ID)) - assert e.value.code() == grpc.StatusCode.NOT_FOUND - assert e.value.details() == 'Service({:s}) not found'.format(SERVICE_R1_R2_UUID) - LOGGER.info('----------------') - - # ----- List when the object does not exist ------------------------------------------------------------------------ - response = context_client_grpc.ListServiceIds(ContextId(**CONTEXT_ID)) - assert len(response.service_ids) == 0 - LOGGER.info('----------------') - - response = context_client_grpc.ListServices(ContextId(**CONTEXT_ID)) - assert len(response.services) == 0 - LOGGER.info('----------------') - - # ----- Dump state of database before create the object ------------------------------------------------------------ - db_entries = database.dump_all() - LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) - for db_entry in db_entries: - LOGGER.info(db_entry) - LOGGER.info('-----------------------------------------------------------') - assert len(db_entries) == 80 - - # ----- Create the object ------------------------------------------------------------------------------------------ - with pytest.raises(grpc.RpcError) as e: - WRONG_SERVICE = copy.deepcopy(SERVICE_R1_R2) - WRONG_SERVICE['service_endpoint_ids'][0]\ - ['topology_id']['context_id']['context_uuid']['uuid'] = 'ca1ea172-728f-441d-972c-feeae8c9bffc' - context_client_grpc.SetService(Service(**WRONG_SERVICE)) - assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT - msg = 'request.service_endpoint_ids[0].topology_id.context_id.context_uuid.uuid(ca1ea172-728f-441d-972c-feeae8c9bffc) is invalid; '\ - 'should be == request.service_id.context_id.context_uuid.uuid({:s})'.format(DEFAULT_CONTEXT_UUID) - assert e.value.details() == msg - - response = context_client_grpc.SetService(Service(**SERVICE_R1_R2)) - assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.service_uuid.uuid == SERVICE_R1_R2_UUID - - CONTEXT_WITH_SERVICE = copy.deepcopy(CONTEXT) - CONTEXT_WITH_SERVICE['service_ids'].append(SERVICE_R1_R2_ID) - response = context_client_grpc.SetContext(Context(**CONTEXT_WITH_SERVICE)) - assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - # ----- Check create event ----------------------------------------------------------------------------------------- - events = events_collector.get_events(block=True, count=2) - - assert isinstance(events[0], ServiceEvent) - assert events[0].event.event_type == EventTypeEnum.EVENTTYPE_CREATE - assert events[0].service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert events[0].service_id.service_uuid.uuid == SERVICE_R1_R2_UUID - - assert isinstance(events[1], ContextEvent) - assert events[1].event.event_type == EventTypeEnum.EVENTTYPE_UPDATE - assert events[1].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - # ----- Update the object ------------------------------------------------------------------------------------------ - response = context_client_grpc.SetService(Service(**SERVICE_R1_R2)) - assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.service_uuid.uuid == SERVICE_R1_R2_UUID - - # ----- Check update event ----------------------------------------------------------------------------------------- - event = events_collector.get_event(block=True) - assert isinstance(event, ServiceEvent) - assert event.event.event_type == EventTypeEnum.EVENTTYPE_UPDATE - assert event.service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert event.service_id.service_uuid.uuid == SERVICE_R1_R2_UUID - - # ----- Dump state of database after create/update the object ------------------------------------------------------ - db_entries = context_database.dump() - LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) - for db_entry in db_entries: - LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover - LOGGER.info('-----------------------------------------------------------') - assert len(db_entries) == 108 - - # ----- Get when the object exists --------------------------------------------------------------------------------- - response = context_client_grpc.GetService(ServiceId(**SERVICE_R1_R2_ID)) - assert response.service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.service_id.service_uuid.uuid == SERVICE_R1_R2_UUID - assert response.service_type == ServiceTypeEnum.SERVICETYPE_L3NM - assert len(response.service_endpoint_ids) == 2 - assert len(response.service_constraints) == 2 - assert response.service_status.service_status == ServiceStatusEnum.SERVICESTATUS_PLANNED - assert len(response.service_config.config_rules) == 3 - - # ----- List when the object exists -------------------------------------------------------------------------------- - response = context_client_grpc.ListServiceIds(ContextId(**CONTEXT_ID)) - assert len(response.service_ids) == 1 - assert response.service_ids[0].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.service_ids[0].service_uuid.uuid == SERVICE_R1_R2_UUID - - response = context_client_grpc.ListServices(ContextId(**CONTEXT_ID)) - assert len(response.services) == 1 - assert response.services[0].service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.services[0].service_id.service_uuid.uuid == SERVICE_R1_R2_UUID - assert response.services[0].service_type == ServiceTypeEnum.SERVICETYPE_L3NM - assert len(response.services[0].service_endpoint_ids) == 2 - assert len(response.services[0].service_constraints) == 2 - assert response.services[0].service_status.service_status == ServiceStatusEnum.SERVICESTATUS_PLANNED - assert len(response.services[0].service_config.config_rules) == 3 - - # ----- Remove the object ------------------------------------------------------------------------------------------ - context_client_grpc.RemoveService(ServiceId(**SERVICE_R1_R2_ID)) - context_client_grpc.RemoveDevice(DeviceId(**DEVICE_R1_ID)) - context_client_grpc.RemoveDevice(DeviceId(**DEVICE_R2_ID)) - context_client_grpc.RemoveTopology(TopologyId(**TOPOLOGY_ID)) - context_client_grpc.RemoveContext(ContextId(**CONTEXT_ID)) - - # ----- Check remove event ----------------------------------------------------------------------------------------- - events = events_collector.get_events(block=True, count=5) - - assert isinstance(events[0], ServiceEvent) - assert events[0].service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert events[0].service_id.service_uuid.uuid == SERVICE_R1_R2_UUID - - assert isinstance(events[1], DeviceEvent) - assert events[1].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - assert events[1].device_id.device_uuid.uuid == DEVICE_R1_UUID - - assert isinstance(events[2], DeviceEvent) - assert events[2].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - assert events[2].device_id.device_uuid.uuid == DEVICE_R2_UUID - - assert isinstance(events[3], TopologyEvent) - assert events[3].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - assert events[3].topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert events[3].topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID - - assert isinstance(events[4], ContextEvent) - assert events[4].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - assert events[4].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - # ----- Stop the EventsCollector ----------------------------------------------------------------------------------- - events_collector.stop() - - # ----- Dump state of database after remove the object ------------------------------------------------------------- - db_entries = context_database.dump() - LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) - for db_entry in db_entries: - LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover - LOGGER.info('-----------------------------------------------------------') - assert len(db_entries) == 0 -""" - -""" -def test_grpc_connection( - context_client_grpc : ContextClient, # pylint: disable=redefined-outer-name - context_db_mb : Tuple[Database, MessageBroker]): # pylint: disable=redefined-outer-name - Session = context_db_mb[0] - - database = Database(Session) - - # ----- Clean the database ----------------------------------------------------------------------------------------- - database.clear() - - # ----- Initialize the EventsCollector ----------------------------------------------------------------------------- - events_collector = EventsCollector(context_client_grpc) - events_collector.start() - - # ----- Prepare dependencies for the test and capture related events ----------------------------------------------- - response = context_client_grpc.SetContext(Context(**CONTEXT)) - assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - response = context_client_grpc.SetTopology(Topology(**TOPOLOGY)) - assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID - - response = context_client_grpc.SetDevice(Device(**DEVICE_R1)) - assert response.device_uuid.uuid == DEVICE_R1_UUID - - response = context_client_grpc.SetDevice(Device(**DEVICE_R2)) - assert response.device_uuid.uuid == DEVICE_R2_UUID - - response = context_client_grpc.SetDevice(Device(**DEVICE_R3)) - assert response.device_uuid.uuid == DEVICE_R3_UUID - - response = context_client_grpc.SetService(Service(**SERVICE_R1_R2)) - assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.service_uuid.uuid == SERVICE_R1_R2_UUID - - CONTEXT_WITH_SERVICE = copy.deepcopy(CONTEXT) - CONTEXT_WITH_SERVICE['service_ids'].append(SERVICE_R1_R2_ID) - response = context_client_grpc.SetContext(Context(**CONTEXT_WITH_SERVICE)) - assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - response = context_client_grpc.SetService(Service(**SERVICE_R2_R3)) - assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.service_uuid.uuid == SERVICE_R2_R3_UUID - - CONTEXT_WITH_SERVICE = copy.deepcopy(CONTEXT) - CONTEXT_WITH_SERVICE['service_ids'].append(SERVICE_R2_R3_ID) - response = context_client_grpc.SetContext(Context(**CONTEXT_WITH_SERVICE)) - assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - response = context_client_grpc.SetService(Service(**SERVICE_R1_R3)) - assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.service_uuid.uuid == SERVICE_R1_R3_UUID - - CONTEXT_WITH_SERVICE = copy.deepcopy(CONTEXT) - CONTEXT_WITH_SERVICE['service_ids'].append(SERVICE_R1_R3_ID) - response = context_client_grpc.SetContext(Context(**CONTEXT_WITH_SERVICE)) - assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - events = events_collector.get_events(block=True, count=11) - - assert isinstance(events[0], ContextEvent) - assert events[0].event.event_type == EventTypeEnum.EVENTTYPE_CREATE - assert events[0].context_id.context_uuid.uuid == DEFAULT_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 == DEFAULT_CONTEXT_UUID - assert events[1].topology_id.topology_uuid.uuid == DEFAULT_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 - - assert isinstance(events[4], DeviceEvent) - assert events[4].event.event_type == EventTypeEnum.EVENTTYPE_CREATE - assert events[4].device_id.device_uuid.uuid == DEVICE_R3_UUID - - assert isinstance(events[5], ServiceEvent) - assert events[5].event.event_type == EventTypeEnum.EVENTTYPE_CREATE - assert events[5].service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert events[5].service_id.service_uuid.uuid == SERVICE_R1_R2_UUID - - assert isinstance(events[6], ContextEvent) - assert events[6].event.event_type == EventTypeEnum.EVENTTYPE_UPDATE - assert events[6].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - assert isinstance(events[7], ServiceEvent) - assert events[7].event.event_type == EventTypeEnum.EVENTTYPE_CREATE - assert events[7].service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert events[7].service_id.service_uuid.uuid == SERVICE_R2_R3_UUID - - assert isinstance(events[8], ContextEvent) - assert events[8].event.event_type == EventTypeEnum.EVENTTYPE_UPDATE - assert events[8].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - assert isinstance(events[9], ServiceEvent) - assert events[9].event.event_type == EventTypeEnum.EVENTTYPE_CREATE - assert events[9].service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert events[9].service_id.service_uuid.uuid == SERVICE_R1_R3_UUID - - assert isinstance(events[10], ContextEvent) - assert events[10].event.event_type == EventTypeEnum.EVENTTYPE_UPDATE - assert events[10].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - # ----- Get when the object does not exist ------------------------------------------------------------------------- - with pytest.raises(grpc.RpcError) as e: - context_client_grpc.GetConnection(ConnectionId(**CONNECTION_R1_R3_ID)) - assert e.value.code() == grpc.StatusCode.NOT_FOUND - assert e.value.details() == 'Connection({:s}) not found'.format(CONNECTION_R1_R3_UUID) - - # ----- List when the object does not exist ------------------------------------------------------------------------ - response = context_client_grpc.ListConnectionIds(ServiceId(**SERVICE_R1_R3_ID)) - assert len(response.connection_ids) == 0 - - response = context_client_grpc.ListConnections(ServiceId(**SERVICE_R1_R3_ID)) - assert len(response.connections) == 0 - - # ----- Dump state of database before create the object ------------------------------------------------------------ - db_entries = context_database.dump() - LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) - for db_entry in db_entries: - LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover - LOGGER.info('-----------------------------------------------------------') - assert len(db_entries) == 187 - - # ----- Create the object ------------------------------------------------------------------------------------------ - with pytest.raises(grpc.RpcError) as e: - WRONG_CONNECTION = copy.deepcopy(CONNECTION_R1_R3) - WRONG_CONNECTION['path_hops_endpoint_ids'][0]\ - ['topology_id']['context_id']['context_uuid']['uuid'] = 'wrong-context-uuid' - context_client_grpc.SetConnection(Connection(**WRONG_CONNECTION)) - assert e.value.code() == grpc.StatusCode.NOT_FOUND - # TODO: should we check that all endpoints belong to same topology? - # TODO: should we check that endpoints form links over the topology? - msg = 'EndPoint({:s}/{:s}:wrong-context-uuid/{:s}) not found'.format( - DEVICE_R1_UUID, WRONG_CONNECTION['path_hops_endpoint_ids'][0]['endpoint_uuid']['uuid'], DEFAULT_TOPOLOGY_UUID) - assert e.value.details() == msg - - response = context_client_grpc.SetConnection(Connection(**CONNECTION_R1_R3)) - assert response.connection_uuid.uuid == CONNECTION_R1_R3_UUID - - # ----- Check create event ----------------------------------------------------------------------------------------- - event = events_collector.get_event(block=True) - assert isinstance(event, ConnectionEvent) - assert event.event.event_type == EventTypeEnum.EVENTTYPE_CREATE - assert event.connection_id.connection_uuid.uuid == CONNECTION_R1_R3_UUID - - # ----- Update the object ------------------------------------------------------------------------------------------ - response = context_client_grpc.SetConnection(Connection(**CONNECTION_R1_R3)) - assert response.connection_uuid.uuid == CONNECTION_R1_R3_UUID - - # ----- Check update event ----------------------------------------------------------------------------------------- - event = events_collector.get_event(block=True) - assert isinstance(event, ConnectionEvent) - assert event.event.event_type == EventTypeEnum.EVENTTYPE_UPDATE - assert event.connection_id.connection_uuid.uuid == CONNECTION_R1_R3_UUID - - # ----- Dump state of database after create/update the object ------------------------------------------------------ - db_entries = context_database.dump() - LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) - for db_entry in db_entries: - LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover - LOGGER.info('-----------------------------------------------------------') - assert len(db_entries) == 203 - - # ----- Get when the object exists --------------------------------------------------------------------------------- - response = context_client_grpc.GetConnection(ConnectionId(**CONNECTION_R1_R3_ID)) - assert response.connection_id.connection_uuid.uuid == CONNECTION_R1_R3_UUID - assert response.service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.service_id.service_uuid.uuid == SERVICE_R1_R3_UUID - assert len(response.path_hops_endpoint_ids) == 6 - assert len(response.sub_service_ids) == 2 - - # ----- List when the object exists -------------------------------------------------------------------------------- - response = context_client_grpc.ListConnectionIds(ServiceId(**SERVICE_R1_R3_ID)) - assert len(response.connection_ids) == 1 - assert response.connection_ids[0].connection_uuid.uuid == CONNECTION_R1_R3_UUID - - response = context_client_grpc.ListConnections(ServiceId(**SERVICE_R1_R3_ID)) - assert len(response.connections) == 1 - assert response.connections[0].connection_id.connection_uuid.uuid == CONNECTION_R1_R3_UUID - assert len(response.connections[0].path_hops_endpoint_ids) == 6 - assert len(response.connections[0].sub_service_ids) == 2 - - # ----- Remove the object ------------------------------------------------------------------------------------------ - context_client_grpc.RemoveConnection(ConnectionId(**CONNECTION_R1_R3_ID)) - context_client_grpc.RemoveService(ServiceId(**SERVICE_R1_R3_ID)) - context_client_grpc.RemoveService(ServiceId(**SERVICE_R2_R3_ID)) - context_client_grpc.RemoveService(ServiceId(**SERVICE_R1_R2_ID)) - context_client_grpc.RemoveDevice(DeviceId(**DEVICE_R1_ID)) - context_client_grpc.RemoveDevice(DeviceId(**DEVICE_R2_ID)) - context_client_grpc.RemoveDevice(DeviceId(**DEVICE_R3_ID)) - context_client_grpc.RemoveTopology(TopologyId(**TOPOLOGY_ID)) - context_client_grpc.RemoveContext(ContextId(**CONTEXT_ID)) - - # ----- Check remove event ----------------------------------------------------------------------------------------- - events = events_collector.get_events(block=True, count=9) - - assert isinstance(events[0], ConnectionEvent) - assert events[0].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - assert events[0].connection_id.connection_uuid.uuid == CONNECTION_R1_R3_UUID - - assert isinstance(events[1], ServiceEvent) - assert events[1].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - assert events[1].service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert events[1].service_id.service_uuid.uuid == SERVICE_R1_R3_UUID - - assert isinstance(events[2], ServiceEvent) - assert events[2].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - assert events[2].service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert events[2].service_id.service_uuid.uuid == SERVICE_R2_R3_UUID - - assert isinstance(events[3], ServiceEvent) - assert events[3].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - assert events[3].service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert events[3].service_id.service_uuid.uuid == SERVICE_R1_R2_UUID - - assert isinstance(events[4], DeviceEvent) - assert events[4].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - assert events[4].device_id.device_uuid.uuid == DEVICE_R1_UUID - - assert isinstance(events[5], DeviceEvent) - assert events[5].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - assert events[5].device_id.device_uuid.uuid == DEVICE_R2_UUID - - assert isinstance(events[6], DeviceEvent) - assert events[6].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - assert events[6].device_id.device_uuid.uuid == DEVICE_R3_UUID - - assert isinstance(events[7], TopologyEvent) - assert events[7].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - assert events[7].topology_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert events[7].topology_id.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID - - assert isinstance(events[8], ContextEvent) - assert events[8].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - assert events[8].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - - # ----- Stop the EventsCollector ----------------------------------------------------------------------------------- - events_collector.stop() - - # ----- Dump state of database after remove the object ------------------------------------------------------------- - db_entries = context_database.dump() - LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) - for db_entry in db_entries: - LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover - LOGGER.info('-----------------------------------------------------------') - assert len(db_entries) == 0 - - -def test_grpc_policy( - context_client_grpc : ContextClient, # pylint: disable=redefined-outer-name - context_db_mb : Tuple[Database, MessageBroker]): # pylint: disable=redefined-outer-name - context_database = context_db_mb[0] - - # ----- Clean the database ----------------------------------------------------------------------------------------- - context_database.clear_all() - - # ----- Initialize the EventsCollector ----------------------------------------------------------------------------- - #events_collector = EventsCollector(context_client_grpc) - #events_collector.start() - - # ----- Get when the object does not exist ------------------------------------------------------------------------- - POLICY_ID = 'no-uuid' - DEFAULT_POLICY_ID = {'uuid': {'uuid': POLICY_ID}} - - with pytest.raises(grpc.RpcError) as e: - context_client_grpc.GetPolicyRule(PolicyRuleId(**DEFAULT_POLICY_ID)) - - assert e.value.code() == grpc.StatusCode.NOT_FOUND - assert e.value.details() == 'PolicyRule({:s}) not found'.format(POLICY_ID) - - # ----- List when the object does not exist ------------------------------------------------------------------------ - response = context_client_grpc.ListPolicyRuleIds(Empty()) - assert len(response.policyRuleIdList) == 0 - - response = context_client_grpc.ListPolicyRules(Empty()) - assert len(response.policyRules) == 0 - - # ----- Dump state of database before create the object ------------------------------------------------------------ - db_entries = context_database.dump() - LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) - for db_entry in db_entries: - LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover - LOGGER.info('-----------------------------------------------------------') - assert len(db_entries) == 0 - - # ----- Create the object ------------------------------------------------------------------------------------------ - response = context_client_grpc.SetPolicyRule(PolicyRule(**POLICY_RULE)) - assert response.uuid.uuid == POLICY_RULE_UUID - - # ----- Check create event ----------------------------------------------------------------------------------------- - # events = events_collector.get_events(block=True, count=1) - # assert isinstance(events[0], PolicyEvent) - # assert events[0].event.event_type == EventTypeEnum.EVENTTYPE_CREATE - # assert events[0].policy_id.uuid.uuid == POLICY_RULE_UUID - - # ----- Update the object ------------------------------------------------------------------------------------------ - response = context_client_grpc.SetPolicyRule(PolicyRule(**POLICY_RULE)) - assert response.uuid.uuid == POLICY_RULE_UUID - - # ----- Dump state of database after create/update the object ------------------------------------------------------ - db_entries = context_database.dump() - LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) - for db_entry in db_entries: - LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover - LOGGER.info('-----------------------------------------------------------') - assert len(db_entries) == 2 - - # ----- Get when the object exists --------------------------------------------------------------------------------- - response = context_client_grpc.GetPolicyRule(PolicyRuleId(**POLICY_RULE_ID)) - assert response.device.policyRuleBasic.policyRuleId.uuid.uuid == POLICY_RULE_UUID - - # ----- List when the object exists -------------------------------------------------------------------------------- - response = context_client_grpc.ListPolicyRuleIds(Empty()) - assert len(response.policyRuleIdList) == 1 - assert response.policyRuleIdList[0].uuid.uuid == POLICY_RULE_UUID - - response = context_client_grpc.ListPolicyRules(Empty()) - assert len(response.policyRules) == 1 - - # ----- Remove the object ------------------------------------------------------------------------------------------ - context_client_grpc.RemovePolicyRule(PolicyRuleId(**POLICY_RULE_ID)) - - # ----- Check remove event ----------------------------------------------------------------------------------------- - # events = events_collector.get_events(block=True, count=2) - - # assert isinstance(events[0], PolicyEvent) - # assert events[0].event.event_type == EventTypeEnum.EVENTTYPE_REMOVE - # assert events[0].policy_id.uuid.uuid == POLICY_RULE_UUID - - - # ----- Stop the EventsCollector ----------------------------------------------------------------------------------- - # events_collector.stop() - - # ----- Dump state of database after remove the object ------------------------------------------------------------- - db_entries = context_database.dump() - LOGGER.info('----- Database Dump [{:3d} entries] -------------------------'.format(len(db_entries))) - for db_entry in db_entries: - LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover - LOGGER.info('-----------------------------------------------------------') - assert len(db_entries) == 0 - - - -# ----- Test misc. Context internal tools ------------------------------------------------------------------------------ - -def test_tools_fast_string_hasher(): - with pytest.raises(TypeError) as e: - fast_hasher(27) - assert str(e.value) == "data(27) must be " + FASTHASHER_DATA_ACCEPTED_FORMAT + ", found <class 'int'>" - - with pytest.raises(TypeError) as e: - fast_hasher({27}) - assert str(e.value) == "data({27}) must be " + FASTHASHER_DATA_ACCEPTED_FORMAT + ", found <class 'set'>" - - with pytest.raises(TypeError) as e: - fast_hasher({'27'}) - assert str(e.value) == "data({'27'}) must be " + FASTHASHER_DATA_ACCEPTED_FORMAT + ", found <class 'set'>" - - with pytest.raises(TypeError) as e: - fast_hasher([27]) - assert str(e.value) == "data[0](27) must be " + FASTHASHER_ITEM_ACCEPTED_FORMAT + ", found <class 'int'>" - - fast_hasher('hello-world') - fast_hasher('hello-world'.encode('UTF-8')) - fast_hasher(['hello', 'world']) - fast_hasher(('hello', 'world')) - fast_hasher(['hello'.encode('UTF-8'), 'world'.encode('UTF-8')]) - fast_hasher(('hello'.encode('UTF-8'), 'world'.encode('UTF-8'))) -""" \ No newline at end of file +from ._test_context import grpc_context +from ._test_topology import grpc_topology +from ._test_device import grpc_device +from ._test_link import grpc_link +#from ._test_service import grpc_service +#from ._test_slice import grpc_slice +#from ._test_connection import grpc_connection +#from ._test_policy import grpc_policy + +def test_grpc_context(context_client_grpc : ContextClient) -> None: + grpc_context(context_client_grpc) + +@pytest.mark.depends(on=['test_grpc_context']) +def test_grpc_topology(context_client_grpc : ContextClient) -> None: + grpc_topology(context_client_grpc) + +@pytest.mark.depends(on=['test_grpc_topology']) +def test_grpc_device(context_client_grpc : ContextClient) -> None: + grpc_device(context_client_grpc) + +@pytest.mark.depends(on=['test_grpc_device']) +def test_grpc_link(context_client_grpc : ContextClient) -> None: + grpc_link(context_client_grpc) + +#@pytest.mark.depends(on=['test_grpc_link']) +#def test_grpc_service(context_client_grpc : ContextClient) -> None: +# grpc_service(context_client_grpc) + +#@pytest.mark.depends(on=['test_grpc_service']) +#def test_grpc_slice(context_client_grpc : ContextClient) -> None: +# grpc_slice(context_client_grpc) + +#@pytest.mark.depends(on=['test_grpc_slice']) +#def test_grpc_connection(context_client_grpc : ContextClient) -> None: +# grpc_connection(context_client_grpc) + +#@pytest.mark.depends(on=['test_grpc_connection']) +#def test_grpc_policy(context_client_grpc : ContextClient) -> None: +# grpc_policy(context_client_grpc)