-
Notifications
You must be signed in to change notification settings - Fork 51
Creating Apps With Python
Creating Frenetic-based apps with Python is definitely the easiest route. Here's a template:
import frenetic
from frenetic.syntax import *
class MyApp(frenetic.App):
client_id = "my_app"
def connected(self):
self.update( id >> SendToController("my_app") )
def switch_up(self,switch_id,ports):
print "switch_up(switch_id=%s)" % switch_id
def switch_down(self,switch_id):
print "switch_down(switch_id=%s)" % switch_id
def port_up(self,switch_id, port_id):
print "port_up(switch_id=%s, port_id=%d)" % (switch_id, port_id)
def port_down(self,switch_id, port_id):
print "port_down(switch_id=%s, port_id=%d)" % (switch_id, port_id)
def packet_in(self, switch_id, port_id, payload):
print "packet_in(switch_id=%s, port_id=%d)" % (switch_id, port_id)
app = MyApp()
app.start_event_loop()
Each of the handlers above is the default implementation, so you can leave them out and your app will use the print statement as above.
In one Terminal window start up a sample Mininet network:
[email protected]:~/src/frenetic/examples$ sudo mn --topo=single,2 --controller=remote
*** Creating network
*** Adding controller
Unable to contact the remote controller at 127.0.0.1:6633
*** Adding hosts:
h1 h2
*** Adding switches:
s1
*** Adding links:
(h1, s1) (h2, s1)
*** Configuring hosts
h1 h2
*** Starting controller
c0
*** Starting 1 switches
s1 ...
*** Starting CLI:
mininet>
In another terminal window, start up Frenetic:
frenetic@ubuntu-1404: ̃src/frenetic$ frenetic http-controller --verbosity debug
[INFO] Calling create!
[INFO] Current uid: 1000
[INFO] Successfully launched OpenFlow controller with pid 3062
[INFO] Connecting to first OpenFlow server socket
[INFO] Failed to open socket to OpenFlow server: (Unix.Unix_error...
[INFO] Retrying in 1 second
[INFO] Successfully connected to first OpenFlow server socket
[INFO] Connecting to second OpenFlow server socket
[INFO] Successfully connected to second OpenFlow server socket
And in the third window, start up your application:
vagrant@frenetic:~/src/frenetic/examples$ python template_python.py
Starting the tornado event loop (does not return).
In the Mininet window, type pingall
. The pings won't succeed, but you'll see the packets go to the controller in the application window:
packet_in(switch_id=1, port_id=1)
packet_in(switch_id=1, port_id=1)
packet_in(switch_id=1, port_id=2)
packet_in(switch_id=1, port_id=2)
packet_in(switch_id=1, port_id=2)
In Python, each Frenetic event has a hook listed below. If the application defines a handler with the same signature, that handler is called on the event. If the user doesn’t define a handler, a default handler simply logs the event (as shown above).
This handler is called when Frenetic has started up. It waits a bit for the switches to send their initial connection messages, then Frenetic calls the handler with a dict of DPid's and values as a list of available ports for that particular switch.
switch_id
is a 64-bit DPID per OpenFlow specs, and port_id
is a 32-bit integer. payload is a Python object of either class Buffered
or NotBuffered
. Buffered objects have attributes buffer_id
and buffer
with the decoded data. NotBuffered
objects only have a data attribute with the decoded data.
Sending payload
to frenetic.Packet.from_payload(switch_id, port_id, payload)
is a nice way to parse the packet. The resulting Packet
object has attributes for all the common OpenFlow headers like ethSrc
. See the Frenetic Programmers Guide for details.
The port_down
event is fired when the port is disconnected, deactivated, reconfigured, or removed from the OpenFlow engine.
The port_up
event is fired when the port is connected, activated, reconfigured, or assigned
to the OpenFlow engine and is ready to use.
The switch_down
event is fired when the switch is gracefully stopped, or the OpenFlow
engine has been stopped.
The switch_up
event is fired when the switch and OpenFlow engine are ready to use. The
operational ports connected to OpenFlow are sent in ports.
Frenetic commands are equivalent to OpenFlow controller-to-switch messages. Commands are called from Python by calling the method listed below. Commands may send a reply, as in port_stats or not, as in pkt_out.
current_switches()
The current_switches command retrieves a dictionary of operational, OpenFlow enabled switches and their operational ports. The dictionary key is the DPID of the switch and the value is the list of port numbers for that switch.
config(options)
config sets compiler options for Frenetic. These options are applied on the next update command. options
is a dict with the following keys:
-
cache_prepare
(”empty” or ”keep”, defaults to empty): If keep, keep old policies after calling update command. There is an implicit Union between the old and new policies in the new setup. -
dedup_flows
(boolean, defaults to true): If true, remove any OpenFlow table rules that are exactly alike. -
field_order
(”default”, ”heuristic”, or a list of < separated fields, defaults to heuristic): Set field order priority. On occasion, setting this may reduce the OpenFlow table size. The heuristic setting attempts the optimal ordering based on the fields in the policy. -
optimize
(boolean, defaults to true): If true, attempt to optimize the number of OpenFlow rules. -
remove_tail_drops
(boolean, defaults to false): If true, remove any drop rules from the end of the OpenFlow table. This is necessary on switches like the Dell where the ACL table incorrectly prioritizes itself over all L2 and L3 table rules.
pkt_out(switch id, payload, plist, inport)
pkt_out
, which in many ways is the analogue of the packet_in hook, sending a packet out to the switch for processing.
The Python parameters are the same as their packet_in
counterparts. The exception is plist
which is either:
- A simple NetKAT Policy, described here.
- A Python list of simple NetKAT Policies
- A single NetKAT
Seq
of policies. Each policy in the sequence must be a simple policy.
port_stats(switch id, port id)
port_stats
retrieves current port-level statistics for a certain switch and port. Sending a port_id of 0 retrieves stats of each operational port on that switch. Statistics attributes include:
-
port_no
Port number -
rx_packets
Number of packets received -
tx_packets
Number of packets transmitted -
rx_bytes
Number of bytes received -
tx_bytes
Number of bytes transmitted -
rx_dropped
Number of packets attempted to receive, but dropped tx dropped Number of packets attempted to transmit, but dropped -
rx_errors
Number of packets errored upon receive -
tx_errors
Number of packets errored upon transmit -
rx_fram_err
Number of packets received with frame errors -
rx_over_err
Number of packets received with buffer overrun errors -
rx_crc_err
Number of packets received with CRC errors collisions Number of collisions detected
In Python, the stats are returned as a list of dictionaries, one dictionary for each port requested. The dict keys are the attributes listed above.
query(label)
query
retrieves statistics from a query bucket named label. This label should have been set up as a SendToQuery(label)
policy. The return value is a two-element list, the first being the number of bytes, the second the number of packets.
update(policy)
update
sends a NetKAT policy to Frenetic, which will compile it into OpenFlow flow tables for each connected switch. In REST, the policy itself is the JSON packet representing the policy – generally the outermost envelope is a policy combinator like Union
.