xm是xen提供的在dom0中管理domU命令,主要包括xm create ,xm shutdown,xm destroy,
xm migrate(迁移)等许多命令,全部都是python脚本语言实现的。这些命令是我们在在linux的终端中输入的命令,但是实际负责功能的执行却不是这些命令,而是xen的xend服务负责执行,xend服务同样也是python脚本实现的。
带着学习python的目的,决定对xen tools(xm与xend)的脚本代码进行分析,然而这部分的代码实在太多,最终选择了其中的一个——xm create命令进行了跟踪分析。
一、xm与xend之间的链接
刚刚开始分析之前需要了解至关重要的一点,xm create负责创建虚拟机,最终却由xend服务负责完成实际的创建工作,xm与xend之间是怎么联系起来的呢?这其中利用了python的xmlrpc机制,即远程过程调用。这个xmlrpc的其中原理,br不慎了解,但是使用起来却十分方便,只需要在服务端进行注册函数或者事实例(对象),客户但通过链接之后便可以直接调用。这在后面的代码分析中一看便知。
二、xm create
首先从最开始的部分开始,xm的源码:在终端运行vim /esr/sbin/xm可以看到xm代码如下:
-
#!/usr/bin/python
-
# -*- mode: python; -*-
-
import sys
-
-
from xen.xm import main
-
-
main.main(sys.argv)
可以看到xm的代码非常的少,我们需要关注的只第五行和第七行,第五行from xen.xm import main意思是从xen.xm这个包中导入main模块。第七行main.main(sys.argv)就是调用main这个模块的main函数。sys.argv是命令行参数列表。例如xm create winxp.cfg ,则sys.argv 就是
['/usr/sbin/xm', 'create', 'winxp.cfg']。
至于xen.xm目录的寻找,可以根据sys.path寻找。这里不多说,直接到/esr/lib64/python2.7/site-packages/xen目录,其中python2.7根据具体的python版本可能不同。在这个目录下可以看到如下内容:
__init__.py __init__.pyc lowlevel remus sv util web xend xm xsview
其中只有__init__.py和__init__.pyc是文件,其他均是目录,这里__init__.py 在这个目录,说明这个目录就是一个目录包,而__init_.pyc是由于import之后产生的,无关紧要。好了,直接就如到xm目录,xm目录下有很多.py文件,这些就是pthon脚本。找到我们需要的main.py,这就是我们在xm源码中看到的main。打开main.py,分析其中的main函数。
-
def main(argv=sys.argv):
-
if len(argv) < 2:
-
usage()
-
-
# intercept --help(-h) and output our own help
-
for help in ['--help', '-h']:
-
if help in argv[1:]:
-
if help == argv[1]:
-
longHelp()
-
sys.exit(0)
-
else:
-
usage(argv[1])
-
-
cmd_name = argv[1]
-
cmd = xm_lookup_cmd(cmd_name)
-
if cmd:
-
# strip off prog name and subcmd
-
args = argv[2:]
-
_, rc = _run_cmd(cmd, cmd_name, args)
-
sys.exit(rc)
-
else:
-
err('Subcommand %s not found!' % cmd_name)
-
usage()
直接从14行开始看cmd_name = argv[1],argv就是我们在xm源码中看到的sys.argv,应为argv[0]是脚本程序名,所有命令参数从argv[1]开始,对于xm create来说,cmd_name 当然就等于create了。下面的cmd = xm_lookup_cmd(cmd_name)这一句比较关键,xm_lookup_cmd的定义也在main.py文件中,
-
ef xm_lookup_cmd(cmd):
-
if commands.has_key(cmd):
-
return commands[cmd]
-
elif aliases.has_key(cmd):
-
deprecated(cmd,aliases[cmd])
-
return commands[aliases[cmd]]
-
elif cmd == 'help':
-
longHelp()
-
sys.exit(0)
-
else:
-
# simulate getopt
对于这个函数,对于xm create命令,我们只需要关注第一个if中的内容。观察main的源码,我么可以看到commands是一个字典,直接查看commands,我们似乎找不到create命令,但是,在main.py中还有这样的两句:
-
for c in IMPORTED_COMMANDS:
-
commands[c] = eval('lambda args: xm_importcommand("%s", args)' % c)
在IMPORTED_COMMANDS中是有create的,所以
commands['create'] = eval('lambda args :xm_importcommand("%s",args)' %c),
因c = create,所以
commands['create'] = eval('lambda args:xm_importcommand(“create”,args)')。既然事抱着学习python的目的,所以这句里面的两个python的知识点还是有必要回忆一下的。
首先事lambda,这里采用了lambda args: expression这种用法。其实lambda就相当与定义了一个函数,args就是参数,后面的expression就相当与函数体(必须是表达式)。这样理解这一句就相当与:
def f(args):
return xm_importcommand("create",args)
另一方面eval也是python中的一个知识点,对表达式求值。lambda不同函数,它是表达式。在具体的需要查看资料进行补充。这里我么只需要知道,以后可以这样使用:commands[c](args).而他实际上就是等价与xm_importcommand(c,args)
从xm_lookup_cmd返回,继续回到main。继续看第19行,这里有一个关于python中下滑线的知识点,详情参见:
http://blog.chinaunix.net/uid-29680694-id-4369071.html。先面我们进入到_run_cmd这个函数。先记住我们传进去的参数,对于xm create,cmd和cmd_name不用多说,而args=argv[2:](这里是深度复制)。所有,args就是我们的domU的配置文件。
-
def _run_cmd(cmd, cmd_name, args):
-
global server
-
-
try:
-
if server is None:
-
if serverType == SERVER_XEN_API:
-
server = XenAPI.Session(serverURI)
-
username, password = parseAuthentication()
-
server.login_with_password(username, password)
-
def logout():
-
try:
-
server.xenapi.session.logout()
-
except:
-
pass
-
atexit.register(logout)
-
else:
-
server = ServerProxy(serverURI)
-
-
return True, cmd(args)
-
以上的_run_cmd()的大部分代码已被去掉,这是因为后面的except代码对于我们分析没有关系。在这个函数的一开始 global server,用global表示这是一个全局变量。有必要先看看一看这个全局变量。由server有关的部分代码也在main.py中
-
##
-
# Configuration File Parsing
-
##
-
-
xmConfigFile = os.getenv(XM_CONFIG_FILE_ENVVAR, XM_CONFIG_FILE_DEFAULT)
-
config = None
-
if os.path.isfile(xmConfigFile):
-
try:
-
config = xml.dom.minidom.parse(xmConfigFile)
-
except:
-
print >>sys.stderr, ('Ignoring invalid configuration file %s.' %
-
xmConfigFile)
-
print config,'main.py'
-
def parseServer():
-
if config:
-
server = config.getElementsByTagName('server')
-
if server:
-
-
st = server[0].getAttribute('type')
-
if st != SERVER_XEN_API and st != SERVER_LEGACY_XMLRPC:
-
print >>sys.stderr, ('Invalid server type %s; using %s.' %
-
(st, SERVER_LEGACY_XMLRPC))
-
st = SERVER_LEGACY_XMLRPC
-
return (st, server[0].getAttribute('uri'))
-
-
return SERVER_LEGACY_XMLRPC, XendClient.uri
-
-
def parseAuthentication():
-
server = config.getElementsByTagName('server')[0]
-
return (server.getAttribute('username'),
-
server.getAttribute('password'))
-
-
serverType, serverURI = parseServer()
-
server = None
这里其实也看到了,最后一行server=None,所以,在_run_cmd()函数中见到的server就是None。但是为了得到serverType,serverURI,不得不多看一点。
首先看第五行,xmConfigureFile = os.getenv(),os也是python的一个系统库,它的的getenv函数是为了获得系统的环境变量值,通常我们是没有XM_CONFIG_FILE_ENVVAR这个环境变量的,所以xmConfigureFile就等于了XM_CONFIG_FILE_DEFAULT
这个默认值,即/etc/xen/ xm-config.xml
这个xml文件的内容很简单,下面就是其关键部分
-
<xm>
-
<server type='Xen-API'
-
uri=''
-
username='me'
-
password='mypassword' />
-
-->
-
</xm>
第九行是对这个文件的解析,调用了python的用于解析xml文件的模块xm.dom.minidom,具体br不太了解。通过parseServer函数,和xm-configre.xml,我猜测serverType是“Xen-Api”,但是最终打印结果事“LegacyXMLRPC”,这涉及到python的xml文件的解析,就部扯远了,这里我们只要知道serverType就行了,至于serverURI,其值是httpu:///var/run/xend/xmlrpc.sock。好了,这部分我们就得到了这两个三个结果,server、serverType和serverURI。
继续回到_run_cmd(),既然server=None并且serverType是“LegacyXMLRPC”,所以直接到第十七行,
server=ServerProxy(serverURI)。
这一行看起来好像很复杂,但其实我们只需要知道怎么使用的就行了。这是python中xmlrpc机制在client端的用法。首先客户端需要由一个这样的server对象,而这个server对象的创建就是通过ServerProxy(uri)来完成的,其中uri就是服务端的ip和端口。这样之后,客户端就可以通过server.xxx调用服务端注册过的函数了。
阅读(1355) | 评论(0) | 转发(0) |