From 2ac2b0c1c177dfec588b9c3439a41340d555897b Mon Sep 17 00:00:00 2001 From: Sithara Nambiar Date: Mon, 20 Apr 2020 21:39:33 +0530 Subject: Route optimization -take 2 Issue-ID: OPTFRA-420 Signed-off-by: Sithara Nambiar Change-Id: I06624f5adee060ce220a82f2a05a17c0986ca904 --- apps/route/optimizers/simple_route_opt.py | 149 ++++++++++--------- config/osdf_config.yaml | 4 + osdfapp.py | 2 +- .../simulators/simulated-config/osdf_config.yaml | 3 + test/simple_route_opt/AAI.json | 164 +++++++++++++++++++++ test/simple_route_opt/routeOpt.json | 34 +++++ test/test_simple_route_opt.py | 57 +++++++ 7 files changed, 346 insertions(+), 67 deletions(-) create mode 100644 test/simple_route_opt/AAI.json create mode 100644 test/simple_route_opt/routeOpt.json create mode 100644 test/test_simple_route_opt.py diff --git a/apps/route/optimizers/simple_route_opt.py b/apps/route/optimizers/simple_route_opt.py index 27c1141..9113516 100644 --- a/apps/route/optimizers/simple_route_opt.py +++ b/apps/route/optimizers/simple_route_opt.py @@ -17,6 +17,7 @@ # import requests +import json from requests.auth import HTTPBasicAuth from osdf.utils.mdc_utils import mdc_from_json @@ -35,18 +36,14 @@ class RouteOpt: """ # DNS server and standard port of AAI.. # TODO: read the port from the configuration and add to DNS - aai_host = "https://aai.api.simpledemo.onap.org:8443" - audit_log.info("base directory") - audit_log.info(BASE_DIR) aai_headers = { "X-TransactionId": "9999", "X-FromAppId": "OOF", "Accept": "application/json", "Content-Type": "application/json", - "Real-Time": "true" } - def isCrossONAPLink(self, logical_link): + def is_cross_onap_link(self, logical_link): """ This method checks if cross link is cross onap :param logical_link: @@ -57,57 +54,54 @@ class RouteOpt: return True return False - def getLinksName(self, routes,initial_start_edge,initial_end_edge, mappingTable): + def get_links_name(self, routes,initial_start_edge,initial_end_edge, mappingTable): routes=list(routes) - arr=routes[0]['x'] + try: + arr=routes[0]['x'] + except Exception as err: + audit_log.info("No satisfiable solutions found") + raise err listOfLinks=[] for i in range(0, len(routes[0]['x'])): + individual_link = {} if arr[i] == 1 : # listOfLinks.append(self.fetchLogicalLinks(initial_start_edge[i], initial_end_edge[i], mappingTable)) - listOfLinks.append(mappingTable[initial_start_edge[i] + ":" + initial_end_edge[i]]) + individual_link["link"] = mappingTable[initial_start_edge[i] + ":" + initial_end_edge[i]] + individual_link["start_node"] = initial_start_edge[i] + individual_link["end_node"] = initial_end_edge[i] + listOfLinks.append(individual_link) return listOfLinks - # def search(self, ip1, ip2, dic): - # if ip1 == "" or ip2 == "": - # return "" - # else: - # string = ip1 + ":" + ip2 - # return dic[string] - # - # def fetchLogicalLinks(self, initial_start_edge, initial_end_edge, mappingTable): - # link_name=self.search(initial_start_edge, initial_end_edge, mappingTable) - # return link_name - - - # def fetchLogicalLinks(self, initial_start_edge, initial_end_edge, mappingTable): - # return mappingTable[initial_start_edge + ":" + initial_end_edge] - def solve(self, mzn_model, dzn_data): return pymzn.minizinc(mzn=mzn_model, data=dzn_data) - def getLinks(self, mzn_model, dzn_data, initial_start_edge,initial_end_edge, mappingTable): + def get_links(self, mzn_model, dzn_data, initial_start_edge,initial_end_edge, mappingTable): routes = self.solve(mzn_model, dzn_data) audit_log.info("mocked minizinc solution====>") audit_log.info(routes) - converted_links=self.getLinksName(routes, initial_start_edge,initial_end_edge, mappingTable) + converted_links=self.get_links_name(routes, initial_start_edge,initial_end_edge, mappingTable) audit_log.info("converted links===>") audit_log.info(converted_links) return converted_links def addition(self, data): - relationship = data["relationship-list"]["relationship"] res = "" - for index, eachItem in enumerate(relationship): - if index == len(relationship) - 1: - res += eachItem["accessNodeId"] - else: - res += eachItem["accessNodeId"] + ":" - - return data["link-name"], res - - def createMapTable(self, logical_links): + if 'relationship-list' in data.keys(): + relationship = data["relationship-list"]["relationship"] + for index, eachItem in enumerate(relationship): + temp = eachItem["relationship-data"][0] + if index == len(relationship) - 1: + res += temp['relationship-value'] + else: + res += temp['relationship-value'] + ":" + + return data["link-name"], res + else: + return data["link-name"], res + + def create_map_table(self, logical_links): result = map(self.addition, logical_links) parseTemplate = {} @@ -118,30 +112,47 @@ class RouteOpt: audit_log.info(parseTemplate) return parseTemplate - def build_dzn_data(self, src_access_node_id, dst_access_node_id): + def build_dzn_data(self, src_access_node_id, dst_access_node_id, osdf_config): Edge_Start = [] Edge_End = [] - logical_links = self.get_logical_links() + logical_links = self.get_logical_links(osdf_config) + + + logical_links = logical_links['logical-link'] audit_log.info("mocked response of AAI received (logical links) successful===>") audit_log.info(logical_links) # prepare map table - mappingTable = self.createMapTable(logical_links) + mappingTable = self.create_map_table(logical_links) + audit_log.info("mapping table created successfully====>") + audit_log.info(mappingTable) # take the logical link where both the p-interface in same onap if logical_links is not None: + audit_log.info('logical links not empty=====>') for logical_link in logical_links: - if not self.isCrossONAPLink(logical_link): - # link is in local ONAP - relationship = logical_link["relationship-list"]["relationship"] - - relationshipStartNode = relationship[0] - relationshipStartNodeID = relationshipStartNode["related-link"].split("/")[-1] - start_accessNodeId = relationshipStartNodeID.split("-")[-3] - Edge_Start.append(start_accessNodeId) - - relationshipEndtNode = relationship[1] - relationshipEndNodeID = relationshipEndtNode["related-link"].split("/")[-1] - end_accessNodeId = relationshipEndNodeID.split("-")[-3] - Edge_End.append(end_accessNodeId) + audit_log.info('logical_link') + audit_log.info(logical_link) + + if 'relationship-list' in logical_link.keys(): + if not self.is_cross_onap_link(logical_link): + # link is in local ONAP + audit_log.info('link is inside onap===>') + relationship = logical_link["relationship-list"]["relationship"] + + relationshipStartNode = relationship[0] + audit_log.info('relationshipStartNode') + audit_log.info(relationshipStartNode) + relationshipStartNodeID = relationshipStartNode["related-link"].split("/")[-4] + audit_log.info('relationshipStartNodeID') + audit_log.info(relationshipStartNodeID) + Edge_Start.append(relationshipStartNodeID) + + relationshipEndtNode = relationship[1] + relationshipEndNodeID = relationshipEndtNode["related-link"].split("/")[-4] + audit_log.info('relationshipEndNodeID') + audit_log.info(relationshipEndNodeID) + Edge_End.append(relationshipEndNodeID) + else: + continue audit_log.info("edge start and end array of i/p address are===>") audit_log.info(Edge_Start) @@ -149,7 +160,6 @@ class RouteOpt: # labeling ip to number for mapping le = preprocessing.LabelEncoder() le.fit(Edge_Start + Edge_End) - # print(le.classes_) dzn_start_edge = le.transform(Edge_Start) final_dzn_start_arr = [] @@ -211,39 +221,46 @@ class RouteOpt: total_node = len(nodeSet) return total_node - def getRoute(self, request): + def get_route(self, request, osdf_config): """ This method checks :param logical_link: :return: """ - routeInfo = request["routeInfo"]["routeRequests"] - routeRequest = routeInfo[0] - src_access_node_id = routeRequest["srcPort"]["accessNodeId"] - dst_access_node_id = routeRequest["dstPort"]["accessNodeId"] + try: + routeInfo = request["routeInfo"]["routeRequests"] + routeRequest = routeInfo[0] + src_access_node_id = routeRequest["srcPort"]["accessNodeId"] + dst_access_node_id = routeRequest["dstPort"]["accessNodeId"] - dzn_data, initial_start_edge, initial_end_edge, mappingTable = self.build_dzn_data(src_access_node_id, dst_access_node_id ) - #mzn_model = "/home/root1/Videos/projects/osdf/test/functest/simulators/osdf/optimizers/routeopt/route_opt.mzn" - mzn_model = os.path.join(BASE_DIR, 'route_opt.mzn') + dzn_data, initial_start_edge, initial_end_edge, mappingTable = self.build_dzn_data(src_access_node_id, dst_access_node_id, osdf_config) + #mzn_model = "/home/root1/Videos/projects/osdf/test/functest/simulators/osdf/optimizers/routeopt/route_opt.mzn" + mzn_model = os.path.join(BASE_DIR, 'route_opt.mzn') - routeSolutions = self.getLinks(mzn_model, dzn_data, initial_start_edge,initial_end_edge, mappingTable) + routeSolutions = self.get_links(mzn_model, dzn_data, initial_start_edge,initial_end_edge, mappingTable) - return { + return { "requestId": request["requestInfo"]["requestId"], "transactionId": request["requestInfo"]["transactionId"], "statusMessage": " ", "requestStatus": "accepted", "solutions": routeSolutions - } + } + except Exception as err: + audit_log.info(err) + raise err - def get_logical_links(self): + def get_logical_links(self, osdf_config): """ This method returns list of all cross ONAP links from /aai/v14/network/logical-links?operation-status="Up" :return: logical-links[] """ - logical_link_url = "/aai/v13/network/logical-links?operational-status=up" - aai_req_url = self.aai_host + logical_link_url + + config = osdf_config.deployment + aai_url = config["aaiUrl"] + aai_req_url = aai_url + config["aaiGetLinksUrl"] + response = requests.get(aai_req_url,headers=self.aai_headers,auth=HTTPBasicAuth("AAI", "AAI"),verify=False) if response.status_code == 200: return response.json() \ No newline at end of file diff --git a/config/osdf_config.yaml b/config/osdf_config.yaml index c7c5898..4802a67 100755 --- a/config/osdf_config.yaml +++ b/config/osdf_config.yaml @@ -50,6 +50,10 @@ configDbUrl: http://config.db.url:8080 configDbGetCellListUrl: 'SDNCConfigDBAPI/getCellList' configDbGetNbrListUrl: 'SDNCConfigDBAPI/getNbrList' +#aai api +aaiUrl: "https://aai.url:30233" +aaiGetLinksUrl: "/aai/v16/network/logical-links" + pciHMSUsername: test pciHMSPassword: passwd diff --git a/osdfapp.py b/osdfapp.py index fdc2c1d..1099e55 100755 --- a/osdfapp.py +++ b/osdfapp.py @@ -101,7 +101,7 @@ def do_route_calc(): """ request_json = request.get_json() audit_log.info("Calculate Route request received!") - response = RouteOpt().getRoute(request_json) + response = RouteOpt().get_route(request_json, osdf_config) return response @app.route("/api/oof/v1/selection/nst", methods=["POST"]) diff --git a/test/functest/simulators/simulated-config/osdf_config.yaml b/test/functest/simulators/simulated-config/osdf_config.yaml index eccad14..414d7c7 100755 --- a/test/functest/simulators/simulated-config/osdf_config.yaml +++ b/test/functest/simulators/simulated-config/osdf_config.yaml @@ -71,3 +71,6 @@ configDbGetNbrListUrl: 'getNbrList' pciHMSUsername: "" # pcihandler username for call back. pciHMSPassword: "" # pcihandler password for call back. +aaiUrl: "https://api.url:30233" +aaiGetLinksUrl: "/aai/v16/network/logical-links" + diff --git a/test/simple_route_opt/AAI.json b/test/simple_route_opt/AAI.json new file mode 100644 index 0000000..6ef264b --- /dev/null +++ b/test/simple_route_opt/AAI.json @@ -0,0 +1,164 @@ +{ + "logical-link": [ + { + "link-name": "link-id-1", + "in-maint": true, + "link-type": "example-link-type-val-16287", + "resource-version": "1585009311719", + "operational-status": "UP", + "relationship-list": { + "relationship": [ + { + "related-to": "p-interface", + "relationship-label": "tosca.relationships.network.LinksTo", + "related-link": "/aai/v16/network/pnfs/pnf/20.20.20.20/p-interfaces/p-interface/p-interface-3", + "relationship-data": [ + { + "relationship-key": "pnf.pnf-name", + "relationship-value": "20.20.20.20" + }, + { + "relationship-key": "p-interface.interface-name", + "relationship-value": "p-interface-3" + } + ], + "related-to-property": [ + { + "property-key": "p-interface.prov-status" + } + ] + }, + { + "related-to": "p-interface", + "relationship-label": "tosca.relationships.network.LinksTo", + "related-link": "/aai/v16/network/pnfs/pnf/10.10.10.10/p-interfaces/p-interface/p-interface-2", + "relationship-data": [ + { + "relationship-key": "pnf.pnf-name", + "relationship-value": "10.10.10.10" + }, + { + "relationship-key": "p-interface.interface-name", + "relationship-value": "p-interface-2" + } + ], + "related-to-property": [ + { + "property-key": "p-interface.prov-status" + } + ] + } + ] + } + }, + { + "link-name": "link-id-2", + "in-maint": true, + "link-type": "example-link-type-val-16287", + "resource-version": "1584943281792", + "operational-status": "UP", + "relationship-list": { + "relationship": [ + { + "related-to": "p-interface", + "relationship-label": "tosca.relationships.network.LinksTo", + "related-link": "/aai/v16/network/pnfs/pnf/22.22.22.22/p-interfaces/p-interface/p-interface-7", + "relationship-data": [ + { + "relationship-key": "pnf.pnf-name", + "relationship-value": "22.22.22.22" + }, + { + "relationship-key": "p-interface.interface-name", + "relationship-value": "p-interface-7" + } + ], + "related-to-property": [ + { + "property-key": "p-interface.prov-status" + } + ] + }, + { + "related-to": "p-interface", + "relationship-label": "tosca.relationships.network.LinksTo", + "related-link": "/aai/v16/network/pnfs/pnf/11.11.11.11/p-interfaces/p-interface/p-interface-6", + "relationship-data": [ + { + "relationship-key": "pnf.pnf-name", + "relationship-value": "11.11.11.11" + }, + { + "relationship-key": "p-interface.interface-name", + "relationship-value": "p-interface-6" + } + ], + "related-to-property": [ + { + "property-key": "p-interface.prov-status" + } + ] + } + ] + } + }, + { + "link-name": "link-id-3", + "in-maint": true, + "link-type": "example-link-type-val-16287", + "resource-version": "1584943345290", + "operational-status": "UP", + "relationship-list": { + "relationship": [ + { + "related-to": "p-interface", + "relationship-label": "tosca.relationships.network.LinksTo", + "related-link": "/aai/v16/network/pnfs/pnf/11.11.11.11/p-interfaces/p-interface/p-interface-5", + "relationship-data": [ + { + "relationship-key": "pnf.pnf-name", + "relationship-value": "11.11.11.11" + }, + { + "relationship-key": "p-interface.interface-name", + "relationship-value": "p-interface-5" + } + ], + "related-to-property": [ + { + "property-key": "p-interface.prov-status" + } + ] + }, + { + "related-to": "p-interface", + "relationship-label": "tosca.relationships.network.LinksTo", + "related-link": "/aai/v16/network/pnfs/pnf/20.20.20.20/p-interfaces/p-interface/p-interface-4", + "relationship-data": [ + { + "relationship-key": "pnf.pnf-name", + "relationship-value": "20.20.20.20" + }, + { + "relationship-key": "p-interface.interface-name", + "relationship-value": "p-interface-4" + } + ], + "related-to-property": [ + { + "property-key": "p-interface.prov-status" + } + ] + } + ] + } + }, + { + "link-name": "rahul", + "in-maint": true, + "link-type": "example-link-type-val-rahul", + "resource-version": "1585023629505", + "operational-status": "UP" + } + ] +} \ No newline at end of file diff --git a/test/simple_route_opt/routeOpt.json b/test/simple_route_opt/routeOpt.json new file mode 100644 index 0000000..887b85b --- /dev/null +++ b/test/simple_route_opt/routeOpt.json @@ -0,0 +1,34 @@ +{ + "requestInfo": { + "transactionId": "xxx-xxx-xxxx", + "requestId": "yyy-yyy-yyyy", + "callbackUrl": "https://wiki.onap.org:5000/callbackUrl", + "sourceId": "", + "requestType": "create", + "numSolutions": 1, + "optimizers": [ + "route" + ], + "timeout": 600 + }, + "routeInfo": { + "routeRequests": [ + { + "srcPort": { + "accessTopologyId": "Topo113", + "accessClientId": "clientU12", + "accessProviderId": "VDF1234", + "accessNodeId": "22.22.22.22", + "accessLtpId": "1345" + }, + "dstPort": { + "accessTopologyId": "Topo3421", + "accessClientId": "clientD123", + "accessProviderId": "VDF3214", + "accessNodeId": "10.10.10.10", + "accessLtpId": "3452" + } + } + ] + } +} diff --git a/test/test_simple_route_opt.py b/test/test_simple_route_opt.py new file mode 100644 index 0000000..3b4facc --- /dev/null +++ b/test/test_simple_route_opt.py @@ -0,0 +1,57 @@ +from apps.route.optimizers.simple_route_opt import RouteOpt +from osdf.utils.interfaces import json_from_file +from unittest.mock import patch +import osdf.config.loader as config_loader +from osdf.utils.programming_utils import DotDict +import unittest + + +class TestSimpleRouteOptimization(unittest.TestCase): + @patch('apps.route.optimizers.simple_route_opt.requests.get') + @patch('apps.route.optimizers.simple_route_opt.pymzn.minizinc') + def test_process_nst_selection_solutions( self, mock_solve, mock_get): + + main_dir = "" + response_data_file = main_dir + "test/simple_route_opt/AAI.json" + mock_get.return_value.json.return_value = json_from_file(response_data_file) + mock_get.return_value.status_code = 200 + mock_solve.return_value = [{'x': [1, 1, 1]}] + self.config_spec = { + "deployment": "test/functest/simulators/simulated-config/osdf_config.yaml", + "core": "test/functest/simulators/simulated-config/common_config.yaml" + } + self.osdf_config = DotDict(config_loader.all_configs(**self.config_spec)) + parameter_data_file = main_dir + "test/simple_route_opt/routeOpt.json" + request_json = json_from_file(parameter_data_file) + mock_response = { + "requestId": "yyy-yyy-yyyy", + "requestStatus": "accepted", + "solutions": [ + { + "end_node": "10.10.10.10", + "link": "link-id-1", + "start_node": "20.20.20.20" + }, + { + "end_node": "11.11.11.11", + "link": "link-id-2", + "start_node": "22.22.22.22" + }, + { + "end_node": "20.20.20.20", + "link": "link-id-3", + "start_node": "11.11.11.11" + } + ], + "statusMessage": " ", + "transactionId": "xxx-xxx-xxxx" + } + routopt = RouteOpt() + actual_response = routopt.get_route(request_json,self.osdf_config) + self.assertEqual(mock_response, actual_response) + + + +if __name__ == '__main__': + unittest.main() + -- cgit 1.2.3-korg