Skip to content

Creating Apps With Python

Craig Riecke edited this page Jun 21, 2016 · 4 revisions

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)

Events

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

connected()

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.

packet_in(switch_id, port_id, payload)

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.

port_down(switch_id, port_id)

The port_down event is fired when the port is disconnected, deactivated, reconfigured, or removed from the OpenFlow engine.

port_up(switch_id, port_id)

The port_up event is fired when the port is connected, activated, reconfigured, or assigned to the OpenFlow engine and is ready to use.

switch_down(switch_id)

The switch_down event is fired when the switch is gracefully stopped, or the OpenFlow engine has been stopped.

switch_up(switch_id, ports)

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.

Commands

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

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

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

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

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

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

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.