Ryu的安装+使用+源码分析+二层交换机

ryu的安装

安装RYU,需要安装一些python的套件:
python-eventlet
python-routes
python-webob
python-paramiko
安装RYU主要有两种方式:
1、pip安装

pip install ryu

2、下载源文件安装
在这里插入图片描述

git clone git://github.com/osrg/ryu.git
cd ryu
sudo pip install -r tools/pip-requires
sudo python setup.py install 

ryu的使用

进入ryu目录,输入ryu-manager yourapp.py运行相应的app在这里插入图片描述
比如:

ryu-manager simple_switch.py

在这里插入图片描述

ryu的源码分析

下面介绍ryu/ryu目录下的主要目录内容。
base
ryu ryu base
在这里插入图片描述
base中有一个非常重要的文件:app_manager.py,其作用是RYU应用的管理中心。用于加载RYU应用程序,接受从APP发送过来的信息,同时也完成消息的路由。

其主要的函数有app注册、注销、查找、并定义了RYUAPP基类,定义了RYUAPP的基本属性。包含name, threads, events, event_handlers和observers等成员,以及对应的许多基本函数。如:start(), stop()等。

这个文件中还定义了AppManager基类,用于管理APP。定义了加载APP等函数。不过如果仅仅是开发APP的话,这个类可以不必关心


class RyuApp(object):
    """
    The base class for Ryu applications.*#ryu应用程序的基类*
    
*ryuapp子类实例化ryu-manager加载所有ryu应用程序模块的请求,_init_应该调用ryuapp,_init
_带着一些参数。在_init_中发送一些事件是不合法的*
    RyuApp subclasses are instantiated after ryu-manager loaded
    all requested Ryu application modules.
    __init__ should call RyuApp.__init__ with the same arguments.
    It's illegal to send any events in __init__.
    
*实例属性“name”是ryu程序中用于消息发送的类的名称*
    The instance attribute 'name' is the name of the class used for
    message routing among Ryu applications.  (Cf. send_event)
    It's set to __class__.__name__ by RyuApp.__init__.
    It's discouraged for subclasses to override this.
    """

    _CONTEXTS = {}
    *"""
    ryu应用程序希望使用字典指定的上下文呢。关键是上下文的名字和值是一个普通类继承上下文。这个类被实例化app_manager和实例在众多ryuapp子类中共享包含_contexts member带有相同的key。一个ryuapp子类可以通过实例获得对实例的引用*
    A dictionary to specify contexts which this Ryu application wants to use.
    Its key is a name of context and its value is an ordinary class
    which implements the context.  The class is instantiated by app_manager
    and the instance is shared among RyuApp subclasses which has _CONTEXTS
    member with the same key.  A RyuApp subclass can obtain a reference to
    the instance via its __init__'s kwargs as the following.

    Example::

        _CONTEXTS = {
            'network': network.Network
        }

        def __init__(self, *args, *kwargs):
            self.network = kwargs['network']
    """

    _EVENTS = []
    """
    *一系列事件类由ryuapp子类产生。这应该是指定的当前仅当事件类都从ryuapp类不同的python模块定义*
    A list of event classes which this RyuApp subclass would generate.
    This should be specified if and only if event classes are defined in
    a different python module from the RyuApp subclass is.
    """

    OFP_VERSIONS = None*设置openflow版本*
    """
   *列出该应用支持openflow的版本。默认的是框架支持的所有版本*
    A list of supported OpenFlow versions for this RyuApp.
    The default is all versions supported by the framework.

    Examples::

        OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION,
                        ofproto_v1_2.OFP_VERSION]
*如果在系统内加载多个应用,则使用交叉点版本*
    If multiple Ryu applications are loaded in the system,
    the intersection of their OFP_VERSIONS is used.
    """

    @classmethod
    def context_iteritems(cls):
        """
        Return iterator over the (key, contxt class) of application context
        """
        return iter(cls._CONTEXTS.items())

    def __init__(self, *_args, **_kwargs):
        super(RyuApp, self).__init__()
        self.name = self.__class__.__name__
        self.event_handlers = {}        # ev_cls -> handlers:list
        self.observers = {}     # ev_cls -> observer-name -> states:set
        self.threads = []
        self.main_thread = None
        self.events = hub.Queue(128)
        self._events_sem = hub.BoundedSemaphore(self.events.maxsize)
        if hasattr(self.__class__, 'LOGGER_NAME'):
            self.logger = logging.getLogger(self.__class__.LOGGER_NAME)
        else:
            self.logger = logging.getLogger(self.name)
        self.CONF = cfg.CONF

        # prevent accidental creation of instances of this class outside RyuApp
        class _EventThreadStop(event.EventBase):
            pass
        self._event_stop = _EventThreadStop()
        self.is_active = True

    def start(self):
        """
        Hook that is called after startup initialization is done.
        *当初始化完成后被调用*
        """
        self.threads.append(hub.spawn(self._event_loop))

    def stop(self):
        if self.main_thread:
            hub.kill(self.main_thread)
        self.is_active = False
        self._send_event(self._event_stop, None)
        hub.joinall(self.threads)

    def set_main_thread(self, thread):
        """
        Set self.main_thread so that stop() can terminate it.

        Only AppManager.instantiate_apps should call this function.
        """
        self.main_thread = thread

    def register_handler(self, ev_cls, handler):
        assert callable(handler)
        self.event_handlers.setdefault(ev_cls, [])
        self.event_handlers[ev_cls].append(handler)

    def unregister_handler(self, ev_cls, handler):
        assert callable(handler)
        self.event_handlers[ev_cls].remove(handler)
        if not self.event_handlers[ev_cls]:
            del self.event_handlers[ev_cls]

    def register_observer(self, ev_cls, name, states=None):
        states = states or set()
        ev_cls_observers = self.observers.setdefault(ev_cls, {})
        ev_cls_observers.setdefault(name, set()).update(states)

    def unregister_observer(self, ev_cls, name):
        observers = self.observers.get(ev_cls, {})
        observers.pop(name)

    def unregister_observer_all_event(self, name):
        for observers in self.observers.values():
            observers.pop(name, None)

    def observe_event(self, ev_cls, states=None):
        brick = _lookup_service_brick_by_ev_cls(ev_cls)
        if brick is not None:
            brick.register_observer(ev_cls, self.name, states)

    def unobserve_event(self, ev_cls):
        brick = _lookup_service_brick_by_ev_cls(ev_cls)
        if brick is not None:
            brick.unregister_observer(ev_cls, self.name)

    def get_handlers(self, ev, state=None):
        """Returns a list of handlers for the specific event.

        :param ev: The event to handle.
        :param state: The current state. ("dispatcher")
                      If None is given, returns all handlers for the event.
                      Otherwise, returns only handlers that are interested
                      in the specified state.
                      The default is None.
        """
        ev_cls = ev.__class__
        handlers = self.event_handlers.get(ev_cls, [])
        if state is None:
            return handlers

        def test(h):
            if not hasattr(h, 'callers') or ev_cls not in h.callers:
                # dynamically registered handlers does not have
                # h.callers element for the event.
                return True
            states = h.callers[ev_cls].dispatchers
            if not states:
                # empty states means all states
                return True
            return state in states

        return filter(test, handlers)

    def get_observers(self, ev, state):
        observers = []
        for k, v in self.observers.get(ev.__class__, {}).items():
            if not state or not v or state in v:
                observers.append(k)

        return observers

    def send_request(self, req):
        """
        Make a synchronous request.
        Set req.sync to True, send it to a Ryu application specified by
        req.dst, and block until receiving a reply.
        Returns the received reply.
        The argument should be an instance of EventRequestBase.
        产生一个同步请求。设置req.sync为真,发送到一个指定的ryu应用通过req.dst,然后直到收到一个回复block。返回接收到的回复,参数应该被evenrequestbase实例。
        """

        assert isinstance(req, EventRequestBase)
        req.sync = True
        req.reply_q = hub.Queue()
        self.send_event(req.dst, req)
        # going to sleep for the reply
        return req.reply_q.get()

    def _event_loop(self):
        while self.is_active or not self.events.empty():
            ev, state = self.events.get()
            self._events_sem.release()
            if ev == self._event_stop:
                continue
            handlers = self.get_handlers(ev, state)
            for handler in handlers:
                try:
                    handler(ev)
                except hub.TaskExit:
                    # Normal exit.
                    # Propagate upwards, so we leave the event loop.
                    raise
                except:
                    LOG.exception('%s: Exception occurred during handler processing. '
                                  'Backtrace from offending handler '
                                  '[%s] servicing event [%s] follows.',
                                  self.name, handler.__name__, ev.__class__.__name__)

    def _send_event(self, ev, state):
        self._events_sem.acquire()
        self.events.put((ev, state))

    def send_event(self, name, ev, state=None):
        """
        Send the specified event to the RyuApp instance specified by name.
        *发送指定的事件给ryuapp通过指定的名称实例化*
        """

        if name in SERVICE_BRICKS:
            if isinstance(ev, EventRequestBase):
                ev.src = self.name
            LOG.debug("EVENT %s->%s %s",
                      self.name, name, ev.__class__.__name__)
            SERVICE_BRICKS[name]._send_event(ev, state)
        else:
            LOG.debug("EVENT LOST %s->%s %s",
                      self.name, name, ev.__class__.__name__)

    def send_event_to_observers(self, ev, state=None):
        """
        Send the specified event to all observers of this RyuApp.
        *发送指定的事件给ryuapp的监测者*
        """

        for observer in self.get_observers(ev, state):
            self.send_event(observer, ev, state)


    def reply_to_request(self, req, rep):
        """
        *通过send_request发送同步请求回复。第一个参数是evenrequesbase的实例,第二个参数是eventreplybase实例*
        Send a reply for a synchronous request sent by send_request.
        The first argument should be an instance of EventRequestBase.
        The second argument should be an instance of EventReplyBase.
        """

        assert isinstance(req, EventRequestBase)
        assert isinstance(rep, EventReplyBase)
        rep.dst = req.src
        if req.sync:
            req.reply_q.put(rep)
        else:
            self.send_event(rep.dst, rep)

*close()是拆卸方法。close是方法的名称。被python上下文管理器选择使用*
    def close(self):
        """
        teardown method.
        The method name, close, is chosen for python context manager
        """
        pass

controller
controller文件夹中许多非常重要的文件,如events.py, ofp_handler.py, controller.py等。其中controller.py中定义了OpenFlowController基类。用于定义OpenFlow的控制器,用于处理交换机和控制器的连接等事件,同时还可以产生事件和路由事件。其事件系统的定义,可以查看events.py和ofp_events.py。

在ofp_handler.py中定义了基本的handler(应该怎么称呼呢?句柄?处理函数?),完成了基本的如:握手,错误信息处理和keep alive 等功能。更多的如packet_in_handler应该在app中定义。

在dpset.py文件中,定义了交换机端的一些消息,如端口状态信息等,用于描述和操作交换机。如添加端口,删除端口等操作。

其他的文件不再赘述。

lib
lib中定义了我们需要使用到的基本的数据结构,如dpid, mac和ip等数据结构。在lib/packet目录下,还定义了许多网络协议,如ICMP, DHCP, MPLS和IGMP等协议内容。而每一个数据包的类中都有parser和serialize两个函数。用于解析和序列化数据包。

lib目录下,还有ovs, netconf目录,对应的目录下有一些定义好的数据类型,不再赘述。

ofproto
在这个目录下,基本分为两类文件,一类是协议的数据结构定义,另一类是协议解析,也即数据包处理函数文件。如ofproto_v1_0.py是1.0版本的OpenFlow协议数据结构的定义,而ofproto_v1_0_parser.py则定义了1.0版本的协议编码和解码。具体内容不赘述,实现功能与协议相同。

topology
包含了switches.py等文件,基本定义了一套交换机的数据结构。event.py定义了交换上的事件。dumper.py定义了获取网络拓扑的内容。最后api.py向上提供了一套调用topology目录中定义函数的接口。

contrib
这个文件夹主要存放的是开源社区贡献者的代码。我没看过。

cmd
定义了RYU的命令系统,具体不赘述。

services
完成了BGP和vrrp的实现。具体我还没有使用这个模块。

tests
tests目录下存放了单元测试以及整合测试的代码,有兴趣的读者可以自行研究。

开发你自己的RYU应用程序

大概浏览了一下RYU的源代码,相信看过OpenDaylight的同学会发现,太轻松了!哈哈,我想我真的不喜欢maven, osgi, xml, yang以及java,但是不能不承认OpenDaylight还是很牛逼的,在学习的读者要坚持啊!

开发RYU的APP,真的再简单不过了。先来最简单的:
from ryu.base import app_manager

class L2Switch(app_manager.RyuApp):
def init(self, *args, **kwargs):
super(L2Switch, self).init(*args, **kwargs)

首先,我们从ryu.base import app_manager,在前面我们也提到过这个文件中定义了RyuApp基类。我们在开发APP的时候只需要继承这个基类,就获得你想要的一个APP的一切了。于是,我们就不用去注册了?!是的,不需要了!

保存文件,可以取一个名字为L2Switch.py。

现在你可以运行你的APP了。快得有点不敢相信吧!但是目前什么都没有,运行之后,马上就会结束,但起码我们的代码没有报错。

运行:
ryu-manager L2Switch.py
继续往里面添加内容:
from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import MAIN_DISPATCHER
from ryu.controller.handler import set_ev_cls
class L2Switch(app_manager.RyuApp):
def init(self, *args, **kwargs):
super(L2Switch, self).init(*args, **kwargs)

@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def packet_in_handler(self, ev):
    msg = ev.msg
    datapath = msg.datapath
    ofp = datapath.ofproto
    ofp_parser = datapath.ofproto_parser

    actions = [ofp_parser.OFPActionOutput(ofp.OFPP_FLOOD)]
    out = ofp_parser.OFPPacketOut(
        datapath=datapath, buffer_id=msg.buffer_id, in_port=msg.in_port,
        actions=actions)
    datapath.send_msg(out)

其中ofp_event完成了事件的定义,从而我们可以在函数中注册handler,监听事件,并作出回应。

packet_in_handler方法用于处理packet_in事件。@set_ev_cls修饰符用于告知RYU,被修饰的函数应该被调用。(翻译得有点烂这句)

set_ev_cls第一个参数表示事件发生时应该调用的函数,第二个参数告诉交换机只有在交换机握手完成之后,才可以被调用。
下面分析具体的数据操作:
ev.msg:每一个事件类ev中都有msg成员,用于携带触发事件的数据包。
msg.datapath:已经格式化的msg其实就是一个packet_in报文,msg.datapath直接可以获得packet_in报文的datapath结构。datapath用于描述一个交换网桥。也是和控制器通信的实体单元。datapath.send_msg()函数用于发送数据到指定datapath。通过datapath.id可获得dpid数据,在后续的教程中会有使用。
datapath.ofproto对象是一个OpenFlow协议数据结构的对象,成员包含OpenFlow协议的数据结构,如动作类型OFPP_FLOOD。
datapath.ofp_parser则是一个按照OpenFlow解析的数据结构。
actions是一个列表,用于存放action list,可在其中添加动作。
通过ofp_parser类,可以构造构造packet_out数据结构。括弧中填写对应字段的赋值即可。
如果datapath.send_msg()函数发送的是一个OpenFlow的数据结构,RYU将把这个数据发送到对应的datapath。
至此,一个简单的HUB已经完成。

RYU进阶——二层交换机

在以上的基础之上,继续修改就可以完成二层交换机的功能。具体代码如下:

import struct
import logging
 
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 L2Switch(app_manager.RyuApp):
 
    OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION]#define the version of OpenFlow
 
    def __init__(self, *args, **kwargs):
        super(L2Switch, 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 = 10,hard_timeout = 30,
            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)
 
        dst = eth.dst
        src = eth.src
 
        dpid = datapath.id    #get the dpid
        self.mac_to_port.setdefault(dpid, {})
 
        self.logger.info("packet in %s %s %s %s", dpid, src, dst , msg.in_port)
        #To learn a mac address to avoid FLOOD next time.
 
        self.mac_to_port[dpid][src] = msg.in_port
 
 
        out_port = ofproto.OFPP_FLOOD
 
        #Look up the out_port 
        if dst in self.mac_to_port[dpid]:
            out_port = self.mac_to_port[dpid][dst]
 
        ofp_parser = datapath.ofproto_parser
 
        actions = [ofp_parser.OFPActionOutput(out_port)]
 
        if out_port != ofproto.OFPP_FLOOD:
            self.add_flow(datapath, msg.in_port, dst, actions)
 
 
        #We always send the packet_out to handle the first packet.
        packet_out = ofp_parser.OFPPacketOut(datapath = datapath, buffer_id = msg.buffer_id,
            in_port = msg.in_port, actions = actions)
        datapath.send_msg(packet_out)
    #To show the message of ports' status.
    @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)

packet_in_handler函数的函数体:

ev.msg:每一个事件类ev中都有msg成员,用于携带触发事件的数据包,(此处是packet_in的消息对象,存储着消息的数据部分)
msg.datapath:已经格式化的msg其实就是一个packet_in报文,msg.datapath直接可以获得packet_in报文的datapath结构。datapath用于描述一个交换网桥。也是和控制器通信的实体单元。
datapath.send_msg():函数用于发送数据到指定datapath。
datapath.ofproto对象是一个OpenFlow协议数据结构的对象,成员包含OpenFlow协议的数据结构,如动作类型OFPP_FLOOD。
datapath.ofp_parser则是一个按照OpenFlow解析的数据结构。
actions是一个列表,用于存放action list,可在其中添加动作。
通过ofp_parser类,可以构造构造packet_out数据结构。括弧中填写对应字段的赋值即可。如果datapath.send_msg()函数发送的是一个OpenFlow的数据结构,RYU将把这个数据发送到对应的datapath。
每当RYU控制器收到OpenFlow协议中的packet_in消息时,packet_in_handler函数就会被调用,因为这个函数被注册到装饰器set_ev_cls中,并且装饰器将packet_in_handler注册到packet_in消息上,每当收到packet_in消息时就调用该函数。

set_ev_cls装饰器的参数:

第一个参数指触发函数的调用事件,这里指packet_in事件。
第二个参数指交换机的状态。比如,当交换机处于与控制器协商(negotiation)阶段时,可能你想忽略此时的packet_in消息,那我们就可以使用MAIN_DISPATCHER作为参数来表明当协商完成后该函数才被调用(即握手后才可以被调用)。

作者:墨痕hz
链接:https://www.jianshu.com/p/2b3bffa31ecb
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
解释的原文链接
https://blog.csdn.net/Jiana_Feng/article/details/107861130添加链接描述
*def init(self, *args, *kwargs):
编写python script的时候,经常需要使用def init(self, *args, **kwargs): 其含义代表什么?
这种写法代表这个方法接受任意个数的参数
如果是没有指定key的参数,比如单单‘apple’,‘people’,即为无指定,则会以list的形式放在args变量里面
如果是有指定key的参数,比如item=‘apple’这种形式,即为有指定,则会以dict的形式放在kwargs变量里面
For example:
在这里插入图片描述
相信代码中的注释已经足以让读者理解这个程序。完成之后,运行:

	
ryu-manager L2Switch.py

然后可以使用Mininet进行pingall测试,成功!
转载自:李呈博客@李呈,http://www.muzixing.com/pages/2014/09/20/ryuru-men-jiao-cheng.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值