REST Linkage

This section describes how to add a REST link function to the switching hub explained in ” Switching Hub”.

Integrating REST API

Ryu has a Web server function corresponding to WSGI. By using this function, it is possible to create a REST API, which is useful to link with other systems or browsers.

Note

WSGI means a unified framework for connecting Web applications and Web servers in Python.

Implementing a Switching Hub with REST API

Let’s add the following two REST APIs to the switching hub explained in ” Switching Hub ”.

  1. MAC address table acquisition API

    Returns the content of the MAC address table held by the switching hub. Returns a pair of MAC address and port number in JSON format.

  2. MAC address table registration API

    Registers a pair of MAC address and port number in the MAC address table and adds a flow entry to the switch.

Let’s take a look at the source code.

import json

from ryu.app import simple_switch_13
from webob import Response
from ryu.controller import ofp_event
from ryu.controller.handler import CONFIG_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.app.wsgi import ControllerBase, WSGIApplication, route
from ryu.lib import dpid as dpid_lib

simple_switch_instance_name = 'simple_switch_api_app'
url = '/simpleswitch/mactable/{dpid}'


class SimpleSwitchRest13(simple_switch_13.SimpleSwitch13):

    _CONTEXTS = {'wsgi': WSGIApplication}

    def __init__(self, *args, **kwargs):
        super(SimpleSwitchRest13, self).__init__(*args, **kwargs)
        self.switches = {}
        wsgi = kwargs['wsgi']
        wsgi.register(SimpleSwitchController,
                      {simple_switch_instance_name: self})

    @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
    def switch_features_handler(self, ev):
        super(SimpleSwitchRest13, self).switch_features_handler(ev)
        datapath = ev.msg.datapath
        self.switches[datapath.id] = datapath
        self.mac_to_port.setdefault(datapath.id, {})

    def set_mac_to_port(self, dpid, entry):
        mac_table = self.mac_to_port.setdefault(dpid, {})
        datapath = self.switches.get(dpid)

        entry_port = entry['port']
        entry_mac = entry['mac']

        if datapath is not None:
            parser = datapath.ofproto_parser
            if entry_port not in mac_table.values():

                for mac, port in mac_table.items():

                    # from known device to new device
                    actions = [parser.OFPActionOutput(entry_port)]
                    match = parser.OFPMatch(in_port=port, eth_dst=entry_mac)
                    self.add_flow(datapath, 1, match, actions)

                    # from new device to known device
                    actions = [parser.OFPActionOutput(port)]
                    match = parser.OFPMatch(in_port=entry_port, eth_dst=mac)
                    self.add_flow(datapath, 1, match, actions)

                mac_table.update({entry_mac: entry_port})
        return mac_table


class SimpleSwitchController(ControllerBase):

    def __init__(self, req, link, data, **config):
        super(SimpleSwitchController, self).__init__(req, link, data, **config)
        self.simple_switch_app = data[simple_switch_instance_name]

    @route('simpleswitch', url, methods=['GET'],
           requirements={'dpid': dpid_lib.DPID_PATTERN})
    def list_mac_table(self, req, **kwargs):

        simple_switch = self.simple_switch_app
        dpid = dpid_lib.str_to_dpid(kwargs['dpid'])

        if dpid not in simple_switch.mac_to_port:
            return Response(status=404)

        mac_table = simple_switch.mac_to_port.get(dpid, {})
        body = json.dumps(mac_table)
        return Response(content_type='application/json', body=body)

    @route('simpleswitch', url, methods=['PUT'],
           requirements={'dpid': dpid_lib.DPID_PATTERN})
    def put_mac_table(self, req, **kwargs):

        simple_switch = self.simple_switch_app
        dpid = dpid_lib.str_to_dpid(kwargs['dpid'])
        try:
            new_entry = req.json if req.body else {}
        except ValueError:
            raise Response(status=400)

        if dpid not in simple_switch.mac_to_port:
            return Response(status=404)

        try:
            mac_table = simple_switch.set_mac_to_port(dpid, new_entry)
            body = json.dumps(mac_table)
            return Response(content_type='application/json', body=body)
        except Exception as e:
            return Response(status=500)

With simple_switch_rest_13.py, two classes are defined.

The first class is controller class SimpleSwitchController, which defines the URL to receive the HTTP request and its corresponding method.

The second class is SimpleSwitchRest13, which is extension of ” Switching Hub ”, to be able to update the MAC address table.

With SimpleSwitchRest13, because flow entry is added to the switch, the FeaturesReply method is overridden and holds the datapath object.

Implementing SimpleSwitchRest13 Class

class SimpleSwitchRest13(simple_switch_13.SimpleSwitch13):

    _CONTEXTS = {'wsgi': WSGIApplication}

# ...

Class variable _CONTEXT is used to specify Ryu’s WSGI-compatible Web server class. By doing so, WSGI’s Web server instance can be acquired by a key called the wsgi key.

def __init__(self, *args, **kwargs):
    super(SimpleSwitchRest13, self).__init__(*args, **kwargs)
    self.switches = {}
    wsgi = kwargs['wsgi']
    wsgi.register(SimpleSwitchController,
                  {simple_switch_instance_name: self})

Constructor acquires the instance of WSGIApplication in order to register the controller class, which is explained in a later section. For registration, the register method is used. When executing the register method, the dictionary object is passed in the key name simple_switch_api_app so that the constructor of the controller can access the instance of the SimpleSwitchRest13 class.

@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
    super(SimpleSwitchRest13, self).switch_features_handler(ev)
    datapath = ev.msg.datapath
    self.switches[datapath.id] = datapath
    self.mac_to_port.setdefault(datapath.id, {})

Parent class switch_features_handler is overridden. This method, upon rising of the SwitchFeatures event, acquires the datapath object stored in event object ev and stores it in instance variable switches. Also, at this time, an empty dictionary is set as the initial value in the MAC address table.

def set_mac_to_port(self, dpid, entry):
    mac_table = self.mac_to_port.setdefault(dpid, {})
    datapath = self.switches.get(dpid)

    entry_port = entry['port']
    entry_mac = entry['mac']

    if datapath is not None:
        parser = datapath.ofproto_parser
        if entry_port not in mac_table.values():

            for mac, port in mac_table.items():

                # from known device to new device
                actions = [parser.OFPActionOutput(entry_port)]
                match = parser.OFPMatch(in_port=port, eth_dst=entry_mac)
                self.add_flow(datapath, 1, match, actions)

                # from new device to known device
                actions = [parser.OFPActionOutput(port)]
                match = parser.OFPMatch(in_port=entry_port, eth_dst=mac)
                self.add_flow(datapath, 1, match, actions)

            mac_table.update({entry_mac: entry_port})
    return mac_table

This method registers the MAC address and port to the specified switch. The method is executed when REST API is called by the PUT method.

In argument entry. a pair of the desired MAC address and connection port is stored.

While referencing the MAC address table self.mac_to_port information, the flow entry to be registered in the switch is searched.

For example, a pair of the following MAC address and connection port is registered in the MAC address table,

  • 00:00:00:00:00:01, 1

and a pair of the MAC address and port passed by the argument entry is

  • 00:00:00:00:00:02, 2

, so the flow entry necessary to register in the switch is as follows:

  • Matching condition: in_port = 1, dst_mac = 00:00:00:00:00:02 Action: output=2
  • Matching condition: in_port = 2, dst_mac = 00:00:00:00:00:01 Action: output=1

To register flow entry, the parent class add_flow method is used. At the end, the information passed by argument entry is stored in the MAC address table.

Implementing SimpleSwitchController Class

Next, let’s talk about the controller class that accepts HTTP requests to REST API. The class name is SimpleSwitchController.

class SimpleSwitchController(ControllerBase):

    def __init__(self, req, link, data, **config):
        super(SimpleSwitchController, self).__init__(req, link, data, **config)
        self.simple_switch_app = data[simple_switch_instance_name]

# ...

The instance of the SimpleSwitchRest13 class is acquired by the contractor.

@route('simpleswitch', url, methods=['GET'], requirements={'dpid': dpid_lib.DPID_PATTERN})
def list_mac_table(self, req, **kwargs):

    simple_switch = self.simple_switch_app
    dpid = dpid_lib.str_to_dpid(kwargs['dpid'])

    if dpid not in simple_switch.mac_to_port:
        return Response(status=404)

    mac_table = simple_switch.mac_to_port.get(dpid, {})
    body = json.dumps(mac_table)
    return Response(content_type='application/json', body=body)

This part is to implement REST API’s URL and its corresponding processing. To associate this method and URL, the route decorator defined in Ryu is used.

The content specified by the decorator is as follows:

  • First argument

    Any name

  • Second argument

    Specify URL. Make URL to be http://<server IP>:8080/simpleswitch/mactable/<data path ID>.

  • Third argument

    Specify the HTTP method. Specify the GET method.

  • Fourth argument

    Specify the format of the specified location. The condition is that the {dpid} part of the URL(/simpleswitch/mactable/{dpid}) matches the expression of a 16-digit hex value defined by DPID_PATTERN of ryu/lib/dpid.py.

The REST API is called by the URL specified by the second argument. If the HTTP method at that time is GET, the list_mac_table method is called. This method acquires the MAC address table corresponding to the data path ID specified in the {dpid} part, converts it to the JSON format and returns it to the caller.

If the data path ID of an unknown switch, which is not connected to Ryu, is specified, response code 404 is returned.

@route('simpleswitch', url, methods=['PUT'], requirements={'dpid': dpid_lib.DPID_PATTERN})
def put_mac_table(self, req, **kwargs):

    simple_switch = self.simple_switch_app
    dpid = dpid_lib.str_to_dpid(kwargs['dpid'])
    try:
        new_entry = req.json if req.body else {}
    except ValueError:
        raise Response(status=400)

    if dpid not in simple_switch.mac_to_port:
        return Response(status=404)

    try:
        mac_table = simple_switch.set_mac_to_port(dpid, new_entry)
        body = json.dumps(mac_table)
        return Response(content_type='application/json', body=body)
    except Exception as e:
        return Response(status=500)

Let’s talk about REST API that registers MAC address table.

URL is the same as API when the MAC address table is acquired but when the HTTP method is PUT, the put_mac_table method is called. With this method, the set_mac_to_port method of the switching hub instance is called inside. When an exception is raised inside the put_mac_table method, response code 500 is returned. Also, as with the list_mac_table method, if the data path ID of an unknown switch, which is not connected to Ryu, is specified, response code 404 is returned.

Executing REST API Added Switching Hub

Let’s execute the switching hub to which REST API has been added.

First, as with ” Switching Hub ”, execute Mininet. Here again, don’t forget to set OpenFlow13 for the OpenFlow version. Then, start the switching hub added with REST API.

$ sudo ovs-vsctl set Bridge s1 protocols=OpenFlow13
$ ryu-manager --verbose ryu.app.simple_switch_rest_13
loading app ryu.app.simple_switch_rest_13
loading app ryu.controller.ofp_handler
creating context wsgi
instantiating app ryu.app.simple_switch_rest_13 of SimpleSwitchRest13
instantiating app ryu.controller.ofp_handler of OFPHandler
BRICK SimpleSwitchRest13
  CONSUMES EventOFPPacketIn
  CONSUMES EventOFPSwitchFeatures
BRICK ofp_event
  PROVIDES EventOFPPacketIn TO {'SimpleSwitchRest13': set(['main'])}
  PROVIDES EventOFPSwitchFeatures TO {'SimpleSwitchRest13': set(['config'])}
  CONSUMES EventOFPSwitchFeatures
  CONSUMES EventOFPPortDescStatsReply
  CONSUMES EventOFPErrorMsg
  CONSUMES EventOFPEchoRequest
  CONSUMES EventOFPEchoReply
  CONSUMES EventOFPHello
  CONSUMES EventOFPPortStatus
(24728) wsgi starting up on http://0.0.0.0:8080
connected socket:<eventlet.greenio.base.GreenSocket object at 0x7f2daf3d7850> address:('127.0.0.1', 37968)
hello ev <ryu.controller.ofp_event.EventOFPHello object at 0x7f2daf38c890>
move onto config mode
EVENT ofp_event->SimpleSwitchRest13 EventOFPSwitchFeatures
switch features ev version=0x4,msg_type=0x6,msg_len=0x20,xid=0x86fc9d2f,OFPSwitchFeatures(auxiliary_id=0,capabilities=79,datapath_id=1,n_buffers=256,n_tables=254)
move onto main mode

In the message at the time of start, there is a line stating “(31135) wsgi starting up on http://0.0.0.0:8080/” and this indicates that the Web server started at port number 8080.

Next, issue a ping from host 1 to host 2 on the mininet shell.

mininet> h1 ping -c 1 h2
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_req=1 ttl=64 time=84.1 ms

--- 10.0.0.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 84.171/84.171/84.171/0.000 ms

At this time, Packet-In to Ryu occurred three times.

EVENT ofp_event->SimpleSwitchRest13 EventOFPPacketIn
packet in 1 00:00:00:00:00:01 ff:ff:ff:ff:ff:ff 1
EVENT ofp_event->SimpleSwitchRest13 EventOFPPacketIn
packet in 1 00:00:00:00:00:02 00:00:00:00:00:01 2
EVENT ofp_event->SimpleSwitchRest13 EventOFPPacketIn
packet in 1 00:00:00:00:00:01 00:00:00:00:00:02 1

Let’s execute REST API that acquires the MAC table of the switching hub. This time, use the curl command to call REST API.

$ curl -X GET http://127.0.0.1:8080/simpleswitch/mactable/0000000000000001
{"00:00:00:00:00:02": 2, "00:00:00:00:00:01": 1}

You can find that two hosts host 1 and host 2 have been learned on the MAC address table.

This time, store the two hosts, host 1 and host 2, in the MAC address table beforehand and execute ping. Temporarily stop the switching hub and Mininet once. Then, start Mininet again, set the OpenFlow version to OpenFlow13 and then start the switching hub.

...
(26759) wsgi starting up on http://0.0.0.0:8080/
connected socket:<eventlet.greenio.GreenSocket object at 0x2afe6d0> address:('127.0.0.1', 48818)
hello ev <ryu.controller.ofp_event.EventOFPHello object at 0x2afec10>
move onto config mode
EVENT ofp_event->SimpleSwitchRest13 EventOFPSwitchFeatures
switch features ev version: 0x4 msg_type 0x6 xid 0x96681337 OFPSwitchFeatures(auxiliary_id=0,capabilities=71,datapath_id=1,n_buffers=256,n_tables=254)
switch_features_handler inside sub class
move onto main mode

Next, call REST API for updating of the MAC address table for each host. The data format when calling REST API shall be {“mac” : “MAC address”, “port” : Connection port number}.

$ curl -X PUT -d '{"mac" : "00:00:00:00:00:01", "port" : 1}' http://127.0.0.1:8080/simpleswitch/mactable/0000000000000001
{"00:00:00:00:00:01": 1}
$ curl -X PUT -d '{"mac" : "00:00:00:00:00:02", "port" : 2}' http://127.0.0.1:8080/simpleswitch/mactable/0000000000000001
{"00:00:00:00:00:02": 2, "00:00:00:00:00:01": 1}

When those commands are executed, the flow entry corresponding to host 1 and host 2 are registered.

Now, let’s execute a ping from host 1 to host 2.

mininet> h1 ping -c 1 h2
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_req=1 ttl=64 time=4.62 ms

--- 10.0.0.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 4.623/4.623/4.623/0.000 ms
...
move onto main mode
(28293) accepted ('127.0.0.1', 44453)
127.0.0.1 - - [19/Nov/2013 19:59:45] "PUT /simpleswitch/mactable/0000000000000001 HTTP/1.1" 200 124 0.002734
EVENT ofp_event->SimpleSwitchRest13 EventOFPPacketIn
packet in 1 00:00:00:00:00:01 ff:ff:ff:ff:ff:ff 1

At this time, the flow entry exists for the switches, Packet-In only occurs when an ARP request is sent from host 1 to host 2 and is not raised for subsequent packet exchange.

Conclusion

This section used a function to reference or update the MAC address table as material to explain how to add REST API. As another practical application, it may be a good idea to create REST API that can add the desired flow entry to a switch and make it possible to operate from a browser.