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('RwsdnalYang', '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
= RwsdnalYang
.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 status
.status
= "success"
358 status
.details
= "Connection was successful"
362 def get_network_list(self
, account
):
364 Get the networks details from ODL
366 url
= self
._network
_topology
_rest
_url
(account
)
367 r
=requests
.get(url
,auth
=(account
.odl
.username
,account
.odl
.password
))
371 url
= self
._node
_inventory
_rest
_url
(account
)
372 r
= requests
.get(url
,auth
=(account
.odl
.username
,account
.odl
.password
))
374 node_inventory
= r
.json()
375 return self
._fill
_network
_list
(nw_topo
,node_inventory
)
378 def _service_functions_path(self
):
379 return 'restconf/config/service-function:service-functions'
382 def _service_function_path(self
):
383 return 'restconf/config/service-function:service-functions/service-function/{}'
386 def _service_function_forwarders_path(self
):
387 return 'restconf/config/service-function-forwarder:service-function-forwarders'
390 def _service_function_forwarder_path(self
):
391 return 'restconf/config/service-function-forwarder:service-function-forwarders/service-function-forwarder/{}'
394 def _service_function_chains_path(self
):
395 return 'restconf/config/service-function-chain:service-function-chains'
398 def _service_function_chain_path(self
):
399 return 'restconf/config/service-function-chain:service-function-chains/service-function-chain/{}'
402 def _sfp_metadata_path(self
):
403 return 'restconf/config/service-function-path-metadata:service-function-metadata/context-metadata/{}'
406 def _sfps_metadata_path(self
):
407 return 'restconf/config/service-function-path-metadata:service-function-metadata'
410 def _sfps_path(self
):
411 return 'restconf/config/service-function-path:service-function-paths'
415 return 'restconf/config/service-function-path:service-function-paths/service-function-path/{}'
419 def _create_rsp_path(self
):
420 return 'restconf/operations/rendered-service-path:create-rendered-path'
423 def _delete_rsp_path(self
):
424 return 'restconf/operations/rendered-service-path:delete-rendered-path'
428 def _get_rsp_paths(self
):
429 return 'restconf/operational/rendered-service-path:rendered-service-paths'
432 def _get_rsp_path(self
):
433 return 'restconf/operational/rendered-service-path:rendered-service-paths/rendered-service-path/{}'
436 def _access_list_path(self
):
437 return 'restconf/config/ietf-access-control-list:access-lists/acl/{}'
440 def _service_function_classifier_path(self
):
441 return 'restconf/config/service-function-classifier:service-function-classifiers/service-function-classifier/{}'
444 def _access_lists_path(self
):
445 return 'restconf/config/ietf-access-control-list:access-lists'
448 def _service_function_classifiers_path(self
):
449 return 'restconf/config/service-function-classifier:service-function-classifiers'
452 def _create_sf(self
,account
,vnffg_chain
,sf_dp_list
):
456 for vnf
in vnffg_chain
.vnf_chain_path
:
457 for vnfr
in vnf
.vnfr_ids
:
458 sf_url
= self
._get
_rest
_url
(account
,self
._service
_function
_path
.format(vnfr
.vnfr_name
))
460 r
=requests
.get(sf_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'})
461 # If the SF is not found; create new SF
462 if r
.status_code
== 200:
463 logger
.info("SF with name %s is already present in ODL. Skipping update", vnfr
.vnfr_name
)
465 elif r
.status_code
!= 404:
469 sf_dict
['name'] = vnfr
.vnfr_name
470 sf_dict
['nsh-aware'] = vnf
.nsh_aware
471 sf_dict
['type'] = vnf
.service_function_type
472 sf_dict
['ip-mgmt-address'] = vnfr
.mgmt_address
473 sf_dict
['rest-uri'] = 'http://{}:{}'.format(vnfr
.mgmt_address
, vnfr
.mgmt_port
)
475 sf_dict
['sf-data-plane-locator'] = list()
476 for vdu
in vnfr
.vdu_list
:
478 if vdu
.port_id
in sf_dp_list
.keys():
479 sf_dp_entry
= sf_dp_list
[vdu
.port_id
]
480 sf_dp
['name'] = sf_dp_entry
.name
481 sf_dp
['ip'] = vdu
.address
482 sf_dp
['port'] = vdu
.port
483 sf_dp
['transport'] = "service-locator:{}".format(vnf
.transport_type
)
485 sf_dp
['service-function-forwarder'] = vnfr
.sff_name
487 sff_name
= sf_dp_entry
.sff_name
489 logger
.error("SFF not found for port %s in SF %s", vdu
.port_id
, vnfr
.vnfr_name
)
490 sf_dp
['service-function-forwarder'] = sff_name
491 sf_dp
['service-function-ovs:ovs-port'] = dict()
492 if sf_dp_entry
.ovsdb_tp_name
is not None:
493 sf_dp
['service-function-ovs:ovs-port']['port-id'] = sf_dp_entry
.ovsdb_tp_name
494 sf_dict
['sf-data-plane-locator'].append(sf_dp
)
496 logger
.error("Port %s not found in SF DP list",vdu
.port_id
)
498 sf_json
['service-function'] = sf_dict
499 sf_data
= json
.dumps(sf_json
)
500 sf_url
= self
._get
_rest
_url
(account
,self
._service
_function
_path
.format(vnfr
.vnfr_name
))
503 r
=requests
.put(sf_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'}, data
=sf_data
)
507 def _create_sff(self
,account
,vnffg_chain
,sff
):
511 #sff_dp_name = "SFF1" + '-' + 'DP1'
512 sff_dp_name
= sff
.dp_name
514 sff_url
= self
._get
_rest
_url
(account
,self
._service
_function
_forwarder
_path
.format(sff
.name
))
516 r
=requests
.get(sff_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'})
517 # If the SFF is not found; create new SF
518 if r
.status_code
== 200:
519 logger
.info("SFF with name %s is already present in ODL. Skipping full update", sff
.name
)
522 for sf_dp
in sff
.sf_dp_list
:
523 for sff_sf
in sff_dict
['service-function-forwarder'][0]['service-function-dictionary']:
524 if sf_dp
.vnfr_name
== sff_sf
['name']:
525 logger
.info("SF with name %s is already found in SFF %s SF Dictionay. Skipping update",sf_dp
.vnfr_name
,sff
.name
)
528 logger
.info("SF with name %s is not found in SFF %s SF Dictionay",sf_dp
.vnfr_name
, sff
.name
)
532 sff_sf_dict
['name'] = sf_dp
.vnfr_name
534 # Below two lines are enabled only for ODL Beryillium
535 sff_sf_dp_loc
['sff-dpl-name'] = sff_dp_name
536 sff_sf_dp_loc
['sf-dpl-name'] = sf_dp
.name
538 sff_sf_dict
['sff-sf-data-plane-locator'] = sff_sf_dp_loc
539 sff_dict
['service-function-forwarder'][0]['service-function-dictionary'].append(sff_sf_dict
)
540 if sff_updated
is True:
541 sff_data
= json
.dumps(sff_dict
)
543 r
=requests
.put(sff_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'}, data
=sff_data
)
546 elif r
.status_code
!= 404:
551 sff_dp_ip
= sff
.dp_ip
552 sff_port
= sff
.sff_port
554 sff_rest_port
= sff
.sff_rest_port
556 if sff_rest_based
is False:
557 sff_bridge_name
= sff
.br_name
558 sff_ovs_op
= {"key": "flow",
562 "dst-port": sff_port
,
569 sff_dict
['name'] = sff_name
570 sff_dict
['service-node'] = ''
571 sff_dict
['ip-mgmt-address'] = sff_ip
573 sff_dict
['rest-uri'] = 'http://{}:{}'.format(sff_ip
, sff_rest_port
)
575 sff_dict
['service-function-forwarder-ovs:ovs-bridge'] = {"bridge-name": sff_bridge_name
}
576 sff_dict
['service-function-dictionary'] = list()
577 for sf_dp
in sff
.sf_dp_list
:
580 sff_sf_dict
['name'] = sf_dp
.vnfr_name
582 # Below set of lines are reqd for Lithium
583 #sff_sf_dict['type'] = sf_dp.service_function_type
584 #sff_sf_dp_loc['ip'] = sf_dp.address
585 #sff_sf_dp_loc['port'] = sf_dp.port
586 #sff_sf_dp_loc['transport'] = sf_dp.transport_type
587 #sff_sf_dp_loc['service-function-forwarder-ovs:ovs-bridge'] = {}
589 # Below two lines are enabled only for ODL Beryillium
590 sff_sf_dp_loc
['sff-dpl-name'] = sff_dp_name
591 sff_sf_dp_loc
['sf-dpl-name'] = sf_dp
.name
593 sff_sf_dict
['sff-sf-data-plane-locator'] = sff_sf_dp_loc
594 sff_dict
['service-function-dictionary'].append(sff_sf_dict
)
596 sff_dict
['sff-data-plane-locator'] = list()
599 sff_dp
['name'] = sff_dp_name
600 dp_loc
['ip'] = sff_dp_ip
601 dp_loc
['port'] = sff_port
602 dp_loc
['transport'] = 'service-locator:vxlan-gpe'
603 sff_dp
['data-plane-locator'] = dp_loc
604 if sff_rest_based
is False:
605 sff_dp
['service-function-forwarder-ovs:ovs-options'] = sff_ovs_op
606 #sff_dp["service-function-forwarder-ovs:ovs-bridge"] = {'bridge-name':sff_bridge_name}
607 sff_dp
["service-function-forwarder-ovs:ovs-bridge"] = {}
608 sff_dict
['sff-data-plane-locator'].append(sff_dp
)
610 sff_json
['service-function-forwarder'] = sff_dict
611 sff_data
= json
.dumps(sff_json
)
613 r
=requests
.put(sff_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'}, data
=sff_data
)
616 def _create_sfc(self
,account
,vnffg_chain
):
620 sfc_dict
['name'] = vnffg_chain
.name
621 sfc_dict
['sfc-service-function'] = list()
622 vnf_chain_list
= sorted(vnffg_chain
.vnf_chain_path
, key
= lambda x
: x
.order
)
623 for vnf
in vnf_chain_list
:
625 sfc_sf_dict
['name'] = vnf
.service_function_type
626 sfc_sf_dict
['type'] = vnf
.service_function_type
627 sfc_sf_dict
['order'] = vnf
.order
628 sfc_dict
['sfc-service-function'].append(sfc_sf_dict
)
629 sfc_json
['service-function-chain'] = sfc_dict
630 sfc_data
= json
.dumps(sfc_json
)
631 sfc_url
= self
._get
_rest
_url
(account
,self
._service
_function
_chain
_path
.format(vnffg_chain
.name
))
634 r
=requests
.put(sfc_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'}, data
=sfc_data
)
637 def _create_sfp_metadata(self
,account
,sfc_classifier
):
638 " Create SFP metadata"
641 sfp_meta_dict
['name'] = sfc_classifier
.name
642 if sfc_classifier
.vnffg_metadata
.ctx1
:
643 sfp_meta_dict
['context-header1'] = sfc_classifier
.vnffg_metadata
.ctx1
644 if sfc_classifier
.vnffg_metadata
.ctx2
:
645 sfp_meta_dict
['context-header2'] = sfc_classifier
.vnffg_metadata
.ctx2
646 if sfc_classifier
.vnffg_metadata
.ctx3
:
647 sfp_meta_dict
['context-header3'] = sfc_classifier
.vnffg_metadata
.ctx3
648 if sfc_classifier
.vnffg_metadata
.ctx4
:
649 sfp_meta_dict
['context-header4'] = sfc_classifier
.vnffg_metadata
.ctx4
651 sfp_meta_json
['context-metadata'] = sfp_meta_dict
652 sfp_meta_data
= json
.dumps(sfp_meta_json
)
653 sfp_meta_url
= self
._get
_rest
_url
(account
,self
._sfp
_metadata
_path
.format(sfc_classifier
.name
))
656 r
=requests
.put(sfp_meta_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'}, data
=sfp_meta_data
)
659 def _create_sfp(self
,account
,vnffg_chain
, sym_chain
=False,classifier_name
=None,vnffg_metadata_name
=None):
663 sfp_dict
['name'] = vnffg_chain
.name
664 sfp_dict
['service-chain-name'] = vnffg_chain
.name
665 sfp_dict
['symmetric'] = sym_chain
666 sfp_dict
['transport-type'] = 'service-locator:vxlan-gpe'
667 if vnffg_metadata_name
:
668 sfp_dict
['context-metadata'] = vnffg_metadata_name
670 sfp_dict
['classifier'] = classifier_name
672 sfp_json
['service-function-path'] = sfp_dict
673 sfp_data
= json
.dumps(sfp_json
)
674 sfp_url
= self
._get
_rest
_url
(account
,self
._sfp
_path
.format(vnffg_chain
.name
))
677 r
=requests
.put(sfp_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'}, data
=sfp_data
)
680 def _create_rsp(self
,account
,vnffg_chain_name
, sym_chain
=True):
684 rsp_json
['input'] = {}
685 rsp_input
['name'] = vnffg_chain_name
686 rsp_input
['parent-service-function-path'] = vnffg_chain_name
687 rsp_input
['symmetric'] = sym_chain
689 rsp_json
['input'] = rsp_input
690 rsp_data
= json
.dumps(rsp_json
)
691 self
._rsp
_data
= rsp_json
692 rsp_url
= self
._get
_rest
_url
(account
,self
._create
_rsp
_path
)
695 r
=requests
.post(rsp_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'}, data
=rsp_data
)
698 output_json
= r
.json()
699 return output_json
['output']['name']
701 def _get_sff_list_for_chain(self
, account
,sf_dp_list
):
703 Get List of all SFF that needs to be created based on VNFs included in VNFFG chain.
707 if sf_dp_list
is None:
708 logger
.error("VM List for vnffg chain is empty while trying to get SFF list")
709 url
= self
._network
_topology
_rest
_url
(account
)
710 r
=requests
.get(url
,auth
=(account
.odl
.username
,account
.odl
.password
))
714 for topo
in nw_topo
['network-topology']['topology']:
715 if ('node' in topo
and len(topo
['node']) > 0):
716 for node
in topo
['node']:
717 if ('termination-point' in node
and len(node
['termination-point']) > 0):
718 for term_point
in node
['termination-point']:
719 if 'ovsdb:interface-external-ids' in term_point
:
720 vm_id
= [res
for res
in term_point
['ovsdb:interface-external-ids'] if res
['external-id-key'] == 'vm-id']
723 vmid
= vm_id
[0]['external-id-value']
724 intf_id
= [res
for res
in term_point
['ovsdb:interface-external-ids'] if res
['external-id-key'] == 'iface-id']
725 if len(intf_id
) == 0:
727 intfid
= intf_id
[0]['external-id-value']
728 if intfid
not in sf_dp_list
.keys():
730 if sf_dp_list
[intfid
].vm_id
!= vmid
:
731 logger
.error("Intf ID %s is not present in VM %s", intfid
, vmid
)
733 sf_dp_list
[intfid
].ovsdb_tp_name
= term_point
['ovsdb:name']
735 if 'ovsdb:managed-by' in node
:
736 rr
=re
.search('network-topology:node-id=\'([-\w\:\/]*)\'',node
['ovsdb:managed-by'])
737 node_id
= rr
.group(1)
738 ovsdb_node
= [node
for node
in topo
['node'] if node
['node-id'] == node_id
]
740 if 'ovsdb:connection-info' in ovsdb_node
[0]:
741 sff_ip
= ovsdb_node
[0]['ovsdb:connection-info']['local-ip']
742 sff_br_name
= node
['ovsdb:bridge-name']
743 sff_br_uuid
= node
['ovsdb:bridge-uuid']
746 if 'ovsdb:openvswitch-other-configs' in ovsdb_node
[0]:
747 for other_key
in ovsdb_node
[0]['ovsdb:openvswitch-other-configs']:
748 if other_key
['other-config-key'] == 'local_ip':
749 local_ip_str
= other_key
['other-config-value']
750 sff_dp_ip
= local_ip_str
.split(',')[0]
753 sff_name
= socket
.getfqdn(sff_ip
)
754 if sff_br_uuid
in sff_list
:
755 sff_list
[sff_name
].add_sf_dp_to_sff(sf_dp_list
[intfid
])
756 sf_dp_list
[intfid
]._update
_sff
_name
(sff_name
)
758 sff_dp_ip
= sff_ip
#overwrite sff_dp_ip to SFF ip for now
759 sff_list
[sff_name
] = Sff(sff_name
,sff_ip
,6000, sff_dp_ip
, 4790,sff_br_uuid
,sff_br_name
)
760 sf_dp_list
[intfid
]._update
_sff
_name
(sff_name
)
761 sff_list
[sff_name
].add_sf_dp_to_sff(sf_dp_list
[intfid
])
765 def _get_sf_dp_list_for_chain(self
,account
,vnffg_chain
):
767 Get list of all Service Function Data Plane Locators present in VNFFG
768 useful for easy reference while creating SF and SFF
771 for vnf
in vnffg_chain
.vnf_chain_path
:
772 for vnfr
in vnf
.vnfr_ids
:
773 for vdu
in vnfr
.vdu_list
:
774 sfdp
= SfDpLocator(vdu
.name
,vdu
.port_id
,vnfr
.vnfr_name
, vdu
.vm_id
)
775 sfdp
._update
_vnf
_params
(vnf
.service_function_type
, vdu
.address
, vdu
.port
, vnf
.transport_type
)
777 sfdp
._update
_sff
_name
(vnfr
.sff_name
)
778 sfdp_list
[vdu
.port_id
] = sfdp
781 def create_sfc(self
, account
, vnffg_chain
):
787 sf_dp_list
= self
._get
_sf
_dp
_list
_for
_chain
(account
,vnffg_chain
)
789 if sff_rest_based
is False and len(vnffg_chain
.sff
) == 0:
790 # Get the list of all SFFs required for vnffg chain
791 sff_list
= self
._get
_sff
_list
_for
_chain
(account
,sf_dp_list
)
793 for sff
in vnffg_chain
.sff
:
794 sff_list
[sff
.name
] = Sff(sff
.name
, sff
.mgmt_address
,sff
.mgmt_port
,sff
.dp_endpoints
[0].address
, sff
.dp_endpoints
[0].port
, sff
.name
)
795 for _
,sf_dp
in sf_dp_list
.items():
796 if sf_dp
.sff_name
and sf_dp
.sff_name
== sff
.name
:
797 sff_list
[sff
.name
].add_sf_dp_to_sff(sf_dp
)
799 #Create all the SF in VNFFG chain
800 self
._create
_sf
(account
,vnffg_chain
,sf_dp_list
)
802 for _
,sff
in sff_list
.items():
803 self
._create
_sff
(account
,vnffg_chain
,sff
)
806 self
._create
_sfc
(account
,vnffg_chain
)
808 self
._create
_sfp
(account
,vnffg_chain
,classifier_name
=vnffg_chain
.classifier_name
,
809 vnffg_metadata_name
=vnffg_chain
.classifier_name
)
811 ## Update to SFF could have deleted some RSP; so get list of SFP and
812 ## check RSP exists for same and create any as necessary
813 #rsp_name = self._create_rsp(account,vnffg_chain)
815 self
._create
_all
_rsps
(account
)
816 self
._recreate
_all
_sf
_classifiers
(account
)
817 return vnffg_chain
.name
819 def _recreate_all_sf_classifiers(self
,account
):
821 Re create all SF classifiers
823 sfcl_url
= self
._get
_rest
_url
(account
,self
._service
_function
_classifiers
_path
)
826 r
=requests
.get(sfcl_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'})
827 if r
.status_code
== 200:
830 elif r
.status_code
== 404:
835 #Delete the classifiers and re-add same back
836 r
=requests
.delete(sfcl_url
,auth
=(account
.odl
.username
,account
.odl
.password
))
841 sfcl_data
= json
.dumps(sfcl_json
)
842 r
=requests
.put(sfcl_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'}, data
=sfcl_data
)
845 def _create_all_rsps(self
,account
):
847 Create all the RSPs for SFP found
849 sfps_url
= self
._get
_rest
_url
(account
,self
._sfps
_path
)
850 r
=requests
.get(sfps_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'})
853 if 'service-function-path' in sfps_json
['service-function-paths']:
854 for sfp
in sfps_json
['service-function-paths']['service-function-path']:
855 rsp_url
= self
._get
_rest
_url
(account
,self
._get
_rsp
_path
.format(sfp
['name']))
856 r
= requests
.get(rsp_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'})
857 if r
.status_code
== 404:
859 logger
.info("Creating RSP for Service Path with name %s",sfp
['name'])
860 self
._create
_rsp
(account
,sfp
['name'])
862 def delete_all_sf(self
, account
):
864 sf_url
= self
._get
_rest
_url
(account
,self
._service
_functions
_path
)
866 r
=requests
.delete(sf_url
,auth
=(account
.odl
.username
,account
.odl
.password
))
870 def delete_all_sff(self
, account
):
871 "Delete all the SFFs"
872 sff_url
= self
._get
_rest
_url
(account
,self
._service
_function
_forwarders
_path
)
874 r
=requests
.delete(sff_url
,auth
=(account
.odl
.username
,account
.odl
.password
))
877 def delete_all_sfc(self
, account
):
878 "Delete all the SFCs"
879 sfc_url
= self
._get
_rest
_url
(account
,self
._service
_function
_chains
_path
)
881 r
=requests
.delete(sfc_url
,auth
=(account
.odl
.username
,account
.odl
.password
))
884 def delete_all_sfp_metadata(self
, account
):
885 "Delete all the SFPs metadata"
886 sfp_metadata_url
= self
._get
_rest
_url
(account
,self
._sfps
_metadata
_path
)
887 print(sfp_metadata_url
)
888 r
=requests
.delete(sfp_metadata_url
,auth
=(account
.odl
.username
,account
.odl
.password
))
891 def delete_all_sfp(self
, account
):
892 "Delete all the SFPs"
893 sfp_url
= self
._get
_rest
_url
(account
,self
._sfps
_path
)
895 r
=requests
.delete(sfp_url
,auth
=(account
.odl
.username
,account
.odl
.password
))
898 def delete_all_rsp(self
, account
):
900 #rsp_list = self.get_rsp_list(account)
901 url
= self
._get
_rest
_url
(account
,self
._get
_rsp
_paths
)
903 r
= requests
.get(url
,auth
=(account
.odl
.username
,account
.odl
.password
))
908 #for vnffg in rsp_list.vnffg_rendered_path:
909 for sfc_rsp
in rsp_list
['rendered-service-paths']['rendered-service-path']:
912 rsp_json
['input'] = {}
913 rsp_input
['name'] = sfc_rsp
['name']
915 rsp_json
['input'] = rsp_input
916 rsp_data
= json
.dumps(rsp_json
)
917 self
._rsp
_data
= rsp_json
918 rsp_url
= self
._get
_rest
_url
(account
,self
._delete
_rsp
_path
)
922 r
=requests
.post(rsp_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'}, data
=rsp_data
)
925 #output_json = r.json()
926 #return output_json['output']['name']
928 def terminate_all_sfc(self
, account
):
929 "Terminate SFC chain"
930 self
.delete_all_rsp(account
)
931 self
.delete_all_sfp(account
)
932 self
.delete_all_sfc(account
)
933 self
.delete_all_sff(account
)
934 self
.delete_all_sf(account
)
936 def _fill_rsp_list(self
,sfc_rsp_list
,sff_list
):
937 vnffg_rsps
= RwsdnalYang
.VNFFGRenderedPaths()
938 for sfc_rsp
in sfc_rsp_list
['rendered-service-paths']['rendered-service-path']:
939 rsp
= vnffg_rsps
.vnffg_rendered_path
.add()
940 rsp
.name
= sfc_rsp
['name']
941 rsp
.path_id
= sfc_rsp
['path-id']
942 for sfc_rsp_hop
in sfc_rsp
['rendered-service-path-hop']:
943 rsp_hop
= rsp
.rendered_path_hop
.add()
944 rsp_hop
.hop_number
= sfc_rsp_hop
['hop-number']
945 rsp_hop
.service_index
= sfc_rsp_hop
['service-index']
946 rsp_hop
.vnfr_name
= sfc_rsp_hop
['service-function-name']
947 rsp_hop
.service_function_forwarder
.name
= sfc_rsp_hop
['service-function-forwarder']
948 for sff
in sff_list
['service-function-forwarders']['service-function-forwarder']:
949 if sff
['name'] == rsp_hop
.service_function_forwarder
.name
:
950 rsp_hop
.service_function_forwarder
.ip_address
= sff
['sff-data-plane-locator'][0]['data-plane-locator']['ip']
951 rsp_hop
.service_function_forwarder
.port
= sff
['sff-data-plane-locator'][0]['data-plane-locator']['port']
956 def get_rsp_list(self
,account
):
959 sff_url
= self
._get
_rest
_url
(account
,self
._service
_function
_forwarders
_path
)
961 r
=requests
.get(sff_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'})
965 url
= self
._get
_rest
_url
(account
,self
._get
_rsp
_paths
)
967 r
= requests
.get(url
,auth
=(account
.odl
.username
,account
.odl
.password
))
970 return self
._fill
_rsp
_list
(r
.json(),sff_list
)
972 def create_sfc_classifier(self
, account
, sfc_classifiers
):
973 "Create SFC Classifiers"
974 self
._create
_sfp
_metadata
(account
,sfc_classifiers
)
975 self
._add
_acl
_rules
(account
, sfc_classifiers
)
976 self
._create
_sf
_classifier
(account
, sfc_classifiers
)
977 return sfc_classifiers
.name
979 def terminate_sfc_classifier(self
, account
, sfc_classifier_name
):
980 "Create SFC Classifiers"
981 self
.delete_all_sfp_metadata(account
)
982 self
._terminate
_sf
_classifier
(account
, sfc_classifier_name
)
983 self
._del
_acl
_rules
(account
, sfc_classifier_name
)
985 def _del_acl_rules(self
,account
,sfc_classifier_name
):
986 " Terminate SF classifiers"
987 acl_url
= self
._get
_rest
_url
(account
,self
._access
_lists
_path
)
989 r
=requests
.delete(acl_url
,auth
=(account
.odl
.username
,account
.odl
.password
))
992 def _terminate_sf_classifier(self
,account
,sfc_classifier_name
):
993 " Terminate SF classifiers"
994 sfcl_url
= self
._get
_rest
_url
(account
,self
._service
_function
_classifiers
_path
)
996 r
=requests
.delete(sfcl_url
,auth
=(account
.odl
.username
,account
.odl
.password
))
999 def _create_sf_classifier(self
,account
,sfc_classifiers
):
1000 " Create SF classifiers"
1001 sf_classifier_json
= {}
1002 sf_classifier_dict
= {}
1003 sf_classifier_dict
['name'] = sfc_classifiers
.name
1004 sf_classifier_dict
['access-list'] = sfc_classifiers
.name
1005 sf_classifier_dict
['scl-service-function-forwarder'] = list()
1009 if sfc_classifiers
.has_field('sff_name') and sfc_classifiers
.sff_name
is not None:
1010 scl_sff_name
= sfc_classifiers
.sff_name
1011 elif sfc_classifiers
.has_field('port_id') and sfc_classifiers
.has_field('vm_id'):
1012 sf_dp
= SfDpLocator(sfc_classifiers
.port_id
, sfc_classifiers
.port_id
,'', sfc_classifiers
.vm_id
)
1014 sf_dp_list
[sfc_classifiers
.port_id
] = sf_dp
1015 self
._get
_sff
_list
_for
_chain
(account
,sf_dp_list
)
1017 if sf_dp
.sff_name
is None:
1018 logger
.error("SFF not found for port %s, VM: %s",sfc_classifiers
.port_id
,sfc_classifiers
.vm_id
)
1020 logger
.info("SFF with name %s found for port %s, VM: %s",sf_dp
.sff_name
, sfc_classifiers
.port_id
,sfc_classifiers
.vm_id
)
1021 scl_sff_name
= sf_dp
.sff_name
1023 rsp_url
= self
._get
_rest
_url
(account
,self
._get
_rsp
_path
.format(sfc_classifiers
.rsp_name
))
1024 r
= requests
.get(rsp_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'})
1025 if r
.status_code
== 200:
1027 if 'rendered-service-path' in rsp_data
and len(rsp_data
['rendered-service-path'][0]['rendered-service-path-hop']) > 0:
1028 scl_sff_name
= rsp_data
['rendered-service-path'][0]['rendered-service-path-hop'][0]['service-function-forwarder']
1030 logger
.debug("SFF for classifer %s found is %s",sfc_classifiers
.name
, scl_sff_name
)
1031 scl_sff
['name'] = scl_sff_name
1032 #scl_sff['interface'] = sff_intf_name
1033 sf_classifier_dict
['scl-service-function-forwarder'].append(scl_sff
)
1035 sf_classifier_json
['service-function-classifier'] = sf_classifier_dict
1037 sfcl_data
= json
.dumps(sf_classifier_json
)
1038 sfcl_url
= self
._get
_rest
_url
(account
,self
._service
_function
_classifier
_path
.format(sfc_classifiers
.name
))
1041 r
=requests
.put(sfcl_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'}, data
=sfcl_data
)
1042 r
.raise_for_status()
1044 def _add_acl_rules(self
, account
,sfc_classifiers
):
1046 access_list_json
= {}
1047 access_list_dict
= {}
1048 acl_entry_list
= list()
1050 for acl_rule
in sfc_classifiers
.match_attributes
:
1052 acl_entry
['rule-name'] = acl_rule
.name
1053 acl_entry
['actions'] = {}
1054 #acl_entry['actions']['netvirt-sfc-acl:rsp-name'] = sfc_classifiers.rsp_name
1055 acl_entry
['actions']['service-function-acl:rendered-service-path'] = sfc_classifiers
.rsp_name
1058 for field
, value
in acl_rule
.as_dict().items():
1059 if field
== 'ip_proto':
1060 matches
['protocol'] = value
1061 elif field
== 'source_ip_address':
1062 matches
['source-ipv4-network'] = value
1063 elif field
== 'destination_ip_address':
1064 matches
['destination-ipv4-network'] = value
1065 elif field
== 'source_port':
1066 matches
['source-port-range'] = {'lower-port':value
, 'upper-port':value
}
1067 elif field
== 'destination_port':
1068 matches
['destination-port-range'] = {'lower-port':value
, 'upper-port':value
}
1069 acl_entry
['matches'] = matches
1070 acl_entry_list
.append(acl_entry
)
1071 acl_list_dict
['ace'] = acl_entry_list
1072 access_list_dict
['acl-name'] = sfc_classifiers
.name
1073 access_list_dict
['access-list-entries'] = acl_list_dict
1074 access_list_json
['acl'] = access_list_dict
1076 acl_data
= json
.dumps(access_list_json
)
1077 acl_url
= self
._get
_rest
_url
(account
,self
._access
_list
_path
.format(sfc_classifiers
.name
))
1080 r
=requests
.put(acl_url
,auth
=(account
.odl
.username
,account
.odl
.password
),headers
={'content-type': 'application/json'}, data
=acl_data
)
1081 r
.raise_for_status()