diff --git a/src/nbi/.gitlab-ci.yml b/src/nbi/.gitlab-ci.yml index 7d98e61603bd3001f8f6ae418adad77a419b8371..8711cfb727ac2ec88430321dc9eaf50c04b3972c 100644 --- a/src/nbi/.gitlab-ci.yml +++ b/src/nbi/.gitlab-ci.yml @@ -48,15 +48,29 @@ unit_test nbi: - build nbi before_script: - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY - - 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 + - > + 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 script: - docker pull "$CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG" - docker run --name $IMAGE_NAME -d -p 9090:9090 -v "$PWD/src/$IMAGE_NAME/tests:/opt/results" --network=teraflowbridge $CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG - sleep 5 - docker ps -a - docker logs $IMAGE_NAME - - docker exec -i $IMAGE_NAME bash -c "coverage run -m pytest --log-level=INFO --verbose $IMAGE_NAME/tests/test_unitary.py $IMAGE_NAME/tests/test_ietf_network.py --junitxml=/opt/results/${IMAGE_NAME}_report.xml" + - > + docker exec -i $IMAGE_NAME bash -c "coverage run -m pytest --log-level=INFO --verbose + $IMAGE_NAME/tests/test_ietf_l2vpn.py + $IMAGE_NAME/tests/test_ietf_network.py + --junitxml=/opt/results/${IMAGE_NAME}_report.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: diff --git a/src/nbi/requirements.in b/src/nbi/requirements.in index 69978edc0b5f3ff2c8d4399e49d1842cc3e9bae8..a1da58d01b4d0647a668a584e6388ee167210f6b 100644 --- a/src/nbi/requirements.in +++ b/src/nbi/requirements.in @@ -17,7 +17,7 @@ Flask==2.1.3 Flask-HTTPAuth==4.5.0 Flask-RESTful==0.3.9 jsonschema==4.4.0 -pyang +pyang==2.6.0 git+https://github.com/robshakir/pyangbind.git requests==2.27.1 werkzeug==2.3.7 diff --git a/src/nbi/tests/PrepareTestScenario.py b/src/nbi/tests/PrepareTestScenario.py index c3461da614a842ae9ebc5185c6dd30c0e9e8709b..88a77b6e099763c5c7c6dda291374bc2fc6e6f80 100644 --- a/src/nbi/tests/PrepareTestScenario.py +++ b/src/nbi/tests/PrepareTestScenario.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging, os, pytest, requests, time -from typing import Dict, List, Optional, Set, Union +import enum, logging, os, pytest, requests, time +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_HTTP, @@ -79,17 +79,73 @@ def slice_client(mock_service : MockService_Dependencies): # pylint: disable=red yield _client _client.close() +class RestRequestMethod(enum.Enum): + GET = 'get' + POST = 'post' + PUT = 'put' + DELETE = 'delete' + +EXPECTED_STATUS_CODES : Set[int] = { + requests.codes['OK' ], + requests.codes['CREATED' ], + requests.codes['ACCEPTED' ], + requests.codes['NO_CONTENT'], +} + def do_rest_request( - url : str, logger : Optional[logging.Logger] = None, timeout=10, expected_status_codes : Set[int] = {200} -) -> Union[Dict, List]: + 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]]: base_url = get_service_baseurl_http(ServiceNameEnum.NBI) or '' request_url = 'http://{:s}:{:s}@{:s}:{:d}{:s}{:s}'.format( USERNAME, PASSWORD, LOCAL_HOST, NBI_SERVICE_PORT, str(base_url), url ) if logger is not None: - logger.warning('Request: GET {:s}'.format(str(request_url))) - reply = requests.get(request_url, timeout=timeout) + 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) - return reply.json() + + 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 +) -> 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 +) -> 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 +) -> 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_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 +) -> 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/nbi/tests/test_debug_api.py b/src/nbi/tests/test_debug_api.py index 06ad8cedc5d70fb58051cbf5b81150b812856054..e284992618a96388371b69b7b0c2a8c4fcaaba70 100644 --- a/src/nbi/tests/test_debug_api.py +++ b/src/nbi/tests/test_debug_api.py @@ -12,27 +12,29 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging, os, pytest, requests, time, urllib -from typing import Tuple +import logging, os, pytest, time, urllib from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME, ServiceNameEnum from common.proto.context_pb2 import Connection, Context, Device, Link, Service, Slice, Topology from common.proto.policy_pb2 import PolicyRuleIdList, PolicyRuleId, PolicyRuleList, PolicyRule from common.Settings import ( ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, ENVVAR_SUFIX_SERVICE_PORT_HTTP, get_env_var_name, - get_service_baseurl_http, get_service_port_grpc, get_service_port_http) + get_service_port_grpc, get_service_port_http +) from common.type_checkers.Assertions import ( validate_connection, validate_connection_ids, validate_connections, validate_context, validate_context_ids, validate_contexts, validate_device, validate_device_ids, validate_devices, validate_link, validate_link_ids, validate_links, validate_service, validate_service_ids, validate_services, validate_topologies, validate_topology, validate_topology_ids) from context.client.ContextClient import ContextClient +from nbi.tests.PrepareTestScenario import do_rest_get_request from .MockService_Dependencies import MockService_Dependencies from .Objects import ( CONNECTION_R1_R3, CONNECTION_R1_R3_ID, CONNECTION_R1_R3_UUID, CONTEXT, CONTEXT_ID, DEVICE_R1, DEVICE_R1_ID, DEVICE_R1_UUID, DEVICE_R2, DEVICE_R2_ID, DEVICE_R2_UUID, DEVICE_R3, DEVICE_R3_ID, DEVICE_R3_UUID, LINK_R1_R2, LINK_R1_R2_ID, LINK_R1_R2_UUID, SERVICE_R1_R2, SERVICE_R1_R2_ID, SERVICE_R1_R2_UUID, SERVICE_R1_R3, SERVICE_R1_R3_ID, SERVICE_R1_R3_UUID, SERVICE_R2_R3, SERVICE_R2_R3_ID, SERVICE_R2_R3_UUID, SLICE_R1_R3, TOPOLOGY, - TOPOLOGY_ID, POLICY_RULE, POLICY_RULE_ID, POLICY_RULE_UUID) + TOPOLOGY_ID, POLICY_RULE, POLICY_RULE_ID, POLICY_RULE_UUID +) @pytest.fixture(scope='session') @@ -100,129 +102,119 @@ def test_populate_database(): client.SetSlice(Slice(**SLICE_R1_R3)) client.SetConnection(Connection(**CONNECTION_R1_R3)) -def do_rest_request(url : str): - base_url = get_service_baseurl_http(ServiceNameEnum.CONTEXT) - request_url = 'http://{:s}:{:s}{:s}{:s}'.format(str(LOCAL_HOST), str(HTTP_PORT), str(base_url), url) - LOGGER.warning('Request: GET {:s}'.format(str(request_url))) - reply = requests.get(request_url) - LOGGER.warning('Reply: {:s}'.format(str(reply.text))) - assert reply.status_code == 200, 'Reply failed with code {}'.format(reply.status_code) - return reply.json() - - def test_rest_get_context_ids(context_service_rest : RestServer): # pylint: disable=redefined-outer-name - reply = do_rest_request('/context_ids') + reply = do_rest_get_request('/context_ids') validate_context_ids(reply) def test_rest_get_contexts(context_service_rest : RestServer): # pylint: disable=redefined-outer-name - reply = do_rest_request('/contexts') + reply = do_rest_get_request('/contexts') validate_contexts(reply) def test_rest_get_context(context_service_rest : RestServer): # pylint: disable=redefined-outer-name context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) - reply = do_rest_request('/context/{:s}'.format(context_uuid)) + reply = do_rest_get_request('/context/{:s}'.format(context_uuid)) validate_context(reply) def test_rest_get_topology_ids(context_service_rest : RestServer): # pylint: disable=redefined-outer-name context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) - reply = do_rest_request('/context/{:s}/topology_ids'.format(context_uuid)) + reply = do_rest_get_request('/context/{:s}/topology_ids'.format(context_uuid)) validate_topology_ids(reply) def test_rest_get_topologies(context_service_rest : RestServer): # pylint: disable=redefined-outer-name context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) - reply = do_rest_request('/context/{:s}/topologies'.format(context_uuid)) + reply = do_rest_get_request('/context/{:s}/topologies'.format(context_uuid)) validate_topologies(reply) def test_rest_get_topology(context_service_rest : RestServer): # pylint: disable=redefined-outer-name context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) topology_uuid = urllib.parse.quote(DEFAULT_TOPOLOGY_NAME) - reply = do_rest_request('/context/{:s}/topology/{:s}'.format(context_uuid, topology_uuid)) + reply = do_rest_get_request('/context/{:s}/topology/{:s}'.format(context_uuid, topology_uuid)) validate_topology(reply, num_devices=3, num_links=3) def test_rest_get_service_ids(context_service_rest : RestServer): # pylint: disable=redefined-outer-name context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) - reply = do_rest_request('/context/{:s}/service_ids'.format(context_uuid)) + reply = do_rest_get_request('/context/{:s}/service_ids'.format(context_uuid)) validate_service_ids(reply) def test_rest_get_services(context_service_rest : RestServer): # pylint: disable=redefined-outer-name context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) - reply = do_rest_request('/context/{:s}/services'.format(context_uuid)) + reply = do_rest_get_request('/context/{:s}/services'.format(context_uuid)) validate_services(reply) def test_rest_get_service(context_service_rest : RestServer): # pylint: disable=redefined-outer-name context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) service_uuid = urllib.parse.quote(SERVICE_R1_R2_UUID, safe='') - reply = do_rest_request('/context/{:s}/service/{:s}'.format(context_uuid, service_uuid)) + reply = do_rest_get_request('/context/{:s}/service/{:s}'.format(context_uuid, service_uuid)) validate_service(reply) def test_rest_get_slice_ids(context_service_rest : RestServer): # pylint: disable=redefined-outer-name context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) - reply = do_rest_request('/context/{:s}/slice_ids'.format(context_uuid)) + reply = do_rest_get_request('/context/{:s}/slice_ids'.format(context_uuid)) #validate_slice_ids(reply) def test_rest_get_slices(context_service_rest : RestServer): # pylint: disable=redefined-outer-name context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) - reply = do_rest_request('/context/{:s}/slices'.format(context_uuid)) + reply = do_rest_get_request('/context/{:s}/slices'.format(context_uuid)) #validate_slices(reply) def test_rest_get_slice(context_service_rest : RestServer): # pylint: disable=redefined-outer-name context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) slice_uuid = urllib.parse.quote(SLICE_R1_R3_UUID, safe='') - reply = do_rest_request('/context/{:s}/slice/{:s}'.format(context_uuid, slice_uuid)) + reply = do_rest_get_request('/context/{:s}/slice/{:s}'.format(context_uuid, slice_uuid)) #validate_slice(reply) def test_rest_get_device_ids(context_service_rest : RestServer): # pylint: disable=redefined-outer-name - reply = do_rest_request('/device_ids') + reply = do_rest_get_request('/device_ids') validate_device_ids(reply) def test_rest_get_devices(context_service_rest : RestServer): # pylint: disable=redefined-outer-name - reply = do_rest_request('/devices') + reply = do_rest_get_request('/devices') validate_devices(reply) def test_rest_get_device(context_service_rest : RestServer): # pylint: disable=redefined-outer-name device_uuid = urllib.parse.quote(DEVICE_R1_UUID, safe='') - reply = do_rest_request('/device/{:s}'.format(device_uuid)) + reply = do_rest_get_request('/device/{:s}'.format(device_uuid)) validate_device(reply) def test_rest_get_link_ids(context_service_rest : RestServer): # pylint: disable=redefined-outer-name - reply = do_rest_request('/link_ids') + reply = do_rest_get_request('/link_ids') validate_link_ids(reply) def test_rest_get_links(context_service_rest : RestServer): # pylint: disable=redefined-outer-name - reply = do_rest_request('/links') + reply = do_rest_get_request('/links') validate_links(reply) def test_rest_get_link(context_service_rest : RestServer): # pylint: disable=redefined-outer-name link_uuid = urllib.parse.quote(LINK_R1_R2_UUID, safe='') - reply = do_rest_request('/link/{:s}'.format(link_uuid)) + reply = do_rest_get_request('/link/{:s}'.format(link_uuid)) validate_link(reply) def test_rest_get_connection_ids(context_service_rest : RestServer): # pylint: disable=redefined-outer-name context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) service_uuid = urllib.parse.quote(SERVICE_R1_R3_UUID, safe='') - reply = do_rest_request('/context/{:s}/service/{:s}/connection_ids'.format(context_uuid, service_uuid)) + reply = do_rest_get_request('/context/{:s}/service/{:s}/connection_ids'.format(context_uuid, service_uuid)) validate_connection_ids(reply) def test_rest_get_connections(context_service_rest : RestServer): # pylint: disable=redefined-outer-name context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) service_uuid = urllib.parse.quote(SERVICE_R1_R3_UUID, safe='') - reply = do_rest_request('/context/{:s}/service/{:s}/connections'.format(context_uuid, service_uuid)) + reply = do_rest_get_request('/context/{:s}/service/{:s}/connections'.format(context_uuid, service_uuid)) validate_connections(reply) def test_rest_get_connection(context_service_rest : RestServer): # pylint: disable=redefined-outer-name connection_uuid = urllib.parse.quote(CONNECTION_R1_R3_UUID, safe='') - reply = do_rest_request('/connection/{:s}'.format(connection_uuid)) + reply = do_rest_get_request('/connection/{:s}'.format(connection_uuid)) validate_connection(reply) def test_rest_get_policyrule_ids(context_service_rest : RestServer): # pylint: disable=redefined-outer-name - reply = do_rest_request('/policyrule_ids') + reply = do_rest_get_request('/policyrule_ids') #validate_policyrule_ids(reply) def test_rest_get_policyrules(context_service_rest : RestServer): # pylint: disable=redefined-outer-name - reply = do_rest_request('/policyrules') + reply = do_rest_get_request('/policyrules') #validate_policyrules(reply) def test_rest_get_policyrule(context_service_rest : RestServer): # pylint: disable=redefined-outer-name policyrule_uuid = urllib.parse.quote(POLICYRULE_UUID, safe='') - reply = do_rest_request('/policyrule/{:s}'.format(policyrule_uuid)) + reply = do_rest_get_request('/policyrule/{:s}'.format(policyrule_uuid)) #validate_policyrule(reply) diff --git a/src/nbi/tests/test_unitary.py b/src/nbi/tests/test_ietf_l2vpn.py similarity index 100% rename from src/nbi/tests/test_unitary.py rename to src/nbi/tests/test_ietf_l2vpn.py diff --git a/src/nbi/tests/test_ietf_network.py b/src/nbi/tests/test_ietf_network.py index 6fa09dd650b157c8b82bdad681a7d07b74b0cd9d..78338c88f5d1c48d0a63ec4b166c9d1730f0af93 100644 --- a/src/nbi/tests/test_ietf_network.py +++ b/src/nbi/tests/test_ietf_network.py @@ -23,7 +23,7 @@ from context.client.ContextClient import ContextClient from nbi.service.rest_server import RestServer from .PrepareTestScenario import ( # pylint: disable=unused-import # be careful, order of symbols is important here! - do_rest_request, mock_service, nbi_service_rest, osm_wim, context_client) + do_rest_get_request, mock_service, nbi_service_rest, osm_wim, context_client) LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.DEBUG) @@ -79,7 +79,7 @@ def test_rest_get_networks(nbi_service_rest : RestServer): # pylint: disable=red with open(TARGET_DATA_FILE, 'r', encoding='UTF-8') as f: target_data = json.load(f) URL = '/restconf/data/ietf-network:networks' - retrieved_data = do_rest_request(URL, logger=LOGGER, expected_status_codes={200}) + retrieved_data = do_rest_get_request(URL, logger=LOGGER, expected_status_codes={200}) sort_data(retrieved_data) sort_data(target_data) diff_data = deepdiff.DeepDiff(target_data, retrieved_data)