Skip to content
Snippets Groups Projects
Commit d9d74bce authored by Manuel Angel Jimenez Quesada's avatar Manuel Angel Jimenez Quesada
Browse files

Add mock_osm_nbi

Add test_unitary and preparetestscenario
Solve some Typo error
Add gitlab-ci file
parent 82ac4c4e
Branches
No related tags found
1 merge request!373Resolve "Add test to feature develop during #234, related to OSM integration"
......@@ -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'
# 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
......@@ -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)
......@@ -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
......
# 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()
# 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.
# 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
# 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
# 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/<account_id>
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/<id>
# ------------------------
def get(self, account_id):
account = self._find(account_id)
if not account:
return {"error": "not found"}, 404
return jsonify(account)
# ------------------------
# PUT /vim_accounts/<id>
# ------------------------
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/<id>
# ------------------------
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
# 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.
# 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/<string:account_id>'
)
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())
# 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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment