From c7e957fb13a87f452980c2ee9dd4fee7eb3f0420 Mon Sep 17 00:00:00 2001 From: gifrerenom <lluis.gifre@cttc.es> Date: Fri, 22 Dec 2023 15:23:01 +0000 Subject: [PATCH] NBI Component - ETSI BMW: - Cosmetic cleanup in unitary test data - Multiple bug fixes - Fixed unitary tests - Added new Python requirement --- src/nbi/requirements.in | 1 + .../nbi_plugins/etsi_bwm/Resources.py | 10 +- .../rest_server/nbi_plugins/etsi_bwm/Tools.py | 12 +- src/nbi/tests/data/topology-dummy.json | 2 +- src/nbi/tests/test_etsi_bwm.py | 218 ++++++++++-------- src/nbi/tests/test_ietf_network.py | 2 +- 6 files changed, 137 insertions(+), 108 deletions(-) diff --git a/src/nbi/requirements.in b/src/nbi/requirements.in index 52094c61f..6e3eb9440 100644 --- a/src/nbi/requirements.in +++ b/src/nbi/requirements.in @@ -13,6 +13,7 @@ # limitations under the License. deepdiff==6.7.* +deepmerge==1.1.* Flask==2.1.3 Flask-HTTPAuth==4.5.0 Flask-RESTful==0.3.9 diff --git a/src/nbi/service/rest_server/nbi_plugins/etsi_bwm/Resources.py b/src/nbi/service/rest_server/nbi_plugins/etsi_bwm/Resources.py index 4c858990b..3fccbbb55 100644 --- a/src/nbi/service/rest_server/nbi_plugins/etsi_bwm/Resources.py +++ b/src/nbi/service/rest_server/nbi_plugins/etsi_bwm/Resources.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import copy, json, logging +import copy, deepmerge, json, logging from common.Constants import DEFAULT_CONTEXT_NAME from context.client.ContextClient import ContextClient from flask_restful import Resource, request @@ -69,8 +69,14 @@ class BwInfoId(_Resource): json_data = request.get_json() if not 'appInsId' in json_data: json_data['appInsId'] = allocationId - service = bwInfo_2_service(self.client, json_data) + + service = self.client.GetService(grpc_service_id(DEFAULT_CONTEXT_NAME, json_data['appInsId'])) + current_bwm = service_2_bwInfo(service) + new_bmw = deepmerge.always_merger.merge(current_bwm, json_data) + + service = bwInfo_2_service(self.client, new_bmw) self.service_client.UpdateService(service) + service = self.client.GetService(grpc_service_id(DEFAULT_CONTEXT_NAME, json_data['appInsId'])) response_bwm = service_2_bwInfo(service) diff --git a/src/nbi/service/rest_server/nbi_plugins/etsi_bwm/Tools.py b/src/nbi/service/rest_server/nbi_plugins/etsi_bwm/Tools.py index a1b66f032..a78d28193 100644 --- a/src/nbi/service/rest_server/nbi_plugins/etsi_bwm/Tools.py +++ b/src/nbi/service/rest_server/nbi_plugins/etsi_bwm/Tools.py @@ -15,8 +15,11 @@ import json import logging import time +from decimal import ROUND_HALF_EVEN, Decimal from flask.json import jsonify -from common.proto.context_pb2 import ContextId, Empty, EndPointId, ServiceId, ServiceTypeEnum, Service, Constraint, Constraint_SLA_Capacity, ConfigRule, ConfigRule_Custom, ConfigActionEnum +from common.proto.context_pb2 import ( + ContextId, Empty, EndPointId, ServiceId, ServiceTypeEnum, Service, Constraint, Constraint_SLA_Capacity, + ConfigRule, ConfigRule_Custom, ConfigActionEnum) from common.tools.grpc.Tools import grpc_message_to_json from common.tools.object_factory.Context import json_context_id from common.tools.object_factory.Service import json_service_id @@ -30,7 +33,10 @@ def service_2_bwInfo(service: Service) -> dict: response['appInsId'] = service.service_id.service_uuid.uuid # String: Application instance identifier for constraint in service.service_constraints: if constraint.WhichOneof('constraint') == 'sla_capacity': - response['fixedAllocation'] = str(constraint.sla_capacity.capacity_gbps*1000) # String: Size of requested fixed BW allocation in [bps] + # String: Size of requested fixed BW allocation in [bps] + fixed_allocation = Decimal(constraint.sla_capacity.capacity_gbps * 1.e9) + fixed_allocation = fixed_allocation.quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN) + response['fixedAllocation'] = str(fixed_allocation) break for config_rule in service.service_config.config_rules: @@ -89,7 +95,7 @@ def bwInfo_2_service(client, bwInfo: dict) -> Service: if 'fixedAllocation' in bwInfo: capacity = Constraint_SLA_Capacity() - capacity.capacity_gbps = float(bwInfo['fixedAllocation']) + capacity.capacity_gbps = float(bwInfo['fixedAllocation']) / 1.e9 constraint = Constraint() constraint.sla_capacity.CopyFrom(capacity) service.service_constraints.append(constraint) diff --git a/src/nbi/tests/data/topology-dummy.json b/src/nbi/tests/data/topology-dummy.json index 4c0f58255..4735bf446 100644 --- a/src/nbi/tests/data/topology-dummy.json +++ b/src/nbi/tests/data/topology-dummy.json @@ -2837,4 +2837,4 @@ } } ] -} +} \ No newline at end of file diff --git a/src/nbi/tests/test_etsi_bwm.py b/src/nbi/tests/test_etsi_bwm.py index 6d77aa749..8925897a7 100644 --- a/src/nbi/tests/test_etsi_bwm.py +++ b/src/nbi/tests/test_etsi_bwm.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import deepdiff, json, logging, pytest from typing import Dict -import json, logging, pytest from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME from common.proto.context_pb2 import ContextId, TopologyId from common.tools.descriptor.Loader import DescriptorLoader, check_descriptor_load_results, validate_empty_scenario @@ -21,8 +21,11 @@ from common.tools.object_factory.Context import json_context_id from common.tools.object_factory.Topology import json_topology_id from context.client.ContextClient import ContextClient from nbi.service.rest_server import RestServer -from .PrepareTestScenario import do_rest_delete_request, do_rest_post_request, do_rest_get_request, do_rest_put_request, do_rest_patch_request, mock_service, nbi_service_rest, context_client - +from .PrepareTestScenario import ( # pylint: disable=unused-import + # be careful, order of symbols is important here! + do_rest_delete_request, do_rest_get_request, do_rest_patch_request, do_rest_post_request, do_rest_put_request, + mock_service, nbi_service_rest, context_client +) LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.DEBUG) @@ -38,24 +41,24 @@ BASE_URL = '/restconf/bwm/v1' def storage() -> Dict: yield dict() -def compare_dicts(dict1, dict2): - # Function to recursively sort dictionaries - def recursively_sort(d): - if isinstance(d, dict): - return {k: recursively_sort(v) for k, v in sorted(d.items())} - if isinstance(d, list): - return [recursively_sort(item) for item in d] - return d - - # Sort dictionaries to ignore the order of fields - sorted_dict1 = recursively_sort(dict1) - sorted_dict2 = recursively_sort(dict2) - - if sorted_dict1 != sorted_dict2: - LOGGER.error(sorted_dict1) - LOGGER.error(sorted_dict2) - - return sorted_dict1 != sorted_dict2 +#def compare_dicts(dict1, dict2): +# # Function to recursively sort dictionaries +# def recursively_sort(d): +# if isinstance(d, dict): +# return {k: recursively_sort(v) for k, v in sorted(d.items())} +# if isinstance(d, list): +# return [recursively_sort(item) for item in d] +# return d +# +# # Sort dictionaries to ignore the order of fields +# sorted_dict1 = recursively_sort(dict1) +# sorted_dict2 = recursively_sort(dict2) +# +# if sorted_dict1 != sorted_dict2: +# LOGGER.error(sorted_dict1) +# LOGGER.error(sorted_dict2) +# +# return sorted_dict1 != sorted_dict2 def check_timestamps(bwm_service): assert 'timeStamp' in bwm_service @@ -71,7 +74,7 @@ def test_prepare_environment(context_client : ContextClient) -> None: # pylint: # Verify the scenario has no services/slices response = context_client.GetContext(ADMIN_CONTEXT_ID) - assert len(response.topology_ids) == 3 + assert len(response.topology_ids) == 1 assert len(response.service_ids ) == 0 assert len(response.slice_ids ) == 0 @@ -81,23 +84,21 @@ def test_get_allocations_empty(nbi_service_rest : RestServer, storage : Dict): # LOGGER.debug('retrieved_data={:s}'.format(json.dumps(retrieved_data, sort_keys=True))) assert len(retrieved_data) == 0 -def test_allocation(nbi_service_rest : RestServer, storage : Dict): +def test_allocation(nbi_service_rest : RestServer, storage : Dict): # pylint: disable=redefined-outer-name, unused-argument URL = BASE_URL + '/bw_allocations' data = { - "allocationDirection":"string", - "appInsId":"service_uuid_01", - "fixedAllocation":"123000.0", - "fixedBWPriority":"SEE_DESCRIPTION", - "requestType":0, - "sessionFilter":[ - { - "dstAddress":"192.168.3.2", - "dstPort":["b"], - "protocol":"string", - "sourceIp":"192.168.1.2", - "sourcePort":["a"] - } - ] + "appInsId" : "service_uuid_01", + "allocationDirection" : "00", + "fixedAllocation" : "123000.0", + "fixedBWPriority" : "SEE_DESCRIPTION", + "requestType" : 0, + "sessionFilter" : [{ + "sourceIp" : "192.168.1.2", + "sourcePort" : ["a"], + "protocol" : "string", + "dstAddress" : "192.168.3.2", + "dstPort" : ["b"], + }] } retrieved_data = do_rest_post_request(URL, body=data, logger=LOGGER, expected_status_codes={200}) LOGGER.debug('retrieved_data={:s}'.format(json.dumps(retrieved_data, sort_keys=True))) @@ -112,24 +113,25 @@ def test_get_allocations(nbi_service_rest : RestServer, storage : Dict): # pylin assert len(retrieved_data) == 1 good_result = [ { - "appInsId":"service_uuid_01", - "fixedAllocation":"123000.0", - "allocationDirection":"string", - "fixedBWPriority":"SEE_DESCRIPTION", - "requestType":"0", - "sessionFilter":[ - { - "dstAddress":"192.168.3.2", - "dstPort":["b"], - "protocol":"string", - "sourceIp":"192.168.1.2", - "sourcePort":["a"] - } - ], + "appInsId" : "service_uuid_01", + "fixedAllocation" : "123000.0", + "allocationDirection" : "00", + "fixedBWPriority" : "SEE_DESCRIPTION", + "requestType" : "0", + "sessionFilter" : [{ + "sourceIp" : "192.168.1.2", + "sourcePort" : ["a"], + "protocol" : "string", + "dstAddress" : "192.168.3.2", + "dstPort" : ["b"], + }], } ] - compare_dicts(retrieved_data, good_result) check_timestamps(retrieved_data[0]) + del retrieved_data[0]['timeStamp'] + diff_data = deepdiff.DeepDiff(good_result, retrieved_data) + LOGGER.error('Differences:\n{:s}'.format(str(diff_data.pretty()))) + assert len(diff_data) == 0 def test_get_allocation(nbi_service_rest : RestServer, storage : Dict): # pylint: disable=redefined-outer-name, unused-argument @@ -138,48 +140,49 @@ def test_get_allocation(nbi_service_rest : RestServer, storage : Dict): # pylint retrieved_data = do_rest_get_request(URL, logger=LOGGER, expected_status_codes={200}) LOGGER.debug('retrieved_data={:s}'.format(json.dumps(retrieved_data, sort_keys=True))) good_result = { - "appInsId":"service_uuid_01", - "fixedAllocation":"123000.0", - "allocationDirection":"string", - "fixedBWPriority":"SEE_DESCRIPTION", - "requestType":"0", - "sessionFilter":[ - { - "dstAddress":"192.168.3.2", - "dstPort":["b"], - "protocol":"string", - "sourceIp":"192.168.1.2", - "sourcePort":["a"] - } - ] + "appInsId" : "service_uuid_01", + "fixedAllocation" : "123000.0", + "allocationDirection": "00", + "fixedBWPriority" : "SEE_DESCRIPTION", + "requestType" : "0", + "sessionFilter" : [{ + "sourceIp" : "192.168.1.2", + "sourcePort" : ["a"], + "protocol" : "string", + "dstAddress" : "192.168.3.2", + "dstPort" : ["b"], + }] } - - compare_dicts(retrieved_data, good_result) check_timestamps(retrieved_data) + del retrieved_data['timeStamp'] + diff_data = deepdiff.DeepDiff(good_result, retrieved_data) + LOGGER.error('Differences:\n{:s}'.format(str(diff_data.pretty()))) + assert len(diff_data) == 0 def test_put_allocation(nbi_service_rest : RestServer, storage : Dict): # pylint: disable=redefined-outer-name, unused-argument assert 'service_uuid_01' in storage URL = BASE_URL + '/bw_allocations/service_uuid_01' changed_allocation = { - "appInsId":"service_uuid_01", - "fixedAllocation":"200.0", - "allocationDirection":"parriba", - "fixedBWPriority":"NOPRIORITY", - "requestType":"0", - "sessionFilter":[ - { - "dstAddress":"192.168.3.2", - "dstPort":["b"], - "protocol":"string", - "sourceIp":"192.168.1.2", - "sourcePort":["a"] - } - ] + "appInsId" : "service_uuid_01", + "fixedAllocation" : "200.0", + "allocationDirection": "00", + "fixedBWPriority" : "NOPRIORITY", + "requestType" : "0", + "sessionFilter" : [{ + "sourceIp" : "192.168.1.2", + "sourcePort" : ["a"], + "protocol" : "string", + "dstAddress" : "192.168.3.2", + "dstPort" : ["b"], + }] } retrieved_data = do_rest_put_request(URL, body=json.dumps(changed_allocation), logger=LOGGER, expected_status_codes={200}) - compare_dicts(retrieved_data, changed_allocation) check_timestamps(retrieved_data) + del retrieved_data['timeStamp'] + diff_data = deepdiff.DeepDiff(changed_allocation, retrieved_data) + LOGGER.error('Differences:\n{:s}'.format(str(diff_data.pretty()))) + assert len(diff_data) == 0 def test_patch_allocation(nbi_service_rest : RestServer, storage : Dict): # pylint: disable=redefined-outer-name, unused-argument @@ -189,26 +192,25 @@ def test_patch_allocation(nbi_service_rest : RestServer, storage : Dict): # pyli "fixedBWPriority":"FULLPRIORITY", } changed_allocation = { - "appInsId":"service_uuid_01", - "fixedAllocation":"200.0", - "allocationDirection":"parriba", - "fixedBWPriority":"FULLPRIORITY", - "requestType":"0", - "sessionFilter":[ - { - "dstAddress":"192.168.3.2", - "dstPort":["b"], - "protocol":"string", - "sourceIp":"192.168.1.2", - "sourcePort":["a"] - } - ] + "appInsId" : "service_uuid_01", + "fixedAllocation" : "200.0", + "allocationDirection": "00", + "fixedBWPriority" : "FULLPRIORITY", + "requestType" : "0", + "sessionFilter" : [{ + "sourceIp" : "192.168.1.2", + "sourcePort" : ["a"], + "protocol" : "string", + "dstAddress" : "192.168.3.2", + "dstPort" : ["b"], + }] } - - retrieved_data = do_rest_patch_request(URL, body=changed_allocation, logger=LOGGER, expected_status_codes={200}) - compare_dicts(retrieved_data, changed_allocation) + retrieved_data = do_rest_patch_request(URL, body=difference, logger=LOGGER, expected_status_codes={200}) check_timestamps(retrieved_data) - + del retrieved_data['timeStamp'] + diff_data = deepdiff.DeepDiff(changed_allocation, retrieved_data) + LOGGER.error('Differences:\n{:s}'.format(str(diff_data.pretty()))) + assert len(diff_data) == 0 def test_delete_allocation(nbi_service_rest : RestServer, storage : Dict): # pylint: disable=redefined-outer-name, unused-argument @@ -222,3 +224,17 @@ def test_get_allocations_empty_final(nbi_service_rest : RestServer, storage : Di retrieved_data = do_rest_get_request(URL, logger=LOGGER, expected_status_codes={200}) LOGGER.debug('retrieved_data={:s}'.format(json.dumps(retrieved_data, sort_keys=True))) assert len(retrieved_data) == 0 + + +def test_cleanup_environment(context_client : ContextClient) -> None: # pylint: disable=redefined-outer-name + # Verify the scenario has no services/slices + response = context_client.GetContext(ADMIN_CONTEXT_ID) + assert len(response.topology_ids) == 1 + assert len(response.service_ids ) == 0 + assert len(response.slice_ids ) == 0 + + # Load descriptors and validate the base scenario + descriptor_loader = DescriptorLoader(descriptors_file=DESCRIPTOR_FILE, context_client=context_client) + descriptor_loader.validate() + descriptor_loader.unload() + validate_empty_scenario(context_client) diff --git a/src/nbi/tests/test_ietf_network.py b/src/nbi/tests/test_ietf_network.py index e41a88af0..fb39a9192 100644 --- a/src/nbi/tests/test_ietf_network.py +++ b/src/nbi/tests/test_ietf_network.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict import deepdiff, json, logging, operator +from typing import Dict from common.Constants import DEFAULT_CONTEXT_NAME from common.proto.context_pb2 import ContextId from common.tools.descriptor.Loader import DescriptorLoader, check_descriptor_load_results, validate_empty_scenario -- GitLab