Merge "Change API layer for NSI selection"
authorkrishna moorthy <krishna.moorthy6@wipro.com>
Thu, 27 Aug 2020 04:58:10 +0000 (04:58 +0000)
committerGerrit Code Review <gerrit@onap.org>
Thu, 27 Aug 2020 04:58:10 +0000 (04:58 +0000)
13 files changed:
apps/route/optimizers/inter_domain_route_opt.py [new file with mode: 0644]
config/osdf_config.yaml
docs/sections/offeredapis.rst
docs/sections/swaggerdoc/oof-optf-opteng-api.json [new file with mode: 0644]
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]
tox.ini

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 f50831f..8269dea 100644 (file)
@@ -11,8 +11,20 @@ This document describes the OSDF HAS (Homing and Allocation Service) API
 To view API documentation in the interactive swagger UI download the following and
 paste into the swagger tool here: https://editor.swagger.io
 
-:download:`oof-osdf-has-api.json <./swaggerdoc/oof-osdf-has-api.json>`
+.. csv-table::
+   :header: "API name", "Swagger JSON"
+   :widths: 10,5
 
+   "OOF OSDF HAS API", ":download:`link <./swaggerdoc/oof-osdf-has-api.json>`"
+   "OOF OPTENG API", ":download:`link <./swaggerdoc/oof-optf-opteng-api.json>`"
+
+OOF OSDF HAS API
+................
 .. swaggerv2doc:: ./swaggerdoc/oof-osdf-has-api.json
 
 
+OOF OPTENG API
+..............
+.. swaggerv2doc:: ./swaggerdoc/oof-optf-opteng-api.json
+
+
diff --git a/docs/sections/swaggerdoc/oof-optf-opteng-api.json b/docs/sections/swaggerdoc/oof-optf-opteng-api.json
new file mode 100644 (file)
index 0000000..4e77f76
--- /dev/null
@@ -0,0 +1,584 @@
+{
+  "swagger": "2.0",
+  "info": {
+    "description": "This is the ONAP Optimization Engine (Generic Solver) API",
+    "version": "1.0.0",
+    "title": "ONAP Optimization ENGINE API",
+    "contact": {
+      "email": "vikas.varma@att.com"
+    },
+    "license": {
+      "name": "Apache 2.0",
+      "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
+    }
+  },
+  "securityDefinitions": {
+    "basicAuth": {
+      "type": "basic",
+      "description": "HTTP Basic Auth"
+    }
+  },
+  "security": [
+    {
+      "basicAuth": []
+    }
+  ],
+  "paths": {
+    "/optengine/v1": {
+      "post": {
+        "tags": [
+          "Generic Solver Optimization"
+        ],
+        "summary": "Call the Generic Optimization engine",
+        "operationId": "optimizationRequest",
+        "description": "call optimization engine",
+        "consumes": [
+          "application/json"
+        ],
+        "produces": [
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "in": "body",
+            "name": "optimizationRequest",
+            "description": "optimization request",
+            "schema": {
+              "$ref": "#/definitions/OptimizationRequest"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "schema": {
+              "$ref": "#/definitions/OptimizationResponse"
+            }
+          },
+          "202": {
+            "description": "An optimization request is accepted"
+          },
+          "400": {
+            "description": "bad request"
+          },
+          "401": {
+            "description": "Request body is not compliant with the API definition"
+          },
+          "404": {
+            "description": "The server cannot find the requested URI"
+          },
+          "405": {
+            "description": "The requested method is not supported by a server."
+          },
+          "500": {
+            "description": "The server encountered an internal server error or timed out"
+          }
+        }
+      }
+    },
+    "/optmodel/v1": {
+      "post": {
+        "tags": [
+          "Request to add the Optimizer model, metadata"
+        ],
+        "summary": "Add/Insert the optimization models in the database",
+        "operationId": "optimModelRequestAPI",
+        "description": "Request to add update the Optimizer model, metadata",
+        "consumes": [
+          "application/json"
+        ],
+        "produces": [
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "in": "body",
+            "name": "optimModelRequest",
+            "description": "optimization model request",
+            "schema": {
+              "$ref": "#/definitions/OptimModelRequest"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "schema": {
+              "$ref": "#/definitions/OptimModelResponse"
+            }
+          },
+          "400": {
+            "description": "bad request"
+          },
+          "401": {
+            "description": "Request body is not compliant with the API definition"
+          },
+          "404": {
+            "description": "The server cannot find the requested URI"
+          },
+          "405": {
+            "description": "The requested method is not supported by a server."
+          },
+          "500": {
+            "description": "The server encountered an internal server error or timed out"
+          }
+        }
+      },
+      "put": {
+        "tags": [
+          "Request to update the Optimizer model, metadata"
+        ],
+        "summary": "Add/update the optimization models in the database",
+        "operationId": "updateModelRequestAPI",
+        "description": "Request to add update the Optimizer model, metadata",
+        "consumes": [
+          "application/json"
+        ],
+        "produces": [
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "in": "body",
+            "name": "optimModelRequest",
+            "description": "optimization model request",
+            "schema": {
+              "$ref": "#/definitions/OptimModelRequest"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "schema": {
+              "$ref": "#/definitions/OptimModelResponse"
+            }
+          },
+          "400": {
+            "description": "bad request"
+          },
+          "401": {
+            "description": "Request body is not compliant with the API definition"
+          },
+          "404": {
+            "description": "The server cannot find the requested URI"
+          },
+          "405": {
+            "description": "The requested method is not supported by a server."
+          },
+          "500": {
+            "description": "The server encountered an internal server error or timed out"
+          }
+        }
+      },
+      "get": {
+        "tags": [
+          "Retrieve all models"
+        ],
+        "summary": "Gets all Optim Model data",
+        "description": "Retrieves all Optim Models",
+        "operationId": "getAllOptModelData",
+        "produces": [
+          "application/json"
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "schema": {
+              "$ref": "#/definitions/ArrayOfOptimModelResponse"
+            }
+          },
+          "400": {
+            "description": "bad request"
+          },
+          "401": {
+            "description": "Request body is not compliant with the API definition"
+          },
+          "404": {
+            "description": "The server cannot find the requested URI"
+          },
+          "405": {
+            "description": "The requested method is not supported by a server."
+          },
+          "500": {
+            "description": "The server encountered an internal server error or timed out"
+          }
+        }
+      }
+    },
+    "/optmodel/v1/{model_id}": {
+      "get": {
+        "tags": [
+          "Retrieve Model Data"
+        ],
+        "summary": "Gets the Optim Model data",
+        "description": "Retrieves the Optim Model data given modelId",
+        "operationId": "getOptModelById",
+        "parameters": [
+          {
+            "in": "path",
+            "name": "model_id",
+            "description": "Model ID",
+            "required": true,
+            "type": "string"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "schema": {
+              "$ref": "#/definitions/OptimModelResponse"
+            }
+          },
+          "400": {
+            "description": "bad request"
+          },
+          "401": {
+            "description": "Request body is not compliant with the API definition"
+          },
+          "404": {
+            "description": "The server cannot find the requested URI"
+          },
+          "405": {
+            "description": "The requested method is not supported by a server."
+          },
+          "500": {
+            "description": "The server encountered an internal server error or timed out"
+          }
+        }
+      },
+      "delete": {
+        "tags": [
+          "Delete Model Data"
+        ],
+        "summary": "Delete the Optim Model data",
+        "description": "Deletes the Optim Model data given modelId",
+        "operationId": "deleteOptModelById",
+        "parameters": [
+          {
+            "in": "path",
+            "name": "model_id",
+            "description": "Model ID",
+            "required": true,
+            "type": "string"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "schema": {
+              "$ref": "#/definitions/DeleteModelResponse"
+            }
+          },
+          "400": {
+            "description": "bad request"
+          },
+          "401": {
+            "description": "Request body is not compliant with the API definition"
+          },
+          "404": {
+            "description": "The server cannot find the requested URI"
+          },
+          "405": {
+            "description": "The requested method is not supported by a server."
+          },
+          "500": {
+            "description": "The server encountered an internal server error or timed out"
+          }
+        }
+      }
+    }
+  },
+  "definitions": {
+    "OptimizationResponse": {
+      "type": "object",
+      "required": [
+        "transactionId",
+        "requestID",
+        "requestStatus"
+      ],
+      "properties": {
+        "transactionId": {
+          "type": "string",
+          "format": "uuid",
+          "description": "unique ID to track an ONAP transaction",
+          "example": "d290f1ee-6c54-4b01-90e6-d701748f0851"
+        },
+        "requestID": {
+          "type": "string",
+          "format": "uuid",
+          "description": "A unique ID to track multiple requests associated with a transaction",
+          "example": "d290f1ee-6c54-4b01-90e6-d701748f0851"
+        },
+        "requestStatus": {
+          "type": "string",
+          "description": "request status (accepted, done, completed,failed)",
+          "example": "done"
+        },
+        "statusMessage": {
+          "type": "string",
+          "description": "Status message (incomplete, complete, unsatisfiable, unknown, unbounded, unsat_or_unbounded, error)",
+          "example": "complete"
+        },
+        "solutions": {
+          "additionalProperties": {
+            "type": "object"
+          },
+          "example": {
+            "SCHEDULED": [
+              [
+                0,
+                1
+              ],
+              [
+                0,
+                1
+              ]
+            ],
+            "OPTIMIZED": 2
+          }
+        }
+      }
+    },
+    "OptimizationRequest": {
+      "type": "object",
+      "required": [
+        "requestInfo",
+        "optimInfo"
+      ],
+      "properties": {
+        "requestInfo": {
+          "$ref": "#/definitions/RequestInfo"
+        },
+        "optimInfo": {
+          "$ref": "#/definitions/OptimInfo"
+        }
+      }
+    },
+    "RequestInfo": {
+      "type": "object",
+      "required": [
+        "transactionId",
+        "requestID",
+        "sourceId"
+      ],
+      "properties": {
+        "transactionId": {
+          "type": "string",
+          "format": "uuid",
+          "description": "unique ID to track an ONAP transaction",
+          "example": "d290f1ee-6c54-4b01-90e6-d701748f0851"
+        },
+        "requestID": {
+          "type": "string",
+          "format": "uuid",
+          "description": "A unique ID to track multiple requests associated with a transaction",
+          "example": "d290f1ee-6c54-4b01-90e6-d701748f0851"
+        },
+        "callbackUrl": {
+          "type": "string",
+          "format": "url",
+          "description": "The end point of a callback service where recommendations are posted.",
+          "example": "myDomain.com/myCallback"
+        },
+        "sourceId": {
+          "type": "string",
+          "description": "The unique ID of a client making an optimization call.",
+          "example": "son-handler"
+        },
+        "timeout": {
+          "type": "integer",
+          "description": "A tolerance window (in second) for expecting solutions",
+          "example": 5
+        }
+      }
+    },
+    "OptimInfo": {
+      "type": "object",
+      "properties": {
+        "modelId": {
+          "type": "string",
+          "description": "ModelId from the database, if its not populated,  assume that solverModel will be populated",
+          "example": "pci_model1"
+        },
+        "solver": {
+          "type": "string",
+          "description": "type of solver (mzn, py, etc.)",
+          "example": "mzn"
+        },
+        "solverArgs": {
+          "type": "object",
+          "description": "Arguments for solver",
+          "additionalProperties": {
+            "type": "object"
+          },
+          "example": {
+            "solver": "cbc",
+            "timeout": 5
+          }
+        },
+        "modelContent": {
+          "type": "string",
+          "description": "a large blob string containing the model (which is not that problematic since models are fairly small)."
+        },
+        "optData": {
+          "$ref": "#/definitions/DataInfo"
+        }
+      }
+    },
+    "DataInfo": {
+      "type": "object",
+      "description": "Data Payload, input data for the solver, either text or json",
+      "properties": {
+        "text": {
+          "type": "string",
+          "description": "Solver data as a string",
+          "example": "flour = 8000; \r\nbanana = 11;\r\n "
+        },
+        "json": {
+          "type": "object",
+          "description": "Solver data as a json",
+          "additionalProperties": {
+            "type": "object"
+          },
+          "example": {
+            "flour": 8000,
+            "banana": 11
+          }
+        }
+      }
+    },
+    "OptimModelRequest": {
+      "type": "object",
+      "required": [
+        "requestInfo",
+        "modelInfo"
+      ],
+      "properties": {
+        "requestInfo": {
+          "$ref": "#/definitions/ModelRequestInfo"
+        },
+        "modelInfo": {
+          "$ref": "#/definitions/OptimModelInfo"
+        }
+      }
+    },
+    "ModelRequestInfo": {
+      "type": "object",
+      "required": [
+        "transactionId",
+        "requestID",
+        "sourceId"
+      ],
+      "properties": {
+        "transactionId": {
+          "type": "string",
+          "format": "uuid",
+          "description": "unique ID to track an ONAP transaction",
+          "example": "d290f1ee-6c54-4b01-90e6-d701748f0851"
+        },
+        "requestID": {
+          "type": "string",
+          "format": "uuid",
+          "description": "A unique ID to track multiple requests associated with a transaction",
+          "example": "d290f1ee-6c54-4b01-90e6-d701748f0851"
+        },
+        "sourceId": {
+          "type": "string",
+          "description": "The unique ID of a client making an optimization call.",
+          "example": "optf-osdf"
+        }
+      }
+    },
+    "OptimModelInfo": {
+      "type": "object",
+      "required": [
+        "modelId",
+        "solver",
+        "description",
+        "modelContent"
+      ],
+      "properties": {
+        "modelId": {
+          "type": "string",
+          "description": "ModelId from the database",
+          "example": "pci_anr_model1"
+        },
+        "solver": {
+          "type": "string",
+          "description": "type of solver (mzn, py, etc.)",
+          "example": "mzn"
+        },
+        "description": {
+          "type": "string",
+          "description": "Description of the model",
+          "example": "mzn model to optimize pci/anr models"
+        },
+        "modelContent": {
+          "type": "string",
+          "description": "a large blob string containing the model (which is not that problematic since models are fairly small).",
+          "example": "mzn content"
+        }
+      }
+    },
+    "ArrayOfOptimModelResponse": {
+      "type": "array",
+      "items": {
+        "$ref": "#/definitions/OptimModelResponse"
+      }
+    },
+    "OptimModelResponse": {
+      "type": "object",
+      "required": [
+        "modelId",
+        "solver",
+        "modelContent"
+      ],
+      "properties": {
+        "modelId": {
+          "type": "string",
+          "description": "ModelId from the database",
+          "example": "pci_anr_model1"
+        },
+        "solver": {
+          "type": "string",
+          "description": "type of solver (mzn, py, etc.)",
+          "example": "mzn"
+        },
+        "description": {
+          "type": "string",
+          "description": "Description of the model",
+          "example": "mzn model to optimize pci/anr models"
+        },
+        "modelContent": {
+          "type": "string",
+          "description": "a large blob string containing the model (which is not that problematic since models are fairly small).",
+          "example": "mzn content"
+        },
+        "statusMessage": {
+          "type": "string",
+          "description": "status message.",
+          "example": "mzn content"
+        }
+      }
+    },
+    "DeleteModelResponse": {
+      "type": "object",
+      "required": [
+        "statusMessage"
+      ],
+      "properties": {
+        "statusMessage": {
+          "type": "string",
+          "description": "status message.",
+          "example": "model data for modelId pci_anr_model1 deleted"
+        }
+      }
+    }
+  },
+  "schemes": [
+    "https"
+  ],
+  "host": "virtserver.swaggerhub.com",
+  "basePath": "/api/oof/"
+}
index 5f45d9a..9b84bc9 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
diff --git a/tox.ini b/tox.ini
index 11c541a..1606b9b 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -1,7 +1,7 @@
 
 [tox]
 skipsdist=True
-envlist = py3, pylint
+envlist = py3, pylint, flake8diff
 
 [testenv]
 distribute = False
@@ -29,3 +29,15 @@ commands = bash -c "pylint --reports=y osdf apps runtime| tee pylint.out"
 
 [testenv:py3]
 basepython=python3.6
+
+[testenv:flake8diff]
+whitelist_externals=bash
+deps = hacking>=2.0.0
+commands =
+  bash -c "files=$(git diff HEAD^ HEAD --diff-filter=d --name-only | grep -E '(^apps\/|osdf\/|runtime\/)'| grep -E '*\.py$'); if [[ -z $files ]]; then exit 0; else flake8 $files; fi"
+
+[flake8]
+select = E,H,W,F
+max-line-length = 119
+ignore =
+per-file-ignores=