01月20, 2017

OpenStack Mitaka Nova API 扩展实践

OpenStack的Nova API通过插件机制为开发者提供了扩展接口功能API Plugins。很多厂商的定制OpenStack,通过扩展接口功能,在不改变社区核心代码的前提下,实现对OpenStack的功能扩展。

为什么引入插件机制?

    插件机制最重要的作用,是能够保持核心代码和扩展代码之间的分离,提高设计的抽象层次,让系统具有更好的弹性和可维护性。 插件机制还给开发者提供了扩展应用系统的方式,可以很方便的引入新逻辑。而这一机制是完全间接地,不需要改变系统核心代码。 通过插件机制用户可以根据自己的需要使用不同的功能插件,或者通过关闭某些插件来禁用特定功能,非常的灵活。 像我们常见的Nagios、Zabbix、Nginx等都提供了插件机制。 OpenStack的插件机制,是通过stevedore这个库实现的。

stevedore插件

    stevedore最初的灵感来自于DougHellmann在设计Ceilometer的插件框架时,他经过对很多实现了插件的软件和库的调研,抽象出了适合Ceilometer的插件框架,最终经过进一步的抽象和提取,形成独立的库,就是stevedore。后来,Nova和Neutron等其他组件也引入了stevedore作为其插件框架。

    stevedore在设计之初就力求简单,所以它并没有通过import或者importlib等方式提供一套全新的代码加载逻辑,而是选择了重用python-setuptools中pkg_resources的功能,因此,stevedore用非常少的代码,实现了OpenStack对于插件的大部分需求。 stevedore细节可以转这里stevedore

stevedore在OpenStack中如何使用?

    使用stevedore给应用程序添加插件支持,非常简单。可以分为声明插件,定义插件,加载插件,执行接口四个步骤。

首先,在setup.cfg中声明插件的入口entry_points。

nova.api.v21.extensions =
    admin_actions = nova.api.openstack.compute.admin_actions:AdminActions
    admin_password = nova.api.openstack.compute.admin_password:AdminPassword
    agents = nova.api.openstack.compute.agents:Agents
    aggregates = nova.api.openstack.compute.aggregates:Aggregates
    assisted_volume_snapshots = nova.api.openstack.compute.assisted_volume_snapshots:AssistedVolumeSnapshots
    attach_interfaces = nova.api.openstack.compute.attach_interfaces:AttachInterfaces
    availability_zone = nova.api.openstack.compute.availability_zone:AvailabilityZone
    baremetal_nodes = nova.api.openstack.compute.baremetal_nodes:BareMetalNodes
    block_device_mapping = nova.api.openstack.compute.block_device_mapping:BlockDeviceMapping

然后,在nova/api/openstack/compute中定义插件。

  • 从Liberty版本开始,废弃了V2之前的插件开发目录nova/api/openstack/compute/contrib,所有的接口放在nova/api/openstack/compute/目录中。

这些所有插件,都实现了同一个基类V21APIExtensionBase。 以我自己实现的二次调度sheduler_hosts.py插件为例:

class SchedulerHostsController(wsgi.Controller):
        def index(self, req):
            hosts = {}
            context = req.environ["nova.context"]
            authorize(context)
            ......
            hosts = _request_handle(context, flavor, image, max_count)
            return {"hosts": hosts}

class SchedulerHosts(extensions.V21APIExtensionBase):
        name = "SchedulerHosts"
        alias = ALIAS
        version = 1

        def get_resources(self):
            resources = [extensions.ResourceExtension(ALIAS,
                SchedulerHostsController())
                ]
            return resources

        def get_controller_extensions(self):
            return []

V21APIExtensionBase 基类定义:

@six.add_metaclass(abc.ABCMeta)
class V21APIExtensionBase(object):
    """Abstract base class for all v2.1 API extensions.
    All v2.1 API extensions must derive from this class and implement
    the abstract methods get_resources and get_controller_extensions
    even if they just return an empty list. The extensions must also
    define the abstract properties.
    """
    def __init__(self, extension_info):
        self.extension_info = extension_info

    @abc.abstractmethod
    def get_resources(self):
        pass

    @abc.abstractmethod
    def get_controller_extensions(self):
        pass

    其它code省略......

插件什么时候被加载?

因为是作为API调用的钩子,所以在API Controller初始化时加载插件。 Nova API启动过程中会加载api-paste.ini中定义的应用,此处执行nova.api.openstack.compute.APIRouterV21.factory方法, LoadedExtensionInfo是一个普通的对象,保存扩展模块到extensions集合中.

class APIRouterV21(nova.api.openstack.APIRouterV21):
    """Routes requests on the OpenStack API to the appropriate controller
    and method.
    """
    def __init__(self, init_only=None):
        self._loaded_extension_info = extension_info.LoadedExtensionInfo()
        super(APIRouterV21, self).__init__(init_only)

    def _register_extension(self, ext):
        return self.loaded_extension_info.register_extension(ext.obj)

    @property
    def loaded_extension_info(self):
        return self._loaded_extension_info

而nova.api.openstack.compute.APIRouterV21继承自nova.api.openstack.APIRouterV21类, factory主要是调用init生成类实例.

class APIRouterV21(base_wsgi.Router):
    def __init__(self, init_only=None, v3mode=False):
        def _check_load_extension(ext):
            ......
            if ext.obj.alias not in blacklist:
                return self._register_extension(ext)
        # 使用stevedore插件库加载指定namespace中的插件
        self.api_extension_manager = stevedore.enabled.EnabledExtensionManager(
            namespace=self.api_extension_namespace(),
            check_func=_check_load_extension,
            invoke_on_load=True,
            invoke_kwds={"extension_info": self.loaded_extension_info})

        ......

这里使用的namespace为nova.api.v21.extensions,也就是setup.cfg中定义的nova.api.v21.extensions组名。 使用stevedore来加载nova.egg-info的entry_points.txt中[nova.api.v21.extensions]定义的扩展模块.

  • 实际处理逻辑中还有一些处理白/黑extenstions名单的逻辑,这里给省略了,感兴趣的可以自己看源码。 黑白名单在nova.conf中都可以配置:extensions_whitelist/extensions_blacklist。

为什么是从nova.egg-info的entry_points.txt中加载? 这要涉及到OpenStack的打包工具pbr。pbr是一个setuptools的扩展工具,被开发出来的主要目的是为了方便使用setuptools。 因为setuptools库使用起来还是有点麻烦,参数太多,而且直接通过指定setup函数的参数的方法实在太不方便了。pbr就是为了方便而生的,它带了了如下的改进:

  • 使用setup.cfg文件来提供包的元数据。
  • 基于requirements.txt文件来实现自动依赖安装。requirements.txt文件中包含了一个项目所要依赖的库,这个文件的格式是和pip兼容的。
  • 利用Sphinx实现文档自动化。
  • 基于git history自动生成AUTHORS和ChangeLog文件。
  • 针对git自动创建文件列表。
  • 基于git tags的版本号管理。

pbr 具体细节就不在此细说了,感兴趣的可以看下其源码实现。

如何验证添加的插件/扩展API是否正常工作呢?

既然是restapi,那测试方式就多了,你可以直接curl,也可以通过脚本来通过httpclient来调用。

我这里呢要给出的是扩展novaclient来实现nova原生命令行支持新加的接口。

    1. 在 novaclient/v2 目录下创建模块文件。

      还是以我自己实现的二次调度为例,我添加了scheduler_hosts.py模块文件。来看一下内容:

      class SchedulerHosts(base.Resource):
          def __repr__(self):
              return "<%s %s>" % (self.hostname, self.azname)

      class SchedulerHostsManager(base.ManagerWithFind):
          resource_class = SchedulerHosts

          def list(self, flavor=None, image=None, max_count=None):
              ......
              return self._list("/os-scheduler-hosts%s" % query_string, "hosts")

          list_all = list
实现了两个类
    1. 在v2/client.py中导入上面的模块。
  from novaclient.v2 import scheduler_hosts
    1. 在v2/shell.py中实现do_cmdname方法。
  @cliutils.arg("--flavor", metavar="<flavor>", default=None, help=_("flavor"))
      @cliutils.arg("--image", metavar="<image>", default=None, help=_("image"))
      @cliutils.arg("--max_count", metavar="<max_count>", default=None, help=_("max_count"))
      def do_scheduler_host_list(cs, args):
          """List all hosts by service."""
          columns = ["hostname", "azname"]
          result = cs.scheduler_hosts.list(args.flavor, args.image, args.max_count)
          utils.print_list(result, columns)
  很简单,注册了参数,及根据规范定义了方法入口。
    1. 检验成果
      [root@w-openstack90]# nova scheduler-host-list --image 426bb355-d07d-4ff8-80e6-d9ae30dcada2 --flavor bb282545-5f4e-4827-9aaa-cbc6aef72a9d --max_count 1
      +----------------------------------+------------------+
      | hostname                         | azname           |
      +----------------------------------+------------------+
      | w-openstack92.*.qihoo.net        | availabilityHost |
      +----------------------------------+------------------+

OK,到此OpenStack Mitak版本中如何添加扩展API就介绍完了,希望对大家有所帮助。

加我微信 alt

关注我们团队公众号 alt

本文链接:https://www.opsdev.cn/post/mitaka-nova-api.html

-- EOF --

Comments

评论加载中...

注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。