mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
merge nxos-specific work from develop into master. (#54931)
* Combine proxy and native minion workflows for NXOS - Normalize SSH and NX-API proxy minion workflows - Add NX-API over unix domain socket support for native minions * Fix typo * Fix states correct_roles bug * Add comment clarification for nxos states * Fix lint issues * Address python3 incompatibility * Fix additional lint issues * Disable pylint W1699 warning * Use new style class syntax * Correct typo * Fix nxos grains * Pass data to grains function * Return nxos grains key * Protect nxos grain with proper __virtual__() check The changes in PR #49676 made the following stacktrace occur when running on a system that doesn't have the proper NXOS/NXAPIClient settings exposed: ``` [CRITICAL] Failed to load grains defined in grain file nxos.system_information in function <function system_information at 0x3aeb758>, error: Traceback (most recent call last): File "/testing/salt/loader.py", line 773, in grains ret = funcs[key](**kwargs) File "/testing/salt/grains/nxos.py", line 36, in system_information data = salt.utils.nxos.version_info() File "/testing/salt/utils/nxos.py", line 318, in version_info client = NxapiClient() File "/testing/salt/utils/nxos.py", line 78, in __init__ raise RuntimeError("No host specified and no UDS found at {0}\n".format(self.NXAPI_UDS)) RuntimeError: No host specified and no UDS found at /tmp/nginx_local/nginx_1_be_nxapi.sock local: True ``` We need to protect the grains from loading when the settings are missing. * Initial nxos_upgrade changes * Revert "Initial nxos_upgrade changes" This reverts commite17ca19fbc
. * New NX-OS salt minion install doc * Add guestshell sync information * Initial nxos_upgrade changes * Initial nxos_upgrade changes * Revert "Initial nxos_upgrade changes" This reverts commite17ca19fbc
. * Revert "Initial nxos_upgrade changes" This reverts commite17ca19fbc
. * Add nxos to index.rst * New nxos_upgrade execution and state modules * Added NXOS UT support - initially for nxos_upgrade.py * Resolved one pylint 'old class style' issue. Excluding 'nxos' sub-directory under tests/unit/modules * Add __init__.py file to treat directory as a package directory. * Addressed PR comments. * Removed pylint disable-msg that was only applicable to python 3+ * Document NAPALM installation inside Guestshell Adding step-by-step guide to install NAPALM inside of the NXOS Guestshell. * show and sendline method fixes * Update doc for starting minion in nxos GuestShell * Revert show method changes * Revert sendline method doc changes * Resolve lint errors * Remove nxos guestshell napalm references This work is delayed so removing the references * Address review comments * Address salt style guide comment * Doc and module updates * Initial nxos module and proxy unit tests * Additional nxos module and proxy unit tests * Add nxos state unit tests * Add tests for replace function * Bug fixes * Fix test_check_password_password_encrypted_false test * Add test_config_nxos_error_ssh test * remove opts modification in init * reduce scope of variable to function where it's used * minor nxos cleanup - raise instead of exit, use named kwargs * use create_autospec in place of raw mocks * _init_ssh's raise is now caught by ping * allow gen_hash to work on any system * change no_save_config option to save_config * update set_password to work with updated gen_hash * passing an invalid algorithm to pycrypto.hash raises * blacken nxos-related files * _fallback_gen_hash also works without a password * remove debugging line, improve error message * lint and black * nxos docfix * remove unused variable * Review comments addressed * mark old nxos functions as deprecated * black * remove unused variables * clean up arguments * simplify save_config logic * minor doc cleanup * make sendline with a list of commands reliably work * Update various doc index files for nxos_upgrade * Fix a few bugs in nxos proxy and execution modules * doc indent fix Co-authored-by: mikewiebe <mwiebe@cisco.com> Co-authored-by: rallytime <nicole@saltstack.com> Co-authored-by: Thomas Stoner <tmstoner@cisco.com> Co-authored-by: tstoner <33665760+tstoner@users.noreply.github.com> Co-authored-by: Chris Van Heuveln <cvanheuv@cisco.com>
This commit is contained in:
parent
2b22bd0c26
commit
1c7ce9d793
34 changed files with 7589 additions and 521 deletions
|
@ -332,6 +332,7 @@ execution modules
|
|||
nspawn
|
||||
nxos
|
||||
nxos_api
|
||||
nxos_upgrade
|
||||
omapi
|
||||
openbsd_sysctl
|
||||
openbsdpkg
|
||||
|
|
5
doc/ref/modules/all/salt.modules.nxos_upgrade.rst
Normal file
5
doc/ref/modules/all/salt.modules.nxos_upgrade.rst
Normal file
|
@ -0,0 +1,5 @@
|
|||
salt.modules.nxos_upgrade module
|
||||
============================
|
||||
|
||||
.. automodule:: salt.modules.nxos_upgrade
|
||||
:members:
|
|
@ -225,6 +225,7 @@ state modules
|
|||
npm
|
||||
ntp
|
||||
nxos
|
||||
nxos_upgrade
|
||||
openstack_config
|
||||
openvswitch_bridge
|
||||
openvswitch_port
|
||||
|
|
5
doc/ref/states/all/salt.states.nxos_upgrade.rst
Normal file
5
doc/ref/states/all/salt.states.nxos_upgrade.rst
Normal file
|
@ -0,0 +1,5 @@
|
|||
salt.states.nxos_upgrade module
|
||||
=======================
|
||||
|
||||
.. automodule:: salt.states.nxos_upgrade
|
||||
:members:
|
|
@ -50,6 +50,7 @@ These guides go into detail how to install Salt on a given platform.
|
|||
fedora
|
||||
freebsd
|
||||
gentoo
|
||||
nxos
|
||||
openbsd
|
||||
osx
|
||||
rhel
|
||||
|
|
405
doc/topics/installation/nxos.rst
Normal file
405
doc/topics/installation/nxos.rst
Normal file
|
@ -0,0 +1,405 @@
|
|||
============================================================
|
||||
Cisco Nexus Salt Minion Installation and Configuration Guide
|
||||
============================================================
|
||||
|
||||
This document describes the Salt Minion installation and configuration on Cisco Nexus switches. These instructions detail the process for managing the Nexus switches using a Proxy Minion or Native Minion on platforms that have GuestShell support.
|
||||
|
||||
.. contents:: Table of Contents
|
||||
|
||||
Pre-Install Tasks
|
||||
=================
|
||||
|
||||
STEP 1: Verify Platform and Software Version Support
|
||||
----------------------------------------------------
|
||||
|
||||
The following platforms and software versions have been certified to work with this version of Salt.
|
||||
|
||||
.. table:: Platform / Software Mininum Requirements
|
||||
:widths: auto
|
||||
:align: center
|
||||
|
||||
=================== ===================== ================ =================== =================
|
||||
Supported Platforms Minimum NX-OS Version SSH Proxy Minion NX-API Proxy Minion GuestShell Minion
|
||||
=================== ===================== ================ =================== =================
|
||||
Cisco Nexus N3k 7.0(3)I2(5) and later Supported Supported Supported
|
||||
Cisco Nexus N9k 7.0(3)I2(5) and later Supported Supported Supported
|
||||
Cisco Nexus N6k 7.3(0)N1(1) and later Supported Not Supported Not Supported
|
||||
Cisco Nexus N7k 7.3(0)D1(1) and later Supported Supported Not Supported
|
||||
=================== ===================== ================ =================== =================
|
||||
|
||||
.. table:: Platform Models
|
||||
:widths: auto
|
||||
:align: center
|
||||
|
||||
======== ===========
|
||||
Platform Description
|
||||
======== ===========
|
||||
N3k Support includes N30xx, N31xx, N32xx and N35xx models
|
||||
N6k Support includes all N6xxx models
|
||||
N7k Support includes all N7xxx models
|
||||
N9k Support includes all N9xxx models
|
||||
======== ===========
|
||||
|
||||
STEP 2: Choose Salt Minion Type
|
||||
-------------------------------
|
||||
|
||||
Using the tables above, select the Salt Minion type.
|
||||
|
||||
Choices:
|
||||
* ``SSH`` Proxy Minion (See `Salt Proxy Minion Configuration`_ Section)
|
||||
* ``NX-API`` Proxy Minon (See `Salt Proxy Minion Configuration`_ Section)
|
||||
* ``GuestShell`` Native Minion (See `GuestShell Salt Minion Installation`_ Section)
|
||||
* Some platforms support a native minon installed directly on the NX-OS device inside the GuestShell
|
||||
* The GuestShell is a secure Linux container environment running CentOS
|
||||
|
||||
STEP 3: Network Connectivity
|
||||
----------------------------
|
||||
|
||||
Ensure that IP reachability exists between the NX-OS Salt Minon device and the SaltStack Master.
|
||||
|
||||
**Note:** The management interface exists in a separate VRF context and requires additional configuration as shown.
|
||||
|
||||
Example: Nexus CLI Configuration for connectivity via management interface
|
||||
|
||||
.. code:: bash
|
||||
|
||||
config term
|
||||
vrf context management
|
||||
ip name-server 10.0.0.202
|
||||
ip domain-name mycompany.com
|
||||
ip route 0.0.0.0/0 10.0.0.1
|
||||
|
||||
interface mgmt0
|
||||
vrf member management
|
||||
ip address 10.0.0.99/24
|
||||
|
||||
ntp server 10.0.0.201 use-vrf management
|
||||
end
|
||||
|
||||
Salt Proxy Minion Configuration
|
||||
===============================
|
||||
|
||||
Here is a sample Proxy Minion directory structure
|
||||
|
||||
.. code:: bash
|
||||
|
||||
saltmaster:/srv/pillar$tree
|
||||
.
|
||||
├── n3k-proxy.sls
|
||||
├── n7k-proxy.sls
|
||||
└── top.sls
|
||||
|
||||
This displays a top sls file and two proxy minon sls files for a Nexus 3k and Nexus 7k device.
|
||||
|
||||
Sample contents for the ``top.sls`` file.
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
saltmaster:/srv/pillar$cat top.sls
|
||||
base:
|
||||
n3k-proxy:
|
||||
- n3k-proxy
|
||||
n7k-proxy:
|
||||
- n7k-proxy
|
||||
|
||||
Proxy Minion Pillar Data
|
||||
------------------------
|
||||
|
||||
Here is a sample Proxy Minon pillar data file.
|
||||
|
||||
All of the data for both ssh and nxapi proxy minion types can be stored in the same pillar data file. To choose ``ssh`` or ``nxapi``, simply set the ``connection:`` parameter accordingly.
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
saltmaster:/srv/pillar$cat n7k-proxy.sls
|
||||
proxy:
|
||||
proxytype: nxos
|
||||
|
||||
# Specify ssh or nxapi connection type (default is ssh)
|
||||
#connection: ssh
|
||||
connection: nxapi
|
||||
|
||||
# Parameters Common to both SSH and NX-API
|
||||
host: n7k.example.com
|
||||
username: admin
|
||||
password: password
|
||||
|
||||
# SSH Parameters
|
||||
prompt_name: n7k
|
||||
ssh_args: '-o PubkeyAuthentication=no'
|
||||
key_accept: True
|
||||
|
||||
# NX-API Parameters
|
||||
transport: https
|
||||
port: 443
|
||||
verify: False
|
||||
|
||||
# Option to prevent auto-save after each configuration command.
|
||||
# Setting this to True will improve performance when using
|
||||
# nxos execution module functions to configure the device.
|
||||
no_save_config: True
|
||||
|
||||
|
||||
* For the most current nxos proxy minion configuration options, See :mod:`salt.proxy.nxos <salt.proxy.nxos>`
|
||||
* For the most current list of nxos execution module functions, See :mod:`salt.modules.nxos<salt.modules.nxos>`
|
||||
|
||||
|
||||
|
||||
GuestShell Salt Minion Installation
|
||||
===================================
|
||||
|
||||
This section is only required when running the SaltStack Minion from the ``guestshell``.
|
||||
|
||||
STEP 1a: Enable the Guestshell on low footprint N3ks
|
||||
----------------------------------------------------
|
||||
|
||||
**NOTE:** Skip down to **STEP 1b** if the target system is not a low footprint N3k.
|
||||
|
||||
Nexus 3xxx switches with 4 GB RAM and 1.6 GB bootflash are advised to use compacted images to reduce the storage resources consumed by the image. As part of the compaction process, the ``guestshell.ova`` is removed from the system image. To make use of the guestshell on these systems, the guestshell.ova may be downloaded and used to install the guestshell.
|
||||
|
||||
Guestshell OVA Download Link_
|
||||
|
||||
.. _Link: https://software.cisco.com/download/home/283970187/type/282088129/release/9.2%25281%2529?catid=268438038
|
||||
|
||||
Starting in release ``9.2(1)`` and onward, the .ova file can be copied to the ``volatile:`` directory which frees up more space on ``bootflash:``.
|
||||
|
||||
Copy the ``guestshell.ova`` file to ``volatile:`` if supported, otherwise copy it to ``bootflash:``
|
||||
|
||||
.. code:: bash
|
||||
|
||||
n3xxx# copy scp://admin@1.2.3.4/guestshell.ova volatile: vrf management
|
||||
guestshell.ova 100% 55MB 10.9MB/s 00:05
|
||||
Copy complete, now saving to disk (please wait)...
|
||||
Copy complete.
|
||||
|
||||
Use the ``guestshell enable`` command to install and enable guestshell.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
n3xxx# guestshell enable package volatile:guestshell.ova
|
||||
|
||||
|
||||
STEP 1b: Enable the Guestshell
|
||||
------------------------------
|
||||
|
||||
The ``guestshell`` container environment is enabled by default on most platforms; however, the default disk and memory resources allotted to guestshell are typically too small to support SaltStack Minion requirements. The resource limits may be increased with the NX-OS CLI ``guestshell resize`` commands as shown below.
|
||||
|
||||
.. table:: Resource Requirements
|
||||
:widths: auto
|
||||
:align: center
|
||||
|
||||
=================== =====================
|
||||
Resource Recommended
|
||||
=================== =====================
|
||||
Disk **500 MB**
|
||||
Memory **350 MB**
|
||||
=================== =====================
|
||||
|
||||
|
||||
``show guestshell detail`` displays the current resource limits:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
n3k# show guestshell detail
|
||||
Virtual service guestshell+ detail
|
||||
State : Activated
|
||||
...
|
||||
Resource reservation
|
||||
Disk : 150 MB
|
||||
Memory : 128 MB
|
||||
|
||||
``guestshell resize rootfs`` sets disk size limits while ``guestshell resize memory`` sets memory limits. The resize commands do not take effect until after the guestshell container is (re)started by ``guestshell reboot`` or ``guestshell enable``.
|
||||
|
||||
|
||||
**Example.** Allocate resources for guestshell by setting new limits to 500MB disk and 350MB memory.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
n3k# guestshell resize rootfs 500
|
||||
n3k# guestshell resize memory 350
|
||||
|
||||
n3k# guestshell reboot
|
||||
Are you sure you want to reboot the guest shell? (y/n) [n] y
|
||||
|
||||
STEP 2: Set Up Guestshell Network
|
||||
---------------------------------
|
||||
|
||||
The ``guestshell`` is an independent CentOS container that does not inherit settings from NX-OS.
|
||||
|
||||
* Use ``guestshell`` to enter the guestshell environment, then become root.
|
||||
* *Optional:* Use ``chvrf`` to specify a vrf namespace; e.g. ``sudo chvrf management``
|
||||
|
||||
.. code:: bash
|
||||
|
||||
n3k# guestshell
|
||||
|
||||
[guestshell@guestshell ~]$ sudo su - # Optional: sudo chvrf management
|
||||
[root@guestshell guestshell]#
|
||||
|
||||
**OPTIONAL: Add DNS Configuration**
|
||||
|
||||
.. code:: bash
|
||||
|
||||
[root@guestshell guestshell]# cat >> /etc/resolv.conf << EOF
|
||||
nameserver 10.0.0.202
|
||||
domain mycompany.com
|
||||
EOF
|
||||
|
||||
|
||||
**OPTIONAL: Define proxy server variables if needed to allow network access to SaltStack package repositories**
|
||||
|
||||
.. code:: bash
|
||||
|
||||
export http_proxy=http://proxy.yourdomain.com:<port>
|
||||
export https_proxy=https://proxy.yourdomain.com:<port>
|
||||
|
||||
|
||||
STEP 3: Install SaltStack Minion
|
||||
---------------------------------
|
||||
|
||||
**OPTIONAL: Upgrade the pip installer**
|
||||
|
||||
``[root@guestshell guestshell]# pip install --upgrade pip``
|
||||
|
||||
|
||||
Install the ``certifi`` python package.
|
||||
|
||||
``[root@guestshell guestshell]# pip install certifi``
|
||||
|
||||
The most current information on installing the SaltStack Minion in a Centos7 environment can be found here_
|
||||
|
||||
.. _here: https://repo.saltstack.com/#rhel
|
||||
|
||||
Information from the install guide is provided here for convenience.
|
||||
|
||||
Run the following commands to install the SaltStack repository and key:
|
||||
|
||||
``[root@guestshell guestshell]# yum install https://repo.saltstack.com/yum/redhat/salt-repo-latest-2.el7.noarch.rpm``
|
||||
|
||||
Run the following command to force yum to revalidate the cache for each repository.
|
||||
|
||||
``[root@guestshell guestshell]# yum clean expire-cache``
|
||||
|
||||
Install the Salt Minion.
|
||||
|
||||
``[root@guestshell guestshell]# yum install salt-minion``
|
||||
|
||||
STEP 4: Configure SaltStack Minion
|
||||
----------------------------------
|
||||
|
||||
Make the following changes to the ``/etc/salt/minion`` configuration file in the NX-OS GuestShell.
|
||||
|
||||
Change the ``master:`` directive to point to the SaltStack Master.
|
||||
|
||||
.. code:: diff
|
||||
|
||||
- #master: salt
|
||||
+ master: saltmaster.example.com
|
||||
|
||||
Change the ``id:`` directive to easily identify the minion running in the GuestShell.
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: diff
|
||||
|
||||
- #id: salt
|
||||
+ id: n3k-guestshell-minion
|
||||
|
||||
Start the Minon in the Guestshell and accept the key on the SaltStack Master.
|
||||
|
||||
``[root@guestshell ~]# systemctl start salt-minion``
|
||||
|
||||
.. code:: bash
|
||||
|
||||
saltmaster: salt-key -L
|
||||
Accepted Keys:
|
||||
Denied Keys:
|
||||
Unaccepted Keys:
|
||||
n3k-guestshell-minion
|
||||
Rejected Keys:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
saltmaster: salt-key -A
|
||||
The following keys are going to be accepted:
|
||||
Unaccepted Keys:
|
||||
n3k-guestshell-minion
|
||||
Proceed? [n/Y] Y
|
||||
Key for minion n3k-guestshell-minion accepted.
|
||||
|
||||
Ping the SaltStack Minon running in the Guestshell.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
saltmaster: salt n3k-guestshell-minion nxos.ping
|
||||
n3k-guestshell-minion:
|
||||
True
|
||||
|
||||
|
||||
GuestShell Salt Minion Persistence
|
||||
===================================
|
||||
|
||||
This section documents SaltStack Minion persistence in the ``guestshell`` after system restarts and high availability switchovers.
|
||||
|
||||
The ``guestshell`` container does not automatically sync filesystem changes from the active processor to the standby processor. This means that SaltStack Minion installation files and related file changes will not be present on the standby until they are manually synced with the following NX-OS exec command:
|
||||
|
||||
``guestshell sync``
|
||||
|
||||
The ``guestshell`` environment uses **systemd** for service management. The SaltStack Minion provides a generic systemd script when installed, but a slight modification as shown below is needed for nodes that run Salt in the management (or other vrf) namespace:
|
||||
|
||||
.. code:: diff
|
||||
|
||||
--- /usr/lib/systemd/system/salt-minion.service.old
|
||||
+++ /usr/lib/systemd/system/salt-minion.service
|
||||
[Unit]
|
||||
Description=The Salt Minion
|
||||
Documentation=man:salt-minion(1) file:///usr/share/doc/salt/html/contents.html
|
||||
https://docs.saltstack.com/en/latest/contents.html
|
||||
After=network.target salt-master.service
|
||||
|
||||
[Service]
|
||||
KillMode=process
|
||||
Type=notify
|
||||
NotifyAccess=all
|
||||
LimitNOFILE=8192
|
||||
|
||||
- ExecStart=/usr/bin/salt-minion
|
||||
+ ExecStart=/bin/nsenter --net=/var/run/netns/management -- /usr/bin/salt-minion
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
|
||||
Change the ``pidfile:`` directive to point to the ``/run`` ``tmpfs`` location in the GuestShell.
|
||||
|
||||
.. code:: diff
|
||||
|
||||
- #pidfile: /var/run/salt-minion.pid
|
||||
+ pidfile: /run/salt-minion.pid
|
||||
|
||||
Next, enable the SaltStack Minion systemd service (the ``enable`` command adds it to systemd for autostarting on the next boot) and optionally start it now:
|
||||
|
||||
.. code:: diff
|
||||
|
||||
systemctl enable salt-minion
|
||||
systemctl start salt-minion
|
||||
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. table:: Nexus Document References
|
||||
:widths: auto
|
||||
:align: center
|
||||
|
||||
=================== =====================
|
||||
References Description
|
||||
=================== =====================
|
||||
GuestShell_N9k_ N9k Guestshell Programmability Guide
|
||||
GuestShell_N3k_ N3k Guestshell Programmability Guide
|
||||
=================== =====================
|
||||
|
||||
.. _Guestshell_N9k: https://www.cisco.com/c/en/us/td/docs/switches/datacenter/nexus9000/sw/9-x/programmability/guide/b_Cisco_Nexus_9000_Series_NX-OS_Programmability_Guide_9x/b_Cisco_Nexus_9000_Series_NX-OS_Programmability_Guide_9x_chapter_0100.html
|
||||
|
||||
.. _GuestShell_N3k: https://www.cisco.com/c/en/us/td/docs/switches/datacenter/nexus3000/sw/programmability/9_x/b_Cisco_Nexus_3000_Series_NX-OS_Programmability_Guide_9x/b_Cisco_Nexus_3000_Series_NX-OS_Programmability_Guide_9x_chapter_0101.html
|
||||
|
|
@ -602,3 +602,27 @@ class LoggingRuntimeError(RuntimeError):
|
|||
"""
|
||||
Raised when we encounter an error while logging
|
||||
"""
|
||||
|
||||
|
||||
class NxosError(SaltException):
|
||||
"""
|
||||
NX-OS Base Exception class
|
||||
"""
|
||||
|
||||
|
||||
class NxosCliError(NxosError):
|
||||
"""
|
||||
NX-OS Cli Error raised when Cli command rejected by the NX-OS device
|
||||
"""
|
||||
|
||||
|
||||
class NxosClientError(NxosError):
|
||||
"""
|
||||
NX-OS Client Error raised for problems connecting to the NX-OS device
|
||||
"""
|
||||
|
||||
|
||||
class NxosRequestNotSupported(NxosError):
|
||||
"""
|
||||
Raised for unsupported client requests
|
||||
"""
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Grains for Cisco NX OS Switches Proxy minions
|
||||
Grains for Cisco NX-OS minions
|
||||
|
||||
.. versionadded: 2016.11.0
|
||||
|
||||
|
@ -12,10 +12,10 @@ from __future__ import absolute_import, print_function, unicode_literals
|
|||
|
||||
import logging
|
||||
|
||||
import salt.modules.nxos
|
||||
|
||||
# Import Salt Libs
|
||||
import salt.utils.nxos
|
||||
import salt.utils.platform
|
||||
from salt.exceptions import NxosClientError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -25,17 +25,20 @@ __virtualname__ = "nxos"
|
|||
|
||||
def __virtual__():
|
||||
try:
|
||||
if salt.utils.platform.is_proxy() and __opts__["proxy"]["proxytype"] == "nxos":
|
||||
return __virtualname__
|
||||
except KeyError:
|
||||
pass
|
||||
salt.utils.nxos.version_info()
|
||||
except NxosClientError as err:
|
||||
return False, err
|
||||
|
||||
return False
|
||||
return __virtualname__
|
||||
|
||||
|
||||
def proxy_functions(proxy=None):
|
||||
if proxy is None:
|
||||
return {}
|
||||
if proxy["nxos.initialized"]() is False:
|
||||
return {}
|
||||
return {"nxos": proxy["nxos.grains"]()}
|
||||
def system_information(proxy=None):
|
||||
if salt.utils.platform.is_proxy():
|
||||
if proxy is None:
|
||||
return {}
|
||||
if proxy["nxos.initialized"]() is False:
|
||||
return {}
|
||||
return {"nxos": proxy["nxos.grains"]()}
|
||||
else:
|
||||
data = salt.utils.nxos.version_info()
|
||||
return salt.utils.nxos.system_info(data)
|
||||
|
|
|
@ -1,49 +1,217 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Execution module for Cisco NX OS Switches Proxy minions
|
||||
Execution module for Cisco NX OS Switches.
|
||||
|
||||
.. versionadded:: 2016.11.0
|
||||
|
||||
For documentation on setting up the nxos proxy minion look in the documentation
|
||||
for :mod:`salt.proxy.nxos <salt.proxy.nxos>`.
|
||||
"""
|
||||
# Import Python libs
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
This module supports execution using a Proxy Minion or Native Minion:
|
||||
1) Proxy Minion: Connect over SSH or NX-API HTTP(S).
|
||||
See :mod:`salt.proxy.nxos <salt.proxy.nxos>` for proxy minion setup details.
|
||||
2) Native Minion: Connect over NX-API Unix Domain Socket (UDS).
|
||||
Install the minion inside the GuestShell running on the NX-OS device.
|
||||
|
||||
# Import Salt libs
|
||||
import salt.utils.platform
|
||||
:maturity: new
|
||||
:platform: nxos
|
||||
|
||||
__proxyenabled__ = ["nxos"]
|
||||
__virtualname__ = "nxos"
|
||||
.. note::
|
||||
|
||||
To use this module over remote NX-API the feature must be enabled on the
|
||||
NX-OS device by executing ``feature nxapi`` in configuration mode.
|
||||
|
||||
def __virtual__():
|
||||
if salt.utils.platform.is_proxy():
|
||||
return __virtualname__
|
||||
return (
|
||||
False,
|
||||
"The nxos execution module failed to load: only available on proxy minions.",
|
||||
)
|
||||
This is not required for NX-API over UDS.
|
||||
|
||||
|
||||
def system_info():
|
||||
"""
|
||||
Return system information for grains of the NX OS proxy minion
|
||||
Configuration example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.system_info
|
||||
switch# conf t
|
||||
switch(config)# feature nxapi
|
||||
|
||||
To check that NX-API is properly enabled, execute ``show nxapi``.
|
||||
|
||||
Output example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
switch# show nxapi
|
||||
nxapi enabled
|
||||
HTTPS Listen on port 443
|
||||
|
||||
Native minon configuration options:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
nxos:
|
||||
cookie: 'username'
|
||||
save_config: False
|
||||
|
||||
cookie
|
||||
Use the option to override the default cookie 'admin:local' when
|
||||
connecting over UDS and use 'username:local' instead. This is needed when
|
||||
running the salt-minion in the GuestShell using a non-admin user.
|
||||
|
||||
This option is ignored for SSH and NX-API Proxy minions.
|
||||
|
||||
save_config:
|
||||
If True, 'copy running-config starting-config' is issues for every
|
||||
configuration command.
|
||||
If False, Running config is not saved to startup config
|
||||
Default: True
|
||||
|
||||
The recommended approach is to use the `save_running_config` function
|
||||
instead of this option to improve performance. The default behavior
|
||||
controlled by this option is preserved for backwards compatibility.
|
||||
|
||||
|
||||
The APIs defined in this execution module can also be executed using
|
||||
salt-call from the GuestShell environment as follows.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt-call --local nxos.show 'show lldp neighbors' raw_text
|
||||
|
||||
.. note::
|
||||
|
||||
The functions in this module should be executed like so:
|
||||
|
||||
salt '*' nxos.<function>
|
||||
salt '*' nxos.get_user username=admin
|
||||
|
||||
For backwards compatibility, the following syntax will be supported
|
||||
until the Sodium release.
|
||||
|
||||
salt '*' nxos.cmd <function>
|
||||
salt '*' nxos.cmd get_user username=admin
|
||||
"""
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
# Import python stdlib
|
||||
import ast
|
||||
import difflib
|
||||
import logging
|
||||
import re
|
||||
from socket import error as socket_error
|
||||
|
||||
# Import Salt libs
|
||||
import salt.utils.nxos
|
||||
import salt.utils.platform
|
||||
from salt.exceptions import CommandExecutionError, NxosError, SaltInvocationError
|
||||
from salt.ext import six
|
||||
from salt.utils.args import clean_kwargs
|
||||
from salt.utils.pycrypto import gen_hash
|
||||
from salt.utils.versions import warn_until
|
||||
|
||||
__virtualname__ = "nxos"
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
DEVICE_DETAILS = {"grains_cache": {}}
|
||||
COPY_RS = "copy running-config startup-config"
|
||||
|
||||
CONNECTION_ERROR_MSG = """
|
||||
Unable to send command to the NX-OS device.
|
||||
Please verify the following and re-try:
|
||||
- 'feature ssh' must be enabled for SSH proxy minions.
|
||||
- 'feature nxapi' must be enabled for NX-API proxy minions.
|
||||
- Settings in the proxy minion configuration file must match device settings.
|
||||
- NX-OS device is reachable from the Salt Master.
|
||||
"""
|
||||
|
||||
|
||||
def __virtual__():
|
||||
return __virtualname__
|
||||
|
||||
|
||||
def ping(**kwargs):
|
||||
"""
|
||||
return cmd("system_info")
|
||||
Ping the device on the other end of the connection.
|
||||
|
||||
.. code-block: bash
|
||||
|
||||
salt '*' nxos.ping
|
||||
"""
|
||||
if salt.utils.platform.is_proxy():
|
||||
return __proxy__["nxos.ping"]()
|
||||
return __utils__["nxos.ping"](**kwargs)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Device Get Functions
|
||||
# -----------------------------------------------------------------------------
|
||||
def check_password(username, password, encrypted=False, **kwargs):
|
||||
"""
|
||||
Verify user password.
|
||||
|
||||
username
|
||||
Username on which to perform password check
|
||||
|
||||
password
|
||||
Password to check
|
||||
|
||||
encrypted
|
||||
Whether or not the password is encrypted
|
||||
Default: False
|
||||
|
||||
.. code-block: bash
|
||||
|
||||
salt '*' nxos.check_password username=admin password=admin
|
||||
salt '*' nxos.check_password username=admin \\
|
||||
password='$5$2fWwO2vK$s7.Hr3YltMNHuhywQQ3nfOd.gAPHgs3SOBYYdGT3E.A' \\
|
||||
encrypted=True
|
||||
"""
|
||||
hash_algorithms = {
|
||||
"1": "md5",
|
||||
"2a": "blowfish",
|
||||
"5": "sha256",
|
||||
"6": "sha512",
|
||||
}
|
||||
password_line = get_user(username, **kwargs)
|
||||
if not password_line:
|
||||
return None
|
||||
if "!" in password_line:
|
||||
return False
|
||||
cur_hash = re.search(r"(\$[0-6](?:\$[^$ ]+)+)", password_line).group(0)
|
||||
if encrypted is False:
|
||||
hash_type, cur_salt, hashed_pass = re.search(
|
||||
r"^\$([0-6])\$([^$]+)\$(.*)$", cur_hash
|
||||
).groups()
|
||||
new_hash = gen_hash(
|
||||
crypt_salt=cur_salt,
|
||||
password=password,
|
||||
algorithm=hash_algorithms[hash_type],
|
||||
force=True,
|
||||
)
|
||||
else:
|
||||
new_hash = password
|
||||
if new_hash == cur_hash:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def check_role(username, role, **kwargs):
|
||||
"""
|
||||
Verify role assignment for user.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.check_role username=admin role=network-admin
|
||||
"""
|
||||
return role in get_roles(username, **kwargs)
|
||||
|
||||
|
||||
def cmd(command, *args, **kwargs):
|
||||
"""
|
||||
run commands from __proxy__
|
||||
:mod:`salt.proxy.nxos<salt.proxy.nxos>`
|
||||
NOTE: This function is preserved for backwards compatibilty. This allows
|
||||
commands to be executed using either of the following syntactic forms.
|
||||
|
||||
salt '*' nxos.cmd <function>
|
||||
|
||||
or
|
||||
|
||||
salt '*' nxos.<function>
|
||||
|
||||
command
|
||||
function from `salt.proxy.nxos` to run
|
||||
function from `salt.modules.nxos` to run
|
||||
|
||||
args
|
||||
positional args to pass to `command` function
|
||||
|
@ -57,11 +225,660 @@ def cmd(command, *args, **kwargs):
|
|||
salt '*' nxos.cmd show_run
|
||||
salt '*' nxos.cmd check_password username=admin password='$5$lkjsdfoi$blahblahblah' encrypted=True
|
||||
"""
|
||||
proxy_prefix = __opts__["proxy"]["proxytype"]
|
||||
proxy_cmd = ".".join([proxy_prefix, command])
|
||||
if proxy_cmd not in __proxy__:
|
||||
return False
|
||||
warn_until("Silicon", "'nxos.cmd COMMAND' is deprecated in favor of 'nxos.COMMAND'")
|
||||
|
||||
for k in list(kwargs):
|
||||
if k.startswith("__pub_"):
|
||||
kwargs.pop(k)
|
||||
return __proxy__[proxy_cmd](*args, **kwargs)
|
||||
local_command = ".".join(["nxos", command])
|
||||
log.info("local command: {}".format(local_command))
|
||||
if local_command not in __salt__:
|
||||
return False
|
||||
return __salt__[local_command](*args, **kwargs)
|
||||
|
||||
|
||||
def find(pattern, **kwargs):
|
||||
"""
|
||||
Find all instances where the pattern is in the running configuration.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.find '^snmp-server.*$'
|
||||
|
||||
.. note::
|
||||
This uses the `re.MULTILINE` regex format for python, and runs the
|
||||
regex against the whole show_run output.
|
||||
"""
|
||||
matcher = re.compile(pattern, re.MULTILINE)
|
||||
return matcher.findall(show_run(**kwargs))
|
||||
|
||||
|
||||
def get_roles(username, **kwargs):
|
||||
"""
|
||||
Get roles assigned to a username.
|
||||
|
||||
.. code-block: bash
|
||||
|
||||
salt '*' nxos.get_roles username=admin
|
||||
"""
|
||||
user = get_user(username)
|
||||
if not user:
|
||||
return []
|
||||
command = "show user-account {0}".format(username)
|
||||
info = show(command, **kwargs)
|
||||
if isinstance(info, list):
|
||||
info = info[0]
|
||||
roles = re.search(r"^\s*roles:(.*)$", info, re.MULTILINE)
|
||||
if roles:
|
||||
roles = roles.group(1).strip().split(" ")
|
||||
else:
|
||||
roles = []
|
||||
return roles
|
||||
|
||||
|
||||
def get_user(username, **kwargs):
|
||||
"""
|
||||
Get username line from switch.
|
||||
|
||||
.. code-block: bash
|
||||
|
||||
salt '*' nxos.get_user username=admin
|
||||
"""
|
||||
command = 'show run | include "^username {0} password 5 "'.format(username)
|
||||
info = show(command, **kwargs)
|
||||
if isinstance(info, list):
|
||||
info = info[0]
|
||||
return info
|
||||
|
||||
|
||||
def grains(**kwargs):
|
||||
"""
|
||||
Get grains for minion.
|
||||
|
||||
.. code-block: bash
|
||||
|
||||
salt '*' nxos.grains
|
||||
"""
|
||||
if not DEVICE_DETAILS["grains_cache"]:
|
||||
ret = salt.utils.nxos.system_info(show_ver(**kwargs))
|
||||
log.debug(ret)
|
||||
DEVICE_DETAILS["grains_cache"].update(ret["nxos"])
|
||||
return DEVICE_DETAILS["grains_cache"]
|
||||
|
||||
|
||||
def grains_refresh(**kwargs):
|
||||
"""
|
||||
Refresh the grains for the NX-OS device.
|
||||
|
||||
.. code-block: bash
|
||||
|
||||
salt '*' nxos.grains_refresh
|
||||
"""
|
||||
DEVICE_DETAILS["grains_cache"] = {}
|
||||
return grains(**kwargs)
|
||||
|
||||
|
||||
def sendline(command, method="cli_show_ascii", **kwargs):
|
||||
"""
|
||||
Send arbitrary commands to the NX-OS device.
|
||||
|
||||
command
|
||||
The command or list of commands to be sent.
|
||||
['cmd1', 'cmd2'] is converted to 'cmd1 ; cmd2'.
|
||||
|
||||
method:
|
||||
``cli_show_ascii``: Return raw test or unstructured output.
|
||||
``cli_show``: Return structured output.
|
||||
``cli_conf``: Send configuration commands to the device.
|
||||
Defaults to ``cli_show_ascii``.
|
||||
NOTE: method is ignored for SSH proxy minion. All data is returned
|
||||
unstructured.
|
||||
|
||||
error_pattern
|
||||
Use the option to pass in a regular expression to search for in the
|
||||
returned output of the command that indicates an error has occurred.
|
||||
This option is only used when proxy minion connection type is ssh and
|
||||
otherwise ignored.
|
||||
|
||||
.. code-block: bash
|
||||
|
||||
salt '*' nxos.sendline 'show run | include "^username admin password"'
|
||||
salt '*' nxos.sendline "['show inventory', 'show version']"
|
||||
salt '*' nxos.sendline 'show inventory ; show version'
|
||||
"""
|
||||
smethods = ["cli_show_ascii", "cli_show", "cli_conf"]
|
||||
if method not in smethods:
|
||||
msg = """
|
||||
INPUT ERROR: Second argument 'method' must be one of {0}
|
||||
Value passed: {1}
|
||||
Hint: White space separated commands should be wrapped by double quotes
|
||||
""".format(
|
||||
smethods, method
|
||||
)
|
||||
return msg
|
||||
|
||||
try:
|
||||
if salt.utils.platform.is_proxy():
|
||||
return __proxy__["nxos.sendline"](command, method, **kwargs)
|
||||
else:
|
||||
return _nxapi_request(command, method, **kwargs)
|
||||
except socket_error as e:
|
||||
return e.strerror + "\n" + CONNECTION_ERROR_MSG
|
||||
except NxosError as e:
|
||||
return e.strerror + "\n" + CONNECTION_ERROR_MSG
|
||||
|
||||
|
||||
def show(commands, raw_text=True, **kwargs):
|
||||
"""
|
||||
Execute one or more show (non-configuration) commands.
|
||||
|
||||
commands
|
||||
The commands to be executed.
|
||||
|
||||
raw_text: ``True``
|
||||
Whether to return raw text or structured data.
|
||||
NOTE: raw_text option is ignored for SSH proxy minion. Data is
|
||||
returned unstructured.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt-call --local nxos.show 'show version'
|
||||
salt '*' nxos.show 'show bgp sessions ; show processes' raw_text=False
|
||||
salt 'regular-minion' nxos.show 'show interfaces' host=sw01.example.com username=test password=test
|
||||
"""
|
||||
warn_until(
|
||||
"Silicon",
|
||||
"'nxos.show commands' is deprecated in favor of 'nxos.sendline commands'",
|
||||
)
|
||||
|
||||
if not isinstance(raw_text, bool):
|
||||
msg = """
|
||||
INPUT ERROR: Second argument 'raw_text' must be either True or False
|
||||
Value passed: {0}
|
||||
Hint: White space separated show commands should be wrapped by double quotes
|
||||
""".format(
|
||||
raw_text
|
||||
)
|
||||
return msg
|
||||
|
||||
if raw_text:
|
||||
method = "cli_show_ascii"
|
||||
else:
|
||||
method = "cli_show"
|
||||
|
||||
response_list = sendline(commands, method, **kwargs)
|
||||
if isinstance(response_list, list):
|
||||
ret = [response for response in response_list if response]
|
||||
if not ret:
|
||||
ret = [""]
|
||||
return ret
|
||||
else:
|
||||
return response_list
|
||||
|
||||
|
||||
def show_ver(**kwargs):
|
||||
"""
|
||||
Shortcut to run `show version` on the NX-OS device.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.show_ver
|
||||
"""
|
||||
command = "show version"
|
||||
info = show(command, **kwargs)
|
||||
if isinstance(info, list):
|
||||
info = info[0]
|
||||
return info
|
||||
|
||||
|
||||
def show_run(**kwargs):
|
||||
"""
|
||||
Shortcut to run `show running-config` on the NX-OS device.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.show_run
|
||||
"""
|
||||
command = "show running-config"
|
||||
info = show(command, **kwargs)
|
||||
if isinstance(info, list):
|
||||
info = info[0]
|
||||
return info
|
||||
|
||||
|
||||
def system_info(**kwargs):
|
||||
"""
|
||||
Return system information for grains of the minion.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.system_info
|
||||
"""
|
||||
warn_until("Silicon", "'nxos.system_info' is deprecated in favor of 'nxos.grains'")
|
||||
return salt.utils.nxos.system_info(show_ver(**kwargs))["nxos"]
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Device Set Functions
|
||||
# -----------------------------------------------------------------------------
|
||||
def add_config(lines, **kwargs):
|
||||
"""
|
||||
Add one or more config lines to the NX-OS device running config.
|
||||
|
||||
lines
|
||||
Configuration lines to add
|
||||
|
||||
save_config
|
||||
If False, don't save configuration commands to startup configuration.
|
||||
If True, save configuration to startup configuration.
|
||||
Default: True
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.add_config 'snmp-server community TESTSTRINGHERE group network-operator'
|
||||
|
||||
.. note::
|
||||
For more than one config added per command, lines should be a list.
|
||||
"""
|
||||
warn_until(
|
||||
"Silicon",
|
||||
"'nxos.add_config lines' is deprecated in favor of 'nxos.config commands'",
|
||||
)
|
||||
|
||||
kwargs = clean_kwargs(**kwargs)
|
||||
return config(lines, **kwargs)
|
||||
|
||||
|
||||
def config(
|
||||
commands=None,
|
||||
config_file=None,
|
||||
template_engine="jinja",
|
||||
context=None,
|
||||
defaults=None,
|
||||
saltenv="base",
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Configures the Nexus switch with the specified commands.
|
||||
|
||||
This method is used to send configuration commands to the switch. It
|
||||
will take either a string or a list and prepend the necessary commands
|
||||
to put the session into config mode.
|
||||
|
||||
.. warning::
|
||||
|
||||
All the commands will be applied directly to the running-config.
|
||||
|
||||
config_file
|
||||
The source file with the configuration commands to be sent to the
|
||||
device.
|
||||
|
||||
The file can also be a template that can be rendered using the template
|
||||
engine of choice.
|
||||
|
||||
This can be specified using the absolute path to the file, or using one
|
||||
of the following URL schemes:
|
||||
|
||||
- ``salt://``, to fetch the file from the Salt fileserver.
|
||||
- ``http://`` or ``https://``
|
||||
- ``ftp://``
|
||||
- ``s3://``
|
||||
- ``swift://``
|
||||
|
||||
commands
|
||||
The commands to send to the switch in config mode. If the commands
|
||||
argument is a string it will be cast to a list.
|
||||
The list of commands will also be prepended with the necessary commands
|
||||
to put the session in config mode.
|
||||
|
||||
.. note::
|
||||
|
||||
This argument is ignored when ``config_file`` is specified.
|
||||
|
||||
template_engine: ``jinja``
|
||||
The template engine to use when rendering the source file. Default:
|
||||
``jinja``. To simply fetch the file without attempting to render, set
|
||||
this argument to ``None``.
|
||||
|
||||
context
|
||||
Variables to add to the template context.
|
||||
|
||||
defaults
|
||||
Default values of the context_dict.
|
||||
|
||||
save_config
|
||||
If False, don't save configuration commands to startup configuration.
|
||||
If True, save configuration to startup configuration.
|
||||
Default: True
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.config commands="['spanning-tree mode mstp']"
|
||||
salt '*' nxos.config config_file=salt://config.txt
|
||||
salt '*' nxos.config config_file=https://bit.ly/2LGLcDy context="{'servers': ['1.2.3.4']}"
|
||||
"""
|
||||
kwargs = clean_kwargs(**kwargs)
|
||||
initial_config = show("show running-config", **kwargs)
|
||||
if isinstance(initial_config, list):
|
||||
initial_config = initial_config[0]
|
||||
if config_file:
|
||||
file_str = __salt__["cp.get_file_str"](config_file, saltenv=saltenv)
|
||||
if file_str is False:
|
||||
raise CommandExecutionError("Source file {} not found".format(config_file))
|
||||
elif commands:
|
||||
if isinstance(commands, (six.string_types, six.text_type)):
|
||||
commands = [commands]
|
||||
file_str = "\n".join(commands)
|
||||
# unify all the commands in a single file, to render them in a go
|
||||
else:
|
||||
raise CommandExecutionError(
|
||||
"Either arg <config_file> or <commands> must be specified"
|
||||
)
|
||||
if template_engine:
|
||||
file_str = __salt__["file.apply_template_on_contents"](
|
||||
file_str, template_engine, context, defaults, saltenv
|
||||
)
|
||||
# whatever the source of the commands would be, split them line by line
|
||||
commands = [line for line in file_str.splitlines() if line.strip()]
|
||||
try:
|
||||
config_result = _configure_device(commands, **kwargs)
|
||||
except socket_error as e:
|
||||
return e.strerror + "\n" + CONNECTION_ERROR_MSG
|
||||
except NxosError as e:
|
||||
return e.strerror + "\n" + CONNECTION_ERROR_MSG
|
||||
|
||||
config_result = _parse_config_result(config_result)
|
||||
current_config = show("show running-config", **kwargs)
|
||||
if isinstance(current_config, list):
|
||||
current_config = current_config[0]
|
||||
diff = difflib.unified_diff(
|
||||
initial_config.splitlines(1)[4:], current_config.splitlines(1)[4:]
|
||||
)
|
||||
clean_diff = "".join([x.replace("\r", "") for x in diff])
|
||||
head = "COMMAND_LIST: "
|
||||
cc = config_result[0]
|
||||
cr = config_result[1]
|
||||
return head + cc + "\n" + cr + "\n" + clean_diff
|
||||
|
||||
|
||||
def _parse_config_result(data):
|
||||
command_list = " ; ".join([x.strip() for x in data[0]])
|
||||
config_result = data[1]
|
||||
if isinstance(config_result, list):
|
||||
result = ""
|
||||
if isinstance(config_result[0], dict):
|
||||
for key in config_result[0]:
|
||||
result += config_result[0][key]
|
||||
config_result = result
|
||||
else:
|
||||
config_result = config_result[0]
|
||||
return [command_list, config_result]
|
||||
|
||||
|
||||
def delete_config(lines, **kwargs):
|
||||
"""
|
||||
Delete one or more config lines to the switch running config.
|
||||
|
||||
lines
|
||||
Configuration lines to remove.
|
||||
|
||||
save_config
|
||||
If False, don't save configuration commands to startup configuration.
|
||||
If True, save configuration to startup configuration.
|
||||
Default: True
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.delete_config 'snmp-server community TESTSTRINGHERE group network-operator'
|
||||
|
||||
.. note::
|
||||
For more than one config deleted per command, lines should be a list.
|
||||
"""
|
||||
if not isinstance(lines, list):
|
||||
lines = [lines]
|
||||
for i, _ in enumerate(lines):
|
||||
lines[i] = "no " + lines[i]
|
||||
result = None
|
||||
try:
|
||||
kwargs = clean_kwargs(**kwargs)
|
||||
result = config(lines, **kwargs)
|
||||
except CommandExecutionError as e:
|
||||
# Some commands will generate error code 400 if they do not exist
|
||||
# and we try to remove them. These can be ignored.
|
||||
if ast.literal_eval(e.message)["code"] != "400":
|
||||
raise
|
||||
return result
|
||||
|
||||
|
||||
def remove_user(username, **kwargs):
|
||||
"""
|
||||
Remove user from switch.
|
||||
|
||||
username
|
||||
Username to remove
|
||||
|
||||
save_config
|
||||
If False, don't save configuration commands to startup configuration.
|
||||
If True, save configuration to startup configuration.
|
||||
Default: True
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.remove_user username=daniel
|
||||
"""
|
||||
user_line = "no username {0}".format(username)
|
||||
kwargs = clean_kwargs(**kwargs)
|
||||
return config(user_line, **kwargs)
|
||||
|
||||
|
||||
def replace(old_value, new_value, full_match=False, **kwargs):
|
||||
"""
|
||||
Replace string or full line matches in switch's running config.
|
||||
|
||||
If full_match is set to True, then the whole line will need to be matched
|
||||
as part of the old value.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.replace 'TESTSTRINGHERE' 'NEWTESTSTRINGHERE'
|
||||
"""
|
||||
if full_match is False:
|
||||
matcher = re.compile("^.*{0}.*$".format(re.escape(old_value)), re.MULTILINE)
|
||||
repl = re.compile(re.escape(old_value))
|
||||
else:
|
||||
matcher = re.compile(old_value, re.MULTILINE)
|
||||
repl = re.compile(old_value)
|
||||
|
||||
lines = {"old": [], "new": []}
|
||||
for line in matcher.finditer(show_run()):
|
||||
lines["old"].append(line.group(0))
|
||||
lines["new"].append(repl.sub(new_value, line.group(0)))
|
||||
|
||||
kwargs = clean_kwargs(**kwargs)
|
||||
if lines["old"]:
|
||||
delete_config(lines["old"], **kwargs)
|
||||
if lines["new"]:
|
||||
add_config(lines["new"], **kwargs)
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def save_running_config(**kwargs):
|
||||
"""
|
||||
Save the running configuration to startup configuration.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.save_running_config
|
||||
"""
|
||||
return config(COPY_RS, **kwargs)
|
||||
|
||||
|
||||
def set_password(
|
||||
username,
|
||||
password,
|
||||
encrypted=False,
|
||||
role=None,
|
||||
crypt_salt=None,
|
||||
algorithm="sha256",
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Set users password on switch.
|
||||
|
||||
username
|
||||
Username to configure
|
||||
|
||||
password
|
||||
Password to configure for username
|
||||
|
||||
encrypted
|
||||
Whether or not to encrypt the password
|
||||
Default: False
|
||||
|
||||
role
|
||||
Configure role for the username
|
||||
Default: None
|
||||
|
||||
crypt_salt
|
||||
Configure crypt_salt setting
|
||||
Default: None
|
||||
|
||||
alogrithm
|
||||
Encryption algorithm
|
||||
Default: sha256
|
||||
|
||||
save_config
|
||||
If False, don't save configuration commands to startup configuration.
|
||||
If True, save configuration to startup configuration.
|
||||
Default: True
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.set_password admin TestPass
|
||||
salt '*' nxos.set_password admin \\
|
||||
password='$5$2fWwO2vK$s7.Hr3YltMNHuhywQQ3nfOd.gAPHgs3SOBYYdGT3E.A' \\
|
||||
encrypted=True
|
||||
"""
|
||||
if algorithm == "blowfish":
|
||||
raise SaltInvocationError("Hash algorithm requested isn't available on nxos")
|
||||
get_user(username, **kwargs) # verify user exists
|
||||
if encrypted is False:
|
||||
hashed_pass = gen_hash(
|
||||
crypt_salt=crypt_salt, password=password, algorithm=algorithm, force=True
|
||||
)
|
||||
else:
|
||||
hashed_pass = password
|
||||
password_line = "username {0} password 5 {1}".format(username, hashed_pass)
|
||||
if role is not None:
|
||||
password_line += " role {0}".format(role)
|
||||
kwargs = clean_kwargs(**kwargs)
|
||||
return config(password_line, **kwargs)
|
||||
|
||||
|
||||
def set_role(username, role, **kwargs):
|
||||
"""
|
||||
Assign role to username.
|
||||
|
||||
username
|
||||
Username for role configuration
|
||||
|
||||
role
|
||||
Configure role for username
|
||||
|
||||
save_config
|
||||
If False, don't save configuration commands to startup configuration.
|
||||
If True, save configuration to startup configuration.
|
||||
Default: True
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.set_role username=daniel role=vdc-admin.
|
||||
"""
|
||||
role_line = "username {0} role {1}".format(username, role)
|
||||
kwargs = clean_kwargs(**kwargs)
|
||||
return config(role_line, **kwargs)
|
||||
|
||||
|
||||
def unset_role(username, role, **kwargs):
|
||||
"""
|
||||
Remove role from username.
|
||||
|
||||
username
|
||||
Username for role removal
|
||||
|
||||
role
|
||||
Role to remove
|
||||
|
||||
save_config
|
||||
If False, don't save configuration commands to startup configuration.
|
||||
If True, save configuration to startup configuration.
|
||||
Default: True
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.unset_role username=daniel role=vdc-admin
|
||||
"""
|
||||
role_line = "no username {0} role {1}".format(username, role)
|
||||
kwargs = clean_kwargs(**kwargs)
|
||||
return config(role_line, **kwargs)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# helper functions
|
||||
# -----------------------------------------------------------------------------
|
||||
def _configure_device(commands, **kwargs):
|
||||
"""
|
||||
Helper function to send configuration commands to the device over a
|
||||
proxy minion or native minion using NX-API or SSH.
|
||||
"""
|
||||
if salt.utils.platform.is_proxy():
|
||||
return __proxy__["nxos.proxy_config"](commands, **kwargs)
|
||||
else:
|
||||
return _nxapi_config(commands, **kwargs)
|
||||
|
||||
|
||||
def _nxapi_config(commands, **kwargs):
|
||||
"""
|
||||
Helper function to send configuration commands using NX-API.
|
||||
"""
|
||||
api_kwargs = __salt__["config.get"]("nxos", {})
|
||||
api_kwargs.update(**kwargs)
|
||||
if not isinstance(commands, list):
|
||||
commands = [commands]
|
||||
try:
|
||||
ret = _nxapi_request(commands, **kwargs)
|
||||
if api_kwargs.get("save_config"):
|
||||
_nxapi_request(COPY_RS, **kwargs)
|
||||
for each in ret:
|
||||
if "Failure" in each:
|
||||
log.error(each)
|
||||
except CommandExecutionError as e:
|
||||
log.error(e)
|
||||
return [commands, repr(e)]
|
||||
return [commands, ret]
|
||||
|
||||
|
||||
def _nxapi_request(commands, method="cli_conf", **kwargs):
|
||||
"""
|
||||
Helper function to send exec and config requests over NX-API.
|
||||
|
||||
commands
|
||||
The exec or config commands to be sent.
|
||||
|
||||
method: ``cli_show``
|
||||
``cli_show_ascii``: Return raw test or unstructured output.
|
||||
``cli_show``: Return structured output.
|
||||
``cli_conf``: Send configuration commands to the device.
|
||||
Defaults to ``cli_conf``.
|
||||
"""
|
||||
if salt.utils.platform.is_proxy():
|
||||
return __proxy__["nxos._nxapi_request"](commands, method=method, **kwargs)
|
||||
else:
|
||||
api_kwargs = __salt__["config.get"]("nxos", {})
|
||||
api_kwargs.update(**kwargs)
|
||||
return __utils__["nxos.nxapi_request"](commands, method=method, **api_kwargs)
|
||||
|
|
406
salt/modules/nxos_upgrade.py
Normal file
406
salt/modules/nxos_upgrade.py
Normal file
|
@ -0,0 +1,406 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2018 Cisco and/or its affiliates.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""
|
||||
Execution module to upgrade Cisco NX-OS Switches.
|
||||
|
||||
.. versionadded:: xxxx.xx.x
|
||||
|
||||
This module supports execution using a Proxy Minion or Native Minion:
|
||||
1) Proxy Minion: Connect over SSH or NX-API HTTP(S).
|
||||
See :mod:`salt.proxy.nxos <salt.proxy.nxos>` for proxy minion setup details.
|
||||
2) Native Minion: Connect over NX-API Unix Domain Socket (UDS).
|
||||
Install the minion inside the GuestShell running on the NX-OS device.
|
||||
|
||||
:maturity: new
|
||||
:platform: nxos
|
||||
:codeauthor: Michael G Wiebe
|
||||
|
||||
.. note::
|
||||
|
||||
To use this module over remote NX-API the feature must be enabled on the
|
||||
NX-OS device by executing ``feature nxapi`` in configuration mode.
|
||||
|
||||
This is not required for NX-API over UDS.
|
||||
|
||||
Configuration example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
switch# conf t
|
||||
switch(config)# feature nxapi
|
||||
|
||||
To check that NX-API is properly enabled, execute ``show nxapi``.
|
||||
|
||||
Output example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
switch# show nxapi
|
||||
nxapi enabled
|
||||
HTTPS Listen on port 443
|
||||
"""
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
# Import python stdlib
|
||||
import ast
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
|
||||
# Import Salt libs
|
||||
from salt.exceptions import CommandExecutionError, NxosError
|
||||
from salt.ext.six import string_types
|
||||
from salt.ext.six.moves import range
|
||||
|
||||
__virtualname__ = "nxos"
|
||||
__virtual_aliases__ = ("nxos_upgrade",)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def __virtual__():
|
||||
return __virtualname__
|
||||
|
||||
|
||||
def check_upgrade_impact(system_image, kickstart_image=None, issu=True, **kwargs):
|
||||
"""
|
||||
Display upgrade impact information without actually upgrading the device.
|
||||
|
||||
system_image (Mandatory Option)
|
||||
Path on bootflash: to system image upgrade file.
|
||||
|
||||
kickstart_image
|
||||
Path on bootflash: to kickstart image upgrade file.
|
||||
(Not required if using combined system/kickstart image file)
|
||||
Default: None
|
||||
|
||||
issu
|
||||
In Service Software Upgrade (non-disruptive). When True,
|
||||
the upgrade will abort if issu is not possible.
|
||||
When False: Force (disruptive) Upgrade/Downgrade.
|
||||
Default: True
|
||||
|
||||
timeout
|
||||
Timeout in seconds for long running 'install all' impact command.
|
||||
Default: 900
|
||||
|
||||
error_pattern
|
||||
Use the option to pass in a regular expression to search for in the
|
||||
output of the 'install all impact' command that indicates an error
|
||||
has occurred. This option is only used when proxy minion connection
|
||||
type is ssh and otherwise ignored.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt 'n9k' nxos.check_upgrade_impact system_image=nxos.9.2.1.bin
|
||||
salt 'n7k' nxos.check_upgrade_impact system_image=n7000-s2-dk9.8.1.1.bin \\
|
||||
kickstart_image=n7000-s2-kickstart.8.1.1.bin issu=False
|
||||
"""
|
||||
# Input Validation
|
||||
if not isinstance(issu, bool):
|
||||
return "Input Error: The [issu] parameter must be either True or False"
|
||||
|
||||
si = system_image
|
||||
ki = kickstart_image
|
||||
dev = "bootflash"
|
||||
cmd = "terminal dont-ask ; show install all impact"
|
||||
|
||||
if ki is not None:
|
||||
cmd = cmd + " kickstart {0}:{1} system {0}:{2}".format(dev, ki, si)
|
||||
else:
|
||||
cmd = cmd + " nxos {0}:{1}".format(dev, si)
|
||||
|
||||
if issu and ki is None:
|
||||
cmd = cmd + " non-disruptive"
|
||||
|
||||
log.info("Check upgrade impact using command: '{}'".format(cmd))
|
||||
kwargs.update({"timeout": kwargs.get("timeout", 900)})
|
||||
error_pattern_list = [
|
||||
"Another install procedure may be in progress",
|
||||
"Pre-upgrade check failed",
|
||||
]
|
||||
kwargs.update({"error_pattern": error_pattern_list})
|
||||
|
||||
# Execute Upgrade Impact Check
|
||||
try:
|
||||
impact_check = __salt__["nxos.sendline"](cmd, **kwargs)
|
||||
except CommandExecutionError as e:
|
||||
impact_check = ast.literal_eval(e.message)
|
||||
return _parse_upgrade_data(impact_check)
|
||||
|
||||
|
||||
def upgrade(system_image, kickstart_image=None, issu=True, **kwargs):
|
||||
"""
|
||||
Upgrade NX-OS switch.
|
||||
|
||||
system_image (Mandatory Option)
|
||||
Path on bootflash: to system image upgrade file.
|
||||
|
||||
kickstart_image
|
||||
Path on bootflash: to kickstart image upgrade file.
|
||||
(Not required if using combined system/kickstart image file)
|
||||
Default: None
|
||||
|
||||
issu
|
||||
Set this option to True when an In Service Software Upgrade or
|
||||
non-disruptive upgrade is required. The upgrade will abort if issu is
|
||||
not possible.
|
||||
Default: True
|
||||
|
||||
timeout
|
||||
Timeout in seconds for long running 'install all' upgrade command.
|
||||
Default: 900
|
||||
|
||||
error_pattern
|
||||
Use the option to pass in a regular expression to search for in the
|
||||
output of the 'install all upgrade command that indicates an error
|
||||
has occurred. This option is only used when proxy minion connection
|
||||
type is ssh and otherwise ignored.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt 'n9k' nxos.upgrade system_image=nxos.9.2.1.bin
|
||||
salt 'n7k' nxos.upgrade system_image=n7000-s2-dk9.8.1.1.bin \\
|
||||
kickstart_image=n7000-s2-kickstart.8.1.1.bin issu=False
|
||||
"""
|
||||
# Input Validation
|
||||
if not isinstance(issu, bool):
|
||||
return "Input Error: The [issu] parameter must be either True or False"
|
||||
|
||||
impact = None
|
||||
upgrade = None
|
||||
maxtry = 60
|
||||
for attempt in range(1, maxtry):
|
||||
# Gather impact data first. It's possible to loose upgrade data
|
||||
# when the switch reloads or switches over to the inactive supervisor.
|
||||
# The impact data will be used if data being collected during the
|
||||
# upgrade is lost.
|
||||
if impact is None:
|
||||
log.info("Gathering impact data")
|
||||
impact = check_upgrade_impact(system_image, kickstart_image, issu, **kwargs)
|
||||
if impact["installing"]:
|
||||
log.info("Another show impact in progress... wait and retry")
|
||||
time.sleep(30)
|
||||
continue
|
||||
# If we are upgrading from a system running a separate system and
|
||||
# kickstart image to a combined image or vice versa then the impact
|
||||
# check will return a syntax error as it's not supported.
|
||||
# Skip the impact check in this case and attempt the upgrade.
|
||||
if impact["invalid_command"]:
|
||||
impact = False
|
||||
continue
|
||||
log.info("Impact data gathered:\n{}".format(impact))
|
||||
|
||||
# Check to see if conditions are sufficent to return the impact
|
||||
# data and not proceed with the actual upgrade.
|
||||
#
|
||||
# Impact data indicates the upgrade or downgrade will fail
|
||||
if impact["error_data"]:
|
||||
return impact
|
||||
# Requested ISSU but ISSU is not possible
|
||||
if issu and not impact["upgrade_non_disruptive"]:
|
||||
impact["error_data"] = impact["upgrade_data"]
|
||||
return impact
|
||||
# Impact data indicates a failure and no module_data collected
|
||||
if not impact["succeeded"] and not impact["module_data"]:
|
||||
impact["error_data"] = impact["upgrade_data"]
|
||||
return impact
|
||||
# Impact data indicates switch already running desired image
|
||||
if not impact["upgrade_required"]:
|
||||
impact["succeeded"] = True
|
||||
return impact
|
||||
# If we get here, impact data indicates upgrade is needed.
|
||||
upgrade = _upgrade(system_image, kickstart_image, issu, **kwargs)
|
||||
if upgrade["installing"]:
|
||||
log.info("Another install is in progress... wait and retry")
|
||||
time.sleep(30)
|
||||
continue
|
||||
# If the issu option is False and this upgrade request includes a
|
||||
# kickstart image then the 'force' option is used. This option is
|
||||
# only available in certain image sets.
|
||||
if upgrade["invalid_command"]:
|
||||
log_msg = "The [issu] option was set to False for this upgrade."
|
||||
log_msg = log_msg + " Attempt was made to ugrade using the force"
|
||||
log_msg = log_msg + " keyword which is not supported in this"
|
||||
log_msg = log_msg + " image. Set [issu=True] and re-try."
|
||||
upgrade["upgrade_data"] = log_msg
|
||||
break
|
||||
break
|
||||
|
||||
# Check for errors and return upgrade result:
|
||||
if upgrade["backend_processing_error"]:
|
||||
# This means we received a backend processing error from the transport
|
||||
# and lost the upgrade data. This also indicates that the upgrade
|
||||
# is in progress so use the impact data for logging purposes.
|
||||
impact["upgrade_in_progress"] = True
|
||||
return impact
|
||||
return upgrade
|
||||
|
||||
|
||||
def _upgrade(system_image, kickstart_image, issu, **kwargs):
|
||||
"""
|
||||
Helper method that does the heavy lifting for upgrades.
|
||||
"""
|
||||
si = system_image
|
||||
ki = kickstart_image
|
||||
dev = "bootflash"
|
||||
cmd = "terminal dont-ask ; install all"
|
||||
|
||||
if ki is None:
|
||||
logmsg = "Upgrading device using combined system/kickstart image."
|
||||
logmsg += "\nSystem Image: {}".format(si)
|
||||
cmd = cmd + " nxos {0}:{1}".format(dev, si)
|
||||
if issu:
|
||||
cmd = cmd + " non-disruptive"
|
||||
else:
|
||||
logmsg = "Upgrading device using separate system/kickstart images."
|
||||
logmsg += "\nSystem Image: {}".format(si)
|
||||
logmsg += "\nKickstart Image: {}".format(ki)
|
||||
if not issu:
|
||||
log.info("Attempting upgrade using force option")
|
||||
cmd = cmd + " force"
|
||||
cmd = cmd + " kickstart {0}:{1} system {0}:{2}".format(dev, ki, si)
|
||||
|
||||
if issu:
|
||||
logmsg += "\nIn Service Software Upgrade/Downgrade (non-disruptive) requested."
|
||||
else:
|
||||
logmsg += "\nDisruptive Upgrade/Downgrade requested."
|
||||
|
||||
log.info(logmsg)
|
||||
log.info("Begin upgrade using command: '{}'".format(cmd))
|
||||
|
||||
kwargs.update({"timeout": kwargs.get("timeout", 900)})
|
||||
error_pattern_list = ["Another install procedure may be in progress"]
|
||||
kwargs.update({"error_pattern": error_pattern_list})
|
||||
|
||||
# Begin Actual Upgrade
|
||||
try:
|
||||
upgrade_result = __salt__["nxos.sendline"](cmd, **kwargs)
|
||||
except CommandExecutionError as e:
|
||||
upgrade_result = ast.literal_eval(e.message)
|
||||
except NxosError as e:
|
||||
if re.search("Code: 500", e.message):
|
||||
upgrade_result = e.message
|
||||
else:
|
||||
upgrade_result = ast.literal_eval(e.message)
|
||||
return _parse_upgrade_data(upgrade_result)
|
||||
|
||||
|
||||
def _parse_upgrade_data(data):
|
||||
"""
|
||||
Helper method to parse upgrade data from the NX-OS device.
|
||||
"""
|
||||
upgrade_result = {}
|
||||
upgrade_result["upgrade_data"] = None
|
||||
upgrade_result["succeeded"] = False
|
||||
upgrade_result["upgrade_required"] = False
|
||||
upgrade_result["upgrade_non_disruptive"] = False
|
||||
upgrade_result["upgrade_in_progress"] = False
|
||||
upgrade_result["installing"] = False
|
||||
upgrade_result["module_data"] = {}
|
||||
upgrade_result["error_data"] = None
|
||||
upgrade_result["backend_processing_error"] = False
|
||||
upgrade_result["invalid_command"] = False
|
||||
|
||||
# Error handling
|
||||
if isinstance(data, string_types) and re.search("Code: 500", data):
|
||||
log.info("Detected backend processing error")
|
||||
upgrade_result["error_data"] = data
|
||||
upgrade_result["backend_processing_error"] = True
|
||||
return upgrade_result
|
||||
|
||||
if isinstance(data, dict):
|
||||
if "code" in data and data["code"] == "400":
|
||||
log.info("Detected client error")
|
||||
upgrade_result["error_data"] = data["cli_error"]
|
||||
|
||||
if re.search("install.*may be in progress", data["cli_error"]):
|
||||
log.info("Detected install in progress...")
|
||||
upgrade_result["installing"] = True
|
||||
|
||||
if re.search("Invalid command", data["cli_error"]):
|
||||
log.info("Detected invalid command...")
|
||||
upgrade_result["invalid_command"] = True
|
||||
else:
|
||||
# If we get here then it's likely we lost access to the device
|
||||
# but the upgrade succeeded. We lost the actual upgrade data so
|
||||
# set the flag such that impact data is used instead.
|
||||
log.info("Probable backend processing error")
|
||||
upgrade_result["backend_processing_error"] = True
|
||||
return upgrade_result
|
||||
|
||||
# Get upgrade data for further parsing
|
||||
# Case 1: Command terminal dont-ask returns empty {} that we don't need.
|
||||
if isinstance(data, list) and len(data) == 2:
|
||||
data = data[1]
|
||||
# Case 2: Command terminal dont-ask does not get included.
|
||||
if isinstance(data, list) and len(data) == 1:
|
||||
data = data[0]
|
||||
|
||||
log.info("Parsing NX-OS upgrade data")
|
||||
upgrade_result["upgrade_data"] = data
|
||||
for line in data.split("\n"):
|
||||
|
||||
log.info("Processing line: ({})".format(line))
|
||||
|
||||
# Check to see if upgrade is disruptive or non-disruptive
|
||||
if re.search(r"non-disruptive", line):
|
||||
log.info("Found non-disruptive line")
|
||||
upgrade_result["upgrade_non_disruptive"] = True
|
||||
|
||||
# Example:
|
||||
# Module Image Running-Version(pri:alt) New-Version Upg-Required
|
||||
# 1 nxos 7.0(3)I7(5a) 7.0(3)I7(5a) no
|
||||
# 1 bios v07.65(09/04/2018) v07.64(05/16/2018) no
|
||||
mo = re.search(r"(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(yes|no)", line)
|
||||
if mo:
|
||||
log.info("Matched Module Running/New Version Upg-Req Line")
|
||||
bk = "module_data" # base key
|
||||
g1 = mo.group(1)
|
||||
g2 = mo.group(2)
|
||||
g3 = mo.group(3)
|
||||
g4 = mo.group(4)
|
||||
g5 = mo.group(5)
|
||||
mk = "module {0}:image {1}".format(g1, g2) # module key
|
||||
upgrade_result[bk][mk] = {}
|
||||
upgrade_result[bk][mk]["running_version"] = g3
|
||||
upgrade_result[bk][mk]["new_version"] = g4
|
||||
if g5 == "yes":
|
||||
upgrade_result["upgrade_required"] = True
|
||||
upgrade_result[bk][mk]["upgrade_required"] = True
|
||||
continue
|
||||
|
||||
# The following lines indicate a successfull upgrade.
|
||||
if re.search(r"Install has been successful", line):
|
||||
log.info("Install successful line")
|
||||
upgrade_result["succeeded"] = True
|
||||
continue
|
||||
|
||||
if re.search(r"Finishing the upgrade, switch will reboot in", line):
|
||||
log.info("Finishing upgrade line")
|
||||
upgrade_result["upgrade_in_progress"] = True
|
||||
continue
|
||||
|
||||
if re.search(r"Switch will be reloaded for disruptive upgrade", line):
|
||||
log.info("Switch will be reloaded line")
|
||||
upgrade_result["upgrade_in_progress"] = True
|
||||
continue
|
||||
|
||||
if re.search(r"Switching over onto standby", line):
|
||||
log.info("Switching over onto standby line")
|
||||
upgrade_result["upgrade_in_progress"] = True
|
||||
continue
|
||||
|
||||
return upgrade_result
|
|
@ -1,84 +1,154 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
r"""
|
||||
Proxy Minion for Cisco NX OS Switches
|
||||
Proxy Minion for Cisco NX-OS Switches
|
||||
|
||||
.. versionadded: 2016.11.0
|
||||
|
||||
The Cisco NX OS Proxy Minion uses the built in SSHConnection module in :mod:`salt.utils.vt_helper <salt.utils.vt_helper>`
|
||||
The Cisco NX-OS Proxy Minion is supported on NX-OS devices for the following connection types:
|
||||
1) Connection Type SSH
|
||||
2) Connection Type NX-API (If Supported By The Device and Image Version).
|
||||
|
||||
To configure the proxy minion:
|
||||
:maturity: new
|
||||
:platform: nxos
|
||||
|
||||
SSH uses the built in SSHConnection module in :mod:`salt.utils.vt_helper <salt.utils.vt_helper>`
|
||||
|
||||
To configure the proxy minion for ssh:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
proxy:
|
||||
proxytype: nxos
|
||||
connection: ssh
|
||||
host: 192.168.187.100
|
||||
username: admin
|
||||
password: admin
|
||||
prompt_name: switch
|
||||
prompt_name: nxos-switch
|
||||
ssh_args: '-o PubkeyAuthentication=no'
|
||||
key_accept: True
|
||||
|
||||
proxytype
|
||||
To configure the proxy minon for nxapi:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
proxy:
|
||||
proxytype: nxos
|
||||
connection: nxapi
|
||||
host: 192.168.187.100
|
||||
username: admin
|
||||
password: admin
|
||||
transport: http
|
||||
port: 80
|
||||
verify: False
|
||||
save_config: False
|
||||
|
||||
proxytype:
|
||||
(REQUIRED) Use this proxy minion `nxos`
|
||||
|
||||
host
|
||||
(REQUIRED) ip address or hostname to connect to
|
||||
connection:
|
||||
(REQUIRED) connection transport type.
|
||||
Choices: `ssh, nxapi`
|
||||
Default: `ssh`
|
||||
|
||||
username
|
||||
(REQUIRED) username to login with
|
||||
host:
|
||||
(REQUIRED) login ip address or dns hostname.
|
||||
|
||||
password
|
||||
(REQUIRED) password to use to login with
|
||||
username:
|
||||
(REQUIRED) login username.
|
||||
|
||||
prompt_name
|
||||
(REQUIRED, this or `prompt_regex` below, but not both)
|
||||
The name in the prompt on the switch. Recommended to use your
|
||||
device's hostname.
|
||||
password:
|
||||
(REQUIRED) login password.
|
||||
|
||||
prompt_regex
|
||||
(REQUIRED, this or `prompt_name` above, but not both)
|
||||
A regular expression that matches the prompt on the switch
|
||||
and any other possible prompt at which you need the proxy minion
|
||||
to continue sending input. This feature was specifically developed
|
||||
for situations where the switch may ask for confirmation. `prompt_name`
|
||||
above would not match these, and so the session would timeout.
|
||||
save_config:
|
||||
If True, 'copy running-config starting-config' is issues for every
|
||||
configuration command.
|
||||
If False, Running config is not saved to startup config
|
||||
Default: True
|
||||
|
||||
Example:
|
||||
The recommended approach is to use the `save_running_config` function
|
||||
instead of this option to improve performance. The default behavior
|
||||
controlled by this option is preserved for backwards compatibility.
|
||||
|
||||
.. code-block:: yaml
|
||||
Conection SSH Args:
|
||||
|
||||
dc01-switch-01#.*|\(y\/n\)\?.*
|
||||
prompt_name:
|
||||
(REQUIRED when `connection` is `ssh`)
|
||||
(REQUIRED, this or `prompt_regex` below, but not both)
|
||||
The name in the prompt on the switch. Recommended to use your
|
||||
device's hostname.
|
||||
|
||||
This should match
|
||||
prompt_regex:
|
||||
(REQUIRED when `connection` is `ssh`)
|
||||
(REQUIRED, this or `prompt_name` above, but not both)
|
||||
A regular expression that matches the prompt on the switch
|
||||
and any other possible prompt at which you need the proxy minion
|
||||
to continue sending input. This feature was specifically developed
|
||||
for situations where the switch may ask for confirmation. `prompt_name`
|
||||
above would not match these, and so the session would timeout.
|
||||
|
||||
.. code-block:: shell
|
||||
Example:
|
||||
|
||||
dc01-switch-01#
|
||||
.. code-block:: yaml
|
||||
|
||||
or
|
||||
nxos-switch#.*|\(y\/n\)\?.*
|
||||
|
||||
.. code-block:: shell
|
||||
This should match
|
||||
|
||||
Flash complete. Reboot this switch (y/n)? [n]
|
||||
.. code-block:: shell
|
||||
|
||||
nxos-switch#
|
||||
|
||||
or
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
Flash complete. Reboot this switch (y/n)? [n]
|
||||
|
||||
|
||||
If neither `prompt_name` nor `prompt_regex` is specified the prompt will be
|
||||
defaulted to
|
||||
If neither `prompt_name` nor `prompt_regex` is specified the prompt will be
|
||||
defaulted to
|
||||
|
||||
.. code-block:: shell
|
||||
.. code-block:: shell
|
||||
|
||||
.+#$
|
||||
.+#$
|
||||
|
||||
which should match any number of characters followed by a `#` at the end
|
||||
of the line. This may be far too liberal for most installations.
|
||||
which should match any number of characters followed by a `#` at the end
|
||||
of the line. This may be far too liberal for most installations.
|
||||
|
||||
ssh_args
|
||||
Any extra args to use to connect to the switch.
|
||||
ssh_args:
|
||||
Extra optional arguments used for connecting to switch.
|
||||
|
||||
key_accept
|
||||
Whether or not to accept a the host key of the switch on initial login.
|
||||
Defaults to False.
|
||||
key_accept:
|
||||
Wheather or not to accept the host key of the switch on initial login.
|
||||
Default: `False`
|
||||
|
||||
Connection NXAPI Args:
|
||||
|
||||
transport:
|
||||
(REQUIRED) when `connection` is `nxapi`.
|
||||
Choices: `http, https`
|
||||
Default: `https`
|
||||
|
||||
port:
|
||||
(REQUIRED) when `connection` is `nxapi`.
|
||||
Default: `80`
|
||||
|
||||
verify:
|
||||
(REQUIRED) when `connection` is `nxapi`.
|
||||
Either a boolean, in which case it controls whether we verify the NX-API
|
||||
TLS certificate, or a string, in which case it must be a path to a CA bundle
|
||||
to use.
|
||||
Default: `True`
|
||||
|
||||
When there is no certificate configuration on the device and this option is
|
||||
set as ``True`` (default), the commands will fail with the following error:
|
||||
``SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:581)``.
|
||||
In this case, you either need to configure a proper certificate on the
|
||||
device (*recommended*), or bypass the checks setting this argument as ``False``
|
||||
with all the security risks considered.
|
||||
|
||||
Check https://www.cisco.com/c/en/us/td/docs/switches/datacenter/nexus3000/sw/programmability/6_x/b_Cisco_Nexus_3000_Series_NX-OS_Programmability_Guide/b_Cisco_Nexus_3000_Series_NX-OS_Programmability_Guide_chapter_01.html
|
||||
to see how to properly configure the certificate.
|
||||
|
||||
|
||||
The functions from the proxy minion can be run from the salt commandline using
|
||||
|
@ -95,12 +165,15 @@ the :mod:`salt.modules.nxos<salt.modules.nxos>` execution module.
|
|||
# Import Python libs
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import copy
|
||||
import logging
|
||||
import multiprocessing
|
||||
import re
|
||||
|
||||
# Import Salt libs
|
||||
from salt.utils.pycrypto import gen_hash, secure_password
|
||||
import salt.utils.nxos
|
||||
from salt.exceptions import CommandExecutionError, NxosCliError
|
||||
from salt.utils.args import clean_kwargs
|
||||
from salt.utils.vt import TerminalException
|
||||
from salt.utils.vt_helper import SSHConnection
|
||||
|
||||
|
@ -108,7 +181,10 @@ log = logging.getLogger(__file__)
|
|||
|
||||
__proxyenabled__ = ["nxos"]
|
||||
__virtualname__ = "nxos"
|
||||
DETAILS = {"grains_cache": {}}
|
||||
|
||||
# Globals used to maintain state for ssh and nxapi proxy minions
|
||||
DEVICE_DETAILS = {"grains_cache": {}}
|
||||
CONNECTION = "ssh"
|
||||
|
||||
|
||||
def __virtual__():
|
||||
|
@ -120,14 +196,144 @@ def __virtual__():
|
|||
return __virtualname__
|
||||
|
||||
|
||||
def _worker_name():
|
||||
return multiprocessing.current_process().name
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Device Connection Connection Agnostic Functions
|
||||
# -----------------------------------------------------------------------------
|
||||
def init(opts=None):
|
||||
"""
|
||||
Required.
|
||||
Can be used to initialize the server connection.
|
||||
Initialize device connection using ssh or nxapi connection type.
|
||||
"""
|
||||
global CONNECTION
|
||||
if __opts__.get("proxy").get("connection") is not None:
|
||||
CONNECTION = __opts__.get("proxy").get("connection")
|
||||
|
||||
if CONNECTION == "ssh":
|
||||
log.info("NXOS PROXY: Initialize ssh proxy connection")
|
||||
return _init_ssh(opts)
|
||||
elif CONNECTION == "nxapi":
|
||||
log.info("NXOS PROXY: Initialize nxapi proxy connection")
|
||||
return _init_nxapi(opts)
|
||||
else:
|
||||
log.error("Unknown Connection Type: {0}".format(CONNECTION))
|
||||
return False
|
||||
|
||||
|
||||
def initialized():
|
||||
"""
|
||||
Since grains are loaded in many different places and some of those
|
||||
places occur before the proxy can be initialized, return whether the
|
||||
init() function has been called.
|
||||
"""
|
||||
if CONNECTION == "ssh":
|
||||
return _initialized_ssh()
|
||||
elif CONNECTION == "nxapi":
|
||||
return _initialized_nxapi()
|
||||
|
||||
|
||||
def ping():
|
||||
"""
|
||||
Helper function for nxos execution module functions that need to
|
||||
ping the nxos device using the proxy minion.
|
||||
"""
|
||||
if CONNECTION == "ssh":
|
||||
return _ping_ssh()
|
||||
elif CONNECTION == "nxapi":
|
||||
return _ping_nxapi()
|
||||
|
||||
|
||||
def grains():
|
||||
"""
|
||||
Helper function for nxos execution module functions that need to
|
||||
retrieve nxos grains using the proxy minion.
|
||||
"""
|
||||
if not DEVICE_DETAILS["grains_cache"]:
|
||||
data = sendline("show version")
|
||||
if CONNECTION == "nxapi":
|
||||
data = data[0]
|
||||
ret = salt.utils.nxos.system_info(data)
|
||||
log.debug(ret)
|
||||
DEVICE_DETAILS["grains_cache"].update(ret["nxos"])
|
||||
return {"nxos": DEVICE_DETAILS["grains_cache"]}
|
||||
|
||||
|
||||
def grains_refresh():
|
||||
"""
|
||||
Helper function for nxos execution module functions that need to
|
||||
refresh nxos grains using the proxy minion.
|
||||
"""
|
||||
DEVICE_DETAILS["grains_cache"] = {}
|
||||
return grains()
|
||||
|
||||
|
||||
def shutdown():
|
||||
"""
|
||||
Not supported. Only used as a place holder to satisfy shutdown function
|
||||
requirement.
|
||||
"""
|
||||
if CONNECTION == "ssh":
|
||||
return _shutdown_ssh()
|
||||
elif CONNECTION == "nxapi":
|
||||
return _shutdown_nxapi()
|
||||
|
||||
|
||||
def sendline(commands, method="cli_show_ascii", **kwargs):
|
||||
"""
|
||||
Helper function for nxos execution module functions that need to
|
||||
send commands to an nxos device using the proxy minion.
|
||||
"""
|
||||
try:
|
||||
if CONNECTION == "ssh":
|
||||
result = _sendline_ssh(commands, **kwargs)
|
||||
elif CONNECTION == "nxapi":
|
||||
result = _nxapi_request(commands, method, **kwargs)
|
||||
except (TerminalException, NxosCliError) as e:
|
||||
log.error(e)
|
||||
raise
|
||||
return result
|
||||
|
||||
|
||||
def proxy_config(commands, save_config=None):
|
||||
"""
|
||||
Helper function for nxos execution module functions that need to
|
||||
configure an nxos device using the proxy minion.
|
||||
"""
|
||||
COPY_RS = "copy running-config startup-config"
|
||||
|
||||
if save_config is None:
|
||||
save_config = DEVICE_DETAILS.get("save_config", True)
|
||||
if not isinstance(commands, list):
|
||||
commands = [commands]
|
||||
try:
|
||||
if CONNECTION == "ssh":
|
||||
_sendline_ssh("config terminal")
|
||||
prev_cmds = []
|
||||
for cmd in commands:
|
||||
prev_cmds.append(cmd)
|
||||
ret = _sendline_ssh(cmd)
|
||||
if save_config:
|
||||
_sendline_ssh(COPY_RS)
|
||||
if ret:
|
||||
log.error(prev_cmds)
|
||||
elif CONNECTION == "nxapi":
|
||||
ret = _nxapi_request(commands)
|
||||
if save_config:
|
||||
_nxapi_request(COPY_RS)
|
||||
for each in ret:
|
||||
if "Failure" in each:
|
||||
log.error(each)
|
||||
except CommandExecutionError as e:
|
||||
log.error(e)
|
||||
raise
|
||||
return [commands, ret]
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# SSH Transport Functions
|
||||
# -----------------------------------------------------------------------------
|
||||
def _init_ssh(opts=None):
|
||||
"""
|
||||
Open a connection to the NX-OS switch over SSH.
|
||||
"""
|
||||
if opts is None:
|
||||
opts = __opts__
|
||||
|
@ -141,7 +347,7 @@ def init(opts=None):
|
|||
log.warning("nxos proxy configuration does not specify a prompt match.")
|
||||
this_prompt = ".+#$"
|
||||
|
||||
DETAILS[_worker_name()] = SSHConnection(
|
||||
DEVICE_DETAILS[_worker_name()] = SSHConnection(
|
||||
host=opts["proxy"]["host"],
|
||||
username=opts["proxy"]["username"],
|
||||
password=opts["proxy"]["password"],
|
||||
|
@ -149,433 +355,156 @@ def init(opts=None):
|
|||
ssh_args=opts["proxy"].get("ssh_args", ""),
|
||||
prompt=this_prompt,
|
||||
)
|
||||
out, err = DETAILS[_worker_name()].sendline("terminal length 0")
|
||||
|
||||
except TerminalException as e:
|
||||
log.error(e)
|
||||
return False
|
||||
DETAILS["initialized"] = True
|
||||
out, err = DEVICE_DETAILS[_worker_name()].sendline("terminal length 0")
|
||||
log.info("SSH session establised for process {}".format(_worker_name()))
|
||||
except Exception as ex: # pylint: disable=broad-except
|
||||
log.error("Unable to connect to %s", opts["proxy"]["host"])
|
||||
log.error("Please check the following:\n")
|
||||
log.error(
|
||||
'-- Verify that "feature ssh" is enabled on your NX-OS device: %s',
|
||||
opts["proxy"]["host"],
|
||||
)
|
||||
log.error("-- Exception Generated: %s", ex)
|
||||
log.error(ex)
|
||||
raise
|
||||
DEVICE_DETAILS["initialized"] = True
|
||||
DEVICE_DETAILS["save_config"] = opts["proxy"].get("save_config", True)
|
||||
|
||||
|
||||
def initialized():
|
||||
return DETAILS.get("initialized", False)
|
||||
def _initialized_ssh():
|
||||
return DEVICE_DETAILS.get("initialized", False)
|
||||
|
||||
|
||||
def ping():
|
||||
"""
|
||||
Ping the device on the other end of the connection
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.cmd ping
|
||||
"""
|
||||
if _worker_name() not in DETAILS:
|
||||
init()
|
||||
def _ping_ssh():
|
||||
if _worker_name() not in DEVICE_DETAILS:
|
||||
try:
|
||||
_init_ssh()
|
||||
except Exception: # pylint: disable=broad-except
|
||||
return False
|
||||
try:
|
||||
return DETAILS[_worker_name()].conn.isalive()
|
||||
return DEVICE_DETAILS[_worker_name()].conn.isalive()
|
||||
except TerminalException as e:
|
||||
log.error(e)
|
||||
return False
|
||||
|
||||
|
||||
def shutdown(opts):
|
||||
"""
|
||||
Disconnect
|
||||
"""
|
||||
DETAILS[_worker_name()].close_connection()
|
||||
def _shutdown_ssh():
|
||||
return "Shutdown of ssh proxy minion is not supported"
|
||||
|
||||
|
||||
def sendline(command):
|
||||
"""
|
||||
Run command through switch's cli
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.cmd sendline 'show run | include "^username admin password"'
|
||||
"""
|
||||
if ping() is False:
|
||||
init()
|
||||
out, err = DETAILS[_worker_name()].sendline(command)
|
||||
def _sendline_ssh(commands, timeout=None, **kwargs):
|
||||
if isinstance(commands, str):
|
||||
commands = [commands]
|
||||
command = " ; ".join(commands)
|
||||
if _ping_ssh() is False:
|
||||
_init_ssh()
|
||||
out, err = DEVICE_DETAILS[_worker_name()].sendline(command)
|
||||
_, out = out.split("\n", 1)
|
||||
out, _, _ = out.rpartition("\n")
|
||||
kwargs = clean_kwargs(**kwargs)
|
||||
_parse_output_for_errors(out, command, **kwargs)
|
||||
return out
|
||||
|
||||
|
||||
def grains():
|
||||
def _parse_output_for_errors(data, command, error_pattern=None):
|
||||
"""
|
||||
Get grains for proxy minion
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.cmd grains
|
||||
Helper method to parse command output for error information
|
||||
"""
|
||||
if not DETAILS["grains_cache"]:
|
||||
ret = system_info()
|
||||
log.debug(ret)
|
||||
DETAILS["grains_cache"].update(ret)
|
||||
return {"nxos": DETAILS["grains_cache"]}
|
||||
|
||||
|
||||
def grains_refresh():
|
||||
"""
|
||||
Refresh the grains from the proxy device.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.cmd grains_refresh
|
||||
"""
|
||||
DETAILS["grains_cache"] = {}
|
||||
return grains()
|
||||
|
||||
|
||||
def get_user(username):
|
||||
"""
|
||||
Get username line from switch
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.cmd get_user username=admin
|
||||
"""
|
||||
return sendline('show run | include "^username {0} password 5 "'.format(username))
|
||||
|
||||
|
||||
def get_roles(username):
|
||||
"""
|
||||
Get roles that the username is assigned from switch
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.cmd get_roles username=admin
|
||||
"""
|
||||
info = sendline("show user-account {0}".format(username))
|
||||
roles = re.search(r"^\s*roles:(.*)$", info, re.MULTILINE)
|
||||
if roles:
|
||||
roles = roles.group(1).strip().split(" ")
|
||||
else:
|
||||
roles = []
|
||||
return roles
|
||||
|
||||
|
||||
def check_password(username, password, encrypted=False):
|
||||
"""
|
||||
Check if passed password is the one assigned to user
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.cmd check_password username=admin password=admin
|
||||
salt '*' nxos.cmd check_password username=admin \\
|
||||
password='$5$2fWwO2vK$s7.Hr3YltMNHuhywQQ3nfOd.gAPHgs3SOBYYdGT3E.A' \\
|
||||
encrypted=True
|
||||
"""
|
||||
hash_algorithms = {
|
||||
"1": "md5",
|
||||
"2a": "blowfish",
|
||||
"5": "sha256",
|
||||
"6": "sha512",
|
||||
}
|
||||
password_line = get_user(username)
|
||||
if not password_line:
|
||||
return None
|
||||
if "!!" in password_line:
|
||||
return False
|
||||
cur_hash = re.search(r"(\$[0-6](?:\$[^$ ]+)+)", password_line).group(0)
|
||||
if encrypted is False:
|
||||
hash_type, cur_salt, hashed_pass = re.search(
|
||||
r"^\$([0-6])\$([^$]+)\$(.*)$", cur_hash
|
||||
).groups()
|
||||
new_hash = gen_hash(
|
||||
crypt_salt=cur_salt, password=password, algorithm=hash_algorithms[hash_type]
|
||||
if re.search("% Invalid", data):
|
||||
raise CommandExecutionError(
|
||||
{
|
||||
"rejected_input": command,
|
||||
"message": "CLI excution error",
|
||||
"code": "400",
|
||||
"cli_error": data.lstrip(),
|
||||
}
|
||||
)
|
||||
else:
|
||||
new_hash = password
|
||||
if new_hash == cur_hash:
|
||||
return True
|
||||
return False
|
||||
if error_pattern:
|
||||
if isinstance(error_pattern, str):
|
||||
error_pattern = [error_pattern]
|
||||
for re_line in error_pattern:
|
||||
if re.search(re_line, data):
|
||||
raise CommandExecutionError(
|
||||
{
|
||||
"rejected_input": command,
|
||||
"message": "CLI excution error",
|
||||
"code": "400",
|
||||
"cli_error": data.lstrip(),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def check_role(username, role):
|
||||
def _worker_name():
|
||||
return multiprocessing.current_process().name
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# NX-API Transport Functions
|
||||
# -----------------------------------------------------------------------------
|
||||
def _init_nxapi(opts):
|
||||
"""
|
||||
Check if user is assigned a specific role on switch
|
||||
Open a connection to the NX-OS switch over NX-API.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.cmd check_role username=admin role=network-admin
|
||||
As the communication is HTTP(S) based, there is no connection to maintain,
|
||||
however, in order to test the connectivity and make sure we are able to
|
||||
bring up this Minion, we are executing a very simple command (``show clock``)
|
||||
which doesn't come with much overhead and it's sufficient to confirm we are
|
||||
indeed able to connect to the NX-API endpoint as configured.
|
||||
"""
|
||||
return role in get_roles(username)
|
||||
|
||||
|
||||
def set_password(
|
||||
username, password, encrypted=False, role=None, crypt_salt=None, algorithm="sha256"
|
||||
):
|
||||
"""
|
||||
Set users password on switch
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.cmd set_password admin TestPass
|
||||
salt '*' nxos.cmd set_password admin \\
|
||||
password='$5$2fWwO2vK$s7.Hr3YltMNHuhywQQ3nfOd.gAPHgs3SOBYYdGT3E.A' \\
|
||||
encrypted=True
|
||||
"""
|
||||
password_line = get_user(username)
|
||||
if encrypted is False:
|
||||
if crypt_salt is None:
|
||||
# NXOS does not like non alphanumeric characters. Using the random module from pycrypto
|
||||
# can lead to having non alphanumeric characters in the salt for the hashed password.
|
||||
crypt_salt = secure_password(8, use_random=False)
|
||||
hashed_pass = gen_hash(
|
||||
crypt_salt=crypt_salt, password=password, algorithm=algorithm
|
||||
proxy_dict = opts.get("proxy", {})
|
||||
conn_args = copy.deepcopy(proxy_dict)
|
||||
conn_args.pop("proxytype", None)
|
||||
try:
|
||||
rpc_reply = __utils__["nxos.nxapi_request"]("show clock", **conn_args)
|
||||
# Execute a very simple command to confirm we are able to connect properly
|
||||
DEVICE_DETAILS["conn_args"] = conn_args
|
||||
DEVICE_DETAILS["initialized"] = True
|
||||
DEVICE_DETAILS["up"] = True
|
||||
DEVICE_DETAILS["save_config"] = opts["proxy"].get("save_config", True)
|
||||
except Exception as ex:
|
||||
log.error("Unable to connect to %s", conn_args["host"])
|
||||
log.error("Please check the following:\n")
|
||||
log.error(
|
||||
'-- Verify that "feature nxapi" is enabled on your NX-OS device: %s',
|
||||
conn_args["host"],
|
||||
)
|
||||
else:
|
||||
hashed_pass = password
|
||||
password_line = "username {0} password 5 {1}".format(username, hashed_pass)
|
||||
if role is not None:
|
||||
password_line += " role {0}".format(role)
|
||||
try:
|
||||
sendline("config terminal")
|
||||
ret = sendline(password_line)
|
||||
sendline("end")
|
||||
sendline("copy running-config startup-config")
|
||||
return "\n".join([password_line, ret])
|
||||
except TerminalException as e:
|
||||
log.error(e)
|
||||
return "Failed to set password"
|
||||
|
||||
|
||||
def remove_user(username):
|
||||
"""
|
||||
Remove user from switch
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.cmd remove_user username=daniel
|
||||
"""
|
||||
try:
|
||||
sendline("config terminal")
|
||||
user_line = "no username {0}".format(username)
|
||||
ret = sendline(user_line)
|
||||
sendline("end")
|
||||
sendline("copy running-config startup-config")
|
||||
return "\n".join([user_line, ret])
|
||||
except TerminalException as e:
|
||||
log.error(e)
|
||||
return "Failed to set password"
|
||||
|
||||
|
||||
def set_role(username, role):
|
||||
"""
|
||||
Assign role to username
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.cmd set_role username=daniel role=vdc-admin
|
||||
"""
|
||||
try:
|
||||
sendline("config terminal")
|
||||
role_line = "username {0} role {1}".format(username, role)
|
||||
ret = sendline(role_line)
|
||||
sendline("end")
|
||||
sendline("copy running-config startup-config")
|
||||
return "\n".join([role_line, ret])
|
||||
except TerminalException as e:
|
||||
log.error(e)
|
||||
return "Failed to set password"
|
||||
|
||||
|
||||
def unset_role(username, role):
|
||||
"""
|
||||
Remove role from username
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.cmd unset_role username=daniel role=vdc-admin
|
||||
"""
|
||||
try:
|
||||
sendline("config terminal")
|
||||
role_line = "no username {0} role {1}".format(username, role)
|
||||
ret = sendline(role_line)
|
||||
sendline("end")
|
||||
sendline("copy running-config startup-config")
|
||||
return "\n".join([role_line, ret])
|
||||
except TerminalException as e:
|
||||
log.error(e)
|
||||
return "Failed to set password"
|
||||
|
||||
|
||||
def show_run():
|
||||
"""
|
||||
Shortcut to run `show run` on switch
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.cmd show_run
|
||||
"""
|
||||
try:
|
||||
ret = sendline("show run")
|
||||
except TerminalException as e:
|
||||
log.error(e)
|
||||
return 'Failed to "show run"'
|
||||
return ret
|
||||
|
||||
|
||||
def show_ver():
|
||||
"""
|
||||
Shortcut to run `show ver` on switch
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.cmd show_ver
|
||||
"""
|
||||
try:
|
||||
ret = sendline("show ver")
|
||||
except TerminalException as e:
|
||||
log.error(e)
|
||||
return 'Failed to "show ver"'
|
||||
return ret
|
||||
|
||||
|
||||
def add_config(lines):
|
||||
"""
|
||||
Add one or more config lines to the switch running config
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.cmd add_config 'snmp-server community TESTSTRINGHERE group network-operator'
|
||||
|
||||
.. note::
|
||||
For more than one config added per command, lines should be a list.
|
||||
"""
|
||||
if not isinstance(lines, list):
|
||||
lines = [lines]
|
||||
try:
|
||||
sendline("config terminal")
|
||||
for line in lines:
|
||||
sendline(line)
|
||||
|
||||
sendline("end")
|
||||
sendline("copy running-config startup-config")
|
||||
except TerminalException as e:
|
||||
log.error(e)
|
||||
return False
|
||||
log.error(
|
||||
"-- Verify that nxapi settings on the NX-OS device and proxy minion config file match"
|
||||
)
|
||||
log.error("-- Exception Generated: %s", ex)
|
||||
raise
|
||||
log.info("nxapi DEVICE_DETAILS info: {}".format(DEVICE_DETAILS))
|
||||
return True
|
||||
|
||||
|
||||
def delete_config(lines):
|
||||
def _initialized_nxapi():
|
||||
return DEVICE_DETAILS.get("initialized", False)
|
||||
|
||||
|
||||
def _ping_nxapi():
|
||||
return DEVICE_DETAILS.get("up", False)
|
||||
|
||||
|
||||
def _shutdown_nxapi():
|
||||
return "Shutdown of nxapi proxy minion is not supported"
|
||||
|
||||
|
||||
def _nxapi_request(commands, method="cli_conf", **kwargs):
|
||||
"""
|
||||
Delete one or more config lines to the switch running config
|
||||
Executes an nxapi_request request over NX-API.
|
||||
|
||||
.. code-block:: bash
|
||||
commands
|
||||
The exec or config commands to be sent.
|
||||
|
||||
salt '*' nxos.cmd delete_config 'snmp-server community TESTSTRINGHERE group network-operator'
|
||||
|
||||
.. note::
|
||||
For more than one config deleted per command, lines should be a list.
|
||||
method: ``cli_show``
|
||||
``cli_show_ascii``: Return raw test or unstructured output.
|
||||
``cli_show``: Return structured output.
|
||||
``cli_conf``: Send configuration commands to the device.
|
||||
Defaults to ``cli_conf``.
|
||||
"""
|
||||
if not isinstance(lines, list):
|
||||
lines = [lines]
|
||||
try:
|
||||
sendline("config terminal")
|
||||
for line in lines:
|
||||
sendline(" ".join(["no", line]))
|
||||
|
||||
sendline("end")
|
||||
sendline("copy running-config startup-config")
|
||||
except TerminalException as e:
|
||||
log.error(e)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def find(pattern):
|
||||
"""
|
||||
Find all instances where the pattern is in the running command
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.cmd find '^snmp-server.*$'
|
||||
|
||||
.. note::
|
||||
This uses the `re.MULTILINE` regex format for python, and runs the
|
||||
regex against the whole show_run output.
|
||||
"""
|
||||
matcher = re.compile(pattern, re.MULTILINE)
|
||||
return matcher.findall(show_run())
|
||||
|
||||
|
||||
def replace(old_value, new_value, full_match=False):
|
||||
"""
|
||||
Replace string or full line matches in switch's running config
|
||||
|
||||
If full_match is set to True, then the whole line will need to be matched
|
||||
as part of the old value.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.cmd replace 'TESTSTRINGHERE' 'NEWTESTSTRINGHERE'
|
||||
"""
|
||||
if full_match is False:
|
||||
matcher = re.compile("^.*{0}.*$".format(re.escape(old_value)), re.MULTILINE)
|
||||
repl = re.compile(re.escape(old_value))
|
||||
else:
|
||||
matcher = re.compile(old_value, re.MULTILINE)
|
||||
repl = re.compile(old_value)
|
||||
|
||||
lines = {"old": [], "new": []}
|
||||
for line in matcher.finditer(show_run()):
|
||||
lines["old"].append(line.group(0))
|
||||
lines["new"].append(repl.sub(new_value, line.group(0)))
|
||||
|
||||
delete_config(lines["old"])
|
||||
add_config(lines["new"])
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def _parser(block):
|
||||
return re.compile("^{block}\n(?:^[ \n].*$\n?)+".format(block=block), re.MULTILINE)
|
||||
|
||||
|
||||
def _parse_software(data):
|
||||
ret = {"software": {}}
|
||||
software = _parser("Software").search(data).group(0)
|
||||
matcher = re.compile("^ ([^:]+): *([^\n]+)", re.MULTILINE)
|
||||
for line in matcher.finditer(software):
|
||||
key, val = line.groups()
|
||||
ret["software"][key] = val
|
||||
return ret["software"]
|
||||
|
||||
|
||||
def _parse_hardware(data):
|
||||
ret = {"hardware": {}}
|
||||
hardware = _parser("Hardware").search(data).group(0)
|
||||
matcher = re.compile("^ ([^:\n]+): *([^\n]+)", re.MULTILINE)
|
||||
for line in matcher.finditer(hardware):
|
||||
key, val = line.groups()
|
||||
ret["hardware"][key] = val
|
||||
return ret["hardware"]
|
||||
|
||||
|
||||
def _parse_plugins(data):
|
||||
ret = {"plugins": []}
|
||||
plugins = _parser("plugin").search(data).group(0)
|
||||
matcher = re.compile("^ (?:([^,]+), )+([^\n]+)", re.MULTILINE)
|
||||
for line in matcher.finditer(plugins):
|
||||
ret["plugins"].extend(line.groups())
|
||||
return ret["plugins"]
|
||||
|
||||
|
||||
def system_info():
|
||||
"""
|
||||
Return system information for grains of the NX OS proxy minion
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nxos.system_info
|
||||
"""
|
||||
data = show_ver()
|
||||
info = {
|
||||
"software": _parse_software(data),
|
||||
"hardware": _parse_hardware(data),
|
||||
"plugins": _parse_plugins(data),
|
||||
}
|
||||
return info
|
||||
if CONNECTION == "ssh":
|
||||
return "_nxapi_request is not available for ssh proxy"
|
||||
conn_args = DEVICE_DETAILS["conn_args"]
|
||||
conn_args.update(kwargs)
|
||||
data = __utils__["nxos.nxapi_request"](commands, method=method, **conn_args)
|
||||
return data
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
State module for Cisco NX OS Switches Proxy minions
|
||||
State module for Cisco NX-OS Switch Proxy and Native minions
|
||||
|
||||
.. versionadded: 2016.11.0
|
||||
|
||||
|
@ -157,7 +157,7 @@ def user_present(
|
|||
correct_roles = True
|
||||
if roles is not None:
|
||||
cur_roles = __salt__["nxos.cmd"]("get_roles", username=name)
|
||||
correct_roles = set(roles) != set(cur_roles)
|
||||
correct_roles = set(roles) == set(cur_roles)
|
||||
|
||||
if not correct_roles:
|
||||
ret["comment"] = "Failed to set correct roles"
|
||||
|
|
120
salt/states/nxos_upgrade.py
Normal file
120
salt/states/nxos_upgrade.py
Normal file
|
@ -0,0 +1,120 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2018 Cisco and/or its affiliates.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""
|
||||
Manage NX-OS System Image Upgrades.
|
||||
|
||||
.. versionadded: xxxx.xx.x
|
||||
|
||||
:maturity: new
|
||||
:platform: nxos
|
||||
:codeauthor: Michael G Wiebe
|
||||
|
||||
For documentation on setting up the nxos proxy minion look in the documentation
|
||||
for :mod:`salt.proxy.nxos<salt.proxy.nxos>`.
|
||||
"""
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
__virtualname__ = "nxos"
|
||||
__virtual_aliases__ = ("nxos_upgrade",)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def __virtual__():
|
||||
return __virtualname__
|
||||
|
||||
|
||||
def image_running(name, system_image, kickstart_image=None, issu=True, **kwargs):
|
||||
"""
|
||||
Ensure the NX-OS system image is running on the device.
|
||||
|
||||
name
|
||||
Name of the salt state task
|
||||
|
||||
system_image
|
||||
Name of the system image file on bootflash:
|
||||
|
||||
kickstart_image
|
||||
Name of the kickstart image file on bootflash:
|
||||
This is not needed if the system_image is a combined system and
|
||||
kickstart image
|
||||
Default: None
|
||||
|
||||
issu
|
||||
Ensure the correct system is running on the device using an in service
|
||||
software upgrade, or force a disruptive upgrade by setting the option
|
||||
to False.
|
||||
Default: False
|
||||
|
||||
timeout
|
||||
Timeout in seconds for long running 'install all' upgrade command.
|
||||
Default: 900
|
||||
|
||||
Examples:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
upgrade_software_image_n9k:
|
||||
nxos.image_running:
|
||||
- name: Ensure nxos.7.0.3.I7.5a.bin is running
|
||||
- system_image: nxos.7.0.3.I7.5a.bin
|
||||
- issu: True
|
||||
|
||||
upgrade_software_image_n7k:
|
||||
nxos.image_running:
|
||||
- name: Ensure n7000-s2-kickstart.8.0.1.bin is running
|
||||
- kickstart_image: n7000-s2-kickstart.8.0.1.bin
|
||||
- system_image: n7000-s2-dk9.8.0.1.bin
|
||||
- issu: False
|
||||
"""
|
||||
ret = {"name": name, "result": False, "changes": {}, "comment": ""}
|
||||
|
||||
if kickstart_image is None:
|
||||
upgrade = __salt__["nxos.upgrade"](
|
||||
system_image=system_image, issu=issu, **kwargs
|
||||
)
|
||||
else:
|
||||
upgrade = __salt__["nxos.upgrade"](
|
||||
system_image=system_image,
|
||||
kickstart_image=kickstart_image,
|
||||
issu=issu,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
if upgrade["upgrade_in_progress"]:
|
||||
ret["result"] = upgrade["upgrade_in_progress"]
|
||||
ret["changes"] = upgrade["module_data"]
|
||||
ret["comment"] = "NX-OS Device Now Being Upgraded - See Change Details Below"
|
||||
elif upgrade["succeeded"]:
|
||||
ret["result"] = upgrade["succeeded"]
|
||||
ret["comment"] = "NX-OS Device Running Image: {}".format(_version_info())
|
||||
else:
|
||||
ret["comment"] = "Upgrade Failed: {}.".format(upgrade["error_data"])
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def _version_info():
|
||||
"""
|
||||
Helper method to return running image version
|
||||
"""
|
||||
if "NXOS" in __grains__["nxos"]["software"]:
|
||||
return __grains__["nxos"]["software"]["NXOS"]
|
||||
elif "kickstart" in __grains__["nxos"]["software"]:
|
||||
return __grains__["nxos"]["software"]["kickstart"]
|
||||
else:
|
||||
return "Unable to detect sofware version"
|
393
salt/utils/nxos.py
Normal file
393
salt/utils/nxos.py
Normal file
|
@ -0,0 +1,393 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2018 Cisco and/or its affiliates.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""
|
||||
Util functions for the NXOS modules.
|
||||
"""
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
# Import Python std lib
|
||||
import collections
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
|
||||
# Import salt libs
|
||||
import salt.utils.http
|
||||
from salt.exceptions import (
|
||||
CommandExecutionError,
|
||||
NxosClientError,
|
||||
NxosError,
|
||||
NxosRequestNotSupported,
|
||||
)
|
||||
from salt.ext.six.moves import zip
|
||||
from salt.utils.args import clean_kwargs
|
||||
|
||||
# Disable pylint check since httplib is not available in python3
|
||||
try:
|
||||
import httplib # pylint: disable=W1699
|
||||
except ImportError:
|
||||
import http.client
|
||||
|
||||
httplib = http.client
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UHTTPConnection(httplib.HTTPConnection): # pylint: disable=W1699
|
||||
"""
|
||||
Subclass of Python library HTTPConnection that uses a unix-domain socket.
|
||||
"""
|
||||
|
||||
def __init__(self, path):
|
||||
httplib.HTTPConnection.__init__(self, "localhost")
|
||||
self.path = path
|
||||
|
||||
def connect(self):
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.connect(self.path)
|
||||
self.sock = sock
|
||||
|
||||
|
||||
class NxapiClient(object):
|
||||
"""
|
||||
Class representing an NX-API client that connects over http(s) or
|
||||
unix domain socket (UDS).
|
||||
"""
|
||||
|
||||
# Location of unix domain socket for NX-API localhost
|
||||
NXAPI_UDS = "/tmp/nginx_local/nginx_1_be_nxapi.sock"
|
||||
# NXAPI listens for remote connections to "http(s)://<switch IP>/ins"
|
||||
# NXAPI listens for local connections to "http(s)://<UDS>/ins_local"
|
||||
NXAPI_REMOTE_URI_PATH = "/ins"
|
||||
NXAPI_UDS_URI_PATH = "/ins_local"
|
||||
NXAPI_VERSION = "1.0"
|
||||
|
||||
def __init__(self, **nxos_kwargs):
|
||||
"""
|
||||
Initialize NxapiClient() connection object. By default this connects
|
||||
to the local unix domain socket (UDS). If http(s) is required to
|
||||
connect to a remote device then
|
||||
nxos_kwargs['host'],
|
||||
nxos_kwargs['username'],
|
||||
nxos_kwargs['password'],
|
||||
nxos_kwargs['transport'],
|
||||
nxos_kwargs['port'],
|
||||
parameters must be provided.
|
||||
"""
|
||||
self.nxargs = self._prepare_conn_args(clean_kwargs(**nxos_kwargs))
|
||||
# Default: Connect to unix domain socket on localhost.
|
||||
if self.nxargs["connect_over_uds"]:
|
||||
if not os.path.exists(self.NXAPI_UDS):
|
||||
raise NxosClientError(
|
||||
"No host specified and no UDS found at {0}\n".format(self.NXAPI_UDS)
|
||||
)
|
||||
|
||||
# Create UHTTPConnection object for NX-API communication over UDS.
|
||||
log.info("Nxapi connection arguments: {0}".format(self.nxargs))
|
||||
log.info("Connecting over unix domain socket")
|
||||
self.connection = UHTTPConnection(self.NXAPI_UDS)
|
||||
else:
|
||||
# Remote connection - Proxy Minion, connect over http(s)
|
||||
log.info("Nxapi connection arguments: {0}".format(self.nxargs))
|
||||
log.info("Connecting over {}".format(self.nxargs["transport"]))
|
||||
self.connection = salt.utils.http.query
|
||||
|
||||
def _use_remote_connection(self, kwargs):
|
||||
"""
|
||||
Determine if connection is local or remote
|
||||
"""
|
||||
kwargs["host"] = kwargs.get("host")
|
||||
kwargs["username"] = kwargs.get("username")
|
||||
kwargs["password"] = kwargs.get("password")
|
||||
if (
|
||||
kwargs["host"] is None
|
||||
or kwargs["username"] is None
|
||||
or kwargs["password"] is None
|
||||
):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def _prepare_conn_args(self, kwargs):
|
||||
"""
|
||||
Set connection arguments for remote or local connection.
|
||||
"""
|
||||
kwargs["connect_over_uds"] = True
|
||||
kwargs["timeout"] = kwargs.get("timeout", 60)
|
||||
kwargs["cookie"] = kwargs.get("cookie", "admin")
|
||||
if self._use_remote_connection(kwargs):
|
||||
kwargs["transport"] = kwargs.get("transport", "https")
|
||||
if kwargs["transport"] == "https":
|
||||
kwargs["port"] = kwargs.get("port", 443)
|
||||
else:
|
||||
kwargs["port"] = kwargs.get("port", 80)
|
||||
kwargs["verify"] = kwargs.get("verify", True)
|
||||
if isinstance(kwargs["verify"], bool):
|
||||
kwargs["verify_ssl"] = kwargs["verify"]
|
||||
else:
|
||||
kwargs["ca_bundle"] = kwargs["verify"]
|
||||
kwargs["connect_over_uds"] = False
|
||||
return kwargs
|
||||
|
||||
def _build_request(self, type, commands):
|
||||
"""
|
||||
Build NX-API JSON request.
|
||||
"""
|
||||
request = {}
|
||||
headers = {
|
||||
"content-type": "application/json",
|
||||
}
|
||||
if self.nxargs["connect_over_uds"]:
|
||||
user = self.nxargs["cookie"]
|
||||
headers["cookie"] = "nxapi_auth=" + user + ":local"
|
||||
request["url"] = self.NXAPI_UDS_URI_PATH
|
||||
else:
|
||||
request["url"] = "{transport}://{host}:{port}{uri}".format(
|
||||
transport=self.nxargs["transport"],
|
||||
host=self.nxargs["host"],
|
||||
port=self.nxargs["port"],
|
||||
uri=self.NXAPI_REMOTE_URI_PATH,
|
||||
)
|
||||
|
||||
if isinstance(commands, (list, set, tuple)):
|
||||
commands = " ; ".join(commands)
|
||||
payload = {}
|
||||
# Some versions of NX-OS fail to process the payload properly if
|
||||
# 'input' gets serialized before 'type' and the payload of 'input'
|
||||
# contains the string 'type'. Use an ordered dict to enforce ordering.
|
||||
payload["ins_api"] = collections.OrderedDict()
|
||||
payload["ins_api"]["version"] = self.NXAPI_VERSION
|
||||
payload["ins_api"]["type"] = type
|
||||
payload["ins_api"]["chunk"] = "0"
|
||||
payload["ins_api"]["sid"] = "1"
|
||||
payload["ins_api"]["input"] = commands
|
||||
payload["ins_api"]["output_format"] = "json"
|
||||
|
||||
request["headers"] = headers
|
||||
request["payload"] = json.dumps(payload)
|
||||
request["opts"] = {"http_request_timeout": self.nxargs["timeout"]}
|
||||
log.info("request: {0}".format(request))
|
||||
return request
|
||||
|
||||
def request(self, type, command_list):
|
||||
"""
|
||||
Send NX-API JSON request to the NX-OS device.
|
||||
"""
|
||||
req = self._build_request(type, command_list)
|
||||
if self.nxargs["connect_over_uds"]:
|
||||
self.connection.request("POST", req["url"], req["payload"], req["headers"])
|
||||
response = self.connection.getresponse()
|
||||
else:
|
||||
response = self.connection(
|
||||
req["url"],
|
||||
method="POST",
|
||||
opts=req["opts"],
|
||||
data=req["payload"],
|
||||
header_dict=req["headers"],
|
||||
decode=True,
|
||||
decode_type="json",
|
||||
**self.nxargs
|
||||
)
|
||||
|
||||
return self.parse_response(response, command_list)
|
||||
|
||||
def parse_response(self, response, command_list):
|
||||
"""
|
||||
Parse NX-API JSON response from the NX-OS device.
|
||||
"""
|
||||
# Check for 500 level NX-API Server Errors
|
||||
if isinstance(response, collections.Iterable) and "status" in response:
|
||||
if int(response["status"]) >= 500:
|
||||
raise NxosError("{}".format(response))
|
||||
else:
|
||||
raise NxosError("NX-API Request Not Supported: {}".format(response))
|
||||
|
||||
if isinstance(response, collections.Iterable):
|
||||
body = response["dict"]
|
||||
else:
|
||||
body = response
|
||||
|
||||
if self.nxargs["connect_over_uds"]:
|
||||
body = json.loads(response.read().decode("utf-8"))
|
||||
|
||||
# Proceed with caution. The JSON may not be complete.
|
||||
# Don't just return body['ins_api']['outputs']['output'] directly.
|
||||
output = body.get("ins_api")
|
||||
if output is None:
|
||||
raise NxosClientError("Unexpected JSON output\n{0}".format(body))
|
||||
if output.get("outputs"):
|
||||
output = output["outputs"]
|
||||
if output.get("output"):
|
||||
output = output["output"]
|
||||
|
||||
# The result list stores results for each command that was sent to
|
||||
# nxapi.
|
||||
result = []
|
||||
# Keep track of successful commands using previous_commands list so
|
||||
# they can be displayed if a specific command fails in a chain of
|
||||
# commands.
|
||||
previous_commands = []
|
||||
|
||||
# Make sure output and command_list lists to be processed in the
|
||||
# subesequent loop.
|
||||
if not isinstance(output, list):
|
||||
output = [output]
|
||||
if not isinstance(command_list, list):
|
||||
command_list = [command_list]
|
||||
if len(command_list) == 1 and ";" in command_list[0]:
|
||||
command_list = [cmd.strip() for cmd in command_list[0].split(";")]
|
||||
|
||||
for cmd_result, cmd in zip(output, command_list):
|
||||
code = cmd_result.get("code")
|
||||
msg = cmd_result.get("msg")
|
||||
log.info("command {}:".format(cmd))
|
||||
log.info("PARSE_RESPONSE: {0} {1}".format(code, msg))
|
||||
if code == "400":
|
||||
raise CommandExecutionError(
|
||||
{
|
||||
"rejected_input": cmd,
|
||||
"code": code,
|
||||
"message": msg,
|
||||
"cli_error": cmd_result.get("clierror"),
|
||||
"previous_commands": previous_commands,
|
||||
}
|
||||
)
|
||||
elif code == "413":
|
||||
raise NxosRequestNotSupported("Error 413: {}".format(msg))
|
||||
elif code != "200":
|
||||
raise NxosError("Unknown Error: {}, Code: {}".format(msg, code))
|
||||
else:
|
||||
previous_commands.append(cmd)
|
||||
result.append(cmd_result["body"])
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def nxapi_request(commands, method="cli_show", **kwargs):
|
||||
"""
|
||||
Send exec and config commands to the NX-OS device over NX-API.
|
||||
|
||||
commands
|
||||
The exec or config commands to be sent.
|
||||
|
||||
method:
|
||||
``cli_show_ascii``: Return raw test or unstructured output.
|
||||
``cli_show``: Return structured output.
|
||||
``cli_conf``: Send configuration commands to the device.
|
||||
Defaults to ``cli_show``.
|
||||
|
||||
transport: ``https``
|
||||
Specifies the type of connection transport to use. Valid values for the
|
||||
connection are ``http``, and ``https``.
|
||||
|
||||
host: ``localhost``
|
||||
The IP address or DNS host name of the device.
|
||||
|
||||
username: ``admin``
|
||||
The username to pass to the device to authenticate the NX-API connection.
|
||||
|
||||
password
|
||||
The password to pass to the device to authenticate the NX-API connection.
|
||||
|
||||
port
|
||||
The TCP port of the endpoint for the NX-API connection. If this keyword is
|
||||
not specified, the default value is automatically determined by the
|
||||
transport type (``80`` for ``http``, or ``443`` for ``https``).
|
||||
|
||||
timeout: ``60``
|
||||
Time in seconds to wait for the device to respond. Default: 60 seconds.
|
||||
|
||||
verify: ``True``
|
||||
Either a boolean, in which case it controls whether we verify the NX-API
|
||||
TLS certificate, or a string, in which case it must be a path to a CA bundle
|
||||
to use. Defaults to ``True``.
|
||||
"""
|
||||
client = NxapiClient(**kwargs)
|
||||
return client.request(method, commands)
|
||||
|
||||
|
||||
def ping(**kwargs):
|
||||
"""
|
||||
Verify connection to the NX-OS device over UDS.
|
||||
"""
|
||||
return NxapiClient(**kwargs).nxargs["connect_over_uds"]
|
||||
|
||||
|
||||
# Grains Functions
|
||||
|
||||
|
||||
def _parser(block):
|
||||
return re.compile("^{block}\n(?:^[ \n].*$\n?)+".format(block=block), re.MULTILINE)
|
||||
|
||||
|
||||
def _parse_software(data):
|
||||
"""
|
||||
Internal helper function to parse sotware grain information.
|
||||
"""
|
||||
ret = {"software": {}}
|
||||
software = _parser("Software").search(data).group(0)
|
||||
matcher = re.compile("^ ([^:]+): *([^\n]+)", re.MULTILINE)
|
||||
for line in matcher.finditer(software):
|
||||
key, val = line.groups()
|
||||
ret["software"][key] = val
|
||||
return ret["software"]
|
||||
|
||||
|
||||
def _parse_hardware(data):
|
||||
"""
|
||||
Internal helper function to parse hardware grain information.
|
||||
"""
|
||||
ret = {"hardware": {}}
|
||||
hardware = _parser("Hardware").search(data).group(0)
|
||||
matcher = re.compile("^ ([^:\n]+): *([^\n]+)", re.MULTILINE)
|
||||
for line in matcher.finditer(hardware):
|
||||
key, val = line.groups()
|
||||
ret["hardware"][key] = val
|
||||
return ret["hardware"]
|
||||
|
||||
|
||||
def _parse_plugins(data):
|
||||
"""
|
||||
Internal helper function to parse plugin grain information.
|
||||
"""
|
||||
ret = {"plugins": []}
|
||||
plugins = _parser("plugin").search(data).group(0)
|
||||
matcher = re.compile("^ (?:([^,]+), )+([^\n]+)", re.MULTILINE)
|
||||
for line in matcher.finditer(plugins):
|
||||
ret["plugins"].extend(line.groups())
|
||||
return ret["plugins"]
|
||||
|
||||
|
||||
def version_info():
|
||||
client = NxapiClient()
|
||||
return client.request("cli_show_ascii", "show version")[0]
|
||||
|
||||
|
||||
def system_info(data):
|
||||
"""
|
||||
Helper method to return parsed system_info
|
||||
from the 'show version' command.
|
||||
"""
|
||||
if not data:
|
||||
return {}
|
||||
info = {
|
||||
"software": _parse_software(data),
|
||||
"hardware": _parse_hardware(data),
|
||||
"plugins": _parse_plugins(data),
|
||||
}
|
||||
return {"nxos": info}
|
|
@ -38,6 +38,12 @@ try:
|
|||
except ImportError:
|
||||
HAS_CRYPT = False
|
||||
|
||||
try:
|
||||
import passlib.context
|
||||
|
||||
HAS_PASSLIB = True
|
||||
except ImportError:
|
||||
HAS_PASSLIB = False
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -70,23 +76,58 @@ def secure_password(length=20, use_random=True):
|
|||
raise CommandExecutionError(six.text_type(exc))
|
||||
|
||||
|
||||
def gen_hash(crypt_salt=None, password=None, algorithm="sha512"):
|
||||
if HAS_CRYPT:
|
||||
methods = {m.name.lower(): m for m in crypt.methods}
|
||||
else:
|
||||
methods = {}
|
||||
known_methods = ["sha512", "sha256", "blowfish", "md5", "crypt"]
|
||||
|
||||
|
||||
def _fallback_gen_hash(crypt_salt=None, password=None, algorithm=None):
|
||||
"""
|
||||
Generate a /etc/shadow-compatible hash for a non-local system
|
||||
"""
|
||||
if algorithm is None:
|
||||
algorithm = 0
|
||||
|
||||
# these are the passlib equivalents to the 'known_methods' defined in crypt
|
||||
schemes = ["sha512_crypt", "sha256_crypt", "bcrypt", "md5_crypt", "des_crypt"]
|
||||
|
||||
ctx = passlib.context.CryptContext(schemes=schemes)
|
||||
return ctx.hash(
|
||||
password, salt=crypt_salt, scheme=schemes[known_methods.index(algorithm)]
|
||||
)
|
||||
|
||||
|
||||
def gen_hash(crypt_salt=None, password=None, algorithm=None, force=False):
|
||||
"""
|
||||
Generate /etc/shadow hash
|
||||
"""
|
||||
if not HAS_CRYPT:
|
||||
raise SaltInvocationError("No crypt module for windows")
|
||||
|
||||
hash_algorithms = dict(md5="$1$", blowfish="$2a$", sha256="$5$", sha512="$6$")
|
||||
if algorithm not in hash_algorithms:
|
||||
raise SaltInvocationError("Algorithm '{0}' is not supported".format(algorithm))
|
||||
|
||||
if password is None:
|
||||
password = secure_password()
|
||||
|
||||
if crypt_salt is None:
|
||||
crypt_salt = secure_password(8)
|
||||
if algorithm not in methods:
|
||||
if force and HAS_PASSLIB:
|
||||
return _fallback_gen_hash(crypt_salt, password, algorithm)
|
||||
else:
|
||||
raise SaltInvocationError(
|
||||
"Algorithm '{}' is not natively supported by this platform, use force=True with passlib installed to override.".format(
|
||||
algorithm
|
||||
)
|
||||
)
|
||||
|
||||
crypt_salt = hash_algorithms[algorithm] + crypt_salt
|
||||
if algorithm is None:
|
||||
# use the most secure natively supported method
|
||||
algorithm = crypt.methods[0].name.lower()
|
||||
|
||||
if crypt_salt is None:
|
||||
crypt_salt = methods[algorithm]
|
||||
elif methods[algorithm].ident:
|
||||
crypt_salt = "${}${}".format(methods[algorithm].ident, crypt_salt)
|
||||
else: # method is crypt (DES)
|
||||
if len(crypt_salt) != 2:
|
||||
raise ValueError(
|
||||
"Invalid salt for hash, 'crypt' salt must be 2 characters."
|
||||
)
|
||||
|
||||
return crypt.crypt(password, crypt_salt)
|
||||
|
|
1
tests/unit/modules/nxos/__init__.py
Normal file
1
tests/unit/modules/nxos/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
251
tests/unit/modules/nxos/nxos_config.py
Normal file
251
tests/unit/modules/nxos/nxos_config.py
Normal file
File diff suppressed because one or more lines are too long
15
tests/unit/modules/nxos/nxos_grains.py
Normal file
15
tests/unit/modules/nxos/nxos_grains.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
n9k_grains = {
|
||||
"nxos": {
|
||||
"software": {
|
||||
"BIOS": "version 07.66",
|
||||
"NXOS": "version 7.0(3)I7(8) [build 7.0(3)I7(7.16)]",
|
||||
"BIOS compile time": "06/12/2019",
|
||||
"NXOS image file is": "bootflash:///nxos.7.0.3.I7.7.16.bin",
|
||||
"NXOS compile time": "11/29/2019 13:00:00 [11/29/2019 21:52:12]",
|
||||
},
|
||||
"hardware": {"Device name": "n9k-device", "bootflash": "21693714 kB"},
|
||||
"plugins": ["Core Plugin", "Ethernet Plugin"],
|
||||
}
|
||||
}
|
128
tests/unit/modules/nxos/nxos_n36k.py
Normal file
128
tests/unit/modules/nxos/nxos_n36k.py
Normal file
|
@ -0,0 +1,128 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
:codeauthor: Thomas Stoner <tmstoner@cisco.com>
|
||||
"""
|
||||
|
||||
# Copyright (c) 2018 Cisco and/or its affiliates.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from tests.unit.modules.nxos.nxos_platform import NXOSPlatform
|
||||
|
||||
|
||||
class N36KPlatform(NXOSPlatform):
|
||||
|
||||
""" Cisco Systems N36K Platform Unit Test Object """
|
||||
|
||||
chassis = "Nexus3000 N3K-C36180YC-R Chassis"
|
||||
|
||||
# Captured output from: show install all impact nxos <image>
|
||||
|
||||
show_install_all_impact = """
|
||||
Installer will perform impact only check. Please wait.
|
||||
|
||||
Verifying image bootflash:/$IMAGE for boot variable "nxos".
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Verifying image type.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "nxos" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "bios" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing module support checks.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Notifying services about system upgrade.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
|
||||
Compatibility check is done:
|
||||
Module bootable Impact Install-type Reason
|
||||
------ -------- -------------- ------------ ------
|
||||
1 yes disruptive reset default upgrade is not hitless
|
||||
|
||||
|
||||
|
||||
Images will be upgraded according to following table:
|
||||
Module Image Running-Version(pri:alt) New-Version Upg-Required
|
||||
------ ---------- ---------------------------------------- -------------------- ------------
|
||||
1 nxos $CVER $NVER $REQ
|
||||
1 bios v01.13(09/27/2018):v01.08(08/22/2017) v01.13(09/27/2018) no
|
||||
"""
|
||||
|
||||
# Captured output from: install all nxos <image>
|
||||
|
||||
install_all_disruptive_success = """
|
||||
Installer will perform compatibility check first. Please wait.
|
||||
Installer is forced disruptive
|
||||
|
||||
Verifying image bootflash:/$IMAGE for boot variable "nxos".
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Verifying image type.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "nxos" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "bios" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing module support checks.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Notifying services about system upgrade.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
|
||||
Compatibility check is done:
|
||||
Module bootable Impact Install-type Reason
|
||||
------ -------- -------------- ------------ ------
|
||||
1 yes disruptive reset default upgrade is not hitless
|
||||
|
||||
|
||||
|
||||
Images will be upgraded according to following table:
|
||||
Module Image Running-Version(pri:alt) New-Version Upg-Required
|
||||
------ ---------- ---------------------------------------- -------------------- ------------
|
||||
1 nxos $CVER $NVER $REQ
|
||||
1 bios v01.13(09/27/2018):v01.08(08/22/2017) v01.13(09/27/2018) no
|
||||
|
||||
|
||||
Install is in progress, please wait.
|
||||
|
||||
Performing runtime checks.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Setting boot variables.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing configuration copy.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Module 1: Refreshing compact flash and upgrading bios/loader/bootrom.
|
||||
Warning: please do not remove or power off the module at this time.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
Finishing the upgrade, switch will reboot in 10 seconds.
|
||||
"""
|
129
tests/unit/modules/nxos/nxos_n3k.py
Normal file
129
tests/unit/modules/nxos/nxos_n3k.py
Normal file
|
@ -0,0 +1,129 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
:codeauthor: Thomas Stoner <tmstoner@cisco.com>
|
||||
"""
|
||||
|
||||
# Copyright (c) 2018 Cisco and/or its affiliates.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from tests.unit.modules.nxos.nxos_platform import NXOSPlatform
|
||||
|
||||
|
||||
class N3KPlatform(NXOSPlatform):
|
||||
|
||||
""" Cisco Systems N3K Platform Unit Test Object """
|
||||
|
||||
chassis = "Nexus 3172 Chassis"
|
||||
|
||||
# Captured output from: show install all impact nxos <image>
|
||||
|
||||
show_install_all_impact = """
|
||||
Installer will perform impact only check. Please wait.
|
||||
|
||||
Verifying image bootflash:/$IMAGE for boot variable "nxos".
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Verifying image type.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "nxos" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "bios" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing module support checks.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Notifying services about system upgrade.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
|
||||
Compatibility check is done:
|
||||
Module bootable Impact Install-type Reason
|
||||
------ -------- -------------- ------------ ------
|
||||
1 yes disruptive reset default upgrade is not hitless
|
||||
|
||||
|
||||
|
||||
Images will be upgraded according to following table:
|
||||
Module Image Running-Version(pri:alt) New-Version Upg-Required
|
||||
------ ---------- ---------------------------------------- -------------------- ------------
|
||||
1 nxos $CVER $NVER $REQ
|
||||
1 bios v01.11(03/06/2018):v01.08(08/22/2017) v01.13(09/27/2018) yes
|
||||
"""
|
||||
|
||||
# Captured output from: show install impact nxos <image>
|
||||
|
||||
install_all_disruptive_success = """
|
||||
Installer will perform compatibility check first. Please wait.
|
||||
Installer is forced disruptive
|
||||
|
||||
Verifying image bootflash:/$IMAGE for boot variable "nxos".
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Verifying image type.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "nxos" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "bios" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing module support checks.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Notifying services about system upgrade.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
|
||||
Compatibility check is done:
|
||||
Module bootable Impact Install-type Reason
|
||||
------ -------- -------------- ------------ ------
|
||||
1 yes disruptive reset default upgrade is not hitless
|
||||
|
||||
|
||||
|
||||
Images will be upgraded according to following table:
|
||||
Module Image Running-Version(pri:alt) New-Version Upg-Required
|
||||
------ ---------- ---------------------------------------- -------------------- ------------
|
||||
1 nxos $CVER $NVER $REQ
|
||||
1 bios v01.11(03/06/2018):v01.08(08/22/2017) v01.13(09/27/2018) yes
|
||||
|
||||
|
||||
|
||||
Install is in progress, please wait.
|
||||
|
||||
Performing runtime checks.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Setting boot variables.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing configuration copy.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Module 1: Refreshing compact flash and upgrading bios/loader/bootrom.
|
||||
Warning: please do not remove or power off the module at this time.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
Finishing the upgrade, switch will reboot in 10 seconds.
|
||||
"""
|
148
tests/unit/modules/nxos/nxos_n5k.py
Normal file
148
tests/unit/modules/nxos/nxos_n5k.py
Normal file
|
@ -0,0 +1,148 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
:codeauthor: Thomas Stoner <tmstoner@cisco.com>
|
||||
"""
|
||||
|
||||
# Copyright (c) 2018 Cisco and/or its affiliates.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from tests.unit.modules.nxos.nxos_platform import NXOSPlatform
|
||||
|
||||
|
||||
class N5KPlatform(NXOSPlatform):
|
||||
|
||||
""" Cisco Systems N5K Platform Unit Test Object """
|
||||
|
||||
chassis = "cisco Nexus 5672UP 16G-FC Chassis"
|
||||
|
||||
# Captured output from: show install all impact kickstart <kimage> system <image>
|
||||
|
||||
show_install_all_impact = """
|
||||
Verifying image bootflash:/$KIMAGE for boot variable "kickstart".
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Verifying image bootflash:/$IMAGE for boot variable "system".
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Verifying image type.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Extracting "system" version from image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Extracting "kickstart" version from image bootflash:/$KIMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Extracting "bios" version from image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing module support checks.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
|
||||
Compatibility check is done:
|
||||
Module bootable Impact Install-type Reason
|
||||
------ -------- -------------- ------------ ------
|
||||
0 yes disruptive reset ISSD is not supported and switch will reset with ascii configuration
|
||||
1 yes disruptive reset ISSD is not supported and switch will reset with ascii configuration
|
||||
2 yes disruptive reset ISSD is not supported and switch will reset with ascii configuration
|
||||
|
||||
|
||||
|
||||
Images will be upgraded according to following table:
|
||||
Module Image Running-Version New-Version Upg-Required
|
||||
------ ---------------- ---------------------- ---------------------- ------------
|
||||
0 system $CVER $NVER $REQ
|
||||
0 kickstart $CVER $NVER $REQ
|
||||
0 bios v0.1.9(03/09/2016) v0.1.6(12/03/2015) no
|
||||
0 power-seq SF-uC:37, SF-FPGA:35 SF-uC:37, SF-FPGA:35 no
|
||||
0 iofpga v0.0.0.39 v0.0.0.39 no
|
||||
1 iofpga v0.0.0.18 v0.0.0.18 no
|
||||
2 iofpga v0.0.0.18 v0.0.0.18 no
|
||||
|
||||
Warning : ISSD is not supported and switch will reset with ASCII configuration.
|
||||
All incompatible configuration will be lost in the target release.
|
||||
Please also refer the downgrade procedure documentation of the release for more details.
|
||||
"""
|
||||
|
||||
# Captured output from: install all kickstart <kimage> system <image> '''
|
||||
|
||||
install_all_disruptive_success = """
|
||||
Verifying image bootflash:/$KIMAGE for boot variable "kickstart".
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Verifying image bootflash:/$IMAGE for boot variable "system".
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Verifying image type.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Extracting "system" version from image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Extracting "kickstart" version from image bootflash:/$KIMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Extracting "bios" version from image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing module support checks.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
|
||||
Compatibility check is done:
|
||||
Module bootable Impact Install-type Reason
|
||||
------ -------- -------------- ------------ ------
|
||||
0 yes disruptive reset ISSD is not supported and switch will reset with ascii configuration
|
||||
1 yes disruptive reset ISSD is not supported and switch will reset with ascii configuration
|
||||
2 yes disruptive reset ISSD is not supported and switch will reset with ascii configuration
|
||||
|
||||
|
||||
|
||||
Images will be upgraded according to following table:
|
||||
Module Image Running-Version New-Version Upg-Required
|
||||
------ ---------------- ---------------------- ---------------------- ------------
|
||||
0 system $CVER $NVER $REQ
|
||||
0 kickstart $CKVER $NKVER $KREQ
|
||||
0 bios v0.1.9(03/09/2016) v0.1.6(12/03/2015) no
|
||||
0 power-seq SF-uC:37, SF-FPGA:35 SF-uC:37, SF-FPGA:35 no
|
||||
0 iofpga v0.0.0.39 v0.0.0.39 no
|
||||
1 iofpga v0.0.0.18 v0.0.0.18 no
|
||||
2 iofpga v0.0.0.18 v0.0.0.18 no
|
||||
|
||||
Warning : ISSD is not supported and switch will reset with ASCII configuration.
|
||||
All incompatible configuration will be lost in the target release.
|
||||
Please also refer the downgrade procedure documentation of the release for more details.
|
||||
|
||||
Install is in progress, please wait.
|
||||
|
||||
Performing runtime checks.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Setting boot variables.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing configuration copy.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Converting startup config.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Finishing the upgrade, switch will reboot in 10 seconds.
|
||||
"""
|
146
tests/unit/modules/nxos/nxos_n7k.py
Normal file
146
tests/unit/modules/nxos/nxos_n7k.py
Normal file
|
@ -0,0 +1,146 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
:codeauthor: Thomas Stoner <tmstoner@cisco.com>
|
||||
"""
|
||||
|
||||
# Copyright (c) 2018 Cisco and/or its affiliates.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from tests.unit.modules.nxos.nxos_platform import NXOSPlatform
|
||||
|
||||
|
||||
class N7KPlatform(NXOSPlatform):
|
||||
|
||||
""" Cisco Systems N7K Platform Unit Test Object """
|
||||
|
||||
chassis = "Nexus7000 C7010 (10 Slot) Chassis"
|
||||
|
||||
# Captured output from: show install all impact kickstart <kimage> system <image>
|
||||
|
||||
show_install_all_impact = """
|
||||
Installer will perform impact only check. Please wait.
|
||||
|
||||
Verifying image bootflash:/$KIMAGE for boot variable "kickstart".
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Verifying image bootflash:/$IMAGE for boot variable "system".
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing module support checks.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Verifying image type.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Extracting "system" version from image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Extracting "kickstart" version from image bootflash:/$KIMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Extracting "bios" version from image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Extracting "lc1n7k" version from image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Notifying services about system upgrade.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
|
||||
Compatibility check is done:
|
||||
Module bootable Impact Install-type Reason
|
||||
------ -------- -------------- ------------ ------
|
||||
2 yes disruptive reset Incompatible image
|
||||
3 yes disruptive reset Incompatible image
|
||||
|
||||
|
||||
|
||||
Images will be upgraded according to following table:
|
||||
Module Image Running-Version(pri:alt) New-Version Upg-Required
|
||||
------ ---------- ---------------------------------------- -------------------- ------------
|
||||
2 system $CVER $NVER $REQ
|
||||
2 kickstart $CKVER $NKVER $KREQ
|
||||
2 bios v2.12.0(05/29/2013):v2.12.0(05/29/2013) v2.13.0(10/23/2018) yes
|
||||
3 lc1n7k 8.3(0)SK(1) 8.3(2) yes
|
||||
3 bios v1.10.21(11/26/12):v1.10.21(11/26/12) v1.10.21(11/26/12) no
|
||||
"""
|
||||
|
||||
# Captured output from: install all kickstart <kimage> system <image>
|
||||
|
||||
install_all_disruptive_success = """
|
||||
Installer will perform compatibility check first. Please wait.
|
||||
|
||||
Verifying image bootflash:/$KIMAGE for boot variable "kickstart".
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Verifying image bootflash:/$IMAGE for boot variable "system".
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing module support checks.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Verifying image type.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Extracting "system" version from image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Extracting "kickstart" version from image bootflash:/$KIMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Extracting "bios" version from image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Notifying services about system upgrade.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
|
||||
Compatibility check is done:
|
||||
Module bootable Impact Install-type Reason
|
||||
------ -------- -------------- ------------ ------
|
||||
5 yes disruptive reset Reset due to single supervisor
|
||||
|
||||
|
||||
|
||||
Images will be upgraded according to following table:
|
||||
Module Image Running-Version(pri:alt) New-Version Upg-Required
|
||||
------ ---------- ---------------------------------------- -------------------- ------------
|
||||
5 system $CVER $NVER $REQ
|
||||
5 kickstart $CKVER $NKVER $KREQ
|
||||
5 bios v2.12.0(05/29/2013):v2.12.0(05/29/2013) v2.13.0(10/23/2018) yes
|
||||
|
||||
|
||||
Install is in progress, please wait.
|
||||
|
||||
Performing runtime checks.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Setting boot variables.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing configuration copy.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Module 5: Upgrading bios/loader/bootrom.
|
||||
Warning: please do not remove or power off the module at this time.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Finishing the upgrade, switch will reboot in 10 seconds.
|
||||
"""
|
129
tests/unit/modules/nxos/nxos_n93k.py
Normal file
129
tests/unit/modules/nxos/nxos_n93k.py
Normal file
|
@ -0,0 +1,129 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
:codeauthor: Thomas Stoner <tmstoner@cisco.com>
|
||||
"""
|
||||
|
||||
# Copyright (c) 2018 Cisco and/or its affiliates.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from tests.unit.modules.nxos.nxos_platform import NXOSPlatform
|
||||
|
||||
|
||||
class N93KPlatform(NXOSPlatform):
|
||||
|
||||
""" Cisco Systems N93K Platform Unit Test Object """
|
||||
|
||||
chassis = "Nexus9000 C9396PX Chassis"
|
||||
|
||||
# Captured output from: show install all nxos <image>
|
||||
|
||||
show_install_all_impact = """
|
||||
Installer will perform impact only check. Please wait.
|
||||
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Verifying image type.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "nxos" version info using image bootflash:$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "bios" version info using image bootflash:$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing module support checks.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Notifying services about system upgrade.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
|
||||
Compatibility check is done:
|
||||
Module bootable Impact Install-type Reason
|
||||
------ -------- -------------- ------------ ------
|
||||
1 yes disruptive reset default upgrade is not hitless
|
||||
|
||||
|
||||
|
||||
Images will be upgraded according to following table:
|
||||
Module Image Running-Version(pri:alt) New-Version Upg-Required
|
||||
------ ---------- ---------------------------------------- -------------------- ------------
|
||||
1 nxos $CVER $NVER $REQ
|
||||
1 bios v07.64(05/16/2018):v07.06(03/02/2014) v07.64(05/16/2018) no
|
||||
"""
|
||||
|
||||
# Captured output from: install all nxos <image>
|
||||
|
||||
install_all_disruptive_success = """
|
||||
Installer will perform compatibility check first. Please wait.
|
||||
Installer is forced disruptive
|
||||
|
||||
Verifying image bootflash:/$IMAGE for boot variable "nxos".
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Verifying image type.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "nxos" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "bios" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing module support checks.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Notifying services about system upgrade.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
|
||||
Compatibility check is done:
|
||||
Module bootable Impact Install-type Reason
|
||||
------ -------- -------------- ------------ ------
|
||||
1 yes disruptive reset default upgrade is not hitless
|
||||
|
||||
|
||||
|
||||
Images will be upgraded according to following table:
|
||||
Module Image Running-Version(pri:alt) New-Version Upg-Required
|
||||
------ ---------- ---------------------------------------- -------------------- ------------
|
||||
1 nxos $CVER $NVER $REQ
|
||||
1 bios v07.64(05/16/2018):v07.06(03/02/2014) v07.64(05/16/2018) no
|
||||
|
||||
|
||||
Install is in progress, please wait.
|
||||
|
||||
Performing runtime checks.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Setting boot variables.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing configuration copy.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Module 1: Refreshing compact flash and upgrading bios/loader/bootrom.
|
||||
Warning: please do not remove or power off the module at this time.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Converting startup config.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Finishing the upgrade, switch will reboot in 10 seconds.
|
||||
"""
|
249
tests/unit/modules/nxos/nxos_n93klxc.py
Normal file
249
tests/unit/modules/nxos/nxos_n93klxc.py
Normal file
|
@ -0,0 +1,249 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
:codeauthor: Thomas Stoner <tmstoner@cisco.com>
|
||||
"""
|
||||
|
||||
# Copyright (c) 2018 Cisco and/or its affiliates.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from tests.unit.modules.nxos.nxos_platform import NXOSPlatform
|
||||
|
||||
|
||||
class N93KLXCPlatform(NXOSPlatform):
|
||||
|
||||
""" Cisco Systems N93K (boot mode lxc) Platform Unit Test Object """
|
||||
|
||||
chassis = "Nexus9000 C9396PX (LXC) Chassis"
|
||||
|
||||
# Captured output from: show install all nxos <image>
|
||||
|
||||
show_install_all_impact = """
|
||||
Installer will perform impact only check. Please wait.
|
||||
|
||||
Verifying image bootflash:/$IMAGE for boot variable "nxos".
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Verifying image type.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "nxos" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "bios" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Notifying services about system upgrade.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
|
||||
Compatibility check is done:
|
||||
Module bootable Impact Install-type Reason
|
||||
------ -------- -------------- ------------ ------
|
||||
1 yes disruptive reset Host kernel is not compatible with target image
|
||||
27 yes disruptive reset Host kernel is not compatible with target image
|
||||
|
||||
|
||||
|
||||
Images will be upgraded according to following table:
|
||||
Module Image Running-Version(pri:alt) New-Version Upg-Required
|
||||
------ ---------- ---------------------------------------- -------------------- ------------
|
||||
1 lcn9k $CVER $NVER $REQ
|
||||
27 nxos $CVER $NVER $REQ
|
||||
27 bios v07.64(05/16/2018):v07.06(03/02/2014) v07.64(05/16/2018) no
|
||||
|
||||
|
||||
Additional info for this installation:
|
||||
--------------------------------------
|
||||
|
||||
"Host kernel is not compatible with target image.
|
||||
Disruptive ISSU will be performed "
|
||||
"""
|
||||
|
||||
# Captured output from: show install all nxos <image> non-disruptive
|
||||
|
||||
show_install_all_impact_non_disruptive = """
|
||||
Installer will perform impact only check. Please wait.
|
||||
|
||||
Verifying image bootflash:/$IMAGE for boot variable "nxos".
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Verifying image type.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "nxos" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "bios" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Notifying services about system upgrade.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
|
||||
Compatibility check is done:
|
||||
Module bootable Impact Install-type Reason
|
||||
------ -------- -------------- ------------ ------
|
||||
1 yes non-disruptive rolling
|
||||
27 yes non-disruptive reset
|
||||
|
||||
|
||||
|
||||
Images will be upgraded according to following table:
|
||||
Module Image Running-Version(pri:alt) New-Version Upg-Required
|
||||
------ ---------- ---------------------------------------- -------------------- ------------
|
||||
1 lcn9k $CVER $NVER $REQ
|
||||
27 nxos $CVER $NVER $REQ
|
||||
27 bios v07.64(05/16/2018):v07.06(03/02/2014) v07.65(09/04/2018) yes
|
||||
"""
|
||||
|
||||
install_all_disruptive_success = """
|
||||
Installer will perform compatibility check first. Please wait.
|
||||
Installer is forced disruptive
|
||||
|
||||
Verifying image bootflash:/$IMAGE for boot variable "nxos".
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Verifying image type.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "nxos" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "bios" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Notifying services about system upgrade.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
|
||||
Compatibility check is done:
|
||||
Module bootable Impact Install-type Reason
|
||||
------ -------- -------------- ------------ ------
|
||||
1 yes disruptive reset default upgrade is not hitless
|
||||
27 yes disruptive reset default upgrade is not hitless
|
||||
|
||||
|
||||
|
||||
Images will be upgraded according to following table:
|
||||
Module Image Running-Version(pri:alt) New-Version Upg-Required
|
||||
------ ---------- ---------------------------------------- -------------------- ------------
|
||||
1 lcn9k $CVER $NVER $REQ
|
||||
27 nxos $CVER $NVER $REQ
|
||||
27 bios v07.64(05/16/2018):v07.06(03/02/2014) v07.65(09/04/2018) yes
|
||||
|
||||
|
||||
Switch will be reloaded for disruptive upgrade.
|
||||
|
||||
|
||||
Install is in progress, please wait.
|
||||
|
||||
Setting boot variables.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing configuration copy.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Module 1: Refreshing compact flash and upgrading bios/loader/bootrom.
|
||||
Warning: please do not remove or power off the module at this time.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Module 27: Refreshing compact flash and upgrading bios/loader/bootrom.
|
||||
Warning: please do not remove or power off the module at this time.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
Finishing the upgrade, switch will reboot in 10 seconds.
|
||||
"""
|
||||
|
||||
# Captured output from: install all nxos <image> non-disruptive
|
||||
|
||||
install_all_non_disruptive_success = """
|
||||
Installer will perform compatibility check first. Please wait.
|
||||
|
||||
Verifying image bootflash:/$IMAGE for boot variable "nxos".
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Verifying image type.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "nxos" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "bios" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Notifying services about system upgrade.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
|
||||
Compatibility check is done:
|
||||
Module bootable Impact Install-type Reason
|
||||
------ -------- -------------- ------------ ------
|
||||
1 yes non-disruptive rolling
|
||||
27 yes non-disruptive reset
|
||||
|
||||
|
||||
|
||||
Images will be upgraded according to following table:
|
||||
Module Image Running-Version(pri:alt) New-Version Upg-Required
|
||||
------ ---------- ---------------------------------------- -------------------- ------------
|
||||
1 lcn9k $CVER $NVER $REQ
|
||||
27 nxos $CVER $NVER $REQ
|
||||
27 bios v07.65(09/04/2018):v07.06(03/02/2014) v07.64(05/16/2018) no
|
||||
|
||||
|
||||
Install is in progress, please wait.
|
||||
|
||||
Setting boot variables.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing configuration copy.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Module 1: Refreshing compact flash and upgrading bios/loader/bootrom.
|
||||
Warning: please do not remove or power off the module at this time.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Module 27: Refreshing compact flash and upgrading bios/loader/bootrom.
|
||||
Warning: please do not remove or power off the module at this time.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
Starting Standby Container, please wait.
|
||||
-- SUCCESS
|
||||
|
||||
Notifying services about the switchover.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
"Switching over onto standby".
|
||||
|
||||
Non-disruptive upgrading.
|
||||
[# ] 0%
|
||||
Module 1 upgrade completed successfully.
|
||||
.
|
||||
|
||||
Non-disruptive upgrading.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
Install has been successful.
|
||||
"""
|
349
tests/unit/modules/nxos/nxos_n95k.py
Normal file
349
tests/unit/modules/nxos/nxos_n95k.py
Normal file
|
@ -0,0 +1,349 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
:codeauthor: Thomas Stoner <tmstoner@cisco.com>
|
||||
"""
|
||||
|
||||
# Copyright (c) 2018 Cisco and/or its affiliates.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from tests.unit.modules.nxos.nxos_platform import NXOSPlatform
|
||||
|
||||
# pylint: disable-msg=C0103
|
||||
|
||||
|
||||
class N95KPlatform(NXOSPlatform):
|
||||
|
||||
""" Cisco Systems N9K Platform Unit Test Object """
|
||||
|
||||
chassis = "Nexus9000 C9508 (8 Slot) Chassis"
|
||||
|
||||
# Captured output from: show install all impact nxos <image>
|
||||
|
||||
show_install_all_impact = """
|
||||
Installer will perform impact only check. Please wait.
|
||||
|
||||
Verifying image bootflash:/$IMAGE for boot variable "nxos".
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Verifying image type.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "bios" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "nxos" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing module support checks.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Notifying services about system upgrade.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
|
||||
Compatibility check is done:
|
||||
Module bootable Impact Install-type Reason
|
||||
------ -------- -------------- ------------ ------
|
||||
1 yes non-disruptive none
|
||||
22 yes non-disruptive none
|
||||
24 yes non-disruptive none
|
||||
26 yes non-disruptive none
|
||||
28 yes non-disruptive none
|
||||
29 yes non-disruptive none
|
||||
30 yes non-disruptive none
|
||||
|
||||
|
||||
|
||||
Images will be upgraded according to following table:
|
||||
Module Image Running-Version(pri:alt) New-Version Upg-Required
|
||||
------ ---------- ---------------------------------------- -------------------- ------------
|
||||
1 lcn9k $CVER $NVER $REQ
|
||||
1 bios v01.48(00:v01.42(00 v01.48(00 no
|
||||
22 lcn9k $CVER $NVER $REQ
|
||||
22 bios v01.48(00:v01.42(00 v01.48(00 no
|
||||
24 lcn9k $CVER $NVER $REQ
|
||||
24 bios v01.48(00:v01.42(00 v01.48(00 no
|
||||
26 lcn9k $CVER $NVER $REQ
|
||||
26 bios v01.48(00:v01.42(00 v01.48(00 no
|
||||
28 nxos $CVER $NVER $REQ
|
||||
28 bios v08.32(10/18/2016):v08.06(09/10/2014) v08.32(10/18/2016) no
|
||||
29 lcn9k $CVER $NVER $REQ
|
||||
29 bios v01.48(00:v01.42(00 v01.48(00 no
|
||||
30 lcn9k $CVER $NVER $REQ
|
||||
30 bios v01.48(00:v01.42(00 v01.48(00 no
|
||||
"""
|
||||
|
||||
# Captured output from: show install all impact nxos <image> non-disruptive
|
||||
|
||||
show_install_all_impact_non_disruptive = """
|
||||
Installer will perform impact only check. Please wait.
|
||||
|
||||
Verifying image bootflash:/$IMAGE for boot variable "nxos".
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Verifying image type.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "bios" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "nxos" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing module support checks.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Notifying services about system upgrade.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
|
||||
Compatibility check is done:
|
||||
Module bootable Impact Install-type Reason
|
||||
------ -------- -------------- ------------ ------
|
||||
1 yes non-disruptive none
|
||||
22 yes non-disruptive none
|
||||
24 yes non-disruptive none
|
||||
26 yes non-disruptive none
|
||||
28 yes non-disruptive none
|
||||
29 yes non-disruptive none
|
||||
30 yes non-disruptive none
|
||||
|
||||
|
||||
|
||||
Images will be upgraded according to following table:
|
||||
Module Image Running-Version(pri:alt) New-Version Upg-Required
|
||||
------ ---------- ---------------------------------------- -------------------- ------------
|
||||
1 lcn9k $CVER $NVER $REQ
|
||||
1 bios v01.48(00:v01.42(00 v01.48(00 no
|
||||
22 lcn9k $CVER $NVER $REQ
|
||||
22 bios v01.48(00:v01.42(00 v01.48(00 no
|
||||
24 lcn9k $CVER $NVER $REQ
|
||||
24 bios v01.48(00:v01.42(00 v01.48(00 no
|
||||
26 lcn9k $CVER $NVER $REQ
|
||||
26 bios v01.48(00:v01.42(00 v01.48(00 no
|
||||
28 nxos $CVER $NVER $REQ
|
||||
28 bios v08.35(08/31/2018):v08.06(09/10/2014) v08.35(08/31/2018) no
|
||||
29 lcn9k $CVER $NVER $REQ
|
||||
29 bios v01.48(00:v01.42(00 v01.48(00 no
|
||||
30 lcn9k $CVER $NVER $REQ
|
||||
30 bios v01.48(00:v01.42(00 v01.48(00 no
|
||||
"""
|
||||
|
||||
# Captured output from: install all nxos <image> non-disruptive
|
||||
|
||||
install_all_non_disruptive_success = """
|
||||
Installer will perform compatibility check first. Please wait.
|
||||
|
||||
Verifying image bootflash:/$IMAGE for boot variable "nxos".
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Verifying image type.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "bios" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "nxos" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing module support checks.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Notifying services about system upgrade.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
|
||||
Compatibility check is done:
|
||||
Module bootable Impact Install-type Reason
|
||||
------ -------- -------------- ------------ ------
|
||||
1 yes non-disruptive none
|
||||
22 yes non-disruptive none
|
||||
24 yes non-disruptive none
|
||||
26 yes non-disruptive none
|
||||
28 yes non-disruptive none
|
||||
29 yes non-disruptive none
|
||||
30 yes non-disruptive none
|
||||
|
||||
|
||||
|
||||
Images will be upgraded according to following table:
|
||||
Module Image Running-Version(pri:alt) New-Version Upg-Required
|
||||
------ ---------- ---------------------------------------- -------------------- ------------
|
||||
1 lcn9k $CVER $NVER $REQ
|
||||
1 bios v01.48(00:v01.42(00 v01.48(00 no
|
||||
22 lcn9k $CVER $NVER $REQ
|
||||
22 bios v01.48(00:v01.42(00 v01.48(00 no
|
||||
24 lcn9k $CVER $NVER $REQ
|
||||
24 bios v01.48(00:v01.42(00 v01.48(00 no
|
||||
26 lcn9k $CVER $NVER $REQ
|
||||
26 bios v01.48(00:v01.42(00 v01.48(00 no
|
||||
28 nxos $CVER $NVER $REQ
|
||||
28 bios v08.32(10/18/2016):v08.06(09/10/2014) v08.32(10/18/2016) no
|
||||
29 lcn9k $CVER $NVER $REQ
|
||||
29 bios v01.48(00:v01.42(00 v01.48(00 no
|
||||
30 lcn9k $CVER $NVER $REQ
|
||||
30 bios v01.48(00:v01.42(00 v01.48(00 no
|
||||
|
||||
|
||||
Install is in progress, please wait.
|
||||
|
||||
Performing runtime checks.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Setting boot variables.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing configuration copy.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Module 1: Refreshing compact flash and upgrading bios/loader/bootrom.
|
||||
Warning: please do not remove or power off the module at this time.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Module 22: Refreshing compact flash and upgrading bios/loader/bootrom.
|
||||
Warning: please do not remove or power off the module at this time.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Module 24: Refreshing compact flash and upgrading bios/loader/bootrom.
|
||||
Warning: please do not remove or power off the module at this time.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Module 26: Refreshing compact flash and upgrading bios/loader/bootrom.
|
||||
Warning: please do not remove or power off the module at this time.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Module 28: Refreshing compact flash and upgrading bios/loader/bootrom.
|
||||
Warning: please do not remove or power off the module at this time.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Module 29: Refreshing compact flash and upgrading bios/loader/bootrom.
|
||||
Warning: please do not remove or power off the module at this time.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Module 30: Refreshing compact flash and upgrading bios/loader/bootrom.
|
||||
Warning: please do not remove or power off the module at this time.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
Install has been successful.
|
||||
"""
|
||||
|
||||
# Captured output from: install all nxos <image>
|
||||
|
||||
install_all_disruptive_success = """
|
||||
Installer will perform compatibility check first. Please wait.
|
||||
Installer is forced disruptive
|
||||
|
||||
Verifying image bootflash:/$IMAGE for boot variable "nxos".
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Verifying image type.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "bios" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "nxos" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing module support checks.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Notifying services about system upgrade.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
|
||||
Compatibility check is done:
|
||||
Module bootable Impact Install-type Reason
|
||||
------ -------- -------------- ------------ ------
|
||||
1 yes disruptive reset default upgrade is not hitless
|
||||
22 yes disruptive reset default upgrade is not hitless
|
||||
24 yes disruptive reset default upgrade is not hitless
|
||||
26 yes disruptive reset default upgrade is not hitless
|
||||
28 yes disruptive reset default upgrade is not hitless
|
||||
29 yes disruptive reset default upgrade is not hitless
|
||||
30 yes disruptive reset default upgrade is not hitless
|
||||
|
||||
|
||||
|
||||
Images will be upgraded according to following table:
|
||||
Module Image Running-Version(pri:alt) New-Version Upg-Required
|
||||
------ ---------- ---------------------------------------- -------------------- ------------
|
||||
1 lcn9k $CVER $NVER $REQ
|
||||
1 bios v01.48(00:v01.42(00 v01.48(00 no
|
||||
22 lcn9k $CVER $NVER $REQ
|
||||
22 bios v01.48(00:v01.42(00 v01.48(00 no
|
||||
24 lcn9k $CVER $NVER $REQ
|
||||
24 bios v01.48(00:v01.42(00 v01.48(00 no
|
||||
26 lcn9k $CVER $NVER $REQ
|
||||
26 bios v01.48(00:v01.42(00 v01.48(00 no
|
||||
28 nxos $CVER $NVER $REQ
|
||||
28 bios v08.32(10/18/2016):v08.06(09/10/2014) v08.35(08/31/2018) yes
|
||||
29 lcn9k $CVER $NVER $REQ
|
||||
29 bios v01.48(00:v01.42(00 v01.48(00 no
|
||||
30 lcn9k $CVER $NVER $REQ
|
||||
30 bios v01.48(00:v01.42(00 v01.48(00 no
|
||||
|
||||
|
||||
Switch will be reloaded for disruptive upgrade.
|
||||
|
||||
Install is in progress, please wait.
|
||||
|
||||
Performing runtime checks.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Setting boot variables.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing configuration copy.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Module 1: Refreshing compact flash and upgrading bios/loader/bootrom.
|
||||
Warning: please do not remove or power off the module at this time.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Module 22: Refreshing compact flash and upgrading bios/loader/bootrom.
|
||||
Warning: please do not remove or power off the module at this time.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Module 24: Refreshing compact flash and upgrading bios/loader/bootrom.
|
||||
Warning: please do not remove or power off the module at this time.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Module 26: Refreshing compact flash and upgrading bios/loader/bootrom.
|
||||
Warning: please do not remove or power off the module at this time.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Module 28: Refreshing compact flash and upgrading bios/loader/bootrom.
|
||||
Warning: please do not remove or power off the module at this time.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Module 29: Refreshing compact flash and upgrading bios/loader/bootrom.
|
||||
Warning: please do not remove or power off the module at this time.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Module 30: Refreshing compact flash and upgrading bios/loader/bootrom.
|
||||
Warning: please do not remove or power off the module at this time.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
Finishing the upgrade, switch will reboot in 10 seconds.
|
||||
"""
|
200
tests/unit/modules/nxos/nxos_platform.py
Normal file
200
tests/unit/modules/nxos/nxos_platform.py
Normal file
|
@ -0,0 +1,200 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
:codeauthor: Thomas Stoner <tmstoner@cisco.com>
|
||||
"""
|
||||
|
||||
# Copyright (c) 2018 Cisco and/or its affiliates.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
from string import Template
|
||||
|
||||
# pylint: disable-msg=C0103
|
||||
# pylint: disable-msg=R0902
|
||||
# pylint: disable-msg=W0613
|
||||
# pylint: disable-msg=C0301
|
||||
|
||||
|
||||
class NXOSPlatform(object):
|
||||
|
||||
""" Cisco Systems Base Platform Unit Test Object """
|
||||
|
||||
chassis = "Unknown NXOS Chassis"
|
||||
|
||||
upgrade_required = False
|
||||
|
||||
show_install_all_impact_no_module_data = """
|
||||
Installer will perform impact only check. Please wait.
|
||||
|
||||
Verifying image bootflash:/$IMAGE for boot variable "nxos".
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Verifying image type.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "nxos" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "bios" version info using image bootflash:/$IMAGE.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing module support checks.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Notifying services about system upgrade.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
|
||||
Compatibility check is done:
|
||||
Module bootable Impact Install-type Reason
|
||||
------ -------- -------------- ------------ ------
|
||||
1 yes disruptive reset default upgrade is not hitless
|
||||
|
||||
|
||||
|
||||
Images will be upgraded according to following table:
|
||||
Module Image Running-Version(pri:alt) New-Version Upg-Required
|
||||
------ ---------- ---------------------------------------- -------------------- ------------
|
||||
"""
|
||||
|
||||
internal_server_error_500 = """
|
||||
Code: 500
|
||||
"""
|
||||
|
||||
invalid_command = """
|
||||
% Invalid command at '^' marker.
|
||||
"""
|
||||
|
||||
internal_server_error_500_dict = {
|
||||
"code": "500",
|
||||
"cli_error": internal_server_error_500,
|
||||
}
|
||||
|
||||
bad_request_client_error_400_invalid_command_dict = {
|
||||
"code": "400",
|
||||
"cli_error": invalid_command,
|
||||
}
|
||||
|
||||
backend_processing_error_500 = internal_server_error_500_dict
|
||||
|
||||
show_install_all_impact_in_progress = """
|
||||
Installer will perform impact only check. Please wait.
|
||||
Another install procedure may be in progress. (0x401E0007)
|
||||
"""
|
||||
|
||||
bad_request_client_error_400_in_progress_dict = {
|
||||
"code": "400",
|
||||
"cli_error": show_install_all_impact_in_progress,
|
||||
}
|
||||
|
||||
show_install_all_impact = None
|
||||
|
||||
install_all_disruptive_success = None
|
||||
|
||||
show_install_all_impact_non_disruptive = None
|
||||
|
||||
install_all_non_disruptive_success = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
"""
|
||||
ckimage - current kickstart image
|
||||
cimage - current system image
|
||||
nkimage - new kickstart image
|
||||
nimage - new system image
|
||||
"""
|
||||
|
||||
self.ckimage = kwargs.get("ckimage", None)
|
||||
self.cimage = kwargs.get("cimage", None)
|
||||
self.nkimage = kwargs.get("nkimage", None)
|
||||
self.nimage = kwargs.get("nimage", None)
|
||||
self.ckversion = self.version_from_image(self.ckimage)
|
||||
self.cversion = self.version_from_image(self.cimage)
|
||||
self.nkversion = self.version_from_image(self.nkimage)
|
||||
self.nversion = self.version_from_image(self.nimage)
|
||||
|
||||
self.upgrade_required = self.cversion != self.nversion
|
||||
|
||||
values = {
|
||||
"KIMAGE": self.nkimage,
|
||||
"IMAGE": self.nimage,
|
||||
"CKVER": self.ckversion,
|
||||
"CVER": self.cversion,
|
||||
"NKVER": self.nkversion,
|
||||
"NVER": self.nversion,
|
||||
"REQ": "no" if self.cversion == self.nversion else "yes",
|
||||
"KREQ": "no" if self.ckversion == self.nkversion else "yes",
|
||||
}
|
||||
|
||||
if self.show_install_all_impact_no_module_data:
|
||||
self.show_install_all_impact_no_module_data = self.templatize(
|
||||
self.show_install_all_impact_no_module_data, values
|
||||
)
|
||||
|
||||
if self.show_install_all_impact:
|
||||
self.show_install_all_impact = self.templatize(
|
||||
self.show_install_all_impact, values
|
||||
)
|
||||
|
||||
if self.show_install_all_impact_non_disruptive:
|
||||
self.show_install_all_impact_non_disruptive = self.templatize(
|
||||
self.show_install_all_impact_non_disruptive, values
|
||||
)
|
||||
|
||||
if self.install_all_non_disruptive_success:
|
||||
self.install_all_non_disruptive_success = self.templatize(
|
||||
self.install_all_non_disruptive_success, values
|
||||
)
|
||||
|
||||
if self.install_all_disruptive_success:
|
||||
self.install_all_disruptive_success = self.templatize(
|
||||
self.install_all_disruptive_success, values
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def templatize(template, values):
|
||||
|
||||
""" Substitute variables in template with their corresponding values """
|
||||
|
||||
return Template(template).substitute(values)
|
||||
|
||||
@staticmethod
|
||||
def version_from_image(image):
|
||||
|
||||
""" Given a NXOS image named image decompose to appropriate image version """
|
||||
|
||||
ver = None
|
||||
if image:
|
||||
match_object = re.search(
|
||||
r"^.*\.(\d+)\.(\d+)\.(\d+)\.(\d+|[A-Z][0-9])\.(?:bin)?(\d+)?.*", image
|
||||
)
|
||||
try:
|
||||
ver = match_object.group(1)
|
||||
ver += "." + match_object.group(2)
|
||||
if match_object.groups()[-1]:
|
||||
ver += "(" + match_object.group(3) + ")"
|
||||
ver += match_object.group(4)
|
||||
ver += "(" + match_object.group(5) + ")"
|
||||
else:
|
||||
ver += (
|
||||
"(" + match_object.group(3) + "." + match_object.group(4) + ")"
|
||||
)
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
return ver
|
227
tests/unit/modules/nxos/nxos_show_cmd_output.py
Normal file
227
tests/unit/modules/nxos/nxos_show_cmd_output.py
Normal file
|
@ -0,0 +1,227 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
n9k_show_ver = """
|
||||
Cisco Nexus Operating System (NX-OS) Software
|
||||
TAC support: http://www.cisco.com/tac
|
||||
Copyright (C) 2002-2018, Cisco and/or its affiliates.
|
||||
All rights reserved.
|
||||
The copyrights to certain works contained in this software are
|
||||
owned by other third parties and used and distributed under their own
|
||||
licenses, such as open source. This software is provided "as is," and unless
|
||||
otherwise stated, there is no warranty, express or implied, including but not
|
||||
limited to warranties of merchantability and fitness for a particular purpose.
|
||||
Certain components of this software are licensed under
|
||||
the GNU General Public License (GPL) version 2.0 or
|
||||
GNU General Public License (GPL) version 3.0 or the GNU
|
||||
Lesser General Public License (LGPL) Version 2.1 or
|
||||
Lesser General Public License (LGPL) Version 2.0.
|
||||
A copy of each such license is available at
|
||||
http://www.opensource.org/licenses/gpl-2.0.php and
|
||||
http://opensource.org/licenses/gpl-3.0.html and
|
||||
http://www.opensource.org/licenses/lgpl-2.1.php and
|
||||
http://www.gnu.org/licenses/old-licenses/library.txt.
|
||||
|
||||
Software
|
||||
BIOS: version 08.36
|
||||
NXOS: version 9.2(1)
|
||||
BIOS compile time: 06/07/2019
|
||||
NXOS image file is: bootflash:///nxos.9.2.1.bin
|
||||
NXOS compile time: 7/17/2018 16:00:00 [07/18/2018 00:21:19]
|
||||
|
||||
|
||||
Hardware
|
||||
cisco Nexus9000 C9504 (4 Slot) Chassis ("Supervisor Module")
|
||||
Intel(R) Xeon(R) CPU E5-2403 0 @ 1.80GHz with 16400084 kB of memory.
|
||||
Processor Board ID SAL1909A7VC
|
||||
|
||||
Device name: n9k-device
|
||||
bootflash: 53298520 kB
|
||||
Kernel uptime is 0 day(s), 18 hour(s), 26 minute(s), 18 second(s)
|
||||
|
||||
Last reset at 931765 usecs after Mon Dec 2 01:21:36 2019
|
||||
Reason: Reset Requested by CLI command reload
|
||||
System version: 9.2(4)
|
||||
Service:
|
||||
|
||||
plugin
|
||||
Core Plugin, Ethernet Plugin
|
||||
|
||||
Active Package(s):
|
||||
"""
|
||||
|
||||
n9k_show_ver_structured = [
|
||||
{
|
||||
"header_str": 'Cisco Nexus Operating System (NX-OS) Software\nTAC support: http://www.cisco.com/tac\nCopyright (C) 2002-2019, Cisco and/or its affiliates.\nAll rights reserved.\nThe copyrights to certain works contained in this software are\nowned by other third parties and used and distributed under their own\nlicenses, such as open source. This software is provided "as is," and unless\notherwise stated, there is no warranty, express or implied, including but not\nlimited to warranties of merchantability and fitness for a particular purpose.\nCertain components of this software are licensed under\nthe GNU General Public License (GPL) version 2.0 or \nGNU General Public License (GPL) version 3.0 or the GNU\nLesser General Public License (LGPL) Version 2.1 or \nLesser General Public License (LGPL) Version 2.0. \nA copy of each such license is available at\nhttp://www.opensource.org/licenses/gpl-2.0.php and\nhttp://opensource.org/licenses/gpl-3.0.html and\nhttp://www.opensource.org/licenses/lgpl-2.1.php and\nhttp://www.gnu.org/licenses/old-licenses/library.txt.\n',
|
||||
"bios_ver_str": "07.66",
|
||||
"kickstart_ver_str": "7.0(3)I7(8) [build 7.0(3)I7(7.16)]",
|
||||
"bios_cmpl_time": "06/12/2019",
|
||||
"kick_file_name": "bootflash:///nxos.7.0.3.I7.7.16.bin",
|
||||
"kick_cmpl_time": " 11/29/2019 13:00:00",
|
||||
"kick_tmstmp": "11/29/2019 21:52:12",
|
||||
"chassis_id": "Nexus9000 C9396PX Chassis",
|
||||
"cpu_name": "Intel(R) Core(TM) i3- CPU @ 2.50GHz",
|
||||
"memory": 16401088,
|
||||
"mem_type": "kB",
|
||||
"proc_board_id": "SAL1821T9EF",
|
||||
"host_name": "dt-n9k5-1",
|
||||
"bootflash_size": 21693714,
|
||||
"kern_uptm_days": 1,
|
||||
"kern_uptm_hrs": 23,
|
||||
"kern_uptm_mins": 32,
|
||||
"kern_uptm_secs": 47,
|
||||
"rr_usecs": 915186,
|
||||
"rr_ctime": "Sun Dec 1 15:47:17 2019",
|
||||
"rr_reason": "Reset Requested by CLI command reload",
|
||||
"rr_sys_ver": "9.2(4)",
|
||||
"rr_service": "",
|
||||
"manufacturer": "Cisco Systems, Inc.",
|
||||
"TABLE_package_list": {"ROW_package_list": {"package_id": {}}},
|
||||
}
|
||||
]
|
||||
|
||||
# Data returned from nxapi for raw text is in list form.
|
||||
n9k_show_ver_list = [
|
||||
'Cisco Nexus Operating System (NX-OS) Software\nTAC support: http://www.cisco.com/tac\nCopyright (C) 2002-2019, Cisco and/or its affiliates.\nAll rights reserved.\nThe copyrights to certain works contained in this software are\nowned by other third parties and used and distributed under their own\nlicenses, such as open source. This software is provided "as is," and unless\notherwise stated, there is no warranty, express or implied, including but not\nlimited to warranties of merchantability and fitness for a particular purpose.\nCertain components of this software are licensed under\nthe GNU General Public License (GPL) version 2.0 or \nGNU General Public License (GPL) version 3.0 or the GNU\nLesser General Public License (LGPL) Version 2.1 or \nLesser General Public License (LGPL) Version 2.0. \nA copy of each such license is available at\nhttp://www.opensource.org/licenses/gpl-2.0.php and\nhttp://opensource.org/licenses/gpl-3.0.html and\nhttp://www.opensource.org/licenses/lgpl-2.1.php and\nhttp://www.gnu.org/licenses/old-licenses/library.txt.\n\nSoftware\n BIOS: version 07.66\n NXOS: version 7.0(3)I7(8) [build 7.0(3)I7(7.16)]\n BIOS compile time: 06/12/2019\n NXOS image file is: bootflash:///nxos.7.0.3.I7.7.16.bin\n NXOS compile time: 11/29/2019 13:00:00 [11/29/2019 21:52:12]\n\n\nHardware\n cisco Nexus9000 C9396PX Chassis \n Intel(R) Core(TM) i3- CPU @ 2.50GHz with 16401088 kB of memory.\n Processor Board ID SAL1821T9EF\n\n Device name: n9k-device\n bootflash: 21693714 kB\nKernel uptime is 1 day(s), 22 hour(s), 54 minute(s), 13 second(s)\n\nLast reset at 915186 usecs after Sun Dec 1 15:47:17 2019\n Reason: Reset Requested by CLI command reload\n System version: 9.2(4)\n Service: \n\nplugin\n Core Plugin, Ethernet Plugin\n\nActive Package(s):\n \n'
|
||||
]
|
||||
|
||||
n9k_show_user_account = """
|
||||
user:salt_test
|
||||
this user account has no expiry date
|
||||
roles:network-operator network-admin dev-ops
|
||||
"""
|
||||
|
||||
n9k_show_user_account_list = [
|
||||
"user:salt_test\n this user account has no expiry date\n roles:network-operator dev-ops network-admin \n"
|
||||
]
|
||||
|
||||
n9k_show_ver_int_list = [
|
||||
'Cisco Nexus Operating System (NX-OS) Software\nTAC support: http://www.cisco.com/tac\nCopyright (C) 2002-2019, Cisco and/or its affiliates.\nAll rights reserved.\nThe copyrights to certain works contained in this software are\nowned by other third parties and used and distributed under their own\nlicenses, such as open source. This software is provided "as is," and unless\notherwise stated, there is no warranty, express or implied, including but not\nlimited to warranties of merchantability and fitness for a particular purpose.\nCertain components of this software are licensed under\nthe GNU General Public License (GPL) version 2.0 or \nGNU General Public License (GPL) version 3.0 or the GNU\nLesser General Public License (LGPL) Version 2.1 or \nLesser General Public License (LGPL) Version 2.0. \nA copy of each such license is available at\nhttp://www.opensource.org/licenses/gpl-2.0.php and\nhttp://opensource.org/licenses/gpl-3.0.html and\nhttp://www.opensource.org/licenses/lgpl-2.1.php and\nhttp://www.gnu.org/licenses/old-licenses/library.txt.\n\nSoftware\n BIOS: version 07.66\n NXOS: version 7.0(3)I7(8) [build 7.0(3)I7(7.16)]\n BIOS compile time: 06/12/2019\n NXOS image file is: bootflash:///nxos.7.0.3.I7.7.16.bin\n NXOS compile time: 11/29/2019 13:00:00 [11/29/2019 21:52:12]\n\n\nHardware\n cisco Nexus9000 C9396PX Chassis \n Intel(R) Core(TM) i3- CPU @ 2.50GHz with 16401088 kB of memory.\n Processor Board ID SAL1821T9EF\n\n Device name: n9k-device\n bootflash: 21693714 kB\nKernel uptime is 1 day(s), 23 hour(s), 24 minute(s), 35 second(s)\n\nLast reset at 915186 usecs after Sun Dec 1 15:47:17 2019\n Reason: Reset Requested by CLI command reload\n System version: 9.2(4)\n Service: \n\nplugin\n Core Plugin, Ethernet Plugin\n\nActive Package(s):\n \n',
|
||||
'Ethernet1/1 is down (XCVR not inserted)\nadmin state is down, Dedicated Interface\n Hardware: 1000/10000 Ethernet, address: 88f0.31dc.7de6 (bia 88f0.31dc.7de6)\n MTU 1500 bytes, BW 10000000 Kbit, DLY 10 usec\n reliability 255/255, txload 1/255, rxload 1/255\n Encapsulation ARPA, medium is broadcast\n Port mode is trunk\n auto-duplex, auto-speed\n Beacon is turned off\n Auto-Negotiation is turned on FEC mode is Auto\n Input flow-control is off, output flow-control is off\n Auto-mdix is turned off\n Switchport monitor is off \n EtherType is 0x8100 \n EEE (efficient-ethernet) : n/a\n admin fec state is auto, oper fec state is off\n Last link flapped never\n Last clearing of "show interface" counters 1d21h\n 0 interface resets\n Load-Interval #1: 30 seconds\n 30 seconds input rate 0 bits/sec, 0 packets/sec\n 30 seconds output rate 0 bits/sec, 0 packets/sec\n input rate 0 bps, 0 pps; output rate 0 bps, 0 pps\n Load-Interval #2: 5 minute (300 seconds)\n 300 seconds input rate 0 bits/sec, 0 packets/sec\n 300 seconds output rate 0 bits/sec, 0 packets/sec\n input rate 0 bps, 0 pps; output rate 0 bps, 0 pps\n RX\n 0 unicast packets 0 multicast packets 0 broadcast packets\n 0 input packets 0 bytes\n 0 jumbo packets 0 storm suppression packets\n 0 runts 0 giants 0 CRC 0 no buffer\n 0 input error 0 short frame 0 overrun 0 underrun 0 ignored\n 0 watchdog 0 bad etype drop 0 bad proto drop 0 if down drop\n 0 input with dribble 0 input discard\n 0 Rx pause\n TX\n 0 unicast packets 0 multicast packets 0 broadcast packets\n 0 output packets 0 bytes\n 0 jumbo packets\n 0 output error 0 collision 0 deferred 0 late collision\n 0 lost carrier 0 no carrier 0 babble 0 output discard\n 0 Tx pause\n\n',
|
||||
]
|
||||
|
||||
n9k_show_ver_int_list_structured = [
|
||||
{
|
||||
"header_str": 'Cisco Nexus Operating System (NX-OS) Software\nTAC support: http://www.cisco.com/tac\nCopyright (C) 2002-2019, Cisco and/or its affiliates.\nAll rights reserved.\nThe copyrights to certain works contained in this software are\nowned by other third parties and used and distributed under their own\nlicenses, such as open source. This software is provided "as is," and unless\notherwise stated, there is no warranty, express or implied, including but not\nlimited to warranties of merchantability and fitness for a particular purpose.\nCertain components of this software are licensed under\nthe GNU General Public License (GPL) version 2.0 or \nGNU General Public License (GPL) version 3.0 or the GNU\nLesser General Public License (LGPL) Version 2.1 or \nLesser General Public License (LGPL) Version 2.0. \nA copy of each such license is available at\nhttp://www.opensource.org/licenses/gpl-2.0.php and\nhttp://opensource.org/licenses/gpl-3.0.html and\nhttp://www.opensource.org/licenses/lgpl-2.1.php and\nhttp://www.gnu.org/licenses/old-licenses/library.txt.\n',
|
||||
"bios_ver_str": "07.66",
|
||||
"kickstart_ver_str": "7.0(3)I7(8) [build 7.0(3)I7(7.16)]",
|
||||
"bios_cmpl_time": "06/12/2019",
|
||||
"kick_file_name": "bootflash:///nxos.7.0.3.I7.7.16.bin",
|
||||
"kick_cmpl_time": " 11/29/2019 13:00:00",
|
||||
"kick_tmstmp": "11/29/2019 21:52:12",
|
||||
"chassis_id": "Nexus9000 C9396PX Chassis",
|
||||
"cpu_name": "Intel(R) Core(TM) i3- CPU @ 2.50GHz",
|
||||
"memory": 16401088,
|
||||
"mem_type": "kB",
|
||||
"proc_board_id": "SAL1821T9EF",
|
||||
"host_name": "n9k-device",
|
||||
"bootflash_size": 21693714,
|
||||
"kern_uptm_days": 1,
|
||||
"kern_uptm_hrs": 23,
|
||||
"kern_uptm_mins": 31,
|
||||
"kern_uptm_secs": 18,
|
||||
"rr_usecs": 915186,
|
||||
"rr_ctime": "Sun Dec 1 15:47:17 2019",
|
||||
"rr_reason": "Reset Requested by CLI command reload",
|
||||
"rr_sys_ver": "9.2(4)",
|
||||
"rr_service": "",
|
||||
"manufacturer": "Cisco Systems, Inc.",
|
||||
"TABLE_package_list": {"ROW_package_list": {"package_id": {}}},
|
||||
},
|
||||
{
|
||||
"TABLE_interface": {
|
||||
"ROW_interface": {
|
||||
"interface": "Ethernet1/1",
|
||||
"state": "down",
|
||||
"state_rsn_desc": "XCVR not inserted",
|
||||
"admin_state": "down",
|
||||
"share_state": "Dedicated",
|
||||
"eth_hw_desc": "1000/10000 Ethernet",
|
||||
"eth_hw_addr": "88f0.31dc.7de6",
|
||||
"eth_bia_addr": "88f0.31dc.7de6",
|
||||
"eth_mtu": "1500",
|
||||
"eth_bw": 10000000,
|
||||
"eth_dly": 10,
|
||||
"eth_reliability": "255",
|
||||
"eth_txload": "1",
|
||||
"eth_rxload": "1",
|
||||
"medium": "broadcast",
|
||||
"eth_mode": "trunk",
|
||||
"eth_duplex": "auto",
|
||||
"eth_speed": "auto-speed",
|
||||
"eth_beacon": "off",
|
||||
"eth_autoneg": "on",
|
||||
"eth_in_flowctrl": "off",
|
||||
"eth_out_flowctrl": "off",
|
||||
"eth_mdix": "off",
|
||||
"eth_swt_monitor": "off",
|
||||
"eth_ethertype": "0x8100",
|
||||
"eth_eee_state": "n/a",
|
||||
"eth_admin_fec_state": "auto",
|
||||
"eth_oper_fec_state": "off",
|
||||
"eth_link_flapped": "never",
|
||||
"eth_clear_counters": "1d21h",
|
||||
"eth_reset_cntr": 0,
|
||||
"eth_load_interval1_rx": 30,
|
||||
"eth_inrate1_bits": "0",
|
||||
"eth_inrate1_pkts": "0",
|
||||
"eth_load_interval1_tx": "30",
|
||||
"eth_outrate1_bits": "0",
|
||||
"eth_outrate1_pkts": "0",
|
||||
"eth_inrate1_summary_bits": "0 bps",
|
||||
"eth_inrate1_summary_pkts": "0 pps",
|
||||
"eth_outrate1_summary_bits": "0 bps",
|
||||
"eth_outrate1_summary_pkts": "0 pps",
|
||||
"eth_load_interval2_rx": "300",
|
||||
"eth_inrate2_bits": "0",
|
||||
"eth_inrate2_pkts": "0",
|
||||
"eth_load_interval2_tx": "300",
|
||||
"eth_outrate2_bits": "0",
|
||||
"eth_outrate2_pkts": "0",
|
||||
"eth_inrate2_summary_bits": "0 bps",
|
||||
"eth_inrate2_summary_pkts": "0 pps",
|
||||
"eth_outrate2_summary_bits": "0 bps",
|
||||
"eth_outrate2_summary_pkts": "0 pps",
|
||||
"eth_inucast": 0,
|
||||
"eth_inmcast": 0,
|
||||
"eth_inbcast": 0,
|
||||
"eth_inpkts": 0,
|
||||
"eth_inbytes": 0,
|
||||
"eth_jumbo_inpkts": "0",
|
||||
"eth_storm_supp": "0",
|
||||
"eth_runts": 0,
|
||||
"eth_giants": 0,
|
||||
"eth_crc": "0",
|
||||
"eth_nobuf": 0,
|
||||
"eth_inerr": "0",
|
||||
"eth_frame": "0",
|
||||
"eth_overrun": "0",
|
||||
"eth_underrun": "0",
|
||||
"eth_ignored": "0",
|
||||
"eth_watchdog": "0",
|
||||
"eth_bad_eth": "0",
|
||||
"eth_bad_proto": "0",
|
||||
"eth_in_ifdown_drops": "0",
|
||||
"eth_dribble": "0",
|
||||
"eth_indiscard": "0",
|
||||
"eth_inpause": "0",
|
||||
"eth_outucast": 0,
|
||||
"eth_outmcast": 0,
|
||||
"eth_outbcast": 0,
|
||||
"eth_outpkts": 0,
|
||||
"eth_outbytes": 0,
|
||||
"eth_jumbo_outpkts": "0",
|
||||
"eth_outerr": "0",
|
||||
"eth_coll": "0",
|
||||
"eth_deferred": "0",
|
||||
"eth_latecoll": "0",
|
||||
"eth_lostcarrier": "0",
|
||||
"eth_nocarrier": "0",
|
||||
"eth_babbles": "0",
|
||||
"eth_outdiscard": "0",
|
||||
"eth_outpause": "0",
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
n9k_get_user_output = "username salt_test password 5 $5$mkXh6O4T$YUVtA89HbXCnue63kgghPlaqPHyaXhdtxPBbPEHhbRC role network-operator"
|
242
tests/unit/modules/nxos/nxos_show_run.py
Normal file
242
tests/unit/modules/nxos/nxos_show_run.py
Normal file
File diff suppressed because one or more lines are too long
1095
tests/unit/modules/test_nxos.py
Normal file
1095
tests/unit/modules/test_nxos.py
Normal file
File diff suppressed because it is too large
Load diff
527
tests/unit/modules/test_nxos_upgrade.py
Normal file
527
tests/unit/modules/test_nxos_upgrade.py
Normal file
|
@ -0,0 +1,527 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
:codeauthor: Thomas Stoner <tmstoner@cisco.com>
|
||||
"""
|
||||
|
||||
# Copyright (c) 2018 Cisco and/or its affiliates.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
# Import Python libs
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
# Import Salt Libs
|
||||
import salt.modules.nxos_upgrade as nxos_upgrade
|
||||
from salt.exceptions import CommandExecutionError, NxosError
|
||||
|
||||
# Import Salt Testing Libs
|
||||
from tests.support.mixins import LoaderModuleMockMixin
|
||||
from tests.support.mock import MagicMock, patch
|
||||
from tests.support.unit import TestCase
|
||||
from tests.unit.modules.nxos.nxos_n3k import N3KPlatform
|
||||
from tests.unit.modules.nxos.nxos_n5k import N5KPlatform
|
||||
from tests.unit.modules.nxos.nxos_n7k import N7KPlatform
|
||||
from tests.unit.modules.nxos.nxos_n36k import N36KPlatform
|
||||
from tests.unit.modules.nxos.nxos_n93k import N93KPlatform
|
||||
from tests.unit.modules.nxos.nxos_n93klxc import N93KLXCPlatform
|
||||
from tests.unit.modules.nxos.nxos_n95k import N95KPlatform
|
||||
|
||||
# pylint: disable-msg=C0103
|
||||
# pylint: disable-msg=C0301
|
||||
# pylint: disable-msg=E1101
|
||||
# pylint: disable-msg=R0904
|
||||
|
||||
|
||||
class NxosUpgradeTestCase(TestCase, LoaderModuleMockMixin):
|
||||
|
||||
""" Test cases for salt.modules.nxos_upgrade """
|
||||
|
||||
platform_list = None
|
||||
|
||||
@staticmethod
|
||||
def assert_platform_upgrade(condition, platform):
|
||||
|
||||
""" Assert platform upgrade condition and display appropriate chassis & images upon assertion failure """
|
||||
|
||||
assert bool(condition), "{0}: Upgrade {1} -> {2}".format(
|
||||
platform.chassis, platform.cimage, platform.nimage
|
||||
)
|
||||
|
||||
def setup_loader_modules(self):
|
||||
|
||||
""" Define list of platforms for Unit Test """
|
||||
|
||||
self.platform_list = [
|
||||
N3KPlatform(cimage="nxos.7.0.3.F3.3.bin", nimage="nxos.9.2.1.255.bin"),
|
||||
N36KPlatform(cimage="nxos.9.1.2.50.bin", nimage="nxos.9.2.2.50.bin"),
|
||||
N5KPlatform(
|
||||
ckimage="n6000-uk9-kickstart.7.3.0.N1.1.bin",
|
||||
cimage="n6000-uk9.7.3.0.N1.1.bin",
|
||||
nkimage="n6000-uk9-kickstart.7.3.3.N2.1.bin",
|
||||
nimage="n6000-uk9.7.3.3.N2.1.bin",
|
||||
),
|
||||
N7KPlatform(
|
||||
ckimage="n7000-s2-kickstart.7.3.0.D1.1.bin",
|
||||
cimage="n7000-s2-dk9.7.3.0.D1.1.bin",
|
||||
nkimage="n7000-s2-kickstart.8.3.1.112.gbin",
|
||||
nimage="n7000-s2-dk9.8.3.1.112.gbin",
|
||||
),
|
||||
N93KPlatform(cimage="nxos.7.0.3.I7.4.bin", nimage="nxos.7.0.3.I7.5.bin"),
|
||||
N93KPlatform(cimage="nxos.7.0.3.I7.5.bin", nimage="nxos.7.0.3.I7.5.bin"),
|
||||
N93KLXCPlatform(cimage="nxos.7.0.3.I7.4.bin", nimage="nxos.7.0.3.I7.5.bin"),
|
||||
N95KPlatform(cimage="nxos.7.0.3.I7.4.bin", nimage="nxos.9.2.2.14.bin"),
|
||||
]
|
||||
|
||||
return {nxos_upgrade: {}}
|
||||
|
||||
def tearDown(self):
|
||||
del self.platform_list
|
||||
|
||||
@staticmethod
|
||||
def test_check_upgrade_impact_input_validation():
|
||||
|
||||
""" UT: nxos_upgrade module:check_upgrade_impact method - input validation """
|
||||
|
||||
result = nxos_upgrade.check_upgrade_impact("dummy-platform-image.bin", issu=1)
|
||||
assert "Input Error" in result
|
||||
|
||||
@staticmethod
|
||||
def test_upgrade_input_validation():
|
||||
|
||||
""" UT: nxos_upgrade module:upgrade method - input validation """
|
||||
|
||||
result = nxos_upgrade.upgrade("dummy-platform-image.bin", issu=1)
|
||||
assert "Input Error" in result
|
||||
|
||||
def test_check_upgrade_impact_backend_processing_error_500(self):
|
||||
|
||||
""" UT: nxos_upgrade module:check_upgrade_impact method - error HTTP code 500 """
|
||||
|
||||
for platform in self.platform_list:
|
||||
if platform.backend_processing_error_500:
|
||||
with patch.dict(
|
||||
nxos_upgrade.__salt__,
|
||||
{
|
||||
"nxos.sendline": MagicMock(
|
||||
return_value=platform.backend_processing_error_500
|
||||
)
|
||||
},
|
||||
):
|
||||
result = nxos_upgrade.check_upgrade_impact(platform.nimage)
|
||||
self.assert_platform_upgrade(
|
||||
result["backend_processing_error"], platform
|
||||
)
|
||||
self.assert_platform_upgrade(not result["succeeded"], platform)
|
||||
|
||||
def test_check_upgrade_impact_internal_server_error_400_invalid_command(self):
|
||||
|
||||
""" UT: nxos_upgrade module:check_upgrade_impact method - invalid command error HTTP code 400 """
|
||||
|
||||
for platform in self.platform_list:
|
||||
if platform.bad_request_client_error_400_invalid_command_dict:
|
||||
with patch.dict(
|
||||
nxos_upgrade.__salt__,
|
||||
{
|
||||
"nxos.sendline": MagicMock(
|
||||
return_value=platform.bad_request_client_error_400_invalid_command_dict
|
||||
)
|
||||
},
|
||||
):
|
||||
result = nxos_upgrade.check_upgrade_impact(platform.nimage)
|
||||
self.assert_platform_upgrade(result["invalid_command"], platform)
|
||||
self.assert_platform_upgrade(not result["succeeded"], platform)
|
||||
|
||||
def test_check_upgrade_impact_internal_server_error_400_in_progress(self):
|
||||
|
||||
""" UT: nxos_upgrade module:check_upgrade_impact method - in-progress error HTTP code 400 """
|
||||
|
||||
for platform in self.platform_list:
|
||||
if platform.bad_request_client_error_400_in_progress_dict:
|
||||
with patch.dict(
|
||||
nxos_upgrade.__salt__,
|
||||
{
|
||||
"nxos.sendline": MagicMock(
|
||||
return_value=platform.bad_request_client_error_400_in_progress_dict
|
||||
)
|
||||
},
|
||||
):
|
||||
result = nxos_upgrade.check_upgrade_impact(platform.nimage)
|
||||
self.assert_platform_upgrade(result["installing"], platform)
|
||||
self.assert_platform_upgrade(not result["succeeded"], platform)
|
||||
|
||||
def test_check_upgrade_impact_internal_server_error_500(self):
|
||||
|
||||
""" UT: nxos_upgrade module:check_upgrade_impact method - internal server error HTTP code 500 """
|
||||
|
||||
for platform in self.platform_list:
|
||||
if platform.internal_server_error_500:
|
||||
with patch.dict(
|
||||
nxos_upgrade.__salt__,
|
||||
{
|
||||
"nxos.sendline": MagicMock(
|
||||
return_value=platform.internal_server_error_500
|
||||
)
|
||||
},
|
||||
):
|
||||
result = nxos_upgrade.check_upgrade_impact(platform.nimage)
|
||||
self.assert_platform_upgrade(
|
||||
platform.internal_server_error_500 in result["error_data"],
|
||||
platform,
|
||||
)
|
||||
self.assert_platform_upgrade(
|
||||
result["backend_processing_error"], platform
|
||||
)
|
||||
self.assert_platform_upgrade(not result["succeeded"], platform)
|
||||
|
||||
def test_check_upgrade_impact_non_disruptive_success(self):
|
||||
|
||||
""" UT: nxos_upgrade module:check_upgrade_impact method - non-disruptive success """
|
||||
|
||||
for platform in self.platform_list:
|
||||
if platform.install_all_non_disruptive_success:
|
||||
with patch.dict(
|
||||
nxos_upgrade.__salt__,
|
||||
{
|
||||
"nxos.sendline": MagicMock(
|
||||
return_value=platform.install_all_non_disruptive_success
|
||||
)
|
||||
},
|
||||
):
|
||||
result = nxos_upgrade.check_upgrade_impact(platform.nimage)
|
||||
self.assert_platform_upgrade(
|
||||
result["upgrade_non_disruptive"], platform
|
||||
)
|
||||
self.assert_platform_upgrade(result["succeeded"], platform)
|
||||
self.assert_platform_upgrade(result["module_data"], platform)
|
||||
|
||||
def test_check_upgrade_impact_disruptive_success(self):
|
||||
|
||||
""" UT: nxos_upgrade module:check_upgrade_impact method - disruptive success """
|
||||
|
||||
for platform in self.platform_list:
|
||||
if platform.install_all_disruptive_success:
|
||||
with patch.dict(
|
||||
nxos_upgrade.__salt__,
|
||||
{
|
||||
"nxos.sendline": MagicMock(
|
||||
return_value=platform.install_all_disruptive_success
|
||||
)
|
||||
},
|
||||
):
|
||||
result = nxos_upgrade.check_upgrade_impact(platform.nimage)
|
||||
self.assert_platform_upgrade(
|
||||
result["upgrade_required"] == platform.upgrade_required,
|
||||
platform,
|
||||
)
|
||||
self.assert_platform_upgrade(
|
||||
not result["upgrade_non_disruptive"], platform
|
||||
)
|
||||
self.assert_platform_upgrade(not result["succeeded"], platform)
|
||||
self.assert_platform_upgrade(
|
||||
result["upgrade_in_progress"], platform
|
||||
)
|
||||
self.assert_platform_upgrade(result["module_data"], platform)
|
||||
|
||||
def test_upgrade_show_install_all_impact_no_module_data(self):
|
||||
|
||||
""" UT: nxos_upgrade module: upgrade method - no module data """
|
||||
|
||||
for platform in self.platform_list:
|
||||
if platform.show_install_all_impact_no_module_data:
|
||||
with patch.dict(
|
||||
nxos_upgrade.__salt__,
|
||||
{
|
||||
"nxos.sendline": MagicMock(
|
||||
return_value=platform.show_install_all_impact_no_module_data
|
||||
)
|
||||
},
|
||||
):
|
||||
result = nxos_upgrade.upgrade(platform.nimage, issu=False)
|
||||
self.assert_platform_upgrade(not result["succeeded"], platform)
|
||||
self.assert_platform_upgrade(
|
||||
result["error_data"] == result["upgrade_data"], platform
|
||||
)
|
||||
|
||||
def test_upgrade_invalid_command(self):
|
||||
|
||||
""" UT: nxos_upgrade module:upgrade method - invalid command """
|
||||
|
||||
for platform in self.platform_list:
|
||||
if platform.invalid_command:
|
||||
with patch.dict(
|
||||
nxos_upgrade.__salt__,
|
||||
{"nxos.sendline": MagicMock(return_value=platform.invalid_command)},
|
||||
):
|
||||
result = nxos_upgrade.upgrade(platform.nimage, platform.nkimage)
|
||||
self.assert_platform_upgrade(result["error_data"], platform)
|
||||
self.assert_platform_upgrade(not result["succeeded"], platform)
|
||||
|
||||
def test_upgrade_install_in_progress(self):
|
||||
|
||||
""" UT: nxos_upgrade module:upgrade method - in-progress """
|
||||
|
||||
for platform in self.platform_list:
|
||||
if platform.show_install_all_impact_in_progress:
|
||||
with patch.dict(
|
||||
nxos_upgrade.__salt__,
|
||||
{
|
||||
"nxos.sendline": MagicMock(
|
||||
return_value=platform.show_install_all_impact_in_progress
|
||||
)
|
||||
},
|
||||
):
|
||||
result = nxos_upgrade.upgrade(platform.nimage, platform.nkimage)
|
||||
self.assert_platform_upgrade(result["error_data"], platform)
|
||||
self.assert_platform_upgrade(not result["succeeded"], platform)
|
||||
|
||||
def test_upgrade_install_in_progress_terminal_dont_ask(self):
|
||||
|
||||
""" UT: nxos_upgrade module:upgrade method - in-progress (terminal don't-ask) """
|
||||
|
||||
for platform in self.platform_list:
|
||||
if platform.invalid_command:
|
||||
with patch.dict(
|
||||
nxos_upgrade.__salt__,
|
||||
{
|
||||
"nxos.sendline": MagicMock(
|
||||
return_value=[
|
||||
{},
|
||||
platform.show_install_all_impact_in_progress,
|
||||
]
|
||||
)
|
||||
},
|
||||
):
|
||||
result = nxos_upgrade.upgrade(platform.nimage, platform.nkimage)
|
||||
self.assert_platform_upgrade(result["error_data"], platform)
|
||||
self.assert_platform_upgrade(not result["succeeded"], platform)
|
||||
|
||||
def test_upgrade_install_in_progress_sans_terminal_dont_ask(self):
|
||||
|
||||
""" UT: nxos_upgrade module:upgrade method - in-progress (sans terminal don't-ask) """
|
||||
|
||||
for platform in self.platform_list:
|
||||
if platform.invalid_command:
|
||||
with patch.dict(
|
||||
nxos_upgrade.__salt__,
|
||||
{
|
||||
"nxos.sendline": MagicMock(
|
||||
return_value=[platform.show_install_all_impact_in_progress]
|
||||
)
|
||||
},
|
||||
):
|
||||
result = nxos_upgrade.upgrade(platform.nimage, platform.nkimage)
|
||||
self.assert_platform_upgrade(result["error_data"], platform)
|
||||
self.assert_platform_upgrade(not result["succeeded"], platform)
|
||||
|
||||
def test_upgrade_internal_server_error_500(self):
|
||||
|
||||
""" UT: nxos_upgrade module:upgrade method - internal server error 500 """
|
||||
|
||||
for platform in self.platform_list:
|
||||
if platform.backend_processing_error_500:
|
||||
with patch.dict(
|
||||
nxos_upgrade.__salt__,
|
||||
{
|
||||
"nxos.sendline": MagicMock(
|
||||
return_value=platform.internal_server_error_500
|
||||
)
|
||||
},
|
||||
):
|
||||
result = nxos_upgrade.upgrade(platform.nimage)
|
||||
self.assert_platform_upgrade(result["error_data"], platform)
|
||||
self.assert_platform_upgrade(
|
||||
result["backend_processing_error"], platform
|
||||
)
|
||||
self.assert_platform_upgrade(not result["succeeded"], platform)
|
||||
|
||||
def test_upgrade_install_all_disruptive(self):
|
||||
|
||||
""" UT: nxos_upgrade module:upgrade method - install all disruptive """
|
||||
|
||||
for platform in self.platform_list:
|
||||
if platform.show_install_all_impact:
|
||||
with patch.dict(
|
||||
nxos_upgrade.__salt__,
|
||||
{
|
||||
"nxos.sendline": MagicMock(
|
||||
side_effect=[
|
||||
platform.show_install_all_impact,
|
||||
platform.install_all_disruptive_success,
|
||||
]
|
||||
)
|
||||
},
|
||||
):
|
||||
result = nxos_upgrade.upgrade(
|
||||
platform.nimage, platform.nkimage, issu=False
|
||||
)
|
||||
self.assert_platform_upgrade(not result["error_data"], platform)
|
||||
if platform.upgrade_required:
|
||||
self.assert_platform_upgrade(
|
||||
result["upgrade_in_progress"], platform
|
||||
)
|
||||
else:
|
||||
self.assert_platform_upgrade(
|
||||
not result["upgrade_in_progress"], platform
|
||||
)
|
||||
|
||||
def test_upgrade_install_all_non_disruptive(self):
|
||||
|
||||
""" UT: nxos_upgrade module:upgrade method - install all non-disruptive """
|
||||
|
||||
for platform in self.platform_list:
|
||||
if platform.show_install_all_impact_non_disruptive:
|
||||
with patch.dict(
|
||||
nxos_upgrade.__salt__,
|
||||
{
|
||||
"nxos.sendline": MagicMock(
|
||||
side_effect=[
|
||||
platform.show_install_all_impact_non_disruptive,
|
||||
platform.install_all_non_disruptive_success,
|
||||
]
|
||||
)
|
||||
},
|
||||
):
|
||||
result = nxos_upgrade.upgrade(
|
||||
platform.nimage, platform.nkimage, issu=True
|
||||
)
|
||||
self.assert_platform_upgrade(not result["error_data"], platform)
|
||||
self.assert_platform_upgrade(result["succeeded"], platform)
|
||||
|
||||
def test_upgrade_CommandExecutionError_Exception(self):
|
||||
|
||||
""" UT: nxos_upgrade module:upgrade method - raise CommandExecutionError exception #1 """
|
||||
|
||||
for platform in self.platform_list:
|
||||
if platform.invalid_command:
|
||||
with patch.dict(
|
||||
nxos_upgrade.__salt__,
|
||||
{
|
||||
"nxos.sendline": MagicMock(
|
||||
side_effect=CommandExecutionError(
|
||||
{
|
||||
"rejected_input": "invalid CLI command",
|
||||
"message": "CLI excution error",
|
||||
"code": "400",
|
||||
"cli_error": platform.invalid_command,
|
||||
}
|
||||
)
|
||||
)
|
||||
},
|
||||
):
|
||||
result = nxos_upgrade.upgrade(
|
||||
platform.nimage, platform.nkimage, issu=False
|
||||
)
|
||||
self.assert_platform_upgrade(result["error_data"], platform)
|
||||
self.assert_platform_upgrade(result["invalid_command"], platform)
|
||||
self.assert_platform_upgrade(not result["succeeded"], platform)
|
||||
|
||||
def test_upgrade_CommandExecutionError_Exception2(self):
|
||||
|
||||
""" UT: nxos_upgrade module:upgrade method - raise CommandExecutionError exception #2 """
|
||||
|
||||
for platform in self.platform_list:
|
||||
if platform.invalid_command:
|
||||
with patch.dict(
|
||||
nxos_upgrade.__salt__,
|
||||
{
|
||||
"nxos.sendline": MagicMock(
|
||||
side_effect=[
|
||||
platform.show_install_all_impact,
|
||||
CommandExecutionError(
|
||||
{
|
||||
"rejected_input": "invalid CLI command",
|
||||
"message": "CLI excution error",
|
||||
"code": "400",
|
||||
"cli_error": platform.invalid_command,
|
||||
}
|
||||
),
|
||||
]
|
||||
)
|
||||
},
|
||||
):
|
||||
result = nxos_upgrade.upgrade(
|
||||
platform.nimage, platform.nkimage, issu=False
|
||||
)
|
||||
if platform.upgrade_required:
|
||||
self.assert_platform_upgrade(result["error_data"], platform)
|
||||
self.assert_platform_upgrade(
|
||||
result["invalid_command"], platform
|
||||
)
|
||||
self.assert_platform_upgrade(not result["succeeded"], platform)
|
||||
else:
|
||||
self.assert_platform_upgrade(result["succeeded"], platform)
|
||||
|
||||
def test_upgrade_NxosError_Exception(self):
|
||||
|
||||
""" UT: nxos_upgrade module:upgrade method - raise NxosError exception """
|
||||
|
||||
for platform in self.platform_list:
|
||||
if platform.internal_server_error_500:
|
||||
with patch.dict(
|
||||
nxos_upgrade.__salt__,
|
||||
{
|
||||
"nxos.sendline": MagicMock(
|
||||
side_effect=[
|
||||
platform.show_install_all_impact,
|
||||
NxosError(platform.internal_server_error_500),
|
||||
]
|
||||
)
|
||||
},
|
||||
):
|
||||
result = nxos_upgrade.upgrade(
|
||||
platform.nimage, platform.nkimage, issu=False
|
||||
)
|
||||
if platform.upgrade_required:
|
||||
self.assert_platform_upgrade(
|
||||
result["upgrade_in_progress"], platform
|
||||
)
|
||||
self.assert_platform_upgrade(not result["succeeded"], platform)
|
||||
else:
|
||||
self.assert_platform_upgrade(
|
||||
not result["upgrade_in_progress"], platform
|
||||
)
|
||||
self.assert_platform_upgrade(result["succeeded"], platform)
|
||||
|
||||
def test_upgrade_NxosError_Exception2(self):
|
||||
|
||||
""" UT: nxos_upgrade module:upgrade method - raise NxosError exception #2 """
|
||||
|
||||
for platform in self.platform_list:
|
||||
if platform.internal_server_error_500:
|
||||
with patch.dict(
|
||||
nxos_upgrade.__salt__,
|
||||
{
|
||||
"nxos.sendline": MagicMock(
|
||||
side_effect=[
|
||||
platform.show_install_all_impact,
|
||||
NxosError(
|
||||
"{'Error Message': 'Not Found', 'Code': 404}"
|
||||
),
|
||||
]
|
||||
)
|
||||
},
|
||||
):
|
||||
result = nxos_upgrade.upgrade(
|
||||
platform.nimage, platform.nkimage, issu=False
|
||||
)
|
||||
if platform.upgrade_required:
|
||||
self.assert_platform_upgrade(
|
||||
result["upgrade_in_progress"], platform
|
||||
)
|
||||
self.assert_platform_upgrade(not result["succeeded"], platform)
|
||||
else:
|
||||
self.assert_platform_upgrade(
|
||||
not result["upgrade_in_progress"], platform
|
||||
)
|
||||
self.assert_platform_upgrade(result["succeeded"], platform)
|
481
tests/unit/proxy/test_nxos.py
Normal file
481
tests/unit/proxy/test_nxos.py
Normal file
|
@ -0,0 +1,481 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
:codeauthor: Mike Wiebe <@mikewiebe>
|
||||
"""
|
||||
|
||||
# Copyright (c) 2019 Cisco and/or its affiliates.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Import Python Libs
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import salt.proxy.nxos as nxos_proxy
|
||||
import salt.utils.nxos as nxos_utils
|
||||
from salt.exceptions import CommandExecutionError
|
||||
|
||||
# Import Salt Testing Libs
|
||||
from tests.support.mixins import LoaderModuleMockMixin
|
||||
from tests.support.mock import MagicMock, create_autospec, patch
|
||||
from tests.support.unit import TestCase
|
||||
from tests.unit.modules.nxos.nxos_grains import n9k_grains
|
||||
from tests.unit.modules.nxos.nxos_show_cmd_output import n9k_show_ver_list
|
||||
|
||||
|
||||
class NxosNxapiProxyTestCase(TestCase, LoaderModuleMockMixin):
|
||||
def setup_loader_modules(self):
|
||||
return {nxos_proxy: {"CONNECTION": "nxapi"}}
|
||||
|
||||
def test_check_virtual(self):
|
||||
|
||||
""" UT: nxos module:check_virtual method - return value """
|
||||
|
||||
result = nxos_proxy.__virtual__()
|
||||
self.assertIn("nxos", result)
|
||||
|
||||
def test_init(self):
|
||||
|
||||
""" UT: nxos module:init method - nxapi proxy """
|
||||
|
||||
with patch.object(nxos_proxy, "__opts__", {"proxy": {"connection": "nxapi"}}):
|
||||
with patch("salt.proxy.nxos._init_nxapi", autospec=True) as init_nxapi:
|
||||
result = nxos_proxy.init()
|
||||
self.assertEqual(result, init_nxapi.return_value)
|
||||
|
||||
def test_init_opts_none(self):
|
||||
|
||||
""" UT: nxos module:init method - __opts__ connection is None """
|
||||
|
||||
with patch.object(nxos_proxy, "__opts__", {"proxy": {"connection": None}}):
|
||||
with patch("salt.proxy.nxos._init_nxapi", autospec=True) as init_nxapi:
|
||||
result = nxos_proxy.init()
|
||||
self.assertEqual(result, init_nxapi.return_value)
|
||||
|
||||
def test_init_bad_connection_type(self):
|
||||
|
||||
""" UT: nxos module:init method - bad CONNECTION type """
|
||||
with patch.object(nxos_proxy, "__opts__", {"proxy": {"connection": "unknown"}}):
|
||||
self.assertFalse(nxos_proxy.init())
|
||||
|
||||
def test_initialized(self):
|
||||
|
||||
""" UT: nxos module:initialized method - nxapi proxy """
|
||||
|
||||
with patch(
|
||||
"salt.proxy.nxos._initialized_nxapi", autospec=True
|
||||
) as initialized_nxapi:
|
||||
result = nxos_proxy.initialized()
|
||||
self.assertEqual(result, initialized_nxapi.return_value)
|
||||
|
||||
def test_ping(self):
|
||||
|
||||
""" UT: nxos module:ping method - nxapi proxy """
|
||||
|
||||
with patch("salt.proxy.nxos._ping_nxapi", autospec=True) as ping_nxapi:
|
||||
result = nxos_proxy.ping()
|
||||
self.assertEqual(result, ping_nxapi.return_value)
|
||||
|
||||
def test_grains(self):
|
||||
|
||||
""" UT: nxos module:grains method - nxapi grains """
|
||||
|
||||
with patch(
|
||||
"salt.proxy.nxos.sendline", autospec=True, return_value=n9k_show_ver_list
|
||||
):
|
||||
result = nxos_proxy.grains()
|
||||
self.assertEqual(result, n9k_grains)
|
||||
|
||||
def test_grains_cache_set(self):
|
||||
|
||||
""" UT: nxos module:grains method - nxapi grains cache set """
|
||||
|
||||
with patch(
|
||||
"salt.proxy.nxos.DEVICE_DETAILS", {"grains_cache": n9k_grains["nxos"]}
|
||||
):
|
||||
with patch(
|
||||
"salt.proxy.nxos.sendline",
|
||||
autospec=True,
|
||||
return_value=n9k_show_ver_list,
|
||||
):
|
||||
result = nxos_proxy.grains()
|
||||
self.assertEqual(result, n9k_grains)
|
||||
|
||||
def test_grains_refresh(self):
|
||||
|
||||
""" UT: nxos module:grains_refresh method - nxapi grains """
|
||||
|
||||
device_details = {"grains_cache": None}
|
||||
|
||||
with patch("salt.proxy.nxos.DEVICE_DETAILS", device_details):
|
||||
with patch("salt.proxy.nxos.grains", autospec=True) as grains:
|
||||
result = nxos_proxy.grains_refresh()
|
||||
self.assertEqual(nxos_proxy.DEVICE_DETAILS["grains_cache"], {})
|
||||
self.assertEqual(result, grains.return_value)
|
||||
|
||||
def test_sendline(self):
|
||||
|
||||
""" UT: nxos module:sendline method - nxapi """
|
||||
|
||||
command = "show version"
|
||||
|
||||
with patch("salt.proxy.nxos._nxapi_request", autospec=True) as nxapi_request:
|
||||
result = nxos_proxy.sendline(command)
|
||||
self.assertEqual(result, nxapi_request.return_value)
|
||||
|
||||
def test_proxy_config(self):
|
||||
|
||||
""" UT: nxos module:proxy_config method - nxapi success path"""
|
||||
|
||||
commands = ["feature bgp", "router bgp 65535"]
|
||||
|
||||
with patch("salt.proxy.nxos.DEVICE_DETAILS", {"save_config": False}):
|
||||
with patch(
|
||||
"salt.proxy.nxos._nxapi_request", autospec=True
|
||||
) as nxapi_request:
|
||||
result = nxos_proxy.proxy_config(commands)
|
||||
self.assertEqual(result, [commands, nxapi_request.return_value])
|
||||
|
||||
def test_proxy_config_save_config(self):
|
||||
|
||||
""" UT: nxos module:proxy_config method - nxapi success path"""
|
||||
|
||||
commands = ["feature bgp", "router bgp 65535"]
|
||||
|
||||
with patch("salt.proxy.nxos.DEVICE_DETAILS", {"save_config": None}):
|
||||
with patch(
|
||||
"salt.proxy.nxos._nxapi_request", autospec=True
|
||||
) as nxapi_request:
|
||||
result = nxos_proxy.proxy_config(commands, save_config=True)
|
||||
self.assertEqual(result, [commands, nxapi_request.return_value])
|
||||
|
||||
def test__init_nxapi(self):
|
||||
|
||||
""" UT: nxos module:_init_nxapi method - successful connectinon """
|
||||
|
||||
opts = {"proxy": {"arg1": None}}
|
||||
nxapi_request = create_autospec(nxos_utils.nxapi_request, return_value="data")
|
||||
|
||||
with patch("salt.proxy.nxos.DEVICE_DETAILS", {}) as device_details:
|
||||
with patch(
|
||||
"salt.proxy.nxos.__utils__", {"nxos.nxapi_request": nxapi_request}
|
||||
):
|
||||
result = nxos_proxy._init_nxapi(opts)
|
||||
|
||||
self.assertTrue(device_details["initialized"])
|
||||
self.assertTrue(device_details["up"])
|
||||
self.assertTrue(device_details["save_config"])
|
||||
self.assertTrue(result)
|
||||
|
||||
nxapi_request.assert_called_with("show clock", **opts["proxy"])
|
||||
|
||||
def test_bad__init_nxapi(self):
|
||||
class NXAPIException(Exception):
|
||||
pass
|
||||
|
||||
nxapi_request = create_autospec(
|
||||
nxos_utils.nxapi_request, side_effect=NXAPIException
|
||||
)
|
||||
|
||||
with patch("salt.proxy.nxos.__utils__", {"nxos.nxapi_request": nxapi_request}):
|
||||
with patch("salt.proxy.nxos.log", autospec=True) as log:
|
||||
with self.assertRaises(NXAPIException):
|
||||
nxos_proxy._init_nxapi({"proxy": {"host": "HOST"}})
|
||||
log.error.assert_called()
|
||||
|
||||
def test__initialized_nxapi(self):
|
||||
|
||||
""" UT: nxos module:_initialized_nxapi method """
|
||||
|
||||
result = nxos_proxy._initialized_nxapi()
|
||||
self.assertFalse(result)
|
||||
|
||||
with patch("salt.proxy.nxos.DEVICE_DETAILS", {"initialized": True}):
|
||||
result = nxos_proxy._initialized_nxapi()
|
||||
self.assertTrue(result)
|
||||
|
||||
def test__ping_nxapi(self):
|
||||
|
||||
""" UT: nxos module:_ping_nxapi method """
|
||||
|
||||
result = nxos_proxy._ping_nxapi()
|
||||
self.assertFalse(result)
|
||||
|
||||
with patch("salt.proxy.nxos.DEVICE_DETAILS", {"up": True}):
|
||||
result = nxos_proxy._ping_nxapi()
|
||||
self.assertTrue(result)
|
||||
|
||||
def test__shutdown_nxapi(self):
|
||||
|
||||
""" UT: nxos module:_shutdown_nxapi method """
|
||||
|
||||
opts = {"id": "value"}
|
||||
|
||||
with patch("salt.proxy.nxos.log", autospec=True):
|
||||
nxos_proxy._shutdown_nxapi()
|
||||
# nothing to test
|
||||
|
||||
def test__nxapi_request_ssh_return(self):
|
||||
|
||||
""" UT: nxos module:_nxapi_request method - CONNECTION == 'ssh' """
|
||||
|
||||
commands = "show version"
|
||||
|
||||
with patch("salt.proxy.nxos.CONNECTION", "ssh"):
|
||||
result = nxos_proxy._nxapi_request(commands)
|
||||
self.assertEqual("_nxapi_request is not available for ssh proxy", result)
|
||||
|
||||
def test__nxapi_request_connect(self):
|
||||
|
||||
""" UT: nxos module:_nxapi_request method """
|
||||
|
||||
commands = "show version"
|
||||
nxapi_request = create_autospec(nxos_utils.nxapi_request, return_value="data")
|
||||
|
||||
with patch("salt.proxy.nxos.DEVICE_DETAILS", {"conn_args": {"arg1": None}}):
|
||||
with patch(
|
||||
"salt.proxy.nxos.__utils__", {"nxos.nxapi_request": nxapi_request}
|
||||
):
|
||||
result = nxos_proxy._nxapi_request(commands)
|
||||
self.assertEqual("data", result)
|
||||
nxapi_request.assert_called_with(commands, method="cli_conf", arg1=None)
|
||||
|
||||
|
||||
class NxosSSHProxyTestCase(TestCase, LoaderModuleMockMixin):
|
||||
def setup_loader_modules(self):
|
||||
return {
|
||||
nxos_proxy: {
|
||||
"__opts__": {
|
||||
"proxy": {
|
||||
"host": "dt-n9k5-1.cisco.com",
|
||||
"username": "admin",
|
||||
"password": "password",
|
||||
}
|
||||
},
|
||||
"CONNECTION": "ssh",
|
||||
}
|
||||
}
|
||||
|
||||
def test_init(self):
|
||||
|
||||
""" UT: nxos module:init method - ssh proxy """
|
||||
|
||||
with patch("salt.proxy.nxos._init_ssh", autospec=True) as init_ssh:
|
||||
result = nxos_proxy.init()
|
||||
self.assertEqual(result, init_ssh.return_value)
|
||||
|
||||
def test_init_opts_none(self):
|
||||
|
||||
""" UT: nxos module:init method - __opts__ connection is None """
|
||||
|
||||
with patch("salt.proxy.nxos.__opts__", {"proxy": {"connection": None}}):
|
||||
with patch("salt.proxy.nxos._init_ssh", autospec=True) as init_ssh:
|
||||
result = nxos_proxy.init()
|
||||
self.assertEqual(result, init_ssh.return_value)
|
||||
|
||||
def test_initialized(self):
|
||||
|
||||
""" UT: nxos module:initialized method - ssh proxy """
|
||||
|
||||
with patch(
|
||||
"salt.proxy.nxos._initialized_ssh", autospec=True
|
||||
) as initialized_ssh:
|
||||
result = nxos_proxy.initialized()
|
||||
self.assertEqual(result, initialized_ssh.return_value)
|
||||
|
||||
def test_ping(self):
|
||||
|
||||
""" UT: nxos module:ping method - ssh proxy """
|
||||
|
||||
with patch("salt.proxy.nxos._ping_ssh", autospec=True) as ping_ssh:
|
||||
result = nxos_proxy.ping()
|
||||
self.assertEqual(result, ping_ssh.return_value)
|
||||
|
||||
def test_grains(self):
|
||||
|
||||
""" UT: nxos module:grains method - ssh grains """
|
||||
|
||||
with patch(
|
||||
"salt.proxy.nxos.sendline", autospec=True, return_value=n9k_show_ver_list[0]
|
||||
):
|
||||
result = nxos_proxy.grains()
|
||||
self.assertEqual(result, n9k_grains)
|
||||
|
||||
def test_sendline(self):
|
||||
|
||||
""" UT: nxos module:sendline method - nxapi """
|
||||
|
||||
command = "show version"
|
||||
|
||||
with patch("salt.proxy.nxos._sendline_ssh", autospec=True) as sendline_ssh:
|
||||
result = nxos_proxy.sendline(command)
|
||||
self.assertEqual(result, sendline_ssh.return_value)
|
||||
|
||||
def test_proxy_config(self):
|
||||
|
||||
""" UT: nxos module:proxy_config method - ssh success path """
|
||||
|
||||
commands = ["feature bgp", "router bgp 65535"]
|
||||
|
||||
with patch("salt.proxy.nxos.DEVICE_DETAILS", {"save_config": False}):
|
||||
with patch("salt.proxy.nxos._sendline_ssh", autospec=True) as sendline_ssh:
|
||||
result = nxos_proxy.proxy_config(commands)
|
||||
self.assertEqual(result, [commands, sendline_ssh.return_value])
|
||||
|
||||
def test_proxy_config_save_config(self):
|
||||
|
||||
""" UT: nxos module:proxy_config method - ssh success path """
|
||||
|
||||
commands = ["feature bgp", "router bgp 65535"]
|
||||
|
||||
with patch("salt.proxy.nxos.DEVICE_DETAILS", {"save_config": None}):
|
||||
with patch("salt.proxy.nxos._sendline_ssh", autospec=True) as sendline_ssh:
|
||||
result = nxos_proxy.proxy_config(commands, save_config=True)
|
||||
self.assertEqual(result, [commands, sendline_ssh.return_value])
|
||||
|
||||
def test_proxy_config_error(self):
|
||||
|
||||
""" UT: nxos module:proxy_config method - CommandExecutionError """
|
||||
|
||||
with patch(
|
||||
"salt.proxy.nxos._sendline_ssh",
|
||||
autospec=True,
|
||||
side_effect=CommandExecutionError,
|
||||
):
|
||||
with self.assertRaises(CommandExecutionError):
|
||||
nxos_proxy.proxy_config("show version", save_config=True)
|
||||
|
||||
def test__init_ssh_device_details(self):
|
||||
with patch("salt.proxy.nxos.SSHConnection", autospec=True) as SSHConnection:
|
||||
SSHConnection().sendline.return_value = ["", ""]
|
||||
|
||||
with patch("salt.proxy.nxos.DEVICE_DETAILS", {}) as device_details:
|
||||
nxos_proxy._init_ssh(None)
|
||||
self.assertIn(nxos_proxy._worker_name(), device_details)
|
||||
self.assertTrue(device_details["initialized"])
|
||||
self.assertTrue(device_details["save_config"])
|
||||
|
||||
with patch.dict(nxos_proxy.__opts__["proxy"], {"save_config": False}):
|
||||
with patch("salt.proxy.nxos.DEVICE_DETAILS", {}) as device_details:
|
||||
nxos_proxy._init_ssh(None)
|
||||
self.assertIn(nxos_proxy._worker_name(), device_details)
|
||||
self.assertTrue(device_details["initialized"])
|
||||
self.assertFalse(device_details["save_config"])
|
||||
|
||||
def test__init_ssh_opts(self):
|
||||
|
||||
""" UT: nxos module:_init_ssh method - successful connectinon """
|
||||
|
||||
with patch("salt.proxy.nxos.DEVICE_DETAILS", {}):
|
||||
with patch("salt.proxy.nxos.SSHConnection", autospec=True) as SSHConnection:
|
||||
SSHConnection().sendline.return_value = ["", ""]
|
||||
nxos_proxy._init_ssh(None)
|
||||
self.assertEqual(
|
||||
nxos_proxy.__opts__["proxy"]["host"],
|
||||
SSHConnection.call_args[1]["host"],
|
||||
)
|
||||
|
||||
opts = MagicMock()
|
||||
nxos_proxy._init_ssh(opts)
|
||||
self.assertEqual(
|
||||
opts["proxy"]["host"], SSHConnection.call_args[1]["host"]
|
||||
)
|
||||
|
||||
def test__init_ssh_prompt(self):
|
||||
|
||||
""" UT: nxos module:_init_ssh method - prompt regex """
|
||||
|
||||
with patch("salt.proxy.nxos.DEVICE_DETAILS", {}):
|
||||
with patch("salt.proxy.nxos.SSHConnection", autospec=True) as SSHConnection:
|
||||
SSHConnection().sendline.return_value = ["", ""]
|
||||
|
||||
with patch.dict(
|
||||
nxos_proxy.__opts__["proxy"], {"prompt_regex": "n9k.*device"}
|
||||
):
|
||||
nxos_proxy._init_ssh(None)
|
||||
self.assertEqual(
|
||||
"n9k.*device", SSHConnection.call_args[1]["prompt"]
|
||||
)
|
||||
|
||||
with patch.dict(
|
||||
nxos_proxy.__opts__["proxy"], {"prompt_name": "n9k-device"}
|
||||
):
|
||||
nxos_proxy._init_ssh(None)
|
||||
self.assertEqual(
|
||||
"n9k-device.*#", SSHConnection.call_args[1]["prompt"]
|
||||
)
|
||||
|
||||
nxos_proxy._init_ssh(None)
|
||||
self.assertEqual(".+#$", SSHConnection.call_args[1]["prompt"])
|
||||
|
||||
def test__initialized_ssh(self):
|
||||
|
||||
""" UT: nxos module:_initialized_ssh method """
|
||||
|
||||
with patch("salt.proxy.nxos.DEVICE_DETAILS", {"initialized": True}):
|
||||
result = nxos_proxy._initialized_ssh()
|
||||
self.assertTrue(result)
|
||||
|
||||
with patch("salt.proxy.nxos.DEVICE_DETAILS", {}):
|
||||
result = nxos_proxy._initialized_ssh()
|
||||
self.assertFalse(result)
|
||||
|
||||
def test__parse_output_for_errors(self):
|
||||
|
||||
""" UT: nxos module:_parse_output_for_errors method """
|
||||
|
||||
data = "% Incomplete command at '^' marker."
|
||||
command = "show"
|
||||
|
||||
with self.assertRaises(CommandExecutionError):
|
||||
nxos_proxy._parse_output_for_errors(
|
||||
data, command, error_pattern="Incomplete"
|
||||
)
|
||||
|
||||
data = "% Incomplete command at '^' marker."
|
||||
command = "show"
|
||||
|
||||
with self.assertRaises(CommandExecutionError):
|
||||
nxos_proxy._parse_output_for_errors(
|
||||
data, command, error_pattern=["Incomplete", "marker"]
|
||||
)
|
||||
|
||||
data = "% Invalid command at '^' marker."
|
||||
command = "show bep"
|
||||
|
||||
with self.assertRaises(CommandExecutionError):
|
||||
nxos_proxy._parse_output_for_errors(data, command)
|
||||
|
||||
data = "% Incomplete command at '^' marker."
|
||||
command = "show"
|
||||
|
||||
nxos_proxy._parse_output_for_errors(data, command)
|
||||
|
||||
data = "% Incomplete command at '^' marker."
|
||||
command = "show"
|
||||
|
||||
nxos_proxy._parse_output_for_errors(data, command, error_pattern="foo")
|
||||
|
||||
def test__init_ssh_raise_exception(self):
|
||||
|
||||
""" UT: nxos module:_init_ssh method - raise exception """
|
||||
|
||||
class SSHException(Exception):
|
||||
pass
|
||||
|
||||
with patch("salt.proxy.nxos.SSHConnection", autospec=True) as SSHConnection:
|
||||
with patch("salt.proxy.nxos.log", autospec=True) as log:
|
||||
with self.assertRaises(SSHException):
|
||||
SSHConnection.side_effect = SSHException
|
||||
nxos_proxy._init_ssh(None)
|
||||
log.error.assert_called()
|
543
tests/unit/states/test_nxos.py
Normal file
543
tests/unit/states/test_nxos.py
Normal file
|
@ -0,0 +1,543 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
:codeauthor: Mike Wiebe <@mikewiebe>
|
||||
"""
|
||||
|
||||
# Copyright (c) 2019 Cisco and/or its affiliates.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Import Python Libs
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import salt.states.nxos as nxos_state
|
||||
|
||||
# Import Salt Testing Libs
|
||||
from tests.support.mixins import LoaderModuleMockMixin
|
||||
from tests.support.mock import MagicMock, patch
|
||||
from tests.support.unit import TestCase
|
||||
|
||||
|
||||
class NxosNxapiStatesTestCase(TestCase, LoaderModuleMockMixin):
|
||||
def setup_loader_modules(self):
|
||||
return {nxos_state: {}}
|
||||
|
||||
def test_user_present_create(self):
|
||||
|
||||
""" UT: nxos module:user_present method - create """
|
||||
|
||||
roles = ["vdc-admin"]
|
||||
|
||||
side_effect = MagicMock(side_effect=[[], "", "set_role", roles, roles])
|
||||
with patch.dict(nxos_state.__opts__, {"test": False}):
|
||||
with patch.dict(nxos_state.__salt__, {"nxos.cmd": side_effect}):
|
||||
|
||||
result = nxos_state.user_present("daniel", roles=roles)
|
||||
|
||||
self.assertEqual(result["name"], "daniel")
|
||||
self.assertTrue(result["result"])
|
||||
self.assertEqual(result["changes"]["roles"]["new"], ["vdc-admin"])
|
||||
self.assertEqual(result["changes"]["roles"]["old"], [])
|
||||
self.assertEqual(result["comment"], "User set correctly")
|
||||
|
||||
def test_user_present_create_opts_test(self):
|
||||
|
||||
""" UT: nxos module:user_present method - create """
|
||||
|
||||
roles = ["vdc-admin"]
|
||||
|
||||
side_effect = MagicMock(side_effect=[[], "", "set_role", roles, roles])
|
||||
with patch.dict(nxos_state.__opts__, {"test": True}):
|
||||
with patch.dict(nxos_state.__salt__, {"nxos.cmd": side_effect}):
|
||||
|
||||
result = nxos_state.user_present("daniel", roles=roles)
|
||||
|
||||
self.assertEqual(result["name"], "daniel")
|
||||
self.assertEqual(result["result"], None)
|
||||
self.assertEqual(result["changes"]["role"]["add"], ["vdc-admin"])
|
||||
self.assertEqual(result["changes"]["role"]["remove"], [])
|
||||
self.assertEqual(result["comment"], "User will be created")
|
||||
|
||||
def test_user_present_create_non_defaults(self):
|
||||
|
||||
""" UT: nxos module:user_present method - create non default opts """
|
||||
|
||||
username = "daniel"
|
||||
password = "ghI&435y55#"
|
||||
roles = ["vdc-admin", "dev-ops"]
|
||||
encrypted = False
|
||||
crypt_salt = "foobar123"
|
||||
algorithm = "md5"
|
||||
|
||||
# [change_password, cur_roles, old_user, new_user, set_role, set_role,
|
||||
# get_roles, correct_password, cur_roles]
|
||||
side_effect = MagicMock(
|
||||
side_effect=[
|
||||
False,
|
||||
[],
|
||||
"",
|
||||
"new_user",
|
||||
"set_role",
|
||||
"set_role",
|
||||
roles,
|
||||
True,
|
||||
roles,
|
||||
]
|
||||
)
|
||||
with patch.dict(nxos_state.__opts__, {"test": False}):
|
||||
with patch.dict(nxos_state.__salt__, {"nxos.cmd": side_effect}):
|
||||
result = nxos_state.user_present(
|
||||
username,
|
||||
password=password,
|
||||
roles=roles,
|
||||
encrypted=encrypted,
|
||||
crypt_salt=crypt_salt,
|
||||
algorithm=algorithm,
|
||||
)
|
||||
|
||||
self.assertEqual(result["name"], "daniel")
|
||||
self.assertTrue(result["result"])
|
||||
self.assertEqual(result["changes"]["password"]["new"], "new_user")
|
||||
self.assertEqual(result["changes"]["password"]["old"], "")
|
||||
self.assertEqual(
|
||||
result["changes"]["roles"]["new"], ["vdc-admin", "dev-ops"]
|
||||
)
|
||||
self.assertEqual(result["changes"]["roles"]["old"], [])
|
||||
self.assertEqual(result["comment"], "User set correctly")
|
||||
|
||||
def test_user_present_create_encrypted_password_no_roles_opts_test(self):
|
||||
|
||||
""" UT: nxos module:user_present method - encrypted password, no roles """
|
||||
|
||||
username = "daniel"
|
||||
password = "$1$foobar12$K7x4Rxua11qakvrRjcwDC/"
|
||||
encrypted = True
|
||||
crypt_salt = "foobar123"
|
||||
algorithm = "md5"
|
||||
|
||||
side_effect = MagicMock(side_effect=[False, "", "new_user", True])
|
||||
|
||||
with patch.dict(nxos_state.__opts__, {"test": True}):
|
||||
with patch.dict(nxos_state.__salt__, {"nxos.cmd": side_effect}):
|
||||
|
||||
result = nxos_state.user_present(
|
||||
username,
|
||||
password=password,
|
||||
encrypted=encrypted,
|
||||
crypt_salt=crypt_salt,
|
||||
algorithm=algorithm,
|
||||
)
|
||||
|
||||
self.assertEqual(result["name"], "daniel")
|
||||
self.assertEqual(result["result"], None)
|
||||
self.assertEqual(result["changes"]["password"], True)
|
||||
self.assertEqual(result["comment"], "User will be created")
|
||||
|
||||
def test_user_present_create_user_exists(self):
|
||||
|
||||
""" UT: nxos module:user_present method - user exists """
|
||||
|
||||
username = "daniel"
|
||||
password = "$1$foobar12$K7x4Rxua11qakvrRjcwDC/"
|
||||
encrypted = True
|
||||
crypt_salt = "foobar123"
|
||||
algorithm = "md5"
|
||||
|
||||
side_effect = MagicMock(side_effect=[True, "user_exists"])
|
||||
|
||||
with patch.dict(nxos_state.__opts__, {"test": False}):
|
||||
with patch.dict(nxos_state.__salt__, {"nxos.cmd": side_effect}):
|
||||
|
||||
result = nxos_state.user_present(
|
||||
username,
|
||||
password=password,
|
||||
encrypted=encrypted,
|
||||
crypt_salt=crypt_salt,
|
||||
algorithm=algorithm,
|
||||
)
|
||||
|
||||
self.assertEqual(result["name"], "daniel")
|
||||
self.assertTrue(result["result"])
|
||||
self.assertEqual(result["changes"], {})
|
||||
self.assertEqual(result["comment"], "User already exists")
|
||||
|
||||
def test_user_present_create_user_exists_opts_test(self):
|
||||
|
||||
""" UT: nxos module:user_present method - user exists """
|
||||
|
||||
username = "daniel"
|
||||
password = "$1$foobar12$K7x4Rxua11qakvrRjcwDC/"
|
||||
roles = ["vdc-admin", "dev-opts"]
|
||||
new_roles = ["network-operator"]
|
||||
encrypted = True
|
||||
crypt_salt = "foobar123"
|
||||
algorithm = "md5"
|
||||
|
||||
side_effect = MagicMock(side_effect=[True, roles, "user_exists"])
|
||||
|
||||
with patch.dict(nxos_state.__opts__, {"test": True}):
|
||||
with patch.dict(nxos_state.__salt__, {"nxos.cmd": side_effect}):
|
||||
|
||||
result = nxos_state.user_present(
|
||||
username,
|
||||
password=password,
|
||||
roles=new_roles,
|
||||
encrypted=encrypted,
|
||||
crypt_salt=crypt_salt,
|
||||
algorithm=algorithm,
|
||||
)
|
||||
|
||||
remove = result["changes"]["roles"]["remove"]
|
||||
remove.sort()
|
||||
self.assertEqual(result["name"], "daniel")
|
||||
self.assertEqual(result["result"], None)
|
||||
self.assertEqual(
|
||||
result["changes"]["roles"]["add"], ["network-operator"]
|
||||
)
|
||||
self.assertEqual(remove, ["dev-opts", "vdc-admin"])
|
||||
self.assertEqual(result["comment"], "User will be updated")
|
||||
|
||||
def test_user_absent(self):
|
||||
|
||||
""" UT: nxos module:user_absent method - remove user """
|
||||
|
||||
username = "daniel"
|
||||
|
||||
side_effect = MagicMock(side_effect=["daniel", "remove_user", ""])
|
||||
|
||||
with patch.dict(nxos_state.__opts__, {"test": False}):
|
||||
with patch.dict(nxos_state.__salt__, {"nxos.cmd": side_effect}):
|
||||
|
||||
result = nxos_state.user_absent(username)
|
||||
|
||||
self.assertEqual(result["name"], "daniel")
|
||||
self.assertTrue(result["result"])
|
||||
self.assertEqual(result["changes"]["old"], "daniel")
|
||||
self.assertEqual(result["changes"]["new"], "")
|
||||
self.assertEqual(result["comment"], "User removed")
|
||||
|
||||
def test_user_absent_user_does_not_exist(self):
|
||||
|
||||
""" UT: nxos module:user_absent method - remove user """
|
||||
|
||||
username = "daniel"
|
||||
|
||||
side_effect = MagicMock(side_effect=[""])
|
||||
|
||||
with patch.dict(nxos_state.__opts__, {"test": False}):
|
||||
with patch.dict(nxos_state.__salt__, {"nxos.cmd": side_effect}):
|
||||
|
||||
result = nxos_state.user_absent(username)
|
||||
|
||||
self.assertEqual(result["name"], "daniel")
|
||||
self.assertTrue(result["result"])
|
||||
self.assertEqual(result["changes"], {})
|
||||
self.assertEqual(result["comment"], "User does not exist")
|
||||
|
||||
def test_user_absent_test_opts(self):
|
||||
|
||||
""" UT: nxos module:user_absent method - remove user """
|
||||
|
||||
username = "daniel"
|
||||
|
||||
side_effect = MagicMock(side_effect=["daniel", "remove_user", ""])
|
||||
|
||||
with patch.dict(nxos_state.__opts__, {"test": True}):
|
||||
with patch.dict(nxos_state.__salt__, {"nxos.cmd": side_effect}):
|
||||
|
||||
result = nxos_state.user_absent(username)
|
||||
|
||||
self.assertEqual(result["name"], "daniel")
|
||||
self.assertEqual(result["result"], None)
|
||||
self.assertEqual(result["changes"]["old"], "daniel")
|
||||
self.assertEqual(result["changes"]["new"], "")
|
||||
self.assertEqual(result["comment"], "User will be removed")
|
||||
|
||||
def test_config_present(self):
|
||||
|
||||
""" UT: nxos module:config_present method - add config """
|
||||
|
||||
config_data = [
|
||||
"snmp-server community randomSNMPstringHERE group network-operator",
|
||||
"snmp-server community AnotherRandomSNMPSTring group network-admin",
|
||||
]
|
||||
snmp_matches1 = [
|
||||
"snmp-server community randomSNMPstringHERE group network-operator"
|
||||
]
|
||||
snmp_matches2 = [
|
||||
["snmp-server community AnotherRandomSNMPSTring group network-admin"]
|
||||
]
|
||||
|
||||
side_effect = MagicMock(
|
||||
side_effect=[
|
||||
[],
|
||||
"add_snmp_config1",
|
||||
snmp_matches1,
|
||||
"add_snmp_config2",
|
||||
snmp_matches2,
|
||||
]
|
||||
)
|
||||
|
||||
with patch.dict(nxos_state.__opts__, {"test": False}):
|
||||
with patch.dict(nxos_state.__salt__, {"nxos.cmd": side_effect}):
|
||||
|
||||
result = nxos_state.config_present(config_data)
|
||||
|
||||
self.assertEqual(result["name"], config_data)
|
||||
self.assertTrue(result["result"])
|
||||
self.assertEqual(result["changes"]["new"], config_data)
|
||||
self.assertEqual(result["comment"], "Successfully added config")
|
||||
|
||||
def test_config_present_already_configured(self):
|
||||
|
||||
""" UT: nxos module:config_present method - add config already configured """
|
||||
|
||||
config_data = [
|
||||
"snmp-server community randomSNMPstringHERE group network-operator",
|
||||
"snmp-server community AnotherRandomSNMPSTring group network-admin",
|
||||
]
|
||||
|
||||
side_effect = MagicMock(side_effect=[config_data[0], config_data[1]])
|
||||
|
||||
with patch.dict(nxos_state.__opts__, {"test": False}):
|
||||
with patch.dict(nxos_state.__salt__, {"nxos.cmd": side_effect}):
|
||||
|
||||
result = nxos_state.config_present(config_data)
|
||||
|
||||
self.assertEqual(result["name"], config_data)
|
||||
self.assertTrue(result["result"])
|
||||
self.assertEqual(result["changes"], {})
|
||||
self.assertEqual(result["comment"], "Config is already set")
|
||||
|
||||
def test_config_present_test_opts(self):
|
||||
|
||||
""" UT: nxos module:config_present method - add config """
|
||||
|
||||
config_data = [
|
||||
"snmp-server community randomSNMPstringHERE group network-operator",
|
||||
"snmp-server community AnotherRandomSNMPSTring group network-admin",
|
||||
]
|
||||
snmp_matches1 = [
|
||||
"snmp-server community randomSNMPstringHERE group network-operator"
|
||||
]
|
||||
snmp_matches2 = [
|
||||
["snmp-server community AnotherRandomSNMPSTring group network-admin"]
|
||||
]
|
||||
|
||||
side_effect = MagicMock(
|
||||
side_effect=[
|
||||
[],
|
||||
"add_snmp_config1",
|
||||
snmp_matches1,
|
||||
"add_snmp_config2",
|
||||
snmp_matches2,
|
||||
]
|
||||
)
|
||||
|
||||
with patch.dict(nxos_state.__opts__, {"test": True}):
|
||||
with patch.dict(nxos_state.__salt__, {"nxos.cmd": side_effect}):
|
||||
|
||||
result = nxos_state.config_present(config_data)
|
||||
|
||||
self.assertEqual(result["name"], config_data)
|
||||
self.assertEqual(result["result"], None)
|
||||
self.assertEqual(result["changes"]["new"], config_data)
|
||||
self.assertEqual(result["comment"], "Config will be added")
|
||||
|
||||
def test_config_present_fail_to_add(self):
|
||||
|
||||
""" UT: nxos module:config_present method - add config fails"""
|
||||
|
||||
config_data = [
|
||||
"snmp-server community randomSNMPstringHERE group network-operator",
|
||||
"snmp-server community AnotherRandomSNMPSTring group network-admin",
|
||||
]
|
||||
snmp_matches1 = [
|
||||
"snmp-server community randomSNMPstringHERE group network-operator"
|
||||
]
|
||||
snmp_matches2 = [
|
||||
["snmp-server community AnotherRandomSNMPSTring group network-admin"]
|
||||
]
|
||||
|
||||
side_effect = MagicMock(
|
||||
side_effect=[[], "add_snmp_config1", "", "add_snmp_config2", ""]
|
||||
)
|
||||
|
||||
with patch.dict(nxos_state.__opts__, {"test": False}):
|
||||
with patch.dict(nxos_state.__salt__, {"nxos.cmd": side_effect}):
|
||||
|
||||
result = nxos_state.config_present(config_data)
|
||||
|
||||
self.assertEqual(result["name"], config_data)
|
||||
self.assertFalse(result["result"])
|
||||
self.assertEqual(result["changes"], {})
|
||||
self.assertEqual(result["comment"], "Failed to add config")
|
||||
|
||||
def test_replace(self):
|
||||
|
||||
""" UT: nxos module:replace method - replace config """
|
||||
|
||||
name = "randomSNMPstringHERE"
|
||||
repl = "NEWrandoSNMPstringHERE"
|
||||
matches_before = [
|
||||
"snmp-server community randomSNMPstringHERE group network-operator"
|
||||
]
|
||||
match_after = []
|
||||
changes = {}
|
||||
changes["new"] = [
|
||||
"snmp-server community NEWrandoSNMPstringHERE group network-operator"
|
||||
]
|
||||
changes["old"] = [
|
||||
"snmp-server community randomSNMPstringHERE group network-operator"
|
||||
]
|
||||
|
||||
side_effect = MagicMock(side_effect=[matches_before, changes, match_after])
|
||||
|
||||
with patch.dict(nxos_state.__opts__, {"test": False}):
|
||||
with patch.dict(nxos_state.__salt__, {"nxos.cmd": side_effect}):
|
||||
|
||||
result = nxos_state.replace(name, repl)
|
||||
|
||||
self.assertEqual(result["name"], name)
|
||||
self.assertTrue(result["result"])
|
||||
self.assertEqual(result["changes"]["new"], changes["new"])
|
||||
self.assertEqual(result["changes"]["old"], changes["old"])
|
||||
self.assertEqual(
|
||||
result["comment"],
|
||||
'Successfully replaced all instances of "randomSNMPstringHERE" with "NEWrandoSNMPstringHERE"',
|
||||
)
|
||||
|
||||
def test_replace_test_opts(self):
|
||||
|
||||
""" UT: nxos module:replace method - replace config """
|
||||
|
||||
name = "randomSNMPstringHERE"
|
||||
repl = "NEWrandoSNMPstringHERE"
|
||||
matches_before = [
|
||||
"snmp-server community randomSNMPstringHERE group network-operator"
|
||||
]
|
||||
match_after = []
|
||||
changes = {}
|
||||
changes["new"] = [
|
||||
"snmp-server community NEWrandoSNMPstringHERE group network-operator"
|
||||
]
|
||||
changes["old"] = [
|
||||
"snmp-server community randomSNMPstringHERE group network-operator"
|
||||
]
|
||||
|
||||
side_effect = MagicMock(side_effect=[matches_before, changes, match_after])
|
||||
|
||||
with patch.dict(nxos_state.__opts__, {"test": True}):
|
||||
with patch.dict(nxos_state.__salt__, {"nxos.cmd": side_effect}):
|
||||
|
||||
result = nxos_state.replace(name, repl)
|
||||
|
||||
self.assertEqual(result["name"], name)
|
||||
self.assertEqual(result["result"], None)
|
||||
self.assertEqual(result["changes"]["new"], changes["new"])
|
||||
self.assertEqual(result["changes"]["old"], changes["old"])
|
||||
self.assertEqual(result["comment"], "Configs will be changed")
|
||||
|
||||
def test_config_absent(self):
|
||||
|
||||
""" UT: nxos module:config_absent method - remove config """
|
||||
|
||||
config_data = [
|
||||
"snmp-server community randomSNMPstringHERE group network-operator",
|
||||
"snmp-server community AnotherRandomSNMPSTring group network-admin",
|
||||
]
|
||||
snmp_matches1 = [
|
||||
"snmp-server community randomSNMPstringHERE group network-operator"
|
||||
]
|
||||
snmp_matches2 = [
|
||||
["snmp-server community AnotherRandomSNMPSTring group network-admin"]
|
||||
]
|
||||
|
||||
side_effect = MagicMock(
|
||||
side_effect=[
|
||||
snmp_matches1,
|
||||
"remove_config",
|
||||
[],
|
||||
snmp_matches2,
|
||||
"remove_config",
|
||||
[],
|
||||
]
|
||||
)
|
||||
|
||||
with patch.dict(nxos_state.__opts__, {"test": False}):
|
||||
with patch.dict(nxos_state.__salt__, {"nxos.cmd": side_effect}):
|
||||
|
||||
result = nxos_state.config_absent(config_data)
|
||||
|
||||
self.assertEqual(result["name"], config_data)
|
||||
self.assertTrue(result["result"])
|
||||
self.assertEqual(result["changes"]["new"], config_data)
|
||||
self.assertEqual(result["comment"], "Successfully deleted config")
|
||||
|
||||
def test_config_absent_already_configured(self):
|
||||
|
||||
""" UT: nxos module:config_absent method - add config removed """
|
||||
|
||||
config_data = [
|
||||
"snmp-server community randomSNMPstringHERE group network-operator",
|
||||
"snmp-server community AnotherRandomSNMPSTring group network-admin",
|
||||
]
|
||||
|
||||
side_effect = MagicMock(side_effect=[[], []])
|
||||
|
||||
with patch.dict(nxos_state.__opts__, {"test": False}):
|
||||
with patch.dict(nxos_state.__salt__, {"nxos.cmd": side_effect}):
|
||||
|
||||
result = nxos_state.config_absent(config_data)
|
||||
|
||||
self.assertEqual(result["name"], config_data)
|
||||
self.assertTrue(result["result"])
|
||||
self.assertEqual(result["changes"], {})
|
||||
self.assertEqual(result["comment"], "Config is already absent")
|
||||
|
||||
def test_config_absent_test_opts(self):
|
||||
|
||||
""" UT: nxos module:config_absent method - remove config """
|
||||
|
||||
config_data = [
|
||||
"snmp-server community randomSNMPstringHERE group network-operator",
|
||||
"snmp-server community AnotherRandomSNMPSTring group network-admin",
|
||||
]
|
||||
snmp_matches1 = [
|
||||
"snmp-server community randomSNMPstringHERE group network-operator"
|
||||
]
|
||||
snmp_matches2 = [
|
||||
["snmp-server community AnotherRandomSNMPSTring group network-admin"]
|
||||
]
|
||||
|
||||
side_effect = MagicMock(
|
||||
side_effect=[
|
||||
snmp_matches1,
|
||||
"remove_config",
|
||||
[],
|
||||
snmp_matches2,
|
||||
"remove_config",
|
||||
[],
|
||||
]
|
||||
)
|
||||
|
||||
with patch.dict(nxos_state.__opts__, {"test": True}):
|
||||
with patch.dict(nxos_state.__salt__, {"nxos.cmd": side_effect}):
|
||||
|
||||
result = nxos_state.config_absent(config_data)
|
||||
|
||||
self.assertEqual(result["name"], config_data)
|
||||
self.assertEqual(result["result"], None)
|
||||
self.assertEqual(result["changes"]["new"], config_data)
|
||||
self.assertEqual(result["comment"], "Config will be removed")
|
|
@ -32,6 +32,7 @@ EXCLUDED_DIRS = [
|
|||
os.path.join("tests", "unit", "files"),
|
||||
os.path.join("tests", "integration", "cloud", "helpers"),
|
||||
os.path.join("tests", "kitchen", "tests"),
|
||||
os.path.join("tests", "unit", "modules", "nxos"),
|
||||
]
|
||||
INCLUDED_DIRS = [
|
||||
os.path.join("tests", "kitchen", "tests", "*", "tests", "*"),
|
||||
|
|
|
@ -6,10 +6,10 @@ from __future__ import absolute_import, print_function, unicode_literals
|
|||
import logging
|
||||
import re
|
||||
|
||||
import salt.utils.platform
|
||||
|
||||
# Import Salt Libs
|
||||
import salt.utils.platform
|
||||
import salt.utils.pycrypto
|
||||
from salt.exceptions import SaltInvocationError
|
||||
|
||||
# Import Salt Testing Libs
|
||||
from tests.support.unit import TestCase, skipIf
|
||||
|
@ -22,25 +22,81 @@ class PycryptoTestCase(TestCase):
|
|||
TestCase for salt.utils.pycrypto module
|
||||
"""
|
||||
|
||||
# The crypt module is only available on Unix systems
|
||||
# https://docs.python.org/dev/library/crypt.html
|
||||
@skipIf(not salt.utils.pycrypto.HAS_CRYPT, "crypt module not available")
|
||||
@skipIf(
|
||||
not salt.utils.pycrypto.HAS_CRYPT or not salt.utils.pycrypto.HAS_PASSLIB,
|
||||
"crypt not available",
|
||||
)
|
||||
def test_gen_hash(self):
|
||||
"""
|
||||
Test gen_hash
|
||||
"""
|
||||
passwd = "test_password"
|
||||
id = "$"
|
||||
if salt.utils.platform.is_darwin():
|
||||
id = ""
|
||||
ret = salt.utils.pycrypto.gen_hash(password=passwd)
|
||||
self.assertTrue(ret.startswith("$6{0}".format(id)))
|
||||
expecteds = {
|
||||
"sha512": {
|
||||
"hashed": "$6$rounds=656000$goodsalt$25xEV0IAcghzQbu8TF5KdDMYk3b4u9nR/38xYU/26xvPgirDavreGhtLfYRYW.RngLmRtD9i8S8XP3dPx4.PV.",
|
||||
"salt": "goodsalt",
|
||||
"badsalt": "badsalt",
|
||||
},
|
||||
"sha256": {
|
||||
"hashed": "$5$rounds=535000$goodsalt$2tSwAugenFhj2sHC1EHyGo.7razFvRhlK0c11k4.xG7",
|
||||
"salt": "goodsalt",
|
||||
"badsalt": "badsalt",
|
||||
},
|
||||
"blowfish": {
|
||||
"hashed": "$2b$12$goodsaltgoodsaltgoodsOaeGcaoZ.j.ugFo3vJZv5uk3W2zf2166",
|
||||
"salt": "goodsaltgoodsaltgoodsa",
|
||||
"badsalt": "badsaltbadsaltbadsaltb",
|
||||
},
|
||||
"md5": {
|
||||
"hashed": "$1$goodsalt$4XQMx4a4e1MpBB8xzz.TQ0",
|
||||
"salt": "goodsalt",
|
||||
"badsalt": "badsalt",
|
||||
},
|
||||
"crypt": {"hashed": "goVHulDpuGA7w", "salt": "go", "badsalt": "ba"},
|
||||
}
|
||||
invalid_salt = "thissaltistoolongthissaltistoolongthissaltistoolongthissaltistoolongthissaltistoolong"
|
||||
|
||||
ret = salt.utils.pycrypto.gen_hash(password=passwd, algorithm="md5")
|
||||
self.assertTrue(ret.startswith("$1{0}".format(id)))
|
||||
if salt.utils.pycrypto.HAS_PASSLIB:
|
||||
methods = salt.utils.pycrypto.known_methods
|
||||
force = True
|
||||
else:
|
||||
methods = salt.utils.pycrypto.methods
|
||||
force = False
|
||||
|
||||
ret = salt.utils.pycrypto.gen_hash(password=passwd, algorithm="sha256")
|
||||
self.assertTrue(ret.startswith("$5{0}".format(id)))
|
||||
for algorithm in methods:
|
||||
expected = expecteds[algorithm]
|
||||
ret = salt.utils.pycrypto.gen_hash(
|
||||
crypt_salt=expected["salt"],
|
||||
password=passwd,
|
||||
algorithm=algorithm,
|
||||
force=force,
|
||||
)
|
||||
self.assertEqual(ret, expected["hashed"])
|
||||
|
||||
ret = salt.utils.pycrypto.gen_hash(
|
||||
crypt_salt=expected["badsalt"],
|
||||
password=passwd,
|
||||
algorithm=algorithm,
|
||||
force=force,
|
||||
)
|
||||
self.assertNotEqual(ret, expected["hashed"])
|
||||
|
||||
ret = salt.utils.pycrypto.gen_hash(
|
||||
crypt_salt=None, password=passwd, algorithm=algorithm, force=force
|
||||
)
|
||||
self.assertNotEqual(ret, expected["hashed"])
|
||||
|
||||
with self.assertRaises(ValueError, msg=algorithm):
|
||||
ret = salt.utils.pycrypto.gen_hash(
|
||||
crypt_salt=invalid_salt,
|
||||
password=passwd,
|
||||
algorithm=algorithm,
|
||||
force=force,
|
||||
)
|
||||
self.assertNotEqual(ret, expected["hashed"])
|
||||
|
||||
with self.assertRaises(SaltInvocationError):
|
||||
salt.utils.pycrypto.gen_hash(algorithm="garbage")
|
||||
|
||||
def test_secure_password(self):
|
||||
"""
|
||||
|
@ -48,5 +104,5 @@ class PycryptoTestCase(TestCase):
|
|||
"""
|
||||
ret = salt.utils.pycrypto.secure_password()
|
||||
check = re.compile(r"[!@#$%^&*()_=+]")
|
||||
assert check.search(ret) is None
|
||||
assert ret
|
||||
self.assertIsNone(check.search(ret))
|
||||
self.assertTrue(ret)
|
||||
|
|
Loading…
Add table
Reference in a new issue