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