Inter Domain Route Calculation for
[optf/osdf.git] / apps / route / optimizers / inter_domain_route_opt.py
1 # -------------------------------------------------------------------------
2 #   Copyright (c) 2020 Fujitsu Limited Intellectual Property
3 #
4 #   Licensed under the Apache License, Version 2.0 (the "License");
5 #   you may not use this file except in compliance with the License.
6 #   You may obtain a copy of the License at
7 #
8 #       http://www.apache.org/licenses/LICENSE-2.0
9 #
10 #   Unless required by applicable law or agreed to in writing, software
11 #   distributed under the License is distributed on an "AS IS" BASIS,
12 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 #   See the License for the specific language governing permissions and
14 #   limitations under the License.
15 #
16 # -------------------------------------------------------------------------
17
18
19 import os
20 import itertools
21 import json
22 import requests
23 from requests.auth import HTTPBasicAuth
24 import urllib3
25
26 from osdf.logging.osdf_logging import audit_log
27 import pymzn
28 from sklearn import preprocessing
29
30 BASE_DIR = os.path.dirname(__file__)
31 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
32
33
34 class InterDomainRouteOpt:
35
36     """
37     This values will need to deleted..
38     only added for the debug purpose
39     """
40     aai_headers = {
41         "X-TransactionId": "9999",
42         "X-FromAppId": "OOF",
43         "Accept": "application/json",
44         "Content-Type": "application/json",
45     }
46
47
48     def get_route(self, request, osdf_config):
49         """
50         This method processes the mdons route request
51         and returns an optimised path for the given
52         two ports
53         """
54
55         try:
56             route_info = request["routeInfo"]["routeRequest"]
57             src_controller_id = route_info["srcDetails"]["controllerId"]
58             src_port_id = route_info["srcDetails"]["interfaceId"]
59             dst_controller_id = route_info["dstDetails"]["controllerId"]
60             dst_port_id = route_info["dstDetails"]["interfaceId"]
61             service_rate = route_info["serviceRate"]
62             dzn_data, mapping_table = self.build_dzn_data(osdf_config, src_controller_id,
63                                                           dst_controller_id, service_rate)
64             audit_log.info("Dzn data")
65             audit_log.info(dzn_data)
66             mzn_model = os.path.join(BASE_DIR, 'route_opt.mzn')
67             links_list = self.find_suitable_path(mzn_model, dzn_data, mapping_table)
68             ordered_list = self.get_ordered_route_list(links_list,
69                                                        src_controller_id, dst_controller_id)
70             solution = self.get_solution_object(ordered_list, src_port_id, dst_port_id)
71             return {
72                 "requestId": request["requestInfo"]["requestId"],
73                 "transactionId": request["requestInfo"]["transactionId"],
74                 "statusMessage": "SUCCESS",
75                 "requestStatus": "accepted",
76                 "solutions": solution
77                 }
78         except Exception as err:
79             audit_log.info(err)
80             raise err
81
82     def get_solution_object(self, ordered_list, src_port_id, dst_port_id):
83         """
84         :param ordered_list: service_route list
85         :param src_port_id: source port id of route
86         :param dst_port_id: destination port id of route
87         :return: solution object of the route respone
88         """
89         service_route_list = []
90         link_list = []
91         for value in ordered_list:
92             service_route_object = {}
93             service_route_object["srcInterfaceId"] = src_port_id
94             service_route_object["dstInterfaceId"] = value["srcPortId"]
95             service_route_object["controllerId"] = value["srcControllerId"]
96             service_route_list.append(service_route_object)
97             link_list.append(value["linkName"])
98             src_port_id = value["dstPortId"]
99             dst_controller_id = value["dstControllerId"]
100         service_route_object = {}
101         service_route_object["srcInterfaceId"] = src_port_id
102         service_route_object["dstInterfaceId"] = dst_port_id
103         service_route_object["controllerId"] = dst_controller_id
104         service_route_list.append(service_route_object)
105         route_info_object = {
106             "serviceRoute" : service_route_list,
107             "linkList" : link_list
108             }
109         solution = {
110             "routeInfo" : route_info_object
111             }
112         return solution
113
114
115     def get_ordered_route_list(self, link_list, src_controller_id, dst_controller_id):
116         """
117         :param link_list: link list from the minizinc response
118         :param src_controller_id: source port id of route
119         :param dst_controller_id: destination port id of route
120         :return: route list in order
121         """
122         ordered_link_list = []
123         flag = True
124         while flag:
125             for item in link_list:
126                 if item["srcControllerId"] == src_controller_id:
127                     ordered_link_list.append(item)
128                     src_controller_id = item["dstControllerId"]
129                     if src_controller_id == dst_controller_id:
130                         flag = False
131         return ordered_link_list
132
133
134     def find_suitable_path(self, mzn_model, dzn_data, mapping_table):
135         """
136         :param mzn_model: minizinc model details
137         :param dzn_data: minizinc data
138         :param mapping_table: list that maintains AAI link details
139         :return: list of link from after running minizinc
140         """
141         minizinc_solution = self.solve(mzn_model, dzn_data)
142         audit_log.info("Minizinc Solution ==========>")
143         routes = list(minizinc_solution)
144         audit_log.info(routes)
145         try:
146             arr = routes[0]['x']
147         except Exception as err:
148             audit_log.info("No minizinc solutions found")
149             raise err
150         links_list = []
151         for i in range(0, len(routes[0]['x'])):
152             if arr[i] == 1:
153                 links_list.append(mapping_table[i])
154         return links_list
155
156
157     def process_inter_domain_link(self, logical_link, osdf_config):
158         """
159         :param logical_link: logical links from AAI
160         :param osdf_config: OSDF config details
161         :return: list of link object with src and dst controller details
162         """
163         link_details = {}
164         link_details["linkName"] = logical_link["link-name"]
165         relationship = logical_link["relationship-list"]["relationship"]
166         flag = 1
167
168         for value in relationship:
169             if value["related-to"] == "p-interface" and flag == 1:
170                 src_port_id = value["relationship-data"][1]["relationship-value"]
171                 src_controller_id = self.get_controller_for_interface(osdf_config, src_port_id)
172                 link_details["srcPortId"] = src_port_id
173                 link_details["srcControllerId"] = src_controller_id
174                 flag += 1
175             elif value["related-to"] == "p-interface" and flag == 2:
176                 dest_port_id = value["relationship-data"][1]["relationship-value"]
177                 dest_controller_id = self.get_controller_for_interface(osdf_config, dest_port_id)
178                 link_details["dstPortId"] = dest_port_id
179                 link_details["dstControllerId"] = dest_controller_id
180         return link_details
181
182
183     def prepare_map_table(self, osdf_config, logical_links):
184         """
185         :param logical_links: logical links from AAI
186         :param osdf_config: OSDF config details
187         :return: list of link object with src and dst controller details
188         """
189         results = map(self.process_inter_domain_link, logical_links,
190                       itertools.repeat(osdf_config, len(logical_links)))
191         new_results = list(results)
192
193         new_list = []
194         new_list += new_results
195         for i in new_results:
196             link_details = {}
197             link_details["linkName"] = i["linkName"]
198             link_details["srcPortId"] = i["dstPortId"]
199             link_details["srcControllerId"] = i["dstControllerId"]
200             link_details["dstPortId"] = i["srcPortId"]
201             link_details["dstControllerId"] = i["srcControllerId"]
202             new_list.append(link_details)
203         return new_list
204
205
206     def solve(self, mzn_model, dzn_data):
207         """
208         :param mzn_model: minizinc template
209         :param dzn_data: minizinc data model
210         :return: minizinc response
211         """
212         return pymzn.minizinc(mzn=mzn_model, data=dzn_data)
213
214
215     def get_links_based_on_bandwidth_attributes(self, logical_links_list,
216                                                 osdf_config, service_rate):
217         """
218         This method filters the logical links based on the
219         bandwidth attribute availability of the interfaces
220         from AAI
221         :return: filtered_list[]
222         """
223         filtered_list = []
224         for logical_link in logical_links_list:
225             relationship = logical_link["relationship-list"]["relationship"]
226             count = 0
227             for value in relationship:
228                 if value["related-to"] == "p-interface":
229                     interface_url = value["related-link"]
230                     if self.get_available_bandwidth_aai(interface_url, osdf_config, service_rate):
231                         count += 1
232             if count == 2:
233                 filtered_list.append(logical_link)
234
235         return  filtered_list
236
237
238     def build_dzn_data(self, osdf_config, src_controller_id, dst_controller_id, service_rate):
239         """
240         :param osdf_config: OSDF config details
241         :param src_controller_id: controller Id of the source port
242         :param dst_controller_id: controller id of the destination port
243         :param service_rate: service rate
244         :return: mapping atble which maintains link details from AAI
245         and minizinc data model to be used by template
246         """
247         logical_links = self.get_inter_domain_links(osdf_config)
248         logical_links_list = logical_links["logical-link"]
249         mapping_table = self.prepare_map_table(osdf_config,
250                                                self.get_links_based_on_bandwidth_attributes(logical_links_list, osdf_config, service_rate))
251
252         edge_start = []
253         edge_end = []
254         for item in mapping_table:
255             edge_start.append(item["srcControllerId"])
256             edge_end.append(item["dstControllerId"])
257         link_cost = []
258         for k in range(0, len(edge_start)):
259             link_cost.append(1)
260         list_controllers = self.get_controllers_from_aai(osdf_config)
261         le = preprocessing.LabelEncoder()
262         le.fit(list_controllers)
263
264         start_edge = le.transform(edge_start)
265         end_edge = le.transform(edge_end)
266         source = le.transform([src_controller_id])
267         destination = le.transform([dst_controller_id])
268
269         final_dzn_start_arr = []
270         for i in start_edge:
271             final_dzn_start_arr.append(i)
272
273         final_dzn_end_arr = []
274         for j in end_edge:
275             final_dzn_end_arr.append(j)
276
277         contollers_length = len(list_controllers)
278         no_of_edges = len(final_dzn_start_arr)
279         dzn_data = {
280             'N': contollers_length,
281             'M': no_of_edges,
282             'Edge_Start': final_dzn_start_arr,
283             'Edge_End': final_dzn_end_arr,
284             'L': link_cost,
285             'Start': source[0],
286             'End' : destination[0]
287             }
288         return dzn_data, mapping_table
289
290
291     def get_inter_domain_links(self, osdf_config):
292         """
293         This method returns list of all cross ONAP links
294         from /aai/v19/network/logical-links?link-type=inter-domain&operational-status="Up"
295         :return: logical-links[]
296         """
297
298         config = osdf_config.deployment
299         aai_url = config["aaiUrl"]
300         aai_req_url = aai_url + config["aaiGetInterDomainLinksUrl"]
301         response = requests.get(aai_req_url, headers=self.aai_headers,
302                                 auth=HTTPBasicAuth("AAI", "AAI"), verify=False)
303         if response.status_code == 200:
304             return response.json()
305
306
307     def get_controller_for_interface(self, osdf_config, port_id):
308         """
309         This method returns returns the controller id
310         given a p-interface from the below query
311         :return: controller_id
312         """
313         data = {
314             "start": ["external-system"],
315             "query": "query/getDomainController?portid="
316         }
317         query = data.get("query") + port_id
318         data.update(query=query)
319         config = osdf_config.deployment
320         aai_url = config["aaiUrl"]
321         aai_req_url = aai_url + config["controllerQueryUrl"]
322         response = requests.put(aai_req_url, data=json.dumps(data),
323                                 headers=self.aai_headers,
324                                 auth=HTTPBasicAuth("AAI", "AAI"),
325                                 verify=False)
326         if response.status_code == 200:
327             response_body = response.json()
328             return response_body["results"][0]["esr-thirdparty-sdnc"]["thirdparty-sdnc-id"]
329
330
331     def get_controllers_from_aai(self, osdf_config):
332         """
333         This method returns returns the list of
334         controller names in AAI
335         :return: controllers_list[]
336         """
337         controllers_list = []
338         config = osdf_config.deployment
339         aai_url = config["aaiUrl"]
340         aai_req_url = aai_url + config["aaiGetControllersUrl"]
341         response = requests.get(aai_req_url,
342                                 headers=self.aai_headers,
343                                 auth=HTTPBasicAuth("AAI", "AAI"),
344                                 verify=False)
345         if response.status_code == 200:
346             response_body = response.json()
347             esr_thirdparty_list = response_body["esr-thirdparty-sdnc"]
348
349             for item in esr_thirdparty_list:
350                 controllers_list.append(item["thirdparty-sdnc-id"])
351             return controllers_list
352
353
354     def get_available_bandwidth_aai(self, interface_url, osdf_config, service_rate):
355         """
356         Checks if the given interface has the required bandwidth
357         :return: boolean flag
358         """
359         config = osdf_config.deployment
360         aai_url = config["aaiUrl"]
361         aai_req_url = aai_url + interface_url + "?depth=all"
362         response = requests.get(aai_req_url,
363                                 headers=self.aai_headers,
364                                 auth=HTTPBasicAuth("AAI", "AAI"), verify=False)
365         if response.status_code == 200:
366             response_body = response.json()
367             available_bandwidth = response_body["bandwidth-attributes"]["bandwidth-attribute"][0]["available-bandwidth-map"]["available-bandwidth"]
368             for i in available_bandwidth:
369                 if i["odu-type"] == service_rate and i["number"] > 0:
370                     return True