前两篇文章讨论了怎么写一个 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 "2014-08-07T00:00:00-00:00" @classmethod def get_resources(cls): # This method registers the URL and the dictionary of # attributes on the neutron-server. exts = [] 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 中加入对自定义扩展的命令支持等内容。
怎样写 OpenStack Neutron 的 Extension (一)