diff --git a/docs/changelog/2025/october.rst b/docs/changelog/2025/october.rst new file mode 100644 index 00000000..ca84a9a8 --- /dev/null +++ b/docs/changelog/2025/october.rst @@ -0,0 +1,62 @@ +October 2025 +========== + +October 28 - Unicon v25.10 +------------------------ + + + +.. csv-table:: Module Versions + :header: "Modules", "Versions" + + ``unicon.plugins``, v25.10 + ``unicon``, v25.10 + + + + +Changelogs +^^^^^^^^^^ +-------------------------------------------------------------------------------- + Fix +-------------------------------------------------------------------------------- + +* unicon + * Modified TestUniconSettings + * Fixed regex pattern to correctly match Invalid input error message in exec command. + +* plugins/linux + * Updated unit tests to accommodate the removal of the default 'uptime' command from LINUX_INIT_EXEC_COMMANDS. + +* adapters + * Updated the log collection to check for runtime directory before moving + +* modified basemultirpconnectionprovider + * Updated token discovery to handle standby locked devices + + +-------------------------------------------------------------------------------- + New +-------------------------------------------------------------------------------- + +* unicon/bases/linux/connection + * Added peripheral support for Linux OS devices + * Updated BaseLinuxConnection to pass device to Spawn initialization, enabling clearing of busy console lines for Linux-based platforms + + +-------------------------------------------------------------------------------- + Add +-------------------------------------------------------------------------------- + +* basemultirpconnection + * Added swap_roles in Multi RP connection which is parent class to have it handled for other connections. + + +-------------------------------------------------------------------------------- + Fix +-------------------------------------------------------------------------------- + +* iosxe/cat9k/stackwise_virtual + * Enhanced the designate handles for condition where a standby & b active + + diff --git a/docs/changelog/index.rst b/docs/changelog/index.rst index 50467cce..4050afbb 100644 --- a/docs/changelog/index.rst +++ b/docs/changelog/index.rst @@ -4,6 +4,7 @@ Changelog .. toctree:: :maxdepth: 2 + 2025/october 2025/september 2025/august 2025/july diff --git a/docs/changelog_plugins/2025/october.rst b/docs/changelog_plugins/2025/october.rst new file mode 100644 index 00000000..8af0480c --- /dev/null +++ b/docs/changelog_plugins/2025/october.rst @@ -0,0 +1,47 @@ +October 2025 +========== + +October 28 - Unicon.Plugins v25.10 +------------------------ + + + +.. csv-table:: Module Versions + :header: "Modules", "Versions" + + ``unicon.plugins``, v25.10 + ``unicon``, v25.10 + + + + +Changelogs +^^^^^^^^^^ +-------------------------------------------------------------------------------- + New +-------------------------------------------------------------------------------- + +* iosxe/c9800 + +* plugins/linux + * Added uptime command to LINUX_INIT_EXEC_COMMANDS by default. + +* unicon + * Added UT for the fix added in unicon + +* iosxe + * Updated the disable prompt pattern not to recongnise grub as disable mode prompt. + + +-------------------------------------------------------------------------------- + Fix +-------------------------------------------------------------------------------- + +* iosxe + * Updated exec error pattern + * Fixed ACM Configure service end-state handling. + +* generic + * Updated syslog pattern to handle RSA key log message + + diff --git a/docs/changelog_plugins/index.rst b/docs/changelog_plugins/index.rst index 13de6e56..7ae6ad64 100644 --- a/docs/changelog_plugins/index.rst +++ b/docs/changelog_plugins/index.rst @@ -4,6 +4,7 @@ Plugins Changelog .. toctree:: :maxdepth: 2 + 2025/october 2025/september 2025/august 2025/july diff --git a/src/unicon/plugins/__init__.py b/src/unicon/plugins/__init__.py index a9782367..5811b417 100644 --- a/src/unicon/plugins/__init__.py +++ b/src/unicon/plugins/__init__.py @@ -1,4 +1,4 @@ -__version__ = "25.9" +__version__ = "25.10" supported_chassis = [ 'single_rp', diff --git a/src/unicon/plugins/generic/patterns.py b/src/unicon/plugins/generic/patterns.py index 590f926b..475acefa 100644 --- a/src/unicon/plugins/generic/patterns.py +++ b/src/unicon/plugins/generic/patterns.py @@ -77,7 +77,7 @@ def __init__(self): r"^.*?(%\w+(-\S+)?-\d+-\w+|" r"yang-infra:|PKI_SSL_IPC:|Guestshell destroyed successfully|" r"%Error opening tftp:\/\/255\.255\.255\.255|Autoinstall trying|" - r"audit: kauditd hold queue overflow|SECURITY WARNING|" + r"audit: kauditd hold queue overflow|SECURITY WARNING|%RSA key|" r"(LC|RP)/\d+/\d+/CPU\d+:\w+\s+\d+\s+\d{2}:\d{2}:\d{2}|" r"\[OK\]" r").*\s*$" diff --git a/src/unicon/plugins/iosxe/cat9k/c9800/__init__.py b/src/unicon/plugins/iosxe/cat9k/c9800/__init__.py index 6b6bb875..6a44eb6c 100644 --- a/src/unicon/plugins/iosxe/cat9k/c9800/__init__.py +++ b/src/unicon/plugins/iosxe/cat9k/c9800/__init__.py @@ -8,13 +8,13 @@ from .statemachine import IosXEc9800SingleRpStateMachine from .settings import IosXEc9800Settings from .. import service_implementation as svc - +from .service_implementation import Rommon class IosXEc9800ServiceList(IosXEServiceList): def __init__(self): super().__init__() self.reload = svc.Reload - self.rommon = svc.Rommon + self.rommon = Rommon class IosXEc9800SingleRpConnection(IosXESingleRpConnection): diff --git a/src/unicon/plugins/iosxe/cat9k/c9800/service_implementation.py b/src/unicon/plugins/iosxe/cat9k/c9800/service_implementation.py new file mode 100644 index 00000000..f1c51a2f --- /dev/null +++ b/src/unicon/plugins/iosxe/cat9k/c9800/service_implementation.py @@ -0,0 +1,30 @@ + + +from unicon.eal.dialogs import Dialog +from unicon.core.errors import SubCommandFailure +from unicon.plugins.generic.service_implementation import Execute as GenericExecute + +class Rommon(GenericExecute): + """C9800-specific Rommon service. + Requires explicit config_register to be passed when invoked. + No Enable Break parsing (not in C9800 show boot output). + """ + def __init__(self, connection, context, **kwargs): + super().__init__(connection, context, **kwargs) + self.start_state = 'rommon' + self.end_state = 'rommon' + self.service_name = 'rommon' + self.timeout = kwargs.get('reload_timeout', 600) + self.__dict__.update(kwargs) + + def pre_service(self, *args, **kwargs): + sm = self.get_sm() + con = self.connection + sm.go_to('enable', con.spawn, context=self.context) + confreg = kwargs.get('config_register', "0x0") + try: + con.configure(f'config-register {confreg}') + except Exception as err: + raise SubCommandFailure(f"Failed to configure config-register {confreg}", err) + + super().pre_service(*args, **kwargs) \ No newline at end of file diff --git a/src/unicon/plugins/iosxe/cat9k/stackwise_virtual/connection_provider.py b/src/unicon/plugins/iosxe/cat9k/stackwise_virtual/connection_provider.py index 432bc746..b47239ac 100644 --- a/src/unicon/plugins/iosxe/cat9k/stackwise_virtual/connection_provider.py +++ b/src/unicon/plugins/iosxe/cat9k/stackwise_virtual/connection_provider.py @@ -2,13 +2,12 @@ Authors: pyATS TEAM (pyats-support@cisco.com, pyats-support-ext@cisco.com) """ - +import re from unicon.eal.dialogs import Dialog from unicon.bases.routers.connection_provider import BaseStackRpConnectionProvider from unicon.plugins.generic.statements import connection_statement_list, custom_auth_statements - class StackwiseVirtualConnectionProvider(BaseStackRpConnectionProvider): """ Implements Stack Connection Provider, This class overrides the base class with the @@ -49,9 +48,11 @@ def designate_handles(self): con.log.debug('{} in state: {}'.format(subcon.alias, subcon.state_machine.current_state)) if subcon1.state_machine.current_state == 'enable': + target_con = subcon1 target_alias = subcon1_alias other_alias = subcon2_alias elif subcon2.state_machine.current_state == 'enable': + target_con = subcon2 target_alias = subcon2_alias other_alias = subcon1_alias @@ -60,7 +61,6 @@ def designate_handles(self): con._handles_designated = True device = con.device - try: # To check if the device is in SVL state output = device.parse("show switch") @@ -72,7 +72,23 @@ def designate_handles(self): # There are case when in non-SVL the device connection # becomes active for both connection and there isn't a standby state # it would have either active and member state or just active state - super().designate_handles() + + # Verify the active and standby + target_con.spawn.sendline(target_con.spawn.settings.SHOW_REDUNDANCY_CMD) + output = target_con.spawn.expect( + target_con.state_machine.get_state('enable').pattern, + timeout=con.settings.EXEC_TIMEOUT).match_output + + state = re.findall(target_con.spawn.settings.REDUNDANCY_STATE_PATTERN, output, flags=re.M) + target_con.log.debug(f'{target_con.spawn} state: {state}') + if any('active' in s.lower() for s in state): + con._set_active_alias(target_alias) + con._set_standby_alias(other_alias) + elif any('standby' in s.lower() for s in state): + con._set_standby_alias(target_alias) + con._set_active_alias(other_alias) + else: + raise ConnectionError('unable to designate handles') except Exception: con.log.exception("Failed to designate handle for SVL stack") diff --git a/src/unicon/plugins/iosxe/patterns.py b/src/unicon/plugins/iosxe/patterns.py index 0b2b7185..060b99fe 100644 --- a/src/unicon/plugins/iosxe/patterns.py +++ b/src/unicon/plugins/iosxe/patterns.py @@ -23,7 +23,7 @@ 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'^(.*?)(\(unlicensed\))?(wlc|WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(recovery-mode\))?(\(rp-rec-mode\))?(\(standby\))?(-stby)?(-standby)?(\(boot\))?(?\s?$' + r'^(?!.*?grub)(.*?)(\(unlicensed\))?(wlc|WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(recovery-mode\))?(\(rp-rec-mode\))?(\(standby\))?(-stby)?(-standby)?(\(boot\))?(?\s?$' self.enable_prompt = \ r'^(.*?)(\(unlicensed\))?(wlc|WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(recovery-mode\))?(\(rp-rec-mode\))?(\(standby\))?(-stby)?(-standby)?(\(boot\))?#[\s\x07]*$' self.maintenance_mode_prompt = \ diff --git a/src/unicon/plugins/iosxe/service_implementation.py b/src/unicon/plugins/iosxe/service_implementation.py index ed79497a..37be8a48 100644 --- a/src/unicon/plugins/iosxe/service_implementation.py +++ b/src/unicon/plugins/iosxe/service_implementation.py @@ -68,7 +68,7 @@ def pre_service(self, *args, **kwargs): if self.acm_configlet: self.connection.state_machine.go_to('acm', self.connection.spawn,context={'acm_configlet': self.acm_configlet}) self.start_state = 'acm' - self.end_state = 'acm' + self.end_state = 'enable' elif self.rules: if self.connection.connected: diff --git a/src/unicon/plugins/iosxe/settings.py b/src/unicon/plugins/iosxe/settings.py index dc9875fe..485d2059 100644 --- a/src/unicon/plugins/iosxe/settings.py +++ b/src/unicon/plugins/iosxe/settings.py @@ -15,7 +15,7 @@ def __init__(self): self.PROMPT_RECOVERY_COMMANDS = ['\r', '\x1e', '\x03'] self.ERROR_PATTERN = [ - r'^%\s*[Ii]nvalid (command|input)', + r'^\s*%\s*[Ii]nvalid (command|input)', r'^%\s*[Ii]ncomplete (command|input)', r'^%\s*[Aa]mbiguous (command|input)', r'% Bad IP address or host name', diff --git a/src/unicon/plugins/linux/settings.py b/src/unicon/plugins/linux/settings.py index 514c4583..436011b4 100644 --- a/src/unicon/plugins/linux/settings.py +++ b/src/unicon/plugins/linux/settings.py @@ -19,7 +19,7 @@ def __init__(self): """ initialize """ super().__init__() - self.LINUX_INIT_EXEC_COMMANDS = [] + self.LINUX_INIT_EXEC_COMMANDS = ['uptime'] ## Prompt recovery commands for Linux # Default commands: Enter key , Ctrl-C, Enter Key diff --git a/src/unicon/plugins/tests/mock/mock_device_iosxe.py b/src/unicon/plugins/tests/mock/mock_device_iosxe.py index 0b5cefaa..514d3db7 100644 --- a/src/unicon/plugins/tests/mock/mock_device_iosxe.py +++ b/src/unicon/plugins/tests/mock/mock_device_iosxe.py @@ -275,10 +275,10 @@ def svl_stack_enable(self, transport, cmd): for idx, port in enumerate(ports): if idx == 0: # standby -> active - self.set_state(port, 'svl_stack_enable') + self.set_state(port, 'svl_post_switchover_active_enable') elif idx == 1: # active -> standby - self.set_state(port, 'svl_standby_switchover') + self.set_state(port, 'svl_post_switchover_standby_enable') def update_show_switch(self, transport): port = self.transport_handles[transport] diff --git a/src/unicon/plugins/tests/mock_data/generic/generic_mock_data_iosxe_ha_asr.yaml b/src/unicon/plugins/tests/mock_data/generic/generic_mock_data_iosxe_ha_asr.yaml index e113229f..69cf5e7a 100644 --- a/src/unicon/plugins/tests/mock_data/generic/generic_mock_data_iosxe_ha_asr.yaml +++ b/src/unicon/plugins/tests/mock_data/generic/generic_mock_data_iosxe_ha_asr.yaml @@ -144,6 +144,7 @@ asr_exec: "term length 0": "" "term width 0": "" "show version": *SV + "show install summary": "" "enable": new_state: enable_asr @@ -216,6 +217,8 @@ config_asr: new_state: config_line_asr "redundancy": new_state: config_asr_redundancy + "end": + new_state: enable_asr config_line_asr: prompt: "%N(config-line)#" diff --git a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_cat9k.yaml b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_cat9k.yaml index 649ab86b..3cfffb3a 100644 --- a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_cat9k.yaml +++ b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_cat9k.yaml @@ -613,6 +613,8 @@ config_c9k2: new_state: config_line_c9k2 "redundancy": new_state: config_c9k_redundancy2 + "end": + new_state: enable_c9k2 config_line_c9k2: prompt: "%N(config-line)#" @@ -922,6 +924,8 @@ config_c9k: new_state: config_line_c9k "line console 0": new_state: config_line_c9k + "end": + new_state: enable_c9k config_line_c9k: prompt: "%N(config-line)#" diff --git a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_cat9k_config_session.yaml b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_cat9k_config_session.yaml index 959f6177..60a087c9 100644 --- a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_cat9k_config_session.yaml +++ b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_cat9k_config_session.yaml @@ -127,6 +127,8 @@ c9k_config4: "no boot system": response: "Config session is locked by process '566', user will be pushed back to exec mode. Command execution is locked, Please try later." new_state: c9k_enable4a + "end": + new_state: c9k_enable4a c9k_config4a: prompt: "%N(config)#" 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 50055a0f..5f16bfd7 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 @@ -68,8 +68,8 @@ general_exec: 78085207K bytes of SATA hard disk at harddisk:. Configuration register is 0x1 - - + "config term": + new_state: general_config "enable": new_state: enable_password @@ -658,6 +658,14 @@ standby_exec: commands: "show version | include operating mode": "" "cisco": "" + "config term": + new_state: configure_standby + +configure_standby: + prompt: "%N-standby(conf)#" + commands: + "end": + new_state: standby_exec iosxe_config_1: prompt: "%N(conf)#" @@ -1572,9 +1580,14 @@ acm_if: prompt: "%N(acm-if)#" commands: "description test": "" + "invalid command example": + response: | + invalid command example + ^ + % Invalid input detected at '^' marker. + "description test1": "" "end": new_state: general_enable - syntax_check: prompt: "%N(syntax)#" commands: @@ -1792,6 +1805,26 @@ security_log_message: "": new_state: general_exec +RSA_key_message: + prompt: " %RSA key size needs to be atleast 3072 bitsfor SSH server functionality to be enabled" + commands: + "": + new_state: general_exec + +enable_reload_RSA_log1: + prompt: "%N#" + commands: + <<: *gen_enable_cmds + "reload": + new_state: enable_reload_RSA_log2 + +enable_reload_RSA_log2: + preface: "Press RETURN to get started!" + prompt: "%N>" + commands: + "": + new_state: RSA_key_message + enable_reload_security_log1: prompt: "%N#" commands: diff --git a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_asr.yaml b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_asr.yaml index 54e2d64a..289b1207 100644 --- a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_asr.yaml +++ b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_asr.yaml @@ -80,7 +80,7 @@ enable_asr: "term width 0": "" "show version": *SV "show version | include operating mode" : "" - + "show install summary": "" "disable": new_state: asr_exec "enable": "" @@ -160,6 +160,8 @@ config_asr: new_state: config_line_asr "redundancy": new_state: config_asr_redundancy + "end": + new_state: enable_asr config_line_asr: prompt: "%N(config-line)#" diff --git a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_cat3k.yaml b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_cat3k.yaml index fe2e59ab..c052594e 100644 --- a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_cat3k.yaml +++ b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_cat3k.yaml @@ -217,6 +217,8 @@ enable_cat3k: config_cat3k: prompt: "%N(config)#" commands: + "end": + new_state: enable_cat3k "no logging console": "" "line vty 0 4": new_state: config_line_cat3k diff --git a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_cat9k_reload.yaml b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_cat9k_reload.yaml index 84c6fa26..771ff345 100644 --- a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_cat9k_reload.yaml +++ b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_cat9k_reload.yaml @@ -176,3 +176,34 @@ cat9k_rommon_boot: commands: "": new_state: c9k_disable + +c9800_rommon_boot: + prompt: "WLC#" + commands: + "show version | include operating mode": + response: "Router operating mode: Autonomous" + "config term": + new_state: config_mode + "reload": + response: | + System configuration has been modified. Save? [yes/no]: n + Reload command is being issued on Active unit, this will reload the whole stack + Proceed with reload? [confirm] + Chassis 1 reloading, reason - Reload command + Oct 6 17:33:34.243: %PMAN-5-EXITACTION: F0/0: pvp: Process manager is exiting: + Oct 6 17:33:35.179: %PMAN-5-EXITACTION: R0/0: pvp: Process manager is exiting: process exit with reload chassis code + new_state: rommon_prompt + +config_mode: + prompt: "WLC(config)#" + commands: + "config-register 0x0": + response: "" + "end": + new_state: c9800_rommon_boot + +rommon_prompt: + prompt: "grub>" + commands: + "": + response: "" \ No newline at end of file diff --git a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_isr.yaml b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_isr.yaml index c7819282..1e65c86c 100644 --- a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_isr.yaml +++ b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_isr.yaml @@ -216,6 +216,8 @@ config_isr: new_state: config_line_isr "line vty 0 4": new_state: config_line_isr + "end": + new_state: enable_isr config_line_isr: prompt: "%N(config-line)#" diff --git a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_vwlc.yaml b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_vwlc.yaml index 7bba0ae7..8dd7b83c 100644 --- a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_vwlc.yaml +++ b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_vwlc.yaml @@ -14,9 +14,17 @@ c9k_vwlc_enable: prompt: "%N#" commands: "show version | include operating mode": "" + "config term": + new_state: c9k_vwlc_configure "reload": new_state: c9k_vwlc_system_config_change +c9k_vwlc_configure: + prompt: "%N(config)#" + commands: + "end": + new_state: c9k_vwlc_enable + c9k_vwlc_system_config_change: prompt: "\nSystem configuration has been modified. Save? [yes/no]:" commands: diff --git a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_svl_stack.yaml b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_svl_stack.yaml index 4193e1c9..95e868d9 100644 --- a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_svl_stack.yaml +++ b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_svl_stack.yaml @@ -22,9 +22,8 @@ svl_stack_exec: H/W Current Switch# Role Mac Address Priority Version State ------------------------------------------------------------------------------------- - 1 Standby bcc4.9346.7880 1 V01 Ready - *2 Active bcc4.9346.9180 3 V04 Ready - + *1 Active bcc4.9346.7880 1 V01 Ready + 2 Standby bcc4.9346.9180 3 V04 Ready "show version": &SV |2 Cisco IOS XE Software, Version BLD_V1612_THROTTLE_LATEST_20200403_053502_V16_12_3_6 Cisco IOS Software [Gibraltar], Catalyst L3 Switch Software (CAT3K_CAA-UNIVERSALK9-M), Experimental Version 16.12.20200403:060733 [S2C-build-v1612_throttle-BLD_V1612_THROTTLE_S2C_20200403_035148-/nobackup/mcpre/BLD-BLD_V1612_THROTTLE_LATEST_20200403_053502 132] @@ -112,8 +111,8 @@ svl_stack_exec: Switch Ports Model SW Version SW Image Mode ------ ----- ----- ---------- ---------- ---- - 1 32 WS-C3850-24P 16.12.4 CAT3K_CAA-UNIVERSALK9 BUNDLE - * 2 32 WS-C3850-24P 16.12.4 CAT3K_CAA-UNIVERSALK9 BUNDLE + * 1 32 WS-C3850-24P 16.12.4 CAT3K_CAA-UNIVERSALK9 BUNDLE + 2 32 WS-C3850-24P 16.12.4 CAT3K_CAA-UNIVERSALK9 BUNDLE Switch 01 @@ -143,13 +142,12 @@ svl_stack_exec: Last reload reason : Admin reload CLI Configuration register is 0x102 - - "sh redundancy state": &SRS |2 - my state = 13 -ACTIVE - peer state = 8 -STANDBY HOT - Mode = Duplex - Unit = Primary - Unit ID = 4 + "sh redundancy state": &ASRS |2 + my state = 13 -ACTIVE + peer state = 8 -STANDBY HOT + Mode = Duplex + Unit = Primary + Unit ID = 4 Redundancy Mode (Operational) = sso Redundancy Mode (Configured) = sso @@ -161,6 +159,17 @@ svl_stack_exec: client count = 113 client_notification_TMR = 30000 milliseconds RF debug mask = 0x0 + "show install summary": &SIS |2 + [ Chassis 1/R0 2/R0 ] Installed Package(s) Information: + State (St): I - Inactive, U - Activated & Uncommitted, + C - Activated & Committed, D - Deactivated & Uncommitted + -------------------------------------------------------------------------------- + Type St Filename/Version + -------------------------------------------------------------------------------- + IMG C 26.01.01.0.227079 + -------------------------------------------------------------------------------- + Auto abort timer: inactive + -------------------------------------------------------------------------------- "enable": new_state: svl_enable_pwd @@ -179,11 +188,14 @@ svl_stack_enable: "show version": *SV "show version | include operating mode" : "" "show switch": *SS - "sh redundancy state": *SRS + "sh redundancy state": *ASRS + "sh redundancy stat | inc my state": | + my state = 13 -ACTIVE + "show install summary": *SIS + "disable": new_state: svl_stack_exec "enable": "" - "config term": new_state: svl_stack_config @@ -243,69 +255,155 @@ svl_switchover_prompt2: timing: - 0:6,0,0.02 - 6:,1,0.005 - new_state: svl_stby_login + new_state: svl_post_switchover_standby_login -# -----------standby-------------------- +# -----------standby before switchover-------------------- -svl_stby_login: +svl_initial_standby_login: prompt: "Username: " commands: "cisco": - new_state: svl_stby_password + new_state: svl_initial_standby_password + +svl_initial_standby_password: # Initial password for p2 + prompt: "Password: " + commands: + "cisco": + new_state: svl_initial_standby_exec + +svl_initial_standby_exec: + prompt: "%N-stby>" + commands: + "term length 0": "" + "term width 0": "" + "show version | include operating mode" : "" + "show switch": *SS + "show version": *SV + "sh redundancy state": &SRS |2 + my state = 8 -STANDBY HOT + peer state = 13 -ACTIVE + Mode = Duplex + Unit = Secondary + Unit ID = 4 + + Redundancy Mode (Operational) = sso + Redundancy Mode (Configured) = sso + Redundancy State = sso + Maintenance Mode = Disabled + Manual Swact = enabled + Communications = Up + + client count = 113 + client_notification_TMR = 30000 milliseconds + RF debug mask = 0x0 + + "sh redundancy stat | inc my state": | + my state = 8 -STANDBY HOT + "enable": + new_state: svl_initial_standby_enable_pwd + +svl_initial_standby_enable_pwd: + prompt: "Password: " + commands: + "cisco": + new_state: svl_initial_standby_enable + +svl_initial_standby_enable: + prompt: "%N-stby#" + commands: + "term length 0": "" + "term width 0": "" + "show version | include operating mode" : "" + "show version": *SV + "show switch": *SS + "sh redundancy state": *SRS + "sh redundancy stat | inc my state": | + my state = 8 -STANDBY HOT + "disable": + new_state: svl_initial_standby_exec + "config term": + new_state: svl_initial_standby_config + +svl_initial_standby_config: + prompt: "%N-stby(config)#" + commands: + "no logging console": "" + "line console 0": + new_state: svl_stack_config_line + "line vty 0 4": + new_state: svl_stack_config_line + "end": + new_state: svl_initial_standby_enable + "redundancy": + new_state: svl_initial_standby_config_redundancy + +svl_initial_standby_config_redundancy: + prompt: "%N(config-red)#" + commands: + "main-cpu": + new_state: svl_config_stack_redundancy_main_cpu + "end": + new_state: svl_initial_standby_enable -svl_stby_password: +# -----------standby post switchover-------------------- + +svl_post_switchover_standby_login: + prompt: "Username: " + commands: + "cisco": + new_state: svl_post_switchover_standby_password + +svl_post_switchover_standby_password: prompt: "Password: " commands: "cisco": - new_state: svl_stby_exec + new_state: svl_post_switchover_standby_exec -svl_stby_exec: +svl_post_switchover_standby_exec: prompt: "%N-stby>" commands: "term length 0": "" "term width 0": "" - "show switch": &SSS |2 + "show switch": &PSS |2 Switch/Stack Mac Address : 00be.7574.6b0c - Local Mac Address Mac persistency wait time: Indefinite H/W Current Switch# Role Mac Address Priority Version State ------------------------------------------------------------------------------------- - 1 Active 00be.7574.6b0c 0 V02 Ready - *2 Standby 2cf8.9bb9.5648 0 V02 Ready + 1 Standby 00be.7574.6b0c 0 V02 Ready + *2 Active 2cf8.9bb9.5648 0 V02 Ready + "show version": *SV "show version | include operating mode" : "" "sh redundancy state": *SRS + "sh redundancy stat | inc my state": | + my state = 8 -STANDBY HOT "enable": - new_state: svl_stby_enable_pwd + new_state: svl_post_switchover_standby_enable_pwd -svl_stby_enable_pwd: +svl_post_switchover_standby_enable_pwd: prompt: "Password: " commands: "cisco": - new_state: svl_stby_enable + new_state: svl_post_switchover_standby_enable -svl_stby_enable: +svl_post_switchover_standby_enable: prompt: "%N-stby#" commands: "term length 0": "" "term width 0": "" "show version | include operating mode" : "" "show version": *SV - "show switch": *SSS + "show switch": *PSS "sh redundancy state": *SRS + "sh redundancy stat | inc my state": | + my state = 8 -STANDBY HOT "disable": - new_state: svl_stby_exec + new_state: svl_post_switchover_standby_exec "config term": - new_state: svl_stby_stack_config - "enable": "" - -svl_standby_switchover: - prompt: "%N-stby>" - commands: - "enable": - new_state: svl_stby_enable + new_state: svl_post_switchover_standby_config -svl_stby_stack_config: +svl_post_switchover_standby_config: prompt: "%N-stby(config)#" commands: "no logging console": "" @@ -314,14 +412,79 @@ svl_stby_stack_config: "line vty 0 4": new_state: svl_stack_config_line "end": - new_state: svl_stack_enable + new_state: svl_post_switchover_standby_enable "redundancy": - new_state: svl_stby_config_stack_redundancy + new_state: svl_post_switchover_standby_config_redundancy -svl_stby_config_stack_redundancy: +svl_post_switchover_standby_config_redundancy: prompt: "%N(config-red)#" commands: "main-cpu": new_state: svl_config_stack_redundancy_main_cpu "end": - new_state: svl_stby_enable + new_state: svl_post_switchover_standby_enable + +# -----------Active post switchover-------------------- + +svl_post_switchover_active_login: + prompt: "Username: " + commands: + "cisco": + new_state: svl_post_switchover_active_password + +svl_post_switchover_active_password: + prompt: "Password: " + commands: + "cisco": + new_state: svl_post_switchover_active_exec + +svl_post_switchover_active_exec: + prompt: "%N>" + commands: + "term length 0": "" + "term width 0": "" + "show version | include operating mode" : "" + "show switch": *PSS + "show version": *SV + "sh redundancy state": *ASRS + "sh redundancy stat | inc my state": | + my state = 13 -ACTIVE + "show install summary": *SIS + "enable": + new_state: svl_post_switchover_active_enable_pwd + +svl_post_switchover_active_enable_pwd: + prompt: "Password: " + commands: + "cisco": + new_state: svl_post_switchover_active_enable + +svl_post_switchover_active_enable: + prompt: "%N#" + commands: + "term length 0": "" + "term width 0": "" + "show version": *SV + "show version | include operating mode" : "" + "show switch": *PSS + "sh redundancy state": *ASRS + "sh redundancy stat | inc my state": | + my state = 13 -ACTIVE + "show install summary": *SIS + "disable": + new_state: svl_post_switchover_active_exec + "config term": + new_state: svl_post_switchover_active_config + +svl_post_switchover_active_config: + prompt: "%N(config)#" + commands: + "no logging console": "" + "line console 0": + new_state: svl_stack_config_line + "line vty 0 4": + new_state: svl_stack_config_line + "end": + new_state: svl_post_switchover_active_enable + "redundancy": + new_state: svl_config_stack_redundancy diff --git a/src/unicon/plugins/tests/mock_data/linux/linux_mock_data.yaml b/src/unicon/plugins/tests/mock_data/linux/linux_mock_data.yaml index 5bf57420..30d65e5a 100644 --- a/src/unicon/plugins/tests/mock_data/linux/linux_mock_data.yaml +++ b/src/unicon/plugins/tests/mock_data/linux/linux_mock_data.yaml @@ -302,7 +302,10 @@ exec: response: "ls -ltr\r\n\x1b[?2004l\rtotal 35828\r\ndrwxr-xr-x 11 root root 4096 Feb 22 14:21 \x1b[01;34mdata\x1b[0m\r\n\x1b[?2004hndfc-web:~ # " timing: - 0:,0,0.1,0.01 - + "uptime": + response: "20:17:07 up 1 min, 1 user, load average: 0.13, 0.05, 0.02" + timing: + - 0:,0,0.1,0.01 trex_console: prompt: "trex> " @@ -633,9 +636,30 @@ ios_sw4_enable: "term length 0": "" "term width 0": "" "show version": "" + "show install summary": "" + "config term": + new_state: ios_sw4_config +ios_sw4_config: + prompt: "Sw04(config)#" + commands: + "no logging console": "" + "line console 0": + new_state: ios_sw4_config_line + "line vty 0 4": + new_state: ios_sw4_config_line + "exit": + new_state: ios_sw4_enable + "end": + new_state: ios_sw4_enable - +ios_sw4_config_line: + prompt: "Sw04(config-line)#" + commands: + "exec-timeout 0": "" + "line vty 0 4": "" + "end": + new_state: ios_sw4_enable exec_ps1: prompt: "Linux$ " diff --git a/src/unicon/plugins/tests/test_iosxe_patterns.py b/src/unicon/plugins/tests/test_iosxe_patterns.py new file mode 100644 index 00000000..9a645349 --- /dev/null +++ b/src/unicon/plugins/tests/test_iosxe_patterns.py @@ -0,0 +1,66 @@ +""" +Unit tests for IOS-XE patterns. + +Tests to ensure patterns like disable_prompt do not incorrectly match +rommon prompts or other unintended strings. +""" + +import unittest +import re +from unicon.plugins.iosxe.patterns import IosXEPatterns + + +class TestIosXEDisablePrompt(unittest.TestCase): + """Test cases for IOS-XE disable_prompt pattern.""" + + def setUp(self): + """Set up test patterns.""" + self.patterns = IosXEPatterns() + self.disable_pattern = self.patterns.disable_prompt + + def test_disable_prompt_matches_valid_prompts(self): + """Test that disable_prompt matches valid disable mode prompts.""" + valid_prompts = [ + 'Router>', + 'Switch>', + 'ios>', + 'wlc>', + 'WLC>', + 'RouterRP>', + 'Router1>', + 'Switch2>', + 'Router(boot)>', + 'Router(standby)>', + 'Router-stby>', + 'Router-standby>', + 'Router(recovery-mode)>', + 'Router(rp-rec-mode)>' + ] + + for prompt in valid_prompts: + with self.subTest(prompt=prompt): + match = re.search(self.disable_pattern, prompt) + self.assertIsNotNone(match, + f"Pattern should match valid disable prompt: {prompt}") + + def test_disable_prompt_does_not_match_rommon_prompts(self): + """Test that disable_prompt does NOT match rommon prompts.""" + rommon_prompts = [ + 'rommon>', + 'rommon 1>', + 'rommon 2 >', + 'rommon>', + 'switch:', + 'grub>', + 'grub >', + ] + + for prompt in rommon_prompts: + with self.subTest(prompt=prompt): + match = re.search(self.disable_pattern, prompt) + self.assertIsNone(match, + f"Pattern should NOT match rommon prompt: {prompt}") + + +if __name__ == '__main__': + unittest.main() diff --git a/src/unicon/plugins/tests/test_plugin_iosxe.py b/src/unicon/plugins/tests/test_plugin_iosxe.py index 2baa168d..5b272816 100644 --- a/src/unicon/plugins/tests/test_plugin_iosxe.py +++ b/src/unicon/plugins/tests/test_plugin_iosxe.py @@ -1453,6 +1453,21 @@ def test_reload_security_log_message(self): finally: d.disconnect() + def test_reload_RSA_key_log_message(self): + d = Connection( + hostname='Router', + start=['mock_device_cli --os iosxe --state enable_reload_RSA_log1 --hostname Router'], + os='iosxe', + credentials=dict(default=dict(username='cisco', password='cisco')), + log_buffer=True, + mit=True, + ) + try: + d.connect() + d.reload(post_reload_wait_time=3) + finally: + d.disconnect() + class TestIosxeAsr1k(unittest.TestCase): def test_connect_asr1k_ha(self): @@ -1637,6 +1652,33 @@ def test_acm_rules_configure(self): c.configure(config_txt, rules=True) c.disconnect() + +class TestIosxeAcmConfigureInvalidInput(unittest.TestCase): + + def test_acm_configure_invalid_input(self): + c = Connection( + hostname='PE1', + start=['mock_device_cli --os iosxe --state general_enable --hostname PE1'], + os='iosxe', + mit=True + ) + c.connect() + config_txt = [ + 'interface loopback 1', + 'description test', + 'invalid command example', # this should trigger % Invalid input + 'description test1' + ] + with self.assertRaises(SubCommandFailure): + c.configure(config_txt, acm_configlet='my_config') + self.assertEqual( + c.state_machine.current_state, + 'enable', + f"Device not recovered to exec mode after invalid input, current: {c.state_machine.current_state}" + ) + c.disconnect() + + class TestIosxeSyntaxConfigure(unittest.TestCase): def test_syntax_configure(self): diff --git a/src/unicon/plugins/tests/test_plugin_iosxe_cat9k.py b/src/unicon/plugins/tests/test_plugin_iosxe_cat9k.py index 32dbfe83..675fb42e 100644 --- a/src/unicon/plugins/tests/test_plugin_iosxe_cat9k.py +++ b/src/unicon/plugins/tests/test_plugin_iosxe_cat9k.py @@ -909,5 +909,26 @@ def test_syslog_messages(self): finally: d.disconnect() +class RommonwithConfigRegister(unittest.TestCase): + + def test_rommon_service_transitions_to_rommon(self): + c = Connection( + hostname='switch', + start=['mock_device_cli --os iosxe --state c9800_rommon_boot'], + os='iosxe', + platform='cat9k', + model='c9800', + mit=True, + credentials=dict(default=dict(username='cisco', password='cisco')), + settings=dict(POST_DISCONNECT_WAIT_SEC=0, GRACEFUL_DISCONNECT_WAIT_SEC=0.2), + log_buffer=True + ) + try: + c.connect() + c.rommon() + self.assertEqual(c.state_machine.current_state, 'rommon') + finally: + c.disconnect() + if __name__ == '__main__': unittest.main() diff --git a/src/unicon/plugins/tests/test_plugin_iosxe_ha.py b/src/unicon/plugins/tests/test_plugin_iosxe_ha.py index 360c89d1..a20e7d4f 100644 --- a/src/unicon/plugins/tests/test_plugin_iosxe_ha.py +++ b/src/unicon/plugins/tests/test_plugin_iosxe_ha.py @@ -155,27 +155,6 @@ def test_switchover(self): self.c.execute('redundancy force-switchover') -class TestIosXEPluginSwitchoverWithStandbyCredentials(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.c = Connection( - hostname='Router', - start=['mock_device_cli --os iosxe --state c9k_login3'], - os='iosxe', - credentials=dict( - default=dict( - username='admin', password='cisco'), - enable=dict( - username='admin', password='cisco'), - disable=dict( - username='admin', password='cisco'))) - cls.c.connect() - - def test_switchover(self): - self.c.execute('redundancy force-switchover') - - class TestIosXEEnableSecret(unittest.TestCase): @classmethod diff --git a/src/unicon/plugins/tests/test_plugin_iosxe_svl_switchover.py b/src/unicon/plugins/tests/test_plugin_iosxe_svl_switchover.py index 3b336c4a..4f8cd426 100644 --- a/src/unicon/plugins/tests/test_plugin_iosxe_svl_switchover.py +++ b/src/unicon/plugins/tests/test_plugin_iosxe_svl_switchover.py @@ -16,11 +16,99 @@ unicon.settings.Settings.GRACEFUL_DISCONNECT_WAIT_SEC = 0.2 +class TestIosXEStackConnect(unittest.TestCase): + + def test_svl_connect1(self): + md = MockDeviceTcpWrapperIOSXE(port=0, state='svl_stack_enable' + ',svl_initial_standby_login', stackwise_virtual=True) + md.start() + testbed = ''' + devices: + Router: + type: router + os: iosxe + platform: cat9k + model: c9500 + submodel: c9500x + chassis_type: stackwise_virtual + connections: + defaults: + class: 'unicon.Unicon' + connections: [p1, p2] + p1: + protocol: telnet + ip: 127.0.0.1 + port: {} + member: 1 + p2: + protocol: telnet + ip: 127.0.0.1 + port: {} + member: 2 + credentials: + default: + username: cisco + password: cisco + '''.format(md.ports[0], md.ports[1]) + t = loader.load(testbed) + d = t.devices.Router + + try: + d.connect() + self.assertTrue(d.active.alias == 'p1') + d.execute('term width 0') + d.configure('no logging console') + finally: + d.disconnect() + md.stop() + + def test_svl_connect2(self): + md = MockDeviceTcpWrapperIOSXE(port=0, state='svl_stack_enable' + ',svl_initial_standby_login', stackwise_virtual=True) + md.start() + testbed = ''' + devices: + Router: + type: router + os: iosxe + platform: cat9k + model: c9500 + submodel: c9500x + chassis_type: stackwise_virtual + connections: + defaults: + class: 'unicon.Unicon' + connections: [p2, p1] + p2: + protocol: telnet + ip: 127.0.0.1 + port: {} + member: 1 + p1: + protocol: telnet + ip: 127.0.0.1 + port: {} + member: 2 + credentials: + default: + username: cisco + password: cisco + '''.format(md.ports[0], md.ports[1]) + t = loader.load(testbed) + d = t.devices.Router + + try: + d.connect() + self.assertTrue(d.active.alias == 'p2') + d.execute('term width 0') + d.configure('no logging console') + finally: + d.disconnect() + md.stop() + class TestIosXESVLSwitchover(unittest.TestCase): def test_svl_stack_switchover(self): - md = MockDeviceTcpWrapperIOSXE(port=0, state='svl_stack_enable' + ',svl_stby_enable', stackwise_virtual=True) + md = MockDeviceTcpWrapperIOSXE(port=0, state='svl_stack_enable' + ',svl_initial_standby_login', stackwise_virtual=True) md.start() testbed = ''' devices: @@ -34,7 +122,7 @@ def test_svl_stack_switchover(self): connections: defaults: class: 'unicon.Unicon' - connections: [p1, p2, p3] + connections: [p1, p2] p1: protocol: telnet ip: 127.0.0.1 @@ -53,15 +141,16 @@ def test_svl_stack_switchover(self): t = loader.load(testbed) d = t.devices.Router - d.connect() - self.assertTrue(d.active.alias == 'p2') - - d.settings.POST_SWITCHOVER_SLEEP = 1 - d.execute('term width 0') - d.configure('no logging console') - d.switchover(timeout=10) - d.disconnect() - md.stop() + try: + d.connect() + self.assertTrue(d.active.alias == 'p1') + d.settings.POST_SWITCHOVER_SLEEP = 1 + d.execute('term width 0') + d.configure('no logging console') + d.switchover(timeout=10) + finally: + d.disconnect() + md.stop() if __name__ == "__main__": diff --git a/src/unicon/plugins/tests/test_plugin_iosxr_ha.py b/src/unicon/plugins/tests/test_plugin_iosxr_ha.py index 2ab87655..1ea6a202 100644 --- a/src/unicon/plugins/tests/test_plugin_iosxr_ha.py +++ b/src/unicon/plugins/tests/test_plugin_iosxr_ha.py @@ -69,6 +69,46 @@ def test_attach_console(self): self.assertIn('exit', ret) self.assertIn('Router#', ret) + +class TestIOSXRPluginHAConnectLearnTokens(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.md = MockDeviceTcpWrapperIOSXR(port=0, state='login,console_standby') + cls.md.start() + + cls.testbed = """ + devices: + Router: + os: iosxr + type: router + tacacs: + username: admin + passwords: + tacacs: admin + connections: + defaults: + class: unicon.Unicon + a: + protocol: telnet + ip: 127.0.0.1 + port: {} + b: + protocol: telnet + ip: 127.0.0.1 + port: {} + """.format(cls.md.ports[0], cls.md.ports[1]) + tb = loader.load(cls.testbed) + cls.r = tb.devices.Router + + @classmethod + def tearDownClass(self): + self.md.stop() + + def test_learn_tokens_ha(self): + self.r.connect(learn_tokens=True) + + class TestIOSXRPluginHAConnectAdmin(unittest.TestCase): @classmethod diff --git a/src/unicon/plugins/tests/test_plugin_linux.py b/src/unicon/plugins/tests/test_plugin_linux.py index 407ca158..28b430e9 100644 --- a/src/unicon/plugins/tests/test_plugin_linux.py +++ b/src/unicon/plugins/tests/test_plugin_linux.py @@ -757,6 +757,8 @@ def test_override_shell_prompt(self): settings = LinuxSettings() prompt = 'shell_prompt' settings.SHELL_PROMPT = prompt + # set it to empty so that the default prompt does not interfere + settings.LINUX_INIT_EXEC_COMMANDS = [] c = Connection(hostname='linux', start=['mock_device_cli --os linux --state exec'], os='linux', diff --git a/src/unicon/plugins/tests/test_plugin_nd.py b/src/unicon/plugins/tests/test_plugin_nd.py index 4a36576e..5d340760 100644 --- a/src/unicon/plugins/tests/test_plugin_nd.py +++ b/src/unicon/plugins/tests/test_plugin_nd.py @@ -723,6 +723,8 @@ def test_override_shell_prompt(self): settings = LinuxSettings() prompt = 'shell_prompt' settings.SHELL_PROMPT = prompt + # set it to empty so that the default prompt does not interfere + settings.LINUX_INIT_EXEC_COMMANDS = [] c = Connection(hostname='nd', start=['mock_device_cli --os nd --state exec'], os='nd',