Commit 80a8951e authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

Merge branch 'feat/device-oc-acl-candidate' into 'develop'

OpenConfigDriver: implemented support for ACLs and L2VPNs

See merge request teraflow-h2020/controller!190
parents 88ce376f bf3f0825
Loading
Loading
Loading
Loading
+33 −15
Original line number Diff line number Diff line
@@ -61,11 +61,13 @@ class NetconfSessionHandler:
        self.__port = int(port)
        self.__username       = settings.get('username')
        self.__password       = settings.get('password')
        self.__vendor         = settings.get('vendor')
        self.__key_filename   = settings.get('key_filename')
        self.__hostkey_verify = settings.get('hostkey_verify', True)
        self.__look_for_keys  = settings.get('look_for_keys', True)
        self.__allow_agent    = settings.get('allow_agent', True)
        self.__force_running  = settings.get('force_running', False)
        self.__commit_per_delete  = settings.get('delete_rule', False)
        self.__device_params  = settings.get('device_params', {})
        self.__manager_params = settings.get('manager_params', {})
        self.__nc_params      = settings.get('nc_params', {})
@@ -90,6 +92,12 @@ class NetconfSessionHandler:
    @property
    def use_candidate(self): return self.__candidate_supported and not self.__force_running

    @property
    def commit_per_rule(self): return self.__commit_per_delete 

    @property
    def vendor(self): return self.__vendor

    @RETRY_DECORATOR
    def get(self, filter=None, with_defaults=None): # pylint: disable=redefined-builtin
        with self.__lock:
@@ -181,8 +189,9 @@ def do_sampling(samples_cache : SamplesCache, resource_key : str, out_samples :
        LOGGER.exception('Error retrieving samples')

def edit_config(
    netconf_handler : NetconfSessionHandler, resources : List[Tuple[str, Any]], delete=False, target='running',
    default_operation='merge', test_option=None, error_option=None, format='xml' # pylint: disable=redefined-builtin
    netconf_handler : NetconfSessionHandler, resources : List[Tuple[str, Any]], delete=False, commit_per_rule= False,
    target='running', default_operation='merge', test_option=None, error_option=None,
    format='xml' # pylint: disable=redefined-builtin
):
    str_method = 'DeleteConfig' if delete else 'SetConfig'
    LOGGER.info('[{:s}] resources = {:s}'.format(str_method, str(resources)))
@@ -195,13 +204,16 @@ def edit_config(
            chk_length(str_resource_name, resource, min_length=2, max_length=2)
            resource_key,resource_value = resource
            chk_string(str_resource_name + '.key', resource_key, allow_empty=False)
            str_config_message = compose_config(resource_key, resource_value, delete=delete)
            str_config_message = compose_config(
                resource_key, resource_value, delete=delete, vendor=netconf_handler.vendor)
            if str_config_message is None: raise UnsupportedResourceKeyException(resource_key)
            LOGGER.info('[{:s}] str_config_message[{:d}] = {:s}'.format(
                str_method, len(str_config_message), str(str_config_message)))
            netconf_handler.edit_config(
                config=str_config_message, target=target, default_operation=default_operation,
                test_option=test_option, error_option=error_option, format=format)
            if commit_per_rule:
                netconf_handler.commit()
            results[i] = True
        except Exception as e: # pylint: disable=broad-except
            str_operation = 'preparing' if target == 'candidate' else ('deleting' if delete else 'setting')
@@ -278,6 +290,9 @@ class OpenConfigDriver(_Driver):
        with self.__lock:
            if self.__netconf_handler.use_candidate:
                with self.__netconf_handler.locked(target='candidate'):
                    if self.__netconf_handler.commit_per_rule:
                           results = edit_config(self.__netconf_handler, resources, target='candidate', commit_per_rule= True)
                    else:
                        results = edit_config(self.__netconf_handler, resources, target='candidate')
                        try:
                            self.__netconf_handler.commit()
@@ -294,6 +309,9 @@ class OpenConfigDriver(_Driver):
        with self.__lock:
            if self.__netconf_handler.use_candidate:
                with self.__netconf_handler.locked(target='candidate'):
                    if self.__netconf_handler.commit_per_rule:
                           results = edit_config(self.__netconf_handler, resources, target='candidate', delete=True, commit_per_rule= True)
                    else:
                        results = edit_config(self.__netconf_handler, resources, target='candidate', delete=True)
                        try:
                            self.__netconf_handler.commit()
+8 −1
Original line number Diff line number Diff line
@@ -37,6 +37,10 @@ def parse(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]:
        #interface_type = xml_interface.find('oci:config/oci:type', namespaces=NAMESPACES)
        #add_value_from_tag(interface, 'type', interface_type)

        interface_type = xml_interface.find('oci:config/oci:type', namespaces=NAMESPACES)
        interface_type.text = interface_type.text.replace('ianaift:','')
        add_value_from_tag(interface, 'type', interface_type)

        interface_mtu = xml_interface.find('oci:config/oci:mtu', namespaces=NAMESPACES)
        add_value_from_tag(interface, 'mtu', interface_mtu, cast=int)

@@ -49,12 +53,15 @@ def parse(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]:
            subinterface = {}

            add_value_from_tag(subinterface, 'name', interface_name)
            add_value_from_tag(subinterface, 'mtu', interface_mtu)
            add_value_from_tag(subinterface, 'type', interface_type)


            subinterface_index = xml_subinterface.find('oci:index', namespaces=NAMESPACES)
            if subinterface_index is None or subinterface_index.text is None: continue
            add_value_from_tag(subinterface, 'index', subinterface_index, cast=int)

            vlan_id = xml_subinterface.find('ocv:vlan/ocv:config/ocv:vlan-id', namespaces=NAMESPACES)
            vlan_id = xml_subinterface.find('ocv:vlan/ocv:match/ocv:single-tagged/ocv:config/ocv:vlan-id', namespaces=NAMESPACES)
            add_value_from_tag(subinterface, 'vlan_id', vlan_id, cast=int)

            # TODO: implement support for multiple IP addresses per subinterface
+26 −3
Original line number Diff line number Diff line
@@ -27,6 +27,9 @@ XPATH_NI_IIP_AP = ".//ocni:inter-instance-policies/ocni:apply-policy"
XPATH_NI_IIP_AP_IMPORT  = ".//ocni:config/ocni:import-policy"
XPATH_NI_IIP_AP_EXPORT  = ".//ocni:config/ocni:export-policy"

XPATH_NI_CPOINTS          = ".//ocni:connection-points/ocni:connection-point"
XPATH_NI_CPOINTS_ENDPOINT = ".//ocni:endpoints/ocni:endpoint/ocni:remote/ocni:config"

def parse(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]:
    response = []
    for xml_network_instance in xml_data.xpath(XPATH_NETWORK_INSTANCES, namespaces=NAMESPACES):
@@ -39,10 +42,11 @@ def parse(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]:
        add_value_from_tag(network_instance, 'name', ni_name)

        ni_type = xml_network_instance.find('ocni:config/ocni:type', namespaces=NAMESPACES)
        ni_type.text = ni_type.text.replace('oc-ni-types:','')
        add_value_from_tag(network_instance, 'type', ni_type)

        #ni_router_id = xml_network_instance.find('ocni:config/ocni:router-id', namespaces=NAMESPACES)
        #add_value_from_tag(network_instance, 'router_id', ni_router_id)
        ni_router_id = xml_network_instance.find('ocni:config/ocni:router-id', namespaces=NAMESPACES)
        add_value_from_tag(network_instance, 'router_id', ni_router_id)

        ni_route_dist = xml_network_instance.find('ocni:config/ocni:route-distinguisher', namespaces=NAMESPACES)
        add_value_from_tag(network_instance, 'route_distinguisher', ni_route_dist)
@@ -53,6 +57,20 @@ def parse(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]:
        if len(network_instance) == 0: continue
        response.append(('/network_instance[{:s}]'.format(network_instance['name']), network_instance))

        for xml_cpoints in xml_network_instance.xpath(XPATH_NI_PROTOCOLS, namespaces=NAMESPACES):
            cpoint = {}
            add_value_from_tag(cpoint, 'name', ni_name)

            connection_point = xml_cpoints.find('ocni:connection-point-id', namespaces=NAMESPACES)
            add_value_from_tag(cpoint, 'connection_point', connection_point)

            for xml_endpoint in xml_cpoints.xpath(XPATH_NI_CPOINTS_ENDPOINT, namespaces=NAMESPACES):
                remote_system = xml_endpoint.find('ocni:remote-system', namespaces=NAMESPACES)
                add_value_from_tag(cpoint, 'remote_system', remote_system)

                VC_ID = xml_endpoint.find('ocni:virtual-circuit-identifier', namespaces=NAMESPACES)
                add_value_from_tag(cpoint, 'VC_ID', VC_ID)

        for xml_protocol in xml_network_instance.xpath(XPATH_NI_PROTOCOLS, namespaces=NAMESPACES):
            #LOGGER.info('xml_protocol = {:s}'.format(str(ET.tostring(xml_protocol))))

@@ -71,6 +89,8 @@ def parse(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]:
            if protocol['identifier'] == 'BGP':
                bgp_as = xml_protocol.find('ocni:bgp/ocni:global/ocni:config/ocni:as', namespaces=NAMESPACES)
                add_value_from_tag(protocol, 'as', bgp_as, cast=int)
                bgp_id = xml_protocol.find('ocni:bgp/ocni:global/ocni:config/ocni:router-id', namespaces=NAMESPACES)
                add_value_from_tag(protocol, 'router_id', bgp_id)

            resource_key = '/network_instance[{:s}]/protocols[{:s}]'.format(
                network_instance['name'], protocol['identifier'])
@@ -94,7 +114,7 @@ def parse(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]:
            add_value_from_tag(table_connection, 'address_family', address_family,
                               cast=lambda s: s.replace('oc-types:', ''))

            default_import_policy = xml_table_connection.find('ocni:default-import-policy', namespaces=NAMESPACES)
            default_import_policy = xml_table_connection.find('ocni:config/ocni:default-import-policy', namespaces=NAMESPACES)
            add_value_from_tag(table_connection, 'default_import_policy', default_import_policy)

            resource_key = '/network_instance[{:s}]/table_connections[{:s}][{:s}][{:s}]'.format(
@@ -125,4 +145,7 @@ def parse(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]:
                    iip_ap['name'], iip_ap['export_policy'])
                response.append((resource_key, iip_ap))




    return response
+1 −1
Original line number Diff line number Diff line
@@ -74,7 +74,7 @@ def parse(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]:
        resource_key = '/routing_policy/bgp_defined_set[{:s}]'.format(bgp_ext_community_set['ext_community_set_name'])
        response.append((resource_key, copy.deepcopy(bgp_ext_community_set)))

        ext_community_member = xml_bgp_ext_community_set.find('ocbp:ext-community-member', namespaces=NAMESPACES)
        ext_community_member = xml_bgp_ext_community_set.find('ocbp:config/ocbp:ext-community-member', namespaces=NAMESPACES)
        if ext_community_member is not None and ext_community_member.text is not None:
            add_value_from_tag(bgp_ext_community_set, 'ext_community_member', ext_community_member)

+5 −3
Original line number Diff line number Diff line
@@ -13,7 +13,7 @@
# limitations under the License.

import json, logging, lxml.etree as ET, re
from typing import Any, Dict
from typing import Any, Dict, Optional
from jinja2 import Environment, PackageLoader, select_autoescape
from device.service.driver_api._Driver import (
    RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES, RESOURCE_ROUTING_POLICIES, RESOURCE_ACL)
@@ -77,9 +77,11 @@ def parse(resource_key : str, xml_data : ET.Element):
    if parser is None: return [(resource_key, xml_data)]
    return parser(xml_data)

def compose_config(resource_key : str, resource_value : str, delete : bool = False) -> str:
def compose_config(
    resource_key : str, resource_value : str, delete : bool = False, vendor : Optional[str] = None
) -> str:
    template_name = '{:s}/edit_config.xml'.format(RE_REMOVE_FILTERS.sub('', resource_key))
    template = JINJA_ENV.get_template(template_name)
    data : Dict[str, Any] = json.loads(resource_value)
    operation = 'delete' if delete else 'merge'
    return '<config>{:s}</config>'.format(template.render(**data, operation=operation).strip())
    return '<config>{:s}</config>'.format(template.render(**data, operation=operation, vendor=vendor).strip())
Loading