Using OpenDaylight’s REST API from Go

Its been a while since I’ve had a chance to play with OpenDaylight. Since I’ve been on a Go kick lately I decided to try and interact with ODL with Go. Using Go’s built-in JSON decoder, its pretty easy to grab information from OpenDaylight. Here’s a quick example of accessing the topology API using Go.

First I defined the main package since I’ll just be running this single script, and import the necessary libraries in lines 1-9.

Next I define a bunch of structs that match the JSON API in lines 11-67

Now I can put it to work, lets step through the main function.

I start with building the URL to grab topology information using a base URL string, then joining it with the directory. I print out the URL just as a check.

func main() {
  baseurl := "http://admin:admin@odl.aristanetworks.com:8080/controller/nb/v2"
 
  // The URL to get the topology of the default slice
  url := strings.Join([]string{baseurl, "topology/default"}, "/")
  fmt.Println(url)

Next I do an HTTP GET. The return value is the response and an error variable, which I check before moving on.

resp, err := http.Get(url)
if err != nil {
  fmt.Println("Error")
}

Now I take the response, read in the body, and store it in contents.

contents, err := ioutil.ReadAll(resp.Body)

And finally I create an EdgeProperties variable based on the structs above to store the JSON data into, and unmarshal the contents into that variable. Once that is done, I am able to access the various fields of the struct and print them out.

var e EdgeProperties
err = json.Unmarshal(contents, &e)
fmt.Println(e.EdgeProperties[0].Edge)
fmt.Println(e.EdgeProperties[0].Edge.TailNodeConnector.Id)
fmt.Println(e.EdgeProperties[0].Properties.Name)
fmt.Println(e.EdgeProperties[0].Properties.TimeStamp)

Now you can run it using go run odl.go.

Go is a fun language to play with. Even though it seems like Python is the preferred network scripting language, its still interesting to see how things work in something like Go. I’d like to keep expanding this to build an OpenDaylight API Go library that could make it easier to use it as an alternative language for interacting with ODL. I have a Github project setup here if you’re interested:
https://github.com/fredhsu/odl-go

Advertisements

Arista eAPI (JSON-RPC over HTTP) in Go

I’ve been wanting to try out Go for a while, and finally decided to give it a try. This is a first stab at using Go to communicate with Arista eAPI via JSON-RPC over HTTP. There is a standard JSON-RPC library in Go, but unfortunately it doesn’t work over HTTP. Here is the code:

I start out by defining a package. Since I’m just using this standalone at this point, we’re using package main.
Next I import a few libraries. One to note is the "github.com/mitchellh/mapstructure" library. This is a handy tool for decoding map structures into Go structures, which we’ll make use of when taking in the ‘unknown’ JSON data and putting it into a struct. To install this library Go has a handy tool that can fetch directly from Github:

go get github.com/mitchellh/mapstructure

Now I move on to defining structs to hold our data in. The first three: Parameters, Request, and JsonRpcResponse are used to decode the initial JSON-RPC stuff. Just as a refresher here’s what JSON-RPC request will look like:

{
   "jsonrpc": "2.0",
   "method": "runCmds",
   "params": {
      "version": 1,
      "cmds": [
         "show version"
      ],
      "format": "json"
   },
   "id": "1"
}

Some things to note when looking at the structs for newcomers to Go like myself:
* The `json:"jsonrpc"` tags tell the JSON library to use that as the actual JSON object name instead of the name given in the struct. I ended up having to do that a lot due to the need for having the struct variable name be capitalized.
* I use the type []map[string]interface{} whenever I’m dealing with data that is not known beforehand, such as the result from the RPC.

Starting a line 54, I create a function to call eAPI via HTTP. I start by filling out the JSON Request struct fields, then marshaling them into a JSON object. After we have the JSON object, I execute an HTTP POST to send the command over and return the response as the return value of the function. Before returning the response I run it through decodeEapiResponse to take the raw response data and put it inside a JsonRpcResponse struct. All of this gets called down in the main function in lines 107-110.

Next you’ll see two functions I implemented to decode the result further into more usable structs. The first was my initial attempt for ShowVersion, which I wrote before I found the mapstructure library. This takes the response and puts everything into the appropriate fields of the struct and returns a ShowVersion struct.

The second decoding function makes use of the mapstructure library and is much cleaner. Now mapstructure takes care of all the manual mapping I did in the previous function for me.

I hope to take this and use goroutines for the calls, that will allow these to happen concurrently. The upside of this will be that I could have commands sent to a number of switches more efficiently. Go isn’t quite as easy as Python, but has some definite advantages in speed, concurrency, and static type checking that make it a useful alternative when performance matters. Any comments or suggestions for improvements are certainly welcome! The code can be cloned from github:
https://github.com/fredhsu/go-eapi.git

Arista JSON eAPI example

One of the great things about working with EOS is the ability to script with JSON-RPC.  No longer does a network admin need to do screen scraping, you can get clean, machine-friendly data from the switch using CLI syntax you’re familiar with.  I’ll outline a simple example using Python.

First add jsonrpclib to your Python environment:

sudo easy_install jsonrpclib

Now we can use that library to make scripting to EOS pretty easy:

from jsonrpclib import Server
switches = ["172.22.28.156", "172.22.28.157", "172.22.28.158"]
username = "admin"
password = "admin"

So far I’ve setup a list of switch IP addresses, and a username/password to use to login to each of them. Now let’s do something useful:

# Going through all the switch IP addresses listed above
for switch in switches:
    urlString = "https://{}:{}@{}/command-api".format(username, password, switch) #1
    switchReq = Server( urlString ) #2
    # Display the current vlan list
    response = switchReq.runCmds( 1, ["show vlan"] ) #3
    print "Switch : " + switch + " VLANs: " 
    print response[0]["vlans"].keys() #4

Now I iterate through each of the switches in the list. On each iteration the script does the following:
1) Creates a string that defines the url to reach the API
2) Start creating a JSON-RPC request with the url
3) Finish building the JSON-RPC request and send the HTTP POST with the commands I want to run on the switch. The JSON response is stored in response. The JSON-RPC library returns the “result” field automatically, so there is no need to parse through the boilerplate JSON-RPC reply.
4) Print out each of the VLANs configured on the switch. The response from the switch is a list, so first I grab the first (in this case only) item indexed by 0. This gives me a dictionary. Next I use the vlans key to select an object from the dictionary. This returns another dictionary, which has the VLAN names as the keys (and details as the values). Since I want to print a list of all the VLANs, I use the keys() method which returns a list of all the keys in the dictionary. Here is the JSON that is being parsed:

{
   "jsonrpc": "2.0",
   "result": [
      {
         "sourceDetail": "",
         "vlans": {
            "1": {
               "status": "active",
               "name": "default",
               "interfaces": {
                  "Ethernet14": {
                     "privatePromoted": false
                  },
                  "Ethernet15": {
                     "privatePromoted": false
                  },
                  "Ethernet16": {
                     "privatePromoted": false
                  },
                  "Ethernet17": {
                     "privatePromoted": false
                  },
                  "Ethernet13": {
                     "privatePromoted": false
                  }
               },
               "dynamic": false
            },
            "51": {
               "status": "active",
               "name": "VLAN0051",
               "interfaces": {
                  "Vxlan1": {
                     "privatePromoted": false
                  }
               },
               "dynamic": false
            },
            "61": {
               "status": "active",
               "name": "VLAN0061",
               "interfaces": {
                  "Vxlan1": {
                     "privatePromoted": false
                  }
               },
               "dynamic": false
            }
         }
      }
   ],
   "id": "CapiExplorer-123"
}

Here’s the full script that also adds a few lines to configure a vlan:

Microsoft OMI Integration with Arista EOS

I just gave a talk at a Microsoft event showing how Arista’s EOS OMI support allows it to integrate with Microsoft’s System Center Virtual Machine Manager and PowerShell.  This allows SCVMM to get information from the switch, and PowerShell scripts to manage and configure a switch.

Here are some examples of PowerShell scripts that interact with the switch.

JSON RPC in Objective C

I am working on a side project for work using JSON RPC from an iPhone (Arista eAPI calls from an iPhone app), so I had to get familiar with using JSON RPC using the iOS Objective C libraries. Here’s a quick rundown of how it works:

There are two major components:
NSJSONSerialization – Converts JSON Data to Dictionaries/Arrays, and vice versa
NSURLConnection – Handles HTTP connection to the API server

First I create a NSMutableDictionary with all the keys/values that I want to encode into JSON to send to the server (or switch in this case). In this example, commands is an array of strings, specific to eAPI calls, and paramsDict puts the other eAPI wrappers around the API call. Then I wrap paramsDict inside the JSON RPC Request format, which is rpcDict:

    NSMutableDictionary *paramsDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:1], @"version",
                                       commands, @"cmds",
                                       @"json", @"format", nil];
    NSMutableDictionary *rpcDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:@"2.0", @"jsonrpc",
                                    @"runCmds", @"method",
                                    paramsDict, @"params",
                                    @"CapiExplorer-123", @"id", nil];

Now I take rpcDict, and serialize it into a NSData JSON object, the resulting jsonData is ready to be sent to the API server:

    NSError *error;
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:rpcDict options:NSJSONWritingPrettyPrinted error:&error];

Now I create the URL request that the application will use to make the RPC request. In this case I’m providing my username and password in the URL string, and _aristaSwitch.switchIP is an NSString containing the IP address of the switch. Also note that I needed to set the content type, and used the jsonData data object as the body:

    NSString *urlString = [NSString stringWithFormat:@"http://user:pass@%@/command-api",
                           _aristaSwitch.switchIP];
    NSURL *url = [NSURL URLWithString:urlString];
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
    [request setURL:url];
    [request setHTTPMethod:@"POST"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
    [request setHTTPBody:jsonData];

Now I can make the connection, send the request, and start receiving a reply:

    NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:request delegate:self];
    [connection start];

Once I’ve sent the request, I need to then handle the data coming back from the server. To do this, I have the class implement NSURLConnectionDelegate and NSURLConnectionDataDelegate. These delegates handle the connection response, and downloading data. Now I implement the necessary functions, the first two handle the initial response and data stream. I’m using receivedData as a global variable to hold the data coming from the server:

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    // It can be called multiple times, for example in the case of a
    // redirect, so each time we reset the data.
    [self.receivedData setLength:0];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    // Append the new data to receivedData.
    [self.receivedData appendData:data];
}

The final method to implement takes care of the data once the connection has finished. Here I use NSJSONSerialization again to turn the received JSON data back into an NSDictionary that I can use.

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSError *error;
    
    NSDictionary *myDictionary = [NSJSONSerialization JSONObjectWithData:self.receivedData options:NSJSONReadingMutableContainers error:&error];
    NSLog(@"myDict %@ %@", myDictionary, error);
    connection = nil;
    self.receivedData = nil;
}

Vagrant with OpenDaylight

As I so often do, I took some inspiration from one of Brent’s posts on setting up OpenDaylight:

And decided to try it out with Vagrant. For those unfamiliar, Vagrant is a tool to make reproducible VMs. By default it works with VirtualBox, but can be configured to work with other virtualization technologies as well. To get started you download and install Vagrant for your OS (Mac, Win, Linux are all supported): http://downloads.vagrantup.com/
And the same goes for VirtualBox if you don’t already have it: https://www.virtualbox.org/wiki/Downloads
Once you’ve got them installed, you can copy someone’s Vagrant file, and run vagrant up in the directory to bring up the VM with the configuration specified in the file. I’ve posted my OpenDaylight Vagrant file on github:
https://github.com/fredhsu/odl-vagrant
So for this example you can clone it:

git clone https://github.com/fredhsu/odl-vagrant.git

Then cd to the directory and type:

vagrant up

This will download create a VM with Ubuntu, install all the packages needed, and then finally startup the OpenDaylight controller. The whole process will take a few minutes, but when its all done you should be able to browse to the controller by going to http://localhost:8080 since I’ve port forwarded port 8080 to the VM from localhost. I’ve also forwarded ports 6633 and 8090 for OpenFlow and the OSGI console respectively. To access the OSGI console you can telnet localhost 8090. You can also ssh to the VM by issuing a vagrant ssh. Another cool thing is that the directory you started the vm from is shared with the VM, so you can copy files back and forth. Check out the vagrant docs for more details.

If anyone has any suggestions on things to add the the Vagrantfile, please feel free to modify and push to the repo. I left out mininet, but might add it to a separate version. I hope people find this useful. I think it is a great way to be able to easily distribute and replicate VM configs. Another nice thing with this setup is that it always downloads a fresh copy of ODL, so you’re running with the latest code each time. No need to maintain a static VM image.

OVSDB Client in Python Part 2

In the last post I left off simply connecting to the OVSDB, and sending/receiving messages. Now I want to add in a couple things:
1) Handling of Echo messages – There should be periodic Echo messages sent back and forth between my client and the ovsdb-server
2) Handling of updates – When we issue a command like `monitor`, not only do we get a snapshot of the database at that time, but we also register ourselves for updates. So I’d like the client to keep listening for updates to OVSDB and allow me to see them

Using the `recv` socket command like we did in the beginning would require the client to keep looping around waiting for a message and that doesn’t seem very efficient. Instead I’m going to leverage select. What `select` will let me do is to wait for incoming data, then take action when we hear something, or take action when there is outgoing data waiting to be processed. Here’s an example:

def listen_for_messages(sock, message_queues):
    # To send something, add a message to queue and append sock to outputs
    inputs = [sock, sys.stdin]
    outputs = []
    while sock:
        readable, writable, exceptional = select(inputs, outputs, [])
        for s in readable:
            if s is sock:
                data = sock.recv(4096)
                message_queues[sock].put(data)
                outputs.append(sock)
                print "recv:" + data
            elif s is sys.stdin:
                print sys.stdin.readline()
                sock.close()
                return
            else:
                print "error"
        for w in writable:
            if w is sock:
                sock.send(message_queues[sock].get_nowait())
                outputs.remove(sock)
            else:
                print "error"

To start things off, I create a few lists that will hold the inputs and outputs I plan to monitor. In this function I initialize the inputs to be the socket connection and stdin, and outputs are empty. Next I create a while loop that will run as long as the socket is open: `while sock:`. Now we get to the select statement. I pass in the inputs and outputs that will be monitored, and the select will return whatever inputs or outputs have data that need to be processed: `readable, writable, exceptional = select(inputs, outputs, [])`
If there are any inputs, they will be assigned to the variable `readable` and we will enter the first for loop. There we check to see if there is socket data or stdin data. When we get data on a socket, we will simply copy that data into our message queue (this is a quick and dirty way to reply to an echo), and put the socket into the output list. If its something from stdin (i.e. user entering stuff on the keyboard), I will just close the socket for now. As you can imagine, one could easily use this to allow the user to send commands to the ovsdb-server with some extra modification.
Now I handle the output list, much the same way I did the input list. In this case, if there is socket data in the output list, I pull the data off the message queue, and send it out the socket. Then I remove the socket from the output queue to leave it empty again.
That should take care of our basic message handler. Using select, we’re able to handle various inputs and outputs in a efficient manner, and in this case, receive and send echo messages. This will keep the connection to the ovsdb-server alive for us. Also, if we had issued a monitor command, it will receive those updates from the server and print them to screen.

If another function wants to send messages out to the server, we would need to slightly modify things so that we can add messages to the queue and put the socket into the output list. I’ve posted a modified version of this code on github where you can have a look if interested: https://github.com/fredhsu/py-ovsdb-client