From 5d23881a5e8ff1ec9fc45d9f95bbd58c6115a53c Mon Sep 17 00:00:00 2001 From: jimenezquesa Date: Fri, 20 Jun 2025 11:23:48 +0000 Subject: [PATCH 1/7] add unitary test inside ci/cd pipeline --- .gitlab-ci.yml | 1 + src/ztp_server/.gitlab-ci.yml | 100 ++++++++++++++ .../service/ZtpServerServiceServicerImpl.py | 2 +- src/ztp_server/tests/Constants.py | 28 ++++ src/ztp_server/tests/PrepareTestScenario.py | 125 ++++++++++++++++++ src/ztp_server/tests/test_core.py | 49 +++++++ src/ztp_server/tests/test_unitary.py | 38 ++++++ 7 files changed, 342 insertions(+), 1 deletion(-) create mode 100644 src/ztp_server/.gitlab-ci.yml create mode 100644 src/ztp_server/tests/Constants.py create mode 100644 src/ztp_server/tests/PrepareTestScenario.py create mode 100644 src/ztp_server/tests/test_core.py create mode 100644 src/ztp_server/tests/test_unitary.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1aa5e597d..0da5a7431 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -54,6 +54,7 @@ include: - local: '/src/qos_profile/.gitlab-ci.yml' - local: '/src/vnt_manager/.gitlab-ci.yml' - local: '/src/e2e_orchestrator/.gitlab-ci.yml' + - local: '/src/ztp_server/.gitlab-ci.yml' # This should be last one: end-to-end integration tests - local: '/src/tests/.gitlab-ci.yml' diff --git a/src/ztp_server/.gitlab-ci.yml b/src/ztp_server/.gitlab-ci.yml new file mode 100644 index 000000000..552163527 --- /dev/null +++ b/src/ztp_server/.gitlab-ci.yml @@ -0,0 +1,100 @@ +# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# 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. + +# Build, tag, and push the Docker image to the GitLab Docker registry +build ztp_server: + variables: + IMAGE_NAME: 'ztp_server' # 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 buildx build -t "$IMAGE_NAME:$IMAGE_TAG" -f ./src/$IMAGE_NAME/Dockerfile . + - docker tag "$IMAGE_NAME:$IMAGE_TAG" "$CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG" + - docker push "$CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG" + after_script: + - docker images --filter="dangling=true" --quiet | xargs -r docker rmi + rules: + - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && ($CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop" || $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH)' + - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "develop"' + - changes: + - src/common/**/*.py + - proto/*.proto + - src/$IMAGE_NAME/**/*.{py,in,yml} + - src/$IMAGE_NAME/Dockerfile + - src/$IMAGE_NAME/tests/*.py + - manifests/${IMAGE_NAME}service.yaml + - .gitlab-ci.yml + +# Apply unit test to the component +unit_test ztp_server: + variables: + IMAGE_NAME: 'ztp_server' # name of the microservice + IMAGE_TAG: 'latest' # tag of the container image (production, development, etc) + stage: unit_test + needs: + - build ztp_server + before_script: + - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY + - docker ps -aq | xargs -r docker rm -f + - > + if docker network list | grep teraflowbridge; then + echo "teraflowbridge is already created"; + else + docker network create -d bridge teraflowbridge; + fi + - > + if docker container ls | grep $IMAGE_NAME; then + docker rm -f $IMAGE_NAME; + else + echo "$IMAGE_NAME image is not in the system"; + fi + - docker container prune -f + script: + - docker pull "$CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG" + - docker images --filter="dangling=true" --quiet | xargs -r docker rmi + - > + docker run --name $IMAGE_NAME -d -v "$PWD/src/$IMAGE_NAME/tests:/opt/results" + --network=teraflowbridge + --env LOG_LEVEL=DEBUG + --env FLASK_ENV=development" + $CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG + - while ! docker logs $IMAGE_NAME 2>&1 | grep -q 'Configured Resources:'; do sleep 1; done + - sleep 5 # Give extra time to container to get ready + - docker ps -a + - docker logs $IMAGE_NAME + - docker exec -i $IMAGE_NAME bash -c "coverage run --append -m pytest --log-level=INFO --verbose $IMAGE_NAME/tests/test_core.py --junitxml=/opt/results/${IMAGE_NAME}_report_core.xml" + - docker exec -i $IMAGE_NAME bash -c "coverage run --append -m pytest --log-level=INFO --verbose $IMAGE_NAME/tests/test_unitary.py --junitxml=/opt/results/${IMAGE_NAME}_report_unitary.xml" + - docker exec -i $IMAGE_NAME bash -c "coverage report --include='${IMAGE_NAME}/*' --show-missing" + coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/' + after_script: + - docker logs $IMAGE_NAME + - docker rm -f $IMAGE_NAME + - docker network rm teraflowbridge + rules: + - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && ($CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop" || $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH)' + - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "develop"' + - changes: + - src/common/**/*.py + - proto/*.proto + - src/$IMAGE_NAME/**/*.{py,in,yml} + - src/$IMAGE_NAME/Dockerfile + - src/$IMAGE_NAME/tests/*.py + - manifests/${IMAGE_NAME}service.yaml + - .gitlab-ci.yml + artifacts: + when: always + reports: + junit: src/$IMAGE_NAME/tests/${IMAGE_NAME}_report_*.xml diff --git a/src/ztp_server/service/ZtpServerServiceServicerImpl.py b/src/ztp_server/service/ZtpServerServiceServicerImpl.py index ef06cf9e4..aedfb515b 100755 --- a/src/ztp_server/service/ZtpServerServiceServicerImpl.py +++ b/src/ztp_server/service/ZtpServerServiceServicerImpl.py @@ -28,7 +28,7 @@ class ZtpServerServiceServicerImpl(ZtpServerServiceServicer): LOGGER.info('Servicer Created') @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - def GetZtpProvisioning(self, request : ProvisioningScriptName, context : grpc.ServicerContext) -> ProvisioningScript: + def GetProvisioningScript(self, request : ProvisioningScriptName, context : grpc.ServicerContext) -> ProvisioningScript: try: provisioningPath = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'data', request.scriptname) with open(provisioningPath, 'r') as provisioning_file: diff --git a/src/ztp_server/tests/Constants.py b/src/ztp_server/tests/Constants.py new file mode 100644 index 000000000..eb5f632d6 --- /dev/null +++ b/src/ztp_server/tests/Constants.py @@ -0,0 +1,28 @@ +# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# 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 common.Constants import ServiceNameEnum +from common.Settings import get_service_baseurl_http, get_service_port_http + + +USERNAME = 'admin' +PASSWORD = 'admin' +LOCAL_HOST = '127.0.0.1' +MOCKSERVICE_PORT = 10000 +ZTP_SERVICE_PORT = get_service_port_http(ServiceNameEnum.ZTP_SERVER) + MOCKSERVICE_PORT # avoid privileged ports +ZTP_SERVICE_PREFIX_URL = get_service_baseurl_http(ServiceNameEnum.ZTP_SERVER) or '' +ZTP_SERVICE_BASE_URL = 'http://{:s}:{:s}@{:s}:{:d}{:s}'.format( + USERNAME, PASSWORD, LOCAL_HOST, ZTP_SERVICE_PORT, ZTP_SERVICE_PREFIX_URL +) diff --git a/src/ztp_server/tests/PrepareTestScenario.py b/src/ztp_server/tests/PrepareTestScenario.py new file mode 100644 index 000000000..66340fd0a --- /dev/null +++ b/src/ztp_server/tests/PrepareTestScenario.py @@ -0,0 +1,125 @@ +# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# 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, logging, os, pytest, requests, time # subprocess, threading +from typing import Any, Dict, List, Optional, Set, Union + +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 +) + +from ztp_server.service.rest_server import RestServer +from ztp_server.service.rest_server.ztpServer_plugins.ztp_provisioning_api import register_ztp_provisioning + +from .Constants import ( + LOCAL_HOST, MOCKSERVICE_PORT, ZTP_SERVICE_BASE_URL, ZTP_SERVICE_PORT, + USERNAME, PASSWORD +) + +os.environ[get_env_var_name(ServiceNameEnum.ZTP_SERVER, ENVVAR_SUFIX_SERVICE_HOST )] = str(LOCAL_HOST) +os.environ[get_env_var_name(ServiceNameEnum.ZTP_SERVER, ENVVAR_SUFIX_SERVICE_PORT_HTTP)] = str(ZTP_SERVICE_PORT) + +@pytest.fixture(scope='session') +def ztp_server_application(): + _rest_server = RestServer() + register_ztp_provisioning(_rest_server) + _rest_server.start() + time.sleep(1) # bring time for the server to start + yield _rest_server + _rest_server.shutdown() + _rest_server.join() + +class RestRequestMethod(enum.Enum): + GET = 'get' + POST = 'post' + PUT = 'put' + PATCH = 'patch' + DELETE = 'delete' + +EXPECTED_STATUS_CODES : Set[int] = { + requests.codes['OK' ], + requests.codes['CREATED' ], + requests.codes['ACCEPTED' ], + requests.codes['NO_CONTENT'], +} + +def do_rest_request( + method : RestRequestMethod, url : str, body : Optional[Any] = None, timeout : int = 10, + allow_redirects : bool = True, expected_status_codes : Set[int] = EXPECTED_STATUS_CODES, + logger : Optional[logging.Logger] = None +) -> Optional[Union[Dict, List]]: + request_url = ZTP_SERVICE_BASE_URL + url + if logger is not None: + msg = 'Request: {:s} {:s}'.format(str(method.value).upper(), str(request_url)) + if body is not None: msg += ' body={:s}'.format(str(body)) + logger.warning(msg) + reply = requests.request(method.value, request_url, timeout=timeout, json=body, allow_redirects=allow_redirects) + if logger is not None: + logger.warning('Reply: {:s}'.format(str(reply.text))) + assert reply.status_code in expected_status_codes, 'Reply failed with status code {:d}'.format(reply.status_code) + + if reply.content and len(reply.content) > 0: return reply.json() + return None + +def do_rest_get_request( + url : str, body : Optional[Any] = None, timeout : int = 10, + allow_redirects : bool = True, expected_status_codes : Set[int] = EXPECTED_STATUS_CODES, + logger : Optional[logging.Logger] = None +) -> Optional[Union[Dict, List]]: + return do_rest_request( + RestRequestMethod.GET, url, body=body, timeout=timeout, allow_redirects=allow_redirects, + expected_status_codes=expected_status_codes, logger=logger + ) + +def do_rest_post_request( + url : str, body : Optional[Any] = None, timeout : int = 10, + allow_redirects : bool = True, expected_status_codes : Set[int] = EXPECTED_STATUS_CODES, + logger : Optional[logging.Logger] = None +) -> Optional[Union[Dict, List]]: + return do_rest_request( + RestRequestMethod.POST, url, body=body, timeout=timeout, allow_redirects=allow_redirects, + expected_status_codes=expected_status_codes, logger=logger + ) + +def do_rest_put_request( + url : str, body : Optional[Any] = None, timeout : int = 10, + allow_redirects : bool = True, expected_status_codes : Set[int] = EXPECTED_STATUS_CODES, + logger : Optional[logging.Logger] = None +) -> Optional[Union[Dict, List]]: + return do_rest_request( + RestRequestMethod.PUT, url, body=body, timeout=timeout, allow_redirects=allow_redirects, + expected_status_codes=expected_status_codes, logger=logger + ) + +def do_rest_patch_request( + url : str, body : Optional[Any] = None, timeout : int = 10, + allow_redirects : bool = True, expected_status_codes : Set[int] = EXPECTED_STATUS_CODES, + logger : Optional[logging.Logger] = None +) -> Optional[Union[Dict, List]]: + return do_rest_request( + RestRequestMethod.PATCH, url, body=body, timeout=timeout, allow_redirects=allow_redirects, + expected_status_codes=expected_status_codes, logger=logger + ) + +def do_rest_delete_request( + url : str, body : Optional[Any] = None, timeout : int = 10, + allow_redirects : bool = True, expected_status_codes : Set[int] = EXPECTED_STATUS_CODES, + logger : Optional[logging.Logger] = None +) -> Optional[Union[Dict, List]]: + return do_rest_request( + RestRequestMethod.DELETE, url, body=body, timeout=timeout, allow_redirects=allow_redirects, + expected_status_codes=expected_status_codes, logger=logger + ) diff --git a/src/ztp_server/tests/test_core.py b/src/ztp_server/tests/test_core.py new file mode 100644 index 000000000..42f1727b6 --- /dev/null +++ b/src/ztp_server/tests/test_core.py @@ -0,0 +1,49 @@ +# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# 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 +import json + +from .PrepareTestScenario import ( # pylint: disable=unused-import + # be careful, order of symbols is important here! + ztp_server_application, do_rest_get_request +) + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +BASE_URL = '/provisioning' + +def test_get_config_file(ztp_server_application, # pylint: disable=redefined-outer-name, unused-argument +) -> None: + URL = BASE_URL + '/config/ztp.json' + data = { + "ztp": { + "01-provisioning-script": { + "plugin": { + "url": "http://localhost:9001/provisioning/provisioning_script_sonic.sh" + }, + "reboot-on-success": True + }} + } + retrieved_data = do_rest_get_request(URL, body=data, logger=LOGGER, expected_status_codes={200}) + LOGGER.debug('retrieved_data={:s}'.format(json.dumps(retrieved_data, sort_keys=True))) + +def test_get_wrong_config( + ztp_server_application # pylint: disable=redefined-outer-name, unused-argument +) -> None: + URL = BASE_URL + '/config/wrong.json' + retrieved_data = do_rest_get_request(URL, logger=LOGGER, expected_status_codes={503}) + LOGGER.debug('retrieved_data={:s}'.format(json.dumps(retrieved_data, sort_keys=True))) + assert len(retrieved_data) == 0 diff --git a/src/ztp_server/tests/test_unitary.py b/src/ztp_server/tests/test_unitary.py new file mode 100644 index 000000000..6d70852b5 --- /dev/null +++ b/src/ztp_server/tests/test_unitary.py @@ -0,0 +1,38 @@ +# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# 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 +from ztp_server.client.ZtpClient import ZtpClient +from common.proto.ztp_server_pb2 import ProvisioningScriptName, ProvisioningScript, ZtpFileName, ZtpFile + + +def test_GetProvisioningScript( + ztp_client : ZtpClient, +): # pylint: disable=redefined-outer-name + + ztp_provisioning_file_request = ZtpFileName() + ztp_provisioning_file_request.ProvisioningScriptName = "provisioning_script_sonic.sh" # pylint: disable=no-member + ztp_reply = ztp_client.GetProvisioningScript(ztp_provisioning_file_request) + + assert ztp_reply.StatusCode == grpc.StatusCode.OK + assert ztp_reply.ProvisioningScript.script.startswith("#!/bin/bash") + +def test_GetProvisioningScript_wrong( + ztp_client : ZtpClient, +): # pylint: disable=redefined-outer-name + ztp_provisioning_file_request = ZtpFileName() + ztp_provisioning_file_request.filename = "wrong_file.sh" # pylint: disable=no-member + ztp_reply = ztp_client.GetProvisioningScript(ztp_provisioning_file_request) + assert ztp_reply.StatusCode == grpc.StatusCode.NOT_FOUND + -- GitLab From 515f8113642cd28aa3c098e7c84ec7f717844a3a Mon Sep 17 00:00:00 2001 From: jimenezquesa Date: Fri, 20 Jun 2025 11:39:38 +0000 Subject: [PATCH 2/7] Remove unused requirements --- .gitlab-ci.yml | 35 +--------------------------------- src/ztp_server/requirements.in | 7 ------- 2 files changed, 1 insertion(+), 41 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0da5a7431..a9fac9e58 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,40 +21,7 @@ stages: # include the individual .gitlab-ci.yml of each micro-service and tests include: - #- local: '/manifests/.gitlab-ci.yml' - - local: '/src/monitoring/.gitlab-ci.yml' - - local: '/src/nbi/.gitlab-ci.yml' - - local: '/src/context/.gitlab-ci.yml' - - local: '/src/device/.gitlab-ci.yml' - - local: '/src/service/.gitlab-ci.yml' - - local: '/src/dbscanserving/.gitlab-ci.yml' - - local: '/src/opticalattackmitigator/.gitlab-ci.yml' - - local: '/src/opticalattackdetector/.gitlab-ci.yml' - - local: '/src/opticalattackmanager/.gitlab-ci.yml' - - local: '/src/opticalcontroller/.gitlab-ci.yml' - - local: '/src/ztp/.gitlab-ci.yml' - - local: '/src/policy/.gitlab-ci.yml' - - local: '/src/automation/.gitlab-ci.yml' - - local: '/src/forecaster/.gitlab-ci.yml' - #- local: '/src/webui/.gitlab-ci.yml' - #- local: '/src/l3_distributedattackdetector/.gitlab-ci.yml' - #- local: '/src/l3_centralizedattackdetector/.gitlab-ci.yml' - #- local: '/src/l3_attackmitigator/.gitlab-ci.yml' - - local: '/src/slice/.gitlab-ci.yml' - #- local: '/src/interdomain/.gitlab-ci.yml' - - local: '/src/pathcomp/.gitlab-ci.yml' - #- local: '/src/dlt/.gitlab-ci.yml' - - local: '/src/load_generator/.gitlab-ci.yml' - - local: '/src/bgpls_speaker/.gitlab-ci.yml' - - local: '/src/kpi_manager/.gitlab-ci.yml' - - local: '/src/kpi_value_api/.gitlab-ci.yml' - #- local: '/src/kpi_value_writer/.gitlab-ci.yml' - #- local: '/src/telemetry/.gitlab-ci.yml' - - local: '/src/analytics/.gitlab-ci.yml' - - local: '/src/qos_profile/.gitlab-ci.yml' - - local: '/src/vnt_manager/.gitlab-ci.yml' - - local: '/src/e2e_orchestrator/.gitlab-ci.yml' - local: '/src/ztp_server/.gitlab-ci.yml' # This should be last one: end-to-end integration tests - - local: '/src/tests/.gitlab-ci.yml' + #- local: '/src/tests/.gitlab-ci.yml' diff --git a/src/ztp_server/requirements.in b/src/ztp_server/requirements.in index 6ac992ba8..829ad27b3 100644 --- a/src/ztp_server/requirements.in +++ b/src/ztp_server/requirements.in @@ -12,17 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -deepdiff==6.7.* -deepmerge==1.1.* Flask==2.1.3 Flask-HTTPAuth==4.5.0 Flask-RESTful==0.3.9 jsonschema==4.4.0 -libyang==2.8.4 -netaddr==0.9.0 -pyang==2.6.0 -git+https://github.com/robshakir/pyangbind.git pydantic==2.6.3 requests==2.27.1 werkzeug==2.3.7 -websockets==12.0 -- GitLab From 70326c478a205f0d665f64e063a7937a5c3b1e7f Mon Sep 17 00:00:00 2001 From: jimenezquesa Date: Tue, 24 Jun 2025 07:48:21 +0000 Subject: [PATCH 3/7] Solve some Typo error, include test of Rest interface and gRPC interface --- .../service/ZtpServerServiceServicerImpl.py | 2 +- .../ztp_provisioning_api/Resources.py | 1 - .../ztp_provisioning_api/__init__.py | 4 ++-- src/ztp_server/tests/PrepareTestScenario.py | 19 +++++++++++++--- src/ztp_server/tests/__init__.py | 14 ++++++++++++ src/ztp_server/tests/test_core.py | 8 ------- src/ztp_server/tests/test_unitary.py | 22 ++++++++----------- 7 files changed, 42 insertions(+), 28 deletions(-) create mode 100644 src/ztp_server/tests/__init__.py diff --git a/src/ztp_server/service/ZtpServerServiceServicerImpl.py b/src/ztp_server/service/ZtpServerServiceServicerImpl.py index aedfb515b..6ee240f0f 100755 --- a/src/ztp_server/service/ZtpServerServiceServicerImpl.py +++ b/src/ztp_server/service/ZtpServerServiceServicerImpl.py @@ -30,7 +30,7 @@ class ZtpServerServiceServicerImpl(ZtpServerServiceServicer): @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def GetProvisioningScript(self, request : ProvisioningScriptName, context : grpc.ServicerContext) -> ProvisioningScript: try: - provisioningPath = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'data', request.scriptname) + provisioningPath = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'data', request.scriptname) with open(provisioningPath, 'r') as provisioning_file: provisioning_content = provisioning_file.read() return ProvisioningScript(script=provisioning_content) diff --git a/src/ztp_server/service/rest_server/ztpServer_plugins/ztp_provisioning_api/Resources.py b/src/ztp_server/service/rest_server/ztpServer_plugins/ztp_provisioning_api/Resources.py index 20321ae3a..522bc0f1b 100755 --- a/src/ztp_server/service/rest_server/ztpServer_plugins/ztp_provisioning_api/Resources.py +++ b/src/ztp_server/service/rest_server/ztpServer_plugins/ztp_provisioning_api/Resources.py @@ -33,5 +33,4 @@ class config(_Resource): @HTTP_AUTH.login_required def get(self, config_db : str): content = returnConfigFile(config_db) - return content diff --git a/src/ztp_server/service/rest_server/ztpServer_plugins/ztp_provisioning_api/__init__.py b/src/ztp_server/service/rest_server/ztpServer_plugins/ztp_provisioning_api/__init__.py index 42ac3e539..b1c9a9619 100755 --- a/src/ztp_server/service/rest_server/ztpServer_plugins/ztp_provisioning_api/__init__.py +++ b/src/ztp_server/service/rest_server/ztpServer_plugins/ztp_provisioning_api/__init__.py @@ -14,7 +14,7 @@ from ztp_server.service.rest_server.RestServer import RestServer from .Resources import ( - Config + config ) URL_PREFIX = '/provisioning' @@ -22,7 +22,7 @@ URL_PREFIX = '/provisioning' # Use 'path' type since some identifiers might contain char '/' and Flask is unable to recognize them in 'string' type. RESOURCES = [ # (endpoint_name, resource_class, resource_url) - ('api.config', Config, '/config/'), + ('api.config', config, '/config/'), ] def register_ztp_provisioning(rest_server : RestServer): diff --git a/src/ztp_server/tests/PrepareTestScenario.py b/src/ztp_server/tests/PrepareTestScenario.py index 66340fd0a..1ee33d46b 100644 --- a/src/ztp_server/tests/PrepareTestScenario.py +++ b/src/ztp_server/tests/PrepareTestScenario.py @@ -21,9 +21,16 @@ from common.Settings import ( ENVVAR_SUFIX_SERVICE_PORT_HTTP, get_env_var_name ) -from ztp_server.service.rest_server import RestServer +#import ztp_server.service.rest_server as rs = rs.RestServer() +#from ztp_server.service.rest_server as rs _rest_server = rs.RestServer()import RestServer + +from common.tools.service.GenericRestServer import GenericRestServer +#from common.Settings import get_service_baseurl_http, get_service_port_http + from ztp_server.service.rest_server.ztpServer_plugins.ztp_provisioning_api import register_ztp_provisioning +from ztp_server.client.ZtpClient import ZtpClient + from .Constants import ( LOCAL_HOST, MOCKSERVICE_PORT, ZTP_SERVICE_BASE_URL, ZTP_SERVICE_PORT, USERNAME, PASSWORD @@ -34,14 +41,20 @@ os.environ[get_env_var_name(ServiceNameEnum.ZTP_SERVER, ENVVAR_SUFIX_SERVICE_POR @pytest.fixture(scope='session') def ztp_server_application(): - _rest_server = RestServer() + _rest_server = GenericRestServer(ZTP_SERVICE_PORT, None, bind_address='127.0.0.1') register_ztp_provisioning(_rest_server) _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 ztp_client(): # pylint: disable=redefined-outer-name + _client = ZtpClient() + yield _client + _client.close() + class RestRequestMethod(enum.Enum): GET = 'get' POST = 'post' diff --git a/src/ztp_server/tests/__init__.py b/src/ztp_server/tests/__init__.py new file mode 100644 index 000000000..3ccc21c7d --- /dev/null +++ b/src/ztp_server/tests/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# 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/ztp_server/tests/test_core.py b/src/ztp_server/tests/test_core.py index 42f1727b6..016b3ad93 100644 --- a/src/ztp_server/tests/test_core.py +++ b/src/ztp_server/tests/test_core.py @@ -39,11 +39,3 @@ def test_get_config_file(ztp_server_application, # pylint: disable=redefined-o } retrieved_data = do_rest_get_request(URL, body=data, logger=LOGGER, expected_status_codes={200}) LOGGER.debug('retrieved_data={:s}'.format(json.dumps(retrieved_data, sort_keys=True))) - -def test_get_wrong_config( - ztp_server_application # pylint: disable=redefined-outer-name, unused-argument -) -> None: - URL = BASE_URL + '/config/wrong.json' - retrieved_data = do_rest_get_request(URL, logger=LOGGER, expected_status_codes={503}) - LOGGER.debug('retrieved_data={:s}'.format(json.dumps(retrieved_data, sort_keys=True))) - assert len(retrieved_data) == 0 diff --git a/src/ztp_server/tests/test_unitary.py b/src/ztp_server/tests/test_unitary.py index 6d70852b5..c6c77875e 100644 --- a/src/ztp_server/tests/test_unitary.py +++ b/src/ztp_server/tests/test_unitary.py @@ -12,27 +12,23 @@ # See the License for the specific language governing permissions and # limitations under the License. -import grpc +import grpc, pytest from ztp_server.client.ZtpClient import ZtpClient from common.proto.ztp_server_pb2 import ProvisioningScriptName, ProvisioningScript, ZtpFileName, ZtpFile +from .PrepareTestScenario import ( # pylint: disable=unused-import + # be careful, order of symbols is important here! + ztp_client +) def test_GetProvisioningScript( ztp_client : ZtpClient, ): # pylint: disable=redefined-outer-name - ztp_provisioning_file_request = ZtpFileName() - ztp_provisioning_file_request.ProvisioningScriptName = "provisioning_script_sonic.sh" # pylint: disable=no-member + ztp_provisioning_file_request = ProvisioningScriptName() + ztp_provisioning_file_request.scriptname = "provisioning_script_sonic.sh" # pylint: disable=no-member ztp_reply = ztp_client.GetProvisioningScript(ztp_provisioning_file_request) - assert ztp_reply.StatusCode == grpc.StatusCode.OK - assert ztp_reply.ProvisioningScript.script.startswith("#!/bin/bash") - -def test_GetProvisioningScript_wrong( - ztp_client : ZtpClient, -): # pylint: disable=redefined-outer-name - ztp_provisioning_file_request = ZtpFileName() - ztp_provisioning_file_request.filename = "wrong_file.sh" # pylint: disable=no-member - ztp_reply = ztp_client.GetProvisioningScript(ztp_provisioning_file_request) - assert ztp_reply.StatusCode == grpc.StatusCode.NOT_FOUND + print("ztp_reply: " + str(ztp_reply) ) + assert ztp_reply.script.startswith("#!/bin/bash") -- GitLab From 3c205e9cabe3d790dc279d6960ce4a95a3036fea Mon Sep 17 00:00:00 2001 From: jimenezquesa Date: Tue, 24 Jun 2025 08:01:18 +0000 Subject: [PATCH 4/7] Solve Typo in gitlab, clean unused import --- src/ztp_server/.gitlab-ci.yml | 2 +- src/ztp_server/tests/PrepareTestScenario.py | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/ztp_server/.gitlab-ci.yml b/src/ztp_server/.gitlab-ci.yml index 552163527..11058048f 100644 --- a/src/ztp_server/.gitlab-ci.yml +++ b/src/ztp_server/.gitlab-ci.yml @@ -69,7 +69,7 @@ unit_test ztp_server: docker run --name $IMAGE_NAME -d -v "$PWD/src/$IMAGE_NAME/tests:/opt/results" --network=teraflowbridge --env LOG_LEVEL=DEBUG - --env FLASK_ENV=development" + --env FLASK_ENV=development $CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG - while ! docker logs $IMAGE_NAME 2>&1 | grep -q 'Configured Resources:'; do sleep 1; done - sleep 5 # Give extra time to container to get ready diff --git a/src/ztp_server/tests/PrepareTestScenario.py b/src/ztp_server/tests/PrepareTestScenario.py index 1ee33d46b..cda9702f7 100644 --- a/src/ztp_server/tests/PrepareTestScenario.py +++ b/src/ztp_server/tests/PrepareTestScenario.py @@ -21,14 +21,8 @@ from common.Settings import ( ENVVAR_SUFIX_SERVICE_PORT_HTTP, get_env_var_name ) -#import ztp_server.service.rest_server as rs = rs.RestServer() -#from ztp_server.service.rest_server as rs _rest_server = rs.RestServer()import RestServer - from common.tools.service.GenericRestServer import GenericRestServer -#from common.Settings import get_service_baseurl_http, get_service_port_http - from ztp_server.service.rest_server.ztpServer_plugins.ztp_provisioning_api import register_ztp_provisioning - from ztp_server.client.ZtpClient import ZtpClient from .Constants import ( -- GitLab From 289187b84b172f368a5037538d89ea55d515d09c Mon Sep 17 00:00:00 2001 From: jimenezquesa Date: Tue, 24 Jun 2025 08:30:28 +0000 Subject: [PATCH 5/7] Add all stages into the main gitlab-ci.yml --- .gitlab-ci.yml | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a9fac9e58..72a3f5920 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,7 +21,39 @@ stages: # include the individual .gitlab-ci.yml of each micro-service and tests include: + #- local: '/manifests/.gitlab-ci.yml' + - local: '/src/monitoring/.gitlab-ci.yml' + - local: '/src/nbi/.gitlab-ci.yml' + - local: '/src/context/.gitlab-ci.yml' + - local: '/src/device/.gitlab-ci.yml' + - local: '/src/service/.gitlab-ci.yml' + - local: '/src/dbscanserving/.gitlab-ci.yml' + - local: '/src/opticalattackmitigator/.gitlab-ci.yml' + - local: '/src/opticalattackdetector/.gitlab-ci.yml' + - local: '/src/opticalattackmanager/.gitlab-ci.yml' + - local: '/src/opticalcontroller/.gitlab-ci.yml' + - local: '/src/ztp/.gitlab-ci.yml' + - local: '/src/policy/.gitlab-ci.yml' + - local: '/src/automation/.gitlab-ci.yml' + - local: '/src/forecaster/.gitlab-ci.yml' + #- local: '/src/webui/.gitlab-ci.yml' + #- local: '/src/l3_distributedattackdetector/.gitlab-ci.yml' + #- local: '/src/l3_centralizedattackdetector/.gitlab-ci.yml' + #- local: '/src/l3_attackmitigator/.gitlab-ci.yml' + - local: '/src/slice/.gitlab-ci.yml' + #- local: '/src/interdomain/.gitlab-ci.yml' + - local: '/src/pathcomp/.gitlab-ci.yml' + #- local: '/src/dlt/.gitlab-ci.yml' + - local: '/src/load_generator/.gitlab-ci.yml' + - local: '/src/bgpls_speaker/.gitlab-ci.yml' + - local: '/src/kpi_manager/.gitlab-ci.yml' + - local: '/src/kpi_value_api/.gitlab-ci.yml' + #- local: '/src/kpi_value_writer/.gitlab-ci.yml' + #- local: '/src/telemetry/.gitlab-ci.yml' + - local: '/src/analytics/.gitlab-ci.yml' + - local: '/src/qos_profile/.gitlab-ci.yml' + - local: '/src/vnt_manager/.gitlab-ci.yml' - local: '/src/ztp_server/.gitlab-ci.yml' # This should be last one: end-to-end integration tests - #- local: '/src/tests/.gitlab-ci.yml' + - local: '/src/tests/.gitlab-ci.yml' -- GitLab From 112beb31a5bc694acea0e2c5f39a876adb537683 Mon Sep 17 00:00:00 2001 From: jimenezquesa Date: Thu, 26 Jun 2025 08:42:41 +0000 Subject: [PATCH 6/7] clean code and add new log at debug level --- .gitlab-ci.yml | 1 + src/ztp_server/tests/test_unitary.py | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 72a3f5920..0da5a7431 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -53,6 +53,7 @@ include: - local: '/src/analytics/.gitlab-ci.yml' - local: '/src/qos_profile/.gitlab-ci.yml' - local: '/src/vnt_manager/.gitlab-ci.yml' + - local: '/src/e2e_orchestrator/.gitlab-ci.yml' - local: '/src/ztp_server/.gitlab-ci.yml' # This should be last one: end-to-end integration tests diff --git a/src/ztp_server/tests/test_unitary.py b/src/ztp_server/tests/test_unitary.py index c6c77875e..eb2e11840 100644 --- a/src/ztp_server/tests/test_unitary.py +++ b/src/ztp_server/tests/test_unitary.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import grpc, pytest +import grpc, pytest, logging, json from ztp_server.client.ZtpClient import ZtpClient from common.proto.ztp_server_pb2 import ProvisioningScriptName, ProvisioningScript, ZtpFileName, ZtpFile @@ -21,6 +21,9 @@ from .PrepareTestScenario import ( # pylint: disable=unused-import ztp_client ) +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + def test_GetProvisioningScript( ztp_client : ZtpClient, ): # pylint: disable=redefined-outer-name @@ -29,6 +32,5 @@ def test_GetProvisioningScript( ztp_provisioning_file_request.scriptname = "provisioning_script_sonic.sh" # pylint: disable=no-member ztp_reply = ztp_client.GetProvisioningScript(ztp_provisioning_file_request) - print("ztp_reply: " + str(ztp_reply) ) - + LOGGER.debug('retrieved_data={:s}'.format(json.dumps(ztp_reply, sort_keys=True))) assert ztp_reply.script.startswith("#!/bin/bash") -- GitLab From cb270cc1ce4f332af4d6e45643342422f51255b2 Mon Sep 17 00:00:00 2001 From: jimenezquesa Date: Thu, 26 Jun 2025 08:48:49 +0000 Subject: [PATCH 7/7] solve minor error on log --- src/ztp_server/tests/test_unitary.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ztp_server/tests/test_unitary.py b/src/ztp_server/tests/test_unitary.py index eb2e11840..bfe3b0904 100644 --- a/src/ztp_server/tests/test_unitary.py +++ b/src/ztp_server/tests/test_unitary.py @@ -16,6 +16,8 @@ import grpc, pytest, logging, json from ztp_server.client.ZtpClient import ZtpClient from common.proto.ztp_server_pb2 import ProvisioningScriptName, ProvisioningScript, ZtpFileName, ZtpFile +from common.tools.grpc.Tools import grpc_message_to_json_string + from .PrepareTestScenario import ( # pylint: disable=unused-import # be careful, order of symbols is important here! ztp_client @@ -32,5 +34,5 @@ def test_GetProvisioningScript( ztp_provisioning_file_request.scriptname = "provisioning_script_sonic.sh" # pylint: disable=no-member ztp_reply = ztp_client.GetProvisioningScript(ztp_provisioning_file_request) - LOGGER.debug('retrieved_data={:s}'.format(json.dumps(ztp_reply, sort_keys=True))) + LOGGER.debug('retrieved_data={:s}'.format(grpc_message_to_json_string(ztp_reply))) assert ztp_reply.script.startswith("#!/bin/bash") -- GitLab