How to automate Cisco UCS with Microsoft Powershell

To test and verify your script you can use the virtual environment UCSPE.

First install Ucs.Manager Package on Powershell.

PS C:\WINDOWS\system32> Install-Module -Name Cisco.UCSManager -RequiredVersion 2.5.3.0

Verify Module is installed.

PS C:\WINDOWS\system32> Get-Package -Name *Ucs*
Name Version Source ProviderName
---- ------- ------ ------------
Cisco.UCS.Core 2.5.3.0 https://www.powershellgallery… PowerShellGet
Cisco.UCSManager 2.5.3.0 https://www.powershellgallery… PowerShellGet

Connect to UCS Manager. A pop-up will be open to enter your login and password.

PS C:\WINDOWS\system32> Connect-Ucs 10.0.100.162
applet de commande Connect-Ucs à la position 1 du pipeline de la commande
Fournissez des valeurs pour les paramètres suivants :
Credential
NumPendingConfigs : 0
Ucs : UCSPE-10-0-100-162
Cookie : 1594713831/e2c5faea-c1d9-412f-b7d1-b582c379477a
Domains : org-root
LastUpdateTime : 14/07/2020 10:03:54
Name : 10.0.100.162
NoSsl : False
NumWatchers : 0
Port : 443
Priv : {aaa, admin, ext-lan-config, ext-lan-policy…}
PromptOnCompleteTransaction : False
Proxy :
RefreshPeriod : 600
SessionId :
TransactionInProgress : False
Uri : https://10.0.100.162
UserName : ucspe
Version : 4.0(4e)
VirtualIpv4Address : 10.0.100.162
WatchThreadStatus : None

You can also enter directly your password.

PS C:\WINDOWS\system32> $ucsm_credentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList "ucspe", $(ConvertTo-SecureString -AsPlainText "ucspe" -Force)
PS C:\WINDOWS\system32> $ucsm_credentials
UserName Password
-------- --------
ucspe System.Security.SecureString
PS C:\WINDOWS\system32> Connect-Ucs 10.0.100.162 -Credential $ucsm_credentials
NumPendingConfigs : 0
Ucs : UCSPE-10-0-100-162
Cookie : 1594715399/f32ff405-981f-4c7a-817a-1d09324c89b6
Domains : org-root
LastUpdateTime : 14/07/2020 10:29:59
Name : 10.0.100.162
NoSsl : False
NumWatchers : 0
Port : 443
Priv : {aaa, admin, ext-lan-config, ext-lan-policy…}
PromptOnCompleteTransaction : False
Proxy :
RefreshPeriod : 600
SessionId :
TransactionInProgress : False
Uri : https://10.0.100.162
UserName : ucspe
Version : 4.0(4e)
VirtualIpv4Address : 10.0.100.162
WatchThreadStatus : None

You can disconnect with the following command:

PS C:\WINDOWS\system32> Disconnect-Ucs
Ucs : UCSPE-10-0-100-162
InCookie : 1594715399/f32ff405-981f-4c7a-817a-1d09324c89b6
Name : 10.0.100.162
OutStatus : success
SessionId :
Uri : https://10.0.100.162
Version : 4.0(4e)

You can Get the UCS blades and servers with this command:

PS C:\WINDOWS\system32> Get-UcsServer

AdminPower : policy
AdminState : in-service
AssetTag :
AssignedToDn :
Association : none
Availability : available
AvailableMemory : 49152
ChassisId : 3
CheckPoint : discovered
ConnPath : {A, B}
ConnStatus : {A, B}
Descr :
Discovery : complete
DiscoveryStatus :
KmipFault : no
KmipFaultDescription :
Lc : undiscovered
LcTs : 1970-01-01T00:00:00.000
LocalId :
LowVoltageMemory : not-applicable
ManagingInst : A
MemorySpeed : not-applicable
MfgTime : not-applicable
Model : UCSB-EX-M4-1
Name :
NumOf40GAdaptorsWithOldFw : 0
NumOf40GAdaptorsWithUnknownFw : 0
NumOfAdaptors : 2
NumOfCores : 10
NumOfCoresEnabled : 10
NumOfCpus : 2
NumOfEthHostIfs : 0
NumOfFcHostIfs : 0
NumOfThreads : 16
OperPower : off
OperPwrTransSrc : unknown
OperQualifier : {}
OperQualifierReason : N/A
OperState : unassociated
Operability : operable
OriginalUuid : 1b4e28ba-2fa1-11d2-0301-b9a761bde3fb
PartNumber :
PolicyLevel : 0
PolicyOwner : local
Presence : equipped
Revision : 0
Sacl :
ScaledMode : single
Serial : SRV107
ServerId : 3/1
SlotId : 1
StorageOperQualifier : unknown
TotalMemory : 49152
UsrLbl :
Uuid : 1b4e28ba-2fa1-11d2-0301-b9a761bde3fb
Vendor : Cisco Systems Inc
Vid :
Ucs : UCSPE-10-0-100-162
Dn : sys/chassis-3/blade-1
Rn : blade-1
Status :
XtraProperty : {}

And you can filter the result with the pipe command and Select-Object

PS C:\WINDOWS\system32> Get-UcsServer | Select-Object Dn
Dn
sys/chassis-3/blade-1
sys/chassis-3/blade-3
sys/chassis-3/blade-7
sys/chassis-3/blade-5
sys/chassis-4/blade-1
sys/chassis-4/blade-2
sys/chassis-5/blade-1
sys/chassis-5/blade-2
sys/chassis-5/blade-3
sys/chassis-6/blade-1
sys/chassis-6/blade-2
sys/chassis-6/blade-3
sys/rack-unit-1
sys/rack-unit-2
sys/rack-unit-3
sys/rack-unit-4
sys/rack-unit-5
sys/rack-unit-6
sys/rack-unit-7
sys/rack-unit-8
sys/rack-unit-9
sys/rack-unit-10
sys/rack-unit-11
sys/rack-unit-12

or with two filters to Get the Serial for each server

PS C:\WINDOWS\system32> Get-UcsServer | Select-Object Dn,Serial
Dn Serial
-- ------
sys/chassis-3/blade-1 SRV107
sys/chassis-3/blade-3 SRV108
sys/chassis-3/blade-7 SRV110
sys/chassis-3/blade-5 SRV126
sys/chassis-4/blade-1 SRV111
sys/chassis-4/blade-2 SRV112
sys/chassis-5/blade-1 SRV113
sys/chassis-5/blade-2 SRV114
sys/chassis-5/blade-3 SRV115
sys/chassis-6/blade-1 SRV122
sys/chassis-6/blade-2 SRV124
sys/chassis-6/blade-3 SRV125
sys/rack-unit-1 ND80
sys/rack-unit-2 ND81
sys/rack-unit-3 RK46
sys/rack-unit-4 RK54
sys/rack-unit-5 RK57
sys/rack-unit-6 RK58
sys/rack-unit-7 RK59
sys/rack-unit-8 RK60
sys/rack-unit-9 RK62
sys/rack-unit-10 RK63
sys/rack-unit-11 RK64
sys/rack-unit-12 RK65

You can see Servers and Blades with the previous command or only the blade.

PS C:\WINDOWS\system32> Get-UcsBlade | Select-Object Dn,Serial
Dn Serial
-- ------
sys/chassis-3/blade-1 SRV107
sys/chassis-3/blade-3 SRV108
sys/chassis-3/blade-5 SRV126
sys/chassis-3/blade-7 SRV110
sys/chassis-4/blade-1 SRV111
sys/chassis-4/blade-2 SRV112
sys/chassis-5/blade-1 SRV113
sys/chassis-5/blade-2 SRV114
sys/chassis-5/blade-3 SRV115
sys/chassis-6/blade-1 SRV122
sys/chassis-6/blade-2 SRV124
sys/chassis-6/blade-3 SRV125

You can search all commands ucs and the documentation with the following commands:

PS C:\WINDOWS\system32> Get-Command -noun Ucs | more
CommandType Name Version Source
----------- ---- ------- ------
Alias Acknowledge-UcsBlade 2.5.3.0 Cisco.UcsManager
Alias Acknowledge-UcsChassis 2.5.3.0 Cisco.UcsManager
Alias Acknowledge-UcsFault 2.5.3.0 Cisco.UcsManager
Alias Acknowledge-UcsFex 2.5.3.0 Cisco.UcsManager
Alias Acknowledge-UcsRackUnit 2.5.3.0 Cisco.UcsManager
Alias Acknowledge-UcsServerUnit 2.5.3.0 Cisco.UcsManager
Alias Acknowledge-UcsSlot 2.5.3.0 Cisco.UcsManager
Alias Add-UcsMo 2.5.3.0 Cisco.UcsManager
Alias Associate-UcsServiceProfile 2.5.3.0 Cisco.UcsManager
Alias Compare-UcsMo 2.5.3.0 Cisco.UcsManager
Alias Decommission-UcsBlade 2.5.3.0 Cisco.UcsManager
Alias Decommission-UcsChassis 2.5.3.0 Cisco.UcsManager
Alias Decommission-UcsFex 2.5.3.0 Cisco.UcsManager
Alias Decommission-UcsRackUnit 2.5.3.0 Cisco.UcsManager
Alias Decommission-UcsServerUnit 2.5.3.0 Cisco.UcsManager
Alias Disable-UcsDiskLocatorLed 2.5.3.0 Cisco.UcsManager
Alias Disassociate-UcsServiceProfile 2.5.3.0 Cisco.UcsManager
Alias Enable-UcsDiskLocatorLed 2.5.3.0 Cisco.UcsManager
Alias Get-UcsCentral 2.5.3.0 Cisco.UcsManager
Alias Get-UcsMo 2.5.3.0 Cisco.UcsManager
Alias Get-UcsOs 2.5.3.0 Cisco.Ucs.Core
[..]

PS C:\WINDOWS\system32> Get-help Get-UcsServer
NOM
Get-UcsServer
RÉSUMÉ
Gets Managed Object of type ComputeBlade, ComputeRackUnit and ComputeServerUnit.
SYNTAXE
Get-UcsServer [-Ucs ] []
DESCRIPTION
Gets Managed Object of type ComputeBlade, ComputeRackUnit and ComputeServerUnit.
LIENS CONNEXES
REMARQUES
Pour consulter les exemples, tapez : "get-help Get-UcsServer -examples".
Pour plus d'informations, tapez : "get-help Get-UcsServer -detailed".
Pour obtenir des informations techniques, tapez : "get-help Get-UcsServer -full".

Vlans

Get Vlans

PS C:\WINDOWS\system32> Get-UCsVlan | Select-Object Dn,Name,Id,SwitchId
Dn Name Id SwitchId
-- ---- -- --------
fabric/eth-estc/net-default default 1 dual
fabric/lan/A/net-default default 1 A
fabric/lan/A/net-finance finance 3 A
fabric/lan/A/net-human-resource human-resource 5 A
fabric/lan/B/net-default default 1 B
fabric/lan/B/net-finance finance 3 B
fabric/lan/B/net-human-resource human-resource 5 B
fabric/lan/net-default default 1 dual

Add Vlans

PS C:\WINDOWS\system32> Get-help Add-UcsVlan
PS C:\WINDOWS\system32> Get-UcsLanCloud | Add-UcsVlan -Name NewVlan42 -Id 42
AssocPrimaryVlanState : ok
AssocPrimaryVlanSwitchId : NONE
Cloud : ethlan
CompressionType : included
ConfigIssues :
ConfigOverlap : ok
DefaultNet : no
EpDn :
Global : 0
Id : 42
IfRole : network
IfType : virtual
Local : 0
Locale : external
McastPolicyName :
Name : NewVlan42
OperMcastPolicyName :
OperState : ok
OverlapStateForA : active
OverlapStateForB : active
PeerDn :
PolicyOwner : local
PubNwDn :
PubNwId : 1
PubNwName :
Sacl :
Sharing : none
SwitchId : dual
Transport : ether
Type : lan
Ucs : UCSPE-10-0-100-162
Dn : fabric/lan/net-NewVlan42
Rn : net-NewVlan42
Status : created
XtraProperty : {}

Verify

PS C:\WINDOWS\system32> Get-UCsVlan | Select-Object Dn,Name,Id,SwitchId
Dn Name Id SwitchId
-- ---- -- --------
fabric/eth-estc/net-default default 1 dual
fabric/lan/A/net-default default 1 A
fabric/lan/A/net-finance finance 3 A
fabric/lan/A/net-human-resource human-resource 5 A
fabric/lan/B/net-default default 1 B
fabric/lan/B/net-finance finance 3 B
fabric/lan/B/net-human-resource human-resource 5 B
fabric/lan/net-default default 1 dual
fabric/lan/net-NewVlan42 NewVlan42 42 dual

In addition to Powershell, you can also use the python SDK (Docs).

Example to add a new vlan:

from ucsmsdk.ucshandle import UcsHandle
from ucsmsdk.mometa.fabric.FabricVlan import FabricVlan
Create a connection handle
handle = UcsHandle("10.0.100.162", "ucspe", "ucspe")
Login to the server
handle.login()
fabric_lan_dn = handle.query_dn("fabric/lan")
newvlan = FabricVlan(parent_mo_or_dn=fabric_lan_dn,
name = "vlan10",
id="10")
handle.add_mo(newvlan)
handle.commit()
Logout from the server
handle.logout()

Service Profile

PS C:\WINDOWS\system32> Get-UcsServiceProfile
AgentPolicyName :
AssignState : unassigned
AssocState : unassociated
BiosProfileName :
BootPolicyName :
ConfigQualifier : {}
ConfigState : not-applied
Descr :
DynamicConPolicyName :
ExtIPPoolName : ext-mgmt
ExtIPState : none
GraphicsCardPolicyName :
HostFwPolicyName :
IdentPoolName :
KvmMgmtPolicyName :
LocalDiskPolicyName :
MaintPolicyName :
MgmtAccessPolicyName :
MgmtFwPolicyName :
Name : ServiceProfile1
OperBiosProfileName :
OperBootPolicyName : org-root/boot-policy-default
OperDynamicConPolicyName :
OperExtIPPoolName :
OperGraphicsCardPolicyName : org-root/graphics-card-policy-default
OperHostFwPolicyName : org-root/fw-host-pack-default
OperIdentPoolName :
OperKvmMgmtPolicyName : org-root/kvm-mgmt-policy-default
OperLocalDiskPolicyName : org-root/local-disk-config-default
OperMaintPolicyName : org-root/maint-default
OperMgmtAccessPolicyName :
OperMgmtFwPolicyName :
OperPersistentMemoryPolicyName :
OperPowerPolicyName : org-root/power-policy-default
OperPowerSyncPolicyName : org-root/power-sync-default
OperScrubPolicyName : org-root/scrub-default
OperSolPolicyName :
OperSrcTemplName :
OperState : unassociated
OperStatsPolicyName : org-root/thr-policy-default
OperVconProfileName :
OperVmediaPolicyName :
Owner : management
PersistentMemoryPolicyName :
PnDn :
PolicyLevel : 0
PolicyOwner : local
PowerPolicyName : default
PowerSyncPolicyName :
PropAcl : 0
ResolveRemote : yes
Sacl :
ScrubPolicyName :
SolPolicyName :
SrcTemplName :
StatsPolicyName : default
SvnicConfig : yes
Type : instance
UsrLbl :
Uuid : derived
UuidSuffix : 0000-000000000000
VconProfileName :
VmediaPolicyName :
Ucs : UCSPE-10-0-100-162
Dn : org-root/ls-ServiceProfile1
Rn : ls-ServiceProfile1
Status :
XtraProperty : {}

A lot of examples can be found here : https://github.com/CiscoUcs

How to automate your Cisco legacy network with Ansible

In the previous article , we introduce ansible with NXOS devices. We can also use ansible for Catalyst, NXOS, NXOS-ACI, etc.

Ansible can be very useful to search something or to backup your configuration.

Example to save your configuration :

---

  - name: Configure IOS
    hosts: routers
    connection: local
    gather_facts: False
    any_errors_fatal: true

    tasks:

      - name: show running
        ios_command:
          commands:
            - 'show run'
        register: running_config
        tags:
        - backup

      - name: save output
        copy: content="{{running_config.stdout[0]}}" dest="./output/{{inventory_hostname}}-show_run.txt"
        tags:
        - backup

In the last part, I use the save my output with register: running_config and then I use the module copy to create a new file with the content save in running_config.

You need to create first the directory named here output. After Ansible will create a file with the device name as prefix and concatenate -show_run.txt

copy: content="{{running_config.stdout[0]}}" dest="./output/{{inventory_hostname}}-show_run.txt"

root@09cf326cc275:/ansible/NXOS#  tree output/
output/
|-- R7-show_run.txt
`-- R7.txt

Inside the file you will have your running configuration.

Playbooks are now mandatory, you can also use ad hoc command to search something on your device.

Example with show ip arp or show version

ansible R7 -i inventory-home -m ios_command -a "commands='show ip arp'"                            
 R7 | SUCCESS => {
     "changed": false, 
     "stdout": [
         "Protocol  Address          Age (min)  Hardware Addr   Type   Interface\nInternet  10.0.100.1              0   000c.2935.812f  ARPA   Ethernet0/0\nInternet  10.0.100.67             -   aabb.cc00.7000  ARPA   Ethernet0/0\nInternet  10.0.100.150            1   a483.e7bf.9979  ARPA   Ethernet0/0"
     ], 
     "stdout_lines": [
         [
             "Protocol  Address          Age (min)  Hardware Addr   Type   Interface", 
             "Internet  10.0.100.1              0   000c.2935.812f  ARPA   Ethernet0/0", 
             "Internet  10.0.100.67             -   aabb.cc00.7000  ARPA   Ethernet0/0", 
             "Internet  10.0.100.150            1   a483.e7bf.9979  ARPA   Ethernet0/0"
         ]
     ]
 }

root@09cf326cc275:/ansible/NXOS# ansible R7 -i inventory-home -m ios_command -a "commands='show version'"

Currently we use only show command, but you can also configure your catalyst devices. The following task will enable ospf on all interfaces. I added a tag named OSPF to be able to play only OSPF task within my playbook.

---

  - name: Configure IOS
    hosts: routers
    connection: local
    gather_facts: False
    any_errors_fatal: true

    tasks:
      - name: Enable ospf
        ios_config:
          lines:
            - network 0.0.0.0 255.255.255.255 ar 0
          parents: router ospf 1
        register: ospf
        tags:
        - OSPF

      - debug: var=ospf
        tags:
        - OSPF
root@09cf326cc275:/ansible/NXOS# ansible-playbook -i inventory-home playbook-ios.yaml --tags OSPF
 PLAY [Configure IOS] *
 TASK [Enable ospf] ***
 changed: [R7]
 TASK [debug] *
 ok: [R7] => {
     "ospf": {
         "banners": {}, 
         "changed": true, 
         "commands": [
             "router ospf 1", 
             "network 0.0.0.0 255.255.255.255 ar 0"
         ], 
         "failed": false, 
         "updates": [
             "router ospf 1", 
             "network 0.0.0.0 255.255.255.255 ar 0"
         ]
     }
 }
 PLAY RECAP ***
 R7                         : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Other example to search an Endpoint on your network with ad hoc command. Here I want to search one server with the @MAC : ecbd.1d44.b6c1.

root@09cf326cc275:/ansible/NXOS# ansible SW1 -i inventory-home -m ios_command -a "commands='show mac address'"  | egrep -v "\n"       
SW1 | SUCCESS => {
    "stdout": [
    ], 
        [
            "Mac Address Table", 
            "-------------------------------------------", 
            "", 
            "----    -----------       --------    -----", 
            "   1    0050.2935.812f    DYNAMIC     Gi0/0", 
            "   1    0050.8824.7718    DYNAMIC     Gi0/0", 
            "   1    0050.bdf0.b6ad    DYNAMIC     Gi0/0", 
            "   1    0050.1878.2797    DYNAMIC     Gi0/0", 
            "   1    0050.9110.af2c    DYNAMIC     Gi0/0", 
            "   1    0050.e7bf.9979    DYNAMIC     Gi0/0", 
            "   1    0050.cc00.2011    DYNAMIC     Gi0/0", 
            "   1    0050.cc00.7000    DYNAMIC     Gi0/0", 
            "   1    0050.eba6.c667    DYNAMIC     Gi0/0", 
            "   1    0050.8b57.d81b    DYNAMIC     Gi0/0", 
            "   1    0050.817a.ce2e    DYNAMIC     Gi0/0", 
            "   1    ecbd.1d44.b6c1    DYNAMIC     Gi0/0", 
            "   1    0050.754a.a8ee    DYNAMIC     Gi0/0", 
        ]
    ]
}

root@09cf326cc275:/ansible/NXOS# ansible catalyst -i inventory-home -m ios_command -a "commands='show mac address'"  | egrep -v "\n" | grep "SUCCESS \|ecbd.1d44.b6c1"
SW1 | SUCCESS => {
            "   1    ecbd.1d44.b6c1    DYNAMIC     Gi0/0",

Here I have found the server on switch SW1 port Gi0/0, which is an uplink port. If I added other switch in my group named catalyst, I’ll be able to found on all switches where is learned this @MAC.

In your inventory file, you need to use groups to organize properly your network. It can be very useful to run one command to only one part of your network or to all.

In the following example we have on DC named DC1 with two different rooms. Each room contains two switches. Now, you can run command only to switches in the Room1 or Room2 or all inside DC1.

[DC1:children]
Room1
Room2

[Room1]
DC1-SW1
DC1-SW2

[Room2]
DC1-SW10
DC1-SW11

How to Automate Cisco NXOS infrastructure with Ansible

You manage a lot of network devices, but you are alone or you don’t have time. Ansible can help you to manage your change on your whole network very quickly based on your own template. In this article we will use Cisco Nexus 9K.

You have a new DNS server, syslog server etc and you need to modify hundred switches. No worries, with ansible it can be very simple.

First you should create at least two files. The first one will be your inventory and contains your switches. The second will be your playbook.

The first thing is to create a service account for ansible in your switches. This account could be centralize or local. In the following I’ll provide my password in cleartext. Of course, it’s not recommended and you should prefer ssh-key.

On my virtual nexus 9k, I only configured my account and my management IP address.

My topology contains :

  • Nexus-1 : IP 10.0.100.99, name: AGG1
  • Nexus-2 : IP 10.0.100.100, name: ACC1
  • Nexus-3 : IP 10.0.100.101, name: ACC2
switch(config-if)# sh run 

!Command: show running-config
!Running configuration last done at: Sat Mar 21 18:28:03 2020
!Time: Sat Mar 21 18:29:45 2020

version 9.3(2) Bios:version  
[..]
username ansible password 5 $5$.FhD0kmO$4PJV/HKJN5ul9aK7160ii.1WQ3s9pjh2QCRL7x7l
EU/  role network-admin
username ansible passphrase  lifetime 99999 warntime 14 gracetime 3
ip domain-lookup

[..]
interface mgmt0
  vrf member management
  ip address 10.0.100.100/24
line console
line vty

The inventory file will be the following. We can use two formats: YAML or INI. This one will use the INI format. This file contains a group named N9K with three switches.

[N9K]
AGR1 ansible_host=10.0.100.99  ansible_port=22
ACC1  ansible_host=10.0.100.100 ansible_port=22
ACC2  ansible_host=10.0.100.101 ansible_port=22

[N9K:vars]
ansible_user=ansible
ansible_password=@ns1b!E.
ansible_connection=network_cli
ansible_network_os=nxos
ansible_python_interpreter="/usr/bin/env python"

The following file uses the YAML format. This first playbook is very simple and contains one task to configure the switch hostname.

---
- name: Setup Nexus Devices

  hosts: all
  connection: local
  gather_facts: False


  tasks:

    - name: configure hostname
      nxos_config:
        lines: hostname {{ inventory_hostname }}
        save_when: modified

Now I’ll verify my playbook, before apply the changes. This command uses the option -i to specify which file should be use as inventory and –check to simulate the changes.

root@09cf326cc275:/ansible/NXOS# ansible-playbook -i inventory-home playbook-home.yaml --check

PLAY [Setup Nexus Devices] ***********************************************************************************************************************

TASK [configure hostname] ************************************************************************************************************************
[WARNING]: Skipping command `copy running-config startup-config` due to check_mode.  Configuration not copied to non-volatile storage
terpreter_discovery.html for more information.
changed: [ACC1]
changed: [AGR1]
changed: [ACC2]

PLAY RECAP ***************************************************************************************************************************************
ACC1                       : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
ACC2                       : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
AGR1                       : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 

Now I’ll do the same without the option –check and my Nexus device should be configured. You can see the message copy running is not there.

root@09cf326cc275:/ansible/NXOS# ansible-playbook -i inventory-home playbook-home.yaml        

PLAY [Setup Nexus Devices] ***********************************************************************************************************************

TASK [configure hostname] ************************************************************************************************************************

changed: [ACC1]
changed: [AGR1]
changed: [ACC2]

PLAY RECAP ***************************************************************************************************************************************
ACC1                       : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
ACC2                       : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
AGR1                       : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Fantastic, my nexus have been configured !! With the command show accounting log, you can verify the command injected by ansible. In my playbook, I added the line save_when: modified to save the configuration after the changes.

AGR1# show accounting log | last 10
Sat Mar 21 18:45:42 2020:type=stop:id=10.0.100.150@pts/2:user=ansible:cmd=shell terminated because the ssh session closed
Sat Mar 21 18:49:11 2020:type=start:id=10.0.100.150@pts/2:user=ansible:cmd=
Sat Mar 21 18:49:12 2020:type=update:id=10.0.100.150@pts/2:user=ansible:cmd=terminal length 0 (SUCCESS)
Sat Mar 21 18:49:12 2020:type=update:id=10.0.100.150@pts/2:user=ansible:cmd=terminal width 511 (SUCCESS)
Sat Mar 21 18:49:20 2020:type=update:id=10.0.100.150@pts/2:user=ansible:cmd=configure terminal ; hostname AGR1 (SUCCESS)
Sat Mar 21 18:49:26 2020:type=update:id=10.0.100.150@pts/2:user=ansible:cmd=Performing configuration copy.
Sat Mar 21 18:49:36 2020:type=start:id=vsh.bin.13650:user=admin:cmd=
Sat Mar 21 18:49:52 2020:type=update:id=10.0.100.150@pts/2:user=ansible:cmd=copy running-config startup-config (SUCCESS)
Sat Mar 21 18:49:53 2020:type=stop:id=10.0.100.150@pts/2:user=ansible:cmd=shell terminated because the ssh session closed
Sat Mar 21 18:52:35 2020:type=update:id=console0:user=admin:cmd=terminal width 511 (SUCCESS)

Now you can imagine the next step. You can add your syslog server for example.

    - name: configure syslog server
      nxos_config:
        lines:
          - logging server 10.0.100.42 4 use-vrf management facility local7
          - logging timestamp milliseconds
        save_when: modified

Before the change:

AGR1(config)# logging timestamp milliseconds ^C
AGR1(config)# sh logging 

Logging console:                enabled (Severity: critical)
Logging monitor:                enabled (Severity: notifications)
Logging linecard:               enabled (Severity: notifications)
Logging timestamp:              Seconds
Logging source-interface :      disabled
Logging rate-limit:             enabled
Logging server:                 disabled
Logging origin_id :             disabled
Logging RFC :                   disabled
Logging logflash:               enabled (Severity: notifications)
Logging logfile:                enabled
        Name - messages: Severity - notifications Size - 4194304

[..]

After the change:

AGR1(config)# 2020 Mar 21 18:58:48 AGR1 %$ VDC-1 %$  %SYSLOG-2-SYSTEM_MSG: Attempt to configure logging server with: hostname/IP 10.0.100.42,severity 4,port 514,facility local7 - syslogd
AGR1(config)# sh logging 

Logging console:                enabled (Severity: critical)
Logging monitor:                enabled (Severity: notifications)
Logging linecard:               enabled (Severity: notifications)
Logging timestamp:              Milliseconds
Logging source-interface :      disabled
Logging rate-limit:             enabled
Logging server:                 enabled
{10.0.100.42}
        This server is temporarily unreachable
        server severity:        warnings
        server facility:        local7
        server VRF:             management
        server port:            514
Logging origin_id :             disabled
Logging RFC :                   disabled
Logging logflash:               enabled (Severity: notifications)
Logging logfile:                enabled
        Name - messages: Severity - notifications Size - 4194304
[..]
root@09cf326cc275:/ansible/NXOS# ansible-playbook -i inventory-home playbook-home.yaml

PLAY [Setup Nexus Devices] ***********************************************************************************************************************

TASK [configure hostname] ************************************************************************************************************************

ok: [ACC1]
ok: [AGR1]
ok: [ACC2]

TASK [configure syslog server] *******************************************************************************************************************
changed: [ACC1]
changed: [ACC2]
changed: [AGR1]

PLAY RECAP ***************************************************************************************************************************************
ACC1                       : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
ACC2                       : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
AGR1                       : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

I can be useful to manage your access-list. Imagine you install a new server for the monitoring and you need to update one entry. This time we will use another module named nxos_acl.

    - name: configure SNMP-ACCESS-LIST
      nxos_acl:
        name: ACL_SNMP-ReadOnly
        seq: "10"
        action: permit
        proto: udp
        src: 10.0.100.42/32
        dest: any
        state: present

Now we have the ACL configured on all switches. When the module exists, prefer to use the specific module.

root@09cf326cc275:/ansible/NXOS# ansible-playbook -i inventory-home playbook-home.yaml

PLAY [Setup Nexus Devices] ***********************************************************************************************************************

TASK [configure hostname] ************************************************************************************************************************

ok: [ACC1]
ok: [AGR1]
ok: [ACC2]

TASK [configure syslog server] *******************************************************************************************************************
changed: [ACC1]
changed: [ACC2]
changed: [AGR1]

TASK [configure SNMP-ACCESS-LIST] ****************************************************************************************************************
changed: [ACC1]
changed: [ACC2]
changed: [AGR1]

PLAY RECAP ***************************************************************************************************************************************
ACC1                       : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
ACC2                       : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
AGR1                       : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 
AGR1(config)# sh ip access-lists ACL_SNMP-ReadOnly

IP access list ACL_SNMP-ReadOnly
        10 permit udp 10.0.100.42/32 any
--
ACC1(config)# sh ip access-lists ACL_SNMP-ReadOnly

IP access list ACL_SNMP-ReadOnly
	10 permit udp 10.0.100.42/32 any
--
ACC2# sh ip access-lists ACL_SNMP-ReadOnly

IP access list ACL_SNMP-ReadOnly
        10 permit udp 10.0.100.42/32 any 

This module is idempotent. Now we will update the ACL with a second entry. The documentation is here.

    - name: configure SNMP-ACCESS-LIST
      nxos_acl:
        name: ACL_SNMP-ReadOnly
        seq: "10"
        action: permit
        proto: udp
        src: 10.0.100.42/32
        dest: any
        state: present

    - name: configure SNMP-ACCESS-LIST
      nxos_acl:
        name: ACL_SNMP-ReadOnly
        seq: "20"
        action: permit
        proto: udp
        src: 10.0.100.43/32
        dest: any
        state: present
root@09cf326cc275:/ansible/NXOS# ansible-playbook -i inventory-home playbook-home.yaml

PLAY [Setup Nexus Devices] ***********************************************************************************************************************

TASK [configure hostname] ************************************************************************************************************************
changed: [ACC1]
changed: [AGR1]
changed: [ACC2]

TASK [configure syslog server] *******************************************************************************************************************
changed: [ACC1]
changed: [ACC2]
changed: [AGR1]

TASK [configure SNMP-ACCESS-LIST] ****************************************************************************************************************
ok: [ACC1]
ok: [AGR1]
ok: [ACC2]

TASK [configure SNMP-ACCESS-LIST] ****************************************************************************************************************
changed: [ACC1]
changed: [AGR1]
changed: [ACC2]

PLAY RECAP ***************************************************************************************************************************************
ACC1                       : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
ACC2                       : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
AGR1                       : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
AGR1(config)# sh ip access-lists ACL_SNMP-ReadOnly

IP access list ACL_SNMP-ReadOnly
        10 permit udp 10.0.100.42/32 any 
        20 permit udp 10.0.100.43/32 any

and they update the last entry with a new IP address.

    - name: configure SNMP-ACCESS-LIST
      nxos_acl:
        name: ACL_SNMP-ReadOnly
        seq: "10"
        action: permit
        proto: udp
        src: 10.0.100.42/32
        dest: any
        state: present

    - name: configure SNMP-ACCESS-LIST
      nxos_acl:
        name: ACL_SNMP-ReadOnly
        seq: "20"
        action: permit
        proto: udp
        src: 10.0.100.44/32
        dest: any
        state: present
root@09cf326cc275:/ansible/NXOS# ansible-playbook -i inventory-home playbook-home.yaml

PLAY [Setup Nexus Devices] ***********************************************************************************************************************

TASK [configure hostname] ************************************************************************************************************************
changed: [ACC1]
changed: [ACC2]
changed: [AGR1]

TASK [configure syslog server] *******************************************************************************************************************
changed: [ACC1]
changed: [AGR1]
changed: [ACC2]

TASK [configure SNMP-ACCESS-LIST] ****************************************************************************************************************
ok: [ACC1]
ok: [AGR1]
ok: [ACC2]

TASK [configure SNMP-ACCESS-LIST] ****************************************************************************************************************
changed: [ACC1]
changed: [AGR1]
changed: [ACC2]

PLAY RECAP ***************************************************************************************************************************************
ACC1                       : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
ACC2                       : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
AGR1                       : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 
AGR1(config)# sh ip access-lists ACL_SNMP-ReadOnly

IP access list ACL_SNMP-ReadOnly
        10 permit udp 10.0.100.42/32 any 
        20 permit udp 10.0.100.44/32 any

You can image a lot of scenario now, and apply your change very quickly.

How to Use Ansible: An Ansible Cheat Sheet

Check hosts in the inventory :

ansible@Deb-Master:~/base$ ansible all --list-hosts
   hosts (1):
     client1

Get list of modules

ansible@Deb-Master:~/base$ /usr/local/bin/ansible-doc -l | egrep ^(aci|mso)
 [WARNING]: win_template parsing did not produce documentation.
 [WARNING]: template parsing did not produce documentation.
 aci_l3out                                                     Manage Layer 3 Outside (L3Out) objects (l3ext:Out)
 aci_interface_policy_cdp                                      Manage CDP interface policies (cdp:IfPol)
 aci_maintenance_group_node                                    Manage maintenance group nodes
 mso_site                                                      Manage sites
 aci_intf_policy_fc                                            Manage Fibre Channel interface policies (fc:IfPol)
 aci_filter_entry                                              Manage filter entries (vz:Entry)
 mso_schema_site_vrf                                           Manage site-local VRFs in schema template
 mso_schema_site_anp_epg_staticleaf                            Manage site-local EPG static leafs in schema template
 aci_intf_policy_port_channel                                  Manage port channel interface policies (lacp:LagPol)
 mso_schema_template_filter_entry                              Manage filter entries in schema templates
 aci_aaa_user_certificate                                      Manage AAA user certificates (aaa:UserCert)
 aci_switch_policy_leaf_profile                                Manage switch policy leaf profiles (infra:NodeP)
 aci_interface_policy_lldp                                     Manage LLDP interface policies (lldp:IfPol)
 mso_schema_template_externalepg                               Manage external EPGs in schema templates
 aci_tenant_span_src_group                                     Manage SPAN source groups (span:SrcGrp)
 aci_access_port_block_to_access_port                          Manage port blocks of Fabric interface policy leaf profile interface selectors (infra:HPortS, infra:PortBlk)
 aci_epg_to_contract                                           Bind EPGs to Contracts (fv:RsCons, fv:RsProv)
 aci_access_port_to_interface_policy_leaf_profile              Manage Fabric interface policy leaf profile interface selectors (infra:HPortS, infra:RsAccBaseGrp, infra:PortBlk)
 aci_firmware_source                                           Manage firmware image sources (firmware:OSource)
 aci_tenant_action_rule_profile                                Manage action rule profiles (rtctrl:AttrP)
[..]

The documentation for a specific module

ansible@Deb-Master:~/base$ /usr/local/bin/ansible-doc  aci_tenant
   ACI_TENANT (/usr/local/lib/python2.7/dist-packages/ansible/modules/network/aci/aci_tenant.py) 
    Manage tenants on Cisco ACI fabrics. 
 This module is maintained by an Ansible Partner
 OPTIONS (= is mandatory):
 certificate_name
     The X.509 certificate name attached to the APIC AAA user used for signature-based authentication.
     If a private_key' filename was provided, this defaults to the private_key' basename, without extension.
     If PEM-formatted content was provided for private_key', this defaults to the username' value.
     (Aliases: cert_name)[Default: (null)]
     type: str
 description
     Description for the tenant.
     (Aliases: descr)[Default: (null)]
     type: str 
 = host
         IP Address or hostname of APIC resolvable by Ansible control host.
         (Aliases: hostname)
         type: str
 output_level
     Influence the output of this ACI module.
      normal' means the standard output, incl. current' dict
      info' adds informational output, incl. previous', proposed' and sent' dicts
     debug' adds debugging output, incl. filter_string', method',response', status' and url' information
     (Choices: debug, info, normal)[Default: normal]
     type: str 
[..]

Convert your code easily with APIC Rest Python Adapter (arya)

Arya is a tool to translate an XML or JSON to Python. Arya will convert your input and use the Cisco sdk COBRA.

Generate the code with arya :

arya -f tenant.xml
 !/usr/bin/env python
 '''
 Autogenerated code using arya
 Original Object Document Input:
 
 
 '''
 raise RuntimeError('Please review the auto generated code before ' +
                     'executing the output. Some placeholders will ' +
                     'need to be changed')
 list of packages that should be imported for this code to work
 import cobra.mit.access
 import cobra.mit.naming
 import cobra.mit.request
 import cobra.mit.session
 import cobra.model.fv
 import cobra.model.vns
 from cobra.internal.codec.xmlcodec import toXMLStr
 log into an APIC and create a directory object
 ls = cobra.mit.session.LoginSession('https://1.1.1.1', 'admin', 'password')
 md = cobra.mit.access.MoDirectory(ls)
 md.login()
 the top level object on which operations will be made
 Confirm the dn below is for your top dn
 topDn = cobra.mit.naming.Dn.fromString('uni/tn-aaaaaaaa-tn')
 topParentDn = topDn.getParent()
 topMo = md.lookupByDn(topParentDn)
 build the request using cobra syntax
 fvTenant = cobra.model.fv.Tenant(topMo, ownerKey='', name='aaaaaaaa-tn', descr='', nameAlias='', ownerTag='')
 vnsSvcCont = cobra.model.vns.SvcCont(fvTenant)
 fvRsTenantMonPol = cobra.model.fv.RsTenantMonPol(fvTenant, tnMonEPGPolName='')
 commit the generated code to APIC
 print toXMLStr(topMo)
 c = cobra.mit.request.ConfigRequest()
 c.addMo(topMo)
 md.commit(c)