This is an alternative of dpset, which supports event passing. event.py: event class switch.py: switch(datapath) discovery app using ofp_event dumper.py: test and example app using switch discovery event
TODO: support link discovery using LLDP. Signed-off-by: YAMADA Hideki <[email protected]> --- ryu/physicaltopology/dumper.py | 87 ++++++++++++++++ ryu/physicaltopology/event.py | 85 ++++++++++++++++ ryu/physicaltopology/switch.py | 207 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 379 insertions(+), 0 deletions(-) create mode 100644 ryu/physicaltopology/__init__.py create mode 100644 ryu/physicaltopology/dumper.py create mode 100644 ryu/physicaltopology/event.py create mode 100644 ryu/physicaltopology/switch.py diff --git a/ryu/physicaltopology/__init__.py b/ryu/physicaltopology/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ryu/physicaltopology/dumper.py b/ryu/physicaltopology/dumper.py new file mode 100644 index 0000000..ada890a --- /dev/null +++ b/ryu/physicaltopology/dumper.py @@ -0,0 +1,87 @@ +# Copyright (C) 2013 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. + + +import logging +import gevent +import gevent.queue + +from ryu.base import app_manager +from ryu.controller import handler + +from .event import EventSwitchEnter, EventSwitchLeave +from .event import EventPortAdd, EventPortDelete, EventPortModify +from .event import EventSwitchListRequest, EventSwitchListReply +import switch + +LOG = logging.getLogger(__name__) + + +class DiscoveryEventDumper(app_manager.RyuApp): + ''' This app dumps discovery events + ''' + + # For testing when multi request threads, there is no race condition + __THREAD_COUNT = 2 + __THREAD_SLEEP = 5 + + def __init__(self): + super(DiscoveryEventDumper, self).__init__() + + for i in range(0, self.__THREAD_COUNT): + self.threads.append(gevent.spawn_later(0, self.__request_loop)) + + self.is_active = True + + @handler.set_ev_cls(EventSwitchEnter) + def dp_enter_handler(self, ev): + LOG.debug(ev) + + @handler.set_ev_cls(EventSwitchLeave) + def dp_leave_handler(self, ev): + LOG.debug(ev) + + @handler.set_ev_cls(EventPortAdd) + def dp_port_add_handler(self, ev): + LOG.debug(ev) + + @handler.set_ev_cls(EventPortDelete) + def dp_port_delete_handler(self, ev): + LOG.debug(ev) + + @handler.set_ev_cls(EventPortModify) + def dp_port_modify_handler(self, ev): + LOG.debug(ev) + + def __get_switches(self, dpid=None): + request = EventSwitchListRequest(dpid) + self.send_request(switch.SwitchDiscovery.__name__, request) + + reply = self.recv_reply(request.xid) + assert reply.xid == request.xid + + return reply.switches + + def __request_loop(self): + while self.is_active: + all_switches = self.__get_switches() + if len(all_switches) > 0: + LOG.debug('request thread(%s) print ALL Switches: ', + id(gevent.getcurrent())) + for sw in all_switches: + LOG.debug(' %s', sw) + #sw1 = self.__get_switches(1) + #LOG.debug('SW1: %s', sw1) + gevent.sleep(self.__THREAD_SLEEP) diff --git a/ryu/physicaltopology/event.py b/ryu/physicaltopology/event.py new file mode 100644 index 0000000..34fa863 --- /dev/null +++ b/ryu/physicaltopology/event.py @@ -0,0 +1,85 @@ +# Copyright (C) 2013 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. + +import logging +from ryu.controller import event + +LOG = logging.getLogger(__name__) + + +class EventSwitchBase(event.EventBase): + def __init__(self, switch): + super(EventSwitchBase, self).__init__() + self.switch = switch + + def __str__(self): + return '%s<dpid=%s, %s ports>' % \ + (self.__class__.__name__, + self.switch.dpid, len(self.switch.ports)) + + +class EventSwitchEnter(EventSwitchBase): + def __init__(self, switch): + super(EventSwitchEnter, self).__init__(switch) + + +class EventSwitchLeave(EventSwitchBase): + def __init__(self, switch): + super(EventSwitchLeave, self).__init__(switch) + + +class EventPortBase(event.EventBase): + def __init__(self, port): + super(EventPortBase, self).__init__() + self.port = port + + def __str__(self): + return '%s<%s>' % (self.__class__.__name__, self.port) + + +class EventPortAdd(EventPortBase): + def __init__(self, port): + super(EventPortAdd, self).__init__(port) + + +class EventPortDelete(EventPortBase): + def __init__(self, port): + super(EventPortDelete, self).__init__(port) + + +class EventPortModify(EventPortBase): + def __init__(self, port): + super(EventPortModify, self).__init__(port) + + +class EventSwitchListRequest(event.EventRequestBase): + # If dpid is None, reply all list + def __init__(self, dpid=None): + super(EventSwitchListRequest, self).__init__() + self.dpid = dpid + + def __str__(self): + return 'EventSwitchListRequest<src=%s, xid=%s, dpid=%s>' % \ + (self.src, self.xid, self.dpid) + + +class EventSwitchListReply(event.EventReplyBase): + def __init__(self, switches): + super(EventSwitchListReply, self).__init__() + self.switches = switches + + def __str__(self): + return 'EventSwitchListReply<src=%s, xid=%s, %s>' % \ + (self.src, self.xid, self.switches) diff --git a/ryu/physicaltopology/switch.py b/ryu/physicaltopology/switch.py new file mode 100644 index 0000000..9f99ead --- /dev/null +++ b/ryu/physicaltopology/switch.py @@ -0,0 +1,207 @@ +# Copyright (C) 2013 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. + +import logging +from ryu.base import app_manager +from ryu.controller import ofp_event, handler + +from .event import EventSwitchEnter, EventSwitchLeave +from .event import EventPortAdd, EventPortDelete, EventPortModify +from .event import EventSwitchListRequest, EventSwitchListReply +LOG = logging.getLogger(__name__) + + +class Port(object): + # This is data class passed by EventPortXXX + def __init__(self, dpid, ofproto, ofpport): + super(Port, self).__init__() + + self.dpid = dpid + self.__ofproto = ofproto + + self.__config = ofpport.config + self.__state = ofpport.state + self.port_no = ofpport.port_no + self.hw_addr = ofpport.hw_addr + self.name = ofpport.name + + def is_reserved(self): + return self.port_no > self.__ofproto.OFPP_MAX + + def is_down(self): + return (self.__state & self.__ofproto.OFPPS_LINK_DOWN) > 0 \ + or (self.__config & self.__ofproto.OFPPC_PORT_DOWN) > 0 + + def is_live(self): + # NOTE: OF1.2 has OFPPS_LIVE state + # return (self.__state & self.__ofproto.OFPPS_LIVE) > 0 + return not self.is_down() + + # for Switch.del_port() + def __eq__(self, other): + return self.dpid == other.dpid and self.port_no == other.port_no + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash((self.dpid, self.port_no)) + + def __str__(self): + LIVE_MSG = {False: 'DOWN', True: 'LIVE'} + return 'Port<dpid=%s, port_no=%s, %s>' % \ + (self.dpid, self.port_no, LIVE_MSG[self.is_live()]) + + +class Switch(object): + # This is data class passed by EventSwitchXXX + def __init__(self, dp): + super(Switch, self).__init__() + + self.__ofproto = dp.ofproto + self.dpid = dp.id + self.ports = [] + + def add_port(self, ofpport): + port = Port(self.dpid, self.__ofproto, ofpport) + if not port.is_reserved(): + self.ports.append(port) + + def del_port(self, ofpport): + self.ports.remove(Port(ofpport)) + + def __str__(self): + msg = 'Switch<dpid=%s, ' % self.dpid + for port in self.ports: + msg += str(port) + ' ' + + msg += '>' + return msg + + +class PortState(dict): + # dict: int port_no -> OFPPort port + # OFPPort is defined in ryu.ofproto.ofproto_v1_X_parser + def __init__(self): + super(PortState, self).__init__() + + def add(self, port_no, port): + self[port_no] = port + + def remove(self, port_no): + del self[port_no] + + def modify(self, port_no, port): + self[port_no] = port + + +class SwitchDiscovery(app_manager.RyuApp): + _EVENTS = [EventSwitchEnter, EventSwitchLeave, + EventPortAdd, EventPortDelete, EventPortModify] + + def __init__(self): + super(SwitchDiscovery, self).__init__() + + self.dps = {} # datapath_id => class Datapath + self.port_state = {} # datapath_id => ports + + def __register(self, dp): + assert dp.id is not None + assert dp.id not in self.dps + + self.dps[dp.id] = dp + self.port_state[dp.id] = PortState() + for port in dp.ports.values(): + self.port_state[dp.id].add(port.port_no, port) + + def __unregister(self, dp): + if dp.id in self.dps: + del self.dps[dp.id] + del self.port_state[dp.id] + + def __get_switch(self, dp): + switch = Switch(dp) + for ofpport in self.port_state[dp.id].itervalues(): + switch.add_port(ofpport) + return switch + + @handler.set_ev_cls(ofp_event.EventOFPStateChange, + [handler.MAIN_DISPATCHER, handler.DEAD_DISPATCHER]) + def state_change_handler(self, ev): + dp = ev.datapath + assert dp is not None + LOG.debug(dp) + + if ev.state == handler.MAIN_DISPATCHER: + self.__register(dp) + switch = self.__get_switch(dp) + LOG.debug('register %s', switch) + self.send_event_to_observers(EventSwitchEnter(switch)) + + elif ev.state == handler.DEAD_DISPATCHER: + # dp.id is None when datapath dies before handshake + if dp.id is None: + return + switch = self.__get_switch(dp) + self.__unregister(dp) + LOG.debug('unregister %s', switch) + self.send_event_to_observers(EventSwitchLeave(switch)) + + @handler.set_ev_cls(ofp_event.EventOFPPortStatus, handler.MAIN_DISPATCHER) + def port_status_handler(self, ev): + msg = ev.msg + reason = msg.reason + dp = msg.datapath + ofpport = msg.desc + + if reason == dp.ofproto.OFPPR_ADD: + #LOG.debug('A port was added.' + + # '(datapath id = %s, port number = %s)', + # dp.id, ofpport.port_no) + self.port_state[dp.id].add(ofpport.port_no, ofpport) + self.send_event_to_observers( + EventPortAdd(Port(dp.id, dp.ofproto, ofpport))) + + elif reason == dp.ofproto.OFPPR_DELETE: + #LOG.debug('A port was deleted.' + + # '(datapath id = %s, port number = %s)', + # dp.id, ofpport.port_no) + self.port_state[dp.id].remove(ofpport.port_no) + self.send_event_to_observers( + EventPortDelete(Port(dp.id, dp.ofproto, ofpport))) + + else: + assert reason == dp.ofproto.OFPPR_MODIFY + #LOG.debug('A port was modified.' + + # '(datapath id = %s, port number = %s)', + # dp.id, ofpport.port_no) + self.port_state[dp.id].modify(ofpport.port_no, ofpport) + self.send_event_to_observers( + EventPortModify(Port(dp.id, dp.ofproto, ofpport))) + + @handler.set_ev_cls(EventSwitchListRequest) + def request_handler(self, ev): + #LOG.debug(ev) + dpid = ev.dpid + + switches = [] + if dpid is None: + # reply all list + for dp in self.dps.itervalues(): + switches.append(self.__get_switch(dp)) + elif dpid in self.dps: + switches.append(self.__get_switch(self.dps[dpid])) + + self.send_reply(ev, EventSwitchListReply(switches)) -- 1.7.1 ------------------------------------------------------------------------------ Everyone hates slow websites. So do we. Make your web apps faster with AppDynamics Download AppDynamics Lite for free today: http://p.sf.net/sfu/appdyn_d2d_feb _______________________________________________ Ryu-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/ryu-devel
