labs ruby

Using Ruby Expect Library to Reboot Ruckus Wireless Access Points via ssh

Abstract

We require a nightly rebooting of our Ruckus Wireless WiFi access points. The Ruckus controllers (ZoneDirector™ and FlexMaster™) do not offer that capability [automated reboots] via their web interface. This blog posts describes a method of using the ruby expect library, ssh, cron, Ruckus’s CLI, and a ruby script to reboot the APs.

Create A Less-privileged User

We need to create a less-privileged (i.e. not a “Super Admin” user). You can probably guess where this is heading: we’re going to need to keep a plaintext copy of this user’s password on disk. Sure, we’re going to do our best job to lock down the file so no one can read it, but we need to plan for the worst: we need to minimize the amount of damage that can be done in the event the account is compromised.

  • log into the web interface of the ZoneDirector (e.g. https://zone-director.pivotallabs.com)
  • Configure → Roles → Create new
    Name: ruckus_reboot
    Description: For automated rebooting of the Ruckus APs
    Administration: checked: Allow ZoneDirector Administration
    selected: Operator Admin (Change settings affecting single AP’s only)
  • Click OK
  • Configure → Users → Create new
  • User Name: ruckus_reboot
    Full Name: Reboot APs
    Password: Iluvmuhdawg! (use a different password; this one is an example)
    Confirm Password: Iluvmuhdawg!
    Role: ruckus_reboot
  • Click OK

Put ruckus_reboot’s password in a safe place

We copy the password into a file that can only be read by the root user. Remember to substitute the password you originally created for the ruckus_reboot user:

echo 'Iluvmuhdawg!' | sudo tee ~root/.ruckus_reboot_passwd.txt
sudo chmod 400 ~root/.ruckus_reboot_passwd.txt

The Manual Procedure

We use the newly-created user to ssh into the ZoneDirector and reboot the APs. First, we log into the ZoneDirector to find out the MAC address of the AP we want to reboot:

$ ssh zone-director.pivotallabs.com

Please login: ruckus_reboot
Password:
Welcome to the Ruckus Wireless ZoneDirector 1100 Command Line Interface
ruckus> enable
ruckus# show ap all
AP:
  ID:
    1:
      MAC Address= 50:a7:33:22:99:b0
      Model= zf7962
...

Secondly, we use the MAC address of the WiFi AP (i.e. “50:a7:33:22:99:b0”) to tell the ZoneDirector to reboot that particular AP:

ruckus# debug
ruckus(debug)# restart-ap 50:a7:33:22:99:b0
The command was executed successfully.
ruckus(debug)# quit
No changes have been saved.
ruckus# exit
Exit Ruckus CLI.

Connection to ruckus-mgt closed.

The Automated Procedure (the Code)

We write a ruby script to automate the manual procedure. There are two aspects: first, we compile a list of the MAC addresses of the WiFi APs we want to reboot; secondly, we reboot the APs. The first is accomplished by using the show ap all command and parsing it for the MAC addresses, and the second is accomplished by looping through the list of MAC addresses and sending the restart-ap command. Copy the following script into a file named ap-reboot.rb:

#!/usr/bin/env ruby
#
#  ap-reboot.rb ruckus-zone-director ruckus-admin-account
#
#  EXAMPLE:  ap-reboot.rb zone-director.pivotallabs.com ruckus_reboot < ~root/.ruckus_reboot_passwd.txt
#
#  This script remotely logs into a Ruckus Zone Director and reboots the APs
#
require 'pty'
require 'expect'

raise "#{__FILE__.gsub(/.*//, '')} ruckus-zone-director ruckus-admin-account" if ARGV.length != 2
password = STDIN.gets.chop
$expect_verbose=true

aps = []

def harvest_mac_addrs(buffers)
  aps = []
  buffers.each do |buffer|
    buffer.lines do |line|
      next if !line.match(/MAC Address/)
      # chop chop for 'r' 'n'
      mac_addr = line.chop.chop.gsub(/.*MAC Address= /, '')
      aps << mac_addr
    end
  end
  aps
end

PTY.spawn("ssh", ARGV[0]) do |ssh_out, ssh_in, pid|
  ssh_out.expect(/ease login: /) { |r| ssh_in.printf("#n") }
  ssh_out.expect(/assword: /) { |r| ssh_in.printf("#{password}n") }
  ssh_out.expect(/uckus> /) { |r| ssh_in.printf("enablen") }
  ssh_out.expect(/uckus# /) { |r| ssh_in.printf("show ap alln") }
  ssh_out.expect(/uckus# /) do |r|
    aps = harvest_mac_addrs(r)
    ssh_in.printf("debugn")
  end
  aps.each do |ap|
    ssh_out.expect(/uckus(debug)# /) do |r|
      ssh_in.printf("restart-ap #{ap}n")
      sleep(120)
    end
  end
  ssh_out.expect(/uckus(debug)# /) { |r| ssh_in.printf("quitn") }
  ssh_out.expect(/uckus# /) { |r| ssh_in.printf("exitn") }
end

Copy the script into place

$ sudo chmod 755 ap-reboot.rb
$ sudo cp -i ap-reboot.rb /usr/local/sbin/

Test the script

  • Log into the ZoneDirector’s web interface (e.g. https://zone-director.pivotallabs.com).
  • Monitor → Access Points

In a terminal, type the following:

sudo ssh zone-director.pivotallabs.com

Reply “yes” if you’re asked if you still want to connect even though the authenticity “can’t be established”. This step is merely to populate the ssh key; you don’t need to log in. Hit Ctrl-C if you’re presented with the “Please login:” prompt.

sudo cat ~root/.ruckus_reboot_passwd.txt | sudo /usr/local/sbin/ap-reboot.rb zone-director.pivotallabs.com ruckus_reboot

The length of time the script takes to finish executing is approximately 2.5 minutes/AP. It requires ~30 seconds per AP for setup, and it waits 120 seconds between each AP reboot.

From your browser, you can watch the APs rebooting (i.e. An AP’s status will change to “Disconnected” when it’s rebooting).

Add the script to the crontab

We decide to reboot the APs beginning at 54 minutes past midnight. We add the line to /etc/crontab on an Apple OS X 10.6 machine. Note that the existence of the /etc/crontab file is distribution-dependent: The file does not exist on later versions of OS X but does exist on FreeBSD 9.1 and Ubuntu 13.04.

$ sudo vim /etc/crontab
    54 0 * * *      root    /usr/local/sbin/ap-reboot.rb zone-director.pivotallabs.com ruckus_reboot < ~root/.ruckus_reboot_passwd.txt > /dev/null 2>&1

Acknowledgements

What to expect from the Ruby expect library? is an excellent primer.