Loading src/device/Dockerfile +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading src/device/requirements.in +3 −0 Original line number Diff line number Diff line Loading @@ -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. Loading src/device/service/Tools.py +32 −16 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading @@ -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 Loading src/device/service/drivers/openconfig/OpenConfigDriver.py +67 −48 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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) Loading @@ -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 Loading Loading @@ -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: Loading Loading @@ -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: Loading @@ -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: Loading src/device/service/drivers/openconfig/templates/ACL/ACL_multivendor.py 0 → 100755 +288 −0 File added.Preview size limit exceeded, changes collapsed. Show changes Loading
src/device/Dockerfile +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading
src/device/requirements.in +3 −0 Original line number Diff line number Diff line Loading @@ -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. Loading
src/device/service/Tools.py +32 −16 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading @@ -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 Loading
src/device/service/drivers/openconfig/OpenConfigDriver.py +67 −48 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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) Loading @@ -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 Loading Loading @@ -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: Loading Loading @@ -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: Loading @@ -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: Loading
src/device/service/drivers/openconfig/templates/ACL/ACL_multivendor.py 0 → 100755 +288 −0 File added.Preview size limit exceeded, changes collapsed. Show changes