Heat 资源插件开发指南

Heat 允许服务提供商通过编写自己的资源插件来扩展编排服务的 capabilities。这些插件是用 Python 编写的,并包含在服务提供商配置的目录中。本指南描述了资源插件的结构和生命周期,以帮助开发人员编写自己的资源插件。

资源插件生命周期

资源插件相对简单,因为它需要扩展一个基础 Resource 类并实现一些相关的生命周期处理方法。资源的的基本生命周期方法是

创建

插件应该创建一个新的物理资源。

update

插件应该使用新的配置更新现有资源,或者告诉引擎资源必须被销毁并重新创建。此方法是可选的;默认行为是创建一个替换资源,然后删除旧资源。

挂起

插件应该挂起物理资源的运行;这是一个可选操作。

resume

插件应该恢复物理资源的运行;这是一个可选操作。

删除

插件应该删除物理资源。

基础类 Resource 实现这些生命周期方法中的每一个,并定义一个或多个处理方法,插件可以实现这些方法来体现和管理插件抽象的实际物理资源。这些处理方法将在后面的章节中详细描述。

Heat 资源基础类

插件必须扩展类 heat.engine.resource.Resource

这个类负责管理插件的整体生命周期。它定义了与生命周期相对应的方法,以及插件处理与特定下游服务通信的工作的基本钩子。例如,当引擎确定是时候创建一个资源时,它会调用适用插件的 create 方法。此方法在 Resource 基础类中实现,并处理大部分簿记和与引擎的交互。然后此方法调用插件类中定义的 handle_create 方法(如果已实现),该方法负责使用特定的服务调用或其他方法来实例化所需的物理资源(服务器、网络、卷等)。

资源状态和动作

基础类负责将资源的 state 报告给引擎。资源的 state 是生命周期动作和该动作的状态的组合。例如,如果资源创建成功,则该资源的 state 将为 CREATE_COMPLETE。或者,如果插件在尝试创建物理资源时遇到错误,则 state 将为 CREATE_FAILED。基础类负责报告和持久化资源 state,因此插件的处理方法只需要根据需要返回数据或引发异常。

资源支持状态

新的资源应该使用 support_status 选项标记从哪个 OpenStack release 版本开始可用。有关详细信息,请参阅 Heat 支持状态使用指南

资源描述

未来资源的重要组成部分是简洁明了的描述。它应该在类 docstring 中,并包含有关资源的信息以及它对最终用户有何用处。docstring 描述用于文档生成,如果资源设计用于公开使用,则应始终定义它。Docstring 应遵循 PEP 257

class CustomResource(resource.Resource):
    """This custom resource has description.

    Now end-users could understand the meaning of the resource existing
    and will use it correctly without any additional questions.
    """

属性和特性

资源的 属性 定义了模板作者在将资源包含在模板时可以操作的设置。一些例子包括

  • 用于 Nova 服务器的 flavor 和 image

  • Neutron LBaaS 节点上要侦听的端口

  • Cinder 卷的大小

注意

属性通常通过 self.properties 访问。这将解析 intrinsic 函数,在需要时提供默认值,并对向后兼容的 schema 更改执行属性转换。self.properties.data 字典提供对模板中用户提供的原始数据的访问,而无需进行任何转换。

特性 描述了物理资源的运行时 state 数据,插件可以将这些数据暴露给 Stack 中的其他资源。通常,这些在物理资源创建并处于可用 state 后才能使用。一些例子包括

  • Nova 服务器的主机 ID

  • Neutron 网络的状态

  • Cinder 卷的创建时间

定义资源属性

资源支持的每个属性都必须在 schema 中定义,该 schema 告知引擎和验证逻辑属性是什么,每种属性的类型是什么,以及验证约束。schema 是一个字典,其键定义属性名称,其值描述该属性的约束。此字典必须分配给插件的 properties_schema 属性。

from heat.common.i18n import _
from heat.engine import constraints
from heat.engine import properties

    nested_schema = {
        "foo": properties.Schema(
            properties.Schema.STRING,
            _('description of foo field'),
            constraints=[
                constraints.AllowedPattern('(Ba[rc]?)+'),
                constraints.Length(max=10,
                                   description="don't go crazy")
            ]
        )
    }
    properties_schema = {
        "property_name": properties.Schema(
            properties.Schema.MAP,
            _('Internationalized description of property'),
            required=True,
            default={"Foo": "Bar"},
            schema=nested_schema
        )
    }

如上所示,一些属性本身可能很复杂,并引用嵌套的 schema 定义。以下是 Schema 构造函数的参数;除了第一个参数外,所有参数都有默认值。

data_type:

定义属性值的类型。有效类型是列表 properties.Schema.TYPES 的成员,当前为 INTEGERSTRINGNUMBERBOOLEANMAPLISTANY;请使用这些符号名称而不是它们等同的字面量。对于 LISTMAP 类型属性,引用的 schema 约束列表或映射中复杂项目的格式。

description:

属性及其功能的描述;也用于文档生成。默认值为 None — 但您应该始终提供描述。

default:

如果模板中未提供此属性,则要分配的默认值。默认值为 None

schema:

此属性的值很复杂,其成员必须符合此引用的 schema 才能有效。引用的 schema 字典的格式与 properties_schema 相同。默认值为 None

必需:

True 如果模板要有效,则此属性必须具有一个值;False 否则。默认值为 False

constraints:

应用于属性值的约束列表。请参阅 属性约束

update_allowed:

True 如果可以更新现有资源,False 表示更新是通过删除和重新创建完成的。默认值为 False

immutable:

True 表示不支持更新,资源更新将在每次更改此属性时失败。False 否则。默认值为 False

support_status:

定义属性的当前状态。有关详细信息,请参阅 Heat 支持状态使用指南

访问插件在运行时属性值然后是一个简单的调用

self.properties['PropertyName']

基于属性类型,没有设置值的属性将返回该类型的“空”默认值

类型

空值

字符串

‘’

数字

0

整数

0

列表

[]

Map

{}

布尔值

False

属性约束

以下是可用的约束类型。描述是可选的,如果提供,则以普通语言向最终用户说明约束。

AllowedPattern(regex, description):

将值约束为与给定的正则表达式匹配;适用于 STRING。

AllowedValues(allowed, description):

列出允许的值。allowed 必须是 collections.abc.Sequencestring。适用于除 MAP 之外的所有值类型。

Length(min, max, description):

约束值的长度。适用于 STRING、LIST、MAP。 minmax 都默认为 None

Range(min, max, description):

约束数值。适用于 INTEGER 和 NUMBER。 minmax 都默认为 None

Modulo(step, offset, description):

从指定的 offset 开始,step 的每个倍数都是有效值。适用于 INTEGER 和 NUMBER。

从模板版本 2017-02-24 开始可用。

CustomConstraint(name, description, environment):

此构造函数从环境中引入一个命名的约束类。如果给定的环境为 None(其默认值),则使用的环境是全局环境。

定义资源属性

属性传达物理资源的运行时 state。请注意,并非所有插件都定义任何属性,这样做是可选的。如果插件需要暴露属性,它将定义一个类似于前面描述的属性 schema 的 attributes_schema。schema 字典中的每个项目由一个属性名称和一个属性 Schema 对象组成。

attributes_schema = {
    "foo": attributes.Schema(
        _("The foo attribute"),
        type=attribute.Schema.STRING
    ),
    "bar": attributes.Schema(
        _("The bar attribute"),
        type=attribute.Schema.STRING
    ),
    "baz": attributes.Schema(
        _("The baz attribute"),
        type=attribute.Schema.STRING
    )
}

以下是 Schema 的参数。

description

属性的描述;也用于文档生成。默认值为 None — 但您应该始终提供描述。

type

定义属性值的类型。有效类型是列表 attributes.Schema.TYPES 的成员,当前为 STRINGNUMBERBOOLEANMAPLIST;请使用这些符号名称而不是它们等同的字面量。

support_status

定义属性的当前状态。有关详细信息,请参阅 Heat 支持状态使用指南

如果定义了属性,则还必须通过插件解析其值。最简单的方法是覆盖 Resource 类中的 _resolve_attribute 方法

def _resolve_attribute(self, name):
    # _example_get_physical_resource is just an example and is not
    # defined in the Resource class
    phys_resource = self._example_get_physical_resource()
    if phys_resource:
        if not hasattr(phys_resource, name):
                # this is usually not needed, but this is a simple
                # example
                raise exception.InvalidTemplateAttribute(name)
        return getattr(phys_resource, name)
    return None

如果插件需要更复杂的属性解析,则插件可以选择覆盖 FnGetAtt。但是,如果选择此方法,则属性的验证和可访问性将是插件的责任。

此外,每个资源默认都有 show 属性。该属性使用来自 heat.engine.resource.Resource 类的默认实现,但如果资源有不同的 show 属性解析方式,则需要覆盖 Resource 类中的 _show_resource 方法

def _show_resource(self):
    """Default implementation; should be overridden by resources.

    :returns: the map of resource information or None
     """
    if self.entity:
        try:
            obj = getattr(self.client(), self.entity)
            resource = obj.get(self.resource_id)
            if isinstance(resource, dict):
                return resource
            else:
                return resource.to_dict()
        except AttributeError as ex:
            LOG.warning("Resolving 'show' attribute has failed : %s",
                        ex)
            return None

属性和属性示例

假设以下简单的属性和属性定义

properties_schema = {
    'foo': properties.Schema(
        properties.Schema.STRING,
        _('foo prop description'),
        default='foo',
        required=True
    ),
    'bar': properties.Schema(
        properties.Schema.INTEGER,
        _('bar prop description'),
        required=True,
        constraints=[
            constraints.Range(5, 10)
        ]
    )
}

attributes_schema = {
    'Attr_1': attributes.Schema(
        _('The first attribute'),
        support_status=support.Status('5.0.0'),
        type=attributes.Schema.STRING
    ),
    'Attr_2': attributes.Schema(
        _('The second attribute'),
        type=attributes.Schema.MAP
    )
}

并且假设定义上述插件的模板引用名称为“Resource::Foo”(请参阅 注册资源插件)。模板作者可以在堆栈中使用此插件,只需在模板中进行以下声明

# ... other sections omitted for brevity ...

resources:
  resource-1:
    type: Resource::Foo
    properties:
      foo: Value of the foo property
      bar: 7

outputs:
  foo-attrib-1:
    value: { get_attr: [resource-1, Attr_1] }
    description: The first attribute of the foo resource
  foo-attrib-2:
    value: { get_attr: [resource-1, Attr_2] }
    description: The second attribute of the foo resource

生命周期处理程序方法

为了完成管理插件支持的物理资源的工作,应实现以下生命周期处理程序方法。请注意,插件不必实现所有这些方法;可选的处理程序将记录为可选。

通常,处理程序方法遵循基本模式。任何生命周期步骤的基本处理程序方法都遵循 handle_<life cycle step> 格式。因此,对于创建步骤,处理程序方法将是 handle_create。一旦调用了处理程序,也可以实现一个可选的 check_<life cycle step>_complete,以便插件可以立即从基本处理程序返回,然后利用内置于基类中的协作多线程,并定期轮询下游服务以获取完成状态;轮询该检查方法,直到它返回 True。同样,对于创建步骤,此方法将是 check_create_complete

创建

handle_create(self)

创建新的物理资源。此函数应进行创建物理资源所需的调用,并在有足够的信息来标识资源后立即返回。该函数应返回此标识信息并实现 check_create_complete,该函数将此信息作为参数,然后定期轮询。这允许在已满足依赖关系的多资源之间进行协作多线程。

注意 一旦已知物理资源的本机标识符,此函数应调用 self.resource_id_set,传递物理资源的本机标识符。这将持久化标识符,并通过访问 self.resource_id 使其可用于插件。

返回值:

已创建的物理资源的表示形式

引发:

如果创建失败,则引发任何 Exception

check_create_complete(self, token)

如果已定义,将使用 handle_create 的返回值调用

参数:

tokenhandle_create 的返回值;用于轮询物理资源的状态。

返回值:

True 如果物理资源处于活动状态并准备好使用;否则为 False

引发:

如果创建失败,则引发任何 Exception

更新(可选)

请注意,heat.engine.resource.Resource 中存在 handle_update 的默认实现,该实现只是引发一个异常,表明更新需要引擎删除并重新创建资源(这是默认行为),因此实现此功能是可选的。

handle_update(self, json_snippet, tmpl_diff, prop_diff)

使用更新的信息更新物理资源。

参数:
  • json_snippet (collections.abc.Mapping) – 更新后的模板中的资源定义

  • tmpl_diff (collections.abc.Mapping) – 相对于原始模板定义已更改的更新定义中的值。

  • prop_diff (collections.abc.Mapping) – 原始定义和更新定义之间不同的属性值;键是属性名称,值是新值。已删除或最初存在但现在不存在的属性的值为 None

注意 在调用 handle_update 之前,我们检查是否需要替换资源,特别是对于处于 *_FAILED 状态的资源,heat.engine.resource.Resource 中存在 needs_replace_failed 的默认实现,该实现始终返回 True,表明需要替换更新。我们覆盖了 OS::Nova::ServerOS::Cinder::Volume 以及所有 neutron 资源 的实现。基本原理是检查底层资源是否存在以及实际状态是否可用。因此,如果需要,请为您的资源插件覆盖 needs_replace_failed 方法。

check_update_complete(self, token)

如果已定义,将使用 handle_update 的返回值调用

参数:

tokenhandle_update 的返回值;用于轮询物理资源的状态。

返回值:

True 如果更新已完成;否则为 False

引发:

如果更新失败,则引发任何 Exception

暂停(可选)

这些处理程序函数是可选的,仅当物理资源支持暂停时才需要实现

handle_suspend(self)

如果物理资源支持,此函数应调用本机 API 并暂停资源的运行。此函数应返回足够的信息,以便 check_suspend_complete 轮询本机 API 以验证操作的状态。

返回值:

一个令牌,包含足够的信息供 check_suspend_complete 验证操作状态。

引发:

如果暂停操作失败,则引发任何 Exception

check_suspend_complete(self, token)

验证暂停操作是否成功完成。

参数:

tokenhandle_suspend 的返回值

返回值:

True 如果暂停操作完成并且物理资源现在已暂停;否则为 False

引发:

如果暂停操作失败,则引发任何 Exception

恢复(可选)

这些处理程序函数是可选的,仅当物理资源支持从暂停状态恢复时才需要实现

handle_resume(self)

如果物理资源支持,此函数应调用本机 API 并恢复已暂停资源的运行。此函数应返回足够的信息,以便 check_resume_complete 轮询本机 API 以验证操作的状态。

返回值:

一个令牌,包含足够的信息供 check_resume_complete 验证操作状态。

引发:

如果恢复操作失败,则引发任何 Exception

check_resume_complete(self, token)

验证恢复操作是否成功完成。

参数:

tokenhandle_resume 的返回值

返回值:

True 如果恢复操作完成并且物理资源现在处于活动状态;否则为 False

引发:

如果恢复操作失败,则引发任何 Exception。

删除

handle_delete(self)

删除物理资源。

返回值:

一个令牌,包含足够的数据来验证操作状态

引发:

如果删除操作失败,则引发任何 Exception

注意

从 Liberty 版本开始,实现 handle_delete 是可选的。父资源类可以处理删除资源的最常见模式

def handle_delete(self):
    if self.resource_id is not None:
        try:
            self.client().<entity>.delete(self.resource_id)
        except Exception as ex:
            self.client_plugin().ignore_not_found(ex)
            return None
        return self.resource_id

为了使这适用于特定的资源,必须在资源实现中覆盖 entitydefault_client_name 属性。例如,Aodh Alarm 的 entity 应等于“alarm”,default_client_name 应等于“aodh”。

handle_delete_snapshot(self, snapshot)

删除资源快照。

参数:

snapshot – 描述当前快照的字典。

返回值:

一个令牌,包含足够的数据来验证操作状态

引发:

如果删除操作失败,则引发任何 Exception

handle_snapshot_delete(self, state)

当删除策略为 SNAPSHOT 时调用,而不是 handle_delete。创建资源的备份,然后删除资源。

参数:

state – 资源的 (action, status) 元组,以确保可以为当前资源创建备份

返回值:

一个令牌,包含足够的数据来验证操作状态

引发:

如果删除操作失败,则引发任何 Exception

check_delete_complete(self, token)

验证删除操作是否成功完成。

参数:

tokenhandle_deletehandle_snapshot_delete(对于删除策略 - Snapshot)的返回值,用于验证操作的状态

返回值:

True 如果删除操作完成并且物理资源已删除;否则为 False

引发:

如果删除操作失败,则引发任何 Exception

check_delete_snapshot_complete(self, token)

验证删除快照操作是否成功完成。

参数:

tokenhandle_delete_snapshot 的返回值,用于验证操作的状态

返回值:

True 如果删除操作完成并且快照已删除;否则为 False

引发:

如果删除操作失败,则引发任何 Exception

资源依赖项

理想情况下,您的资源不应具有“隐藏”依赖项,即 Heat 应该能够从资源属性和其他资源/资源属性引用的资源推断您的资源实例的任何入站或出站依赖项。这由 heat.engine.resource.Resource.add_dependencies() 处理。

如果这不可能,请不要简单地覆盖资源插件中的 add_dependencies()!这之前曾导致 问题,通常是由于未捕获的异常,如果您觉得需要覆盖 add_dependencies(),请通过 OFTC 上的 #heat IRC 频道或 openstack-discuss 邮件列表与 Heat 开发人员联系,讨论更好的解决方案的可能性。

注册资源插件

为了使您的插件可用于堆栈模板,插件必须使用引擎注册引用名称。这通过在插件模块中定义一个 resource_mapping 函数来完成,该函数返回模板资源类型名称及其对应实现类的映射

def resource_mapping():
    return { 'My::Custom::Plugin': MyResourceClass }

这将允许模板作者将资源定义为

resources:
  my_resource:
    type: My::Custom::Plugin
    properties:
    # ... your plug-in's properties ...

请注意,您可以通过简单地返回一个包含每个模板类型名称的映射来定义每个模块的多个插件。您还可以使用此方法在多个模板类型名称下注册单个资源插件(只有在受向后兼容性限制时才需要这样做)。

配置引擎

为了使用您的插件,Heat 必须配置为从特定目录读取您的资源。 plugin_dirs 配置选项列出了引擎在本地文件系统中搜索插件的目录。只需将包含您资源的的文件放置在其中一个目录中,并在服务下次启动时,引擎将使其可用。

有关配置编排服务的更多信息,请参阅 配置 Heat

测试

测试可以位于插件的 tests 命名空间/目录中。Heat 插件加载器将隐式地不加载该目录下的任何内容。当您的插件测试具有您不希望安装在生产环境中的依赖项时,这非常有用。

整合

您可以在 heat/engine/resources 中找到插件类。一个非常简单的入门示例是 random_string.py;它很不寻常,因为它不操作云中的任何内容!

资源贡献

Heat 团队有兴趣添加新的资源,以便 Heat 可以访问额外的 OpenStack 或 StackForge 项目。以下清单定义了候选资源被考虑纳入的条件

  • 必须封装一个 OpenStack 或 StackForge 项目,或者与 OpenStack 用户相关的第三方项目。

  • 必须在 OpenStack 的 global-requirements.txt 文件中列出其依赖项,或者它应该能够在缺少依赖项时有条件地禁用自身,而不会导致崩溃或以其他方式影响 heat-engine 服务的正常运行。

  • 资源的的支持状态标志必须设置为 UNSUPPORTED,以表明 Heat 团队不对支持此资源负责。

  • 代码质量必须与官方资源相当。Heat 团队可以在审查阶段提供帮助。

如果您有一个合适的资源,欢迎联系 Heat 团队。如果出于任何原因您的资源不符合上述要求,但您仍然认为它对其他用户有用,鼓励您将其托管在您自己的存储库中,并将其作为常规的 Python 可安装包进行共享。您可以在官方 Heat git 存储库的 contrib 目录中找到包含所有必需打包文件的示例资源插件。