diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ef37446a71297fbfe4994aca039f1bcc38d394bf..7938f7ec1aa82e5d883b7af4bd8c4f6884ebe779 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,6 +4,7 @@ stages: - test - dependencies - deploy + - integration_test # include the individual .gitlab-ci.yml of each micro-service include: @@ -11,3 +12,4 @@ include: #- local: '/src/monitoring/.gitlab-ci.yml' - local: '/src/context/.gitlab-ci.yml' - local: '/src/device/.gitlab-ci.yml' + - local: '/src/integration_tester/.gitlab-ci.yml' diff --git a/run_integration_tests.sh b/run_integration_tests.sh index 0da2a7e00c70546c6170f9dea69e5309647b73d4..b9232c7176ebf0cfb2c49404b0fd6194660c9214 100755 --- a/run_integration_tests.sh +++ b/run_integration_tests.sh @@ -5,3 +5,6 @@ #ENDPOINT=($(kubectl --namespace teraflow-development get service contextservice -o 'jsonpath={.spec.clusterIP} {.spec.ports[?(@.name=="grpc")].port}')) #docker run -it --env TEST_TARGET_ADDRESS=${ENDPOINT[0]} --env TEST_TARGET_PORT=${ENDPOINT[1]} context_service:test + + +kubectl run integration-test --restart=Never --rm -i --tty --image centos -- /bin/bash diff --git a/src/common/tests/Assertions.py b/src/common/tests/Assertions.py index 6cf2f757bb87174882344ff4c762716e217accb3..7e08621f107805dd89978ad380675ef7b547d582 100644 --- a/src/common/tests/Assertions.py +++ b/src/common/tests/Assertions.py @@ -28,3 +28,16 @@ def validate_topology(message): assert 'topoId' in message assert 'device' in message assert 'link' in message + +def validate_topology_is_empty(message): + validate_topology(message) + assert len(message['device']) == 0 + assert len(message['link']) == 0 + +def validate_topology_has_devices(message): + validate_topology(message) + assert len(message['device']) > 0 + +def validate_topology_has_links(message): + validate_topology(message) + assert len(message['link']) > 0 diff --git a/src/context/tests/Dockerfile b/src/context/tests/Dockerfile deleted file mode 100644 index ed70aa86e5c2db5eddb8bf7fc4106bdcc7f8323c..0000000000000000000000000000000000000000 --- a/src/context/tests/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM context:latest - -# Run integration tests -ENTRYPOINT ["pytest", "-v", "--log-level=DEBUG", "context/tests/test_integration.py"] diff --git a/src/context/tests/test_integration.py b/src/context/tests/test_integration.py deleted file mode 100644 index b8d5de420bb9ead65b9fdf7dab3cd5147765b8cc..0000000000000000000000000000000000000000 --- a/src/context/tests/test_integration.py +++ /dev/null @@ -1,24 +0,0 @@ -#import logging, os, pytest, sys -# -#from pathlib import Path -#sys.path.append(__file__.split('src')[0] + 'src') -#print(sys.path) -# -#from context.client.ContextClient import ContextClient -#from context.proto.context_pb2 import Empty -#from .tools.ValidateTopology import validate_topology_dict -# -#LOGGER = logging.getLogger(__name__) -#LOGGER.setLevel(logging.DEBUG) -# -#@pytest.fixture(scope='session') -#def remote_context_client(): -# address = os.environ.get('TEST_TARGET_ADDRESS') -# if(address is None): raise Exception('EnvironmentVariable(TEST_TARGET_ADDRESS) not specified') -# port = os.environ.get('TEST_TARGET_PORT') -# if(port is None): raise Exception('EnvironmentVariable(TEST_TARGET_PORT) not specified') -# return ContextClient(address=address, port=port) -# -#def test_remote_get_topology(remote_context_client): -# response = remote_context_client.GetTopology(Empty()) -# validate_topology_dict(response) diff --git a/src/device/tests/Dockerfile b/src/device/tests/Dockerfile deleted file mode 100644 index e8789ce7442b58063f96970d2f52b8c31b1818c5..0000000000000000000000000000000000000000 --- a/src/device/tests/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM device:latest - -# Run integration tests -ENTRYPOINT ["pytest", "-v", "--log-level=DEBUG", "device/tests/test_integration.py"] diff --git a/src/device/tests/test_integration.py b/src/device/tests/test_integration.py deleted file mode 100644 index 3f1519aba7b1a35972d467279eeb45c2723390b2..0000000000000000000000000000000000000000 --- a/src/device/tests/test_integration.py +++ /dev/null @@ -1,24 +0,0 @@ -#import logging, os, pytest, sys - -#from pathlib import Path -#sys.path.append(__file__.split('src')[0] + 'src') -#print(sys.path) - -#from context.client.ContextClient import ContextClient -#from context.proto.context_pb2 import Empty -#from .tools.ValidateTopology import validate_topology_dict - -#LOGGER = logging.getLogger(__name__) -#LOGGER.setLevel(logging.DEBUG) - -#@pytest.fixture(scope='session') -#def remote_context_client(): -# address = os.environ.get('TEST_TARGET_ADDRESS') -# if(address is None): raise Exception('EnvironmentVariable(TEST_TARGET_ADDRESS) not specified') -# port = os.environ.get('TEST_TARGET_PORT') -# if(port is None): raise Exception('EnvironmentVariable(TEST_TARGET_PORT) not specified') -# return ContextClient(address=address, port=port) - -#def test_remote_get_topology(remote_context_client): -# response = remote_context_client.GetTopology(Empty()) -# validate_topology_dict(response) diff --git a/src/integration_tester/.gitlab-ci.yml b/src/integration_tester/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..fef34bcf8ce20bbd98e5bd613c5571041874ebd2 --- /dev/null +++ b/src/integration_tester/.gitlab-ci.yml @@ -0,0 +1,56 @@ +# Build, tag, and push the Docker images to the GitLab Docker registry +build integration_tester: + variables: + IMAGE_NAME: 'integration_tester' # name of the microservice + IMAGE_TAG: 'latest' # tag of the container image (production, development, etc) + stage: build + before_script: + - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY + script: + - docker build -t "$IMAGE_NAME:$IMAGE_TAG" -f ./src/$IMAGE_NAME/Dockerfile ./src/ + - docker tag "$IMAGE_NAME:$IMAGE_TAG" "$CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG" + - docker push "$CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG" + rules: + - changes: + - src/common/** + - src/context/** + - src/device/** + - src/$IMAGE_NAME/** + - .gitlab-ci.yml + +# Pull, execute, and run unitary tests for the Docker image from the GitLab registry +test integration_tester: + variables: + IMAGE_NAME: 'integration_tester' # name of the microservice + IMAGE_TAG: 'latest' # tag of the container image (production, development, etc) + stage: test + needs: + - build integration_tester + before_script: + - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY + script: + - docker pull "$CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG" + rules: + - changes: + - src/common/** + - src/context/** + - src/device/** + - src/$IMAGE_NAME/** + - .gitlab-ci.yml + +# Run integration tests in Kubernetes Cluster +integration_test integration_tester: + stage: integration_test + needs: + - build integration_tester + - test integration_tester + - deploy context + - deploy device + - dependencies all + - dependencies context_device + script: + - kubectl version + - kubectl get all + - kubectl run $IMAGE_NAME --image "$IMAGE_NAME:$IMAGE_TAG" --restart=Never --rm -i --tty + - kubectl get all + when: manual diff --git a/src/integration_tester/Dockerfile b/src/integration_tester/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..98deb984268f89c0f5beda52457347d00e5d0503 --- /dev/null +++ b/src/integration_tester/Dockerfile @@ -0,0 +1,33 @@ +FROM python:3-slim + +# Install dependencies +RUN apt-get --yes --quiet --quiet update && \ + apt-get --yes --quiet --quiet install wget g++ && \ + rm -rf /var/lib/apt/lists/* + +# Set Python to show logs as they occur +ENV PYTHONUNBUFFERED=0 + +# Get generic Python packages +RUN python3 -m pip install --upgrade pip setuptools wheel pip-tools + +# Set working directory +WORKDIR /var/teraflow + +# Create module sub-folders +RUN mkdir -p /var/teraflow/tests + +# Get Python packages per module +COPY tests/requirements.in tests/requirements.in +RUN pip-compile --output-file=tests/requirements.txt tests/requirements.in +RUN python3 -m pip install -r tests/requirements.in + +# Add files into working directory +COPY common/. common +COPY context/. context +COPY device/. device + +# Run integration tests +ENTRYPOINT ["pytest", "-v", "--log-level=DEBUG", \ + "tests/test_context_device.py" \ +] diff --git a/src/integration_tester/__init__.py b/src/integration_tester/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/integration_tester/definitions.py b/src/integration_tester/definitions.py new file mode 100644 index 0000000000000000000000000000000000000000..ebdc3a43de438722716bf4f8d87eab3165526980 --- /dev/null +++ b/src/integration_tester/definitions.py @@ -0,0 +1,92 @@ +from common.database.api.context.OperationalStatus import OperationalStatus + +TOPOLOGY_ID = { + 'contextId': {'contextUuid': {'uuid': 'admin'}}, + 'topoId': {'uuid': 'admin'} +} + +DEVICE_ID_DEV1 = {'device_id': {'uuid': 'dev1'}} +DEVICE_DEV1 = { + 'device_id': {'device_id': {'uuid': 'dev1'}}, 'device_type': 'ROADM', 'device_config': {'device_config': ''}, + 'devOperationalStatus': OperationalStatus.ENABLED.value, + 'endpointList' : [ + {'port_id': {'topoId': TOPOLOGY_ID, 'dev_id': {'device_id': {'uuid': 'dev1'}}, 'port_id': {'uuid' : 'to-dev2'}}, 'port_type': 'WDM'}, + {'port_id': {'topoId': TOPOLOGY_ID, 'dev_id': {'device_id': {'uuid': 'dev1'}}, 'port_id': {'uuid' : 'to-dev3'}}, 'port_type': 'WDM'}, + ] +} + +DEVICE_ID_DEV2 = {'device_id': {'uuid': 'dev2'}} +DEVICE_DEV2 = { + 'device_id': {'device_id': {'uuid': 'dev2'}}, 'device_type': 'ROADM', 'device_config': {'device_config': ''}, + 'devOperationalStatus': OperationalStatus.ENABLED.value, + 'endpointList' : [ + {'port_id': {'topoId': TOPOLOGY_ID, 'dev_id': {'device_id': {'uuid': 'dev2'}}, 'port_id': {'uuid' : 'to-dev1'}}, 'port_type': 'WDM'}, + {'port_id': {'topoId': TOPOLOGY_ID, 'dev_id': {'device_id': {'uuid': 'dev2'}}, 'port_id': {'uuid' : 'to-dev3'}}, 'port_type': 'WDM'}, + ] +} + +DEVICE_ID_DEV3 = {'device_id': {'uuid': 'dev3'}} +DEVICE_DEV3 = { + 'device_id': {'device_id': {'uuid': 'dev3'}}, + 'device_type': 'ROADM', + 'device_config': {'device_config': ''}, + 'devOperationalStatus': OperationalStatus.ENABLED.value, + 'endpointList' : [ + {'port_id': {'topoId': TOPOLOGY_ID, 'dev_id': {'device_id': {'uuid': 'dev3'}}, 'port_id': {'uuid' : 'to-dev1'}}, 'port_type': 'WDM'}, + {'port_id': {'topoId': TOPOLOGY_ID, 'dev_id': {'device_id': {'uuid': 'dev3'}}, 'port_id': {'uuid' : 'to-dev2'}}, 'port_type': 'WDM'}, + ] +} + +LINK_ID_DEV1_DEV2 = {'link_id': {'uuid': 'dev1/to-dev2 ==> dev2/to-dev1'}} +LINK_DEV1_DEV2 = { + 'link_id': {'link_id': {'uuid': 'dev1/to-dev2 ==> dev2/to-dev1'}}, + 'endpointList' : [ + {'topoId': TOPOLOGY_ID, 'dev_id': {'device_id': {'uuid': 'dev1'}}, 'port_id': {'uuid' : 'to-dev2'}}, + {'topoId': TOPOLOGY_ID, 'dev_id': {'device_id': {'uuid': 'dev2'}}, 'port_id': {'uuid' : 'to-dev1'}}, + ] +} + +LINK_ID_DEV1_DEV3 = {'link_id': {'uuid': 'dev1/to-dev3 ==> dev3/to-dev1'}} +LINK_DEV1_DEV3 = { + 'link_id': {'link_id': {'uuid': 'dev1/to-dev3 ==> dev3/to-dev1'}}, + 'endpointList' : [ + {'topoId': TOPOLOGY_ID, 'dev_id': {'device_id': {'uuid': 'dev1'}}, 'port_id': {'uuid' : 'to-dev3'}}, + {'topoId': TOPOLOGY_ID, 'dev_id': {'device_id': {'uuid': 'dev3'}}, 'port_id': {'uuid' : 'to-dev1'}}, + ] +} + +LINK_ID_DEV2_DEV1 = {'link_id': {'uuid': 'dev2/to-dev1 ==> dev1/to-dev2'}} +LINK_DEV2_DEV1 = { + 'link_id': {'link_id': {'uuid': 'dev2/to-dev1 ==> dev1/to-dev2'}}, + 'endpointList' : [ + {'topoId': TOPOLOGY_ID, 'dev_id': {'device_id': {'uuid': 'dev2'}}, 'port_id': {'uuid' : 'to-dev1'}}, + {'topoId': TOPOLOGY_ID, 'dev_id': {'device_id': {'uuid': 'dev1'}}, 'port_id': {'uuid' : 'to-dev2'}}, + ] +} + +LINK_ID_DEV2_DEV3 = {'link_id': {'uuid': 'dev2/to-dev3 ==> dev3/to-dev2'}} +LINK_DEV2_DEV3 = { + 'link_id': {'link_id': {'uuid': 'dev2/to-dev3 ==> dev3/to-dev2'}}, + 'endpointList' : [ + {'topoId': TOPOLOGY_ID, 'dev_id': {'device_id': {'uuid': 'dev2'}}, 'port_id': {'uuid' : 'to-dev3'}}, + {'topoId': TOPOLOGY_ID, 'dev_id': {'device_id': {'uuid': 'dev3'}}, 'port_id': {'uuid' : 'to-dev2'}}, + ] +} + +LINK_ID_DEV3_DEV1 = {'link_id': {'uuid': 'dev3/to-dev1 ==> dev1/to-dev3'}} +LINK_DEV3_DEV1 = { + 'link_id': {'link_id': {'uuid': 'dev3/to-dev1 ==> dev1/to-dev3'}}, + 'endpointList' : [ + {'topoId': TOPOLOGY_ID, 'dev_id': {'device_id': {'uuid': 'dev3'}}, 'port_id': {'uuid' : 'to-dev1'}}, + {'topoId': TOPOLOGY_ID, 'dev_id': {'device_id': {'uuid': 'dev1'}}, 'port_id': {'uuid' : 'to-dev3'}}, + ] +} + +LINK_ID_DEV3_DEV2 = {'link_id': {'uuid': 'dev3/to-dev2 ==> dev2/to-dev3'}} +LINK_DEV3_DEV2 = { + 'link_id': {'link_id': {'uuid': 'dev3/to-dev2 ==> dev2/to-dev3'}}, + 'endpointList' : [ + {'topoId': TOPOLOGY_ID, 'dev_id': {'device_id': {'uuid': 'dev3'}}, 'port_id': {'uuid' : 'to-dev2'}}, + {'topoId': TOPOLOGY_ID, 'dev_id': {'device_id': {'uuid': 'dev2'}}, 'port_id': {'uuid' : 'to-dev3'}}, + ] +} diff --git a/src/integration_tester/requirements.in b/src/integration_tester/requirements.in new file mode 100644 index 0000000000000000000000000000000000000000..25abdad1b5767117956a88b816399635348884c7 --- /dev/null +++ b/src/integration_tester/requirements.in @@ -0,0 +1,6 @@ +grpcio-health-checking +grpcio +prometheus-client +pytest +pytest-benchmark +redis diff --git a/src/integration_tester/test_context_device.py b/src/integration_tester/test_context_device.py new file mode 100644 index 0000000000000000000000000000000000000000..a0eaa1fff4bcb7139c474aec6e97ea63aa377626 --- /dev/null +++ b/src/integration_tester/test_context_device.py @@ -0,0 +1,79 @@ +import logging, os, pytest +from google.protobuf.json_format import MessageToDict +from src.common.tests.Assertions import validate_device_id, validate_link_id, validate_topology_has_devices, \ + validate_topology_has_links, validate_topology_is_empty +from common.database.Factory import get_database, DatabaseEngineEnum +from common.database.api.Database import Database +from context.client.ContextClient import ContextClient +from context.proto.context_pb2 import Device, Empty, Link +from device.client.DeviceClient import DeviceClient +from .definitions import DEVICE_DEV1, DEVICE_DEV2, DEVICE_DEV3, LINK_DEV1_DEV2, LINK_DEV1_DEV3, LINK_DEV2_DEV1, \ + LINK_DEV2_DEV3, LINK_DEV3_DEV1, LINK_DEV3_DEV2 + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +def get_setting(name): + value = os.environ.get(name) + if value is None: raise Exception('Unable to find variable({})'.format(name)) + return value + +@pytest.fixture(scope='session') +def redis_database(): + _database = get_database(engine=DatabaseEngineEnum.REDIS) + return _database + +@pytest.fixture(scope='session') +def context_client(): + service_host = get_setting('CONTEXTSERVICE_SERVICE_HOST') + service_port = get_setting('CONTEXTSERVICE_SERVICE_PORT_GRPC') + _client = ContextClient(address=service_host, port=service_port) + yield _client + _client.close() + +@pytest.fixture(scope='session') +def device_client(): + service_host = get_setting('DEVICESERVICE_SERVICE_HOST') + service_port = get_setting('DEVICESERVICE_SERVICE_PORT_GRPC') + _client = DeviceClient(address=service_host, port=service_port) + yield _client + _client.close() + +def test_clean_database(redis_database : Database): + # should work + redis_database.clear_all() + +def test_get_topology_empty(context_client : ContextClient): + # should work + validate_topology_is_empty(MessageToDict( + context_client.GetTopology(Empty()), + including_default_value_fields=True, preserving_proto_field_name=True, + use_integers_for_enums=False)) + +def test_add_devices(device_client : DeviceClient): + # should work + for device in [DEVICE_DEV1, DEVICE_DEV2, DEVICE_DEV3]: + validate_device_id(MessageToDict( + device_client.AddDevice(Device(**device)), + including_default_value_fields=True, preserving_proto_field_name=True, + use_integers_for_enums=False)) + + # should work + validate_topology_has_devices(MessageToDict( + context_client.GetTopology(Empty()), + including_default_value_fields=True, preserving_proto_field_name=True, + use_integers_for_enums=False)) + +def test_add_links(context_client : ContextClient): + # should work + for link in [LINK_DEV1_DEV2, LINK_DEV1_DEV3, LINK_DEV2_DEV1, LINK_DEV2_DEV3, LINK_DEV3_DEV1, LINK_DEV3_DEV2]: + validate_link_id(MessageToDict( + context_client.AddLink(Link(**link)), + including_default_value_fields=True, preserving_proto_field_name=True, + use_integers_for_enums=False)) + + # should work + validate_topology_has_links(MessageToDict( + context_client.GetTopology(Empty()), + including_default_value_fields=True, preserving_proto_field_name=True, + use_integers_for_enums=False))