Sunday, September 30, 2012

Tcl Expect with Python - part 1

Note. highly recommended the go-to book on the subject: Exploring Expect: A Tcl-based Toolkit for Automating Interactive Programs (Nutshell Handbooks)

I have a love-hate relationship with Expect. I love what it can do for automating interactive sessions with routers, but I hate the unpredictability associated with interactive sessions. It seems that we will make a script, tweak it enough to make it work, just to do it all over again in a few months. It is just a fragile process, whenever there is a new hardware, new terminal server, or even a new OS, we will hit something that require some more fine tuning. We are much better off with known API, autoprovioning via DHCP/TFTP, or other tools for what we use Expect for today.

But until the day when all the network vendor can agree on a given set of API and hooks (if ever), Expect is still a must have in any network engineer's tool bag. It will save you time and keep you sane. 

Note that this is a post for the regular Expect based on Tcl, more on PExpect module in later post.

For those who have not heard of Expect, or want a refresher, here are a few documentation link. 
References:  
http://wiki.tcl.tk/201 (updated link on 12/29/2016)


Here is a useful link for special ASCII Characters, when you want to send those Ctrl-C, Ctrl-X, space, etc:
http://www.bbdsoft.com/ascii.html
[Update 12/29/2016, this link expired (thank you Kirk for pointing it out). Still searching for a good link]

At the heart of the game, Expect is about 1. spawing a child process, 2. look for certain words in the stream via expect, 3. send something over. 

So say I want to telnet to a bunch of Public route-servers and look at how bing.com's IP looks from the route server. I find out that bing.com resolves to 131.253.13.32 from where I am at. 

Non-authoritative answer:
Name: bing.com
Address: 131.253.13.32

From Route Server (http://traceroute.org/#Route%20Servers) I can find a list of public route servers that  I can telnet to. 

1. Using AT&T's route server as a first example, I am going to write a simple script that telnets to the box and displays the bgp information: 

#!/usr/bin/expect
set timout 5
# Spawns a child process that telnets to AT&T public route server
spawn telnet route-server.ip.att.net
# The expect command waits until the child process returns "Username:"
# then sends 'reviews' and a return key
expect "Username:" {send "rviews\r"};
# The process then sits and wait for the route-server> prompt
# Then sends the command for route 131.253.13.32 (one of the ip for bing.com)
expect "route-server>" {send "show ip bgp 131.253.13.32\r"};
# The output is longer than 1 page, so it sendds the ASCII key for space twice
send "\032\032"
# Looks for the same prompt, then types 'exit'
expect "route-server>" {send exit\r};
2. The first script can now be cron'd and give me the information that I wanted. But what if I want to telnet to a bunch of route-server, not just AT&T? Here is a modified version that uses the first argument as the telnet target: 

#!/usr/bin/expect
set routeServ [lindex $argv 0]
set timout 20
spawn telnet $routeServ
expect "Username:" {send "rviews\r"};
expect {
  -re "route.*" {send "show ip bgp 131.253.13.32\r"};
}
send "\032\032"
expect {
  -re "route.*" {send exit\r};
}

Notice that the script also now uses -re to indicate the expected string back will be regular expression. This is because some of the route server prompt are 'route-server>' as in the case of AT&T and Comcast, while some are 'route-view>', as in the case of Oregon Route-View servers. 

Now that is just another script that I can use Python to tie in, reading from a list of route-servers:

$cat route-servers 
route-server.ip.att.net 
route-server.newyork.ny.ibone.comcast.net
route-views.oregon-ix.net
$

Here is the Python script: 

#!/usr/bin/env python
import subprocess
f = open("route-servers", "r")
for i in f.readlines():
subprocess.check_call(["./routeServ_2.exp", i.strip()])


3. Now we are getting somewhere, but there is another problem: the command is embedded into the Expect script itself. So if I want to run more commands, I need to modify the expect script itself. That is not very flexible. I will make one more modification to the Expect script itself so that it reads all the command from another text file: 

$cat commands.txt 
show ip route 131.253.13.32
show ip bgp 131.253.13.32

Here is the new version of the Expect script. Note that 'timeout -1' disables the default timeout of 10 seconds, as some of the route-servers are pretty slow. 

#!/usr/bin/expect
set routeServ [lindex $argv 0]
# disable the timeout value in the next line
# since some of the route-servers are really slow
set timout -1

spawn telnet $routeServ
expect "Username:" {send "rviews\r"};
expect {
  -re "route-.*" {send \r}
}
#process the command file
#this can be more elegant, but this is more clear
set file [open "commands.txt" r]
set file_data [read $file]
set command [split $file_data "\n"]
foreach line $command {
  puts $line
  expect {
    -re "route-.*" {send $line\r}
    continue
  }
  send "\032\032\032"
  sleep 2
}

Now I will tie it in with Python, there is no change to the Python script except that it calls the new Expect script. By using Python, I can leverage the getpass, subprocess, sys, and other standard libraries and not have to deal with Tcl or Expect. 

#!/usr/bin/env python
import subprocess
f = open("route-servers", "r")
for i in f.readlines():
    subprocess.check_call(["./routeServ_3.exp", i.strip()])

4. Here is the output of the result as it shows on the screen (Comcast route server does not allow for 'show ip route x.x.x.x', boo):

$./routeServ_3.py 
spawn telnet route-server.ip.att.net
Trying 12.0.1.28...
Connected to route-server.cbbtier3.att.net.
Escape character is '^]'.

-------------- route-server.ip.att.net ---------------
---------  AT&T IP Services Route Monitor  -----------

The information available through route-server.ip.att.net is offered
by AT&T's Internet engineering organization to the Internet community.

This router maintains eBGP peerings with customer-facing routers
throughout the AT&T IP Services Backbone:

IPv4:
 12.123.21.243  Atlanta   12.123.133.124 Austin    12.123.41.250  Cambridge
 12.123.5.240   Chicago   12.123.17.244  Dallas    12.123.139.124 Detroit
 12.123.37.250  Denver    12.123.134.124 Houston   12.123.29.249  LA
 12.123.1.236   New York  12.123.33.249  Orlando   12.123.137.124 Philly
 12.123.142.124 Phoenix   12.123.145.124 SanDiego  12.123.13.241  SanFran
 12.123.25.245  St.Louis  12.123.45.252  Seattle   12.123.9.241   WashDC

IPv6:
 2001:1890:FF:FFFF:12:122:124:12   Atlanta
 2001:1890:FF:FFFF:12:122:127:66   Chicago
 2001:1890:FF:FFFF:12:122:124:138  Dallas
 2001:1890:FF:FFFF:12:122:120:7    Fort Lauderdale
 2001:1890:FF:FFFF:12:122:125:6    Los Angeles
 2001:1890:FF:FFFF:12:122:125:44   New York
 2001:1890:FF:FFFF:12:122:125:106  Philadelphia
 2001:1890:FF:FFFF:12:122:125:132  Phoenix
 2001:1890:FF:FFFF:12:122:126:232  San Francisco
 2001:1890:FF:FFFF:12:122:125:224  Seattle
 2001:1890:FF:FFFF:12:122:126:9    St. Louis
 2001:1890:FF:FFFF:12:122:126:64   Washington

*** Please Note:
Ping and traceroute delay figures measured here are unreliable, due to the
high CPU load experienced when complicated "show" commands are running.

For questions about this route-server, send email to: jayb@att.com

*** Log in with username "rviews", no password required ***


User Access Verification

Username: Kerberos: No default realm defined for Kerberos!
rviews
route-server>show ip route 131.253.13.32

route-server>show ip bgp 131.253.13.32
show ip route 131.253.13.32
Routing entry for 131.253.12.0/22
  Known via "bgp 65000", distance 20, metric 0
  Tag 7018, type external
  Last update from 12.123.1.236 22:16:35 ago
  Routing Descriptor Blocks:
  * 12.123.1.236, from 12.123.1.236, 22:16:35 ago
      Route metric is 0, traffic share count is 1
      AS Hops 3
      Route tag 7018

route-server>
show ip bgp 131.253.13.32
BGP routing table entry for 131.253.12.0/22, version 36560492
Paths: (18 available, best #2, table Default-IP-Routing-Table)
  Not advertised to any peer
  7018 8075 8075, (received & used)
    12.123.13.241 from 12.123.13.241 (12.123.13.241)
      Origin IGP, localpref 100, valid, external
      Community: 7018:2500 7018:32113
  7018 8075 8075, (received & used)
    12.123.1.236 from 12.123.1.236 (12.123.1.236)
      Origin IGP, localpref 100, valid, external, best
      Community: 7018:2500 7018:38001
  7018 8075 8075, (received & used)
    12.123.142.124 from 12.123.142.124 (12.123.142.124)
      Origin IGP, localpref 100, valid, external
      Community: 7018:2500 7018:33051
  7018 8075 8075, (received & used)
    12.123.133.124 from 12.123.133.124 (12.123.133.124)
      Origin IGP, localpref 100, valid, external
      Community: 7018:2500 7018:36244
  7018 8075 8075, (received & used)
    12.123.17.244 from 12.123.17.244 (12.123.17.244)
      Origin IGP, localpref 100, valid, external
      Community: 7018:2500 7018:36244
  7018 8075 8075, (received & used)
    12.123.21.243 from 12.123.21.243 (12.123.21.243)
      Origin IGP, localpref 100, valid, external
      Community: 7018:2500 7018:36244
  7018 8075 8075, (received & used)
    12.123.137.124 from 12.123.137.124 (12.123.137.124)
      Origin IGP, localpref 100, valid, external
      Community: 7018:2500 7018:38001
  7018 8075 8075, (received & used)
    12.123.41.250 from 12.123.41.250 (12.123.41.250)
      Origin IGP, localpref 100, valid, external
      Community: 7018:2500 7018:38001
  7018 8075 8075, (received & used)
    12.123.139.124 from 12.123.139.124 (12.123.139.124)
          
route-server>spawn telnet route-server.newyork.ny.ibone.comcast.net
Trying 66.208.229.1...
Connected to route-server.newyork.ny.ibone.comcast.net.
Escape character is '^]'.

********************************************************************************
                       Comcast NETO Route Server
        This route server is provided by Comcast National Engineering &
Technical Operations to provide visibility into the Internet routing table
from the perspective of Comcast's network.  This server has BGP peerings to
the following routers and the Internet routing table view from each device:

68.86.80.0 - Ashburn, VA
68.86.80.2 - New York, NY
68.86.80.5 - Chicago, IL
68.86.80.6 - Chicago, IL 2
68.86.80.7 - Denver, CO
68.86.80.10 - San Jose, CA
68.86.80.11 - Los Angeles, CA
68.86.80.12 - Atlanta, GA
68.86.80.13 - Dallas, TX
          

Note: Due to high CPU utilization on this device, ping and traceroute results
may be unreliable.  This route server should not be used to measure network
performance as a result.

Login with username: rviews

Location:   New York City
Network:  Comcast Route Server
********************************************************************************

User Access Verification

Username: Kerberos: No default realm defined for Kerberos!
rviews
route-server.newyork.ny.ibone>show ip route 131.253.13.32

route-server.newyork.ny.ibone>show ip bgp 131.253.13.32
show ip route 131.253.13.32
Command authorization failed.

route-server.newyork.ny.ibone>
show ip bgp 131.253.13.32
BGP routing table entry for 131.253.12.0/22, version 1378467282
Paths: (8 available, best #8, table Default-IP-Routing-Table)
  Advertised to update-groups:
     2         
  8075 8075, (received & used)
    68.86.1.38 (metric 81950) from 68.86.80.11 (68.86.1.11)
      Origin IGP, metric 0, localpref 300, valid, internal
      Community: 7922:38 7922:3020
      Originator: 68.86.1.38, Cluster list: 68.86.1.11, 68.86.1.10
  8075 8075, (received & used)
    68.86.1.75 (metric 74391) from 68.86.80.13 (68.86.1.13)
      Origin IGP, metric 0, localpref 300, valid, internal
      Community: 7922:75 7922:3020
      Originator: 68.86.1.75, Cluster list: 68.86.1.13, 68.86.1.7
  8075 8075, (received & used)
    68.86.1.40 (metric 69750) from 68.86.80.12 (68.86.1.12)
      Origin IGP, metric 0, localpref 300, valid, internal
      Community: 7922:40 7922:3020
      Originator: 68.86.1.40, Cluster list: 68.86.1.12
  8075 8075, (received & used)
    68.86.1.42 (metric 66845) from 68.86.80.0 (68.86.1.0)
      Origin IGP, metric 0, localpref 300, valid, internal
      Community: 7922:42 7922:3020
      Originator: 68.86.1.42, Cluster list: 68.86.1.0
  8075 8075, (received & used)
    68.86.1.38 (metric 81950) from 68.86.80.10 (68.86.1.10)
      Origin IGP, metric 0, localpref 300, valid, internal
      Community: 7922:38 7922:3020
      Originator: 68.86.1.38, Cluster list: 68.86.1.10
  8075 8075, (received & used)
    68.86.1.43 (metric 69675) from 68.86.80.6 (68.86.1.6)
      Origin IGP, metric 0, localpref 300, valid, internal
      Community: 7922:43 7922:3020
      Originator: 68.86.1.43, Cluster list: 68.86.1.6
  8075 8075, (received & used)
    68.86.1.75 (metric 74391) from 68.86.80.7 (68.86.1.7)
      Origin IGP, metric 0, localpref 300, valid, internal
          
route-server.newyork.ny.ibone>spawn telnet route-views.oregon-ix.net
Trying 128.223.51.103...
Connected to route-views.oregon-ix.net.
Escape character is '^]'.

 **********************************************************************

                    Oregon Exchange BGP Route Viewer
          route-views.oregon-ix.net / route-views.routeviews.org

 route views data is archived on http://archive.routeviews.org

 This hardware is part of a grant from Cisco Systems.
 Please contact help@routeviews.org if you have questions or
 comments about this service, its use, or if you might be able to
 contribute your view.

 This router has views of the full routing tables from several ASes.
 The list of ASes is documented under "Current Participants" on
 http://www.routeviews.org/.

                          **************

 route-views.routeviews.org is now using AAA for logins.  Login with
 username "rviews".  See http://routeviews.org/aaa.html

 **********************************************************************


User Access Verification

Username: rviews
route-views>show ip route 131.253.13.32

route-views>show ip bgp 131.253.13.32
show ip route 131.253.13.32
Routing entry for 131.253.12.0/22
  Known via "bgp 6447", distance 20, metric 0
  Tag 8075, type external
  Last update from 207.46.32.34 7w0d ago
  Routing Descriptor Blocks:
  * 207.46.32.34, from 207.46.32.34, 7w0d ago
      Route metric is 0, traffic share count is 1
      AS Hops 1
      Route tag 8075

route-views>
show ip bgp 131.253.13.32
BGP routing table entry for 131.253.12.0/22, version 215869
Paths: (33 available, best #20, table Default-IP-Routing-Table)
  Not advertised to any peer
  3277 3267 8075 8075
    194.85.102.33 from 194.85.102.33 (194.85.4.4)
      Origin IGP, localpref 100, valid, external
      Community: 3277:3267 3277:65100 3277:65320 3277:65326 3277:65330
  3333 3356 8075 8075
    193.0.0.56 from 193.0.0.56 (193.0.0.56)
      Origin IGP, localpref 100, valid, external
  3561 3356 8075 8075
    206.24.210.102 from 206.24.210.102 (206.24.210.102)
      Origin IGP, localpref 100, valid, external
  3356 8075 8075
    4.69.184.193 from 4.69.184.193 (4.69.184.193)
      Origin IGP, metric 0, localpref 100, valid, external
      Community: 209:80 209:888 701:30 703:30 1120:2 1299:2869 1299:5509 1299:5583 2914:470 3356:3 3356:22 3356:100 3356:123 3356:575 3356:2012 3549:200 3549:666 4657:905 5511:1550 6453:80 6762:20008 7018:80 7473:22009 7473:23009 10026:11010 10026:11020 10026:11030 10026:11040 10026:11050 10026:11060 10026:11070 10026:11080 10026:11090 10026:11100 25160:209 25160:1239 65009:7018
  2914 3356 8075 8075
    129.250.0.11 from 129.250.0.11 (129.250.0.12)
      Origin IGP, metric 6, localpref 100, valid, external
      Community: 2914:420 2914:1008 2914:2000 2914:3000 65504:3356
  19214 12989 8075 8075
    208.74.64.40 from 208.74.64.40 (208.74.64.40)
      Origin IGP, localpref 100, valid, external
  101 101 8075 8075
    209.124.176.223 from 209.124.176.223 (209.124.176.223)
      Origin IGP, localpref 100, valid, external
      Community: 101:20400 101:22200
      Extended Community: RT:101:22200
  6939 8075 8075
    216.218.252.164 from 216.218.252.164 (216.218.252.164)
      Origin IGP, localpref 100, valid, external
  1239 3356 8075 8075
    144.228.241.130 from 144.228.241.130 (144.228.241.130)
          
route-views>$





Sunday, September 16, 2012

PyMongo and MongoDB

In the last post I mention that JSon is the way a lot of information over the web is serialized and exchanged. As part of a movement to move away from a pull model (SNMP) to push model (syslog, sFlow), many network vendors are picking up JSon as the way to bubble up information to the network engineers.

So now that you have a JSon object, how do you persist it? You can obviously pickle and shelve it, but that is not recommended for long term storage, plus that does not scale very well. Any SQL flavor will obviously do the job, but to be honest, coming up with a database schema is kind of a pain in the butt. Especially in the development stage. I want some simple database that allows me to persist my object the way I view it and get out of the way. Also would be nice if it is proven scalable so I dont need to change much by the time I am done.

In comes MongoDB. It is pretty awesome that it matches the object model as I write the script, so it doesn't require a mind shift. It is scalable as many web scale companies are using it. More importantly, it just let me store the object, and "gets out of my way", I say that in a positive sense, of course. No offense, but I have enough headache on hand trying to solve the problem at hand without needing to worry about databases. MongoDB also has the added benefit of cross-platform and batteries included, both are pretty Pythonic.

Note that I am barely scratching the surface of the MongoDB capabilities, especially the query portion. But just to share my experience so far. Leave me comments if you have interesting ideas and use cases.

Here is how to get started:

1. Go to the MongoDB site to install the package according to the OS you are using. I have installed them on Win7 and Mac, pretty easy.
MongoDB Site

2. Install PyMongo, on both Win7 and Mac, I just use easy install.
PyMongo

3. (Optional) You can watch the tutorial given by the primary developer of PyMongo during PyCon 2012.
PyCon2012 MongoDB Tutorial Video

4. (Optional) Book: MongoDB and Python
MongoDB and Python: Patterns and processes for the popular document-oriented database

Here are some of the things I have tried:

0. Start MongoDB using the binary with default port on localhost:
$ ./mongod

1. Import the PyMongo and create connection:

>>> import pymongo
>>> from pymongo import Connection
>>> connection = Connection()
>>> connection
Connection('localhost', 27017)

2. Connects to database 'network' (it will just create one if it does not exist already): 
>>> db = connection.network
>>> 
>>> db
Database(Connection('localhost', 27017), u'network')
>>>

3. Connects to 'switches' collection (again, it will just create one if it does not exist already): 
>>> collection = db['switches']

4. Create 1 document and insert: 
>>> post = {"Name": "R1", "ASN": "65001"}
>>> posts = db.posts
>>> posts.insert(post)
ObjectId('5056039ac4c1b468ddbbe27f')
>>> db.collection_names()
[u'system.indexes', u'posts']
>>> 
>>> posts.find_one()
{u'_id': ObjectId('5056039ac4c1b468ddbbe27f'), u'Name': u'R1', u'ASN': u'65001'}
>>> posts.find_one({"ASN": "65001"})
{u'_id': ObjectId('5056039ac4c1b468ddbbe27f'), u'Name': u'R1', u'ASN': u'65001'}
>>>

3. Multiple inserts: 

>>> new_posts = [{"Name": "R2", "ASN": "65002"},
... {"Name": "R3", "ASN": "65003"}]
>>> posts.insert(new_posts)
[ObjectId('5056045ac4c1b468ddbbe280'), ObjectId('5056045ac4c1b468ddbbe281')]
>>> 

4. Query all the documents: 
>>> for post in posts.find():
...     post
...
{u'_id': ObjectId('5056039ac4c1b468ddbbe27f'), u'Name': u'R1', u'ASN': u'65001'}
{u'_id': ObjectId('5056045ac4c1b468ddbbe280'), u'Name': u'R2', u'ASN': u'65002'}
{u'_id': ObjectId('5056045ac4c1b468ddbbe281'), u'Name': u'R3', u'ASN': u'65003'}
>>> 

5. Query for particular match: 
>>> for post in posts.find({"Name": "R2"}):
...     print("Found: ", post)
...
('Found: ', {u'_id': ObjectId('5056045ac4c1b468ddbbe280'), u'Name': u'R2', u'ASN': u'65002'})
>>>

6. Trying out the count method: 
>>> posts.count()
3
>>>
>>> posts.find({"Name": "R1"}).count()
1
>>> 

7. Try to remove entry: 
>>> for post in posts.find():
...     posts
...
Collection(Database(Connection('localhost', 27017), u'network'), u'posts')
Collection(Database(Connection('localhost', 27017), u'network'), u'posts')
Collection(Database(Connection('localhost', 27017), u'network'), u'posts')
>>>
>>>
>>> posts.remove({"Name": "R2"})
>>> for post in posts.find():
...     post
...
{u'_id': ObjectId('5056039ac4c1b468ddbbe27f'), u'Name': u'R1', u'ASN': u'65001'}
{u'_id': ObjectId('5056045ac4c1b468ddbbe281'), u'Name': u'R3', u'ASN': u'65003'}
>>> 

>>> for post in posts.find():
...     post
...
{u'_id': ObjectId('5056039ac4c1b468ddbbe27f'), u'Name': u'R1', u'ASN': u'65001'}
{u'_id': ObjectId('5056045ac4c1b468ddbbe281'), u'Name': u'R3', u'ASN': u'65003'}
>>>
>>>
>>>
>>>

8. Try to update R1 entry: 
>>> posts.update({"Name": "R1"}, {"DataCenter": "SJC"})
>>> for post in posts.find():
...     post
...
{u'DataCenter': u'SJC', u'_id': ObjectId('5056039ac4c1b468ddbbe27f')}
{u'_id': ObjectId('5056045ac4c1b468ddbbe281'), u'Name': u'R3', u'ASN': u'65003'}
>>> 

9. Test that the data persist. Exit out of Python and start again: 
$ python
>>> from pymongo import Connection
>>> connection = Connection()
>>> connection
Connection('localhost', 27017)
>>> db = connection.network
>>> db
Database(Connection('localhost', 27017), u'network')
>>> posts = db.posts
>>> posts
Collection(Database(Connection('localhost', 27017), u'network'), u'posts')
>>> db.collection_names()
[u'system.indexes', u'posts']
>>> for post in posts.find():
...     post
...
{u'_id': ObjectId('5056039ac4c1b468ddbbe27f'), u'Name': u'R1'}
{u'_id': ObjectId('5056045ac4c1b468ddbbe281'), u'Name': u'R3', u'ASN': u'65003'}
>>> 

So there you have it, a quick 5 minute introductory to PyMongo in a network flavor. I will share more experiences if I start to work with PyMongo/MongoDB more. So far it looks to be the one I will use if I ever need a database in my projects. 




[Book Review] Mac OS X Lion: The Missing Manual

Book Review for Mac OS X Lion: The Missing Manual , by David Pogue, ISBM 1449397492

This post will not touch much on Python, I was hoping to pick up enough AppleScript chops to use Python as an automation glue for Mac OS X by reading this book. But it turns out that would not be the case. So please skip this if you feel like this is a distraction from the topic.


I bought the first Mac OS X Missing Manual back in 2002 with my first iBook. Over the years I bought the Leopard and Snow Leopard versions of the missing manuals, so this is my fourth Mac OS X Missing Manual book. Pogue's book is really what I ever needed if I wanted to learn something about the Mac OS X besides a quick search online. It is an excellent source of information, sometimes I feel it is more than what I needed to know but having more information is better than the other way around, I think. One thing I did learn is that I prefer digital copy of technical books to be able to work on the topic covered, this book in particular was most useful when I can try out the examples as I read them. 

The overall theme really is the iPod-ification and social integration of the Mac OS as many article have pointed out, and Pogue's book points them out whenever possible. 

After more than 10 years of using Mac and working with *Nix system on a daily basis, there are still new items that I picked up from this book, I am sure some of them have been there and i just never knew about them. Here are some examples:

- New share menu: part of the iPad-ification of the new Mac OS. 
- Spotlight tips (quick calculator and dictionary). 
- Use keywords to search with limit (email, folder, app),
- Organize LaunchPad with folders and tricks.  
- Things I don't use enough to go deep into: iCloud, Time Machine, Dashboard, 
- Instant Accented Characters (i.e. holding down e will give you options for accented e in other languages).  
- Dictation: The new speak to type feature (somebody say iPad?) :)
- Chinese appeal: New font, new dictionary, Safari Baidu search, write Chinese via trackpad. 
- Share Bottom, wow, they are everywhere. 

Bottom line, if you are new to the OS or new to Mac, I would definitely recommend this book. 


Saturday, September 15, 2012

Python with JSon

JSon (JavaScript Object Notation) seems to be the new kid on the block when it comes to network information exchange over the network. JSon obviously is not new, it has been around for a long time, and is a pretty dominate API used for many sites, including Twitter. Glad to see various vendors starting to pick up this method.

In my personal opinion, JSon offers the same type of hierarchy structure as XML but it is more lightweight and therefore a better fit to bubble up messages from routers and switches that is more complex to express than syslog.

In a nutshell, a json object is a Python dictionary that have other nested objects:
Python 2.7 JSon Doc

Say we want to encode router1 into a json object, router1 has the following information:

Name: R1
ASN: 65001
Address:
  1 Infinite Loop
  Cupertino, CA
Interfaces:
  Eth1/1: To R2, 192.168.1.1
  Eth1/2: To R3, 192.168.1.3
BGP Neighbors:
  192.168.1.2, R2, 65002
  192.168.1.3, R3, 65003

We can simply create a dictionary:

>>> r1 = {'Name': 'r1', 'ASN': '65001', 'Address': {'Street': '1 Infinite Loop', 'City': 'Cupertino', 'State': 'CA'}, 'Interfaces': {'Eth1/1': {'Description': 'To R2', 'IP': '192.168.1.1'}, 'Eth1/2': {'Description': 'To R3', 'IP': '192.168.1.3'}}, 'BGP Neighbors': {'192.168.1.2':['R2', '65002'], '192.168.1.4': ['R3', '65003']}}
>>>

>>> r1['Name']
'r1'
>>> r1['BGP Neighbors']
{'192.168.1.4': ['R3', '65003'], '192.168.1.2': ['R2', '65002']}
>>>
>>>
>>>
>>> r1['Address']
{'City': 'Cupertino', 'State': 'CA', 'Street': '1 Infinite Loop'}
>>>
>>>
>>>
>>> r1['Interfaces']
{'Eth1/1': {'IP': '192.168.1.1', 'Description': 'To R2'}, 'Eth1/2': {'IP': '192.168.1.3', 'Description': 'To R3'}}
>>>

Then make it into a JSon object ready to be transferred over:

>>> import json
>>> R1_Json = json.dumps(r1)
>>>
>>>
>>> R1_Json
'{"BGP Neighbors": {"192.168.1.4": ["R3", "65003"], "192.168.1.2": ["R2", "65002"]}, "Interfaces": {"Eth1/1": {"IP": "192.168.1.1", "Description": "To R2"}, "Eth1/2": {"IP": "192.168.1.3", "Description": "To R3"}}, "Address": {"City": "Cupertino", "State": "CA", "Street": "1 Infinite Loop"}, "Name": "r1", "ASN": "65001"}'
>>>

On the other hand, if you received a JSon object, you can load it:

>>> import json
>>> New_R1 = json.loads(R1_Json)

note that the keys and values are unicode (indicated by the 'u' in front):

>>> New_R1
{u'ASN': u'65001', u'Interfaces': {u'Eth1/1': {u'IP': u'192.168.1.1', u'Description': u'To R2'}, u'Eth1/2': {u'IP': u'192.168.1.3', u'Description': u'To R3'}}, u'BGP Neighbors': {u'192.168.1.4': [u'R3', u'65003'], u'192.168.1.2': [u'R2', u'65002']}, u'Name': u'r1', u'Address': {u'City': u'Cupertino', u'State': u'CA', u'Street': u'1 Infinite Loop'}}
>>>

>>> New_R1['Name']
u'r1'
>>> New_R1['Address']
{u'City': u'Cupertino', u'State': u'CA', u'Street': u'1 Infinite Loop'}
>>>
>>> New_R1['Interfaces']
{u'Eth1/1': {u'IP': u'192.168.1.1', u'Description': u'To R2'}, u'Eth1/2': {u'IP': u'192.168.1.3', u'Description': u'To R3'}}
>>> New_R1['BGP Neighbors']
{u'192.168.1.4': [u'R3', u'65003'], u'192.168.1.2': [u'R2', u'65002']}
>>>

With this tool in hand, working with a NoSQL document-based database like MongoDB is pretty straight forward. Stay tuned for more on MongoDB (hint: PyMongo is really cool!).




Coding is hard

I dont care what other people say, learn to code is hard. It might be easy to some, but for most of us it is hard, especially if you try to learn it after you have already set in your ways. Family, work, and living a life gets in the way of learning, and it is just so easy to give up. For me, I have given up multiple times, given myself many excuses, but at the end I have decided to stuck it out. Now I roughly play with Python about 10 hours a week.

As mention in my reply to the following blog, the key for me is to remind myself to have fun and make it relevant in daily work. The first part keeps me from banging my head into the wall and second part keeps me motivated with something to show for.
http://millionchimpanzees.blogspot.com/2012/09/my-19-days-of-python-day-3-and-i-hit.html

Cheers to all of us network engineers who have chosen this road less traveled (so far). :)






Saturday, September 1, 2012

[Book Review] Regular Expression Cookbook, 2nd Edition

Book review for Regular Expressions Cookbook , by Jan Goyyaerts and Steven Levithan, ISBN 1449319432.

We work with text file a lot in scripts and O'Reilly cookbooks are awesome. They are quick references when you need something fast. This book is full of good examples when you need to, say, validate an IPv4 address in a bunch of text, or find a particular BGP neighbor / IP combination.

Here is a quick example of what I drew up from Recipe 3.5 to search for IPs in a list of IPs:

##### Script #####

# Modified from "Regular Expression Cookbook 2nd Edition", Recipe 3.5

import re

pattern = raw_input("Type in Pattern to Search: ")
f = raw_input("Type the file to search: ")
subject = open(f, 'r').readlines()

reobj = re.compile(pattern)

print('... Start ...')
for line in subject:
    if reobj.search(line):
        print("Matched: " + pattern + " in line " + line.strip())

print('... Done ...')
#####

I use the following script to generate a file that has all the IPs in 192.168.1.0/24 and 10.0.0.0/24 called TestIP.txt:

#####
#!/usr/bin/env python

f = open('TestIP.txt', 'w')

# prints all the IPs in 192.168.1.0/24
for i in range(256):
    f.write('192.168.1.'+str(i)+'\n')

# prints all the IPs in 10.0.0.0/24
for i in range(256):
    f.write('10.0.0.'+str(i)+'\n')

f.close()
#####

##### TestIP.txt #####
.....
192.168.1.0
192.168.1.1
192.168.1.2
192.168.1.3
192.168.1.4
192.168.1.5
192.168.1.6
<blah>
192.168.1.252
192.168.1.253
192.168.1.254
192.168.1.255
10.0.0.0
10.0.0.1
10.0.0.2
10.0.0.3
10.0.0.4
10.0.0.5
<blah>
10.0.0.252
10.0.0.253
10.0.0.254
10.0.0.255

##### Here is one usage ###
>>> 
Type in Pattern to Search: 10.0.0.5
Type the file to search: TestIP.txt
... Start ...
Matched: 10.0.0.5 in line 10.0.0.5
Matched: 10.0.0.5 in line 10.0.0.50
Matched: 10.0.0.5 in line 10.0.0.51
Matched: 10.0.0.5 in line 10.0.0.52
Matched: 10.0.0.5 in line 10.0.0.53
Matched: 10.0.0.5 in line 10.0.0.54
Matched: 10.0.0.5 in line 10.0.0.55
Matched: 10.0.0.5 in line 10.0.0.56
Matched: 10.0.0.5 in line 10.0.0.57
Matched: 10.0.0.5 in line 10.0.0.58
Matched: 10.0.0.5 in line 10.0.0.59
... Done ...
>>> 

##### You can also use meta characters to narrow down your search ($ means end) 
>>> 
Type in Pattern to Search: 10.0.0.5$
Type the file to search: TestIP.txt
Matched: 10.0.0.5$ in line 10.0.0.5
###
Done
>>>


### Some examples that allows for IPv4 and IPv6 addrsses

8.16 Checking IPv4 address, disallowing leading 0:
^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3} (?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$
8.17 Checking IPv6 address within text8.16 :
(?<![:.\w])(?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4}(?![:.\w])



Here is the review as it appears on Amazon:


O'Reilly cookbooks are awesome. But just like I don't read the recipes cover-to-cover in regular cookbooks, I don't read all the recipes in the O'Reilly cookbooks either. Also just like regular cookbooks, the day before Thanksgiving is not a good time to open the cookbook for the first time, I at least glance thru all the recipes to know what is there, pick out a few that I can use right away, and dog ear the ones I think I will come back to. So here are the criteria that I review this book with: 

1. Easy Navigation: Yep, this book is easy to navigate. If I need to do, say form validation, I know I should start at Chapter 4 "Validation and Formatting". 
2. Clear and precise explanation: Yes, I think the explanation are short and precise to the topic of discussion. 
3. Pointer for more information: This is hard to do, but the book has a section on "See Also" for correlation between recipes and a general pointer toward 'Master Regular Expression' in the introduction chapter. 
4. Easy Reading: Hum.. here is more of a wish list of mine, I wish the book is broken down into different books by language. The book covers these languages, VB.NET, C#, Java, JavaScript, XRegExp, PHP, Perl, Python, and Ruby. I typically skip down to Python and occasionally stop at C# and PHP. The book is over 600 pages and listed at $49.99. I would have been happy to pay 1/5 of the price to get one that just focus on Python, and another 1/5 of the price to get one on PHP. 

All in all, it is a good value and a keeper on the bookshelf. But I really think it should be broken down into language-specific cookbook as most reader probably use only one or two languages on a daily basis. With today's print-on-demand, e-book format, I think it would be very minimal work for the author and a whole lot of less skipping for the readers. Just my 2 cents.