Inter Domain Route Calculation for 99/109699/5
authorMehreen Kaleem <mehreen.kaleem@us.fujitsu.com>
Tue, 30 Jun 2020 16:49:05 +0000 (16:49 +0000)
committerkrishna moorthy <krishna.moorthy6@wipro.com>
Fri, 14 Aug 2020 09:31:38 +0000 (09:31 +0000)
the MDONS use case

Change-Id: Ic831fd92028ef3c1ac86f5067d68c19a7af3e8d6
Issue-ID: OPTFRA-753
Signed-off-by: Mehreen Kaleem <mehreen.kaleem@us.fujitsu.com>
apps/route/optimizers/inter_domain_route_opt.py [new file with mode: 0644]
config/osdf_config.yaml
osdfapp.py
test/functest/simulators/simulated-config/osdf_config.yaml
test/inter_domain_route_opt/bandwidth_attributes.json [new file with mode: 0644]
test/inter_domain_route_opt/controllers_for_interfaces.json [new file with mode: 0644]
test/inter_domain_route_opt/controllers_list.json [new file with mode: 0644]
test/inter_domain_route_opt/get_links.json [new file with mode: 0644]
test/inter_domain_route_opt/request.json [new file with mode: 0644]
test/test_inter_domain_route_opt.py [new file with mode: 0644]

diff --git a/apps/route/optimizers/inter_domain_route_opt.py b/apps/route/optimizers/inter_domain_route_opt.py
new file mode 100644 (file)
index 0000000..253c7b2
--- /dev/null
@@ -0,0 +1,370 @@
+# -------------------------------------------------------------------------
+#   Copyright (c) 2020 Fujitsu Limited Intellectual Property
+#
+#   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 os
+import itertools
+import json
+import requests
+from requests.auth import HTTPBasicAuth
+import urllib3
+
+from osdf.logging.osdf_logging import audit_log
+import pymzn
+from sklearn import preprocessing
+
+BASE_DIR = os.path.dirname(__file__)
+urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
+
+
+class InterDomainRouteOpt:
+
+    """
+    This values will need to deleted..
+    only added for the debug purpose
+    """
+    aai_headers = {
+        "X-TransactionId": "9999",
+        "X-FromAppId": "OOF",
+        "Accept": "application/json",
+        "Content-Type": "application/json",
+    }
+
+
+    def get_route(self, request, osdf_config):
+        """
+        This method processes the mdons route request
+        and returns an optimised path for the given
+        two ports
+        """
+
+        try:
+            route_info = request["routeInfo"]["routeRequest"]
+            src_controller_id = route_info["srcDetails"]["controllerId"]
+            src_port_id = route_info["srcDetails"]["interfaceId"]
+            dst_controller_id = route_info["dstDetails"]["controllerId"]
+            dst_port_id = route_info["dstDetails"]["interfaceId"]
+            service_rate = route_info["serviceRate"]
+            dzn_data, mapping_table = self.build_dzn_data(osdf_config, src_controller_id,
+                                                          dst_controller_id, service_rate)
+            audit_log.info("Dzn data")
+            audit_log.info(dzn_data)
+            mzn_model = os.path.join(BASE_DIR, 'route_opt.mzn')
+            links_list = self.find_suitable_path(mzn_model, dzn_data, mapping_table)
+            ordered_list = self.get_ordered_route_list(links_list,
+                                                       src_controller_id, dst_controller_id)
+            solution = self.get_solution_object(ordered_list, src_port_id, dst_port_id)
+            return {
+                "requestId": request["requestInfo"]["requestId"],
+                "transactionId": request["requestInfo"]["transactionId"],
+                "statusMessage": "SUCCESS",
+                "requestStatus": "accepted",
+                "solutions": solution
+                }
+        except Exception as err:
+            audit_log.info(err)
+            raise err
+
+    def get_solution_object(self, ordered_list, src_port_id, dst_port_id):
+        """
+        :param ordered_list: service_route list
+        :param src_port_id: source port id of route
+        :param dst_port_id: destination port id of route
+        :return: solution object of the route respone
+        """
+        service_route_list = []
+        link_list = []
+        for value in ordered_list:
+            service_route_object = {}
+            service_route_object["srcInterfaceId"] = src_port_id
+            service_route_object["dstInterfaceId"] = value["srcPortId"]
+            service_route_object["controllerId"] = value["srcControllerId"]
+            service_route_list.append(service_route_object)
+            link_list.append(value["linkName"])
+            src_port_id = value["dstPortId"]
+            dst_controller_id = value["dstControllerId"]
+        service_route_object = {}
+        service_route_object["srcInterfaceId"] = src_port_id
+        service_route_object["dstInterfaceId"] = dst_port_id
+        service_route_object["controllerId"] = dst_controller_id
+        service_route_list.append(service_route_object)
+        route_info_object = {
+            "serviceRoute" : service_route_list,
+            "linkList" : link_list
+            }
+        solution = {
+            "routeInfo" : route_info_object
+            }
+        return solution
+
+
+    def get_ordered_route_list(self, link_list, src_controller_id, dst_controller_id):
+        """
+        :param link_list: link list from the minizinc response
+        :param src_controller_id: source port id of route
+        :param dst_controller_id: destination port id of route
+        :return: route list in order
+        """
+        ordered_link_list = []
+        flag = True
+        while flag:
+            for item in link_list:
+                if item["srcControllerId"] == src_controller_id:
+                    ordered_link_list.append(item)
+                    src_controller_id = item["dstControllerId"]
+                    if src_controller_id == dst_controller_id:
+                        flag = False
+        return ordered_link_list
+
+
+    def find_suitable_path(self, mzn_model, dzn_data, mapping_table):
+        """
+        :param mzn_model: minizinc model details
+        :param dzn_data: minizinc data
+        :param mapping_table: list that maintains AAI link details
+        :return: list of link from after running minizinc
+        """
+        minizinc_solution = self.solve(mzn_model, dzn_data)
+        audit_log.info("Minizinc Solution ==========>")
+        routes = list(minizinc_solution)
+        audit_log.info(routes)
+        try:
+            arr = routes[0]['x']
+        except Exception as err:
+            audit_log.info("No minizinc solutions found")
+            raise err
+        links_list = []
+        for i in range(0, len(routes[0]['x'])):
+            if arr[i] == 1:
+                links_list.append(mapping_table[i])
+        return links_list
+
+
+    def process_inter_domain_link(self, logical_link, osdf_config):
+        """
+        :param logical_link: logical links from AAI
+        :param osdf_config: OSDF config details
+        :return: list of link object with src and dst controller details
+        """
+        link_details = {}
+        link_details["linkName"] = logical_link["link-name"]
+        relationship = logical_link["relationship-list"]["relationship"]
+        flag = 1
+
+        for value in relationship:
+            if value["related-to"] == "p-interface" and flag == 1:
+                src_port_id = value["relationship-data"][1]["relationship-value"]
+                src_controller_id = self.get_controller_for_interface(osdf_config, src_port_id)
+                link_details["srcPortId"] = src_port_id
+                link_details["srcControllerId"] = src_controller_id
+                flag += 1
+            elif value["related-to"] == "p-interface" and flag == 2:
+                dest_port_id = value["relationship-data"][1]["relationship-value"]
+                dest_controller_id = self.get_controller_for_interface(osdf_config, dest_port_id)
+                link_details["dstPortId"] = dest_port_id
+                link_details["dstControllerId"] = dest_controller_id
+        return link_details
+
+
+    def prepare_map_table(self, osdf_config, logical_links):
+        """
+        :param logical_links: logical links from AAI
+        :param osdf_config: OSDF config details
+        :return: list of link object with src and dst controller details
+        """
+        results = map(self.process_inter_domain_link, logical_links,
+                      itertools.repeat(osdf_config, len(logical_links)))
+        new_results = list(results)
+
+        new_list = []
+        new_list += new_results
+        for i in new_results:
+            link_details = {}
+            link_details["linkName"] = i["linkName"]
+            link_details["srcPortId"] = i["dstPortId"]
+            link_details["srcControllerId"] = i["dstControllerId"]
+            link_details["dstPortId"] = i["srcPortId"]
+            link_details["dstControllerId"] = i["srcControllerId"]
+            new_list.append(link_details)
+        return new_list
+
+
+    def solve(self, mzn_model, dzn_data):
+        """
+        :param mzn_model: minizinc template
+        :param dzn_data: minizinc data model
+        :return: minizinc response
+        """
+        return pymzn.minizinc(mzn=mzn_model, data=dzn_data)
+
+
+    def get_links_based_on_bandwidth_attributes(self, logical_links_list,
+                                                osdf_config, service_rate):
+        """
+        This method filters the logical links based on the
+        bandwidth attribute availability of the interfaces
+        from AAI
+        :return: filtered_list[]
+        """
+        filtered_list = []
+        for logical_link in logical_links_list:
+            relationship = logical_link["relationship-list"]["relationship"]
+            count = 0
+            for value in relationship:
+                if value["related-to"] == "p-interface":
+                    interface_url = value["related-link"]
+                    if self.get_available_bandwidth_aai(interface_url, osdf_config, service_rate):
+                        count += 1
+            if count == 2:
+                filtered_list.append(logical_link)
+
+        return  filtered_list
+
+
+    def build_dzn_data(self, osdf_config, src_controller_id, dst_controller_id, service_rate):
+        """
+        :param osdf_config: OSDF config details
+        :param src_controller_id: controller Id of the source port
+        :param dst_controller_id: controller id of the destination port
+        :param service_rate: service rate
+        :return: mapping atble which maintains link details from AAI
+        and minizinc data model to be used by template
+        """
+        logical_links = self.get_inter_domain_links(osdf_config)
+        logical_links_list = logical_links["logical-link"]
+        mapping_table = self.prepare_map_table(osdf_config,
+                                               self.get_links_based_on_bandwidth_attributes(logical_links_list, osdf_config, service_rate))
+
+        edge_start = []
+        edge_end = []
+        for item in mapping_table:
+            edge_start.append(item["srcControllerId"])
+            edge_end.append(item["dstControllerId"])
+        link_cost = []
+        for k in range(0, len(edge_start)):
+            link_cost.append(1)
+        list_controllers = self.get_controllers_from_aai(osdf_config)
+        le = preprocessing.LabelEncoder()
+        le.fit(list_controllers)
+
+        start_edge = le.transform(edge_start)
+        end_edge = le.transform(edge_end)
+        source = le.transform([src_controller_id])
+        destination = le.transform([dst_controller_id])
+
+        final_dzn_start_arr = []
+        for i in start_edge:
+            final_dzn_start_arr.append(i)
+
+        final_dzn_end_arr = []
+        for j in end_edge:
+            final_dzn_end_arr.append(j)
+
+        contollers_length = len(list_controllers)
+        no_of_edges = len(final_dzn_start_arr)
+        dzn_data = {
+            'N': contollers_length,
+            'M': no_of_edges,
+            'Edge_Start': final_dzn_start_arr,
+            'Edge_End': final_dzn_end_arr,
+            'L': link_cost,
+            'Start': source[0],
+            'End' : destination[0]
+            }
+        return dzn_data, mapping_table
+
+
+    def get_inter_domain_links(self, osdf_config):
+        """
+        This method returns list of all cross ONAP links
+        from /aai/v19/network/logical-links?link-type=inter-domain&operational-status="Up"
+        :return: logical-links[]
+        """
+
+        config = osdf_config.deployment
+        aai_url = config["aaiUrl"]
+        aai_req_url = aai_url + config["aaiGetInterDomainLinksUrl"]
+        response = requests.get(aai_req_url, headers=self.aai_headers,
+                                auth=HTTPBasicAuth("AAI", "AAI"), verify=False)
+        if response.status_code == 200:
+            return response.json()
+
+
+    def get_controller_for_interface(self, osdf_config, port_id):
+        """
+        This method returns returns the controller id
+        given a p-interface from the below query
+        :return: controller_id
+        """
+        data = {
+            "start": ["external-system"],
+            "query": "query/getDomainController?portid="
+        }
+        query = data.get("query") + port_id
+        data.update(query=query)
+        config = osdf_config.deployment
+        aai_url = config["aaiUrl"]
+        aai_req_url = aai_url + config["controllerQueryUrl"]
+        response = requests.put(aai_req_url, data=json.dumps(data),
+                                headers=self.aai_headers,
+                                auth=HTTPBasicAuth("AAI", "AAI"),
+                                verify=False)
+        if response.status_code == 200:
+            response_body = response.json()
+            return response_body["results"][0]["esr-thirdparty-sdnc"]["thirdparty-sdnc-id"]
+
+
+    def get_controllers_from_aai(self, osdf_config):
+        """
+        This method returns returns the list of
+        controller names in AAI
+        :return: controllers_list[]
+        """
+        controllers_list = []
+        config = osdf_config.deployment
+        aai_url = config["aaiUrl"]
+        aai_req_url = aai_url + config["aaiGetControllersUrl"]
+        response = requests.get(aai_req_url,
+                                headers=self.aai_headers,
+                                auth=HTTPBasicAuth("AAI", "AAI"),
+                                verify=False)
+        if response.status_code == 200:
+            response_body = response.json()
+            esr_thirdparty_list = response_body["esr-thirdparty-sdnc"]
+
+            for item in esr_thirdparty_list:
+                controllers_list.append(item["thirdparty-sdnc-id"])
+            return controllers_list
+
+
+    def get_available_bandwidth_aai(self, interface_url, osdf_config, service_rate):
+        """
+        Checks if the given interface has the required bandwidth
+        :return: boolean flag
+        """
+        config = osdf_config.deployment
+        aai_url = config["aaiUrl"]
+        aai_req_url = aai_url + interface_url + "?depth=all"
+        response = requests.get(aai_req_url,
+                                headers=self.aai_headers,
+                                auth=HTTPBasicAuth("AAI", "AAI"), verify=False)
+        if response.status_code == 200:
+            response_body = response.json()
+            available_bandwidth = response_body["bandwidth-attributes"]["bandwidth-attribute"][0]["available-bandwidth-map"]["available-bandwidth"]
+            for i in available_bandwidth:
+                if i["odu-type"] == service_rate and i["number"] > 0:
+                    return True
index 4802a67..19f5574 100755 (executable)
@@ -53,6 +53,10 @@ configDbGetNbrListUrl: 'SDNCConfigDBAPI/getNbrList'
 #aai api
 aaiUrl: "https://aai.url:30233"
 aaiGetLinksUrl: "/aai/v16/network/logical-links"
+aaiGetControllersUrl: /aai/v19/external-system/esr-thirdparty-sdnc-list
+controllerQueryUrl: /aai/v19/query?format=resource
+aaiGetInterDomainLinksUrl: /aai/v19/network/logical-links?link-type=inter-domain&operational-status=up
+
 
 pciHMSUsername: test
 pciHMSPassword: passwd
index 1099e55..b455292 100755 (executable)
@@ -33,6 +33,7 @@ from apps.nst.optimizers.nst_select_processor import process_nst_selection
 from apps.pci.optimizers.pci_opt_processor import process_pci_optimation
 from apps.placement.models.api.placementRequest import PlacementAPI
 from apps.placement.optimizers.conductor.remote_opt_processor import process_placement_opt
+from apps.route.optimizers.inter_domain_route_opt import InterDomainRouteOpt
 from apps.route.optimizers.simple_route_opt import RouteOpt
 from apps.slice_selection.models.api.nsi_selection_request import NSISelectionAPI
 from apps.slice_selection.optimizers.conductor.remote_opt_processor import process_nsi_selection_opt
@@ -104,6 +105,16 @@ def do_route_calc():
     response = RouteOpt().get_route(request_json, osdf_config)
     return response
 
+@app.route("/api/oof/mdons/route/v1", methods=["POST"])
+def do_mdons_route_calc():
+    """
+    Perform the inter domain route calculation
+    """
+    request_json = request.get_json()
+    audit_log.info("Inter Domain Calculation  Route request received!")
+    response = InterDomainRouteOpt().get_route(request_json, osdf_config)
+    return response
+
 @app.route("/api/oof/v1/selection/nst", methods=["POST"])
 def do_nst_selection():
     request_json = request.get_json()
index 414d7c7..d4d20c9 100755 (executable)
@@ -73,4 +73,8 @@ pciHMSPassword: ""   # pcihandler password for call back.
 
 aaiUrl: "https://api.url:30233"
 aaiGetLinksUrl: "/aai/v16/network/logical-links"
+aaiGetControllersUrl: /aai/v19/external-system/esr-thirdparty-sdnc-list
+controllerQueryUrl: /aai/v19/query?format=resource
+aaiGetInterDomainLinksUrl: /aai/v19/network/logical-links?link-type=inter-domain&operational-status=up
+
 
diff --git a/test/inter_domain_route_opt/bandwidth_attributes.json b/test/inter_domain_route_opt/bandwidth_attributes.json
new file mode 100644 (file)
index 0000000..0de7e51
--- /dev/null
@@ -0,0 +1,176 @@
+{
+   "int-1-bw":{
+      "interface-name":"int1",
+      "bandwidth-attributes":{
+         "bandwidth-attribute":[
+            {
+               "bwa-id":"bw6",
+               "resource-version":"1596387588545",
+               "available-bandwidth-map":{
+                  "available-bandwidth":[
+                     {
+                        "ab-id":"ab226",
+                        "odu-type":"ODU2",
+                        "number":1,
+                        "resource-version":"1596387588545"
+                     },
+                     {
+                        "ab-id":"ab112",
+                        "odu-type":"ODU4",
+                        "number":8,
+                        "resource-version":"1596387588545"
+                     }
+                  ]
+               }
+            }
+         ]
+      },
+      "resource-version":"1596387588545",
+      "in-maint":false
+   },
+   "int-3-bw":{
+      "interface-name":"int3",
+      "bandwidth-attributes":{
+         "bandwidth-attribute":[
+            {
+               "bwa-id":"bw6",
+               "resource-version":"1596387588545",
+               "available-bandwidth-map":{
+                  "available-bandwidth":[
+                     {
+                        "ab-id":"ab226",
+                        "odu-type":"ODU2",
+                        "number":1,
+                        "resource-version":"1596387588545"
+                     },
+                     {
+                        "ab-id":"ab112",
+                        "odu-type":"ODU4",
+                        "number":8,
+                        "resource-version":"1596387588545"
+                     }
+                  ]
+               }
+            }
+         ]
+      },
+      "resource-version":"1596387588545",
+      "in-maint":false
+   },
+   "int-4-bw":{
+      "interface-name":"int4",
+      "bandwidth-attributes":{
+         "bandwidth-attribute":[
+            {
+               "bwa-id":"bw6",
+               "resource-version":"1596387588545",
+               "available-bandwidth-map":{
+                  "available-bandwidth":[
+                     {
+                        "ab-id":"ab226",
+                        "odu-type":"ODU2",
+                        "number":1,
+                        "resource-version":"1596387588545"
+                     },
+                     {
+                        "ab-id":"ab112",
+                        "odu-type":"ODU4",
+                        "number":8,
+                        "resource-version":"1596387588545"
+                     }
+                  ]
+               }
+            }
+         ]
+      },
+      "resource-version":"1596387588545",
+      "in-maint":false
+   },
+   "int-5-bw":{
+      "interface-name":"int5",
+      "bandwidth-attributes":{
+         "bandwidth-attribute":[
+            {
+               "bwa-id":"bw6",
+               "resource-version":"1596387588545",
+               "available-bandwidth-map":{
+                  "available-bandwidth":[
+                     {
+                        "ab-id":"ab226",
+                        "odu-type":"ODU2",
+                        "number":1,
+                        "resource-version":"1596387588545"
+                     },
+                     {
+                        "ab-id":"ab112",
+                        "odu-type":"ODU4",
+                        "number":8,
+                        "resource-version":"1596387588545"
+                     }
+                  ]
+               }
+            }
+         ]
+      },
+      "resource-version":"1596387588545",
+      "in-maint":false
+   },
+   "int-6-bw":{
+      "interface-name":"int6",
+      "bandwidth-attributes":{
+         "bandwidth-attribute":[
+            {
+               "bwa-id":"bw6",
+               "resource-version":"1596387588545",
+               "available-bandwidth-map":{
+                  "available-bandwidth":[
+                     {
+                        "ab-id":"ab226",
+                        "odu-type":"ODU2",
+                        "number":1,
+                        "resource-version":"1596387588545"
+                     },
+                     {
+                        "ab-id":"ab112",
+                        "odu-type":"ODU4",
+                        "number":8,
+                        "resource-version":"1596387588545"
+                     }
+                  ]
+               }
+            }
+         ]
+      },
+      "resource-version":"1596387588545",
+      "in-maint":false
+   },
+   "int-7-bw":{
+      "interface-name":"int7",
+      "bandwidth-attributes":{
+         "bandwidth-attribute":[
+            {
+               "bwa-id":"bw6",
+               "resource-version":"1596387588545",
+               "available-bandwidth-map":{
+                  "available-bandwidth":[
+                     {
+                        "ab-id":"ab226",
+                        "odu-type":"ODU2",
+                        "number":1,
+                        "resource-version":"1596387588545"
+                     },
+                     {
+                        "ab-id":"ab112",
+                        "odu-type":"ODU4",
+                        "number":8,
+                        "resource-version":"1596387588545"
+                     }
+                  ]
+               }
+            }
+         ]
+      },
+      "resource-version":"1596387588545",
+      "in-maint":false
+   }
+}
diff --git a/test/inter_domain_route_opt/controllers_for_interfaces.json b/test/inter_domain_route_opt/controllers_for_interfaces.json
new file mode 100644 (file)
index 0000000..3de47d1
--- /dev/null
@@ -0,0 +1,62 @@
+{
+   "int-1-cont":{
+      "results":[
+         {
+            "esr-thirdparty-sdnc":{
+               "thirdparty-sdnc-id":"Controller1",
+               "resource-version":"1593421890494"
+            }
+         }
+      ]
+   },
+   "int-3-cont":{
+      "results":[
+         {
+            "esr-thirdparty-sdnc":{
+               "thirdparty-sdnc-id":"Controller2",
+               "resource-version":"1593421890494"
+            }
+         }
+      ]
+   },
+   "int-4-cont":{
+      "results":[
+         {
+            "esr-thirdparty-sdnc":{
+               "thirdparty-sdnc-id":"Controller2",
+               "resource-version":"1593421890494"
+            }
+         }
+      ]
+   },
+   "int-5-cont":{
+      "results":[
+         {
+            "esr-thirdparty-sdnc":{
+               "thirdparty-sdnc-id":"Controller3",
+               "resource-version":"1593421890494"
+            }
+         }
+      ]
+   },
+   "int-6-cont":{
+      "results":[
+         {
+            "esr-thirdparty-sdnc":{
+               "thirdparty-sdnc-id":"Controller3",
+               "resource-version":"1593421890494"
+            }
+         }
+      ]
+   },
+   "int-7-cont":{
+      "results":[
+         {
+            "esr-thirdparty-sdnc":{
+               "thirdparty-sdnc-id":"Controller4",
+               "resource-version":"1593421890494"
+            }
+         }
+      ]
+   }
+}
diff --git a/test/inter_domain_route_opt/controllers_list.json b/test/inter_domain_route_opt/controllers_list.json
new file mode 100644 (file)
index 0000000..158f530
--- /dev/null
@@ -0,0 +1,16 @@
+{
+   "esr-thirdparty-sdnc":[
+      {
+         "thirdparty-sdnc-id":"Controller1"
+      },
+      {
+         "thirdparty-sdnc-id":"Controller2"
+      },
+      {
+         "thirdparty-sdnc-id":"Controller3"
+      },
+      {
+         "thirdparty-sdnc-id":"Controller4"
+      }
+   ]
+}
diff --git a/test/inter_domain_route_opt/get_links.json b/test/inter_domain_route_opt/get_links.json
new file mode 100644 (file)
index 0000000..0e70523
--- /dev/null
@@ -0,0 +1,157 @@
+{
+   "logical-link":[
+      {
+         "link-name":"link1",
+         "in-maint":false,
+         "link-type":"inter-domain",
+         "resource-version":"1588952379221",
+         "operational-status":"up",
+         "relationship-list":{
+            "relationship":[
+               {
+                  "related-to":"p-interface",
+                  "relationship-label":"tosca.relationships.network.LinksTo",
+                  "related-link":"/aai/v19/network/pnfs/pnf/pnf1/p-interfaces/p-interface/int1",
+                  "relationship-data":[
+                     {
+                        "relationship-key":"pnf.pnf-name",
+                        "relationship-value":"pnf1"
+                     },
+                     {
+                        "relationship-key":"p-interface.interface-name",
+                        "relationship-value":"int1"
+                     }
+                  ],
+                  "related-to-property":[
+                     {
+                        "property-key":"p-interface.prov-status"
+                     }
+                  ]
+               },
+               {
+                  "related-to":"p-interface",
+                  "relationship-label":"tosca.relationships.network.LinksTo",
+                  "related-link":"/aai/v19/network/pnfs/pnf/pnf2/p-interfaces/p-interface/int3",
+                  "relationship-data":[
+                     {
+                        "relationship-key":"pnf.pnf-name",
+                        "relationship-value":"pnf2"
+                     },
+                     {
+                        "relationship-key":"p-interface.interface-name",
+                        "relationship-value":"int3"
+                     }
+                  ],
+                  "related-to-property":[
+                     {
+                        "property-key":"p-interface.prov-status"
+                     }
+                  ]
+               }
+            ]
+         }
+      },
+      {
+         "link-name":"link2",
+         "in-maint":false,
+         "link-type":"inter-domain",
+         "resource-version":"1588952379221",
+         "operational-status":"up",
+         "relationship-list":{
+            "relationship":[
+               {
+                  "related-to":"p-interface",
+                  "relationship-label":"tosca.relationships.network.LinksTo",
+                  "related-link":"/aai/v19/network/pnfs/pnf/pnf2/p-interfaces/p-interface/int4",
+                  "relationship-data":[
+                     {
+                        "relationship-key":"pnf.pnf-name",
+                        "relationship-value":"pnf2"
+                     },
+                     {
+                        "relationship-key":"p-interface.interface-name",
+                        "relationship-value":"int4"
+                     }
+                  ],
+                  "related-to-property":[
+                     {
+                        "property-key":"p-interface.prov-status"
+                     }
+                  ]
+               },
+               {
+                  "related-to":"p-interface",
+                  "relationship-label":"tosca.relationships.network.LinksTo",
+                  "related-link":"/aai/v19/network/pnfs/pnf/pnf3/p-interfaces/p-interface/int5",
+                  "relationship-data":[
+                     {
+                        "relationship-key":"pnf.pnf-name",
+                        "relationship-value":"pnf3"
+                     },
+                     {
+                        "relationship-key":"p-interface.interface-name",
+                        "relationship-value":"int5"
+                     }
+                  ],
+                  "related-to-property":[
+                     {
+                        "property-key":"p-interface.prov-status"
+                     }
+                  ]
+               }
+            ]
+         }
+      },
+      {
+         "link-name":"link3",
+         "in-maint":false,
+         "link-type":"inter-domain",
+         "resource-version":"1588952379221",
+         "operational-status":"up",
+         "relationship-list":{
+            "relationship":[
+               {
+                  "related-to":"p-interface",
+                  "relationship-label":"tosca.relationships.network.LinksTo",
+                  "related-link":"/aai/v19/network/pnfs/pnf/pnf3/p-interfaces/p-interface/int6",
+                  "relationship-data":[
+                     {
+                        "relationship-key":"pnf.pnf-name",
+                        "relationship-value":"pnf3"
+                     },
+                     {
+                        "relationship-key":"p-interface.interface-name",
+                        "relationship-value":"int6"
+                     }
+                  ],
+                  "related-to-property":[
+                     {
+                        "property-key":"p-interface.prov-status"
+                     }
+                  ]
+               },
+               {
+                  "related-to":"p-interface",
+                  "relationship-label":"tosca.relationships.network.LinksTo",
+                  "related-link":"/aai/v19/network/pnfs/pnf/pnf4/p-interfaces/p-interface/int7",
+                  "relationship-data":[
+                     {
+                        "relationship-key":"pnf.pnf-name",
+                        "relationship-value":"pnf4"
+                     },
+                     {
+                        "relationship-key":"p-interface.interface-name",
+                        "relationship-value":"int7"
+                     }
+                  ],
+                  "related-to-property":[
+                     {
+                        "property-key":"p-interface.prov-status"
+                     }
+                  ]
+               }
+            ]
+         }
+      }
+   ]
+}
diff --git a/test/inter_domain_route_opt/request.json b/test/inter_domain_route_opt/request.json
new file mode 100644 (file)
index 0000000..041a32f
--- /dev/null
@@ -0,0 +1,30 @@
+{
+   "requestInfo":{
+      "transactionId":"123456",
+      "requestId":"789456",
+      "callbackUrl":"",
+      "callbackHeader": "",
+      "sourceId":"SDNC",
+      "requestType":"create",
+      "numSolutions":1,
+      "optimizers":[
+         "route"
+      ],
+      "timeout":600
+   },
+   "routeInfo":{
+      "routeRequest":{
+         "srcDetails":{
+            "interfaceId":"int19",
+            "nodeId":"pnf1",
+            "controllerId":"Controller1"
+         },
+         "dstDetails":{
+            "interfaceId":"int20",
+            "nodeId":"pnf4",
+            "controllerId":"Controller3"
+         },
+         "serviceRate":"ODU2"
+      }
+   }
+}
diff --git a/test/test_inter_domain_route_opt.py b/test/test_inter_domain_route_opt.py
new file mode 100644 (file)
index 0000000..3d18abc
--- /dev/null
@@ -0,0 +1,151 @@
+# -------------------------------------------------------------------------
+#   Copyright (c) 2020 Fujitsu Limited Intellectual Property
+#
+#   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 unittest
+
+from unittest.mock import patch
+from apps.route.optimizers.inter_domain_route_opt import InterDomainRouteOpt
+import osdf.config.loader as config_loader
+from osdf.utils.interfaces import json_from_file
+from osdf.utils.programming_utils import DotDict
+
+count = 1 
+
+def mocked_requests_get(*args, **kwargs):
+    class MockResponse:
+        def __init__(self, json_data, status_code):
+            self.json_data = json_data
+            self.status_code = status_code
+
+        def json(self):
+            return self.json_data
+  
+    main_dir = ""
+    response_data_file = main_dir + "test/inter_domain_route_opt/get_links.json"
+    bandwidth_attributes = main_dir + "test/inter_domain_route_opt/bandwidth_attributes.json"
+    bandwidth_attribute_values = json_from_file(bandwidth_attributes)
+    
+    controllers_list = main_dir + "test/inter_domain_route_opt/controllers_list.json"
+    
+    if args[0] == 'https://api.url:30233/aai/v19/network/logical-links?link-type=inter-domain&operational-status=up':
+        return MockResponse(json_from_file(response_data_file), 200)
+    elif args[0] == 'https://api.url:30233/aai/v19/network/pnfs/pnf/pnf1/p-interfaces/p-interface/int1?depth=all':
+        return MockResponse(bandwidth_attribute_values["int-1-bw"], 200)
+    elif args[0] == 'https://api.url:30233/aai/v19/network/pnfs/pnf/pnf2/p-interfaces/p-interface/int3?depth=all':
+        return MockResponse(bandwidth_attribute_values["int-3-bw"], 200)
+    elif args[0] == 'https://api.url:30233/aai/v19/network/pnfs/pnf/pnf2/p-interfaces/p-interface/int4?depth=all':
+        return MockResponse(bandwidth_attribute_values["int-4-bw"], 200)
+    elif args[0] == 'https://api.url:30233/aai/v19/network/pnfs/pnf/pnf3/p-interfaces/p-interface/int5?depth=all':
+        return MockResponse(bandwidth_attribute_values["int-5-bw"], 200)
+    elif args[0] == 'https://api.url:30233/aai/v19/network/pnfs/pnf/pnf3/p-interfaces/p-interface/int6?depth=all':
+        return MockResponse(bandwidth_attribute_values["int-6-bw"], 200)
+    elif args[0] == 'https://api.url:30233/aai/v19/network/pnfs/pnf/pnf4/p-interfaces/p-interface/int7?depth=all':
+        return MockResponse(bandwidth_attribute_values["int-7-bw"], 200)
+    elif args[0] == 'https://api.url:30233/aai/v19/external-system/esr-thirdparty-sdnc-list':
+        return MockResponse(json_from_file(controllers_list), 200)                                             
+    return MockResponse(None, 404)   
+    
+
+def mocked_requests_put(*args, **kwargs):
+    class MockResponse:
+        def __init__(self, json_data, status_code):
+            self.json_data = json_data
+            self.status_code = status_code
+
+        def json(self):
+            return self.json_data
+    main_dir = ""
+    controllers_for_interfaces = main_dir + "test/inter_domain_route_opt/controllers_for_interfaces.json"
+    controllers_for_interfaces_values = json_from_file(controllers_for_interfaces)
+
+    global count
+      
+    if count == 1:
+        count += 1
+        return MockResponse(controllers_for_interfaces_values["int-1-cont"], 200)
+    elif count == 2:
+        count += 1
+        return MockResponse(controllers_for_interfaces_values["int-3-cont"], 200)
+    elif count == 3:
+        count += 1
+        return MockResponse(controllers_for_interfaces_values["int-4-cont"], 200)
+    elif count == 4:
+        count += 1
+        return MockResponse(controllers_for_interfaces_values["int-5-cont"], 200)
+    elif count == 5:
+      count += 1
+      return MockResponse(controllers_for_interfaces_values["int-6-cont"], 200)
+    elif count == 6:
+        count += 1
+        return MockResponse(controllers_for_interfaces_values["int-7-cont"], 200)
+            
+    return MockResponse(None, 404)            
+    
+            
+
+class TestInterDomainRouteOpt(unittest.TestCase):
+    @patch('apps.route.optimizers.inter_domain_route_opt.requests.get', side_effect=mocked_requests_get)
+    @patch('apps.route.optimizers.inter_domain_route_opt.requests.put', side_effect=mocked_requests_put)
+    @patch('apps.route.optimizers.simple_route_opt.pymzn.minizinc')               
+    def test_process_get_route(self, mock_solve , mock_put, mock_get):      
+        main_dir = ""
+        mock_solve.return_value = [{'x': [1, 1, 0, 0, 0, 0]}]
+        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/inter_domain_route_opt/request.json"
+        request_json = json_from_file(parameter_data_file)
+        routopt = InterDomainRouteOpt()
+        actual_response = routopt.get_route(request_json,self.osdf_config)
+        mock_response = {
+            "requestId":"789456",
+            "transactionId":"123456",
+            "statusMessage":"SUCCESS",
+            "requestStatus":"accepted",
+            "solutions":{
+                "routeInfo":{
+                "serviceRoute":[
+                    {
+                     "srcInterfaceId":"int19",
+                     "dstInterfaceId":"int1",
+                     "controllerId":"Controller1"
+                },
+                    {
+                     "srcInterfaceId":"int3",
+                     "dstInterfaceId":"int4",
+                     "controllerId":"Controller2"
+                },
+                    {
+                     "srcInterfaceId":"int5",
+                     "dstInterfaceId":"int20",
+                     "controllerId":"Controller3"
+                }
+                ],
+               "linkList":[
+                    "link1",
+                    "link2"
+                ]
+                }
+           }
+        }
+        self.assertEqual(mock_response, actual_response)
+        
+        
+if __name__ == '__main__':
+    unittest.main()
+        
\ No newline at end of file