Newton Share 迁移改进

https://blueprints.launchpad.net/manila/+spec/newton-migration-improvements

Share 迁移是一项允许管理员在后端之间移动 share 的功能。理想情况下,移动的数据应该与之前完全相同。由于如果数据被移动到另一个位置或后端,导出位置可能需要更改,因此客户端可能需要重新挂载 share,因此该操作在大多数情况下预计会造成破坏。为了处理这种破坏性,本规范提出了一种两阶段迁移方法。

问题描述

当 share 在后端创建时,在用例场景中无法将其移动到另一个后端。管理员必须手动执行此操作,即在目标后端创建一个空 share,挂载两个 share,复制数据,并自行处理所有可能的困难,同时效率低下,因为这种方法会下载和重新上传所有正在复制的数据。由于来自多个云环境的几个管理员容易遇到这些场景,因此有必要提供一种以通用方式执行此操作的功能。

用例

Share 可能需要迁移的场景有几种

面向管理员

  • 维护/疏散

    • 为了硬件/软件升级而疏散后端

    • 疏散出现故障的后端

    • 疏散已 EOL 的后端

  • 优化

    • 碎片整理后端以创建可以离线以节省电力的空闲后端

    • 重新平衡后端以最大化可用性能

    • 将数据和计算移动到更近的位置,以减少网络利用率并降低延迟/增加带宽

  • 真正的迁移

    • 从旧硬件代迁移到较新的代

    • 从一个供应商迁移到另一个供应商

面向用户(通过另一个功能,例如 share-modify 或 share groups)

  • 更改 share 类型

  • 更改 AZ

  • 更改 share 协议

  • 更改 share 网络(对于 DHSS=true)shares

  • 更改 share group

  • 在没有可用空间时扩展 share

提议的变更

本规范旨在重新评估 Share 迁移设计,包括在 Austin Summit 2016 上讨论的改进。

有两种可能的迁移方式

  1. 驱动程序了解目标后端并能够在后端级别移动。

  2. Manila 使用数据服务将 share 的数据迁移到目标后端创建的另一个 share。

在 (1) 中,后端可以使用不同的机制,例如复制或快照,可能不需要挂载 share,也可能能够在不将 share 更改为只读、更改导出位置且不造成破坏的情况下执行此操作。操作完成后,如果需要,它应返回一个新的导出位置列表以更新数据库。这种方法称为驱动程序辅助方法。

在 (2) 中,share 必须在非增量复制方法期间更改为只读,预计会对租户应用程序造成停机。这种方法可能无法保留文件元数据,也称为主机辅助方法。

重要的是要注意,当从一个后端迁移 share 到另一个后端时,目标后端需要支持与源 share 协议完全相同的协议。迁移本身不会更改 share 协议,另一个名为“Share Modify”尚未实现的功能应负责执行此操作,并且可以使用 share 迁移功能来执行此操作。

一些属性,例如“Share Network”、“Share Type”和“Availability Zone”可以通过迁移进行修改,管理员可以通过 API 执行此操作。

由于预计迁移会造成破坏,无论是由驱动程序执行(如上文 #1),还是由 manila 代码执行(如上文 #2),它都是以两阶段的可能性设计的。调用“migrationg-start”时,share 将被迁移,但在完成第一阶段时会暂停,不会造成任何破坏,以便管理员可以准备好何时调用“migration-complete”以完成迁移,这可能会造成破坏。

备选方案

没有实现该功能,管理员需要手动执行此操作,效率较低。多个云提供商需要创建脚本来执行此操作并自行处理每个故障场景。本规范提出了一种通用的方法来执行此操作。

数据模型影响

为了跟踪 share 迁移的操作、支持两阶段迁移并正确处理错误,数据库中需要一个字段。在 Jobs 表(参见 [1])实现之前,Share 表中的一个附加字段能够满足这些要求。该字段名为“task_state”,其工作方式类似于状态,可以通过 API “reset-task-state” 重置。

为了在迁移过程中拥有更改类型,在某个时刻,share 需要具有具有不同类型的两个实例。目前,由于 ‘share_type_id’ 字段位于数据库中的 ‘Shares’ 表中,这不可能实现。因此,本规范包括一个数据库迁移,将 ‘share_type_id’ 字段移动到 ‘ShareInstances’ 表。

REST API 影响

引入了五个仅限管理员的新 API 方法

  1. (POST, 202, 400, 409) migration-start: 迁移 share

URL: /shares/<share_id>/action

Body

{
  'migration_start': {
    'force_host_assisted_migration': false,
    'preserve_metadata': true,
    'writable': true,
    'nondisruptive': true,
    'host': 'ubuntu@generic2#GENERIC2',
    'new_share_type_id': 'foo_share_type_id',
    'new_share_network_id': 'bar_share_network_id'
  }
}
  1. (POST, 202, 400) migration-complete: 触发迁移的第二阶段

URL: /shares/<share_id>/action

Body

{"migration_complete": {}}
  1. (POST, 200, 400) migration-get-progress: 尝试获取迁移进度

URL: /shares/<share_id>/action

Body

{"migration_get_progress": {}}

示例响应

RESP BODY: {
  "task_state": "data_copying_in_progress",
  "total_progress": 50,
}
  1. (POST, 202, 400) migration-cancel: 尝试取消迁移

URL: /shares/<share_id>/action

Body

{"migration_cancel": {}}
  1. (POST, 202, 400) reset-task-state: 将任务状态字段值重置为所需值

URL: /shares/<share_id>/action

Body

{"reset_task_state": {"task_state": "migration_error}}

API 详细信息

  1. migration-start [--force-host-assisted-migration <True/False>] [--preserve-metadata <True/False>] [--writable <True/False>] [--non-disruptive <True/False>] [--new-share-type <new_share_type>] [--new-share-network <new_share_network>] <share> <host@backend#pool>

force-host-assisted-migration (默认 False):

强制使用主机辅助方法,从而使用数据服务在后端之间移动复制数据。这将跳过否则将首先运行的驱动程序辅助方法。

preserve-metadata (默认 True):

是否应强制执行元数据保留。如果设置为 True,这将阻止主机辅助迁移运行。将查询驱动程序以验证此功能,如果驱动程序无法实现,将跳过驱动程序辅助方法,并且迁移将失败。

writable (默认 True):

是否应仅在 share 保持可写状态时才执行迁移。如果设置为 True,这将阻止主机辅助迁移运行。将查询驱动程序以验证此功能,如果驱动程序无法实现,将跳过驱动程序辅助方法,并且迁移将失败。

non-disruptive (默认 False):

是否应仅在迁移过程中不中断 share 访问时才执行迁移。为此,预计导出位置不会更改。如果设置为 True,这将阻止主机辅助迁移运行。将查询驱动程序以验证此功能,如果驱动程序无法实现,将跳过驱动程序辅助方法,并且迁移将失败。

new-share-type (默认 None):

应设置为迁移 share 中的新 share 类型。

new-share-network (默认 None):

应设置为迁移 share 中的新 share 网络。

share:

要移动的 share。

host@backend#pool:

将 share 组合迁移到的 host@backend#pool 组合。

  1. migration-complete <share>

share:

应完成迁移的 share。Share 必须处于主机辅助的“复制完成”或驱动程序辅助的“驱动程序阶段 1 完成”任务状态,才能调用其阶段 2 迁移。

  1. migration-get-progress <share>

share:

应从中获取迁移进度的 share。将显示总进度以及当前任务状态值。如果 share 正在迁移或驱动程序无法获取进度,则会返回错误消息。

  1. migration-cancel <share>

share:

应取消迁移的 share。Share 必须处于主机辅助的“复制完成”或驱动程序辅助的“驱动程序阶段 1 完成”任务状态才能取消。

  1. reset-task-state [--task-state <state>] <share>

task-state (默认 None):

要重置任务状态字段的值。

share:

应将任务状态字段重置为其提供的值的 share。

驱动程序影响

供应商可以在其驱动程序中实现驱动程序辅助迁移,以便在同一供应商或使用供应商兼容协议的后端之间有效地迁移数据。

为了支持主机辅助迁移,以 DHSS=False 模式运行的现有驱动程序不需要实现任何额外的代码,而代码处理其 share 协议必须存在于数据服务中,并且需要手动设置网络。但是,强烈建议支持管理网络以减少网络配置工作量。

对于 DHSS=True 模式驱动程序,如果现有驱动程序没有管理网络支持,以允许 share 与管理网络中的节点之间的连接,则主机辅助方法将无法工作。

添加驱动程序接口

def migration_check_compatibility(
        self, context, source_share, destination_share,
        share_server=None, destination_share_server=None):
    """Checks destination compatibility for migration of a given share.

    .. note::
        Is called to test compatibility with destination backend.

    Driver should check if it is compatible with destination backend so
    driver-assisted migration can proceed.

    :param context: The 'context.RequestContext' object for the request.
    :param source_share: Reference to the share to be migrated.
    :param destination_share: Reference to the share model to be used by
        migrated share.
    :param share_server: Share server model or None.
    :param destination_share_server: Destination Share server model or
        None.
    :return: A dictionary containing values indicating if destination
        backend is compatible, if share can remain writable during
        migration, if it can preserve all file metadata and if it can
        perform migration of given share non-disruptively.

        Example::

        {
            'compatible': True,
            'writable': True,
            'preserve_metadata': True,
            'nondisruptive': True,
        }
    """
    return {
        'compatible': False,
        'writable': False,
        'preserve_metadata': False,
        'nondisruptive': False,
    }

def migration_start(
        self, context, source_share, destination_share,
        share_server=None, destination_share_server=None):
    """Starts migration of a given share to another host.

    .. note::
       Is called in source share's backend to start migration.

    Driver should implement this method if willing to perform migration
    in a driver-assisted way, useful for when source share's backend driver
    is compatible with destination backend driver. This method should
    start the migration procedure in the backend and end. Following steps
    should be done in 'migration_continue'.

    :param context: The 'context.RequestContext' object for the request.
    :param source_share: Reference to the original share model.
    :param destination_share: Reference to the share model to be used by
        migrated share.
    :param share_server: Share server model or None.
    :param destination_share_server: Destination Share server model or
        None.
    """
    raise NotImplementedError()

def migration_continue(
        self, context, source_share, destination_share,
        share_server=None, destination_share_server=None):
    """Continues migration of a given share to another host.

    .. note::
        Is called in source share's backend to continue migration.

    Driver should implement this method to continue monitor the migration
    progress in storage and perform following steps until 1st phase is
    completed.

    :param context: The 'context.RequestContext' object for the request.
    :param source_share: Reference to the original share model.
    :param destination_share: Reference to the share model to be used by
        migrated share.
    :param share_server: Share server model or None.
    :param destination_share_server: Destination Share server model or
        None.
    :return: Boolean value to indicate if 1st phase is finished.
    """
    raise NotImplementedError()

def migration_complete(
        self, context, source_share, destination_share,
        share_server=None, destination_share_server=None):
    """Completes migration of a given share to another host.

    .. note::
        Is called in source share's backend to complete migration.

    If driver is implementing 2-phase migration, this method should
    perform the disruptive tasks related to the 2nd phase of migration,
    thus completing it. Driver should also delete all original share data
    from source backend.

    :param context: The 'context.RequestContext' object for the request.
    :param source_share: Reference to the original share model.
    :param destination_share: Reference to the share model to be used by
        migrated share.
    :param share_server: Share server model or None.
    :param destination_share_server: Destination Share server model or
        None.
    :return: List of export locations to update the share with.
    """
    raise NotImplementedError()

def migration_cancel(
        self, context, source_share, destination_share,
        share_server=None, destination_share_server=None):
    """Cancels migration of a given share to another host.

    .. note::
       Is called in source share's backend to cancel migration.

    If possible, driver can implement a way to cancel an in-progress
    migration.

    :param context: The 'context.RequestContext' object for the request.
    :param source_share: Reference to the original share model.
    :param destination_share: Reference to the share model to be used by
        migrated share.
    :param share_server: Share server model or None.
    :param destination_share_server: Destination Share server model or
        None.
    """
    raise NotImplementedError()

def migration_get_progress(
        self, context, source_share, destination_share,
        share_server=None, destination_share_server=None):
    """Obtains progress of migration of a given share to another host.

    .. note::
        Is called in source share's backend to obtain migration progress.

    If possible, driver can implement a way to return migration progress
    information.
    :param context: The 'context.RequestContext' object for the request.
    :param source_share: Reference to the original share model.
    :param destination_share: Reference to the share model to be used by
        migrated share.
    :param share_server: Share server model or None.
    :param destination_share_server: Destination Share server model or
        None.
    :return: A dictionary with at least 'total_progress' field containing
        the percentage value.
    """
    raise NotImplementedError()

def connection_get_info(self, context, share, share_server=None):
    """Is called to provide necessary generic migration logic.

    :param context: The 'context.RequestContext' object for the request.
    :param share: Reference to the share being migrated.
    :param share_server: Share server model or None.
    :return: A dictionary with migration information.
    """

    Has a default implementation that can be overridden.

驱动程序辅助迁移的一般方法是,将调用驱动程序来分析与目标后端的兼容性,并返回一个字典,其中包含描述它们是否兼容以及支持哪些功能(例如保留元数据、保持可写和无破坏)的信息。为了获得执行此分析的信息,建议驱动程序从 manila 配置文件中读取其他相关后端的数据。理想情况下,它们应该通过 RPC 调用相互通信,但不应在 RPC 响应中包含敏感数据(例如密码),因此目前无法采用这种方法。目前,建议驱动程序还测试与目标后端的连接。

如果目标 share 具有定义的 share 网络 ID,则意味着 share 需要 share 服务器,因此 manila 代码将向目标后端发送请求,以便它可以提供 share 服务器,如果不存在,则将在目标后端上创建,由 manila 调用。

然后,将调用源后端驱动程序的 migration_start 方法来执行迁移。此方法应启动存储中的迁移作业并返回。Manila 将根据定期任务调用 migration_continue 方法,以便驱动程序可以执行后续步骤以继续迁移,直到完成第一阶段,此时驱动程序应返回。

驱动程序还应确保在迁移未完成时,源 share 实例必须可恢复且其数据保持完整。

最后,管理员将调用 migration-complete 来执行迁移的最后,可能具有破坏性的步骤。驱动程序此时应删除源 share。Manila 还会使用 update_access 驱动程序接口将现有的访问规则应用于目标实例。

此外,如果主机辅助迁移使用的几个基本驱动程序类实现的当前实现不受驱动程序支持,则驱动程序可以覆盖这些方法,添加特殊行为以支持主机辅助迁移方法。

安全影响

为了访问 share 的数据,必须由实体挂载。挂载 share 的实体负责数据。在迁移过程中,如果使用主机辅助方法,数据服务将挂载正在迁移的 share 并复制数据,从而在有限的时间内将 share 的内容暴露给数据服务节点。数据服务可以通过管理网络访问,因此它授予能够连接到数据服务的人员访问数据。建议限制对该节点的访问。

此更改包括对 rootwrap 权限文件的新的条目,用于必须以 root 身份运行的以下命令

  • ls -pA1 –group-directories-first %s

  • touch –reference=%s %s

通知影响

其他最终用户影响

性能影响

所有与复制相关的命令都是资源密集型的,应在仅安装了数据服务的单独节点上运行,从而不会破坏其他服务。

其他部署者影响

引入了新的配置选项。大多数选项具有默认值,而其他一些选项需要管理员输入。为了使数据服务能够处理多个不同协议的 share 挂载,需要对其进行配置

  • 节点必须设置为管理网络,并且 config 选项 ‘data_node_access_ip’ 必须设置为连接到管理网络的此节点接口的 IP 值。这足以挂载基于 IP 的访问规则的 share。

  • 需要安装用于 NFS 和 CIFS 等协议的协议库到此节点。

  • 对于基于证书的访问规则的协议,需要在节点上安装证书,并且需要设置 config 选项 ‘data_node_access_cert’。

  • 对于基于用户的访问规则的协议,用户必须在节点和后端安全服务中配置为管理员。必须将用户名设置为 config 选项 ‘data_node_access_admin_user’,并且 config 选项 ‘data_node_mount_options’ 必须设置为包含用户名、密码和安装为管理员用户所需的域的命令参数。

  • 除 NFS 和 CIFS 之外的其他协议尚未经过测试,如果它们的访问类型包含在支持的协议中,它们可能有效。

  • 为了正确检查与目标后端之间的兼容性,驱动程序将依赖于它们的本地配置文件来读取有关其他后端的信息,因此建议部署者尝试保持多个 manila-share 节点的配置文件同步,并将最新的值加载到服务的内存中。

开发人员影响

建议驱动程序供应商和 CI 维护者启用迁移测试,以验证主机辅助方法是否适用于他们各自的驱动程序和共享协议。

实现

收到迁移共享的 API 请求后,Share API 层将执行以下验证

检查共享是否有副本

  • 如果为 True,则返回错误 409(冲突)。目前不支持迁移具有副本的共享。

检查共享的状态

  • 如果不可用,则返回错误 400(无效)。

检查共享是否正在执行其他任务

  • 如果正在忙,则返回错误 400(无效)。

检查目标主机是否不同

  • 如果相同,则返回错误 400(无效)。

检查是否有快照

  • 如果有,则返回错误 400(无效)。目前不支持迁移具有快照的共享。

检查目标主机是否可用

  • 如果不可用,则返回错误 400(无效)。

检查提供的 new_share_type 和 share_network_id 是否存在

  • 如果不存在,则返回错误 400(无效)。

如果所有验证都成功,它应将 task_state 设置为 MIGRATION_STARTING,并异步调用调度器以根据共享类型验证主机。如果主机验证失败,调度器会将 task_state 设置为 MIGRATION_ERROR(无通知)。否则,它将异步调用源共享的管理器以继续迁移。

如果提供了 new_share_type,它将在调度器中验证主机时使用,而不是共享的原始类型。如果提供了 new_share_network,它将在创建将由迁移的共享使用的共享实例模型时使用,从而在必要时触发新的共享服务器的创建。

在共享管理器中,它会将共享的 task_state 更改为 MIGRATION_IN_PROGRESS,实例状态更改为 MIGRATING。然后,如果 force_host_assisted_migration API 参数设置为 False,它将准备调用驱动程序辅助迁移。

首先,它将尝试执行驱动程序辅助迁移,方法是创建目标共享实例模型,获取其共享服务器并调用一个检查兼容性的方法。如果成功且返回的 capabilities 与 API 提供的 ‘writable’、‘preserve_metadata’ 和 ‘non-disruptive’ 值相对应,则 task_state 设置为 MIGRATION_DRIVER_STARTING,并调用驱动程序的 migration_start 方法,在该方法中,驱动程序预计会启动迁移作业并返回可访问目标实例的导出位置列表(如果此时可以确定)。

此时,任务状态设置为 MIGRATION_DRIVER_IN_PROGRESS,并且一个周期性任务运行以调用驱动程序的 migration_continue 方法来执行迁移的下一步,直到它返回 True,表示第一阶段迁移已完成,允许将任务状态设置为 MIGRATION_DRIVER_PHASE1_DONE。

如果在第一阶段迁移完成之前引发任何异常,则会清理所有已分配的数据,例如目标共享实例模型和共享服务器。如果在第二阶段迁移期间引发异常,则不会清理数据,以便管理员可以分析故障并可能手动修复。

如果驱动程序辅助迁移失败到 migration_start 驱动程序调用,并且提供的 API 值 ‘preserve_metadata’、‘writable’ 和 ‘nondisruptive’ 都为 False,则主机辅助方法将接管。

主机辅助方法代码包括通过驱动程序更改共享的所有访问规则为只读(规则不在 DB 中更改),通过 RPCAPI 异步在目标主机中创建新的共享,并等待其状态变为“可用”,获取源和目标后端的 connection_info 字典,并异步调用数据服务以执行迁移。

数据服务 RPCAPI 包括“migration_start”,它根据迁移逻辑执行数据复制(例如设置适当的状态并根据需要通知源后端)、“data_copy_cancel”和“data_copy_get_progress”,这些将在下面详细说明,并且适用于任何数据复制作业。

connection_info 字典包括与正在迁移的共享兼容的访问映射,以及两个模板,一个用于 mount 命令,一个用于 unmount 命令。基本驱动程序类对这些模板具有默认实现,如果驱动程序需要特定的自定义行为,可以覆盖这些模板。虽然数据服务期望模板中至少存在“%(path)s”部分,以便可以将其替换为适当的导出位置,但每个后端部分的 manila.conf 配置文件都可以自定义这两个命令模板,但需要通过自定义“connection_get_info”方法来覆盖其他模板元素。

默认 mount 模板是:mount -vt %(proto)s %(options) %(export)s %(path)s 默认 unmount 模板是:umount -v %(path)s

以下访问映射在驱动程序基类中预定义

{
  'ip': ['nfs'],
  'user': ['cifs'],
}

数据服务执行迁移的艰巨工作,它负责调用 API 以添加适当的访问规则,以便能够挂载源共享和目标共享,挂载、复制、卸载和删除访问规则。

确定适当访问规则类型的方法是根据共享的协议和驱动程序配置的访问映射。共享的协议将选择存在协议条目的访问类型,然后与另一个后端的访问映射相交,以添加不会导致任何相关驱动程序出错的访问规则。

与相交访问映射中存在的访问类型相关的所有访问规则都将被添加,并在迁移后删除。为了正确填充访问规则条目的 ‘access_to’ 字段,数据服务读取每种类型的一个配置选项,如下所示

  • 如果访问类型为 ‘user’,它将读取 ‘data_node_admin_user’ 配置选项。在这种情况下,管理员也应填充 ‘data_node_mount_options’,例如 ‘-o user=foo,pass=bar’(如果挂载共享是必要的)。

  • 如果访问类型为 ‘cert’,它将读取 ‘data_node_access_cert’ 配置选项。

  • 如果访问类型为 ‘ip’,它将读取 ‘data_node_access_ip’ 配置选项。

  • 否则,它将抛出一个错误消息,说明提供的访问类型不受支持。

预计数据服务节点已在管理网络中正确配置,已安装适当的库和证书,并已配置安全服务用户。

复制工作由专门的复制类完成。该类负责递归地遍历文件,复制它们,尝试保留所有元数据 (cp -P –preserve=all),选择性地验证源和目标的 SHA-2 哈希是否匹配,并最终尝试应用所有文件的元数据。所有操作都以 root 用户身份执行,因此可以绕过用户限制并在复制后将其设置回文件。由于所有复制操作都是通过 rootwrap 运行 linux 命令执行的,因此数据服务线程将休眠直到进程退出,从而允许数据服务在节点复制字节时处理多个 RPC 请求。如果上述任何操作失败或未通过验证,它将重试一次,如果第二次失败,它将返回错误并导致迁移失败。

复制完成后,数据服务会将任务状态设置为 DATA_COPYING_COMPLETED,允许管理员调用 migration-complete。

共享管理器的 migration_complete 方法将首先检查迁移共享的 task_state 值,以确定是否调用驱动程序的 migration_complete 方法或主机辅助方法。驱动程序预计将执行迁移的最后破坏性步骤并返回与迁移实例相关的导出位置列表。主机辅助 migration_complete 根据 DB(原始规则)将访问规则应用于新的共享,将目标共享状态设置为“可用”,删除源共享并将 task_state 设置为 MIGRATION_SUCCESS。

migration_cancel API 只能在复制期间或第一阶段完成后调用。migration_get_progress API 可以在任何时候调用,但它仅在迁移或复制文件的步骤时才查询驱动程序或数据服务以获取进度。

负责人

主要负责人

ganso

工作项

  • 实现此规范中同意的更改。由于它们是对现有代码的改进,因此可以将其实现为单个补丁。

  • 使用 CLI 命令更新 python-manilaclient。

  • 更新 manila-ui。

  • 记录实现情况(见下文)。

依赖项

  • 更新驱动程序中实现的访问接口。

测试

  • 单元测试

  • Tempest 测试

文档影响

  • 文档字符串

  • Devref

  • 安全指南

  • 用户指南

  • 发布说明

参考资料

[1] Newton 设计峰会 etherpad 讨论

https://etherpad.openstack.org/p/newton-manila-data-service-migration

[2] Mitaka 设计峰会 etherpad 讨论

https://etherpad.openstack.org/p/mitaka-manila-migration-improvements

[3] Mitaka 合并的主要补丁

https://review.openstack.org/#/c/244286/
https://review.openstack.org/#/c/250515/

[4] Liberty 设计峰会 etherpad 讨论

https://etherpad.openstack.org/p/YVR-manila-liberty-share-migration

[5] Liberty 合并的主要补丁

https://review.openstack.org/#/c/179790/

[6] 访问支持映射

http://docs.openstack.org/developer/manila/devref/share_back_ends_feature_support_mapping.html