TripleO 模板和部署计划存储

本设计规范描述了一个部署计划的存储解决方案。部署计划由一组角色组成,这些角色反过来定义了一个 Heat 主模板,Heat 可以使用该模板创建表示部署计划的堆栈;以及定义主模板所需参数的环境文件。

本规范主要供 Tuskar 使用。

https://blueprints.launchpad.net/tuskar/+spec/tripleo-juno-tuskar-template-storage

问题描述

注意

本规范中使用的术语在 Tuskar REST API 规范中定义。

为了实现本规范的目标,我们需要首先为角色、部署计划和相关概念定义存储域模型。这些相关概念包括 Heat 模板和环境文件。这些模型必须考虑诸如版本控制和对象之间适当关系之类的要求。

我们还需要为这些模型创建一个存储机制。存储机制应与域模型区分开,允许后者保持稳定,而前者保留足够的灵活性,可以根据需要和可用性使用各种后端。特定模型的存储要求包括诸如版本控制和安全存储之类的项目。

提议的变更

变更摘要

以下拟议的变更分为三个部分

  • 存储域模型:定义模板、环境文件、角色和部署计划的域模型。

  • 存储 API 接口:定义与底层存储驱动程序相关的 Python API;负责将存储的内容转换为模型对象,反之亦然。每个模型都需要自己的存储接口。

  • 存储驱动程序:定义存储后端需要实现才能被 Python API 接口使用的 API。此处讨论了初始和未来的驱动程序支持计划。

应注意的是,每个存储接口都将由用户作为 Tuskar 设置的一部分指定。因此,域模型可以假定已定义适当的存储接口 - 模板存储、环境存储等 - 并可全局访问以供使用。

存储域模型

存储 API 需要以下域模型

  • 模板

  • 环境文件

  • 角色

  • 部署计划

前两个直接映射到 Heat 概念;后两个是 Tuskar 概念。

请注意,每个模型还将包含一个保存方法。如果未设置 uuid,则保存方法将在存储区调用 create;如果实例具有 uuid,则将在存储区调用 update。

模板模型

模板模型表示 Heat 模板。

class Template:
    uuid = UUID string
    name = string
    version = integer
    description = string
    content = string
    created_at = datetime

    # This is derived from the content from within the template store.
    parameters = dict of parameter names with their types and defaults

环境文件模型

环境文件定义了 Heat 堆栈的参数和资源注册表。

class EnvironmentFile:
    uuid = UUID string
    content = string
    created_at = datetime
    updated_at = datetime

    # These are derived from the content from within the environment file store.
    resource_registry = list of provider resource template names
    parameters = dict of parameter names and their values

    def add_provider_resource(self, template):
        # Adds the specified template object to the environment file as a
        # provider resource.  This updates the parameters and resource registry
        # in the content.  The provider resource type will be derived from the
        # template file name.

    def remove_provider_resource(self, template):
        # Removes the provider resource that matches the template from the
        # environment file.  This updates the parameters and resource registry
        # in the content.

    def set_parameters(self, params_dict):
        # The key/value pairs in params_dict correspond to parameter names/
        # desired values.  This method updates the parameters section in the
        # content to the values specified in params_dict.

角色模型

角色是云的可扩展单元。部署计划指定一个或多个角色。每个角色必须指定一个主要角色模板。它还必须指定该模板的依赖项。

class Role:
    uuid = UUID string
    name = string
    version = integer
    description = string
    role_template_uuid = Template UUID string
    dependent_template_uuids = list of Template UUID strings
    created_at = datetime

    def retrieve_role_template(self):
        # Retrieves the Template with uuid matching role_template_uuid

    def retrieve_dependent_templates(self):
        # Retrieves the list of Templates with uuids matching
        # dependent_template_uuids

部署计划模型

部署计划定义要部署的应用程序。它通过指定角色列表来做到这一点。这些角色用于构建一个环境文件,其中包含角色模板和注册每个角色主要模板作为提供程序资源所需的资源注册表所需的参数。还构建了一个主模板,以便可以将计划作为单个 Heat 堆栈部署。

class DeploymentPlan:
    uuid = UUID string
    name = string
    description = string
    role_uuids = list of Role UUID strings
    master_template_uuid = Template UUID string
    environment_file_uuid = EnvironmentFile UUID string
    created_at = datetime
    updated_at = datetime

    def retrieve_roles(self):
        # Retrieves the list of Roles with uuids matching role_uuids

    def retrieve_master_template(self):
        # Retrieves the Template with uuid matching master_template_uuid

    def retrieve_environment_file(self):
        # Retrieves the EnvironmentFile with uuid matching environment_file_uuid

    def add_role(self, role):
        # Adds a Role to the plan.  This operation will modify the master
        # template and environment file through template munging operations
        # specified in a separate spec.

    def remove_role(self, role):
        # Removes a Role from the plan.  This operation will modify the master
        # template and environment file through template munging operations
        # specified in a separate spec.

    def get_dependent_templates(self):
        # Returns a list of dependent templates.  This consists of the
        # associated role templates.

存储 API 接口

上述每个模型都有自己的 Python 存储接口。这些是管理器类,用于查询和执行针对存储驱动程序的 CRUD 操作,并返回模型的实例以供使用(不包括 delete,它返回 None)。存储接口将模型绑定到正在使用的驱动程序;这允许我们将每个模型存储在不同的位置。

请注意,每个存储区还包含一个序列化方法和一个反序列化方法。序列化方法接收相关对象并返回包含所有值属性的字典;反序列化方法执行相反的操作。

驱动程序在 下一节 中讨论。

模板 API

class TemplateStore:

    def create(self, name, content, description=None):
        # Creates a Template.  If no template exists with a matching name,
        # the template version is set to 0; otherwise it is set to the
        # greatest existing version plus one.

    def retrieve(self, uuid):
        # Retrieves the Template with the specified uuid.  Queries a Heat
        # template parser for template parameters and dependent template names.

    def retrieve_by_name(self, name, version=None):
        # Retrieves the Template with the specified name and version.  If no
        # version is specified, retrieves the latest version of the Template.

    def delete(self, uuid):
        # Deletes the Template with the specified uuid.

    def list(self, only_latest=False):
        # Returns a list of all Templates.  If only_latest is True, filters
        # the list to the latest version of each Template name.

环境文件 API

环境文件需要安全存储以保护参数值。

class EnvironmentFileStore:

    def create(self):
        # Creates an empty EnvironmentFile.

    def retrieve(self, uuid):
        # Retrieves the EnvironmentFile with the specified uuid.

    def update(self, model):
        # Updates an EnvironmentFile.

    def delete(self, uuid):
        # Deletes the EnvironmentFile with the specified uuid.

    def list(self):
        # Returns a list of all EnvironmentFiles.

角色 API

class RoleStore:

    def create(self, name, role_template, description=None):
               version=None, template_uuid=None):
        # Creates a Role.  If no role exists with a matching name, the
        # template version is set to 0; otherwise it is set to the greatest
        # existing version plus one.
        #
        # Dependent templates are derived from the role_template.  The
        # create method will take all dependent template names from
        # role_template, retrieve the latest version of each from the
        # TemplateStore, and use those as the dependent template list.
        #
        # If a dependent template is missing from the TemplateStore, then
        # an exception is raised.

    def retrieve(self, uuid):
        # Retrieves the Role with the specified uuid.

    def retrieve_by_name(self, name, version=None):
        # Retrieves the Role with the specified name and version.  If no
        # version is specified, retrieves the latest version of the Role.

    def update(self, model):
        # Updates a Role.

    def delete(self, uuid):
        # Deletes the Role with the specified uuid.

    def list(self, only_latest=False):
        # Returns a list of all Roles.  If only_latest is True, filters
        # the list to the latest version of each Role.

部署计划 API

class DeploymentPlanStore:

    def create(self, name, description=None):
        # Creates a DeploymentPlan.  Also creates an associated empty master
        # Template and EnvironmentFile; these will be modified as Roles are

    def retrieve(self, uuid):
        # Retrieves the DeploymentPlan with the specified uuid.

    def update(self, model):
        # Updates a DeploymentPlan.

    def delete(self, uuid):
        # Deletes the DeploymentPlan with the specified uuid.

    def list(self):
        # Retrieves a list of all DeploymentPlans.

存储驱动程序

存储驱动程序通过存储对象字典来运行。对于诸如 Glance 之类的存储解决方案,这些字典存储为平面文件。对于诸如数据库之类的存储解决方案,该字典被转换为表行。驱动程序负责理解它如何存储对象字典。

每个存储驱动程序必须提供以下方法。

class Driver:

    def create(self, filename, object_dict):
        # Stores the specified content under filename and returns the resulting
        # uuid.

    def retrieve(self, uuid):
        # Returns the object_dict matching the uuid.

    def update(self, uuid, object_dict):
        # Updates the object_dict specified by the uuid.

    def delete(self, uuid):
        # Deletes the content specified by the uuid.

    def list(self):
        # Return a list of all content.

对于 Juno,我们将尝试使用关系数据库和 Heat 的组合。Heat 将用于安全存储敏感的环境参数。数据库表将用于所有其他内容。使用 Heat 进行安全存储依赖于 PATCH 支持 添加到 Heat API。此错误的目标是在 Juno-2 中完成。

这仅仅是一个短期解决方案,因为我们理解在引入不必要的数据库依赖关系方面存在一些犹豫。从长远来看,我们希望在 Glance 从图像存储更新为更通用的工件存储库后,用 Glance 替换数据库。但是,此功能目前正在开发中,不能依赖于在 Juno 周期中使用它。本规范中描述的架构应允许从一个到另一个的相对容易的切换。

替代方案

Heat 模板中建模关系

该规范建议将计划关联的角色或角色依赖模板之类的关系建模为对象的直接属性。但是,这些信息似乎可以作为计划的环境文件的一部分或通过遍历角色模板的依赖关系图获得。为什么不简单地以这种方式推导出关系呢?

角色是 Tuskar 的抽象。在 Heat 中,它对应于用作提供程序资源的模板;但是,角色具有附加要求,例如自身及其依赖模板的版本控制,或者在计划中选择可用角色的能力。这些不是 Heat 打算满足的要求,并且完全在 Heat 中满足它们感觉像是滥用机制。

从实际的角度来看,在 Heat 模板中建模关系需要 Tuskar 对 Heat 模板进行就地修改以处理版本控制。例如,如果计算角色的版本 1 指定 {{compute.yaml: 1}, {compute-config.yaml: 1}},而该角色的版本 2 指定 {{compute.yaml: 1}, {compute-config.yaml: 2}},则允许同时使用这两个版本的角色,唯一的办法是允许以编程方式修改 compute.yaml 以指向 compute-config.yaml 的正确版本。

Swift 作为存储后端

Swift 被认为是替换关系数据库的选项,但最终被驳回了两个关键原因

  • Swift 中的版本控制系统不提供对对象当前版本的静态引用。相反,它具有“最新”版本,并且该版本是动态的,并在添加新版本时更改,因此无法将部署固定到版本。

  • 我们需要在角色中的提供程序资源之间创建关系,而 Swift 不支持存储对象之间的关系。

尽管如此,在寻求 Swift 团队的指导后,有人建议使用命名约定或使用不同的容器可以为我们提供足够的控制来模拟满足我们要求的版本控制系统。这些建议使 Swift 成为一个更有利的选项。

文件系统作为存储后端

简要考虑了文件系统,并且可能会包含它以提供更简单的开发人员设置。但是,为了创建一个具有版本控制和关系的生产就绪系统,这将需要重新实现许多其他数据库和服务为我们提供的内容。因此,此选项仅保留用于缺少关键功能的开发选项。

安全驱动程序替代方案

Barbican,OpenStack 安全存储服务,如果 PATCH 支持未及时添加到 Heat,则为我们提供了一种替代方案。

目前,除了 Barbican 之外的唯一替代方案是在其他选项中实现我们自己的密码学。这不是一个有利的选择,因为它增加了超出本提案范围的技术复杂性和风险。

关于敏感数据,另一种选择是不存储任何数据。这将要求 REST API 调用者在每次 Heat 创建(以及可能更新)时提供敏感信息。

安全影响

其中一些配置值,例如服务密码,将是敏感的。因此,Heat 或 Barbican 将用于存储所有配置值。

虽然可以通过 Tuskar API 控制访问,但可以提供提供程序资源文件或配置文件的大文件。这些应针对合理的限制进行验证。

其他最终用户影响

模板存储将主要由 Tuskar API 使用,但如果将来直接使用,则需要对其进行记录。

性能影响

在 Glance 和 Barbican 中存储模板将导致通过本地网络进行 API 调用,而不是直接访问数据库。这些可能具有更高的开销。但是,Tuskar 中使用的读取和写入预计会不频繁,并且仅会在操作部署计划时触发简单的读取和写入。

其他部署者影响

开发人员影响

TripleO 将通过存储 API 访问敏感和不敏感存储。

实现

负责人

主要负责人

d0ugal

其他贡献者

tzumainn

工作项

  • 实现存储 API

  • 创建基于 Glance 和 Barbican 的存储驱动程序

  • 创建数据库存储驱动程序

依赖项

  • Glance

  • Barbican

测试

  • API 逻辑将通过模拟外部服务的单元测试套件进行验证。

  • Tempest 将用于集成测试。

文档影响

代码应使用文档字符串和注释进行记录。如果它在 Tuskar 之外使用,则应开发进一步的用户文档。

参考资料