cinder manage/unmanage 管理卷 分析

mange命令
类似于create命令,但是区别是mange,不会在后端创建一个新的LUN,而是将一个已有的LUN纳入Cinder的管理(会对这个LUN从命名)。

cinder manage  
 :必须是pool之一;
:必须是storage可以识别的,如netapp下可以是path,或者uuid。

unmanage命令
类似于delete命令,但是不会从后端将LUN删除。

cinder unmanage

一、cinder manage

(1)测试:

[root@node1 ~]# cinder manage cinder1@netapp-iscsi-20170613161543#vol_09052017_154346_88  /vol/vol_09052017_154346_88/lun_31072017_164348_21 --name miaomiao --volume-type netapp_volume_type --id-type source-name --description jojo --availability-zone nova 
+--------------------------------+------------------------------------------------------------+
| Property                       | Value                                                      |
+--------------------------------+------------------------------------------------------------+
| attachments                    | []                                                         |
| availability_zone              | nova                                                       |
| bootable                       | false                                                      |
| consistencygroup_id            | None                                                       |
| created_at                     | 2017-07-31T08:44:36.000000                                 |
| description                    | jojo                                                       |
| encrypted                      | False                                                      |
| id                             | a89c9e8a-b589-497c-b0f7-f81149f62226                       |
| metadata                       | {}                                                         |
| migration_status               | None                                                       |
| multiattach                    | False                                                      |
| name                           | miaomiao                                                   |
| os-vol-host-attr:host          | cinder1@netapp-iscsi-20170613161543#vol_09052017_154346_88 |
| os-vol-mig-status-attr:migstat | None                                                       |
| os-vol-mig-status-attr:name_id | None                                                       |
| os-vol-tenant-attr:tenant_id   | 7b7d6627cf044d80aa6ec3f2f4ade2ab                           |
| replication_status             | None                                                       |
| size                           | 0                                                          |
| snapshot_id                    | None                                                       |
| source_volid                   | None                                                       |
| status                         | creating                                                   |
| updated_at                     | 2017-07-31T08:44:37.000000                                 |
| user_id                        | 3f56d2976eac468dbe836ebcc4400858                           |
| volume_type                    | netapp_volume_type              

(2)源码分析

cinder.volume.api.API#manage_existing:

    def manage_existing(self, context, host, cluster_name, ref, name=None,
                        description=None, volume_type=None, metadata=None,
                        availability_zone=None, bootable=False):
        if volume_type and 'extra_specs' not in volume_type:
            extra_specs = volume_types.get_volume_type_extra_specs(
                volume_type['id'])
            volume_type['extra_specs'] = extra_specs

        service = self._get_service_by_host_cluster(context, host,
                                                    cluster_name)

        if availability_zone is None:
            availability_zone = service.availability_zone

        manage_what = {
            'context': context,
            'name': name,
            'description': description,
            'host': service.host,
            'cluster_name': service.cluster_name,
            'ref': ref,
            'volume_type': volume_type,
            'metadata': metadata,
            'availability_zone': availability_zone,
            'bootable': bootable,
        }

        try:
            # 生成task-flow
            flow_engine = manage_existing.get_flow(self.scheduler_rpcapi,
                                                   self.db,
                                                   manage_what)
        except Exception:
            msg = _('Failed to manage api volume flow.')
            LOG.exception(msg)
            raise exception.CinderException(msg)

        # Attaching this listener will capture all of the notifications that
        # taskflow sends out and redirect them to a more useful log for
        # cinder's debugging (or error reporting) usage.
        with flow_utils.DynamicLogListener(flow_engine, logger=LOG):
            flow_engine.run()
            vol_ref = flow_engine.storage.fetch('volume')
            LOG.info(_LI("Manage volume request issued successfully."),
                     resource=vol_ref)
            return vol_ref

上段代码里manage_existing.get_flow是把cinder-manage最核心的里的任务串成一个线性任务。
在 TaskFlow 中使用 flow(流) 来关联各个 Task, 并且规定这些 Task 之间的执行和回滚顺序. flow 中不仅能够包含 task 还能够嵌套 flow. 常见类型有以下几种:

  • Linear(linear_flow.Flow): 线性流, 该类型 flow 中的 task/flow 按照加入的顺序来依次执行, 按照加入的倒序依次回滚.
  • Unordered(unordered_flow.Flow): 无序流, 该类型 flow 中的 task/flow 可能按照任意的顺序来执行和回滚.
  • Graph(graph_flow.Flow): 图流, 该类型 flow 中的 task/flow 按照显式指定的依赖关系或通过其间的 provides/requires 属性的隐含依赖关系来执行和回滚.

cinder.volume.flows.api.manage_existing.get_flow

ACTION = 'volume:manage_existing'

def get_flow(scheduler_rpcapi, db_api, create_what):

    flow_name = ACTION.replace(":", "_") + "_api"
    # 初始化taskflow.patterns.linear_flow.Flow 线性流程模式
    api_flow = linear_flow.Flow(flow_name)

    # This will cast it out to either the scheduler or volume manager via
    # the rpc apis provided.
    # 往api_flow添加两个任务
    api_flow.add(EntryCreateTask(db_api),
                 ManageCastTask(scheduler_rpcapi, db_api))

    # Now load (but do not run) the flow using the provided initial data.
    # 把create_what作为任务参数导入到api_flow的各个任务去
    return taskflow.engines.load(api_flow, store=create_what)

EntryCreateTask(db_api) 和 ManageCastTask(scheduler_rpcapi, db_api)

EntryCreateTask 和 ManageCastTask 都继承至CinderTask类。做为任务类,最重要的是复写了taskflow.atom.Atom的execute(执行业务)和revert(回退业务)方法。Atom 是 TaskFlow 的最小单位, 其他的所有类, 包括 Task 类都是 Atom 类的子类.

cinder manage/unmanage 管理卷 分析_第1张图片
task类关系图.png
  • EntryCreateTask

EntryCreateTask的execute是创建一条size为0 的volume记录,仅仅是在数据库层操作;revert是把该volume销毁。

class EntryCreateTask(flow_utils.CinderTask):
    """Creates an entry for the given volume creation in the database.

    Reversion strategy: remove the volume_id created from the database.
    """
    default_provides = set(['volume_properties', 'volume'])

    def __init__(self, db):
        requires = ['availability_zone', 'description', 'metadata',
                    'name', 'host', 'cluster_name', 'bootable', 'volume_type',
                    'ref']
        super(EntryCreateTask, self).__init__(addons=[ACTION],
                                              requires=requires)
        self.db = db

    def execute(self, context, **kwargs):
        """Creates a database entry for the given inputs and returns details.

        Accesses the database and creates a new entry for the to be created
        volume using the given volume properties which are extracted from the
        input kwargs.
        """
        volume_type = kwargs.pop('volume_type')
        volume_type_id = volume_type['id'] if volume_type else None

        volume_properties = {
            'size': 0,
            'user_id': context.user_id,
            'project_id': context.project_id,
            'status': 'managing',
            'attach_status': fields.VolumeAttachStatus.DETACHED,
            # Rename these to the internal name.
            'display_description': kwargs.pop('description'),
            'display_name': kwargs.pop('name'),
            'host': kwargs.pop('host'),
            'cluster_name': kwargs.pop('cluster_name'),
            'availability_zone': kwargs.pop('availability_zone'),
            'volume_type_id': volume_type_id,
            'metadata': kwargs.pop('metadata') or {},
            'bootable': kwargs.pop('bootable'),
        }
        # 在数据库创建卷记录
        volume = objects.Volume(context=context, **volume_properties)
        volume.create()

        return {
            'volume_properties': volume_properties,
            'volume': volume,
        }

    def revert(self, context, result, optional_args=None, **kwargs):
        # We never produced a result and therefore can't destroy anything.
        if isinstance(result, ft.Failure):
            return

        vol_id = result['volume_id']
        try:
            self.db.volume_destroy(context.elevated(), vol_id)
        except exception.CinderException:
            LOG.exception(_LE("Failed destroying volume entry: %s."), vol_id)

  • ManageCastTask
    ManageCastTask会真正管理物理卷,去后端查找lun并纳入cinder管理。
class ManageCastTask(flow_utils.CinderTask):
    """Performs a volume manage cast to the scheduler and to the volume manager.

    This which will signal a transition of the api workflow to another child
    and/or related workflow.
    """

    def __init__(self, scheduler_rpcapi, db):
        requires = ['volume', 'volume_properties', 'volume_type', 'ref']
        super(ManageCastTask, self).__init__(addons=[ACTION],
                                             requires=requires)
        self.scheduler_rpcapi = scheduler_rpcapi
        self.db = db

    def execute(self, context, volume, **kwargs):
        request_spec = kwargs.copy()
        request_spec['volume_id'] = volume.id

        # Call the scheduler to ensure that the host exists and that it can
        # accept the volume
        self.scheduler_rpcapi.manage_existing(context, volume,
                                              request_spec=request_spec)

    def revert(self, context, result, flow_failures, volume, **kwargs):
        # Restore the source volume status and set the volume to error status.
        # common.error_out 这个函数会调用对象的save方法修改状态
        common.error_out(volume, status='error_managing')
        LOG.error(_LE("Volume %s: manage failed."), volume.id)
        exc_info = False
        # all(x)如果all(x)参数x对象的所有元素不为0、''、False或者x为空对象,则返回True,否则返回False
        if all(flow_failures[-1].exc_info):
            exc_info = flow_failures[-1].exc_info
        LOG.error(_LE('Unexpected build error:'), exc_info=exc_info)

它通过rpc,在scheduler建立一系列子任务作成一个嵌套taskflow:

cinder.volume.flows.manager.manage_existing.get_flow:

    volume_flow.add(create_mgr.NotifyVolumeActionTask(db, "manage_existing.start"),
                    PrepareForQuotaReservationTask(db, driver),
                    create_api.QuotaReserveTask(),
                    ManageExistingTask(db, driver),
                    create_api.QuotaCommitTask(),
                    create_mgr.CreateVolumeOnFinishTask(db, "manage_existing.end"))
  • create_mgr.NotifyVolumeActionTask(db, "manage_existing.start") : 发出一个rpc通知 "manage_existing.start"
  • PrepareForQuotaReservationTask(db, driver) : 通过驱动按下列格式返回卷的大小等信息。
    # 调用netapp api查询得到lun的大小
    size = self.driver.manage_existing_get_size(volume,
                                                        manage_existing_ref)
    {'size': size,
    'volume_type_id': volume.volume_type_id,
    'volume_properties': volume,
    'volume_spec': {'status': volume.status,
                    'volume_name': volume.name,
                    'volume_id': volume.id}}
  • create_api.QuotaReserveTask() : 预留一个卷的quota
  • ManageExistingTask(db, driver) : 调用驱动方法,把lun交给cinder管理
  • create_api.QuotaCommitTask() : 提交之前预留的quota
  • create_mgr.CreateVolumeOnFinishTask(db, "manage_existing.end") : 发出一个rpc通知 "manage_existing.end";更新volume状态available,更新launched_at时间。

以netapp为例,ManageExistingTask调用cinder.volume.drivers.netapp.dataontap.block_base.NetAppBlockStorageLibrary#manage_existing:

    def manage_existing(self, volume, existing_ref):
        """Brings an existing storage object under Cinder management.

        existing_ref can contain source-id or source-name or both.
        source-id: lun uuid.
        source-name: complete lun path eg. /vol/vol0/lun.
        """
        # 调用netapp api,去拿到lun信息
        lun = self._get_existing_vol_with_manage_ref(existing_ref)
        # 通过卷获得卷类型,然后查到卷类型的扩展规格
        extra_specs = na_utils.get_volume_extra_specs(volume)
        # 判断这个lun和上面查到的扩展规格信息是否匹配,netapp只有block_7mode有实现这个函数,block_base直接pass不检查
        self._check_volume_type_for_lun(volume, lun, existing_ref, extra_specs)
        # 获取qos policy 组信息
        qos_policy_group_info = self._setup_qos_for_volume(volume, extra_specs)
        qos_policy_group_name = (
            na_utils.get_qos_policy_group_name_from_info(
                qos_policy_group_info))
        # 取出lun的元数据
        path = lun.get_metadata_property
        
        # 如果lun的名字不等于cinder 卷的id,需要把lun名改成cinder卷id!
        if lun.name == volume['name']:
            new_path = path
            LOG.info(_LI("LUN with given ref %s need not be renamed "
                         "during manage operation."), existing_ref)
        else:
            # partition() 方法用来根据指定的分隔符将字符串进行分割。如果字符串包含指定的分隔符,则返回一个3元的元组,第一个为分隔符左边的子串,第二个为分隔符本身,第三个为分隔符右边的子串。
            (rest, splitter, name) = path.rpartition('/')
            # 用cinder卷id来做lun的新path
            new_path = '%s/%s' % (rest, volume['name'])
            self.zapi_client.move_lun(path, new_path)
            lun = self._get_existing_vol_with_manage_ref(
                {'source-name': new_path})

        # 重设qos policy 组信息
        if qos_policy_group_name is not None:
            self.zapi_client.set_lun_qos_policy_group(new_path,
                                                      qos_policy_group_name)
        # 把这个lun加到内存字典lun_table。lun_table是一个格式如{vol_id:NetAppLun}的字典,每次netapp的cinder-volume服务起来时候,会初始化这个表并加载lun数据。
        self._add_lun_to_table(lun)
        LOG.info(_LI("Manage operation completed for LUN with new path"
                     " %(path)s and uuid %(uuid)s."),
                 {'path': lun.get_metadata_property('Path'),
                  'uuid': lun.get_metadata_property('UUID')})
        
        # 有些模式,在创建、管理卷的时候还需要修改一些必要信息。
        # 比如cinder.volume.drivers.netapp.dataontap.block_cmode.NetAppBlockStorageCmodeLibrary#_get_volume_model_update还要改{'replication_status': fields.ReplicationStatus.ENABLED}。
        # block_bass则不用。
        return self._get_volume_model_update(volume)

二、cinder unmanage

(1)测试:

[root@node1 ~]# cinder unmanage 2e774095-3452-436a-a524-733551a7f269   

(2)源码分析:

cinder.api.contrib.volume_unmanage.VolumeUnmanageController#unmanage

    vol = self.volume_api.get(context, id)
    self.volume_api.delete(context, vol, unmanage_only=True)

cinder.volume.manager.VolumeManager#delete_volume

  • 把lun信息从lun_table移除
  • 删除volume记录
  • 后端的lun不变

你可能感兴趣的:(cinder manage/unmanage 管理卷 分析)