X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=rwlaunchpad%2Fplugins%2Frwvns%2Fvala%2Frwsdn_odl%2Frwsdn_odl.py;fp=rwlaunchpad%2Fplugins%2Frwvns%2Fvala%2Frwsdn_odl%2Frwsdn_odl.py;h=3eb39fcd611cff87861f470d9e1f41ea5ab3bd52;hb=6f07e6f33f751ab4ffe624f6037f887b243bece2;hp=0000000000000000000000000000000000000000;hpb=72a563886272088feb7cb52e4aafbe6d2c580ff9;p=osm%2FSO.git diff --git a/rwlaunchpad/plugins/rwvns/vala/rwsdn_odl/rwsdn_odl.py b/rwlaunchpad/plugins/rwvns/vala/rwsdn_odl/rwsdn_odl.py new file mode 100644 index 00000000..3eb39fcd --- /dev/null +++ b/rwlaunchpad/plugins/rwvns/vala/rwsdn_odl/rwsdn_odl.py @@ -0,0 +1,1082 @@ + +# +# Copyright 2016 RIFT.IO Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import logging + +import requests + +import json +import re +import socket +import time + +import gi +gi.require_version('RwTypes', '1.0') +gi.require_version('RwsdnYang', '1.0') +gi.require_version('RwSdn', '1.0') +gi.require_version('RwTopologyYang','1.0') + +from gi.repository import ( + GObject, + RwSdn, # Vala package + RwTypes, + RwsdnYang, + RwTopologyYang as RwTl, + ) + +import rw_status +import rwlogger + + +logger = logging.getLogger('rwsdn.sdnodl') +logger.setLevel(logging.DEBUG) + + +sff_rest_based = True + +class UnknownAccountError(Exception): + pass + + +class MissingFileError(Exception): + pass + + +rwstatus = rw_status.rwstatus_from_exc_map({ + IndexError: RwTypes.RwStatus.NOTFOUND, + KeyError: RwTypes.RwStatus.NOTFOUND, + UnknownAccountError: RwTypes.RwStatus.NOTFOUND, + MissingFileError: RwTypes.RwStatus.NOTFOUND, + }) + + +class SdnOdlPlugin(GObject.Object, RwSdn.Topology): + + def __init__(self): + GObject.Object.__init__(self) + self.sdnodl = SdnOdl() + + + @rwstatus + def do_init(self, rwlog_ctx): + if not any(isinstance(h, rwlogger.RwLogger) for h in logger.handlers): + logger.addHandler( + rwlogger.RwLogger( + category="rw-cal-log", + subcategory="odl", + log_hdl=rwlog_ctx, + ) + ) + + @rwstatus(ret_on_failure=[None]) + def do_validate_sdn_creds(self, account): + """ + Validates the sdn account credentials for the specified account. + Performs an access to the resources using Keystone API. If creds + are not valid, returns an error code & reason string + + @param account - a SDN account + + Returns: + Validation Code and Details String + """ + #logger.debug('Received validate SDN creds') + status = self.sdnodl.validate_account_creds(account) + #logger.debug('Done with validate SDN creds: %s', type(status)) + return status + + @rwstatus(ret_on_failure=[None]) + def do_get_network_list(self, account): + """ + Returns the list of discovered networks + + @param account - a SDN account + + """ + logger.debug('Received Get network list: ') + nwtop = self.sdnodl.get_network_list( account) + logger.debug('Done with get network list: %s', type(nwtop)) + return nwtop + + @rwstatus(ret_on_failure=[""]) + def do_create_vnffg_chain(self, account,vnffg_chain): + """ + Creates Service Function chain in ODL + + @param account - a SDN account + + """ + logger.debug('Received Create VNFFG chain ') + vnffg_id = self.sdnodl.create_sfc( account,vnffg_chain) + logger.debug('Done with create VNFFG chain with name : %s', vnffg_id) + return vnffg_id + + @rwstatus + def do_terminate_vnffg_chain(self, account,vnffg_id): + """ + Terminate Service Function chain in ODL + + @param account - a SDN account + + """ + logger.debug('Received terminate VNFFG chain for id %s ', vnffg_id) + # TODO: Currently all the RSP, SFPs , SFFs and SFs are deleted + # Need to handle deletion of specific RSP, SFFs, SFs etc + self.sdnodl.terminate_all_sfc(account) + logger.debug('Done with terminate VNFFG chain with name : %s', vnffg_id) + + @rwstatus(ret_on_failure=[None]) + def do_get_vnffg_rendered_paths(self, account): + """ + Get ODL Rendered Service Path List (SFC) + + @param account - a SDN account + """ + vnffg_list = self.sdnodl.get_rsp_list(account) + return vnffg_list + + @rwstatus(ret_on_failure=[None]) + def do_create_vnffg_classifier(self, account, vnffg_classifier): + """ + Add VNFFG Classifier + + @param account - a SDN account + """ + classifier_name = self.sdnodl.create_sfc_classifier(account,vnffg_classifier) + return classifier_name + + @rwstatus(ret_on_failure=[None]) + def do_terminate_vnffg_classifier(self, account, vnffg_classifier_name): + """ + Add VNFFG Classifier + + @param account - a SDN account + """ + self.sdnodl.terminate_sfc_classifier(account,vnffg_classifier_name) + + +class Sff(object): + """ + Create SFF object to hold SFF related details + """ + + def __init__(self,sff_name, mgmt_address, mgmt_port, dp_address, dp_port,sff_dp_name, sff_br_name=''): + self.name = sff_name + self.ip = mgmt_address + self.sff_rest_port = mgmt_port + self.sff_port = dp_port + self.dp_name = sff_dp_name + self.dp_ip = dp_address + self.br_name = sff_br_name + self.sf_dp_list = list() + + def add_sf_dp_to_sff(self,sf_dp): + self.sf_dp_list.append(sf_dp) + + def __repr__(self): + return 'Name:{},Bridge Name:{}, IP: {}, SF List: {}'.format(self.dp_name,self.br_name, self.ip, self.sf_dp_list) + +class SfDpLocator(object): + """ + Create Service Function Data Plane Locator related Object to hold details related to each DP Locator endpoint + """ + def __init__(self,name,sfdp_id,vnfr_name,vm_id): + self.name = name + self.port_id = sfdp_id + self.vnfr_name = vnfr_name + self.vm_id = vm_id + self.sff_name = None + self.ovsdb_tp_name = None + + def _update_sff_name(self,sff_name): + self.sff_name = sff_name + + def _update_vnf_params(self,service_function_type,address, port,transport_type): + self.service_function_type = service_function_type + self.address = address + self.port = port + self.transport_type = "service-locator:{}".format(transport_type) + + def __repr__(self): + 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) + +class SdnOdl(object): + """ + SDN ODL Class to support REST based API calls + """ + + @property + def _network_topology_path(self): + return 'restconf/operational/network-topology:network-topology' + + @property + def _node_inventory_path(self): + return 'restconf/operational/opendaylight-inventory:nodes' + + def _network_topology_rest_url(self,account): + return '{}/{}'.format(account.odl.url,self._network_topology_path) + + def _node_inventory_rest_url(self,account): + return '{}/{}'.format(account.odl.url,self._node_inventory_path) + + def _get_rest_url(self,account, rest_path): + return '{}/{}'.format(account.odl.url,rest_path) + + + def _get_peer_termination_point(self,node_inv,tp_id): + for node in node_inv['nodes']['node']: + if "node-connector" in node and len(node['node-connector']) > 0: + for nodec in node['node-connector']: + if ("flow-node-inventory:name" in nodec and nodec["flow-node-inventory:name"] == tp_id): + return(node['id'], nodec['id']) + return (None,None) + + def _get_termination_point_mac_address(self,node_inv,tp_id): + for node in node_inv['nodes']['node']: + if "node-connector" in node and len(node['node-connector']) > 0: + for nodec in node['node-connector']: + if ("flow-node-inventory:name" in nodec and nodec["flow-node-inventory:name"] == tp_id): + return nodec.get("flow-node-inventory:hardware-address") + + def _add_host(self,ntwk,node,term_point,vmid,node_inv): + for ntwk_node in ntwk.node: + if ntwk_node.node_id == vmid: + break + else: + ntwk_node = ntwk.node.add() + if "ovsdb:bridge-name" in node: + ntwk_node.rw_node_attributes.ovs_bridge_name = node["ovsdb:bridge-name"] + ntwk_node.node_id = vmid + intf_id = [res for res in term_point['ovsdb:interface-external-ids'] if res['external-id-key'] == 'iface-id'] + if intf_id: + ntwk_node_tp = ntwk_node.termination_point.add() + ntwk_node_tp.tp_id = intf_id[0]['external-id-value'] + att_mac = [res for res in term_point['ovsdb:interface-external-ids'] if res['external-id-key'] == 'attached-mac'] + if att_mac: + ntwk_node_tp.l2_termination_point_attributes.mac_address = att_mac[0]['external-id-value'] + peer_node,peer_node_tp = self._get_peer_termination_point(node_inv,term_point['tp-id']) + if peer_node and peer_node_tp: + nw_lnk = ntwk.link.add() + nw_lnk.source.source_tp = ntwk_node_tp.tp_id + nw_lnk.source.source_node = ntwk_node.node_id + nw_lnk.destination.dest_tp = term_point['tp-id'] + nw_lnk.destination.dest_node = node['node-id'] + nw_lnk.link_id = peer_node_tp + '-' + 'source' + + nw_lnk = ntwk.link.add() + nw_lnk.source.source_tp = term_point['tp-id'] + nw_lnk.source.source_node = node['node-id'] + nw_lnk.destination.dest_tp = ntwk_node_tp.tp_id + nw_lnk.destination.dest_node = ntwk_node.node_id + nw_lnk.link_id = peer_node_tp + '-' + 'dest' + + def _get_address_from_node_inventory(self,node_inv,node_id): + for node in node_inv['nodes']['node']: + if node['id'] == node_id: + return node["flow-node-inventory:ip-address"] + return None + + def _fill_network_list(self,nw_topo,node_inventory): + """ + Fill Topology related information + """ + nwtop = RwTl.YangData_IetfNetwork() + + for topo in nw_topo['network-topology']['topology']: + if ('node' in topo and len(topo['node']) > 0): + ntwk = nwtop.network.add() + ntwk.network_id = topo['topology-id'] + ntwk.server_provided = True + for node in topo['node']: + if ('termination-point' in node and len(node['termination-point']) > 0): + ntwk_node = ntwk.node.add() + ntwk_node.node_id = node['node-id'] + addr = self._get_address_from_node_inventory(node_inventory,ntwk_node.node_id) + if addr: + ntwk_node.l2_node_attributes.management_address.append(addr) + for term_point in node['termination-point']: + ntwk_node_tp = ntwk_node.termination_point.add() + ntwk_node_tp.tp_id = term_point['tp-id'] + mac_address = self._get_termination_point_mac_address(node_inventory,term_point['tp-id']) + if mac_address: + ntwk_node_tp.l2_termination_point_attributes.mac_address = mac_address + if 'ovsdb:interface-external-ids' in term_point: + vm_id = [res for res in term_point['ovsdb:interface-external-ids'] if res['external-id-key'] == 'vm-id'] + if vm_id: + vmid = vm_id[0]['external-id-value'] + self._add_host(ntwk,node,term_point,vmid,node_inventory) + if ('link' in topo and len(topo['link']) > 0): + for link in topo['link']: + nw_link = ntwk.link.add() + if 'destination' in link: + nw_link.destination.dest_tp = link['destination'].get('dest-tp') + nw_link.destination.dest_node = link['destination'].get('dest-node') + if 'source' in link: + nw_link.source.source_node = link['source'].get('source-node') + nw_link.source.source_tp = link['source'].get('source-tp') + nw_link.link_id = link.get('link-id') + return nwtop + + + def validate_account_creds(self, account): + """ + Validate the SDN account credentials by accessing the rest API using the provided credentials + """ + status = RwsdnYang.SdnConnectionStatus() + url = '{}/{}'.format(account.odl.url,"restconf") + try: + r=requests.get(url,auth=(account.odl.username,account.odl.password)) + r.raise_for_status() + except requests.exceptions.HTTPError as e: + msg = "SdnOdlPlugin: SDN account credential validation failed. Exception: %s", str(e) + #logger.error(msg) + print(msg) + status.status = "failure" + status.details = "Invalid Credentials: %s" % str(e) + except Exception as e: + msg = "SdnPdlPlugin: SDN connection failed. Exception: %s", str(e) + #logger.error(msg) + print(msg) + status.status = "failure" + status.details = "Connection Failed (Invlaid URL): %s" % str(e) + else: + print("SDN Successfully connected") + status.status = "success" + status.details = "Connection was successful" + + return status + + def get_network_list(self, account): + """ + Get the networks details from ODL + """ + url = self._network_topology_rest_url(account) + r=requests.get(url,auth=(account.odl.username,account.odl.password)) + r.raise_for_status() + nw_topo = r.json() + + url = self._node_inventory_rest_url(account) + r = requests.get(url,auth=(account.odl.username,account.odl.password)) + r.raise_for_status() + node_inventory = r.json() + return self._fill_network_list(nw_topo,node_inventory) + + @property + def _service_functions_path(self): + return 'restconf/config/service-function:service-functions' + + @property + def _service_function_path(self): + return 'restconf/config/service-function:service-functions/service-function/{}' + + @property + def _service_function_forwarders_path(self): + return 'restconf/config/service-function-forwarder:service-function-forwarders' + + @property + def _service_function_forwarder_path(self): + return 'restconf/config/service-function-forwarder:service-function-forwarders/service-function-forwarder/{}' + + @property + def _service_function_chains_path(self): + return 'restconf/config/service-function-chain:service-function-chains' + + @property + def _service_function_chain_path(self): + return 'restconf/config/service-function-chain:service-function-chains/service-function-chain/{}' + + @property + def _sfp_metadata_path(self): + return 'restconf/config/service-function-path-metadata:service-function-metadata/context-metadata/{}' + + @property + def _sfps_metadata_path(self): + return 'restconf/config/service-function-path-metadata:service-function-metadata' + + @property + def _sfps_path(self): + return 'restconf/config/service-function-path:service-function-paths' + + @property + def _sfp_path(self): + return 'restconf/config/service-function-path:service-function-paths/service-function-path/{}' + + + @property + def _create_rsp_path(self): + return 'restconf/operations/rendered-service-path:create-rendered-path' + + @property + def _delete_rsp_path(self): + return 'restconf/operations/rendered-service-path:delete-rendered-path' + + + @property + def _get_rsp_paths(self): + return 'restconf/operational/rendered-service-path:rendered-service-paths' + + @property + def _get_rsp_path(self): + return 'restconf/operational/rendered-service-path:rendered-service-paths/rendered-service-path/{}' + + @property + def _access_list_path(self): + return 'restconf/config/ietf-access-control-list:access-lists/acl/{}' + + @property + def _service_function_classifier_path(self): + return 'restconf/config/service-function-classifier:service-function-classifiers/service-function-classifier/{}' + + @property + def _access_lists_path(self): + return 'restconf/config/ietf-access-control-list:access-lists' + + @property + def _service_function_classifiers_path(self): + return 'restconf/config/service-function-classifier:service-function-classifiers' + + + def _create_sf(self,account,vnffg_chain,sf_dp_list): + "Create SF" + sf_json = {} + + for vnf in vnffg_chain.vnf_chain_path: + for vnfr in vnf.vnfr_ids: + sf_url = self._get_rest_url(account,self._service_function_path.format(vnfr.vnfr_name)) + print(sf_url) + r=requests.get(sf_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}) + # If the SF is not found; create new SF + if r.status_code == 200: + logger.info("SF with name %s is already present in ODL. Skipping update", vnfr.vnfr_name) + continue + elif r.status_code != 404: + r.raise_for_status() + + sf_dict = {} + sf_dict['name'] = vnfr.vnfr_name + sf_dict['nsh-aware'] = vnf.nsh_aware + sf_dict['type'] = vnf.service_function_type + sf_dict['ip-mgmt-address'] = vnfr.mgmt_address + sf_dict['rest-uri'] = 'http://{}:{}'.format(vnfr.mgmt_address, vnfr.mgmt_port) + + sf_dict['sf-data-plane-locator'] = list() + for vdu in vnfr.vdu_list: + sf_dp = {} + if vdu.port_id in sf_dp_list.keys(): + sf_dp_entry = sf_dp_list[vdu.port_id] + sf_dp['name'] = sf_dp_entry.name + sf_dp['ip'] = vdu.address + sf_dp['port'] = vdu.port + sf_dp['transport'] = "service-locator:{}".format(vnf.transport_type) + if vnfr.sff_name: + sf_dp['service-function-forwarder'] = vnfr.sff_name + else: + sff_name = sf_dp_entry.sff_name + if sff_name is None: + logger.error("SFF not found for port %s in SF %s", vdu.port_id, vnfr.vnfr_name) + sf_dp['service-function-forwarder'] = sff_name + sf_dp['service-function-ovs:ovs-port'] = dict() + if sf_dp_entry.ovsdb_tp_name is not None: + sf_dp['service-function-ovs:ovs-port']['port-id'] = sf_dp_entry.ovsdb_tp_name + sf_dict['sf-data-plane-locator'].append(sf_dp) + else: + logger.error("Port %s not found in SF DP list",vdu.port_id) + + sf_json['service-function'] = sf_dict + sf_data = json.dumps(sf_json) + sf_url = self._get_rest_url(account,self._service_function_path.format(vnfr.vnfr_name)) + print(sf_url) + print(sf_data) + r=requests.put(sf_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=sf_data) + r.raise_for_status() + + + def _create_sff(self,account,vnffg_chain,sff): + "Create SFF" + sff_json = {} + sff_dict = {} + #sff_dp_name = "SFF1" + '-' + 'DP1' + sff_dp_name = sff.dp_name + + sff_url = self._get_rest_url(account,self._service_function_forwarder_path.format(sff.name)) + print(sff_url) + r=requests.get(sff_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}) + # If the SFF is not found; create new SF + if r.status_code == 200: + logger.info("SFF with name %s is already present in ODL. Skipping full update", sff.name) + sff_dict = r.json() + sff_updated = False + for sf_dp in sff.sf_dp_list: + for sff_sf in sff_dict['service-function-forwarder'][0]['service-function-dictionary']: + if sf_dp.vnfr_name == sff_sf['name']: + logger.info("SF with name %s is already found in SFF %s SF Dictionay. Skipping update",sf_dp.vnfr_name,sff.name) + break + else: + logger.info("SF with name %s is not found in SFF %s SF Dictionay",sf_dp.vnfr_name, sff.name) + sff_updated = True + sff_sf_dict = {} + sff_sf_dp_loc = {} + sff_sf_dict['name'] = sf_dp.vnfr_name + + # Below two lines are enabled only for ODL Beryillium + sff_sf_dp_loc['sff-dpl-name'] = sff_dp_name + sff_sf_dp_loc['sf-dpl-name'] = sf_dp.name + + sff_sf_dict['sff-sf-data-plane-locator'] = sff_sf_dp_loc + sff_dict['service-function-forwarder'][0]['service-function-dictionary'].append(sff_sf_dict) + if sff_updated is True: + sff_data = json.dumps(sff_dict) + print(sff_data) + r=requests.put(sff_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=sff_data) + r.raise_for_status() + return + elif r.status_code != 404: + r.raise_for_status() + + sff_name = sff.name + sff_ip = sff.ip + sff_dp_ip = sff.dp_ip + sff_port = sff.sff_port + sff_bridge_name = '' + sff_rest_port = sff.sff_rest_port + sff_ovs_op = {} + if sff_rest_based is False: + sff_bridge_name = sff.br_name + sff_ovs_op = {"key": "flow", + "nshc1": "flow", + "nsp": "flow", + "remote-ip": "flow", + "dst-port": sff_port, + "nshc3": "flow", + "nshc2": "flow", + "nshc4": "flow", + "nsi": "flow"} + + + sff_dict['name'] = sff_name + sff_dict['service-node'] = '' + sff_dict['ip-mgmt-address'] = sff_ip + if sff_rest_based: + sff_dict['rest-uri'] = 'http://{}:{}'.format(sff_ip, sff_rest_port) + else: + sff_dict['service-function-forwarder-ovs:ovs-bridge'] = {"bridge-name": sff_bridge_name} + sff_dict['service-function-dictionary'] = list() + for sf_dp in sff.sf_dp_list: + sff_sf_dict = {} + sff_sf_dp_loc = {} + sff_sf_dict['name'] = sf_dp.vnfr_name + + # Below set of lines are reqd for Lithium + #sff_sf_dict['type'] = sf_dp.service_function_type + #sff_sf_dp_loc['ip'] = sf_dp.address + #sff_sf_dp_loc['port'] = sf_dp.port + #sff_sf_dp_loc['transport'] = sf_dp.transport_type + #sff_sf_dp_loc['service-function-forwarder-ovs:ovs-bridge'] = {} + + # Below two lines are enabled only for ODL Beryillium + sff_sf_dp_loc['sff-dpl-name'] = sff_dp_name + sff_sf_dp_loc['sf-dpl-name'] = sf_dp.name + + sff_sf_dict['sff-sf-data-plane-locator'] = sff_sf_dp_loc + sff_dict['service-function-dictionary'].append(sff_sf_dict) + + sff_dict['sff-data-plane-locator'] = list() + sff_dp = {} + dp_loc = {} + sff_dp['name'] = sff_dp_name + dp_loc['ip'] = sff_dp_ip + dp_loc['port'] = sff_port + dp_loc['transport'] = 'service-locator:vxlan-gpe' + sff_dp['data-plane-locator'] = dp_loc + if sff_rest_based is False: + sff_dp['service-function-forwarder-ovs:ovs-options'] = sff_ovs_op + #sff_dp["service-function-forwarder-ovs:ovs-bridge"] = {'bridge-name':sff_bridge_name} + sff_dp["service-function-forwarder-ovs:ovs-bridge"] = {} + sff_dict['sff-data-plane-locator'].append(sff_dp) + + sff_json['service-function-forwarder'] = sff_dict + sff_data = json.dumps(sff_json) + print(sff_data) + r=requests.put(sff_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=sff_data) + r.raise_for_status() + + def _create_sfc(self,account,vnffg_chain): + "Create SFC" + sfc_json = {} + sfc_dict = {} + sfc_dict['name'] = vnffg_chain.name + sfc_dict['sfc-service-function'] = list() + vnf_chain_list = sorted(vnffg_chain.vnf_chain_path, key = lambda x: x.order) + for vnf in vnf_chain_list: + sfc_sf_dict = {} + sfc_sf_dict['name'] = vnf.service_function_type + sfc_sf_dict['type'] = vnf.service_function_type + sfc_sf_dict['order'] = vnf.order + sfc_dict['sfc-service-function'].append(sfc_sf_dict) + sfc_json['service-function-chain'] = sfc_dict + sfc_data = json.dumps(sfc_json) + sfc_url = self._get_rest_url(account,self._service_function_chain_path.format(vnffg_chain.name)) + print(sfc_url) + print(sfc_data) + r=requests.put(sfc_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=sfc_data) + r.raise_for_status() + + def _create_sfp_metadata(self,account,sfc_classifier): + " Create SFP metadata" + sfp_meta_json = {} + sfp_meta_dict = {} + sfp_meta_dict['name'] = sfc_classifier.name + if sfc_classifier.vnffg_metadata.ctx1: + sfp_meta_dict['context-header1'] = sfc_classifier.vnffg_metadata.ctx1 + if sfc_classifier.vnffg_metadata.ctx2: + sfp_meta_dict['context-header2'] = sfc_classifier.vnffg_metadata.ctx2 + if sfc_classifier.vnffg_metadata.ctx3: + sfp_meta_dict['context-header3'] = sfc_classifier.vnffg_metadata.ctx3 + if sfc_classifier.vnffg_metadata.ctx4: + sfp_meta_dict['context-header4'] = sfc_classifier.vnffg_metadata.ctx4 + + sfp_meta_json['context-metadata'] = sfp_meta_dict + sfp_meta_data = json.dumps(sfp_meta_json) + sfp_meta_url = self._get_rest_url(account,self._sfp_metadata_path.format(sfc_classifier.name)) + print(sfp_meta_url) + print(sfp_meta_data) + r=requests.put(sfp_meta_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=sfp_meta_data) + r.raise_for_status() + + def _create_sfp(self,account,vnffg_chain, sym_chain=False,classifier_name=None,vnffg_metadata_name=None): + "Create SFP" + sfp_json = {} + sfp_dict = {} + sfp_dict['name'] = vnffg_chain.name + sfp_dict['service-chain-name'] = vnffg_chain.name + sfp_dict['symmetric'] = sym_chain + sfp_dict['transport-type'] = 'service-locator:vxlan-gpe' + if vnffg_metadata_name: + sfp_dict['context-metadata'] = vnffg_metadata_name + if classifier_name: + sfp_dict['classifier'] = classifier_name + + sfp_json['service-function-path'] = sfp_dict + sfp_data = json.dumps(sfp_json) + sfp_url = self._get_rest_url(account,self._sfp_path.format(vnffg_chain.name)) + print(sfp_url) + print(sfp_data) + r=requests.put(sfp_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=sfp_data) + r.raise_for_status() + + def _create_rsp(self,account,vnffg_chain_name, sym_chain=True): + "Create RSP" + rsp_json = {} + rsp_input = {} + rsp_json['input'] = {} + rsp_input['name'] = vnffg_chain_name + rsp_input['parent-service-function-path'] = vnffg_chain_name + rsp_input['symmetric'] = sym_chain + + rsp_json['input'] = rsp_input + rsp_data = json.dumps(rsp_json) + self._rsp_data = rsp_json + rsp_url = self._get_rest_url(account,self._create_rsp_path) + print(rsp_url) + print(rsp_data) + r=requests.post(rsp_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=rsp_data) + r.raise_for_status() + print(r.json()) + output_json = r.json() + return output_json['output']['name'] + + def _get_sff_list_for_chain(self, account,sf_dp_list): + """ + Get List of all SFF that needs to be created based on VNFs included in VNFFG chain. + """ + + sff_list = {} + if sf_dp_list is None: + logger.error("VM List for vnffg chain is empty while trying to get SFF list") + url = self._network_topology_rest_url(account) + r=requests.get(url,auth=(account.odl.username,account.odl.password)) + r.raise_for_status() + nw_topo = r.json() + + for topo in nw_topo['network-topology']['topology']: + if ('node' in topo and len(topo['node']) > 0): + for node in topo['node']: + if ('termination-point' in node and len(node['termination-point']) > 0): + for term_point in node['termination-point']: + if 'ovsdb:interface-external-ids' in term_point: + vm_id = [res for res in term_point['ovsdb:interface-external-ids'] if res['external-id-key'] == 'vm-id'] + if len(vm_id) == 0: + continue + vmid = vm_id[0]['external-id-value'] + intf_id = [res for res in term_point['ovsdb:interface-external-ids'] if res['external-id-key'] == 'iface-id'] + if len(intf_id) == 0: + continue + intfid = intf_id[0]['external-id-value'] + if intfid not in sf_dp_list.keys(): + continue + if sf_dp_list[intfid].vm_id != vmid: + logger.error("Intf ID %s is not present in VM %s", intfid, vmid) + continue + sf_dp_list[intfid].ovsdb_tp_name = term_point['ovsdb:name'] + + if 'ovsdb:managed-by' in node: + rr=re.search('network-topology:node-id=\'([-\w\:\/]*)\'',node['ovsdb:managed-by']) + node_id = rr.group(1) + ovsdb_node = [node for node in topo['node'] if node['node-id'] == node_id] + if ovsdb_node: + if 'ovsdb:connection-info' in ovsdb_node[0]: + sff_ip = ovsdb_node[0]['ovsdb:connection-info']['local-ip'] + sff_br_name = node['ovsdb:bridge-name'] + sff_br_uuid = node['ovsdb:bridge-uuid'] + sff_dp_ip = sff_ip + + if 'ovsdb:openvswitch-other-configs' in ovsdb_node[0]: + for other_key in ovsdb_node[0]['ovsdb:openvswitch-other-configs']: + if other_key['other-config-key'] == 'local_ip': + local_ip_str = other_key['other-config-value'] + sff_dp_ip = local_ip_str.split(',')[0] + break + + sff_name = socket.getfqdn(sff_ip) + if sff_br_uuid in sff_list: + sff_list[sff_name].add_sf_dp_to_sff(sf_dp_list[intfid]) + sf_dp_list[intfid]._update_sff_name(sff_name) + else: + sff_dp_ip = sff_ip #overwrite sff_dp_ip to SFF ip for now + sff_list[sff_name] = Sff(sff_name,sff_ip,6000, sff_dp_ip, 4790,sff_br_uuid,sff_br_name) + sf_dp_list[intfid]._update_sff_name(sff_name) + sff_list[sff_name].add_sf_dp_to_sff(sf_dp_list[intfid]) + return sff_list + + + def _get_sf_dp_list_for_chain(self,account,vnffg_chain): + """ + Get list of all Service Function Data Plane Locators present in VNFFG + useful for easy reference while creating SF and SFF + """ + sfdp_list = {} + for vnf in vnffg_chain.vnf_chain_path: + for vnfr in vnf.vnfr_ids: + for vdu in vnfr.vdu_list: + sfdp = SfDpLocator(vdu.name,vdu.port_id,vnfr.vnfr_name, vdu.vm_id) + sfdp._update_vnf_params(vnf.service_function_type, vdu.address, vdu.port, vnf.transport_type) + if vnfr.sff_name: + sfdp._update_sff_name(vnfr.sff_name) + sfdp_list[vdu.port_id] = sfdp + return sfdp_list + + def create_sfc(self, account, vnffg_chain): + "Create SFC chain" + + sff_list = {} + sf_dp_list = {} + + sf_dp_list = self._get_sf_dp_list_for_chain(account,vnffg_chain) + + if sff_rest_based is False and len(vnffg_chain.sff) == 0: + # Get the list of all SFFs required for vnffg chain + sff_list = self._get_sff_list_for_chain(account,sf_dp_list) + + for sff in vnffg_chain.sff: + 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) + for _,sf_dp in sf_dp_list.items(): + if sf_dp.sff_name and sf_dp.sff_name == sff.name: + sff_list[sff.name].add_sf_dp_to_sff(sf_dp) + + #Create all the SF in VNFFG chain + self._create_sf(account,vnffg_chain,sf_dp_list) + + for _,sff in sff_list.items(): + self._create_sff(account,vnffg_chain,sff) + + + self._create_sfc(account,vnffg_chain) + + self._create_sfp(account,vnffg_chain,classifier_name=vnffg_chain.classifier_name, + vnffg_metadata_name=vnffg_chain.classifier_name) + + ## Update to SFF could have deleted some RSP; so get list of SFP and + ## check RSP exists for same and create any as necessary + #rsp_name = self._create_rsp(account,vnffg_chain) + #return rsp_name + self._create_all_rsps(account) + self._recreate_all_sf_classifiers(account) + return vnffg_chain.name + + def _recreate_all_sf_classifiers(self,account): + """ + Re create all SF classifiers + """ + sfcl_url = self._get_rest_url(account,self._service_function_classifiers_path) + print(sfcl_url) + #Get the classifier + r=requests.get(sfcl_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}) + if r.status_code == 200: + print(r) + sfcl_json = r.json() + elif r.status_code == 404: + return + else: + r.raise_for_status() + + #Delete the classifiers and re-add same back + r=requests.delete(sfcl_url,auth=(account.odl.username,account.odl.password)) + r.raise_for_status() + #Readd it back + time.sleep(3) + print(sfcl_json) + sfcl_data = json.dumps(sfcl_json) + r=requests.put(sfcl_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=sfcl_data) + r.raise_for_status() + + def _create_all_rsps(self,account): + """ + Create all the RSPs for SFP found + """ + sfps_url = self._get_rest_url(account,self._sfps_path) + r=requests.get(sfps_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}) + r.raise_for_status() + sfps_json = r.json() + if 'service-function-path' in sfps_json['service-function-paths']: + for sfp in sfps_json['service-function-paths']['service-function-path']: + rsp_url = self._get_rest_url(account,self._get_rsp_path.format(sfp['name'])) + r = requests.get(rsp_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}) + if r.status_code == 404: + # Create the RSP + logger.info("Creating RSP for Service Path with name %s",sfp['name']) + self._create_rsp(account,sfp['name']) + + def delete_all_sf(self, account): + "Delete all the SFs" + sf_url = self._get_rest_url(account,self._service_functions_path) + print(sf_url) + r=requests.delete(sf_url,auth=(account.odl.username,account.odl.password)) + r.raise_for_status() + + + def delete_all_sff(self, account): + "Delete all the SFFs" + sff_url = self._get_rest_url(account,self._service_function_forwarders_path) + print(sff_url) + r=requests.delete(sff_url,auth=(account.odl.username,account.odl.password)) + r.raise_for_status() + + def delete_all_sfc(self, account): + "Delete all the SFCs" + sfc_url = self._get_rest_url(account,self._service_function_chains_path) + print(sfc_url) + r=requests.delete(sfc_url,auth=(account.odl.username,account.odl.password)) + r.raise_for_status() + + def delete_all_sfp_metadata(self, account): + "Delete all the SFPs metadata" + sfp_metadata_url = self._get_rest_url(account,self._sfps_metadata_path) + print(sfp_metadata_url) + r=requests.delete(sfp_metadata_url,auth=(account.odl.username,account.odl.password)) + r.raise_for_status() + + def delete_all_sfp(self, account): + "Delete all the SFPs" + sfp_url = self._get_rest_url(account,self._sfps_path) + print(sfp_url) + r=requests.delete(sfp_url,auth=(account.odl.username,account.odl.password)) + r.raise_for_status() + + def delete_all_rsp(self, account): + "Delete all the RSP" + #rsp_list = self.get_rsp_list(account) + url = self._get_rest_url(account,self._get_rsp_paths) + print(url) + r = requests.get(url,auth=(account.odl.username,account.odl.password)) + r.raise_for_status() + print(r.json()) + rsp_list = r.json() + + #for vnffg in rsp_list.vnffg_rendered_path: + for sfc_rsp in rsp_list['rendered-service-paths']['rendered-service-path']: + rsp_json = {} + rsp_input = {} + rsp_json['input'] = {} + rsp_input['name'] = sfc_rsp['name'] + + rsp_json['input'] = rsp_input + rsp_data = json.dumps(rsp_json) + self._rsp_data = rsp_json + rsp_url = self._get_rest_url(account,self._delete_rsp_path) + print(rsp_url) + print(rsp_data) + + r=requests.post(rsp_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=rsp_data) + r.raise_for_status() + print(r.json()) + #output_json = r.json() + #return output_json['output']['name'] + + def terminate_all_sfc(self, account): + "Terminate SFC chain" + self.delete_all_rsp(account) + self.delete_all_sfp(account) + self.delete_all_sfc(account) + self.delete_all_sff(account) + self.delete_all_sf(account) + + def _fill_rsp_list(self,sfc_rsp_list,sff_list): + vnffg_rsps = RwsdnYang.VNFFGRenderedPaths() + for sfc_rsp in sfc_rsp_list['rendered-service-paths']['rendered-service-path']: + rsp = vnffg_rsps.vnffg_rendered_path.add() + rsp.name = sfc_rsp['name'] + rsp.path_id = sfc_rsp['path-id'] + for sfc_rsp_hop in sfc_rsp['rendered-service-path-hop']: + rsp_hop = rsp.rendered_path_hop.add() + rsp_hop.hop_number = sfc_rsp_hop['hop-number'] + rsp_hop.service_index = sfc_rsp_hop['service-index'] + rsp_hop.vnfr_name = sfc_rsp_hop['service-function-name'] + rsp_hop.service_function_forwarder.name = sfc_rsp_hop['service-function-forwarder'] + for sff in sff_list['service-function-forwarders']['service-function-forwarder']: + if sff['name'] == rsp_hop.service_function_forwarder.name: + rsp_hop.service_function_forwarder.ip_address = sff['sff-data-plane-locator'][0]['data-plane-locator']['ip'] + rsp_hop.service_function_forwarder.port = sff['sff-data-plane-locator'][0]['data-plane-locator']['port'] + break + return vnffg_rsps + + + def get_rsp_list(self,account): + "Get RSP list" + + sff_url = self._get_rest_url(account,self._service_function_forwarders_path) + print(sff_url) + r=requests.get(sff_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}) + r.raise_for_status() + sff_list = r.json() + + url = self._get_rest_url(account,self._get_rsp_paths) + print(url) + r = requests.get(url,auth=(account.odl.username,account.odl.password)) + r.raise_for_status() + print(r.json()) + return self._fill_rsp_list(r.json(),sff_list) + + def create_sfc_classifier(self, account, sfc_classifiers): + "Create SFC Classifiers" + self._create_sfp_metadata(account,sfc_classifiers) + self._add_acl_rules(account, sfc_classifiers) + self._create_sf_classifier(account, sfc_classifiers) + return sfc_classifiers.name + + def terminate_sfc_classifier(self, account, sfc_classifier_name): + "Create SFC Classifiers" + self.delete_all_sfp_metadata(account) + self._terminate_sf_classifier(account, sfc_classifier_name) + self._del_acl_rules(account, sfc_classifier_name) + + def _del_acl_rules(self,account,sfc_classifier_name): + " Terminate SF classifiers" + acl_url = self._get_rest_url(account,self._access_lists_path) + print(acl_url) + r=requests.delete(acl_url,auth=(account.odl.username,account.odl.password)) + r.raise_for_status() + + def _terminate_sf_classifier(self,account,sfc_classifier_name): + " Terminate SF classifiers" + sfcl_url = self._get_rest_url(account,self._service_function_classifiers_path) + print(sfcl_url) + r=requests.delete(sfcl_url,auth=(account.odl.username,account.odl.password)) + r.raise_for_status() + + def _create_sf_classifier(self,account,sfc_classifiers): + " Create SF classifiers" + sf_classifier_json = {} + sf_classifier_dict = {} + sf_classifier_dict['name'] = sfc_classifiers.name + sf_classifier_dict['access-list'] = sfc_classifiers.name + sf_classifier_dict['scl-service-function-forwarder'] = list() + scl_sff = {} + scl_sff_name = '' + + if sfc_classifiers.has_field('sff_name') and sfc_classifiers.sff_name is not None: + scl_sff_name = sfc_classifiers.sff_name + elif sfc_classifiers.has_field('port_id') and sfc_classifiers.has_field('vm_id'): + sf_dp = SfDpLocator(sfc_classifiers.port_id, sfc_classifiers.port_id,'', sfc_classifiers.vm_id) + sf_dp_list= {} + sf_dp_list[sfc_classifiers.port_id] = sf_dp + self._get_sff_list_for_chain(account,sf_dp_list) + + if sf_dp.sff_name is None: + logger.error("SFF not found for port %s, VM: %s",sfc_classifiers.port_id,sfc_classifiers.vm_id) + else: + logger.info("SFF with name %s found for port %s, VM: %s",sf_dp.sff_name, sfc_classifiers.port_id,sfc_classifiers.vm_id) + scl_sff_name = sf_dp.sff_name + else: + rsp_url = self._get_rest_url(account,self._get_rsp_path.format(sfc_classifiers.rsp_name)) + r = requests.get(rsp_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}) + if r.status_code == 200: + rsp_data = r.json() + if 'rendered-service-path' in rsp_data and len(rsp_data['rendered-service-path'][0]['rendered-service-path-hop']) > 0: + scl_sff_name = rsp_data['rendered-service-path'][0]['rendered-service-path-hop'][0]['service-function-forwarder'] + + logger.debug("SFF for classifer %s found is %s",sfc_classifiers.name, scl_sff_name) + scl_sff['name'] = scl_sff_name + #scl_sff['interface'] = sff_intf_name + sf_classifier_dict['scl-service-function-forwarder'].append(scl_sff) + + sf_classifier_json['service-function-classifier'] = sf_classifier_dict + + sfcl_data = json.dumps(sf_classifier_json) + sfcl_url = self._get_rest_url(account,self._service_function_classifier_path.format(sfc_classifiers.name)) + print(sfcl_url) + print(sfcl_data) + r=requests.put(sfcl_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=sfcl_data) + r.raise_for_status() + + def _add_acl_rules(self, account,sfc_classifiers): + "Create ACL rules" + access_list_json = {} + access_list_dict = {} + acl_entry_list = list() + acl_list_dict = {} + for acl_rule in sfc_classifiers.match_attributes: + acl_entry = {} + acl_entry['rule-name'] = acl_rule.name + acl_entry['actions'] = {} + #acl_entry['actions']['netvirt-sfc-acl:rsp-name'] = sfc_classifiers.rsp_name + acl_entry['actions']['service-function-acl:rendered-service-path'] = sfc_classifiers.rsp_name + + matches = {} + for field, value in acl_rule.as_dict().items(): + if field == 'ip_proto': + matches['protocol'] = value + elif field == 'source_ip_address': + matches['source-ipv4-network'] = value + elif field == 'destination_ip_address': + matches['destination-ipv4-network'] = value + elif field == 'source_port': + matches['source-port-range'] = {'lower-port':value, 'upper-port':value} + elif field == 'destination_port': + matches['destination-port-range'] = {'lower-port':value, 'upper-port':value} + acl_entry['matches'] = matches + acl_entry_list.append(acl_entry) + acl_list_dict['ace'] = acl_entry_list + access_list_dict['acl-name'] = sfc_classifiers.name + access_list_dict['access-list-entries'] = acl_list_dict + access_list_json['acl'] = access_list_dict + + acl_data = json.dumps(access_list_json) + acl_url = self._get_rest_url(account,self._access_list_path.format(sfc_classifiers.name)) + print(acl_url) + print(acl_data) + r=requests.put(acl_url,auth=(account.odl.username,account.odl.password),headers={'content-type': 'application/json'}, data=acl_data) + r.raise_for_status()