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 类的子类.
- 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不变