diff --git a/run_local_tests.sh b/run_local_tests.sh index ef21ad852626c5bf74f60c6998618f8c21826b20..04177cd009ffa574488ba74007a2c6d95e7220c1 100755 --- a/run_local_tests.sh +++ b/run_local_tests.sh @@ -22,7 +22,9 @@ coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \ # centralizedcybersecurity/tests/test_unitary.py coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \ - context/tests/test_unitary.py + context/tests/test_unitary_fast_hasher.py \ + context/tests/test_unitary_grpc.py \ + #context/tests/test_unitary_rest.py #coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \ # device/tests/test_unitary_driverapi.py \ diff --git a/src/context/service/database/ConfigModel.py b/src/context/service/database/ConfigModel.py index 9bb7884692c09d1fdbce96540b1959220eed2863..6d979fd34b60bab77e1762bbd85119000aebd954 100644 --- a/src/context/service/database/ConfigModel.py +++ b/src/context/service/database/ConfigModel.py @@ -20,7 +20,7 @@ class ORM_ConfigActionEnum(Enum): grpc_to_enum__config_action = functools.partial( grpc_to_enum, ConfigActionEnum, ORM_ConfigActionEnum) -class ConfigModel(Model): +class ConfigModel(Model): # pylint: disable=abstract-method pk = PrimaryKeyField() def dump(self) -> List[Dict]: @@ -29,7 +29,7 @@ class ConfigModel(Model): config_rules = sorted(config_rules, key=operator.itemgetter('position')) return [remove_dict_key(config_rule, 'position') for config_rule in config_rules] -class ConfigRuleModel(Model): +class ConfigRuleModel(Model): # pylint: disable=abstract-method pk = PrimaryKeyField() config_fk = ForeignKeyField(ConfigModel) position = IntegerField(min_value=0, required=True) diff --git a/src/context/service/database/ConstraintModel.py b/src/context/service/database/ConstraintModel.py index 8f0eab01343d54c2646603a273dbeeb70f936e1e..a01519dc08c222350c043621cb8e3d2aaaf0bb8b 100644 --- a/src/context/service/database/ConstraintModel.py +++ b/src/context/service/database/ConstraintModel.py @@ -9,7 +9,7 @@ from context.service.database.Tools import remove_dict_key LOGGER = logging.getLogger(__name__) -class ConstraintsModel(Model): +class ConstraintsModel(Model): # pylint: disable=abstract-method pk = PrimaryKeyField() def dump(self) -> List[Dict]: @@ -18,7 +18,7 @@ class ConstraintsModel(Model): constraints = sorted(constraints, key=operator.itemgetter('position')) return [remove_dict_key(constraint, 'position') for constraint in constraints] -class ConstraintModel(Model): +class ConstraintModel(Model): # pylint: disable=abstract-method pk = PrimaryKeyField() constraints_fk = ForeignKeyField(ConstraintsModel) position = IntegerField(min_value=0, required=True) diff --git a/src/context/service/database/ContextModel.py b/src/context/service/database/ContextModel.py index bab5e285ae39add8708fb4f00c6dcf8ae4519fe1..b9ff4b0abc164ffc355c86e9aed293e68a8b18c6 100644 --- a/src/context/service/database/ContextModel.py +++ b/src/context/service/database/ContextModel.py @@ -14,12 +14,12 @@ class ContextModel(Model): return {'context_uuid': {'uuid': self.context_uuid}} def dump_service_ids(self) -> List[Dict]: - from .ServiceModel import ServiceModel + from .ServiceModel import ServiceModel # pylint: disable=import-outside-toplevel db_service_pks = self.references(ServiceModel) return [ServiceModel(self.database, pk).dump_id() for pk in db_service_pks] def dump_topology_ids(self) -> List[Dict]: - from .TopologyModel import TopologyModel + from .TopologyModel import TopologyModel # pylint: disable=import-outside-toplevel db_topology_pks = self.references(TopologyModel) return [TopologyModel(self.database, pk).dump_id() for pk in db_topology_pks] diff --git a/src/context/service/database/DeviceModel.py b/src/context/service/database/DeviceModel.py index 5c70b877c3b1f1c112d1f83b307e1e899579d6a6..dcfd9387efb170e7d2fe647d3a4455aa48634b56 100644 --- a/src/context/service/database/DeviceModel.py +++ b/src/context/service/database/DeviceModel.py @@ -49,7 +49,7 @@ class DeviceModel(Model): return [DriverModel(self.database, pk).dump() for pk,_ in db_driver_pks] def dump_endpoints(self) -> List[Dict]: - from .EndPointModel import EndPointModel + from .EndPointModel import EndPointModel # pylint: disable=import-outside-toplevel db_endpoints_pks = self.references(EndPointModel) return [EndPointModel(self.database, pk).dump() for pk,_ in db_endpoints_pks] @@ -66,7 +66,7 @@ class DeviceModel(Model): if include_endpoints: result['device_endpoints'] = self.dump_endpoints() return result -class DriverModel(Model): +class DriverModel(Model): # pylint: disable=abstract-method pk = PrimaryKeyField() device_fk = ForeignKeyField(DeviceModel) driver = EnumeratedField(ORM_DeviceDriverEnum, required=True) diff --git a/src/context/service/database/LinkModel.py b/src/context/service/database/LinkModel.py index 7cbd87de8fa05d877eaa92b70837d3e2df40ee2c..1c2442df027302a642d55816e5d71f31db329483 100644 --- a/src/context/service/database/LinkModel.py +++ b/src/context/service/database/LinkModel.py @@ -14,7 +14,7 @@ class LinkModel(Model): return {'link_uuid': {'uuid': self.link_uuid}} def dump_endpoint_ids(self) -> List[Dict]: - from .RelationModels import LinkEndPointModel + from .RelationModels import LinkEndPointModel # pylint: disable=import-outside-toplevel db_endpoints = get_related_instances(self, LinkEndPointModel, 'endpoint_fk') return [db_endpoint.dump_id() for db_endpoint in sorted(db_endpoints, key=operator.attrgetter('pk'))] diff --git a/src/context/service/database/RelationModels.py b/src/context/service/database/RelationModels.py index 4d3efa4d2975df309020d289ce316ee5da3d5223..4531e0594f3f213f4a00b1fe70dfb2d8dc0a0f5e 100644 --- a/src/context/service/database/RelationModels.py +++ b/src/context/service/database/RelationModels.py @@ -10,22 +10,22 @@ from .TopologyModel import TopologyModel LOGGER = logging.getLogger(__name__) -class LinkEndPointModel(Model): +class LinkEndPointModel(Model): # pylint: disable=abstract-method pk = PrimaryKeyField() link_fk = ForeignKeyField(LinkModel) endpoint_fk = ForeignKeyField(EndPointModel) -class ServiceEndPointModel(Model): +class ServiceEndPointModel(Model): # pylint: disable=abstract-method pk = PrimaryKeyField() service_fk = ForeignKeyField(ServiceModel) endpoint_fk = ForeignKeyField(EndPointModel) -class TopologyDeviceModel(Model): +class TopologyDeviceModel(Model): # pylint: disable=abstract-method pk = PrimaryKeyField() topology_fk = ForeignKeyField(TopologyModel) device_fk = ForeignKeyField(DeviceModel) -class TopologyLinkModel(Model): +class TopologyLinkModel(Model): # pylint: disable=abstract-method pk = PrimaryKeyField() topology_fk = ForeignKeyField(TopologyModel) link_fk = ForeignKeyField(LinkModel) diff --git a/src/context/service/database/ServiceModel.py b/src/context/service/database/ServiceModel.py index 650fc8275c8f761b3ade2bcdf18c0f7ec65dce0e..e5973837efd2c2109c52fc2b3eff9d6208f89d00 100644 --- a/src/context/service/database/ServiceModel.py +++ b/src/context/service/database/ServiceModel.py @@ -49,7 +49,7 @@ class ServiceModel(Model): } def dump_endpoint_ids(self) -> List[Dict]: - from .RelationModels import ServiceEndPointModel + from .RelationModels import ServiceEndPointModel # pylint: disable=import-outside-toplevel db_endpoints = get_related_instances(self, ServiceEndPointModel, 'endpoint_fk') return [db_endpoint.dump_id() for db_endpoint in sorted(db_endpoints, key=operator.attrgetter('pk'))] diff --git a/src/context/service/database/Tools.py b/src/context/service/database/Tools.py index bb88e8d2209fa7b26ff189e17bebef153f9a74bf..36ffbcd46fcf686371b0799445ce4f9ce5b75838 100644 --- a/src/context/service/database/Tools.py +++ b/src/context/service/database/Tools.py @@ -26,15 +26,33 @@ def grpc_to_enum(grpc_enum_class, orm_enum_class : Enum, grpc_enum_value): # 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. +FASTHASHER_ITEM_ACCEPTED_FORMAT = 'Union[bytes, str]' +FASTHASHER_DATA_ACCEPTED_FORMAT = 'Union[{fmt:s}, List[{fmt:s}], Tuple[{fmt:s}]]'.format( + fmt=FASTHASHER_ITEM_ACCEPTED_FORMAT) + def fast_hasher(data : Union[bytes, str, List[Union[bytes, str]], Tuple[Union[bytes, str]]], digest_size : int = 8): hasher = hashlib.blake2b(digest_size=digest_size) # Do not accept sets, dicts, or other unordered dats tructures since their order is arbitrary thus producing # different hashes depending on the order. Consider adding support for sets or dicts with previous sorting of # items by their key. - if not isinstance(data, (list, tuple)): data = [data] - for item in data: - if isinstance(item, str): item = item.encode('UTF-8') - if not isinstance(item, bytes): - raise TypeError('data type must be Union[bytes, str, List[Union[bytes, str]], Tuple[Union[bytes, str]]]') + + if isinstance(data, bytes): + data = [data] + elif isinstance(data, str): + data = [data.encode('UTF-8')] + elif isinstance(data, (list, tuple)): + pass + else: + msg = 'data({:s}) must be {:s}, found {:s}' + raise TypeError(msg.format(str(data), FASTHASHER_DATA_ACCEPTED_FORMAT, str(type(data)))) + + for i,item in enumerate(data): + if isinstance(item, str): + item = item.encode('UTF-8') + elif isinstance(item, bytes): + pass + else: + msg = 'data[{:d}]({:s}) must be {:s}, found {:s}' + raise TypeError(msg.format(i, str(item), FASTHASHER_ITEM_ACCEPTED_FORMAT, str(type(item)))) hasher.update(item) return hasher.hexdigest() diff --git a/src/context/service/database/TopologyModel.py b/src/context/service/database/TopologyModel.py index 871dd2e8f90876a5d74dc2e7d1bf9c8cefc49a2a..3716aee8e128aa1cb366433e7d246e60716c319e 100644 --- a/src/context/service/database/TopologyModel.py +++ b/src/context/service/database/TopologyModel.py @@ -21,12 +21,12 @@ class TopologyModel(Model): } def dump_device_ids(self) -> List[Dict]: - from .RelationModels import TopologyDeviceModel + from .RelationModels import TopologyDeviceModel # pylint: disable=import-outside-toplevel db_devices = get_related_instances(self, TopologyDeviceModel, 'device_fk') return [db_device.dump_id() for db_device in sorted(db_devices, key=operator.attrgetter('pk'))] def dump_link_ids(self) -> List[Dict]: - from .RelationModels import TopologyLinkModel + from .RelationModels import TopologyLinkModel # pylint: disable=import-outside-toplevel db_links = get_related_instances(self, TopologyLinkModel, 'link_fk') return [db_link.dump_id() for db_link in sorted(db_links, key=operator.attrgetter('pk'))] diff --git a/src/context/tests/example_objects.py b/src/context/tests/example_objects.py new file mode 100644 index 0000000000000000000000000000000000000000..bf157178f9c502ea70397235cdab1a5a6580a95c --- /dev/null +++ b/src/context/tests/example_objects.py @@ -0,0 +1,142 @@ +import copy +from common.Constants import DEFAULT_CONTEXT_UUID, DEFAULT_TOPOLOGY_UUID +from context.proto.context_pb2 import ( + ConfigActionEnum, DeviceDriverEnum, DeviceOperationalStatusEnum, ServiceStatusEnum, ServiceTypeEnum) + +# Some example objects to be used by the tests + +## use "copy.deepcopy" to prevent propagating forced changes during tests +CONTEXT_ID = {'context_uuid': {'uuid': DEFAULT_CONTEXT_UUID}} +CONTEXT = {'context_id': copy.deepcopy(CONTEXT_ID)} + +TOPOLOGY_ID = { + 'context_id': copy.deepcopy(CONTEXT_ID), + 'topology_uuid': {'uuid': DEFAULT_TOPOLOGY_UUID}, +} +TOPOLOGY = { + 'topology_id': copy.deepcopy(TOPOLOGY_ID), + 'device_ids': [], + 'link_ids': [], +} + +DEVICE1_UUID = 'DEV1' +DEVICE1_ID = {'device_uuid': {'uuid': DEVICE1_UUID}} +DEVICE1 = { + 'device_id': copy.deepcopy(DEVICE1_ID), + 'device_type': 'packet-router', + 'device_config': {'config_rules': [ + {'action': ConfigActionEnum.CONFIGACTION_SET, + 'resource_key': 'device/resource-1/value', + 'resource_value': 'value1'}, + {'action': ConfigActionEnum.CONFIGACTION_SET, + 'resource_key': 'device/resource-2/value', + 'resource_value': 'value2'}, + {'action': ConfigActionEnum.CONFIGACTION_SET, + 'resource_key': 'device/resource-3/value', + 'resource_value': 'value3'}, + ]}, + 'device_operational_status': DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED, + 'device_drivers': [DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG, DeviceDriverEnum.DEVICEDRIVER_P4], + 'device_endpoints': [ + {'endpoint_id': { + 'topology_id': copy.deepcopy(TOPOLOGY_ID), + 'device_id': copy.deepcopy(DEVICE1_ID), + 'endpoint_uuid': {'uuid': 'EP1'}, + }, 'endpoint_type': 'port-packet-100G'}, + {'endpoint_id': { + 'topology_id': copy.deepcopy(TOPOLOGY_ID), + 'device_id': copy.deepcopy(DEVICE1_ID), + 'endpoint_uuid': {'uuid': 'EP2'}, + }, 'endpoint_type': 'port-packet-100G'}, + {'endpoint_id': { + 'topology_id': copy.deepcopy(TOPOLOGY_ID), + 'device_id': copy.deepcopy(DEVICE1_ID), + 'endpoint_uuid': {'uuid': 'EP3'}, + }, 'endpoint_type': 'port-packet-100G'}, + ], +} + +DEVICE2_UUID = 'DEV2' +DEVICE2_ID = {'device_uuid': {'uuid': DEVICE2_UUID}} +DEVICE2 = { + 'device_id': copy.deepcopy(DEVICE2_ID), + 'device_type': 'packet-router', + 'device_config': {'config_rules': [ + {'action': ConfigActionEnum.CONFIGACTION_SET, + 'resource_key': 'device/resource-1/value', + 'resource_value': 'value4'}, + {'action': ConfigActionEnum.CONFIGACTION_SET, + 'resource_key': 'device/resource-2/value', + 'resource_value': 'value5'}, + {'action': ConfigActionEnum.CONFIGACTION_SET, + 'resource_key': 'device/resource-3/value', + 'resource_value': 'value6'}, + ]}, + 'device_operational_status': DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED, + 'device_drivers': [DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG, DeviceDriverEnum.DEVICEDRIVER_P4], + 'device_endpoints': [ + {'endpoint_id': { + 'topology_id': copy.deepcopy(TOPOLOGY_ID), + 'device_id': copy.deepcopy(DEVICE2_ID), + 'endpoint_uuid': {'uuid': 'EP1'}, + }, 'endpoint_type': 'port-packet-100G'}, + {'endpoint_id': { + 'topology_id': copy.deepcopy(TOPOLOGY_ID), + 'device_id': copy.deepcopy(DEVICE2_ID), + 'endpoint_uuid': {'uuid': 'EP2'}, + }, 'endpoint_type': 'port-packet-100G'}, + {'endpoint_id': { + 'topology_id': copy.deepcopy(TOPOLOGY_ID), + 'device_id': copy.deepcopy(DEVICE2_ID), + 'endpoint_uuid': {'uuid': 'EP3'}, + }, 'endpoint_type': 'port-packet-100G'}, + ], +} + +LINK_UUID = 'DEV1/EP2 ==> DEV2/EP1' +LINK_ID = {'link_uuid': {'uuid': LINK_UUID}} +LINK = { + 'link_id': copy.deepcopy(LINK_ID), + 'link_endpoint_ids' : [ + {'topology_id': copy.deepcopy(TOPOLOGY_ID), + 'device_id': copy.deepcopy(DEVICE1_ID), + 'endpoint_uuid': {'uuid' : 'EP2'}}, + {'topology_id': copy.deepcopy(TOPOLOGY_ID), + 'device_id': copy.deepcopy(DEVICE2_ID), + 'endpoint_uuid': {'uuid' : 'EP1'}}, + ] +} + +SERVICE_UUID = 'SVC:DEV1/EP2-DEV2/EP1' +SERVICE_ID = { + 'context_id': copy.deepcopy(CONTEXT_ID), + 'service_uuid': {'uuid': SERVICE_UUID}, +} +SERVICE = { + 'service_id': copy.deepcopy(SERVICE_ID), + 'service_type': ServiceTypeEnum.SERVICETYPE_L3NM, + 'service_endpoint_ids' : [ + {'topology_id': copy.deepcopy(TOPOLOGY_ID), + 'device_id': copy.deepcopy(DEVICE1_ID), + 'endpoint_uuid': {'uuid' : 'EP2'}}, + {'topology_id': copy.deepcopy(TOPOLOGY_ID), + 'device_id': copy.deepcopy(DEVICE2_ID), + 'endpoint_uuid': {'uuid' : 'EP1'}}, + ], + 'service_constraints': [ + {'constraint_type': 'latency_ms', 'constraint_value': '15.2'}, + {'constraint_type': 'jitter_us', 'constraint_value': '1.2'}, + ], + 'service_status': {'service_status': ServiceStatusEnum.SERVICESTATUS_ACTIVE}, + 'service_config': {'config_rules': [ + {'action': ConfigActionEnum.CONFIGACTION_SET, + 'resource_key': 'service/resource-1/value', + 'resource_value': 'value7'}, + {'action': ConfigActionEnum.CONFIGACTION_SET, + 'resource_key': 'service/resource-2/value', + 'resource_value': 'value8'}, + {'action': ConfigActionEnum.CONFIGACTION_SET, + 'resource_key': 'service/resource-3/value', + 'resource_value': 'value9'}, + ]}, +} diff --git a/src/context/tests/test_unitary_fast_hasher.py b/src/context/tests/test_unitary_fast_hasher.py new file mode 100644 index 0000000000000000000000000000000000000000..94ce3aebe21752c3311a38bb92a285aa83e337f5 --- /dev/null +++ b/src/context/tests/test_unitary_fast_hasher.py @@ -0,0 +1,30 @@ +import logging, pytest +from context.service.database.Tools import ( + FASTHASHER_DATA_ACCEPTED_FORMAT, FASTHASHER_ITEM_ACCEPTED_FORMAT, fast_hasher) + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +def test_fast_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_grpc.py similarity index 71% rename from src/context/tests/test_unitary.py rename to src/context/tests/test_unitary_grpc.py index 4cdb998495d0c06ce05857ff39a79f6054d08c7b..2ffa597fa704cdef18477e137d2ae5a0f69d89dd 100644 --- a/src/context/tests/test_unitary.py +++ b/src/context/tests/test_unitary_grpc.py @@ -2,181 +2,58 @@ import copy, logging, pytest from common.Constants import DEFAULT_CONTEXT_UUID, DEFAULT_TOPOLOGY_UUID from common.orm.Database import Database from common.orm.Factory import get_database_backend, BackendEnum +from context.Config import GRPC_SERVICE_PORT, GRPC_MAX_WORKERS, GRPC_GRACE_PERIOD from context.client.ContextClient import ContextClient -from context.proto.context_pb2 import ConfigActionEnum, Context, ContextId, Device, DeviceDriverEnum, DeviceId, DeviceOperationalStatusEnum, Empty, Link, LinkId, Service, ServiceId, ServiceStatusEnum, ServiceTypeEnum, Topology, TopologyId +from context.proto.context_pb2 import ( + Context, ContextId, Device, DeviceId, DeviceOperationalStatusEnum, Empty, Link, LinkId, Service, ServiceId, + ServiceStatusEnum, ServiceTypeEnum, Topology, TopologyId) from context.service.grpc_server.ContextService import ContextService -from context.Config import GRPC_SERVICE_PORT, GRPC_MAX_WORKERS, GRPC_GRACE_PERIOD #, RESTAPI_SERVICE_PORT, \ -# RESTAPI_BASE_URL -#from context.service.rest_server.Server import Server -#from context.service.rest_server.resources.Context import Context -#from .populate_database import populate_example - -grpc_port = 10000 + GRPC_SERVICE_PORT # avoid privileged ports -#restapi_port = 10000 + RESTAPI_SERVICE_PORT # avoid privileged ports +from .example_objects import ( + CONTEXT, CONTEXT_ID, DEVICE1, DEVICE1_ID, DEVICE1_UUID, DEVICE2_ID, DEVICE2_UUID, LINK, LINK_ID, LINK_UUID, + SERVICE, SERVICE_ID, SERVICE_UUID, TOPOLOGY, TOPOLOGY_ID) LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.DEBUG) -## use "copy.deepcopy" to prevent propagating forced changes during tests -CONTEXT_ID = {'context_uuid': {'uuid': DEFAULT_CONTEXT_UUID}} -CONTEXT = {'context_id': copy.deepcopy(CONTEXT_ID)} - -TOPOLOGY_ID = { - 'context_id': copy.deepcopy(CONTEXT_ID), - 'topology_uuid': {'uuid': DEFAULT_TOPOLOGY_UUID}, -} -TOPOLOGY = { - 'topology_id': copy.deepcopy(TOPOLOGY_ID), - 'device_ids': [], - 'link_ids': [], -} - -DEVICE1_UUID = 'DEV1' -DEVICE1_ID = {'device_uuid': {'uuid': DEVICE1_UUID}} -DEVICE1 = { - 'device_id': copy.deepcopy(DEVICE1_ID), - 'device_type': 'packet-router', - 'device_config': {'config_rules': [ - {'action': ConfigActionEnum.CONFIGACTION_SET, - 'resource_key': 'device/resource-1/value', - 'resource_value': 'value1'}, - {'action': ConfigActionEnum.CONFIGACTION_SET, - 'resource_key': 'device/resource-2/value', - 'resource_value': 'value2'}, - {'action': ConfigActionEnum.CONFIGACTION_SET, - 'resource_key': 'device/resource-3/value', - 'resource_value': 'value3'}, - ]}, - 'device_operational_status': DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED, - 'device_drivers': [DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG, DeviceDriverEnum.DEVICEDRIVER_P4], - 'device_endpoints': [ - {'endpoint_id': { - 'topology_id': copy.deepcopy(TOPOLOGY_ID), - 'device_id': copy.deepcopy(DEVICE1_ID), - 'endpoint_uuid': {'uuid': 'EP1'}, - }, 'endpoint_type': 'port-packet-100G'}, - {'endpoint_id': { - 'topology_id': copy.deepcopy(TOPOLOGY_ID), - 'device_id': copy.deepcopy(DEVICE1_ID), - 'endpoint_uuid': {'uuid': 'EP2'}, - }, 'endpoint_type': 'port-packet-100G'}, - {'endpoint_id': { - 'topology_id': copy.deepcopy(TOPOLOGY_ID), - 'device_id': copy.deepcopy(DEVICE1_ID), - 'endpoint_uuid': {'uuid': 'EP3'}, - }, 'endpoint_type': 'port-packet-100G'}, - ], -} - -DEVICE2_UUID = 'DEV2' -DEVICE2_ID = {'device_uuid': {'uuid': DEVICE2_UUID}} -DEVICE2 = { - 'device_id': copy.deepcopy(DEVICE2_ID), - 'device_type': 'packet-router', - 'device_config': {'config_rules': [ - {'action': ConfigActionEnum.CONFIGACTION_SET, - 'resource_key': 'device/resource-1/value', - 'resource_value': 'value4'}, - {'action': ConfigActionEnum.CONFIGACTION_SET, - 'resource_key': 'device/resource-2/value', - 'resource_value': 'value5'}, - {'action': ConfigActionEnum.CONFIGACTION_SET, - 'resource_key': 'device/resource-3/value', - 'resource_value': 'value6'}, - ]}, - 'device_operational_status': DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED, - 'device_drivers': [DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG, DeviceDriverEnum.DEVICEDRIVER_P4], - 'device_endpoints': [ - {'endpoint_id': { - 'topology_id': copy.deepcopy(TOPOLOGY_ID), - 'device_id': copy.deepcopy(DEVICE2_ID), - 'endpoint_uuid': {'uuid': 'EP1'}, - }, 'endpoint_type': 'port-packet-100G'}, - {'endpoint_id': { - 'topology_id': copy.deepcopy(TOPOLOGY_ID), - 'device_id': copy.deepcopy(DEVICE2_ID), - 'endpoint_uuid': {'uuid': 'EP2'}, - }, 'endpoint_type': 'port-packet-100G'}, - {'endpoint_id': { - 'topology_id': copy.deepcopy(TOPOLOGY_ID), - 'device_id': copy.deepcopy(DEVICE2_ID), - 'endpoint_uuid': {'uuid': 'EP3'}, - }, 'endpoint_type': 'port-packet-100G'}, - ], -} - -LINK_UUID = 'DEV1/EP2 ==> DEV2/EP1' -LINK_ID = {'link_uuid': {'uuid': LINK_UUID}} -LINK = { - 'link_id': copy.deepcopy(LINK_ID), - 'link_endpoint_ids' : [ - {'topology_id': copy.deepcopy(TOPOLOGY_ID), - 'device_id': copy.deepcopy(DEVICE1_ID), - 'endpoint_uuid': {'uuid' : 'EP2'}}, - {'topology_id': copy.deepcopy(TOPOLOGY_ID), - 'device_id': copy.deepcopy(DEVICE2_ID), - 'endpoint_uuid': {'uuid' : 'EP1'}}, - ] -} - -SERVICE_UUID = 'SVC:DEV1/EP2-DEV2/EP1' -SERVICE_ID = { - 'context_id': copy.deepcopy(CONTEXT_ID), - 'service_uuid': {'uuid': SERVICE_UUID}, -} -SERVICE = { - 'service_id': copy.deepcopy(SERVICE_ID), - 'service_type': ServiceTypeEnum.SERVICETYPE_L3NM, - 'service_endpoint_ids' : [ - {'topology_id': copy.deepcopy(TOPOLOGY_ID), - 'device_id': copy.deepcopy(DEVICE1_ID), - 'endpoint_uuid': {'uuid' : 'EP2'}}, - {'topology_id': copy.deepcopy(TOPOLOGY_ID), - 'device_id': copy.deepcopy(DEVICE2_ID), - 'endpoint_uuid': {'uuid' : 'EP1'}}, - ], - 'service_constraints': [ - {'constraint_type': 'latency_ms', 'constraint_value': '15.2'}, - {'constraint_type': 'jitter_us', 'constraint_value': '1.2'}, - ], - 'service_status': {'service_status': ServiceStatusEnum.SERVICESTATUS_ACTIVE}, - 'service_config': {'config_rules': [ - {'action': ConfigActionEnum.CONFIGACTION_SET, - 'resource_key': 'service/resource-1/value', - 'resource_value': 'value7'}, - {'action': ConfigActionEnum.CONFIGACTION_SET, - 'resource_key': 'service/resource-2/value', - 'resource_value': 'value8'}, - {'action': ConfigActionEnum.CONFIGACTION_SET, - 'resource_key': 'service/resource-3/value', - 'resource_value': 'value9'}, - ]}, -} - -@pytest.fixture(scope='session') -def context_database(): - database_backend = get_database_backend(engine=BackendEnum.INMEMORY) +GRPC_PORT = 10000 + GRPC_SERVICE_PORT # avoid privileged ports + +SCENARIOS = [ + (BackendEnum.INMEMORY, {}), + (BackendEnum.REDIS, { + 'REDIS_SERVICE_HOST': '10.1.7.194', + 'REDIS_SERVICE_PORT': 30283, + 'REDIS_DATABASE_ID': 0, + }), +] + +@pytest.fixture(scope='session', ids=[str(scenario[0].value) for scenario in SCENARIOS], params=SCENARIOS) +def context_database(request): + backend,settings = request.param + LOGGER.info('Running fixture with backend={}, settings={}...'.format(str(backend), str(settings))) + database_backend = get_database_backend(backend=backend, **settings) _database = Database(database_backend) return _database @pytest.fixture(scope='session') def context_service(context_database : Database): # pylint: disable=redefined-outer-name _service = ContextService( - context_database, port=grpc_port, max_workers=GRPC_MAX_WORKERS, grace_period=GRPC_GRACE_PERIOD) + context_database, port=GRPC_PORT, max_workers=GRPC_MAX_WORKERS, grace_period=GRPC_GRACE_PERIOD) _service.start() yield _service _service.stop() @pytest.fixture(scope='session') def context_client(context_service : ContextService): # pylint: disable=redefined-outer-name - _client = ContextClient(address='127.0.0.1', port=grpc_port) + _client = ContextClient(address='127.0.0.1', port=GRPC_PORT) yield _client _client.close() -def test_context_instances( + +def test_context_initial_state( context_client : ContextClient, context_database : Database): # pylint: disable=redefined-outer-name context_database.clear_all() + response = context_client.ListContextIds(Empty()) assert len(response.context_ids) == 0 @@ -195,16 +72,24 @@ def test_context_instances( LOGGER.info('-----------------------------------------------------------') assert len(db_entries) == 0 + +def test_context_set( + context_client : ContextClient, context_database : Database): # pylint: disable=redefined-outer-name + response = context_client.SetContext(Context(**CONTEXT)) assert response.context_uuid.uuid == DEFAULT_CONTEXT_UUID 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)) + LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover LOGGER.info('-----------------------------------------------------------') assert len(db_entries) == 2 + +def test_context_get_list( + context_client : ContextClient, context_database : Database): # pylint: disable=redefined-outer-name + response = context_client.GetContext(ContextId(**CONTEXT_ID)) assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID assert len(response.topology_ids) == 0 @@ -220,6 +105,10 @@ def test_context_instances( assert len(response.contexts[0].topology_ids) == 0 assert len(response.contexts[0].service_ids) == 0 + +def test_context_remove( + context_client : ContextClient, context_database : Database): # pylint: disable=redefined-outer-name + context_client.RemoveContext(ContextId(**CONTEXT_ID)) db_entries = context_database.dump() @@ -229,7 +118,8 @@ def test_context_instances( LOGGER.info('-----------------------------------------------------------') assert len(db_entries) == 0 -def test_topology_instances( + +def test_topology_initial_state( context_client : ContextClient, context_database : Database): # pylint: disable=redefined-outer-name context_database.clear_all() @@ -252,22 +142,35 @@ def test_topology_instances( 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)) + LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover LOGGER.info('-----------------------------------------------------------') assert len(db_entries) == 2 + +def test_topology_set( + context_client : ContextClient, context_database : Database): # pylint: disable=redefined-outer-name + response = context_client.SetTopology(Topology(**TOPOLOGY)) - LOGGER.info('response={:s}'.format(str(response))) assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID assert response.topology_uuid.uuid == DEFAULT_TOPOLOGY_UUID 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)) + LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover LOGGER.info('-----------------------------------------------------------') assert len(db_entries) == 5 + +def test_topology_get_list( + context_client : ContextClient, context_database : Database): # pylint: disable=redefined-outer-name + + response = context_client.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) == 0 + assert len(response.link_ids) == 0 + response = context_client.ListTopologyIds(ContextId(**CONTEXT_ID)) assert len(response.topology_ids) == 1 assert response.topology_ids[0].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID @@ -280,11 +183,9 @@ def test_topology_instances( assert len(response.topologies[0].device_ids) == 0 assert len(response.topologies[0].link_ids) == 0 - response = context_client.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) == 0 - assert len(response.link_ids) == 0 + +def test_topology_remove( + context_client : ContextClient, context_database : Database): # pylint: disable=redefined-outer-name context_client.RemoveTopology(TopologyId(**TOPOLOGY_ID)) context_client.RemoveContext(ContextId(**CONTEXT_ID)) @@ -296,7 +197,8 @@ def test_topology_instances( LOGGER.info('-----------------------------------------------------------') assert len(db_entries) == 0 -def test_device_instances( + +def test_device_initial_state( context_client : ContextClient, context_database : Database): # pylint: disable=redefined-outer-name context_database.clear_all() @@ -325,20 +227,36 @@ def test_device_instances( 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)) + LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover LOGGER.info('-----------------------------------------------------------') assert len(db_entries) == 5 + +def test_device_set( + context_client : ContextClient, context_database : Database): # pylint: disable=redefined-outer-name + response = context_client.SetDevice(Device(**DEVICE1)) assert response.device_uuid.uuid == DEVICE1_UUID 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)) + LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover LOGGER.info('-----------------------------------------------------------') assert len(db_entries) == 25 + +def test_device_get_list( + context_client : ContextClient, context_database : Database): # pylint: disable=redefined-outer-name + + response = context_client.GetDevice(DeviceId(**DEVICE1_ID)) + assert response.device_id.device_uuid.uuid == DEVICE1_UUID + 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 len(response.device_endpoints) == 3 + response = context_client.ListDeviceIds(Empty()) assert len(response.device_ids) == 1 assert response.device_ids[0].device_uuid.uuid == DEVICE1_UUID @@ -352,13 +270,9 @@ def test_device_instances( assert len(response.devices[0].device_drivers) == 2 assert len(response.devices[0].device_endpoints) == 3 - response = context_client.GetDevice(DeviceId(**DEVICE1_ID)) - assert response.device_id.device_uuid.uuid == DEVICE1_UUID - 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 len(response.device_endpoints) == 3 + +def test_device_add_to_topology( + context_client : ContextClient, context_database : Database): # pylint: disable=redefined-outer-name TOPOLOGY_WITH_DEVICE = copy.deepcopy(TOPOLOGY) TOPOLOGY_WITH_DEVICE['device_ids'].append(DEVICE1_ID) @@ -380,6 +294,10 @@ def test_device_instances( LOGGER.info('-----------------------------------------------------------') assert len(db_entries) == 25 + +def test_device_remove( + context_client : ContextClient, context_database : Database): # pylint: disable=redefined-outer-name + context_client.RemoveDevice(DeviceId(**DEVICE1_ID)) context_client.RemoveTopology(TopologyId(**TOPOLOGY_ID)) context_client.RemoveContext(ContextId(**CONTEXT_ID)) @@ -391,7 +309,8 @@ def test_device_instances( LOGGER.info('-----------------------------------------------------------') assert len(db_entries) == 0 -def test_link_instances( + +def test_link_initial_state( context_client : ContextClient, context_database : Database): # pylint: disable=redefined-outer-name context_database.clear_all() @@ -416,20 +335,32 @@ def test_link_instances( 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)) + LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover LOGGER.info('-----------------------------------------------------------') assert len(db_entries) == 5 + +def test_link_set( + context_client : ContextClient, context_database : Database): # pylint: disable=redefined-outer-name + response = context_client.SetLink(Link(**LINK)) assert response.link_uuid.uuid == LINK_UUID 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)) + LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover LOGGER.info('-----------------------------------------------------------') assert len(db_entries) == 30 + +def test_link_get_list( + context_client : ContextClient, context_database : Database): # pylint: disable=redefined-outer-name + + response = context_client.GetLink(LinkId(**LINK_ID)) + assert response.link_id.link_uuid.uuid == LINK_UUID + assert len(response.link_endpoint_ids) == 2 + response = context_client.ListLinkIds(Empty()) assert len(response.link_ids) == 1 assert response.link_ids[0].link_uuid.uuid == LINK_UUID @@ -439,9 +370,9 @@ def test_link_instances( assert response.links[0].link_id.link_uuid.uuid == LINK_UUID assert len(response.links[0].link_endpoint_ids) == 2 - response = context_client.GetLink(LinkId(**LINK_ID)) - assert response.link_id.link_uuid.uuid == LINK_UUID - assert len(response.link_endpoint_ids) == 2 + +def test_link_add_to_topology( + context_client : ContextClient, context_database : Database): # pylint: disable=redefined-outer-name TOPOLOGY_WITH_LINK = copy.deepcopy(TOPOLOGY) TOPOLOGY_WITH_LINK['link_ids'].append(LINK_ID) @@ -465,6 +396,10 @@ def test_link_instances( LOGGER.info('-----------------------------------------------------------') assert len(db_entries) == 32 + +def test_link_remove( + context_client : ContextClient, context_database : Database): # pylint: disable=redefined-outer-name + context_client.RemoveLink(LinkId(**LINK_ID)) context_client.RemoveDevice(DeviceId(**DEVICE1_ID)) context_client.RemoveDevice(DeviceId(**DEVICE2_ID)) @@ -478,7 +413,8 @@ def test_link_instances( LOGGER.info('-----------------------------------------------------------') assert len(db_entries) == 0 -def test_service_instances( + +def test_service_initial_state( context_client : ContextClient, context_database : Database): # pylint: disable=redefined-outer-name context_database.clear_all() @@ -504,10 +440,14 @@ def test_service_instances( 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)) + LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover LOGGER.info('-----------------------------------------------------------') assert len(db_entries) == 2 + +def test_service_set( + context_client : ContextClient, context_database : Database): # pylint: disable=redefined-outer-name + response = context_client.SetService(Service(**SERVICE)) assert response.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID assert response.service_uuid.uuid == SERVICE_UUID @@ -515,10 +455,23 @@ def test_service_instances( 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)) + LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover LOGGER.info('-----------------------------------------------------------') assert len(db_entries) == 42 + +def test_service_get_list( + context_client : ContextClient, context_database : Database): # pylint: disable=redefined-outer-name + + response = context_client.GetService(ServiceId(**SERVICE_ID)) + assert response.service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID + assert response.service_id.service_uuid.uuid == SERVICE_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_ACTIVE + assert len(response.service_config.config_rules) == 3 + response = context_client.ListServiceIds(ContextId(**CONTEXT_ID)) assert len(response.service_ids) == 1 assert response.service_ids[0].context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID @@ -534,14 +487,9 @@ def test_service_instances( assert response.services[0].service_status.service_status == ServiceStatusEnum.SERVICESTATUS_ACTIVE assert len(response.services[0].service_config.config_rules) == 3 - response = context_client.GetService(ServiceId(**SERVICE_ID)) - assert response.service_id.context_id.context_uuid.uuid == DEFAULT_CONTEXT_UUID - assert response.service_id.service_uuid.uuid == SERVICE_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_ACTIVE - assert len(response.service_config.config_rules) == 3 + +def test_service_remove( + context_client : ContextClient, context_database : Database): # pylint: disable=redefined-outer-name context_client.RemoveService(ServiceId(**SERVICE_ID)) context_client.RemoveDevice(DeviceId(**DEVICE1_ID)) @@ -555,30 +503,3 @@ def test_service_instances( LOGGER.info(' [{:>4s}] {:40s} :: {:s}'.format(*db_entry)) # pragma: no cover LOGGER.info('-----------------------------------------------------------') assert len(db_entries) == 0 - -#@pytest.fixture(scope='session') -#def context_service_rest(context_database : Database): -# _rest_server = Server(port=restapi_port, base_url=RESTAPI_BASE_URL) -# _rest_server.add_resource(Context, '/context', endpoint='api.context', resource_class_args=(context_database,)) -# _rest_server.start() -# time.sleep(1) # bring time for the server to start -# yield _rest_server -# _rest_server.shutdown() -# _rest_server.join() -# -#def test_get_topology_completed_rest_api(context_service_rest : Server): -# # should work -# request_url = 'http://127.0.0.1:{}{}/context'.format(restapi_port, RESTAPI_BASE_URL) -# LOGGER.warning('Request: GET {}'.format(str(request_url))) -# reply = requests.get(request_url) -# LOGGER.warning('Reply: {}'.format(str(reply.text))) -# assert reply.status_code == 200, 'Reply failed with code {}'.format(reply.status_code) -# json_reply = reply.json() -# topology = MessageToDict( -# Topology(**json_reply['topologies'][0]), -# including_default_value_fields=True, preserving_proto_field_name=True, -# use_integers_for_enums=False) -# validate_topology(topology) -# validate_topology_has_devices(topology) -# validate_topology_has_links(topology) -# \ No newline at end of file diff --git a/src/context/tests/test_unitary_rest.py b/src/context/tests/test_unitary_rest.py new file mode 100644 index 0000000000000000000000000000000000000000..01c58581a274ac74aa8162d06d4551fbed3a77fa --- /dev/null +++ b/src/context/tests/test_unitary_rest.py @@ -0,0 +1,57 @@ +import logging, pytest, requests, time +from google.protobuf.json_format import MessageToDict +from common.orm.Database import Database +from common.orm.Factory import get_database_backend, BackendEnum +from context.proto.context_pb2 import Context, Topology +from context.Config import RESTAPI_SERVICE_PORT, RESTAPI_BASE_URL +from context.service.rest_server.Server import Server +from context.service.rest_server.resources.Context import Context +#from .populate_database import populate_example + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +RESTAPI_PORT = 10000 + RESTAPI_SERVICE_PORT # avoid privileged ports + +SCENARIOS = [ + (BackendEnum.INMEMORY, {}), + #(BackendEnum.REDIS, { + # 'REDIS_SERVICE_HOST': '10.1.7.194', + # 'REDIS_SERVICE_PORT': 30283, + # 'REDIS_DATABASE_ID': 0, + #}), +] + +@pytest.fixture(scope='session', ids=[str(scenario[0].value) for scenario in SCENARIOS], params=SCENARIOS) +def context_database(request): + backend,settings = request.param + LOGGER.info('Running fixture with backend={}, settings={}...'.format(str(backend), str(settings))) + database_backend = get_database_backend(backend=backend, **settings) + _database = Database(database_backend) + return _database + +@pytest.fixture(scope='session') +def context_service_rest(context_database : Database): # pylint: disable=redefined-outer-name + _rest_server = Server(port=RESTAPI_PORT, base_url=RESTAPI_BASE_URL) + _rest_server.add_resource(Context, '/context', endpoint='api.context', resource_class_args=(context_database,)) + _rest_server.start() + time.sleep(1) # bring time for the server to start + yield _rest_server + _rest_server.shutdown() + _rest_server.join() + +def test_get_topology_completed_rest_api(context_service_rest : Server): # pylint: disable=redefined-outer-name + # should work + request_url = 'http://127.0.0.1:{}{}/context'.format(RESTAPI_PORT, RESTAPI_BASE_URL) + LOGGER.warning('Request: GET {}'.format(str(request_url))) + reply = requests.get(request_url) + LOGGER.warning('Reply: {}'.format(str(reply.text))) + assert reply.status_code == 200, 'Reply failed with code {}'.format(reply.status_code) + json_reply = reply.json() + topology = MessageToDict( + Topology(**json_reply['topologies'][0]), + including_default_value_fields=True, preserving_proto_field_name=True, + use_integers_for_enums=False) + validate_topology(topology) + validate_topology_has_devices(topology) + validate_topology_has_links(topology)