mirror of
https://github.com/xen-project/xen.git
synced 2026-10-29 10:40:03 +01:00
438 lines
16 KiB
Python
Executable File
438 lines
16 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
import string, sys, os, time, socket, getopt, signal, syslog
|
|
import Xc, xenctl.utils, xenctl.console_client, re
|
|
|
|
config_dir = '/etc/xc/'
|
|
config_file = xc_config_file = config_dir + 'defaults'
|
|
|
|
def main_usage ():
|
|
print >>sys.stderr,"""
|
|
Usage: %s <args>
|
|
|
|
This tool is used to create and start new domains. It reads defaults
|
|
from a file written in Python, having allowed variables to be set and
|
|
passed into the file. Further command line arguments allow the
|
|
defaults to be overridden. The defaults for each parameter are listed
|
|
in [] brackets. Arguments are as follows:
|
|
|
|
Arguments to control the parsing of the defaults file:
|
|
-f config_file -- Use the specified defaults script.
|
|
Default: ['%s']
|
|
-L state_file -- Load virtual machine memory state from state_file
|
|
-D foo=bar -- Set variable foo=bar before parsing config
|
|
E.g. '-D vmid=3;ip=1.2.3.4'
|
|
-h -- Print extended help message, including all arguments
|
|
-n -- Dry run only, don't actually create domain
|
|
-q -- Quiet - write output only to the system log
|
|
-s -- Don't start the domain, just build it.
|
|
""" % (sys.argv[0], xc_config_file)
|
|
|
|
def extra_usage ():
|
|
print >>sys.stderr,"""
|
|
Arguments to override current config read from '%s':
|
|
-c -- Turn into console terminal after domain is created
|
|
-k image -- Path to kernel image ['%s']
|
|
-r ramdisk -- Path to ramdisk (or empty) ['%s']
|
|
-b builder_fn -- Function to use to build domain ['%s']
|
|
-m mem_size -- Initial memory allocation in MB [%dMB]
|
|
-N domain_name -- Set textual name of domain ['%s']
|
|
-a auto_restart -- Restart domain on exit, yes/no ['%d']
|
|
-e vbd_expert -- Saftey catch to avoid some disk accidents ['%d']
|
|
-d udisk,dev,rw -- Add disk, partition, or virtual disk to domain. E.g. to
|
|
make partion sda4 available to the domain as hda1 with
|
|
read-write access: '-d phy:sda4,hda1,rw' To add
|
|
multiple disks use multiple -d flags or seperate with ';'
|
|
Default: ['%s']
|
|
-i vfr_ipaddr -- Add IP address to the list which Xen will route to
|
|
the domain. Use multiple times to add more IP addrs.
|
|
Default: ['%s']
|
|
|
|
Args to override the kernel command line, which is concatenated from these:
|
|
-I cmdline_ip -- Override 'ip=ipaddr:nfsserv:gateway:netmask::eth0:off'
|
|
Default: ['%s']
|
|
-R cmdline_root -- Override root device parameters.
|
|
Default: ['%s']
|
|
-E cmdline_extra -- Override extra kernel args and rc script env vars.
|
|
Default: ['%s']
|
|
|
|
""" % (config_file,
|
|
image, ramdisk, builder_fn, mem_size, domain_name, auto_restart,
|
|
vbd_expert,
|
|
printvbds( vbd_list ),
|
|
reduce ( (lambda a,b: a+':'+b), vfr_ipaddr,'' )[1:],
|
|
cmdline_ip, cmdline_root, cmdline_extra)
|
|
|
|
def config_usage (): pass
|
|
|
|
def answer ( s ):
|
|
s = string.lower(s)
|
|
if s == 'yes' or s == 'true' or s == '1': return 1
|
|
return 0
|
|
|
|
def printvbds ( v ):
|
|
s=''
|
|
for (a,b,c) in v:
|
|
s = s + '; %s,%s,%s' % (a,b,c)
|
|
return s[2:]
|
|
|
|
def output(string):
|
|
global quiet
|
|
syslog.syslog(string)
|
|
if not quiet:
|
|
print string
|
|
return
|
|
|
|
bail=False; dryrun=False; extrahelp=False; quiet = False
|
|
image=''; ramdisk=''; builder_fn=''; restore=0; state_file=''
|
|
mem_size=0; domain_name=''; vfr_ipaddr=[];
|
|
vbd_expert=0; auto_restart=False;
|
|
vbd_list = []; cmdline_ip = ''; cmdline_root=''; cmdline_extra=''
|
|
pci_device_list = []; console_port = -1
|
|
auto_console = False
|
|
dontstart = False
|
|
flags = 0
|
|
|
|
##### Determine location of defaults file
|
|
#####
|
|
|
|
try:
|
|
opts, args = getopt.getopt(sys.argv[1:], "h?nqcsf:D:k:r:b:m:N:a:e:d:i:I:R:E:L:" )
|
|
|
|
for opt in opts:
|
|
if opt[0] == '-f': config_file= opt[1]
|
|
if opt[0] == '-h' or opt[0] == '-?' : bail=True; extrahelp=True
|
|
if opt[0] == '-n': dryrun=True
|
|
if opt[0] == '-D':
|
|
for o in string.split( opt[1], ';' ):
|
|
(l,r) = string.split( o, '=' )
|
|
exec "%s='%s'" % (l,r)
|
|
if opt[0] == '-q': quiet = True
|
|
if opt[0] == '-L': restore = True; state_file = opt[1]
|
|
if opt[0] == '-s': dontstart = True
|
|
|
|
|
|
except getopt.GetoptError:
|
|
bail=True
|
|
|
|
|
|
try:
|
|
os.stat( config_file )
|
|
except:
|
|
try:
|
|
d = config_dir + config_file
|
|
os.stat( d )
|
|
config_file = d
|
|
except:
|
|
print >> sys.stderr, "Unable to open config file '%s'" % config_file
|
|
bail = True
|
|
|
|
|
|
##### Parse the config file
|
|
#####
|
|
|
|
if not quiet:
|
|
print "Parsing config file '%s'" % config_file
|
|
|
|
try:
|
|
execfile ( config_file )
|
|
except (AssertionError,IOError):
|
|
print >>sys.stderr,"Exiting %s" % sys.argv[0]
|
|
bail = True
|
|
|
|
##### Print out config if necessary
|
|
#####
|
|
|
|
if bail:
|
|
main_usage()
|
|
config_usage()
|
|
if extrahelp: extra_usage()
|
|
sys.exit(1)
|
|
|
|
##### Parse any command line overrides
|
|
#####
|
|
|
|
x_vbd_list = []
|
|
x_vfr_ipaddr = []
|
|
|
|
for opt in opts:
|
|
if opt[0] == '-k': image = opt[1]
|
|
if opt[0] == '-r': ramdisk = opt[1]
|
|
if opt[0] == '-b': builder_fn = opt[1]
|
|
if opt[0] == '-m': mem_size = int(opt[1])
|
|
if opt[0] == '-C': cpu = int(opt[1])
|
|
if opt[0] == '-N': domain_name = opt[1]
|
|
if opt[0] == '-a': auto_restart = answer(opt[1])
|
|
if opt[0] == '-e': vbd_expert = answer(opt[1])
|
|
if opt[0] == '-I': cmdline_ip = opt[1]
|
|
if opt[0] == '-R': cmdline_root = opt[1]
|
|
if opt[0] == '-E': cmdline_extra = opt[1]
|
|
if opt[0] == '-i': x_vfr_ipaddr.append(opt[1])
|
|
if opt[0] == '-c': auto_console = True
|
|
if opt[0] == '-d':
|
|
try:
|
|
vv = string.split(opt[1],';')
|
|
for v in vv:
|
|
(udisk,dev,mode) = string.split(v,',')
|
|
x_vbd_list.append( (udisk,dev,mode) )
|
|
except:
|
|
print >>sys.stderr, "Invalid block device specification : %s" % opt[1]
|
|
sys.exit(1)
|
|
|
|
if x_vbd_list: vbd_list = x_vbd_list
|
|
if x_vfr_ipaddr: vfr_ipaddr = x_vfr_ipaddr
|
|
|
|
cmdline = cmdline_ip +' '+ cmdline_root +' '+ cmdline_extra
|
|
|
|
syslog.openlog('xc_dom_create.py %s' % config_file, 0, syslog.LOG_DAEMON)
|
|
|
|
##### Print some debug info just in case things don't work out...
|
|
#####
|
|
|
|
output('VM image : "%s"' % image)
|
|
output('VM ramdisk : "%s"' % ramdisk)
|
|
output('VM memory (MB) : "%d"' % mem_size)
|
|
output('VM IP address(es) : "%s"'
|
|
% reduce((lambda a,b: a+'; '+b),vfr_ipaddr,'' )[2:])
|
|
output('VM block device(s) : "%s"' % printvbds( vbd_list ))
|
|
output('VM cmdline : "%s"' % cmdline)
|
|
|
|
if dryrun:
|
|
sys.exit(1)
|
|
|
|
##### Code beyond this point is actually used to manage the mechanics of
|
|
##### starting (and watching if necessary) guest virtual machines.
|
|
|
|
# Obtain an instance of the Xen control interface
|
|
xc = Xc.new()
|
|
|
|
# This function creates, builds and starts a domain, using the values
|
|
# in the global variables, set above. It is used in the subsequent
|
|
# code for starting the new domain and rebooting it if appropriate.
|
|
def make_domain():
|
|
"""Create, build and start a domain.
|
|
Returns: [int] the ID of the new domain.
|
|
"""
|
|
|
|
# set up access to the global variables declared above
|
|
global image, ramdisk, mem_size, cpu, domain_name, vfr_ipaddr, netmask
|
|
global vbd_list, cmdline, xc, vbd_expert, builder_fn
|
|
|
|
if not os.path.isfile( image ):
|
|
print "Image file '" + image + "' does not exist"
|
|
sys.exit()
|
|
|
|
if ramdisk and not os.path.isfile( ramdisk ):
|
|
print "Ramdisk file '" + ramdisk + "' does not exist"
|
|
sys.exit()
|
|
|
|
id = xc.domain_create( mem_kb=mem_size*1024, name=domain_name, cpu=cpu )
|
|
if id <= 0:
|
|
print "Error creating domain"
|
|
sys.exit()
|
|
|
|
cmsg = 'new_control_interface(dom='+str(id)+', console_port='+str(console_port)+')'
|
|
|
|
cons_response = xenctl.utils.xend_control_message(cmsg)
|
|
|
|
if not cons_response['success']:
|
|
print "Error creating initial event channel"
|
|
print "Error type: " + cons_response['error_type']
|
|
if cons_response['error_type'] == 'exception':
|
|
print "Exception type: " + cons_response['exception_type']
|
|
print "Exception value: " + cons_response['exception_value']
|
|
xc.domain_destroy ( dom=id )
|
|
sys.exit()
|
|
|
|
if restore:
|
|
ret = eval('xc.%s_restore ( dom=id, state_file=state_file, progress=1)' % (builder_fn) )
|
|
if ret < 0:
|
|
print "Error restoring domain"
|
|
print "Return code = " + str(ret)
|
|
xc.domain_destroy ( dom=id )
|
|
sys.exit()
|
|
else:
|
|
|
|
ret = eval('xc.%s_build ( dom=id, image=image, ramdisk=ramdisk, cmdline=cmdline, control_evtchn=cons_response["remote_port"], flags=flags )' % builder_fn )
|
|
if ret < 0:
|
|
print "Error building Linux guest OS: "
|
|
print "Return code = " + str(ret)
|
|
xc.domain_destroy ( dom=id )
|
|
sys.exit()
|
|
|
|
# setup the virtual block devices
|
|
|
|
# set the expertise level appropriately
|
|
xenctl.utils.VBD_EXPERT_MODE = vbd_expert
|
|
|
|
if not (flags & 1<<4): # It's not a block backend (or it's old IO world)
|
|
cmsg = 'new_block_interface(dom='+str(id)+')'
|
|
xend_response = xenctl.utils.xend_control_message(cmsg)
|
|
if not xend_response['success']:
|
|
print "Error creating block interface"
|
|
print "Error type: " + xend_response['error_type']
|
|
if xend_response['error_type'] == 'exception':
|
|
print "Exception type: " + xend_response['exception_type']
|
|
print "Exception val: " + xend_response['exception_value']
|
|
xc.domain_destroy ( dom=id )
|
|
sys.exit()
|
|
|
|
for ( uname, virt_name, rw ) in vbd_list:
|
|
virt_dev = xenctl.utils.blkdev_name_to_number( virt_name )
|
|
|
|
segments = xenctl.utils.lookup_disk_uname( uname )
|
|
if not segments:
|
|
print "Error looking up %s\n" % uname
|
|
xc.domain_destroy ( dom=id )
|
|
sys.exit()
|
|
|
|
if len(segments) > 1:
|
|
print "New I/O world cannot deal with multi-extent vdisks"
|
|
xc.domain_destroy ( dom=id )
|
|
sys.exit()
|
|
seg = segments[0]
|
|
cmsg = 'new_block_device(dom=' + str(id) + \
|
|
',handle=0,vdev=' + str(virt_dev) + \
|
|
',pdev=' + str(seg['device']) + \
|
|
',start_sect=' + str(seg['start_sector']) + \
|
|
',nr_sect=' + str(seg['nr_sectors']) + \
|
|
',readonly=' + str(not re.match('w',rw)) + ')'
|
|
xend_response = xenctl.utils.xend_control_message(cmsg)
|
|
if not xend_response['success']:
|
|
print "Error creating virtual block device"
|
|
print "Error type: " + xend_response['error_type']
|
|
if xend_response['error_type'] == 'exception':
|
|
print "Exception type: " + xend_response['exception_type']
|
|
print "Exception val: " + xend_response['exception_value']
|
|
xc.domain_destroy ( dom=id )
|
|
sys.exit()
|
|
else: # It's a block backend - notify Xend.
|
|
cmsg = 'set_block_backend(dom='+str(id)+')'
|
|
xend_response = xenctl.utils.xend_control_message(cmsg)
|
|
if not xend_response['success']:
|
|
print "Error registering network backend"
|
|
print "Error type: " + xend_response['error_type']
|
|
if xend_response['error_type'] == 'exception':
|
|
print "Exception type: " + xend_response['exception_type']
|
|
print "Exception val: " + xend_response['exception_value']
|
|
xc.domain_destroy ( dom=id )
|
|
sys.exit()
|
|
|
|
if not (flags & 1<<5): # If it's not the net backend, give it a frontend.
|
|
cmsg = 'new_network_interface(dom='+str(id)+')'
|
|
xend_response = xenctl.utils.xend_control_message(cmsg)
|
|
if not xend_response['success']:
|
|
print "Error creating network interface"
|
|
print "Error type: " + xend_response['error_type']
|
|
if xend_response['error_type'] == 'exception':
|
|
print "Exception type: " + xend_response['exception_type']
|
|
print "Exception val: " + xend_response['exception_value']
|
|
xc.domain_destroy ( dom=id )
|
|
sys.exit()
|
|
else: # It's a new net backend - notify Xend.
|
|
cmsg = 'set_network_backend(dom='+str(id)+')'
|
|
xend_response = xenctl.utils.xend_control_message(cmsg)
|
|
if not xend_response['success']:
|
|
print "Error registering network backend"
|
|
print "Error type: " + xend_response['error_type']
|
|
if xend_response['error_type'] == 'exception':
|
|
print "Exception type: " + xend_response['exception_type']
|
|
print "Exception val: " + xend_response['exception_value']
|
|
xc.domain_destroy ( dom=id )
|
|
sys.exit()
|
|
|
|
# check for physical device access
|
|
for (pci_bus, pci_dev, pci_func) in pci_device_list:
|
|
if xc.physdev_pci_access_modify(
|
|
dom=id, bus=pci_bus, dev=pci_dev,
|
|
func=pci_func, enable=1 ) < 0:
|
|
print "Non-fatal error enabling PCI device access."
|
|
else:
|
|
print "Enabled PCI access (%d:%d:%d)." % \
|
|
(pci_bus,pci_dev,pci_func)
|
|
|
|
if restore:
|
|
# send an unsolicited ARP reply for all non link-local IPs
|
|
gw=xenctl.utils.get_current_ipgw()
|
|
if gw == '': gw='255.255.255.255'
|
|
nlb=open('/proc/sys/net/ipv4/ip_nonlocal_bind','r').read()[0]=='1'
|
|
if not nlb: print >>open('/proc/sys/net/ipv4/ip_nonlocal_bind','w'), '1'
|
|
for ip in vfr_ipaddr:
|
|
if not xenctl.utils.check_subnet(ip,'169.254.0.0','255.255.0.0'):
|
|
print '/usr/sbin/arping -A -b -I eth0 -c 1 -s %s %s' % (ip,gw)
|
|
os.system('/usr/sbin/arping -A -b -I eth0 -c 1 -s %s %s' % (ip,gw))
|
|
if not nlb: print >>open('/proc/sys/net/ipv4/ip_nonlocal_bind','w'), '0'
|
|
|
|
if not dontstart:
|
|
if xc.domain_start( dom=id ) < 0:
|
|
print "Error starting domain"
|
|
xc.domain_destroy ( dom=id )
|
|
sys.exit()
|
|
|
|
return (id, cons_response['console_port'])
|
|
# end of make_domain()
|
|
|
|
def mkpidfile():
|
|
global current_id
|
|
if not os.path.isdir('/var/run/xendomains/'):
|
|
os.mkdir('/var/run/xendomains/')
|
|
|
|
fd = open('/var/run/xendomains/%d.pid' % current_id, 'w')
|
|
print >> fd, str(os.getpid())
|
|
fd.close()
|
|
return
|
|
|
|
def rmpidfile():
|
|
global current_id
|
|
os.unlink('/var/run/xendomains/%d.pid' % current_id)
|
|
|
|
def death_handler(dummy1,dummy2):
|
|
global current_id
|
|
os.unlink('/var/run/xendomains/%d.pid' % current_id)
|
|
output('Auto-restart daemon: daemon PID = %d for domain %d is now exiting'
|
|
% (os.getpid(),current_id))
|
|
sys.exit(0)
|
|
return
|
|
|
|
# The starting / monitoring of the domain actually happens here...
|
|
|
|
# start the domain and record its ID number
|
|
(current_id, current_port) = make_domain()
|
|
output("VM started in domain %d. Console I/O available on TCP port %d." % (current_id,current_port))
|
|
|
|
if auto_console:
|
|
xenctl.console_client.connect('127.0.0.1',int(current_port))
|
|
|
|
# if the auto_restart flag is set then keep polling to see if the domain is
|
|
# alive - restart if it is not by calling make_domain() again (it's necessary
|
|
# to update the id variable, since the new domain may have a new ID)
|
|
|
|
if auto_restart:
|
|
# turn ourselves into a background daemon
|
|
try:
|
|
pid = os.fork()
|
|
if pid > 0:
|
|
sys.exit(0)
|
|
os.setsid()
|
|
pid = os.fork()
|
|
if pid > 0:
|
|
output('Auto-restart daemon PID = %d' % pid)
|
|
sys.exit(0)
|
|
signal.signal(signal.SIGTERM,death_handler)
|
|
except OSError:
|
|
print >> sys.stderr, 'Problem starting auto-restart daemon'
|
|
sys.exit(1)
|
|
|
|
mkpidfile()
|
|
|
|
while True:
|
|
time.sleep(1)
|
|
info = xc.domain_getinfo(current_id, 1)
|
|
if info == [] or info[0]['dom'] != current_id:
|
|
output("Auto-restart daemon: Domain %d has terminated, restarting VM in new domain"
|
|
% current_id)
|
|
rmpidfile()
|
|
(current_id, current_port) = make_domain()
|
|
mkpidfile()
|
|
output("Auto-restart daemon: VM restarted in domain %d. Console on port %d." % (current_id,current_port))
|