diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 2fc814dd..15816be7 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] group: [1, 2, 3, 4, 5] steps: - uses: actions/checkout@v2 diff --git a/docs/changelog/2025/july.rst b/docs/changelog/2025/july.rst new file mode 100644 index 00000000..4f7f956c --- /dev/null +++ b/docs/changelog/2025/july.rst @@ -0,0 +1,42 @@ +July 2025 +========== + +July 29 - Unicon v25.7 +------------------------ + + + +.. csv-table:: Module Versions + :header: "Modules", "Versions" + + ``unicon.plugins``, v25.7 + ``unicon``, v25.7 + + + + +Changelogs +^^^^^^^^^^ +-------------------------------------------------------------------------------- + Fix +-------------------------------------------------------------------------------- + +* bases/router + * connection_provider + * Updated logout service logic for single rp and multi rp connections + +* router/connection + * Initialized the UNICON_BACKEND_DECODE_ERROR_LIMIT to None for iosxr HA device connections + + +-------------------------------------------------------------------------------- + New +-------------------------------------------------------------------------------- + +* mock device + * Add mock device for svl stack + +* iosxe + * Added cert-trustpool config pattern + + diff --git a/docs/changelog/index.rst b/docs/changelog/index.rst index f0203d1d..8c9c2b08 100644 --- a/docs/changelog/index.rst +++ b/docs/changelog/index.rst @@ -4,6 +4,7 @@ Changelog .. toctree:: :maxdepth: 2 + 2025/july 2025/june 2025/may 2025/april diff --git a/docs/changelog_plugins/2025/july.rst b/docs/changelog_plugins/2025/july.rst new file mode 100644 index 00000000..d45e1738 --- /dev/null +++ b/docs/changelog_plugins/2025/july.rst @@ -0,0 +1,39 @@ +July 2025 +========== + +July 29 - Unicon.Plugins v25.7 +------------------------ + + + +.. csv-table:: Module Versions + :header: "Modules", "Versions" + + ``unicon.plugins``, v25.7 + ``unicon``, v25.7 + + + + +Changelogs +^^^^^^^^^^ +-------------------------------------------------------------------------------- + Fix +-------------------------------------------------------------------------------- + +* iosxr + * switchover + * Fixed timeout handling by using explicit timeout parameter instead of self.timeout. + * Update monitor service prompt pattern + * Increase monitor stop timeout + * Update execute() service to exit unsupported modes (e.g. monitor mode) + + +-------------------------------------------------------------------------------- + New +-------------------------------------------------------------------------------- + +* iosxe + * Added support for 9500 and 9500x SVL switchover + + diff --git a/docs/changelog_plugins/changelog_modify_generice_rommon_prompt_pattern_20241028185708.rst b/docs/changelog_plugins/changelog_modify_generice_rommon_prompt_pattern_20241028185708.rst deleted file mode 100644 index 4ba1e475..00000000 --- a/docs/changelog_plugins/changelog_modify_generice_rommon_prompt_pattern_20241028185708.rst +++ /dev/null @@ -1,5 +0,0 @@ --------------------------------------------------------------------------------- - Fix --------------------------------------------------------------------------------- -* Generic - * Modified rommon_prompt regex pattern to accommodate various outputs diff --git a/docs/changelog_plugins/index.rst b/docs/changelog_plugins/index.rst index 86f4672f..e68da516 100644 --- a/docs/changelog_plugins/index.rst +++ b/docs/changelog_plugins/index.rst @@ -4,6 +4,7 @@ Plugins Changelog .. toctree:: :maxdepth: 2 + 2025/july 2025/june 2025/may 2025/april diff --git a/docs/conf.py b/docs/conf.py index 50f1193e..d7960ac6 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -36,7 +36,7 @@ 'sphinx.ext.autodoc', 'sphinx.ext.napoleon', 'sphinx.ext.intersphinx', - 'sphinxcontrib.mockautodoc', + # 'sphinxcontrib.mockautodoc', ] if os.environ.get('DEVNET', None) == 'true': diff --git a/src/unicon/plugins/__init__.py b/src/unicon/plugins/__init__.py index e2440643..f3565396 100644 --- a/src/unicon/plugins/__init__.py +++ b/src/unicon/plugins/__init__.py @@ -1,4 +1,4 @@ -__version__ = "25.6" +__version__ = "25.7" supported_chassis = [ 'single_rp', diff --git a/src/unicon/plugins/iosxe/cat9k/c9500x/stackwise_virtual/__init__.py b/src/unicon/plugins/iosxe/cat9k/c9500x/stackwise_virtual/__init__.py index ded18442..b59e13c0 100644 --- a/src/unicon/plugins/iosxe/cat9k/c9500x/stackwise_virtual/__init__.py +++ b/src/unicon/plugins/iosxe/cat9k/c9500x/stackwise_virtual/__init__.py @@ -13,6 +13,7 @@ class IosXEC9500xStackwiseVirtualServiceList(StackIosXEServiceList): def __init__(self): super().__init__() self.reload = svc.SVLStackReload + self.switchover = svc.SVLStackSwitchover class IosXEC9500xStackwiseVirtualRPConnection(IosXEStackRPConnection): diff --git a/src/unicon/plugins/iosxe/cat9k/c9500x/stackwise_virtual/service_implementation.py b/src/unicon/plugins/iosxe/cat9k/c9500x/stackwise_virtual/service_implementation.py index 5b7b04c3..acaaa34c 100644 --- a/src/unicon/plugins/iosxe/cat9k/c9500x/stackwise_virtual/service_implementation.py +++ b/src/unicon/plugins/iosxe/cat9k/c9500x/stackwise_virtual/service_implementation.py @@ -11,7 +11,8 @@ from unicon.plugins.iosxe.stack.utils import StackUtils from unicon.plugins.generic.statements import custom_auth_statements from unicon.plugins.iosxe.stack.service_statements import (switch_prompt, - stack_reload_stmt_list) + stack_reload_stmt_list, + stack_switchover_stmt_list) utils = StackUtils() @@ -247,3 +248,110 @@ def boot(con): if return_output: Result = namedtuple('Result', ['result', 'output']) self.result = Result(self.result, reload_cmd_output.match_output.replace(reload_cmd, '', 1)) + + +class SVLStackSwitchover(BaseService): + """ Get Rp state + + Service to get the redundancy state of the device rp. + + Arguments: + target: Service target, by default active + + Returns: + Expected return values are ACTIVE, STANDBY, MEMBER + raise SubCommandFailure on failure. + + Example: + .. code-block:: python + + rtr.get_rp_state() + rtr.get_rp_state(target='standby') + """ + + def __init__(self, connection, context, **kwargs): + super().__init__(connection, context, **kwargs) + self.start_state = 'enable' + self.end_state = 'enable' + self.timeout = connection.settings.STACK_SWITCHOVER_TIMEOUT + self.command = "redundancy force-switchover" + self.dialog = Dialog(stack_switchover_stmt_list) + self.__dict__.update(kwargs) + + def call_service(self, command=None, + reply=Dialog([]), + timeout=None, + *args, **kwargs): + + switchover_cmd = command or self.command + timeout = timeout or self.timeout + conn = self.connection.active + + expected_active_sw = self.connection.standby.member_id + dialog = self.dialog + + if reply: + dialog = reply + self.dialog + + # added connection dialog in case switchover ask for username/password + connect_dialog = self.connection.connection_provider.get_connection_dialog() + dialog += connect_dialog + + conn.log.info('Processing on active rp %s-%s' % (conn.hostname, conn.alias)) + conn.sendline(switchover_cmd) + try: + # A loop has been implemented to handle the + # "Press RETURN to get started" prompt twice. Based on extensive + # testing during SVL reloads on 9500x devices, it was observed + # that the device is not fully ready after the first prompt. + # As a result, the logic accounts for this behavior by waiting for + # the second occurrence of the message, which is assumed to be the + # default behavior for these devices. + for _ in range(2): + match_object = dialog.process(conn.spawn, timeout=timeout, + prompt_recovery=self.prompt_recovery, + context=conn.context) + except Exception as e: + raise SubCommandFailure('Error during switchover ', e) from e + + # try boot up original active rp with current active system + # image, if it moved to rommon state. + if 'state' in conn.context and conn.context.state == 'rommon': + try: + conn.state_machine.detect_state(conn.spawn, context=conn.context) + conn.state_machine.go_to('enable', conn.spawn, timeout=timeout, + prompt_recovery=self.prompt_recovery, + context=conn.context, dialog=Dialog([switch_prompt])) + except Exception as e: + self.connection.log.warning('Fail to bring up original active rp from rommon state.', e) + finally: + conn.context.pop('state') + + # To ensure the stack is ready to accept the login + self.connection.log.info('Sleeping for %s secs.' % \ + self.connection.settings.POST_SWITCHOVER_SLEEP) + sleep(self.connection.settings.POST_SWITCHOVER_SLEEP) + + # check all members are ready + conn.state_machine.detect_state(conn.spawn, context=conn.context) + + interval = self.connection.settings.SWITCHOVER_POSTCHECK_INTERVAL + if utils.is_all_member_ready(conn, timeout=timeout, interval=interval): + self.connection.log.info('All members are ready.') + else: + self.connection.log.info('Timeout in %s secs. ' + 'Not all members are in Ready state.' % timeout) + self.result = False + return + + self.connection.log.info('Disconnecting and reconnecting') + self.connection.disconnect() + self.connection.connect() + + self.connection.log.info('Verifying active and standby switch State.') + if self.connection.active.member_id == expected_active_sw: + self.connection.log.info('Switchover successful') + self.result = True + else: + self.connection.log.info('Switchover failed') + self.result = False diff --git a/src/unicon/plugins/iosxe/cat9k/stackwise_virtual/__init__.py b/src/unicon/plugins/iosxe/cat9k/stackwise_virtual/__init__.py index 62c6e2a7..647fe27b 100644 --- a/src/unicon/plugins/iosxe/cat9k/stackwise_virtual/__init__.py +++ b/src/unicon/plugins/iosxe/cat9k/stackwise_virtual/__init__.py @@ -5,7 +5,7 @@ from unicon.plugins.iosxe.stack import IosXEStackRPConnection from .connection_provider import StackwiseVirtualConnectionProvider -from unicon.plugins.iosxe.stack.service_implementation import StackReload +from unicon.plugins.iosxe.stack.service_implementation import StackReload, StackSwitchover class IosXECat9kStackwiseVirtualServiceList(StackIosXEServiceList): @@ -13,6 +13,7 @@ class IosXECat9kStackwiseVirtualServiceList(StackIosXEServiceList): def __init__(self): super().__init__() self.reload = StackReload + self.switchover = StackSwitchover class IosXECat9kStackwiseVirtualRPConnection(IosXEStackRPConnection): diff --git a/src/unicon/plugins/iosxe/patterns.py b/src/unicon/plugins/iosxe/patterns.py index c6a415db..0aa5d41c 100644 --- a/src/unicon/plugins/iosxe/patterns.py +++ b/src/unicon/plugins/iosxe/patterns.py @@ -29,7 +29,7 @@ def __init__(self): self.maintenance_mode_prompt = \ 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|ca-trustpool)\S*\)#\s?$' + 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|ca-trustpool|cert-trustpool)\S*\)#\s?$' self.config_pki_prompt = r'^(.*)\(config-pki-hexmode\)#\s?$' diff --git a/src/unicon/plugins/iosxr/patterns.py b/src/unicon/plugins/iosxr/patterns.py index 7df84e98..8cb08414 100755 --- a/src/unicon/plugins/iosxr/patterns.py +++ b/src/unicon/plugins/iosxr/patterns.py @@ -56,4 +56,4 @@ def __init__(self): # r1 Monitor Time: 00:00:06 SysUptime: 15:48:49 self.monitor_time_regex = r'(?P\S+).*?Monitor Time: (?P