2727d8a4d1cdf60e634f621af2ec6c5636dd78df
[osm/SO.git] / rwlaunchpad / plugins / rwvns / vala / rwsdn_odl / rwsdn_odl.py
1
2 #
3 # Copyright 2016 RIFT.IO Inc
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 #
17
18 import logging
19
20 import requests
21
22 import json
23 import re
24 import socket
25 import time
26
27 import gi
28 gi.require_version('RwTypes', '1.0')
29 gi.require_version('RwsdnalYang', '1.0')
30 gi.require_version('RwSdn', '1.0')
31 gi.require_version('RwTopologyYang','1.0')
32
33 from gi.repository import (
34 GObject,
35 RwSdn, # Vala package
36 RwTypes,
37 RwsdnalYang,
38 RwTopologyYang as RwTl,
39 )
40
41 import rw_status
42 import rwlogger
43
44
45 logger = logging.getLogger('rwsdn.sdnodl')
46 logger.setLevel(logging.DEBUG)
47
48
49 sff_rest_based = True
50
51 class UnknownAccountError(Exception):
52 pass
53
54
55 class MissingFileError(Exception):
56 pass
57
58
59 rwstatus = rw_status.rwstatus_from_exc_map({
60 IndexError: RwTypes.RwStatus.NOTFOUND,
61 KeyError: RwTypes.RwStatus.NOTFOUND,
62 UnknownAccountError: RwTypes.RwStatus.NOTFOUND,
63 MissingFileError: RwTypes.RwStatus.NOTFOUND,
64 })
65
66
67 class SdnOdlPlugin(GObject.Object, RwSdn.Topology):
68
69 def __init__(self):
70 GObject.Object.__init__(self)
71 self.sdnodl = SdnOdl()
72
73
74 @rwstatus
75 def do_init(self, rwlog_ctx):
76 if not any(isinstance(h, rwlogger.RwLogger) for h in logger.handlers):
77 logger.addHandler(
78 rwlogger.RwLogger(
79 category="rw-cal-log",
80 subcategory="odl",
81 log_hdl=rwlog_ctx,
82 )
83 )
84
85 @rwstatus(ret_on_failure=[None])
86 def do_validate_sdn_creds(self, account):
87 """
88 Validates the sdn account credentials for the specified account.
89 Performs an access to the resources using Keystone API. If creds
90 are not valid, returns an error code & reason string
91
92 @param account - a SDN account
93
94 Returns:
95 Validation Code and Details String
96 """
97 #logger.debug('Received validate SDN creds')
98 status = self.sdnodl.validate_account_creds(account)
99 #logger.debug('Done with validate SDN creds: %s', type(status))
100 return status
101
102 @rwstatus(ret_on_failure=[None])
103 def do_get_network_list(self, account):
104 """
105 Returns the list of discovered networks
106
107 @param account - a SDN account
108
109 """
110 logger.debug('Received Get network list: ')
111 nwtop = self.sdnodl.get_network_list( account)
112 logger.debug('Done with get network list: %s', type(nwtop))
113 return nwtop
114
115 @rwstatus(ret_on_failure=[""])
116 def do_create_vnffg_chain(self, account,vnffg_chain):
117 """
118 Creates Service Function chain in ODL
119
120 @param account - a SDN account
121
122 """
123 logger.debug('Received Create VNFFG chain ')
124 vnffg_id = self.sdnodl.create_sfc( account,vnffg_chain)
125 logger.debug('Done with create VNFFG chain with name : %s', vnffg_id)
126 return vnffg_id
127
128 @rwstatus
129 def do_terminate_vnffg_chain(self, account,vnffg_id):
130 """
131 Terminate Service Function chain in ODL
132
133 @param account - a SDN account
134
135 """
136 logger.debug('Received terminate VNFFG chain for id %s ', vnffg_id)
137 # TODO: Currently all the RSP, SFPs , SFFs and SFs are deleted
138 # Need to handle deletion of specific RSP, SFFs, SFs etc
139 self.sdnodl.terminate_all_sfc(account)
140 logger.debug('Done with terminate VNFFG chain with name : %s', vnffg_id)
141
142 @rwstatus(ret_on_failure=[None])
143 def do_get_vnffg_rendered_paths(self, account):
144 """
145 Get ODL Rendered Service Path List (SFC)
146
147 @param account - a SDN account
148 """
149 vnffg_list = self.sdnodl.get_rsp_list(account)
150 return vnffg_list
151
152 @rwstatus(ret_on_failure=[None])
153 def do_create_vnffg_classifier(self, account, vnffg_classifier):
154 """
155 Add VNFFG Classifier
156
157 @param account - a SDN account
158 """
159 classifier_name = self.sdnodl.create_sfc_classifier(account,vnffg_classifier)
160 return classifier_name
161
162 @rwstatus(ret_on_failure=[None])
163 def do_terminate_vnffg_classifier(self, account, vnffg_classifier_name):
164 """
165 Add VNFFG Classifier
166
167 @param account - a SDN account
168 """
169 self.sdnodl.terminate_sfc_classifier(account,vnffg_classifier_name)
170
171
172 class Sff(object):
173 """
174 Create SFF object to hold SFF related details
175 """
176
177 def __init__(self,sff_name, mgmt_address, mgmt_port, dp_address, dp_port,sff_dp_name, sff_br_name=''):
178 self.name = sff_name
179 self.ip = mgmt_address
180 self.sff_rest_port = mgmt_port
181 self.sff_port = dp_port
182 self.dp_name = sff_dp_name
183 self.dp_ip = dp_address
184 self.br_name = sff_br_name
185 self.sf_dp_list = list()
186
187 def add_sf_dp_to_sff(self,sf_dp):
188 self.sf_dp_list.append(sf_dp)
189
190 def __repr__(self):
191 return 'Name:{},Bridge Name:{}, IP: {}, SF List: {}'.format(self.dp_name,self.br_name, self.ip, self.sf_dp_list)
192
193 class SfDpLocator(object):
194 """
195 Create Service Function Data Plane Locator related Object to hold details related to each DP Locator endpoint
196 """
197 def __init__(self,name,sfdp_id,vnfr_name,vm_id):
198 self.name = name
199 self.port_id = sfdp_id
200 self.vnfr_name = vnfr_name
201 self.vm_id = vm_id
202 self.sff_name = None
203 self.ovsdb_tp_name = None
204
205 def _update_sff_name(self,sff_name):
206 self.sff_name = sff_name
207
208 def _update_vnf_params(self,service_function_type,address, port,transport_type):
209 self.service_function_type = service_function_type
210 self.address = address
211 self.port = port
212 self.transport_type = "service-locator:{}".format(transport_type)
213
214 def __repr__(self):
215 return 'Name:{},Port id:{}, VNFR ID: {}, VM ID: {}, SFF Name: {}'.format(self.name,self.port_id, self.vnfr_name, self.vm_id,self.sff_name)
216
217 class SdnOdl(object):
218 """
219 SDN ODL Class to support REST based API calls
220 """
221
222 @property
223 def _network_topology_path(self):
224 return 'restconf/operational/network-topology:network-topology'
225
226 @property
227 def _node_inventory_path(self):
228 return 'restconf/operational/opendaylight-inventory:nodes'
229
230 def _network_topology_rest_url(self,account):
231 return '{}/{}'.format(account.odl.url,self._network_topology_path)
232
233 def _node_inventory_rest_url(self,account):
234 return '{}/{}'.format(account.odl.url,self._node_inventory_path)
235
236 def _get_rest_url(self,account, rest_path):
237 return '{}/{}'.format(account.odl.url,rest_path)
238
239
240 def _get_peer_termination_point(self,node_inv,tp_id):
241 for node in node_inv['nodes']['node']:
242 if "node-connector" in node and len(node['node-connector']) > 0:
243 for nodec in node['node-connector']:
244 if ("flow-node-inventory:name" in nodec and nodec["flow-node-inventory:name"] == tp_id):
245 return(node['id'], nodec['id'])
246 return (None,None)
247
248 def _get_termination_point_mac_address(self,node_inv,tp_id):
249 for node in node_inv['nodes']['node']:
250 if "node-connector" in node and len(node['node-connector']) > 0:
251 for nodec in node['node-connector']:
252 if ("flow-node-inventory:name" in nodec and nodec["flow-node-inventory:name"] == tp_id):
253 return nodec.get("flow-node-inventory:hardware-address")
254
255 def _add_host(self,ntwk,node,term_point,vmid,node_inv):
256 for ntwk_node in ntwk.node:
257 if ntwk_node.node_id == vmid:
258 break
259 else:
260 ntwk_node = ntwk.node.add()
261 if "ovsdb:bridge-name" in node:
262 ntwk_node.rw_node_attributes.ovs_bridge_name = node["ovsdb:bridge-name"]
263 ntwk_node.node_id = vmid
264 intf_id = [res for res in term_point['ovsdb:interface-external-ids'] if res['external-id-key'] == 'iface-id']
265 if intf_id:
266 ntwk_node_tp = ntwk_node.termination_point.add()
267 ntwk_node_tp.tp_id = intf_id[0]['external-id-value']
268 att_mac = [res for res in term_point['ovsdb:interface-external-ids'] if res['external-id-key'] == 'attached-mac']
269 if att_mac:
270 ntwk_node_tp.l2_termination_point_attributes.mac_address = att_mac[0]['external-id-value']
271 peer_node,peer_node_tp = self._get_peer_termination_point(node_inv,term_point['tp-id'])
272 if peer_node and peer_node_tp:
273 nw_lnk = ntwk.link.add()
274 nw_lnk.source.source_tp = ntwk_node_tp.tp_id
275 nw_lnk.source.source_node = ntwk_node.node_id
276 nw_lnk.destination.dest_tp = term_point['tp-id']
277 nw_lnk.destination.dest_node = node['node-id']
278 nw_lnk.link_id = peer_node_tp + '-' + 'source'
279
280 nw_lnk = ntwk.link.add()
281 nw_lnk.source.source_tp = term_point['tp-id']
282 nw_lnk.source.source_node = node['node-id']
283 nw_lnk.destination.dest_tp = ntwk_node_tp.tp_id
284 nw_lnk.destination.dest_node = ntwk_node.node_id
285 nw_lnk.link_id = peer_node_tp + '-' + 'dest'
286
287 def _get_address_from_node_inventory(self,node_inv,node_id):
288 for node in node_inv['nodes']['node']:
289 if node['id'] == node_id:
290 return node["flow-node-inventory:ip-address"]
291 return None
292
293 def _fill_network_list(self,nw_topo,node_inventory):
294 """
295 Fill Topology related information
296 """
297 nwtop = RwTl.YangData_IetfNetwork()
298
299 for topo in nw_topo['network-topology']['topology']:
300 if ('node' in topo and len(topo['node']) > 0):
301 ntwk = nwtop.network.add()
302 ntwk.network_id = topo['topology-id']
303 ntwk.server_provided = True
304 for node in topo['node']:
305 if ('termination-point' in node and len(node['termination-point']) > 0):
306 ntwk_node = ntwk.node.add()
307 ntwk_node.node_id = node['node-id']
308 addr = self._get_address_from_node_inventory(node_inventory,ntwk_node.node_id)
309 if addr:
310 ntwk_node.l2_node_attributes.management_address.append(addr)
311 for term_point in node['termination-point']:
312 ntwk_node_tp = ntwk_node.termination_point.add()
313 ntwk_node_tp.tp_id = term_point['tp-id']
314 mac_address = self._get_termination_point_mac_address(node_inventory,term_point['tp-id'])
315 if mac_address:
316 ntwk_node_tp.l2_termination_point_attributes.mac_address = mac_address
317 if 'ovsdb:interface-external-ids' in term_point:
318 vm_id = [res for res in term_point['ovsdb:interface-external-ids'] if res['external-id-key'] == 'vm-id']
319 if vm_id:
320 vmid = vm_id[0]['external-id-value']
321 self._add_host(ntwk,node,term_point,vmid,node_inventory)
322 if ('link' in topo and len(topo['link']) > 0):
323 for link in topo['link']:
324 nw_link = ntwk.link.add()
325 if 'destination' in link:
326 nw_link.destination.dest_tp = link['destination'].get('dest-tp')
327 nw_link.destination.dest_node = link['destination'].get('dest-node')
328 if 'source' in link:
329 nw_link.source.source_node = link['source'].get('source-node')
330 nw_link.source.source_tp = link['source'].get('source-tp')
331 nw_link.link_id = link.get('link-id')
332 return nwtop
333
334
335 def validate_account_creds(self, account):
336 """
337 Validate the SDN account credentials by accessing the rest API using the provided credentials
338 """
339 status = RwsdnalYang.SdnConnectionStatus()
340 url = '{}/{}'.format(account.odl.url,"restconf")
341 try:
342 r=requests.get(url,auth=(account.odl.username,account.odl.password))
343 r.raise_for_status()
344 except requests.exceptions.HTTPError as e:
345 msg = "SdnOdlPlugin: SDN account credential validation failed. Exception: %s", str(e)
346 #logger.error(msg)
347 print(msg)
348 status.status = "failure"
349 status.details = "Invalid Credentials: %s" % str(e)
350 except Exception as e:
351 msg = "SdnPdlPlugin: SDN connection failed. Exception: %s", str(e)
352 #logger.error(msg)
353 print(msg)
354 status.status = "failure"
355 status.details = "Connection Failed (Invlaid URL): %s" % str(e)
356 else:
357 status.status = "success"
358 status.details = "Connection was successful"
359
360 return status
361
362 def get_network_list(self, account):
363 """
364 Get the networks details from ODL
365 """
366 url = self._network_topology_rest_url(account)
367 r=requests.get(url,auth=(account.odl.username,account.odl.password))
368 r.raise_for_status()
369 nw_topo = r.json()
370
371 url = self._node_inventory_rest_url(account)
372 r = requests.get(url,auth=(account.odl.username,account.odl.password))
373 r.raise_for_status()
374 node_inventory = r.json()
375 return self._fill_network_list(nw_topo,node_inventory)
376
377 @property
378 def _service_functions_path(self):
379 return 'restconf/config/service-function:service-functions'
380
381 @property
382 def _service_function_path(self):
383 return 'restconf/config/service-function:service-functions/service-function/{}'
384
385 @property
386 def _service_function_forwarders_path(self):
387 return 'restconf/config/service-function-forwarder:service-function-forwarders'
388
389 @property
390 def _service_function_forwarder_path(self):
391 return 'restconf/config/service-function-forwarder:service-function-forwarders/service-function-forwarder/{}'
392
393 @property
394 def _service_function_chains_path(self):
395 return 'restconf/config/service-function-chain:service-function-chains'
396
397 @property
398 def _service_function_chain_path(self):
399 return 'restconf/config/service-function-chain:service-function-chains/service-function-chain/{}'
400
401 @property
402 def _sfp_metadata_path(self):
403 return 'restconf/config/service-function-path-metadata:service-function-metadata/context-metadata/{}'
404
405 @property
406 def _sfps_metadata_path(self):
407 return 'restconf/config/service-function-path-metadata:service-function-metadata'
408
409 @property
410 def _sfps_path(self):
411 return 'restconf/config/service-function-path:service-function-paths'
412
413 @property
414 def _sfp_path(self):
415 return 'restconf/config/service-function-path:service-function-paths/service-function-path/{}'
416
417
418 @property
419 def _create_rsp_path(self):
420 return 'restconf/operations/rendered-service-path:create-rendered-path'
421
422 @property
423 def _delete_rsp_path(self):
424 return 'restconf/operations/rendered-service-path:delete-rendered-path'
425
426
427 @property
428 def _get_rsp_paths(self):
429 return 'restconf/operational/rendered-service-path:rendered-service-paths'
430
431 @property
432 def _get_rsp_path(self):
433 return 'restconf/operational/rendered-service-path:rendered-service-paths/rendered-service-path/{}'
434
435 @property
436 def _access_list_path(self):
437 return 'restconf/config/ietf-access-control-list:access-lists/acl/{}'
438
439 @property
440 def _service_function_classifier_path(self):
441 return 'restconf/config/service-function-classifier:service-function-classifiers/service-function-classifier/{}'
442
443 @property
444 def _access_lists_path(self):
445 return 'restconf/config/ietf-access-control-list:access-lists'
446
447 @property
448 def _service_function_classifiers_path(self):
449 return 'restconf/config/service-function-classifier:service-function-classifiers'
450
451
452 def _create_sf(self,account,vnffg_chain,sf_dp_list):
453 "Create SF"
454 sf_json = {}
455
456 for vnf in vnffg_chain.vnf_chain_path:
457 for vnfr in vnf.vnfr_ids:
458 sf_url = self._get_rest_url(account,self._service_function_path.format(vnfr.vnfr_name))
459 print(sf_url)
460 r=requests.get(sf_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'})
461 # If the SF is not found; create new SF
462 if r.status_code == 200:
463 logger.info("SF with name %s is already present in ODL. Skipping update", vnfr.vnfr_name)
464 continue
465 elif r.status_code != 404:
466 r.raise_for_status()
467
468 sf_dict = {}
469 sf_dict['name'] = vnfr.vnfr_name
470 sf_dict['nsh-aware'] = vnf.nsh_aware
471 sf_dict['type'] = vnf.service_function_type
472 sf_dict['ip-mgmt-address'] = vnfr.mgmt_address
473 sf_dict['rest-uri'] = 'http://{}:{}'.format(vnfr.mgmt_address, vnfr.mgmt_port)
474
475 sf_dict['sf-data-plane-locator'] = list()
476 for vdu in vnfr.vdu_list:
477 sf_dp = {}
478 if vdu.port_id in sf_dp_list.keys():
479 sf_dp_entry = sf_dp_list[vdu.port_id]
480 sf_dp['name'] = sf_dp_entry.name
481 sf_dp['ip'] = vdu.address
482 sf_dp['port'] = vdu.port
483 sf_dp['transport'] = "service-locator:{}".format(vnf.transport_type)
484 if vnfr.sff_name:
485 sf_dp['service-function-forwarder'] = vnfr.sff_name
486 else:
487 sff_name = sf_dp_entry.sff_name
488 if sff_name is None:
489 logger.error("SFF not found for port %s in SF %s", vdu.port_id, vnfr.vnfr_name)
490 sf_dp['service-function-forwarder'] = sff_name
491 sf_dp['service-function-ovs:ovs-port'] = dict()
492 if sf_dp_entry.ovsdb_tp_name is not None:
493 sf_dp['service-function-ovs:ovs-port']['port-id'] = sf_dp_entry.ovsdb_tp_name
494 sf_dict['sf-data-plane-locator'].append(sf_dp)
495 else:
496 logger.error("Port %s not found in SF DP list",vdu.port_id)
497
498 sf_json['service-function'] = sf_dict
499 sf_data = json.dumps(sf_json)
500 sf_url = self._get_rest_url(account,self._service_function_path.format(vnfr.vnfr_name))
501 print(sf_url)
502 print(sf_data)
503 r=requests.put(sf_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=sf_data)
504 r.raise_for_status()
505
506
507 def _create_sff(self,account,vnffg_chain,sff):
508 "Create SFF"
509 sff_json = {}
510 sff_dict = {}
511 #sff_dp_name = "SFF1" + '-' + 'DP1'
512 sff_dp_name = sff.dp_name
513
514 sff_url = self._get_rest_url(account,self._service_function_forwarder_path.format(sff.name))
515 print(sff_url)
516 r=requests.get(sff_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'})
517 # If the SFF is not found; create new SF
518 if r.status_code == 200:
519 logger.info("SFF with name %s is already present in ODL. Skipping full update", sff.name)
520 sff_dict = r.json()
521 sff_updated = False
522 for sf_dp in sff.sf_dp_list:
523 for sff_sf in sff_dict['service-function-forwarder'][0]['service-function-dictionary']:
524 if sf_dp.vnfr_name == sff_sf['name']:
525 logger.info("SF with name %s is already found in SFF %s SF Dictionay. Skipping update",sf_dp.vnfr_name,sff.name)
526 break
527 else:
528 logger.info("SF with name %s is not found in SFF %s SF Dictionay",sf_dp.vnfr_name, sff.name)
529 sff_updated = True
530 sff_sf_dict = {}
531 sff_sf_dp_loc = {}
532 sff_sf_dict['name'] = sf_dp.vnfr_name
533
534 # Below two lines are enabled only for ODL Beryillium
535 sff_sf_dp_loc['sff-dpl-name'] = sff_dp_name
536 sff_sf_dp_loc['sf-dpl-name'] = sf_dp.name
537
538 sff_sf_dict['sff-sf-data-plane-locator'] = sff_sf_dp_loc
539 sff_dict['service-function-forwarder'][0]['service-function-dictionary'].append(sff_sf_dict)
540 if sff_updated is True:
541 sff_data = json.dumps(sff_dict)
542 print(sff_data)
543 r=requests.put(sff_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=sff_data)
544 r.raise_for_status()
545 return
546 elif r.status_code != 404:
547 r.raise_for_status()
548
549 sff_name = sff.name
550 sff_ip = sff.ip
551 sff_dp_ip = sff.dp_ip
552 sff_port = sff.sff_port
553 sff_bridge_name = ''
554 sff_rest_port = sff.sff_rest_port
555 sff_ovs_op = {}
556 if sff_rest_based is False:
557 sff_bridge_name = sff.br_name
558 sff_ovs_op = {"key": "flow",
559 "nshc1": "flow",
560 "nsp": "flow",
561 "remote-ip": "flow",
562 "dst-port": sff_port,
563 "nshc3": "flow",
564 "nshc2": "flow",
565 "nshc4": "flow",
566 "nsi": "flow"}
567
568
569 sff_dict['name'] = sff_name
570 sff_dict['service-node'] = ''
571 sff_dict['ip-mgmt-address'] = sff_ip
572 if sff_rest_based:
573 sff_dict['rest-uri'] = 'http://{}:{}'.format(sff_ip, sff_rest_port)
574 else:
575 sff_dict['service-function-forwarder-ovs:ovs-bridge'] = {"bridge-name": sff_bridge_name}
576 sff_dict['service-function-dictionary'] = list()
577 for sf_dp in sff.sf_dp_list:
578 sff_sf_dict = {}
579 sff_sf_dp_loc = {}
580 sff_sf_dict['name'] = sf_dp.vnfr_name
581
582 # Below set of lines are reqd for Lithium
583 #sff_sf_dict['type'] = sf_dp.service_function_type
584 #sff_sf_dp_loc['ip'] = sf_dp.address
585 #sff_sf_dp_loc['port'] = sf_dp.port
586 #sff_sf_dp_loc['transport'] = sf_dp.transport_type
587 #sff_sf_dp_loc['service-function-forwarder-ovs:ovs-bridge'] = {}
588
589 # Below two lines are enabled only for ODL Beryillium
590 sff_sf_dp_loc['sff-dpl-name'] = sff_dp_name
591 sff_sf_dp_loc['sf-dpl-name'] = sf_dp.name
592
593 sff_sf_dict['sff-sf-data-plane-locator'] = sff_sf_dp_loc
594 sff_dict['service-function-dictionary'].append(sff_sf_dict)
595
596 sff_dict['sff-data-plane-locator'] = list()
597 sff_dp = {}
598 dp_loc = {}
599 sff_dp['name'] = sff_dp_name
600 dp_loc['ip'] = sff_dp_ip
601 dp_loc['port'] = sff_port
602 dp_loc['transport'] = 'service-locator:vxlan-gpe'
603 sff_dp['data-plane-locator'] = dp_loc
604 if sff_rest_based is False:
605 sff_dp['service-function-forwarder-ovs:ovs-options'] = sff_ovs_op
606 #sff_dp["service-function-forwarder-ovs:ovs-bridge"] = {'bridge-name':sff_bridge_name}
607 sff_dp["service-function-forwarder-ovs:ovs-bridge"] = {}
608 sff_dict['sff-data-plane-locator'].append(sff_dp)
609
610 sff_json['service-function-forwarder'] = sff_dict
611 sff_data = json.dumps(sff_json)
612 print(sff_data)
613 r=requests.put(sff_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=sff_data)
614 r.raise_for_status()
615
616 def _create_sfc(self,account,vnffg_chain):
617 "Create SFC"
618 sfc_json = {}
619 sfc_dict = {}
620 sfc_dict['name'] = vnffg_chain.name
621 sfc_dict['sfc-service-function'] = list()
622 vnf_chain_list = sorted(vnffg_chain.vnf_chain_path, key = lambda x: x.order)
623 for vnf in vnf_chain_list:
624 sfc_sf_dict = {}
625 sfc_sf_dict['name'] = vnf.service_function_type
626 sfc_sf_dict['type'] = vnf.service_function_type
627 sfc_sf_dict['order'] = vnf.order
628 sfc_dict['sfc-service-function'].append(sfc_sf_dict)
629 sfc_json['service-function-chain'] = sfc_dict
630 sfc_data = json.dumps(sfc_json)
631 sfc_url = self._get_rest_url(account,self._service_function_chain_path.format(vnffg_chain.name))
632 print(sfc_url)
633 print(sfc_data)
634 r=requests.put(sfc_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=sfc_data)
635 r.raise_for_status()
636
637 def _create_sfp_metadata(self,account,sfc_classifier):
638 " Create SFP metadata"
639 sfp_meta_json = {}
640 sfp_meta_dict = {}
641 sfp_meta_dict['name'] = sfc_classifier.name
642 if sfc_classifier.vnffg_metadata.ctx1:
643 sfp_meta_dict['context-header1'] = sfc_classifier.vnffg_metadata.ctx1
644 if sfc_classifier.vnffg_metadata.ctx2:
645 sfp_meta_dict['context-header2'] = sfc_classifier.vnffg_metadata.ctx2
646 if sfc_classifier.vnffg_metadata.ctx3:
647 sfp_meta_dict['context-header3'] = sfc_classifier.vnffg_metadata.ctx3
648 if sfc_classifier.vnffg_metadata.ctx4:
649 sfp_meta_dict['context-header4'] = sfc_classifier.vnffg_metadata.ctx4
650
651 sfp_meta_json['context-metadata'] = sfp_meta_dict
652 sfp_meta_data = json.dumps(sfp_meta_json)
653 sfp_meta_url = self._get_rest_url(account,self._sfp_metadata_path.format(sfc_classifier.name))
654 print(sfp_meta_url)
655 print(sfp_meta_data)
656 r=requests.put(sfp_meta_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=sfp_meta_data)
657 r.raise_for_status()
658
659 def _create_sfp(self,account,vnffg_chain, sym_chain=False,classifier_name=None,vnffg_metadata_name=None):
660 "Create SFP"
661 sfp_json = {}
662 sfp_dict = {}
663 sfp_dict['name'] = vnffg_chain.name
664 sfp_dict['service-chain-name'] = vnffg_chain.name
665 sfp_dict['symmetric'] = sym_chain
666 sfp_dict['transport-type'] = 'service-locator:vxlan-gpe'
667 if vnffg_metadata_name:
668 sfp_dict['context-metadata'] = vnffg_metadata_name
669 if classifier_name:
670 sfp_dict['classifier'] = classifier_name
671
672 sfp_json['service-function-path'] = sfp_dict
673 sfp_data = json.dumps(sfp_json)
674 sfp_url = self._get_rest_url(account,self._sfp_path.format(vnffg_chain.name))
675 print(sfp_url)
676 print(sfp_data)
677 r=requests.put(sfp_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=sfp_data)
678 r.raise_for_status()
679
680 def _create_rsp(self,account,vnffg_chain_name, sym_chain=True):
681 "Create RSP"
682 rsp_json = {}
683 rsp_input = {}
684 rsp_json['input'] = {}
685 rsp_input['name'] = vnffg_chain_name
686 rsp_input['parent-service-function-path'] = vnffg_chain_name
687 rsp_input['symmetric'] = sym_chain
688
689 rsp_json['input'] = rsp_input
690 rsp_data = json.dumps(rsp_json)
691 self._rsp_data = rsp_json
692 rsp_url = self._get_rest_url(account,self._create_rsp_path)
693 print(rsp_url)
694 print(rsp_data)
695 r=requests.post(rsp_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=rsp_data)
696 r.raise_for_status()
697 print(r.json())
698 output_json = r.json()
699 return output_json['output']['name']
700
701 def _get_sff_list_for_chain(self, account,sf_dp_list):
702 """
703 Get List of all SFF that needs to be created based on VNFs included in VNFFG chain.
704 """
705
706 sff_list = {}
707 if sf_dp_list is None:
708 logger.error("VM List for vnffg chain is empty while trying to get SFF list")
709 url = self._network_topology_rest_url(account)
710 r=requests.get(url,auth=(account.odl.username,account.odl.password))
711 r.raise_for_status()
712 nw_topo = r.json()
713
714 for topo in nw_topo['network-topology']['topology']:
715 if ('node' in topo and len(topo['node']) > 0):
716 for node in topo['node']:
717 if ('termination-point' in node and len(node['termination-point']) > 0):
718 for term_point in node['termination-point']:
719 if 'ovsdb:interface-external-ids' in term_point:
720 vm_id = [res for res in term_point['ovsdb:interface-external-ids'] if res['external-id-key'] == 'vm-id']
721 if len(vm_id) == 0:
722 continue
723 vmid = vm_id[0]['external-id-value']
724 intf_id = [res for res in term_point['ovsdb:interface-external-ids'] if res['external-id-key'] == 'iface-id']
725 if len(intf_id) == 0:
726 continue
727 intfid = intf_id[0]['external-id-value']
728 if intfid not in sf_dp_list.keys():
729 continue
730 if sf_dp_list[intfid].vm_id != vmid:
731 logger.error("Intf ID %s is not present in VM %s", intfid, vmid)
732 continue
733 sf_dp_list[intfid].ovsdb_tp_name = term_point['ovsdb:name']
734
735 if 'ovsdb:managed-by' in node:
736 rr=re.search('network-topology:node-id=\'([-\w\:\/]*)\'',node['ovsdb:managed-by'])
737 node_id = rr.group(1)
738 ovsdb_node = [node for node in topo['node'] if node['node-id'] == node_id]
739 if ovsdb_node:
740 if 'ovsdb:connection-info' in ovsdb_node[0]:
741 sff_ip = ovsdb_node[0]['ovsdb:connection-info']['local-ip']
742 sff_br_name = node['ovsdb:bridge-name']
743 sff_br_uuid = node['ovsdb:bridge-uuid']
744 sff_dp_ip = sff_ip
745
746 if 'ovsdb:openvswitch-other-configs' in ovsdb_node[0]:
747 for other_key in ovsdb_node[0]['ovsdb:openvswitch-other-configs']:
748 if other_key['other-config-key'] == 'local_ip':
749 local_ip_str = other_key['other-config-value']
750 sff_dp_ip = local_ip_str.split(',')[0]
751 break
752
753 sff_name = socket.getfqdn(sff_ip)
754 if sff_br_uuid in sff_list:
755 sff_list[sff_name].add_sf_dp_to_sff(sf_dp_list[intfid])
756 sf_dp_list[intfid]._update_sff_name(sff_name)
757 else:
758 sff_dp_ip = sff_ip #overwrite sff_dp_ip to SFF ip for now
759 sff_list[sff_name] = Sff(sff_name,sff_ip,6000, sff_dp_ip, 4790,sff_br_uuid,sff_br_name)
760 sf_dp_list[intfid]._update_sff_name(sff_name)
761 sff_list[sff_name].add_sf_dp_to_sff(sf_dp_list[intfid])
762 return sff_list
763
764
765 def _get_sf_dp_list_for_chain(self,account,vnffg_chain):
766 """
767 Get list of all Service Function Data Plane Locators present in VNFFG
768 useful for easy reference while creating SF and SFF
769 """
770 sfdp_list = {}
771 for vnf in vnffg_chain.vnf_chain_path:
772 for vnfr in vnf.vnfr_ids:
773 for vdu in vnfr.vdu_list:
774 sfdp = SfDpLocator(vdu.name,vdu.port_id,vnfr.vnfr_name, vdu.vm_id)
775 sfdp._update_vnf_params(vnf.service_function_type, vdu.address, vdu.port, vnf.transport_type)
776 if vnfr.sff_name:
777 sfdp._update_sff_name(vnfr.sff_name)
778 sfdp_list[vdu.port_id] = sfdp
779 return sfdp_list
780
781 def create_sfc(self, account, vnffg_chain):
782 "Create SFC chain"
783
784 sff_list = {}
785 sf_dp_list = {}
786
787 sf_dp_list = self._get_sf_dp_list_for_chain(account,vnffg_chain)
788
789 if sff_rest_based is False and len(vnffg_chain.sff) == 0:
790 # Get the list of all SFFs required for vnffg chain
791 sff_list = self._get_sff_list_for_chain(account,sf_dp_list)
792
793 for sff in vnffg_chain.sff:
794 sff_list[sff.name] = Sff(sff.name, sff.mgmt_address,sff.mgmt_port,sff.dp_endpoints[0].address, sff.dp_endpoints[0].port, sff.name)
795 for _,sf_dp in sf_dp_list.items():
796 if sf_dp.sff_name and sf_dp.sff_name == sff.name:
797 sff_list[sff.name].add_sf_dp_to_sff(sf_dp)
798
799 #Create all the SF in VNFFG chain
800 self._create_sf(account,vnffg_chain,sf_dp_list)
801
802 for _,sff in sff_list.items():
803 self._create_sff(account,vnffg_chain,sff)
804
805
806 self._create_sfc(account,vnffg_chain)
807
808 self._create_sfp(account,vnffg_chain,classifier_name=vnffg_chain.classifier_name,
809 vnffg_metadata_name=vnffg_chain.classifier_name)
810
811 ## Update to SFF could have deleted some RSP; so get list of SFP and
812 ## check RSP exists for same and create any as necessary
813 #rsp_name = self._create_rsp(account,vnffg_chain)
814 #return rsp_name
815 self._create_all_rsps(account)
816 self._recreate_all_sf_classifiers(account)
817 return vnffg_chain.name
818
819 def _recreate_all_sf_classifiers(self,account):
820 """
821 Re create all SF classifiers
822 """
823 sfcl_url = self._get_rest_url(account,self._service_function_classifiers_path)
824 print(sfcl_url)
825 #Get the classifier
826 r=requests.get(sfcl_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'})
827 if r.status_code == 200:
828 print(r)
829 sfcl_json = r.json()
830 elif r.status_code == 404:
831 return
832 else:
833 r.raise_for_status()
834
835 #Delete the classifiers and re-add same back
836 r=requests.delete(sfcl_url,auth=(account.odl.username,account.odl.password))
837 r.raise_for_status()
838 #Readd it back
839 time.sleep(3)
840 print(sfcl_json)
841 sfcl_data = json.dumps(sfcl_json)
842 r=requests.put(sfcl_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=sfcl_data)
843 r.raise_for_status()
844
845 def _create_all_rsps(self,account):
846 """
847 Create all the RSPs for SFP found
848 """
849 sfps_url = self._get_rest_url(account,self._sfps_path)
850 r=requests.get(sfps_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'})
851 r.raise_for_status()
852 sfps_json = r.json()
853 if 'service-function-path' in sfps_json['service-function-paths']:
854 for sfp in sfps_json['service-function-paths']['service-function-path']:
855 rsp_url = self._get_rest_url(account,self._get_rsp_path.format(sfp['name']))
856 r = requests.get(rsp_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'})
857 if r.status_code == 404:
858 # Create the RSP
859 logger.info("Creating RSP for Service Path with name %s",sfp['name'])
860 self._create_rsp(account,sfp['name'])
861
862 def delete_all_sf(self, account):
863 "Delete all the SFs"
864 sf_url = self._get_rest_url(account,self._service_functions_path)
865 print(sf_url)
866 r=requests.delete(sf_url,auth=(account.odl.username,account.odl.password))
867 r.raise_for_status()
868
869
870 def delete_all_sff(self, account):
871 "Delete all the SFFs"
872 sff_url = self._get_rest_url(account,self._service_function_forwarders_path)
873 print(sff_url)
874 r=requests.delete(sff_url,auth=(account.odl.username,account.odl.password))
875 r.raise_for_status()
876
877 def delete_all_sfc(self, account):
878 "Delete all the SFCs"
879 sfc_url = self._get_rest_url(account,self._service_function_chains_path)
880 print(sfc_url)
881 r=requests.delete(sfc_url,auth=(account.odl.username,account.odl.password))
882 r.raise_for_status()
883
884 def delete_all_sfp_metadata(self, account):
885 "Delete all the SFPs metadata"
886 sfp_metadata_url = self._get_rest_url(account,self._sfps_metadata_path)
887 print(sfp_metadata_url)
888 r=requests.delete(sfp_metadata_url,auth=(account.odl.username,account.odl.password))
889 r.raise_for_status()
890
891 def delete_all_sfp(self, account):
892 "Delete all the SFPs"
893 sfp_url = self._get_rest_url(account,self._sfps_path)
894 print(sfp_url)
895 r=requests.delete(sfp_url,auth=(account.odl.username,account.odl.password))
896 r.raise_for_status()
897
898 def delete_all_rsp(self, account):
899 "Delete all the RSP"
900 #rsp_list = self.get_rsp_list(account)
901 url = self._get_rest_url(account,self._get_rsp_paths)
902 print(url)
903 r = requests.get(url,auth=(account.odl.username,account.odl.password))
904 r.raise_for_status()
905 print(r.json())
906 rsp_list = r.json()
907
908 #for vnffg in rsp_list.vnffg_rendered_path:
909 for sfc_rsp in rsp_list['rendered-service-paths']['rendered-service-path']:
910 rsp_json = {}
911 rsp_input = {}
912 rsp_json['input'] = {}
913 rsp_input['name'] = sfc_rsp['name']
914
915 rsp_json['input'] = rsp_input
916 rsp_data = json.dumps(rsp_json)
917 self._rsp_data = rsp_json
918 rsp_url = self._get_rest_url(account,self._delete_rsp_path)
919 print(rsp_url)
920 print(rsp_data)
921
922 r=requests.post(rsp_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=rsp_data)
923 r.raise_for_status()
924 print(r.json())
925 #output_json = r.json()
926 #return output_json['output']['name']
927
928 def terminate_all_sfc(self, account):
929 "Terminate SFC chain"
930 self.delete_all_rsp(account)
931 self.delete_all_sfp(account)
932 self.delete_all_sfc(account)
933 self.delete_all_sff(account)
934 self.delete_all_sf(account)
935
936 def _fill_rsp_list(self,sfc_rsp_list,sff_list):
937 vnffg_rsps = RwsdnalYang.VNFFGRenderedPaths()
938 for sfc_rsp in sfc_rsp_list['rendered-service-paths']['rendered-service-path']:
939 rsp = vnffg_rsps.vnffg_rendered_path.add()
940 rsp.name = sfc_rsp['name']
941 rsp.path_id = sfc_rsp['path-id']
942 for sfc_rsp_hop in sfc_rsp['rendered-service-path-hop']:
943 rsp_hop = rsp.rendered_path_hop.add()
944 rsp_hop.hop_number = sfc_rsp_hop['hop-number']
945 rsp_hop.service_index = sfc_rsp_hop['service-index']
946 rsp_hop.vnfr_name = sfc_rsp_hop['service-function-name']
947 rsp_hop.service_function_forwarder.name = sfc_rsp_hop['service-function-forwarder']
948 for sff in sff_list['service-function-forwarders']['service-function-forwarder']:
949 if sff['name'] == rsp_hop.service_function_forwarder.name:
950 rsp_hop.service_function_forwarder.ip_address = sff['sff-data-plane-locator'][0]['data-plane-locator']['ip']
951 rsp_hop.service_function_forwarder.port = sff['sff-data-plane-locator'][0]['data-plane-locator']['port']
952 break
953 return vnffg_rsps
954
955
956 def get_rsp_list(self,account):
957 "Get RSP list"
958
959 sff_url = self._get_rest_url(account,self._service_function_forwarders_path)
960 print(sff_url)
961 r=requests.get(sff_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'})
962 r.raise_for_status()
963 sff_list = r.json()
964
965 url = self._get_rest_url(account,self._get_rsp_paths)
966 print(url)
967 r = requests.get(url,auth=(account.odl.username,account.odl.password))
968 r.raise_for_status()
969 print(r.json())
970 return self._fill_rsp_list(r.json(),sff_list)
971
972 def create_sfc_classifier(self, account, sfc_classifiers):
973 "Create SFC Classifiers"
974 self._create_sfp_metadata(account,sfc_classifiers)
975 self._add_acl_rules(account, sfc_classifiers)
976 self._create_sf_classifier(account, sfc_classifiers)
977 return sfc_classifiers.name
978
979 def terminate_sfc_classifier(self, account, sfc_classifier_name):
980 "Create SFC Classifiers"
981 self.delete_all_sfp_metadata(account)
982 self._terminate_sf_classifier(account, sfc_classifier_name)
983 self._del_acl_rules(account, sfc_classifier_name)
984
985 def _del_acl_rules(self,account,sfc_classifier_name):
986 " Terminate SF classifiers"
987 acl_url = self._get_rest_url(account,self._access_lists_path)
988 print(acl_url)
989 r=requests.delete(acl_url,auth=(account.odl.username,account.odl.password))
990 r.raise_for_status()
991
992 def _terminate_sf_classifier(self,account,sfc_classifier_name):
993 " Terminate SF classifiers"
994 sfcl_url = self._get_rest_url(account,self._service_function_classifiers_path)
995 print(sfcl_url)
996 r=requests.delete(sfcl_url,auth=(account.odl.username,account.odl.password))
997 r.raise_for_status()
998
999 def _create_sf_classifier(self,account,sfc_classifiers):
1000 " Create SF classifiers"
1001 sf_classifier_json = {}
1002 sf_classifier_dict = {}
1003 sf_classifier_dict['name'] = sfc_classifiers.name
1004 sf_classifier_dict['access-list'] = sfc_classifiers.name
1005 sf_classifier_dict['scl-service-function-forwarder'] = list()
1006 scl_sff = {}
1007 scl_sff_name = ''
1008
1009 if sfc_classifiers.has_field('sff_name') and sfc_classifiers.sff_name is not None:
1010 scl_sff_name = sfc_classifiers.sff_name
1011 elif sfc_classifiers.has_field('port_id') and sfc_classifiers.has_field('vm_id'):
1012 sf_dp = SfDpLocator(sfc_classifiers.port_id, sfc_classifiers.port_id,'', sfc_classifiers.vm_id)
1013 sf_dp_list= {}
1014 sf_dp_list[sfc_classifiers.port_id] = sf_dp
1015 self._get_sff_list_for_chain(account,sf_dp_list)
1016
1017 if sf_dp.sff_name is None:
1018 logger.error("SFF not found for port %s, VM: %s",sfc_classifiers.port_id,sfc_classifiers.vm_id)
1019 else:
1020 logger.info("SFF with name %s found for port %s, VM: %s",sf_dp.sff_name, sfc_classifiers.port_id,sfc_classifiers.vm_id)
1021 scl_sff_name = sf_dp.sff_name
1022 else:
1023 rsp_url = self._get_rest_url(account,self._get_rsp_path.format(sfc_classifiers.rsp_name))
1024 r = requests.get(rsp_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'})
1025 if r.status_code == 200:
1026 rsp_data = r.json()
1027 if 'rendered-service-path' in rsp_data and len(rsp_data['rendered-service-path'][0]['rendered-service-path-hop']) > 0:
1028 scl_sff_name = rsp_data['rendered-service-path'][0]['rendered-service-path-hop'][0]['service-function-forwarder']
1029
1030 logger.debug("SFF for classifer %s found is %s",sfc_classifiers.name, scl_sff_name)
1031 scl_sff['name'] = scl_sff_name
1032 #scl_sff['interface'] = sff_intf_name
1033 sf_classifier_dict['scl-service-function-forwarder'].append(scl_sff)
1034
1035 sf_classifier_json['service-function-classifier'] = sf_classifier_dict
1036
1037 sfcl_data = json.dumps(sf_classifier_json)
1038 sfcl_url = self._get_rest_url(account,self._service_function_classifier_path.format(sfc_classifiers.name))
1039 print(sfcl_url)
1040 print(sfcl_data)
1041 r=requests.put(sfcl_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=sfcl_data)
1042 r.raise_for_status()
1043
1044 def _add_acl_rules(self, account,sfc_classifiers):
1045 "Create ACL rules"
1046 access_list_json = {}
1047 access_list_dict = {}
1048 acl_entry_list = list()
1049 acl_list_dict = {}
1050 for acl_rule in sfc_classifiers.match_attributes:
1051 acl_entry = {}
1052 acl_entry['rule-name'] = acl_rule.name
1053 acl_entry['actions'] = {}
1054 #acl_entry['actions']['netvirt-sfc-acl:rsp-name'] = sfc_classifiers.rsp_name
1055 acl_entry['actions']['service-function-acl:rendered-service-path'] = sfc_classifiers.rsp_name
1056
1057 matches = {}
1058 for field, value in acl_rule.as_dict().items():
1059 if field == 'ip_proto':
1060 matches['protocol'] = value
1061 elif field == 'source_ip_address':
1062 matches['source-ipv4-network'] = value
1063 elif field == 'destination_ip_address':
1064 matches['destination-ipv4-network'] = value
1065 elif field == 'source_port':
1066 matches['source-port-range'] = {'lower-port':value, 'upper-port':value}
1067 elif field == 'destination_port':
1068 matches['destination-port-range'] = {'lower-port':value, 'upper-port':value}
1069 acl_entry['matches'] = matches
1070 acl_entry_list.append(acl_entry)
1071 acl_list_dict['ace'] = acl_entry_list
1072 access_list_dict['acl-name'] = sfc_classifiers.name
1073 access_list_dict['access-list-entries'] = acl_list_dict
1074 access_list_json['acl'] = access_list_dict
1075
1076 acl_data = json.dumps(access_list_json)
1077 acl_url = self._get_rest_url(account,self._access_list_path.format(sfc_classifiers.name))
1078 print(acl_url)
1079 print(acl_data)
1080 r=requests.put(acl_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=acl_data)
1081 r.raise_for_status()