diff --git a/.gitignore b/.gitignore index 56de96a..8602cb8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ .DS_Store *.pyc *.log +config.json +build +dist +*.egg-info +*.eggs diff --git a/.travis.yml b/.travis.yml index 4644365..d683d08 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1 +1,6 @@ script: ./run-tests.sh +sudo: false +cache: + directories: + - $HOME/.local/lib/python2.7/site-packages/ + - $HOME/build/dlinknctu/OmniUI/ui/node_modules/ diff --git a/README.md b/README.md index 58a930c..34be943 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,6 @@ $ `sudo apt-get install build-essential python-dev git python-virtualenv python- 2. Update existing packages $ `sudo apt-get update` $ `sudo pip install --upgrade pip virtualenv` -$ `sudo pip install flask flask_cors gevent pymongo` 3. Download & Install Mininet $ `git clone git://github.com/mininet/mininet` @@ -38,17 +37,25 @@ $ `git clone https://github.com/dlinknctu/OmniUI.git -b dev` 5. Install MongoDB * Please refer to [Install MongoDB](http://docs.mongodb.org/manual/installation/) -6. Modify OmniUI database credentials -$ `gedit ~/OmniUI/core/config.json` - **Modify the following (UIPusher and DbCollection section):** - `"ControllerType":""` **If using Ryu, fill ControllerType as "floodlight"** - `"dbip":"",` - `"dbport":"",` - `"db":"",` - `"user":""` - `"password":""` +###Installation & Execution of OmniUI Core### +1. Modify OmniUI database credentials +$ `gedit ~/OmniUI/core/etc/config.json` + **Modify the following (UIPusher and DbCollection section):** + `"ControllerType":""` **If using Ryu, fill ControllerType as "floodlight"** + `"dbip":"",` + `"dbport":"",` + `"db":"",` + `"user":""` + `"password":""` -###Installation & Execution### +2. Install OmniUI core and dependencies +$ `cd ~/OmnuUI/core/` +$ `sudo python setup.py install` + +3. Run OmniUI core +$ `/usr/local/bin/omniui` + +###Installation & Execution of Controllers### - Installation & Execution of controller adapter * Please refer to `/adapter//README.md` diff --git a/adapter/floodlight/README.md b/adapter/floodlight/README.md index 2c367ad..24b3f54 100644 --- a/adapter/floodlight/README.md +++ b/adapter/floodlight/README.md @@ -22,16 +22,12 @@ $ `cp -r ~/OmniUI/adapter/floodlight/omniui/ .` $ `cd ~/floodlight; ant` ###Execution### -1. Compile OmniUI core -$ `cd ~; python ~/OmniUI/core/core.py` -**Successful compilation starts the OmniUI core** - -2. Run the Floodlight Controller (Using a 2nd Terminal) +1. Run the Floodlight Controller (Using a 2nd Terminal) $ `java -jar floodlight/target/floodlight.jar` -3. Run a simple Mininet topology (Using a 3rd Terminal) +2. Run a simple Mininet topology (Using a 3rd Terminal) $ `sudo mn --controller=remote --topo tree,depth=2` -4. Start-up OmniUI Web UI (Using a 4th Terminal) +3. Start-up OmniUI Web UI (Using a 4th Terminal) $ `firefox ~/OmniUI/webui/index.html` diff --git a/adapter/floodlight/install.sh b/adapter/floodlight/install.sh new file mode 100755 index 0000000..0d29132 --- /dev/null +++ b/adapter/floodlight/install.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +echo -e "\033[32mClone Floodlight 0.91 from GitHub repository\033[0m" +git clone https://github.com/floodlight/floodlight.git -b v0.91 floodlight + +echo -e '\033[32mCopy OmniUI module into Floodlight directory\033[0m' +cp -r adapter/floodlight/omniui floodlight/src/main/java/net/floodlightcontroller/ + +echo -e '\033[32mModify META-INF\033[0m' +echo "net.floodlightcontroller.omniui.OmniUI" >> \ + floodlight/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule + +echo -e '\033[32mModify floodlightdefault.properties\033[0m' +file='floodlight/src/main/resources/floodlightdefault.properties' +sed '2i\ + net.floodlightcontroller.omniui.OmniUI,\\ + ' $file > $file.tmp +mv $file.tmp $file + +# Compile Floodlight +echo -e '\033[32mCompile Floodlight\033[0m' +cd floodlight +ant diff --git a/adapter/pox/omniui/flow_modify.py b/adapter/pox/omniui/flow_modify.py index ce4cfb8..696a5b7 100644 --- a/adapter/pox/omniui/flow_modify.py +++ b/adapter/pox/omniui/flow_modify.py @@ -7,19 +7,18 @@ log = core.getLogger() -Addxid = 0 -Modifyxid = 0 -Deletexid = 0 - - +Barrier_Addxid = 0 +Barrier_Modifyxid = 0 +Barrier_Deletexid = 0 +add_success = False +mod_success = False +del_success = False class ReplyEvent (Event): def __init__ (self, reply): Event.__init__(self) self.reply = reply - - class flow_modify (EventMixin): _eventMixin_events = set([ReplyEvent,]) def __init__ (self): @@ -27,7 +26,6 @@ def __init__ (self): self._record_rules_dict = {} self._record_rules_list = [] - def _handle_GoingUpEvent (self, event): self.listenTo(core.FlowModEvent_Generator) @@ -55,13 +53,11 @@ def _parse_json_format(self): def _add_flow(self,command_type): - global Addxid - Addxid = of.generate_xid() + global Barrier_Addxid msg = of.ofp_flow_mod() msg.command = of.OFPFC_ADD self._match_field(msg) - # print "====mod payload ====\n",self.payload for connection in core.openflow._connections.values() : if str(dpidToStr(connection.dpid)) == self.dpid: @@ -69,23 +65,18 @@ def _add_flow(self,command_type): """match actions""" self._match_action(msg) connection.send(msg) - barrier = of.ofp_barrier_request() - barrier.xid = xid + barrier.xid = of.generate_xid() + Barrier_Addxid = barrier.xid connection.send(barrier) - # for recover self._record_rules(dpid = self.dpid , msg = msg) - def _modify_flow(self,command_type): - global Modifyxid - Modifyxid = of.generate_xid() + global Barrier_Modifyxid msg = of.ofp_flow_mod() - # print "====mod payload ====\n",self.payload - if command_type == "MOD_ST": msg.command = of.OFPFC_MODIFY_STRICT elif command_type == "MOD": @@ -101,23 +92,23 @@ def _modify_flow(self,command_type): connection.send(msg) barrier = of.ofp_barrier_request() - barrier.xid = xid + barrier.xid = of.generate_xid() + Barrier_Modifyxid = barrier.xid connection.send(barrier) # for recover self._record_rules(dpid = self.dpid , msg = msg) - def _delete_flow(self,command_type): - Deletexid = of.generate_xid() + global Barrier_Deletexid msg = of.ofp_flow_mod() + if command_type == "DEL_ST": msg.command = of.OFPFC_DELETE_STRICT elif command_type == "DEL": msg.command = of.OFPFC_DELETE - # print "=== del payload ===\n",self.payload self._match_field(msg) for connection in core.openflow._connections.values() : @@ -126,18 +117,16 @@ def _delete_flow(self,command_type): connection.send(msg) barrier = of.ofp_barrier_request() - barrier.xid = xid + barrier.xid = of.generate_xid() + Barrier_Deletexid = barrier.xid connection.send(barrier) # for recover self._record_rules(dpid = self.dpid , msg = msg) - def _parse_actions(self,actions): _actions = actions.replace(' ','') - self.actions = _actions.split('=')[0] - if self.actions != "strip-vlan": - self.actions_argu = _actions.split('=')[1] + self.actions = _actions.split(',') def _match_field(self,msg): @@ -146,11 +135,11 @@ def _match_field(self,msg): if self.payload.has_key("dstIP"): msg.match.nw_dst = IPAddr (str(self.payload['dstIP']).split('/')[0]) - msg.match.wildcards = msg.match.wildcards & (((32 - int(str(self.payload['dstIP']).split('/')[1]) ) << 8) | 0x3fc0ff ) + msg.match.wildcards = msg.match.wildcards & 0x303fff | ((32 - int(str(self.payload['dstIP']).split('/')[1]) ) << 14) if self.payload.has_key("srcIP"): - msg.match.nw_src = IPAddr (str(self.payload['dstIP']).split('/')[0]) - msg.match.wildcards = msg.match.wildcards & (((32 - int(str(self.payload['dstIP']).split('/')[1]) ) << 14) | 0x303fff ) + msg.match.nw_src = IPAddr (str(self.payload['srcIP']).split('/')[0]) + msg.match.wildcards = msg.match.wildcards & 0x3fc0ff | ((32 - int(str(self.payload['srcIP']).split('/')[1]) ) << 8) if self.payload.has_key("srcMac"): msg.match.dl_src = EthAddr(self.payload['srcMac']) @@ -176,6 +165,9 @@ def _match_field(self,msg): if self.payload.has_key("vlan"): msg.match.dl_vlan = int(self.payload['vlan']) + if self.payload.has_key("vlanP"): + msg.match.dl_vlan_pcp = int(self.payload['vlanP']) + if self.payload.has_key("dlType"): msg.match.dl_type = int(self.payload['dlType']) @@ -189,85 +181,99 @@ def _match_field(self,msg): msg.match.nw_proto = int(self.payload['netProtocol']) if self.payload.has_key("tosBits"): - msg.match.nw_tos=int(self.payload['tosBits']) + msg.match.nw_tos = int(self.payload['tosBits']) self.dpid = self.payload['switch'].replace(':','-')[6:] self._parse_actions(self.payload['actions']) - def _match_action(self,msg): - if(self.actions == "OUTPUT"): - msg.actions.append(of.ofp_action_output(port = int(self.actions_argu))) + + if len(self.actions) == 1 and self.actions[0] == "" : + return + + for action in self.actions: + + action_name = action.split('=')[0] + if action_name != "STRIP_VLAN": + action_argu = action.split('=')[1] + + if(action_name == "OUTPUT"): + msg.actions.append(of.ofp_action_output(port = int(action_argu))) - elif(self.actions == "enqueue"): - port = self.actions_argu.split(':')[0] - queue_id = self.actions_argu.split(':')[1] - msg.actions.append(of.ofp_action_enqueue(port = int(port) , queue_id = int(queue_id))) + elif(action_name == "ENQUEUE"): + port = action_argu.split(':')[0] + queue_id = action_argu.split(':')[1] + msg.actions.append(of.ofp_action_enqueue(port = int(port) , queue_id = int(queue_id))) - elif(self.actions == "strip-vlan"): - msg.actions.append(of.ofp_action_strip_vlan()) + elif(action_name == "STRIP_VLAN"): + msg.actions.append(of.ofp_action_strip_vlan()) - elif(self.actions == "set-vlan-id"): - msg.actions.append(of.ofp_action_vlan_vid(vlan_vid = int(self.actions_argu))) + elif(action_name == "SET_VLAN_VID"): + msg.actions.append(of.ofp_action_vlan_vid(vlan_vid = int(action_argu))) - elif(self.actions == "set-vlan-priority"): - msg.actions.append(of.ofp_action_vlan_pcp(vlan_pcp = int(self.actions_argu))) + elif(action_name == "SET_VLAN_PCP"): + msg.actions.append(of.ofp_action_vlan_pcp(vlan_pcp = int(action_argu))) - elif(self.actions == "SET_DL_SRC"): - msg.actions.append(of.ofp_action_dl_addr(type = 4 , dl_addr = EthAddr(self.actions_argu))) + elif(action_name == "SET_DL_SRC"): + msg.actions.append(of.ofp_action_dl_addr(type = 4 , dl_addr = EthAddr(action_argu))) - elif(self.actions == "SET_DL_DST"): - msg.actions.append(of.ofp_action_dl_addr(type = 5 , dl_addr = EthAddr(self.actions_argu))) + elif(action_name == "SET_DL_DST"): + msg.actions.append(of.ofp_action_dl_addr(type = 5 , dl_addr = EthAddr(action_argu))) - elif(self.actions == "SET_NW_TOS"): - msg.actions.append(of.ofp_action_nw_tos(nw_tos = int(self.actions_argu))) - - elif(self.actions == "SET_NW_SRC"): - msg.actions.append(of.ofp_action_nw_addr(type = 6 , nw_addr = IPAddr(self.actions_argu))) + elif(action_name == "SET_NW_TOS"): + msg.actions.append(of.ofp_action_nw_tos(nw_tos = int(action_argu))) - elif(self.actions == "SET_NW_DST"): - msg.actions.append(of.ofp_action_nw_addr(type = 7 , nw_addr = IPAddr(self.actions_argu))) + elif(action_name == "SET_NW_SRC"): + msg.actions.append(of.ofp_action_nw_addr(type = 6 , nw_addr = IPAddr(action_argu))) - elif(self.actions == "SET_TP_SRC"): - msg.actions.append(of.ofp_action_tp_port(type = 9 , tp_port = int(self.actions_argu))) - - elif(self.actions == "SET_TP_DST"): - msg.actions.append(of.ofp_action_tp_port(type = 10 , tp_port = int(self.actions_argu))) + elif(action_name == "SET_NW_DST"): + msg.actions.append(of.ofp_action_nw_addr(type = 7 , nw_addr = IPAddr(action_argu))) + elif(action_name == "SET_TP_SRC"): + msg.actions.append(of.ofp_action_tp_port(type = 9 , tp_port = int(action_argu))) + elif(action_name == "SET_TP_DST"): + msg.actions.append(of.ofp_action_tp_port(type = 10 , tp_port = int(action_argu))) def _record_rules(self,dpid,msg): + self._record_rules_dict['dpid'] = dpid self._record_rules_dict['msg'] = msg self._record_rules_list.append(self._record_rules_dict) + def check_barrierin(self): + global add_success,mod_success,del_success -# def _handle_BarrierIn(event): -# global Addxid,Modifyxid,Deletexid -# if Addxid == event.xid: -# print "=== add event ===\n",event -# elif Modifyxid == event.xid -# print "=== modify event ===\n",event -# elif Deletexid == event.xid -# print "=== delete event ===\n",event - + if add_success: + add_success = False + return True + elif mod_success: + mod_success = False + return True + elif del_success: + del_success = False + return True + else: + return False + +def _handle_BarrierIn(event): + global add_success,mod_success,del_success + + if Barrier_Addxid == event.xid: + add_success = True + elif Barrier_Modifyxid == event.xid: + mod_success = True + elif Barrier_Deletexid == event.xid: + del_success = True def _raise_ReplyEvent(self): self.raiseEvent(ReplyEvent, reply) # pass - - - """reply status for deletion or modification or addition success or failure""" - - - - - def launch (): core.registerNew(flow_modify) - # core.openflow.addListenerByName("BarrierIn", _handle_BarrierIn) \ No newline at end of file + core.openflow.addListenerByName("BarrierIn", _handle_BarrierIn) diff --git a/adapter/pox/omniui/flow_stats.py b/adapter/pox/omniui/flow_stats.py index 19f9b24..779c8cf 100644 --- a/adapter/pox/omniui/flow_stats.py +++ b/adapter/pox/omniui/flow_stats.py @@ -118,8 +118,8 @@ def _handle_flowstats_received (event): translate wildcards' 8~13 bit to IP SrcMask 14~19 bit to IP DstMask """ - flows['srcIPMask'] = 32 - (f.match.wildcards & 0X3f00) - flows['dstIPMask'] = 32 - (f.match.wildcards & 0xfc000) + flows['srcIPMask'] = 32 - ((f.match.wildcards & 0x3f00) >> 8) + flows['dstIPMask'] = 32 - ((f.match.wildcards & 0xfc000) >> 14) flows['dstIP'] = str(f.match.nw_dst) @@ -136,63 +136,66 @@ def _handle_flowstats_received (event): flows['ingressPort'] = f.match.in_port flows['dstMac'] = str(f.match.dl_dst) - # print "\n",f.actions[0].type - actions_dict['type'] = ofp_action_type(f.actions[0].type) - - # print "actions_dict\n",actions_dict - - if actions_dict['type'] == "OUTPUT": - actions_dict['value'] = f.actions[0].port + for action in f.actions: + + actions_dict = {} + + actions_dict['type'] = ofp_action_type(action.type) + + if actions_dict['type'] == "OUTPUT": + actions_dict['value'] = action.port - elif actions_dict['type'] == "SET_VLAN_VID": - actions_dict['value'] = f.actions[0].vlan_vid + elif actions_dict['type'] == "SET_VLAN_VID": + actions_dict['value'] = action.vlan_vid - elif actions_dict['type'] == "SET_VLAN_PCP": - actions_dict['value'] = f.actions[0].vlan_pcp + elif actions_dict['type'] == "SET_VLAN_PCP": + actions_dict['value'] = action.vlan_pcp - elif actions_dict['type'] == "STRIP_VLAN": - actions_dict['value'] = "no_return_value" + #elif actions_dict['type'] == "STRIP_VLAN": + #actions_dict['value'] = "no_return_value" - elif actions_dict['type'] == "SET_DL_SRC": - actions_dict['value'] = str(f.actions[0].dl_addr) + elif actions_dict['type'] == "SET_DL_SRC": + actions_dict['value'] = str(action.dl_addr) - elif actions_dict['type'] == "SET_DL_DST": - actions_dict['value'] = str(f.actions[0].dl_addr) - - elif actions_dict['type'] == "SET_NW_SRC": - actions_dict['value'] = str(f.actions[0].nw_addr) + elif actions_dict['type'] == "SET_DL_DST": + actions_dict['value'] = str(action.dl_addr) - elif actions_dict['type'] == "SET_NW_DST": - actions_dict['value'] = str(f.actions[0].nw_addr) + elif actions_dict['type'] == "SET_NW_SRC": + actions_dict['value'] = str(action.nw_addr) - elif actions_dict['type'] == "SET_NW_TOS": - actions_dict['value'] = str(f.actions[0].nw_tos) - + elif actions_dict['type'] == "SET_NW_DST": + actions_dict['value'] = str(action.nw_addr) - elif actions_dict['type'] == "SET_TP_SRC": - actions_dict['value'] = f.actions[0].tp_port - - elif actions_dict['type'] == "SET_TP_DST": - actions_dict['value'] = f.actions[0].tp_port - - elif actions_dict['type'] == "ENQUEUE": + elif actions_dict['type'] == "SET_NW_TOS": + actions_dict['value'] = str(action.nw_tos) - tmp=[] - tmp.append(f.actions[0].port) - tmp.append(f.actions[0].queue_id) - actions_dict['value'] = tmp + elif actions_dict['type'] == "SET_TP_SRC": + actions_dict['value'] = action.tp_port + elif actions_dict['type'] == "SET_TP_DST": + actions_dict['value'] = action.tp_port + elif actions_dict['type'] == "ENQUEUE": + tmp=[] + tmp.append(action.port) + tmp.append(action.queue_id) + actions_dict['value'] = tmp - actions.append(actions_dict) + actions.append(actions_dict) flows['actions'] = actions flows['priority'] = f.priority flows['srcIP'] = str(f.match.nw_src) flows['vlan'] = f.match.dl_vlan + flows['vlanP'] = f.match.dl_vlan_pcp + if flows['vlanP']==None: + flows['vlanP']=0 flows['counterPacket'] = f.packet_count + flows['tosBits'] = f.match.nw_tos; + if flows['tosBits']==None: + flows['tosBits']=0 if f.match.tp_dst == None : flows['dstPort'] = 0 diff --git a/adapter/pox/omniui/upload_webcore.py b/adapter/pox/omniui/upload_webcore.py index 55e92bb..aefbd9d 100644 --- a/adapter/pox/omniui/upload_webcore.py +++ b/adapter/pox/omniui/upload_webcore.py @@ -79,13 +79,6 @@ json_switches = [] payload = "null" - - - - - - - def _setAttribs (parent, child): attrs = ['command', 'request_version', 'close_connection', 'raw_requestline', 'requestline', 'path', 'headers', 'wfile', @@ -189,7 +182,6 @@ def do_content (self, is_get): core.flow_stats._send_ofp_stats_request() #invoke request function in flow_stats component self.wfile.write(json_switches) elif self.path == "/wm/omniui/add/json": - global payload l = self.headers.get("Content-Length", "") if l == "": @@ -197,14 +189,14 @@ def do_content (self, is_get): else: data = json.loads(self.rfile.read(int(l))) payload = data - # print "payload====\n",payload core.FlowModEvent_Generator._raise_FlowModEvent() - - - - + time.sleep(5) + get_barrierin = core.flow_modify.check_barrierin() + if get_barrierin: + self.send_response(200, "OK") + else: + self.send_response(200, "NO GET Barrier Response") else: - # print "path",self.path self.send_error(404, "File not found on CoreHandler") def send_favicon (self, is_get = False): @@ -417,7 +409,6 @@ def handle_one_request(self): while True: for m in self.server.matches: if self.path.startswith(m[0]): - #print m,self.path handler = m[1](self, m[0], m[3]) #pb = self.rec.getPlayback() #handler = m[1](pb, *self.args[1:]) @@ -511,19 +502,41 @@ def _handle_openflow_discovery_LinkEvent(self,event): link = event.link link_dict ={} - sw1 = str(link).split()[0] - sw2 = str(link).split()[2] - + sw0 = str(link).split('(')[1] + sw0 = sw0.split(')')[0] + sw0 = sw0.split(', ') + sw1_id = hex(int(sw0[0].split('=')[1])).split('x')[1] + sw1_pr = sw0[1].split('=')[1] + sw2_id = hex(int(sw0[2].split('=')[1])).split('x')[1] + sw2_pr = sw0[3].split('=')[1] + """ - add "00:00:" and replace '-' to ':' + change dpid=xx to dpid=00:00:00:00:00:00:00:xx """ - link_dict['src-switch'] = "00:00:" + sw1.split('.')[0].replace('-',':') #dpid1 - link_dict['src-port'] = sw1.split('.')[1] #port1 - link_dict['dst-switch'] = "00:00:" + sw2.split('.')[0].replace('-',':') #dpid2 - link_dict['dst-port'] = sw2.split('.')[1] #port2 + link_dict['src-switch'] = self.change_switch_id_format(sw1_id) #dpid1 + link_dict['src-port'] = sw1_pr #port1 + link_dict['dst-switch'] = self.change_switch_id_format(sw2_id) #dpid2 + link_dict['dst-port'] = sw2_pr #port2 self.links.append(link_dict) + def change_switch_id_format (self, dpid): + pre_id = "" + id_len = len(dpid) + double = id_len/2 + leave = id_len%2 + for i in range(8-double-leave): + pre_id = pre_id + "00:" + if leave == 1: + dpid = '0' + dpid + id_len = len(dpid) + for i in range(id_len): + pre_id = pre_id + dpid[i] + if i%2 != 0: + pre_id = pre_id + ":" + id_len = len(pre_id) + dpid = pre_id[:id_len-1] + return dpid def _handle_GoingUpEvent (self, event): self.listenTo(core.flow_stats) @@ -540,7 +553,7 @@ def _json_format_translation(self): global json_switches global json_links - if self.links and self.ports and self.flows: + if self.ports and self.flows: file_ports = [] file_flows = [] @@ -548,43 +561,34 @@ def _json_format_translation(self): file_links = [] file_nodes_dict = {} - - for l1 in self.links: if file_links : for l2 in file_links : if l1['src-port'] != l2['src-port'] and l1['src-switch'] != l2['src-switch'] and l1['dst-port'] != l2['dst-port'] and l1['dst-switch'] != l2['dst-switch'] : - if l1['src-port'] != l2['dsr-port'] and l1['src-switch'] != l2['dst-switch'] and l1['dst-port'] != l2['src-port'] and l1['src-switch'] != l2['dst-switch'] : + if not(l1['src-port'] == l2['dst-port'] and l1['src-switch'] == l2['dst-switch'] and l1['dst-port'] == l2['src-port'] and l1['src-switch'] == l2['dst-switch']) : file_links.append(l1) if l1['src-switch'] not in dpid_used : dpid_used.append(l1['src-switch']) if l1['dst-switch'] not in dpid_used : dpid_used.append(l1['dst-switch']) - else : file_links.append(l1) dpid_used.append(l1['src-switch']) dpid_used.append(l1['dst-switch']) - for f_index in self.flows: for p_index in self.ports: if p_index['dpid'] == f_index['dpid'] : if f_index['dpid'] not in dpid_cannot_use : dpid_cannot_use.append(p_index['dpid']) - - for p_index_i in p_index['ports']: file_ports.append(p_index_i) for f_index_i in f_index['flows']: file_flows.append(f_index_i) - - p_temp = file_ports[:] f_temp = file_flows[:] - file_nodes_dict['ports'] = p_temp file_nodes_dict['flows'] = f_temp file_nodes_dict['dpid'] = p_index['dpid'] @@ -610,8 +614,6 @@ def _json_format_translation(self): json_switches = json.dumps(file_nodes[:]) json_links = json.dumps(file_links[:]) - print json_links - dpid_cannot_use[:] = [] dpid_used[:] = [] diff --git a/adapter/ryu/README.md b/adapter/ryu/README.md index 0b54009..2d2cf7a 100644 --- a/adapter/ryu/README.md +++ b/adapter/ryu/README.md @@ -8,16 +8,46 @@ $ `git clone git://github.com/osrg/ryu.git` $ `cd ryu; sudo python ./setup.py install` ###Execution### +<<<<<<< HEAD +**Ryu 1.0** + +======= +>>>>>>> cc14ed5e79713b94a9db5ca79711229fc6fc5f47 1. Compile OmniUI core $ `cd ~; python ~/OmniUI/core/core.py` **Successful compilation starts the OmniUI core** 2. Run the Ryu Controller (Using a 2nd Terminal) -$ `PYTHONPATH=. ~/ryu/bin/ryu-manager --observe-links ~/OmniUI/adapter/ryu/omniui/omniui.py ~/ryu/ryu/app/simple_switch.py` +<<<<<<< HEAD +$ `ryu-manager --observe-links ~/OmniUI/adapter/ryu/omniui/omniui.py ~/OmniUI/adapter/ryu/omniui/simple_switch.py` +======= +$ `PYTHONPATH=. ~/ryu/bin/ryu-manager --observe-links ~/OmniUI/adapter/ryu/omniui/omniui.py ~/OmniUI/adapter/ryu/omniui/simple_switch_13.py` +>>>>>>> cc14ed5e79713b94a9db5ca79711229fc6fc5f47 3. Run a simple Mininet topology (Using a 3rd Terminal) $ `sudo mn --topo single,3 --mac --switch ovsk --controller remote` 4. Start-up OmniUI Web UI (Using a 4th Terminal) $ `firefox ~/OmniUI/webui/index.html` +<<<<<<< HEAD + + +**Ryu 1.3 (without web UI)** + +1. Initiating the Ryu controller, Ryu app, OmniUI adapter and Simple Switch 1.3 application
+$ `ryu-manager --observe-links ~/OmniUI/adapter/ryu/omniui/omniui.py ~/OmniUI/adapter/ryu/omniui/simple_switch_13.py` + +2. Emulate 1 switch with 3 hosts +$ `sudo mn --topo single,3 --mac --controller remote --switch ovsk,protocols=OpenFlow13` + +3. Make a switch supports OF 1.3 +$ `sudo ovs-vsctl set bridge s1 protocols=OpenFlow13` + +4. An example of adding flow +$ `curl -X POST -d '{"command": "ADD","switch": "00:00:00:00:00:00:00:01","idleTimeout": "3600","hardTimeout": "3600","priority": "1","ingressPort": "1","srcMac": "00:00:00:00:00:01","dstMac": "00:00:00:00:00:02","dlType": "2048","vlan": "0","vlanP": "0","netProtocol": "17","ip_proto": "17","srcIP": "0.0.0.0/0","dstIP": "0.0.0.0/0","srcPort": "0","dstPort": "0","actions": "OUTPUT=2,POP_VLAN","dstIPMask": "0","srcIPMask": "0","active": "true","tosBits": "0"}' http://localhost:8080/wm/omniui/add/json` + +5. Check flow in mininet +$ `dpctl dump-flows -O OpenFlow13` +======= +>>>>>>> cc14ed5e79713b94a9db5ca79711229fc6fc5f47 diff --git a/adapter/ryu/omniui/omniui.py b/adapter/ryu/omniui/omniui.py index bcf4dd8..fb4791b 100644 --- a/adapter/ryu/omniui/omniui.py +++ b/adapter/ryu/omniui/omniui.py @@ -244,6 +244,16 @@ def mod_flow_entry(self, req, **kwargs): else: return Response(status=404) + ryuFlow={} + if dp.ofproto.OFP_VERSION == ofproto_v1_0.OFP_VERSION: + ryuFlow = self.ryuFlow_v1_0(dp, omniFlow) + ofctl_v1_0.mod_flow_entry(dp, ryuFlow, cmd) + elif dp.ofproto.OFP_VERSION == ofproto_v1_3.OFP_VERSION: + ryuFlow = self.ryuFlow_v1_3(dp, omniFlow) + ofctl_v1_3.mod_flow_entry(dp, ryuFlow, cmd) + else: + return Response(status=404) + if dp.ofproto.OFP_VERSION == ofproto_v1_0.OFP_VERSION: ryuFlow = self.ryuFlow_v1_0(dp, omniFlow) ofctl_v1_0.mod_flow_entry(dp, ryuFlow, cmd) @@ -292,12 +302,13 @@ def ryuFlow_v1_0(self, dp, flows): if actions is not None: actions = flows.get('actions').split(',') for act in actions: - action = self.to_action(dp, act) + action = self.to_action_v1_0(dp, act) ryuFlow['actions'].append(action) return ryuFlow - def to_action(self, dp, actions): + # repack 1.0 actions + def to_action_v1_0(self, dp, actions): actions_type = actions.split('=')[0] if actions_type == 'OUTPUT': ryuAction = { @@ -367,6 +378,185 @@ def to_action(self, dp, actions): return ryuAction + # restore to Ryu Openflow v1.3 flow format + def ryuFlow_v1_3(self, dp, omniFlow): + ryuFlow = { + 'cookie': int(omniFlow.get('cookie', 0)), + 'cookie_mask': int(omniFlow.get('cookie_mask', 0)), + 'table_id ': int(omniFlow.get('table_id', 0)), + 'idle_timeout': int(omniFlow.get('idleTimeout', 0)), + 'hard_timeout': int(omniFlow.get('hardTimeout', 0)), + 'priority': int(omniFlow.get('priority', 0)), + 'buffer_id': int(omniFlow.get('buffer_id', dp.ofproto.OFP_NO_BUFFER)), + 'out_port': int(omniFlow.get('out_port', dp.ofproto.OFPP_ANY)), + 'out_group': int(omniFlow.get('out_group', dp.ofproto.OFPG_ANY)), + 'flags': int(omniFlow.get('flags', 0)), + 'match': {}, + 'actions': [] + } + + # convert match field from omniui to ryu + for key in omniFlow: + match = self.to_match_v1_3(dp, key, omniFlow) + if match is not None: + ryuFlow['match'].update(match) + + # handle mutiple actions + acts = omniFlow.get('actions').split(',') + for a in acts: + action = self.to_action_v1_3(dp, a) + if action is not None: + ryuFlow['actions'].append(action) + + return ryuFlow + + # repack 1.3 match + def to_match_v1_3(self, dp, omni_key, omniFlow): + # convert key from omniui to ryu, and change its type + convert = { + 'ingressPort': ['in_port', int], + 'in_phy_port': ['in_phy_port', int], + 'metadata': ['metadata', str], + 'dstMac': ['dl_dst', str], + 'srcMac': ['dl_src', str], + 'eth_dst': ['eth_dst', str], + 'eth_src': ['eth_src', str], + 'dlType': ['dl_type', int], + 'eth_type': ['eth_type', int], + 'vlan': ['dl_vlan', str], + 'vlan_vid': ['vlan_vid', str], + 'vlanP': ['vlan_pcp', int], + 'ip_dscp': ['ip_dscp', int], + 'ip_ecn': ['ip_ecn', int], + 'netProtocol': ['nw_proto', int], + 'ip_proto': ['ip_proto', int], + 'srcIP': ['nw_src', str], + 'dstIP': ['nw_dst', str], + 'ipv4_src': ['ipv4_src', str], + 'ipv4_dst': ['ipv4_dst', str], + 'srcPort': ['tp_src', int], + 'dstPort': ['tp_dst', int], + 'tcp_src': ['tcp_src', int], + 'tcp_dst': ['tcp_dst', int], + 'udp_src': ['udp_src', int], + 'udp_dst': ['udp_dst', int], + 'sctp_src': ['sctp_src', int], + 'sctp_dst': ['sctp_dst', int], + 'icmpv4_type': ['icmpv4_type', int], + 'icmpv4_code': ['icmpv4_code', int], + 'arp_op': ['arp_op', int], + 'arp_spa': ['arp_spa', str], + 'arp_tpa': ['arp_tpa', str], + 'arp_sha': ['arp_sha', str], + 'arp_tha': ['arp_tha', str], + 'ipv6_src': ['ipv6_src', str], + 'ipv6_dst': ['ipv6_dst', str], + 'ipv6_flabel': ['ipv6_flabel', int], + 'icmpv6_type': ['icmpv6_type', int], + 'icmpv6_code': ['icmpv6_code', int], + 'ipv6_nd_target': ['ipv6_nd_target', str], + 'ipv6_nd_sll': ['ipv6_nd_sll', str], + 'ipv6_nd_tll': ['ipv6_nd_tll', str], + 'mpls_label': ['mpls_label', int], + 'mpls_tc': ['mpls_tc', int], + 'mpls_bos': ['mpls_bos', int], + 'pbb_isid': ['pbb_isid', int], + 'tunnel_id': ['tunnel_id', int], + 'ipv6_exthdr': ['ipv6_exthdr', int] + } + + for key, value in convert.items(): + if omni_key == key: + ryuMatch = { + value[0]: value[1](omniFlow.get(omni_key)) + } + return ryuMatch + + return None + + # repack 1.3 actions + def to_action_v1_3(self, dp, dic): + action_type = dic.split('=')[0] + if action_type == 'OUTPUT': + ryuAction = { + 'type': action_type, + 'port': dic.split('=')[1] + } + elif action_type == 'COPY_TTL_OUT': + ryuAction = { + 'type': action_type + } + elif action_type == 'COPY_TTL_IN': + ryuAction = { + 'type': action_type + } + elif action_type == 'SET_MPLS_TTL': + ryuAction = { + 'type': action_type, + 'mpls_ttl': dic.split('=')[1] + } + elif action_type == 'DEC_MPLS_TTL': + ryuAction = { + 'type': action_type + } + elif action_type == 'PUSH_VLAN': + ryuAction = { + 'type': action_type, + 'ethertype': dic.split('=')[1] + } + elif action_type == 'POP_VLAN': + ryuAction = { + 'type': action_type + } + elif action_type == 'PUSH_MPLS': + ryuAction = { + 'type': action_type, + 'ethertype': dic.split('=')[1] + } + elif action_type == 'POP_MPLS': + ryuAction = { + 'type': action_type, + 'ethertype': dic.split('=')[1] + } + elif action_type == 'SET_QUEUE': + ryuAction = { + 'type': action_type, + 'queue_id': dic.split('=')[1] + } + elif action_type == 'GROUP': + ryuAction = { + 'type': action_type, + 'group_id': dic.split('=')[1] + } + elif action_type == 'SET_NW_TTL': + ryuAction = { + 'type': action_type, + 'nw_ttl': dic.split('=')[1] + } + elif action_type == 'DEC_NW_TTL': + ryuAction = { + 'type': action_type + } + elif action_type == 'SET_FIELD': + ryuAction = { + 'type': action_type, + 'field': dic.split('=')[1].split(':')[0], + 'value': dic.split('=')[1].split(':')[1] + } + elif action_type == 'PUSH_PBB': + ryuAction = { + 'type': action_type, + 'ethertype': dic.split('=')[1] + } + elif action_type == 'POP_PBB': + ryuAction = { + 'type': action_type + } + else: + ryuAction = None + + return ryuAction + # restore Ryu-format dpid def nospaceDPID(self, dpid): return "".join(dpid) diff --git a/adapter/ryu/omniui/simple_switch.py b/adapter/ryu/omniui/simple_switch.py new file mode 100644 index 0000000..0970bb0 --- /dev/null +++ b/adapter/ryu/omniui/simple_switch.py @@ -0,0 +1,110 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +An OpenFlow 1.0 L2 learning switch implementation. +""" + +import logging +import struct + +from ryu.base import app_manager +from ryu.controller import mac_to_port +from ryu.controller import ofp_event +from ryu.controller.handler import MAIN_DISPATCHER +from ryu.controller.handler import set_ev_cls +from ryu.ofproto import ofproto_v1_0 +from ryu.lib.mac import haddr_to_bin +from ryu.lib.packet import packet +from ryu.lib.packet import ethernet + + +class SimpleSwitch(app_manager.RyuApp): + OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION] + + def __init__(self, *args, **kwargs): + super(SimpleSwitch, self).__init__(*args, **kwargs) + self.mac_to_port = {} + + def add_flow(self, datapath, in_port, dst, actions): + ofproto = datapath.ofproto + + match = datapath.ofproto_parser.OFPMatch( + in_port=in_port, dl_dst=haddr_to_bin(dst)) + + mod = datapath.ofproto_parser.OFPFlowMod( + datapath=datapath, match=match, cookie=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=ofproto.OFP_DEFAULT_PRIORITY, + flags=ofproto.OFPFF_SEND_FLOW_REM, actions=actions) + datapath.send_msg(mod) + + @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) + def _packet_in_handler(self, ev): + msg = ev.msg + datapath = msg.datapath + ofproto = datapath.ofproto + + pkt = packet.Packet(msg.data) + eth = pkt.get_protocol(ethernet.ethernet) + # Ignore the LLDP packet + if eth.ethertype == 35020: + return + dst = eth.dst + src = eth.src + + dpid = datapath.id + self.mac_to_port.setdefault(dpid, {}) + + self.logger.info("packet in %s %s %s %s", dpid, src, dst, msg.in_port) + + # learn a mac address to avoid FLOOD next time. + self.mac_to_port[dpid][src] = msg.in_port + + if dst in self.mac_to_port[dpid]: + out_port = self.mac_to_port[dpid][dst] + else: + out_port = ofproto.OFPP_FLOOD + + actions = [datapath.ofproto_parser.OFPActionOutput(out_port)] + + # install a flow to avoid packet_in next time + if out_port != ofproto.OFPP_FLOOD: + self.add_flow(datapath, msg.in_port, dst, actions) + + data = None + if msg.buffer_id == ofproto.OFP_NO_BUFFER: + data = msg.data + + out = datapath.ofproto_parser.OFPPacketOut( + datapath=datapath, buffer_id=msg.buffer_id, in_port=msg.in_port, + actions=actions, data=data) + datapath.send_msg(out) + + @set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER) + def _port_status_handler(self, ev): + msg = ev.msg + reason = msg.reason + port_no = msg.desc.port_no + + ofproto = msg.datapath.ofproto + if reason == ofproto.OFPPR_ADD: + self.logger.info("port added %s", port_no) + elif reason == ofproto.OFPPR_DELETE: + self.logger.info("port deleted %s", port_no) + elif reason == ofproto.OFPPR_MODIFY: + self.logger.info("port modified %s", port_no) + else: + self.logger.info("Illeagal port state %s %s", port_no, reason) diff --git a/adapter/ryu/omniui/simple_switch_13.py b/adapter/ryu/omniui/simple_switch_13.py new file mode 100644 index 0000000..cf1c2b3 --- /dev/null +++ b/adapter/ryu/omniui/simple_switch_13.py @@ -0,0 +1,120 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ryu.base import app_manager +from ryu.controller import ofp_event +from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER +from ryu.controller.handler import set_ev_cls +from ryu.ofproto import ofproto_v1_3 +from ryu.lib.packet import packet +from ryu.lib.packet import ethernet + + +class SimpleSwitch13(app_manager.RyuApp): + OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] + + def __init__(self, *args, **kwargs): + super(SimpleSwitch13, self).__init__(*args, **kwargs) + self.mac_to_port = {} + + @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) + def switch_features_handler(self, ev): + datapath = ev.msg.datapath + ofproto = datapath.ofproto + parser = datapath.ofproto_parser + + # install table-miss flow entry + # + # We specify NO BUFFER to max_len of the output action due to + # OVS bug. At this moment, if we specify a lesser number, e.g., + # 128, OVS will send Packet-In with invalid buffer_id and + # truncated packet data. In that case, we cannot output packets + # correctly. The bug has been fixed in OVS v2.1.0. + match = parser.OFPMatch() + actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, + ofproto.OFPCML_NO_BUFFER)] + self.add_flow(datapath, 0, match, actions) + + def add_flow(self, datapath, priority, match, actions, buffer_id=None): + ofproto = datapath.ofproto + parser = datapath.ofproto_parser + + inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, + actions)] + if buffer_id: + mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id, + priority=priority, match=match, + instructions=inst) + else: + mod = parser.OFPFlowMod(datapath=datapath, priority=priority, + match=match, instructions=inst) + datapath.send_msg(mod) + + @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) + def _packet_in_handler(self, ev): + # If you hit this you might want to increase + # the "miss_send_length" of your switch + if ev.msg.msg_len < ev.msg.total_len: + self.logger.debug("packet truncated: only %s of %s bytes", + ev.msg.msg_len, ev.msg.total_len) + msg = ev.msg + datapath = msg.datapath + ofproto = datapath.ofproto + parser = datapath.ofproto_parser + in_port = msg.match['in_port'] + + pkt = packet.Packet(msg.data) + eth = pkt.get_protocols(ethernet.ethernet)[0] + + # Ignore the LLDP packet + if eth.ethertype == 35020: + return + + + dst = eth.dst + src = eth.src + + dpid = datapath.id + self.mac_to_port.setdefault(dpid, {}) + + self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port) + + # learn a mac address to avoid FLOOD next time. + self.mac_to_port[dpid][src] = in_port + + if dst in self.mac_to_port[dpid]: + out_port = self.mac_to_port[dpid][dst] + else: + out_port = ofproto.OFPP_FLOOD + + actions = [parser.OFPActionOutput(out_port)] + + # install a flow to avoid packet_in next time + if out_port != ofproto.OFPP_FLOOD: + match = parser.OFPMatch(in_port=in_port, eth_dst=dst) + # verify if we have a valid buffer_id, if yes avoid to send both + # flow_mod & packet_out + if msg.buffer_id != ofproto.OFP_NO_BUFFER: + self.add_flow(datapath, 1, match, actions, msg.buffer_id) + return + else: + self.add_flow(datapath, 1, match, actions) + data = None + if msg.buffer_id == ofproto.OFP_NO_BUFFER: + data = msg.data + + out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id, + in_port=in_port, actions=actions, data=data) + datapath.send_msg(out) diff --git a/core/config.json b/core/config.json index ad29112..7b3c1c6 100644 --- a/core/config.json +++ b/core/config.json @@ -1,17 +1,17 @@ { - "ControllerType":"trema", + "ControllerType":"floodlight", "LogFile": "core.log", "REST": { - "ip": "localhost", + "ip": "192.168.1.159", "port": "5567" }, "UIPusher": { "enable":"True", - "dbip":"140.113.215.200", + "dbip":"127.0.0.1", "dbport":"27017", "db":"omniui", - "user":"xxxxxx", - "password":"xxxxxx", + "user":"sdn", + "password":"sdn", "queryinterval":"5" }, "ControllerAdapter": { @@ -25,11 +25,11 @@ }, "DbCollection": { "enable":"True", - "dbip":"140.113.215.200", + "dbip":"127.0.0.1", "dbport":"27017", "db":"omniui", - "user":"xxxxxx", - "password":"xxxxxx", + "user":"sdn", + "password":"sdn", "interval": "86400" } } diff --git a/core/etc/config.json b/core/etc/config.json new file mode 100644 index 0000000..792587f --- /dev/null +++ b/core/etc/config.json @@ -0,0 +1,40 @@ +{ + "ControllerType":"floodlight", + "LogFile": "core.log", + "REST": { + "ip": "192.168.1.159", + "port": "5567" + }, + "UIPusher": { + "enable":"True", + "dbip":"127.0.0.1", + "dbport":"27017", + "db":"omniui", + "user":"sdn", + "password":"sdn", + "queryinterval":"5" + }, + "ControllerAdapter": { + "ip": "localhost", + "port": "8080", + "interval": "5" + }, + "Flow_mod":{ + "ip":"localhost", + "port":"8080" + }, + "BusyLink_Detect": { + "ip": "localhost", + "port": "8080", + "interval": "5" + }, + "DbCollection": { + "enable":"True", + "dbip":"127.0.0.1", + "dbport":"27017", + "db":"omniui", + "user":"sdn", + "password":"sdn", + "interval": "86400" + } +} diff --git a/core/setup.py b/core/setup.py new file mode 100644 index 0000000..1d64ccc --- /dev/null +++ b/core/setup.py @@ -0,0 +1,16 @@ +from setuptools import setup + +setup( + name = 'omniui', + version = '1.0.0', + description = 'A Diagnosis, Analytic and Management Framework for SDN', + author = 'D-Link NCTU Joint Research Center', + url = 'https://github.com/dlinknctu/omniui', + install_requires = ['Flask==0.10.1', 'Flask_Cors', 'gevent', 'pymongo'], + packages = ['src', 'src.floodlight_modules','src.trema_modules','src.pox_modules', 'test'], + data_files = [('etc', ['etc/config.json'])], + entry_points = { + 'console_scripts': ['omniui=src.core:main'], + }, + test_suite = 'test' +) \ No newline at end of file diff --git a/core/src/__init__.py b/core/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/core.py b/core/src/core.py similarity index 94% rename from core/core.py rename to core/src/core.py index a007c48..82c2709 100644 --- a/core/core.py +++ b/core/src/core.py @@ -12,6 +12,8 @@ from threading import Thread from flask import Flask, Response, request, abort, render_template from flask_cors import * +from pkg_resources import Requirement, resource_filename +rootPath = resource_filename(Requirement.parse("omniui"),"") app = Flask(__name__) app.config['CORS_ORIGINS'] = ['http://localhost'] @@ -64,12 +66,14 @@ def __init__(self): restHandlers = {} global sseHandlers sseHandlers = {} + + def start(self): #Load config file - with open(os.path.join(sys.path[0],'config.json'),'r') as input: + with open(os.path.join(rootPath, 'etc/config.json'),'r') as input: data = input.read() config = json.loads(data) #Default values - logFile = 'log.txt' + logFile = '/tmp/omniui.log' logLevel = logging.ERROR restIP = 'localhost' restPort = 5567 @@ -92,8 +96,7 @@ def __init__(self): #Loading module sys.modules['plugins'] = plugins = type(sys)('plugins') plugins.__path__ = [] - plugins.__path__.append (os.path.join(sys.path[0],ControllerType)) - + plugins.__path__.append (os.path.join(rootPath, 'src', ControllerType)) for module in config: if module != "LogFile" and module != "REST" and module != "ControllerType" : # load modules other than LogFile and REST @@ -240,6 +243,8 @@ def kill(self): def main(): Watcher() - Core() + coreInstance = Core() + coreInstance.start() + if __name__ == "__main__": main() diff --git a/core/src/floodlight_modules/busylink_detect.py b/core/src/floodlight_modules/busylink_detect.py new file mode 100644 index 0000000..a42e375 --- /dev/null +++ b/core/src/floodlight_modules/busylink_detect.py @@ -0,0 +1,174 @@ +import httplib +import logging +import json +import core +logger = logging.getLogger(__name__) + +class BusyLink_Detect: + def __init__(self,core,parm): + """ BusyLinkDetect init""" + self.controllerIP = "localhost" + self.controllerPort = "8080" + self.timerInterval = 5 + + self.baseState = 1 + self.finalState = 3 + self.threshold = 0.8 + self.statistics = {} + self.BLD_result = [] + + #load config + if(parm): + if(parm.has_key("ip")): + self.controllerIP = parm["ip"] + if(parm.has_key("port")): + self.controllerPort = parm["port"] + if(parm.has_key("interval")): + self.timerInterval = int(parm["interval"]) + logger.debug('IP =%s port = %s interval = %s' % (self.controllerIP,self.controllerPort,self.timerInterval)) + core.registerEvent("periodicQuery",self.periodicQuery,self.timerInterval) + core.registerEventHandler("periodicQuery", self.busyLinkDetect) + + def overthreshold(self,link,id): + link['state'] += 1 + if link['state'] >= self.finalState: + print "%s is busy!!!!!!!!" % id + link['state'] = self.finalState + self.BLD_result.append(id) + #print "overthreshold!!!!!! state = %d" % link['state'] + + def underthreshold(self,link): + link['state'] -= 1 + if link['state'] < self.baseState: + link['state'] = self.baseState + #print "underthreshold!!!!!! state = %d" % link['state'] + + def periodicQuery(self): + self.periodicQueryLink() + self.periodicQueryPort() + + def parsePortFeatures(self,features): + if features == 0: + return 0 + turn_binary = bin(features)[2:] + binary_len = len(turn_binary) + if binary_len < 12: + turn_binary = '0'*(12-binary_len) + turn_binary + + if turn_binary[5] == '1': + return 10*(1024**3)/8.0 #10Gb + if turn_binary[6] == '1' or turn_binary[7] == '1': + return 1024**3/8.0 #1Gb + if turn_binary[8] == '1' or turn_binary[9] == '1': + return 100*(1024**2)/8.0 #100Mb + if turn_binary[10] == '1' or turn_binary[11] == '1': + return 10*(1024**2)/8.0 #10Mb + return 0 + + def queryLinkCapacity(self): + try: + conn = httplib.HTTPConnection(self.controllerIP, int(self.controllerPort)) + conn.request("GET", "/wm/core/switch/all/features/json") + response = conn.getresponse().read() + except Exception, e: + logger.error("connection error for inquiring features: "+str(e)) + return + finally: + conn.close() + try: + data = json.loads(response) + self.capacity = {} + for switch_id in data: + switch = data[switch_id] + ports = switch['ports'] + for port in ports: + result = self.parsePortFeatures(port['currentFeatures']) + self.capacity["%s_%d" % (switch_id,port['portNumber'])] = result + except Exception, e: + logger.error("json parse error for features: "+str(e)) + + def periodicQueryLink(self): + try: + conn = httplib.HTTPConnection(self.controllerIP, int(self.controllerPort)) + conn.request("GET", "/wm/omniui/link/json") + response = conn.getresponse().read() + except Exception, e: + logger.error("connection error for inquiring links: "+str(e)) + return + finally: + conn.close() + try: + data = json.loads(response) + self.links = {} + for link in data: + tmp = {} + tmp['source'] = link['src-switch'] + tmp['target'] = link['dst-switch'] + tmp['sourcePort'] = link['src-port'] + tmp['targetPort'] = link['dst-port'] + id = "dpid %s, port %s -- dpid %s, port %s" % (tmp['source'],tmp['sourcePort'],tmp['target'],tmp['targetPort']) + self.links[id] = tmp + except Exception, e: + logger.error("json parse error for links: "+str(e)) + self.queryLinkCapacity() + + def periodicQueryPort(self): + try: + conn = httplib.HTTPConnection(self.controllerIP, int(self.controllerPort)) + conn.request("GET", "/wm/omniui/switch/json") + response = conn.getresponse().read() + except Exception, e: + logger.error("connection error for inquiring switches: "+str(e)) + return + finally: + conn.close() + try: + data = json.loads(response) + self.switches= {} + for switch in data: + tmp = {} + for port in switch['ports']: + tmp[port['PortNumber']] = port['recvBytes'] + self.switches[switch['dpid']] = tmp + except Exception, e: + logger.error("json parse error for switch: "+str(e)) + + def busyLinkDetect(self,event): + self.BLD_result = [] + #calculate link's countBytes and capacity + for link_id in self.links: + link = self.links[link_id] + src = link['source'] + srcp = link['sourcePort'] + dest = link['target'] + destp = link['targetPort'] + total_bytes = self.switches[src][srcp] + self.switches[dest][destp] + link['countBytes'] = total_bytes + link['capacity'] = min(self.capacity["%s_%d" % (src,srcp)],self.capacity["%s_%d" % (dest,destp)]) + + #initialize self.statistics value + if len(self.statistics) == 0: + self.statistics = dict(self.links) + for link_id in self.statistics: + link = self.statistics[link_id] + link['state'] = self.baseState + + #check threshold + for link_id in self.links: + if link_id in self.statistics: + if (self.links[link_id]['countBytes'] - self.statistics[link_id]['countBytes']) / self.statistics[link_id]['capacity'] >= self.threshold: + self.overthreshold(self.statistics[link_id],link_id) + else: + self.underthreshold(self.statistics[link_id]) + self.statistics[link_id]['countBytes'] = self.links[link_id]['countBytes'] + else: + self.statistics[link_id] = dict(self.links[link_id]) + self.statistics[link_id]['state'] = self.baseState + #remove unexisted link info + for link_id in self.statistics: + if link_id not in self.links: + del self.statistics[link_id] + + #return result + if len(self.BLD_result)>0: + print self.BLD_result diff --git a/core/floodlight_modules/controlleradapter.py b/core/src/floodlight_modules/controlleradapter.py similarity index 99% rename from core/floodlight_modules/controlleradapter.py rename to core/src/floodlight_modules/controlleradapter.py index 38f1355..c8570e4 100644 --- a/core/floodlight_modules/controlleradapter.py +++ b/core/src/floodlight_modules/controlleradapter.py @@ -2,7 +2,6 @@ import logging import json from threading import Thread -import core logger = logging.getLogger(__name__) class ControllerAdapter: diff --git a/core/floodlight_modules/dbcollection.py b/core/src/floodlight_modules/dbcollection.py similarity index 99% rename from core/floodlight_modules/dbcollection.py rename to core/src/floodlight_modules/dbcollection.py index ba5545f..151c0f8 100644 --- a/core/floodlight_modules/dbcollection.py +++ b/core/src/floodlight_modules/dbcollection.py @@ -1,5 +1,3 @@ - -from core import Core from pymongo import MongoClient import datetime,time,calendar diff --git a/core/floodlight_modules/flow_mod.py b/core/src/floodlight_modules/flow_mod.py similarity index 98% rename from core/floodlight_modules/flow_mod.py rename to core/src/floodlight_modules/flow_mod.py index df78d4e..a8b41e2 100644 --- a/core/floodlight_modules/flow_mod.py +++ b/core/src/floodlight_modules/flow_mod.py @@ -1,6 +1,5 @@ import httplib import json -import core class Flow_mod: diff --git a/core/floodlight_modules/uipusher.py b/core/src/floodlight_modules/uipusher.py similarity index 99% rename from core/floodlight_modules/uipusher.py rename to core/src/floodlight_modules/uipusher.py index 6c096dc..7586b85 100644 --- a/core/floodlight_modules/uipusher.py +++ b/core/src/floodlight_modules/uipusher.py @@ -1,5 +1,4 @@ import logging -from core import Core from pymongo import MongoClient import json from bson import json_util diff --git a/core/pox_modules/controlleradapter.py b/core/src/pox_modules/controlleradapter.py similarity index 99% rename from core/pox_modules/controlleradapter.py rename to core/src/pox_modules/controlleradapter.py index 38f1355..21ddd2a 100644 --- a/core/pox_modules/controlleradapter.py +++ b/core/src/pox_modules/controlleradapter.py @@ -2,9 +2,9 @@ import logging import json from threading import Thread -import core logger = logging.getLogger(__name__) + class ControllerAdapter: def __init__(self,core,parm): """ ControllerAdapter init""" diff --git a/core/pox_modules/dbcollection.py b/core/src/pox_modules/dbcollection.py similarity index 99% rename from core/pox_modules/dbcollection.py rename to core/src/pox_modules/dbcollection.py index ba5545f..e14c3bb 100644 --- a/core/pox_modules/dbcollection.py +++ b/core/src/pox_modules/dbcollection.py @@ -1,8 +1,7 @@ - -from core import Core from pymongo import MongoClient import datetime,time,calendar + class DbCollection: def __init__(self,core,parm): # membeRs diff --git a/core/pox_modules/flow_mod.py b/core/src/pox_modules/flow_mod.py similarity index 80% rename from core/pox_modules/flow_mod.py rename to core/src/pox_modules/flow_mod.py index 32130fa..a8b41e2 100644 --- a/core/pox_modules/flow_mod.py +++ b/core/src/pox_modules/flow_mod.py @@ -1,6 +1,5 @@ import httplib import json -import core class Flow_mod: @@ -10,16 +9,16 @@ def __init__(self,core,parm): self.Port = "8080" self.path = '/wm/omniui/add/json' self.headers = { - 'Content-type': 'application/json', - 'Accept': 'application/json', - } + 'Content-type': 'application/json', + 'Accept': 'application/json', + } # register rest api self.Url="http://"+self.IP+":"+self.Port+"/wm/omniui/add/json" core.registerRestApi("flowmod", self.flowHandler) def flowHandler(self,data): # return JSONP format - body = data + body = json.dumps(data.get_json(force=True)) conn = httplib.HTTPConnection(self.IP,self.Port) conn.request('POST',self.path,body,self.headers) response = conn.getresponse() diff --git a/core/pox_modules/uipusher.py b/core/src/pox_modules/uipusher.py similarity index 92% rename from core/pox_modules/uipusher.py rename to core/src/pox_modules/uipusher.py index 6f241ce..7586b85 100644 --- a/core/pox_modules/uipusher.py +++ b/core/src/pox_modules/uipusher.py @@ -1,5 +1,4 @@ import logging -from core import Core from pymongo import MongoClient import json from bson import json_util @@ -15,6 +14,8 @@ def __init__(self,core,parm): # register rest api core.registerRestApi("info/topology", self.topologyHandler) core.registerRestApi("stat", self.statisticHandler) + # register sse handler + core.registerSSEHandler('updatetopo', self.topoHandler) # save core for ipc use self.core = core @@ -36,7 +37,12 @@ def __init__(self,core,parm): except: print "database connection failed" - def topologyHandler(self, request): + def topoHandler(self): + # return JSON format + result = self.core.invokeIPC("periodicInquiry") + return result + + def topologyHandler(self,request): # return JSONP format result = self.core.invokeIPC("periodicInquiry") return "omniui(%s);" % result @@ -57,9 +63,15 @@ def controllerHandler(self,event): key.pop("counterByte",None) key.pop("counterPacket",None) key.pop("duration",None) - key['actions'] = "".join(["{0}:{1}".format(dic['type'],dic['value']) for dic in key['actions']]) + for dic in key['actions']: + if dic['type'] == "STRIP_VLAN": + key['actions'] = "".join(["{0}".format(dic['type'])]) + else: + key['actions'] = "".join(["{0}:{1}".format(dic['type'],dic['value'])]) key['dpid'] = str(node['dpid']) key['date'] = int(now - reduntTime) + if isinstance(key['actions'],list): + del key['actions'] hashkey = frozenset(key.items()) if hashkey in self.cache: if self.diff[hashkey][2] > flow['duration']: @@ -113,8 +125,7 @@ def statisticHandler(self,request): if self.enable == False: return "Time\t1\n" #parse json data - data = json.load(request.body) - + data = request.get_json(force=True) #declare variable multiGroup = {} output = "Time" @@ -199,6 +210,5 @@ def statisticHandler(self,request): if tmpIndex >= len(multiGroup[date]): tmpIndex = 0 output+=tmp+"\n" - print output return output diff --git a/core/trema_modules/controlleradapter.py b/core/src/trema_modules/controlleradapter.py similarity index 99% rename from core/trema_modules/controlleradapter.py rename to core/src/trema_modules/controlleradapter.py index 230d26b..f404744 100644 --- a/core/trema_modules/controlleradapter.py +++ b/core/src/trema_modules/controlleradapter.py @@ -2,11 +2,9 @@ import logging import json from threading import Thread -import core logger = logging.getLogger(__name__) - class ControllerAdapter: def __init__(self,core,parm): #members diff --git a/core/trema_modules/dbcollection.py b/core/src/trema_modules/dbcollection.py similarity index 98% rename from core/trema_modules/dbcollection.py rename to core/src/trema_modules/dbcollection.py index fc6da9d..aec4921 100644 --- a/core/trema_modules/dbcollection.py +++ b/core/src/trema_modules/dbcollection.py @@ -1,7 +1,7 @@ - -from core import Core +#from core import Core from pymongo import MongoClient -import datetime,time,calendar +import datetime, time, calendar + class DbCollection: def __init__(self,core,parm): diff --git a/core/trema_modules/flow_mod.py b/core/src/trema_modules/flow_mod.py similarity index 99% rename from core/trema_modules/flow_mod.py rename to core/src/trema_modules/flow_mod.py index 9856273..809f27b 100644 --- a/core/trema_modules/flow_mod.py +++ b/core/src/trema_modules/flow_mod.py @@ -1,6 +1,5 @@ import httplib import json -import core class Flow_mod: diff --git a/core/trema_modules/uipusher.py b/core/src/trema_modules/uipusher.py similarity index 99% rename from core/trema_modules/uipusher.py rename to core/src/trema_modules/uipusher.py index d0854c0..c46eda7 100644 --- a/core/trema_modules/uipusher.py +++ b/core/src/trema_modules/uipusher.py @@ -1,5 +1,4 @@ import logging -from core import Core from pymongo import MongoClient import json from bson import json_util diff --git a/core/test/__init__.py b/core/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/test/test_core.py b/core/test/test_core.py new file mode 100644 index 0000000..6e1fd08 --- /dev/null +++ b/core/test/test_core.py @@ -0,0 +1,53 @@ +import sys +sys.path.append('src') +import unittest +import core + +class CoreTestCase(unittest.TestCase): + def handler(self): + return + + def setUp(self): + self.coreInstance = core.Core() + + def test_registerRestApi(self): + self.coreInstance.registerRestApi('rest', self.handler) + expected = self.handler + result = core.restHandlers['rest'] + self.assertEquals(expected, result) + + def test_registerSSEHandler(self): + self.coreInstance.registerSSEHandler('sse', self.handler) + expected = self.handler + result = core.sseHandlers['sse'] + self.assertEquals(expected, result) + + def test_registerEventHandler(self): + self.coreInstance.registerEventHandler('event', self.handler) + expected = self.handler + result = None + for handler in self.coreInstance.eventHandlers: + if(handler.eventName == 'event'): + result = handler.handler + self.assertEquals(expected, result) + + def test_registerIPC(self): + self.coreInstance.registerIPC('ipc', self.handler) + expected = self.handler + result = self.coreInstance.ipcHandlers['ipc'] + self.assertEquals(expected, result) + + def test_eventHandler(self): + eventHandlerInstance = core.EventHandler('event', self.handler) + self.assertEquals(eventHandlerInstance.eventName, 'event') + self.assertEquals(eventHandlerInstance.handler, self.handler) + +""" + def registerEvent(self,eventName,generator,interval): + thread = Thread(target=self.iterate, args=(eventName,generator,interval)) + self.threads.append(thread) + thread.start() +""" + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/run-tests.sh b/run-tests.sh index adb5d83..e9d9156 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -1,4 +1,27 @@ #!/bin/bash -cd ui -npm install -npm test +function test_ui { + cd $rootpath/ui + npm install + npm test +} + +function install_core { + cd $rootpath/core + python setup.py install --user +} + +function test_core { + cd $rootpath/core + python setup.py test +} + +function install_floodlight { + cd $rootpath + $rootpath/adapter/floodlight/install.sh +} + +rootpath=`pwd` +test_ui +install_core +test_core +install_floodlight diff --git a/webui/index.html b/webui/index.html index 40291f9..4d548e2 100644 --- a/webui/index.html +++ b/webui/index.html @@ -44,6 +44,7 @@


+



diff --git a/webui/js/flowmod.js b/webui/js/flowmod.js index 8be9c03..28c9dc9 100644 --- a/webui/js/flowmod.js +++ b/webui/js/flowmod.js @@ -34,7 +34,7 @@ $(function() { if($input.eq(i).val() != "") { flow[$(this).text()] = $input.eq(i).val(); } else { - flow[$(this).text()] = defaultFlow[$(this).text()]; + //flow[$(this).text()] = defaultFlow[$(this).text()]; } }); if(!jQuery.isEmptyObject(flow)) { @@ -46,10 +46,15 @@ $(function() { return a.toUpperCase(); }); } - var srcCIDR = flow["srcIP"].split(/\//); - var dstCIDR = flow["dstIP"].split(/\//); - flow["srcIPMask"] = (srcCIDR.length == 2)? srcCIDR[1]: "32"; - flow["dstIPMask"] = (dstCIDR.length == 2)? dstCIDR[1]: "32"; + if("srcIP" in flow){ + var srcCIDR = flow["srcIP"].split(/\//); + flow["srcIPMask"] = (srcCIDR.length == 2)? srcCIDR[1]: "32"; + } + if("dstIP" in flow){ + var dstCIDR = flow["dstIP"].split(/\//); + flow["dstIPMask"] = (dstCIDR.length == 2)? dstCIDR[1]: "32"; + } + if(!("actions" in flow)) flow["actions"] = ""; sendFlow(flow); } $(this).dialog("close"); @@ -71,7 +76,7 @@ $(function() { }).replace(/(strip_vlan)/, function(a) { return a.toUpperCase(); }); - } + }else flow["actions"] = ""; sendFlow(flow); } $(this).dialog("close"); @@ -87,6 +92,7 @@ $(function() { }); if(!jQuery.isEmptyObject(flow)) { flow["command"] = "DEL"; + if(!("actions" in flow)) flow["actions"] = ""; sendFlow(flow); } $(this).dialog("close"); @@ -153,7 +159,12 @@ function modFlow(i) { flow["dstIP"] += ("/" + flow["dstIPMask"]); for(var k in flow) { - flow[k] = flow[k].toString(); + if(flow[k]!=null){ + flow[k] = flow[k].toString(); + var checkNone = flow[k].split(/\//); + if(checkNone[0]=="None") delete flow[k]; + if(flow[k]=="0") delete flow[k]; + }else delete flow[k]; } $("#actions-dialog").data("flow", flow).dialog("open"); @@ -171,7 +182,12 @@ function delFlow(i) { flow["dstIP"] += ("/" + flow["dstIPMask"]); for(var k in flow) { - flow[k] = flow[k].toString(); + if(flow[k]!=null){ + flow[k] = flow[k].toString(); + var checkNone = flow[k].split(/\//); + if(checkNone[0]=="None") delete flow[k]; + if(flow[k]=="0") delete flow[k]; + }else delete flow[k]; } sendFlow(flow); diff --git a/webui/js/match.js b/webui/js/match.js index 9eaa0b8..bf873ef 100644 --- a/webui/js/match.js +++ b/webui/js/match.js @@ -57,7 +57,7 @@ function getflowmsg(f){ } function startmatch(f2,flow2,j2,compare2){ - if(flow2.srcMac!="00:00:00:00:00:00") + if(flow2.srcMac!="00:00:00:00:00:00" && flow2.srcMac!="None") { if("srcMac" in f2) { @@ -69,7 +69,7 @@ function startmatch(f2,flow2,j2,compare2){ return } } - if(flow2.dstMac!="00:00:00:00:00:00") + if(flow2.dstMac!="00:00:00:00:00:00" && flow2.dstMac!="None") { if("dstMac" in f2){ console.log("HAVE DSTMAC"); @@ -80,7 +80,7 @@ function startmatch(f2,flow2,j2,compare2){ return } } - if(flow2.dlType!="0") + if(flow2.dlType!="0" && flow2.dlType!=null) { if("dlType" in f2){ console.log("HAVE DLTYPE"); @@ -91,7 +91,7 @@ function startmatch(f2,flow2,j2,compare2){ return; } } - if(flow2.srcIP!="0.0.0.0") + if(flow2.srcIP!="0.0.0.0" && flow2.srcIP!="None") { if("srcIP" in f2){ console.log("HAVE SRCIP"); @@ -102,7 +102,7 @@ function startmatch(f2,flow2,j2,compare2){ return; } } - if(flow2.dstIP!="0.0.0.0") + if(flow2.dstIP!="0.0.0.0" && flow2.dstIP!="None") { if("dstIP" in f2){ console.log("HAVE DSTIP"); @@ -135,7 +135,7 @@ function startmatch(f2,flow2,j2,compare2){ return; } } - if(flow2.ingressPort!="0") + if(flow2.ingressPort!="0" && flow2.ingressPort!=null) { if("ingressPort" in f2){ console.log("HAVE INGRESSPORT"); @@ -146,7 +146,7 @@ function startmatch(f2,flow2,j2,compare2){ return; } } - if(flow2.netProtocol!="0") + if(flow2.netProtocol!="0" && flow2.netProtocol!=null) { if("netProtocol" in f2){ console.log("HAVE NETPROTOCOL"); @@ -157,7 +157,7 @@ function startmatch(f2,flow2,j2,compare2){ return; } } - if(flow2.vlan!="0") + if(flow2.vlan!="0" && flow2.vlan!=null) { if("vlan" in f2){ console.log("HAVE VLAN"); @@ -168,6 +168,17 @@ function startmatch(f2,flow2,j2,compare2){ return; } } + if(flow2.vlanP!="0") + { + if("vlanP" in f2){ + console.log("HAVE VLANP"); + if(flow2.vlanP == f2["vlanP"]) console.log("match vlanP"); + else{ console.log("match vlanP failed"); return; } + }else{ + console.log("NOT HAVE VLANP"); + return; + } + } if(flow2.tosBits!="0") { if("tosBits" in f2){ @@ -223,6 +234,17 @@ function nexthop(ff3,flow3){ case "SET_NW_TOS": f3["tos-bits"]=flow3.actions[act].value; break; + case "SET_VLAN_VID": + f3["vlan"]=flow3.actions[act].value; + break; + case "SET_VLAN_PCP": + f3["vlanP"]=flow3.actions[act].value; + break; + case "STRIP_VLAN": + f3["vlan"]=null; + break; + case "ENQUEUE": + break; default: console.log("no support this actions"); break; @@ -363,7 +385,7 @@ function highlight(i){ delete hflow["wildcards"]; hflow["switch"] = node.id; for(var k in hflow) { - hflow[k] = hflow[k].toString(); + if(hflow[k]!=null) hflow[k] = hflow[k].toString(); } getflowmsg(hflow); } diff --git a/webui/js/omniui.js b/webui/js/omniui.js index f852a69..91000ee 100644 --- a/webui/js/omniui.js +++ b/webui/js/omniui.js @@ -317,14 +317,14 @@ function loadJSONP(){ function sendFlow(f){ for(var i in f) { - f[i] = f[i].replace(/\s+/g, ""); + if(f[i]!=null) f[i] = f[i].replace(/\s+/g, ""); } var url = getFlowModUrl(); var data = JSON.stringify(f); var callback = function(resp) { - var stat = JSON.parse(resp[2]); console.log(resp); loadJSONP(); + $("#info").empty(); clearcolor(); }; console.log(data); diff --git a/webui/stat13.html b/webui/stat13.html index ba21155..c127083 100644 --- a/webui/stat13.html +++ b/webui/stat13.html @@ -11,6 +11,7 @@ +