RIFT OSM R1 Initial Submission
[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('RwsdnYang', '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 RwsdnYang,
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 = RwsdnYang.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 print("SDN Successfully connected")
358 status.status = "success"
359 status.details = "Connection was successful"
360
361 return status
362
363 def get_network_list(self, account):
364 """
365 Get the networks details from ODL
366 """
367 url = self._network_topology_rest_url(account)
368 r=requests.get(url,auth=(account.odl.username,account.odl.password))
369 r.raise_for_status()
370 nw_topo = r.json()
371
372 url = self._node_inventory_rest_url(account)
373 r = requests.get(url,auth=(account.odl.username,account.odl.password))
374 r.raise_for_status()
375 node_inventory = r.json()
376 return self._fill_network_list(nw_topo,node_inventory)
377
378 @property
379 def _service_functions_path(self):
380 return 'restconf/config/service-function:service-functions'
381
382 @property
383 def _service_function_path(self):
384 return 'restconf/config/service-function:service-functions/service-function/{}'
385
386 @property
387 def _service_function_forwarders_path(self):
388 return 'restconf/config/service-function-forwarder:service-function-forwarders'
389
390 @property
391 def _service_function_forwarder_path(self):
392 return 'restconf/config/service-function-forwarder:service-function-forwarders/service-function-forwarder/{}'
393
394 @property
395 def _service_function_chains_path(self):
396 return 'restconf/config/service-function-chain:service-function-chains'
397
398 @property
399 def _service_function_chain_path(self):
400 return 'restconf/config/service-function-chain:service-function-chains/service-function-chain/{}'
401
402 @property
403 def _sfp_metadata_path(self):
404 return 'restconf/config/service-function-path-metadata:service-function-metadata/context-metadata/{}'
405
406 @property
407 def _sfps_metadata_path(self):
408 return 'restconf/config/service-function-path-metadata:service-function-metadata'
409
410 @property
411 def _sfps_path(self):
412 return 'restconf/config/service-function-path:service-function-paths'
413
414 @property
415 def _sfp_path(self):
416 return 'restconf/config/service-function-path:service-function-paths/service-function-path/{}'
417
418
419 @property
420 def _create_rsp_path(self):
421 return 'restconf/operations/rendered-service-path:create-rendered-path'
422
423 @property
424 def _delete_rsp_path(self):
425 return 'restconf/operations/rendered-service-path:delete-rendered-path'
426
427
428 @property
429 def _get_rsp_paths(self):
430 return 'restconf/operational/rendered-service-path:rendered-service-paths'
431
432 @property
433 def _get_rsp_path(self):
434 return 'restconf/operational/rendered-service-path:rendered-service-paths/rendered-service-path/{}'
435
436 @property
437 def _access_list_path(self):
438 return 'restconf/config/ietf-access-control-list:access-lists/acl/{}'
439
440 @property
441 def _service_function_classifier_path(self):
442 return 'restconf/config/service-function-classifier:service-function-classifiers/service-function-classifier/{}'
443
444 @property
445 def _access_lists_path(self):
446 return 'restconf/config/ietf-access-control-list:access-lists'
447
448 @property
449 def _service_function_classifiers_path(self):
450 return 'restconf/config/service-function-classifier:service-function-classifiers'
451
452
453 def _create_sf(self,account,vnffg_chain,sf_dp_list):
454 "Create SF"
455 sf_json = {}
456
457 for vnf in vnffg_chain.vnf_chain_path:
458 for vnfr in vnf.vnfr_ids:
459 sf_url = self._get_rest_url(account,self._service_function_path.format(vnfr.vnfr_name))
460 print(sf_url)
461 r=requests.get(sf_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'})
462 # If the SF is not found; create new SF
463 if r.status_code == 200:
464 logger.info("SF with name %s is already present in ODL. Skipping update", vnfr.vnfr_name)
465 continue
466 elif r.status_code != 404:
467 r.raise_for_status()
468
469 sf_dict = {}
470 sf_dict['name'] = vnfr.vnfr_name
471 sf_dict['nsh-aware'] = vnf.nsh_aware
472 sf_dict['type'] = vnf.service_function_type
473 sf_dict['ip-mgmt-address'] = vnfr.mgmt_address
474 sf_dict['rest-uri'] = 'http://{}:{}'.format(vnfr.mgmt_address, vnfr.mgmt_port)
475
476 sf_dict['sf-data-plane-locator'] = list()
477 for vdu in vnfr.vdu_list:
478 sf_dp = {}
479 if vdu.port_id in sf_dp_list.keys():
480 sf_dp_entry = sf_dp_list[vdu.port_id]
481 sf_dp['name'] = sf_dp_entry.name
482 sf_dp['ip'] = vdu.address
483 sf_dp['port'] = vdu.port
484 sf_dp['transport'] = "service-locator:{}".format(vnf.transport_type)
485 if vnfr.sff_name:
486 sf_dp['service-function-forwarder'] = vnfr.sff_name
487 else:
488 sff_name = sf_dp_entry.sff_name
489 if sff_name is None:
490 logger.error("SFF not found for port %s in SF %s", vdu.port_id, vnfr.vnfr_name)
491 sf_dp['service-function-forwarder'] = sff_name
492 sf_dp['service-function-ovs:ovs-port'] = dict()
493 if sf_dp_entry.ovsdb_tp_name is not None:
494 sf_dp['service-function-ovs:ovs-port']['port-id'] = sf_dp_entry.ovsdb_tp_name
495 sf_dict['sf-data-plane-locator'].append(sf_dp)
496 else:
497 logger.error("Port %s not found in SF DP list",vdu.port_id)
498
499 sf_json['service-function'] = sf_dict
500 sf_data = json.dumps(sf_json)
501 sf_url = self._get_rest_url(account,self._service_function_path.format(vnfr.vnfr_name))
502 print(sf_url)
503 print(sf_data)
504 r=requests.put(sf_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=sf_data)
505 r.raise_for_status()
506
507
508 def _create_sff(self,account,vnffg_chain,sff):
509 "Create SFF"
510 sff_json = {}
511 sff_dict = {}
512 #sff_dp_name = "SFF1" + '-' + 'DP1'
513 sff_dp_name = sff.dp_name
514
515 sff_url = self._get_rest_url(account,self._service_function_forwarder_path.format(sff.name))
516 print(sff_url)
517 r=requests.get(sff_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'})
518 # If the SFF is not found; create new SF
519 if r.status_code == 200:
520 logger.info("SFF with name %s is already present in ODL. Skipping full update", sff.name)
521 sff_dict = r.json()
522 sff_updated = False
523 for sf_dp in sff.sf_dp_list:
524 for sff_sf in sff_dict['service-function-forwarder'][0]['service-function-dictionary']:
525 if sf_dp.vnfr_name == sff_sf['name']:
526 logger.info("SF with name %s is already found in SFF %s SF Dictionay. Skipping update",sf_dp.vnfr_name,sff.name)
527 break
528 else:
529 logger.info("SF with name %s is not found in SFF %s SF Dictionay",sf_dp.vnfr_name, sff.name)
530 sff_updated = True
531 sff_sf_dict = {}
532 sff_sf_dp_loc = {}
533 sff_sf_dict['name'] = sf_dp.vnfr_name
534
535 # Below two lines are enabled only for ODL Beryillium
536 sff_sf_dp_loc['sff-dpl-name'] = sff_dp_name
537 sff_sf_dp_loc['sf-dpl-name'] = sf_dp.name
538
539 sff_sf_dict['sff-sf-data-plane-locator'] = sff_sf_dp_loc
540 sff_dict['service-function-forwarder'][0]['service-function-dictionary'].append(sff_sf_dict)
541 if sff_updated is True:
542 sff_data = json.dumps(sff_dict)
543 print(sff_data)
544 r=requests.put(sff_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=sff_data)
545 r.raise_for_status()
546 return
547 elif r.status_code != 404:
548 r.raise_for_status()
549
550 sff_name = sff.name
551 sff_ip = sff.ip
552 sff_dp_ip = sff.dp_ip
553 sff_port = sff.sff_port
554 sff_bridge_name = ''
555 sff_rest_port = sff.sff_rest_port
556 sff_ovs_op = {}
557 if sff_rest_based is False:
558 sff_bridge_name = sff.br_name
559 sff_ovs_op = {"key": "flow",
560 "nshc1": "flow",
561 "nsp": "flow",
562 "remote-ip": "flow",
563 "dst-port": sff_port,
564 "nshc3": "flow",
565 "nshc2": "flow",
566 "nshc4": "flow",
567 "nsi": "flow"}
568
569
570 sff_dict['name'] = sff_name
571 sff_dict['service-node'] = ''
572 sff_dict['ip-mgmt-address'] = sff_ip
573 if sff_rest_based:
574 sff_dict['rest-uri'] = 'http://{}:{}'.format(sff_ip, sff_rest_port)
575 else:
576 sff_dict['service-function-forwarder-ovs:ovs-bridge'] = {"bridge-name": sff_bridge_name}
577 sff_dict['service-function-dictionary'] = list()
578 for sf_dp in sff.sf_dp_list:
579 sff_sf_dict = {}
580 sff_sf_dp_loc = {}
581 sff_sf_dict['name'] = sf_dp.vnfr_name
582
583 # Below set of lines are reqd for Lithium
584 #sff_sf_dict['type'] = sf_dp.service_function_type
585 #sff_sf_dp_loc['ip'] = sf_dp.address
586 #sff_sf_dp_loc['port'] = sf_dp.port
587 #sff_sf_dp_loc['transport'] = sf_dp.transport_type
588 #sff_sf_dp_loc['service-function-forwarder-ovs:ovs-bridge'] = {}
589
590 # Below two lines are enabled only for ODL Beryillium
591 sff_sf_dp_loc['sff-dpl-name'] = sff_dp_name
592 sff_sf_dp_loc['sf-dpl-name'] = sf_dp.name
593
594 sff_sf_dict['sff-sf-data-plane-locator'] = sff_sf_dp_loc
595 sff_dict['service-function-dictionary'].append(sff_sf_dict)
596
597 sff_dict['sff-data-plane-locator'] = list()
598 sff_dp = {}
599 dp_loc = {}
600 sff_dp['name'] = sff_dp_name
601 dp_loc['ip'] = sff_dp_ip
602 dp_loc['port'] = sff_port
603 dp_loc['transport'] = 'service-locator:vxlan-gpe'
604 sff_dp['data-plane-locator'] = dp_loc
605 if sff_rest_based is False:
606 sff_dp['service-function-forwarder-ovs:ovs-options'] = sff_ovs_op
607 #sff_dp["service-function-forwarder-ovs:ovs-bridge"] = {'bridge-name':sff_bridge_name}
608 sff_dp["service-function-forwarder-ovs:ovs-bridge"] = {}
609 sff_dict['sff-data-plane-locator'].append(sff_dp)
610
611 sff_json['service-function-forwarder'] = sff_dict
612 sff_data = json.dumps(sff_json)
613 print(sff_data)
614 r=requests.put(sff_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=sff_data)
615 r.raise_for_status()
616
617 def _create_sfc(self,account,vnffg_chain):
618 "Create SFC"
619 sfc_json = {}
620 sfc_dict = {}
621 sfc_dict['name'] = vnffg_chain.name
622 sfc_dict['sfc-service-function'] = list()
623 vnf_chain_list = sorted(vnffg_chain.vnf_chain_path, key = lambda x: x.order)
624 for vnf in vnf_chain_list:
625 sfc_sf_dict = {}
626 sfc_sf_dict['name'] = vnf.service_function_type
627 sfc_sf_dict['type'] = vnf.service_function_type
628 sfc_sf_dict['order'] = vnf.order
629 sfc_dict['sfc-service-function'].append(sfc_sf_dict)
630 sfc_json['service-function-chain'] = sfc_dict
631 sfc_data = json.dumps(sfc_json)
632 sfc_url = self._get_rest_url(account,self._service_function_chain_path.format(vnffg_chain.name))
633 print(sfc_url)
634 print(sfc_data)
635 r=requests.put(sfc_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=sfc_data)
636 r.raise_for_status()
637
638 def _create_sfp_metadata(self,account,sfc_classifier):
639 " Create SFP metadata"
640 sfp_meta_json = {}
641 sfp_meta_dict = {}
642 sfp_meta_dict['name'] = sfc_classifier.name
643 if sfc_classifier.vnffg_metadata.ctx1:
644 sfp_meta_dict['context-header1'] = sfc_classifier.vnffg_metadata.ctx1
645 if sfc_classifier.vnffg_metadata.ctx2:
646 sfp_meta_dict['context-header2'] = sfc_classifier.vnffg_metadata.ctx2
647 if sfc_classifier.vnffg_metadata.ctx3:
648 sfp_meta_dict['context-header3'] = sfc_classifier.vnffg_metadata.ctx3
649 if sfc_classifier.vnffg_metadata.ctx4:
650 sfp_meta_dict['context-header4'] = sfc_classifier.vnffg_metadata.ctx4
651
652 sfp_meta_json['context-metadata'] = sfp_meta_dict
653 sfp_meta_data = json.dumps(sfp_meta_json)
654 sfp_meta_url = self._get_rest_url(account,self._sfp_metadata_path.format(sfc_classifier.name))
655 print(sfp_meta_url)
656 print(sfp_meta_data)
657 r=requests.put(sfp_meta_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=sfp_meta_data)
658 r.raise_for_status()
659
660 def _create_sfp(self,account,vnffg_chain, sym_chain=False,classifier_name=None,vnffg_metadata_name=None):
661 "Create SFP"
662 sfp_json = {}
663 sfp_dict = {}
664 sfp_dict['name'] = vnffg_chain.name
665 sfp_dict['service-chain-name'] = vnffg_chain.name
666 sfp_dict['symmetric'] = sym_chain
667 sfp_dict['transport-type'] = 'service-locator:vxlan-gpe'
668 if vnffg_metadata_name:
669 sfp_dict['context-metadata'] = vnffg_metadata_name
670 if classifier_name:
671 sfp_dict['classifier'] = classifier_name
672
673 sfp_json['service-function-path'] = sfp_dict
674 sfp_data = json.dumps(sfp_json)
675 sfp_url = self._get_rest_url(account,self._sfp_path.format(vnffg_chain.name))
676 print(sfp_url)
677 print(sfp_data)
678 r=requests.put(sfp_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=sfp_data)
679 r.raise_for_status()
680
681 def _create_rsp(self,account,vnffg_chain_name, sym_chain=True):
682 "Create RSP"
683 rsp_json = {}
684 rsp_input = {}
685 rsp_json['input'] = {}
686 rsp_input['name'] = vnffg_chain_name
687 rsp_input['parent-service-function-path'] = vnffg_chain_name
688 rsp_input['symmetric'] = sym_chain
689
690 rsp_json['input'] = rsp_input
691 rsp_data = json.dumps(rsp_json)
692 self._rsp_data = rsp_json
693 rsp_url = self._get_rest_url(account,self._create_rsp_path)
694 print(rsp_url)
695 print(rsp_data)
696 r=requests.post(rsp_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=rsp_data)
697 r.raise_for_status()
698 print(r.json())
699 output_json = r.json()
700 return output_json['output']['name']
701
702 def _get_sff_list_for_chain(self, account,sf_dp_list):
703 """
704 Get List of all SFF that needs to be created based on VNFs included in VNFFG chain.
705 """
706
707 sff_list = {}
708 if sf_dp_list is None:
709 logger.error("VM List for vnffg chain is empty while trying to get SFF list")
710 url = self._network_topology_rest_url(account)
711 r=requests.get(url,auth=(account.odl.username,account.odl.password))
712 r.raise_for_status()
713 nw_topo = r.json()
714
715 for topo in nw_topo['network-topology']['topology']:
716 if ('node' in topo and len(topo['node']) > 0):
717 for node in topo['node']:
718 if ('termination-point' in node and len(node['termination-point']) > 0):
719 for term_point in node['termination-point']:
720 if 'ovsdb:interface-external-ids' in term_point:
721 vm_id = [res for res in term_point['ovsdb:interface-external-ids'] if res['external-id-key'] == 'vm-id']
722 if len(vm_id) == 0:
723 continue
724 vmid = vm_id[0]['external-id-value']
725 intf_id = [res for res in term_point['ovsdb:interface-external-ids'] if res['external-id-key'] == 'iface-id']
726 if len(intf_id) == 0:
727 continue
728 intfid = intf_id[0]['external-id-value']
729 if intfid not in sf_dp_list.keys():
730 continue
731 if sf_dp_list[intfid].vm_id != vmid:
732 logger.error("Intf ID %s is not present in VM %s", intfid, vmid)
733 continue
734 sf_dp_list[intfid].ovsdb_tp_name = term_point['ovsdb:name']
735
736 if 'ovsdb:managed-by' in node:
737 rr=re.search('network-topology:node-id=\'([-\w\:\/]*)\'',node['ovsdb:managed-by'])
738 node_id = rr.group(1)
739 ovsdb_node = [node for node in topo['node'] if node['node-id'] == node_id]
740 if ovsdb_node:
741 if 'ovsdb:connection-info' in ovsdb_node[0]:
742 sff_ip = ovsdb_node[0]['ovsdb:connection-info']['local-ip']
743 sff_br_name = node['ovsdb:bridge-name']
744 sff_br_uuid = node['ovsdb:bridge-uuid']
745 sff_dp_ip = sff_ip
746
747 if 'ovsdb:openvswitch-other-configs' in ovsdb_node[0]:
748 for other_key in ovsdb_node[0]['ovsdb:openvswitch-other-configs']:
749 if other_key['other-config-key'] == 'local_ip':
750 local_ip_str = other_key['other-config-value']
751 sff_dp_ip = local_ip_str.split(',')[0]
752 break
753
754 sff_name = socket.getfqdn(sff_ip)
755 if sff_br_uuid in sff_list:
756 sff_list[sff_name].add_sf_dp_to_sff(sf_dp_list[intfid])
757 sf_dp_list[intfid]._update_sff_name(sff_name)
758 else:
759 sff_dp_ip = sff_ip #overwrite sff_dp_ip to SFF ip for now
760 sff_list[sff_name] = Sff(sff_name,sff_ip,6000, sff_dp_ip, 4790,sff_br_uuid,sff_br_name)
761 sf_dp_list[intfid]._update_sff_name(sff_name)
762 sff_list[sff_name].add_sf_dp_to_sff(sf_dp_list[intfid])
763 return sff_list
764
765
766 def _get_sf_dp_list_for_chain(self,account,vnffg_chain):
767 """
768 Get list of all Service Function Data Plane Locators present in VNFFG
769 useful for easy reference while creating SF and SFF
770 """
771 sfdp_list = {}
772 for vnf in vnffg_chain.vnf_chain_path:
773 for vnfr in vnf.vnfr_ids:
774 for vdu in vnfr.vdu_list:
775 sfdp = SfDpLocator(vdu.name,vdu.port_id,vnfr.vnfr_name, vdu.vm_id)
776 sfdp._update_vnf_params(vnf.service_function_type, vdu.address, vdu.port, vnf.transport_type)
777 if vnfr.sff_name:
778 sfdp._update_sff_name(vnfr.sff_name)
779 sfdp_list[vdu.port_id] = sfdp
780 return sfdp_list
781
782 def create_sfc(self, account, vnffg_chain):
783 "Create SFC chain"
784
785 sff_list = {}
786 sf_dp_list = {}
787
788 sf_dp_list = self._get_sf_dp_list_for_chain(account,vnffg_chain)
789
790 if sff_rest_based is False and len(vnffg_chain.sff) == 0:
791 # Get the list of all SFFs required for vnffg chain
792 sff_list = self._get_sff_list_for_chain(account,sf_dp_list)
793
794 for sff in vnffg_chain.sff:
795 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)
796 for _,sf_dp in sf_dp_list.items():
797 if sf_dp.sff_name and sf_dp.sff_name == sff.name:
798 sff_list[sff.name].add_sf_dp_to_sff(sf_dp)
799
800 #Create all the SF in VNFFG chain
801 self._create_sf(account,vnffg_chain,sf_dp_list)
802
803 for _,sff in sff_list.items():
804 self._create_sff(account,vnffg_chain,sff)
805
806
807 self._create_sfc(account,vnffg_chain)
808
809 self._create_sfp(account,vnffg_chain,classifier_name=vnffg_chain.classifier_name,
810 vnffg_metadata_name=vnffg_chain.classifier_name)
811
812 ## Update to SFF could have deleted some RSP; so get list of SFP and
813 ## check RSP exists for same and create any as necessary
814 #rsp_name = self._create_rsp(account,vnffg_chain)
815 #return rsp_name
816 self._create_all_rsps(account)
817 self._recreate_all_sf_classifiers(account)
818 return vnffg_chain.name
819
820 def _recreate_all_sf_classifiers(self,account):
821 """
822 Re create all SF classifiers
823 """
824 sfcl_url = self._get_rest_url(account,self._service_function_classifiers_path)
825 print(sfcl_url)
826 #Get the classifier
827 r=requests.get(sfcl_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'})
828 if r.status_code == 200:
829 print(r)
830 sfcl_json = r.json()
831 elif r.status_code == 404:
832 return
833 else:
834 r.raise_for_status()
835
836 #Delete the classifiers and re-add same back
837 r=requests.delete(sfcl_url,auth=(account.odl.username,account.odl.password))
838 r.raise_for_status()
839 #Readd it back
840 time.sleep(3)
841 print(sfcl_json)
842 sfcl_data = json.dumps(sfcl_json)
843 r=requests.put(sfcl_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=sfcl_data)
844 r.raise_for_status()
845
846 def _create_all_rsps(self,account):
847 """
848 Create all the RSPs for SFP found
849 """
850 sfps_url = self._get_rest_url(account,self._sfps_path)
851 r=requests.get(sfps_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'})
852 r.raise_for_status()
853 sfps_json = r.json()
854 if 'service-function-path' in sfps_json['service-function-paths']:
855 for sfp in sfps_json['service-function-paths']['service-function-path']:
856 rsp_url = self._get_rest_url(account,self._get_rsp_path.format(sfp['name']))
857 r = requests.get(rsp_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'})
858 if r.status_code == 404:
859 # Create the RSP
860 logger.info("Creating RSP for Service Path with name %s",sfp['name'])
861 self._create_rsp(account,sfp['name'])
862
863 def delete_all_sf(self, account):
864 "Delete all the SFs"
865 sf_url = self._get_rest_url(account,self._service_functions_path)
866 print(sf_url)
867 r=requests.delete(sf_url,auth=(account.odl.username,account.odl.password))
868 r.raise_for_status()
869
870
871 def delete_all_sff(self, account):
872 "Delete all the SFFs"
873 sff_url = self._get_rest_url(account,self._service_function_forwarders_path)
874 print(sff_url)
875 r=requests.delete(sff_url,auth=(account.odl.username,account.odl.password))
876 r.raise_for_status()
877
878 def delete_all_sfc(self, account):
879 "Delete all the SFCs"
880 sfc_url = self._get_rest_url(account,self._service_function_chains_path)
881 print(sfc_url)
882 r=requests.delete(sfc_url,auth=(account.odl.username,account.odl.password))
883 r.raise_for_status()
884
885 def delete_all_sfp_metadata(self, account):
886 "Delete all the SFPs metadata"
887 sfp_metadata_url = self._get_rest_url(account,self._sfps_metadata_path)
888 print(sfp_metadata_url)
889 r=requests.delete(sfp_metadata_url,auth=(account.odl.username,account.odl.password))
890 r.raise_for_status()
891
892 def delete_all_sfp(self, account):
893 "Delete all the SFPs"
894 sfp_url = self._get_rest_url(account,self._sfps_path)
895 print(sfp_url)
896 r=requests.delete(sfp_url,auth=(account.odl.username,account.odl.password))
897 r.raise_for_status()
898
899 def delete_all_rsp(self, account):
900 "Delete all the RSP"
901 #rsp_list = self.get_rsp_list(account)
902 url = self._get_rest_url(account,self._get_rsp_paths)
903 print(url)
904 r = requests.get(url,auth=(account.odl.username,account.odl.password))
905 r.raise_for_status()
906 print(r.json())
907 rsp_list = r.json()
908
909 #for vnffg in rsp_list.vnffg_rendered_path:
910 for sfc_rsp in rsp_list['rendered-service-paths']['rendered-service-path']:
911 rsp_json = {}
912 rsp_input = {}
913 rsp_json['input'] = {}
914 rsp_input['name'] = sfc_rsp['name']
915
916 rsp_json['input'] = rsp_input
917 rsp_data = json.dumps(rsp_json)
918 self._rsp_data = rsp_json
919 rsp_url = self._get_rest_url(account,self._delete_rsp_path)
920 print(rsp_url)
921 print(rsp_data)
922
923 r=requests.post(rsp_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=rsp_data)
924 r.raise_for_status()
925 print(r.json())
926 #output_json = r.json()
927 #return output_json['output']['name']
928
929 def terminate_all_sfc(self, account):
930 "Terminate SFC chain"
931 self.delete_all_rsp(account)
932 self.delete_all_sfp(account)
933 self.delete_all_sfc(account)
934 self.delete_all_sff(account)
935 self.delete_all_sf(account)
936
937 def _fill_rsp_list(self,sfc_rsp_list,sff_list):
938 vnffg_rsps = RwsdnYang.VNFFGRenderedPaths()
939 for sfc_rsp in sfc_rsp_list['rendered-service-paths']['rendered-service-path']:
940 rsp = vnffg_rsps.vnffg_rendered_path.add()
941 rsp.name = sfc_rsp['name']
942 rsp.path_id = sfc_rsp['path-id']
943 for sfc_rsp_hop in sfc_rsp['rendered-service-path-hop']:
944 rsp_hop = rsp.rendered_path_hop.add()
945 rsp_hop.hop_number = sfc_rsp_hop['hop-number']
946 rsp_hop.service_index = sfc_rsp_hop['service-index']
947 rsp_hop.vnfr_name = sfc_rsp_hop['service-function-name']
948 rsp_hop.service_function_forwarder.name = sfc_rsp_hop['service-function-forwarder']
949 for sff in sff_list['service-function-forwarders']['service-function-forwarder']:
950 if sff['name'] == rsp_hop.service_function_forwarder.name:
951 rsp_hop.service_function_forwarder.ip_address = sff['sff-data-plane-locator'][0]['data-plane-locator']['ip']
952 rsp_hop.service_function_forwarder.port = sff['sff-data-plane-locator'][0]['data-plane-locator']['port']
953 break
954 return vnffg_rsps
955
956
957 def get_rsp_list(self,account):
958 "Get RSP list"
959
960 sff_url = self._get_rest_url(account,self._service_function_forwarders_path)
961 print(sff_url)
962 r=requests.get(sff_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'})
963 r.raise_for_status()
964 sff_list = r.json()
965
966 url = self._get_rest_url(account,self._get_rsp_paths)
967 print(url)
968 r = requests.get(url,auth=(account.odl.username,account.odl.password))
969 r.raise_for_status()
970 print(r.json())
971 return self._fill_rsp_list(r.json(),sff_list)
972
973 def create_sfc_classifier(self, account, sfc_classifiers):
974 "Create SFC Classifiers"
975 self._create_sfp_metadata(account,sfc_classifiers)
976 self._add_acl_rules(account, sfc_classifiers)
977 self._create_sf_classifier(account, sfc_classifiers)
978 return sfc_classifiers.name
979
980 def terminate_sfc_classifier(self, account, sfc_classifier_name):
981 "Create SFC Classifiers"
982 self.delete_all_sfp_metadata(account)
983 self._terminate_sf_classifier(account, sfc_classifier_name)
984 self._del_acl_rules(account, sfc_classifier_name)
985
986 def _del_acl_rules(self,account,sfc_classifier_name):
987 " Terminate SF classifiers"
988 acl_url = self._get_rest_url(account,self._access_lists_path)
989 print(acl_url)
990 r=requests.delete(acl_url,auth=(account.odl.username,account.odl.password))
991 r.raise_for_status()
992
993 def _terminate_sf_classifier(self,account,sfc_classifier_name):
994 " Terminate SF classifiers"
995 sfcl_url = self._get_rest_url(account,self._service_function_classifiers_path)
996 print(sfcl_url)
997 r=requests.delete(sfcl_url,auth=(account.odl.username,account.odl.password))
998 r.raise_for_status()
999
1000 def _create_sf_classifier(self,account,sfc_classifiers):
1001 " Create SF classifiers"
1002 sf_classifier_json = {}
1003 sf_classifier_dict = {}
1004 sf_classifier_dict['name'] = sfc_classifiers.name
1005 sf_classifier_dict['access-list'] = sfc_classifiers.name
1006 sf_classifier_dict['scl-service-function-forwarder'] = list()
1007 scl_sff = {}
1008 scl_sff_name = ''
1009
1010 if sfc_classifiers.has_field('sff_name') and sfc_classifiers.sff_name is not None:
1011 scl_sff_name = sfc_classifiers.sff_name
1012 elif sfc_classifiers.has_field('port_id') and sfc_classifiers.has_field('vm_id'):
1013 sf_dp = SfDpLocator(sfc_classifiers.port_id, sfc_classifiers.port_id,'', sfc_classifiers.vm_id)
1014 sf_dp_list= {}
1015 sf_dp_list[sfc_classifiers.port_id] = sf_dp
1016 self._get_sff_list_for_chain(account,sf_dp_list)
1017
1018 if sf_dp.sff_name is None:
1019 logger.error("SFF not found for port %s, VM: %s",sfc_classifiers.port_id,sfc_classifiers.vm_id)
1020 else:
1021 logger.info("SFF with name %s found for port %s, VM: %s",sf_dp.sff_name, sfc_classifiers.port_id,sfc_classifiers.vm_id)
1022 scl_sff_name = sf_dp.sff_name
1023 else:
1024 rsp_url = self._get_rest_url(account,self._get_rsp_path.format(sfc_classifiers.rsp_name))
1025 r = requests.get(rsp_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'})
1026 if r.status_code == 200:
1027 rsp_data = r.json()
1028 if 'rendered-service-path' in rsp_data and len(rsp_data['rendered-service-path'][0]['rendered-service-path-hop']) > 0:
1029 scl_sff_name = rsp_data['rendered-service-path'][0]['rendered-service-path-hop'][0]['service-function-forwarder']
1030
1031 logger.debug("SFF for classifer %s found is %s",sfc_classifiers.name, scl_sff_name)
1032 scl_sff['name'] = scl_sff_name
1033 #scl_sff['interface'] = sff_intf_name
1034 sf_classifier_dict['scl-service-function-forwarder'].append(scl_sff)
1035
1036 sf_classifier_json['service-function-classifier'] = sf_classifier_dict
1037
1038 sfcl_data = json.dumps(sf_classifier_json)
1039 sfcl_url = self._get_rest_url(account,self._service_function_classifier_path.format(sfc_classifiers.name))
1040 print(sfcl_url)
1041 print(sfcl_data)
1042 r=requests.put(sfcl_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=sfcl_data)
1043 r.raise_for_status()
1044
1045 def _add_acl_rules(self, account,sfc_classifiers):
1046 "Create ACL rules"
1047 access_list_json = {}
1048 access_list_dict = {}
1049 acl_entry_list = list()
1050 acl_list_dict = {}
1051 for acl_rule in sfc_classifiers.match_attributes:
1052 acl_entry = {}
1053 acl_entry['rule-name'] = acl_rule.name
1054 acl_entry['actions'] = {}
1055 #acl_entry['actions']['netvirt-sfc-acl:rsp-name'] = sfc_classifiers.rsp_name
1056 acl_entry['actions']['service-function-acl:rendered-service-path'] = sfc_classifiers.rsp_name
1057
1058 matches = {}
1059 for field, value in acl_rule.as_dict().items():
1060 if field == 'ip_proto':
1061 matches['protocol'] = value
1062 elif field == 'source_ip_address':
1063 matches['source-ipv4-network'] = value
1064 elif field == 'destination_ip_address':
1065 matches['destination-ipv4-network'] = value
1066 elif field == 'source_port':
1067 matches['source-port-range'] = {'lower-port':value, 'upper-port':value}
1068 elif field == 'destination_port':
1069 matches['destination-port-range'] = {'lower-port':value, 'upper-port':value}
1070 acl_entry['matches'] = matches
1071 acl_entry_list.append(acl_entry)
1072 acl_list_dict['ace'] = acl_entry_list
1073 access_list_dict['acl-name'] = sfc_classifiers.name
1074 access_list_dict['access-list-entries'] = acl_list_dict
1075 access_list_json['acl'] = access_list_dict
1076
1077 acl_data = json.dumps(access_list_json)
1078 acl_url = self._get_rest_url(account,self._access_list_path.format(sfc_classifiers.name))
1079 print(acl_url)
1080 print(acl_data)
1081 r=requests.put(acl_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=acl_data)
1082 r.raise_for_status()