
# 
#   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()
