diff --git a/Makefile b/Makefile index a7e23825..38b4d19c 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ PYPIREPO = pypitest DEPENDENCIES = robotframework pyyaml dill coverage Sphinx==7.4.7 \ sphinxcontrib-napoleon sphinxcontrib-mockautodoc \ - sphinx-rtd-theme asyncssh PrettyTable "cryptography>=43.0" + sphinx-rtd-theme==3.1.0 asyncssh PrettyTable "cryptography>=43.0" .PHONY: clean package distribute develop undevelop help devnet\ diff --git a/docs/changelog/2016/may.rst b/docs/changelog/2016/may.rst index 51190a1f..0b785e63 100644 --- a/docs/changelog/2016/may.rst +++ b/docs/changelog/2016/may.rst @@ -18,5 +18,5 @@ Features: - Support for linux platform - Support for NXOS vdc - Unicon Device logging enhancements - Shorthand notations for Dialog callbacks - - Renamed `config` service to `Configure` + - Renamed ``config``service to``Configure`` - Bug fixes diff --git a/docs/changelog/2018/february.rst b/docs/changelog/2018/february.rst index 09b53da8..f746ec87 100644 --- a/docs/changelog/2018/february.rst +++ b/docs/changelog/2018/february.rst @@ -27,12 +27,12 @@ Features and Bug Fixes: - Added learn hostname feature - Linux connections now support the `learn_hostname` connect option to + Linux connections now support the ``learn_hostname`` connect option to automatically learn the hostname. This improves reliable prompt matching as the default prompt matching may result in false positives when the command output contains one of the - prompt pattern characters `> # % ~ $` at the end of a line. + prompt pattern characters ``> # % ~ $`` at the end of a line. - Prompt stripping update @@ -69,8 +69,8 @@ Features and Bug Fixes: - Removed OS static checking list, and made a warning instead. - New feature that allows user to specify initial exec and config command - when connecting to a device. Users can now specify `init_exec_commands` - and `init_config_commands` options when connecting to a device. + when connecting to a device. Users can now specify ``init_exec_commands`` + and ``init_config_commands`` options when connecting to a device. - The terminal variable is now set to VT100 before launching the telnet or ssh connection to a device. This is to tell devices not to use fancy ANSI diff --git a/docs/changelog/2018/march.rst b/docs/changelog/2018/march.rst index 1b6658bb..367205cf 100644 --- a/docs/changelog/2018/march.rst +++ b/docs/changelog/2018/march.rst @@ -69,7 +69,7 @@ Features and Bug Fixes: - new NXOS subcommand for attaching to bash shell using context managers - CIMC plugin update - Add response for `Enter 'yes' or 'no' to confirm` pattern + Add response for ``Enter 'yes' or 'no' to confirm`` pattern - New feature: CLI proxy This feature allows users to connect to devices via another device. diff --git a/docs/changelog/2019/april.rst b/docs/changelog/2019/april.rst index 73c69179..0fe4fb4d 100755 --- a/docs/changelog/2019/april.rst +++ b/docs/changelog/2019/april.rst @@ -22,7 +22,7 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ - learn_hostname feature updated to allow common plugin-specific default device - names such as `Router` to be learned if no hostname has been set on the + names such as ``Router`` to be learned if no hostname has been set on the device. - The iosxr plugin enable pattern is now more strict. diff --git a/docs/changelog/2019/jul.rst b/docs/changelog/2019/jul.rst index 26f4cad0..05e6c657 100644 --- a/docs/changelog/2019/jul.rst +++ b/docs/changelog/2019/jul.rst @@ -85,9 +85,9 @@ Features and Bug Fixes: - If one or more credentials are supplied: - - The ``tacacs`` and ``passwords`` pyATS testbed keys are ignored. + - The ``tacacs``and``passwords`` pyATS testbed keys are ignored. - - Use of any of the following `unicon.Unicon.Connection` arguments cause a + - Use of any of the following ``unicon.Unicon.Connection`` arguments cause a deprecation warning to be raised : - ``username`` @@ -109,13 +109,13 @@ Features and Bug Fixes: that server block. - The ``login_creds`` argument (specified either in pyATS connection - block or as a `unicon.Unicon.Connection` parameter), now controls + block or as a ``unicon.Unicon.Connection`` parameter), now controls the order credentials are applied when username/password prompts are received while connecting to the device. - - The ``prompts/login`` and ``prompts/password`` parameters are now + - The ``prompts/login``and``prompts/password`` parameters are now expected to be explicitly set in the pyATS connection block or - as `unicon.Unicon.Connection` parameters. + as ``unicon.Unicon.Connection`` parameters. - The switchover service now accepts a ``switchover_creds`` parameter that allows users to define what credentials to use should a username or diff --git a/docs/changelog/2019/jun.rst b/docs/changelog/2019/jun.rst index 79402083..05fa9d52 100755 --- a/docs/changelog/2019/jun.rst +++ b/docs/changelog/2019/jun.rst @@ -51,7 +51,7 @@ Features and Bug Fixes: - Fix reload service that was hanging when mgmt connection was attempted. - Updated execute() service to allow override of default service dialogs by - passing `service_dialog` + passing ``service_dialog`` - improve ping extd_ping judgement and fix endless ping dialog on erroneous value @@ -68,8 +68,8 @@ Features and Bug Fixes: - core - - modifed ``unicon_record``, ``unicon_replay``, ``unicon_speed`` environment - variables to ``UNICON_RECORD``, ``UNICON_REPLAY``, and ``UNICON_REPLAY_SPEED``. + - modifed ``unicon_record``,``unicon_replay``,``unicon_speed`` environment + variables to ``UNICON_RECORD``,``UNICON_REPLAY``, and``UNICON_REPLAY_SPEED``. - Disconnect timers may now be updated via Settings object diff --git a/docs/changelog/2020/april.rst b/docs/changelog/2020/april.rst index 31fc173f..36155661 100644 --- a/docs/changelog/2020/april.rst +++ b/docs/changelog/2020/april.rst @@ -30,7 +30,7 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ * Fixed unittests failures seen with multiprocessing on Mac-py38 environment -* Added `goto_enable` and `standby_goto_enable` key to generic connect service to +* Added ``goto_enable``and``standby_goto_enable`` key to generic connect service to allow user to disable device behavior of going to enable state in every device connect call, Default is True not to interrupt intuitive device behavior diff --git a/docs/changelog/2020/december.rst b/docs/changelog/2020/december.rst index 931e9151..97d97e18 100644 --- a/docs/changelog/2020/december.rst +++ b/docs/changelog/2020/december.rst @@ -43,7 +43,7 @@ Features and Bug Fixes: * Removed deprecation message from nxos->aci->n9k * Fixed a bug where the buffer might not be empty after connecting to the device * ASA Plugin - - Add error_pattern to capture `*** WARNING ***` + - Add error_pattern to capture ``*** WARNING ***`` * FXOS/FTD Plugin - Added support for "* " in chassis prompt, e.g. "FirePower* #" * Linux diff --git a/docs/changelog/2020/feb.rst b/docs/changelog/2020/feb.rst index 0ebf0c5b..f5180dd8 100644 --- a/docs/changelog/2020/feb.rst +++ b/docs/changelog/2020/feb.rst @@ -29,9 +29,9 @@ Upgrade Instructions Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -* Added `debug_statement` boolean argument to `Statement class` to print the statement matched pattern. +* Added ``debug_statement``boolean argument to``Statement class`` to print the statement matched pattern. -* Added `STATEMENT_LOG_DEBUG` boolean argument to `Settings class` to print all the matched patterns. +* Added ``STATEMENT_LOG_DEBUG``boolean argument to``Settings class`` to print all the matched patterns. * Add python3.8 support. diff --git a/docs/changelog/2021/april.rst b/docs/changelog/2021/april.rst index b4489437..147a6854 100644 --- a/docs/changelog/2021/april.rst +++ b/docs/changelog/2021/april.rst @@ -30,7 +30,7 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * adapters.topology * Modified Unicon: diff --git a/docs/changelog/2021/august.rst b/docs/changelog/2021/august.rst index d230a9f2..2385df6e 100644 --- a/docs/changelog/2021/august.rst +++ b/docs/changelog/2021/august.rst @@ -1,8 +1,8 @@ August 2021 -======== +=========== August 31st ------- +----------- .. csv-table:: Module Versions :header: "Modules", "Versions" diff --git a/docs/changelog/2021/december.rst b/docs/changelog/2021/december.rst index 2b38b671..8066cc36 100644 --- a/docs/changelog/2021/december.rst +++ b/docs/changelog/2021/december.rst @@ -31,7 +31,7 @@ Upgrade Instructions Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * playback @@ -44,7 +44,7 @@ Features and Bug Fixes: -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * playback diff --git a/docs/changelog/2021/february.rst b/docs/changelog/2021/february.rst index 80965e03..d7ba6734 100644 --- a/docs/changelog/2021/february.rst +++ b/docs/changelog/2021/february.rst @@ -6,7 +6,7 @@ february 26th -------------------------------------------------------------------------------- - Features and Bug Fixes: +Features and Bug Fixes: -------------------------------------------------------------------------------- * Dialogs diff --git a/docs/changelog/2021/january.rst b/docs/changelog/2021/january.rst index a8b0d0a9..42401e4c 100644 --- a/docs/changelog/2021/january.rst +++ b/docs/changelog/2021/january.rst @@ -6,7 +6,7 @@ January 27th -------------------------------------------------------------------------------- - Features and Bug Fixes: +Features and Bug Fixes: -------------------------------------------------------------------------------- * GENERIC PLUGIN @@ -52,7 +52,7 @@ January 27th * REMOVED NXOS/ACI/N9K PLUGIN (USE OS NXOS, PLATFORM=ACI INSTEAD) * ALL PLUGINS - * `Series` Has Been Renamed To `Platform` + * ``Series``Has Been Renamed To``Platform`` * ADDED NEW HP COMWARE PLUGINS diff --git a/docs/changelog/2021/july.rst b/docs/changelog/2021/july.rst index 02798bbe..42dfdbe2 100644 --- a/docs/changelog/2021/july.rst +++ b/docs/changelog/2021/july.rst @@ -1,8 +1,8 @@ July 2021 -======== +========= July 27 ------- +------- .. csv-table:: Module Versions :header: "Modules", "Versions" @@ -30,7 +30,7 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * connection provider @@ -41,7 +41,7 @@ Features and Bug Fixes: * changed basicConfig from stderr to stdout from mock device to prevent stderr output * statemachine - * Log warning when `add_state_pattern` is used + * Log warning when ``add_state_pattern`` is used * prompt recovery * Use warning on hostname mismatch instead of raising exception diff --git a/docs/changelog/2021/june.rst b/docs/changelog/2021/june.rst index 3462090e..72e8097e 100644 --- a/docs/changelog/2021/june.rst +++ b/docs/changelog/2021/june.rst @@ -1,8 +1,8 @@ June 2021 -======== +========= June 29 ------- +------- .. csv-table:: Module Versions :header: "Modules", "Versions" @@ -30,7 +30,7 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxe/sdwan @@ -44,7 +44,7 @@ Features and Bug Fixes: * Added warning log message if reconnect occurs * unicon.eal.dialogs - * Fixed `sendline_cred_user` and `sendline_cred_pass` implementation + * Fixed ``sendline_cred_user``and``sendline_cred_pass`` implementation * generic * Do not insert username for device SSH command diff --git a/docs/changelog/2021/march.rst b/docs/changelog/2021/march.rst index efb3be5e..7abe7c67 100644 --- a/docs/changelog/2021/march.rst +++ b/docs/changelog/2021/march.rst @@ -30,7 +30,7 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * statemachine diff --git a/docs/changelog/2021/may.rst b/docs/changelog/2021/may.rst index 23e9c33f..4f5243ef 100644 --- a/docs/changelog/2021/may.rst +++ b/docs/changelog/2021/may.rst @@ -30,11 +30,11 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * dialog processor diff --git a/docs/changelog/2021/october.rst b/docs/changelog/2021/october.rst index 3e0bad78..ae0cd391 100644 --- a/docs/changelog/2021/october.rst +++ b/docs/changelog/2021/october.rst @@ -1,5 +1,5 @@ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * playback @@ -11,7 +11,7 @@ -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * dialog diff --git a/docs/changelog/2021/september.rst b/docs/changelog/2021/september.rst index 4361d8f7..d0da06d7 100644 --- a/docs/changelog/2021/september.rst +++ b/docs/changelog/2021/september.rst @@ -1,8 +1,8 @@ September 2021 -======== +============== September 28 ------- +------------ .. csv-table:: Module Versions :header: "Modules", "Versions" @@ -30,11 +30,11 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * No new features -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * No Changes diff --git a/docs/changelog/2022/april.rst b/docs/changelog/2022/april.rst index a388f6c0..1f9cd180 100644 --- a/docs/changelog/2022/april.rst +++ b/docs/changelog/2022/april.rst @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * router.services diff --git a/docs/changelog/2022/august.rst b/docs/changelog/2022/august.rst index 1d58fcbb..78278c1f 100644 --- a/docs/changelog/2022/august.rst +++ b/docs/changelog/2022/august.rst @@ -1,5 +1,5 @@ August 2022 -========== +=========== August 30 - Unicon v22.8 ------------------------ @@ -32,10 +32,10 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * connection parse_spawn_command - * Fixed Although `login_creds='local'` is specified, `default` is selected + * Fixed Although ``login_creds='local'``is specified,``default`` is selected diff --git a/docs/changelog/2022/february.rst b/docs/changelog/2022/february.rst index b90ac788..7b5b55fc 100644 --- a/docs/changelog/2022/february.rst +++ b/docs/changelog/2022/february.rst @@ -1,8 +1,8 @@ February 2022 -========== +============= February 24 - Unicon v22.2 ------------------------- +-------------------------- @@ -38,7 +38,7 @@ Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * routers/connection provider @@ -54,7 +54,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * statemachine diff --git a/docs/changelog/2022/january.rst b/docs/changelog/2022/january.rst index 09a7b59f..a123e377 100644 --- a/docs/changelog/2022/january.rst +++ b/docs/changelog/2022/january.rst @@ -32,7 +32,7 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * bases diff --git a/docs/changelog/2022/july.rst b/docs/changelog/2022/july.rst index 5a0fcea1..d560a24a 100644 --- a/docs/changelog/2022/july.rst +++ b/docs/changelog/2022/july.rst @@ -32,7 +32,7 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * Playback * Modified _mock_helper: diff --git a/docs/changelog/2022/june.rst b/docs/changelog/2022/june.rst index 73d9f37c..3806a52a 100644 --- a/docs/changelog/2022/june.rst +++ b/docs/changelog/2022/june.rst @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * routers diff --git a/docs/changelog/2022/march.rst b/docs/changelog/2022/march.rst index 8d6e91c0..3fb177e9 100644 --- a/docs/changelog/2022/march.rst +++ b/docs/changelog/2022/march.rst @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * plugin manager @@ -46,7 +46,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxe diff --git a/docs/changelog/2022/may.rst b/docs/changelog/2022/may.rst index 4dd2e53f..a9b214f3 100644 --- a/docs/changelog/2022/may.rst +++ b/docs/changelog/2022/may.rst @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * router connection provider @@ -48,7 +48,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * enhancement for retry and service_dialog arguments diff --git a/docs/changelog/2022/november.rst b/docs/changelog/2022/november.rst index 4322868c..9ec73702 100644 --- a/docs/changelog/2022/november.rst +++ b/docs/changelog/2022/november.rst @@ -1,8 +1,8 @@ November 2022 -========== +============= November 28 - Unicon v22.11 ------------------------- +--------------------------- @@ -32,7 +32,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * Router diff --git a/docs/changelog/2022/october.rst b/docs/changelog/2022/october.rst index 38ca1533..313e4f6a 100644 --- a/docs/changelog/2022/october.rst +++ b/docs/changelog/2022/october.rst @@ -1,8 +1,8 @@ october 2022 -========== +============ October 25 - Unicon v22.10 ------------------------- +-------------------------- diff --git a/docs/changelog/2022/september.rst b/docs/changelog/2022/september.rst index b42a5d23..b0a60cc6 100644 --- a/docs/changelog/2022/september.rst +++ b/docs/changelog/2022/september.rst @@ -1,8 +1,8 @@ September 2022 -========== +============== September 27 - Unicon v22.9 ------------------------- +--------------------------- @@ -32,7 +32,7 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * connection base @@ -41,7 +41,7 @@ Features and Bug Fixes: -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * mock_device diff --git a/docs/changelog/2023/august.rst b/docs/changelog/2023/august.rst index ae56c0fe..ce7d6b6e 100644 --- a/docs/changelog/2023/august.rst +++ b/docs/changelog/2023/august.rst @@ -1,5 +1,5 @@ August 2023 -========== +=========== August 29 - Unicon v23.8 ------------------------ @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * unicon @@ -48,7 +48,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxe diff --git a/docs/changelog/2023/february.rst b/docs/changelog/2023/february.rst index bb07996f..769180b8 100644 --- a/docs/changelog/2023/february.rst +++ b/docs/changelog/2023/february.rst @@ -1,8 +1,8 @@ February 2023 -========== +============= February 28 - Unicon v23.2 ------------------------- +-------------------------- diff --git a/docs/changelog/2023/january.rst b/docs/changelog/2023/january.rst index 8f977f6b..bd11f7da 100644 --- a/docs/changelog/2023/january.rst +++ b/docs/changelog/2023/january.rst @@ -1,62 +1,62 @@ -January 2023 -========== - -January 31 - Unicon v23.1 ------------------------- - - - -.. csv-table:: Module Versions - :header: "Modules", "Versions" - - ``unicon.plugins``, v23.1 - ``unicon``, v23.1 - -Install Instructions -^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: bash - - bash$ pip install unicon.plugins - bash$ pip install unicon - -Upgrade Instructions -^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: bash - - bash$ pip install --upgrade unicon.plugins - bash$ pip install --upgrade unicon - -Features and Bug Fixes: -^^^^^^^^^^^^^^^^^^^^^^^ - - - - -Changelogs -^^^^^^^^^^ - - - --------------------------------------------------------------------------------- - New --------------------------------------------------------------------------------- -* iosxe - * Added Configure Error Patterns - * "% SR feature is not configured yet, please enable Segment-routing first." - * "% {address} overlaps with {interfaces}" - * "%{interface} is linked to a VRF. Enable {protocol} on that VRF first." - * "% VRF {vrf} not configured" - * "% Incomplete command." - * "%CLNS: System ID ({system_id}) must not change when defining additional area addresses" - * "% Specify remote-as or peer-group commands first" - * "% Policy commands not allowed without an address family" - * Added switchover proceed pattern - --------------------------------------------------------------------------------- - Fix --------------------------------------------------------------------------------- -* IOSXE - * Add support for chassis keyword argument for bash console service - +January 2023 +============ + +January 31 - Unicon v23.1 +------------------------- + + + +.. csv-table:: Module Versions + :header: "Modules", "Versions" + + ``unicon.plugins``, v23.1 + ``unicon``, v23.1 + +Install Instructions +^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: bash + + bash$ pip install unicon.plugins + bash$ pip install unicon + +Upgrade Instructions +^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: bash + + bash$ pip install --upgrade unicon.plugins + bash$ pip install --upgrade unicon + +Features and Bug Fixes: +^^^^^^^^^^^^^^^^^^^^^^^ + + + + +Changelogs +^^^^^^^^^^ + + + +-------------------------------------------------------------------------------- +New +-------------------------------------------------------------------------------- +* iosxe + * Added Configure Error Patterns + * "% SR feature is not configured yet, please enable Segment-routing first." + * "% {address} overlaps with {interfaces}" + * "%{interface} is linked to a VRF. Enable {protocol} on that VRF first." + * "% VRF {vrf} not configured" + * "% Incomplete command." + * "%CLNS: System ID ({system_id}) must not change when defining additional area addresses" + * "% Specify remote-as or peer-group commands first" + * "% Policy commands not allowed without an address family" + * Added switchover proceed pattern + +-------------------------------------------------------------------------------- +Fix +-------------------------------------------------------------------------------- +* IOSXE + * Add support for chassis keyword argument for bash console service + diff --git a/docs/changelog/2023/july.rst b/docs/changelog/2023/july.rst index 74bbe41c..f3210ffa 100644 --- a/docs/changelog/2023/july.rst +++ b/docs/changelog/2023/july.rst @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * patterns @@ -45,7 +45,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * generic @@ -54,7 +54,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxe diff --git a/docs/changelog/2023/june.rst b/docs/changelog/2023/june.rst index 65e5735f..a81d7159 100644 --- a/docs/changelog/2023/june.rst +++ b/docs/changelog/2023/june.rst @@ -37,12 +37,12 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * unicon.eal.backend - * Refactored backend to use telnetlib by default. All telnet connections will now use `telnetlib` implementation instead of system telnet. - * Set `.settings.BACKEND = "unicon.eal.backend.pty_backend"` to revert to the system telnet client. + * Refactored backend to use telnetlib by default. All telnet connections will now use ``telnetlib`` implementation instead of system telnet. + * Set ``.settings.BACKEND = "unicon.eal.backend.pty_backend"`` to revert to the system telnet client. * unicon.mock * Update mock_device_cli to work with telnetlib backend diff --git a/docs/changelog/2023/march.rst b/docs/changelog/2023/march.rst index 4242e00b..2f2a7d59 100644 --- a/docs/changelog/2023/march.rst +++ b/docs/changelog/2023/march.rst @@ -38,7 +38,7 @@ Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxr diff --git a/docs/changelog/2023/may.rst b/docs/changelog/2023/may.rst index 7e645df2..6ec097cf 100644 --- a/docs/changelog/2023/may.rst +++ b/docs/changelog/2023/may.rst @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * backend diff --git a/docs/changelog/2023/november.rst b/docs/changelog/2023/november.rst index c5b210bf..2075b208 100644 --- a/docs/changelog/2023/november.rst +++ b/docs/changelog/2023/november.rst @@ -1,8 +1,8 @@ November 2023 -========== +============= November 27 - Unicon v23.11 ------------------------- +--------------------------- @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * unicon diff --git a/docs/changelog/2023/october.rst b/docs/changelog/2023/october.rst index 3b34aed9..467e68fe 100644 --- a/docs/changelog/2023/october.rst +++ b/docs/changelog/2023/october.rst @@ -37,12 +37,12 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * unicon.eal.backend * Added telnetlib backend. - * Set `.settings.BACKEND = "auto"` to use the new telnetlib backend. + * Set ``.settings.BACKEND = "auto"`` to use the new telnetlib backend. * unicon.mock * Update mock_device_cli to work with telnetlib backend @@ -57,7 +57,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * unicon.adapters @@ -67,7 +67,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * cheetah diff --git a/docs/changelog/2023/september.rst b/docs/changelog/2023/september.rst index f4e70155..239112c8 100644 --- a/docs/changelog/2023/september.rst +++ b/docs/changelog/2023/september.rst @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * unicon.routers.bases diff --git a/docs/changelog/2024/September.rst b/docs/changelog/2024/September.rst index 2806f924..a6e3643b 100644 --- a/docs/changelog/2024/September.rst +++ b/docs/changelog/2024/September.rst @@ -1,8 +1,8 @@ September 2024 -========== +============== September 24 - Unicon v24.9 ------------------------- +--------------------------- @@ -18,7 +18,7 @@ September 24 - Unicon v24.9 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * backend.spawn0 @@ -36,7 +36,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * generic diff --git a/docs/changelog/2024/april.rst b/docs/changelog/2024/april.rst index d00f23e6..9ae84000 100644 --- a/docs/changelog/2024/april.rst +++ b/docs/changelog/2024/april.rst @@ -1,7 +1,7 @@ April 2024 ========== - - Unicon v24.4 +- Unicon v24.4 ------------------------ @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * sshutils @@ -57,7 +57,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * unicon @@ -66,7 +66,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * iosxe @@ -84,7 +84,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxr/spitfire diff --git a/docs/changelog/2024/august.rst b/docs/changelog/2024/august.rst index fb03cbd1..b9366197 100644 --- a/docs/changelog/2024/august.rst +++ b/docs/changelog/2024/august.rst @@ -1,5 +1,5 @@ August 2024 -========== +=========== August 27 - Unicon v24.8 ------------------------ diff --git a/docs/changelog/2024/february.rst b/docs/changelog/2024/february.rst index cfcbdb21..95884409 100644 --- a/docs/changelog/2024/february.rst +++ b/docs/changelog/2024/february.rst @@ -1,8 +1,8 @@ February 2024 -========== +============= February 27 - Unicon v24.2 ------------------------- +-------------------------- @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * connection_provider @@ -51,7 +51,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * utils diff --git a/docs/changelog/2024/january.rst b/docs/changelog/2024/january.rst index 813f8be1..a7738213 100644 --- a/docs/changelog/2024/january.rst +++ b/docs/changelog/2024/january.rst @@ -1,5 +1,5 @@ January 2024 -========== +============ 30 - Unicon v24.1 ------------------------ @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * pty_backend @@ -54,7 +54,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * service_implementation diff --git a/docs/changelog/2024/july.rst b/docs/changelog/2024/july.rst index 2b9608b1..6a6d0996 100644 --- a/docs/changelog/2024/july.rst +++ b/docs/changelog/2024/july.rst @@ -37,10 +37,10 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * generic - * Updated with `sleep_time` to handle the copy command + * Updated with ``sleep_time`` to handle the copy command diff --git a/docs/changelog/2024/june.rst b/docs/changelog/2024/june.rst index fe7ceafb..527baeab 100644 --- a/docs/changelog/2024/june.rst +++ b/docs/changelog/2024/june.rst @@ -1,7 +1,7 @@ June 2024 ========== - - Unicon v24.6 +- Unicon v24.6 ------------------------ @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * stackswitchover @@ -53,7 +53,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * iosxe.stack.utils diff --git a/docs/changelog/2024/march.rst b/docs/changelog/2024/march.rst index 5b7c0c79..22ab2ef6 100644 --- a/docs/changelog/2024/march.rst +++ b/docs/changelog/2024/march.rst @@ -1,7 +1,7 @@ March 2024 ========== - - Unicon v24.3 +- Unicon v24.3 ------------------------ @@ -37,15 +37,15 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * backend - * Option to use `UNICON_BACKEND` environment variable to select backend + * Option to use ``UNICON_BACKEND`` environment variable to select backend -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * bases @@ -54,7 +54,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * discovery_tokens @@ -66,7 +66,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxr diff --git a/docs/changelog/2024/may.rst b/docs/changelog/2024/may.rst index 1986ea03..6b2b3f21 100644 --- a/docs/changelog/2024/may.rst +++ b/docs/changelog/2024/may.rst @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * playback @@ -46,7 +46,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxe diff --git a/docs/changelog/2024/november.rst b/docs/changelog/2024/november.rst index dc60d032..66a3b38a 100644 --- a/docs/changelog/2024/november.rst +++ b/docs/changelog/2024/november.rst @@ -1,8 +1,8 @@ November 2024 -========== +============= November 26 - Unicon v24.11 ------------------------- +--------------------------- @@ -18,7 +18,7 @@ November 26 - Unicon v24.11 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * unicon/bases diff --git a/docs/changelog/2024/october.rst b/docs/changelog/2024/october.rst index fe00ac57..2411d1bd 100644 --- a/docs/changelog/2024/october.rst +++ b/docs/changelog/2024/october.rst @@ -1,8 +1,8 @@ October 2024 -========== +============ October 29 - Unicon v24.10 ------------------------- +-------------------------- @@ -18,7 +18,7 @@ October 29 - Unicon v24.10 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * generic diff --git a/docs/changelog/2025/april.rst b/docs/changelog/2025/april.rst index a4b76776..2bb1997d 100644 --- a/docs/changelog/2025/april.rst +++ b/docs/changelog/2025/april.rst @@ -18,7 +18,7 @@ April 29 - Unicon v25.4 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * connection @@ -40,7 +40,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * linux @@ -52,7 +52,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * linux @@ -67,7 +67,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * iosxe diff --git a/docs/changelog/2025/august.rst b/docs/changelog/2025/august.rst index 57792877..f28d6be1 100644 --- a/docs/changelog/2025/august.rst +++ b/docs/changelog/2025/august.rst @@ -1,5 +1,5 @@ August 2025 -========== +=========== August 23 - Unicon v25.8 ------------------------ @@ -18,7 +18,7 @@ August 23 - Unicon v25.8 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * unicon/bases @@ -28,7 +28,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * cheetah @@ -40,7 +40,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * generic/patterns diff --git a/docs/changelog/2025/december.rst b/docs/changelog/2025/december.rst index d2b29fab..3c98c6fa 100644 --- a/docs/changelog/2025/december.rst +++ b/docs/changelog/2025/december.rst @@ -1,8 +1,8 @@ December 2025 -========== +============= December 30 - Unicon v25.11 ------------------------- +--------------------------- @@ -18,7 +18,7 @@ December 30 - Unicon v25.11 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * unicon @@ -27,7 +27,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * patterns diff --git a/docs/changelog/2025/february.rst b/docs/changelog/2025/february.rst index f91bcd29..950c1cd4 100644 --- a/docs/changelog/2025/february.rst +++ b/docs/changelog/2025/february.rst @@ -1,8 +1,8 @@ February 2025 -========== +============= February 25 - Unicon v25.2 ------------------------- +-------------------------- @@ -18,7 +18,7 @@ February 25 - Unicon v25.2 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * router.connection_provider @@ -27,7 +27,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * generic @@ -35,7 +35,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * iosxe diff --git a/docs/changelog/2025/january.rst b/docs/changelog/2025/january.rst index c9c0b793..fe7f3584 100644 --- a/docs/changelog/2025/january.rst +++ b/docs/changelog/2025/january.rst @@ -1,7 +1,7 @@ January 2025 -========== +============ - - Unicon v25.1 +- Unicon v25.1 ------------------------ @@ -18,7 +18,7 @@ January 2025 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * backend @@ -32,7 +32,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxr diff --git a/docs/changelog/2025/july.rst b/docs/changelog/2025/july.rst index 4f7f956c..5cf7893a 100644 --- a/docs/changelog/2025/july.rst +++ b/docs/changelog/2025/july.rst @@ -18,7 +18,7 @@ July 29 - Unicon v25.7 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * bases/router @@ -30,7 +30,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * mock device diff --git a/docs/changelog/2025/june.rst b/docs/changelog/2025/june.rst index 6d1e3920..b0137fbf 100644 --- a/docs/changelog/2025/june.rst +++ b/docs/changelog/2025/june.rst @@ -18,7 +18,7 @@ June 29 - Unicon v25.6 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * bases/connection @@ -27,7 +27,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * unicon @@ -35,7 +35,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * iosxe diff --git a/docs/changelog/2025/march.rst b/docs/changelog/2025/march.rst index 9eb04471..23cfaee9 100644 --- a/docs/changelog/2025/march.rst +++ b/docs/changelog/2025/march.rst @@ -18,7 +18,7 @@ March 25 - Unicon v25.3 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * unicon.robot @@ -30,7 +30,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * connection_provider @@ -39,7 +39,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * generic diff --git a/docs/changelog/2025/may.rst b/docs/changelog/2025/may.rst index a7e23904..d6fb7c9c 100644 --- a/docs/changelog/2025/may.rst +++ b/docs/changelog/2025/may.rst @@ -1,7 +1,7 @@ May 2025 ========== - - Unicon v25.5 +- Unicon v25.5 ------------------------ @@ -18,7 +18,7 @@ May 2025 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Add +Add -------------------------------------------------------------------------------- * connection provider @@ -31,7 +31,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * generic diff --git a/docs/changelog/2025/october.rst b/docs/changelog/2025/october.rst index ca84a9a8..2779f5fc 100644 --- a/docs/changelog/2025/october.rst +++ b/docs/changelog/2025/october.rst @@ -1,8 +1,8 @@ October 2025 -========== +============ October 28 - Unicon v25.10 ------------------------- +-------------------------- @@ -18,7 +18,7 @@ October 28 - Unicon v25.10 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * unicon @@ -36,7 +36,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * unicon/bases/linux/connection @@ -45,7 +45,7 @@ Changelogs -------------------------------------------------------------------------------- - Add +Add -------------------------------------------------------------------------------- * basemultirpconnection @@ -53,7 +53,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxe/cat9k/stackwise_virtual diff --git a/docs/changelog/2025/september.rst b/docs/changelog/2025/september.rst index 73f1e031..1241cd7f 100644 --- a/docs/changelog/2025/september.rst +++ b/docs/changelog/2025/september.rst @@ -1,8 +1,8 @@ September 2025 -========== +============== September 30 - Unicon v25.9 ------------------------- +--------------------------- @@ -18,7 +18,7 @@ September 30 - Unicon v25.9 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * backend/spawn @@ -26,7 +26,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * generic/service_statements diff --git a/docs/changelog/2026/april.rst b/docs/changelog/2026/april.rst new file mode 100644 index 00000000..0c2d3500 --- /dev/null +++ b/docs/changelog/2026/april.rst @@ -0,0 +1,48 @@ +April 2026 +========== + +April 28 - Unicon v26.4 +----------------------- + + + +.. csv-table:: Module Versions + :header: "Modules", "Versions" + + ``unicon.plugins``, v26.4 + ``unicon``, v26.4 + + + + +Changelogs +^^^^^^^^^^ + +-------------------------------------------------------------------------------- + Fix +-------------------------------------------------------------------------------- + +* mock + * Modified MockDevice + * Handle PTY Ctrl-C interrupts in the core mock device run loop so + + +-------------------------------------------------------------------------------- + Fix +-------------------------------------------------------------------------------- + +* iosxe + * Modified ``boot_image`` / settings + * Added support for ordered ``BOOT_FILE_REGEX`` lists so discovered boot images are queued by regex priority across filesystems. + +* iosxe/ie3k + * Modified ``IosXEIe3kSettings`` + * Updated boot image selection to prioritize ``.SSA.bin``, then ``.SPA.bin``, then other ``.bin``images through ordered``BOOT_FILE_REGEX`` entries. + +* iosxe/cat9k + * Modified ``Rommon``&``HARommon`` service implementation + * Updated enable break regex to handle 'no' option for ENABLE_BREAK variable. + * Modified ``HARommon`` + * Ensures HA rommon breaks boot on all consoles via active reload plus parallel standby interrupts with improved state validation. + + diff --git a/docs/changelog/2026/february.rst b/docs/changelog/2026/february.rst index 4b84d82b..ac7ec40a 100644 --- a/docs/changelog/2026/february.rst +++ b/docs/changelog/2026/february.rst @@ -1,8 +1,8 @@ February 2026 -========== +============= February 24 - Unicon v26.2 ------------------------- +-------------------------- @@ -18,7 +18,7 @@ February 24 - Unicon v26.2 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * mock_device @@ -30,7 +30,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * stackwisevirtualconnectionprovider @@ -49,7 +49,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * linux diff --git a/docs/changelog/2026/january.rst b/docs/changelog/2026/january.rst index 87cc9876..481b62d6 100644 --- a/docs/changelog/2026/january.rst +++ b/docs/changelog/2026/january.rst @@ -1,8 +1,8 @@ January 2026 -========== +============ January 27 - Unicon v26.1 ------------------------- +------------------------- @@ -18,7 +18,7 @@ January 27 - Unicon v26.1 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * unicon/patterns @@ -33,7 +33,7 @@ Changelogs -------------------------------------------------------------------------------- - Add +Add -------------------------------------------------------------------------------- * nxos/n9kv @@ -41,7 +41,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * iosxe/c8kv/statemachine @@ -54,7 +54,7 @@ Changelogs -------------------------------------------------------------------------------- - Recovery. +Recovery. -------------------------------------------------------------------------------- * iosxe/c8kv/statements @@ -63,7 +63,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * pid_tokens diff --git a/docs/changelog/2026/march.rst b/docs/changelog/2026/march.rst index ed865568..00218883 100644 --- a/docs/changelog/2026/march.rst +++ b/docs/changelog/2026/march.rst @@ -18,17 +18,17 @@ March 31 - Unicon v26.3 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * bases/router/connection_provider * Added logic to raise a traceback when when HA sync does not complete within POST_BOOT_TIMEOUT -* removed unused `pyats` and `genie` imports +* removed unused ``pyats``and``genie`` imports -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * iosxe/ie9k @@ -39,7 +39,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxe diff --git a/docs/changelog/index.rst b/docs/changelog/index.rst index 64f23864..9d84dac4 100644 --- a/docs/changelog/index.rst +++ b/docs/changelog/index.rst @@ -4,6 +4,7 @@ Changelog .. toctree:: :maxdepth: 2 + 2026/april 2026/march 2026/february 2026/january diff --git a/docs/changelog/undistributed/template.rst b/docs/changelog/undistributed/template.rst index 7cdb37f3..2ec55e20 100644 --- a/docs/changelog/undistributed/template.rst +++ b/docs/changelog/undistributed/template.rst @@ -4,14 +4,14 @@ Templates ========= -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * * : * -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * * : @@ -21,7 +21,7 @@ Examples ======== -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * Module * Modified Class: diff --git a/docs/changelog_plugins/2020/april.rst b/docs/changelog_plugins/2020/april.rst index 4a7157ba..38f87183 100644 --- a/docs/changelog_plugins/2020/april.rst +++ b/docs/changelog_plugins/2020/april.rst @@ -35,9 +35,9 @@ Features and Bug Fixes: * Enhance IOSXR enable pattern to accomodate different preceding card/slot. -* Adding `copy` service to the HA IOSXE plugin implementation. +* Adding ``copy`` service to the HA IOSXE plugin implementation. -* Supporting `reset_standby_rp` on IOSXE. +* Supporting ``reset_standby_rp`` on IOSXE. * Updating XR spitfire plugin run prompts pattern. diff --git a/docs/changelog_plugins/2021/april.rst b/docs/changelog_plugins/2021/april.rst index 1a8c817a..472f3610 100644 --- a/docs/changelog_plugins/2021/april.rst +++ b/docs/changelog_plugins/2021/april.rst @@ -30,48 +30,48 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * AIREOS PLUGIN - * Add error_pattern for `^[Rr]Equest [Ff]Ailed And R'^(.*?) Already In Use` - * Add error_pattern For `Wlan Identifier Is Invalid` and `^Request Failed` + * Add error_pattern for ``^[Rr]Equest [Ff]Ailed And R'^(.*?) Already In Use`` + * Add error_pattern For ``Wlan Identifier Is Invalid``and``^Request Failed `` * NXOS/ACI * Inherit services from nxos plugin * GENERIC PLUGIN - * Add syslog message handler to `connect`, `execute` and `configure` services + * Add syslog message handler to ``connect``,``execute``and``configure`` services * IOSXE/CAT9K - * Support `rommon()` and `reload()` services + * Support ``rommon()``and``reload()`` services * IOSXE * New exec error_pattern to match '% Bad IP address or host name% Unknown command or computer name, or unable to find computer address' * New configure error_pattern to match '% IP routing table does not exist' * GENERIC EXECUTE AND CONFIGURE SERVICES - * Added `append_error_pattern` argument + * Added ``append_error_pattern`` argument * NXOS - * Added `skip_poap` statement for reload service - * Add execute statement list for `execute` service + * Added ``skip_poap`` statement for reload service + * Add execute statement list for ``execute`` service * Add add error_pattern for "command failed...aborting" * NXOS PLUGIN * Add dialog to handle commit confirm message - * Use 'commit' as default commit command for `configure_dual` service + * Use 'commit' as default commit command for ``configure_dual`` service -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * NXOS/ACI * attach_console service for NXOS/ACI plugin * IOSXR - * Updated `run_prompt` pattern to accept more variety + * Updated ``run_prompt`` pattern to accept more variety * IOSXR/SPITFIRE * Fixed failed config handling when transitioning from config to enable state @@ -80,11 +80,11 @@ Features and Bug Fixes: * Updated shell prompt pattern * AIREOS PLUGIN - * Changed error_pattern `^(%\S*)?Error` To `^(%\S*)?(Error|error)` so it's case insensitive + * Changed error_pattern ``^(%\S*)?Error``To``^(%\S*)?(Error|error)`` so it's case insensitive * JUNOS PLUGIN - * Update `configure` service to allow `commit_cmd` override + * Update ``configure``service to allow``commit_cmd`` override * IOSXE * Updated config prompt pattern to include "cloud" diff --git a/docs/changelog_plugins/2021/august.rst b/docs/changelog_plugins/2021/august.rst index b4780790..4dfdcec3 100644 --- a/docs/changelog_plugins/2021/august.rst +++ b/docs/changelog_plugins/2021/august.rst @@ -1,8 +1,8 @@ August 2021 -======== +=========== August 31st ------- +----------- .. csv-table:: Module Versions :header: "Modules", "Versions" @@ -30,7 +30,7 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * generic @@ -38,7 +38,7 @@ Features and Bug Fixes: * Automatically set extd_ping to 'y' if extended option is specified * Handle invalid input errors * Add address to ping command if no other options are given - * Deprecated arguments `int` and `src_addr` for ``interface`` and ``source`` - * Modified reload service, added `raise_on_error` option + * Deprecated arguments ``int``and``src_addr``for``interface``and``source`` + * Modified reload service, added ``raise_on_error`` option diff --git a/docs/changelog_plugins/2021/december.rst b/docs/changelog_plugins/2021/december.rst index 781d6573..c6bf4dfb 100644 --- a/docs/changelog_plugins/2021/december.rst +++ b/docs/changelog_plugins/2021/december.rst @@ -31,7 +31,7 @@ Upgrade Instructions Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * iosxe @@ -40,7 +40,7 @@ Features and Bug Fixes: -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxr/spitfire diff --git a/docs/changelog_plugins/2021/february.rst b/docs/changelog_plugins/2021/february.rst index 9483594d..0bcf58ec 100644 --- a/docs/changelog_plugins/2021/february.rst +++ b/docs/changelog_plugins/2021/february.rst @@ -1,8 +1,8 @@ February 2021 -============ +============= February 23rd ------------- +------------- .. csv-table:: Module Versions :header: "Modules", "Versions" @@ -31,24 +31,24 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * Generic plugin * Add syslog message handler to connect, execute and configure services * IOSXE/CAT9K - * Support `rommon()` and `reload()` services + * Support ``rommon()``and``reload()`` services * Generic execute and configure services - * Added `append_error_pattern` argument + * Added ``append_error_pattern`` argument * Aireos plugin * Add ERROR_PATTERN for ^[Rr]equest [Ff]ailed and r'^(.*?) already in use' * Add ERROR_PATTERN for r'WLAN Identifier is invalid' and r'^Request failed' -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * setup.py diff --git a/docs/changelog_plugins/2021/january.rst b/docs/changelog_plugins/2021/january.rst index 52c51926..718df850 100644 --- a/docs/changelog_plugins/2021/january.rst +++ b/docs/changelog_plugins/2021/january.rst @@ -30,7 +30,7 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * GENERIC PLUGIN @@ -76,7 +76,7 @@ Features and Bug Fixes: * REMOVED NXOS/ACI/N9K PLUGIN (USE OS NXOS, PLATFORM=ACI INSTEAD) * ALL PLUGINS - * `Series` Has Been Renamed To `Platform` + * ``Series``Has Been Renamed To``Platform`` * ADDED NEW HP COMWARE PLUGINS diff --git a/docs/changelog_plugins/2021/july.rst b/docs/changelog_plugins/2021/july.rst index a859650b..8b2f182a 100644 --- a/docs/changelog_plugins/2021/july.rst +++ b/docs/changelog_plugins/2021/july.rst @@ -1,8 +1,8 @@ July 2021 -======== +========= July 27th ------- +--------- .. csv-table:: Module Versions :header: "Modules", "Versions" @@ -30,7 +30,7 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxe @@ -91,7 +91,7 @@ Features and Bug Fixes: -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * nxos/mds diff --git a/docs/changelog_plugins/2021/june.rst b/docs/changelog_plugins/2021/june.rst index 15dcdaf5..aaf26858 100644 --- a/docs/changelog_plugins/2021/june.rst +++ b/docs/changelog_plugins/2021/june.rst @@ -1,8 +1,8 @@ June 2021 -======== +========= June 29 ------- +------- .. csv-table:: Module Versions :header: "Modules", "Versions" @@ -30,7 +30,7 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * generic diff --git a/docs/changelog_plugins/2021/march.rst b/docs/changelog_plugins/2021/march.rst index 53e4b01a..5af777be 100644 --- a/docs/changelog_plugins/2021/march.rst +++ b/docs/changelog_plugins/2021/march.rst @@ -30,14 +30,14 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * IOSXE/pattern * Allow 'WLC' to default prompt patterns * Comware - * Changed from `hp_comware` to `comware` + * Changed from ``hp_comware``to``comware`` * IOSXE/CAT9K * image_to_boot argument support for reload service @@ -67,13 +67,13 @@ Features and Bug Fixes: -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * NXOS * Add 'mode' to configure() service as argument. * configure_dual service is now deprecated. - * Fixed `switchto` and `switchback` service and added UTs + * Fixed ``switchto``and``switchback`` service and added UTs * FXOS/FP4K * New plugin for Firepower 4000 series diff --git a/docs/changelog_plugins/2021/may.rst b/docs/changelog_plugins/2021/may.rst index 592bbcfc..c3130396 100644 --- a/docs/changelog_plugins/2021/may.rst +++ b/docs/changelog_plugins/2021/may.rst @@ -30,7 +30,7 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxr/spitfire @@ -73,7 +73,7 @@ Features and Bug Fixes: -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * iosxe diff --git a/docs/changelog_plugins/2021/october.rst b/docs/changelog_plugins/2021/october.rst index a266dbd6..95e5b3b8 100644 --- a/docs/changelog_plugins/2021/october.rst +++ b/docs/changelog_plugins/2021/october.rst @@ -1,5 +1,5 @@ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * nxos @@ -16,7 +16,7 @@ -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * iosxe/cat9k diff --git a/docs/changelog_plugins/2021/september.rst b/docs/changelog_plugins/2021/september.rst index 63f810a5..98738dd5 100644 --- a/docs/changelog_plugins/2021/september.rst +++ b/docs/changelog_plugins/2021/september.rst @@ -1,8 +1,8 @@ September 2021 -======== +============== September 28th ------- +-------------- .. csv-table:: Module Versions :header: "Modules", "Versions" @@ -30,7 +30,7 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxr @@ -47,4 +47,4 @@ Features and Bug Fixes: * Use generic pre-connection statement list to handle syslog messages on connect * linux - * Add `sudo` service + * Add ``sudo`` service diff --git a/docs/changelog_plugins/2022/april.rst b/docs/changelog_plugins/2022/april.rst index 37409fbc..03301d3d 100644 --- a/docs/changelog_plugins/2022/april.rst +++ b/docs/changelog_plugins/2022/april.rst @@ -2,7 +2,7 @@ April 2022 ========== April 26 - Unicon.Plugins v22.4 ------------------------- +------------------------------- @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * iosxr/spitfire @@ -45,7 +45,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxe diff --git a/docs/changelog_plugins/2022/august.rst b/docs/changelog_plugins/2022/august.rst index edb98bfc..349cc27a 100644 --- a/docs/changelog_plugins/2022/august.rst +++ b/docs/changelog_plugins/2022/august.rst @@ -1,8 +1,8 @@ August 2022 -========== +=========== August 30 - Unicon.Plugins v22.8 ------------------------- +-------------------------------- @@ -32,7 +32,7 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * generic @@ -46,8 +46,8 @@ Features and Bug Fixes: * Refactor iosxe reload service, rename context variable boot_image to grub_boot_image * Update press_any_key regex pattern * Update grub_prompt regex pattern - * Add escape char regex setting `ESCAPE_CHAR_PROMPT_PATTERN` - * Add grub regex pattern setting `GRUB_REGEX_PATTERN` to match menu entries + * Add escape char regex setting ``ESCAPE_CHAR_PROMPT_PATTERN`` + * Add grub regex pattern setting ``GRUB_REGEX_PATTERN`` to match menu entries * linux * Updated linux prompt pattern diff --git a/docs/changelog_plugins/2022/february.rst b/docs/changelog_plugins/2022/february.rst index 77ec3770..77cf6845 100644 --- a/docs/changelog_plugins/2022/february.rst +++ b/docs/changelog_plugins/2022/february.rst @@ -1,8 +1,8 @@ February 2022 -========== +============= February 24 - Unicon.Plugins v22.2 ------------------------- +---------------------------------- @@ -38,7 +38,7 @@ Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * utils @@ -47,6 +47,6 @@ Changelogs * Modified load_pid_token_csv_file * Renamed to load_token_csv_file * Adjusted logic to support dynamic csv loading based on header fields - * Added an optional `key` argument to allow for different keys to be specified other than pid + * Added an optional ``key`` argument to allow for different keys to be specified other than pid diff --git a/docs/changelog_plugins/2022/january.rst b/docs/changelog_plugins/2022/january.rst index 84feab65..93c9369d 100644 --- a/docs/changelog_plugins/2022/january.rst +++ b/docs/changelog_plugins/2022/january.rst @@ -1,8 +1,8 @@ January 2022 -========== +============ January 25 - Unicon.Plugins v22.1 ------------------------- +--------------------------------- @@ -37,11 +37,11 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * generic - * Added `CONFIG_TRANSITION_WAIT` setting to allow changes the config transition wait time + * Added ``CONFIG_TRANSITION_WAIT`` setting to allow changes the config transition wait time * iosxe/iec3400 * New plugin for IEC3400 device @@ -56,7 +56,7 @@ Changelogs * Added support for ROMMON init commands -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * iosxe @@ -75,4 +75,4 @@ Changelogs * nxos * Added l2rib client support to statemachine - * New `l2rib_dt` service \ No newline at end of file + * New ``l2rib_dt`` service \ No newline at end of file diff --git a/docs/changelog_plugins/2022/july.rst b/docs/changelog_plugins/2022/july.rst index bd874d87..b3a8611f 100644 --- a/docs/changelog_plugins/2022/july.rst +++ b/docs/changelog_plugins/2022/july.rst @@ -2,7 +2,7 @@ July 2022 ========== July 26 - Unicon.Plugins v22.7 ------------------------- +------------------------------ @@ -38,7 +38,7 @@ Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * general diff --git a/docs/changelog_plugins/2022/june.rst b/docs/changelog_plugins/2022/june.rst index 1f4b5546..aabba814 100644 --- a/docs/changelog_plugins/2022/june.rst +++ b/docs/changelog_plugins/2022/june.rst @@ -2,7 +2,7 @@ June 2022 ========== June 28 - Unicon.Plugins v22.6 ------------------------- +------------------------------ @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * iosxe * settings: diff --git a/docs/changelog_plugins/2022/march.rst b/docs/changelog_plugins/2022/march.rst index e27ff1b9..fa476b25 100644 --- a/docs/changelog_plugins/2022/march.rst +++ b/docs/changelog_plugins/2022/march.rst @@ -2,7 +2,7 @@ March 2022 ========== March 29 - Unicon.Plugins v22.3 ------------------------- +------------------------------- @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxe @@ -59,7 +59,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * dnos10 diff --git a/docs/changelog_plugins/2022/may.rst b/docs/changelog_plugins/2022/may.rst index a9fd3eca..048e7a92 100644 --- a/docs/changelog_plugins/2022/may.rst +++ b/docs/changelog_plugins/2022/may.rst @@ -2,7 +2,7 @@ May 2022 ========== May 31 - Unicon.Plugins v22.5 ------------------------- +----------------------------- @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxe diff --git a/docs/changelog_plugins/2022/november.rst b/docs/changelog_plugins/2022/november.rst index 1446e4ab..607e8690 100644 --- a/docs/changelog_plugins/2022/november.rst +++ b/docs/changelog_plugins/2022/november.rst @@ -1,8 +1,8 @@ November 2022 -========== +============= November 28 - Unicon.Plugins v22.11 ------------------------- +----------------------------------- @@ -28,7 +28,7 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - Add +Add -------------------------------------------------------------------------------- * generic @@ -42,7 +42,7 @@ Features and Bug Fixes: * add allow_state_change for configure service. -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * sros diff --git a/docs/changelog_plugins/2022/october.rst b/docs/changelog_plugins/2022/october.rst index db1f1f59..630558f0 100644 --- a/docs/changelog_plugins/2022/october.rst +++ b/docs/changelog_plugins/2022/october.rst @@ -1,8 +1,8 @@ October 2022 -========== +============ October 25 - Unicon.Plugins v22.10 ------------------------- +---------------------------------- @@ -28,7 +28,7 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * nxos/patterns * Modified NxosPatterns: diff --git a/docs/changelog_plugins/2022/september.rst b/docs/changelog_plugins/2022/september.rst index 95ee8187..eeb7ab38 100644 --- a/docs/changelog_plugins/2022/september.rst +++ b/docs/changelog_plugins/2022/september.rst @@ -1,8 +1,8 @@ September 2022 -========== +============== September 27 - Unicon.Plugins v22.9 ------------------------- +----------------------------------- @@ -32,7 +32,7 @@ Features and Bug Fixes: ^^^^^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * generic diff --git a/docs/changelog_plugins/2023/april.rst b/docs/changelog_plugins/2023/april.rst index 8c7cf9ad..39e08aa0 100644 --- a/docs/changelog_plugins/2023/april.rst +++ b/docs/changelog_plugins/2023/april.rst @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * linux diff --git a/docs/changelog_plugins/2023/august.rst b/docs/changelog_plugins/2023/august.rst index 58b0ff86..058e85e5 100644 --- a/docs/changelog_plugins/2023/august.rst +++ b/docs/changelog_plugins/2023/august.rst @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * unicon @@ -48,7 +48,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxe diff --git a/docs/changelog_plugins/2023/february.rst b/docs/changelog_plugins/2023/february.rst index 1394dfce..1e16cd08 100644 --- a/docs/changelog_plugins/2023/february.rst +++ b/docs/changelog_plugins/2023/february.rst @@ -38,7 +38,7 @@ Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxe/cat9k diff --git a/docs/changelog_plugins/2023/january.rst b/docs/changelog_plugins/2023/january.rst index c4df89cd..f5173974 100644 --- a/docs/changelog_plugins/2023/january.rst +++ b/docs/changelog_plugins/2023/january.rst @@ -40,7 +40,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxe diff --git a/docs/changelog_plugins/2023/july.rst b/docs/changelog_plugins/2023/july.rst index e8fe6daa..2c9d0edf 100644 --- a/docs/changelog_plugins/2023/july.rst +++ b/docs/changelog_plugins/2023/july.rst @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxe diff --git a/docs/changelog_plugins/2023/june.rst b/docs/changelog_plugins/2023/june.rst index 94a0bca0..17045ce2 100644 --- a/docs/changelog_plugins/2023/june.rst +++ b/docs/changelog_plugins/2023/june.rst @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxe diff --git a/docs/changelog_plugins/2023/march.rst b/docs/changelog_plugins/2023/march.rst index 1dc06b6a..fd04711d 100644 --- a/docs/changelog_plugins/2023/march.rst +++ b/docs/changelog_plugins/2023/march.rst @@ -38,7 +38,7 @@ Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * nxos diff --git a/docs/changelog_plugins/2023/may.rst b/docs/changelog_plugins/2023/may.rst index 056a8758..40dc7c8c 100644 --- a/docs/changelog_plugins/2023/may.rst +++ b/docs/changelog_plugins/2023/may.rst @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * generic @@ -55,7 +55,7 @@ Changelogs -------------------------------------------------------------------------------- - Modify +Modify -------------------------------------------------------------------------------- * iosxr diff --git a/docs/changelog_plugins/2023/november.rst b/docs/changelog_plugins/2023/november.rst index 93db0044..df821c7e 100644 --- a/docs/changelog_plugins/2023/november.rst +++ b/docs/changelog_plugins/2023/november.rst @@ -1,8 +1,8 @@ November 2023 -========== +============= November 27 - Unicon.Plugins v23.11 ------------------------- +----------------------------------- @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * sonic @@ -45,7 +45,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * generic diff --git a/docs/changelog_plugins/2023/october.rst b/docs/changelog_plugins/2023/october.rst index 04cf9fe1..cacffc1e 100644 --- a/docs/changelog_plugins/2023/october.rst +++ b/docs/changelog_plugins/2023/october.rst @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * generic @@ -52,7 +52,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * generic diff --git a/docs/changelog_plugins/2023/september.rst b/docs/changelog_plugins/2023/september.rst index 9c04de89..362b6dbc 100644 --- a/docs/changelog_plugins/2023/september.rst +++ b/docs/changelog_plugins/2023/september.rst @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * generic diff --git a/docs/changelog_plugins/2024/September.rst b/docs/changelog_plugins/2024/September.rst index a74c4d1d..dc0f2cc1 100644 --- a/docs/changelog_plugins/2024/September.rst +++ b/docs/changelog_plugins/2024/September.rst @@ -1,8 +1,8 @@ September 2024 -========== +============== September 24 - Unicon.Plugins v24.9 ------------------------- +----------------------------------- @@ -18,7 +18,7 @@ September 24 - Unicon.Plugins v24.9 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxr @@ -33,7 +33,7 @@ Changelogs -------------------------------------------------------------------------------- - Add +Add -------------------------------------------------------------------------------- * apic plugin diff --git a/docs/changelog_plugins/2024/april.rst b/docs/changelog_plugins/2024/april.rst index 2ef86bdf..42ccf1da 100644 --- a/docs/changelog_plugins/2024/april.rst +++ b/docs/changelog_plugins/2024/april.rst @@ -1,7 +1,7 @@ April 2024 ========== - - Unicon.Plugins v24.4 +- Unicon.Plugins v24.4 ------------------------ @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * generic @@ -48,7 +48,7 @@ Changelogs * Update escape character handler timing settings * Revert adding connection closed statement to execute service * Update config transition logic - * Add `result_check_per_command` option to disable/enable error checking per configuration command + * Add ``result_check_per_command`` option to disable/enable error checking per configuration command * iosxe * Fix operating mode logic @@ -58,8 +58,8 @@ Changelogs * iosxr * Add standby locked state to single RP statemachine * Change default behavior of ``configure()`` service, error check after all commands by default - * Add handler for `show configuration failed` errors to ``configure()`` service. - * Add `SHOW_CONFIG_FAILED_CMD` setting for command to use, default `show configuration failed` + * Add handler for ``show configuration failed``errors to`` configure()`` service. + * Add ``SHOW_CONFIG_FAILED_CMD``setting for command to use, default``show configuration failed`` * other * update pid token list diff --git a/docs/changelog_plugins/2024/august.rst b/docs/changelog_plugins/2024/august.rst index 66a7ef25..e57ac782 100644 --- a/docs/changelog_plugins/2024/august.rst +++ b/docs/changelog_plugins/2024/august.rst @@ -1,8 +1,8 @@ August 2024 -========== +=========== August 27 - Unicon.Plugins v24.8 ------------------------- +-------------------------------- diff --git a/docs/changelog_plugins/2024/february.rst b/docs/changelog_plugins/2024/february.rst index ea612936..f13eb0bc 100644 --- a/docs/changelog_plugins/2024/february.rst +++ b/docs/changelog_plugins/2024/february.rst @@ -1,8 +1,8 @@ February 2024 -========== +============= February 27 - Unicon.Plugins v24.2 ------------------------- +---------------------------------- @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * generic @@ -50,7 +50,7 @@ Changelogs -------------------------------------------------------------------------------- - Add +Add -------------------------------------------------------------------------------- * utils diff --git a/docs/changelog_plugins/2024/january.rst b/docs/changelog_plugins/2024/january.rst index 9765faba..fd90f875 100644 --- a/docs/changelog_plugins/2024/january.rst +++ b/docs/changelog_plugins/2024/january.rst @@ -1,8 +1,8 @@ January 2024 -========== +============ 30 - Unicon.Plugins v24.1 ------------------------- +------------------------- @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Add +Add -------------------------------------------------------------------------------- * generic @@ -45,7 +45,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxe diff --git a/docs/changelog_plugins/2024/july.rst b/docs/changelog_plugins/2024/july.rst index 73ef6427..72562eee 100644 --- a/docs/changelog_plugins/2024/july.rst +++ b/docs/changelog_plugins/2024/july.rst @@ -2,7 +2,7 @@ July 2024 ========== July 30 - Unicon.Plugins v24.7 ------------------------- +------------------------------ @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxe diff --git a/docs/changelog_plugins/2024/june.rst b/docs/changelog_plugins/2024/june.rst index cd7b898e..b4ec5fd9 100644 --- a/docs/changelog_plugins/2024/june.rst +++ b/docs/changelog_plugins/2024/june.rst @@ -1,7 +1,7 @@ June 2024 ========== - - Unicon.Plugins v24.6 +- Unicon.Plugins v24.6 ------------------------ @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * other @@ -46,10 +46,10 @@ Changelogs -------------------------------------------------------------------------------- - Add +Add -------------------------------------------------------------------------------- * iosxr - * Added `admin_host` state support + * Added ``admin_host`` state support diff --git a/docs/changelog_plugins/2024/march.rst b/docs/changelog_plugins/2024/march.rst index 37b8e7f5..0c05e371 100644 --- a/docs/changelog_plugins/2024/march.rst +++ b/docs/changelog_plugins/2024/march.rst @@ -1,7 +1,7 @@ March 2024 ========== - - Unicon.Plugins v24.3 +- Unicon.Plugins v24.3 ------------------------ @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * cheetah/ap diff --git a/docs/changelog_plugins/2024/may.rst b/docs/changelog_plugins/2024/may.rst index aaa05730..70ae7229 100644 --- a/docs/changelog_plugins/2024/may.rst +++ b/docs/changelog_plugins/2024/may.rst @@ -2,7 +2,7 @@ May 2024 ========== May 28 - Unicon.Plugins v24.5 ------------------------- +----------------------------- @@ -37,7 +37,7 @@ Features and Bug Fixes: Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * sros diff --git a/docs/changelog_plugins/2024/november.rst b/docs/changelog_plugins/2024/november.rst index d72930e1..47ffba82 100644 --- a/docs/changelog_plugins/2024/november.rst +++ b/docs/changelog_plugins/2024/november.rst @@ -1,8 +1,8 @@ November 2024 -========== +============= November 26 - Unicon.Plugins v24.11 ------------------------- +----------------------------------- @@ -18,7 +18,7 @@ November 26 - Unicon.Plugins v24.11 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxe @@ -41,7 +41,7 @@ Changelogs -------------------------------------------------------------------------------- - Add +Add -------------------------------------------------------------------------------- * iosxe diff --git a/docs/changelog_plugins/2024/october.rst b/docs/changelog_plugins/2024/october.rst index 6772e328..b40d2db0 100644 --- a/docs/changelog_plugins/2024/october.rst +++ b/docs/changelog_plugins/2024/october.rst @@ -1,8 +1,8 @@ October 2024 -========== +============ October 29 - Unicon.Plugins v24.10 ------------------------- +---------------------------------- @@ -18,7 +18,7 @@ October 29 - Unicon.Plugins v24.10 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * pid tokens @@ -26,7 +26,7 @@ Changelogs -------------------------------------------------------------------------------- - Add +Add -------------------------------------------------------------------------------- * apic plugin diff --git a/docs/changelog_plugins/2025/april.rst b/docs/changelog_plugins/2025/april.rst index 82df75a0..9f375267 100644 --- a/docs/changelog_plugins/2025/april.rst +++ b/docs/changelog_plugins/2025/april.rst @@ -2,7 +2,7 @@ April 2025 ========== April 29 - Unicon.Plugins v25.4 ------------------------- +------------------------------- @@ -18,7 +18,7 @@ April 29 - Unicon.Plugins v25.4 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxr @@ -31,7 +31,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * iosxe diff --git a/docs/changelog_plugins/2025/august.rst b/docs/changelog_plugins/2025/august.rst index 8abf503f..8416c5bc 100644 --- a/docs/changelog_plugins/2025/august.rst +++ b/docs/changelog_plugins/2025/august.rst @@ -1,8 +1,8 @@ August 2025 -========== +=========== August 23 - Unicon.Plugins v25.8 ------------------------- +--------------------------------- @@ -18,7 +18,7 @@ August 23 - Unicon.Plugins v25.8 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * iosxe @@ -26,7 +26,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxe diff --git a/docs/changelog_plugins/2025/december.rst b/docs/changelog_plugins/2025/december.rst index 767c4c85..3fad3ab6 100644 --- a/docs/changelog_plugins/2025/december.rst +++ b/docs/changelog_plugins/2025/december.rst @@ -1,8 +1,8 @@ December 2025 -========== +============= December 30 - Unicon.Plugins v25.11 ------------------------- +----------------------------------- @@ -18,7 +18,7 @@ December 30 - Unicon.Plugins v25.11 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxe @@ -35,7 +35,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * generic/settings.py diff --git a/docs/changelog_plugins/2025/february.rst b/docs/changelog_plugins/2025/february.rst index abc6f684..83e2ddd7 100644 --- a/docs/changelog_plugins/2025/february.rst +++ b/docs/changelog_plugins/2025/february.rst @@ -1,8 +1,8 @@ February 2025 -========== +============= February 25 - Unicon.Plugins v25.2 ------------------------- +---------------------------------- @@ -18,7 +18,7 @@ February 25 - Unicon.Plugins v25.2 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * generic @@ -31,6 +31,6 @@ Changelogs * Update prompt pattern * iosxe - * Updated the enable, disable and maintenance states to support `(unlicensed)` prompt + * 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 204d5a75..6732c35c 100644 --- a/docs/changelog_plugins/2025/january.rst +++ b/docs/changelog_plugins/2025/january.rst @@ -1,7 +1,7 @@ January 2025 -========== +============ - - Unicon.Plugins v25.1 +- Unicon.Plugins v25.1 ------------------------ @@ -18,7 +18,7 @@ January 2025 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxr diff --git a/docs/changelog_plugins/2025/july.rst b/docs/changelog_plugins/2025/july.rst index d45e1738..87289a8e 100644 --- a/docs/changelog_plugins/2025/july.rst +++ b/docs/changelog_plugins/2025/july.rst @@ -2,7 +2,7 @@ July 2025 ========== July 29 - Unicon.Plugins v25.7 ------------------------- +------------------------------ @@ -18,7 +18,7 @@ July 29 - Unicon.Plugins v25.7 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxr @@ -30,7 +30,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * iosxe diff --git a/docs/changelog_plugins/2025/june.rst b/docs/changelog_plugins/2025/june.rst index c4f1c46a..58385e00 100644 --- a/docs/changelog_plugins/2025/june.rst +++ b/docs/changelog_plugins/2025/june.rst @@ -2,7 +2,7 @@ June 2025 ========== June 29 - Unicon.Plugins v25.6 ------------------------- +------------------------------ @@ -18,7 +18,7 @@ June 29 - Unicon.Plugins v25.6 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxr/spitfire @@ -27,14 +27,14 @@ Changelogs * unicon.plugins * IOSXE * Stack - * Fixed `image_to_boot` parameter in reload service so that it is actually used in the reload process. + * Fixed ``image_to_boot`` parameter in reload service so that it is actually used in the reload process. * generic * Update syslog pattern -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * iosxe diff --git a/docs/changelog_plugins/2025/march.rst b/docs/changelog_plugins/2025/march.rst index 107b4d42..f295b764 100644 --- a/docs/changelog_plugins/2025/march.rst +++ b/docs/changelog_plugins/2025/march.rst @@ -2,7 +2,7 @@ March 2025 ========== March 25 - Unicon.Plugins v25.3 ------------------------- +------------------------------- diff --git a/docs/changelog_plugins/2025/may.rst b/docs/changelog_plugins/2025/may.rst index 696fff0f..a756a1a2 100644 --- a/docs/changelog_plugins/2025/may.rst +++ b/docs/changelog_plugins/2025/may.rst @@ -1,7 +1,7 @@ May 2025 ========== - - Unicon.Plugins v25.5 +- Unicon.Plugins v25.5 ------------------------ @@ -18,7 +18,7 @@ May 2025 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Add +Add -------------------------------------------------------------------------------- * iosxe/cat9k/stackwise_virtual @@ -32,7 +32,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * generic @@ -44,7 +44,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * nxos diff --git a/docs/changelog_plugins/2025/october.rst b/docs/changelog_plugins/2025/october.rst index 8af0480c..38c56e45 100644 --- a/docs/changelog_plugins/2025/october.rst +++ b/docs/changelog_plugins/2025/october.rst @@ -1,8 +1,8 @@ October 2025 -========== +============ October 28 - Unicon.Plugins v25.10 ------------------------- +---------------------------------- @@ -18,7 +18,7 @@ October 28 - Unicon.Plugins v25.10 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * iosxe/c9800 @@ -34,7 +34,7 @@ Changelogs -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * iosxe diff --git a/docs/changelog_plugins/2025/september.rst b/docs/changelog_plugins/2025/september.rst index f3fb1a26..c98b08e2 100644 --- a/docs/changelog_plugins/2025/september.rst +++ b/docs/changelog_plugins/2025/september.rst @@ -1,8 +1,8 @@ September 2025 -========== +============== September 30 - Unicon v25.9 ------------------------- +--------------------------- @@ -18,7 +18,7 @@ September 30 - Unicon v25.9 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * generic @@ -33,7 +33,7 @@ Changelogs -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * iosxe/cat9k add rommon variable support in reload service diff --git a/docs/changelog_plugins/2026/april.rst b/docs/changelog_plugins/2026/april.rst new file mode 100644 index 00000000..ed695fa7 --- /dev/null +++ b/docs/changelog_plugins/2026/april.rst @@ -0,0 +1,49 @@ +April 2026 +========== + +April 28 - Unicon.Plugins v26.4 +------------------------------- + + + +.. csv-table:: Module Versions + :header: "Modules", "Versions" + + ``unicon.plugins``, v26.4 + ``unicon``, v26.4 + + + + +Changelogs +^^^^^^^^^^ + +-------------------------------------------------------------------------------- + Fix +-------------------------------------------------------------------------------- + +* iosxe/stack + * StackSwitchover + * Added state machine transitions to handle 'Press RETURN' and login prompts post-switchover + * StackRommon + * Fixed handling of mixed console states during rommon boot operations + +* iosxe + * pattern + * Updated ``syslog_message_pattern``to recognize the trailing``key config-key password-encrypt`` master-key warning line so it is treated as banner/syslog noise instead of prompt content. + * Updated ``disable_prompt``to avoid matching the master-key warning line ending in````, which could lead to incorrect hostname learning during login. + * Added a regression unittest covering backend-style prompt matching for the master-key warning buffer. + * Updated login credential handling for post-logout reconnects + * Reuse the last successful device credential from ``current_credentials`` when reconnecting after logout + * Added a cat9k unittest that reproduces console relogin after logout and verifies the device credential is reused instead of the terminal server credential + * Updated cat9k mock data to include the initial terminal server password hop and the console banner before relogin + + +-------------------------------------------------------------------------------- + New +-------------------------------------------------------------------------------- + +* iosxe/ar1k/service_implementation.py + * Added a new HA Reload implementation for ASR1k. + + diff --git a/docs/changelog_plugins/2026/february.rst b/docs/changelog_plugins/2026/february.rst index e8189ef4..e0f4e494 100644 --- a/docs/changelog_plugins/2026/february.rst +++ b/docs/changelog_plugins/2026/february.rst @@ -1,8 +1,8 @@ February 2026 -========== +============= February 24 - Unicon.Plugins v26.2 ------------------------- +---------------------------------- @@ -18,7 +18,7 @@ February 24 - Unicon.Plugins v26.2 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * generic diff --git a/docs/changelog_plugins/2026/january.rst b/docs/changelog_plugins/2026/january.rst index d33fe64d..10a92ef9 100644 --- a/docs/changelog_plugins/2026/january.rst +++ b/docs/changelog_plugins/2026/january.rst @@ -1,8 +1,8 @@ January 2026 -========== +============ January 27 - Unicon v26.1 ------------------------- +------------------------- @@ -18,7 +18,7 @@ January 27 - Unicon v26.1 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * generic diff --git a/docs/changelog_plugins/2026/march.rst b/docs/changelog_plugins/2026/march.rst index 29ca9e4b..1b816234 100644 --- a/docs/changelog_plugins/2026/march.rst +++ b/docs/changelog_plugins/2026/march.rst @@ -2,7 +2,7 @@ March 2026 ========== March 31 - Unicon.Plugins v26.3 ------------------------- +------------------------------- @@ -18,7 +18,7 @@ March 31 - Unicon.Plugins v26.3 Changelogs ^^^^^^^^^^ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * generic diff --git a/docs/changelog_plugins/changelog_lsheikal_update_generic_password_ok_20251112073999.rst b/docs/changelog_plugins/changelog_lsheikal_update_generic_password_ok_20251112073999.rst index 6de866d9..77c1f281 100644 --- a/docs/changelog_plugins/changelog_lsheikal_update_generic_password_ok_20251112073999.rst +++ b/docs/changelog_plugins/changelog_lsheikal_update_generic_password_ok_20251112073999.rst @@ -1,5 +1,5 @@ -------------------------------------------------------------------------------- - New +New -------------------------------------------------------------------------------- * generic/statements.py * Updated the password_ok_stmt to use escape_char_callback instead of sendline. diff --git a/docs/changelog_plugins/index.rst b/docs/changelog_plugins/index.rst index 6b9290fb..5760bbc4 100644 --- a/docs/changelog_plugins/index.rst +++ b/docs/changelog_plugins/index.rst @@ -4,6 +4,7 @@ Plugins Changelog .. toctree:: :maxdepth: 2 + 2026/april 2026/march 2026/february 2026/january diff --git a/docs/changelog_plugins/undistributed/template.rst b/docs/changelog_plugins/undistributed/template.rst index 65314f95..c9aec2d1 100644 --- a/docs/changelog_plugins/undistributed/template.rst +++ b/docs/changelog_plugins/undistributed/template.rst @@ -1,5 +1,5 @@ -------------------------------------------------------------------------------- - Fix +Fix -------------------------------------------------------------------------------- * * diff --git a/docs/conf.py b/docs/conf.py index d7960ac6..a31dd0df 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -94,7 +94,13 @@ # directories to ignore when looking for source files. # README.rst and DESCRIPTION.rst are placed in some packages, but are not # built into the cisco-shared Sphinx documentation. -exclude_patterns = ['_build', 'tests'] +exclude_patterns = [ + '_build', 'tests', + 'changelog/undistributed.rst', 'changelog/undistributed/*', + 'changelog_plugins/undistributed.rst', 'changelog_plugins/undistributed/*', + 'changelog_plugins/changelog_*.rst', +] +suppress_warnings = ['docutils', 'ref.ref', 'toc.not_included', 'misc.highlighting_failure'] # The reST default role (used for this markup: `text`) to use for all # documents. @@ -151,7 +157,7 @@ # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -html_favicon = 'favicon.ico' +html_favicon = 'favicon.ico' if os.path.exists('favicon.ico') else None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, diff --git a/docs/developer_guide/eal.rst b/docs/developer_guide/eal.rst index 7edcfdcd..03352408 100644 --- a/docs/developer_guide/eal.rst +++ b/docs/developer_guide/eal.rst @@ -114,7 +114,7 @@ Spawn ----- You can ``Spawn`` any command to interact with it. Once the command is spawned -you can interact with it using APIs like ``send`` and ``expect``. +you can interact with it using APIs like ``send``and``expect``. This is how, in a nutshell, it works @@ -241,7 +241,7 @@ the **username** prompt. Let us generalise the above program a bit. We may come across some routers where username prompt doesn't look like ``username:``, it may also show up like -``login::``. The good news is, ``expect`` method can take a list of patterns. +``login::``. The good news is,``expect`` method can take a list of patterns. .. code-block:: python @@ -413,14 +413,14 @@ in the next section. .. note:: - Mention ``args``, ``loop_continue``, ``continue_timer`` only if you want to + Mention ``args``,``loop_continue``,``continue_timer`` only if you want to change the the default values. This will help reducting the clutter. **Timeout Statement**: By default, if none of Statement patterns get match within timeout period ``TimeoutError`` execption gets raised. If we want to add some custom action when timeout occurs before ``TimeoutError`` execption, this can be done by adding a -``Statement`` with pattern set as ``__timeout__``. Action set for this Statement +``Statement``with pattern set as``__timeout__``. Action set for this Statement will get invoke if timeout occurs. .. code-block:: python @@ -435,8 +435,8 @@ will get invoke if timeout occurs. .. note:: - Make sure to set ``continue_timer`` as ``False`` for timeout statement, else it may - will end up in infinite loop. If ``continue_timer`` set as ``True``, then ``Dialog`` will + Make sure to set ``continue_timer``as``False`` for timeout statement, else it may + will end up in infinite loop. If ``continue_timer``set as``True``, then``Dialog`` will start trying to match all patterns again and timeout period will be reset to original one. diff --git a/docs/developer_guide/plugins.rst b/docs/developer_guide/plugins.rst index 97a5fdc0..8d8bd73e 100644 --- a/docs/developer_guide/plugins.rst +++ b/docs/developer_guide/plugins.rst @@ -143,7 +143,7 @@ Users implementing a new platform have to define a ``Connection class``, with th parameters which are listed below in this section. The new ``Connection`` class should satisfy the following conditions - * It should be subclass (direct or indirect) of ``Connection``, ``BaseSingleRpConnection`` or ``BaseDualRpConnection`` + * It should be subclass (direct or indirect) of ``Connection``,``BaseSingleRpConnection``or``BaseDualRpConnection`` * ``Connection`` follows class hierarchy which is aligned/derived according to the os, platform and model @@ -164,7 +164,7 @@ subcommand_list List of subcommand supported settings Settings to be used for this connection ========================= ======================================== -``os`` and ``chassis_type`` of the implementation has to be mentioned in the connection. +``os``and``chassis_type`` of the implementation has to be mentioned in the connection. .. code-block:: python @@ -202,7 +202,7 @@ The connection class for any platform depends on the connection provider for ini connection. As the name suggests, their role is to provide a method to let the application connect and disconnect to the device. -This class provides two essential methods, namely ``connect`` and ``disconnect``. +This class provides two essential methods, namely ``connect``and``disconnect``. The ``connect`` method defines all the steps involved in the connection process, which are defined as separate methods. These steps vary depending on the chassis type and the device, changing the behaviour of these diff --git a/docs/developer_guide/service_framework.rst b/docs/developer_guide/service_framework.rst index 8ae11765..a9bdbaee 100644 --- a/docs/developer_guide/service_framework.rst +++ b/docs/developer_guide/service_framework.rst @@ -83,7 +83,7 @@ Before start coding pre_service, let us go through __init__ of BaseService Class .. note:: Any input object sent by the user calling your service, if not passed - directly to the ``send`` or ``sendline`` spawn method, must be properly + directly to the ``send``or``sendline`` spawn method, must be properly converted to a string form. Users are allowed to specify non-string objects as input. diff --git a/docs/developer_guide/statemachine.rst b/docs/developer_guide/statemachine.rst index 5d2a4c1f..d2f3501d 100644 --- a/docs/developer_guide/statemachine.rst +++ b/docs/developer_guide/statemachine.rst @@ -19,8 +19,8 @@ platform implementation is over. In this chapter we will go through the important APIs and using an example device we will try to implement a working statemachine. -Before you go further, please make sure you have gone through -:doc:`Expect Abstraction Library <../user_guide/eal>` +Before you go further, please make sure you have gone through the Expect +Abstraction Library material. .. note:: @@ -92,7 +92,7 @@ Continuing from the previous example, lets add a few ``Path``. config_to_enable = Path(config, enable, "end", None) Please note that ``Dialog`` in above example will be different in all the lines, -based on the nature of interaction caused by the ``command``, a blank ``Dialog`` +based on the nature of interaction caused by the ``command``, a blank``Dialog`` has been used just for example. The ``command`` option can also be a callable function. This can be used in case @@ -121,7 +121,7 @@ required for creating shortest paths between any two given states. It uses all the ``Path`` instances to make way from any given state to any state. It provides APIs required for state migration, which we shall see shortly. -Let's create a sample *statemachine* class. All the ``State`` and the ``Path`` +Let's create a sample *statemachine* class. All the ``State``and the``Path`` instances, which we created above are eventually fed into the custom statemachine class. @@ -160,7 +160,7 @@ statemachine class. .. note:: - Please note that we don't want same ``State`` and ``Path`` instances to be + Please note that we don't want same ``State``and``Path`` instances to be reused by different *statemachine* classes. Hence we create ``State`` and ``Path`` instances inside the scope of *statemachine* class. diff --git a/docs/playback/index.rst b/docs/playback/index.rst index e17092eb..96670fd0 100644 --- a/docs/playback/index.rst +++ b/docs/playback/index.rst @@ -91,7 +91,7 @@ Here a few examples on how to use it. easypy jobfile.py -testbed_file mytestbed.yaml --replay recording1 --speed .25 In case the dash argument cannot be used, environment variable -``UNICON_REPLAY`` and ``UNICON_REPLAY_SPEED`` can be used instead. +``UNICON_REPLAY``and``UNICON_REPLAY_SPEED`` can be used instead. .. code-block:: bash diff --git a/docs/user_guide/connection.rst b/docs/user_guide/connection.rst index dcfbbd64..3f9f5c0d 100644 --- a/docs/user_guide/connection.rst +++ b/docs/user_guide/connection.rst @@ -258,7 +258,7 @@ The following testbed YAML shows these three kinds of override: .. note :: - Details specified under the ``arguments``, ``settings`` or + Details specified under the ``arguments``,``settings`` or ``service_attributes`` connection block keys take precedence over any identically-named details passed to the ``device.connect()`` call. @@ -277,7 +277,7 @@ The following testbed YAML shows these three kinds of override: If you want to change to default timeout value for execute and configure service, -you can set the ``EXEC_TIMEOUT`` and ``CONFIG_TIMEOUT`` in the testbed file: +you can set the ``EXEC_TIMEOUT``and``CONFIG_TIMEOUT`` in the testbed file: .. code-block:: yaml @@ -352,7 +352,7 @@ For more info on testbed refer to :ref:`topology` package. .. note:: unicon Connection arguments may be passed in the pyATS - ``device.connect()``. For example: ``device.connect(learn_hostname=True)``. + ``device.connect()``. For example:``device.connect(learn_hostname=True)``. @@ -1045,7 +1045,7 @@ Extend Settings Attributes """""""""""""""""""""""""" It is possible to extend list settings attributes of the connection like ``ERROR_PATTERN`` -and ``CONFIGURE_ERROR_PATTERN`` by using ``overwrite_settings=False`` argument. +and ``CONFIGURE_ERROR_PATTERN``by using``overwrite_settings=False`` argument. .. code-block:: python diff --git a/docs/user_guide/passwords.rst b/docs/user_guide/passwords.rst index 92d24066..c4f25e8b 100644 --- a/docs/user_guide/passwords.rst +++ b/docs/user_guide/passwords.rst @@ -13,7 +13,7 @@ Credentials The ``credentials`` connection parameter defines a dictionary of named credentials. A credential is a dictionary typically containing both -``username`` and ``password`` keys. +``username``and``password`` keys. The ``login_creds`` connection parameter defines an optional sequence of credential names to try. Each time the device prompts for a username or @@ -141,7 +141,7 @@ Settings can also be specified for the connection in the topology file as shown SENDLINE_AFTER_CRED: terminal_server -The post credential action supports ``send`` and ``sendline``, you can specify a string to be sent, +The post credential action supports ``send``and``sendline``, you can specify a string to be sent, e.g. `send( )` to send a space or `send(\\x03)` to send Ctrl-C. Quotes should not be specified. .. code-block:: yaml @@ -296,16 +296,16 @@ When a router asks for an enable password, the password sent is determined by the following checks. If all checks are done and still no enable password is found then an exception is raised. -#. The ``enable_password`` field of the credential specified by the ``login_creds`` in the connect call. -#. The ``default`` credential ``enable_password`` -#. The ``enable`` credential ``password`` (legacy) -#. The ``default`` credential ``password`` (legacy) +#. The ``enable_password``field of the credential specified by the``login_creds`` in the connect call. +#. The ``default``credential``enable_password`` +#. The ``enable``credential``password`` (legacy) +#. The ``default``credential``password`` (legacy) Password sequences in service calls ----------------------------------- -Several services, including ``reload`` and ``switchover``, accept a +Several services, including ``reload``and``switchover``, accept a credential list that is used to authenticate against a sequence of username/password prompts encountered while the service is running. @@ -474,7 +474,7 @@ If connecting via ssh, the username of the currently logged in user is used by default if not otherwise specified via credentials or via ``command`` or ``ssh_options`` keys in one of the following forms: -``ssh -l username
`` or ``ssh username@
``. +``ssh -l username
``or``ssh username@
``. In order to execute a command that leads to a username/password prompt, you must explicitly add the password statement to the reply Dialog. @@ -527,7 +527,7 @@ Otherwise, the password from the ``default`` credential is sent. Otherwise, a nxos password logic ------------------- -The ``switchto`` service accepts a ``vdc_cred`` argument that identifies a +The ``switchto``service accepts a``vdc_cred`` argument that identifies a named credential to use to authenticate against the VDC. SSH passphrase diff --git a/docs/user_guide/proxy.rst b/docs/user_guide/proxy.rst index 9e34f0af..9b50ac72 100644 --- a/docs/user_guide/proxy.rst +++ b/docs/user_guide/proxy.rst @@ -36,7 +36,7 @@ specified in the topology YAML file. CLI proxy with pyATS topology integration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Example topology file with a ``proxy`` configuration for the ``cli`` connection. +Example topology file with a ``proxy``configuration for the``cli`` connection. Please note that credentials have been left out of this example. .. code:: yaml @@ -607,7 +607,7 @@ The SSH tunnel host can be a testbed server or can be another device from the te SSH tunnel with pyATS topology integration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Example topology file with a ``sshtunnel`` configuration for the ``a`` connection of device R2. +Example topology file with a ``sshtunnel``configuration for the``a`` connection of device R2. .. code:: yaml diff --git a/docs/user_guide/services/aci.rst b/docs/user_guide/services/aci.rst index be05e48c..0a3a0920 100644 --- a/docs/user_guide/services/aci.rst +++ b/docs/user_guide/services/aci.rst @@ -8,7 +8,7 @@ This section lists the services which are supported for Application Centric Infr * `reload <#reload>`__ The ACI plugin supports only APIC and N9K (in ACI mode). Specify ``os=apic`` for APIC, specify -``os=nxos`` and ``platform=aci`` for N9K. +``os=nxos``and``platform=aci`` for N9K. .. note:: diff --git a/docs/user_guide/services/asa_fp2k.rst b/docs/user_guide/services/asa_fp2k.rst index 0afdf2ea..c35fdfe5 100644 --- a/docs/user_guide/services/asa_fp2k.rst +++ b/docs/user_guide/services/asa_fp2k.rst @@ -25,7 +25,7 @@ The following generic services are also available: execute ------- -The services ``fxos``, ``fxos_mgmt``, ``sudo``, ``disable``, ``enable``, ``rommon`` +The services ``fxos``,``fxos_mgmt``,``sudo``,``disable``,``enable``,``rommon`` are aliases to the execute service and are based on the generic execute implementation. You can use these methods to switch between states and/or execute commands in that state. diff --git a/docs/user_guide/services/fxos.rst b/docs/user_guide/services/fxos.rst index 893465dd..4962016b 100644 --- a/docs/user_guide/services/fxos.rst +++ b/docs/user_guide/services/fxos.rst @@ -28,7 +28,7 @@ The following generic services are also available: execute ------- -The services ``ftd``, ``fxos``, ``fxos_mgmt``, ``expert``, ``sudo``, ``disable``, ``enable``, +The services ``ftd``,``fxos``,``fxos_mgmt``,``expert``,``sudo``,``disable``,``enable``, ``rommon`` are aliases to the execute service and are based on the generic execute implementation. You can use these methods to switch between states and/or execute commands in that state. diff --git a/docs/user_guide/services/linux.rst b/docs/user_guide/services/linux.rst index 09b76d18..63b6b6b1 100644 --- a/docs/user_guide/services/linux.rst +++ b/docs/user_guide/services/linux.rst @@ -59,7 +59,7 @@ or it raises an exception. You can expect a TimeoutError or SubCommandFailure error in case anything goes wrong. This list of valid return codes can be specified via ``valid_retcodes``. If the setting -`CHECK_RETURN_CODE` is set to ``True`` or the ``check_retcode`` option is True, +`CHECK_RETURN_CODE` is set to ``True``or the``check_retcode`` option is True, the command `echo $?` is used to check the return value of the (last) executed command. If the value is not in the list, a SubCommandFailure is raised. diff --git a/docs/user_guide/supported_platforms.rst b/docs/user_guide/supported_platforms.rst index aeae35e7..973e6acd 100644 --- a/docs/user_guide/supported_platforms.rst +++ b/docs/user_guide/supported_platforms.rst @@ -8,9 +8,9 @@ model (specific model support). These values help Unicon load the most accurate connection plugin for the given network device, and corresponds to ther pyATS testbed YAML counterparts. -For example, if ``os=iosxe`` and ``platform=abc``, since ``abc`` is not found in +For example, if ``os=iosxe``and``platform=abc``, since``abc`` is not found in the iosxe table, it will fallback to use the generic ``iosxe`` plugin. If -``os=iosxe`` and ``platform=cat3k``, it will use the specific plugin ``iosxe/cat3k``. +``os=iosxe``and``platform=cat3k``, it will use the specific plugin``iosxe/cat3k``. .. tip:: @@ -36,59 +36,59 @@ the iosxe table, it will fallback to use the generic ``iosxe`` plugin. If ``apic`` ``aireos`` ``asa`` - ``asa``, ``asav`` - ``asa``, ``fp2k`` - ``cheetah``, ``ap`` + ``asa``,``asav`` + ``asa``,``fp2k`` + ``cheetah``,``ap`` ``cimc`` ``comware`` ``confd`` - ``confd``, ``esc`` - ``confd``, ``nfvis`` + ``confd``,``esc`` + ``confd``,``nfvis`` ``dnos6`` ``dnos10`` ``fxos``,,,,"Tested with FP2K." - ``fxos``, ``fp4k`` - ``fxos``, ``fp9k`` - ``fxos``, ``ftd``,,,"Deprecated, please use one of the other fxos plugins." + ``fxos``,``fp4k`` + ``fxos``,``fp9k`` + ``fxos``,``ftd``,,,"Deprecated, please use one of the other fxos plugins." ``gaia``, , , , "Check Point Gaia OS" ``hvrp`` - ``ios``, ``ap`` - ``ios``, ``iol`` - ``ios``, ``iosv`` - ``ios``, ``pagent``,,,"See example below." + ``ios``,``ap`` + ``ios``,``iol`` + ``ios``,``iosv`` + ``ios``,``pagent``,,,"See example below." ``iosxe`` - ``iosxe``, ``cat3k`` - ``iosxe``, ``cat3k``, ``ewlc`` - ``iosxe``, ``cat8k`` - ``iosxe``, ``cat9k``, - ``iosxe``, ``cat9k``, ``c9500``, ``c9500x``, "See example below." - ``iosxe``, ``c9800`` - ``iosxe``, ``c9800``, ``ewc_ap`` - ``iosxe``, ``csr1000v`` - ``iosxe``, ``csr1000v``, ``vewlc`` - ``iosxe``, ``iec3400`` - ``iosxe``, ``sdwan`` + ``iosxe``,``cat3k`` + ``iosxe``,``cat3k``,``ewlc`` + ``iosxe``,``cat8k`` + ``iosxe``,``cat9k``, + ``iosxe``,``cat9k``,``c9500``,``c9500x``, "See example below." + ``iosxe``,``c9800`` + ``iosxe``,``c9800``,``ewc_ap`` + ``iosxe``,``csr1000v`` + ``iosxe``,``csr1000v``,``vewlc`` + ``iosxe``,``iec3400`` + ``iosxe``,``sdwan`` ``iosxr`` - ``iosxr``, ``asr9k`` - ``iosxr``, ``iosxrv`` - ``iosxr``, ``iosxrv9k`` - ``iosxr``, ``moonshine`` - ``iosxr``, ``ncs5k`` - ``iosxr``, ``spitfire`` + ``iosxr``,``asr9k`` + ``iosxr``,``iosxrv`` + ``iosxr``,``iosxrv9k`` + ``iosxr``,``moonshine`` + ``iosxr``,``ncs5k`` + ``iosxr``,``spitfire`` ``ironware`` ``ise`` ``linux``, , , , "Generic Linux server with bash prompts" ``nd``, , , , "Nexus Dashboard (ND) Linux server. identical to os: linux" ``nxos`` - ``nxos``, ``mds`` - ``nxos``, ``n5k`` - ``nxos``, ``n7k`` - ``nxos``, ``n9k`` - ``nxos``, ``nxosv`` - ``nxos``, ``aci`` + ``nxos``,``mds`` + ``nxos``,``n5k`` + ``nxos``,``n7k`` + ``nxos``,``n9k`` + ``nxos``,``nxosv`` + ``nxos``,``aci`` ``nso``,,,, "Network Service Orchestrator" ``ons``,,,, "Optical Networking System" - ``sdwan``, ``viptela``,,,"Identical to os=viptela." + ``sdwan``,``viptela``,,,"Identical to os=viptela." ``sros`` ``staros`` ``vos`` @@ -119,7 +119,7 @@ your pyATS testbed YAML with it: .. tip:: - in the above example, ``platform`` and ``model`` is not provided, hence Unicon + in the above example, ``platform``and``model`` is not provided, hence Unicon will use the most generic ``os=iosxe`` connection implementation for my device. diff --git a/setup.py b/setup.py index f05dcf46..683bbec0 100755 --- a/setup.py +++ b/setup.py @@ -133,10 +133,10 @@ def version_info(*paths): 'wheel', 'coverage', 'restview', - 'Sphinx', + 'Sphinx==7.4.7', 'sphinxcontrib-napoleon', 'sphinxcontrib-mockautodoc', - 'sphinx-rtd-theme'], + 'sphinx-rtd-theme==3.1.0'], }, # any data files placed outside this package. diff --git a/src/unicon/plugins/__init__.py b/src/unicon/plugins/__init__.py index 352cc5d8..3d8782a7 100644 --- a/src/unicon/plugins/__init__.py +++ b/src/unicon/plugins/__init__.py @@ -1,4 +1,4 @@ -__version__ = "26.3" +__version__ = "26.4" supported_chassis = [ 'single_rp', diff --git a/src/unicon/plugins/generic/patterns.py b/src/unicon/plugins/generic/patterns.py index 4ccb1c71..e68a3751 100644 --- a/src/unicon/plugins/generic/patterns.py +++ b/src/unicon/plugins/generic/patterns.py @@ -73,11 +73,14 @@ def __init__(self): # *May 28 09:01:11.975: PKI_SSL_IPC: SUDI certificate chain and key pair are invalid # SECURITY WARNING - Module: SSH, Command: crypto key generate rsa ..., Reason: SSH RSA host key uses insufficient key length, Remediation: Configure SSH RSA host key with minimum key length of 3072 bits # Switch#[OK] + # % WARNING: The master key is not configured, so passwords/secrets might not be encrypted. + # Configure the master key by using the following command: "key config-key password-encrypt self.syslog_message_pattern = ( 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|%RSA key|INSECURE DYNAMIC WARNING|" + r"key config-key password-encrypt|" 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/generic/utils.py b/src/unicon/plugins/generic/utils.py index 31d2f636..5bd79358 100644 --- a/src/unicon/plugins/generic/utils.py +++ b/src/unicon/plugins/generic/utils.py @@ -42,6 +42,67 @@ def get_redundancy_details(self, connection, timeout=None, who='my'): show_red_out[show_red_out.find("=") + 1:].strip() return redundancy_details + def is_active_standby_ready(self, connection, timeout=120, interval=30): + """Check whether active and standby RP are ready using show redundancy. + + Parses 'show redundancy' output to verify: + - Active RP is in ACTIVE state + - Standby RP is in STANDBY HOT state + + Args: + connection: connection object to execute on + timeout: maximum time to wait in seconds + interval: polling interval in seconds + Returns: + True if both RPs are ready, False on timeout + """ + start_time = time.time() + + # Current Processor Information : + # ----------------------- + # Active Location = slot 6 + # Current Software state = ACTIVE + + p_active = re.compile( + r'Current Processor Information[\s\S]*?' + r'Current Software state\s*=\s*(?P\S+)') + + # Peer Processor Information : + # ---------------------------- + # Standby Location = slot 7 + # Current Software state = STANDBY HOT + + p_standby = re.compile( + r'Peer Processor Information[\s\S]*?' + r'Current Software state\s*=\s*(?P[\S ]+)') + + while (time.time() - start_time) < timeout: + try: + output = connection.execute('show redundancy', timeout=60) + except Exception as err: + connection.log.error( + 'Failed to execute show redundancy: {}'.format(err)) + time.sleep(interval) + continue + + active_match = p_active.search(output) + standby_match = p_standby.search(output) + + active_state = active_match.group('state').strip() if active_match else 'Unknown' + standby_state = standby_match.group('state').strip() if standby_match else 'Unknown' + + connection.log.info( + 'Active RP state: {}, Standby RP state: {}'.format( + active_state, standby_state)) + + if active_state == 'ACTIVE' and standby_state == 'STANDBY HOT': + return True + + connection.log.info('Sleeping for {} secs.'.format(interval)) + time.sleep(interval) + + return False + def flatten_splitlines_command(self, command): if isinstance(command, str): for item in command.splitlines(): diff --git a/src/unicon/plugins/iosxe/asrk/__init__.py b/src/unicon/plugins/iosxe/asrk/__init__.py new file mode 100644 index 00000000..30dea054 --- /dev/null +++ b/src/unicon/plugins/iosxe/asrk/__init__.py @@ -0,0 +1,22 @@ +""" ASRK IOS-XE connection implementation. + +Overrides the HA reload service to use parallel processing +for active and standby RP boot output via ThreadPoolExecutor. +""" + +from unicon.plugins.iosxe import ( + IosXEDualRPConnection, + HAIosXEServiceList) + +from . import service_implementation as svc + + +class AsrkHAIosXEServiceList(HAIosXEServiceList): + def __init__(self): + super().__init__() + self.reload = svc.HAReload + + +class AsrkIosXEDualRPConnection(IosXEDualRPConnection): + platform = 'asr1k' + subcommand_list = AsrkHAIosXEServiceList diff --git a/src/unicon/plugins/iosxe/asrk/service_implementation.py b/src/unicon/plugins/iosxe/asrk/service_implementation.py new file mode 100644 index 00000000..46e1fcef --- /dev/null +++ b/src/unicon/plugins/iosxe/asrk/service_implementation.py @@ -0,0 +1,261 @@ +""" ASRK IOS-XE HA Reload service implementation. + +Uses ThreadPoolExecutor to process active and standby RP boot +dialogs in parallel, preventing standby timeout errors during reload. +""" + +import logging +import warnings +from time import sleep +from concurrent.futures import ThreadPoolExecutor, wait as wait_futures, ALL_COMPLETED + +from unicon.eal.dialogs import Dialog +from unicon.core.errors import SubCommandFailure +from unicon.logs import UniconStreamHandler, UNICON_LOG_FORMAT +from unicon.plugins.generic.statements import custom_auth_statements +from unicon.plugins.generic.service_implementation import ReloadResult +from unicon.plugins.generic.utils import GenericUtils + +from unicon.plugins.iosxe.service_implementation import HAReload as IosXEHAReload + +utils = GenericUtils() + + +class HAReload(IosXEHAReload): + """ASRK-specific HA Reload service. + + Overrides call_service to use ThreadPoolExecutor for processing + active and standby RP boot dialogs in parallel. This prevents the + standby spawn buffer from accumulating unprocessed boot output that + causes timeout errors during the config sync wait phase. + """ + + def call_service(self, command=[], reload_command=[], reply=Dialog([]), + timeout=None, *args, **kwargs): + con = self.connection + sm = self.get_sm() + + # --- IOS-XE specific: image_to_boot, boot_cmd --- + self.context["image_to_boot"] = \ + kwargs.pop("image_to_boot", kwargs.pop('image', '')) + + if sm.current_state == 'rommon' and reload_command: + con.active.context['boot_cmd'] = reload_command + + # Determine the reload command + if command: + reload_cmd = command or "reload" + else: + reload_cmd = reload_command or "reload" + + # --- Parameters from kwargs --- + dialog = kwargs.pop('dialog', Dialog([])) + return_output = kwargs.pop('return_output', False) + reload_creds = kwargs.pop('reload_creds', None) + target_standby_state = kwargs.pop('target_standby_state', 'STANDBY HOT') + error_pattern = kwargs.pop('error_pattern', None) + append_error_pattern = kwargs.pop('append_error_pattern', None) + + # --- Error pattern setup --- + if error_pattern is None: + self.error_pattern = con.settings.ERROR_PATTERN + else: + self.error_pattern = error_pattern + if not isinstance(self.error_pattern, list): + raise ValueError('error_pattern should be a list') + if append_error_pattern: + if not isinstance(append_error_pattern, list): + raise ValueError('append_error_pattern should be a list') + self.error_pattern += append_error_pattern + + # --- Log buffer setup --- + lb = UniconStreamHandler(self.log_buffer) + lb.setFormatter(logging.Formatter(fmt=UNICON_LOG_FORMAT)) + con.log.addHandler(lb) + for subcon in con.subconnections: + subcon.log.addHandler(lb) + self.log_buffer.seek(0) + self.log_buffer.truncate() + + # --- Dialog handling --- + if reply: + if dialog: + con.log.warning("**** Both 'reply' and 'dialog' were provided " + "to the reload service. Ignoring 'dialog'.") + dialog = reply + elif dialog: + warnings.warn('**** "dialog" parameter is deprecated. ' + 'Use "reply" instead. ****', + category=DeprecationWarning) + + timeout = timeout or self.timeout + fmt_str = "+++ reloading %s with reload_command '%s' and timeout is %s +++" + con.log.info(fmt_str % (con.hostname, reload_cmd, timeout)) + + dialog += self.dialog + custom_auth_stmt = custom_auth_statements( + con.settings.LOGIN_PROMPT, con.settings.PASSWORD_PROMPT) + + if reload_creds: + context = con.active.context.copy() + context.update(cred_list=reload_creds) + sby_context = con.standby.context.copy() + sby_context.update(cred_list=reload_creds) + else: + context = con.active.context + sby_context = con.standby.context + + if custom_auth_stmt: + dialog += Dialog(custom_auth_stmt) + + # --- Send reload command --- + if reload_cmd: + con.active.spawn.sendline(reload_cmd) + + try: + # --- Define parallel tasks --- + active_result = {} + + def process_active(): + """Process reload dialog on active RP and bring to 'any' state.""" + reload_output = dialog.process( + con.active.spawn, + context=context, + prompt_recovery=self.prompt_recovery, + timeout=timeout) + active_result['output'] = reload_output + + con.active.state_machine.go_to( + 'any', + con.active.spawn, + prompt_recovery=self.prompt_recovery, + timeout=con.connection_timeout, + context=context) + + def process_standby(): + """Consume boot output from standby RP spawn.""" + con.log.info('Processing standby RP boot output in parallel') + try: + dialog.process( + con.standby.spawn, + context=sby_context, + prompt_recovery=self.prompt_recovery, + timeout=timeout) + except Exception: + con.log.warning( + 'Standby dialog processing did not match any ' + 'statement, continuing with state discovery') + + # --- Process both RPs in parallel --- + con.log.info('Processing active and standby reload in parallel') + executor = ThreadPoolExecutor(max_workers=2) + active_future = executor.submit(process_active) + standby_future = executor.submit(process_standby) + + wait_futures([active_future, standby_future], + timeout=timeout, return_when=ALL_COMPLETED) + + # Re-raise active thread exceptions + if active_future.exception(): + raise active_future.exception() + + # Set reload result from active processing + if 'output' in active_result: + self.result = active_result['output'].match_output + self.get_service_result() + + # --- Bring standby to good state (sequentially, after threads done) --- + con.log.info('Waiting for config sync to finish') + standby_wait_time = con.settings.POST_HA_RELOAD_CONFIG_SYNC_WAIT + standby_wait_interval = 50 + standby_sync_try = standby_wait_time // standby_wait_interval + 1 + for round in range(standby_sync_try): + con.standby.spawn.sendline() + try: + con.standby.state_machine.go_to( + 'any', + con.standby.spawn, + context=sby_context, + timeout=standby_wait_interval, + prompt_recovery=self.prompt_recovery, + dialog=con.connection_provider.get_connection_dialog() + ) + break + except Exception as err: + if round == standby_sync_try - 1: + raise Exception( + 'Bringing standby to any state failed within {} sec' + .format(standby_wait_time)) from err + + # If standby is in rommon, transition to disable state + if con.standby.state_machine.current_state == 'rommon': + con.log.info('Standby is in ROMMON state, transitioning to disable mode') + con.standby.state_machine.go_to( + 'disable', + con.standby.spawn, + context=sby_context, + timeout=timeout, + prompt_recovery=self.prompt_recovery, + dialog=con.connection_provider.get_connection_dialog() + ) + + except Exception as err: + if hasattr(con.device, 'clean') \ + and hasattr(con.device.clean, 'device_recovery') \ + and con.device.clean.device_recovery.get('golden_image'): + con.log.error( + 'Reload failed booting device using golden image: ' + '{}'.format(con.device.clean.device_recovery['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 SubCommandFailure('Reload failed : {}'.format(err)) + + con.disconnect() + con.connect() + + # Issue init commands to disable console logging + exec_commands = con.active.settings.HA_INIT_EXEC_COMMANDS + for exec_command in exec_commands: + con.execute(exec_command, prompt_recovery=self.prompt_recovery) + config_commands = con.active.settings.HA_INIT_CONFIG_COMMANDS + + config_lock_retries_ori = con.settings.CONFIG_LOCK_RETRIES + config_lock_retry_sleep_ori = con.settings.CONFIG_LOCK_RETRY_SLEEP + con.active.settings.CONFIG_LOCK_RETRY_SLEEP = \ + con.active.settings.CONFIG_POST_RELOAD_RETRY_DELAY_SEC + con.active.settings.CONFIG_LOCK_RETRIES = \ + con.active.settings.CONFIG_POST_RELOAD_MAX_RETRIES + + try: + con.configure(config_commands, + target='active', + prompt_recovery=self.prompt_recovery) + except Exception: + raise + finally: + con.settings.CONFIG_LOCK_RETRIES = config_lock_retries_ori + con.settings.CONFIG_LOCK_RETRY_SLEEP = config_lock_retry_sleep_ori + + # Check active and standby RP are ready using show redundancy + con.log.info('Wait for Active and Standby RP to be ready.') + interval = con.settings.RELOAD_POSTCHECK_INTERVAL \ + if hasattr(con.settings, 'RELOAD_POSTCHECK_INTERVAL') else 30 + if utils.is_active_standby_ready(con, timeout=timeout, interval=interval): + con.log.info('Active and Standby RPs are ready.') + else: + con.log.info('Timeout in %s secs. ' + 'Standby RP is not in STANDBY HOT state.' % timeout) + + con.log.info("+++ Reload Completed Successfully +++") + self.log_buffer.seek(0) + reload_output = self.log_buffer.read() + self.log_buffer.truncate() + + con.log.removeHandler(lb) + for subcon in con.subconnections: + subcon.log.removeHandler(lb) + + self.result = True + if return_output: + self.result = ReloadResult(self.result, reload_output) diff --git a/src/unicon/plugins/iosxe/cat9k/service_implementation.py b/src/unicon/plugins/iosxe/cat9k/service_implementation.py index 8bd383ee..018a5347 100644 --- a/src/unicon/plugins/iosxe/cat9k/service_implementation.py +++ b/src/unicon/plugins/iosxe/cat9k/service_implementation.py @@ -1,9 +1,10 @@ import re +from concurrent.futures import ThreadPoolExecutor, as_completed -from unicon.eal.dialogs import Dialog -from unicon.core.errors import SubCommandFailure +from unicon.eal.dialogs import Dialog, Statement +from unicon.core.errors import ConnectionError, SubCommandFailure from unicon.plugins.generic.service_statements import ( reload_statement_list, ha_reload_statement_list) @@ -14,6 +15,7 @@ from ..service_implementation import Reload as XEReload from ..statements import boot_from_rommon_stmt +from .statements import boot_interrupt_stmt class Reload(XEReload): @@ -130,7 +132,7 @@ def pre_service(self, *args, **kwargs): con.spawn, context=self.context) boot_info = con.execute('show boot') - m = re.search(r'Enable Break = (yes|no|0|1)|ENABLE_BREAK variable (= yes|does not exist)', boot_info) + m = re.search(r'Enable Break = (yes|no|0|1)|ENABLE_BREAK variable (= yes|= no|does not exist)', boot_info) if m: break_enabled = m.group() if all(i not in break_enabled for i in ['yes', '1']): @@ -141,27 +143,144 @@ def pre_service(self, *args, **kwargs): class HARommon(Rommon): - """ Brings device to the Rommon prompt and executes commands specified + """Brings device to rommon on HA systems. """ def __init__(self, connection, context, **kwargs): super().__init__(connection, context, **kwargs) def pre_service(self, *args, **kwargs): + """Prepare HA device for rommon entry. + + Steps: + 1) Ensure prompt recovery/connection is ready. + 2) Resolve the active RP handle (fails if active cannot be determined). + 3) Detect state for all consoles. + 4) If active is already in rommon, validate if all consoles are in rommon and return. + 5) Check break enable on active. + 6) Trigger reload from active and break to rommon on each console in parallel. + 7) Validate that all consoles report rommon. + + Raises: + - ConnectionError: when reconnect is not allowed or connection fails. + - SubCommandFailure: when active cannot be resolved, break enable state + cannot be determined, or any console fails to reach rommon. + """ + # Step 1: Ensure prompt recovery/connection is ready. con = self.connection + self.prompt_recovery = con.prompt_recovery + if 'prompt_recovery' in kwargs: + self.prompt_recovery = kwargs.get('prompt_recovery') + if not con.is_connected: + if not con.reconnect: + raise ConnectionError("Connection is not established to device") + con.log.warning('+++ Reconnecting +++') + con.connect() - # call pre_service to reload to rommon - super().pre_service(*args, **kwargs) + # Step 2: Resolve the active RP handle (fails if active cannot be determined). + subconnections = list(con._subconnections.values()) + active_subcon = getattr(con, 'active', None) + if active_subcon is None: + raise SubCommandFailure('Active connection is not set; cannot continue') + + # Step 3: Detect state for all consoles. + for subcon in subconnections: + subcon.state_machine.detect_state(subcon.spawn, context=subcon.context) + + # Step 4: If active is already in rommon, validate if all consoles are in rommon and return. + if active_subcon.state_machine.current_state == 'rommon': + non_rommon = [] + for subcon in subconnections: + if subcon.state_machine.current_state != 'rommon': + non_rommon.append(subcon.alias) + if non_rommon: + raise SubCommandFailure( + 'Active already in rommon but other connections are not: {}'.format( + ', '.join(non_rommon) + ) + ) + con.log.info('All connections already in rommon; skipping reload/break flow') + return + + # Step 5: Check break enable on active. + active_subcon.state_machine.go_to( + 'enable', active_subcon.spawn, context=active_subcon.context + ) + con.log.info('Checking break enable on active %s', active_subcon.alias) + boot_info = active_subcon.execute('show boot') + m = re.search( + r'Enable Break = (yes|no|0|1)|ENABLE_BREAK variable (= yes|= no|does not exist)', + boot_info, + ) + if m: + break_enabled = m.group() + if all(i not in break_enabled for i in ['yes', '1']): + active_subcon.configure('boot enable-break') + else: + raise SubCommandFailure( + f"Could not determine if break is enabled on {active_subcon.alias}, " + "cannot transition to rommon" + ) - # check connection states - subcon1, subcon2 = list(con._subconnections.values()) + # Step 6: Trigger reload from active and break to rommon on each console in parallel. + rommon_timeout = kwargs.get('rommon_timeout', self.timeout) + + # Prepare worker function to reload active subcon if not already in rommon + def active_reload_to_rommon(): + con.log.info('Reloading from active %s', active_subcon.alias) + active_subcon.state_machine.go_to( + 'rommon', + active_subcon.spawn, + context=active_subcon.context, + prompt_recovery=active_subcon.prompt_recovery, + timeout=rommon_timeout, + ) - # Check current state - for subcon in [subcon1, subcon2]: - subcon.sendline() - subcon.state_machine.go_to( - 'any', + # Prepare worker function to break to rommon on standby/other connections + rommon_pattern = active_subcon.state_machine.get_state('rommon').pattern + break_dialog = Dialog([ + boot_interrupt_stmt, + Statement( + pattern=rommon_pattern, + action=None, + loop_continue=False, + continue_timer=False, + trim_buffer=True, + ), + ]) + + def break_to_rommon(subcon): + con.log.info('Waiting for boot interrupt on %s', subcon.alias) + break_dialog.process( subcon.spawn, context=subcon.context, prompt_recovery=subcon.prompt_recovery, - timeout=subcon.connection_timeout, + timeout=rommon_timeout, ) + + tasks = [] + if active_subcon.state_machine.current_state != 'rommon': + tasks.append((active_reload_to_rommon, ())) + for subcon in subconnections: + if subcon is not active_subcon and subcon.state_machine.current_state != 'rommon': + tasks.append((break_to_rommon, (subcon,))) + + con.log.info('Transitioning %d subconnections to rommon in parallel', len(tasks)) + if tasks: + with ThreadPoolExecutor(max_workers=len(tasks)) as executor: + futures = [executor.submit(func, *args) for func, args in tasks] + for future in as_completed(futures): + future.result() + + # Step 7: Validate that all consoles report rommon. + non_rommon = [] + for subcon in subconnections: + subcon.state_machine.detect_state(subcon.spawn, context=subcon.context) + con.log.debug('%s in state: %s', subcon.alias, subcon.state_machine.current_state) + if subcon.state_machine.current_state != 'rommon': + non_rommon.append(subcon.alias) + if non_rommon: + raise SubCommandFailure( + 'Failed to transition the following subconnections to rommon: {}'.format( + ', '.join(non_rommon) + ) + ) \ No newline at end of file diff --git a/src/unicon/plugins/iosxe/ie3k/__init__.py b/src/unicon/plugins/iosxe/ie3k/__init__.py index f15d9549..8a40b9d8 100644 --- a/src/unicon/plugins/iosxe/ie3k/__init__.py +++ b/src/unicon/plugins/iosxe/ie3k/__init__.py @@ -1,13 +1,34 @@ -from unicon.plugins.iosxe import IosXEDualRPConnection, IosXESingleRpConnection +from unicon.plugins.iosxe import ( + IosXEDualRPConnection, IosXESingleRpConnection, + IosXEServiceList, HAIosXEServiceList +) +from unicon.plugins.iosxe.cat9k.service_implementation import ( + Rommon as Cat9kRommon, + HARommon as Cat9kHARommon +) from .settings import IosXEIe3kSettings +class IosXEIe3kServiceList(IosXEServiceList): + def __init__(self): + super().__init__() + self.rommon = Cat9kRommon + + +class IosXEIe3kHAServiceList(HAIosXEServiceList): + def __init__(self): + super().__init__() + self.rommon = Cat9kHARommon + + class IosXEIe3kSingleRpConnection(IosXESingleRpConnection): platform = 'ie3k' + subcommand_list = IosXEIe3kServiceList settings = IosXEIe3kSettings() class IosXEIe3kDualRPConnection(IosXEDualRPConnection): platform = 'ie3k' + subcommand_list = IosXEIe3kHAServiceList settings = IosXEIe3kSettings() diff --git a/src/unicon/plugins/iosxe/ie3k/settings.py b/src/unicon/plugins/iosxe/ie3k/settings.py index dd8c54ec..f358bd21 100644 --- a/src/unicon/plugins/iosxe/ie3k/settings.py +++ b/src/unicon/plugins/iosxe/ie3k/settings.py @@ -6,4 +6,5 @@ class IosXEIe3kSettings(IosXESettings): def __init__(self): super().__init__() - self.BOOT_FILESYSTEM = ["sdflash:", "flash:"] + self.BOOT_FILESYSTEM = ["flash:", "sdflash:"] + self.BOOT_FILE_REGEX = [r'(\S+\.SSA\.bin)', r'(\S+\.SPA\.bin)', r'(\S+\.bin)'] diff --git a/src/unicon/plugins/iosxe/ie9k/__init__.py b/src/unicon/plugins/iosxe/ie9k/__init__.py index 6d655bb8..84bee751 100644 --- a/src/unicon/plugins/iosxe/ie9k/__init__.py +++ b/src/unicon/plugins/iosxe/ie9k/__init__.py @@ -1,13 +1,26 @@ from unicon.plugins.iosxe import IosXEDualRPConnection, IosXESingleRpConnection +from unicon.plugins.iosxe.ie3k import IosXEIe3kServiceList, IosXEIe3kHAServiceList from .settings import IosXEIe9kSettings +class IosXEIe9kServiceList(IosXEIe3kServiceList): + def __init__(self): + super().__init__() + + +class IosxeIe9kHAServiceList(IosXEIe3kHAServiceList): + def __init__(self): + super().__init__() + + class IosXEIe9kSingleRpConnection(IosXESingleRpConnection): platform = 'ie9k' + subcommand_list = IosXEIe9kServiceList settings = IosXEIe9kSettings() class IosXEIe9kDualRPConnection(IosXEDualRPConnection): platform = 'ie9k' + subcommand_list = IosxeIe9kHAServiceList settings = IosXEIe9kSettings() diff --git a/src/unicon/plugins/iosxe/ie9k/settings.py b/src/unicon/plugins/iosxe/ie9k/settings.py index 0cd9f25a..cb580644 100644 --- a/src/unicon/plugins/iosxe/ie9k/settings.py +++ b/src/unicon/plugins/iosxe/ie9k/settings.py @@ -6,4 +6,4 @@ class IosXEIe9kSettings(IosXESettings): def __init__(self): super().__init__() - self.BOOT_FILESYSTEM = ["sdflash:", "flash:"] + self.BOOT_FILESYSTEM = ["flash:", "sdflash:"] diff --git a/src/unicon/plugins/iosxe/patterns.py b/src/unicon/plugins/iosxe/patterns.py index d19b8e98..d62f6dd0 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'^(?!.*?grub)(.*?)(\(unlicensed\))?(wlc|WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(recovery-mode\))?(\(rp-rec-mode\))?(\(standby\))?(-stby)?(-standby)?(\(boot\))?(?\s?$' + r'^(?!.*?grub)(?!.*<\S+>\s*$)(.*?)(\(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|eWLC|Router|RouterRP|Switch|ios|switch|%N)([a-zA-Z0-9-]*)(\(recovery-mode\))?(\(rp-rec-mode\))?(\(standby\))?(-stby)?(-standby)?(\(boot\))?#[\s\x07]*$' self.maintenance_mode_prompt = \ diff --git a/src/unicon/plugins/iosxe/stack/service_implementation.py b/src/unicon/plugins/iosxe/stack/service_implementation.py index f1abdf47..2d7e9892 100644 --- a/src/unicon/plugins/iosxe/stack/service_implementation.py +++ b/src/unicon/plugins/iosxe/stack/service_implementation.py @@ -148,7 +148,25 @@ def call_service(self, command=None, sleep(self.connection.settings.POST_SWITCHOVER_SLEEP) # check all members are ready - conn.state_machine.detect_state(conn.spawn, context=conn.context) + # Check all members are ready + # After switchover, the device continues to reload/boot. We need to wait + # for the device to reach a stable state before attempting to verify + # member readiness. The go_to('any') will handle the boot sequence including + # "Press RETURN to get started" prompts and login sequences. + self.connection.log.info('Waiting for device to reach stable state post-switchover') + try: + # Wait for device to reach any recognizable state (handles booting device) + conn.state_machine.go_to('any', conn.spawn, timeout=timeout, + prompt_recovery=self.prompt_recovery, + context=conn.context) + + # Now transition to enable mode for verification + conn.state_machine.go_to('enable', conn.spawn, timeout=timeout, + prompt_recovery=self.prompt_recovery, + context=conn.context) + except Exception as e: + self.connection.log.warning('Failed to reach enable state post-switchover: %s' % e) + raise interval = self.connection.settings.SWITCHOVER_POSTCHECK_INTERVAL if utils.is_all_member_ready(conn, timeout=timeout, interval=interval): @@ -458,36 +476,57 @@ def __init__(self, connection, context, **kwargs): def pre_service(self, reload_command=None, timeout=None, *args, **kwargs): con = self.connection - sm = self.get_sm() - con = self.connection - sm.go_to('enable', - con.spawn, - context=self.context) - boot_info = con.execute('show boot') - m = re.search(r'Enable Break = (yes|no)', boot_info) - if m: - break_enabled = m.group(1) - if break_enabled == 'no': - con.configure('boot enable-break') - else: - raise SubCommandFailure('Could not determine if break is enabled, cannot transition to rommon') + timeout = timeout or self.timeout + + con.log.info('Checking current state of all subconnections') + rommon_conns = [] + non_rommon_conns = [] + + for subconn in con._subconnections.values(): + state = subconn.state_machine.detect_state(subconn.spawn, subconn.context) + + if state == 'rommon': + rommon_conns.append(subconn) + else: + non_rommon_conns.append(subconn) + + # If ALL are in ROMMON, do nothing and skip synchronization. + if not non_rommon_conns: + con.log.info('Uniform state detected (All ROMMON). Skipping synchronization logic.') + super().pre_service(*args, **kwargs) + return + + reload_conn = non_rommon_conns[0] - if reload_command: - reload_dialog = self.dialog + # Ensure break is enabled so the reload stops at ROMMON + reload_conn.state_machine.go_to('enable', reload_conn.spawn, context=reload_conn.context) + boot_info = reload_conn.execute('show boot') + if 'Enable Break = no' in boot_info or 'ENABLE_BREAK variable does not exist' in boot_info: + reload_conn.configure('boot enable-break') + + # Prepare and send reload + reload_cmd = reload_command if reload_command else 'reload' + reload_dialog = self.dialog + if not reload_command: reload_dialog += Dialog([switch_prompt] + stack_factory_reset_stmt_list) - timeout = timeout or self.timeout - con.sendline(reload_command) - try: - reload_cmd_output = reload_dialog.process(con.spawn, - timeout=timeout, - prompt_recovery=con.prompt_recovery, - context=con.context) - self.result=reload_cmd_output.match_output - self.get_service_result() - except Exception as e: - raise SubCommandFailure('Error during reload', e) from e - sleep(self.connection.settings.STACK_ROMMON_SLEEP) + reload_conn.sendline(reload_cmd) + try: + reload_dialog.process(reload_conn.spawn, timeout=timeout, context=reload_conn.context) + except Exception as e: + con.log.warning(f'Reload command issued, proceeding to sync loop. (Note: {e})') + + con.log.info('Waiting for all consoles to reach rommon state...') + sleep(self.connection.settings.STACK_ROMMON_SLEEP) + + max_wait = 30 + poll_interval = 15 + waited = 0 + while waited < max_wait: + sleep(poll_interval) + waited += poll_interval + + all_rommon = True for subconn in con._subconnections.values(): subconn.sendline() subconn.state_machine.go_to( @@ -495,12 +534,15 @@ def pre_service(self, reload_command=None, timeout=None, *args, **kwargs): subconn.spawn, context=subconn.context, prompt_recovery=subconn.prompt_recovery, - timeout=subconn.settings.STACK_SWITCHOVER_TIMEOUT, ) self.connection.log.debug('{} in state: {}'.format(subconn.alias, subconn.state_machine.current_state)) + con.log.info(f'Sync in progress... ({waited}/{max_wait}s)') + + if not all_rommon: + raise SubCommandFailure('Timeout: Stack failed to synchronize all consoles to ROMMON.') + super().pre_service(*args, **kwargs) - # send boot command for each subconnection for subconn in con._subconnections.values(): subconn.sendline() diff --git a/src/unicon/plugins/iosxe/statements.py b/src/unicon/plugins/iosxe/statements.py index 9f07dfe4..71e326ad 100644 --- a/src/unicon/plugins/iosxe/statements.py +++ b/src/unicon/plugins/iosxe/statements.py @@ -172,18 +172,48 @@ def boot_image(spawn, context, session): filesystems = ["flash:"] # Collect all possible boot images from the filesystems and # attempt to boot each one until successful / max attempts reached. - context['filesystem_images'] = [] - for fs in filesystems: - spawn.buffer = '' - spawn.sendline('dir {}'.format(fs)) - dir_listing = spawn.expect(patterns.rommon_prompt).match_output - boot_file_regex = spawn.settings.BOOT_FILE_REGEX if \ - hasattr(spawn.settings, 'BOOT_FILE_REGEX') else r'(\S+\.bin)' - matches = re.findall(boot_file_regex, dir_listing) - if matches: - context['filesystem_images'].extend( - [fs + image_name for image_name in matches] - ) + boot_file_regex = spawn.settings.BOOT_FILE_REGEX if \ + hasattr(spawn.settings, 'BOOT_FILE_REGEX') else r'(\S+\.bin)' + if isinstance(boot_file_regex, list): + # Group matches into one bucket per BOOT_FILE_REGEX entry. + # Example: + # BOOT_FILE_REGEX = [r'(\S+\.SSA\.bin)', r'(\S+\.SPA\.bin)', r'(\S+\.bin)'] + # ranked_matches[0] collects all .SSA.bin images, + # ranked_matches[1] collects all .SPA.bin images, + # ranked_matches[2] collects the remaining .bin images. + # + # Flattening these buckets later makes the final boot queue + # prefer earlier BOOT_FILE_REGEX entries across all + # filesystems instead of only within each filesystem. + ranked_matches = [[] for _ in boot_file_regex] + for fs in filesystems: + spawn.buffer = '' + spawn.sendline('dir {}'.format(fs)) + dir_listing = spawn.expect(patterns.rommon_prompt).match_output + seen = set() + for rank, pattern in enumerate(boot_file_regex): + image_names = re.findall(pattern, dir_listing) + for image_name in image_names: + full_image_name = fs + image_name + if full_image_name not in seen: + ranked_matches[rank].append(full_image_name) + seen.add(full_image_name) + context['filesystem_images'] = [ + image_name + for rank_bucket in ranked_matches + for image_name in rank_bucket + ] + else: + context['filesystem_images'] = [] + for fs in filesystems: + spawn.buffer = '' + spawn.sendline('dir {}'.format(fs)) + dir_listing = spawn.expect(patterns.rommon_prompt).match_output + matches = re.findall(boot_file_regex, dir_listing) + if matches: + context['filesystem_images'].extend( + [fs + image_name for image_name in matches] + ) # Attempt to boot the first image from the list. if context['filesystem_images']: cmd = "boot {}".format(context['filesystem_images'].pop(0)) diff --git a/src/unicon/plugins/tests/mock/mock_device_iosxe.py b/src/unicon/plugins/tests/mock/mock_device_iosxe.py index 514d3db7..8163c8d7 100644 --- a/src/unicon/plugins/tests/mock/mock_device_iosxe.py +++ b/src/unicon/plugins/tests/mock/mock_device_iosxe.py @@ -193,6 +193,15 @@ def ha_asr1k_enable_reload_to_rommon(self, transport, cmd): self._write('\n'.format(prompt), other_transport) return True + def ha_asrk_active_reload_proceed(self, transport, cmd): + if 'prompt' in self.transport_ports[self.transport_handles[transport]]: + prompt = self.transport_ports[self.transport_handles[transport]]['prompt'] + if cmd == "" and prompt == 'Proceed with reload? [confirm]': + if len(self.transport_ports) > 1: + self.state_change_switchover( + transport, 'ha_asrk_active_reload_boot', + 'ha_asrk_standby_boot') + return True class MockDeviceTcpWrapperIOSXE(MockDeviceTcpWrapper): diff --git a/src/unicon/plugins/tests/mock/mock_device_iosxe_cat9k.py b/src/unicon/plugins/tests/mock/mock_device_iosxe_cat9k.py index 008846b8..700f743d 100644 --- a/src/unicon/plugins/tests/mock/mock_device_iosxe_cat9k.py +++ b/src/unicon/plugins/tests/mock/mock_device_iosxe_cat9k.py @@ -25,7 +25,10 @@ def cat9k_ha_active_enable_reload_proceed(self, transport, cmd): prompt = self.transport_ports[self.transport_handles[transport]]['prompt'] if len(self.transport_ports) > 1 : self.state_change_switchover( - transport, 'cat9k_ha_active_enable_reload', 'cat9k_ha_active_enable') + transport, + 'cat9k_ha_active_enable_reload', + 'cat9k_ha_standby_boot_to_rommon', + ) return True def cat9k_ha_active_config_redundancy_mc(self, transport, cmd): 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 a09ef350..af60576a 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 @@ -1121,20 +1121,14 @@ grub_execute: # Login sequence for login_creds c9k_login7: - prompt: "Username: " - commands: - "ts_user": # First credential from login_creds: [ts, default] - terminal server auth - new_state: c9k_password7_ts - -c9k_password7_ts: prompt: "Password: " commands: - "ts_pass": + "ts_line_pass_7": # Initial terminal server password prompt response: | Password OK - new_state: c9k_password7_ok # Intermediate state after Password OK + new_state: c9k_password7_ok c9k_password7_ok: prompt: "" # Empty prompt - requires 'enter' to proceed @@ -1145,14 +1139,91 @@ c9k_password7_ok: c9k_login7_device: prompt: "Username: " commands: - "admin": # Second credential from login_creds: [ts, default] - device credential + "device_user_7": # Device credential after terminal server hop new_state: c9k_password7_device c9k_password7_device: prompt: "Password: " commands: - "cisco": - new_state: c9k_exec + "device_pass_7": + new_state: c9k_exec_relogin + +c9k_exec_relogin: + prompt: "%N>" + commands: + "term length 0": "" + "term width 0": "" + "show version | include operating mode": "" + "show install summary": "" + "show version": |2 + Cisco IOS XE Software, Version 16.09.02 + Cisco IOS Software [Fuji], Catalyst L3 Switch Software (c9k_IOSXE), Version 16.9.2, RELEASE SOFTWARE (fc4) + Technical Support: http://www.cisco.com/techsupport + Copyright (c) 1986-2018 by Cisco Systems, Inc. + + + ROM: IOS-XE ROMMON + %N uptime is 9 minutes + "enable": + new_state: enable_c9k_relogin + "exit": + new_state: c9k_console_available + +enable_c9k_relogin: + prompt: "%N#" + commands: + "enable": "" + "config term": + new_state: c9k_config_relogin + "term length 0": "" + "term width 0": "" + "show version | include operating mode": "" + "show install summary": "" + "show version": |2 + Cisco IOS XE Software, Version 16.09.02 + Cisco IOS Software [Fuji], Catalyst L3 Switch Software (c9k_IOSXE), Version 16.9.2, RELEASE SOFTWARE (fc4) + Technical Support: http://www.cisco.com/techsupport + Copyright (c) 1986-2018 by Cisco Systems, Inc. + + + ROM: IOS-XE ROMMON + %N uptime is 9 minutes + "exit": + new_state: c9k_console_available + +c9k_config_relogin: + prompt: "%N(config)#" + commands: + "no logging console": "" + "line vty 0 4": + new_state: c9k_config_line_relogin + "line console 0": + new_state: c9k_config_line_relogin + "end": + new_state: enable_c9k_relogin + +c9k_config_line_relogin: + prompt: "%N(config-line)#" + commands: + "exec-timeout 0": "" + "line vty 0 4": "" + "exit": + new_state: c9k_config_relogin + "end": + new_state: enable_c9k_relogin + +c9k_console_available: + prompt: | + FE1 con0 is now available + + + + + + Press RETURN to get started. + commands: + "": + new_state: c9k_login7_device # ================================ # Login sequence for login_creds with fallback testing @@ -1204,4 +1275,3 @@ c9k_password8_fallback: commands: "fallback_pass": new_state: c9k_exec - 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 db5e2e1e..fa402992 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 @@ -1023,6 +1023,8 @@ enable_secret_exec: enable_secret_password: prompt: "Password: " commands: + "Secret12345": + new_state: general_enable "Secret12345!": new_state: general_enable @@ -1557,7 +1559,9 @@ meraki_container_shell: commands: "show version | include operating mode": "" "exit": - BusyBox v1.32.0 (2021-10-12 10:15:40 UTC) built-in shell (ash) + response: | + BusyBox v1.32.0 (2021-10-12 10:15:40 UTC) built-in shell (ash) + new_state: enable_container keys: ctrl-c: new_state: meraki_ctrl_c diff --git a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_asrk_ha_reload.yaml b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_asrk_ha_reload.yaml new file mode 100644 index 00000000..75d9b227 --- /dev/null +++ b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_asrk_ha_reload.yaml @@ -0,0 +1,230 @@ + +ha_asrk_active_disable: + prompt: "%N>" + commands: + "show version | include operating mode": "" + "enable": + new_state: ha_asrk_active_enable + +ha_asrk_active_enable: + prompt: "%N#" + commands: + "term length 0": "" + "term width 0": "" + "show version | include operating mode": "" + "show version": | + Cisco IOS XE Software, Version 17.07.01 + Cisco IOS Software [Bengaluru], ASR1000 Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 17.7.1 + Copyright (c) 1986-2021 by Cisco Systems, Inc. + + ROM: 17.7(2r) + + Router uptime is 3 days, 4 hours, 15 minutes + Uptime for this control processor is 3 days, 4 hours, 17 minutes + System returned to ROM by LocalSoft + System image file is "bootflash:/asr1k.bin" + Last reload reason: LocalSoft + + cisco ASR1009-X (RP3) processor (revision RP3) with 4125167K/24590K bytes of memory. + Processor board ID FXS2230Q31P + Router operating mode: Autonomous + 32768K bytes of non-volatile configuration memory. + 8388608K bytes of physical memory. + 7116799K bytes of eUSB flash at bootflash:. + + Configuration register is 0x2102 + "sh redundancy stat | inc my state": |2 + my state = 13 -ACTIVE + "show redundancy sta | in my": |2 + my state = 13 -ACTIVE + "show redundancy sta | in peer": |2 + peer state = 8 -STANDBY HOT + "show redundancy sta | inc Redundancy State": |2 + Redundancy State = sso + "sh redundancy state": &show_redundancy_state |2 + my state = 13 -ACTIVE + peer state = 8 -STANDBY HOT + Mode = Duplex + Unit = Primary + Unit ID = 48 + + Redundancy Mode (Operational) = sso + Redundancy Mode (Configured) = sso + Redundancy State = sso + Maintenance Mode = Disabled + Manual Swact = enabled + Communications = Up + + client count = 84 + client_notification_TMR = 30000 milliseconds + RF debug mask = 0x0 + "show redundancy": |2 + Redundancy Mode (Operational) = sso + Redundancy Mode (Configured) = sso + Redundancy State = sso + Maintenance Mode = Disabled + Manual Swact = enabled + Communications = Up + + Current Processor Information : + ------------------------------- + Active Location = slot 6 + Current Software state = ACTIVE + Uptime in current state = 3 days, 4 hours + Image Version = Cisco IOS Software [Bengaluru], Version 17.7.1 + BOOT = bootflash:/asr1k.bin,1; + CONFIG_FILE = + Configuration register = 0x2102 + + Peer Processor Information : + ---------------------------- + Standby Location = slot 7 + Current Software state = STANDBY HOT + Uptime in current state = 3 days, 3 hours + Image Version = Cisco IOS Software [Bengaluru], Version 17.7.1 + BOOT = bootflash:/asr1k.bin,1; + CONFIG_FILE = + Configuration register = 0x2102 + "reload": + new_state: ha_asrk_active_reload_confirm + "config term": + new_state: ha_asrk_active_config + +ha_asrk_active_reload_confirm: + prompt: "System configuration has been modified. Save? [yes/no]: " + commands: + "n": + new_state: ha_asrk_active_reload_proceed + "y": + new_state: ha_asrk_active_reload_proceed + +ha_asrk_active_reload_proceed: + prompt: "Proceed with reload? [confirm]" + commands: + "": + new_state: ha_asrk_active_reload_boot + +ha_asrk_active_reload_boot: + preface: |2 + + Sep 20 19:20:19.451: %PMAN-5-EXITACTION: R1/0: pvp: Process manager is exiting: process exit with reload chassis code + + Initializing Hardware ... + + System Bootstrap, Version 17.7(2r), RELEASE SOFTWARE + Copyright (c) 1994-2021 by cisco Systems, Inc. + + Current image running: Boot ROM1 + Last reset cause: LocalSoft + + ASR1000-RP3 platform with 8388608 Kbytes of main memory + + Cisco IOS Software [Bengaluru], ASR1000 Software, Version 17.7.1 + Copyright (c) 1986-2021 by Cisco Systems, Inc. + + cisco ASR1009-X (RP3) processor with 4125167K/24590K bytes of memory. + Processor board ID FXS2230Q31P + + Press RETURN to get started! + prompt: "" + commands: + "": + new_state: ha_asrk_active_disable + +ha_asrk_active_config: + prompt: "%N(config)#" + commands: + "no logging console": "" + "line console 0": "" + "line vty 0 4": "" + "exec-timeout 0": "" + "redundancy": + new_state: ha_asrk_active_config_redundancy + "end": + new_state: ha_asrk_active_enable + +ha_asrk_active_config_redundancy: + prompt: "%N(config-red)#" + commands: + "main-cpu": + new_state: ha_asrk_active_config_redundancy_mc + +ha_asrk_active_config_redundancy_mc: + prompt: "%N(config-red-mc)#" + commands: + "standby console enable": "" + "end": + new_state: ha_asrk_active_enable + +ha_asrk_standby_disable: + prompt: "%N-stby>" + commands: + "show version | include operating mode": "" + "enable": + new_state: ha_asrk_standby_enable + +ha_asrk_standby_disable_locked: + prompt: "%N-stby>" + commands: + "enable": "Standby console disabled" + +ha_asrk_standby_boot: + preface: |2 + + Sep 20 19:20:19.891: %PMAN-5-EXITACTION: R0/0: pvp: Process manager is exiting: process exit with reload fru code + Initializing Hardware ... + + System Bootstrap, Version 17.7(2r), RELEASE SOFTWARE + Copyright (c) 1994-2021 by cisco Systems, Inc. + + Current image running: Boot ROM0 + Last reset cause: LocalSoft + + ASR1000-RP3 platform with 67108864 Kbytes of main memory + + Cisco IOS Software [Bengaluru], ASR1000 Software, Version 17.7.1 + Copyright (c) 1986-2021 by Cisco Systems, Inc. + + cisco ASR1009-X (RP3) processor with 4125167K/24590K bytes of memory. + + Press RETURN to get started! + prompt: "" + commands: + "": + new_state: ha_asrk_standby_disable + +ha_asrk_standby_enable: + prompt: "%N-stby#" + commands: + "term length 0": "" + "term width 0": "" + "show version | include operating mode": "" + "show version": | + Cisco IOS XE Software, Version 17.07.01 + Cisco IOS Software [Bengaluru], ASR1000 Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 17.7.1 + Copyright (c) 1986-2021 by Cisco Systems, Inc. + "sh redundancy stat | inc my state": |2 + my state = 8 -STANDBY HOT + "show redundancy sta | in my": |2 + my state = 8 -STANDBY HOT + "show redundancy sta | in peer": |2 + peer state = 13 -ACTIVE + "show redundancy sta | inc Redundancy State": |2 + Redundancy State = sso + "sh redundancy state": |2 + my state = 8 -STANDBY HOT + peer state = 13 -ACTIVE + Mode = Duplex + Unit = Secondary + Unit ID = 49 + + Redundancy Mode (Operational) = sso + Redundancy Mode (Configured) = sso + Redundancy State = sso + Maintenance Mode = Disabled + Manual Swact = enabled + Communications = Up + + client count = 84 + client_notification_TMR = 30000 milliseconds + RF debug mask = 0x0 diff --git a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_cat9k_ha_reload.yaml b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_cat9k_ha_reload.yaml index e60781be..5b74086c 100644 --- a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_cat9k_ha_reload.yaml +++ b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_cat9k_ha_reload.yaml @@ -191,6 +191,17 @@ cat9k_ha_active_enable_reload: new_state: cat9k_ha_standby_disable_locked +cat9k_ha_standby_boot_to_rommon: + preface: + response: file|mock_data/iosxe/c9k_boot_rommon.txt + timing: + - 0:,0,0.02 + prompt: "" + keys: + ctrl-c: + new_state: cat9k_ha_standby_rommon + + cat9k_ha_active_enable_wrerase_confirm: prompt: "Erasing the nvram filesystem will remove all configuration files! Continue? [confirm]" commands: @@ -204,6 +215,7 @@ cat9k_ha_active_enable_wrerase_confirm: cat9k_ha_active_config: prompt: "%N(config)#" commands: + "config-register 0x0": "" "no logging console": "" "line console 0": "" "line vty 0 4": "" @@ -412,4 +424,3 @@ cat9k_ha_active_install_add_1: FAILED: install_add_activate_commit : Copy bootflash:asr1000rpx86-universalk9.BLD_V16 new_state: cat9k_ha_active_enable - diff --git a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_stack.yaml b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_stack.yaml index 068e150f..a39878e8 100644 --- a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_stack.yaml +++ b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_stack.yaml @@ -262,6 +262,21 @@ stack_enable: 170355 drwx 4096 May 3 2015 15:15:37 +00:00 modules 170357 -rw- 23128995 Jan 4 2015 04:23:42 +00:00 CAT3K_CAA-3-UNIVERSALK9_20150104-042336-UTC.core.gz 170356 -rw- 183910 Jan 4 2015 04:23:42 +00:00 CAT3K_CAA-UNIVERSALK9-042336-UTC.core.gz.coregen.dbg + + + "show boot": | + --------------------------- + Switch 1 + --------------------------- + Current Boot Variables: + BOOT variable does not exist + Boot Variables on next reload: + BOOT variable = flash:packages.conf; + Manual Boot = yes + Enable Break = yes + Boot Mode = DEVICE + iPXE Timeout = 0 + stack_shell_confirm: prompt: "Are you sure you want to continue? [y/n] " commands: @@ -291,6 +306,18 @@ stack_config: new_state: stack_enable "redundancy": new_state: config_stack_redundancy + "show boot": | + --------------------------- + Switch 1 + --------------------------- + Current Boot Variables: + BOOT variable does not exist + Boot Variables on next reload: + BOOT variable = flash:packages.conf; + Manual Boot = yes + Enable Break = yes + Boot Mode = DEVICE + iPXE Timeout = 0 config_stack_redundancy: prompt: "%N(config-red)#" diff --git a/src/unicon/plugins/tests/test_iosxe_patterns.py b/src/unicon/plugins/tests/test_iosxe_patterns.py index 9a645349..dc01169a 100644 --- a/src/unicon/plugins/tests/test_iosxe_patterns.py +++ b/src/unicon/plugins/tests/test_iosxe_patterns.py @@ -7,7 +7,28 @@ import unittest import re +from unicon.plugins.generic.settings import GenericSettings from unicon.plugins.iosxe.patterns import IosXEPatterns +from unicon.eal.backend.spawn import RawSpawn + + +class DummySpawn(RawSpawn): + """Minimal spawn implementation for exercising backend match logic.""" + + def _send(self, command, *args, **kwargs): + return True + + def read_update_buffer(self, size=None): + return False + + def is_readable(self, timeout=0.01): + return False + + def is_writable(self, timeout=0.01): + return True + + def close(self, *args, **kwargs): + return True class TestIosXEDisablePrompt(unittest.TestCase): @@ -17,6 +38,17 @@ def setUp(self): """Set up test patterns.""" self.patterns = IosXEPatterns() self.disable_pattern = self.patterns.disable_prompt + self.settings = GenericSettings() + + def _match_with_backend(self, pattern, buffer): + """Mirror backend prompt matching via RawSpawn.match_buffer().""" + spawn = DummySpawn( + spawn_command='', + settings=self.settings, + match_mode_detect=True, + ) + spawn.buffer = buffer + return spawn.match_buffer(pattern) is not False def test_disable_prompt_matches_valid_prompts(self): """Test that disable_prompt matches valid disable mode prompts.""" @@ -61,6 +93,27 @@ def test_disable_prompt_does_not_match_rommon_prompts(self): self.assertIsNone(match, f"Pattern should NOT match rommon prompt: {prompt}") + def test_disable_prompt_does_not_match_master_key_warning_line(self): + """Test backend prompt matching ignores the master-key warning line.""" + warning_buffer = ( + 'User Access Verification\r\n' + 'Username: lab\r\n' + 'Password: \r\n' + '% WARNING: The master key is not configured, so passwords/secrets ' + 'might not be encrypted.\r\n' + 'Configure the master key by using the following command: ' + '"key config-key password-encrypt ' + ) + + disable_pattern = self.disable_pattern.replace( + '%N', self.settings.DEFAULT_LEARNED_HOSTNAME + ) + self.assertFalse( + self._match_with_backend(disable_pattern, warning_buffer), + "Pattern should NOT match the master-key warning buffer when %N " + "is replaced with DEFAULT_LEARNED_HOSTNAME" + ) + 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 2ef289f2..aeb2dbe4 100644 --- a/src/unicon/plugins/tests/test_plugin_iosxe.py +++ b/src/unicon/plugins/tests/test_plugin_iosxe.py @@ -92,6 +92,146 @@ def test_boot_image_uses_cached_filesystem_images_fifo_order(self): self.assertEqual(context['filesystem_images'], ['flash:emergency.bin']) self.assertEqual(context['boot_prompt_count'], 3) + def test_boot_image_orders_images_with_boot_file_regex_list(self): + spawn = Mock() + spawn.settings = SimpleNamespace( + MAX_BOOT_ATTEMPTS=5, + FIND_BOOT_IMAGE=True, + BOOT_FILESYSTEM=['flash:'], + BOOT_FILE_REGEX=[ + r'(\S+\.SSA\.bin)', + r'(\S+\.SPA\.bin)', + r'(\S+\.bin)', + ], + ) + spawn.expect.return_value = Mock( + match_output='flash: ie35xx.BLD_x.SPA.bin ie35xx.BLD_y.SSA.bin ie35xx.BLD_z.bin' + ) + + context = {} + session = {} + + boot_image(spawn, context, session) + + self.assertEqual( + spawn.sendline.call_args_list, + [ + unittest.mock.call('dir flash:'), + unittest.mock.call('boot flash:ie35xx.BLD_y.SSA.bin'), + ], + ) + self.assertEqual( + context['filesystem_images'], + ['flash:ie35xx.BLD_x.SPA.bin', 'flash:ie35xx.BLD_z.bin'], + ) + + def test_boot_image_deduplicates_images_from_boot_file_regex_list(self): + spawn = Mock() + spawn.settings = SimpleNamespace( + MAX_BOOT_ATTEMPTS=5, + FIND_BOOT_IMAGE=True, + BOOT_FILESYSTEM=['flash:'], + BOOT_FILE_REGEX=[ + r'(\S+\.SSA\.bin)', + r'(\S+\.bin)', + ], + ) + spawn.expect.return_value = Mock( + match_output='flash: image1.SSA.bin image2.bin' + ) + + context = {} + session = {} + + boot_image(spawn, context, session) + + self.assertEqual( + spawn.sendline.call_args_list, + [ + unittest.mock.call('dir flash:'), + unittest.mock.call('boot flash:image1.SSA.bin'), + ], + ) + self.assertEqual(context['filesystem_images'], ['flash:image2.bin']) + + def test_boot_image_orders_filesystem_images_by_boot_file_regex_priority(self): + spawn = Mock() + spawn.settings = SimpleNamespace( + MAX_BOOT_ATTEMPTS=5, + FIND_BOOT_IMAGE=True, + BOOT_FILESYSTEM=['bootflash:', 'flash:'], + BOOT_FILE_REGEX=[ + r'(\S+\.SSA\.bin)', + r'(\S+\.SPA\.bin)', + r'(\S+\.bin)', + ], + ) + spawn.expect.side_effect = [ + Mock(match_output='bootflash: bootflash_spa.SPA.bin bootflash_other.bin'), + Mock(match_output='flash: flash_ssa.SSA.bin flash_other.bin'), + ] + + context = {} + session = {} + + boot_image(spawn, context, session) + + self.assertEqual( + spawn.sendline.call_args_list, + [ + unittest.mock.call('dir bootflash:'), + unittest.mock.call('dir flash:'), + unittest.mock.call('boot flash:flash_ssa.SSA.bin'), + ], + ) + self.assertEqual( + context['filesystem_images'], + [ + 'bootflash:bootflash_spa.SPA.bin', + 'bootflash:bootflash_other.bin', + 'flash:flash_other.bin', + ], + ) + + def test_boot_image_orders_filesystem_images_by_boot_file_regex_priority(self): + spawn = Mock() + spawn.settings = SimpleNamespace( + MAX_BOOT_ATTEMPTS=5, + FIND_BOOT_IMAGE=True, + BOOT_FILESYSTEM=['bootflash:', 'flash:'], + BOOT_FILE_REGEX=[ + r'(\S+\.SSA\.bin)', + r'(\S+\.SPA\.bin)', + r'(\S+\.bin)', + ], + ) + spawn.expect.side_effect = [ + Mock(match_output='bootflash: bootflash_spa.SPA.bin bootflash_other.bin'), + Mock(match_output='flash: flash_ssa.SSA.bin flash_other.bin'), + ] + + context = {} + session = {} + + boot_image(spawn, context, session) + + self.assertEqual( + spawn.sendline.call_args_list, + [ + unittest.mock.call('dir bootflash:'), + unittest.mock.call('dir flash:'), + unittest.mock.call('boot flash:flash_ssa.SSA.bin'), + ], + ) + self.assertEqual( + context['filesystem_images'], + [ + 'bootflash:bootflash_spa.SPA.bin', + 'bootflash:bootflash_other.bin', + 'flash:flash_other.bin', + ], + ) + class TestIosXEPluginConnect(unittest.TestCase): @@ -2078,4 +2218,4 @@ def test_switchto_maintenance(self): c.disconnect() if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/src/unicon/plugins/tests/test_plugin_iosxe_asrk_ha_reload.py b/src/unicon/plugins/tests/test_plugin_iosxe_asrk_ha_reload.py new file mode 100644 index 00000000..d4e98cad --- /dev/null +++ b/src/unicon/plugins/tests/test_plugin_iosxe_asrk_ha_reload.py @@ -0,0 +1,488 @@ +""" +Unittest for IOSXE/ASRK HAReload service implementation. +""" +import unittest +from io import StringIO +from unittest.mock import MagicMock, patch, call + +import unicon +from unicon import Connection +from unicon.eal.dialogs import Dialog +from unicon.core.errors import SubCommandFailure +from unicon.plugins.generic.service_implementation import ReloadResult +from unicon.plugins.iosxe.asrk.service_implementation import HAReload +from unicon.plugins.tests.mock.mock_device_iosxe import ( + MockDeviceTcpWrapperIOSXE, MockDeviceIOSXE) + + +class TestASRKHAReload(unittest.TestCase): + """Test ASRK HAReload service implementation.""" + + def _make_mock_connection(self): + """Return a MagicMock connection wired for ASRK HAReload.""" + con = MagicMock() + con.hostname = 'Router' + con.connection_timeout = 60 + + settings = MagicMock() + settings.ERROR_PATTERN = [] + settings.LOGIN_PROMPT = 'Username:' + settings.PASSWORD_PROMPT = 'Password:' + settings.POST_HA_RELOAD_CONFIG_SYNC_WAIT = 50 + settings.RELOAD_POSTCHECK_INTERVAL = 30 + settings.CONFIG_LOCK_RETRIES = 3 + settings.CONFIG_LOCK_RETRY_SLEEP = 1 + con.settings = settings + + # Active subconnection + conn_active = MagicMock() + conn_active.spawn = MagicMock() + conn_active.context = {'hostname': 'Router'} + conn_active.state_machine = MagicMock() + conn_active.state_machine.current_state = 'enable' + conn_active.settings = MagicMock() + conn_active.settings.HA_INIT_EXEC_COMMANDS = [] + conn_active.settings.HA_INIT_CONFIG_COMMANDS = [] + conn_active.settings.CONFIG_POST_RELOAD_RETRY_DELAY_SEC = 1 + conn_active.settings.CONFIG_POST_RELOAD_MAX_RETRIES = 3 + conn_active.settings.CONFIG_LOCK_RETRIES = 3 + conn_active.settings.CONFIG_LOCK_RETRY_SLEEP = 1 + con.active = conn_active + + # Standby subconnection + conn_standby = MagicMock() + conn_standby.spawn = MagicMock() + conn_standby.context = {'hostname': 'Router'} + conn_standby.state_machine = MagicMock() + conn_standby.state_machine.current_state = 'enable' + conn_standby.settings = settings + con.standby = conn_standby + + con.subconnections = [conn_active, conn_standby] + con.connection_provider = MagicMock() + con.connection_provider.get_connection_dialog.return_value = Dialog([]) + + return con + + def _make_service(self, con): + """Create an HAReload service instance with mocked internals.""" + svc = HAReload(con, con.active.context) + svc.prompt_recovery = False + svc.timeout = 60 + svc.dialog = Dialog([]) + svc.log_buffer = StringIO() + svc.error_pattern = [] + svc.context = {} + svc.get_sm = MagicMock() + svc.get_sm.return_value.current_state = 'enable' + svc.get_service_result = MagicMock() + return svc + + @patch('unicon.plugins.iosxe.asrk.service_implementation.utils') + @patch('unicon.plugins.iosxe.asrk.service_implementation.custom_auth_statements', + return_value=[]) + @patch('unicon.eal.dialogs.Dialog.process', + return_value=MagicMock(match_output='reload output')) + def test_reload_complete_flow(self, mock_process, mock_auth, mock_utils): + """Test complete HA reload flow succeeds.""" + mock_utils.is_active_standby_ready.return_value = True + con = self._make_mock_connection() + svc = self._make_service(con) + + svc.call_service(reload_command='reload') + + # Reload command sent on active spawn + con.active.spawn.sendline.assert_any_call('reload') + + # ThreadPoolExecutor should have called dialog.process twice + # (once for active, once for standby) + self.assertEqual(mock_process.call_count, 2) + + # Active RP brought to 'any' state during parallel processing + active_go_to_calls = con.active.state_machine.go_to.call_args_list + active_states = [c[0][0] for c in active_go_to_calls] + self.assertIn('any', active_states) + + # disconnect + connect called to re-establish connections + con.disconnect.assert_called_once() + con.connect.assert_called_once() + + # is_active_standby_ready called + mock_utils.is_active_standby_ready.assert_called_once() + + self.assertTrue(svc.result) + + @patch('unicon.plugins.iosxe.asrk.service_implementation.utils') + @patch('unicon.plugins.iosxe.asrk.service_implementation.custom_auth_statements', + return_value=[]) + @patch('unicon.eal.dialogs.Dialog.process', + return_value=MagicMock(match_output='reload output')) + def test_reload_with_return_output(self, mock_process, mock_auth, + mock_utils): + """Test reload returns ReloadResult when return_output=True.""" + mock_utils.is_active_standby_ready.return_value = True + con = self._make_mock_connection() + svc = self._make_service(con) + + svc.call_service(reload_command='reload', return_output=True) + + self.assertIsInstance(svc.result, ReloadResult) + + @patch('unicon.plugins.iosxe.asrk.service_implementation.utils') + @patch('unicon.plugins.iosxe.asrk.service_implementation.custom_auth_statements', + return_value=[]) + @patch('unicon.eal.dialogs.Dialog.process', + return_value=MagicMock(match_output='reload output')) + def test_reload_standby_not_ready(self, mock_process, mock_auth, + mock_utils): + """Test reload completes even when standby does not reach STANDBY HOT.""" + mock_utils.is_active_standby_ready.return_value = False + con = self._make_mock_connection() + svc = self._make_service(con) + + svc.call_service(reload_command='reload') + + # Should still succeed; standby not ready is a warning, not failure + self.assertTrue(svc.result) + mock_utils.is_active_standby_ready.assert_called_once() + + @patch('unicon.plugins.iosxe.asrk.service_implementation.utils') + @patch('unicon.plugins.iosxe.asrk.service_implementation.custom_auth_statements', + return_value=[]) + @patch('unicon.eal.dialogs.Dialog.process') + def test_reload_active_exception_raises(self, mock_process, mock_auth, + mock_utils): + """Test that exception in active RP processing is raised.""" + mock_process.side_effect = Exception('Active RP dialog failed') + con = self._make_mock_connection() + svc = self._make_service(con) + + with self.assertRaises(SubCommandFailure) as ctx: + svc.call_service(reload_command='reload') + + self.assertIn('Reload failed', str(ctx.exception)) + + @patch('unicon.plugins.iosxe.asrk.service_implementation.utils') + @patch('unicon.plugins.iosxe.asrk.service_implementation.custom_auth_statements', + return_value=[]) + @patch('unicon.eal.dialogs.Dialog.process', + return_value=MagicMock(match_output='reload output')) + def test_reload_uses_command_parameter(self, mock_process, mock_auth, + mock_utils): + """Test that 'command' parameter is used as reload command.""" + mock_utils.is_active_standby_ready.return_value = True + con = self._make_mock_connection() + svc = self._make_service(con) + + svc.call_service(command='reload force') + + con.active.spawn.sendline.assert_any_call('reload force') + + @patch('unicon.plugins.iosxe.asrk.service_implementation.utils') + @patch('unicon.plugins.iosxe.asrk.service_implementation.custom_auth_statements', + return_value=[]) + @patch('unicon.eal.dialogs.Dialog.process', + return_value=MagicMock(match_output='reload output')) + def test_reload_with_reload_creds(self, mock_process, mock_auth, + mock_utils): + """Test reload with custom reload credentials.""" + mock_utils.is_active_standby_ready.return_value = True + con = self._make_mock_connection() + svc = self._make_service(con) + + svc.call_service(reload_command='reload', + reload_creds=['admin', 'password']) + + # Verify call succeeds with reload_creds + self.assertTrue(svc.result) + + @patch('unicon.plugins.iosxe.asrk.service_implementation.utils') + @patch('unicon.plugins.iosxe.asrk.service_implementation.custom_auth_statements', + return_value=[]) + @patch('unicon.eal.dialogs.Dialog.process', + return_value=MagicMock(match_output='reload output')) + def test_reload_error_pattern_validation(self, mock_process, mock_auth, + mock_utils): + """Test that error_pattern must be a list.""" + mock_utils.is_active_standby_ready.return_value = True + con = self._make_mock_connection() + svc = self._make_service(con) + + with self.assertRaises(ValueError): + svc.call_service(reload_command='reload', + error_pattern='not a list') + + @patch('unicon.plugins.iosxe.asrk.service_implementation.utils') + @patch('unicon.plugins.iosxe.asrk.service_implementation.custom_auth_statements', + return_value=[]) + @patch('unicon.eal.dialogs.Dialog.process', + return_value=MagicMock(match_output='reload output')) + def test_reload_append_error_pattern(self, mock_process, mock_auth, + mock_utils): + """Test that append_error_pattern extends error patterns.""" + mock_utils.is_active_standby_ready.return_value = True + con = self._make_mock_connection() + svc = self._make_service(con) + + svc.call_service(reload_command='reload', + append_error_pattern=['extra_error']) + + self.assertIn('extra_error', svc.error_pattern) + + @patch('unicon.plugins.iosxe.asrk.service_implementation.utils') + @patch('unicon.plugins.iosxe.asrk.service_implementation.custom_auth_statements', + return_value=[]) + @patch('unicon.eal.dialogs.Dialog.process', + return_value=MagicMock(match_output='reload output')) + def test_reload_standby_rommon_transition(self, mock_process, mock_auth, + mock_utils): + """Test standby in rommon is transitioned to disable state.""" + mock_utils.is_active_standby_ready.return_value = True + con = self._make_mock_connection() + con.standby.state_machine.current_state = 'rommon' + svc = self._make_service(con) + + svc.call_service(reload_command='reload') + + # Standby should have go_to('disable') called for rommon recovery + standby_go_to_calls = con.standby.state_machine.go_to.call_args_list + standby_states = [c[0][0] for c in standby_go_to_calls] + self.assertIn('disable', standby_states) + + @patch('unicon.plugins.iosxe.asrk.service_implementation.utils') + @patch('unicon.plugins.iosxe.asrk.service_implementation.custom_auth_statements', + return_value=[]) + @patch('unicon.eal.dialogs.Dialog.process', + return_value=MagicMock(match_output='reload output')) + def test_reload_config_lock_settings_restored(self, mock_process, + mock_auth, mock_utils): + """Test CONFIG_LOCK settings are restored after configure.""" + mock_utils.is_active_standby_ready.return_value = True + con = self._make_mock_connection() + svc = self._make_service(con) + + original_retries = con.settings.CONFIG_LOCK_RETRIES + original_sleep = con.settings.CONFIG_LOCK_RETRY_SLEEP + + svc.call_service(reload_command='reload') + + self.assertEqual(con.settings.CONFIG_LOCK_RETRIES, original_retries) + self.assertEqual(con.settings.CONFIG_LOCK_RETRY_SLEEP, original_sleep) + + @patch('unicon.plugins.iosxe.asrk.service_implementation.utils') + @patch('unicon.plugins.iosxe.asrk.service_implementation.custom_auth_statements', + return_value=[]) + @patch('unicon.eal.dialogs.Dialog.process', + return_value=MagicMock(match_output='reload output')) + def test_reload_golden_image_recovery(self, mock_process, mock_auth, + mock_utils): + """Test golden image recovery on reload failure.""" + # Make standby go_to('any') fail to trigger exception path + con = self._make_mock_connection() + con.standby.state_machine.go_to.side_effect = Exception('standby failed') + con.settings.POST_HA_RELOAD_CONFIG_SYNC_WAIT = 0 + + # Set up golden image recovery + con.device = MagicMock() + con.device.clean.device_recovery = { + 'golden_image': 'bootflash:/golden.bin' + } + + svc = self._make_service(con) + + with self.assertRaises(SubCommandFailure): + svc.call_service(reload_command='reload') + + con.device.api.device_recovery_boot.assert_called_once_with( + golden_image='bootflash:/golden.bin') + + @patch('unicon.plugins.iosxe.asrk.service_implementation.utils') + @patch('unicon.plugins.iosxe.asrk.service_implementation.custom_auth_statements', + return_value=[]) + @patch('unicon.eal.dialogs.Dialog.process', + return_value=MagicMock(match_output='reload output')) + def test_reload_log_handlers_cleaned_up(self, mock_process, mock_auth, + mock_utils): + """Test log handlers are added then removed.""" + mock_utils.is_active_standby_ready.return_value = True + con = self._make_mock_connection() + svc = self._make_service(con) + + svc.call_service(reload_command='reload') + + # Log handlers should be added and removed + con.log.addHandler.assert_called() + con.log.removeHandler.assert_called() + for subcon in con.subconnections: + subcon.log.addHandler.assert_called() + subcon.log.removeHandler.assert_called() + + @patch('unicon.plugins.iosxe.asrk.service_implementation.utils') + @patch('unicon.plugins.iosxe.asrk.service_implementation.custom_auth_statements', + return_value=[]) + @patch('unicon.eal.dialogs.Dialog.process', + return_value=MagicMock(match_output='reload output')) + def test_reload_from_rommon_sets_boot_cmd(self, mock_process, mock_auth, + mock_utils): + """Test that boot_cmd is set when in rommon state.""" + mock_utils.is_active_standby_ready.return_value = True + con = self._make_mock_connection() + svc = self._make_service(con) + svc.get_sm.return_value.current_state = 'rommon' + + svc.call_service(reload_command='boot bootflash:/image.bin') + + self.assertEqual(con.active.context['boot_cmd'], + 'boot bootflash:/image.bin') + + +class TestGenericUtilsIsActiveStandbyReady(unittest.TestCase): + """Test GenericUtils.is_active_standby_ready method.""" + + def setUp(self): + from unicon.plugins.generic.utils import GenericUtils + self.utils = GenericUtils() + + def _make_mock_connection(self): + con = MagicMock() + con.log = MagicMock() + return con + + @patch('unicon.plugins.generic.utils.time') + def test_returns_true_when_both_ready(self, mock_time): + """Test returns True when active=ACTIVE and standby=STANDBY HOT.""" + mock_time.time.side_effect = [0, 1] # start, first check + con = self._make_mock_connection() + con.execute.return_value = """ +Redundancy Mode (Operational) = sso + +Current Processor Information : +------------------------------- +Active Location = slot 6 +Current Software state = ACTIVE +Uptime in current state = 3 days, 4 hours + +Peer Processor Information : +---------------------------- +Standby Location = slot 7 +Current Software state = STANDBY HOT +Uptime in current state = 3 days, 4 hours +""" + result = self.utils.is_active_standby_ready( + con, timeout=120, interval=30) + self.assertTrue(result) + + @patch('unicon.plugins.generic.utils.time') + def test_returns_false_on_timeout(self, mock_time): + """Test returns False when standby never reaches STANDBY HOT.""" + # Simulate time passing beyond timeout + mock_time.time.side_effect = [0, 10, 50, 130] + mock_time.sleep = MagicMock() + con = self._make_mock_connection() + con.execute.return_value = """ +Current Processor Information : +------------------------------- +Current Software state = ACTIVE + +Peer Processor Information : +---------------------------- +Current Software state = DISABLED +""" + result = self.utils.is_active_standby_ready( + con, timeout=120, interval=30) + self.assertFalse(result) + + @patch('unicon.plugins.generic.utils.time') + def test_retries_on_execute_exception(self, mock_time): + """Test retries when show redundancy fails.""" + mock_time.time.side_effect = [0, 10, 50, 130] + mock_time.sleep = MagicMock() + con = self._make_mock_connection() + con.execute.side_effect = Exception('Connection error') + + result = self.utils.is_active_standby_ready( + con, timeout=120, interval=30) + self.assertFalse(result) + con.log.error.assert_called() + + @patch('unicon.plugins.generic.utils.time') + def test_handles_missing_peer_section(self, mock_time): + """Test handles output with no Peer Processor section.""" + mock_time.time.side_effect = [0, 10, 130] + mock_time.sleep = MagicMock() + con = self._make_mock_connection() + con.execute.return_value = """ +Current Processor Information : +------------------------------- +Current Software state = ACTIVE +""" + result = self.utils.is_active_standby_ready( + con, timeout=120, interval=30) + self.assertFalse(result) + + +class NoInterChangeConsoleMockDeviceIOSXEASRK(MockDeviceIOSXE): + """Mock device that handles ASRK HA reload with dual-RP switchover.""" + + def ha_asrk_active_reload_proceed(self, transport, cmd): + if 'prompt' in self.transport_ports[self.transport_handles[transport]]: + prompt = self.transport_ports[self.transport_handles[transport]]['prompt'] + if cmd == "" and prompt == 'Proceed with reload? [confirm]': + if len(self.transport_ports) > 1: + self.state_change_switchover( + transport, 'ha_asrk_active_reload_boot', + 'ha_asrk_standby_boot') + return True + + +class TestASRKHAReloadMockDevice(unittest.TestCase): + """Test ASRK HAReload using mock device with TCP wrapper.""" + + @classmethod + def setUpClass(cls): + cls.md = MockDeviceTcpWrapperIOSXE( + hostname='Router', + port=0, + state='ha_asrk_active_disable,ha_asrk_standby_disable') + cls.md.mockdevice = NoInterChangeConsoleMockDeviceIOSXEASRK( + state='ha_asrk_active_disable,ha_asrk_standby_disable', + hostname='Router') + cls.md.start() + + cls.con = Connection( + hostname='Router', + start=[ + 'telnet 127.0.0.1 {}'.format(cls.md.ports[0]), + 'telnet 127.0.0.1 {}'.format(cls.md.ports[1]), + ], + os='iosxe', + platform='asrk', + credentials=dict( + default=dict(username='admin', password='lab'), + ), + settings=dict( + POST_DISCONNECT_WAIT_SEC=0, + GRACEFUL_DISCONNECT_WAIT_SEC=0.2, + ), + log_buffer=True, + ) + cls.con.connect() + + @classmethod + @patch.object(unicon.settings.Settings, 'POST_DISCONNECT_WAIT_SEC', 0) + @patch.object(unicon.settings.Settings, 'GRACEFUL_DISCONNECT_WAIT_SEC', 0.2) + def tearDownClass(cls): + cls.con.disconnect() + cls.md.stop() + + def test_asrk_ha_reload(self): + """Test ASRK HA reload with mock device completes successfully.""" + self.con.settings.POST_HA_RELOAD_CONFIG_SYNC_WAIT = 0 + self.con.settings.POST_RELOAD_WAIT = 1 + result = self.con.reload(reload_command='reload') + self.assertTrue(result) + self.assertEqual(self.con.active.state_machine.current_state, 'enable') + + +if __name__ == '__main__': + unittest.main() diff --git a/src/unicon/plugins/tests/test_plugin_iosxe_cat9k.py b/src/unicon/plugins/tests/test_plugin_iosxe_cat9k.py index 62c09ad7..32242a15 100644 --- a/src/unicon/plugins/tests/test_plugin_iosxe_cat9k.py +++ b/src/unicon/plugins/tests/test_plugin_iosxe_cat9k.py @@ -198,11 +198,11 @@ def test_connect_login_creds(self): type: cat9k credentials: default: - username: admin - password: cisco + username: device_user_7 + password: device_pass_7 ts: - username: ts_user - password: ts_pass + username: ts_line_user_7 + password: ts_line_pass_7 connections: defaults: class: unicon.Unicon @@ -211,6 +211,9 @@ def test_connect_login_creds(self): ip: 127.0.0.1 port: {} login_creds: [ts, default] + arguments: + settings: + SENDLINE_AFTER_CRED: ts """.format(md.ports[0]) tb = loader.load(testbed) @@ -219,13 +222,60 @@ def test_connect_login_creds(self): device.connect() self.assertEqual(device.state_machine.current_state, 'enable') self.assertIn('current_credentials', device.credentials) - self.assertEqual(device.credentials['current_credentials']['username'], 'admin') + self.assertEqual(device.credentials['current_credentials']['username'], 'device_user_7') # Should match the 'default' credential, not 'ts' self.assertEqual(device.credentials['current_credentials'], device.credentials['default']) finally: device.disconnect() md.stop() + def test_logout_relogin_uses_device_credential(self): + """ + Test that relogin after logout reuses the device credential, not the + terminal server credential, for console login_creds flows. + """ + md = MockDeviceTcpWrapperIOSXE(port=0, state='c9k_login7', hostname='switch') + md.start() + + testbed = """ + devices: + R1: + os: iosxe + type: cat9k + credentials: + default: + username: device_user_7 + password: device_pass_7 + ts: + username: ts_line_user_7 + password: ts_line_pass_7 + connections: + defaults: + class: unicon.Unicon + a: + protocol: telnet + ip: 127.0.0.1 + port: {} + login_creds: [ts, default] + arguments: + settings: + SENDLINE_AFTER_CRED: ts + """.format(md.ports[0]) + + tb = loader.load(testbed) + device = tb.devices.R1 + try: + device.connect() + device.logout() + + device.connection_provider.connect() + + self.assertEqual(device.state_machine.current_state, 'enable') + self.assertEqual(device.credentials['current_credentials'], device.credentials['default']) + finally: + device.disconnect() + md.stop() + def test_connect_login_creds_with_fallback(self): """ Test login credentials with fallback mechanism for IOSXE Cat9k device connection. diff --git a/src/unicon/plugins/tests/test_plugin_iosxe_stack.py b/src/unicon/plugins/tests/test_plugin_iosxe_stack.py index 594163fe..b1cbcefc 100644 --- a/src/unicon/plugins/tests/test_plugin_iosxe_stack.py +++ b/src/unicon/plugins/tests/test_plugin_iosxe_stack.py @@ -217,6 +217,15 @@ def test_switchover_context(self): self.c.active.context.state = 'rommon' self.c.switchover() + def test_switchover_with_extended_timeout(self): + """ + Test switchover with extended timeout to handle device boot sequence. + """ + # Reset state to ensure clean test + self.c.active.context.state = None + + # Perform switchover with extended timeout + self.c.switchover(timeout=1500) class TestIosXEStackReload(unittest.TestCase): @@ -424,5 +433,56 @@ def test_get_redundancy_details(self): }, rd) +class TestIosXEStackRommon(unittest.TestCase): + + def test_stack_rommon_mixed_states(self): + """Test StackRommon pre_service with mixed states (rommon and enable)""" + md = MockDeviceTcpWrapperIOSXE(hostname='Router', port=0, + state='stack_enable,stack_rommon,stack_enable,stack_enable,stack_rommon', + stack=True) + md.start() + try: + d = Connection(hostname='Router', + start=['telnet 127.0.0.1 ' + str(i) for i in md.ports[:]], + os='iosxe', + chassis_type='stack', + username='cisco', + tacacs_password='cisco', + enable_password='cisco', + log_buffer=True) + d.settings.STACK_ROMMON_SLEEP = 1 + d.settings.STACK_BOOT_TIMEOUT = 200 + d.connect() + + # Mock the log to capture messages + with patch.object(d.log, 'info') as mock_log_info, \ + patch.object(d.log, 'warning') as mock_log_warning: + + # Execute rommon service which should trigger mixed state handling + try: + d.rommon('dir flash:', timeout=20) + except Exception: + # May fail in mock environment but we're testing the pre_service logic + pass + + # Verify that mixed state detection was logged + log_messages = [call[0][0] for call in mock_log_info.call_args_list] + + # Check for key log messages that indicate mixed state handling + self.assertTrue( + any('Waiting for all consoles to reach rommon state' in str(msg) for msg in log_messages), + "Expected 'Waiting for all consoles to reach rommon state' log message" + ) + + self.assertTrue( + any('Sync in progress' in str(msg) for msg in log_messages), + "Expected 'Sync in progress' log message indicating synchronization" + ) + + d.disconnect() + finally: + md.stop() + + if __name__ == "__main__": unittest.main() diff --git a/src/unicon/plugins/tests/test_utils.py b/src/unicon/plugins/tests/test_utils.py index d4622a8b..02adb5eb 100644 --- a/src/unicon/plugins/tests/test_utils.py +++ b/src/unicon/plugins/tests/test_utils.py @@ -4,17 +4,18 @@ from pyats.topology.loader import load from unittest.mock import MagicMock, patch from unicon.plugins.utils import AbstractTokenDiscovery +from unicon.plugins.utils import _get_current_credentials +from unicon.plugins.utils import get_current_credential from unicon.plugins.utils import load_token_csv_file -from unicon.plugins.tests.mock.mock_device_generic import ( - MockDeviceTcpWrapperGeneric -) +from unicon.plugins.tests.mock.mock_device_generic import MockDeviceTcpWrapperGeneric +from unicon.core.errors import CredentialsExhaustedError from unicon.core.errors import LearnTokenError class TestAbstractTokenDiscoveryConnection(unittest.TestCase): - """ Run unit testing on AbstractTokenDiscovery - Test that connections work, tokens get discovered, and connections get - redirected to corresponding plugins + """Run unit testing on AbstractTokenDiscovery + Test that connections work, tokens get discovered, and connections get + redirected to corresponding plugins """ def setUp(self) -> None: @@ -43,20 +44,21 @@ def setUp(self) -> None: def test_asa_learn_tokens_from_show_version(self): # Set up device to use correct mock_device data - self.dev.connections.cli.command = \ + self.dev.connections.cli.command = ( "mock_device_cli --os generic --state asa_username --hostname ASA" + ) self.dev.platform = "iamu571_overwriteme" - self.dev.connections.cli['arguments'] = {} - self.dev.connections.cli['arguments']['learn_tokens'] = True - self.dev.connections.cli['arguments']['learn_hostname'] = True - self.dev.connections.cli['arguments']['overwrite_testbed_tokens'] = True + self.dev.connections.cli["arguments"] = {} + self.dev.connections.cli["arguments"]["learn_tokens"] = True + self.dev.connections.cli["arguments"]["learn_hostname"] = True + self.dev.connections.cli["arguments"]["overwrite_testbed_tokens"] = True # Test connection succeeds and tokens learned try: self.dev.connect() - self.assertEqual(self.dev.os, 'asa') - self.assertEqual(self.dev.version, '8.4.1') - self.assertEqual(self.dev.platform, 'asa5520') + self.assertEqual(self.dev.os, "asa") + self.assertEqual(self.dev.version, "8.4.1") + self.assertEqual(self.dev.platform, "asa5520") finally: self.dev.disconnect() @@ -64,24 +66,23 @@ def test_asa_learn_tokens_from_show_version(self): with open(self.dev.logfile) as f: log_contents = f.read() self.assertRegex( - log_contents, - r'\+\+\+ Unicon plugin asa( \(unicon\.plugins\.asa\))? \+\+\+' + log_contents, r"\+\+\+ Unicon plugin asa( \(unicon\.plugins\.asa\))? \+\+\+" ) - def test_ios_learn_tokens_from_show_version(self): # Set up device to use correct mock_device data - self.dev.connections.cli.command = \ + self.dev.connections.cli.command = ( "mock_device_cli --os generic --state ios_login" + ) # Test connection succeeds and tokens learned try: self.dev.connect(learn_tokens=True, learn_hostname=True) - self.assertEqual(self.dev.state_machine.current_state, 'enable') - self.assertEqual(self.dev.os, 'ios') - self.assertEqual(self.dev.version, '15') - self.assertEqual(self.dev.platform, 'c7200p') - self.assertEqual(self.dev.pid, '7206VXR') + self.assertEqual(self.dev.state_machine.current_state, "enable") + self.assertEqual(self.dev.os, "ios") + self.assertEqual(self.dev.version, "15") + self.assertEqual(self.dev.platform, "c7200p") + self.assertEqual(self.dev.pid, "7206VXR") finally: self.dev.disconnect() @@ -89,25 +90,25 @@ def test_ios_learn_tokens_from_show_version(self): with open(self.dev.logfile) as f: log_contents = f.read() self.assertRegex( - log_contents, - r'\+\+\+ Unicon plugin ios( \(unicon\.plugins\.ios\))? \+\+\+' + log_contents, r"\+\+\+ Unicon plugin ios( \(unicon\.plugins\.ios\))? \+\+\+" ) # Test that finding a pid from show version that exists in refernce file, # is enough to get all tokens. 'show inventory' not called def test_iosxe_learn_tokens_from_show_version_pid_number(self): # Set up device to use correct mock_device data - self.dev.connections.cli.command = \ + self.dev.connections.cli.command = ( "mock_device_cli --os generic --state iosxe_login" + ) # Test connection succeeds and tokens learned try: self.dev.connect(learn_tokens=True, learn_hostname=True) - self.assertEqual(self.dev.os, 'iosxe') - self.assertEqual(self.dev.version, '15.2') - self.assertEqual(self.dev.platform, 'asr1k') - self.assertEqual(self.dev.model, 'asr1006') - self.assertEqual(self.dev.pid, 'ASR1006') + self.assertEqual(self.dev.os, "iosxe") + self.assertEqual(self.dev.version, "15.2") + self.assertEqual(self.dev.platform, "asr1k") + self.assertEqual(self.dev.model, "asr1006") + self.assertEqual(self.dev.pid, "ASR1006") finally: self.dev.disconnect() @@ -116,37 +117,40 @@ def test_iosxe_learn_tokens_from_show_version_pid_number(self): log_contents = f.read() self.assertRegex( log_contents, - r'\+\+\+ Unicon plugin iosxe( \(unicon\.(internal\.)?plugins\.iosxe\))? \+\+\+' + r"\+\+\+ Unicon plugin iosxe( \(unicon\.(internal\.)?plugins\.iosxe\))? \+\+\+", ) + # test for controller mode for sdwan def test_iosxe_learn_tokens_from_show_version_sdwan(self): # Set up device to use correct mock_device data - self.dev.connections.cli.command = \ + self.dev.connections.cli.command = ( "mock_device_cli --os generic --state iosxe_login3" + ) # Test connection succeeds and tokens learned try: self.dev.connect(learn_tokens=True) - self.assertEqual(self.dev.os, 'iosxe') - self.assertEqual(self.dev.version, '17.14') - self.assertEqual(self.dev.platform, 'sdwan') - self.assertEqual(self.dev.model, 'c5000') - self.assertEqual(self.dev.pid, 'WS-C5002') + self.assertEqual(self.dev.os, "iosxe") + self.assertEqual(self.dev.version, "17.14") + self.assertEqual(self.dev.platform, "sdwan") + self.assertEqual(self.dev.model, "c5000") + self.assertEqual(self.dev.pid, "WS-C5002") finally: self.dev.disconnect() def test_iosxr_learn_tokens_from_show_version(self): # Set up device to use correct mock_device data - self.dev.connections.cli.command = \ + self.dev.connections.cli.command = ( "mock_device_cli --os generic --state iosxr_login" + ) # Test connection succeeds and tokens learned try: self.dev.connect(learn_tokens=True, learn_hostname=True) - self.assertEqual(self.dev.os, 'iosxr') - self.assertEqual(self.dev.os_flavor, 'lnt') - self.assertEqual(self.dev.version, '5.2.3.12i') - self.assertEqual(self.dev.platform, 'iosxrv') + self.assertEqual(self.dev.os, "iosxr") + self.assertEqual(self.dev.os_flavor, "lnt") + self.assertEqual(self.dev.version, "5.2.3.12i") + self.assertEqual(self.dev.platform, "iosxrv") finally: self.dev.disconnect() @@ -155,23 +159,24 @@ def test_iosxr_learn_tokens_from_show_version(self): log_contents = f.read() self.assertRegex( log_contents, - r'\+\+\+ Unicon plugin iosxr/iosxrv( \(unicon\.plugins\.iosxr\.iosxrv\))? \+\+\+' + r"\+\+\+ Unicon plugin iosxr/iosxrv( \(unicon\.plugins\.iosxr\.iosxrv\))? \+\+\+", ) def test_nxos_learn_tokens_from_show_version(self): # Set up device to use correct mock_device data - self.dev.connections.cli.command = \ + self.dev.connections.cli.command = ( "mock_device_cli --os generic --state nxos_login" - self.dev.connections.cli.settings['LEARN_DEVICE_TOKENS'] = True + ) + self.dev.connections.cli.settings["LEARN_DEVICE_TOKENS"] = True # Test connection succeeds and tokens learned try: self.dev.connect(learn_hostname=True) - self.assertEqual(self.dev.os, 'nxos') - self.assertEqual(self.dev.version, '7.3.5n1.1') - self.assertEqual(self.dev.platform, 'n5k') - self.assertEqual(self.dev.model, 'n5500') - self.assertEqual(self.dev.pid, 'N5K-C5548P') + self.assertEqual(self.dev.os, "nxos") + self.assertEqual(self.dev.version, "7.3.5n1.1") + self.assertEqual(self.dev.platform, "n5k") + self.assertEqual(self.dev.model, "n5500") + self.assertEqual(self.dev.pid, "N5K-C5548P") finally: self.dev.disconnect() @@ -180,22 +185,23 @@ def test_nxos_learn_tokens_from_show_version(self): log_contents = f.read() self.assertRegex( log_contents, - r'\+\+\+ Unicon plugin nxos/n5k( \(unicon\.plugins\.nxos\.n5k\))? \+\+\+' + r"\+\+\+ Unicon plugin nxos/n5k( \(unicon\.plugins\.nxos\.n5k\))? \+\+\+", ) def test_learn_tokens_with_show_inventory(self): # Set up device to use correct mock_device data - self.dev.connections.cli.command = \ + self.dev.connections.cli.command = ( "mock_device_cli --os generic --state iosxe_login2" + ) # Test connection succeeds and tokens learned try: self.dev.connect(learn_tokens=True, learn_hostname=True) - self.assertEqual(self.dev.os, 'iosxe') - self.assertEqual(self.dev.version, '15.2') - self.assertEqual(self.dev.platform, 'cat5k') - self.assertEqual(self.dev.model, 'c5000') - self.assertEqual(self.dev.pid, 'WS-C5002') + self.assertEqual(self.dev.os, "iosxe") + self.assertEqual(self.dev.version, "15.2") + self.assertEqual(self.dev.platform, "cat5k") + self.assertEqual(self.dev.model, "c5000") + self.assertEqual(self.dev.pid, "WS-C5002") finally: self.dev.disconnect() @@ -204,18 +210,19 @@ def test_learn_tokens_with_show_inventory(self): log_contents = f.read() self.assertRegex( log_contents, - r'\+\+\+ Unicon plugin iosxe( \(unicon\.(internal\.)?plugins\.iosxe\))? \+\+\+' + r"\+\+\+ Unicon plugin iosxe( \(unicon\.(internal\.)?plugins\.iosxe\))? \+\+\+", ) def test_linux_learn_tokens(self): - self.dev.connections.cli.command = \ + self.dev.connections.cli.command = ( "mock_device_cli --os generic --state connect_ssh" + ) # Test connection succeeds and tokens learned try: self.dev.connect(learn_tokens=True) - self.assertEqual(self.dev.os, 'linux') - self.assertEqual(self.dev.version, '4.18.0-240.22.1.el8_3.x86_64') + self.assertEqual(self.dev.os, "linux") + self.assertEqual(self.dev.version, "4.18.0-240.22.1.el8_3.x86_64") finally: self.dev.disconnect() @@ -224,12 +231,11 @@ def test_linux_learn_tokens(self): log_contents = f.read() self.assertRegex( log_contents, - r'\+\+\+ Unicon plugin linux( \(unicon(\.internal)?\.plugins\.linux\))? \+\+\+' + r"\+\+\+ Unicon plugin linux( \(unicon(\.internal)?\.plugins\.linux\))? \+\+\+", ) def test_learn_tokens_conf_state(self): - self.dev.connections.cli.command = \ - "mock_device_cli --os generic --state config" + self.dev.connections.cli.command = "mock_device_cli --os generic --state config" # If this manages to connect, it was able to recover from the config # state and learn tokens @@ -242,16 +248,17 @@ def test_learn_tokens_conf_state(self): def test_iosxr_learn_tokens(self): # Set up device to use correct mock_device data - self.dev.connections.cli.command = \ + self.dev.connections.cli.command = ( "mock_device_cli --os generic --state iosxr_login" + ) # Test connection succeeds and tokens learned try: self.dev.connect(learn_tokens=True, learn_hostname=True) - self.assertEqual(self.dev.os, 'iosxr') - self.assertEqual(self.dev.os_flavor, 'lnt') - self.assertEqual(self.dev.version, '5.2.3.12i') - self.assertEqual(self.dev.platform, 'iosxrv') + self.assertEqual(self.dev.os, "iosxr") + self.assertEqual(self.dev.os_flavor, "lnt") + self.assertEqual(self.dev.version, "5.2.3.12i") + self.assertEqual(self.dev.platform, "iosxrv") finally: self.dev.disconnect() @@ -260,12 +267,14 @@ def test_iosxr_learn_tokens(self): log_contents = f.read() self.assertRegex( log_contents, - r'\+\+\+ Unicon plugin iosxr/iosxrv( \(unicon\.plugins\.iosxr\.iosxrv\))? \+\+\+' + r"\+\+\+ Unicon plugin iosxr/iosxrv( \(unicon\.plugins\.iosxr\.iosxrv\))? \+\+\+", ) def test_learn_tokens_failure_exception(self): - self.dev.connections.cli.command = 'bash -c "while :; do echo -n R1# && read resp; done"' - self.dev.connections.cli.settings['LEARN_DEVICE_TOKENS'] = True + self.dev.connections.cli.command = ( + 'bash -c "while :; do echo -n R1# && read resp; done"' + ) + self.dev.connections.cli.settings["LEARN_DEVICE_TOKENS"] = True try: with self.assertRaises(LearnTokenError): self.dev.connect(learn_tokens=True, learn_hostname=True) @@ -274,8 +283,7 @@ def test_learn_tokens_failure_exception(self): class TestAbstractTokenDiscoveryStandardization(unittest.TestCase): - """ Run unit testing on AbstractTokenDiscovery.standardize_tokens() - """ + """Run unit testing on AbstractTokenDiscovery.standardize_tokens()""" def setUp(self) -> None: self.mock_con = MagicMock() @@ -301,41 +309,39 @@ def setUp(self) -> None: self.tb = load(self.testbed) self.dev = self.tb.devices.R1 - def test_version_standardization(self): discovery = AbstractTokenDiscovery(self.mock_con) tokens_to_test = [ - {'before':'17.7(3)Ab', 'after':'17.7.3ab'}, - {'before':'17.7.2(19700101:12345)', 'after':'17.7.2'}, - {'before':'1.4.0', 'after':'1.4'}, - {'before':'6.0(2)U6(5b)', 'after':'6.0.2u6.5b'}, - {'before':'03.03.02.SG', 'after':'3.3.2.sg'}, - {'before':'7.3(5)N1(1)', 'after':'7.3.5n1.1'}, + {"before": "17.7(3)Ab", "after": "17.7.3ab"}, + {"before": "17.7.2(19700101:12345)", "after": "17.7.2"}, + {"before": "1.4.0", "after": "1.4"}, + {"before": "6.0(2)U6(5b)", "after": "6.0.2u6.5b"}, + {"before": "03.03.02.SG", "after": "3.3.2.sg"}, + {"before": "7.3(5)N1(1)", "after": "7.3.5n1.1"}, ] for tokens in tokens_to_test: - standardized_tokens = \ - discovery.standardize_token_values({'version':tokens['before']}) - self.assertEqual(tokens['after'], standardized_tokens['version']) - + standardized_tokens = discovery.standardize_token_values( + {"version": tokens["before"]} + ) + self.assertEqual(tokens["after"], standardized_tokens["version"]) # Make sure pid's don't get put in lower case def test_pid_standardization(self): discovery = AbstractTokenDiscovery(self.mock_con) - standardized_tokens = \ - discovery.standardize_token_values({'pid':'QWERTY12345'}) - self.assertEqual('QWERTY12345', standardized_tokens['pid']) + standardized_tokens = discovery.standardize_token_values({"pid": "QWERTY12345"}) + self.assertEqual("QWERTY12345", standardized_tokens["pid"]) # Test that white spaces are removed - standardized_tokens = \ - discovery.standardize_token_values({'platform':'Q WE RTY 12345'}) - self.assertEqual('qwerty12345', standardized_tokens['platform']) + standardized_tokens = discovery.standardize_token_values( + {"platform": "Q WE RTY 12345"} + ) + self.assertEqual("qwerty12345", standardized_tokens["platform"]) class TestAbstractTokenDiscoveryMisc(unittest.TestCase): - """ Run unit testing on AbstractTokenDiscovery args, settings, etc. - """ + """Run unit testing on AbstractTokenDiscovery args, settings, etc.""" def setUp(self) -> None: self.mock_con = MagicMock() @@ -363,94 +369,92 @@ def setUp(self) -> None: self.dev = self.tb.devices.R1 self.mock_con.device = self.dev - # Test that token discovery can be disabled using the learn_tokens boolean - @patch('unicon.plugins.utils.AbstractTokenDiscovery.learn_device_tokens') + @patch("unicon.plugins.utils.AbstractTokenDiscovery.learn_device_tokens") def test_learn_tokens_argument(self, mock_call): # make sure the learn_device_tokens func is never called if set to false - self.dev.connections.cli.command = \ + self.dev.connections.cli.command = ( "mock_device_cli --os generic --state iosxe_enable" + ) self.dev.connect() mock_call.assert_not_called() def test_assign_tokens(self): - self.dev.os = 'generic' - self.dev.version = '0' - self.dev.platform = 'shoes' - self.dev.pid = 'J0HN-VVICK' + self.dev.os = "generic" + self.dev.version = "0" + self.dev.platform = "shoes" + self.dev.pid = "J0HN-VVICK" discovery = AbstractTokenDiscovery(self.mock_con) # Test that values are not overwritten by default and that learned # tokens get applied if a tokens prefeined value is 'generic' or is # non existent discovery.learned_tokens = { - 'os': 'ios', - 'version': 'asdasd', - 'platform': 'asdasd', - 'model': 'asdasd', - 'pid': 'asdasd', + "os": "ios", + "version": "asdasd", + "platform": "asdasd", + "model": "asdasd", + "pid": "asdasd", } discovery.assign_tokens(overwrite_testbed_tokens=False) - self.assertEqual(self.dev.os, 'ios') - self.assertEqual(self.dev.version, '0') - self.assertEqual(self.dev.platform, 'shoes') - self.assertEqual(self.dev.model, 'asdasd') - self.assertEqual(self.dev.pid, 'J0HN-VVICK') + self.assertEqual(self.dev.os, "ios") + self.assertEqual(self.dev.version, "0") + self.assertEqual(self.dev.platform, "shoes") + self.assertEqual(self.dev.model, "asdasd") + self.assertEqual(self.dev.pid, "J0HN-VVICK") # Test that values are overwritten if specified discovery.learned_tokens = { - 'os': 'overwrite1', - 'version': 'overwrite2', - 'platform': 'overwrite3', - 'model': 'overwrite4', - 'pid': 'overwrite5', + "os": "overwrite1", + "version": "overwrite2", + "platform": "overwrite3", + "model": "overwrite4", + "pid": "overwrite5", } discovery.assign_tokens(overwrite_testbed_tokens=True) - self.assertEqual(self.dev.os, 'overwrite1') - self.assertEqual(self.dev.version, 'overwrite2') - self.assertEqual(self.dev.platform, 'overwrite3') - self.assertEqual(self.dev.model, 'overwrite4') - self.assertEqual(self.dev.pid, 'overwrite5') - + self.assertEqual(self.dev.os, "overwrite1") + self.assertEqual(self.dev.version, "overwrite2") + self.assertEqual(self.dev.platform, "overwrite3") + self.assertEqual(self.dev.model, "overwrite4") + self.assertEqual(self.dev.pid, "overwrite5") def test_pid_token_lookup(self): discovery = AbstractTokenDiscovery(self.mock_con) - tokens = discovery.lookup_tokens_using_pid('ASR1001-2XOC3POS') - self.assertDictEqual({ - 'pid': 'ASR1001-2XOC3POS', - 'os': 'iosxe', - 'platform': 'asr1k', - 'model': 'asr1001' - }, - tokens) - - tokens = discovery.lookup_tokens_using_pid('N9K-C9508') - self.assertDictEqual({ - 'pid': 'N9K-C9508', - 'os': 'nxos', - 'platform': 'n9k', - 'model': 'n9500' - }, - tokens) + tokens = discovery.lookup_tokens_using_pid("ASR1001-2XOC3POS") + self.assertDictEqual( + { + "pid": "ASR1001-2XOC3POS", + "os": "iosxe", + "platform": "asr1k", + "model": "asr1001", + }, + tokens, + ) + + tokens = discovery.lookup_tokens_using_pid("N9K-C9508") + self.assertDictEqual( + {"pid": "N9K-C9508", "os": "nxos", "platform": "n9k", "model": "n9500"}, + tokens, + ) def test_pid_file_sorted(self): token_csv_file = os.path.join( - Path(os.path.realpath(__file__)).parents[1], - os.path.join('pid_tokens.csv') + Path(os.path.realpath(__file__)).parents[1], os.path.join("pid_tokens.csv") ) pid_data = load_token_csv_file(token_csv_file) keys = list(pid_data.keys()) sorted_keys = sorted(pid_data.keys()) - self.assertListEqual(keys, sorted_keys, msg= - "All rows in the pid_tokens.csv file must be in ascending sorted " - "order based on PID (first column)") + self.assertListEqual( + keys, + sorted_keys, + msg="All rows in the pid_tokens.csv file must be in ascending sorted " + "order based on PID (first column)", + ) class TestAbstractTokenDiscoveryHAConnection(unittest.TestCase): - def test_learn_token_HA(self): - md = MockDeviceTcpWrapperGeneric(port=0, - state='asr_exec_standby, asr_login') + md = MockDeviceTcpWrapperGeneric(port=0, state="asr_exec_standby, asr_login") md.start() testbed = """ @@ -491,32 +495,121 @@ def test_learn_token_HA(self): tb = load(testbed) dev = tb.devices.Router try: - dev.connect(init_config_commands=[], - connection_timeout=60) + dev.connect(init_config_commands=[], connection_timeout=60) dev.disconnect() finally: md.stop() - self.assertEqual(dev.os, 'iosxe') - self.assertEqual(dev.version, '16.7') - self.assertEqual(dev.platform, 'asr1k') - self.assertEqual(dev.model, 'asr1006') - self.assertEqual(dev.pid, 'ASR1006') + self.assertEqual(dev.os, "iosxe") + self.assertEqual(dev.version, "16.7") + self.assertEqual(dev.platform, "asr1k") + self.assertEqual(dev.model, "asr1006") + self.assertEqual(dev.pid, "ASR1006") class TestUtils(unittest.TestCase): - def test_load_token_csv_file(self): self.maxDiff = None lookup_file = os.path.join( - Path(os.path.realpath(__file__)).parents[1], - os.path.join('pid_tokens.csv') + Path(os.path.realpath(__file__)).parents[1], os.path.join("pid_tokens.csv") ) # Test default behavior data = load_token_csv_file(file_path=lookup_file) for name, item in data.items(): - for key in ('os', 'platform', 'model', 'submodel'): - self.assertIn(key, item, f'{key} not in {name} after loading') + for key in ("os", "platform", "model", "submodel"): + self.assertIn(key, item, f"{key} not in {name} after loading") + + +class TestCredentialSelection(unittest.TestCase): + def test_consume_current_credentials_returns_matching_named_credential(self): + shared_credential = {"username": "cisco", "password": "lab"} + context = { + "login_creds": ["default", "fallback"], + "previous_credential": "default", + } + session = {} + credentials = { + "default": shared_credential, + "fallback": {"username": "backup", "password": "backup"}, + "current_credentials": shared_credential, + } + + current_credential = _get_current_credentials( + context=context, + session=session, + credentials=credentials, + ) + + self.assertEqual(current_credential, "default") + + def test_consume_current_credentials_returns_none_without_relogin_context(self): + context = { + "login_creds": ["default", "fallback"], + } + session = {} + credentials = { + "default": {"username": "cisco", "password": "lab"}, + "current_credentials": {"username": "cisco", "password": "lab"}, + } + + current_credential = _get_current_credentials( + context=context, + session=session, + credentials=credentials, + ) + + self.assertIsNone(current_credential) + + def test_get_current_credential_prefers_current_credentials_before_iterator(self): + shared_credential = {"username": "cisco", "password": "lab"} + context = { + "credentials": { + "default": shared_credential, + "fallback": {"username": "backup", "password": "backup"}, + "current_credentials": shared_credential, + }, + "login_creds": ["default", "fallback"], + "previous_credential": "default", + "cred_list": ["fallback", "default"], + } + session = {} + + current_credential = get_current_credential(context, session) + + self.assertEqual(current_credential, "default") + self.assertEqual(session["current_credential"], "default") + self.assertNotIn("cred_iter", session) + + def test_get_current_credential_uses_credential_iterator_when_no_current_match( + self, + ): + context = { + "credentials": { + "default": {"username": "cisco", "password": "lab"}, + "fallback": {"username": "backup", "password": "backup"}, + }, + "cred_list": ["fallback", "default"], + } + session = {} + + current_credential = get_current_credential(context, session) + + self.assertEqual(current_credential, "fallback") + self.assertEqual(session["current_credential"], "fallback") + self.assertIn("cred_iter", session) + + def test_get_current_credential_raises_when_all_credentials_are_exhausted(self): + context = { + "credentials": { + "default": {"username": "cisco", "password": "lab"}, + }, + "cred_list": [], + } + session = {} + + with self.assertRaises(CredentialsExhaustedError): + get_current_credential(context, session) + if __name__ == "__main__": unittest.main() diff --git a/src/unicon/plugins/utils.py b/src/unicon/plugins/utils.py index 70fedcaa..cefc7a8e 100644 --- a/src/unicon/plugins/utils.py +++ b/src/unicon/plugins/utils.py @@ -1,4 +1,4 @@ -""" Utilities used by multiple plugins. +"""Utilities used by multiple plugins. Module: unicon.plugins @@ -10,37 +10,64 @@ Module for defining utilities used across various plugins. """ -import os import re import csv from pathlib import Path +from typing import Any, Iterator, Optional from prettytable import PrettyTable from unicon.utils import to_plaintext -from unicon.eal.dialogs import Dialog -from unicon.eal.dialogs import Statement -from unicon.core.errors import UniconAuthenticationError -from unicon.core.errors import CredentialsExhaustedError +from unicon.eal.dialogs import Dialog, Statement +from unicon.core.errors import CredentialsExhaustedError, UniconAuthenticationError # Declare token types for abstract token discovery -TOKEN_TYPES = ['os', 'os_flavor', 'version', 'platform', 'model', 'submodel', 'pid', 'chassis_type'] -OPTIONAL_TOKENS = ['os_flavor'] +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' +PID_TOKEN_FILE = Path(__file__).parent / "pid_tokens.csv" +ContextDict = dict[str, Any] +SessionDict = dict[str, Any] +CredentialsDict = dict[str, Any] -def _fallback_cred(context): - creds = [context['default_cred_name']] \ - if 'default_cred_name' in context else [] - if context.get('fallback_creds'): - creds.extend(context['fallback_creds']) + +def _fallback_cred(context: ContextDict) -> list[str]: + """Build the default credential fallback order. + + Args: + context: Connection context containing default and fallback credential + configuration. + + Returns: + The ordered list of credential names to try by default. + """ + creds = [context["default_cred_name"]] if "default_cred_name" in context else [] + if context.get("fallback_creds"): + creds.extend(context["fallback_creds"]) return creds -def _get_creds_to_try(context): - """ Get list of credentials to try. """ - creds_to_try = context.get('cred_list', _fallback_cred(context)) +def _get_creds_to_try(context: ContextDict) -> list[str]: + """Get the ordered list of credentials to try. + + Args: + context: Connection context that may define an explicit credential + list or fallback credential settings. + + Returns: + The normalized list of credential names to iterate through. + """ + creds_to_try = context.get("cred_list") if creds_to_try is None: creds_to_try = _fallback_cred(context) @@ -50,45 +77,100 @@ def _get_creds_to_try(context): return creds_to_try -def get_current_credential(context, session): - """ Gets the current credential name to try, if available. - - If a current credential name has not been set, try to get the next - credential name from the credential list. - - The following optional context keys may be set: - - credentials - Dict of all known credentials, keyed by name. +def _get_current_credentials( + context: ContextDict, + session: SessionDict, + credentials: CredentialsDict, +) -> Optional[str]: + """Resolve the named credential behind ``current_credentials`` for relogin. - - cred_list - List of credential names to try. - If not specified, or specified as None, defaults to - [context.default_cred_name] if set, otherwise []. - If specified as a non-list, then it is reassigned to a single- - element list. + This only applies when a previous credential exists and a fresh credential + iterator has not started yet, which keeps normal login/fallback sequencing + unchanged while allowing post-logout relogin to reuse the last successful + device credential. - - default_cred_name - Default credential name. + Args: + context: Connection context containing relogin state. + session: Session state for the active connection attempt. + credentials: Credential definitions keyed by credential name. - The following session variables are used: - - current_credential : The credential currently being used. - This credential is set to the next credential in the cred_list if - not previously set. - - - cred_iter : Initialized to an iterable based on cred_list, keeps - track of which credential to try next. - - Raises - ------ - CredentialsExhaustedError - If the session's current_credential was not set and all credential - names in cred_list have already been tried. + Returns: + The matching credential name when the stored ``current_credentials`` + entry should be reused, otherwise ``None``. """ - credentials = context.get('credentials') - current_credential = None + login_creds = context.get("login_creds") + # Only use this relogin shortcut when login credentials were explicitly set. + if not login_creds: + return None + + # The shortcut only makes sense when there are multiple credentials to choose from. + if isinstance(login_creds, list): + # A single configured credential should follow the normal path. + if len(login_creds) <= 1: + return None + else: + # Non-list login credentials are not part of the multi-credential relogin flow. + return None + + # Once iteration has started, keep using the existing credential sequence. + if session.get("cred_iter") is not None: + return None + + # This path is only for relogin after a previously successful credential. + if not context.get("previous_credential"): + return None + + current_credentials = credentials.get("current_credentials") + # Without a stored current_credentials entry, there is nothing to reuse. + if not current_credentials: + return None + + for credential_name, credential_data in credentials.items(): + # Skip the synthetic lookup entry and only match named credentials. + if credential_name == "current_credentials": + continue + if credential_data == current_credentials: + return credential_name + + return None + + +def get_current_credential(context: ContextDict, session: SessionDict) -> Optional[str]: + """Get the current credential name to use for this login attempt. + + The function first reuses an already selected credential, then checks + whether a relogin should reuse ``current_credentials``, and finally falls + back to the configured credential iterator. + + Args: + context: Connection context containing credentials and credential-order + configuration. + session: Session state used to cache the selected credential and + iterator progress. + + Returns: + The selected credential name, or ``None`` when no credentials are + configured in the context. + + Raises: + CredentialsExhaustedError: All configured credentials have already + been tried. + """ + credentials = context.get("credentials") + current_credential: Optional[str] = None if credentials: - current_credential = session.get('current_credential') + current_credential = session.get("current_credential") + if not current_credential: + current_credential = _get_current_credentials( + context=context, + session=session, + credentials=credentials, + ) if not current_credential: - creds_to_try = session.get('cred_iter', - iter(_get_creds_to_try(context))) - session['cred_iter'] = creds_to_try + creds_to_try: Iterator[str] = session.get( + "cred_iter", iter(_get_creds_to_try(context)) + ) + session["cred_iter"] = creds_to_try try: current_credential = next(creds_to_try) except StopIteration: @@ -96,36 +178,36 @@ def get_current_credential(context, session): pass if not current_credential: - raise CredentialsExhaustedError( - creds_tried=_get_creds_to_try(context)) + raise CredentialsExhaustedError(creds_tried=_get_creds_to_try(context)) else: - session['current_credential'] = current_credential + session["current_credential"] = current_credential return current_credential def invalidate_current_credential(context, session): - """ The current credential is no longer to be used. + """The current credential is no longer to be used. Save aside the previous credential name in the context so it outlives the session. """ - context['previous_credential'] = session['current_credential'] - session['current_credential'] = None + context["previous_credential"] = session["current_credential"] + session["current_credential"] = None def common_cred_username_handler(spawn, context, credential): - """ Send the current credential's username. """ + """Send the current credential's username.""" try: - spawn.sendline(to_plaintext( - context['credentials'][credential]['username'])) + spawn.sendline(to_plaintext(context["credentials"][credential]["username"])) except KeyError: - raise UniconAuthenticationError("No username found " - "for credential {}.".format(credential)) + raise UniconAuthenticationError( + "No username found for credential {}.".format(credential) + ) -def common_cred_password_handler(spawn, context, session, credential, - reuse_current_credential=False): - """ Send the current credential's password. +def common_cred_password_handler( + spawn, context, session, credential, reuse_current_credential=False +): + """Send the current credential's password. The current credential is then invalidated as long as reuse_current_credential is set to `False`. @@ -134,29 +216,29 @@ def common_cred_password_handler(spawn, context, session, credential, are requested, the next credential in session['cred_iter'] is consumed. """ try: - spawn.sendline(to_plaintext( - context['credentials'][credential]['password'])) + spawn.sendline(to_plaintext(context["credentials"][credential]["password"])) except KeyError: - raise UniconAuthenticationError("No password found " - "for credential {}.".format(credential)) + raise UniconAuthenticationError( + "No password found for credential {}.".format(credential) + ) if not reuse_current_credential: invalidate_current_credential(context=context, session=session) def slugify(text): - """ Simple slugify + """Simple slugify Returns string stripped of special chars, replaced with _ """ text = text.lower() - pattern = re.compile(r'[^a-z0-9]+') - text = re.sub(pattern, '_', text) - text = re.sub(r'_{2,}', '_', text).strip('_') + pattern = re.compile(r"[^a-z0-9]+") + text = re.sub(pattern, "_", text) + text = re.sub(r"_{2,}", "_", text).strip("_") return text def sanitize(s): - """ Remove escape codes and non ASCII characters from output. + """Remove escape codes and non ASCII characters from output. Remove crypto related lines as workaround for deprecation messages. @@ -167,24 +249,26 @@ def sanitize(s): ../python3.10/site-packages/asyncssh/crypto/cipher.py:30: CryptographyDeprecationWarning: SEED has been deprecated from cryptography.hazmat.primitives.ciphers.algorithms import SEED, TripleDES """ - ansi_escape = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]') - s = ansi_escape.sub('', s) + ansi_escape = re.compile(r"\x1B\[[0-?]*[ -/]*[@-~]") + s = ansi_escape.sub("", s) mpa = dict.fromkeys(range(32)) # clean up crypto deprecation warnings lines = s.splitlines() new_lines = [] for line in lines: - if 'CryptographyDeprecationWarning' not in line \ - and 'cryptography.hazmat.primitives.ciphers.algorithms' not in line: - new_lines.append(line) - s = '\n'.join(new_lines) + if ( + "CryptographyDeprecationWarning" not in line + and "cryptography.hazmat.primitives.ciphers.algorithms" not in line + ): + new_lines.append(line) + s = "\n".join(new_lines) - return s.translate(mpa).strip().replace(' ', '') + return s.translate(mpa).strip().replace(" ", "") -def load_token_csv_file(file_path, key='pid'): - """ Opens the provided .csv file and loads the contents into a dictionary +def load_token_csv_file(file_path, key="pid"): + """Opens the provided .csv file and loads the contents into a dictionary A header is required for correct loading. For example: pid,os,platform,model,etc... @@ -223,19 +307,18 @@ def load_token_csv_file(file_path, key='pid'): return ret_dict -def get_device_mode(con): - '''Check the mode of device - ''' - output = con.execute('show version | include operating mode') - if output: - pattern = re.compile(r'.*operating mode:\s*(?P[\w-]+).*', re.DOTALL) - m = pattern.match(output) - if m: - return m.groupdict().get('mode') +def get_device_mode(con): + """Check the mode of device""" + output = con.execute("show version | include operating mode") + if output: + pattern = re.compile(r".*operating mode:\s*(?P[\w-]+).*", re.DOTALL) + m = pattern.match(output) + if m: + return m.groupdict().get("mode") -class AbstractTokenDiscovery(): +class AbstractTokenDiscovery: def __init__(self, con, execute_target=None): # Putting these imports at the top creates a circular import chain # Import them during object initialization if not already imported @@ -249,7 +332,6 @@ def __init__(self, con, execute_target=None): if not Uname: from genie.libs.parser.generic.show_platform import Uname - self.con = con self.device = con.device self.execute_target = execute_target @@ -261,17 +343,17 @@ def __init__(self, con, execute_target=None): # Attach commands and accompying classes for cleaner looping self.commands_and_classes = { - 'show version': ShowVersion, - 'show inventory': ShowInventory, - 'uname -a': Uname, + "show version": ShowVersion, + "show inventory": ShowInventory, + "uname -a": Uname, } # Fill in starting token values - self.learned_tokens = {token_type:None for token_type in TOKEN_TYPES} - self.predefined_tokens = \ - {token_type:getattr(self.device, token_type, None) - for token_type in TOKEN_TYPES} - + self.learned_tokens = {token_type: None for token_type in TOKEN_TYPES} + self.predefined_tokens = { + token_type: getattr(self.device, token_type, None) + for token_type in TOKEN_TYPES + } def update_learned_tokens(self, new_tokens, overwrite_existing_values=True): for token_type, token_value in self.learned_tokens.items(): @@ -279,29 +361,26 @@ def update_learned_tokens(self, new_tokens, overwrite_existing_values=True): if token_value is None or overwrite_existing_values: self.learned_tokens[token_type] = new_tokens[token_type] - def all_tokens_learned(self): - for token ,token_value in self.learned_tokens.items(): + for token, token_value in self.learned_tokens.items(): if token not in OPTIONAL_TOKENS: - if token_value == '' or token_value is None: + if token_value == "" or token_value is None: return False return True - def lookup_tokens_using_pid(self, pid_to_check): try: data = self.pid_data[pid_to_check] except KeyError: - return {'pid': pid_to_check} + return {"pid": pid_to_check} else: return { - 'os': data['os'], - 'platform': data['platform'], - 'model': data['model'], - 'pid': pid_to_check, + "os": data["os"], + "platform": data["platform"], + "model": data["model"], + "pid": pid_to_check, } - def discover_tokens(self): """ Loop through the commands one at a time and parse the output (if any). @@ -313,23 +392,28 @@ def discover_tokens(self): device = self.device controller_mode = None - discovery_prompt_stmt = \ - Statement(pattern=self.con.state_machine\ - .get_state('learn_tokens_state').pattern) - dialog = Dialog([ - discovery_prompt_stmt, - generic_statements.more_prompt_stmt, - generic_statements.syslog_msg_stmt - ]) + discovery_prompt_stmt = Statement( + pattern=self.con.state_machine.get_state("learn_tokens_state").pattern + ) + dialog = Dialog( + [ + discovery_prompt_stmt, + generic_statements.more_prompt_stmt, + generic_statements.syslog_msg_stmt, + ] + ) # Try to get to enable mode, ignore failure from unicon.plugins.generic.statements import generic_statements - enable_dialog = dialog + Dialog([ - generic_statements.enable_password_stmt, - generic_statements.syslog_msg_stmt - ]) + + enable_dialog = dialog + Dialog( + [ + generic_statements.enable_password_stmt, + generic_statements.syslog_msg_stmt, + ] + ) try: - self.con.sendline('enable') + self.con.sendline("enable") enable_dialog.process(self.con.spawn, context=self.con.context) except Exception: pass @@ -340,70 +424,78 @@ def discover_tokens(self): self.con.sendline(cmd) except Exception as e: self.con.log.debug( - f"Failed to execute command '{cmd}' on {self}. Reason: {e}") + f"Failed to execute command '{cmd}' on {self}. Reason: {e}" + ) continue else: - outcome = dialog.process(self.con.spawn, - timeout=self.con.spawn.settings.EXEC_TIMEOUT - ) + outcome = dialog.process( + self.con.spawn, timeout=self.con.spawn.settings.EXEC_TIMEOUT + ) if not outcome.match_output: continue # Try to parse the output from the command try: - parsed_output = \ - self.commands_and_classes[cmd](device=self.device)\ - .cli(output=outcome.match_output) + parsed_output = self.commands_and_classes[cmd]( + device=self.device + ).cli(output=outcome.match_output) except Exception as e: - self.con.log.debug(f"Failed to parse command '{cmd}' on " - f"{device}. Reason: {e}") + self.con.log.debug( + f"Failed to parse command '{cmd}' on {device}. Reason: {e}" + ) else: # this controller will be se to true if device is in controller mode. - if parsed_output.get('operating_mode', '') == 'Controller-Managed': + if parsed_output.get("operating_mode", "") == "Controller-Managed": controller_mode = True - self.update_learned_tokens(parsed_output, - overwrite_existing_values=False) + self.update_learned_tokens( + parsed_output, overwrite_existing_values=False + ) # If pid learned from show version, use to get other tokens - if 'pid' in self.learned_tokens: + if "pid" in self.learned_tokens: tokens_from_pid = self.lookup_tokens_using_pid( - self.learned_tokens['pid']) + self.learned_tokens["pid"] + ) if tokens_from_pid: self.update_learned_tokens( - tokens_from_pid, overwrite_existing_values=True) - - if cmd == 'show inventory' and \ - parsed_output.get('inventory_item_index', None): + tokens_from_pid, overwrite_existing_values=True + ) - if parsed_output.get('chassis_type'): - self.learned_tokens['chassis_type'] = \ - parsed_output.get('chassis_type') + 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(): - tokens_from_pid_lookup = \ - self.lookup_tokens_using_pid( - entry_data.get('pid', None)) + for _, entry_data in parsed_output[ + "inventory_item_index" + ].items(): + tokens_from_pid_lookup = self.lookup_tokens_using_pid( + entry_data.get("pid", None) + ) if tokens_from_pid_lookup: self.update_learned_tokens( tokens_from_pid_lookup, - overwrite_existing_values=True) + overwrite_existing_values=True, + ) break if self.all_tokens_learned(): self.con.log.debug( - "All tokens discovered, ending token discovery early") + "All tokens discovered, ending token discovery early" + ) # if the controller is True device is in controller mode set the # platform to sdwan if controller_mode: - self.update_learned_tokens({'platform':'sdwan'}) + self.update_learned_tokens({"platform": "sdwan"}) break if controller_mode: - self.update_learned_tokens({'platform':'sdwan'}) - + self.update_learned_tokens({"platform": "sdwan"}) def standardize_token_values(self, tokens): """ @@ -412,45 +504,38 @@ def standardize_token_values(self, tokens): """ ret_dict = {} for token_type, token_value in tokens.items(): - if not token_value: ret_dict[token_type] = None else: # Remove all white space - modified_value = re.sub(r'\s', r'', token_value) + modified_value = re.sub(r"\s", r"", token_value) - if token_type != 'pid': + if token_type != "pid": modified_value = modified_value.lower() - if token_type == 'version': - + if token_type == "version": # Remove brackets that have a colon in them. Typically seen # in experimental version builds containing dates # 17.7.1(20210101:01234) -> 17.7.1 # 17.6.20210302:012459 -> 17.6 - modified_value = re.sub(r'\.?\(?\d{8}\:\d+\)?', - r'', - modified_value) + modified_value = re.sub(r"\.?\(?\d{8}\:\d+\)?", r"", modified_value) # Remove brackets around numbers. If a number is in a # bracket, then treat it as minor version # 17.7(1) -> 17.7.1 - modified_value = re.sub(r'\((\w+)\)', - r'.\1', - modified_value) + modified_value = re.sub(r"\((\w+)\)", r".\1", modified_value) # Remove 0s from front of version numbers and remove # leading/trailing 0s # 17.07.01 -> 17.7.1 - modified_value = re.sub(r'\.0+(\d)', r'.\1', modified_value) - modified_value = re.sub(r'\.0+$|^0+', r'', modified_value) + modified_value = re.sub(r"\.0+(\d)", r".\1", modified_value) + modified_value = re.sub(r"\.0+$|^0+", r"", modified_value) ret_dict[token_type] = modified_value return ret_dict - def assign_tokens(self, overwrite_testbed_tokens): """ Assign tokens to the device. Don't overwrite token values unless asked @@ -461,51 +546,51 @@ def assign_tokens(self, overwrite_testbed_tokens): device = self.device # Loop through token types and update/assign tokens to device for token_type in TOKEN_TYPES: - # Get the value of the token defined in the testbed (if any) - predefined_token_value = self.predefined_tokens.get(token_type, - None) + predefined_token_value = self.predefined_tokens.get(token_type, None) # Get the value of the token that was learned using various commands - learned_token_value = self.learned_tokens.get(token_type, None) + learned_token_value = self.learned_tokens.get(token_type, None) # If Device has no specified token, assign one if learned_token_value and not predefined_token_value: - con.log.debug(f"Learned new token for {device}. " - f"Token type: {token_type}, " - f"Token value: {learned_token_value}") + con.log.debug( + f"Learned new token for {device}. " + f"Token type: {token_type}, " + f"Token value: {learned_token_value}" + ) setattr(device, token_type, learned_token_value) continue # If device has token specified as 'generic', assign learned token # with an overwrite warning - if learned_token_value and predefined_token_value == 'generic': - con.log.debug(f"Overwriting 'generic' {token_type} device token" - f" with '{learned_token_value}' for {device}") + if learned_token_value and predefined_token_value == "generic": + con.log.debug( + f"Overwriting 'generic' {token_type} device token" + f" with '{learned_token_value}' for {device}" + ) setattr(device, token_type, learned_token_value) continue # If we're overwriting testbed tokens if learned_token_value and overwrite_testbed_tokens: - con.log.debug(f"Overwriting {token_type} token with " - f"'{learned_token_value}' for {device}") + con.log.debug( + f"Overwriting {token_type} token with " + f"'{learned_token_value}' for {device}" + ) setattr(device, token_type, learned_token_value) continue # Warn user about mismatched defined vs learned tokens - if learned_token_value \ - and learned_token_value != predefined_token_value: - - if token_type == 'version': + if learned_token_value and learned_token_value != predefined_token_value: + if token_type == "version": # Trim letters in version comparison to increase reliability - trimmed_learned_token = \ - re.sub(r'[a-zA-Z]+', - r'', - str(learned_token_value)) - trimmed_predefined_token = \ - re.sub(r'[a-zA-Z]+', - r'', - str(predefined_token_value)) + trimmed_learned_token = re.sub( + r"[a-zA-Z]+", r"", str(learned_token_value) + ) + trimmed_predefined_token = re.sub( + r"[a-zA-Z]+", r"", str(predefined_token_value) + ) if trimmed_learned_token == trimmed_predefined_token: continue @@ -514,12 +599,15 @@ def assign_tokens(self, overwrite_testbed_tokens): f"tokens for {device}. The token for {token_type} defined " f"in the testbed is {predefined_token_value}, but the " f"learned token is {learned_token_value}. The value of the " - f"token defined in the testbed will be used.") + f"token defined in the testbed will be used." + ) continue # Predefined token and learned token are the same, everything is OK - con.log.debug(f"Predefined and learned {token_type} are the same: " - f"{predefined_token_value}") + con.log.debug( + f"Predefined and learned {token_type} are the same: " + f"{predefined_token_value}" + ) def show_results(self): """ @@ -529,41 +617,48 @@ def show_results(self): """ device = self.device # Make a table and set each column as left "l" justified - t = PrettyTable(['Token Type', 'Defined in Testbed', - 'Learned from Device', 'Used for this job']) - t.align['Token Type'] = \ - t.align['Defined in Testbed'] = \ - t.align['Learned from Device'] = \ - t.align['Used for this job'] = "l" + t = PrettyTable( + [ + "Token Type", + "Defined in Testbed", + "Learned from Device", + "Used for this job", + ] + ) + t.align["Token Type"] = t.align["Defined in Testbed"] = t.align[ + "Learned from Device" + ] = t.align["Used for this job"] = "l" for token_type in TOKEN_TYPES: - t.add_row([token_type, - self.predefined_tokens.get(token_type, None), - self.learned_tokens.get(token_type, None), - getattr(device, token_type, None)]) + t.add_row( + [ + token_type, + self.predefined_tokens.get(token_type, None), + self.learned_tokens.get(token_type, None), + getattr(device, token_type, None), + ] + ) table_title = f"Abstract-Token Discovery Results for: {device.name}" - self.con.log.info(f'\n{t.get_string(title=table_title)}') - + self.con.log.info(f"\n{t.get_string(title=table_title)}") def learn_device_tokens(self, overwrite_testbed_tokens=False): if not self.con.device: - self.con.log.debug('No device object, cannot learn tokens') + self.con.log.debug("No device object, cannot learn tokens") return {} - if self.con.state_machine.current_state == 'standby_locked': - self.con.log.info('Device is locked, cannot learn tokens') + 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 +++') + self.con.log.info("+++ Learning device tokens +++") else: - self.con.log.debug('+++ Learning device tokens +++') + self.con.log.debug("+++ Learning device tokens +++") # Parse commands using generic parsers to get device abstraction tokens self.discover_tokens() # Force tokens to be same format - self.predefined_tokens = \ - self.standardize_token_values(self.predefined_tokens) + self.predefined_tokens = self.standardize_token_values(self.predefined_tokens) self.learned_tokens = self.standardize_token_values(self.learned_tokens) # Assign tokens to device as attributes based on a few rules