怎样写 openstack的neutron Neutron 的 Plugin

鉴于不知道Neutron的人也不会看这篇文章,而知道的人也不用我再啰嗦Neutron是什么东西,我决定跳过Neutron简介,直接爆料。
首先要介绍一下我的开发环境。我没有使用DevStack,而是直接在电脑上安装了三个Virtual Box,然后根据OpenStack的Ubuntu 安装指南部署了一个环境:一个控制节点,一个网络节点和一个计算节点。接下来我会直接在控制节点上修改 &your path&/neutron/ 下面的文件,然后通过重启neutron 的各个service来更新我的修改。如果使用DevStack的话估计和我的情况也差不太多。
那我的neutron在安装完之后的完整路径是&/usr/lib/python2.7/dist-packages/neutron。neutron下的文件结构如下:
- neutron/
  - agent/
  - api/
  - cmd/
  - common/
  - debug/
  - extensions/
  - locale/
  - notifiers/
  - openstack/
  - plugins/
  - scheduler/
  - server/
  - services/
  - manager.py
  - neutron_plugin_base_v2.py
  - service.py
  - wsgi.py
其中,自己写的plugin就会放在plugins/ 下面,而neutron_plugin_base_v2.py中定义了一个plugin应该实现的最小的API集合。其他还有一些在extensions/ 和db/ 下面的文件也比较重要,会在接下来的一些文章中提到。最后省略了一些目前我没有特别用到的文件。
如果有人对Neutron如何加载plugin感兴趣,可以再manager.py中加一些断点用pdb进行debug。它里面有一个class叫NeutronManager, 在初始化过程中有一个语句加载了plugin:plugin_provider = cfg.CONF.core_plugin。具体debug方法可以参考Yong Sheng Gong的一个ppt,链接在这里:http://www.slideshare.net/gongys2004/inside-neutron-2
好,现在我们就要开始实现自己的plugin了。
第一步就是在plugins下面创建自己的文件夹和一些文件:
  - plugins
    - myplugin
      - __init__.py
      - plugin.py
上述两个文件是最基本的,当然plugin.py也可以用不同的名字。但是__init__.py的名字是不能改的,这个用来告诉Python myplugin可以看做是一个module。plugin.py里面可以先定义一个基本空的class 叫做MyPlugin,虽然现在这个plugin什么用都没有。
from neutron.db import db_base_plugin_v2
from mon import log
LOG = log.getLogger(__name__)
class MyPlugin(db_base_plugin_v2.NeutronDbPluginV2):
def __init__(self):
("MyPlugin is started.")
代码中出现的 neutron.db 和 mon 我会在之后的章节中提到。现在暂时不用去管它们。&
第二步,搞定plugin.py之后就要想办法在Neutron里注册一下这个plugin,这样Neutron启动的时候就会认识这个plugin并且知道怎么加载它。这时候需要找到一个entry_points.txt文件。它不在之前我们提到的那个neutron大文件夹下面,而是在与之平行的一个neutron egg info 文件夹下。具体在我的环境中:
- neutron-2014.1.egg-info
  - entry_points.txt
在这个文件中,有一个选项是[neutron.core_plugins],所有的注册的core_plugins都在里面。在它下面我们可以自己加一行:
  myplugin = neutron.plugins.myplugin.plugin:MyPlugin
然后在 /etc/neutron/neturon.conf 中有一个选项是[DEFAULT],在它下面有一行用来设置core plugin:
  core_plugin = myplugin
如果没有找到entry_points.txt这个文件的话,据说另外一个办法就是直接在 neutron.conf 中指明这个plugin:
  core_plugin =&neutron.plugins.myplugin.plugin.MyPlugin
不过这个方法我没有尝试过。
做完这两步之后再重启一下neutron就搞定收工了。看一下/var/log/neutron/server.log,如果出现了我们那行log信息的话就说明Neutron已经加载了我们的什么都干不了的plugin了。
那么plugin具体是怎么实现的呢?请看下回分解。
阅读(...) 评论()怎样写 OpenStack Neutron 的 Extension (一) - 好大一片云 - 推酷
怎样写 OpenStack Neutron 的 Extension (一) - 好大一片云
前两篇文章讨论了怎么写一个 Neutron 的插件。但是最基本的插件只包括 Network, Port,和 Subnet 三种资源。如果需要引入新的资源,比如一个二层的 gateway 的话,就需要在插件的基础上再写一个 extension, 也就是扩展。
Neutron 已经预定义了很多扩展,可以参看 neutron/extensions 下面的文件,我在这里就不一一列举了。如果正好有一种是你需要的,那直接拿过来用就好了。如果需要自己从头搭起的话,可以现在 自己的 plugin 文件夹下面创建一个 extensions 文件夹,然后在这个文件夹下面创建两个文件: __init__.py 和 myextension.py:
- neutron/
- plugins/
- myplugin/
- __init__.py
- plugin.py
- extensions/
- __init__.py
- myextension.py
__init__.py 是空的就行了。在 myextension.py 中需要定义两个东西:一个叫RESOURCE_ATTRIBUTE_MAP 的词典和一个叫 Myextension 的类。RESOURCE_ATTRIBUTE_MAP里面放的就是你的这个新扩展的属性,例如:
RESOURCE_ATTRIBUTE_MAP = {
'myextensions': {
'id': {'allow_post': False, 'allow_put': False,
'is_visible': True},
'name': {'allow_post': True, 'allow_put': True,
'is_visible': True},
'tenant_id': {'allow_post': True, 'allow_put': False,
'validate': {'type:string': None},
'required_by_policy': True,
'is_visible': True}
需要注意的是在词典中,第一层的 key ‘myextensions’ 就是文件名 ’myextension‘ 加上一个 ‘s'。第二层的 keys ’id‘, ’name‘, ’tenant_id‘ 就是这个扩展的三个属性。第三层的 keys 在 neutron/api/v2/attributes.py 中有比较详细的解释,我把它搬到这里来了:
# The following is a short reference for understanding attribute info:
# default: default value of the attribute (if missing, the attribute
# becomes mandatory.
# allow_post: the attribute can be used on POST requests.
# allow_put: the attribute can be used on PUT requests.
# validate: specifies rules for validating data in the attribute.
# convert_to: transformation to apply to the value before it is returned
# is_visible: the attribute is returned in GET responses.
# required_by_policy: the attribute is required by the policy engine and
# should therefore be filled by the API layer even if not present in
# request body.
# enforce_policy: the attribute is actively part of the policy enforcing
# mechanism, ie: there might be rules which refer to this attribute.
定义新类的时候需要注意一点,这个类的名字与包含这个类的文件名的唯一区别必须是一个首字母大写,另一个首字母小写。也就是说把MyExtension当做类的名字可能就会导致出错。具体原因可以参考 neutron/api/extensions.py 中 ExtensionManager 的_load_all_extensions_from_path 方法的实现。Myextension 这个类可以继承 neutron/api/extensions.py 这个文件中的一个类:ExtensionDescriptor,也可以自己定义。下面给出一个继承了该类的定义:
from neutron.api import extensionsfrom neutron import managerfrom neutron.api.v2 import base
class Myextension(extensions.ExtensionDescriptor):
# The name of this class should be the same as the file name
# The first letter must be changed from lower case to upper case
# There are a couple of methods and their properties defined in the
# parent class of this class, ExtensionDescriptor you can check them
@classmethod
def get_name(cls):
# You can coin a name for this extension
return &My Extension&
@classmethod
def get_alias(cls):
# This alias will be used by your core_plugin class to load
# the extension
return &my-extensions&
@classmethod
def get_description(cls):
# A small description about this extension
return &An extension defined by myself. Haha!&
@classmethod
def get_namespace(cls):
# The XML namespace for this extension
return &http://docs.openstack.org/ext/myextension/api/v1.0&
@classmethod
def get_updated(cls):
# Specify when was this extension last updated,
# good for management when there are changes in the design
return &T00:00:00-00:00&
@classmethod
def get_resources(cls):
# This method registers the URL and the dictionary
# attributes on the neutron-server.
plugin = manager.NeutronManager.get_plugin()
resource_name = 'myextension'
collection_name = '%ss' % resource_name
params = RESOURCE_ATTRIBUTE_MAP.get(collection_name, dict())
controller = base.create_resource(collection_name, resource_name,
plugin, params, allow_bulk=False)
ex = extensions.ResourceExtension(collection_name, controller)
exts.append(ex)
return exts
到这一步为止,这个 myextension.py 文件基本就算是大功告成了,接下来需要去配置 /etc/neutron/neutron.conf 文件,告诉 Neutron 去哪里找到这个扩展。那么在该文件的[DEFAULT]下面,我们可以找到一个选项叫做api_extensions_path,并且把刚刚创建的 extensions 文件夹的位置赋给它,例如:
api_extensions_path = /usr/lib/python2.7/dist-packages/neutron/plugins/myplugin/extensions
然后在自己的 MyPlugin 类的定义中还要加一句话,告诉 Neutron 我的插件支持这个扩展。需要注意的是这里的方括号中的名字应该与上面 get_alias() 方法获得的名字一致。
class MyPlugin(db_base_plugin_v2.NeutronDbPluginV2):  ...  supported_extension_aliases = ['my-extensions']    def __init__(self):    ...  ...
最后重启一下 neutron server, “service neutron-server restart”, 如果看到 /var/log/neutron/server.log 里面有 Loaded extension: my-extensions 的字样就说明成功了。
在接下来的一些文章中,我会继续讨论一下如何实现一个扩展的不同操作,如何在 CLI 中加入对自定义扩展的命令支持等内容。
已发表评论数()
请填写推刊名
描述不能大于100个字符!
权限设置: 公开
仅自己可见
正文不准确
标题不准确
排版有问题
主题不准确
没有分页内容
图片无法显示
视频无法显示
与原文不一致  Openstack允许不同租户创建自己的网络,并通过租户自己的Router连接到外网。网络是允许重叠的,即不同租户都可以创建了192.168.1.0/24这个网段,都有一个192.168.1.1的IP,这样就需要在底层实现对不同租户网络的隔离。
  Neutron(openstack中的网络管理模块)实现隔离的方式不止一种(根据不同的plugin而不同),我只讨论我所配置的Openstack(应该也是最觉的配置方式之一)的情况,即OVS_Neutron_Plugin,即选用Open vSwith作为虚拟网桥的方式,对处于不同物理节点上的ovs bridge,采用GRE隧道的方式连接(另外一种是Vlan)。
  根据查看Neutron相关部分代码和实验验证,得知Neutron是通过Vlan的方式来作网络隔离的,这一点也不意外。下表列出了某计算节点上br-int的端口配置情况,可以看到不同的网络分配了不同的tag值,这里的tag就是Vlan ID,所有从这些port进入的数据都会被加上相应的Vlan ID在交换机里传输和处理,不同的Vlan的端口是不会接收到其它Vlan的数据的,这样就在一个物理节点为解决了隔离问题。
root@compute1:~# ovs-vsctl show
a9505710-5f04-4e25-88a5-03b04a367393
Bridge br-int
Port "qvo29608ccb-bb"
Interface "qvo29608ccb-bb"
Port br-int
Interface br-int
type: internal
Port "qvocaad8131-1c"
Interface "qvocaad8131-1c"
Port "qvoae9adf7e-04"
Interface "qvoae9adf7e-04"
Port patch-tun
Interface patch-tun
type: patch
options: {peer=patch-int}
Port "qvo222d72f1-80"
Interface "qvo222d72f1-80"
Port "qvo68d15feb-77"
Interface "qvo68d15feb-77"
ovs_version: "2.0.0"
  为什么说只是在一个物理节点上解决了隔离问题,因为Neutron为同一个网络在不同的物理节点上(比如控制节点和计算节点)上分配的Vlan ID是不一样的,这样做的原因我想一是为了部署的灵活,更重要的是为了充分地利用Vlan资源。因为Vlan ID最大只能到4096,在复杂的云网络中是比较稀缺的资源,不同的物理节点上自由分配Vlan ID可以使资源的利用最大化,特别是在系统的物理节点非常多的情况下。
  那么既然不同物理节点的Vlan分配不一致,但是不同物理节点上的虚拟网络必须要保持一致,这就需要一个翻译的过程。前面已经提到,不同节点间的虚拟网络是通过GRE隧道连接的,这个翻译正是在数据进出隧道的时候完成的。我们可以看Neutron在某计算节点的br-tun桥中为此而下属OpenFlow流规则。
root@compute1:~# ovs-ofctl dump-flows br-tun |grep "vlan" cookie=0x0, duration=1302.92s, table=2, n_packets=4, n_bytes=300, idle_age=1285, priority=1,tun_id=0x1 actions=mod_vlan_vid:3,resubmit(,10) cookie=0x0, duration=s, table=2, n_packets=3, n_bytes=210, idle_age=1290, priority=1,tun_id=0x3 actions=mod_vlan_vid:1,resubmit(,10) cookie=0x0, duration=s, table=2, n_packets=6, n_bytes=468, idle_age=1288, priority=1,tun_id=0x4 actions=mod_vlan_vid:2,resubmit(,10) cookie=0x0, duration=s, table=21, n_packets=0, n_bytes=0, idle_age=1303, hard_age=1302, priority=1,dl_vlan=3 actions=strip_vlan,set_tunnel:0x1,output:2 cookie=0x0, duration=s, table=21, n_packets=0, n_bytes=0, idle_age=1303, hard_age=1302, priority=1,dl_vlan=2 actions=strip_vlan,set_tunnel:0x4,output:2 cookie=0x0, duration=s, table=21, n_packets=0, n_bytes=0, idle_age=1304, hard_age=1302, priority=1,dl_vlan=1 actions=strip_vlan,set_tunnel:0x3,output:2
  可以看到此Openstack下有三个租户网络,以粗体部分为例,解释一下这些流规则的语义:第4条规则,当有vlan id = 3的数据准备进入隧道离开本节点到达对岸节点的时候,将会去掉原有的Vlan ID,而加上tun id =1;第1条规则,则是相反的操作,当有tun id=1的数据从隧道出来的时候,将会为其加上vlan id=3,再由下一级流表进行后续处理,这样就在一个节点上完成了vlan id=3 &--& tun id=1的翻译(转换)。
  我们再来看此隧道对面的节点(为控制节点)的隧道又是如何配置的呢?
root@controller:~# ovs-ofctl dump-flows br-tun |grep vlan
cookie=0x0, duration=1398.155s, table=2, n_packets=0, n_bytes=0, idle_age=1398, priority=1,tun_id=0x1 actions=mod_vlan_vid:1,resubmit(,10)
cookie=0x0, duration=1397.476s, table=2, n_packets=0, n_bytes=0, idle_age=1397, priority=1,tun_id=0x5 actions=mod_vlan_vid:2,resubmit(,10)
cookie=0x0, duration=1396.634s, table=2, n_packets=0, n_bytes=0, idle_age=1396, priority=1,tun_id=0x3 actions=mod_vlan_vid:4,resubmit(,10)
cookie=0x0, duration=1397.033s, table=2, n_packets=0, n_bytes=0, idle_age=1397, priority=1,tun_id=0x4 actions=mod_vlan_vid:3,resubmit(,10)
cookie=0x0, duration=1396.687s, table=21, n_packets=4, n_bytes=300, idle_age=1385, priority=1,dl_vlan=4 actions=strip_vlan,set_tunnel:0x3,output:2
cookie=0x0, duration=1397.139s, table=21, n_packets=6, n_bytes=468, idle_age=1383, priority=1,dl_vlan=3 actions=strip_vlan,set_tunnel:0x4,output:2
cookie=0x0, duration=1397.537s, table=21, n_packets=0, n_bytes=0, idle_age=1397, priority=1,dl_vlan=2 actions=strip_vlan,set_tunnel:0x5,output:2
cookie=0x0, duration=1398.218s, table=21, n_packets=4, n_bytes=300, idle_age=1380, priority=1,dl_vlan=1 actions=strip_vlan,set_tunnel:0x1,output:2
  可以看到在控制节点上,将tun id 1 翻译成了vlan id =1,证明在不同的物理节点上为不同的网络分配Vlan确实是不同的。
  下图是Neutron实现整个隔离过程的总结,两台虚拟机属于同一虚拟网络,但它们在各自的节点上分配的Vlan Id却不同,对过各自的Tunnel交换机对Vlan &--& GRE的转换,实现一致性。
  接下来将分析一旦OVS网桥接上SDN控制器将是什么情况。从某种意义上,我们也可以说Neutron其实就是一个SDN的控制器,因为通过它可以进行端口的创建和删除,流规则的配置等操作,但是可惜的是它不是通过开放的OpenFlow和OF-Config来实现这些操作的,而是通过OVS特有的接口OVSDB的方式来实现的。对研究OpenFlow的人来说,常常需要将Neutron所创建的这些OVS Bridge连接上OpenFlow的控制器。这里选用的是Floodlight,它是Big Switch公司控制器的开源版,当OVS Bridge连上Openflow控制器之后,它就由一个OpenFlow hybrid的混合交换机变成一个Openflow交换机,它的行为就遵从OpenFlow协议的规范,在每个交换机里的流规则不再是一条简单的Normal动作通配一切,而是由控制器对每一条流的源/目的等信息进行解析再根据策略实时下发转发规则,这对网络流量的行为监控和管理都十分有利。但在下发任何规则之前,Floodlight将会将接入的交换机原有的规则全部清空(也许也可以保留或者部分保留,待研究,但这不是根本问题)。
  清空规则对tunnel的影响是最大的,如前所示Neutron为了保证网络的隔离和不同物理节点上虚拟网络的连续性,在br-tun的bridge上配置了一系列的转换规则,清除这些规则意味着转换关系的丢失。如果只是转换关系的丢失,我们完全可以通过控制器再次添加相同的OpenFlow流来弥补,但关键的问题还不在于此。前面提到Neutron为每一个租户网络分配了不同的Vlan,属于某个租户网络的Port都统一打上了对应的Vlan tag,以使每一个出现在网络中的数据帧都有对应的vlan标记。但是这种为交换机access port加上vlan tag就可以为数据包自动加上相应vlan标记的方式是传统网络设备的特性,并不是OpenFlow规范所支持的内容,也就是说,前面提到过的OVS Bridge上的tag(如下),在OVS作为OpenFlow交换机的时候是无意义的(除非使用非OpenFlow规范内的Normal动作)。  
Bridge br-int
  Port "qvo68d15feb-77"
Interface "qvo68d15feb-77"
&  也就是说,一旦将OVS Bridge连上控制器,由虚拟主机进入交换机中的数据包将不再被加上Vlan tag,网络隔离被破坏。事实上我们也可以从wireshark抓取的GRE封装包来印证,这是未连接控制器的数据时,由Vlan3的虚拟机发出的数据包。可以看到只有Gre隧道的key=0x1,而封装的以太帧中也没有Vlan信息(进入br-int时应是有的,但是进入tunnel的时候被strip_vlan动作去掉了,参见前文),符合前面的分析。
  而一旦连接上控制器,数据包没有Vlan信息(进入br-int就没有),Gre key =0(没有相应的规则添加tun id了),亦即不再有任何标识来隔离不同网络。
&  更进一步地,为了证明我们的分析,现在只将br-tun连接控制器,这时我们可以看到数据包变成了如下情况:
  因为控制器清除了br-tun上的转换信息,所以隧道数据gre key = 0,没有被设置,但是br-int并没有连接控制器,它依然是一个openflow hybrid的交换机,里面只有一条规则,即对所有数据包执行Normal动作,OpenFlow对Normal没有规范,完全由厂商自行设计实现,代表传统转发行为,所以它端口的vlan tag依然有效,GRE封装的以太帧类型是802.1Q(即Vlan),其ID=3。
  至此,问题的根源已经明了,那就是ovs端口上的tag标签只是一种传统交换机的特性,不是OpenFlow规范里的内容。虽然OVS是我们最常用的虚拟OpenFlow交换机,但是它却远不只是一个OpenFlow交换机,它还支持许多传统交换机的功能,比如设置Mirror,它也有自己特性,比如OVSDB接口。而端口的Vlan tag刚好是一个OpenFlow之外的特性。
  事实上我们查看OpenFLow的协议文档(1.3.2版本)关于端口配置的部分(7.2.1节:Port Structures):
enum ofp_port_config {
OFPPC_PORT_DOWN = 1 && 0, /* Port is administratively down. */
OFPPC_NO_RECV = 1 && 2, /* Drop all packets received by port. */
OFPPC_NO_FWD = 1 && 5, /* Drop packets forwarded to port. */
OFPPC_NO_PACKET_IN = 1 && 6 /* Do not send packet-in msgs for port. */
  较早的1.0版本的OpenFlow中对Port的描述为
/*Flags to indicate behavior of the physical port. These flags are
* used in ofp_phy_port to describe the current configuration. They are
* used in the ofp_port_mod message to configure the port's behavior.
enum ofp_port_config {
OFPPC_PORT_DOWN = 1 && 0, /* Port is administratively down. */
OFPPC_NO_STP = 1 && 1, /* Disable 802.1D spanning tree on port. */
OFPPC_NO_RECV = 1 && 2, /* Drop all packets except 802.1D spanning tree packets. */
OFPPC_NO_RECV_STP = 1 && 3, /* Drop received 802.1D STP packets. */
OFPPC_NO_FLOOD = 1 && 4, /* Do not include this port when flooding. */
OFPPC_NO_FWD = 1 && 5, /* Drop packets forwarded to port. */
OFPPC_NO_PACKET_IN = 1 && 6 /* Do not send packet-in msgs for port. */
都没有关于Vlan的对应设置,也印证了OFport没有Vlan tag功能的结论。
  这里需要强调的是,并没有说OpenFlow不支持VLAN,大家都知道OpenFlow的Match里是有Vlan ID和Vlan codepoint等项匹配的。这里所说的只是OpenFlow定义的交换机端口里没有关于Vlan的配置项,而Match和Action里则都有相应的内容。
  其实OVS_Neutron_plugin本来就是为OVS设计而不是为控制器设计的,强行为其加上控制器会破坏其功能也就不中为怪了。就我已知Neutron有过为Floodlight设计的Plugin,但是功能较为简单,代码更新状况不是很好,可能也与早期的OF功能比较简单有关系;另外还有ryu控制器的Plugin,但是我没有特别调查过不是特别了解。
  如果一定要在控制器环境下使用Vlan来进行隔离,暂时想到两种思路,一是使数据进入OVS之前就为其加上Vlan tag,比如通过Linux Bridge,这应该是一种较为简单有效的方式,但是需要修改Neutron;另外一种就是在控制器上修改,使每个Port的动作都附加上额外的Mod_vlan(Strip Vlan)动作,但是这需要控制器对云环境有感知能力,即需要知道Port对应的网络才行。
阅读(...) 评论()

我要回帖

更多关于 openstack的neutron 的文章

 

随机推荐