Using Ansible to configure AP’s on a Cisco 9800 Controller using a Google Sheet

Using Ansible to configure AP’s on a Cisco 9800 Controller using a Google Sheet




I work for a university with nearly 10000 AP’s and growing. There is a steady amount of new construction in addition to replacing older technology on a ~ 5 year cycle. I recently discovered Ansible and have been using it to upgrade ios on switches and build some utilities for provisioning ports. Spending a lot of time configuring AP’s I thought I would try to build some efficiencies using Ansible. I started building some scripts to apply tags, but eventually I got ambitious and thought maybe I could apply all the configuration options from a Google Sheet without even accessing the controller gui. I am going to detail and document the process.


BTW, thanks to all those who post examples, youtube videos, and the like. It has been very helpful and motivated me to share this process.


This Ansible script will read information from a Google sheet and make the following changes:

  • Apply Names
  • Apply Locations
  • Change to static addresses
  • Apply Tags
  • Write Tags
  • Enter Primary and Secondary Controllers
  • Write the Config



Start with a Google Sheet


Prior to using Ansible I always started with a predictive design that details the names and locations of the Access Points. From there I would scan the mac addresses into a google sheet column. enter the AP names into a column, enter the IP addresses (I have them pre-configured for DNS/DHCP with the mac address of each AP). Then I add other details that the controller will need including:


  • Mask

  • Gateway

  • The mac address parsed for controller (1bad.f00d.cafe  format)

  • The default name which is “AP” followed by the mac address separated with dashes

  • Primary & Secondary controller and associated IP’s

  • Tags (Policy, RF, Site) 


Most of these entries are either derived from row 1, or derived from the mac address, not entered manually. I also added a column called “skip” which allows you to skip configuring AP’s. The use case is configuring lets say 100 Access Points for a new deployment and you are doing a box of 10 at a time. Putting a “x” on all the AP you want to skip allows the Ansible script to run much more efficiently by not trying to connect to AP’s that are not connected to the controller.


Here is an example of the google sheet that I use:


  https://docs.google.com/spreadsheets/d/1C1yModG1MwwDSvnZHdA8aCAoX1FkgmCdSQiAy1Fzymk/edit?usp=sharing


Connect AP's to a switch


Connect the AP's to a network switch (either a staging switch or production location...your choice) and make sure they attach to a controller. In my case I have two a primary and secondary controller and the script will put them on the correct controller as defined in the spreadsheet regardless of which one they use for the initial connection. Once connected it should look like this on the controller:




Ansible


I am assuming that you know some basics about ansible already. There are two main files that you need to be aware of: the inventory file which is a text file with a list of the devices that you will connect to in your automation playbook. The second file is the playbook which is the script that executes and provide the automation.


My inventory file contains all the Cisco 9800 Controllers in my environment.

ctrl9800.txt

--------------

[cat9800]

172.26.225.200 ansible_connection=network_cli ansible_network=ios ansible_user=adminaccount  ansible_port=22 ansible_host=172.26.225.200

172.26.225.201 ansible_connection=network_cli ansible_network=ios ansible_user=adminaccount  ansible_port=22 ansible_host=172.26.225.201


[all:vars]

ansible_connection=network_cli

ansible_network_os=ios

ansible_become=yes

ansible_command_timeout=1800

boot_file_loc=flash



Ansible Playbook

playbook-ctrlFromGsheet.yaml

--------------------------------------------

---


- name: Configure using Google Sheet

  hosts: cat9800

  vars_prompt:


  - name: sheetid

    prompt: " What is the Google Sheet ID ? "

    private: no

    when: sheetid == ""


  vars:

    apmacs: []


    maclist: []

   

  tasks:

   - ios_command:

       commands:

        - show version | i uptime

     register: show

   - debug: var=show.stdout_lines

# the above task is not necessary just used to make sure I was connecting to the # controller and thought I would display the uptime

   - name: Download JSON content play

     uri:

       url: http://gsx2json.com/api?id={{sheetid}}&columns=false


       return_content: yes

     register: jsoncontent


# Connects to the Google sheet based on Sheet ID. Remember to publish the

# sheet!


   - name: Define List

     debug: msg= "{{ jsoncontent.json | json_query(jmesquery) }}"

     vars:

             jmesquery: "[*].{number: number, name: name, fqdn: fqdn, mac: mac, ip: ip, host: host, subnet: subnet, mask: mask, gateway: gateway, skip: skip, parsedfordnslookup: parsedfordnslookup, parsedforcontroller: parsedforcontroller, defaultname: defaultname, primary: primary, primaryip: primaryip, secondary: secondary, secondaryip: secondaryip, ptag: ptag, rtag: rtag, stag: stag}"


# Associates the information in the sheet with the column headers

# Column headers eliminates spaces and return in lowercase   


   - name: Apply names based on Mac

     ignore_errors: True

     ios_command:

       commands:

         - ap name {{item.defaultname}} name {{item.name}}

     loop: "{{jsoncontent.json.rows}}"

     when:

       - item.skip == 0


# Changes default name to AP name


   - name: Pause to let AP reconnect to controller

     pause:

       seconds: 25


# Pause to allow time to reconnect to controller after name change


   - name: Apply Location based on Name

     ignore_errors: True

     ios_command:

       commands:

         - ap name {{item.name}} location {{item.name}}

     loop: "{{jsoncontent.json.rows}}"

     when:

       - item.skip == 0


# Changes from default location to actual based on name

# You may prefer a to add a column in the spreadsheet to have a name

# different from the AP name


   - name: Apply Static IP addresses

     ignore_errors: True

     ansible.netcommon.cli_command:

       command: "ap name {{item.name}} static-ip ip-address {{item.ip}} netmask {{item.mask}} gateway {{item.gateway}}"

       prompt: 

         - '\[confirm\]' 

       answer: 

         - "\r" 

     loop: "{{jsoncontent.json.rows}}"

     when: 

       - item.skip == 0


# Change from dhcp to static addresses


   - name: Pause to let AP reconnect to controller

     pause:

       seconds: 20



   - name: Apply Policy RF Site Tags

     ignore_errors: True

     ios_config:

       commands:

         - policy-tag {{item.ptag}}

         - rf-tag {{item.rtag}}

         - site-tag {{item.stag}}

       parents: "ap {{ item.parsedforcontroller }}"

     loop: "{{jsoncontent.json.rows }}"

     when:

       - item.skip == 0


# Apply Tags


   - name: Pause to let AP Reconnect

     pause:

       minutes: 1


   - name: write tags

     ios_command:

       commands:

          - "ap name {{item.name}} write tag-config"

     loop: "{{jsoncontent.json.rows }}"

     when:

       - item.skip == 0


# Write Tags


   - name: set primary and backup controllers

     ignore_errors: True

     ios_command:

        commands:

          - ap name {{item.name}} controller secondary {{item.secondary}} {{item.secondaryip}}

          - ap name {{item.name}} controller primary {{item.primary}} {{item.primaryip}}

     loop: "{{jsoncontent.json.rows}}" 

     when:

       - item.skip == 0


# Set Primary and Secondary Controllers. Do secondary first.


   - name: Write Config

     ios_command:

        commands:

          - wr


   - name:

     debug:

       msg: 

        - "********************************************************"

        - " Note: AP's will take a few minutes to move controllers"

        - "********************************************************"

        - " If all google sheet AP's have been configured go to "

        - " File --> Publish --> Unpublish "

        - "********************************************************"



Executing the Ansible Playbook:

Prior to executing the script make sure that you publish the sheet (temporarily) using File Publish. 

Also make sure you copy the google sheet id which is located as follows:

https://docs.google.com/spreadsheets/d/1C1yModG1MwwMWtfnZHdA8aCAoX1FkgmCdSQiAy1Fzymk/edit#gid=0

All characters left of /edit up to the /

from the mac/linux prompt

#ansible-playbook -i ctrl9800.txt playbook-ctrlFromGsheet.yaml -u admin -k

Substitute the name you use to connect to the controller after the -u (instead of admin)

You will be prompted for a password for controller access, the prompted for the google sheet id.  The script will take a few minutes to run but make all the necessary changes to the controller for all AP's listed in the sheet (without an x in the skip column).

Comments/Questions invited.
Tom Klimek

Comments

  1. Hi, I tried to use this playbook but it's not working for me. Here is the error I get:

    ERROR! Invalid vars_prompt data structure, found unsupported key 'when'

    The error appears to be in '/Users/tluna/Downloads/playbook-ctrlFromGsheet.yaml': line 5, column 3, but may
    be elsewhere in the file depending on the exact syntax problem.

    The offending line appears to be:


    - name: sheetid
    ^ here

    ReplyDelete

Post a Comment