Commit 48658bbb authored by Pablo Armingol's avatar Pablo Armingol
Browse files

1) Pyangbind bug fixes

2) Support for ACL rules on L2VPN and L3VPN
3)Display ACL rules in WebUI
parent fab0847a
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@ FROM python:3.9-slim

# Install dependencies
RUN apt-get --yes --quiet --quiet update && \
    apt-get --yes --quiet --quiet install wget g++ && \
    apt-get --yes --quiet --quiet install wget g++ git && \
    rm -rf /var/lib/apt/lists/*

# Set Python to show logs as they occur
+2 −1
Original line number Diff line number Diff line
@@ -29,7 +29,8 @@ xmltodict==0.12.0
tabulate
ipaddress
macaddress

pyang
git+https://github.com/robshakir/pyangbind.git
# pip's dependency resolver does not take into account installed packages.
# p4runtime does not specify the version of grpcio/protobuf it needs, so it tries to install latest one
# adding here again grpcio==1.47.* and protobuf==3.20.* with explicit versions to prevent collisions
+32 −16
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@ import json, logging
from typing import Any, Dict, List, Tuple, Union
from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME
from common.method_wrappers.ServiceExceptions import InvalidArgumentException
from common.proto.context_pb2 import ConfigActionEnum, Device, DeviceConfig
from common.proto.context_pb2 import ConfigActionEnum, ConfigRule_ACL, Device, DeviceConfig
from common.proto.device_pb2 import MonitoringSettings
from common.proto.kpi_sample_types_pb2 import KpiSampleType
from common.tools.grpc.ConfigRules import update_config_rule_custom
@@ -143,6 +143,7 @@ def _raw_config_rules_to_grpc(

        if resource_value is None: continue
        resource_value = json.loads(resource_value) if isinstance(resource_value, str) else resource_value
        if isinstance(resource_value, ConfigRule_ACL): resource_value = grpc_message_to_json(resource_value)
        resource_value = {field_name : (field_value, False) for field_name,field_value in resource_value.items()}
        update_config_rule_custom(device_config.config_rules, resource_key, resource_value, new_action=config_action)

@@ -163,20 +164,35 @@ def compute_rules_to_add_delete(
    device : Device, request : Device
) -> Tuple[List[Tuple[str, Any]], List[Tuple[str, Any]]]:
    # convert config rules from context into a dictionary  
    # TODO: add support for non-custom config rules
    context_config_rules = {
        config_rule.custom.resource_key: config_rule.custom.resource_value
        for config_rule in device.device_config.config_rules
        if config_rule.WhichOneof('config_rule') == 'custom'
    }

    # convert config rules from request into a list
    # TODO: add support for non-custom config rules
    request_config_rules = [
        (config_rule.action, config_rule.custom.resource_key, config_rule.custom.resource_value)
        for config_rule in request.device_config.config_rules
        if config_rule.WhichOneof('config_rule') == 'custom'
    ]
    context_config_rules = {}
    for config_rule in device.device_config.config_rules: 
        config_rule_kind = config_rule.WhichOneof('config_rule')
        if config_rule_kind == 'custom':    # process "custom" rules
            context_config_rules[config_rule.custom.resource_key] = config_rule.custom.resource_value # get the resource value of the rule resource
        elif config_rule_kind == 'acl':     # process "custom" rules
            device_uuid = config_rule.acl.endpoint_id.device_id.device_uuid.uuid # get the device name
            endpoint_uuid = config_rule.acl.endpoint_id.endpoint_uuid.uuid       # get the endpoint name
            acl_ruleset_name = config_rule.acl.rule_set.name                     # get the acl name
            ACL_KEY_TEMPLATE = '/device[{:s}]/endpoint[{:s}]/acl_ruleset[{:s}]'
            key_or_path = ACL_KEY_TEMPLATE.format(device_uuid, endpoint_uuid, acl_ruleset_name)            
            context_config_rules[key_or_path] = config_rule.acl                  # get the resource value of the acl
 
    request_config_rules = []
    for config_rule in request.device_config.config_rules:
        config_rule_kind = config_rule.WhichOneof('config_rule')
        if config_rule_kind == 'custom': # resource management of "custom" rule  
            request_config_rules.append((
                config_rule.action, config_rule.custom.resource_key, config_rule.custom.resource_value
            ))
        elif config_rule_kind == 'acl':  # resource management of "acl" rule  
            device_uuid = config_rule.acl.endpoint_id.device_id.device_uuid.uuid
            endpoint_uuid = config_rule.acl.endpoint_id.endpoint_uuid.uuid
            acl_ruleset_name = config_rule.acl.rule_set.name
            ACL_KEY_TEMPLATE = '/device[{:s}]/endpoint[{:s}]/acl_ruleset[{:s}]'
            key_or_path = ACL_KEY_TEMPLATE.format(device_uuid, endpoint_uuid, acl_ruleset_name) 
            request_config_rules.append((
                config_rule.action, key_or_path, config_rule.acl
            ))

    resources_to_set    : List[Tuple[str, Any]] = [] # key, value
    resources_to_delete : List[Tuple[str, Any]] = [] # key, value
+12 −11
Original line number Diff line number Diff line
@@ -190,7 +190,7 @@ def do_sampling(
    except: # pylint: disable=bare-except
        logger.exception('Error retrieving samples')

def edit_config(
def edit_config(                                                                                                            # edit the configuration of openconfig devices
    netconf_handler : NetconfSessionHandler, logger : logging.Logger, 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
@@ -201,21 +201,22 @@ def edit_config(
    for i,resource in enumerate(resources):
        str_resource_name = 'resources[#{:d}]'.format(i)
        try:
            logger.debug('[{:s}] resource = {:s}'.format(str_method, str(resource)))
            # logger.debug('[{:s}] resource = {:s}'.format(str_method, str(resource)))
            chk_type(str_resource_name, resource, (list, tuple))
            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(
            str_config_messages = compose_config(                                                                          # get template for configuration
                resource_key, resource_value, delete=delete, vendor=netconf_handler.vendor)
            for str_config_message in str_config_messages:                                                                 # configuration of the received templates 
                if str_config_message is None: raise UnsupportedResourceKeyException(resource_key)
            logger.debug('[{:s}] str_config_message[{:d}] = {:s}'.format(
                str_method, len(str_config_message), str(str_config_message)))
            netconf_handler.edit_config(
                # logger.debug('[{:s}] str_config_message[{:d}] = {:s}'.format(
                #    str_method, len(str_config_message), str(str_config_message)))
                netconf_handler.edit_config(                                                                               # configure the device
                    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()
                    netconf_handler.commit()                                                                               # configuration commit
            results[i] = True
        except Exception as e: # pylint: disable=broad-except
            str_operation = 'preparing' if target == 'candidate' else ('deleting' if delete else 'setting')
+88 −97
Original line number Diff line number Diff line
from openconfig_acl import openconfig_acl
from .openconfig_acl import openconfig_acl
from pyangbind.lib.serialise import pybindIETFXMLEncoder

def acl_set_mgmt(parameters):
    Acl_name = parameters['name']
    Acl_type = parameters['type']
    ID       = parameters['sequence_id']
    DEL      = parameters['DEL']
from common.tools.grpc.Tools import grpc_message_to_json
import logging
def acl_mgmt(parameters,vendor):                                                                                       # acl templates management
    acl   = []    
    data = grpc_message_to_json(parameters,use_integers_for_enums=True)                                         # acl rule parameters management
    acl.append(acl_set_mgmt(data,vendor))                                                                              # acl_set template
    acl.append(acl_interface(data,vendor))                                                                             # acl interface template
    return acl
    
def acl_set_mgmt(parameters,vendor):
    type     = ["ACL_UNDEFINED", "ACL_IPV4","ACL_IPV6","ACL_L2","ACL_MPLS","ACL_MIXED"]
    f_action = ["UNDEFINED", "DROP","ACCEPT","REJECT"]
    l_action = ["UNDEFINED", "LOG_NONE","LOG_SYSLOG"]
    
    Acl_data    = parameters["rule_set"]
    Acl_name    = Acl_data['name']
    Acl_type    = type[Acl_data['type']]
    Acl_desc    = Acl_data['description']
    Acl_entries = Acl_data['entries']

    # Create an instance of the YANG model
    acl_instance = openconfig_acl()

    # Access the entry container
    acl_set                    = acl_instance.acl.acl_sets.acl_set.add(name = Acl_name, type=Acl_type)
    acl_entrie = acl_set.acl_entries.acl_entry.add(ID)

    if DEL: 
        # Dump the entire instance as RFC 7950 XML
        acl_set = pybindIETFXMLEncoder.serialise(acl_instance)
        #Delete replace
        acl_set = acl_set.replace('<acl-entry>','<acl-entry xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="delete">')
        #Generic replaces
        acl_set = acl_set.replace('<openconfig-acl xmlns="http://openconfig.net/yang/acl">',"")
        acl_set = acl_set.replace('<acl>','<acl xmlns="http://openconfig.net/yang/acl">')
        acl_set = acl_set.replace('</openconfig-acl>','')
        
    else:
    acl_set.config.name        = Acl_name
    acl_set.config.type        = Acl_type
    acl_set.config.description = Acl_desc
    LOGGER = logging.getLogger(__name__)

    LOGGER.warning("VALORES DE ENTRIES",Acl_entries)

    for entry in Acl_entries:
        ID     = entry['sequence_id']
        desc   = entry['description']
        match  = entry['match']
        action = entry['action']

        acl_entrie = acl_set.acl_entries.acl_entry.add(ID)
        acl_entrie.config.sequence_id = ID
        acl_entrie.config.description= desc
        
        # Configuration per type
        if "L2" in Acl_type:
            for variable, valor in parameters.items():
                if   "source_mac"          in variable and len(valor) != 0: acl_entrie.l2.config.source_mac = valor
                elif "destination_mac"     in variable and len(valor) != 0: acl_entrie.l2.config.destination_mac = valor
            for key, value in match.items():
                if   "src_address"     in key and len(value) != 0: acl_entrie.l2.config.source_mac = value
                elif "dst_address"     in key and len(value) != 0: acl_entrie.l2.config.destination_mac = value
                
        elif "IPV4" in Acl_type:
            for variable, valor in parameters.items():
                if   "source_address"       in variable and len(valor) != 0: acl_entrie.ipv4.config.source_address = valor
                elif "destination_address"  in variable and len(valor) != 0: acl_entrie.ipv4.config.destination_address = valor
                elif "protocol"             in variable and len(valor) != 0: acl_entrie.ipv4.config.protocol = valor
                elif "hop_limit"            in variable and len(valor) != 0: acl_entrie.ipv4.config.hop_limit = valor
                elif "dscp"                 in variable and len(valor) != 0: acl_entrie.ipv4.config.dscp = valor
                  
            for variable, valor in parameters.items():
                if   "source_port"          in variable and len(valor) != 0: acl_entrie.transport.config.source_port = valor
                elif "destination_port"     in variable and len(valor) != 0: acl_entrie.transport.config.destination_port = valor
                elif "tcp_flags"            in variable and len(valor) != 0: acl_entrie.transport.config.tcp_flags = valor
            for key, value in match.items():
                if   "src_address"       in key and len(value) != 0: acl_entrie.ipv4.config.source_address = value
                elif "dst_address"       in key and len(value) != 0: acl_entrie.ipv4.config.destination_address = value
                elif "protocol"          in key                    : acl_entrie.ipv4.config.protocol = value
                elif "hop_limit"         in key                    : acl_entrie.ipv4.config.hop_limit = value
                elif "dscp"              in key                    : acl_entrie.ipv4.config.dscp = value
                    
            for key, value in match.items():
                if   "src_port"     in key : acl_entrie.transport.config.source_port = value
                elif "dst_port"     in key : acl_entrie.transport.config.destination_port = value
                elif "tcp_flags"    in key : acl_entrie.transport.config.tcp_flags = value
                
        elif "IPV6" in Acl_type:
            for variable, valor in parameters.items():
                if   "source_address"       in variable and len(valor) != 0: acl_entrie.ipv6.config.source_address = valor
                elif "destination_address"  in variable and len(valor) != 0: acl_entrie.ipv6.config.destination_address = valor
                elif "protocol"             in variable and len(valor) != 0: acl_entrie.ipv6.config.protocol = valor
                elif "hop_limit"            in variable and len(valor) != 0: acl_entrie.ipv6.config.hop_limit = valor
                elif "dscp"                 in variable and len(valor) != 0: acl_entrie.ipv6.config.dscp = valor
            for key, value in match.items():
                if   "src_address"       in key and len(value) != 0: acl_entrie.ipv6.config.source_address = value
                elif "dst_address"       in key and len(value) != 0: acl_entrie.ipv6.config.destination_address = value
                elif "protocol"          in key                    : acl_entrie.ipv6.config.protocol = value
                elif "hop_limit"         in key                    : acl_entrie.ipv6.config.hop_limit = value
                elif "dscp"              in key                    : acl_entrie.ipv6.config.dscp = value
        
        for variable, valor in parameters.items():
            if   "forwarding_action"        in variable and len(valor) != 0: acl_entrie.actions.config.forwarding_action = valor
            elif "log_action"               in variable and len(valor) != 0: acl_entrie.actions.config.log_action = valor
        for key, value in action.items():
            if   "forward_action"        in key : acl_entrie.actions.config.forwarding_action = f_action[value]
            elif "log_action"            in key : acl_entrie.actions.config.log_action = l_action[value]
            
    # Dump the entire instance as RFC 7950 XML
    acl_set = pybindIETFXMLEncoder.serialise(acl_instance)
@@ -68,10 +81,12 @@ def acl_set_mgmt(parameters):

    return(acl_set)

def acl_interface(parameters):
    ID           = parameters['id']
    DEL          = parameters['DEL']
    verify       = str(parameters)                                    #Verify transforms the received parameters into a string format for later making verifications and modifications
def acl_interface(parameters,vendor):
    type      = ["ACL_UNDEFINED", "ACL_IPV4","ACL_IPV6","ACL_L2","ACL_MPLS","ACL_MIXED"]
    ID        = parameters['endpoint_id']['endpoint_uuid']['uuid']
    Acl_data  = parameters["rule_set"]
    Acl_name  = Acl_data['name']
    Acl_type  = type[Acl_data['type']]

    # Create an instance of the YANG model
    acl_instance = openconfig_acl()
@@ -79,44 +94,20 @@ def acl_interface(parameters):
    # Access the entry container
    interface = acl_instance.acl.interfaces.interface.add(id = ID)

    if DEL: 
        acl_set = pybindIETFXMLEncoder.serialise(acl_instance)                                      
        acl_set = acl_set.replace('<interface>','<interface xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="delete">')     

        # Dump the entire instance as RFC 7950 XML
        acl_set = acl_set.replace('<openconfig-acl xmlns="http://openconfig.net/yang/acl">'," ")     
        acl_set = acl_set.replace('<acl>','<acl xmlns="http://openconfig.net/yang/acl">')            
        acl_set = acl_set.replace('</openconfig-acl>','')                                           
    else:
    #Config - Interface
    interface.config.id = ID
        #If the Interface parameter is defined [OPTIONAL-PARAMETER]
        if verify.find('interface')>0:
            Interface = parameters['interface']        
            if  len(Interface) != 0: interface.interface_ref.config.interface = Interface        #If interface parameter has a value
        
        #If the Subinterface parameter is defined [OPTIONAL-PARAMETER]
        if verify.find('subinterface')>0:
            Subinterface = parameters['subinterface']        
            if  len(Interface) != 0: interface.interface_ref.config.subinterface = Subinterface   #If subinterface parameter has a value
    
         # Configuration per type
        if verify.find('set_name_ingress')>0:                           #If set_name_ingress is defined
            Ingress_name = parameters['set_name_ingress']        
            if  len(Ingress_name) != 0: 
                Ingress_type         = parameters['type_ingress']
                ingress= interface.ingress_acl_sets.ingress_acl_set.add(set_name = Ingress_name, type = Ingress_type)
                ingress.config.set_name = Ingress_name
                ingress.config.type     = Ingress_type
        
        if verify.find('set_name_egress')>0:                            #If set_name_egress is defined
            Egress_name = parameters['set_name_egress']        
            if  len(Egress_name) != 0: 
                Egress_name         = parameters['set_name_egress']
                Egress_type         = parameters['type_egress']
                egress= interface.egress_acl_sets.egress_acl_set.add(set_name = Egress_name, type = Egress_type)
                egress.config.set_name = Egress_name
                egress.config.type     = Egress_type
    #If the Interface parameter is defined [OPTIONAL-PARAMETER]
    interface.interface_ref.config.interface = ID        # Interface parameter 
    
    #TODO: add subinterface management
    if   vendor == "ADVA"   : interface.interface_ref.config.subinterface = '0'    # Subinterface parameter
    elif vendor == "Juniper": interface.interface_ref.config.subinterface = '0'
    else:                     interface.interface_ref.config.subinterface = Acl_data['subinterface']
    # Configuration ingress type
    ingress= interface.ingress_acl_sets.ingress_acl_set.add(set_name = Acl_name, type = Acl_type)
    ingress.config.set_name = Acl_name
    ingress.config.type     = Acl_type

    # Dump the entire instance as RFC 7950 XML
    acl_set = pybindIETFXMLEncoder.serialise(acl_instance)                                      
Loading