跳转到: 导航, 搜索

Manila/design/manila-liberty-consistency-groups

< Manila‎ | 设计

一致性组

本文档描述了核心 Manila 中一致性组功能的的设计选择和步骤。

一致性组是一种机制,允许对多个文件系统共享进行保证,能够在完全相同的时间点创建快照。例如,数据库可以将表、日志和配置放在不同的卷上。如果我们要从先前的某个时间点恢复数据库,那么只有在完全相同的时间点一起恢复日志、表和配置才有意义。

Manila 核心变更

核心 Manila 的变更与 Cinder 中所做的工作非常相似。从宏观层面上讲,数据库、共享服务、驱动程序、调度服务和 API 服务中的变更都需要进行修改。

阶段 1

  • 创建/删除 CG
  • 在 CG 中创建共享
  • 删除 CG 中的共享
  • 快照整个 CG
  • 从 CGSnapshot 创建 CG

潜在的未来功能

  • 在 CG 中添加/删除共享
  • CG 迁移
  • CG 复制

Manila 与 Cinder

  • 不同的 CLI 语法
  • Restful api 的调整
    • API 在 Liberty 版本中将是一个“实验性”微版本
    • 将 /create_from_src 更改为在 POST /consistency-groups 的 body 中仅使用 cgsnapshot_id
    • 删除操作将使用 DELETE HTTP 动词,通过 admin actions 扩展 (/action) 支持 os-force_delete
  • 在 Driver API 处,需要将 cgsnapshot 对象和 cg 对象(包含有关 CG 中所有卷的信息)传递给驱动程序,而不仅仅是 cg/cgsnapshot 名称。这对于不管理 CG 结构的后端(例如 NetApp cDOT)非常重要,因为它们需要执行诸如循环遍历 CG 中的快照以删除它们之类的事情。
  • policy.json 中一致性组操作的默认策略是默认策略,而在 Cinder 中,它是 'nobody'
  • 与 Cinder 不同,CGsnapshot 中的快照与普通快照不同。CGsnapshot 被视为单个单元,而不是快照的集合。
  • consistency_group_support 调度器能力具有多个值,而不是布尔值。这是为了适应需要将 CG 中的所有卷放在同一池中的后端,以及同一主机。它还允许在未来添加其他值,如果后端可以支持其他范例,例如跨多个后端的 CG。

用户工作流

  • 快照多个共享
    1. 创建一个一致性组
    2. 指定 CG 后创建共享
    3. 创建一个 CGSnapshot
  • 创建一个 CG
    1. POST 到 /consistency-group
      • 指定 share_types 或将使用默认共享类型
  • 复制一个一致性组
    1. 创建一个 CGSnapshot
    2. 使用 CGSnapshot id 创建 CG
  • 删除 CG
    1. 删除 CG 的所有 CGSnapshot
    2. 删除 CG 中的所有共享
    3. 删除 CG(只能删除空的 CG)
  • 列出给定 CG 中的共享
    1. GET /shares?consistencygroup_id=<cg_id>
  • 列出在给定 CGSnapshot 中捕获的共享
    1. GET /cgsnapshots/<id>/members

系统工作流

  • 创建 CG
    1. 在数据库中创建 CG
    2. 将一致性组的创建投递给调度器
      1. 为 CG 选择一个主机
    3. 将创建投递给共享服务
      1. 从数据库中获取 CG 信息
      2. 调用驱动程序
      3. 使用驱动程序返回的信息更新数据库(例如:CG 状态)
  • 从 cgsnapshot 创建 CG
    1. 在数据库中创建一个新的 CG
    2. 在数据库中从 CG 快照成员创建新的共享
    3. 将 CG 创建调度到调度器,并使用原始 CG 主机/池 + share_type(取决于能力)
    4. 使用有关源共享、新共享、cg 和 cgsnapshot 信息调用驱动程序。
    5. 更新 CGsnapshot 状态和所有共享状态为可用
  • 创建 cg 快照
    1. 在数据库中创建 cgsnapshot 实体
    2. 对于 CG 中的所有共享,在数据库中创建 cgsnapshot_member
    3. 将 create_cgsnapshot 投递给驱动程序
  • 将共享添加到 CG
    • 在共享创建时
      1. 将共享投递给调度器,并使用 CG 的主机
        • 如果支持 perpool:转到与 CG 相同的池
        • 如果支持 per backend:转到与 CG 共享类型匹配的同一主机上的池
  • 删除共享(在 CG 中)
    • 如果共享具有 CG 的快照(检查 share_id 是否在 CGSnapshotMembers 表中),则不允许

调度器

manila/scheduler/filter_scheduler.py 现在应该查找 consistency_group_support 作为一种能力。

consistency_group_support 默认值:None

  • None - 不支持 CG
  • host - CG 中的共享必须位于与 CG 共享类型匹配的同一主机上的池中
  • pool - CG 中的共享必须位于 CG 所在的同一池中

DB

  • 新表格
class ConsistencyGroup(BASE, ManilaBase):
   """Represents a consistencygroup."""
   __tablename__ = 'consistency_groups'
   id = Column(String(36), primary_key=True)
 
   user_id = Column(String(255), nullable=False)
   project_id = Column(String(255), nullable=False)
   deleted = Column(String(36), default='False')

   host = Column(String(255))
   name = Column(String(255))
   description = Column(String(255))
   status = Column(String(255))
   source_cgsnapshot_id = Column(String(36))
   share_network_id = Column(String(36), ForeignKey('share_networks.id'),
                             nullable=True)
   share_server_id = Column(String(36), ForeignKey('share_servers.id'),
                             nullable=True)

class ConsistencyGroupShareTypeMapping(BASE, ManilaBase):
   """Represents the share types in a consistency group"""
   __tablename__ = 'consistency_group_share_type_mappings'
   id = Column(String(36), primary_key=True)
   deleted = Column(String(36), default='False')
   consistency_group_id = Column(String(36), 
                                 ForeignKey('consistency_groups.id'), 
                                 nullable=False)
   share_type_id = Column(String(36), 
                          ForeignKey('share_types.id'), 
                          nullable=False)
  consistency_group = orm.relationship(
       ConsistencyGroup,
       backref="share_types",
       foreign_keys=consistency_group_id,
       primaryjoin=('and_('
                    'ConsistencyGroupShareTypeMapping.consistency_group_id '
                    '== ConsistencyGroup.id,'
                    'ConsistencyGroupShareTypeMapping.deleted == "False")')
   )
class CGSnapshot(BASE, ManilaBase):
   """Represents a cgsnapshot."""
   __tablename__ = 'cgsnapshots'
   id = Column(String(36), primary_key=True)

   consistency_group_id = Column(String(36), ForeignKey('consistency_groups.id'))
   user_id = Column(String(255), nullable=False)
   project_id = Column(String(255), nullable=False)
   deleted = Column(String(36), default='False')

   name = Column(String(255))
   description = Column(String(255))
   status = Column(String(255))

   consistency_group = orm.relationship(
       ConsistencyGroup,
       backref="cgsnapshots",
       foreign_keys=consistency_group_id,
       primaryjoin=('and_('
                    'CGSnapshot.consistency_group_id == ConsistencyGroup.id,'
                    'CGSnapshot.deleted == "False")')
   )
class CGSnapshotMembers(BASE, ManilaBase):
   __tablename__ = 'cgsnapshot_members'
   id = Column(String(36), primary_key=True)
   cgsnapshot_id = Column(String(36), ForeignKey('cgsnapshots.id'))
   share_id = Column(String(36), ForeignKey('shares.id'))
   size = Column(Integer)
   status = Column(String(255))
   share_proto = Column(String(255))
   share_type_id = Column(String(36), ForeignKey('share_types.id'),
                          nullable=True)
   user_id = Column(String(255))
   project_id = Column(String(255))
   deleted = Column(String(36), default='False')

   cgsnapshot = relationship(
       ConsistencyGroup,
       backref="cgsnapshot_members",
       foreign_keys=cgsnapshot_id,
       primaryjoin='CGSnapshotMembers.cgsnapshot_id == CGSnapshot.id')

   share = orm.relationship(Share, backref="cgsnapshot_members",
                            foreign_keys=share_id,
                            primaryjoin='and_('
                            'CGSnapshotMember.share_id == Share.id,'
                            'CGSnapshotMember.deleted == "False")')
  • 共享中的新字段
   consistency_group_id = Column(String(36), ForeignKey('consistency_groups.id'), nullable=True)
   consistency_group = relationship(
       ConsistencyGroup,
       backref="shares",
       foreign_keys=consistency_group_id,
       primaryjoin='Share.consistency_group_id == ConsistencyGroup.id')
  
   source_cgsnapshot_member_id = Column(String(36), nullable=True)

API

Schemas: https://wiki.openstack.org/wiki/Manila/design/manila-liberty-consistency-groups/api-schema

GET /consistency-groups/
GET /consistency-groups/detail
POST /consistency-groups
GET /consistency-groups/<id>
DELETE consistency-groups/<id>
PUT /consistency-groups/<id>
GET /cgsnapshots/
GET /cgsnapshots/detail
POST /cgsnapshots
PUT /cgsnapshots/<id>
GET /cgsnapshots/<id>
GET /cgsnapshots/<id>/members
DELETE /cgsnapshots/<id>
Admin Actions
POST /consistency-groups/<id>/action # os-reset-status and os-force-delete
Body:
{"os-reset_status": { "status": "available"}}
POST /cgsnapshots/<id>/action # os-reset-status and os-force-delete
Body:
{"os-force_delete": null}
Updates to Share resource
Adds consistency_group_id to POST/shares
Adds source_cgsnapshot_member_id
Add consistency_group_id query filter to /shares

政策

policy.json - 所有一致性组策略应默认为默认策略

   "consistency_group:create" : "",
   "consistency_group:delete": "",
   "consistency_group:update": "",
   "consistency_group:get": "",
   "consistency_group:get_all": "",

   "consistency_group:create_cgsnapshot" : "",
   "consistency_group:delete_cgsnapshot": "",
   "consistency_group:get_cgsnapshot": "",
   "consistency_group:get_all_cgsnapshots": "",

驱动API

update_share_stats 现在应该返回 'consistency_group_support' pool/host/None。

   def create_consistency_group(self, context, cg_dict, share_server=None):
       """Create a consistency group.

       :param context:
       :param cg_dict: The consistency group details
           EXAMPLE:
           {'status': 'creating',
            'project_id': '13c0be6290934bd98596cfa004650049',
            'user_id': 'a0314a441ca842019b0952224aa39192',
            'description': None,
            'deleted': 'False',
            'created_at': datetime.datetime(2015, 8, 10, 15, 14, 6),
            'updated_at': None,
            'source_cgsnapshot_id': 'f6aa3b59-57eb-421e-965c-4e182538e36a',
            'host': 'openstack2@cmodeSSVMNFS',
            'deleted_at': None,
            'share_types': [<models.ConsistencyGroupShareTypeMapping>],
            'id': 'eda52174-0442-476d-9694-a58327466c14',
            'name': None
           }
       :return: (cg_model_update, share_update_list)
           cg_model_update - a dict containing any values to be updated
           for the CG in the database. This value may be None.
       """
       raise NotImplementedError()

   def create_consistency_group_from_cgsnapshot(self, context, cg_dict,
                                                cgsnapshot_dict, share_server=None):
       """Create a consistency group from a cgsnapshot.

       :param context:
       :param cg_dict: The consistency group details
           EXAMPLE:
           {'status': 'creating',
            'project_id': '13c0be6290934bd98596cfa004650049',
            'user_id': 'a0314a441ca842019b0952224aa39192',
            'description': None,
            'deleted': 'False',
            'created_at': datetime.datetime(2015, 8, 10, 15, 14, 6),
            'updated_at': None,
            'source_cgsnapshot_id': 'f6aa3b59-57eb-421e-965c-4e182538e36a',
            'host': 'openstack2@cmodeSSVMNFS',
            'deleted_at': None,
            'shares': [<models.Share>], # The new shares being created
            'share_types': [<models.ConsistencyGroupShareTypeMapping>],
            'id': 'eda52174-0442-476d-9694-a58327466c14',
            'name': None
           }
       :param cgsnapshot_dict:
           EXAMPLE:
           {'status': 'available',
            'project_id': '13c0be6290934bd98596cfa004650049',
            'user_id': 'a0314a441ca842019b0952224aa39192',
            'description': None,
            'deleted': '0',
            'created_at': datetime.datetime(2015, 8, 10, 0, 5, 58),
            'updated_at': datetime.datetime(2015, 8, 10, 0, 5, 58),
            'consistency_group_id': '4b04fdc3-00b9-4909-ba1a-06e9b3f88b67',
            'cgsnapshot_members': [
               {'status': 'available',
                'share_type_id': '1a9ed31e-ee70-483d-93ba-89690e028d7f',
                'share_id': 'e14b5174-e534-4f35-bc4f-fe81c1575d6f',
                'user_id': 'a0314a441ca842019b0952224aa39192',
                'deleted': 'False',
                'created_at': datetime.datetime(2015, 8, 10, 0, 5, 58),
                'share': <models.Share>,
                'updated_at': datetime.datetime(2015, 8, 10, 0, 5, 58),
                'share_proto': 'NFS',
                'project_id': '13c0be6290934bd98596cfa004650049',
                'cgsnapshot_id': 'f6aa3b59-57eb-421e-965c-4e182538e36a',
                'deleted_at': None,
                'id': '6813e06b-a8f5-4784-b17d-f3e91afa370e',
                'size': 1
               }
            ],
            'deleted_at': None,
            'id': 'f6aa3b59-57eb-421e-965c-4e182538e36a',
            'name': None
           }
       :return: (cg_model_update, share_update_list)
           cg_model_update - a dict containing any values to be updated
           for the CG in the database. This value may be None.
 
           share_update_list - a list of dictionaries containing dicts for
           every share created in the CG. Any share dicts should at a minimum
           contain the 'id' key and 'export_locations'. Export locations
           should be in the same format as returned by a share_create. This
           list may be empty or None.
           EXAMPLE:
           [
            {'id': 'uuid', 'export_locations': ['export_path']}
           ]
       """
       raise NotImplementedError()

   def delete_consistency_group(self, context, cg_dict, share_server=None):
       """Delete a consistency group

       :param context:
       :param cg_dict:
       :return: cg_model_update
           cg_model_update - a dict containing any values to be updated
           for the CG in the database. This value may be None.
       """
       raise NotImplementedError()

   def create_cgsnapshot(self, context, snap_dict, share_server=None):
       """Create a consistency group snapshot.
 
       :param context:
       :param snap_dict:
       :return: (cgsnapshot_update, member_update_list)
           cgsnapshot_update - a dict containing any values to be updated
           for the CGSnapshot in the database. This value may be None.

           member_update_list -  a list of dictionaries containing for every
           member of the cgsnapshot. Each dict should contains values to be
           updated for teh CGSnapshotMember in the database. This list may be
           empty or None.
       """
       raise NotImplementedError()
   def delete_cgsnapshot(self, context, snap_dict, share_server=None):
       """Delete a consistency group snapshot
 
       :param context:
       :param snap_dict:
       :return: (cgsnapshot_update, member_update_list)
           cgsnapshot_update - a dict containing any values to be updated
           for the CGSnapshot in the database. This value may be None.
       """
       raise NotImplementedError()

Manila 客户端

  • 我们是否希望将命令别名为 Cinder 一致的命令和改进后的名称?(例如 cg-create = consisgroup-create) 也许我们将 cinderclient 命令别名为这样?
  cg-snapshot-create   Creates a cgsnapshot.
  cg-snapshot-delete   Removes one or more cgsnapshots.
  cg-snapshot-list     Lists all cgsnapshots.
  cg-snapshot-show     Shows cgsnapshot details.
 
  cg-create  Creates a consistency group (--cgsnapshot to create from existing cg snapshot)
  cg-delete  Removes one or more consistency groups.
  cg-list    Lists all consistencygroups.
  cg-show    Shows details of a consistency group.
  cg-update  Updates a consistencygroup.
  create --consistency-group Creates a share and puts it into consistency group

未解决的问题 / 常见问题解答

  1. 如果部署中没有后端支持 CG,我们如何避免混淆?
    • 看起来 Cinder 的策略默认设置为 'nobody',但这似乎很奇怪,是否有更好的方法?
      • (ameade) 我想不出一种方法让 devstack 自动设置策略。
      • (ameade) 我们选择使用 os-consistency-groups 扩展来实现这一点
  2. 我们是否需要一种伪造 CG 的方法,使用通用驱动程序?
    • 否,但如果它无法在通用驱动程序中支持,则不会是核心功能。
    • Ben 说我们应该在通用驱动程序中伪造它,以便它可以被 gate 测试。
  3. 我们需要一种保护在 CG 中的共享被删除的机制吗?(例如:受保护的共享?)CG 中的共享是否比普通共享更有价值?
  4. 我们是否应该重载 Snapshots 表以包含 CGSnapshot 中的快照,以避免添加新的数据库表?还是继续使用新的表?
  5. 为什么不让 cg 在删除 cg 时删除其中的所有共享?
    • 如果后端在删除共享时失败,这可能会导致奇怪的状态,并且不容易确定失败的位置。这也会增加实现中的复杂性,可以由客户端隐藏。

关于 Cinder 实现的说明

  • 在卷创建时可以指定一致性组
  • 创建一致性组后,它将被调度到一个池,并且在 CG 中创建的所有卷都将转到该池

Cinder cli

   cgsnapshot-create   Creates a cgsnapshot and snapshots of every volume in the CG that show up in snapshot-list.
   cgsnapshot-delete   Removes one or more cgsnapshots.
   cgsnapshot-list     Lists all cgsnapshots.
   cgsnapshot-show     Shows cgsnapshot details.  <-- /cgsnapshots/detail?all_tenants=1&name=blah
   consisgroup-create  Creates a consistency group.
   consisgroup-create-from-src Creates a consistency group from a cgsnapshot filled with volumes from the snapshots in the cg.
   consisgroup-delete  Removes one or more consistency groups.
   consisgroup-list    Lists all consistencygroups.
   consisgroup-show    Shows details of a consistency group. <-- /consistencygroups/detail?all_tenants=1&name=blah
   consisgroup-update  Updates a consistencygroup.
   
   create --consisgroup-id

Cinder API

GET /consistencygroups/detail
POST /consistencygroups
 {'consistencygroup':{
   'name': <string>,
   'description': <string>,
   'volume_types': <string>,
   'availability_zone': <string>,
 }}
POST /consistencygroups/create_from_src
 {'consistencygroup-from-src':{
   'name': <string>,
   'description': <string>,
   'cgsnapshot_id': <string>,
 }}
GET /consistencygroups/<id>
POST consistencygroups/<id>/delete
  {"consistencygroup": {"force": false}}
PUT /consistencygroups/<id>
 {'consistencygroup':{
   'name': <string>,
   'description': <string>,
   'add_volumes': <string>,
   'remove_volumes': <string>,
 }}

GET /cgsnapshots/detail
GET /cgsnapshots/<id>
POST /cgsnapshots
 {'cgsnapshot':{
   'consistencygroup_id': <string>,
 }}
DELETE /cgsnapshots/<id>

Adds consistencygroup_id to POST/volumes


Xings 提交消息

   1) Create a CG, specifying all volume types that can be supported by this
   CG. The scheduler chooses a backend that supports all specified volume types.
   The CG will be empty when it is first created.   Backend needs to report
   consistencygroup_support = True.  Volume type can have the following in
   extra specs: {'capabilities:consistencygroup_support': '<is> True'}.
   If consistencygroup_support is not in volume type extra specs, it will be
   added to filter_properties by the scheduler to make sure that the scheduler
   will select the backend which reports consistency group support capability.
   
   Create CG CLI:
   cinder consisgroup-create --volume-type type1,type2 mycg1
   
   This will add a CG entry in the new consistencygroups table.
   
   2) After the CG is created, create a new volume and add to the CG.
   Repeat until all volumes are created for the CG.
   
   Create volume CLI (with CG):
   cinder create --volume-type type1 --consisgroup-id <CG uuid> 10
   
   This will add a consistencygroup_id foreign key in the new volume
   entry in the db.
   
   3) Create a snapshot of the CG (cgsnapshot).
   
   Create cgsnapshot CLI:
   cinder cgsnapshot-create <CG uuid>
   
   This will add a cgsnapshot entry in the new cgsnapshots table, create
   snapshot for each volume in the CG, and add a cgsnapshot_id foreign key
   in each newly created snapshot entry in the db.

问题

  1. 为什么 policy.json 默认允许 'nobody' 执行 CG 命令?
  2. 为什么在创建 CG 时需要指定卷类型?
    • 这是因为 CG 中的所有卷都需要位于同一后端上,以便后端能够遵守 CG。在文档中需要注意这一点,以避免指定冲突的卷类型。
  3. 如果通过 API 添加了位于不同后端的卷到 CG,会发生什么?
    • 目前,API 确保卷的卷类型与 CG 支持的卷类型匹配,并且 CG 和卷位于同一主机上(但可以位于不同的池中)。
  4. 使用 CG 创建的卷最终会位于同一主机上吗?
    • 是的,如果卷具有 CG,它将被投递到与 CG 相同的池。
    • 如果池已满,但同一后端上有其他可用的池,会发生什么?这应该失败吗?
  5. 如果驱动程序不支持 create_from_src,会发生什么?
    • CG 进入 ERROR 状态,并且卷从快照创建,也处于 ERROR 状态
    • 一致性组中的卷无法删除,并且具有卷的 CG 无法删除,因此必须先从 CG 中删除卷,然后才能删除它。但是,如果您不支持修改 CG 怎么办?
  6. 我们为什么需要 CG 的强制删除?
  7. 如果您不想在执行 snapshot-list 时看到 CG 中的快照,该怎么办?
    • 我想 API 过滤可以允许某人如果他们不想看到所有快照,则看不到它们。

需要提交的 Cinder Bug

  1. 强制删除 CG 后会发生什么?
    • 卷仍然存在,但仍然认为它们在 CG 中,这意味着您仍然无法删除该卷
  2. consisgroup-create-from-src 说 cgsnapshot 是 cinderclient 中的可选参数
  3. 在 CG 中创建卷可能最终不在 cg 中。执行的唯一检查是卷的卷类型与 CG 支持的卷类型匹配,但卷类型与后端不一定是 1-to-1 的关系。后端 A 和 B 都可能与卷类型 1 匹配。创建具有卷类型 1 的 cg 并最终位于后端 A。然后创建一个指定 CG 的卷,但最终位于后端 B

主要实现

299b99437c2de84222fd585f06c2d05ca213225b ConsistencyGroup: Return 400 instead of 500 for invalid body
adb4c80be82caacad83f1366a4b34e5653fd5dab Create Consistency Group from CG Snapshot API
1a62a6e60fda73bf31256fbf684dc03bb6cf0038 Modify Consistency Group API
fc7a4384be2ccf8ff24f6c0f72c681ad9133801a Add tests for consistency groups DB migration 
9082273305b0b9c117eb677a0e34c2ffde4e66f0 Volume types need to be specified when creating CG
764c5ec749821d36bb0215dc6002d3caea95d3b1 Delete consistency group failed
cf961f83ac323dfad1fa5e227d1e502a17529ecc Consistency Groups

外部驱动实现

8d5b795b37ed021c2639066689645f8aa0b1012f PureISCSIDriver consistency group updates.
39c1a8e08dc854e22ada315a46c20c99df2facf8 Add support to PureISCSIDriver for Consistency Groups
92a817708a3938b1b734d2caaa206b310996d8d0 EMC VMAX driver Kilo update