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 ”.
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.
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.