【openstack】Nova(Folsom)虚拟化层Driver分析
【转载】Nova(Folsom)虚拟化层Driver分析
本文是我一个同事的一篇分析,征求了同事的意见,获取同意后,在此转载。
本文依据Openstack目前最新folsom版本中,从代码走读的角度,分析了Openstack异构不同hypervisor下virt部分的代码结构。同时,给出了当前不同hypervisor对于Openstack定义的通用接口的支持情况。
1.Folsom版本virt代码重构
两个版本相比,virt部分的逻辑结构更加清晰。下面是两个版本virt代码中libvirt的结构对比:
2.Driver类型
当前支持的虚拟化平台,主要有以下五种:
3. virt继承关系
在Folsom版本中,对应不同的hypervisor实现,所有插件类全部继承于一个基类/nova/virt/driver.py,文件中定义到了所有需要driver支持的接口(当然有少部分接口是选配的),文件中仅有接口定义。
各个driver的继承关系如下:
4. ComputeDriver基类
接下来,我们来看下/nova/virt/driver.py中的全部接口定义,它是整个virt部分的基础。详见文档中第一部分~
5. 基类接口在不同hypervisor下的支持情况
上面那份表格第二部分,罗列出了不同hypervisor对基类中全部接口的支持情况。从对比中可以看出,Openstack对于libvirt的支持最好,XenAPI也基本都覆盖到了;这也和Openstack设计时候的出发点有关。另外,对于VMWareAPI、Hyper-V,一些基本的功能也大致覆盖到了,但接口总体支持情况仅能算一般。
接下来,我们分别看一下不同hypervisor的Driver情况~
6. 创vm中调用virt
之前我在分析《创建vm》文档中写到过,经过调度的消息在compute模块中,会走到/nova/compute/manager.py中ComputeManager的__init__()方法。方法继续走到nova.virt.connection.get_connection处:

- driver_name=known_drivers.get(FLAGS.connection_type)
- ifdriver_nameisNone:
- raiseexception.VirtDriverNotFound(name=FLAGS.connection_type)
- conn=importutils.import_object_ns('nova.virt',driver_name,
- read_only=read_only)
- ifconnisNone:
- LOG.error(_('Failedtoopenconnectiontounderlyingvirtplatform'))
- sys.exit(1)
- returnutils.check_isinstance(conn,driver.ComputeDriver)
原有essex版本中的基类connections.py,目前在folsom版本中已被简化,仅用来读取配置文件中的hypervisor类型,如:
并加载一个工具类/nova/openstack/common/importutils.py,来返回一个nova.virt.libvirt类。如下:
7. libvirt Driver
首先看一下libvirt的包结构,结构很简单,核心的方法都放在/nova/virt/libvirt/driver.py中,比起此前essex版本中放到connection.py的做法结构更加清晰了:
7.1 有关libvirt
目前libvirt下,一共支持kvm、lxc、qemu、uml、xen五种具体的libvirt_type,默认选用kvm。
虚拟机的状态映射如下:
VIR内部状态 | 对外呈现状态 | 说明 |
VIR_DOMAIN_NOSTATE | NOSTATE | |
VIR_DOMAIN_RUNNING | RUNNING | |
VIR_DOMAIN_BLOCKED | RUNNING | 仅在Xen中存在Blocked状态 |
VIR_DOMAIN_PAUSED | PAUSED | |
VIR_DOMAIN_SHUTDOWN | SHUTDOWN | libvirt API文档中已指出,此时vm可能仍旧处于Running状态 |
VIR_DOMAIN_SHUTOFF | SHUTDOWN | |
VIR_DOMAIN_CRASHED | CRASHED | |
VIR_DOMAIN_PMSUSPENDED | SUSPENDED |
7.2 libvirt连接hypervisor
接下来开始看代码。整个libvrit的driver.py文件非常长(3000+行python)。。。初始化部分,首先调用基类ComputerDriver类中的__init__(),之后主要由配置文件读取了一些基本的配置项。
libvrit接口由_conn = property(_get_connection)来连接底层的hypervisor。而实际的连接工作,由内部的一个静态方法_connect()进行调用,一种是只读方式,另一种是拥有读写权限的,后一种需要用root身份登录。代码如下:

- @staticmethod
- def_connect(uri,read_only):
- def_connect_auth_cb(creds,opaque):
- iflen(creds)==0:
- return0
- LOG.warning(
- _("Cannothandleauthenticationrequestfor%dcredentials")
- %len(creds))
- raiseexception.NovaException(
- _("Cannothandleauthenticationrequestfor%dcredentials")
- %len(creds))
- auth=[[libvirt.VIR_CRED_AUTHNAME,
- libvirt.VIR_CRED_ECHOPROMPT,
- libvirt.VIR_CRED_REALM,
- libvirt.VIR_CRED_PASSPHRASE,
- libvirt.VIR_CRED_NOECHOPROMPT,
- libvirt.VIR_CRED_EXTERNAL],
- _connect_auth_cb,
- None]
- ifread_only:
- returnlibvirt.openReadOnly(uri)
- else:
- returnlibvirt.openAuth(uri,auth,0)
7.3 libvirt创建vm实例
我们接着来看下spawn()方法,这个是libvirt对外的创建vm接口。传入参数,都是由Nova处理过的虚拟机、镜像、网络、安全等方面的参数。

- @exception.wrap_exception()
- defspawn(self,context,instance,image_meta,injected_files,
- admin_password,network_info=None,block_device_info=None):
- xml=self.to_xml(instance,network_info,image_meta,
- block_device_info=block_device_info)
- self._create_image(context,instance,xml,network_info=network_info,
- block_device_info=block_device_info,
- files=injected_files,
- admin_pass=admin_password)
- self._create_domain_and_network(xml,instance,network_info,
- block_device_info)
- LOG.debug(_("Instanceisrunning"),instance=instance)
- def_wait_for_boot():
- """CalledatanintervaluntiltheVMisrunning."""
- state=self.get_info(instance)['state']
- ifstate==power_state.RUNNING:
- LOG.info(_("Instancespawnedsuccessfully."),
- instance=instance)
- raiseutils.LoopingCallDone()
- timer=utils.LoopingCall(_wait_for_boot)
- timer.start(interval=0.5).wait()
方法入口处的self.to_xml()方法,把之前传入的虚拟机、网络、镜像等信息,转化为创建vm时使用的xml描述文件。这个方法中,针对不同libvrit_type(xen/kvm/qemu/…)做了大量的分别处理。方法最终走到config.py中,调用基类LibvirtConfigObject的to_xml()方法,生成描述文件,代码如下:

- defto_xml(self,pretty_print=True):
- root=self.format_dom()
- xml_str=etree.tostring(root,pretty_print=pretty_print)
- LOG.debug("GeneratedXML%s"%(xml_str,))
- returnxml_str
回到spawn()中,_create_image()方法略过。接下来看下_create_domain_and_network(),用来设置卷映射、构建网络参数,最后创建vm实例。以下是方法的简明分析:
其中,在底层创建vm的动作,是在_create_domain()中完成的。它根据上一步生成好的xml的内容,定义一个虚拟机,相当于创建了一个virDomain对象,注意这时还没有启动这个虚拟机。
另外,其他需要vm的动作都是通过组装+调用该方法实现的。

- def_create_domain(self,xml=None,domain=None,launch_flags=0):
- """Createadomain.
- Eitherdomainorxmlmustbepassedin.Ifbotharepassed,then
- thedomaindefinitionisoverwrittenfromthexml.
- """
- ifxml:
- domain=self._conn.defineXML(xml)
- domain.createWithFlags(launch_flags)
- self._enable_hairpin(domain.XMLDesc(0))
- returndomain
其余libvirt API分析,因时间关系暂略。
8. XenAPI Driver
还是先看下XenAPI驱动部分的包结构。和libvirt的结构基本相同。
XenAPI Driver部分,一共分两个类,包括连接XenServer或者Xen Cloud的XenAPIDriver部分,以及一个调用XenAPI的XenAPISession类。
连接的初始化部分如下,其中,所有Xen相关的配置,和其他配置项一起保存在nova.conf中,通过之前《创建vm》文档中提到的flags.py来读取。配置项太长了,这里就不贴了。

- classXenAPIDriver(driver.ComputeDriver):
- """AconnectiontoXenServerorXenCloudPlatform"""
- def__init__(self,read_only=False):
- super(XenAPIDriver,self).__init__()
- url=FLAGS.xenapi_connection_url
- username=FLAGS.xenapi_connection_username
- password=FLAGS.xenapi_connection_password
- ifnoturlorpasswordisNone:
- raiseException(_('Mustspecifyxenapi_connection_url,'
- 'xenapi_connection_username(optionally),and'
- 'xenapi_connection_passwordtouse'
- 'compute_driver=xenapi.XenAPIDriver'))
- self._session=XenAPISession(url,username,password)
- self._volumeops=volumeops.VolumeOps(self._session)
- self._host_state=None
- self._host=host.Host(self._session)
- self._vmops=vmops.VMOps(self._session)
- self._initiator=None
- self._hypervisor_hostname=None
- self._pool=pool.ResourcePool(self._session)
之后对于Xen主机、卷、资源池的操作,都是通过上面初始化了的对象,通过XenAPISession发送消息。不细写了~
9. VMWareApi Driver
目前,Openstack对于VMWare虚拟化的支持,还是主要集中于对于ESX的异构,暂时还无法覆盖到vCenter的层面。
依旧先来看下VMwareAPI驱动的包结构:
还是先看下driver.py,文件开头的配置部分列出了连接ESX所需的一些基础配置项,包括主机地址、用户名、密码等常规内容。这些都可以在nova.conf中进行配置。如下:

- vmwareapi_opts=[
- cfg.StrOpt('vmwareapi_host_ip',
- default=None,
- help='URLforconnectiontoVMWareESXhost.Requiredif'
- 'compute_driverisvmwareapi.VMWareESXDriver.'),
- cfg.StrOpt('vmwareapi_host_username',
- default=None,
- help='UsernameforconnectiontoVMWareESXhost.'
- 'Usedonlyifcompute_driveris'
- 'vmwareapi.VMWareESXDriver.'),
- cfg.StrOpt('vmwareapi_host_password',
- default=None,
- help='PasswordforconnectiontoVMWareESXhost.'
- 'Usedonlyifcompute_driveris'
- 'vmwareapi.VMWareESXDriver.'),
- cfg.FloatOpt('vmwareapi_task_poll_interval',
- default=5.0,
- help='Theintervalusedforpollingofremotetasks.'
- 'Usedonlyifcompute_driveris'
- 'vmwareapi.VMWareESXDriver.'),
- cfg.IntOpt('vmwareapi_api_retry_count',
- default=10,
- help='Thenumberoftimesweretryonfailures,e.g.,'
- 'socketerror,etc.'
- 'Usedonlyifcompute_driveris'
- 'vmwareapi.VMWareESXDriver.'),
- cfg.StrOpt('vmwareapi_vlan_interface',
- default='vmnic0',
- help='Physicalethernetadapternameforvlannetworking'),
- ]
- FLAGS=flags.FLAGS
- FLAGS.register_opts(vmwareapi_opts)
以下是配置文件的sample,因未配置所以这几项目前是None:
继续看driver.py的代码,可以发现VMWare Driver部分的代码结构,与XenAPI的非常相似。都是有两个类,包括一个作为ESX主机连接对象的VMWareESXDriver部分+一个用于与ESX主机建立连接并处理全部请求调用的VMWareAPISession类。代码不细写了~
10. Hyper-V Driver
在essex版本规划中,本来计划要删掉的Hyper-V driver,由于微软积极投入开发人员更新代码,反而把功能补齐了。这里是Hyper-V driver的包结构:
Hyper-V的Driver支持,主要是使用一个基于python的Windows Management Instrumentation (WMI)来实现。这里有微软关于WMI的介绍:http://msdn.microsoft.com/en-us/library/cc723875%28v=VS.85%29.aspx。
主要处理类都在HyperVDriver的初始化方法中定义了:

- classHyperVDriver(driver.ComputeDriver):
- def__init__(self):
- super(HyperVDriver,self).__init__()
- self._volumeops=volumeops.VolumeOps()
- self._vmops=vmops.VMOps(self._volumeops)
- self._snapshotops=snapshotops.SnapshotOps()
- self._livemigrationops=livemigrationops.LiveMigrationOps(
- self._volumeops)
其他基本相同,过程略。