Using Python and Netmiko to Automate SAN Zoning

UPDATE github repo for this (H/T Scott Lowe)

One of the customers I’m currently supporting is performing a migration of their NetApp storage from classic 7-mode to Clustered Data OnTap. It’s a fiber channel environment so lots of FC zoning changes are required. To minimize mistakes and ensure consistency, some form of automation is needed. The Cisco MDS fiber channel switches run a version of NX-OS but it’s relatively old and doesn’t include an accessible API-based interface.

Enter Kirk Byers' excellent NetMiko python library. The initial task was to get zones available to present the new Clustered Data OnTap storage to various hosts. Since the customer already has zoning to the 7mode environment, we can get the host WWPNs from existing fcaliases on the switches and create our new zones using that information. With netmiko, I can leverage ssh to grab the information needed in a programmatic fashion and I can push the new zones directly to the switches.

The first trick is generating zoning commands using existing fcalias entries. We’re relying on straightforward pattern matching of hostnames to fcalias names on the switch to do this. Here’s the help output from the script genzonesfromexistingfcaliases.py

usage: genzonesfromexistingfcaliases.py [-h] --hostname HOSTNAME
                                        --hosts_filename HOSTS_FILENAME --vsan
                                        VSAN --zoneset ZONESET
                                        [--fcalias_filename FCALIAS_FILENAME]
                                        [--target_fcalias TARGET_FCALIAS]
                                        [--username USERNAME]
                                        [--get_from_switch]
                                        [--password PASSWORD] [--use_keys]
                                        [--backout] [--key_file KEY_FILE]

Generate zoning commands from input file listing of short hostnames one per
line. Will match against switch fcalias entries by hostname pattern. print to
STDOUT. redirect with > filename.txt

optional arguments:
  -h, --help            show this help message and exit
  --hostname HOSTNAME   MDS switch fqdn or IP.
  --hosts_filename HOSTS_FILENAME
                        list of hosts to match against. one per line
  --vsan VSAN           VSAN for fcaliases/zones
  --zoneset ZONESET     zoneset name
  --fcalias_filename FCALIAS_FILENAME
                        generated fcaliases output from 'ssh
                        username@switchname show fcalias >
                        switch_fcaliases.txt'
  --target_fcalias TARGET_FCALIAS
                        optional fcalias name of cDOT cluster on switch.
                        default ='NAC1'
  --username USERNAME   optional username to ssh into mds switch. Alternate:
                        set environment variable MDS_USERNAME. If neither
                        exists, defaults to current OS username
  --get_from_switch     get fcaliases directly from switch instead of file.
                        NOTE: not yet implemented
  --password PASSWORD   optional password to ssh into mds switch. Alternate:
                        set environment variable MDS_PASSWORD. If unset
                        use_keys defaults to True.
  --use_keys            use ssh keys to log into switch
  --backout             generate backout commands
  --key_file KEY_FILE   filename for ssh key file

There are several inputs required here. You need a switch hostname to login to, a file of hostnames to pattern match against, a VSAN identifier, and a zoneset identifier as well. Those of course you get from the switch as well. In the current version I supply the output of show fcalias from the switch in a text file. I have a todo item to simply grab this directly from the switch during the run of the script. Also, it wouldn’t be too hard to use devalias entries in lieu of fcaliases.

The output is a simple text file of zoning commands. If you have an ssh key setup on the switch already, you can log straight in without providing a password parameter or (better) environment variable. Here’s an example of running it:

sharney@ubuntu-san-automation:~/source$ python ./genzonesfromexistingfcaliases.py \
--hostname mds1 --hosts_file my_hosts.txt --fcalias_filename mds1_sh_fcalias.txt \
--vsan 101 --zoneset ZS_MDS1 > mds1_my_hosts_zones.txt

which produced a file that looks like this:

zone name myhostt001_01_NAC1 vsan 101
   member fcalias NAC1
   member fcalias myhostt001_01


zone name myhostt002_01_NAC1 vsan 101
   member fcalias NAC1
   member fcalias myhostt002_01


zoneset name ZS_MDS1 vsan 101
   member myhostt001_01_NAC1
   member myhostt002_01_NAC1

the NAC1 fcalias is a default target fcalias as noted in the help output. This a text file you could just cut and paste directly into the switch and then activate the zoneset. You’ll also note that we can create a backout file which removes the configuration entries.

While cut and pasting is fine, where’s the fun in that. dozones.py can actually login to the switch and execute. Here’s the help output

./dozones.py --help
usage: dozones.py [-h] --hostname HOSTNAME --filename FILENAME
                  [--username USERNAME]
                  [--password PASSWORD] [--use_keys] [--dry_run]
                  [--key_file KEY_FILE] [--activate_zoneset]

push zoning configuration to MDS switch from generated command file. Zoneset
name and VSAN are derived form the input file.

optional arguments:
  -h, --help            show this help message and exit
  --hostname HOSTNAME   MDS switch fqdn or IP.
  --filename FILENAME   Generated file with zoning commands to push to the mds
                        switch.
  --username USERNAME   optional username to ssh into mds switch. Alternate:
                        set environment variable MDS_USERNAME. If neither
                        exists, defaults to current OS username
  --password PASSWORD   optional password to ssh into mds switch. Alternate:
                        set environment variable MDS_PASSWORD. If unset
                        use_keys defaults to True.
  --use_keys            use ssh keys to log into switch
  --dry_run             don't do anything. just print some debug output
  --key_file KEY_FILE   filename for ssh key file
  --activate_zoneset    add the 'zoneset activate' command to activate the updated zoneset

The --activate parameter is provided as perhaps an additional safety valve. Once you activate the zoneset you’re making the change ‘for real’ so to speak and an error here would be significant. You can add zones and even add zones to a zoneset without necessarily activating that zoneset. You could then do a final check of the active zoneset and your updated zoneset prior to manually activating if desired.

Here’s a sample --dry_run which does login to switch but doesn’t execute the commands.

sharney@ubuntu-san-automation:~/source$ ./dozones.py --hostname 10.10.1.1  --filename mds1_my_hosts_zones.txt --dry_run --activate
DRY RUN: prompt = MDS1#
DRY RUN: command list = ['zone name myhostt001_01_NAC1 vsan 101', 'member fcalias NAC1', 'member fcalias myhostt001_01', 'zone name myhostt002_01_NAC1 vsan 101', 'member fcalias NAC1', 'member fcalias myhostt002_01', 'zoneset name ZS_MDS1 vsan 101', 'member myhostt001_01_NAC1', 'member myhostt002_01_NAC1']
['zoneset activate name ZS_MDS1 vsan 101']
DRY RUN: activate command = ['zoneset activate name ZS_MDS1 vsan 101']

when you run it without --dry_run it will show you the commands executed on the switch. eg:

sharney@ubuntu-san-automation:~/source$ ./dozones.py --hostname 10.10.1.1  --filename mds1_my_hosts_zones.txt -activate
config term
Enter configuration commands, one per line.  End with CNTL/Z.
MDS1(config-zone)# zone name myhostt001_01_NAC1 vsan 101
MDS1(config-zone)# member fcalias NAC1
MDS1(config-zone)# member fcalias myhostt001_01
MDS1(config-zone)# zone name myhostt002_01_NAC1 vsan 101
MDS1(config-zone)# member fcalias NAC1
MDS1(config-zone)# member fcalias myhostt002_01
MDS1(config-zone)# zoneset name ZS_MDS1 vsan 201
MDS1(config-zoneset)# member myhostt001_01_NAC1
MDS1(config-zoneset)# member myhostt002_01_NAC1
MDS1(config-zoneset)# end
MDS1# 
['zoneset activate name ZS_MDS1 vsan 201']
config term
Enter configuration commands, one per line.  End with CNTL/Z.
MDS1(config)# zoneset activate name ZS_MDS1 vsan 101
WARNING: New Zoneset ZS_MDS1 is significantly bigger than current active Zoneset ZS_MDS1. Do you want to continue? (y/n) [n] y
MDS1# 

One interesting wrinkle came up in testing that pushing a large amount of zoning changes to an active zoneset results in a confirmation prompt on the switch. With an assist from Kirk Byers (netmiko’s author) I was able to work through this and catch and respond to the prompt which preserves the automation. Also note that dozones.py does copy the updated switch running configuration to the startup configuration.

This has been used several times at the customer site now. This is a large enough customer that we have a switch in a lab that we can test against. I don’t have a virtual MDS switch I can test and as near as I can tell if that’s available it’s at a substantial subscription cost. It’s better to be able to test against a real switch in any case and we have one.

This is just a first step, of course. Some plans going forward

  • add the automated grab of fcalias entries
  • reduce the required manual inputs for the zoning commands generation script. There’s too many opportunites for error.
  • there are some assumptions here that one VSAN is used, that pattern matching against legacy existing zones will work
  • ultimately create scripts to generate new zoning workflow
  • ‘ansiblize’ this to create provisioning workflows. I’ve done some initial work on an ansible module for NetApp cDOT
  • Matt Oswalt’s work on this was a key jumping off point. with UCS and CDOT having virtualized interfaces (WWPNs) that we can grab directly, it’s not too hard to see that one can bulid a playbook or series of playbooks to provision these items.

 Share!

 
comments powered by Disqus