I just finished testing my enhancements to xenguest-install.py that
let me specify multiple volumes and multiple NICs to export to the
new domU. Seems to work ok for me, though I'm only testing it on the
paravirtualized server. In case anybody else would find such a thing
useful, I've attached it here.
(Note that I gave up on the default bridging script. It just didn't
make sense to me when I wanted more than a single NIC. Instead, I
used the logic on http://wiki.xensource.com/xenwiki/XenNetworkingSuse?
highlight=%28XenNetworking%29 pretty much verbatim.)
#!/usr/bin/python
#
# Quick and dirty script to set up a Xen guest and kick off an install
#
# Copyright 2005-2006 Red Hat, Inc.
# Jeremy Katz <katzj@xxxxxxxxxx>
# Option handling added by Andrew Puch <apuch@xxxxxxxxxx>
#
# This software may be freely redistributed under the terms of the GNU
# general public license.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
import os, sys, time, stat
import subprocess
import tempfile
import urlgrabber.grabber as grabber
import random
from optparse import OptionParser
XENCONFIGPATH="/etc/xen/"
# stolen directly from xend/server/netif.py
def randomMAC():
"""Generate a random MAC address.
Uses OUI (Organizationally Unique Identifier) 00-16-3E, allocated to
Xensource, Inc. The OUI list is available at
http://standards.ieee.org/regauth/oui/oui.txt.
The remaining 3 fields are random, with the first bit of the first
random field set 0.
@return: MAC address string
"""
mac = [ 0x00, 0x16, 0x3e,
random.randint(0x00, 0x7f),
random.randint(0x00, 0xff),
random.randint(0x00, 0xff) ]
return ':'.join(map(lambda x: "%02x" % x, mac))
def yes_or_no(s):
s = s.lower()
if s in ("y", "yes", "1", "true", "t"):
return True
elif s in ("n", "no", "0", "false", "f"):
return False
raise ValueError, "A yes or no response is required"
def get_name(options):
name = options.name
while not name:
print "What is the name of your virtual machine? ",
name = sys.stdin.readline().strip()
return name
def get_ram(options):
ram = options.memory
while not ram or ram < 256:
if ram and ram < 256:
print "ERROR: Installs currently require 256 megs of RAM."
print ""
print "How much RAM should be allocated (in megabytes)? ",
ram = sys.stdin.readline().strip()
ram = int(ram)
return ram
def get_disk_size(whichDisk):
print "\t\tHow large would you like " + whichDisk + " to be (in gigabytes)? ",
disksize = sys.stdin.readline().strip()
size = float(disksize)
return size
def get_disks(options):
diskInput = options.diskfile
while not diskInput:
print "Which whitespace-seperated path(s) would you like to use for a disk?: ",
diskInput = sys.stdin.readline().strip()
disks = diskInput.split()
for disk in disks:
print "\tChecking " + disk + "..."
if not os.path.exists(disk):
size = get_disk_size(disk)
# FIXME: should we create the disk here?
fd = os.open(disk, os.O_WRONLY | os.O_CREAT)
off = long(size * 1024L * 1024L * 1024L)
os.lseek(fd, off, 0)
os.write(fd, '\x00')
os.close(fd)
return disks
def get_macs(options):
bridgesInput = options.bridges
while not bridgesInput:
print "Which whitespace-seperated bridge(s) would you like to use for networking?: ",
bridgesInput = sys.stdin.readline().strip()
bridges = bridgesInput.split()
macs = []
for bridge in bridges:
mac = randomMAC()
macs += ["mac=" + mac + ",bridge=" + bridge]
print "\tConfiguring " + bridge + " to use MAC " + mac
return macs
def get_full_virt(options):
if options.fullvirt is not None:
return options.fullvirt
while 1:
print "Would you like a fully virtualized guest (yes or no)? This will allow you "
print " to run unmodified operating systems."
res = sys.stdin.readline().strip()
try:
return yes_or_no(res)
except ValueError, e:
print e
def get_virt_cdboot(options):
if options.cdrom:
return True
while 1:
print "Would you like to boot the guest from a virtual CD?"
res = sys.stdin.readline().strip()
try:
return yes_or_no(res)
except ValueError, e:
print e
def get_virt_cdrom(options):
cdrom = options.cdrom
while not cdrom:
print "What would you like to use for the virtual CD image?"
cdrom = sys.stdin.readline().strip()
return cdrom
def start_hvm_guest(name, ram, disks, macs, cdrom):
if os.uname()[4] in ("x86_64"):
qemu = "/usr/lib64/xen/bin/qemu-dm"
else:
qemu = "/usr/lib/xen/bin/qemu-dm"
if os.environ.has_key("DISPLAY"):
hasX = True
else:
hasX = False
diskTypes = {}
for disk in disks:
if stat.S_ISBLK(os.stat(disk)[stat.ST_MODE]):
diskTypes[disk] = "phy"
else:
diskTypes[disk] = "file"
if stat.S_ISBLK(os.stat(disk)[stat.ST_MODE]):
type = "phy"
else:
type = "file"
cfg = "%s%s" %(XENCONFIGPATH, name)
f = open(cfg, "w+")
# FIXME: need to enable vncviewer by default once vncviewer copes with
# the geometry changes
buf = "# Automatically generated xen config file\n"
buf += "name = \"" + name + "\"\n"
buf += "builder = \"hvm\"\n"
buf += "memory = \"" + ram + "\"\n"
buf += "disk = [ '"
for whichDisk in range(len(disks)):
disk = disks[whichDisk]
# have we already done any? If so, we need to start a new grouping
if whichDisk > 0:
buf += ", '"
buf += diskTypes[disk] + ":" + disk + ",ioemu:hd" + chr(ord('a') + whichDisk) + ",w'"
buf += " ]\n"
buf += "vif = [ '"
for whichMAC in range(len(macs)):
mac = macs[whichMAC]
# have we already done any? If so, we need to start a new grouping
if whichMAC > 0:
buf += ", '"
buf += mac + ",type=ioemu'"
buf += " ]\n"
buf += "on_reboot = 'restart'\n"
buf += "on_crash = 'restart'\n"
buf += "kernel = '/usr/lib/xen/boot/hvmloader'\n"
buf += "device_model = '" + devmodel + "'\n"
buf += "sdl = 0 # use SDL for graphics\n"
buf += "vnc = " + hasX + "# use VNC for graphics\n"
buf += "vncviewer = 0 # spawn vncviewer by default\n"
buf += "nographic = " + noX + " # don't use graphics\n"
buf += "serial='pty' # enable serial console\n"
f.write(buf)
f.close()
if cdrom:
cdstr = [ "cdrom=%s" %(cdrom,), "boot=d" ]
else:
cdstr = []
print "\n\nStarting guest..."
# this is kind of lame. we call xm create, then change our
# guest config file to use a boot loader instead of the passed kernel
cmd = ["/usr/sbin/xm", "create", "-c"]
cmd.extend(cdstr)
cmd.append(cfg)
print cmd
child = os.fork()
if (not child):
os.execvp(cmd[0], cmd)
os._exit(1)
status = -1
try:
(pid, status) = os.waitpid(child, 0)
except OSError, (errno, msg):
print __name__, "waitpid:", msg
print ("XM command exited. Your guest can be restarted by running\n"
"'xm create -c %s'. Otherwise, you can reconnect to the console\n"
"with vncviewer or 'xm console'" %(name,))
def get_paravirt_install_image(src):
if src.startswith("http://") or src.startswith("ftp://"):
try:
kernel = grabber.urlopen("%s/images/xen/vmlinuz" %(src,))
initrd = grabber.urlopen("%s/images/xen/initrd.img" %(src,))
except IOError:
print >> sys.stderr, "Invalid URL location given"
sys.exit(2);
elif src.startswith("nfs:"):
nfsmntdir = tempfile.mkdtemp(prefix="xennfs.", dir="/var/lib/xen")
cmd = ["mount", "-o", "ro", src[4:], nfsmntdir]
ret = subprocess.call(cmd)
if ret != 0:
print >> sys.stderr, "Mounting nfs share failed!"
sys.exit(1)
try:
kernel = open("%s/images/xen/vmlinuz" %(nfsmntdir,), "r")
initrd = open("%s/images/xen/initrd.img" %(nfsmntdir,), "r")
except IOError:
print >> sys.stderr, "Invalid NFS location given"
sys.exit(2)
(kfd, kfn) = tempfile.mkstemp(prefix="vmlinuz.", dir="/var/lib/xen")
os.write(kfd, kernel.read())
os.close(kfd)
kernel.close()
(ifd, ifn) = tempfile.mkstemp(prefix="initrd.img.", dir="/var/lib/xen")
os.write(ifd, initrd.read())
os.close(ifd)
initrd.close()
# and unmount
if src.startswith("nfs"):
cmd = ["umount", nfsmntdir]
ret = subprocess.call(cmd)
os.rmdir(nfsmntdir)
return (kfn, ifn)
def get_paravirt_install(options):
src = options.location
while True:
if src and (src.startswith("http://") or src.startswith("ftp://")):
return src
elif src and src.startswith("nfs:"):
return src
if src is not None: print "Invalid source specified. Please specify an NFS, HTTP, or FTP install source"
print "What is the install location? ",
src = sys.stdin.readline().strip()
def start_paravirt_install(name, ram, disks, macs, src, extra = ""):
(kfn, ifn) = get_paravirt_install_image(src)
diskTypes = {}
for disk in disks:
if stat.S_ISBLK(os.stat(disk)[stat.ST_MODE]):
diskTypes[disk] = "phy"
else:
diskTypes[disk] = "file"
cfg = "%s%s" %(XENCONFIGPATH, name)
f = open(cfg, "w+")
buf = "# Automatically generated xen config file\n"
buf += "name = \"" + name + "\"\n"
buf += "memory = \"" + str(ram) + "\"\n"
buf += "disk = [ '"
for whichDisk in range(len(disks)):
disk = disks[whichDisk]
# have we already done any? If so, we need to start a new grouping
if whichDisk > 0:
buf += ", '"
buf += diskTypes[disk] + ":" + disk + ",xvd" + chr(ord('a') + whichDisk) + ",w'"
buf += " ]\n"
buf += "vif = [ '"
for whichMAC in range(len(macs)):
mac = macs[whichMAC]
# have we already done any? If so, we need to start a new grouping
if whichMAC > 0:
buf += ", '"
buf += mac + "'"
buf += " ]\n"
buf += "#bootloader=\"/usr/bin/pygrub\"\n"
buf += "\n"
buf += "on_reboot = 'destroy'\n"
buf += "on_crash = 'destroy'\n"
f.write(buf)
f.close()
print "\n\nStarting install..."
# this is kind of lame. we call xm create, then change our
# guest config file to use a boot loader instead of the passed kernel
cmd = ["/usr/sbin/xm", "create", "-c", "kernel=%s" %(kfn,),
"ramdisk=%s" %(ifn,),
"extra=method=%s %s" %(src,extra),
cfg]
child = os.fork()
if (not child):
os.execvp(cmd[0], cmd)
os._exit(1)
time.sleep(5)
f = open(cfg, "r")
buf = f.read()
f.close()
os.unlink(kfn)
os.unlink(ifn)
buf = buf.replace("#bootloader", "bootloader")
buf = buf.replace("'destroy'", "'restart'")
f = open(cfg, "w+")
f.write(buf)
f.close()
status = -1
try:
(pid, status) = os.waitpid(child, 0)
except OSError, (errno, msg):
print __name__, "waitpid:", msg
print ("If your install has exited, you can restart your guest by running\n"
"'xm create -c %s'. Otherwise, you can reconnect to the console\n"
"by running 'xm console %s'" %(name, name))
def parse_args():
parser = OptionParser()
parser.add_option("-n", "--name", type="string", dest="name",
help="Name of the guest instance")
parser.add_option("-f", "--file", type="string", dest="diskfile",
help="File(s) to use as the disk image")
parser.add_option("-r", "--ram", type="int", dest="memory",
help="Memory to allocate for guest instance in megabytes")
parser.add_option("-b", "--bridges", type="string", dest="bridges",
help="Which networking bridges to connect to. One virtual NIC will be created for each bridge.")
# vmx/svm options
if is_hvm_capable():
parser.add_option("-v", "--hvm", action="store_true", dest="fullvirt",
help="This guest should be a fully virtualized guest")
parser.add_option("-c", "--cdrom", type="string", dest="cdrom",
help="File to use a virtual CD-ROM device for fully virtualized guests")
# paravirt options
parser.add_option("-p", "--paravirt", action="store_false", dest="fullvirt",
help="This guest should be a paravirtualized guest")
parser.add_option("-l", "--location", type="string", dest="location",
help="Installation source for paravirtualized guest (eg, nfs:host:/path, http://host/path, ftp://host/path)")
parser.add_option("-x", "--extra-args", type="string",
dest="extra", default="",
help="Additional arguments to pass to the installer with paravirt guests")
(options,args) = parser.parse_args()
return options
def get_cpu_flags():
f = open("/proc/cpuinfo")
lines = f.readlines()
f.close()
for line in lines:
if not line.startswith("flags"):
continue
# get the actual flags
flags = line[:-1].split(":", 1)[1]
# and split them
flst = flags.split(" ")
return flst
return []
def is_hvm_capable():
flags = get_cpu_flags()
if "vmx" in flags:
return True
if "svm" in flags:
return True
return False
def main():
options = parse_args()
hvm = False
name = get_name(options)
ram = get_ram(options)
disks = get_disks(options)
macs = get_macs(options)
if is_hvm_capable():
hvm = get_full_virt(options)
if not hvm:
src = get_paravirt_install(options)
start_paravirt_install(name, ram, disks, macs, src, options.extra)
else:
if get_virt_cdboot(options):
cdrom = get_virt_cdrom(options)
else:
cdrom = None
start_hvm_guest(name, ram, disks, macs, cdrom)
if __name__ == "__main__":
main()