From 51178e2e9d4806ab002f8c341940aa904d2215bc Mon Sep 17 00:00:00 2001 From: Mircea Ulinic Date: Tue, 18 Jul 2017 11:59:26 +0000 Subject: [PATCH 1/4] SSH-based driver eggs --- napalm_iosxr/iosxr_ssh.py | 147 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 napalm_iosxr/iosxr_ssh.py diff --git a/napalm_iosxr/iosxr_ssh.py b/napalm_iosxr/iosxr_ssh.py new file mode 100644 index 0000000..913f69c --- /dev/null +++ b/napalm_iosxr/iosxr_ssh.py @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Napalm Automation. All rights reserved. +# +# The contents of this file are 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. + +from __future__ import unicode_literals + +# import stdlib +import re +import copy +import socket + +# import third party lib +from netmiko import ConnectHandler +from netmiko import __version__ as netmiko_version +from netmiko.ssh_exception import NetMikoTimeoutException +from netmiko.ssh_exception import NetMikoAuthenticationException + +# import NAPALM base +import napalm_base.helpers +import napalm_iosxr.constants as C +from napalm_base.base import NetworkDriver +from napalm_base.utils import py23_compat +from napalm_base.exceptions import ConnectionException +from napalm_base.exceptions import MergeConfigException +from napalm_base.exceptions import ReplaceConfigException +from napalm_base.exceptions import CommandTimeoutException +from napalm_base.utils.py23_compat import text_type + + +class IOSXRSSHDriver(NetworkDriver): + ''' + SSH-based driver for IOS-XR. + ''' + + def __init__(self, hostname, username, password, timeout=60, optional_args=None): + self.hostname = hostname + self.username = username + self.password = password + self.timeout = timeout + self.pending_changes = False + self.replace = False + if optional_args is None: + optional_args = {} + self.port = optional_args.get('port', 22) + self.lock_on_connect = optional_args.get('config_lock', False) + # Netmiko possible arguments + netmiko_argument_map = { + 'keepalive': 30, + 'verbose': False, + 'global_delay_factor': 1, + 'use_keys': False, + 'key_file': None, + 'ssh_strict': False, + 'system_host_keys': False, + 'alt_host_keys': False, + 'alt_key_file': '', + 'ssh_config_file': None + } + fields = netmiko_version.split('.') + fields = [int(x) for x in fields] + maj_ver, min_ver, bug_fix = fields + if maj_ver >= 2: + netmiko_argument_map['allow_agent'] = False + elif maj_ver == 1 and min_ver >= 1: + netmiko_argument_map['allow_agent'] = False + # Build dict of any optional Netmiko args + self.netmiko_optional_args = {} + for k, v in netmiko_argument_map.items(): + try: + self.netmiko_optional_args[k] = optional_args[k] + except KeyError: + self.netmiko_optional_args[k] = v + + def open(self): + try: + self.device = ConnectHandler(device_type='cisco_xr', + ip=self.hostname, + port=self.port, + username=self.username, + password=self.password, + **self.netmiko_optional_args) + self.device.timeout = self.timeout + except NetMikoTimeoutException as t_err: + raise ConnectionException(t_err.args[0]) + except NetMikoAuthenticationException as au_err: + raise ConnectionException(au_err.args[0]) + + def close(self): + if hasattr(self.device, 'remote_conn'): + self.device.remote_conn.close() + + def is_alive(self): + null = chr(0) + try: + # Try sending ASCII null byte to maintain + # the connection alive + self.device.send_command(null) + except (socket.error, EOFError): + # If unable to send, we can tell for sure + # that the connection is unusable, + # hence return False. + return { + 'is_alive': False + } + return { + 'is_alive': self.device.remote_conn.transport.is_active() + } + + def _send_command(self, command): + ''' + Helper to send the command and get the output. + ''' + output = self.device.send_command_timing(command, + delay_factor=self.netmiko_optional_args['global_delay_factor'], + max_loops=self.timeout/self.netmiko_optional_args['global_delay_factor']) + # The output has a newline after the command prompt + # and another line with the timestamp. + # e.g.: + # + # Tue Jul 18 11:53:32.372 UTC + # For this reason, we need to strip the first two lines + # and return only what comes after. + return '\n'.join(output.splitlines()[2:]) + + def cli(self, commands): + ''' + Execute raw CLI commands and return the output, + as provided by the device. + ''' + if not isinstance(commands, (list, tuple)): + raise TypeError('Please enter a valid list of commands!') + cli_output = {} + for command in commands: + response = self._send_command(command) + cli_output[command] = response + return cli_output From a22a73b7ebb84257b4d429d5c39ebcabb9a15057 Mon Sep 17 00:00:00 2001 From: Mircea Ulinic Date: Tue, 18 Jul 2017 14:59:57 +0000 Subject: [PATCH 2/4] Configuration side ready --- napalm_iosxr/iosxr_ssh.py | 178 ++++++++++++++++++++++++++++++++++---- 1 file changed, 161 insertions(+), 17 deletions(-) diff --git a/napalm_iosxr/iosxr_ssh.py b/napalm_iosxr/iosxr_ssh.py index 913f69c..f8bd5a5 100644 --- a/napalm_iosxr/iosxr_ssh.py +++ b/napalm_iosxr/iosxr_ssh.py @@ -17,8 +17,9 @@ # import stdlib import re -import copy import socket +import difflib +import logging # import third party lib from netmiko import ConnectHandler @@ -35,8 +36,13 @@ from napalm_base.exceptions import MergeConfigException from napalm_base.exceptions import ReplaceConfigException from napalm_base.exceptions import CommandTimeoutException +from napalm_base.exceptions import LockError +from napalm_base.exceptions import UnlockError from napalm_base.utils.py23_compat import text_type +logging.basicConfig(filename='iosxr.log', level=logging.DEBUG) +log = logging.getLogger(__file__) + class IOSXRSSHDriver(NetworkDriver): ''' @@ -81,8 +87,160 @@ def __init__(self, hostname, username, password, timeout=60, optional_args=None) self.netmiko_optional_args[k] = optional_args[k] except KeyError: self.netmiko_optional_args[k] = v + log.debug('Creating a new instance of the NAPALM IOS-XR SSH driver') + log.debug('Connecting to %s:%d as %s', self.hostname, self.port, self.username) + log.debug('Optional args:') + log.debug(self.netmiko_optional_args) + self._in_config_mode = False + + def _send_command(self, command, configuration=False): + ''' + Helper to send the command and get the output. + ''' + if self._in_config_mode and not configuration: + # When the driver is in config mode, + # you can still execute arbitrary commands, not configuration-related. + command = 'do {base_cmd}'.format(base_cmd=command) + log.debug('Sending command: %s', command) + output = self.device.send_command_timing(command, + delay_factor=self.netmiko_optional_args['global_delay_factor'], + max_loops=self.timeout/self.netmiko_optional_args['global_delay_factor']) + log.debug('Received the output:') + log.debug(output) + # The output has a newline after the command prompt + # and another line with the timestamp. + # e.g.: + # + # Tue Jul 18 11:53:32.372 UTC + # For this reason, we need to strip the first two lines + # and return only what comes after. + return '\n'.join(output.splitlines()[2:]) + + def lock(self): + ''' + Lock the configuration DB. + ''' + if self._in_config_mode: + log.info('Already in configuration mode') + return + log.debug('Trying to lock the config DB') + cfg_lock_out = self._send_command('configure exclusive') + # Current Configuration Session Line User Date Lock + # 00000011-000a7139-0000008c /dev/vty0 username Tue Jul 18 11:02:16 2017 * + # Can not enter exclusive mode. The Configuration Namespace is locked by another agent. + cfg_lock_lines = cfg_lock_out.splitlines() + if not cfg_lock_lines: + # Nothing back, means everything was fine, config lock succeeded. + self._in_config_mode = True + return + if 'Can not enter exclusive mode' in cfg_lock_lines[-1] or\ + 'Cannot enter exclusive mode' in cfg_lock_lines[-1]: # on the bloody IOS-XR >= 6.x + rgx = '([0-9a-z-]+)\s+([a-z0-9\/]+)\s+(\w+)\s+(.*)\s+(.?)' # a beautiful regex + # to extract the timestamp and the username locking the config database + lock_user = 'unknown' + lock_ts = 'unknown' + for line in cfg_lock_lines: + rgx_res = re.search(rgx, line, re.I) + if not rgx_res: + continue + if rgx_res.group(5) != '*': + continue + lock_user = rgx_res.group(3) + lock_ts = rgx_res.group(4) + lock_msg = 'Configuration DB locked by {usr} since {ts}'.format(usr=lock_user, + ts=lock_ts) + log.error(lock_msg) + raise LockError(lock_msg) + self._in_config_mode = True + + def _load_config(self, filename=None, config=None, replace=False): + self.replace = replace + err_class = ReplaceConfigException if replace else MergeConfigException + if not self._in_config_mode: + # Enter in config mode and lock the DB. + self.lock() + if filename: + log.debug('Reading configuration from %s', filename) + with open(filename, 'r') as cfg_file: + config = cfg_file.read() + log.debug('Loading configuration') + log.debug(config) + if not config: + raise err_class('Please provide a valid config to load.') + self.pending_changes = True + for line in config.splitlines(): + out = self._send_command(line, configuration=True) + if '''Invalid input detected at '^' marker''' in out: + log.error('Invalid configuration %s', line) + log.error(out) + log.error('Discarding the candidate configuration') + self.discard_config() # rollback on error. + raise err_class('Invalid configuration: {}'.format(line)) + + def load_merge_candidate(self, filename=None, config=None): + ''' + Load the configuration changes in the candidate configuration and merge. + ''' + return self._load_config(filename=filename, config=config) + + def load_replace_candidate(self, filename=None, config=None): + ''' + Load the configuration changes in the candidate configuration and replace. + ''' + return self._load_config(filename=filename, config=config, replace=True) + + def compare_config(self): + ''' + Compare the candidate with the running configuration. + ''' + if self._in_config_mode and self.pending_changes: + show_candidate = self._send_command('show configuration merge', configuration=True) + show_running = self._send_command('show running-config', configuration=True) + diff = difflib.unified_diff(show_running.splitlines(1)[2:-2], + show_candidate.splitlines(1)[2:-2]) + return ''.join([line.replace('\r', '') for line in diff]) + return '' + + def discard_config(self): + ''' + Discard the configuration changes made in the candidate. + ''' + log.debug('Discarding the candidate config') + if self._in_config_mode: + discarding = self._send_command('abort', configuration=True) + # When executing abort, it also quites the configuration mode. + self.unlock() + + def commit_config(self): + ''' + Commit the configuration changes. + ''' + log.debug('Committing') + if self._in_config_mode and self.pending_changes: + commit_cmd = 'commit {replace} save-running filename disk0:rollback-0'.format( + replace='replace' if self.replace else '') + committing = self._send_command(commit_cmd, configuration=True) + if 'This could be a few minutes if your config is large. Confirm?' in committing: + log.debug('Confirming file copy') + confirming = self._send_command('\n', configuration=True) + log.debug('Exiting config mode') + exiting = self._send_command('exit', configuration=True) + self.unlock() + + def unlock(self): + ''' + Unlock the configuration DB. + ''' + log.debug('Unlocking the config DB') + self.pending_changes = False + self.replace = False + if not self.lock_on_connect: + self._in_config_mode = False def open(self): + ''' + Open the connection with the device. + ''' try: self.device = ConnectHandler(device_type='cisco_xr', ip=self.hostname, @@ -91,6 +249,8 @@ def open(self): password=self.password, **self.netmiko_optional_args) self.device.timeout = self.timeout + if self.lock_on_connect: + self.lock() except NetMikoTimeoutException as t_err: raise ConnectionException(t_err.args[0]) except NetMikoAuthenticationException as au_err: @@ -117,22 +277,6 @@ def is_alive(self): 'is_alive': self.device.remote_conn.transport.is_active() } - def _send_command(self, command): - ''' - Helper to send the command and get the output. - ''' - output = self.device.send_command_timing(command, - delay_factor=self.netmiko_optional_args['global_delay_factor'], - max_loops=self.timeout/self.netmiko_optional_args['global_delay_factor']) - # The output has a newline after the command prompt - # and another line with the timestamp. - # e.g.: - # - # Tue Jul 18 11:53:32.372 UTC - # For this reason, we need to strip the first two lines - # and return only what comes after. - return '\n'.join(output.splitlines()[2:]) - def cli(self, commands): ''' Execute raw CLI commands and return the output, From b15af5212a60ec572141cd79e0bbee12185e4da5 Mon Sep 17 00:00:00 2001 From: Mircea Ulinic Date: Tue, 18 Jul 2017 15:46:30 +0000 Subject: [PATCH 3/4] Prepare iosxr_ssh subpackage --- napalm_iosxr_ssh/__init__.py | 31 ++++++++++++++++ napalm_iosxr_ssh/constants.py | 23 ++++++++++++ .../iosxr_ssh.py | 0 .../templates/delete_ntp_peers.j2 | 3 ++ .../templates/delete_ntp_servers.j2 | 3 ++ napalm_iosxr_ssh/templates/delete_probes.j2 | 16 ++++++++ .../templates/delete_snmp_config.j2 | 14 +++++++ napalm_iosxr_ssh/templates/delete_users.j2 | 13 +++++++ napalm_iosxr_ssh/templates/schedule_probes.j2 | 12 ++++++ napalm_iosxr_ssh/templates/set_hostname.j2 | 1 + napalm_iosxr_ssh/templates/set_ntp_peers.j2 | 3 ++ napalm_iosxr_ssh/templates/set_ntp_servers.j2 | 3 ++ napalm_iosxr_ssh/templates/set_probes.j2 | 37 +++++++++++++++++++ napalm_iosxr_ssh/templates/set_users.j2 | 16 ++++++++ napalm_iosxr_ssh/templates/snmp_config.j2 | 23 ++++++++++++ 15 files changed, 198 insertions(+) create mode 100644 napalm_iosxr_ssh/__init__.py create mode 100644 napalm_iosxr_ssh/constants.py rename {napalm_iosxr => napalm_iosxr_ssh}/iosxr_ssh.py (100%) create mode 100644 napalm_iosxr_ssh/templates/delete_ntp_peers.j2 create mode 100644 napalm_iosxr_ssh/templates/delete_ntp_servers.j2 create mode 100644 napalm_iosxr_ssh/templates/delete_probes.j2 create mode 100644 napalm_iosxr_ssh/templates/delete_snmp_config.j2 create mode 100644 napalm_iosxr_ssh/templates/delete_users.j2 create mode 100644 napalm_iosxr_ssh/templates/schedule_probes.j2 create mode 100644 napalm_iosxr_ssh/templates/set_hostname.j2 create mode 100644 napalm_iosxr_ssh/templates/set_ntp_peers.j2 create mode 100644 napalm_iosxr_ssh/templates/set_ntp_servers.j2 create mode 100644 napalm_iosxr_ssh/templates/set_probes.j2 create mode 100644 napalm_iosxr_ssh/templates/set_users.j2 create mode 100644 napalm_iosxr_ssh/templates/snmp_config.j2 diff --git a/napalm_iosxr_ssh/__init__.py b/napalm_iosxr_ssh/__init__.py new file mode 100644 index 0000000..c249995 --- /dev/null +++ b/napalm_iosxr_ssh/__init__.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Napalm Automation. All rights reserved. +# +# The contents of this file are 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. + +''' +napalm-iosxr-ssh package. +''' + +# Import stdlib +import pkg_resources + +# Import local modules +from napalm_iosxr_ssh.iosxr_ssh import IOSXRSSHDriver # noqa + +__all__ = ('IOSXRSSHDriver',) + +try: + __version__ = pkg_resources.get_distribution('napalm-iosxr').version +except pkg_resources.DistributionNotFound: + __version__ = "Not installed" diff --git a/napalm_iosxr_ssh/constants.py b/napalm_iosxr_ssh/constants.py new file mode 100644 index 0000000..64111a5 --- /dev/null +++ b/napalm_iosxr_ssh/constants.py @@ -0,0 +1,23 @@ +"""Constants for the IOS-XR driver.""" + +from __future__ import unicode_literals + +from napalm_base.constants import * # noqa + +SR_638170159_SOLVED = False +# this flag says if the Cisco TAC SR 638170159 +# has been solved +# +# "XML Agent Does not retrieve correct BGP routes data" +# is a weird bug reported on 2016-02-22 22:54:21 +# briefly, all BGP routes are handled by the XML agent +# in such a way they have the following details: +# +# - all neighbors are 0.0.0.0 +# - all routes are 0.0.0.0/0 +# - all RD = 0000000000000000 +# +# because of this none of the data retrieved +# from the BGP oper is usable thus has direct implications +# in our implementation of `get_route_to` when retrieving +# the BGP protocol specific attributes. diff --git a/napalm_iosxr/iosxr_ssh.py b/napalm_iosxr_ssh/iosxr_ssh.py similarity index 100% rename from napalm_iosxr/iosxr_ssh.py rename to napalm_iosxr_ssh/iosxr_ssh.py diff --git a/napalm_iosxr_ssh/templates/delete_ntp_peers.j2 b/napalm_iosxr_ssh/templates/delete_ntp_peers.j2 new file mode 100644 index 0000000..cee41bf --- /dev/null +++ b/napalm_iosxr_ssh/templates/delete_ntp_peers.j2 @@ -0,0 +1,3 @@ +{% for peer in peers %} +no ntp peer {{peer}} +{% endfor %} diff --git a/napalm_iosxr_ssh/templates/delete_ntp_servers.j2 b/napalm_iosxr_ssh/templates/delete_ntp_servers.j2 new file mode 100644 index 0000000..38accdf --- /dev/null +++ b/napalm_iosxr_ssh/templates/delete_ntp_servers.j2 @@ -0,0 +1,3 @@ +{% for server in servers %} +no ntp server {{server}} +{% endfor %} diff --git a/napalm_iosxr_ssh/templates/delete_probes.j2 b/napalm_iosxr_ssh/templates/delete_probes.j2 new file mode 100644 index 0000000..fbc9e7b --- /dev/null +++ b/napalm_iosxr_ssh/templates/delete_probes.j2 @@ -0,0 +1,16 @@ +ipsla + {% set probe_id = 0 %} + {% for probe_name, probe_test in probes.iteritems() %} + {% for test_name, test_details in probe_test.iteritems() %} + no schedule operation {{probe_id + loop.index}} + {% endfor %} + {% set probe_id = probe_id + probe_test.keys()|length %} + {% endfor %} + {% set probe_id = 0 %} + {% for probe_name, probe_test in probes.iteritems() %} + {% for test_name, test_details in probe_test.iteritems() %} + no operation {{probe_id + loop.index}} + {% endfor %} + {% set probe_id = probe_id + probe_test.keys()|length %} + {% endfor %} +! diff --git a/napalm_iosxr_ssh/templates/delete_snmp_config.j2 b/napalm_iosxr_ssh/templates/delete_snmp_config.j2 new file mode 100644 index 0000000..0543dfe --- /dev/null +++ b/napalm_iosxr_ssh/templates/delete_snmp_config.j2 @@ -0,0 +1,14 @@ +{% if (location is defined) and location %} +no snmp-server location "{{location}}" +{% endif %} +{% if (contact is defined) and contact %} +no snmp-server contact "{{contact}}" +{% endif %} +{% if (chassis_id is defined) and chassis_id %} +no snmp-server chassis-id "{{chassis_id}}" +{% endif %} +{% if (community is defined) and community %} +{% for comm_name, comm_details in community.iteritems() %} +no community {{comm_name}} +{% endfor %} +{% endif %} diff --git a/napalm_iosxr_ssh/templates/delete_users.j2 b/napalm_iosxr_ssh/templates/delete_users.j2 new file mode 100644 index 0000000..0dab7e1 --- /dev/null +++ b/napalm_iosxr_ssh/templates/delete_users.j2 @@ -0,0 +1,13 @@ +{%- for user_name, user_details in users.iteritems() %} +{%- if user_details %} +username {{user_name}} +{%- endif %} +{%- if user_details.get('password') %} + no password +{%- endif %} +{%- if user_details.get('level') %} + no group +{%- endif %} +{%- else %} +no username {{user_name}} +{%- endfor %} diff --git a/napalm_iosxr_ssh/templates/schedule_probes.j2 b/napalm_iosxr_ssh/templates/schedule_probes.j2 new file mode 100644 index 0000000..d5cb14e --- /dev/null +++ b/napalm_iosxr_ssh/templates/schedule_probes.j2 @@ -0,0 +1,12 @@ +ipsla + {% set probe_id = 0 %} + {% for probe_name, probe_test in probes.iteritems() %} + {% for test_name, test_details in probe_test.iteritems() %} + schedule operation {{probe_id + loop.index}} + start-time now + life forever + ! + {% endfor %} + {% set probe_id = probe_id + probe_test.keys()|length %} + {% endfor %} +! diff --git a/napalm_iosxr_ssh/templates/set_hostname.j2 b/napalm_iosxr_ssh/templates/set_hostname.j2 new file mode 100644 index 0000000..5425366 --- /dev/null +++ b/napalm_iosxr_ssh/templates/set_hostname.j2 @@ -0,0 +1 @@ +hostname {{hostname}} diff --git a/napalm_iosxr_ssh/templates/set_ntp_peers.j2 b/napalm_iosxr_ssh/templates/set_ntp_peers.j2 new file mode 100644 index 0000000..1dd886b --- /dev/null +++ b/napalm_iosxr_ssh/templates/set_ntp_peers.j2 @@ -0,0 +1,3 @@ +{% for peer in peers %} +ntp peer {{peer}} +{% endfor %} diff --git a/napalm_iosxr_ssh/templates/set_ntp_servers.j2 b/napalm_iosxr_ssh/templates/set_ntp_servers.j2 new file mode 100644 index 0000000..1cebc15 --- /dev/null +++ b/napalm_iosxr_ssh/templates/set_ntp_servers.j2 @@ -0,0 +1,3 @@ +{% for server in servers %} +ntp server {{server}} +{% endfor %} diff --git a/napalm_iosxr_ssh/templates/set_probes.j2 b/napalm_iosxr_ssh/templates/set_probes.j2 new file mode 100644 index 0000000..51a2146 --- /dev/null +++ b/napalm_iosxr_ssh/templates/set_probes.j2 @@ -0,0 +1,37 @@ +ipsla + {% set probe_id = 0 %} + {% for probe_name, probe_test in probes.iteritems() %} + {% for test_name, test_details in probe_test.iteritems() %} + no schedule operation {{probe_id + loop.index}} + {% endfor %} + {% set probe_id = probe_id + probe_test.keys()|length %} + {% endfor %} + {% set probe_id = 0 %} + {% for probe_name, probe_test in probes.iteritems() %} + {% for test_name, test_details in probe_test.iteritems() %} + operation {{probe_id + loop.index}} + {% if test_details.probe_type is defined %} + type {{test_details.probe_type|replace('ping', 'echo')|replace('-', ' ')}} + {% else %} + type icmp echo + {% endif %} + tag {{test_name}} + history + lives 1 + filter all + buckets {{test_details.probe_count}} + ! + {% if test_details.source is defined %} + source address {{test_details.source}} + {% endif %} + destination address {{test_details.target}} + {% if test_details.test_interval is defined %} + timeout {{(test_details.test_interval - 1) * 1000}} + frequency {{test_details.test_interval}} + {% endif %} + ! + {% endfor %} + {% set probe_id = probe_id + probe_test.keys()|length %} + ! + {% endfor %} +! diff --git a/napalm_iosxr_ssh/templates/set_users.j2 b/napalm_iosxr_ssh/templates/set_users.j2 new file mode 100644 index 0000000..5cc43d1 --- /dev/null +++ b/napalm_iosxr_ssh/templates/set_users.j2 @@ -0,0 +1,16 @@ +{%- for user_name, user_details in users.items() %} +username {{user_name}} + {% set user_level = user_details.level|default(1) %} + {%- if user_level == 15 %} + group root-system + {%- elif user_level == 5 %} + group operator + {%- elif user_level == 2 %} + group serviceadmin + {%- elif user_level == 1 %} + group sysadmin + {% endif %} + {%- if user_details.get('password') %} + password {{user_details.password}} + {%- endif %} +{%- endfor %} diff --git a/napalm_iosxr_ssh/templates/snmp_config.j2 b/napalm_iosxr_ssh/templates/snmp_config.j2 new file mode 100644 index 0000000..4ea4d7d --- /dev/null +++ b/napalm_iosxr_ssh/templates/snmp_config.j2 @@ -0,0 +1,23 @@ +{% if (location is defined) and location %} +snmp-server location "{{location}}" +{% endif %} +{% if (contact is defined) and contact %} +snmp-server contact "{{contact}}" +{% endif %} +{% if (chassis_id is defined) and chassis_id %} +snmp-server chassis-id "{{chassis_id}}" +{% endif %} +{% if (community is defined) and community %} +{% for comm_name, comm_details in community.iteritems() %} +{% if (comm_details is defined) and comm_details %} +{% if (comm_details.get('mode') is defined) and comm_details.get('mode') == 'rw' %} +snmp-server community {{comm_name}} RW +{% else %} +snmp-server community {{comm_name}} RO +{% endif %} +{% else %} +snmp-server community {{comm_name}} RO +{% endif %} +{% endfor %} +{% endif %} + From d87f5561d2e0443a378548767573825bef24f16c Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 23 Aug 2017 17:36:12 +0100 Subject: [PATCH 4/4] Added interfaces parsing --- napalm_iosxr_ssh/iosxr_ssh.py | 22 ++++++++++++++++++ .../cisco_xr_show_interfaces.tpl | 23 +++++++++++++++++++ .../cisco_xr_show_interfaces_admin.tpl | 23 +++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 napalm_iosxr_ssh/utils/textfsm_templates/cisco_xr_show_interfaces.tpl create mode 100644 napalm_iosxr_ssh/utils/textfsm_templates/cisco_xr_show_interfaces_admin.tpl diff --git a/napalm_iosxr_ssh/iosxr_ssh.py b/napalm_iosxr_ssh/iosxr_ssh.py index f8bd5a5..9d42a64 100644 --- a/napalm_iosxr_ssh/iosxr_ssh.py +++ b/napalm_iosxr_ssh/iosxr_ssh.py @@ -289,3 +289,25 @@ def cli(self, commands): response = self._send_command(command) cli_output[command] = response return cli_output + + def get_interfaces(self): + + interfaces = {} + + INTERFACE_DEFAULTS = { + 'is_enabled': False, + 'is_up': False, + 'mac_address': u'', + 'description': u'', + 'speed': -1, + 'last_flapped': -1.0 + } + + interfaces_command = 'show interfaces' + + interfaces_ssh_reply = self._send_command(interfaces_command) + + t = napalm_base.helpers.textfsm_extractor(self, "cisco_xr_show_interfaces", interfaces_ssh_reply) + t = napalm_base.helpers.textfsm_extractor(self, "cisco_xr_show_interfaces_admin", interfaces_ssh_reply) + + return interfaces diff --git a/napalm_iosxr_ssh/utils/textfsm_templates/cisco_xr_show_interfaces.tpl b/napalm_iosxr_ssh/utils/textfsm_templates/cisco_xr_show_interfaces.tpl new file mode 100644 index 0000000..fa55e5b --- /dev/null +++ b/napalm_iosxr_ssh/utils/textfsm_templates/cisco_xr_show_interfaces.tpl @@ -0,0 +1,23 @@ +Value Required INTERFACE (\S+) +Value LINK_STATUS (\w+) +Value ADMIN_STATE (\S+) +Value HARDWARE_TYPE (\w+) +Value ADDRESS ([a-zA-Z0-9]+.[a-zA-Z0-9]+.[a-zA-Z0-9]+) +Value BIA ([a-zA-Z0-9]+.[a-zA-Z0-9]+.[a-zA-Z0-9]+) +Value DESCRIPTION (.*) +Value IP_ADDRESS (\d+\.\d+\.\d+\.\d+\/\d+) +Value MTU (\d+) +Value DUPLEX (.+?) +Value SPEED (.+?) +Value BANDWIDTH (\d+\s+\w+) +Value ENCAPSULATION (\w+) + +Start + ^${INTERFACE}\sis\s+${LINK_STATUS},\s+line\sprotocol\sis\s+${ADMIN_STATE} + ^\s+Hardware\s+is\s+${HARDWARE_TYPE}(\s+)?(Ethernet)?(,)?(\s+address\s+is\s+${ADDRESS}\s+\(bia\s+${BIA})? + ^\s+Description:\s+${DESCRIPTION} + ^\s+Internet\s+Address\s+is\s+${IP_ADDRESS} + ^\s+MTU\s+${MTU}.*BW\s+${BANDWIDTH} + ^\s+${DUPLEX}, ${SPEED},.+link + ^\s+Encapsulation\s+${ENCAPSULATION} + ^\s+Last -> Record \ No newline at end of file diff --git a/napalm_iosxr_ssh/utils/textfsm_templates/cisco_xr_show_interfaces_admin.tpl b/napalm_iosxr_ssh/utils/textfsm_templates/cisco_xr_show_interfaces_admin.tpl new file mode 100644 index 0000000..5ca715a --- /dev/null +++ b/napalm_iosxr_ssh/utils/textfsm_templates/cisco_xr_show_interfaces_admin.tpl @@ -0,0 +1,23 @@ +Value Required INTERFACE (\S+) +Value LINK_STATUS (\w+) +Value ADMIN_STATE (\S+) +Value HARDWARE_TYPE (\w+) +Value ADDRESS ([a-zA-Z0-9]+.[a-zA-Z0-9]+.[a-zA-Z0-9]+) +Value BIA ([a-zA-Z0-9]+.[a-zA-Z0-9]+.[a-zA-Z0-9]+) +Value DESCRIPTION (.*) +Value IP_ADDRESS (\d+\.\d+\.\d+\.\d+\/\d+) +Value MTU (\d+) +Value DUPLEX (.+?) +Value SPEED (.+?) +Value BANDWIDTH (\d+\s+\w+) +Value ENCAPSULATION (\w+) + +Start + ^${INTERFACE}\sis.+\s${LINK_STATUS},\s+line\sprotocol\sis.+\s+${ADMIN_STATE} + ^\s+Hardware\s+is\s+${HARDWARE_TYPE}(\s+)?(Ethernet)?(,)?(\s+address\s+is\s+${ADDRESS}\s+\(bia\s+${BIA})? + ^\s+Description:\s+${DESCRIPTION} + ^\s+Internet\s+Address\s+is\s+${IP_ADDRESS} + ^\s+MTU\s+${MTU}.*BW\s+${BANDWIDTH} + ^\s+${DUPLEX}, ${SPEED},.+link + ^\s+Encapsulation\s+${ENCAPSULATION} + ^\s+Last -> Record \ No newline at end of file