From d9d74bcef2c2aa6e8782c657a5d798a612d27fb1 Mon Sep 17 00:00:00 2001 From: jimenezquesa Date: Thu, 26 Jun 2025 15:17:17 +0000 Subject: [PATCH 1/8] Add mock_osm_nbi Add test_unitary and preparetestscenario Solve some Typo error Add gitlab-ci file --- .gitlab-ci.yml | 1 + src/osm_client/.gitlab-ci.yml | 133 +++++++++++++ src/osm_client/Config.py | 2 +- src/osm_client/Dockerfile | 2 +- src/osm_client/tests/PrepareTestScenario.py | 43 ++++ src/osm_client/tests/__init__.py | 14 ++ src/osm_client/tests/test_unitary.py | 45 +++++ src/tests/tools/mock_osm_nbi/Dockerfile | 50 +++++ .../tools/mock_osm_nbi/ResourceOsmClient.py | 183 ++++++++++++++++++ src/tests/tools/mock_osm_nbi/__init__.py | 14 ++ src/tests/tools/mock_osm_nbi/__main__.py | 97 ++++++++++ src/tests/tools/mock_osm_nbi/requirements.in | 22 +++ 12 files changed, 604 insertions(+), 2 deletions(-) create mode 100644 src/osm_client/.gitlab-ci.yml create mode 100644 src/osm_client/tests/PrepareTestScenario.py create mode 100644 src/osm_client/tests/__init__.py create mode 100644 src/osm_client/tests/test_unitary.py create mode 100644 src/tests/tools/mock_osm_nbi/Dockerfile create mode 100644 src/tests/tools/mock_osm_nbi/ResourceOsmClient.py create mode 100644 src/tests/tools/mock_osm_nbi/__init__.py create mode 100644 src/tests/tools/mock_osm_nbi/__main__.py create mode 100644 src/tests/tools/mock_osm_nbi/requirements.in diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1aa5e597d..a12aafb1a 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/osm_client/.gitlab-ci.yml' # This should be last one: end-to-end integration tests - local: '/src/tests/.gitlab-ci.yml' diff --git a/src/osm_client/.gitlab-ci.yml b/src/osm_client/.gitlab-ci.yml new file mode 100644 index 000000000..fe2057118 --- /dev/null +++ b/src/osm_client/.gitlab-ci.yml @@ -0,0 +1,133 @@ +# 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 osm_client: + variables: + IMAGE_NAME: 'osm_client' # 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 + +build mock_osm_nbi: + variables: + IMAGE_NAME: 'mock_osm_nbi' # 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/tests/tools/$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/tests/tools/$IMAGE_NAME/**/*.{py,in,yml} + - src/tests/tools/$IMAGE_NAME/Dockerfile + - manifests/${IMAGE_NAME}service.yaml + - .gitlab-ci.yml + +# Apply unit test to the component +unit_test osm_client: + variables: + IMAGE_NAME: 'osm_client' # name of the microservice + MOCK_IMAGE_NAME: 'mock_osm_nbi' + IMAGE_TAG: 'latest' # tag of the container image (production, development, etc) + stage: unit_test + needs: + - build osm_client + - BUILD mock_osm_nbi + 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 + - > + docker run --name $MOCK_IMAGE_NAME -d + --network=teraflowbridge + --env LOG_LEVEL=DEBUG + --env FLASK_ENV=development + $CI_REGISTRY_IMAGE/$MOCK_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 logs $MOCK_IMAGE_NAME + - 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 rm -f $MOCK_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/osm_client/Config.py b/src/osm_client/Config.py index 12b1d0983..621c8113f 100644 --- a/src/osm_client/Config.py +++ b/src/osm_client/Config.py @@ -14,5 +14,5 @@ from common.Settings import get_setting -DEFAULT_OSM_ADDRESS = '127.0.0.1' +DEFAULT_OSM_ADDRESS = 'mock_osm_nbi' OSM_ADDRESS = get_setting('OSM_ADDRESS', default=DEFAULT_OSM_ADDRESS) diff --git a/src/osm_client/Dockerfile b/src/osm_client/Dockerfile index 3c885973c..579972127 100644 --- a/src/osm_client/Dockerfile +++ b/src/osm_client/Dockerfile @@ -18,7 +18,7 @@ FROM python:3.10.16-slim # Install dependencies RUN apt-get --yes --quiet --quiet update RUN apt-get --yes --quiet --quiet install wget g++ git build-essential cmake make git \ - libpcre2-dev python3-dev python3-pip python3-cffi curl software-properties-common && \ + libpcre2-dev python3-dev python3-pip python3-cffi curl software-properties-common libmagic1 libmagic-dev && \ rm -rf /var/lib/apt/lists/* # Set Python to show logs as they occur diff --git a/src/osm_client/tests/PrepareTestScenario.py b/src/osm_client/tests/PrepareTestScenario.py new file mode 100644 index 000000000..16b17266e --- /dev/null +++ b/src/osm_client/tests/PrepareTestScenario.py @@ -0,0 +1,43 @@ +# 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 pytest, os + +from common.Settings import ( + ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, + ENVVAR_SUFIX_SERVICE_PORT_HTTP, get_env_var_name, get_service_port_grpc +) + +from common.Constants import ServiceNameEnum +from osm_client.client.OsmClient import OsmClient +from osm_client.service.OsmClientService import OsmClientService + +LOCAL_HOST = '127.0.0.1' +GRPC_PORT = 10000 + int(get_service_port_grpc(ServiceNameEnum.OSMCLIENT)) + +os.environ[get_env_var_name(ServiceNameEnum.OSMCLIENT, ENVVAR_SUFIX_SERVICE_HOST )] = str(LOCAL_HOST) +os.environ[get_env_var_name(ServiceNameEnum.OSMCLIENT, ENVVAR_SUFIX_SERVICE_PORT_HTTP)] = str(GRPC_PORT) + +@pytest.fixture(scope='session') +def osm_client_service(): # pylint: disable=redefined-outer-name + _service = OsmClientService() + _service.start() + yield _service + _service.stop() + +@pytest.fixture(scope='session') +def osm_client(osm_client_service : OsmClientService): # pylint: disable=redefined-outer-name + _client = OsmClient() + yield _client + _client.close() diff --git a/src/osm_client/tests/__init__.py b/src/osm_client/tests/__init__.py new file mode 100644 index 000000000..3ccc21c7d --- /dev/null +++ b/src/osm_client/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/osm_client/tests/test_unitary.py b/src/osm_client/tests/test_unitary.py new file mode 100644 index 000000000..283213499 --- /dev/null +++ b/src/osm_client/tests/test_unitary.py @@ -0,0 +1,45 @@ +# 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, pytest +from osm_client.client.OsmClient import OsmClient +from common.proto.osm_client_pb2 import CreateRequest, CreateResponse, NsiListResponse +from common.proto.context_pb2 import Empty + + +from .PrepareTestScenario import ( # pylint: disable=unused-import + # be careful, order of symbols is important here! + osm_client_service, osm_client +) + +def test_OsmClient( + osm_client : OsmClient, +): # pylint: disable=redefined-outer-name + + nbi_list_request = Empty() + + osm_list_reply = osm_client.NsiList(nbi_list_request) + assert len(osm_list_reply.id) == 0 + #assert osm_list_reply.id == [] + + nbi_create_request = CreateRequest() + nbi_create_request.nst_name = "nst1" + nbi_create_request.nsi_name = "nsi1" + nbi_create_request.account = "account1" + + osm_create_reply = osm_client.NsiCreate(nbi_create_request) + assert osm_create_reply.succeded == True + + osm_list_reply2 = osm_client.NsiList(nbi_list_request) + assert len(osm_list_reply2.id) == 1 diff --git a/src/tests/tools/mock_osm_nbi/Dockerfile b/src/tests/tools/mock_osm_nbi/Dockerfile new file mode 100644 index 000000000..f2ac216e1 --- /dev/null +++ b/src/tests/tools/mock_osm_nbi/Dockerfile @@ -0,0 +1,50 @@ +# 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 python:3.10.16-slim + +# Install dependencies +RUN apt-get --yes --quiet --quiet update && \ + apt-get --yes --quiet --quiet install wget g++ git build-essential cmake libpcre2-dev python3-dev python3-cffi && \ + 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 +RUN python3 -m pip install --upgrade setuptools wheel +RUN python3 -m pip install --upgrade pip-tools + +# Create component sub-folders, and copy content +RUN mkdir -p /var/teraflow/mock_osm_nbi +WORKDIR /var/teraflow/mock_osm_nbi +COPY src/tests/tools/mock_osm_nbi/requirements.in requirements.in +# COPY . . + + + +RUN ls + +# Get specific Python packages +RUN pip-compile --quiet --output-file=requirements.txt requirements.in +RUN python3 -m pip install -r requirements.txt + +RUN python3 -m pip list + +WORKDIR /var/teraflow +COPY src/tests/tools/mock_osm_nbi/. mock_osm_nbi/ + +# Start the service +ENTRYPOINT ["python", "-m", "mock_osm_nbi"] \ No newline at end of file diff --git a/src/tests/tools/mock_osm_nbi/ResourceOsmClient.py b/src/tests/tools/mock_osm_nbi/ResourceOsmClient.py new file mode 100644 index 000000000..f038ec960 --- /dev/null +++ b/src/tests/tools/mock_osm_nbi/ResourceOsmClient.py @@ -0,0 +1,183 @@ +# 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. + +# REST-API resource implementing minimal support for "IETF YANG Data Model for Traffic Engineering Tunnels, +# Label Switched Paths and Interfaces". +# Ref: https://www.ietf.org/archive/id/draft-ietf-teas-yang-te-34.html + +import logging +from flask import abort, jsonify, make_response, request +from flask_restful import Resource +from uuid import uuid4 + +LOG_LEVEL = logging.DEBUG + +logging.basicConfig(level=LOG_LEVEL, format="[%(asctime)s] %(levelname)s:%(name)s:%(message)s") +LOGGER = logging.getLogger(__name__) + +logging.getLogger('werkzeug').setLevel(logging.INFO) + +OSM_NSI = [] + +OSM_VIM_DB = [ + { + "_id": "account1", + "name": "account1", + "vim_type": "account1", + "tenant": "account1", + "vim_url": "http://account1.local" + } + ] + + +class OsmNST(Resource): + def get(self): + LOGGER.info("get NST request received") + mock_nsts = [ + { + "_id": "nst1", + "name": "nst1", + "description": "Nst_Mock", + "vendor": "ExampleVendor", + "version": "1.0" + }, + { + "_id": "nst2", + "name": "nst2", + "description": "Nst_Mock", + "vendor": "AnotherVendor", + "version": "2.1" + } + ] + return make_response(jsonify(mock_nsts), 200) + +class OsmNBI(Resource): + def get(self): + LOGGER.info("get NBI request received") + + LOGGER.info(str(OSM_NSI)) + + #return make_response(jsonify(OSM_NSI), 200) + return [nsi["id"] for nsi in OSM_NSI] + + def post(self): + LOGGER.info("post request received") + + LOGGER.info(str(request)) + + payload = request.get_json(silent=True) or {} + qs = request.args + + nst_id = payload.get("nstId") or qs.get("nstId") + name = payload.get("nsiName") or qs.get("nsiName") + desc = payload.get("nsiDescription") or qs.get("nsiDescription", "") + + new_nsi = { + "id": str(uuid4()), + "name": name, + "nstId": nst_id, + "description": desc, + "operationalStatus": "CREATED" + } + + OSM_NSI.append(new_nsi) + #nsi = {} + #nsi["_Id"] = json_request["nstId"] + #nsi["name"] = json_request["nsiName"] + #nsi["Description"] = json_request["nsiDescription"] + #nsi["vimAccountId"] = json_request["vimAccountId"] + + #if not isinstance(nsi["nstId"], list): abort(400) + #OSM_NSI[nsi["nstId"]] = nsi + + return make_response(jsonify(new_nsi), 201) + + +class VimAccounts(Resource): + def get(self): + return jsonify(OSM_VIM_DB) + + def post(self): + payload = request.get_json(silent=True) or {} + name = payload.get("name") + vim_type = payload.get("vim_type") + + if not name or not vim_type: + return {"error": "name and vim_type are required"}, 400 + + new_vim = { + "_id": str(uuid4()), + "name": name, + "vim_type": vim_type, + "tenant": payload.get("tenant", "admin"), + "vim_url": payload.get("vim_url", "http://mock.local") + } + + # Store new_vim in local DDBB + OSM_VIM_DB.append(new_vim) + return jsonify(new_vim), 201 + + +class VimAccountItem(Resource): + """ + Mock -> /osm/admin/v1/vim_accounts/ + Soporta: + • GET → devuelve la VIM Account concreta + • PUT → actualiza campos permitidos + • DELETE → elimina la cuenta + """ + + @staticmethod + def _find(account_id): + """Busca la cuenta por _id en la base in-memory""" + return next( + (acc for acc in OSM_VIM_DB if acc["_id"] == account_id), + None + ) + + # ------------------------ + # GET /vim_accounts/ + # ------------------------ + def get(self, account_id): + account = self._find(account_id) + if not account: + return {"error": "not found"}, 404 + return jsonify(account) + + # ------------------------ + # PUT /vim_accounts/ + # ------------------------ + def put(self, account_id): + account = self._find(account_id) + if not account: + return {"error": "not found"}, 404 + + payload = request.get_json(silent=True) or {} + # campos actualizables + for field in ("name", "vim_type", "tenant", "vim_url"): + if field in payload: + account[field] = payload[field] + + return jsonify(account) + + # ------------------------ + # DELETE /vim_accounts/ + # ------------------------ + def delete(self, account_id): + account = self._find(account_id) + if not account: + return {"error": "not found"}, 404 + + OSM_VIM_DB.remove(account) + return "", 204 \ No newline at end of file diff --git a/src/tests/tools/mock_osm_nbi/__init__.py b/src/tests/tools/mock_osm_nbi/__init__.py new file mode 100644 index 000000000..3ccc21c7d --- /dev/null +++ b/src/tests/tools/mock_osm_nbi/__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/tests/tools/mock_osm_nbi/__main__.py b/src/tests/tools/mock_osm_nbi/__main__.py new file mode 100644 index 000000000..370d8486e --- /dev/null +++ b/src/tests/tools/mock_osm_nbi/__main__.py @@ -0,0 +1,97 @@ +# 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. + +# Mock IETF ACTN SDN controller +# ----------------------------- +# REST server implementing minimal support for: +# - IETF YANG Data Model for Transport Network Client Signals +# Ref: https://www.ietf.org/archive/id/draft-ietf-ccamp-client-signal-yang-10.html +# - IETF YANG Data Model for Traffic Engineering Tunnels, Label Switched Paths and Interfaces +# Ref: https://www.ietf.org/archive/id/draft-ietf-teas-yang-te-34.html + + +import functools, logging, sys, time +from flask import Flask, jsonify, make_response, request +from flask_restful import Api, Resource +from uuid import uuid4 + +from .ResourceOsmClient import OsmNBI, OsmNST, VimAccounts, VimAccountItem + + +BIND_ADDRESS = '0.0.0.0' +BIND_PORT = 443 +BASE_URL = '/osm' +STR_ENDPOINT = 'https://{:s}:{:s}{:s}'.format(str(BIND_ADDRESS), str(BIND_PORT), str(BASE_URL)) +LOG_LEVEL = logging.DEBUG + +logging.basicConfig(level=LOG_LEVEL, format="[%(asctime)s] %(levelname)s:%(name)s:%(message)s") +LOGGER = logging.getLogger(__name__) + +logging.getLogger('werkzeug').setLevel(logging.WARNING) + +def log_request(logger : logging.Logger, response): + timestamp = time.strftime('[%Y-%b-%d %H:%M]') + logger.info('%s %s %s %s %s', timestamp, request.remote_addr, request.method, request.full_path, response.status) + return response + +class Health(Resource): + def get(self): + LOGGER.info('health request received') + return make_response(jsonify({"hola"}), 200) + +class OsmAdmin(Resource): + def post(self): + LOGGER.info('token request received') + data = request.get_json(silent=True) or {} + + token = str(uuid4()) + + return jsonify({"id": token}) + +def main(): + LOGGER.info('Starting...') + + app = Flask(__name__) + app.after_request(functools.partial(log_request, LOGGER)) + + api = Api(app, prefix=BASE_URL) + + api.add_resource( + Health, '/' + ) + api.add_resource( + OsmNBI, '/nsilcm/v1/netslice_instances_content' + ) + api.add_resource( + OsmAdmin, '/admin/v1/tokens' + ) + api.add_resource( + OsmNST, '/nst/v1/netslice_templates' + ) + api.add_resource( + VimAccounts, '/admin/v1/vim_accounts' + ) + + api.add_resource( + VimAccountItem, '/admin/v1/vim_accounts/' + ) + + LOGGER.info('Listening on {:s}...'.format(str(STR_ENDPOINT))) + app.run(debug=True, host=BIND_ADDRESS, port=BIND_PORT, ssl_context='adhoc') + + LOGGER.info('Bye') + return 0 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/tests/tools/mock_osm_nbi/requirements.in b/src/tests/tools/mock_osm_nbi/requirements.in new file mode 100644 index 000000000..dbb8e95d6 --- /dev/null +++ b/src/tests/tools/mock_osm_nbi/requirements.in @@ -0,0 +1,22 @@ +# 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. + +cryptography==39.0.1 +pyopenssl==23.0.0 +Flask==2.1.3 +Flask-HTTPAuth==4.5.0 +Flask-RESTful==0.3.9 +jsonschema==4.4.0 +requests==2.27.1 +werkzeug==2.3.7 -- GitLab From 40c731bdbdf4fdb04f3081cb8ae2b8e5c984cd25 Mon Sep 17 00:00:00 2001 From: jimenezquesa Date: Thu, 26 Jun 2025 15:28:33 +0000 Subject: [PATCH 2/8] Clean code (comments, unused lines...) --- src/osm_client/tests/test_unitary.py | 1 - src/tests/tools/mock_osm_nbi/Dockerfile | 5 ---- .../tools/mock_osm_nbi/ResourceOsmClient.py | 26 ++++++++----------- 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/src/osm_client/tests/test_unitary.py b/src/osm_client/tests/test_unitary.py index 283213499..738b7eb06 100644 --- a/src/osm_client/tests/test_unitary.py +++ b/src/osm_client/tests/test_unitary.py @@ -31,7 +31,6 @@ def test_OsmClient( osm_list_reply = osm_client.NsiList(nbi_list_request) assert len(osm_list_reply.id) == 0 - #assert osm_list_reply.id == [] nbi_create_request = CreateRequest() nbi_create_request.nst_name = "nst1" diff --git a/src/tests/tools/mock_osm_nbi/Dockerfile b/src/tests/tools/mock_osm_nbi/Dockerfile index f2ac216e1..ed64806cc 100644 --- a/src/tests/tools/mock_osm_nbi/Dockerfile +++ b/src/tests/tools/mock_osm_nbi/Dockerfile @@ -31,11 +31,6 @@ RUN python3 -m pip install --upgrade pip-tools RUN mkdir -p /var/teraflow/mock_osm_nbi WORKDIR /var/teraflow/mock_osm_nbi COPY src/tests/tools/mock_osm_nbi/requirements.in requirements.in -# COPY . . - - - -RUN ls # Get specific Python packages RUN pip-compile --quiet --output-file=requirements.txt requirements.in diff --git a/src/tests/tools/mock_osm_nbi/ResourceOsmClient.py b/src/tests/tools/mock_osm_nbi/ResourceOsmClient.py index f038ec960..9e58e7790 100644 --- a/src/tests/tools/mock_osm_nbi/ResourceOsmClient.py +++ b/src/tests/tools/mock_osm_nbi/ResourceOsmClient.py @@ -68,7 +68,6 @@ class OsmNBI(Resource): LOGGER.info(str(OSM_NSI)) - #return make_response(jsonify(OSM_NSI), 200) return [nsi["id"] for nsi in OSM_NSI] def post(self): @@ -92,19 +91,17 @@ class OsmNBI(Resource): } OSM_NSI.append(new_nsi) - #nsi = {} - #nsi["_Id"] = json_request["nstId"] - #nsi["name"] = json_request["nsiName"] - #nsi["Description"] = json_request["nsiDescription"] - #nsi["vimAccountId"] = json_request["vimAccountId"] - - #if not isinstance(nsi["nstId"], list): abort(400) - #OSM_NSI[nsi["nstId"]] = nsi return make_response(jsonify(new_nsi), 201) class VimAccounts(Resource): + """ + Mock -> /osm/admin/v1/vim_accounts + Support: + • GET → get all VIM Account + • POST → Create a VIM Account + """ def get(self): return jsonify(OSM_VIM_DB) @@ -132,15 +129,15 @@ class VimAccounts(Resource): class VimAccountItem(Resource): """ Mock -> /osm/admin/v1/vim_accounts/ - Soporta: - • GET → devuelve la VIM Account concreta - • PUT → actualiza campos permitidos - • DELETE → elimina la cuenta + Support: + • GET → get VIM Account + • PUT → Upadate fields + • DELETE → Delete VIM Account """ @staticmethod def _find(account_id): - """Busca la cuenta por _id en la base in-memory""" + """Search VIM account""" return next( (acc for acc in OSM_VIM_DB if acc["_id"] == account_id), None @@ -164,7 +161,6 @@ class VimAccountItem(Resource): return {"error": "not found"}, 404 payload = request.get_json(silent=True) or {} - # campos actualizables for field in ("name", "vim_type", "tenant", "vim_url"): if field in payload: account[field] = payload[field] -- GitLab From 9144322b10687ae9a4379ce7f9b78b445262a213 Mon Sep 17 00:00:00 2001 From: jimenezquesa Date: Thu, 26 Jun 2025 15:31:40 +0000 Subject: [PATCH 3/8] Solve Typo error in gitlab-ci file --- src/osm_client/.gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/osm_client/.gitlab-ci.yml b/src/osm_client/.gitlab-ci.yml index fe2057118..626e078bb 100644 --- a/src/osm_client/.gitlab-ci.yml +++ b/src/osm_client/.gitlab-ci.yml @@ -71,7 +71,7 @@ unit_test osm_client: stage: unit_test needs: - build osm_client - - BUILD mock_osm_nbi + - build mock_osm_nbi before_script: - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY - docker ps -aq | xargs -r docker rm -f -- GitLab From 80b4bdd9cd981d7ae9a4b523352247d3a3132899 Mon Sep 17 00:00:00 2001 From: jimenezquesa Date: Thu, 26 Jun 2025 15:50:24 +0000 Subject: [PATCH 4/8] Includo mock_osm_nbi in gitlab-ci file --- src/osm_client/.gitlab-ci.yml | 31 +++++++------------------------ 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/src/osm_client/.gitlab-ci.yml b/src/osm_client/.gitlab-ci.yml index 626e078bb..4635f37a0 100644 --- a/src/osm_client/.gitlab-ci.yml +++ b/src/osm_client/.gitlab-ci.yml @@ -16,6 +16,7 @@ build osm_client: variables: IMAGE_NAME: 'osm_client' # name of the microservice + MOCK_IMAGE_NAME: 'mock_osm_nbi' # name of the mock IMAGE_TAG: 'latest' # tag of the container image (production, development, etc) stage: build before_script: @@ -24,6 +25,10 @@ build osm_client: - 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" + # Build mock images + - docker buildx build -t "$MOCK_IMAGE_NAME:$IMAGE_TAG" -f ./src/tests/tools/$MOCK_IMAGE_NAME/Dockerfile . + - docker tag "$MOCK_IMAGE_NAME:$IMAGE_TAG" "$CI_REGISTRY_IMAGE/$MOCK_IMAGE_NAME:$IMAGE_TAG" + - docker push "$CI_REGISTRY_IMAGE/$MOCK_IMAGE_NAME:$IMAGE_TAG" after_script: - docker images --filter="dangling=true" --quiet | xargs -r docker rmi rules: @@ -36,30 +41,8 @@ build osm_client: - src/$IMAGE_NAME/Dockerfile - src/$IMAGE_NAME/tests/*.py - manifests/${IMAGE_NAME}service.yaml - - .gitlab-ci.yml - -build mock_osm_nbi: - variables: - IMAGE_NAME: 'mock_osm_nbi' # 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/tests/tools/$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/tests/tools/$IMAGE_NAME/**/*.{py,in,yml} - - src/tests/tools/$IMAGE_NAME/Dockerfile - - manifests/${IMAGE_NAME}service.yaml + - src/tests/tools/$MOCK_IMAGE_NAME/**/*.{py,in,yml} + - src/tests/tools/$MOCK_IMAGE_NAME/Dockerfile - .gitlab-ci.yml # Apply unit test to the component -- GitLab From f5544b83ae27a2ab16c9de77f81b53a976c88d49 Mon Sep 17 00:00:00 2001 From: jimenezquesa Date: Thu, 26 Jun 2025 15:52:13 +0000 Subject: [PATCH 5/8] solve error on gitlab-ci file --- src/osm_client/.gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/osm_client/.gitlab-ci.yml b/src/osm_client/.gitlab-ci.yml index 4635f37a0..2065aa89f 100644 --- a/src/osm_client/.gitlab-ci.yml +++ b/src/osm_client/.gitlab-ci.yml @@ -54,7 +54,6 @@ unit_test osm_client: stage: unit_test needs: - build osm_client - - build mock_osm_nbi before_script: - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY - docker ps -aq | xargs -r docker rm -f -- GitLab From f25711f07961c2bbd55ef215d4bd3fb1f62dc4b9 Mon Sep 17 00:00:00 2001 From: jimenezquesa Date: Thu, 26 Jun 2025 16:30:41 +0000 Subject: [PATCH 6/8] add waiting loop to gitlab-ci --- src/osm_client/.gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/osm_client/.gitlab-ci.yml b/src/osm_client/.gitlab-ci.yml index 2065aa89f..12571da2d 100644 --- a/src/osm_client/.gitlab-ci.yml +++ b/src/osm_client/.gitlab-ci.yml @@ -85,7 +85,7 @@ unit_test osm_client: --env LOG_LEVEL=DEBUG --env FLASK_ENV=development $CI_REGISTRY_IMAGE/$MOCK_IMAGE_NAME:$IMAGE_TAG - - while ! docker logs $IMAGE_NAME 2>&1 | grep -q 'Configured Resources:'; do sleep 1; done + - while ! docker logs $IMAGE_NAME 2>&1 | grep -q 'Configured Rules'; do sleep 1; done - sleep 5 # Give extra time to container to get ready - docker ps -a - docker logs $IMAGE_NAME -- GitLab From 2b6e4f772b877e0fd8099ad583f0d90b4eacc56d Mon Sep 17 00:00:00 2001 From: jimenezquesa Date: Thu, 26 Jun 2025 17:07:21 +0000 Subject: [PATCH 7/8] Remove Unused comments --- src/osm_client/.gitlab-ci.yml | 1 + src/osm_client/Config.py | 2 +- src/tests/tools/mock_osm_nbi/Dockerfile | 2 +- src/tests/tools/mock_osm_nbi/ResourceOsmClient.py | 4 ---- src/tests/tools/mock_osm_nbi/__main__.py | 9 --------- 5 files changed, 3 insertions(+), 15 deletions(-) diff --git a/src/osm_client/.gitlab-ci.yml b/src/osm_client/.gitlab-ci.yml index 12571da2d..0ebb12f52 100644 --- a/src/osm_client/.gitlab-ci.yml +++ b/src/osm_client/.gitlab-ci.yml @@ -78,6 +78,7 @@ unit_test osm_client: --network=teraflowbridge --env LOG_LEVEL=DEBUG --env FLASK_ENV=development + --env OSM_ADDRESS=mock_osm_nbi $CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG - > docker run --name $MOCK_IMAGE_NAME -d diff --git a/src/osm_client/Config.py b/src/osm_client/Config.py index 621c8113f..12b1d0983 100644 --- a/src/osm_client/Config.py +++ b/src/osm_client/Config.py @@ -14,5 +14,5 @@ from common.Settings import get_setting -DEFAULT_OSM_ADDRESS = 'mock_osm_nbi' +DEFAULT_OSM_ADDRESS = '127.0.0.1' OSM_ADDRESS = get_setting('OSM_ADDRESS', default=DEFAULT_OSM_ADDRESS) diff --git a/src/tests/tools/mock_osm_nbi/Dockerfile b/src/tests/tools/mock_osm_nbi/Dockerfile index ed64806cc..9efd362d4 100644 --- a/src/tests/tools/mock_osm_nbi/Dockerfile +++ b/src/tests/tools/mock_osm_nbi/Dockerfile @@ -42,4 +42,4 @@ WORKDIR /var/teraflow COPY src/tests/tools/mock_osm_nbi/. mock_osm_nbi/ # Start the service -ENTRYPOINT ["python", "-m", "mock_osm_nbi"] \ No newline at end of file +ENTRYPOINT ["python", "-m", "mock_osm_nbi"] diff --git a/src/tests/tools/mock_osm_nbi/ResourceOsmClient.py b/src/tests/tools/mock_osm_nbi/ResourceOsmClient.py index 9e58e7790..5a32998d4 100644 --- a/src/tests/tools/mock_osm_nbi/ResourceOsmClient.py +++ b/src/tests/tools/mock_osm_nbi/ResourceOsmClient.py @@ -12,10 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -# REST-API resource implementing minimal support for "IETF YANG Data Model for Traffic Engineering Tunnels, -# Label Switched Paths and Interfaces". -# Ref: https://www.ietf.org/archive/id/draft-ietf-teas-yang-te-34.html - import logging from flask import abort, jsonify, make_response, request from flask_restful import Resource diff --git a/src/tests/tools/mock_osm_nbi/__main__.py b/src/tests/tools/mock_osm_nbi/__main__.py index 370d8486e..2d15c0c3d 100644 --- a/src/tests/tools/mock_osm_nbi/__main__.py +++ b/src/tests/tools/mock_osm_nbi/__main__.py @@ -12,15 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Mock IETF ACTN SDN controller -# ----------------------------- -# REST server implementing minimal support for: -# - IETF YANG Data Model for Transport Network Client Signals -# Ref: https://www.ietf.org/archive/id/draft-ietf-ccamp-client-signal-yang-10.html -# - IETF YANG Data Model for Traffic Engineering Tunnels, Label Switched Paths and Interfaces -# Ref: https://www.ietf.org/archive/id/draft-ietf-teas-yang-te-34.html - - import functools, logging, sys, time from flask import Flask, jsonify, make_response, request from flask_restful import Api, Resource -- GitLab From f086c39d0f7bdd3784359d29747ed79df2b4dab6 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Thu, 26 Jun 2025 17:12:12 +0000 Subject: [PATCH 8/8] code cleanup --- src/tests/tools/mock_osm_nbi/ResourceOsmClient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/tools/mock_osm_nbi/ResourceOsmClient.py b/src/tests/tools/mock_osm_nbi/ResourceOsmClient.py index 5a32998d4..51047c93b 100644 --- a/src/tests/tools/mock_osm_nbi/ResourceOsmClient.py +++ b/src/tests/tools/mock_osm_nbi/ResourceOsmClient.py @@ -172,4 +172,4 @@ class VimAccountItem(Resource): return {"error": "not found"}, 404 OSM_VIM_DB.remove(account) - return "", 204 \ No newline at end of file + return "", 204 -- GitLab