在构建这样一个拓扑并让多RYU控制器连接之前需要知道的几个重要信息:
一,RYU控制器有三个端口:
(其中前面两个端口的信息存在ryu/ryu/ofproto/ofproto_common.py中)
二,restAPI的相关操作则可以在ryu/ryu/app/ofctl_rest.py中查看,其中描述流表的增删查看等等操作和具体代码
三,为使拓扑灵活度更强,特地设定了一种基于参数的拓扑,通过JSON类型的参数设置拓扑(这个JSON文件储存了主机个数IP地址交换机个数和链路等等),相关代码已传至Github,本次使用的只是一个简陋版基于参数的拓扑,以便于把重心放在“多RYU控制器”上,本次拓扑:
MASTER
___|___
_____ / | \ _____
___ / / | \ \ _____
/ / / | \ \ \
c0 c1 c2 c3 c4 c5 c6
| | | | | | |
s0 s1 s2 s3 s4 s5 s6
/\ /\ /\ /\ /\ /\ /\
h0 h1 h2 h3 h4 h5 h6 h7 h8 h9 h10 h11 h12 h13
先写好基于参数的拓扑脚本,并尝试运行,(脚本懒得改模块了就一并搬过来了)这里的例子是创建了7个控制器,每1个控制器管辖1个交换机,每个交换机连接2个主机,相邻两个交换机之间相连,由图可见由于没有开启RYU控制器所以没有连接上控制器,所以pingall的时候也都ping不通
#!/usr/bin/python
from mininet.net import Mininet
from mininet.node import Controller, RemoteController, OVSSwitch
from mininet.cli import CLI
from mininet.log import setLogLevel, info
from mininet.link import Link, Intf, TCLink
from mininet.topo import Topo
from ryu.app.wsgi import ControllerBase
from ryu.app.wsgi import Response
from ryu.app.wsgi import WSGIApplication
import logging
import os
def multiControllerNet(con_num=7, sw_num=7, host_num=14):
"Create a network from semi-scratch with multiple controllers."
controller_list = []
switch_list = []
host_list = []
net = Mininet(controller=None, switch=OVSSwitch, link=TCLink)
for i in xrange(con_num):
name = 'controller%s' % str(i)
c = net.addController(name, controller=RemoteController,ip='127.0.0.1',port=6661 + i)
controller_list.append(c)
print("*** Creating %s" % name)
print("*** Creating switches")
switch_list = [net.addSwitch('s%d' % n) for n in xrange(sw_num)]
print(switch_list)
print("*** Creating hosts")
host_list = [net.addHost('h%d' % n) for n in xrange(host_num)]
print("*** Creating links of host2switch.")
for i in xrange(0, sw_num):
net.addLink(switch_list[i], host_list[i * 2])
net.addLink(switch_list[i], host_list[i * 2 + 1])
print("*** Creating interior links of switch2switch.")
for i in xrange(0, sw_num, sw_num / con_num):
for j in xrange(sw_num / con_num):
for k in xrange(sw_num / con_num):
if j != k and j > k:
net.addLink(switch_list[i + j], switch_list[i + k])
print("*** Creating intra links of switch2switch.")
for i in range(con_num-1):
net.addLink(switch_list[i],switch_list[i+1])
print("*** Starting network")
net.build()
for c in controller_list:
c.start()
_No = 0
for i in xrange(0, sw_num, sw_num / con_num):
for j in xrange(sw_num / con_num):
switch_list[i + j].start([controller_list[_No]])
_No += 1
# print "*** Testing network"
# net.pingAll()
print("*** Running CLI")
CLI(net)
print("*** Stopping network")
net.stop()
if __name__ == '__main__':
setLogLevel('info') # for CLI output
multiControllerNet(con_num=7, sw_num=7, host_num=14)
它继承了mininetAPI里的原始控制器类,并调用restAPI的脚本,这里只写了一些些功能并没有完全把ofctl_rest.py里的功能都写出来(其中有几个flow是供来做参考的),其中的match_flow是自己定义的一种匹配方式:事先设定一个你想要的匹配域A,kind=1时把有包含A的流表取出来,kind=2时取其补集
#!/usr/bin/python
import os
import sys
import json
import re
import subprocess
import argparse
import logging
import ast
import parser
import requests
from flask import Flask
from flask import request
from flask import url_for
from flask import jsonify
from ryu.app.wsgi import ControllerBase
from ryu.app.wsgi import Response
from ryu.app.wsgi import WSGIApplication
#from mininet.net import Mininet
#from mininet.node import Switch, OVSSwitch, Ryu,RemoteController,Controller
sw='3'
swag='{"match":{"in_port": 1,"dl_dst":"00:00:00:00:00:02"}}'
flow1='{"dpid":3, "cookie": 1, "cookie_mask": 1,"table_id": 1,"idle_timeout": 300,"hard_timeout": 300,"priority": 35,"flags": 1,"match":{"in_port":1},"actions":[{"type":"OUTPUT","port": 2}]}'
flow2='{"dpid":3, "cookie": 1, "cookie_mask": 1,"table_id": 1,"idle_timeout": 300,"hard_timeout": 300, "priority": 1,"flags": 1,"match":{"in_port":1},"actions":[]}'
flow3='{"dpid":3, "cookie": 1, "cookie_mask": 1,"table_id": 1,"idle_timeout": 320,"hard_timeout": 340, "priority": 1,"flags": 2,"match":{"dl_dst": "00:00:00:00:00:01", "dl_src": "00:00:00:00:00:02", "in_port": 2},"actions":[]}'
flow4='{"dpid":3 ,"cookie": 1,"cookie_mask": 1,"table_id": 0,"idle_timeout": 30,"hard_timeout": 3000,"priority": 11111,"flags": 1,"match":{"dl_src":"00:00:00:00:00:02","dl_dst":"00:00:00:00:00:01"},"actions":[ { "type": "OUTPUT","port": 2}]}'
class MyController():
def __init__(self,cip,cport):
self.cip=cip
self.cport=cport
self.path='http://' + self.cip + ':' + self.cport
#get the list of all switche
def get_switches(self):
item = self.path+ '/stats/switches'
tmpres = os.popen('curl %s -s' % item).readlines()
ss = tmpres[0][:-1]
ss = ss[1:]
sl = ss.split(', ')
return (sl)
# get the desc of a single switch
def get_switch_desc(sid):
if self.findswitch(sid):
item = self.path + '/stats/desc/' + sid
tmpres = os.popen('curl %s -s' % item).readlines()
return (tmpres)
else:
return 0
# get the flowtable of a single switch
def get_flow_states(self,sid):
if self.findswitch(sid):
item = self.path + '/stats/flow/' + sid
tmpres = os.popen('curl %s -s' % item).readlines()
flow_table = json.loads(tmpres[0])
return (flow_table)
else:
print('Error:not such a flowtable')
return 'Error:not such a flowtable'
# figure out whether sid is useful
def findswitch(self,sid):
if sid not in self.get_switches():
print('Error:not such a switch')
return 0
else:
return 1
# match the flowtable of a single switch you want
def match_flow(self, swag, sid, kind):
if self.findswitch(sid):
flow_table = self.get_flow_states(sid)
table = json.loads(swag)
print(table['match'])
number = 0;
location1 = [];
location2 = [];
for flow in flow_table[sid]:
flag = 0;
for element in flow['match']:
if element in table['match']:
if flow['match'][element] == table['match'][element]:
flag = flag + 1;
else:
flag = -20;
if flag > 0:
print('flow' + str(number) + 'matchable')
location2.append(number)
else:
print('flow' + str(number) + 'unmatchable')
location1.append(number);
number = number + 1;
if kind == 1:
flag = 0
for i in location1:
del flow_table[sid][i - flag]
flag = flag + 1
if kind == 2:
flag = 0
for i in location2:
del flow_table[sid][i - flag]
flag = flag + 1
return flow_table
else:
return 0
# add the flow
def add_flow(self,flow):
item=self.path+'/stats/flowentry/add'
tmpres = os.popen('curl -X POST -d \'%s\' %s -s' %(flow,item)).readlines()
return 'adding flow'
# delete the flow with match and priority
def del_flow_mp(self,flow):
item=self.path+'/stats/flowentry/delete_strict'
tmpres = os.popen('curl -X POST -d \'%s\' %s -s' %(flow,item)).readlines()
return 'deleting flow'
# delete the flow with match
def del_flow_m(self,flow):
item=self.path+'/stats/flowentry/delete'
tmpres = os.popen('curl -X POST -d \'%s\' %s -s' %(flow,item)).readlines()
return 'deleting flow'
# delete all the flow
def del_all_flow(self,sid):
if self.findswitch(sid):
item =self.path+'/stats/flowentry/clear/'+sid
tmptres = os.system('curl -X DELETE %s -s'%item)
return 'clear!'
else:
return 'Error:not such a switch'
它继承了控制器类,通过它管控各个控制器,这里就展示两个最主要的功能,开启控制器和关闭控制器,其中可见开启控制器使用了tcp_listen_port&wsgi_port,为使端口不冲突,特地给每个控制器设置了不一样的端口,在配置端口的时候可事先使用netstats -ap查看端口的使用情况,至于关闭控制器功能则就是将其端口对应的进程所关闭
#!/usr/bin/python
from tf import MyController
from mininet.net import Mininet
from mininet.node import Controller, RemoteController, OVSSwitch
from mininet.cli import CLI
from mininet.log import setLogLevel, info
from mininet.link import Link, Intf, TCLink
from mininet.topo import Topo
from ryu.app.wsgi import ControllerBase
from ryu.app.wsgi import Response
from ryu.app.wsgi import WSGIApplication
import logging
import time
import os
flow4='{"dpid":33 ,"cookie": 1,"cookie_mask": 1,"table_id": 0,"idle_timeout": 30,"hard_timeout": 3000,"priority": 11111,"flags": 1,"match":{},"actions":[ { "type": "OUTPUT","port":"controller"}]}'
class master(MyController):
def __init__(self,cnum):
self.cnum=cnum
self.clist=[]
def createcontroller(self):
for i in range(0,self.cnum):
tcp_port=6661+i;
lis_port=9090+i;
tcp_port=str(tcp_port)
lis_port=str(lis_port)
name='app'+str(i)
item='ryu-manager ofctl_rest.py simple_switch_13.py --ofp-tcp-listen-port='+tcp_port+' --wsapi-port '+lis_port+'&'
os.system(item)
controller=MyController('0.0.0.0',lis_port)
self.clist.append(controller)
def clear(self):
for i in range(0,self.cnum):
command='''kill -9 $(netstat -nlp | grep :'''+str(9090+i)+''' | awk '{print $7}' | awk -F"/" '{ print $1 }')'''
os.system(command)
os.system("netstat -an|awk '/tcp/ {print $6}'|sort|uniq -c")
写好一个简易流表操作脚本从而达到通过RYU进行流表操作的目的
#!/usr/bin/python
from rf import master
from tf import MyController
imoprt time
me=master(7)
me.createcontroller()
time.sleep(20)
for i in range(7):
print(me.clist[i].get_flow_states(str(i)))
time.sleep(200)
for i in range(7):
print(me.clist[i].get_flow_states(str(i)))
me.clear()
注意到这里有两个休眠期,在开启这个脚本的时候等待20秒是为了开启拓扑脚本,然后将获取到这个拓扑的每个交换机的流表,然后等待两百秒是为了进行pingall,然后再获取一次流表,如果没有错的话,第一次获取到的流表只有连向控制器的流表,而第二次就是有连向其他交换机接口的流表了
出现(进程号)wsgi starting up on http://0.0.0.0:wsgi_port就是开启成功了,开启成功后就有流了(0号交换机的编号出了点问题,不过不影响实验)
接下来再第一个休眠期里开启拓扑,休眠期过后可见这些控制器获取到的流表,可以看出其流表中的流表项都只有1个,那就是连接上控制器的那一个(u只是unicode码所致)
在第二个休眠期pingall然后再次获取流表,光是1号交换机的流表就让人应接不暇,然后结束