美文网首页OpenStack
cinder manage/unmanage 管理卷 分析

cinder manage/unmanage 管理卷 分析

作者: 笨手笨脚越 | 来源:发表于2017-07-31 14:48 被阅读49次

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

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

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

    cinder unmanage <vol-id>

    一、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 类的子类.

    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 管理卷 分析

        本文链接:https://www.haomeiwen.com/subject/bocrlxtx.html