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 commit e17ca19fbc.

* 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 commit e17ca19fbc.

* Revert "Initial nxos_upgrade changes"

This reverts commit e17ca19fbc.

* 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:
David Hilton 2020-05-04 04:13:07 -04:00 committed by GitHub
parent 2b22bd0c26
commit 1c7ce9d793
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 7589 additions and 521 deletions

View file

@ -332,6 +332,7 @@ execution modules
nspawn
nxos
nxos_api
nxos_upgrade
omapi
openbsd_sysctl
openbsdpkg

View file

@ -0,0 +1,5 @@
salt.modules.nxos_upgrade module
============================
.. automodule:: salt.modules.nxos_upgrade
:members:

View file

@ -225,6 +225,7 @@ state modules
npm
ntp
nxos
nxos_upgrade
openstack_config
openvswitch_bridge
openvswitch_port

View file

@ -0,0 +1,5 @@
salt.states.nxos_upgrade module
=======================
.. automodule:: salt.states.nxos_upgrade
:members:

View file

@ -50,6 +50,7 @@ These guides go into detail how to install Salt on a given platform.
fedora
freebsd
gentoo
nxos
openbsd
osx
rhel

View 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

View file

@ -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
"""

View file

@ -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)

View file

@ -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)

View 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

View file

@ -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

View file

@ -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
View 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
View 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}

View file

@ -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)

View file

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

File diff suppressed because one or more lines are too long

View 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"],
}
}

View 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.
"""

View 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.
"""

View 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.
"""

View 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.
"""

View 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.
"""

View 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.
"""

View 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.
"""

View 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

View 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"

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View 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)

View 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()

View 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")

View file

@ -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", "*"),

View file

@ -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)