[置顶] neutron plugin 与 extension 编写流程

neutron plugin 与 extension 编写流程

关于neutron,它是openstack中管理网络相关的一个项目,它主要负责管理了openstack中的虚拟网络,它将网络作为一种服务提供给租户,它的设计遵循了SDN(soft define network)的设计原则从而实现了网络的虚拟化。
neutron采用了插件技术,关于neutron的具体技术细节,可以参考:

https://yeasy.gitbooks.io/openstack_understand_neutron/content/index.html

下面介绍plugin与extension的编写流程:

plugin

neutron的完整目录在

/usr/lib/python2.7/dist-packages/neutron

- neutron/   - agent/   - api/   - cmd/   - common/   - db/   - debug/   - extensions/   - locale/   - notifiers/   - openstack/   - plugins/   - scheduler/   - server/   - services/   - manager.py   - neutron_plugin_base_v2.py   - service.py   - wsgi.py   - ...

这个目录中plugins目录下存放的就是neutron的插件,neutron在启动的时候会以此载入plugin下面的插件。当我们需要实现自己的插件的时候,首先第一步,就是在plugins目录下建立一个子目录,子目录如下:

  • neutron
      - plugins
        - myplugin
          - __init__.py
          - plugin.py

上述两个文件是最基本的,当然plugin.py也可以用不同的名字。但是__init__.py的名字是不能改的,这个用来告诉neutron myplugin可以看做是一个module。plugin.py里面可以先定义一个基本空的class 叫做MyPlugin,虽然现在这个plugin什么用都没有。

from neutron.db import db_base_plugin_v2
from neutron.openstack.common import log

LOG = log.getLogger(__name__)

class MyPlugin(db_base_plugin_v2.NeutronDbPluginV2):

    def __init__(self):
        LOG.info("MyPlugin is started.")

这个简单的插件写完之后,我们需要将其注册到neutron中,让neutron知道我们自己实现了这么一个插件。这时候我们就需要在neutron中注册这个插件,注册方法是在/etc/neutron/neutron.conf中添加一行:

core_plugin = neutron.plugins.myplugin.plugin.MyPlugin

这样,一个简单的neutron插件就添加完成了。当然,这里只是说了添加流程,具体实现上有一些细节需要注意的,接下来进行这方面说明。

plugin 实现细节

首先,Neutron对最基本的三个资源:Network, Port 和 Subnet 的基本调用都已经定义好了API接口。如果你的插件也需要用到这些资源,最好直接实现它们的接口。API接口的定义可以再 neutron/neutron_plugin_base_v2.py 这个文件中找到,其中每个参数的作用也有比较详细的介绍。对于用不着的资源,直接放任不管就好了,最多下次不小心调用了会发出“该接口没有被实现”的错误,不会有其他影响。这里是一个 Network API 实现的范例,其实它什么也没有做,但是确实是一个有效的接口实现:

from neutron import neutron_plugin_base_v2
class MyPlugin(neutron_plugin_base_v2.NeutronPluginBaseV2):
    def __init__(self):
        pass

    def create_network(self, context, network):
        # Create a network by using data from network dictionary
        # Send back a dictionary to display created network's info
        return network

    def update_network(self, context, id, network):
        # Update a created network matched by id with
        # data in the network dictionary. Send back a
        # dictionary to display the network's updated info
        return network

    def get_network(self, context, id, fields=None):
        network = {}
        # List information of a specific network matched by id
        # and return it in a form of dictionary
        return network

    def get_networks(self, context, filters=None, fields=None):
        network = {}
        # List all networks that are active
        return network

    def delete_network(self, context, id):
        # Delete a specific network matched by id
        # return back the id of the network.
        return id

如果在具体实现这些接口的过程中,需要参考的话,有两个地方非常值得参考:一个是 neutron/db/db_base_plugin_v2.py,这个是neutron官方给出的一个基于数据库的实现。它只是操作数据库中的内容,模拟各个资源的创建、修改、删除等操作,但没有在物理机器上做任何改变。第二个地方就是 neutron/plugins 里面收纳的各个公司的插件实现,我们可以从中学习到其他公司是怎么写插件的。

plugin 与 extension

neutron针对每个plugin 还提供了extension,顾名思义,extension即拓展。在neutron中,我们将plugin的资源分为network、port、subnet三类,但是在实际的应用中,很可能需要的不仅仅是这三类的基本资源,而是更多需要拓展功能的自定义的资源。针对neutron client请求这些自定义的资源时,我们会请求处理放在extension中,这样便有了extension存在的必要性。因此,这便是neutron plugin和extension的区别。

extension

这里我在之前的plugin中添加一个extension。
首先在插件目录下建立extension目录:

- neutron/   - plugins/     - myplugin/       - __init__.py       - plugin.py       - extensions/         - __init__.py         - myextension.py

其中extension的名字和myextension.py都是可以自定义的。
接下来编写myextension.py中的代码,首先需要定义资源属性:

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 中有比较详细的解释。

定义新类的时候需要注意一点,这个类的名字与包含这个类的文件名的唯一区别必须是一个首字母大写,另一个首字母小写。也就是说把MyExtension当做类的名字可能就会导致出错。具体原因可以参考 neutron/api/extensions.py 中 ExtensionManager 的_load_all_extensions_from_path 方法的实现。Myextension 这个类可以继承 neutron/api/extensions.py 这个文件中的一个类:ExtensionDescriptor,也可以自己定义。下面给出一个继承了该类的定义:

from neutron.api import extensions
from neutron import manager
from 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

其中get_alias方法返回的是插件的名字,这个名字必须跟myplugin/plugin.py中声明的extension要一致:

class MyPlugin(db_base_plugin_v2.NeutronDbPluginV2):
  ...
  supported_extension_aliases = ['my-extensions']
  
  def __init__(self):
    ...
  ...

这样extension的实现基本就算完成了,最后只需要在/etc/neutron/neutron.conf指定一下extension的位置即可。
api_extensions_path =/usr/lib/python2.7/dist-packages/neutron/plugins/myplugin/extensions

neutron client

这样extension的添加大体完成。梳理一下我们所做的工作,在neutron/plugins/myplugin/plugin.py中应该是这样子的:

from neutron import neutron_plugin_base_v2


class MyPlugin(neutron_plugin_base_v2.NeutronPluginBaseV2):
    def __init__(self):
        pass

   [...] 
  
  def create_myextension(self, context, myextension):
        return myextension

    def update_myextension(self, context, id, myextension):
        return myextension

    def get_myextension(self, context, id, fields=None):
        myextension = {}
        return myextension

    def get_myextensions(self, context, filters=None, fields=None):
        myextensions = {}
        return myextensions

    def delete_myextension(self, context, id):
        return id
 
  [...]

因此,neutron-server端的实现需要我们提供一个neutron client来对其进行请求操作。那么具体的client请求中,首先我们需要设置命令,在neutron同级目录中,neutronclient/shell.py中:

from neutronclient.neutron.v2_0.myextension import extension as my_ext
COMMAND_V2 = {
    'net-list': network.ListNetwork,
    'net-external-list': network.ListExternalNetwork,
    'net-show': network.ShowNetwork,
    'net-create': network.CreateNetwork,
    'net-delete': network.DeleteNetwork,
    'net-update': network.UpdateNetwork,
    ...
  'myextension-list': my_ext.ListExtension,
  'myextension-show': my_ext.ShowExtension,
  'myextension-create': my_ext.CreateExtension,
  'myextension-delete': my_ext.DeleteExtension,
  'myextension-update': my_ext.UpdateExtension,
  ...
  }

我们添加了自定义插件的命令,这样我们可以通过neutron 命令对其进行请求操作。
对于neutronclient,其目录结构是这样的:

- neutronclient/ 
  - neutron/ 
    - v2_0/ 
      - myextension/ 
        - __init__.py 
        - extension.py

要实现neutron的CLI,主要是实现extension.py文件,我们需要在其添加我们在neutron extension中实现的几个命令对应的类。即List/Show/Create/Delete/UpdateExtension
具体实现参考如下:

import argparse
import logging

from neutronclient.neutron import v2_0 as neutronV20
from neutronclient.openstack.common.gettextutils import _

RESOURCE = 'myextension'

class ListExtension(neutronV20.ListCommand):
    """List extensions"""

    resource = RESOURCE
    log = logging.getLogger(__name__ + '.ListExtension')
    list_columns = ['id', 'name']


class ShowExtension(neutronV20.ShowCommand):
    """Show information of a given extension."""

    resource = RESOURCE
    log = logging.getLogger(__name__ + '.ShowExtension')


class CreatePhysicalGateway(neutronV20.CreateCommand):
    """Create an extension."""

    resource = RESOURCE
    log = logging.getLogger(__name__ + '.CreateExtension')

    def add_known_arguments(self, parser):
        parser.add_argument(
            'name', metavar='NAME',
            help=_('Name of extension to create'))


    def args2body(self, parsed_args):
        body = {self.resource: {
            'name': parsed_args.name}}
        return body



class UpdateExtension(neutronV20.UpdateCommand):
    """update a given extension."""

    resource = RESOURCE
    log = logging.getLogger(__name__ + '.UpdateExtension')      
class DeleteExtension(neutronV20.DeleteCommand):
    """Delete a given extension."""

    resource = RESOURCE
    log = logging.getLogger(__name__ + '.DeleteExtension')

这些 class 处在接受 CLI 命令的第一线,负责将命令转化成 API call。需要特别注意的是 CreateExtension 这个类,它有两个方法 add_known_arguments 和 args2body。前者定义了 CLI 命令接受哪些参数,后者规定如何将收到的参数打包起来。

这些参数打包之后就会发给 neutron 后台中我们自己定义的 plugin controller,但是如何发送这些参数还需要我们去 /neutronclient/v2_0/client.py 的 Client 类中设置:

首先是 uri 路径:

 myextensions_path = "/myextensions"
 myextension_path = "/myextensions/%s"

然后是每个操作所对应的传递方法:

@APIParamsCall
    def list_myextensions(self, retrieve_all=True, **_params):
        """Fetches a list of all myextensions for a tenant."""
        return self.list('myextensions', self.myextensions_path, retrieve_all,
                         **_params)

    @APIParamsCall
    def show_myextension(self, myextension, **_params):
        """Fetches information of a certain entry in myextension."""
        return self.get(self.myextension_path % (myextension), params=_params)

    @APIParamsCall
    def create_myextension(self, body=None):
        """Creates a new myextension entry."""
        return self.post(self.myextensions_path, body=body)

    @APIParamsCall
    def delete_myextension(self, myextension):
        """Deletes the specified myextension."""
        return self.delete(self.myextension_path % (myextension))

   @APIParamsCall
    def update_myextension(self, myextension, body=None):
        """Updates a myextension."""
        return self.put(self.myextension_path % (myextension), body=body)

如此一来,我们自己实现的 neutron plugin 就能够收到 CLI 发送过来的命令啦。

参考资料

本文参考资料如下:
怎样写 OpenStack Neutron 的 Plugin (一)
怎样写 OpenStack Neutron 的 Plugin (二)
怎样写 OpenStack Neutron 的 Extension (一)
怎样写 OpenStack Neutron 的 Extension (二)
怎样写 OpenStack Neutron 的 Extension (三)
怎样写 OpenStack Neutron 的 Extension (四)

你可能感兴趣的:(openstack,neutron)