# Ryu 本身就有提供 WSGI 對應的 Web 伺服器。透過這個機制建立相關的 REST API 可以與其他系統或瀏覽器整合。
# 備註: WSGI 是 Python 提供用來連結網頁應用程式和網頁伺服器的框架

# 安裝包含 REST API 的 Switching Hub
# 接下來讓我們實際加入兩個先前在「 交換器( Switching Hub ) 」說明過的API。

# 两个API功能
# MAC 位址表取得 API
# 取得 Switching hub 中儲存的 MAC 位址表內容。 成對的 MAC 位址和連接埠號將以 JSON 的資料形態回傳。

# MAC 位址表註冊 API
# MAC 位址和連接埠號成對的新增進 MAC 位址表,同時加同時加到交換器的 Flow Entry 中。

import json
import logging
from ryu.app import simple_switch_13
from ryu.controller import ofp_event
from ryu.controller.handler import CONFIG_DISPATCHER, set_ev_cls
from ryu.app.wsgi import ControllerBase
from webob import Response
from ryu.app.wsgi import route
from ryu.app.wsgi import WSGIApplication
from ryu.lib import dpid as dpid_lib

simple_switch_instance_name = 'simple_switch_api_app'
# 指定url为如下方式,其中,{dpid}的部分必須與 ryu/lib/dpid.py 中 DPID_PATTERN 16个进制的数字定义相吻合。
url = '/simpleswitch/mactable/{dpid}'


# simple_switch_rest_13.py 是用來定義兩個類別。

# 前者是控制器類別 SimpleSwitchController ,其中定義收到 HTTP request 時所需要回應的相對方法。

# 後者是``SimpleSwitchRest13`` 的類別,用來擴充「 交換器( Switching Hub ) 」讓它得以更新 MAC 位址表.

# 由於在 SimpleSwitchRest13 中已經有加入 Flow Entry 的功能,因此 FeaturesReply 方法被覆寫( overridden )並保留 datapath 物件

# 其实简单而言,ryu作为控制平面起到承上启下的作用,
# 面对上层的 HTTP request 需要 SimpleSwitchController 类别做出回应,
# 面对下层需要 SimpleSwitchRest13 类别 对数据平面作出回应

class SimpleSwitchRest13(simple_switch_13.SimpleSwitch13):
    _CONTEXTS = {'wsgi': WSGIApplication}

    # _CONTEXTS用来建立Ryu中WSGI网页服务器所对应的类别,因此可通过wsgi Key来取得网页服务器的实体

    def __init__(self, *args, **kwargs):
        super(SimpleSwitchRest13, self).__init__(*args, **kwargs)
        self.switches = {}
        wsgi = kwargs['wsgi']  # 通过wsgi Key来取得网页服务器的实体
        wsgi.register(SimpleSwitchController, {simple_switch_instance_name: self})
        # WSGIApplication的实体wsgi注册Controller类别
        # 注册则可以使用register方法。在使用register方法的时候,dictionary object会在名为simple_switch_api_app的Key中被传递。
        # 因此Controller的建构子就可以存取到simple_switch_api_app的实体

    # 父类别switch_features_handler已经被覆写( overridden )。
    # 这个方法会在SwitchFeatures事件发生时被触发,从事件物件ev取得datapath物件后存放至``switches``变数中。
    # 此时MAC位址的初始值将会设定为空白字典( empty dictionary )形态。

    @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
    def switch_features_handler(self, ev):
        super(SimpleSwitchRest13, self).switch_features_handler(ev)
        datapath = ev.msg.datapath
        self.switches[datapath.id] = datapath  # 存储datapath到switches,一个dpid代表一个switch,key=dpid,value=datapath
        self.mac_to_port.setdefault(datapath.id, {})  # 初始化mac_to_port表,
        # 上面这一步是HELLO信息过后,交换机和控制器才准备交换信息,存储所有的交换机datapath,
        # 以及初始化其mac_to_port表,此时是empty dictionary
        # mac_to_port  mac地址,和连接端口信息


    def set_mac_to_port(self, dpid, entry):
        """
              该方法将MAC地址和端口注册到指定的交换机。该方法主要被REST API的PUT方法所调用。
              PUT 指定交换机下发流表
        """
        # 參數entry則是用來儲存已經註冊的MAC位址和連結埠的資訊。
        # 參照MAC位址表的self.mac_to_port資訊,被註冊到交換器的FlowEntry將被搜尋

        mac_table = self.mac_to_port.setdefault(dpid, {})
        # 获取 dpid 的 mac_to_port表,字典形式存放,key=dpid


        datapath = self.switches.get(dpid)
        # 获取datapath,如果为None,证明没有该交换机

        # for i in entry:
        #     print(i)
        entry_port = entry['port']
        entry_mac = entry['mac']
        # 目的端口,目的地址

        # 交换机存在
        if datapath is not None:
            parser = datapath.ofproto_parser

            # entry_port不在mac_table里,下发两条流表项
            if entry_port not in mac_table.values():
                for mac, port in mac_table.items():
                    # from known device to new device
                    actions = [parser.OFPActionOutput(entry_port)]
                    match = parser.OFPMatch(in_port=port, eth_dst=entry_mac)
                    self.add_flow(datapath, 1, match, actions)

                    # from known device to new device
                    actions = [parser.OFPActionOutput(port)]
                    match = parser.OFPMatch(in_port=entry_port, eth_dst=mac)
                    self.add_flow(datapath, 1, match, actions)
                # 添加entry_mac,entry_port到mac_table
                mac_table.update({entry_mac: entry_port})
                # 这里可以看出来 mac_to_port 字典元素的key和value
        return mac_table

    # 例如:一個成對的MAC位址和連接埠號將被登錄在MAC位址表中。
    # 00: 00:00: 00:00: 01, 1

    # 而且成對的MAC位址和連接埠號將被當作參數entry。
    # 00: 00:00: 00:00: 02, 2

    # 最後被加入交換器當中的FlowEntry將會如下所示。

    # match條件:in_port = 1, dst_mac = 00:00: 00:00: 00:02 action:output = 2
    # match條件:in_port = 2, dst_mac = 00:00: 00:00: 00:01 action:output = 1


class SimpleSwitchController(ControllerBase):
    """
        定义收到HTTP请求时所需要回应的操作
    """

    def __init__(self, req, link, data, **config):
        super(SimpleSwitchController, self).__init__(req, link, data, **config)
        self.simpl_switch_spp = data[simple_switch_instance_name] # 请求数据data中获得SimpleSwitchRest13的实体

    # 被裝飾器( Decorator )處理的內容說明如下。
    # 第一個參數 任意的名稱
    # 第二個參數 指定URL 使得URL為http: // < 伺服器IP >: 8080 / simpleswitch / mactable / < datapathID >
    # 第三參數 指定HTTP相對應的方法。 指定GET相對應的方法。
    # 第四參數 指定URL的形式。 URL( / simpleswitch / mactable / {dpid}) 中{dpid}的部分必須與ryu / lib / dpid.py中DPID_PATTERN16個16進味的數字定義相吻合。
    
    # 當REST API被第二參數所指定的URL呼叫時,相對的HTTP GET list_mac_table方法就會被觸發。
    # 該方法將會取得{dpid}中儲存的datapathID以得到MAC位址並轉換成JSON的格式進行回傳。

    # 如果連結到Ryu的未知交換器data path ID被指定時,Ryu會返回編碼為404的回應。

    @route('simpleswitch', url, methods=['GET'],
           requirements={'dpid': dpid_lib.DPID_PATTERN})
    def list_mac_table(self, req, **kwargs):
        simple_switch = self.simpl_switch_spp
        # 获取{dpid}
        dpid = dpid_lib.str_to_dpid(kwargs['dpid'])  # 取出{dpid}中儲存的datapathID
        # 如果没有dpid,返回404
        if dpid not in simple_switch.mac_to_port:
            return Response(status=404)

        # 获取mac_table
        mac_table = simple_switch.mac_to_port.get(dpid, {})
        body = json.dumps(mac_table)
        return Response(content_type='application/json', body=body)

    # 使用PUT方式设置mac_table
    @route('simpleswitch', url, methods=['PUT'],
           requirements={'dpid': dpid_lib.DPID_PATTERN})
    def put_mac_table(self, req, **kwargs):
        simple_switch = self.simple_switch_spp
        dpid = dpid_lib.str_to_dpid(kwargs['dpid'])
        new_entry =eval(req.body)


        if dpid not in simple_switch.mac_to_port:
            return Response(status=400)

        try:
            mac_table = simple_switch.set_mac_to_port(dpid, new_entry)
            body = json.dumps(mac_table)
            return Response(content_type='application/json', body=body)
        except Exception as e:
            return Response(status=500)

在这里插入图片描述
在这里插入图片描述

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐