1  Cinder架构图

Cinder是在虚拟机和具体存储设备之间引入了一层“逻辑存储卷”的抽象,Cinder本身并不是一种存储技术,只是提供一个中间的抽象层,Cinder通过调用不同存储后端类型的驱动接口来管理相对应的后端存储,为用户提供统一的卷相关操作的存储接口。

由上图可以看出,目前的Cinder组件主要由cinder-api、cinder-scheduler、cinder-volume以及cinder-backup几个服务所组成,它们之间通过消息队列进行通信。

2  Cinder源码结构

跟Glance一样,先看setup.cfg文件里[entry_points]里console_scripts的内容,它写明Cinder的各项服务的入口点:

console_scripts =
cinder-api = cinder.cmd.api:main
cinder-backup = cinder.cmd.backup:main
cinder-manage = cinder.cmd.manage:main
cinder-rootwrap = oslo_rootwrap.cmd:main
cinder-rtstool = cinder.cmd.rtstool:main
cinder-scheduler = cinder.cmd.scheduler:main
cinder-volume = cinder.cmd.volume:main
cinder-volume-usage-audit = cinder.cmd.volume_usage_audit:main

服务

描述

cinder-api

进入Cinder的HTTP接口。

cinder-backup

用于提供存储卷的备份功能,支持将块存储卷备份到OpenStack备份存储后端,比如Swift、Ceph等。

cinder-manage

用于cinder管理的命令行接口。

cinder-rtstool

伴随LIO(Linux-IO Target)支持而增加的工具。

cinder-scheduler

根据预定的策略选择合适的cinder-volume节点来处理用户的请求。

cinder-volume

通过相关驱动程序架构直接与块存储服务进行交互。

cinder-volume-usage-audit

用于卷使用情况统计。

3  Cinder各服务功能概述

从前面我们可以看到Cinder内部服务中主要是由cinder-api、cinder-scheduler、cinder-volume和cinder-backup组成的,而且各服务之间是使用高级消息队列进行通信的,以下对各服务进行概述。

3.1  cinder-api

cinder-api的作用主要是为用户提供Restful风格的接口,接收client的请求,在该服务中可以对用户的权限和传入的参数进行提前的检查,无误后方才将请求信息交给消息队列,由后续的其它服务根据消息队列信息进行处理。

3.2  cinder-scheduler

cinder-scheduler是一个调度器,用于选择合适的存储节点,该服务中包含过滤器算法和权重计算算法,Cinder默认的过滤算法有三个:

(1)AvailabilityZoneFilter过滤算法:判断cinder host的availability zone是否与目标zone一致,否则过滤掉该节点;

(2)CapacityFilter过滤算法:判断host的可用存储空间是否不小于要分配卷的大小,否则过滤掉该节点;

(3)CapabilitiesFilter过滤算法:检查host的属性否和volume type中的extra specs相同,不相同则过滤掉该节点。

通过指定的过滤算法可能会得到一系列的host,这时还需使用权重计算算法来计算各节点的权重值,权重值最大的会认为是最优节点,cinder-scheduler会基于消息队列服务的rpc调用来让最优节点对请求进行处理,以下列出几个计算权重的算法:

(1)AllocatedCapacityWeigher算法:存储空间使用最小的节点为最优节点;

(2)CapacityWeigher算法:可用存储空间最大的节点成为最优节点;

(3)ChanceWeigher算法:随机选择一个节点作为最优节点。

3.3  cinder-volume

cinder-volume是部署在存储节点上的服务,cinder-volume的主要功能是对后端存储进行一层抽象封装,为用户提供统一的接口,cinder-volume通过调用后端存储驱动API来进行存储相关的操作。cinder-volume服务执行的功能可以由如下表列出:

卷操作

创建卷

克隆卷

扩展卷

删除卷

卷虚机从操作

挂载卷到虚拟机

从虚拟机里分离出卷

卷-快照操作

创建卷的快照

从已有卷快照创建卷

删除快照

卷-镜像操作

从镜像创建卷

从卷创建镜像

3.4  cinder-backup

cinder-backup的功能是将volume备份到别的存储设备上去,以后可以通过restone操作恢复。

cinder-backup跟volume做快照还是有很大区别的,以下列出主要的几点区别:

(1)快照依赖于源volume,如果源volume被删除了,那快照是无用的,但backup是不依赖于源volume的,因为源volume也被备份到备份存储设备上去了,通过restone操作可以完全恢复。

(2)快照和源volume通常放在一起,由同一个volume provider管理,而backup存放在独立的备份设备上,有自己的备份方案和实现,和volume provider没有关系。

(3)cinder-backup是具有容灾功能的,但因此备份往往需要较大的空间,而快照snapshot则提供volume provider内快捷的回溯功能。

我们可以通过使用cinder backup-create命令创建某个卷的备份,通过cinder backup-list命令查看创建的备份。

4  Cinder组件重要流程分析

4.1  Cinder服务启动流程

Cinder的主要服务包括cinder-api、cinder-scheduler和cinder-volume等,这里介绍下cinder-api服务的启动流程,其它类似。

cinder-api服务启动流程

从/cmd/api.py的main函数中我们可以看到以下几行代码:

def main():
objects.register_all()
gmr_opts.set_defaults(CONF)
CONF(sys.argv[1:], project='cinder',
version=version.version_string())
config.set_middleware_defaults()
logging.setup(CONF, "cinder")
python_logging.captureWarnings(True)
utils.monkey_patch() gmr.TextGuruMeditation.setup_autorun(version, conf=CONF) rpc.init(CONF)
launcher = service.process_launcher()
server = service.WSGIService('osapi_volume')
launcher.launch_service(server, workers=server.workers)
launcher.wait()

(1)register_all函数是导入/cinder/object目录下的多个模块

(2)接着是加载配置文件、设置中间件和初始化日志模块

(3)初始化rpc模块,用于与其它服务进行通信

(4)启动wsgi服务,监听客户端发送的请求

4.2  创建卷过程分析

比如客户端敲入命令:openstack volume create oop2 --size=1 --debug

首先我们知道该请求会先在cinder-api服务中进行处理,该请求首先会匹配到/cinder/api/v2/volumes.py文件的create函数:

def create(self, req, body):
...# 对卷的参数进行检查,比如卷名、size参数,参数赋予kwargs字典等
# 调用/cinder/volume/api.py里的create函数进行创建
new_volume = self.volume_api.create(context,
size,
volume.get('display_name'),
volume.get('display_description'),
**kwargs) retval = self._view_builder.detail(req, new_volume) return retval

查看api.py文件里的create函数:

def create(self, context, size, name, description, ...):
# 验证该用户是否有权限在该project下做这个操作
check_policy(context, 'create_from_image' if image_id else 'create')
# 获取调用cinder-scheduler服务端方法的rpc客户端
sched_rpcapi = (self.scheduler_rpcapi if (
not cgsnapshot and not source_cg and
not group_snapshot and not source_group)
else None)
# 获取调用cinder-volume服务端方法的rpc客户端
volume_rpcapi = (self.volume_rpcapi if (
not cgsnapshot and not source_cg and
not group_snapshot and not source_group)
else None)
# 调用/cinder/volume/flows/api/create_volume.py的get_flow方法,并返回api入口流
flow_engine = create_volume.get_flow(self.db,
self.image_service,
availability_zones,
create_what,
sched_rpcapi,
volume_rpcapi)

在cinder组件的代码中我们可以经常看到在处理一个操作时,会创建一个类flow的实例对象,然后往该对象中添加任务来进行工作流程的处理,查看get_flow函数的实现:

def get_flow(db_api, image_service_api, availability_zones, create_what,
scheduler_rpcapi=None, volume_rpcapi=None):
"""Constructs and returns the api entrypoint flow. This flow will do the following: 1. Inject keys & values for dependent tasks.
2. Extracts and validates the input keys & values.
3. Reserves the quota (reverts quota on any failures).
4. Creates the database entry.
5. Commits the quota.
6. Casts to volume manager or scheduler for further processing.
""" flow_name = ACTION.replace(":", "_") + "_api"
# 获取类flow的实例化对象,可以添加task到flow对象
api_flow = linear_flow.Flow(flow_name) # 添加ExtractVolumeRequestTask,该任务是将接收到的一系列参数值通过一系列条件的验证将验证结果存储起来供其它task使用
api_flow.add(ExtractVolumeRequestTask(
image_service_api,
availability_zones,
rebind={'size': 'raw_size',
'availability_zone': 'raw_availability_zone',
'volume_type': 'raw_volume_type'})) # 添加多个task
# QuotaReserveTask: 为卷保留配额,失败时可以回滚
# EntryCreateTask: 将卷的创建过程写入数据库,比如卷的状态,此时卷的状态未"creating"
# QuotaCommitTask: 用于提交保留
api_flow.add(QuotaReserveTask(),
EntryCreateTask(),
QuotaCommitTask()) # 将请求通过基于消息队列服务的rpc方式发送到scheduler或者volume管理程序去处理请求
if scheduler_rpcapi and volume_rpcapi:
# This will cast it out to either the scheduler or volume manager via
# the rpc apis provided.
# 添加VolumeCastTask
# VolumeCastTask: 将卷创建工作转移到scheduler或volume管理程序去处理,也就表示工作流程从api服务中转到
# 其它服务中去执行
api_flow.add(VolumeCastTask(scheduler_rpcapi, volume_rpcapi, db_api)) # Now load (but do not run) the flow using the provided initial data.
return taskflow.engines.load(api_flow, store=create_what)

上面的代码中比较关键的代码是VolumeCastTask该任务类它会调用它的execute函数,execute函数中会调用_cast_create_volume函数:

def _cast_create_volume(self, context, request_spec, filter_properties):
# 这里判断用户是否有指定host进行创建卷操作,如果没有则将创建任务交给 scheduler管理程序去完成
# 如果用户有指定host则跳过scheduler,直接将创建任务交给volume管理程序去完成
if not source_volume_ref:
# Cast to the scheduler and let it handle whatever is needed
# to select the target host for this volume.
# 这里会直接调用到SchedulerAPI.create_volume函数
# SchedulerAPI.create_volume函数会通过消息异步调用SchedulerManager.create_volume函数,
# 也就是/cinder/scheduler/manager.py中的create_volume函数
self.scheduler_rpcapi.create_volume(
context,
volume,
snapshot_id=snapshot_id,
image_id=image_id,
request_spec=request_spec,
filter_properties=filter_properties)
else:
# Bypass the scheduler and send the request directly to the volume
# manager.
volume.host = source_volume_ref.host
volume.cluster_name = source_volume_ref.cluster_name
volume.scheduled_at = timeutils.utcnow()
volume.save()
if not cgsnapshot_id:
self.volume_rpcapi.create_volume(
context,
volume,
request_spec,
filter_properties,
allow_reschedule=False)

到这里cinder-api服务就完成它所有的工作了,它会通过消息异步调用cinder-scheduler服务里面的函数,以下创建卷的工作流程开始在cinder-scheduler服务中进行,查看scheduler服务中的manager.py文件中的create_volume函数:

def create_volume(self, context, volume, snapshot_id=None, image_id=None,
request_spec=None, filter_properties=None):
# 确保调度程序已经准备好
self._wait_for_scheduler() try:
# 这里会调用/cinder/scheduler/flows/create_volume.py中的get_flow函数
flow_engine = create_volume.get_flow(context,
self.driver,
request_spec,
filter_properties,
volume,
snapshot_id,
image_id)

主要查看get_flow函数的实现:

def get_flow(context, driver_api, request_spec=None,
filter_properties=None,
volume=None, snapshot_id=None, image_id=None): """Constructs and returns the scheduler entrypoint flow. This flow will do the following: 1. Inject keys & values for dependent tasks.
2. Extract a scheduler specification from the provided inputs.
3. Use provided scheduler driver to select host and pass volume creation
request further.
"""
create_what = {
'context': context,
'raw_request_spec': request_spec,
'filter_properties': filter_properties,
'volume': volume,
'snapshot_id': snapshot_id,
'image_id': image_id,
} flow_name = ACTION.replace(":", "_") + "_scheduler"
# 获取类flow实例对象
scheduler_flow = linear_flow.Flow(flow_name) # This will extract and clean the spec from the starting values.
# ExtractSchedulerSpecTask: 从请求规范中提取规范对象
scheduler_flow.add(ExtractSchedulerSpecTask(
rebind={'request_spec': 'raw_request_spec'})) # This will activate the desired scheduler driver (and handle any
# driver related failures appropriately).
# ScheduleCreateVolumeTask: 激活scheduler程序的驱动程序并处理任何后续故障
scheduler_flow.add(ScheduleCreateVolumeTask(driver_api)) # Now load (but do not run) the flow using the provided initial data.
return taskflow.engines.load(scheduler_flow, store=create_what)

ScheduleCreateVolumeTask任务会执行它的execute函数,execute函数会调用过滤器FilterScheduler的schedule_create_volume函数来选择最佳的存储后端并将工作流程过渡到volume管理服务中:

def schedule_create_volume(self, context, request_spec, filter_properties):
# 选择一个最合适的backend来进行准备创建的卷的存储
backend = self._schedule(context, request_spec, filter_properties) if not backend:
raise exception.NoValidBackend(reason=_("No weighed backends "
"available")) backend = backend.obj
volume_id = request_spec['volume_id'] # 更新数据库当前卷的状态
updated_volume = driver.volume_update_db(context, volume_id,
backend.host,
backend.cluster_name)
self._post_select_populate_filter_properties(filter_properties,
backend) # context is not serializable
filter_properties.pop('context', None) # 通过消息队列请求调用volume_rpcapi.create_volume
# VolumeAPI.create_volume会通过消息队列远程调用VolumeManager.create_volume
# 最后会调用到/cinder/volume/manager.py中的create_volume函数,也就是创建卷的工作流程会转入到cinder-volume的服务中
self.volume_rpcapi.create_volume(context, updated_volume, request_spec,
filter_properties,
allow_reschedule=True)

至此cinder-scheduler服务的工作也已经全部完成了,接下来的工作会调用进cinder-volume服务的manager.py文件中create_volume函数,该函数也是通过建立flow实例对象,然后添加任务来完成创建工作,我们直接看get_flow函数:

def get_flow(context, manager, db, driver, scheduler_rpcapi, host, volume,
allow_reschedule, reschedule_context, request_spec,
filter_properties, image_volume_cache=None): """Constructs and returns the manager entrypoint flow. This flow will do the following: 1. Determines if rescheduling is enabled (ahead of time).
2. Inject keys & values for dependent tasks.
3. Selects 1 of 2 activated only on *failure* tasks (one to update the db
status & notify or one to update the db status & notify & *reschedule*).
4. Extracts a volume specification from the provided inputs.
5. Notifies that the volume has started to be created.
6. Creates a volume from the extracted volume specification.
7. Attaches a on-success *only* task that notifies that the volume creation
has ended and performs further database status updates.
""" flow_name = ACTION.replace(":", "_") + "_manager"
# 获取类flow实例对象
volume_flow = linear_flow.Flow(flow_name) # This injects the initial starting flow values into the workflow so that
# the dependency order of the tasks provides/requires can be correctly
# determined.
create_what = {
'context': context,
'filter_properties': filter_properties,
'request_spec': request_spec,
'volume': volume,
} # ExtractVolumeRefTask: 提取给定卷ID的卷引用
volume_flow.add(ExtractVolumeRefTask(db, host, set_error=False)) retry = filter_properties.get('retry', None) # Always add OnFailureRescheduleTask and we handle the change of volume's
# status when reverting the flow. Meanwhile, no need to revert process of
# ExtractVolumeRefTask.
do_reschedule = allow_reschedule and request_spec and retry
volume_flow.add(OnFailureRescheduleTask(reschedule_context, db, driver,
scheduler_rpcapi, do_reschedule)) LOG.debug("Volume reschedule parameters: %(allow)s "
"retry: %(retry)s", {'allow': allow_reschedule, 'retry': retry}) # ExtractVolumeSpecTask: 结合数据库存取的该卷的信息,提供有用的、易分析的卷相关数据结构给其它任务使用
# NotifyVolumeActionTask: 在卷开始创建时执行相关的通知信息
# CreateVolumeFromSpecTask: 该任务是根据卷的规格真实创建卷
# CreateVolumeOnFinishTask: 当卷创建成功后,会使用该任务将卷在数据库的状态更新为available
volume_flow.add(ExtractVolumeSpecTask(db),
NotifyVolumeActionTask(db, "create.start"),
CreateVolumeFromSpecTask(manager,
db,
driver,
image_volume_cache),
CreateVolumeOnFinishTask(db, "create.end")) # Now load (but do not run) the flow using the provided initial data.
return taskflow.engines.load(volume_flow, store=create_what)

上面代码中进行卷创建的工作是在CreateVolumeFromSpecTask该任务中,该任务类首先是执行execute函数,execute函数中ongoing根据要创建的卷的类型调用相对应的方法来进行卷的创建:

# 根据不同类型的卷调用不同的方法来创建
if create_type == 'raw':
model_update = self._create_raw_volume(volume, **volume_spec)
elif create_type == 'snap':
model_update = self._create_from_snapshot(context, volume,
**volume_spec)
elif create_type == 'source_vol':
model_update = self._create_from_source_volume(
context, volume, **volume_spec)
elif create_type == 'source_replica':
model_update = self._create_from_source_replica(
context, volume, **volume_spec)

这里我们卷类型应该是raw,则它会调用_create_raw_volume函数:

ret = self.driver.create_volume(volume)

在该函数中,它会根据配置文件中指定的后端存储类型来调用相对应的在driver目录下的逻辑代码,比如我们配置的后端存储是Ceph的块设备存储,那里它就会调用到/cinder/volume/drivers/rbd.py文件的create_volume函数来进行卷的创建。

4.3  挂载卷到虚拟机过程分析

在OpenStack的搭建示例中,使用的是lvm后端存储,然后再使用ISCSI的方式来进行访问后端存储卷,这里我们采用的后端存储方式是Ceph的块设备存储,libvirt是支持直接挂载rbd image的,然后通过rbd协议来访问image,以下是挂载操作过程的源码分析分析,大部分的工作其实都是在nova组件服务中完成的。

我们可以通过敲入命令nova volume-attach instance-name volume-id来将volume-id的卷挂载到实例instance-name中,这个操作首先会调用到/nova/api/openstack/compute/volumes.py中的create函数:

def create(self, req, server_id, body):
"""Attach a volume to an instance."""
context = req.environ['nova.context']
context.can(vol_policies.BASE_POLICY_NAME)
context.can(va_policies.POLICY_ROOT % 'create') volume_id = body['volumeAttachment']['volumeId']
device = body['volumeAttachment'].get('device') instance = common.get_instance(self.compute_api, context, server_id) if instance.vm_state in (vm_states.SHELVED,
vm_states.SHELVED_OFFLOADED):
_check_request_version(req, '2.20', 'attach_volume',
server_id, instance.vm_state) device = self.compute_api.attach_volume(context, instance,
volume_id, device) # The attach is async
attachment = {}
attachment['id'] = volume_id
attachment['serverId'] = server_id
attachment['volumeId'] = volume_id
attachment['device'] = device # TODO(justinsb): How do I return "accepted" here?
return {'volumeAttachment': attachment}

这里的关键执行时调用了/nova/compute/api.py的attach_volume函数:

def attach_volume(self, context, instance, volume_id, device=None,
disk_bus=None, device_type=None):
if device and not block_device.match_device(device):
raise exception.InvalidDevicePath(path=device) is_shelved_offloaded = instance.vm_state == vm_states.SHELVED_OFFLOADED
if is_shelved_offloaded:
return self._attach_volume_shelved_offloaded(context,
instance,
volume_id,
device,
disk_bus,
device_type) return self._attach_volume(context, instance, volume_id, device,
disk_bus, device_type)

这里主要是根据虚拟机的当前状态来判断执行哪个函数,我们当前的虚拟机是在运行的,它走的逻辑是_attach_volume函数,我们查看该函数:

def _attach_volume(self, context, instance, volume_id, device,
disk_bus, device_type):
# 远程目的主机确定设备名和更新数据库并返回相关信息对象
volume_bdm = self._create_volume_bdm(
context, instance, device, volume_id, disk_bus=disk_bus,
device_type=device_type)
try:
# 这个函数中会通过远程调用cinder-volume服务里的函数来检查该卷是否是可添加的且更新数据库的状态
self._check_attach_and_reserve_volume(context, volume_id, instance)
self.compute_rpcapi.attach_volume(context, instance, volume_bdm)
except Exception:
with excutils.save_and_reraise_exception():
volume_bdm.destroy() return volume_bdm.device_name

前面一些代码主要都是用以检查卷和更新数据库信息的,可以看到后面调用到attach_volume继续attach的任务,在attach_volume函数中通过rpc方式调用到目的计算节点上的函数进行任务的执行,这里调用到的是目的计算节点上/nova/compute/manager.py中的attach_volume函数,该函数里根据之前的参数信息转换了一个driver_block_device类实例对象,然后调用_attach_volume函数并将该实例作为参数传入:

def _attach_volume(self, context, instance, bdm):
context = context.elevated()
# 因为我们创建的volume,所以这里调用的attach方法是/nova/virt/block_device.py里的attach函数
bdm.attach(context, instance, self.volume_api, self.driver,
do_check_attach=False, do_driver_attach=True)
info = {'volume_id': bdm.volume_id}
self._notify_about_instance_usage(
context, instance, "volume.attach", extra_usage_info=info)

查看attach函数:

def attach(self, context, instance, volume_api, virt_driver,
do_check_attach=True, do_driver_attach=False, **kwargs):
# 获取有关要attach的volume的信息对象
volume = volume_api.get(context, self.volume_id)
if do_check_attach:
# 检查volume的状态属性是否符合attach的要求
volume_api.check_attach(context, volume, instance=instance) volume_id = volume['id']
context = context.elevated() # 返回该计算节点信息,比如节点名、节点ip、操作系统架构等
connector = virt_driver.get_volume_connector(instance)
# 获取卷的信息,比如如果是存储在Ceph集群的,会包含集群名、monitor节点ip等,确保拥有的信息能访问集群中的该volume
connection_info = volume_api.initialize_connection(context,
volume_id,
connector)
if 'serial' not in connection_info:
connection_info['serial'] = self.volume_id
self._preserve_multipath_id(connection_info) # If do_driver_attach is False, we will attach a volume to an instance
# at boot time. So actual attach is done by instance creation code.
if do_driver_attach:
# 远程调用cinder组件服务中的函数去获取该卷的加密元数据
encryption = encryptors.get_encryption_metadata(
context, volume_api, volume_id, connection_info) virt_driver.attach_volume(
context, connection_info, instance,
self['mount_device'], disk_bus=self['disk_bus'],
device_type=self['device_type'], encryption=encryption)
self['connection_info'] = connection_info
if self.volume_size is None:
self.volume_size = volume.get('size') mode = 'rw'
if 'data' in connection_info:
mode = connection_info['data'].get('access_mode', 'rw')

这段代码中initialize_connection函数主要都是远程调用了volume组件服务来获取信息,这里我们可以详细看下这个函数在cinder组件服务里所做的事情,主要查看/cinder/volume/manager.py的initialize_connection函数:

def initialize_connection(self, context, volume, connector):
# TODO(jdg): Add deprecation warning
# 验证driver是否已经初始化
utils.require_driver_initialized(self.driver)
# 对于rbd驱动没有重写该方法,所以没做任何事
self.driver.validate_connector(connector) # 对于rbd driver,该方法是空的,因为rbd不需要像ISCSI那样创建target、创建protal
model_update = self.driver.create_export(context.elevated(),
volume, connector)
if model_update:
volume.update(model_update)
volume.save()
# 对于ceph rbd驱动,这里是获取有关该卷所在集群的信息,比如monitor、ip、secret_uuid等
conn_info = self.driver.initialize_connection(volume, connector) conn_info = self._parse_connection_options(context, volume, conn_info)
LOG.info(_LI("Initialize volume connection completed successfully."),
resource=volume)
return conn_info

注意这里的driver是根据配置文件的配置的后端存储类型的driver,由于我们配置的是Ceph的块设备作为后端存储,因此其实例driver调用的函数都是/cinder/volume/drivers/rbd.py里的函数。

现在我们回到nova组件代码中,查看virt_driver.attach_volume调用的是/nova/virt/libvirt/driver.py中的attach_volume函数:

def attach_volume(self, context, connection_info, instance, mountpoint,
disk_bus=None, device_type=None, encryption=None):
guest = self._host.get_guest(instance) disk_dev = mountpoint.rpartition("/")[2]
bdm = {
'device_name': disk_dev,
'disk_bus': disk_bus,
'device_type': device_type} # Note(cfb): If the volume has a custom block size, check that
# that we are using QEMU/KVM and libvirt >= 0.10.2. The
# presence of a block size is considered mandatory by
# cinder so we fail if we can't honor the request.
data = {}
if ('data' in connection_info):
data = connection_info['data']
if ('logical_block_size' in data or 'physical_block_size' in data):
if ((CONF.libvirt.virt_type != "kvm" and
CONF.libvirt.virt_type != "qemu")):
msg = _("Volume sets block size, but the current "
"libvirt hypervisor '%s' does not support custom "
"block size") % CONF.libvirt.virt_type
raise exception.InvalidHypervisorType(msg) disk_info = blockinfo.get_info_from_bdm(
instance, CONF.libvirt.virt_type, instance.image_meta, bdm)
self._connect_volume(connection_info, disk_info)
if disk_info['bus'] == 'scsi':
disk_info['unit'] = self._get_scsi_controller_max_unit(guest) + 1 conf = self._get_volume_config(connection_info, disk_info) self._check_discard_for_attach_volume(conf, instance) state = guest.get_power_state(self._host)
live = state in (power_state.RUNNING, power_state.PAUSED) if encryption:
encryptor = self._get_volume_encryptor(connection_info,
encryption)
encryptor.attach_volume(context, **encryption) guest.attach_device(conf, persistent=True, live=live)

这段代码中关键的地方有首先获取该该虚拟机的实例对象,然后将一系列参数都封装进conf对象中,然后guest调用attach_device函数来完成挂载工作,查看该函数:

def attach_device(self, conf, persistent=False, live=False):
"""Attaches device to the guest. :param conf: A LibvirtConfigObject of the device to attach
:param persistent: A bool to indicate whether the change is
persistent or not
:param live: A bool to indicate whether it affect the guest
in running state
"""
flags = persistent and libvirt.VIR_DOMAIN_AFFECT_CONFIG or 0
flags |= live and libvirt.VIR_DOMAIN_AFFECT_LIVE or 0 # 把conf中的信息转换成xml的格式,然后可通过libvirt工具将卷挂载到guest中
device_xml = conf.to_xml()
if six.PY3 and isinstance(device_xml, six.binary_type):
device_xml = device_xml.decode('utf-8') LOG.debug("attach device xml: %s", device_xml)
self._domain.attachDeviceFlags(device_xml, flags=flags)

这一段代码的主要工作就是将conf转换成libvirt挂载卷需要的xml格式的文件,然后由libvirt提供的功能来卷挂载到虚拟机实例上去。

Cinder组件解析的更多相关文章

  1. .NetCore中的日志(1)日志组件解析

    .NetCore中的日志(1)日志组件解析 0x00 问题的产生 日志记录功能在开发中很常用,可以记录程序运行的细节,也可以记录用户的行为.在之前开发时我一般都是用自己写的小工具来记录日志,输出目标包 ...

  2. Cinder 组件详解 - 每天5分钟玩转 OpenStack(47)

    本节我们将详细讲解 Cinder 的各个子服务. cinder-api cinder-api 是整个 Cinder 组件的门户,所有 cinder 的请求都首先由 nova-api 处理.cinder ...

  3. OpenStack Cinder组件支持的块存储设备表

    摘自恒天云官网:http://www.hengtianyun.com/download-show-id-18.html OpenStack的Cinder组件底层可以连接多种存储设备和方案,每一个Ope ...

  4. Ext 常用组件解析

    Ext 常用组件解析 Panel 定义&常用属性 //1.使用initComponent Ext.define('MySecurity.view.resource.ResourcePanel' ...

  5. Ionic 常用组件解析

    Ionic 常用组件解析 $ionicModal(弹出窗口): //创建一个窗口 //此处注意目录的起始位置为app $ionicModal.fromTemplateUrl('app/security ...

  6. Cinder组件

    cinder-api cinder-api 是整个 Cinder 组件的门户,所有 cinder 的请求都首先由 cinder-api 处理. cinder-api 向外界暴露若干 HTTP REST ...

  7. React Native组件(三)Text组件解析

    相关文章 React Native探索系列 React Native组件系列 前言 此前介绍了最基本的View组件,接下来就是最常用的Text组件,对于Text组件的一些常用属性,这篇文章会给出简单的 ...

  8. React Native组件解析(二)之Text

    React Native组件解析(二)之Text 1. 概述 Text组件对应于iOS的UILabel,Android的TextView,用来显示文本.但是Text组件的内部使用的并不是flexbox ...

  9. O047、 Cinder 组件详解

    参考https://www.cnblogs.com/CloudMan6/p/5585637.html   cinder-api   cinder-api 是整个Cinder 组件的门户,所有cinde ...

随机推荐

  1. yii 页面加载完成后弹出模态框

    <?php $js = <<<JS $('#page-modal').modal('show');//页面加载完显示模态框 $('.modal-dialog').css('wi ...

  2. 获取访问MySQL的应用

    接到业务需求,要我统计哪个应用访问了哪些表,一般来讲可以通过: 1.show full processlist; 2.SELECT HOST FROM information_schema.proce ...

  3. xcode开启后,每次调试运行要输入密码

    1.contorl+空格 打开终端 2.输入DevToolsSecurity --status查看状态,如果是Developer mode is currently disabled.那就对了 3.输 ...

  4. js中进行金额计算

    js中进行金额计算parseFloat   在js中进行以元为单位进行金额计算时 使用parseFloat会产生精度问题var price = 10.99;var quantity = 7;var n ...

  5. MUI框架 picker日期选择器实例

    MUI官方文档点我 (一)准备工作,下载相关的js.cs文件,地址 (二)新建普通html页面 1)引入相关js.cs文件 2) 一个input,记录下id: <form> <lab ...

  6. 如鹏网学习笔记(九)JavaScript

    JavaScript笔记 一.JavaScript简介 1,JavaScript是一种计算机编程语言,可以像等其他编程语言那样定义变量,执行循环等. 2,JavaScript代码主要执行在浏览器上,为 ...

  7. 记一次C#面试

    最近参加了工作后的第一次面试,虽然最终没谈成,但是收获还是不少,不管是技术还是面试经验还是得多多积累呀. 这一次面试与在学校时候参加过的面试区别还是挺大的.校园招聘的面试问的问题似乎都比较具体,直接针 ...

  8. 分ip统计网站访问次数

    package web.listener; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; ...

  9. data-id 和 id 的区别

    作者:Zeropoint零点 来源:CSDN 原文:https://blog.csdn.net/qq_41648132/article/details/80364335 版权声明:本文为Zeropoi ...

  10. line-height属性的深入了解

    line-height属性的细节与大多数CSS属性不同,line-height支持属性值设置为无单位的数字.有无单位在子元素继承属性时有微妙的不同. 语法line-height: normal | & ...