diff --git a/src/nbi/requirements.in b/src/nbi/requirements.in index 52094c61f2b50e99a35a8ca999c579422b7f22b7..6e3eb94404f9d12431c715080cf210a02c7c82f4 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 4c858990b1cc794e4bd3ce7714442fd4f621ace0..3fccbbb55c8959ae0c478526d2ea24882ebfef1f 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 a1b66f0325842729fa972bd9ff4d297cd750414a..a78d2819317623b9ccaaec62e808f6435c88b630 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 4c0f582554ec2fbd5c1cc27176eeb14626e88ac9..4735bf4465220432641793ab253d94de1365c534 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 6d77aa74975113bfd175b9be1648e605bba98cf2..8925897a720a6afa981e9ea0cd9c0d9a5261c5cd 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 e41a88af07db0e266e80ac082a519c41c86fe523..fb39a91922ca0d76f950c5e0c8c98847547b7ac9 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