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

Reply via email to