aboutsummaryrefslogtreecommitdiffstats
path: root/tools/xenctl
diff options
context:
space:
mode:
authorkaf24@scramble.cl.cam.ac.uk <kaf24@scramble.cl.cam.ac.uk>2004-03-15 17:56:41 +0000
committerkaf24@scramble.cl.cam.ac.uk <kaf24@scramble.cl.cam.ac.uk>2004-03-15 17:56:41 +0000
commit784905204897c004a990a25aed49dcccb6e2da54 (patch)
treea0de0dc03705b308ec662fbaf70748661abb9496 /tools/xenctl
parent58ae7777f2bc2910ae57900e33a20fc7ef1e3b7f (diff)
downloadxen-784905204897c004a990a25aed49dcccb6e2da54.tar.gz
xen-784905204897c004a990a25aed49dcccb6e2da54.tar.bz2
xen-784905204897c004a990a25aed49dcccb6e2da54.zip
bitkeeper revision 1.800 (4055ee59_zI1OKOsC2EKXBIYFm3OtA)
console_client.py, __init__.py, setup.py, Makefile: new file Many files: New console-terminal client in xenctl.console_client Python package. New option to xc_dom_create (-c on cmdline or auto_console in defaults file) to automatically become a console client. utils.py: Rename: tools/xc/py/XenoUtil.py -> tools/xenctl/lib/utils.py
Diffstat (limited to 'tools/xenctl')
-rw-r--r--tools/xenctl/Makefile10
-rw-r--r--tools/xenctl/lib/__init__.py0
-rw-r--r--tools/xenctl/lib/console_client.py60
-rw-r--r--tools/xenctl/lib/utils.py1014
-rw-r--r--tools/xenctl/setup.py8
5 files changed, 1092 insertions, 0 deletions
diff --git a/tools/xenctl/Makefile b/tools/xenctl/Makefile
new file mode 100644
index 0000000000..e3583f5dd8
--- /dev/null
+++ b/tools/xenctl/Makefile
@@ -0,0 +1,10 @@
+
+all:
+ python setup.py build
+
+install: all
+ if [ "$(prefix)" = "" ]; then python setup.py install; \
+ else python setup.py install --home="$(prefix)"; fi
+
+clean:
+ rm -rf build *.pyc *.pyo *.o *.a *~
diff --git a/tools/xenctl/lib/__init__.py b/tools/xenctl/lib/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tools/xenctl/lib/__init__.py
diff --git a/tools/xenctl/lib/console_client.py b/tools/xenctl/lib/console_client.py
new file mode 100644
index 0000000000..74fc978bf2
--- /dev/null
+++ b/tools/xenctl/lib/console_client.py
@@ -0,0 +1,60 @@
+
+##############################################
+# Console client for Xen guest OSes
+# Copyright (c) 2004, K A Fraser
+##############################################
+
+import errno, os, signal, socket, struct, sys, termios
+
+def __child_death(signum, frame):
+ global stop
+ stop = True
+
+def __recv_from_sock(sock):
+ global stop
+ stop = False
+ print "************ REMOTE CONSOLE: CTRL-] TO QUIT ********"
+ while not stop:
+ try:
+ data = sock.recv(1)
+ os.write(1, data)
+ except socket.error, error:
+ if error[0] != errno.EINTR:
+ raise
+ print
+ print "************ REMOTE CONSOLE EXITED *****************"
+ os.wait()
+
+def __send_to_sock(sock):
+ while 1:
+ data = os.read(0,1)
+ if ord(data[0]) == ord(']')-64:
+ break
+ sock.send(data)
+ sys.exit(0)
+
+def connect(host,port):
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
+ struct.pack('ii', 0, 0))
+ sock.connect((host,port))
+
+ oattrs = termios.tcgetattr(0)
+ nattrs = termios.tcgetattr(0)
+ nattrs[3] = nattrs[3] & ~(termios.ECHO | termios.ICANON)
+ nattrs[6][termios.VMIN] = 1
+ nattrs[6][termios.VTIME] = 0
+ termios.tcsetattr(0, termios.TCSAFLUSH, nattrs)
+
+ try:
+ if os.fork():
+ signal.signal(signal.SIGCHLD, __child_death)
+ __recv_from_sock(sock)
+ else:
+ __send_to_sock(sock)
+ finally:
+ termios.tcsetattr(0, termios.TCSAFLUSH, oattrs)
+
+if __name__ == '__main__':
+ main(str(sys.argv[1]),int(sys.argv[2]))
diff --git a/tools/xenctl/lib/utils.py b/tools/xenctl/lib/utils.py
new file mode 100644
index 0000000000..3f0914f73f
--- /dev/null
+++ b/tools/xenctl/lib/utils.py
@@ -0,0 +1,1014 @@
+import os, re, socket, string, sys, tempfile
+
+##### Module variables
+
+"""Location of the Virtual Disk management database.
+ defaults to /var/db/xen_vdisks.sqlite
+"""
+VD_DB_FILE = "/var/db/xen_vdisks.sqlite"
+
+"""VBD expertise level - determines the strictness of the sanity checking.
+ This mode determines the level of complaints when disk sharing occurs
+ through the current VBD mappings.
+ 0 - only allow shared mappings if both domains have r/o access (always OK)
+ 1 - also allow sharing with one dom r/w and the other r/o
+ 2 - allow sharing with both doms r/w
+"""
+VBD_EXPERT_MODE = 0
+
+##### Module initialisation
+
+try:
+ # try to import sqlite (not everyone will have it installed)
+ import sqlite
+except ImportError:
+ # on failure, just catch the error, don't do anything
+ pass
+
+
+##### Networking-related functions
+
+def get_current_ipaddr(dev='eth0'):
+ """Return a string containing the primary IP address for the given
+ network interface (default 'eth0').
+ """
+ fd = os.popen( '/sbin/ifconfig ' + dev + ' 2>/dev/null' )
+ lines = fd.readlines()
+ for line in lines:
+ m = re.search( '^\s+inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*',
+ line )
+ if m:
+ return m.group(1)
+ return None
+
+def get_current_ipmask(dev='eth0'):
+ """Return a string containing the primary IP netmask for the given
+ network interface (default 'eth0').
+ """
+ fd = os.popen( '/sbin/ifconfig ' + dev + ' 2>/dev/null' )
+ lines = fd.readlines()
+ for line in lines:
+ m = re.search( '^.+Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*',
+ line )
+ if m:
+ return m.group(1)
+ return None
+
+def get_current_ipgw(dev='eth0'):
+ """Return a string containing the IP gateway for the given
+ network interface (default 'eth0').
+ """
+ fd = os.popen( '/sbin/route -n' )
+ lines = fd.readlines()
+ for line in lines:
+ m = re.search( '^\S+\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)' +
+ '\s+\S+\s+\S*G.*' + dev + '.*', line )
+ if m:
+ return m.group(1)
+ return None
+
+def setup_vfr_rules_for_vif(dom,vif,addr):
+ """Takes a tuple ( domain-id, vif-id, ip-addr ), where the ip-addr
+ is expressed as a textual dotted quad, and set up appropriate routing
+ rules in Xen. No return value.
+ """
+ fd = os.open( '/proc/xen/vfr', os.O_WRONLY )
+ if ( re.search( '169\.254', addr) ):
+ os.write( fd, 'ADD ACCEPT srcaddr=' + addr +
+ ' srcaddrmask=255.255.255.255' +
+ ' srcdom=' + str(dom) + ' srcidx=' + str(vif) +
+ ' dstdom=0 dstidx=0 proto=any\n' )
+ else:
+ os.write( fd, 'ADD ACCEPT srcaddr=' + addr +
+ ' srcaddrmask=255.255.255.255' +
+ ' srcdom=' + str(dom) + ' srcidx=' + str(vif) +
+ ' dst=PHYS proto=any\n' )
+ os.write( fd, 'ADD ACCEPT dstaddr=' + addr +
+ ' dstaddrmask=255.255.255.255' +
+ ' src=ANY' +
+ ' dstdom=' + str(dom) + ' dstidx=' + str(vif) +
+ ' proto=any\n' )
+ os.close( fd )
+ return None
+
+def add_offset_to_ip( ip, off ):
+ l = string.split(ip,'.')
+ a = ( (string.atoi(l[0])<<24) | (string.atoi(l[1])<<16) |
+ (string.atoi(l[2])<<8) | string.atoi(l[3]) ) + off
+
+ return '%d.%d.%d.%d' % ( ((a>>24)&0xff), ((a>>16)&0xff),
+ ((a>>8)&0xff), (a&0xff) )
+
+def check_subnet( ip, network, netmask ):
+ l = string.split(ip,'.')
+ n_ip = ( (string.atoi(l[0])<<24) | (string.atoi(l[1])<<16) |
+ (string.atoi(l[2])<<8) | string.atoi(l[3]) )
+
+ l = string.split(network,'.')
+ n_net = ( (string.atoi(l[0])<<24) | (string.atoi(l[1])<<16) |
+ (string.atoi(l[2])<<8) | string.atoi(l[3]) )
+
+ l = string.split(netmask,'.')
+ n_mask = ( (string.atoi(l[0])<<24) | (string.atoi(l[1])<<16) |
+ (string.atoi(l[2])<<8) | string.atoi(l[3]) )
+
+ return (n_ip&n_mask)==(n_net&n_mask)
+
+
+##### VBD-related Functions
+
+def blkdev_name_to_number(name):
+ """Take the given textual block-device name (e.g., '/dev/sda1',
+ 'hda') and return the device number used by the OS. """
+
+ if not re.match( '/dev/', name ):
+ name = '/dev/' + name
+
+ return os.stat(name).st_rdev
+
+# lookup_blkdev_partn_info( '/dev/sda3' )
+def lookup_raw_partn(partition):
+ """Take the given block-device name (e.g., '/dev/sda1', 'hda')
+ and return a dictionary { device, start_sector,
+ nr_sectors, type }
+ device: Device number of the given partition
+ start_sector: Index of first sector of the partition
+ nr_sectors: Number of sectors comprising this partition
+ type: 'Disk' or identifying name for partition type
+ """
+
+ if not re.match( '/dev/', partition ):
+ partition = '/dev/' + partition
+
+ drive = re.split( '[0-9]', partition )[0]
+
+ if drive == partition:
+ fd = os.popen( '/sbin/sfdisk -s ' + drive + ' 2>/dev/null' )
+ line = fd.readline()
+ if line:
+ return [ { 'device' : blkdev_name_to_number(drive),
+ 'start_sector' : long(0),
+ 'nr_sectors' : long(line) * 2,
+ 'type' : 'Disk' } ]
+ return None
+
+ # determine position on disk
+ fd = os.popen( '/sbin/sfdisk -d ' + drive + ' 2>/dev/null' )
+
+ #['/dev/sda3 : start= 16948575, size=16836120, Id=83, bootable\012']
+ lines = fd.readlines()
+ for line in lines:
+ m = re.search( '^' + partition + '\s*: start=\s*([0-9]+), ' +
+ 'size=\s*([0-9]+), Id=\s*(\S+).*$', line)
+ if m:
+ return [ { 'device' : blkdev_name_to_number(drive),
+ 'start_sector' : long(m.group(1)),
+ 'nr_sectors' : long(m.group(2)),
+ 'type' : m.group(3) } ]
+
+ return None
+
+def lookup_disk_uname( uname ):
+ """Lookup a list of segments for either a physical or a virtual device.
+ uname [string]: name of the device in the format \'vd:id\' for a virtual
+ disk, or \'phy:dev\' for a physical device
+ returns [list of dicts]: list of extents that make up the named device
+ """
+ ( type, d_name ) = string.split( uname, ':' )
+
+ if type == "phy":
+ segments = lookup_raw_partn( d_name )
+ elif type == "vd":
+ segments = vd_lookup( d_name )
+
+ return segments
+
+
+##### Management of the Xen control daemon
+##### (c) Keir Fraser, University of Cambridge
+
+def xend_control_message( message ):
+ """Takes a textual control message and sends it to the 'xend' Xen
+ control daemon. Returns a dictionary containing the daemon's multi-part
+ response."""
+ tmpdir = tempfile.mkdtemp()
+ try:
+ ctl = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM, 0)
+ ctl.bind(tmpdir+'/sock')
+ ctl.sendto(message, '/var/run/xend/management_sock')
+ data, addr = ctl.recvfrom(2048)
+ ctl.close()
+ finally:
+ if os.path.exists(tmpdir+'/sock'):
+ os.unlink(tmpdir+'/sock')
+ if os.path.exists(tmpdir):
+ os.rmdir(tmpdir)
+ return eval(data)
+
+
+##### VD Management-related functions
+
+##### By Mark Williamson, <mark.a.williamson@intel.com>
+##### (C) Intel Research Cambridge
+
+# TODO:
+#
+# Plenty of room for enhancement to this functionality (contributions
+# welcome - and then you get to have your name in the source ;-)...
+#
+# vd_unformat() : want facilities to unallocate virtual disk
+# partitions, possibly migrating virtual disks of them, with checks to see if
+# it's safe and options to force it anyway
+#
+# vd_create() : should have an optional argument specifying a physical
+# disk preference - useful to allocate for guest doms to do RAID
+#
+# vd_undelete() : add ability to "best effort" undelete as much of a
+# vdisk as is left in the case that some of it has already been
+# reallocated. Some people might still be able to recover some of
+# their data this way, even if some of the disk has disappeared.
+#
+# It'd be nice if we could wipe virtual disks for security purposes -
+# should be easy to do this using dev if=/dev/{zero,random} on each
+# extent in turn. There could be another optional flag to vd_create
+# in order to allow this.
+#
+# Error codes could be more expressive - i.e. actually tell why the
+# error occurred rather than "it broke". Currently the code avoids
+# using exceptions to make control scripting simpler and more
+# accessible to beginners - therefore probably should just use more
+# return codes.
+#
+# Enhancements / additions to the example scripts are also welcome:
+# some people will interact with this code mostly through those
+# scripts.
+#
+# More documentation of how this stuff should be used is always nice -
+# if you have a novel configuration that you feel isn't discussed
+# enough in the HOWTO (which is currently a work in progress), feel
+# free to contribute a walkthrough, or something more substantial.
+#
+
+
+def __vd_no_database():
+ """Called when no database found - exits with an error
+ """
+ print >> sys.stderr, "ERROR: Could not locate the database file at " + VD_DB_FILE
+ sys.exit(1)
+
+
+def vd_format(partition, extent_size_mb):
+ """Format a partition or drive for use a virtual disk storage.
+ partition [string]: device file representing the partition
+ extent_size_mb [string]: extent size in megabytes to use on this disk
+ """
+
+ if not os.path.isfile(VD_DB_FILE):
+ vd_init_db(VD_DB_FILE)
+
+ if not re.match( '/dev/', partition ):
+ partition = '/dev/' + partition
+
+ cx = sqlite.connect(VD_DB_FILE)
+ cu = cx.cursor()
+
+ cu.execute("select * from vdisk_part where partition = \'"
+ + partition + "\'")
+ row = cu.fetchone()
+
+ extent_size = extent_size_mb * 2048 # convert megabytes to sectors
+
+ if not row:
+ part_info = lookup_raw_partn(partition)[0]
+
+ cu.execute("INSERT INTO vdisk_part(partition, part_id, extent_size) " +
+ "VALUES ( \'" + partition + "\', "
+ + str(blkdev_name_to_number(partition))
+ + ", " + str(extent_size) + ")")
+
+
+ cu.execute("SELECT max(vdisk_extent_no) FROM vdisk_extents "
+ + "WHERE vdisk_id = 0")
+
+ max_id, = cu.fetchone()
+
+ if max_id != None:
+ new_id = max_id + 1
+ else:
+ new_id = 0
+
+ num_extents = part_info['nr_sectors'] / extent_size
+
+ for i in range(num_extents):
+ sql ="""INSERT INTO vdisk_extents(vdisk_extent_no, vdisk_id,
+ part_id, part_extent_no)
+ VALUES ("""+ str(new_id + i) + ", 0, "\
+ + str(blkdev_name_to_number(partition))\
+ + ", " + str(num_extents - (i + 1)) + ")"
+ cu.execute(sql)
+
+ cx.commit()
+ cx.close()
+ return 0
+
+
+def vd_create(size_mb, expiry):
+ """Create a new virtual disk.
+ size_mb [int]: size in megabytes for the new virtual disk
+ expiry [int]: expiry time in seconds from now
+ """
+
+ if not os.path.isfile(VD_DB_FILE):
+ __vd_no_database()
+
+ cx = sqlite.connect(VD_DB_FILE)
+ cu = cx.cursor()
+
+ size = size_mb * 2048
+
+ cu.execute("SELECT max(vdisk_id) FROM vdisks")
+ max_id, = cu.fetchone()
+ new_id = int(max_id) + 1
+
+ # fetch a list of extents from the expired disks, along with information
+ # about their size
+ cu.execute("""SELECT vdisks.vdisk_id, vdisk_extent_no, part_extent_no,
+ vdisk_extents.part_id, extent_size
+ FROM vdisks NATURAL JOIN vdisk_extents
+ NATURAL JOIN vdisk_part
+ WHERE expires AND expiry_time <= datetime('now')
+ ORDER BY expiry_time ASC, vdisk_extent_no DESC
+ """) # aims to reuse the last extents
+ # from the longest-expired disks first
+
+ allocated = 0
+
+ if expiry:
+ expiry_ts = "datetime('now', '" + str(expiry) + " seconds')"
+ expires = 1
+ else:
+ expiry_ts = "NULL"
+ expires = 0
+
+ # we'll use this to build the SQL statement we want
+ building_sql = "INSERT INTO vdisks(vdisk_id, size, expires, expiry_time)" \
+ +" VALUES ("+str(new_id)+", "+str(size)+ ", " \
+ + str(expires) + ", " + expiry_ts + "); "
+
+ counter = 0
+
+ while allocated < size:
+ row = cu.fetchone()
+ if not row:
+ print "ran out of space, having allocated %d meg of %d" % (allocated, size)
+ cx.close()
+ return -1
+
+
+ (vdisk_id, vdisk_extent_no, part_extent_no, part_id, extent_size) = row
+ allocated += extent_size
+ building_sql += "UPDATE vdisk_extents SET vdisk_id = " + str(new_id) \
+ + ", " + "vdisk_extent_no = " + str(counter) \
+ + " WHERE vdisk_extent_no = " + str(vdisk_extent_no) \
+ + " AND vdisk_id = " + str(vdisk_id) + "; "
+
+ counter += 1
+
+
+ # this will execute the SQL query we build to store details of the new
+ # virtual disk and allocate space to it print building_sql
+ cu.execute(building_sql)
+
+ cx.commit()
+ cx.close()
+ return str(new_id)
+
+
+def vd_lookup(id):
+ """Lookup a Virtual Disk by ID.
+ id [string]: a virtual disk identifier
+ Returns [list of dicts]: a list of extents as dicts, containing fields:
+ device : Linux device number of host disk
+ start_sector : within the device
+ nr_sectors : size of this extent
+ type : set to \'VD Extent\'
+
+ part_device : Linux device no of host partition
+ part_start_sector : within the partition
+ """
+
+ if not os.path.isfile(VD_DB_FILE):
+ __vd_no_database()
+
+ cx = sqlite.connect(VD_DB_FILE)
+ cu = cx.cursor()
+
+ cu.execute("-- types int")
+ cu.execute("""SELECT COUNT(*)
+ FROM vdisks
+ WHERE (expiry_time > datetime('now') OR NOT expires)
+ AND vdisk_id = """ + id)
+ count, = cu.fetchone()
+
+ if not count:
+ cx.close()
+ return None
+
+ cu.execute("SELECT size from vdisks WHERE vdisk_id = " + id)
+ real_size, = cu.fetchone()
+
+ # This query tells PySQLite how to convert the data returned from the
+ # following query - the use of the multiplication confuses it otherwise ;-)
+ # This row is significant to PySQLite but is syntactically an SQL comment.
+
+ cu.execute("-- types str, int, int, int")
+
+ # This SQL statement is designed so that when the results are fetched they
+ # will be in the right format to return immediately.
+ cu.execute("""SELECT partition, vdisk_part.part_id,
+ round(part_extent_no * extent_size) as start,
+ extent_size
+
+ FROM vdisks NATURAL JOIN vdisk_extents
+ NATURAL JOIN vdisk_part
+
+ WHERE vdisk_extents.vdisk_id = """ + id
+ + " ORDER BY vdisk_extents.vdisk_extent_no ASC"
+ )
+
+ extent_tuples = cu.fetchall()
+
+ # use this function to map the results from the database into a dict
+ # list of extents, for consistency with the rest of the code
+ def transform ((partition, part_device, part_offset, nr_sectors)):
+ return {
+ # the disk device this extent is on - for passing to Xen
+ 'device' : lookup_raw_partn(partition)[0]['device'],
+ # the offset of this extent within the disk - for passing to Xen
+ 'start_sector' : long(part_offset + lookup_raw_partn(partition)[0]['start_sector']),
+ # extent size, in sectors
+ 'nr_sectors' : nr_sectors,
+ # partition device this extent is on (useful to know for xenctl.utils fns)
+ 'part_device' : part_device,
+ # start sector within this partition (useful to know for xenctl.utils fns)
+ 'part_start_sector' : part_offset,
+ # type of this extent - handy to know
+ 'type' : 'VD Extent' }
+
+ cx.commit()
+ cx.close()
+
+ extent_dicts = map(transform, extent_tuples)
+
+ # calculate the over-allocation in sectors (happens because
+ # we allocate whole extents)
+ allocated_size = 0
+ for i in extent_dicts:
+ allocated_size += i['nr_sectors']
+
+ over_allocation = allocated_size - real_size
+
+ # trim down the last extent's length so the resulting VBD will be the
+ # size requested, rather than being rounded up to the nearest extent
+ extent_dicts[len(extent_dicts) - 1]['nr_sectors'] -= over_allocation
+
+ return extent_dicts
+
+
+def vd_enlarge(vdisk_id, extra_size_mb):
+ """Create a new virtual disk.
+ vdisk_id [string] : ID of the virtual disk to enlarge
+ extra_size_mb [int]: size in megabytes to increase the allocation by
+ returns [int] : 0 on success, otherwise non-zero
+ """
+
+ if not os.path.isfile(VD_DB_FILE):
+ __vd_no_database()
+
+ cx = sqlite.connect(VD_DB_FILE)
+ cu = cx.cursor()
+
+ extra_size = extra_size_mb * 2048
+
+ cu.execute("-- types int")
+ cu.execute("SELECT COUNT(*) FROM vdisks WHERE vdisk_id = " + vdisk_id
+ + " AND (expiry_time > datetime('now') OR NOT expires)")
+ count, = cu.fetchone()
+
+ if not count: # no such vdisk
+ cx.close()
+ return -1
+
+ cu.execute("-- types int")
+ cu.execute("""SELECT SUM(extent_size)
+ FROM vdisks NATURAL JOIN vdisk_extents
+ NATURAL JOIN vdisk_part
+ WHERE vdisks.vdisk_id = """ + vdisk_id)
+
+ real_size, = cu.fetchone() # get the true allocated size
+
+ cu.execute("-- types int")
+ cu.execute("SELECT size FROM vdisks WHERE vdisk_id = " + vdisk_id)
+
+ old_size, = cu.fetchone()
+
+
+ cu.execute("--- types int")
+ cu.execute("""SELECT MAX(vdisk_extent_no)
+ FROM vdisk_extents
+ WHERE vdisk_id = """ + vdisk_id)
+
+ counter = cu.fetchone()[0] + 1 # this stores the extent numbers
+
+
+ # because of the extent-based allocation, the VD may already have more
+ # allocated space than they asked for. Find out how much we really
+ # need to add.
+ add_size = extra_size + old_size - real_size
+
+ # fetch a list of extents from the expired disks, along with information
+ # about their size
+ cu.execute("""SELECT vdisks.vdisk_id, vdisk_extent_no, part_extent_no,
+ vdisk_extents.part_id, extent_size
+ FROM vdisks NATURAL JOIN vdisk_extents
+ NATURAL JOIN vdisk_part
+ WHERE expires AND expiry_time <= datetime('now')
+ ORDER BY expiry_time ASC, vdisk_extent_no DESC
+ """) # aims to reuse the last extents
+ # from the longest-expired disks first
+
+ allocated = 0
+
+ building_sql = "UPDATE vdisks SET size = " + str(old_size + extra_size)\
+ + " WHERE vdisk_id = " + vdisk_id + "; "
+
+ while allocated < add_size:
+ row = cu.fetchone()
+ if not row:
+ cx.close()
+ return -1
+
+ (dead_vd_id, vdisk_extent_no, part_extent_no, part_id, extent_size) = row
+ allocated += extent_size
+ building_sql += "UPDATE vdisk_extents SET vdisk_id = " + vdisk_id \
+ + ", " + "vdisk_extent_no = " + str(counter) \
+ + " WHERE vdisk_extent_no = " + str(vdisk_extent_no) \
+ + " AND vdisk_id = " + str(dead_vd_id) + "; "
+
+ counter += 1
+
+
+ # this will execute the SQL query we build to store details of the new
+ # virtual disk and allocate space to it print building_sql
+ cu.execute(building_sql)
+
+ cx.commit()
+ cx.close()
+ return 0
+
+
+def vd_undelete(vdisk_id, expiry_time):
+ """Create a new virtual disk.
+ vdisk_id [int]: size in megabytes for the new virtual disk
+ expiry_time [int]: expiry time, in seconds from now
+ returns [int]: zero on success, non-zero on failure
+ """
+
+ if not os.path.isfile(VD_DB_FILE):
+ __vd_no_database()
+
+ if vdisk_id == '0': # undeleting vdisk 0 isn't sane!
+ return -1
+
+ cx = sqlite.connect(VD_DB_FILE)
+ cu = cx.cursor()
+
+ cu.execute("-- types int")
+ cu.execute("SELECT COUNT(*) FROM vdisks WHERE vdisk_id = " + vdisk_id)
+ count, = cu.fetchone()
+
+ if not count:
+ cx.close()
+ return -1
+
+ cu.execute("-- types int")
+ cu.execute("""SELECT SUM(extent_size)
+ FROM vdisks NATURAL JOIN vdisk_extents
+ NATURAL JOIN vdisk_part
+ WHERE vdisks.vdisk_id = """ + vdisk_id)
+
+ real_size, = cu.fetchone() # get the true allocated size
+
+
+ cu.execute("-- types int")
+ cu.execute("SELECT size FROM vdisks WHERE vdisk_id = " + vdisk_id)
+
+ old_size, = cu.fetchone()
+
+ if real_size < old_size:
+ cx.close()
+ return -1
+
+ if expiry_time == 0:
+ expires = '0'
+ else:
+ expires = '1'
+
+ # this will execute the SQL query we build to store details of the new
+ # virtual disk and allocate space to it print building_sql
+ cu.execute("UPDATE vdisks SET expiry_time = datetime('now','"
+ + str(expiry_time) + " seconds'), expires = " + expires
+ + " WHERE vdisk_id = " + vdisk_id)
+
+ cx.commit()
+ cx.close()
+ return 0
+
+
+
+
+def vd_list():
+ """Lists all the virtual disks registered in the system.
+ returns [list of dicts]
+ """
+
+ if not os.path.isfile(VD_DB_FILE):
+ __vd_no_database()
+
+ cx = sqlite.connect(VD_DB_FILE)
+ cu = cx.cursor()
+
+ cu.execute("""SELECT vdisk_id, size, expires, expiry_time
+ FROM vdisks
+ WHERE (NOT expires) OR expiry_time > datetime('now')
+ """)
+
+ ret = cu.fetchall()
+
+ cx.close()
+
+ def makedicts((vdisk_id, size, expires, expiry_time)):
+ return { 'vdisk_id' : str(vdisk_id), 'size': size,
+ 'expires' : expires, 'expiry_time' : expiry_time }
+
+ return map(makedicts, ret)
+
+
+def vd_refresh(id, expiry):
+ """Change the expiry time of a virtual disk.
+ id [string] : a virtual disk identifier
+ expiry [int] : expiry time in seconds from now (0 = never expire)
+ returns [int]: zero on success, non-zero on failure
+ """
+
+ if not os.path.isfile(VD_DB_FILE):
+ __vd_no_database()
+
+ cx = sqlite.connect(VD_DB_FILE)
+ cu = cx.cursor()
+
+ cu.execute("-- types int")
+ cu.execute("SELECT COUNT(*) FROM vdisks WHERE vdisk_id = " + id
+ + " AND (expiry_time > datetime('now') OR NOT expires)")
+ count, = cu.fetchone()
+
+ if not count:
+ cx.close()
+ return -1
+
+ if expiry:
+ expires = 1
+ expiry_ts = "datetime('now', '" + str(expiry) + " seconds')"
+ else:
+ expires = 0
+ expiry_ts = "NULL"
+
+ cu.execute("UPDATE vdisks SET expires = " + str(expires)
+ + ", expiry_time = " + expiry_ts
+ + " WHERE (expiry_time > datetime('now') OR NOT expires)"
+ + " AND vdisk_id = " + id)
+
+ cx.commit()
+ cx.close()
+
+ return 0
+
+
+def vd_delete(id):
+ """Deletes a Virtual Disk, making its extents available for future VDs.
+ id [string] : identifier for the virtual disk to delete
+ returns [int] : 0 on success, -1 on failure (VD not found
+ or already deleted)
+ """
+
+ if not os.path.isfile(VD_DB_FILE):
+ __vd_no_database()
+
+ cx = sqlite.connect(VD_DB_FILE)
+ cu = cx.cursor()
+
+ cu.execute("-- types int")
+ cu.execute("SELECT COUNT(*) FROM vdisks WHERE vdisk_id = " + id
+ + " AND (expiry_time > datetime('now') OR NOT expires)")
+ count, = cu.fetchone()
+
+ if not count:
+ cx.close()
+ return -1
+
+ cu.execute("UPDATE vdisks SET expires = 1, expiry_time = datetime('now')"
+ + " WHERE vdisk_id = " + id)
+
+ cx.commit()
+ cx.close()
+
+ return 0
+
+
+def vd_freespace():
+ """Returns the amount of free space available for new virtual disks, in MB
+ returns [int] : free space for VDs in MB
+ """
+
+ if not os.path.isfile(VD_DB_FILE):
+ __vd_no_database()
+
+ cx = sqlite.connect(VD_DB_FILE)
+ cu = cx.cursor()
+
+ cu.execute("-- types int")
+
+ cu.execute("""SELECT SUM(extent_size)
+ FROM vdisks NATURAL JOIN vdisk_extents
+ NATURAL JOIN vdisk_part
+ WHERE expiry_time <= datetime('now') AND expires""")
+
+ sum, = cu.fetchone()
+
+ cx.close()
+
+ return sum / 2048
+
+
+def vd_init_db(path):
+ """Initialise the VD SQLite database
+ path [string]: path to the SQLite database file
+ """
+
+ cx = sqlite.connect(path)
+ cu = cx.cursor()
+
+ cu.execute(
+ """CREATE TABLE vdisk_extents
+ ( vdisk_extent_no INT,
+ vdisk_id INT,
+ part_id INT,
+ part_extent_no INT )
+ """)
+
+ cu.execute(
+ """CREATE TABLE vdisk_part
+ ( part_id INT,
+ partition VARCHAR,
+ extent_size INT )
+ """)
+
+ cu.execute(
+ """CREATE TABLE vdisks
+ ( vdisk_id INT,
+ size INT,
+ expires BOOLEAN,
+ expiry_time TIMESTAMP )
+ """)
+
+
+ cu.execute(
+ """INSERT INTO vdisks ( vdisk_id, size, expires, expiry_time )
+ VALUES ( 0, 0, 1, datetime('now') )
+ """)
+
+ cx.commit()
+ cx.close()
+
+ VD_DB_FILE = path
+
+
+
+def vd_cp_to_file(vdisk_id,filename):
+ """Writes the contents of a specified vdisk out into a disk file, leaving
+ the original copy in the virtual disk pool."""
+
+ cx = sqlite.connect(VD_DB_FILE)
+ cu = cx.cursor()
+
+ extents = vd_lookup(vdisk_id)
+
+ if not extents:
+ return -1
+
+ file_idx = 0 # index into source file, in sectors
+
+ for i in extents:
+ cu.execute("""SELECT partition, extent_size FROM vdisk_part
+ WHERE part_id = """ + str(i['part_device']))
+
+ (partition, extent_size) = cu.fetchone()
+
+ os.system("dd bs=1b if=" + partition + " of=" + filename
+ + " skip=" + str(i['part_start_sector'])
+ + " seek=" + str(file_idx)
+ + " count=" + str(i['nr_sectors'])
+ + " > /dev/null")
+
+ file_idx += i['nr_sectors']
+
+ cx.close()
+
+ return 0 # should return -1 if something breaks
+
+
+def vd_mv_to_file(vdisk_id,filename):
+ """Writes a vdisk out into a disk file and frees the space originally
+ taken within the virtual disk pool.
+ vdisk_id [string]: ID of the vdisk to write out
+ filename [string]: file to write vdisk contents out to
+ returns [int]: zero on success, nonzero on failure
+ """
+
+ if vd_cp_to_file(vdisk_id,filename):
+ return -1
+
+ if vd_delete(vdisk_id):
+ return -1
+
+ return 0
+
+
+def vd_read_from_file(filename,expiry):
+ """Reads the contents of a file directly into a vdisk, which is
+ automatically allocated to fit.
+ filename [string]: file to read disk contents from
+ returns [string] : vdisk ID for the destination vdisk
+ """
+
+ size_bytes = os.stat(filename).st_size
+
+ (size_mb,leftover) = divmod(size_bytes,1048580) # size in megabytes
+ if leftover > 0: size_mb += 1 # round up if not an exact number of MB
+
+ vdisk_id = vd_create(size_mb, expiry)
+
+ if vdisk_id < 0:
+ return -1
+
+ cx = sqlite.connect(VD_DB_FILE)
+ cu = cx.cursor()
+
+ cu.execute("""SELECT partition, extent_size, part_extent_no
+ FROM vdisk_part NATURAL JOIN vdisk_extents
+ WHERE vdisk_id = """ + vdisk_id + """
+ ORDER BY vdisk_extent_no ASC""")
+
+ extents = cu.fetchall()
+
+ size_sectors = size_mb * 2048 # for feeding to dd
+
+ file_idx = 0 # index into source file, in sectors
+
+ def write_extent_to_vd((partition, extent_size, part_extent_no),
+ file_idx, filename):
+ """Write an extent out to disk and update file_idx"""
+
+ os.system("dd bs=512 if=" + filename + " of=" + partition
+ + " skip=" + str(file_idx)
+ + " seek=" + str(part_extent_no * extent_size)
+ + " count=" + str(min(extent_size, size_sectors - file_idx))
+ + " > /dev/null")
+
+ return extent_size
+
+ for i in extents:
+ file_idx += write_extent_to_vd(i, file_idx, filename)
+
+ cx.close()
+
+ return vdisk_id
+
+
+
+
+def vd_extents_validate(new_extents,new_writeable):
+ """Validate the extents against the existing extents.
+ Complains if the list supplied clashes against the extents that
+ are already in use in the system.
+ new_extents [list of dicts]: list of new extents, as dicts
+ new_writeable [int]: 1 if they are to be writeable, 0 otherwise
+ returns [int]: either the expertise level of the mapping if it doesn't
+ exceed VBD_EXPERT_MODE or -1 if it does (error)
+ """
+
+ import Xc # this is only needed in this function
+
+ xc = Xc.new()
+
+ ##### Probe for explicitly created virtual disks and build a list
+ ##### of extents for comparison with the ones that are being added
+
+ probe = xc.vbd_probe()
+
+ old_extents = [] # this will hold a list of all existing extents and
+ # their writeable status, as a list of (device,
+ # start, size, writeable?) tuples
+
+ for vbd in probe:
+ this_vbd_extents = xc.vbd_getextents(vbd['dom'],vbd['vbd'])
+ for vbd_ext in this_vbd_extents:
+ vbd_ext['writeable'] = vbd['writeable']
+ old_extents.append(vbd_ext)
+
+ ##### Now scan /proc/mounts for compile a list of extents corresponding to
+ ##### any devices mounted in DOM0. This list is added on to old_extents
+
+ regexp = re.compile("/dev/(\S*) \S* \S* (..).*")
+ fd = open('/proc/mounts', "r")
+
+ while True:
+ line = fd.readline()
+ if not line: # if we've run out of lines then stop reading
+ break
+
+ m = regexp.match(line)
+
+ # if the regexp didn't match then it's probably a line we don't
+ # care about - skip to next line
+ if not m:
+ continue
+
+ # lookup the device
+ ext_list = lookup_raw_partn(m.group(1))
+
+ # if lookup failed, skip to next mounted device
+ if not ext_list:
+ continue
+
+ # set a writeable flag as appropriate
+ for ext in ext_list:
+ ext['writeable'] = m.group(2) == 'rw'
+
+ # now we've got here, the contents of ext_list are in a
+ # suitable format to be added onto the old_extents list, ready
+ # for checking against the new extents
+
+ old_extents.extend(ext_list)
+
+ fd.close() # close /proc/mounts
+
+ ##### By this point, old_extents contains a list of extents, in
+ ##### dictionary format corresponding to every extent of physical
+ ##### disk that's either part of an explicitly created VBD, or is
+ ##### mounted under DOM0. We now check these extents against the
+ ##### proposed additions in new_extents, to see if a conflict will
+ ##### happen if they are added with write status new_writeable
+
+ level = 0 # this'll accumulate the max warning level
+
+ # Search for clashes between the new extents and the old ones
+ # Takes time O(len(new_extents) * len(old_extents))
+ for new_ext in new_extents:
+ for old_ext in old_extents:
+ if(new_ext['device'] == old_ext['device']):
+
+ new_ext_start = new_ext['start_sector']
+ new_ext_end = new_ext_start + new_ext['nr_sectors'] - 1
+
+ old_ext_start = old_ext['start_sector']
+ old_ext_end = old_ext_start + old_ext['nr_sectors'] - 1
+
+ if((old_ext_start <= new_ext_start <= old_ext_end) or
+ (old_ext_start <= new_ext_end <= old_ext_end)):
+ if (not old_ext['writeable']) and new_writeable:
+ level = max(1,level)
+ elif old_ext['writeable'] and (not new_writeable):
+ level = max(1,level)
+ elif old_ext['writeable'] and new_writeable:
+ level = max(2,level)
+
+
+ ##### level now holds the warning level incurred by the current
+ ##### VBD setup and we complain appropriately to the user
+
+
+ if level == 1:
+ print >> sys.stderr, """Warning: one or more hard disk extents
+ writeable by one domain are also readable by another."""
+ elif level == 2:
+ print >> sys.stderr, """Warning: one or more hard disk extents are
+ writeable by two or more domains simultaneously."""
+
+ if level > VBD_EXPERT_MODE:
+ print >> sys.stderr, """ERROR: This kind of disk sharing is not allowed
+ at the current safety level (%d).""" % VBD_EXPERT_MODE
+ level = -1
+
+ return level
+
diff --git a/tools/xenctl/setup.py b/tools/xenctl/setup.py
new file mode 100644
index 0000000000..5dce290385
--- /dev/null
+++ b/tools/xenctl/setup.py
@@ -0,0 +1,8 @@
+
+from distutils.core import setup, Extension
+
+setup(name = "xenctl",
+ version = "1.0",
+ packages = ["xenctl"],
+ package_dir = { "xenctl" : "lib" },
+ )