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:maincinder-backup = cinder.cmd.backup:maincinder-manage = cinder.cmd.manage:maincinder-rootwrap = oslo_rootwrap.cmd:maincinder-rtstool = cinder.cmd.rtstool:maincinder-scheduler = cinder.cmd.scheduler:maincinder-volume = cinder.cmd.volume:maincinder-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 andnot group_snapshot and not source_group)else None)# 获取调用cinder-volume服务端方法的rpc客户端volume_rpcapi = (self.volume_rpcapi if (not cgsnapshot and not source_cg andnot 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.hostvolume.cluster_name = source_volume_ref.cluster_namevolume.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 creationrequest 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.objvolume_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 serializablefilter_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 dbstatus & 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 creationhas 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 retryvolume_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 asyncattachment = {}attachment['id'] = volume_idattachment['serverId'] = server_idattachment['volumeId'] = volume_idattachment['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_OFFLOADEDif 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等,确保拥有的信息能访问集群中的该volumeconnection_info = volume_api.initialize_connection(context,volume_id,connector)if 'serial' not in connection_info:connection_info['serial'] = self.volume_idself._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_infoif 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、创建protalmodel_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" andCONF.libvirt.virt_type != "qemu")):msg = _("Volume sets block size, but the current ""libvirt hypervisor '%s' does not support custom ""block size") % CONF.libvirt.virt_typeraise 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) + 1conf = 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 ispersistent or not:param live: A bool to indicate whether it affect the guestin running state"""flags = persistent and libvirt.VIR_DOMAIN_AFFECT_CONFIG or 0flags |= 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提供的功能来卷挂载到虚拟机实例上去。

转载于:https://www.cnblogs.com/luohaixian/p/8134967.html

Cinder组件解析相关推荐

  1. 【SSM框架系列】Spring-MVC的组件解析

    SpringMVC完整执行流程 用户发送请求至前端控制器DispatcherServlet. DispatcherServlet收到请求调用HandlerMapping处理器映射器. 处理器映射器找到 ...

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

    Cinder 组件详解 - 每天5分钟玩转 OpenStack(47) 本节我们将详细讲解 Cinder 的各个子服务. cinder-api cinder-api 是整个 Cinder 组件的门户, ...

  3. element-plus 组件解析 - Collapse 折叠面板

    element-plus 组件解析 - Collapse 折叠面板 1, 组件介绍 2,组件组成 3,组件实现 3.1,el-collapse 1,v-model="activeNames& ...

  4. Cinder 组件详解

    理解 Block Storage 操作系统获得存储空间的方式一般有两种: 通过某种协议(SAS,SCSI,SAN,iSCSI 等)挂接裸硬盘,然后分区.格式化.创建文件系统:或者直接使用裸硬盘存储数据 ...

  5. 跨平台的.NET邮件协议MailKit组件解析

    发起的.NET Core开源组织号召,进展的速度是我自己也没有想到的,很多园友都积极参与(虽然有些人诚心砸场子,要是以我以前的宝脾气,这会应该被我打住院了吧,不过幸好是少数,做一件事总有人说好,也有人 ...

  6. 【转】开源的C# websocket-sharp组件解析

    下面我们介绍一款WebSocket组件websocket-sharp的相关内容. 一.websocket-sharp组件概述 websocket-sharp是一个C#实现websocket协议客户端和 ...

  7. OpenStack七大组件解析

    b站视频链接:0-尚硅谷-Linux云计算- 虚拟化技术 - 为何需要云计算这种"新事物"_哔哩哔哩_bilibili 视频.课件.资料: 百度网盘链接:https://pan.b ...

  8. 中国移动oneos框架基础及其组件解析

    <关键字> 中国移动oneos .开发环境 .开机自启动. shell .单元测试. 源码分析 1.oneos系统 1.1 开发手册 OneOS是中国移动针对物联网领域推出的轻量级操作系统 ...

  9. Rasa Core Policy策略组件解析

    RASA CORE Policy (策略组件) RASA NLU模块提供了用户消息中意图.槽位等信息,RASA DST模块提供了对话跟踪功能,记录了用户的历史消息,Policy要根据这些信息预测出,下 ...

最新文章

  1. 网易裁员事件,除了气愤,我们还该思考些什么?
  2. runloop - CFRunLoopObserverRef
  3. React开发(112):不要写多余的select
  4. linux.命令格式,【Linux基础知识】Linux命令格式介绍
  5. 分享两条关于Eclipse Perl插件EPIC的tips吧~
  6. 在水晶报表中插入子报表,并动态添加数据源
  7. atitit.javascript调用java in swt attilax 总结
  8. multiplot 安装与配置
  9. JSOI2007 文本生成器
  10. mac使用Java命令运行Java程序
  11. vue 微信公众号 前端开发
  12. Algorithm:数学建模大赛(CUMCM/NPMCM)之05A《长江水质综合评价与预测》
  13. 一维和二维傅里叶变换的图片直观理解
  14. 七周成为数据分析师 第一周:数据分析思维
  15. 在Unity Shader中实现漫反射光照模型(逐顶点漫反射光照、逐像素漫反射光照、半兰伯特光照)
  16. 【深度学习论文阅读】TCN:An Empirical Evaluation of Generic Convolutional and Recurrent Networks for Sequence
  17. S2-052的POC测试
  18. 上手评测:华为nova8和nova7Pro哪个好?区别是什么
  19. 波卡Polkadot
  20. windows查看php的端口,WINDOWS下常用的服务以及对应的端口 - Windows操作系

热门文章

  1. python怎么连接excel_python怎么连接excel
  2. jquery显示与隐藏效果
  3. DOM对象和jquery对象相互转换
  4. Go语言基础进阶—程序结构—命名
  5. 寻找无向图的关节点(Articulation Points)和判断图是否是双连通图(Biconnect Graph)
  6. bzoj 1056 1862: [Zjoi2006]GameZ游戏排名系统(Treap+Hash)
  7. matlab repmat 函数的使用
  8. [Python+sklearn] 拆分数据集为训练和测试子集 sklearn.model_selection.train_test_split()
  9. js系列教程5-数据结构和算法全解
  10. prometheus-operator架构部署( prometheus-server, pushgateway, grafana, alertmanater,servicemonitor...)