Ryu学习---ryu/ryu/topology/switches.py

Ryu拓扑发现的核心模块是ryu/topology目录下的switches.py,本文主要对class Switches类模块进行分析

Switches类

该类是Ryu拓扑发现的核心所在。Switches类是app_manager.RyuApp类的子类,当运行switches应用时会被实例化,其__init__函数主要包括:

self.name = 'switches'
self.dps = {}                 # datapath_id => Datapath class
self.port_state = {}          # datapath_id => ports
self.ports = PortDataState()  # Port class -> PortData class
self.links = LinkState()      # Link class -> timestamp
self.is_active = True

self.dps

self.dps字典中键为dpid,值为Datapath类,会在_register函数中添加新成员,_unregister函数中删除成员。遍历该字典可以得到连接的所有交换机。

self.port_state

self.port_state字典中键为dpid,值为PortState类。遍历该字典可以得到所有交换机对应的端口信息情况。当交换机连接时,会检查交换机的id是否在self.port_state中,不在则创建PortState类实例,把交换机的所有端口号和端口存储到该实例中,如_register函数中第4至8行代码所示;交换机断开时,会从self.port_state中删除,如_unregister函数中所示。
PortState类: 保存了从port_no(int型)到port(OFPPort类实例)的映射。该类主要用作self.port_state字典的值(键是dpid),用于存储dpid对应的交换机的所有端口情况。OFPPort类定义在ryu/ofproto目录下对应的ofproto_v1_X_parser.py中(X代表版本号),继承自一个namedtuple,保存有port_no等信息。

self.ports

self.ports是PortDataState类的实例,保存每个端口(port_no)对应的LLDP报文数据(保存在PortData类实例中),遍历self.ports用于发送LLDP报文。
PortData类: 保存每个端口与对应的LLDP报文数据,数据成员有:
self.is_down = is_down
self.lldp_data = lldp_data(这是LLDP报文的数据)
self.timestamp = None
self.sent = 0
每调用一次lldp_sent函数,便会把self.timestamp置为当前的时间(time.time()),并将self.sent加1;每调用一次lldp_received函数,便会把self.sent置为0。
PortDataState类: 保存从Port类(下面介绍)到PortData类的映射。该类维护了一个类似双向循环链表的数据结构,并重写了__init__(),使得遍历该类的实例(self.ports)时,会按照该双向循环链表从哨兵节点(self._root)后一个节点开始遍历。
  包含一个add_port函数,传入port和lldp_data,port作键,构建的PortData类实例作为值。
  包含一个lldp_sent(self,port)函数,根据传入的port(Port类实例)获得对应的PortData类实例port_data,然后调用port_data.lldp_sent()(该函数会设置时间戳),再调用self._move_last_key(port),把该port移到类似双向循环链表的数据结构中哨兵节点的前面(相当于下次遍历的末尾);最后返回port_data。 
Port类: 存储端口相关信息,数据成员有:
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
其中要特别注意的是dpid和port_no,即交换机ID和端口号。

self.links

self.links是LinkState类的实例,保存所有连接(Link类)到时间戳的映射。遍历self.links的键即可得到所有交换机之间的连接情况。
LinkState类: 保存从Link类到时间戳的映射。数据成员self._map字典用于存储Link两端互相映射的关系。
Link类: 保存的是源端口和目的端口(都是Port类实例),数据成员有:
self.src=src
self.dst=dst

如果ryu-manager启动时加了–observe-links参数,则下面的self.link_discovery将为真,从而执行if下面的语句:

self.link_discovery = self.CONF.observe_links
if self.link_discovery:
    self.install_flow = self.CONF.install_lldp_flow
    self.explicit_drop = self.CONF.explicit_drop
    self.lldp_event = hub.Event()
    self.link_event = hub.Event()
    self.threads.append(hub.spawn(self.lldp_loop))
    self.threads.append(hub.spawn(self.link_loop))

综上所述,该初始化函数__init__()主要是创建用于存储相关信息的数据结构,创建两个事件,然后调用hub.spawn创建两个新线程执行self.lldp_loop和self.link_loop两个函数。

state_change_handle函数

该函数用于处理EventOFPStateChange事件,当交换机连接或者断开时会触发该事件。从ev.datapath获得Datapath类实例dp,且不为空。

@set_ev_cls(ofp_event.EventOFPStateChange,
                [MAIN_DISPATCHER, DEAD_DISPATCHER])
    def state_change_handler(self, ev):
        dp = ev.datapath
        assert dp is not None
        LOG.debug(dp)

如果状态是MAIN_DISPATHCHER
先判断从上述得到的dp的dpid是否在self.dps里有,若在则报出重复链接的警告,不在则进行后续一系列操作。
首先,调用_register()函数,将dp.id和dp添加到self.dps中。然后判断该dp.id是否在self.port_state中,如果不在,则创建该dp.id对应的PortState实例,并遍历dp.ports.values,将所有port(OFPPort类型)添加到PortState实例中。

if ev.state == MAIN_DISPATCHER:
            dp_multiple_conns = False
            if dp.id in self.dps:
                LOG.warning('Multiple connections from %s', dpid_to_str(dp.id))
                dp_multiple_conns = True
                (self.dps[dp.id]).close()

            self._register(dp)
            switch = self._get_switch(dp.id)
            LOG.debug('register %s', switch)
            
(这里附上_register函数:
class Switches(app_manager.RyuApp)
#...
 def _register(self, dp):
 	assert dp.id is not None
	self.dps[dp.id] = dp
    if dp.id not in self.port_state:
       self.port_state[dp.id] = PortState()
       for port in dp.ports.values():
           self.port_state[dp.id].add(port.port_no, port)

调用_get_switch()函数,如果dp.id在self.dps中,则创建一个Switch类实例,并把self.port_state中对应的端口都添加到该实例中,最终返回该实例。

def _get_switch(self, dpid):
        if dpid in self.dps:
            switch = Switch(self.dps[dpid])
            for ofpport in self.port_state[dpid].values():
                switch.add_port(ofpport)
            return switch

(这里是Switch类代码
class Switch(object):
    # This is data class passed by EventSwitchXXX
    def __init__(self, dp):
        super(Switch, self).__init__()

        self.dp = dp
        self.ports = []

    def add_port(self, ofpport):
        port = Port(self.dp.id, self.dp.ofproto, ofpport)
        if not port.is_reserved():
            self.ports.append(port)

    def del_port(self, ofpport):
        self.ports.remove(Port(ofpport))

    def to_dict(self):
        d = {'dpid': dpid_to_str(self.dp.id),
             'ports': [port.to_dict() for port in self.ports]}
        return d

    def __str__(self):
        msg = 'Switch<dpid=%s, ' % self.dp.id
        for port in self.ports:
            msg += str(port) + ' '

        msg += '>'
        return

如果交换机没有重复连接,触发EventSwitchEnter事件,否则触发EventSwitchReconnected事件

if not dp_multiple_conns:
   self.send_event_to_observers(event.EventSwitchEnter(switch))
else:
   evt = event.EventSwitchReconnected(switch)
   self.send_event_to_observers(evt)

如果没设置self.link_discovery,返回,否则执行下一步。如果设置了self.install_flow,则根据Openflow版本生成相应流表项,使得收到的LLDP报文(根据目的MAC地址匹配)上报给控制器。

if not self.link_discovery:
   return

#这部分就是app会用到的生成流表项并安装
if self.install_flow:
	ofproto = dp.ofproto
	ofproto_parser = dp.ofproto_parser

	if ofproto.OFP_VERSION == ofproto_v1_0.OFP_VERSION:
       rule = nx_match.ClsRule()
       rule.set_dl_dst(addrconv.mac.text_to_bin(lldp.LLDP_MAC_NEAREST_BRIDGE))
       rule.set_dl_type(ETH_TYPE_LLDP)
       actions = [ofproto_parser.OFPActionOutput(
                        ofproto.OFPP_CONTROLLER, self.LLDP_PACKET_LEN)]
       dp.send_flow_mod(rule=rule, cookie=0, command=ofproto.OFPFC_ADD,
                        idle_timeout=0, hard_timeout=0, actions=actions,
                        priority=0xFFFF)
     elif ofproto.OFP_VERSION >= ofproto_v1_2.OFP_VERSION:
          match = ofproto_parser.OFPMatch(
                        eth_type=ETH_TYPE_LLDP,
                        eth_dst=lldp.LLDP_MAC_NEAREST_BRIDGE)
          # OFPCML_NO_BUFFER is set so that the LLDP is not buffered on switch
          parser = ofproto_parser
          actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
                                            ofproto.OFPCML_NO_BUFFER)]
          inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, actions)]
          mod = parser.OFPFlowMod(datapath=dp, match=match,
                                  idle_timeout=0, hard_timeout=0,
                                  instructions=inst,priority=0xFFFF)
          dp.send_msg(mod)
else:
     LOG.error('cannot install flow. unsupported version. %x', dp.ofproto.OFP_VERSION)

如果交换机没有重复连接,则遍历switche.ports的所有端口(switch.ports是switch = self._get_switch(dp.id)得到的),如果端口port不是被保留的,则调用self._port_add(port),该函数会调用LLDPPacket.lldp_packet()函数生成LLDP报文数据lldp_data(用于和“port.is_down()”一起构造PortData类实例),然后调用self.ports.add_port函数[即PortDataState类的add_port(port,lldp_data)。add_port()函数会检查port是否在self.ports中,不在则将该port添加到双向循环链表中哨兵节点的后面(下次检查的开头),并把port和对应的PortData类实例(该端口对应的LLDP报文)添加到self.ports中。

# Do not add ports while dp has multiple connections to controller.
if not dp_multiple_conns:
   for port in switch.ports:
       if not port.is_reserved():
          self._port_added(port)
          
self.lldp_event.set()

( _port_add()函数:
class Switches(app_manager.RyuApp)
#...
    def _port_added(self, port):
        lldp_data = LLDPPacket.lldp_packet(
            port.dpid, port.port_no, port.hw_addr, self.DEFAULT_TTL)
        self.ports.add_port(port, lldp_data)
        # LOG.debug('_port_added dpid=%s, port_no=%s, live=%s',
        #           port.dpid, port.port_no, port.is_live())
 )
 
 ( self.ports.add_port(port,lldp_data)函数:
 class PortDataState(dict)
 #...
 	def add_port(self,port,lldp_data):
 		if port not in self:
 			self._prepend_key(port)
 			self[port]=PortData(port.is_down(),lldp_data)
 		else:
 			self[port].is_down=port.is_down()

如果状态是DEAD_DISPATCHER:
如果dp.id为None,即握手之前交换机就断开连接了,则直接返回;否则执行下一步。
调用_get_switch()获得Switch实例;
调用_unregister(),从self.dps和self.port_state中删除该dpid对应的数据;
触发EventSwitchLeave事件;
如果没有设置link_discovery,返回;否则执行下一步。
遍历switch.ports中的每个端口port,如果不是保留端口,则调用PortDataState类的del_port(),将self.ports中port对应的数据删除;调用Swithes类的_link_down()。

elif ev.state == DEAD_DISPATCHER:
     # dp.id is None when datapath dies before handshake
     if dp.id is None:
        return

     switch = self._get_switch(dp.id)
     if switch:
        if switch.dp is dp:
           self._unregister(dp)
           LOG.debug('unregister %s', switch)
           evt = event.EventSwitchLeave(switch)
           self.send_event_to_observers(evt)

           if not self.link_discovery:
              return

           for port in switch.ports:
               if not port.is_reserved():
                  self.ports.del_port(port)
                  self._link_down(port)
           self.lldp_event.set()

_link_down函数执行如下操作:
a. 调用self.links(LinkState类实例)的port_deleted函数。在port_deleted函数里,首先调用get_peer()获得对端端口,然后生成两个Link对象(src->dst和dst->src),并将这两个对象从self.links中删除(反向Link可能不存在);删除src->dst和dst->src之间的映射(存储在_map字典中)。最后返回传入的port对应的对端port和传入的port本身。
b. 根据返回的“传入的port对应的对端port和传入的port本身”,创建Link对象,触发EventLinkDelete事件(如果反向连接也存在,会触发两次EventLinkDelete事件)。
c. 调用self.ports.move_front(dst)(self.ports是PortDataState类的实例),该函数会从self.ports中得到dst对应的PortData类实例port_data,如果port_data不为None,则调用clear_timestamp函数将其timestamp属性置为None,并将dst移动到双向循环链表中哨兵节点的后面(下次检查的开头)。

class Switches(app_manager.RyuAPP)
#...
def _link_down(self, port):
        try:
            dsts, rev_link_dsts = self.links.port_deleted(port)
        except KeyError:
            # LOG.debug('key error. src=%s, dst=%s',
            #           port, self.links.get_peer(port))
            return
        for dst in dsts:
            link = Link(port, dst)
            self.send_event_to_observers(event.EventLinkDelete(link))
        for rev_link_dst in rev_link_dsts:
            rev_link = Link(rev_link_dst, port)
            self.send_event_to_observers(event.EventLinkDelete(rev_link))
            self.ports.move_front(rev_link_dst)
(self.links.port_deleted,
self.links是LinkState实例,
所以下面函数在LinkState中)
class LinkState(dict)
#...
    def port_deleted(self, src):
        dsts = self.get_peers(src)

        rev_link_dsts = []
        for dst in dsts:
            link = Link(src, dst)
            rev_link = Link(dst, src)
            del self[link]
            self.pop(rev_link, None)
            if src in self._map[dst]:
                del self._map[dst][src]
                rev_link_dsts.append(dst)

        del self._map[src]
        return dsts, rev_link_dsts
class PortDataState(dict)
#...
def move_front(self, port):
    port_data = self.get(port, None)
    if port_data is not None:
       port_data.clear_timestamp()
       self._move_front_key(port)
class PortDataState(dict)
#...
def _move_front_key(self, key):
    self._remove_key(key)
    self._prepend_key(key)

def _remove_key(self, key):
    link_prev, link_next, key = self._map.pop(key)
    link_prev[self._NEXT] = link_next
    link_next[self._PREV] = link_prev

def _prepend_key(self, key):
    root = self._root
    first = root[self._NEXT]
    first[self._PREV] = root[self._NEXT] = self._map[key] = [root, first, key]                                           

Switches类代码分析下来真是头都要大了,三天的时间还是在借助了参考博客说明的情况下,只能说把代码理顺了(代码小白的痛…),继续学习!

参考: Ryu拓扑发现原理分析

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值