aboutsummaryrefslogtreecommitdiffstats
path: root/tools/xenctl
diff options
context:
space:
mode:
authorkaf24@scramble.cl.cam.ac.uk <kaf24@scramble.cl.cam.ac.uk>2004-06-11 18:31:12 +0000
committerkaf24@scramble.cl.cam.ac.uk <kaf24@scramble.cl.cam.ac.uk>2004-06-11 18:31:12 +0000
commit69db76b274eb93f02c066e5451542b06e568d1dc (patch)
tree1c4d2d9fa676c7f5a0defd5874c840d7bf72cc81 /tools/xenctl
parent46dcd03da3242c5a0409b88f0b623ffaec127272 (diff)
parent8b980d633cd157fdea71de4e8db397181fa4b678 (diff)
downloadxen-69db76b274eb93f02c066e5451542b06e568d1dc.tar.gz
xen-69db76b274eb93f02c066e5451542b06e568d1dc.tar.bz2
xen-69db76b274eb93f02c066e5451542b06e568d1dc.zip
bitkeeper revision 1.955.1.2 (40c9fa70tPu0dbYE7l64lI-VfVdUYQ)
Merge scramble.cl.cam.ac.uk:/auto/groups/xeno/BK/xeno.bk into scramble.cl.cam.ac.uk:/local/scratch/kaf24/var/bk/xeno-unstable.bk
Diffstat (limited to 'tools/xenctl')
-rw-r--r--tools/xenctl/lib/ip.py95
-rw-r--r--tools/xenctl/lib/vdisk.py944
-rw-r--r--tools/xenctl/setup.py2
3 files changed, 1040 insertions, 1 deletions
diff --git a/tools/xenctl/lib/ip.py b/tools/xenctl/lib/ip.py
new file mode 100644
index 0000000000..0f7f61e3f8
--- /dev/null
+++ b/tools/xenctl/lib/ip.py
@@ -0,0 +1,95 @@
+import os
+import re
+import socket
+import struct
+
+##### 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 = readlines(fd)
+ 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 = readlines(fd)
+ 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 = readlines(fd)
+ 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 inet_aton(addr):
+ """Convert an IP addr in IPv4 dot notation into an int.
+ """
+ b = socket.inet_aton(addr)
+ return struct.unpack('!I', b)[0]
+
+def inet_ntoa(n):
+ """Convert an int into an IP addr in IPv4 dot notation.
+ """
+ b = struct.pack('!I', n)
+ return socket.inet_ntoa(b)
+
+def add_offset_to_ip(addr, offset):
+ """Add a numerical offset to an IP addr in IPv4 dot notation.
+ """
+ n = inet_aton(addr)
+ n += offset
+ return inet_ntoa(n)
+
+def check_subnet( ip, network, netmask ):
+ n_ip = inet_aton(ip)
+ n_net = inet_aton(network)
+ n_mask = inet_aton(netmask)
+ return (n_ip & n_mask) == (n_net & n_mask)
+
diff --git a/tools/xenctl/lib/vdisk.py b/tools/xenctl/lib/vdisk.py
new file mode 100644
index 0000000000..6c4d144f9f
--- /dev/null
+++ b/tools/xenctl/lib/vdisk.py
@@ -0,0 +1,944 @@
+import os
+import re
+import socket
+import string
+import sys
+import tempfile
+import struct
+
+##### 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_SAFETY_RR = 0
+VBD_SAFETY_RW = 1
+VBD_SAFETY_WW = 2
+
+VBD_SECTORS_PER_MB = 2048
+
+##### 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
+
+
+
+##### 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 = readline(fd)
+ 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 = readlines(fd)
+ 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
+
+
+##### 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 readlines(fd):
+ """Version of readlines safe against EINTR.
+ """
+ import errno
+
+ lines = []
+ while 1:
+ try:
+ line = fd.readline()
+ except IOError, ex:
+ if ex.errno == errno.EINTR:
+ continue
+ else:
+ raise
+ if line == '': break
+ lines.append(line)
+ return lines
+
+def readline(fd):
+ """Version of readline safe against EINTR.
+ """
+ while 1:
+ try:
+ return fd.readline()
+ except IOError, ex:
+ if ex.errno == errno.EINTR:
+ continue
+ else:
+ raise
+
+
+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 * VBD_SECTORS_PER_MB # 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 * VBD_SECTORS_PER_MB
+
+ 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 * VBD_SECTORS_PER_MB
+
+ 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 / VBD_SECTORS_PER_MB
+
+
+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 * VBD_SECTORS_PER_MB # 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, safety=VBD_SAFETY_RR):
+ """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 = readline(fd)
+ 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 > safety:
+ print >> sys.stderr, """ERROR: This kind of disk sharing is not allowed
+ at the current safety level (%d).""" % safety
+ level = -1
+
+ return level
+
diff --git a/tools/xenctl/setup.py b/tools/xenctl/setup.py
index 1b6a98c10a..1d67a843ef 100644
--- a/tools/xenctl/setup.py
+++ b/tools/xenctl/setup.py
@@ -2,7 +2,7 @@
from distutils.core import setup, Extension
import sys
-modules = [ 'xenctl.console_client', 'xenctl.utils' ]
+modules = [ 'xenctl.console_client', 'xenctl.utils', 'xenctl.ip' , 'xenctl.vdisk' ]
# We need the 'tempfile' module from Python 2.3. We install this ourselves
# if the installed Python is older than 2.3.