Arista eAPI from Microsoft PowerShell

I haven’t really played around with Windows in a while, but I’ve had a few people show me some cool things in PowerShell, so I thought I’d give it a try with eAPI. Here’s a really simple script that is able to fetch information from an Arista switch, and put it into an PowerShell object so that it can be used for whatever you’d like. Now I just need to find an excuse to buy a Surface Pro 2 ūüôā

I start off by just setting up some variables for the username, password, etc.. Variables in PowerShell start with a $ sign.

$username = "admin"
$password = "admin"
$switchIp = "172.22.28.157"

I’m able to insert variables directly into the string for the URL.

#URL
$eApiUrl = "https://$switchIp/command-api"

Now I create an array to hold the commands I want to send, and put that inside a hash table. Arrays are created with @() and Hash tables (Dictionaries in Python, Maps in Go) with @{}:

$cmds = @('show version')
$params = @{version= 1;cmds= $cmds; format="json"}

Now I create a new PowerShell object with all the required fields. PowerShell has this cool pipe operator (|) like Unix and Elixir. This allows you string together a bunch of stuff, in this case we end with piping the output to ConvertTo-Json to turn the object into a JSON string. Then I have to convert that string into an ASCII one to make it web friendly:

$command = (New-Object PSObject | Add-Member -PassThru NoteProperty jsonrpc '2.0' |
Add-Member -PassThru NoteProperty method 'runCmds' |
Add-Member -PassThru NoteProperty params $params |
Add-Member -PassThru NoteProperty id '1') | ConvertTo-Json
$bytes = [System.Text.Encoding]::ASCII.GetBytes($command)

After we have our command ready to go, we create the web connection and POST the JSON-RPC call. I also tell the system to ignore the web certificate since I haven’t installed the cert for the SSL connection.

[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
$web = [System.Net.WebRequest]::Create($eApiUrl)
$web.Method = "POST"
$web.ContentType = "application/json"
$web.Credentials = New-Object System.Net.NetworkCredential -ArgumentList $username, $password
$stream = $web.GetRequestStream()
$stream.Write($bytes, 0, $bytes.Length)
$stream.close()

Finally we take the response, and put it back into a PowerShell object using ConvertFrom-Json

$reader = New-Object System.IO.StreamReader -ArgumentList $web.GetResponse().GetResponseStream()
$response = $reader.ReadToEnd() | ConvertFrom-Json
$reader.Close()

Once we’ve got an object we can pull out pieces of the response:

$response.result
Write-Host "Model is: " + $response.result.modelName

This is what the output looks like:
Screen Shot 2014-08-20 at 4.44.14 PM

Here’s the full script from start to finish:

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:

Handling packets on the OpenDaylight controller

One of the actions that an OpenFlow switch can take is to punt a packet to the controller. ¬†This example will take a look at how we can see those packets, and do something with them. ¬†I hope to follow this up with another post that does something more exciting, but for now I’ll just try to print out what type of packet it is. ¬†This is one of the things that (as far as I know) ¬†you would only be able to do with an OSGi module, and is not available via the REST API.

First we create our Maven pom.xml with the required imports. In this case we’ll need some parts of the SAL and switchmanager:

 
Now we can create our activator. The key ingredient here is to register for callbacks from the Data Packet Service via OSGi in our public void configureInstance method:

            c.add(createContainerServiceDependency(containerName).setService(
                    IDataPacketService.class).setCallbacks(
                    "setDataPacketService", "unsetDataPacketService")
                    .setRequired(true));

This ties into methods that we implement in our GetPackets class:

    void setDataPacketService(IDataPacketService s) {
        this.dataPacketService = s;
    }

    void unsetDataPacketService(IDataPacketService s) {
        if (this.dataPacketService == s) {
            this.dataPacketService = null;
        }
    }

We make the class implement the IListenDataPacket interface to get notified of packets received on the controller:

public class GetPackets implements IListenDataPacket

And we override the public PacketResult receiveDataPacket(RawPacket inPkt) method:

    @Override
    public PacketResult receiveDataPacket(RawPacket inPkt) {
        if (inPkt == null) {
            return PacketResult.IGNORED;
        }
        log.trace("Received a frame of size: {}",
                        inPkt.getPacketData().length);
        Packet formattedPak = this.dataPacketService.decodeDataPacket(inPkt);
        System.out.println("packet");
        System.out.println(formattedPak);        
        if (formattedPak instanceof Ethernet) {
            System.out.println(formattedPak);
            Object nextPak = formattedPak.getPayload();
            if (nextPak instanceof IPv4) {
                IPv4 ipPak = (IPv4)nextPak;
                System.out.println("IP");
                log.trace("Handled IP packet");
                int sipAddr = ipPak.getSourceAddress();
                InetAddress sip = NetUtils.getInetAddress(sipAddr);
                int dipAddr = ipPak.getDestinationAddress();
                InetAddress dip = NetUtils.getInetAddress(dipAddr);
                System.out.println("SRC IP:");
                System.out.println(sip);
                System.out.println("DST IP:");
                System.out.println(dip);

                Object frame = ipPak.getPayload();
                if (frame instanceof ICMP) {
                    System.out.println("ICMP from instance");
                }
                String protocol = IPProtocols.getProtocolName(ipPak.getProtocol());
                if (protocol == IPProtocols.ICMP.toString()) {
                    ICMP icmpPak = (ICMP)ipPak.getPayload();
                    System.out.println("ICMP from checking protocol");
                    handleICMPPacket((Ethernet) formattedPak, icmpPak, inPkt.getIncomingNodeConnector());
                }
            }
        }
        return PacketResult.IGNORED;
    }

You’ll notice that we can keep going into the different payloads of the frame/packet to get to the next network layer. However, using instanceof can be slow, so an alternative is to pull out the protocol field, and do a comparison. In my example I’ve specifically handled ICMP packets, and used both methods for determining if the IP packet is ICMP.

Adding flows in OpenDaylight using Python and REST API

Building on my last post I’ve put together a small script that will find the shortest path between two switches, then install some flows to create a path between them. I’m leveraging some tools from the NetworkX library in Python and the Northbound REST API on the OpenDaylight controller.

I’m using Mininet as my test network, and the built-in tree topology that was used previously. When loaded into the controller the topology looks like this:
Tree3 Topo
Mininet creates two hosts off each leaf node. The end goal here is to ping between H1 and H8, which are connected to S3 (port 1) and S7 (port 2). First I want to get the shortest path between S3 and S7. To do that I load the nodes(switches) and edges(links) into a NetworkX Graph object:

I wrote a helper function to make building the URLs for the REST calls a little easier. The URL path to get the edges is:/controller/nb/v2/topology/default/

This returns a list of ‘edgeProperties’. An edge has a headNodeConnector and a tailNodeConnector, which are the ports on either end of the edge (each edge is unidirectional, so there will be two entries for each link). These NodeConnectors have nodes associated with them that I use to create tuple to signify an edge. For example, the edge between S4 and S2 has a headNodeConnector of:

            "edge": {
                "tailNodeConnector": {
                    "@type": "OF",
                    "@id": "2",
                    "node": {
                        "@type": "OF",
                        "@id": "00:00:00:00:00:00:00:02"
                    }
                },
                "headNodeConnector": {
                    "@type": "OF",
                    "@id": "3",
                    "node": {
                        "@type": "OF",
                        "@id": "00:00:00:00:00:00:00:04"
                    }
                }

From which I extract the node id’s (i.e. OpenFlow IDs) to create a tuple:
(00:00:00:00:00:00:00:04, 00:00:00:00:00:00:00:02)
And then add that as an edge to my Graph object. You’ll also see the ID of the NodeConnector itself as one of the attributes, which we’ll use later when defining ingress and output ports for our flows. Here’s code:

# Get all the edges/links
resp, content = h.request(build_url(baseUrl, 'topology', containerName), "GET")
edgeProperties = json.loads(content)
odlEdges = edgeProperties['edgeProperties']

# Put nodes and edges into a graph
graph = nx.Graph()
for edge in odlEdges:
  e = (edge['edge']['headNodeConnector']['node']['@id'], edge['edge']['tailNodeConnector']['node']['@id'])
  graph.add_edge(*e)

I do something similar with the nodes, using the URL path: /controller/nb/v2/switch/default/nodes
That returns a list of all the switches in the network, and I add them as nodes to the graph.

# Get all the nodes/switches
resp, content = h.request(build_url(baseUrl, 'switch', containerName) + '/nodes/', "GET")
nodeProperties = json.loads(content)
odlNodes = nodeProperties['nodeProperties']
for node in odlNodes:
  graph.add_node(node['node']['@id'])

Now I run Djikstra’s algorithm provided by the shortest_path method. This returns a list of nodes along the shortest path.

shortest_path = nx.shortest_path(graph, "00:00:00:00:00:00:00:03", "00:00:00:00:00:00:00:07")

In this case the list is:
[00:00:00:00:00:00:00:03, 00:00:00:00:00:00:00:02, 00:00:00:00:00:00:00:01, 00:00:00:00:00:00:00:05, 00:00:00:00:00:00:00:07]

Now I can build a series of flow entries for each switch along the path, not counting the ones directly connected to the hosts. To do this I created a couple functions. find_edge will look for an edge that has a particular head node and tail node. push_path takes a path and the edges from the API call and pushes the appropriate flows to the switches. To create a flow on the switch I use an HTTP POST and send over a JSON object that describes the flow. An example JSON flow entry object:

{"installInHw":"false","name":"test2","node":{"@id":"00:00:00:00:00:00:00:07","@type":"OF"}, "ingressPort":"1","priority":"500","etherType":"0x800","nwSrc":"10.0.0.7","nwDst":"10.0.0.3", "actions":"OUTPUT=2"}

And the code:

def find_edge(edges, headNode, tailNode):
  for edge in odlEdges:
    if edge['edge']['headNodeConnector']['node']['@id'] == headNode and edge['edge']['tailNodeConnector']['node']['@id'] == tailNode:
      return edge
  return None

def push_path(path, odlEdges, srcIP, dstIP, baseUrl):
  for i, node in enumerate(path[1:-1]):
    flowName = "fromIP" + srcIP[-1:] + "Po" + str(i)
    ingressEdge = find_edge(odlEdges, shortest_path[i], node)
    egressEdge = find_edge(odlEdges, node, shortest_path[i+2])
    newFlow = build_flow_entry(flowName, ingressEdge, egressEdge, node, srcIP, dstIP)
    switchType = newFlow['node']['@type']
    postUrl = build_flow_url(baseUrl, 'default', switchType, node, flowName)
    # post the flow to the controller
    resp, content = post_dict(h, postUrl, newFlow)

def build_flow_entry(flowName, ingressEdge, egressEdge, node, srcIP, dstIP):
  # Since I don't specify the EtherType, it looks like the IP field is ignored
  # Alternatively I could add a second flow with 0x806 for ARP then 0x800 for IP
  defaultPriority = "500"
  newFlow = {"installInHw":"false"}
  ingressPort = ingressEdge['edge']['tailNodeConnector']['@id']
  egressPort = egressEdge['edge']['headNodeConnector']['@id']
  switchType = egressEdge['edge']['headNodeConnector']['node']['@type']
  newFlow.update({"name":flowName})
  newFlow.update({"node":ingressEdge['edge']['tailNodeConnector']['node']})
  newFlow.update({"ingressPort":ingressPort, "priority":defaultPriority})
  newFlow.update({"nwSrc":srcIP, "nwDst":dstIP})  # This can probably be ignored for this example
  newFlow.update({"actions":"OUTPUT=" + egressPort})
  return newFlow

def post_dict(h, url, d):
  resp, content = h.request(
      uri = url,
      method = 'POST',
      headers={'Content-Type' : 'application/json'},
      body=json.dumps(d),
      )
  return resp, content

A few things to note:

  • I didn’t specify an EtherType so that it will send any packet over the links. I needed to do this so that ARP messages would make it across. But in doing so, the IP addresses are ignored. This works fine for my simple example, but I think I may revise this later to handle both ARP and IP separately so that there are not such broad flow entries installed. Then I could specify IP match criteria.
  • I chose to not install the flow to the switch immediately so that I can take a look at them before actually putting them to work (InstallInHw : false)
  • I’m not yet error checking on the response, but you should get an HTTP 201 for successful flow entries.

After pushing these flows, I reverse the path, and push the reverse path onto the switches:

shortest_path.reverse()
push_path(shortest_path, odlEdges, dstIP, srcIP, baseUrl)

Finally I add the entries for the leaf nodes connected to the hosts. There is probably a way to do this with host tracker to make this dynamic, but I just hardcoded it for now:

node3FlowFromHost = {"installInHw":"false","name":"node3from","node":{"@id":"00:00:00:00:00:00:00:03","@type":"OF"},"ingressPort":"1","priority":"500","nwSrc":"10.0.0.1","actions":"OUTPUT=3"}
node7FlowFromHost = {"installInHw":"false","name":"node7from","node":{"@id":"00:00:00:00:00:00:00:07","@type":"OF"},"ingressPort":"2","priority":"500","nwSrc":"10.0.0.8","actions":"OUTPUT=3"}
node3FlowToHost = {"installInHw":"false","name":"node3to","node":{"@id":"00:00:00:00:00:00:00:03","@type":"OF"},"ingressPort":"3","priority":"500","nwDst":"10.0.0.1","actions":"OUTPUT=1"}
node7FlowToHost = {"installInHw":"false","name":"node7to","node":{"@id":"00:00:00:00:00:00:00:07","@type":"OF"},"ingressPort":"3","priority":"500","nwDst":"10.0.0.8","actions":"OUTPUT=2"}
postUrl = build_flow_url(baseUrl, 'default', "OF", "00:00:00:00:00:00:00:03", "node3from")
resp, content = post_dict(h, postUrl, node3FlowFromHost)
postUrl = build_flow_url(baseUrl, 'default', "OF", "00:00:00:00:00:00:00:07", "node7from")
resp, content = post_dict(h, postUrl, node7FlowFromHost)
postUrl = build_flow_url(baseUrl, 'default', "OF", "00:00:00:00:00:00:00:03", "node3to")
resp, content = post_dict(h, postUrl, node3FlowToHost)
postUrl = build_flow_url(baseUrl, 'default', "OF", "00:00:00:00:00:00:00:07", "node7to")
resp, content = post_dict(h, postUrl, node7FlowToHost)

Ok, after all that, I can run my script and then go to the controller to see all the flows added.
2013-06-14-080757_418x428_scrot
Now I install them into the switches by clicking on a flow and then ‘Install Flow’.
2013-06-14-081202_988x333_scrot
After the flows have all been installed, I can then try out my pings in mininet:
Mininet Ping 1
And just for good measure I can go over to the Troubleshooting tab on the controller to see the stats of these flows, and see how packets are matching on my flow entries.
2013-06-14-080824_984x180_scrot
You can find the complete script on Github: https://github.com/fredhsu/odl-scripts/tree/master/python/addflow

Modifying DSCP value with onePK

In addition to playing around with OpenFlow, I’ve started working with Cisco onePK as well. ¬†You can get to the SDK here: ¬†http://developer.cisco.com/web/onepk/home

One of the services provided is the Data Path Service Set (DPSS), which lets you intercept and modify packets as they go through the router. ¬†Currently this is only available in the C SDK, so I had to dust off my pointer skills and dive into some C. ¬†I thought an interesting exercise would be to take packets and change their DSCP value, so I wrote the following based on the tutorials and examples to change outgoing packets to have a DSCP of 12. ¬†I’ll break down the code into chunks, or if you’d like you can just go to my github site and look at the whole thing:
https://github.com/fredhsu/myonepk-c/tree/master/myfirstapp
First off we after adding the libraries we define some global variables:

static network_interface_t *intf = NULL;
static ace_t* ace;
static acl_t* acl;
static target_t* targ = NULL;
static class_t * acl_class = NULL;
static filter_t * acl_filter = NULL;
static interface_filter_t* intf_filter = NULL;
static onep_collection_t* intfs = NULL;
static unsigned int count = 0;
static network_element_t* ne1 = NULL;
static onep_status_t rc;
static onep_if_name name;
static onep_dpss_handle_t *dpss_handle;
static onep_dpss_packet_loop_t *pak_loop;
static onep_dpss_traffic_reg_t *reg_handle;
static session_config_t *config;
static network_application_t* myapp = NULL;
struct sockaddr_in v4addr;

Some variables to take note of are the ACL, the Network Element, and the DPSS handle and packet loop. These will all become more clear as we move down the code. The variable rc, is used throughout for checking the return code of functions to ensure things went ok. I only did some spotty checks for this example, which generally takes the form of checking if rc == ONEPK_OK

The next part of the code is a callback function that will modify the packets when packets are sent to the agent from the router.

void dpss_tutorial_pak_injector (
onep_dpss_traffic_reg_t *reg,
onep_dpss_paktype_t *pak,
void *client_context,
bool *return_pak)

First I extract a dpss handle, target, and target interface from the *reg variable that was passed when the callback was called:

rc = onep_dpss_traffic_reg_get_dpss_handle (reg, &dpss);
rc = onep_dpss_traffic_reg_get_target(reg, &targ);
rc = onep_policy_get_target_interface(targ, &intf);

Now just as a check, I print out the interface name that I’m listening for packets on:

onep_if_name name;
rc = onep_interface_get_name(intf,name);
printf("Packet arrived on interface [%s]\n",name);

Now I’m going to modify the packet’s DSCP value. I will do so by modifying the bits in the 2nd octet to be a 48, which will set the bits for DSCP to be 12, and the remaning bit. For a refresher, you can look at the IP Packet header here:

http://en.wikipedia.org/wiki/IPv4#Header

First I create a pointer to an array of bytes that I will copy into the packet.

uint8_t newdscp[1] = {48};

Then I modify the packet, with an offset of 1, modifying 1 byte, of the layer 3 header, and inserting the bytes from the array we just created:

rc = onep_dpss_modify_packet(pak,
ONEP_DPSS_LAYER_3,
1,
1,
newdscp,
1
);

Then there is some error checking and we return from the function. Note that I am making use of the default behavior of return_pak = true to return the packet to the router. You can explicitly do this by setting return_pak = false and then telling the agent to return the packet manually as well.

Now I go into the main() function. ¬†The first 20 or so lines are just getting a session established and a connection to the network element I’m using. ¬†Then I create an interface filter and go through the interfaces on the router to find the one we want. The last line below selects the second interface which in my case is the one I have connected to a laptop with a sniffer:

onep_interface_filter_new(&intf_filter);
rc = onep_element_get_interface_list(ne1, intf_filter, &intfs);
rc = onep_collection_get_size(intfs, &count);
if (count <= 0 ) {
fprintf(stderr, "\nNo interfaces available");
return ONEP_FAIL;
}
onep_collection_get_by_index(intfs, 2, (void *)&intf);

Now I create an ACL and class to define what traffic will be “interesting” and handled by the agent:

onep_acl_create_l3_acl(AF_INET, ne1, &acl);
onep_acl_create_l3_ace(1, TRUE, &ace); // seq#, permit, ace obj
onep_acl_set_l3_ace_protocol(ace, ONEP_PROTOCOL_ALL);
onep_acl_set_l3_ace_src_prefix(ace, NULL, 24);
onep_acl_set_l3_ace_dst_prefix(ace, NULL, 24);
onep_acl_add_ace(acl, ace);
onep_policy_create_class(ne1, ONEP_CLASS_OPER_OR, &acl_class);
onep_policy_create_acl_filter(acl, &acl_filter);
onep_policy_add_class_filter(acl_class, acl_filter);

Then we create a DPSS packet loop to receive the packets:

onep_dpss_packet_loop_start(1, &pak_loop);
onep_dpss_initialize(&dpss_handle, "dpss_tutorial", ne1);
onep_dpss_packet_loop_register_dpss_handle(pak_loop, dpss_handle);

Now I create a policy interface target to set the interface we’re getting packets from. Also note here that we’ve set that it will handle packets that are leaving the interface (I got stuck here for a bit):

onep_policy_create_interface_target(intf, ONEP_TARGET_LOCATION_HARDWARE_DEFINED_OUTPUT, &targ);

Finally I register for the packets:

onep_dpss_register_for_packets(dpss_handle,
targ,
acl_class,
ONEP_DPSS_ACTION_PUNT,
dpss_tutorial_pak_injector,
0,
&reg_handle);

You’ll see here we provide the DPSS handle we created earlier, the target policy that defines the interface and direction, and the callback function we defined earlier. Also I’ve specified ONEP_DPSS_ACTION_PUNT which tells the router to punt the packets to the agent. With this action set, the router will not forward the packet on, but will take the returned packet from our callback. Another option here is to do a copy where the packet will go on, but a copy will be sent to the agent.

Now all that’s left is to setup the router, run the program, and test it out. ¬†On the router we have to configure onePK and the DPSS service set from the global config mode:

onep
datapath transport gre interface GigabitEthernet0/0
transport socket
start

You’ll see that DPSS will create a GRE tunnel between the router and the agent via the interface specified. Now to run the application, first we have to start dpss_mp to receive the packets. First edit the dpss.conf file located at:

/opt/cisco/onep/c64/sdk-c64-0.7.0.503g/c/bin/dpss.conf

And modify it to your host’s IP address. Then you can run dpss_mp, I’ve done so with the following command:

sudo LD_LIBRARY_PATH=/opt/cisco/onep/c64/sdk-c64-0.7.0.503g/c/lib/ /opt/cisco/onep/c64/sdk-c64-0.7.0.503g/c/bin/dpss_mp_64-0.7.0.503

Finally make and run the program. While the program is running, I went to the router and initiated a telnet to my laptop that was connected and running wireshark. When I look at the traces:
Capture
Success!

Pieces used in running OpenDaylight

I’m running through Brent Salisbury’s excellent walk-thrus (http://www.networkstatic.net) on installing OpenDaylight (http://www.opendaylight.org/). ¬†As someone who is more a network guy than a coder, I thought I’d try to decipher some of the things that are being used.

Eclipse(http://www.eclipse.org) : an IDE that is commonly used for Java projects.

Maven(http://maven.apache.org/): an automated build system that helps handle dependencies when building a project.

m2e: a plugin for Eclipse that allows you to work with Maven projects

OSGI (http://www.osgi.org): a module system for Java that allows components to be added/removed to an application without reboot.

Tycho (http://eclipse.org/tycho/): a set of Maven plugins that are used for making OSGI bundles.

All of these components come together in building OpenDaylight. ¬†You could do without the Eclipse stuff if you just wanted to run on the command line, and/or use a different IDE. ¬†Brent’s videos do a great job of getting the controller up and running, certainly worth a watch.