From cd4ea25c0f344126024df8b661eaf4469a7c2c23 Mon Sep 17 00:00:00 2001 From: Taarini Sarath Chander Date: Fri, 28 Feb 2025 13:18:48 -0500 Subject: [PATCH 1/2] Releasing v25.2 --- Makefile | 2 +- docs/changelog/2025/february.rst | 48 +++++ docs/changelog/2025/january.rst | 4 +- docs/changelog/index.rst | 1 + docs/changelog_plugins/2025/february.rst | 36 ++++ docs/changelog_plugins/2025/january.rst | 4 +- docs/changelog_plugins/index.rst | 1 + setup.py | 2 +- src/unicon/plugins/__init__.py | 2 +- src/unicon/plugins/generic/patterns.py | 4 +- .../plugins/generic/service_implementation.py | 3 +- src/unicon/plugins/generic/settings.py | 1 + src/unicon/plugins/generic/statements.py | 13 +- src/unicon/plugins/iosxe/patterns.py | 6 +- src/unicon/plugins/iosxe/settings.py | 1 + src/unicon/plugins/iosxe/statements.py | 5 +- src/unicon/plugins/iosxe/vpagent/__init__.py | 11 + .../iosxe/vpagent/connection_provider.py | 88 ++++++++ src/unicon/plugins/iosxe/vpagent/settings.py | 9 + src/unicon/plugins/iosxr/settings.py | 2 + src/unicon/plugins/staros/patterns.py | 4 +- .../mock_data/iosxe/iosxe_mock_data.yaml | 13 ++ .../iosxe/iosxe_mock_data_vpagent.yaml | 200 ++++++++++++++++++ .../mock_data/staros/staros_mock_data.yaml | 15 +- .../plugins/tests/test_plugin_generic.py | 41 ++++ src/unicon/plugins/tests/test_plugin_iosxe.py | 23 ++ .../plugins/tests/test_plugin_staros.py | 17 +- src/unicon/plugins/utils.py | 31 ++- 28 files changed, 552 insertions(+), 35 deletions(-) create mode 100644 docs/changelog/2025/february.rst create mode 100644 docs/changelog_plugins/2025/february.rst create mode 100644 src/unicon/plugins/iosxe/vpagent/__init__.py create mode 100644 src/unicon/plugins/iosxe/vpagent/connection_provider.py create mode 100644 src/unicon/plugins/iosxe/vpagent/settings.py create mode 100644 src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_vpagent.yaml diff --git a/Makefile b/Makefile index 0dfc069b..6537e95a 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ PYPIREPO = pypitest DEPENDENCIES = robotframework pyyaml dill coverage Sphinx \ sphinxcontrib-napoleon sphinxcontrib-mockautodoc \ - sphinx-rtd-theme asyncssh PrettyTable "cryptography>=44.0" + sphinx-rtd-theme asyncssh PrettyTable "cryptography>=43.0" .PHONY: clean package distribute develop undevelop help devnet\ diff --git a/docs/changelog/2025/february.rst b/docs/changelog/2025/february.rst new file mode 100644 index 00000000..e158f30a --- /dev/null +++ b/docs/changelog/2025/february.rst @@ -0,0 +1,48 @@ +February 2025 +========== + +February 25 - Unicon v25.2 +------------------------ + + + +.. csv-table:: Module Versions + :header: "Modules", "Versions" + + ``unicon.plugins``, v25.2 + ``unicon``, v25.2 + + + + +Changelogs +^^^^^^^^^^ +-------------------------------------------------------------------------------- + Fix +-------------------------------------------------------------------------------- + +* router.connection_provider + * Modified disconnect + * Added sendline('exit') on disconnect + + +-------------------------------------------------------------------------------- + Fix +-------------------------------------------------------------------------------- + +* generic + * updated exception for Recover device using golden image if reload is failed. + + +-------------------------------------------------------------------------------- + New +-------------------------------------------------------------------------------- + +* iosxe/vpagent + * Add connection provider for vpagent + +* iosxe + * Added to Configure Error Patterns + * Added the regex to match error pattern "127.0 / 255.0 is an invalid network." + + diff --git a/docs/changelog/2025/january.rst b/docs/changelog/2025/january.rst index 8c1ef435..c9c0b793 100644 --- a/docs/changelog/2025/january.rst +++ b/docs/changelog/2025/january.rst @@ -56,6 +56,4 @@ Changelogs * Added below config error patterns * % VLAN [] already in use * Added below config error patterns - * % VNI is either already in use or exceeds the maximum allowable VNIs. - - + * % VNI is either already in use or exceeds the maximum allowable VNIs. \ No newline at end of file diff --git a/docs/changelog/index.rst b/docs/changelog/index.rst index a0fa018b..f8818805 100644 --- a/docs/changelog/index.rst +++ b/docs/changelog/index.rst @@ -4,6 +4,7 @@ Changelog .. toctree:: :maxdepth: 2 + 2025/february 2025/january 2024/november 2024/october diff --git a/docs/changelog_plugins/2025/february.rst b/docs/changelog_plugins/2025/february.rst new file mode 100644 index 00000000..abc6f684 --- /dev/null +++ b/docs/changelog_plugins/2025/february.rst @@ -0,0 +1,36 @@ +February 2025 +========== + +February 25 - Unicon.Plugins v25.2 +------------------------ + + + +.. csv-table:: Module Versions + :header: "Modules", "Versions" + + ``unicon.plugins``, v25.2 + ``unicon``, v25.2 + + + + +Changelogs +^^^^^^^^^^ +-------------------------------------------------------------------------------- + Fix +-------------------------------------------------------------------------------- + +* generic + * Updates to setup patterns + * Update connection refused handler to clear line after max count + * Update token discovery to use rv1 parsers + * Update token discovery to handle standby locked devices + +* staros + * Update prompt pattern + +* iosxe + * Updated the enable, disable and maintenance states to support `(unlicensed)` prompt + + diff --git a/docs/changelog_plugins/2025/january.rst b/docs/changelog_plugins/2025/january.rst index e180b782..204d5a75 100644 --- a/docs/changelog_plugins/2025/january.rst +++ b/docs/changelog_plugins/2025/january.rst @@ -37,6 +37,4 @@ Changelogs * update syslog message pattern * unicon.plugins - * Fix syntax warning - - + * Fix syntax warning \ No newline at end of file diff --git a/docs/changelog_plugins/index.rst b/docs/changelog_plugins/index.rst index e663d67b..30cf5fe5 100644 --- a/docs/changelog_plugins/index.rst +++ b/docs/changelog_plugins/index.rst @@ -4,6 +4,7 @@ Plugins Changelog .. toctree:: :maxdepth: 2 + 2025/february 2025/january 2024/november 2024/october diff --git a/setup.py b/setup.py index 26355090..f05dcf46 100755 --- a/setup.py +++ b/setup.py @@ -51,7 +51,7 @@ def version_info(*paths): install_requires = ['unicon {range}'.format(range = version_range), 'pyyaml', 'PrettyTable', - 'cryptography>=44.0'] + 'cryptography>=43.0'] # launch setup setup( diff --git a/src/unicon/plugins/__init__.py b/src/unicon/plugins/__init__.py index 2c4f89c7..83f3d5d2 100644 --- a/src/unicon/plugins/__init__.py +++ b/src/unicon/plugins/__init__.py @@ -1,4 +1,4 @@ -__version__ = '25.1' +__version__ = '25.2' supported_chassis = [ 'single_rp', diff --git a/src/unicon/plugins/generic/patterns.py b/src/unicon/plugins/generic/patterns.py index bc9099ce..e5177b26 100644 --- a/src/unicon/plugins/generic/patterns.py +++ b/src/unicon/plugins/generic/patterns.py @@ -51,8 +51,8 @@ def __init__(self): self.press_ctrlx = r"^(.*?)Press Ctrl\+x to Exit the session" self.connected = r'^(.*?)Connected.' - self.enter_basic_mgmt_setup = r'Would you like to enter basic management setup\? \[yes/no\]:' - self.kerberos_no_realm = r'^(.*)Kerberos:\s*No default realm defined for Kerberos!' + self.enter_basic_mgmt_setup = r'Would you like to enter basic management setup\? \[yes/no\]:\s*$' + self.kerberos_no_realm = r'^(.*)Kerberos:\s*No default realm defined for Kerberos!\s*$' self.passphrase_prompt = r'^.*Enter passphrase for key .*?:\s*?' diff --git a/src/unicon/plugins/generic/service_implementation.py b/src/unicon/plugins/generic/service_implementation.py index a216f867..77700048 100644 --- a/src/unicon/plugins/generic/service_implementation.py +++ b/src/unicon/plugins/generic/service_implementation.py @@ -1172,7 +1172,8 @@ def call_service(self, except Exception as e: if hasattr(con.device, 'clean') and hasattr(con.device.clean, 'device_recovery') and\ con.device.clean.device_recovery.get('golden_image'): - con.log.error(f'Reload failed booting device using golden image: {con.device.clean.device_recovery["golden_image"]}') + con.log.exception(f"Reload failed to install with file: {getattr(con.device.clean, 'images', [None])[0]}") + con.log.info(f'Booting the device using golden_image.') con.device.api.device_recovery_boot(golden_image=con.device.clean.device_recovery['golden_image']) con.log.info('Successfully booted the device using golden_image.') raise diff --git a/src/unicon/plugins/generic/settings.py b/src/unicon/plugins/generic/settings.py index 2bb32dce..1d63ef06 100644 --- a/src/unicon/plugins/generic/settings.py +++ b/src/unicon/plugins/generic/settings.py @@ -55,6 +55,7 @@ def __init__(self): self.CONSOLE_TIMEOUT = 60 self.BOOT_TIMEOUT = 600 self.MAX_BOOT_ATTEMPTS = 3 + self.CONNECTION_REFUSED_MAX_COUNT = 3 # Temporary enable secret used during setup # this is used if no password is available diff --git a/src/unicon/plugins/generic/statements.py b/src/unicon/plugins/generic/statements.py index ee3f4d74..b61354f8 100644 --- a/src/unicon/plugins/generic/statements.py +++ b/src/unicon/plugins/generic/statements.py @@ -43,13 +43,16 @@ def terminal_position_handler(spawn, session, context): spawn.send('\x1b[0;200R') -def connection_refused_handler(spawn): +def connection_refused_handler(spawn, context): """ handles connection refused scenarios """ - if spawn.device: - spawn.device.api.execute_clear_line() - spawn.device.connect() - return + context.setdefault('connection_refused_count', 0) + context['connection_refused_count'] += 1 + if context.get('connection_refused_count') < spawn.settings.CONNECTION_REFUSED_MAX_COUNT: + if spawn.device: + spawn.device.api.execute_clear_line() + spawn.device.connect() + return raise Exception('Connection refused to device %s' % (str(spawn))) diff --git a/src/unicon/plugins/iosxe/patterns.py b/src/unicon/plugins/iosxe/patterns.py index 5258d44a..01d6eafc 100644 --- a/src/unicon/plugins/iosxe/patterns.py +++ b/src/unicon/plugins/iosxe/patterns.py @@ -23,11 +23,11 @@ def __init__(self): self.want_continue_confirm = r'.*Do you want to continue\?\s*\[confirm]\s*$' self.want_continue_yes = r'.*Do you want to continue\?\s*\[y/n]\?\s*\[yes]:\s*$' self.disable_prompt = \ - r'^(.*?)(WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(standby\))?(-stby)?(-standby)?(\(boot\))?(\(recovery-mode\))?>\s?$' + r'^(.*?)(\(unlicensed\))?(WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(standby\))?(-stby)?(-standby)?(\(boot\))?(\(recovery-mode\))?>\s?$' self.enable_prompt = \ - r'^(.*?)(WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(standby\))?(-stby)?(-standby)?(\(boot\))?(\(recovery-mode\))?#[\s\x07]*$' + r'^(.*?)(\(unlicensed\))?(WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(standby\))?(-stby)?(-standby)?(\(boot\))?(\(recovery-mode\))?#[\s\x07]*$' self.maintenance_mode_prompt = \ - r'^(.*?)(WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(standby\))?(-stby)?(-standby)?(\(boot\))?\(maint-mode\)#[\s\x07]*$' + r'^(.*?)(\(unlicensed\))?(WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(standby\))?(-stby)?(-standby)?(\(boot\))?\(maint-mode\)#[\s\x07]*$' self.press_enter = ReloadPatterns().press_enter self.config_prompt = r'^(.*)\((?!.*pki-hexmode).*(con|cfg|ipsec-profile|ca-trustpoint|ca-certificate-map|cs-server|ca-profile|gkm-local-server|cloud|host-list|config-gkm-group|gkm-sa-ipsec|gdoi-coop-ks-config|wsma|enforce-rule|DDNS)\S*\)#\s?$' diff --git a/src/unicon/plugins/iosxe/settings.py b/src/unicon/plugins/iosxe/settings.py index 9293b2ff..dc9875fe 100644 --- a/src/unicon/plugins/iosxe/settings.py +++ b/src/unicon/plugins/iosxe/settings.py @@ -26,6 +26,7 @@ def __init__(self): r'routing table \S+ does not exist', r'^%\s*SR feature is not configured yet, please enable Segment-routing first.', r'^%\s*\S+ overlaps with \S+', + r'^\S+ / \S+ is an [Ii]nvalid network\.', r'^%\S+ is linked to a VRF. Enable \S+ on that VRF first.', r'% VRF \S+ not configured', r'% Incomplete command.', diff --git a/src/unicon/plugins/iosxe/statements.py b/src/unicon/plugins/iosxe/statements.py index ce46dea2..fa4d0569 100644 --- a/src/unicon/plugins/iosxe/statements.py +++ b/src/unicon/plugins/iosxe/statements.py @@ -230,10 +230,11 @@ def boot_finished_deco(func): ''' @wraps(func) - def wrapper(spawn, context, session): + def wrapper(spawn, session, context, **kwargs): + args = [a for a in [spawn, session, context] if a] if context: context.pop('boot_start_time', None) - return func(spawn) + return func(*args, **kwargs) return wrapper diff --git a/src/unicon/plugins/iosxe/vpagent/__init__.py b/src/unicon/plugins/iosxe/vpagent/__init__.py new file mode 100644 index 00000000..402095b4 --- /dev/null +++ b/src/unicon/plugins/iosxe/vpagent/__init__.py @@ -0,0 +1,11 @@ +from unicon.plugins.iosxe import IosXESingleRpConnection +from unicon.plugins.iosxe.vpagent.settings import VpagentIosxeSettings +from unicon.plugins.iosxe.vpagent.connection_provider import VpagentSingleRpConnectionProvider + + + +class VpagentSingleRpConnection(IosXESingleRpConnection): + platform = 'vpagent' + chassis_type = 'single_rp' + connection_provider_class = VpagentSingleRpConnectionProvider + settings = VpagentIosxeSettings() \ No newline at end of file diff --git a/src/unicon/plugins/iosxe/vpagent/connection_provider.py b/src/unicon/plugins/iosxe/vpagent/connection_provider.py new file mode 100644 index 00000000..d63d0b7d --- /dev/null +++ b/src/unicon/plugins/iosxe/vpagent/connection_provider.py @@ -0,0 +1,88 @@ +from time import sleep + +from unicon.plugins.iosxe.connection_provider import IosxeSingleRpConnectionProvider + + +class VpagentSingleRpConnectionProvider(IosxeSingleRpConnectionProvider): + """ Implements Vpagent singleRP Connection Provider, + This class overrides the base class with the + additional dialogs and steps required for + connecting to any device via generic implementation + """ + + def __init__(self, *args, **kwargs): + + """ Initializes the generic connection provider + """ + super().__init__(*args, **kwargs) + + + def establish_connection(self): + """ Reads the device state and brings it to the right state + Note: Passive hostname learning is enabled by default and will + give a warning if the device hostname does not match the learned + hostname. The learned hostname is only used if user specifies + `learn_hostname=True`. A timeout may occur if the prompt pattern + uses the hostname, the timeout error includes the hostname and + a hint to check the hostname if a mismatch was detected. + """ + con = self.connection + + # Enable hostname learning by default + con.state_machine.learn_hostname = True + con.state_machine.learn_pattern = con.settings.DEFAULT_LEARNED_HOSTNAME + + context = self.connection.context + if (login_creds := context.get('login_creds')): + context.update(cred_list=login_creds) + + # Before accessing the vm since device is not ready and connection may be + # closed by vcenter manger we need to wait before accessing the device. + timeout = con.settings.WAITE_TIMEOUT + con.log.info(f'sleeping for {timeout} seconds before accessing the device!') + sleep(timeout) + + dialog = self.get_connection_dialog() + # Try to bring device to any state connection may be closed during bring the device + # to a valid and cause an IO error + output = self._get_device_to_any(con, context, dialog) + # if device is still in generic state that means we could not bring device to any state and + # spawn is closed so we need to create a new spawn and try again to bring device to any state + if con.state_machine.current_state == 'generic': + con.setup_connection() + output = self._get_device_to_any(con, context, dialog) + + if con.state_machine.current_state == "config": + con.state_machine.go_to('enable', + self.connection.spawn, + context=context, + prompt_recovery=self.prompt_recovery, + timeout=self.connection.connection_timeout) + + if con.state_machine.current_state not in ['rommon', 'standby_locked', 'shell']: + cur_state = con.state_machine.get_state(con.state_machine.current_state) + # if the learn hostname is set to True the pattern for state machine updated with + # '(?P])\s*$' - self.config_prompt = r'^(.*?)(\[\S+\]%N\(\S+\)[#>])\s*$' + self.exec_prompt = r'^(.*?)(\[\S+\] ?%N[#>])\s*$' + self.config_prompt = r'^(.*?)(\[\S+\] ?%N\(\S+\)[#>])\s*$' self.monitor_main_prompt = r'^(.*?\(Q\)uit,\s+ Prev Menu,\s+ Pause,\s+ Re-Display Options.*)$' self.monitor_sub_prompt = r'^(.*?\(B\)egin Protocol Decoding\s+\(Q\)uit,\s+ Prev Menu,\s+ Re-Display Options\s+Select:)\s*' self.yes_no_prompt = r'^(.*?)Are you sure \? \[Yes | No\]:\s*' diff --git a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data.yaml b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data.yaml index 6133a63e..05f6d924 100644 --- a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data.yaml +++ b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data.yaml @@ -1654,6 +1654,12 @@ transition_to_general_enable2: "": new_state: general_enable +connection_refused_loop: + commands: + "": + response: | + telnet: connect to address 127.0.0.1: Connection refused + confirm_abort_copy: preface: | @@ -1681,3 +1687,10 @@ general_enable_no_operating_mode: commands: <<: *gen_enable_cmds "show version | include operating mode" : "unexpected output" + +unlicensed_prompt: + prompt: "(unlicensed)UUT#" + commands: + "show version | include operating mode": "" + "end": + new_state: general_enable \ No newline at end of file diff --git a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_vpagent.yaml b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_vpagent.yaml new file mode 100644 index 00000000..b55d0be5 --- /dev/null +++ b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_vpagent.yaml @@ -0,0 +1,200 @@ +vpagent_login: + prompt: "Would you like to enter the initial configuration dialog? [yes/no]:" + commands: + "no": + new_state: vpagent_return + +vpagent_return: + preface: |2 + Primary Partition created...Size 7559 MB + + Drive communication & 1st Sector Write OK... + + %Error formatting flash2: (Didn't find 55AA signature in System Sector) + + ATA device information structure + ------------------------------------ + Configuration : 0x427A + Default Cylinder # : 16383 + Default Head # : 15 + Bytes Per Sector : 0 + Default Sectors Per Track : 63 + Default Sectors Per Card : 0 + Current Cylinder # : 17475 + Current Head # : 15 + Current Sectors Per Track : 63 + Current Sectors Per Card : 251 + Serial Number : 00000000000000000001 + Buffer Info : 0x0 0x40 0x0 + Firmware Version : 00000001 + Model Number : VMware Virtual IDE H + + % IOSv: Failed to create/open configuration file for writing: flash:/nvram + prompt: "Press RETURN to get started!" + commands: + "": + new_state: vpagent_exec + + +vpagent_exec: + preface: |2 + *Mar 1 00:00:00.937: %ATA-6-DEV_FOUND: device 0x1F0 + *Mar 1 00:00:04.256: %ATA-6-DEV_FOUND: device 0x1F1 + *Mar 1 00:00:04.256: %ATA-3-DEV_ERROR: Failure on device 0x1F1, unknown filesystem + *Mar 1 00:00:04.256: %ATA-3-DEV_ERROR: Failure on device 0x1F1, unknown filesystem + *Mar 1 00:00:07.827: %NVRAM-5-CONFIG_NVRAM_NOT_FOUND: NVRAM configuration 'flash:/nvram' could not be found on disk. + *Feb 12 19:57:29.114: %MAINBOARD_GE-3-PHY_AUTO_NEG: GigabitEthernet0/0, auto negotiation configuration failed + *Feb 12 19:57:29.249: %MAINBOARD_GE-3-PHY_AUTO_NEG: GigabitEthernet0/1, auto negotiation configuration failed + *Feb 12 19:57:29.383: %MAINBOARD_GE-3-PHY_AUTO_NEG: GigabitEthernet0/2, auto negotiation configuration failed + *Feb 12 19:57:29.518: %MAINBOARD_GE-3-PHY_AUTO_NEG: GigabitEthernet0/3, auto negotiation configuration failed + *Feb 12 19:57:29.651: %MAINBOARD_GE-3-PHY_AUTO_NEG: GigabitEthernet0/4, auto negotiation configuration failed + *Feb 12 19:57:29.784: %MAINBOARD_GE-3-PHY_AUTO_NEG: GigabitEthernet0/5, auto negotiation configuration failed + *Feb 12 19:57:29.917: %MAINBOARD_GE-3-PHY_AUTO_NEG: GigabitEthernet0/6, auto negotiation configuration failed + *Feb 12 19:57:30.007: %CTS-6-ENV_DATA_START_STATE: Environment Data Download in start state + *Feb 12 19:57:30.162: %PA-3-PA_INIT_FAILED: Performance Agent failed to initialize (Missing Data and APPX License) + *Feb 12 19:57:31.029: %LINK-3-UPDOWN: Interface GigabitEthernet0/0, changed state to up + *Feb 12 19:57:31.163: %LINK-3-UPDOWN: Interface GigabitEthernet0/1, changed state to down + *Feb 12 19:57:31.298: %LINK-3-UPDOWN: Interface GigabitEthernet0/2, changed state to down + *Feb 12 19:57:31.432: %LINK-3-UPDOWN: Interface GigabitEthernet0/3, changed state to down + *Feb 12 19:57:31.567: %LINK-3-UPDOWN: Interface GigabitEthernet0/4, changed state to down + *Feb 12 19:57:31.700: %LINK-3-UPDOWN: Interface GigabitEthernet0/5, changed state to down + *Feb 12 19:57:31.833: %LINK-3-UPDOWN: Interface + % IOSv: Failed to create/open configuration file for writing: flash:/nvram + GigabitEthernet0/6, changed state to down + *Feb 12 19:57:32.029: %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet0/0, changed state to up + *Feb 12 19:57:32.163: %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet0/1, changed state to down + *Feb 12 19:57:32.298: %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet0/2, changed state to down + *Feb 12 19:57:32.432: %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet0/3, changed state to down + *Feb 12 19:57:32.567: %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet0/4, changed state to down + *Feb 12 19:57:32.700: %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet0/5, changed state to down + *Feb 12 19:57:32.833: %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet0/6, changed state to down + *Feb 12 19:57:39.572: AUTOINSTALL: GigabitEthernet0/0 is assigned 5.199.0.46 + *Feb 12 20:00:10.563: %IP-5-WEBINST_KILL: Terminating DNS process + *Feb 12 20:00:11.863: %LINK-5-CHANGED: Interface GigabitEthernet0/1, changed state to administratively down + *Feb 12 20:00:11.901: %LINK-5-CHANGED: Interface GigabitEthernet0/2, changed state to administratively down + *Feb 12 20:00:11.937: %LINK-5-CHANGED: Interface GigabitEthernet0/3, changed state to administratively down + *Feb 12 20:00:11.973: %LINK-5-CHANGED: Interface GigabitEthernet0/4, changed state to administratively down + *Feb 12 20:00:12.009: %LINK-5-CHANGED: Interface GigabitEthernet0/5, changed state to administratively down + *Feb 12 20:00:12.044: %LINK-5-CHANGED: Interface GigabitEthernet0/6, changed state to administratively down + *Feb 12 20:00:17.577: %ATA-3-DEV_ERROR: Failure on device 0x1F0, reset + *Feb 12 20:00:17.579: %ATA-3-DEV_ERROR: Failure on device 0x1F0, reset + *Feb 12 20:00:17.580: %ATA-3-DEV_ERROR: Failure on device 0x1F0, reset + *Feb 12 20:00:17.580: %ATA-3-DEV_ERROR: Failure on device 0x1F1, unknown filesystem + *Feb 12 20:00:17.583: %ATA-3-DEV_ERROR: Failure on device 0x1F0, r + ************************************************************************** + * IOSv - Cisco Systems Confidential * + * * + * This software is provided as is without warranty for internal * + * development and testing purposes only under the terms of the Cisco * + * Early Field Trial agreement. Under no circumstances may this software * + * be used for production purposes or deployed in a production * + * environment. * + * * + * By using the software, you agree to abide by the terms and conditions * + * of the Cisco Early Field Trial Agreement as well as the terms and * + * conditions of the Cisco End User License Agreement at * + * http://www.cisco.com/go/eula * + * * + * Unauthorized use or distribution of this software is expressly * + * Prohibited. * + + prompt: "%N>" + commands: + "term length 0": "" + "term width 0": "" + "show version | include operating mode" : "" + "show version": &SV |2 + Cisco IOS Software, IOSv Software (VIOS-TPGEN_ADVENTERPRISEK9-M), Experimental Version 15.4(20140131:100343) [anrajama-tb4_bld_1 132] + Copyright (c) 1986-2014 by Cisco Systems, Inc. + Compiled Wed 16-Apr-14 13:35 by anrajama + + *** IOSv: UNSUPPORTED DEMO VERSION ONLY *** + + ROM: Bootstrap program is IOSv + + Router uptime is 3 minutes + System returned to ROM by reload + System image file is "cdrom:/boot/vios-tpgen_adventerprisek9-m" + Last reload reason: Unknown reason + + + + This product contains cryptographic features and is subject to United + States and local country laws governing import, export, transfer and + use. Delivery of Cisco cryptographic products does not imply + third-party authority to import, export, distribute or use encryption. + Importers, exporters, distributors and users are responsible for + compliance with U.S. and local country laws. By using this product you + agree to comply with applicable laws and regulations. If you are unable + to comply with U.S. and local laws, return this product immediately. + + A summary of U.S. laws governing Cisco cryptographic products may be found at: + http://www.cisco.com/wwl/export/crypto/tool/stqrg.html + + If you require further assistance please contact us by sending email to + export@cisco.com. + + Cisco IOSv (revision 1.0) with with 946434K/99328K bytes of memory. + Processor board ID 931PJ7UMMPU5GJ98ODSYT + 7 Gigabit Ethernet interfaces + DRAM configuration is 72 bits wide with parity disabled. + 256K bytes of non-volatile configuration memory. + 4062633K bytes of ATA System CompactFlash 0 (Read/Write) + 0K bytes of ATA CompactFlash 1 (Read/Write) + 4062633K bytes of ATA CompactFlash 2 (Read/Write) + 0K bytes of ATA CompactFlash 3 (Read/Write) + + + + Configuration register is 0x0 + + + "enable": + new_state: enable_vapagent + + +enable_vapagent: + prompt: "%N#" + commands: + "term length 0": "" + "term width 0": "" + "show version": *SV + "show version | include operating mode" : "" + + "disable": + new_state: vapagent_exec + "enable": "" + + + + "config term": + new_state: config_vpagent + + + +config_vpagent: + prompt: "%N(conf)#" + commands: + "no logging console": "" + "line vty 0 4": + new_state: config_line_vpagent + "line console 0": + new_state: config_line_vpagent + "redundancy": + new_state: config_vpagent_redundancy + +config_line_vpagent: + prompt: "%N(config-line)#" + commands: + "exec-timeout 0": "" + "line vty 0 4": "" + "end": + new_state: enable_vapagent + +config_vpagent_redundancy: + prompt: "%N(config-red)#" + commands: + "main-cpu": + new_state: config_vpagent_redundancy + "end": + new_state: enable_vapagent diff --git a/src/unicon/plugins/tests/mock_data/staros/staros_mock_data.yaml b/src/unicon/plugins/tests/mock_data/staros/staros_mock_data.yaml index 309829ae..1569eb72 100644 --- a/src/unicon/plugins/tests/mock_data/staros/staros_mock_data.yaml +++ b/src/unicon/plugins/tests/mock_data/staros/staros_mock_data.yaml @@ -8,7 +8,7 @@ staros_connect: staros_exec: prompt: "[local]host_name# " - commands: + commands: &staros_exec_cmds "terminal length 0": "" "terminal width 512": "" "no timestamps": "" @@ -224,3 +224,16 @@ staros_call_finish: prompt: "" keys: <<: *monitor_keys + + + +staros_connect2: + preface: Escape character is '^]'. + prompt: "" + commands: + "": + new_state: staros_exec2 + +staros_exec2: + prompt: "[local] host_name# " + commands: *staros_exec_cmds diff --git a/src/unicon/plugins/tests/test_plugin_generic.py b/src/unicon/plugins/tests/test_plugin_generic.py index 4e2bbdac..836df53c 100644 --- a/src/unicon/plugins/tests/test_plugin_generic.py +++ b/src/unicon/plugins/tests/test_plugin_generic.py @@ -1477,6 +1477,47 @@ def test_connection_refused_handler_without_peripheral(self): d.disconnect() md.stop() + def test_connection_refused_handler_repeat(self): + md = MockDeviceTcpWrapper(device_os='iosxe', hostname='R1', + port=0, state='connection_refused_loop') + md.start() + template_testbed = """ + devices: + R1: + os: iosxe + credentials: + default: + username: cisco + password: cisco + connections: + defaults: + class: unicon.Unicon + a: + protocol: telnet + ip: 127.0.0.1 + port: {} + peripherals: + terminal_server: + ts: [20] + ts: + os: ios + credentials: + default: + username: cisco + password: cisco + connections: + cli: + command: mock_device_cli --os ios --state exec --hostname ts + """.format(md.ports[0]) + t = loader.load(template_testbed) + d = t.devices.R1 + with self.assertRaisesRegex(unicon.core.errors.ConnectionError, + 'failed to connect to R1'): + try: + d.connect() + finally: + d.disconnect() + md.stop() if __name__ == "__main__": diff --git a/src/unicon/plugins/tests/test_plugin_iosxe.py b/src/unicon/plugins/tests/test_plugin_iosxe.py index 4ba0c066..25e0b0e7 100644 --- a/src/unicon/plugins/tests/test_plugin_iosxe.py +++ b/src/unicon/plugins/tests/test_plugin_iosxe.py @@ -266,6 +266,29 @@ def test_operating_mode_check(self): finally: c.disconnect() + def test_vpagent_connect(self): + c = Connection(hostname='Router', + start=['mock_device_cli --os iosxe --state vpagent_login --hostname Router'], + os='iosxe', + credentials=dict(default=dict(username='cisco', password='cisco'))) + try: + c.connect() + self.assertEqual(c.spawn.match.match_output, 'end\r\nRouter#') + finally: + c.disconnect() + + def test_unlicensed_prompt(self): + c = Connection(hostname='UUT', + start=['mock_device_cli --os iosxe --state unlicensed_prompt --hostname UUT'], + os='iosxe', + credentials=dict(default=dict(username='cisco', password='cisco')), + mit=True) + try: + c.connect() + self.assertEqual(c.spawn.match.last_match.group(2), '(unlicensed)') + finally: + c.disconnect() + class TestIosXEPluginExecute(unittest.TestCase): diff --git a/src/unicon/plugins/tests/test_plugin_staros.py b/src/unicon/plugins/tests/test_plugin_staros.py index 7abaa8bf..e5bd5a0e 100644 --- a/src/unicon/plugins/tests/test_plugin_staros.py +++ b/src/unicon/plugins/tests/test_plugin_staros.py @@ -68,6 +68,21 @@ def test_monitor(self): self.assertTrue('Call Finished - Waiting to trace next matching call' in r) +class TestStarosConnect(unittest.TestCase): + + def test_connect(self): + c = Connection(hostname='host_name', + start=['mock_device_cli --os staros --state staros_connect2'], + os='staros', + username='cisco', + tacacs_password='cisco', + connection_timeout=15, + mit=True) + try: + c.connect() + finally: + c.disconnect() + + if __name__ == "__main__": unittest.main() - diff --git a/src/unicon/plugins/utils.py b/src/unicon/plugins/utils.py index 9d3d9315..6535b125 100644 --- a/src/unicon/plugins/utils.py +++ b/src/unicon/plugins/utils.py @@ -22,10 +22,13 @@ from unicon.core.errors import CredentialsExhaustedError # Declare token types for abstract token discovery -TOKEN_TYPES = ['os', 'os_flavor', 'version', 'platform', 'model', 'pid'] +TOKEN_TYPES = ['os', 'os_flavor', 'version', 'platform', 'model', 'submodel', 'pid', 'chassis_type'] +OPTIONAL_TOKENS = ['os_flavor'] ShowVersion = None ShowInventory = None Uname = None +PID_TOKEN_FILE = Path(__file__).parent / 'pid_tokens.csv' + def _fallback_cred(context): return [context['default_cred_name']] \ @@ -234,10 +237,10 @@ def __init__(self, con, execute_target=None): # Import them during object initialization if not already imported global ShowVersion if not ShowVersion: - from genie.libs.parser.generic.show_platform import ShowVersion + from genie.libs.parser.generic.rv1.show_platform import ShowVersion global ShowInventory if not ShowInventory: - from genie.libs.parser.generic.show_platform import ShowInventory + from genie.libs.parser.generic.rv1.show_platform import ShowInventory global Uname if not Uname: from genie.libs.parser.generic.show_platform import Uname @@ -249,7 +252,7 @@ def __init__(self, con, execute_target=None): # Load the pid token lookup file self.pid_data = {} - self.pid_lookup_file = Path(__file__).parent / 'pid_tokens.csv' + self.pid_lookup_file = PID_TOKEN_FILE self.pid_data = load_token_csv_file(file_path=self.pid_lookup_file) # Attach commands and accompying classes for cleaner looping @@ -274,9 +277,10 @@ def update_learned_tokens(self, new_tokens, overwrite_existing_values=True): def all_tokens_learned(self): - for _,token_value in self.learned_tokens.items(): - if token_value == '' or token_value is None: - return False + for token ,token_value in self.learned_tokens.items(): + if token not in OPTIONAL_TOKENS: + if token_value == '' or token_value is None: + return False return True @@ -284,7 +288,7 @@ def lookup_tokens_using_pid(self, pid_to_check): try: data = self.pid_data[pid_to_check] except KeyError: - return None + return {'pid': pid_to_check} else: return { 'os': data['os'], @@ -368,6 +372,11 @@ def discover_tokens(self): if cmd == 'show inventory' and \ parsed_output.get('inventory_item_index', None): + + if parsed_output.get('chassis_type'): + self.learned_tokens['chassis_type'] = \ + parsed_output.get('chassis_type') + # Look though pids that were found with show inventory for _,entry_data in \ parsed_output['inventory_item_index'].items(): @@ -535,7 +544,11 @@ def show_results(self): def learn_device_tokens(self, overwrite_testbed_tokens=False): if not self.con.device: self.con.log.debug('No device object, cannot learn tokens') - return + return {} + + if self.con.state_machine.current_state == 'standby_locked': + self.con.log.info('Device is locked, cannot learn tokens') + return {} if overwrite_testbed_tokens: self.con.log.info('+++ Learning device tokens +++') From 6ac51e5586d88f00231bf4b97c75c53af5c25985 Mon Sep 17 00:00:00 2001 From: Taarini Sarath Chander Date: Tue, 11 Mar 2025 13:47:06 -0400 Subject: [PATCH 2/2] Releasing v25.2 --- docs/changelog/2025/february.rst | 3 - src/unicon/plugins/iosxe/vpagent/__init__.py | 11 - .../iosxe/vpagent/connection_provider.py | 88 -------- src/unicon/plugins/iosxe/vpagent/settings.py | 9 - .../iosxe/iosxe_mock_data_vpagent.yaml | 200 ------------------ src/unicon/plugins/tests/test_plugin_iosxe.py | 11 - 6 files changed, 322 deletions(-) delete mode 100644 src/unicon/plugins/iosxe/vpagent/__init__.py delete mode 100644 src/unicon/plugins/iosxe/vpagent/connection_provider.py delete mode 100644 src/unicon/plugins/iosxe/vpagent/settings.py delete mode 100644 src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_vpagent.yaml diff --git a/docs/changelog/2025/february.rst b/docs/changelog/2025/february.rst index e158f30a..f91bcd29 100644 --- a/docs/changelog/2025/february.rst +++ b/docs/changelog/2025/february.rst @@ -38,9 +38,6 @@ Changelogs New -------------------------------------------------------------------------------- -* iosxe/vpagent - * Add connection provider for vpagent - * iosxe * Added to Configure Error Patterns * Added the regex to match error pattern "127.0 / 255.0 is an invalid network." diff --git a/src/unicon/plugins/iosxe/vpagent/__init__.py b/src/unicon/plugins/iosxe/vpagent/__init__.py deleted file mode 100644 index 402095b4..00000000 --- a/src/unicon/plugins/iosxe/vpagent/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -from unicon.plugins.iosxe import IosXESingleRpConnection -from unicon.plugins.iosxe.vpagent.settings import VpagentIosxeSettings -from unicon.plugins.iosxe.vpagent.connection_provider import VpagentSingleRpConnectionProvider - - - -class VpagentSingleRpConnection(IosXESingleRpConnection): - platform = 'vpagent' - chassis_type = 'single_rp' - connection_provider_class = VpagentSingleRpConnectionProvider - settings = VpagentIosxeSettings() \ No newline at end of file diff --git a/src/unicon/plugins/iosxe/vpagent/connection_provider.py b/src/unicon/plugins/iosxe/vpagent/connection_provider.py deleted file mode 100644 index d63d0b7d..00000000 --- a/src/unicon/plugins/iosxe/vpagent/connection_provider.py +++ /dev/null @@ -1,88 +0,0 @@ -from time import sleep - -from unicon.plugins.iosxe.connection_provider import IosxeSingleRpConnectionProvider - - -class VpagentSingleRpConnectionProvider(IosxeSingleRpConnectionProvider): - """ Implements Vpagent singleRP Connection Provider, - This class overrides the base class with the - additional dialogs and steps required for - connecting to any device via generic implementation - """ - - def __init__(self, *args, **kwargs): - - """ Initializes the generic connection provider - """ - super().__init__(*args, **kwargs) - - - def establish_connection(self): - """ Reads the device state and brings it to the right state - Note: Passive hostname learning is enabled by default and will - give a warning if the device hostname does not match the learned - hostname. The learned hostname is only used if user specifies - `learn_hostname=True`. A timeout may occur if the prompt pattern - uses the hostname, the timeout error includes the hostname and - a hint to check the hostname if a mismatch was detected. - """ - con = self.connection - - # Enable hostname learning by default - con.state_machine.learn_hostname = True - con.state_machine.learn_pattern = con.settings.DEFAULT_LEARNED_HOSTNAME - - context = self.connection.context - if (login_creds := context.get('login_creds')): - context.update(cred_list=login_creds) - - # Before accessing the vm since device is not ready and connection may be - # closed by vcenter manger we need to wait before accessing the device. - timeout = con.settings.WAITE_TIMEOUT - con.log.info(f'sleeping for {timeout} seconds before accessing the device!') - sleep(timeout) - - dialog = self.get_connection_dialog() - # Try to bring device to any state connection may be closed during bring the device - # to a valid and cause an IO error - output = self._get_device_to_any(con, context, dialog) - # if device is still in generic state that means we could not bring device to any state and - # spawn is closed so we need to create a new spawn and try again to bring device to any state - if con.state_machine.current_state == 'generic': - con.setup_connection() - output = self._get_device_to_any(con, context, dialog) - - if con.state_machine.current_state == "config": - con.state_machine.go_to('enable', - self.connection.spawn, - context=context, - prompt_recovery=self.prompt_recovery, - timeout=self.connection.connection_timeout) - - if con.state_machine.current_state not in ['rommon', 'standby_locked', 'shell']: - cur_state = con.state_machine.get_state(con.state_machine.current_state) - # if the learn hostname is set to True the pattern for state machine updated with - # '(?P" - commands: - "term length 0": "" - "term width 0": "" - "show version | include operating mode" : "" - "show version": &SV |2 - Cisco IOS Software, IOSv Software (VIOS-TPGEN_ADVENTERPRISEK9-M), Experimental Version 15.4(20140131:100343) [anrajama-tb4_bld_1 132] - Copyright (c) 1986-2014 by Cisco Systems, Inc. - Compiled Wed 16-Apr-14 13:35 by anrajama - - *** IOSv: UNSUPPORTED DEMO VERSION ONLY *** - - ROM: Bootstrap program is IOSv - - Router uptime is 3 minutes - System returned to ROM by reload - System image file is "cdrom:/boot/vios-tpgen_adventerprisek9-m" - Last reload reason: Unknown reason - - - - This product contains cryptographic features and is subject to United - States and local country laws governing import, export, transfer and - use. Delivery of Cisco cryptographic products does not imply - third-party authority to import, export, distribute or use encryption. - Importers, exporters, distributors and users are responsible for - compliance with U.S. and local country laws. By using this product you - agree to comply with applicable laws and regulations. If you are unable - to comply with U.S. and local laws, return this product immediately. - - A summary of U.S. laws governing Cisco cryptographic products may be found at: - http://www.cisco.com/wwl/export/crypto/tool/stqrg.html - - If you require further assistance please contact us by sending email to - export@cisco.com. - - Cisco IOSv (revision 1.0) with with 946434K/99328K bytes of memory. - Processor board ID 931PJ7UMMPU5GJ98ODSYT - 7 Gigabit Ethernet interfaces - DRAM configuration is 72 bits wide with parity disabled. - 256K bytes of non-volatile configuration memory. - 4062633K bytes of ATA System CompactFlash 0 (Read/Write) - 0K bytes of ATA CompactFlash 1 (Read/Write) - 4062633K bytes of ATA CompactFlash 2 (Read/Write) - 0K bytes of ATA CompactFlash 3 (Read/Write) - - - - Configuration register is 0x0 - - - "enable": - new_state: enable_vapagent - - -enable_vapagent: - prompt: "%N#" - commands: - "term length 0": "" - "term width 0": "" - "show version": *SV - "show version | include operating mode" : "" - - "disable": - new_state: vapagent_exec - "enable": "" - - - - "config term": - new_state: config_vpagent - - - -config_vpagent: - prompt: "%N(conf)#" - commands: - "no logging console": "" - "line vty 0 4": - new_state: config_line_vpagent - "line console 0": - new_state: config_line_vpagent - "redundancy": - new_state: config_vpagent_redundancy - -config_line_vpagent: - prompt: "%N(config-line)#" - commands: - "exec-timeout 0": "" - "line vty 0 4": "" - "end": - new_state: enable_vapagent - -config_vpagent_redundancy: - prompt: "%N(config-red)#" - commands: - "main-cpu": - new_state: config_vpagent_redundancy - "end": - new_state: enable_vapagent diff --git a/src/unicon/plugins/tests/test_plugin_iosxe.py b/src/unicon/plugins/tests/test_plugin_iosxe.py index 25e0b0e7..f939a86e 100644 --- a/src/unicon/plugins/tests/test_plugin_iosxe.py +++ b/src/unicon/plugins/tests/test_plugin_iosxe.py @@ -266,17 +266,6 @@ def test_operating_mode_check(self): finally: c.disconnect() - def test_vpagent_connect(self): - c = Connection(hostname='Router', - start=['mock_device_cli --os iosxe --state vpagent_login --hostname Router'], - os='iosxe', - credentials=dict(default=dict(username='cisco', password='cisco'))) - try: - c.connect() - self.assertEqual(c.spawn.match.match_output, 'end\r\nRouter#') - finally: - c.disconnect() - def test_unlicensed_prompt(self): c = Connection(hostname='UUT', start=['mock_device_cli --os iosxe --state unlicensed_prompt --hostname UUT'],