3 # Copyright 2016 RIFT.IO Inc
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
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')
33 from gi
.repository
import (
38 RwTopologyYang
as RwTl
,
45 logger
= logging
.getLogger('rwsdn.sdnodl')
46 logger
.setLevel(logging
.DEBUG
)
51 class UnknownAccountError(Exception):
55 class MissingFileError(Exception):
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
,
67 class SdnOdlPlugin(GObject
.Object
, RwSdn
.Topology
):
70 GObject
.Object
.__init
__(self
)
71 self
.sdnodl
= SdnOdl()
75 def do_init(self
, rwlog_ctx
):
76 if not any(isinstance(h
, rwlogger
.RwLogger
) for h
in logger
.handlers
):
79 category
="rw-cal-log",
85 @rwstatus(ret_on_failure
=[None])
86 def do_validate_sdn_creds(self
, account
):
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
92 @param account - a SDN account
95 Validation Code and Details String
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))
102 @rwstatus(ret_on_failure
=[None])
103 def do_get_network_list(self
, account
):
105 Returns the list of discovered networks
107 @param account - a SDN account
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
))
115 @rwstatus(ret_on_failure
=[""])
116 def do_create_vnffg_chain(self
, account
,vnffg_chain
):
118 Creates Service Function chain in ODL
120 @param account - a SDN account
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
)
129 def do_terminate_vnffg_chain(self
, account
,vnffg_id
):
131 Terminate Service Function chain in ODL
133 @param account - a SDN account
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
)
142 @rwstatus(ret_on_failure
=[None])
143 def do_get_vnffg_rendered_paths(self
, account
):
145 Get ODL Rendered Service Path List (SFC)
147 @param account - a SDN account
149 vnffg_list
= self
.sdnodl
.get_rsp_list(account
)
152 @rwstatus(ret_on_failure
=[None])
153 def do_create_vnffg_classifier(self
, account
, vnffg_classifier
):
157 @param account - a SDN account
159 classifier_name
= self
.sdnodl
.create_sfc_classifier(account
,vnffg_classifier
)
160 return classifier_name
162 @rwstatus(ret_on_failure
=[None])
163 def do_terminate_vnffg_classifier(self
, account
, vnffg_classifier_name
):
167 @param account - a SDN account
169 self
.sdnodl
.terminate_sfc_classifier(account
,vnffg_classifier_name
)
174 Create SFF object to hold SFF related details
177 def __init__(self
,sff_name
, mgmt_address
, mgmt_port
, dp_address
, dp_port
,sff_dp_name
, sff_br_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()
187 def add_sf_dp_to_sff(self
,sf_dp
):
188 self
.sf_dp_list
.append(sf_dp
)
191 return 'Name:{},Bridge Name:{}, IP: {}, SF List: {}'.format(self
.dp_name
,self
.br_name
, self
.ip
, self
.sf_dp_list
)
193 class SfDpLocator(object):
195 Create Service Function Data Plane Locator related Object to hold details related to each DP Locator endpoint
197 def __init__(self
,name
,sfdp_id
,vnfr_name
,vm_id
):
199 self
.port_id
= sfdp_id
200 self
.vnfr_name
= vnfr_name
203 self
.ovsdb_tp_name
= None
205 def _update_sff_name(self
,sff_name
):
206 self
.sff_name
= sff_name
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
212 self
.transport_type
= "service-locator:{}".format(transport_type
)
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
)
217 class SdnOdl(object):
219 SDN ODL Class to support REST based API calls
223 def _network_topology_path(self
):
224 return 'restconf/operational/network-topology:network-topology'
227 def _node_inventory_path(self
):
228 return 'restconf/operational/opendaylight-inventory:nodes'
230 def _network_topology_rest_url(self
,account
):
231 return '{}/{}'.format(account
.odl
.url
,self
._network
_topology
_path
)
233 def _node_inventory_rest_url(self
,account
):
234 return '{}/{}'.format(account
.odl
.url
,self
._node
_inventory
_path
)
236 def _get_rest_url(self
,account
, rest_path
):
237 return '{}/{}'.format(account
.odl
.url
,rest_path
)
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'])
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")
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
:
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']
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']
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'
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'
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"]
293 def _fill_network_list(self
,nw_topo
,node_inventory
):
295 Fill Topology related information
297 nwtop
= RwTl
.YangData_IetfNetwork()
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
)
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'])
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']
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')
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')
335 def validate_account_creds(self
, account
):
337 Validate the SDN account credentials by accessing the rest API using the provided credentials
339 status
= RwsdnYang
.SdnConnectionStatus()
340 url
= '{}/{}'.format(account
.odl
.url
,"restconf")
342 r
=requests
.get(url
,auth
=(account
.odl
.username
,account
.odl
.password
))
344 except requests
.exceptions
.HTTPError
as e
:
345 msg
= "SdnOdlPlugin: SDN account credential validation failed. Exception: %s", str(e
)
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
)
354 status
.status
= "failure"
355 status
.details
= "Connection Failed (Invlaid URL): %s" % str(e
)
357 print("SDN Successfully connected")
358 status
.status
= "success"
359 status
.details
= "Connection was successful"
363 def get_network_list(self
, account
):
365 Get the networks details from ODL
367 url
= self
._network
_topology
_rest
_url
(account
)
368 r
=requests
.get(url
,auth
=(account
.odl
.username
,account
.odl
.password
))
372 url
= self
._node
_inventory
_rest
_url
(account
)
373 r
= requests
.get(url
,auth
=(account
.odl
.username
,account
.odl
.password
))
375 node_inventory
= r
.json()
376 return self
._fill
_network
_list
(nw_topo
,node_inventory
)
379 def _service_functions_path(self
):
380 return 'restconf/config/service-function:service-functions'
383 def _service_function_path(self
):
384 return 'restconf/config/service-function:service-functions/service-function/{}'
387 def _service_function_forwarders_path(self
):
388 return 'restconf/config/service-function-forwarder:service-function-forwarders'
391 def _service_function_forwarder_path(self
):
392 return 'restconf/config/service-function-forwarder:service-function-forwarders/service-function-forwarder/{}'
395 def _service_function_chains_path(self
):
396 return 'restconf/config/service-function-chain:service-function-chains'
399 def _service_function_chain_path(self
):
400 return 'restconf/config/service-function-chain:service-function-chains/service-function-chain/{}'
403 def _sfp_metadata_path(self
):
404 return 'restconf/config/service-function-path-metadata:service-function-metadata/context-metadata/{}'
407 def _sfps_metadata_path(self
):
408 return 'restconf/config/service-function-path-metadata:service-function-metadata'
411 def _sfps_path(self
):
412 return 'restconf/config/service-function-path:service-function-paths'
416 return 'restconf/config/service-function-path:service-function-paths/service-function-path/{}'
420 def _create_rsp_path(self
):
421 return 'restconf/operations/rendered-service-path:create-rendered-path'
424 def _delete_rsp_path(self
):
425 return 'restconf/operations/rendered-service-path:delete-rendered-path'
429 def _get_rsp_paths(self
):
430 return 'restconf/operational/rendered-service-path:rendered-service-paths'
433 def _get_rsp_path(self
):
434 return 'restconf/operational/rendered-service-path:rendered-service-paths/rendered-service-path/{}'
437 def _access_list_path(self
):
438 return 'restconf/config/ietf-access-control-list:access-lists/acl/{}'
441 def _service_function_classifier_path(self
):
442 return 'restconf/config/service-function-classifier:service-function-classifiers/service-function-classifier/{}'
445 def _access_lists_path(self
):
446 return 'restconf/config/ietf-access-control-list:access-lists'
449 def _service_function_classifiers_path(self
):
450 return 'restconf/config/service-function-classifier:service-function-classifiers'
453 def _create_sf(self
,account
,vnffg_chain
,sf_dp_list
):
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
))
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
)
466 elif r
.status_code
!= 404:
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
)
476 sf_dict
['sf-data-plane-locator'] = list()
477 for vdu
in vnfr
.vdu_list
:
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
)
486 sf_dp
['service-function-forwarder'] = vnfr
.sff_name
488 sff_name
= sf_dp_entry
.sff_name
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
)
497 logger
.error("Port %s not found in SF DP list",vdu
.port_id
)
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
))
504 r
=requests
.put(sf_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'}, data
=sf_data
)
508 def _create_sff(self
,account
,vnffg_chain
,sff
):
512 #sff_dp_name = "SFF1" + '-' + 'DP1'
513 sff_dp_name
= sff
.dp_name
515 sff_url
= self
._get
_rest
_url
(account
,self
._service
_function
_forwarder
_path
.format(sff
.name
))
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
)
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
)
529 logger
.info("SF with name %s is not found in SFF %s SF Dictionay",sf_dp
.vnfr_name
, sff
.name
)
533 sff_sf_dict
['name'] = sf_dp
.vnfr_name
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
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
)
544 r
=requests
.put(sff_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'}, data
=sff_data
)
547 elif r
.status_code
!= 404:
552 sff_dp_ip
= sff
.dp_ip
553 sff_port
= sff
.sff_port
555 sff_rest_port
= sff
.sff_rest_port
557 if sff_rest_based
is False:
558 sff_bridge_name
= sff
.br_name
559 sff_ovs_op
= {"key": "flow",
563 "dst-port": sff_port
,
570 sff_dict
['name'] = sff_name
571 sff_dict
['service-node'] = ''
572 sff_dict
['ip-mgmt-address'] = sff_ip
574 sff_dict
['rest-uri'] = 'http://{}:{}'.format(sff_ip
, sff_rest_port
)
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
:
581 sff_sf_dict
['name'] = sf_dp
.vnfr_name
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'] = {}
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
594 sff_sf_dict
['sff-sf-data-plane-locator'] = sff_sf_dp_loc
595 sff_dict
['service-function-dictionary'].append(sff_sf_dict
)
597 sff_dict
['sff-data-plane-locator'] = list()
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
)
611 sff_json
['service-function-forwarder'] = sff_dict
612 sff_data
= json
.dumps(sff_json
)
614 r
=requests
.put(sff_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'}, data
=sff_data
)
617 def _create_sfc(self
,account
,vnffg_chain
):
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
:
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
))
635 r
=requests
.put(sfc_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'}, data
=sfc_data
)
638 def _create_sfp_metadata(self
,account
,sfc_classifier
):
639 " Create SFP metadata"
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
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
))
657 r
=requests
.put(sfp_meta_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'}, data
=sfp_meta_data
)
660 def _create_sfp(self
,account
,vnffg_chain
, sym_chain
=False,classifier_name
=None,vnffg_metadata_name
=None):
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
671 sfp_dict
['classifier'] = classifier_name
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
))
678 r
=requests
.put(sfp_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'}, data
=sfp_data
)
681 def _create_rsp(self
,account
,vnffg_chain_name
, sym_chain
=True):
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
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
)
696 r
=requests
.post(rsp_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'}, data
=rsp_data
)
699 output_json
= r
.json()
700 return output_json
['output']['name']
702 def _get_sff_list_for_chain(self
, account
,sf_dp_list
):
704 Get List of all SFF that needs to be created based on VNFs included in VNFFG chain.
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
))
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']
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:
728 intfid
= intf_id
[0]['external-id-value']
729 if intfid
not in sf_dp_list
.keys():
731 if sf_dp_list
[intfid
].vm_id
!= vmid
:
732 logger
.error("Intf ID %s is not present in VM %s", intfid
, vmid
)
734 sf_dp_list
[intfid
].ovsdb_tp_name
= term_point
['ovsdb:name']
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
]
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']
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]
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
)
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
])
766 def _get_sf_dp_list_for_chain(self
,account
,vnffg_chain
):
768 Get list of all Service Function Data Plane Locators present in VNFFG
769 useful for easy reference while creating SF and SFF
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
)
778 sfdp
._update
_sff
_name
(vnfr
.sff_name
)
779 sfdp_list
[vdu
.port_id
] = sfdp
782 def create_sfc(self
, account
, vnffg_chain
):
788 sf_dp_list
= self
._get
_sf
_dp
_list
_for
_chain
(account
,vnffg_chain
)
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
)
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
)
800 #Create all the SF in VNFFG chain
801 self
._create
_sf
(account
,vnffg_chain
,sf_dp_list
)
803 for _
,sff
in sff_list
.items():
804 self
._create
_sff
(account
,vnffg_chain
,sff
)
807 self
._create
_sfc
(account
,vnffg_chain
)
809 self
._create
_sfp
(account
,vnffg_chain
,classifier_name
=vnffg_chain
.classifier_name
,
810 vnffg_metadata_name
=vnffg_chain
.classifier_name
)
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)
816 self
._create
_all
_rsps
(account
)
817 self
._recreate
_all
_sf
_classifiers
(account
)
818 return vnffg_chain
.name
820 def _recreate_all_sf_classifiers(self
,account
):
822 Re create all SF classifiers
824 sfcl_url
= self
._get
_rest
_url
(account
,self
._service
_function
_classifiers
_path
)
827 r
=requests
.get(sfcl_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'})
828 if r
.status_code
== 200:
831 elif r
.status_code
== 404:
836 #Delete the classifiers and re-add same back
837 r
=requests
.delete(sfcl_url
,auth
=(account
.odl
.username
,account
.odl
.password
))
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
)
846 def _create_all_rsps(self
,account
):
848 Create all the RSPs for SFP found
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'})
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:
860 logger
.info("Creating RSP for Service Path with name %s",sfp
['name'])
861 self
._create
_rsp
(account
,sfp
['name'])
863 def delete_all_sf(self
, account
):
865 sf_url
= self
._get
_rest
_url
(account
,self
._service
_functions
_path
)
867 r
=requests
.delete(sf_url
,auth
=(account
.odl
.username
,account
.odl
.password
))
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
)
875 r
=requests
.delete(sff_url
,auth
=(account
.odl
.username
,account
.odl
.password
))
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
)
882 r
=requests
.delete(sfc_url
,auth
=(account
.odl
.username
,account
.odl
.password
))
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
))
892 def delete_all_sfp(self
, account
):
893 "Delete all the SFPs"
894 sfp_url
= self
._get
_rest
_url
(account
,self
._sfps
_path
)
896 r
=requests
.delete(sfp_url
,auth
=(account
.odl
.username
,account
.odl
.password
))
899 def delete_all_rsp(self
, account
):
901 #rsp_list = self.get_rsp_list(account)
902 url
= self
._get
_rest
_url
(account
,self
._get
_rsp
_paths
)
904 r
= requests
.get(url
,auth
=(account
.odl
.username
,account
.odl
.password
))
909 #for vnffg in rsp_list.vnffg_rendered_path:
910 for sfc_rsp
in rsp_list
['rendered-service-paths']['rendered-service-path']:
913 rsp_json
['input'] = {}
914 rsp_input
['name'] = sfc_rsp
['name']
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
)
923 r
=requests
.post(rsp_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'}, data
=rsp_data
)
926 #output_json = r.json()
927 #return output_json['output']['name']
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
)
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']
957 def get_rsp_list(self
,account
):
960 sff_url
= self
._get
_rest
_url
(account
,self
._service
_function
_forwarders
_path
)
962 r
=requests
.get(sff_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'})
966 url
= self
._get
_rest
_url
(account
,self
._get
_rsp
_paths
)
968 r
= requests
.get(url
,auth
=(account
.odl
.username
,account
.odl
.password
))
971 return self
._fill
_rsp
_list
(r
.json(),sff_list
)
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
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
)
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
)
990 r
=requests
.delete(acl_url
,auth
=(account
.odl
.username
,account
.odl
.password
))
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
)
997 r
=requests
.delete(sfcl_url
,auth
=(account
.odl
.username
,account
.odl
.password
))
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()
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
)
1015 sf_dp_list
[sfc_classifiers
.port_id
] = sf_dp
1016 self
._get
_sff
_list
_for
_chain
(account
,sf_dp_list
)
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
)
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
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:
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']
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
)
1036 sf_classifier_json
['service-function-classifier'] = sf_classifier_dict
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
))
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()
1045 def _add_acl_rules(self
, account
,sfc_classifiers
):
1047 access_list_json
= {}
1048 access_list_dict
= {}
1049 acl_entry_list
= list()
1051 for acl_rule
in sfc_classifiers
.match_attributes
:
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
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
1077 acl_data
= json
.dumps(access_list_json
)
1078 acl_url
= self
._get
_rest
_url
(account
,self
._access
_list
_path
.format(sfc_classifiers
.name
))
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()