Commit e8909590 authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

Merge branch 'feat/tid-openconfig-multivendor' into 'develop'

OpenConfig driver multivendor & ACLs

See merge request !104
parents 2cc1f0e9 57fa1503
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
+3 −0
Original line number Diff line number Diff line
@@ -29,6 +29,9 @@ xmltodict==0.12.0
tabulate
ipaddress
macaddress
yattag
pyang
git+https://github.com/robshakir/pyangbind.git
websockets==10.4

# pip's dependency resolver does not take into account installed packages.
+32 −16
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@ import json, logging
from typing import Any, Dict, List, Optional, 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, Link, Location
from common.proto.context_pb2 import ConfigActionEnum, ConfigRule_ACL, Device, DeviceConfig, Link, Location
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
@@ -256,6 +256,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)

@@ -276,20 +277,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
+67 −48
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import anytree, copy, logging, pytz, queue, re, threading
#import lxml.etree as ET
from datetime import datetime, timedelta
@@ -28,7 +29,7 @@ from device.service.driver_api.Exceptions import UnsupportedResourceKeyException
from device.service.driver_api._Driver import _Driver
from device.service.driver_api.AnyTreeTools import TreeNode, get_subnode, set_subnode_value #dump_subtree
#from .Tools import xml_pretty_print, xml_to_dict, xml_to_file
from .templates import ALL_RESOURCE_KEYS, EMPTY_CONFIG, compose_config, get_filter, parse
from .templates import ALL_RESOURCE_KEYS, EMPTY_CONFIG, compose_config, get_filter, parse, cli_compose_config
from .RetryDecorator import retry

DEBUG_MODE = False
@@ -61,6 +62,7 @@ class NetconfSessionHandler:
        self.__username         = settings.get('username')
        self.__password         = settings.get('password')
        self.__vendor           = settings.get('vendor')
        self.__version           = settings.get('version', "1")
        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)
@@ -70,6 +72,7 @@ class NetconfSessionHandler:
        self.__device_params    = settings.get('device_params', {})
        self.__manager_params   = settings.get('manager_params', {})
        self.__nc_params        = settings.get('nc_params', {})
        self.__message_renderer = settings.get('message_renderer','jinja')
        self.__manager : Manager   = None
        self.__candidate_supported = False

@@ -97,6 +100,12 @@ class NetconfSessionHandler:
    @property
    def vendor(self): return self.__vendor
    
    @property
    def version(self): return self.__version
    
    @property
    def message_renderer(self): return self.__message_renderer

    @RETRY_DECORATOR
    def get(self, filter=None, with_defaults=None): # pylint: disable=redefined-builtin
        with self.__lock:
@@ -192,14 +201,20 @@ 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
):
    str_method = 'DeleteConfig' if delete else 'SetConfig'
    #logger.debug('[{:s}] resources = {:s}'.format(str_method, str(resources)))
    results = [None for _ in resources]
    results = []
    if "L2VSI" in resources[0][1] and netconf_handler.vendor == "CISCO":
        #Configure by CLI
        logger.warning("CLI Configuration")
        cli_compose_config(resources, delete=delete, host= netconf_handler._NetconfSessionHandler__address, user=netconf_handler._NetconfSessionHandler__username, passw=netconf_handler._NetconfSessionHandler__password)        
        for i,resource in enumerate(resources):
            results.append(True)
    else:
        for i,resource in enumerate(resources):
            str_resource_name = 'resources[#{:d}]'.format(i)
            try:
@@ -208,22 +223,26 @@ 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, vendor=netconf_handler.vendor)
                str_config_messages = compose_config(                                                                          # get template for configuration
                    resource_key, resource_value, delete=delete, vendor=netconf_handler.vendor, message_renderer=netconf_handler.message_renderer)
                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(
                    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()
            results[i] = True
                        netconf_handler.commit()                                                                               # configuration commit
                
                #results[i] = True
                results.append(True)
            except Exception as e: # pylint: disable=broad-except
                str_operation = 'preparing' if target == 'candidate' else ('deleting' if delete else 'setting')
                msg = '[{:s}] Exception {:s} {:s}: {:s}'
                logger.exception(msg.format(str_method, str_operation, str_resource_name, str(resource)))
            results[i] = e # if validation fails, store the exception
                #results[i] = e # if validation fails, store the exception
                results.append(e)

        if not commit_per_rule:
            try:
+288 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading