SDNLAB技术分享(二):从Toaster示例初探ODL MD-SAL架构



  • 分享嘉宾
    魏静波.png
    魏静波,目前就职于江苏省未来网络创新研究院,从事CDN、SDN领域的工作。在此之前就职于华为技术有限公司中央研发部2012实验室,从事网络设备监控、管理系统的设计、开发工作。


    Toaster是wiki上的一个例子。通过学习它,我们可以了大致了解MD-SAL架构的实现原理和设计思想。下面我们就直奔主题,看看Toaster例子吧。例子原文的网址是:https://wiki.opendaylight.org/view/OpenDaylight_Controller:MD-SAL:Toaster_Step-By-Step

    简单说来,Toaster例子描述了如何利用MD-SAL框架来实现软件控制面包机来烤面包。假设有一家公司叫“九阳面包机公司”,想开发一套系统通过手机来控制九阳面包机烤面包。为了有个比较,我们先设想一下如果不利用MD-SAL框架,如何实现?

    没有MD-SAL架构,我们可能会这样做:

    • 开发一个能通过私有协议控制九阳面包机的后台服务,后台服务会与真实的面包机交互,来控制面包机。
    • 开发一个九阳手机APP应用,来调用这个后台服务提供的接口,最终实现手机控制面包机烤面包。

    这样用户安装了九阳面包机的APP,就可以通过手机控制九阳面包机,进行烤面包了。如下图所示:

    从Toaster示例初探ODL-MD-SAL架构-图1.png

    然后,另外一家叫“美的面包机公司”,看到九阳公司这样做,面包机卖的很火。所以也开发了一套控制美的面包机的系统。

    这时问题来了,如果用户想控制多个不同品牌的面包机,那就不得不安装多个APP应用。如下图:

    从Toaster示例初探ODL-MD-SAL架构-图2.png

    我们看看Toaster的例子是怎样通过MD-SAL框架来解决这个问题的。

    从Toaster示例初探ODL-MD-SAL架构-图3.png

    见上图的实现步骤:

    • 1)所有的面包机厂商,按照MD-SAL中约定好的“面包机南向接口协议”,实现面包机和MD-SAL框架的交互。
    • 2)面包机APP应用程序通过调用MD-SAL提供的北向接口,实现APP和MD-SAL框架的交互。
    • 3)MD-SAL框架内部完成北向接口和南向接口之间的对接。这样只要是遵循了MD-SAL框架南向接口协议的面包机,都可以被这个APP控制。用户就不需要安装那么多的APP了。

    从这里我们可以看出MD-SAL的设计思想或者主要目的是在于:将上层应用(app)和底层设备(面包机)之间解耦,使得底层设备之间的差异不影响上层应用,上层应用可以兼容更多的底层设备。

    把这个思想延伸到ODL控制器上,也就是说MD-SAL框架使得ODL控制器可以兼容多家厂商的SDN交换机(只要SDN交换机都遵循同一种南向接口协议,例如openflow协议)。这样通过ODL控制器,上层应用可以不用关心市场上多种SDN交换机之间有哪些差异。

    下面我们继续通过Toaster例子,来了解这个MD-SAL框架内部是如何实现上面的1)、2)、3)的。
    见下图,要实现屏蔽多家厂商面包机差异化的目标。我们要关注几个重要部分,包括:

    • Toaster.yang文件及相应自动生成的代码。
    • 两个重要的osgi bundle:consumer和provider。
    • MD-SAL框架中提供提供数据存储服务的模块:Data Service。

    从Toaster示例初探ODL-MD-SAL架构-图4.png

    实现的大致步骤如下:

    步骤一:通过yang语法定义Toaster.yang文件。这是个yang模型,来定义面包机的标准属性和标准动作。例子里定义了面包机的生产厂家、型号、面包机工作状态。以及控制面包机可以被控制的动作,比如开始烤面包、取消烤面包等。(关于yang语言的语法,想详细了解的同学可以在网上查看RFC 6020 :http://tools.ietf.org/html/rfc6020)
    我们打开Toaster.yang文件,看一些关键内容。

    文件这里定义了面包机的属性信息,生成厂商、型号、面包机状态(up表示没有在工作,down表示正在烤面包)

    从Toaster示例初探ODL-MD-SAL架构-图5.png

    这里定义了面包机动作的标准接口,及接口所需要的参数,例如rpc make-toast为开始烤面包的接口,rpc cancel-toast是取消考面包的接口。

    从Toaster示例初探ODL-MD-SAL架构-图6.png

    步骤二:定义好yang文件后,利用maven插件编译工程,将定义的Toaster.yang文件的内容自动生成java代码。可以在src/main/yang-gen-asl里面看到自动生成的代码。如下图:

    从Toaster示例初探ODL-MD-SAL架构-图7.png

    从Toaster示例初探ODL-MD-SAL架构-图8.png

    步骤三:根据yang定义的属性和接口分别实现consumer和provider两个osgi bundle。而实现这两个bundler,都要依赖之前yang文件自动生成的java代码。

    步骤四:根据定义好的南向接口协议,provider与面包机之间实现交互(实现Toaster.yang模型中定义的标准属性和动作)。例如实现获取面包机厂商、型号、状态信息、执行开始烤面包、执行取消烤面包等一系列动作。

    步骤五:开发手机APP应用, APP应用调用consumer提供北向接口,来控制面包机烤面包。

    最后,通过手机APP远程控制面包机的系统就完成了。其中步骤四和步骤五不是Toaster例子的重点,所以在wiki的Toaster例子中并没有描述,例子中主要讲的是如何实现consumer和provider的。

    下面我们简略看一下步骤三的实现过程,这里主要是对网上例子中的实现过程做一些解释说明和总结。更细节实现,可以直接看网上的例子,那里面是最全的。

    Provider Bundler实现步骤:
    见下图,橙色部分代码是Yang文件自动生成的代码。开发过程中也会对部分自动代码进行二次编辑,添加实现。例如在ToasterProviderModule.java中添加构造对象、注册服务的功能,见图中的第5步。

    绿色部分是ODL的 MD-SAL框架内部提供的数据存储服务, Provider会向MD-SAL框架中注册可Provider Bundler可以提供的服务:

    从Toaster示例初探ODL-MD-SAL架构-图9.png

    Consumer Bundler实现步骤:
    见下图,橙色部分代码是根据yang文件自动生成的代码,开发过程中也会对部分自动代码进行二次编辑,添加实现。例如KitchenServiceModule.java中添加构造实例对象实现、添加从MD-SAL框架中获取控制面包机的服务的实现,见图中的第5步。

    绿色部分是ODL的 MD-SAL框架内部提供的数据存储服务,Consumer通过数据存储服务获取Provider已注册的服务:

    从Toaster示例初探ODL-MD-SAL架构-图10.png

    要看懂上面说的实现过程,可能还有要花点时间结合wiki上的例子中的代码来看。不过简单说来就是通过Yang、maven、osgi、java的一些技术和技巧,最终实现将Provider Bundler的中开发好的服务,注册进 ODL MD-SAL框架中。而Consumer Bundler可以从ODL MD-SAL框架中获取这些服务,并实现调用。从而实现Consumer Bunder端对底层面包机的控制操作。

    有想更深入了解Toaster细节实现的同学,也可以查看Toaster源代码来学习。下面分享一下Toaster源码环境搭建。环境搭建前,事先要准备好eclipse开发环境、git客户端以及安装maven。做好这些准备工作后就可以开始搭环境了:

    步骤一:从https://git.opendaylight.org/gerrit/p/controller.git下载ODL的源代码

    从Toaster示例初探ODL-MD-SAL架构-图11.png

    步骤二:从eclipse导入sample工程(不需要导入整个ODL的代码,只要导入Sample文件夹里的代码即可)

    从Toaster示例初探ODL-MD-SAL架构-图12.png

    步骤三:导入成功后maven会下载相关依赖jar包。下载完后,工程项目依然会有一些编译错误,这是正常现象,要解决编译报错问题需继续步骤四。

    步骤四:通过maven插件来根据yang文件自动生成代码。

    从Toaster示例初探ODL-MD-SAL架构-图13.png

    步骤五:自动代码生成完后,需要将自动生成的代码加载到编译路径下,加载完成后项目工程就不会再有编译错误了。

    从Toaster示例初探ODL-MD-SAL架构-图14.png

    从Toaster示例初探ODL-MD-SAL架构-图15.png

    完成以上步骤,就可以在eclipse上畅快的查看Toaster的源码了。

    从Toaster示例初探ODL-MD-SAL架构-图16.png

    通过对Toaster例子的学习,我们对ODL MD-SAL做一个简要总结:

    • MD-SAL主要包含的内容

      1. 数据服务(提供数据存储,Toaster例子中provider向其注册服务,consumer向其获取服务)
      2. RPCs接口(同步调用),接口方式包含JAVA APIs,DOM APIs,REST APIs。(Toaster例子中makeToast()、cancelToast()就是通过YANG语言定义、生成的RPCs接口)
      3. 事件通知(异步调用, Toaster例子中也给出里开发示例,详见wiki例子)
      4. YANG 模型驱动语言工具,(Toaster例子中的yang文件都是利用这个语法进定义的,语法规则详见RFC6020 :http://tools.ietf.org/html/rfc6020)
    • MD-SAL的设计思想及目标
      通过对底层设备服务抽象,模块间解耦实现上层应用对底层的设备的去差异化

    • MD-SAL主要研发方向(哪些人需要用MD-SAL开发)

      1. 利用YANG定义YANG MODEL(适合某一个组织制定统一MODEL标准并推进)
      2. 基于定义好的MODEL和特定协议(如openflow协议)开发provider和consumer,实现与底层设备交互,实现MODEL中定义的接口,并封装成北向接口(适合底层设备厂商,或者某些开源组织)
      3. 基于已经定制好、成熟MODEL的开发上层应用(适合开发业务系统为主的厂商)
      4. 以上方向全介入开发(适合既有设备端产品,也有上层应用系统的厂商,利用YANG来定制私有的model,方便自己的系统有更好的扩展性)
    • MD-SAL适用范围及误区,同时满足下面两种条件的场景适合利用MD-SAL开发

      1. 底层设备功能简单,功能容易被抽象统一。
      2. 与底层设备交互有多种方式,导致上层应用兼容多种底层设备比较困难。

    SDNLAB技术分享(二):从Toaster示例初探ODL MD-SAL架构
    SDNLAB君 • 15-09-28 • 2,299 人围观
    【编者的话】本文系OpenDayLight SDNLAB研究群(群号:194240432)分享整理而成,分享者魏静波通过对ODL wiki上的toaster例子进行深入浅出的细致讲解形象的描述了ODL MD-SAL的开发架构原理。
    分享嘉宾

    魏静波
    魏静波,目前就职于江苏省未来网络创新研究院,从事CDN、SDN领域的工作。在此之前就职于华为技术有限公司中央研发部2012实验室,从事网络设备监控、管理系统的设计、开发工作。

    Toaster是wiki上的一个例子。通过学习它,我们可以了大致了解MD-SAL架构的实现原理和设计思想。下面我们就直奔主题,看看Toaster例子吧。例子原文的网址是:https://wiki.opendaylight.org/view/OpenDaylight_Controller:MD-SAL:Toaster_Step-By-Step

    简单说来,Toaster例子描述了如何利用MD-SAL框架来实现软件控制面包机来烤面包。假设有一家公司叫“九阳面包机公司”,想开发一套系统通过手机来控制九阳面包机烤面包。为了有个比较,我们先设想一下如果不利用MD-SAL框架,如何实现?

    没有MD-SAL架构,我们可能会这样做:

    开发一个能通过私有协议控制九阳面包机的后台服务,后台服务会与真实的面包机交互,来控制面包机。
    开发一个九阳手机APP应用,来调用这个后台服务提供的接口,最终实现手机控制面包机烤面包。
    这样用户安装了九阳面包机的APP,就可以通过手机控制九阳面包机,进行烤面包了。如下图所示:

    从Toaster示例初探ODL MD-SAL架构 图1
    然后,另外一家叫“美的面包机公司”,看到九阳公司这样做,面包机卖的很火。所以也开发了一套控制美的面包机的系统。

    这时问题来了,如果用户想控制多个不同品牌的面包机,那就不得不安装多个APP应用。如下图:

    从Toaster示例初探ODL MD-SAL架构 图2
    我们看看Toaster的例子是怎样通过MD-SAL框架来解决这个问题的。

    从Toaster示例初探ODL MD-SAL架构 图3
    见上图的实现步骤:

    1)所有的面包机厂商,按照MD-SAL中约定好的“面包机南向接口协议”,实现面包机和MD-SAL框架的交互。
    2)面包机APP应用程序通过调用MD-SAL提供的北向接口,实现APP和MD-SAL框架的交互。
    3)MD-SAL框架内部完成北向接口和南向接口之间的对接。这样只要是遵循了MD-SAL框架南向接口协议的面包机,都可以被这个APP控制。用户就不需要安装那么多的APP了。
    从这里我们可以看出MD-SAL的设计思想或者主要目的是在于:将上层应用(app)和底层设备(面包机)之间解耦,使得底层设备之间的差异不影响上层应用,上层应用可以兼容更多的底层设备。

    把这个思想延伸到ODL控制器上,也就是说MD-SAL框架使得ODL控制器可以兼容多家厂商的SDN交换机(只要SDN交换机都遵循同一种南向接口协议,例如openflow协议)。这样通过ODL控制器,上层应用可以不用关心市场上多种SDN交换机之间有哪些差异。

    下面我们继续通过Toaster例子,来了解这个MD-SAL框架内部是如何实现上面的1)、2)、3)的。
    见下图,要实现屏蔽多家厂商面包机差异化的目标。我们要关注几个重要部分,包括:

    Toaster.yang文件及相应自动生成的代码。
    两个重要的osgi bundle:consumer和provider。
    MD-SAL框架中提供提供数据存储服务的模块:Data Service。
    从Toaster示例初探ODL MD-SAL架构 图4
    实现的大致步骤如下:

    步骤一:通过yang语法定义Toaster.yang文件。这是个yang模型,来定义面包机的标准属性和标准动作。例子里定义了面包机的生产厂家、型号、面包机工作状态。以及控制面包机可以被控制的动作,比如开始烤面包、取消烤面包等。(关于yang语言的语法,想详细了解的同学可以在网上查看RFC 6020 :http://tools.ietf.org/html/rfc6020)
    我们打开Toaster.yang文件,看一些关键内容。

    文件这里定义了面包机的属性信息,生成厂商、型号、面包机状态(up表示没有在工作,down表示正在烤面包)

    从Toaster示例初探ODL MD-SAL架构 图5
    这里定义了面包机动作的标准接口,及接口所需要的参数,例如rpc make-toast为开始烤面包的接口,rpc cancel-toast是取消考面包的接口。

    从Toaster示例初探ODL MD-SAL架构 图6
    步骤二:定义好yang文件后,利用maven插件编译工程,将定义的Toaster.yang文件的内容自动生成java代码。可以在src/main/yang-gen-asl里面看到自动生成的代码。如下图:

    从Toaster示例初探ODL MD-SAL架构 图7
    从Toaster示例初探ODL MD-SAL架构 图8
    步骤三:根据yang定义的属性和接口分别实现consumer和provider两个osgi bundle。而实现这两个bundler,都要依赖之前yang文件自动生成的java代码。

    步骤四:根据定义好的南向接口协议,provider与面包机之间实现交互(实现Toaster.yang模型中定义的标准属性和动作)。例如实现获取面包机厂商、型号、状态信息、执行开始烤面包、执行取消烤面包等一系列动作。

    步骤五:开发手机APP应用, APP应用调用consumer提供北向接口,来控制面包机烤面包。

    最后,通过手机APP远程控制面包机的系统就完成了。其中步骤四和步骤五不是Toaster例子的重点,所以在wiki的Toaster例子中并没有描述,例子中主要讲的是如何实现consumer和provider的。

    下面我们简略看一下步骤三的实现过程,这里主要是对网上例子中的实现过程做一些解释说明和总结。更细节实现,可以直接看网上的例子,那里面是最全的。

    Provider Bundler实现步骤:
    见下图,橙色部分代码是Yang文件自动生成的代码。开发过程中也会对部分自动代码进行二次编辑,添加实现。例如在ToasterProviderModule.java中添加构造对象、注册服务的功能,见图中的第5步。

    绿色部分是ODL的 MD-SAL框架内部提供的数据存储服务, Provider会向MD-SAL框架中注册可Provider Bundler可以提供的服务:

    从Toaster示例初探ODL MD-SAL架构 图9
    Consumer Bundler实现步骤:
    见下图,橙色部分代码是根据yang文件自动生成的代码,开发过程中也会对部分自动代码进行二次编辑,添加实现。例如KitchenServiceModule.java中添加构造实例对象实现、添加从MD-SAL框架中获取控制面包机的服务的实现,见图中的第5步。

    绿色部分是ODL的 MD-SAL框架内部提供的数据存储服务,Consumer通过数据存储服务获取Provider已注册的服务:

    从Toaster示例初探ODL MD-SAL架构 图10
    要看懂上面说的实现过程,可能还有要花点时间结合wiki上的例子中的代码来看。不过简单说来就是通过Yang、maven、osgi、java的一些技术和技巧,最终实现将Provider Bundler的中开发好的服务,注册进 ODL MD-SAL框架中。而Consumer Bundler可以从ODL MD-SAL框架中获取这些服务,并实现调用。从而实现Consumer Bunder端对底层面包机的控制操作。

    有想更深入了解Toaster细节实现的同学,也可以查看Toaster源代码来学习。下面分享一下Toaster源码环境搭建。环境搭建前,事先要准备好eclipse开发环境、git客户端以及安装maven。做好这些准备工作后就可以开始搭环境了:

    步骤一:从https://git.opendaylight.org/gerrit/p/controller.git下载ODL的源代码

    从Toaster示例初探ODL MD-SAL架构 图11
    步骤二:从eclipse导入sample工程(不需要导入整个ODL的代码,只要导入Sample文件夹里的代码即可)

    从Toaster示例初探ODL MD-SAL架构 图12
    步骤三:导入成功后maven会下载相关依赖jar包。下载完后,工程项目依然会有一些编译错误,这是正常现象,要解决编译报错问题需继续步骤四。

    步骤四:通过maven插件来根据yang文件自动生成代码。

    从Toaster示例初探ODL MD-SAL架构 图13
    步骤五:自动代码生成完后,需要将自动生成的代码加载到编译路径下,加载完成后项目工程就不会再有编译错误了。

    从Toaster示例初探ODL MD-SAL架构 图14
    从Toaster示例初探ODL MD-SAL架构 图15
    完成以上步骤,就可以在eclipse上畅快的查看Toaster的源码了。

    从Toaster示例初探ODL MD-SAL架构 图16
    通过对Toaster例子的学习,我们对ODL MD-SAL做一个简要总结:

    MD-SAL主要包含的内容
    数据服务(提供数据存储,Toaster例子中provider向其注册服务,consumer向其获取服务)
    RPCs接口(同步调用),接口方式包含JAVA APIs,DOM APIs,REST APIs。(Toaster例子中makeToast()、cancelToast()就是通过YANG语言定义、生成的RPCs接口)
    事件通知(异步调用, Toaster例子中也给出里开发示例,详见wiki例子)
    YANG 模型驱动语言工具,(Toaster例子中的yang文件都是利用这个语法进定义的,语法规则详见RFC6020 :http://tools.ietf.org/html/rfc6020)
    MD-SAL的设计思想及目标
    通过对底层设备服务抽象,模块间解耦实现上层应用对底层的设备的去差异化

    MD-SAL主要研发方向(哪些人需要用MD-SAL开发)
    利用YANG定义YANG MODEL(适合某一个组织制定统一MODEL标准并推进)
    基于定义好的MODEL和特定协议(如openflow协议)开发provider和consumer,实现与底层设备交互,实现MODEL中定义的接口,并封装成北向接口(适合底层设备厂商,或者某些开源组织)
    基于已经定制好、成熟MODEL的开发上层应用(适合开发业务系统为主的厂商)
    以上方向全介入开发(适合既有设备端产品,也有上层应用系统的厂商,利用YANG来定制私有的model,方便自己的系统有更好的扩展性)
    MD-SAL适用范围及误区,同时满足下面两种条件的场景适合利用MD-SAL开发
    底层设备功能简单,功能容易被抽象统一。
    与底层设备交互有多种方式,导致上层应用兼容多种底层设备比较困难。
    注:基于MD-SAL开发系统会增加开发难度和工作量,并可能会对系统性能带来一些损耗。在不必要的场景下不建议使用。

    最后给大家一个问题思考一下,在Toaster例子中为什么不直接通过provider bundler封装北向接口给上层应用调用,而是再多开发出一个consumer bundler来封装北向接口给上层调用?

    之所以这样做,个人认为有两个层面的原因:一个是有利于业务层面的分层,另一个重要原因是实现模块的解耦,更有利于ODL的整个开源生态环境。

    有利于业务层面分层的意思是:provider端与底层面包机进行交互,更靠近底层。因此Provider模块职责的重点是在于如何实现控制面包机的原子操作。比如检查面包机状态、开始考面包机、取消烤面包机等。provider端开发的重点,应该放在如何更高效的实现与面包机的南向协议,使得provider和面包机的通讯更加稳定和高效。

    而consumer层为上层提供服务,更偏向于上层业务。consumer封装的北向接口可以有一定的业务逻辑。一个北向接口中可能会执行多个provider的原子操作。比如consumer封装的“烤面包的北向接口”,内部先要调用provider的检查面包机的状态的接口,检查面包机是否正在烤其他面包,如果没有烤其他面包,才可以调用provider提供的烤面包机接口。consumer开发重点应该放在对provider原子接口的封装上,更多的考虑可能会出现的业务场景,结合实际业务场景封装出合适的北向接口。这样有利于上层业务系统调用北向接口时,只专注自己的业务实现,而不必过多关心底层设备交互的内部细节。

    通过划分出provider层和consumer层和应用层,可以使得系统间层次结构清晰合理。每一层的开发人员都可以各尽其责,专注于自己所在的业务领域。这在大型、复杂业务的系统实现过程中尤为重要。

    划分出provider和consumer另一个重要原因是模块间的解耦。ODL控制是开源系统,拿面包机例子来说,当定义了Toaster.yang后,可以有多个组织或个人来分别实现toaster.provider和toaster.consumer。它们是ODL系统的osgi插件,可以安装、卸载。当上层应用APP开发好后,如果觉的现在用的这个toaster.provider插件不好用。比如不稳定、效率低,如果有其他人开发出了一个更好用的toaster.providerII,那我们就可以在ODL系统中更换这个插件,而上层app应用和consumer端都不需要做任何修改。也可以更换consumer插件,而provider端不需要做任何改变。这种形式有利于ODL开源系统相同的插件存在一种良性竞争关系,优胜劣汰。从而有利于ODL开源系统的生态环境可以不断的进化,这一点也是MD-SAL强大、有生命力的地方。

    本次分享就到这里,内容如有不正之处,欢迎大家及时指出纠正。

    Q&A

    广州_追网球猫
    请问MD-SAL的Li版的开发和He版的开发相同吗?

    大概开发流程是相同的,可能有些细节不太一样。这个要查一下版本发布说明,wiki的例子是基于Helium版本的。

    桂电-胡启伟
    数据存储服务在流量大的时候,注册服务和获取服务需求增大,这时候性能怎么考虑的,查完一次之后会有缓存吗,不用第二次还去查。

    这个本身就是缓存,而且是在ODL启动的时候进行注册和获取的,基本上可以不用考虑获取服务的性能问题。

    胖子@南京
    缓存的话,集群缓存怎么同步?

    我没有去看这块的源码,但是缓存同步实现是很好实现的。就是两个控制器之间交互一下,交互过程中类似于把hashmap中的数据互相拷贝一份。还是比较简单的,想深究的话,还是要查看源码,这块的实现思路有很多。如果考虑数据的完整性,可能还会定时进行同步。

    山西-文斌王
    请问如果要是想实现一个toaster gui该如何做呢?gui要实现的功能是设置toaster的种类以及烤熟的程度。

    gui属于页面设计,这块就是随意发挥了。页面设计的原则,简单,就是让别人看到界面直接就会操作。至于设置toaster的种类、烤熟程度,这块就是调用北向接口了几设置接口参数了。比如烤熟程度就是可以作为一个接口参数。

    杭州-嗔戒
    请问关于部署问题能不能讲一下,部署jar包的放置具体是怎么一个方式

    wiki上的例子是作为osgi bundle安装在odl中的。安装好后可以通过HTTP请求来调用restconf的toaster接口。

    胖子@南京
    不部署到控制器里面,单独的bundle可以吗

    不可以,要依赖ODL的MD-SAL框架。需要调用ODL MD-SAL的数据服务,所以要部署在一起。你说的独立部署我感觉是不行的,如果把ODL里面的MD-SAL数据服务的bundler拆出来和例子部署,也许能行。不过这个也搞复杂了。

    转载自: http://www.sdnlab.com/13972.html


登录后回复
 

与 青云QingCloud 社区 的连接断开,我们正在尝试重连,请耐心等待