ia64/xen-unstable

view tools/xenctl/lib/utils.py @ 1372:5a6113c65ead

bitkeeper revision 1.891.1.12 (40a248b0WTGoOa9206iWkyGN0mTPNw)

Allow forcing of IRQ trigger-type to edge or level
(NB. DANGEROUS!).
author kaf24@scramble.cl.cam.ac.uk
date Wed May 12 15:54:24 2004 +0000 (2004-05-12)
parents 00059c1948cf
children 0fab6364d23b
line source
1 import os, re, socket, string, sys, tempfile
3 ##### Module variables
5 """Location of the Virtual Disk management database.
6 defaults to /var/db/xen_vdisks.sqlite
7 """
8 VD_DB_FILE = "/var/db/xen_vdisks.sqlite"
10 """VBD expertise level - determines the strictness of the sanity checking.
11 This mode determines the level of complaints when disk sharing occurs
12 through the current VBD mappings.
13 0 - only allow shared mappings if both domains have r/o access (always OK)
14 1 - also allow sharing with one dom r/w and the other r/o
15 2 - allow sharing with both doms r/w
16 """
17 VBD_EXPERT_MODE = 0
19 ##### Module initialisation
21 try:
22 # try to import sqlite (not everyone will have it installed)
23 import sqlite
24 except ImportError:
25 # on failure, just catch the error, don't do anything
26 pass
29 ##### Networking-related functions
31 def get_current_ipaddr(dev='eth0'):
32 """Return a string containing the primary IP address for the given
33 network interface (default 'eth0').
34 """
35 fd = os.popen( '/sbin/ifconfig ' + dev + ' 2>/dev/null' )
36 lines = fd.readlines()
37 for line in lines:
38 m = re.search( '^\s+inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*',
39 line )
40 if m:
41 return m.group(1)
42 return None
44 def get_current_ipmask(dev='eth0'):
45 """Return a string containing the primary IP netmask for the given
46 network interface (default 'eth0').
47 """
48 fd = os.popen( '/sbin/ifconfig ' + dev + ' 2>/dev/null' )
49 lines = fd.readlines()
50 for line in lines:
51 m = re.search( '^.+Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*',
52 line )
53 if m:
54 return m.group(1)
55 return None
57 def get_current_ipgw():
58 """Return a string containing the default IP gateway."""
59 fd = os.popen( '/sbin/route -n' )
60 lines = fd.readlines()
61 for line in lines:
62 m = re.search( '^0.0.0.0+\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)' +
63 '\s+0.0.0.0+\s+\S*G.*', line )
64 if m:
65 return m.group(1)
66 return None
68 def setup_vfr_rules_for_vif(dom,vif,addr):
69 """Takes a tuple ( domain-id, vif-id, ip-addr ), where the ip-addr
70 is expressed as a textual dotted quad, and set up appropriate routing
71 rules in Xen. No return value.
72 """
73 fd = os.open( '/proc/xen/vfr', os.O_WRONLY )
74 if ( re.search( '169\.254', addr) ):
75 os.write( fd, 'ADD ACCEPT srcaddr=' + addr +
76 ' srcaddrmask=255.255.255.255' +
77 ' srcdom=' + str(dom) + ' srcidx=' + str(vif) +
78 ' dstdom=0 dstidx=0 proto=any\n' )
79 else:
80 os.write( fd, 'ADD ACCEPT srcaddr=' + addr +
81 ' srcaddrmask=255.255.255.255' +
82 ' srcdom=' + str(dom) + ' srcidx=' + str(vif) +
83 ' dst=PHYS proto=any\n' )
84 os.write( fd, 'ADD ACCEPT dstaddr=' + addr +
85 ' dstaddrmask=255.255.255.255' +
86 ' src=ANY' +
87 ' dstdom=' + str(dom) + ' dstidx=' + str(vif) +
88 ' proto=any\n' )
89 os.close( fd )
90 return None
92 def add_offset_to_ip( ip, off ):
93 l = string.split(ip,'.')
94 a = ( (string.atoi(l[0])<<24) | (string.atoi(l[1])<<16) |
95 (string.atoi(l[2])<<8) | string.atoi(l[3]) ) + off
97 return '%d.%d.%d.%d' % ( ((a>>24)&0xff), ((a>>16)&0xff),
98 ((a>>8)&0xff), (a&0xff) )
100 def check_subnet( ip, network, netmask ):
101 l = string.split(ip,'.')
102 n_ip = ( (string.atoi(l[0])<<24) | (string.atoi(l[1])<<16) |
103 (string.atoi(l[2])<<8) | string.atoi(l[3]) )
105 l = string.split(network,'.')
106 n_net = ( (string.atoi(l[0])<<24) | (string.atoi(l[1])<<16) |
107 (string.atoi(l[2])<<8) | string.atoi(l[3]) )
109 l = string.split(netmask,'.')
110 n_mask = ( (string.atoi(l[0])<<24) | (string.atoi(l[1])<<16) |
111 (string.atoi(l[2])<<8) | string.atoi(l[3]) )
113 return (n_ip&n_mask)==(n_net&n_mask)
116 ##### VBD-related Functions
118 def blkdev_name_to_number(name):
119 """Take the given textual block-device name (e.g., '/dev/sda1',
120 'hda') and return the device number used by the OS. """
122 if not re.match( '/dev/', name ):
123 name = '/dev/' + name
125 return os.stat(name).st_rdev
127 # lookup_blkdev_partn_info( '/dev/sda3' )
128 def lookup_raw_partn(partition):
129 """Take the given block-device name (e.g., '/dev/sda1', 'hda')
130 and return a dictionary { device, start_sector,
131 nr_sectors, type }
132 device: Device number of the given partition
133 start_sector: Index of first sector of the partition
134 nr_sectors: Number of sectors comprising this partition
135 type: 'Disk' or identifying name for partition type
136 """
138 if not re.match( '/dev/', partition ):
139 partition = '/dev/' + partition
141 drive = re.split( '[0-9]', partition )[0]
143 if drive == partition:
144 fd = os.popen( '/sbin/sfdisk -s ' + drive + ' 2>/dev/null' )
145 line = fd.readline()
146 if line:
147 return [ { 'device' : blkdev_name_to_number(drive),
148 'start_sector' : long(0),
149 'nr_sectors' : long(line) * 2,
150 'type' : 'Disk' } ]
151 return None
153 # determine position on disk
154 fd = os.popen( '/sbin/sfdisk -d ' + drive + ' 2>/dev/null' )
156 #['/dev/sda3 : start= 16948575, size=16836120, Id=83, bootable\012']
157 lines = fd.readlines()
158 for line in lines:
159 m = re.search( '^' + partition + '\s*: start=\s*([0-9]+), ' +
160 'size=\s*([0-9]+), Id=\s*(\S+).*$', line)
161 if m:
162 return [ { 'device' : blkdev_name_to_number(drive),
163 'start_sector' : long(m.group(1)),
164 'nr_sectors' : long(m.group(2)),
165 'type' : m.group(3) } ]
167 return None
169 def lookup_disk_uname( uname ):
170 """Lookup a list of segments for either a physical or a virtual device.
171 uname [string]: name of the device in the format \'vd:id\' for a virtual
172 disk, or \'phy:dev\' for a physical device
173 returns [list of dicts]: list of extents that make up the named device
174 """
175 ( type, d_name ) = string.split( uname, ':' )
177 if type == "phy":
178 segments = lookup_raw_partn( d_name )
179 elif type == "vd":
180 segments = vd_lookup( d_name )
182 return segments
185 ##### Management of the Xen control daemon
186 ##### (c) Keir Fraser, University of Cambridge
188 def xend_control_message( message ):
189 """Takes a textual control message and sends it to the 'xend' Xen
190 control daemon. Returns a dictionary containing the daemon's multi-part
191 response."""
192 tmpdir = tempfile.mkdtemp()
193 try:
194 ctl = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM, 0)
195 ctl.bind(tmpdir+'/sock')
196 ctl.sendto(message, '/var/run/xend/management_sock')
197 data, addr = ctl.recvfrom(2048)
198 ctl.close()
199 finally:
200 if os.path.exists(tmpdir+'/sock'):
201 os.unlink(tmpdir+'/sock')
202 if os.path.exists(tmpdir):
203 os.rmdir(tmpdir)
204 return eval(data)
207 ##### VD Management-related functions
209 ##### By Mark Williamson, <mark.a.williamson@intel.com>
210 ##### (C) Intel Research Cambridge
212 # TODO:
213 #
214 # Plenty of room for enhancement to this functionality (contributions
215 # welcome - and then you get to have your name in the source ;-)...
216 #
217 # vd_unformat() : want facilities to unallocate virtual disk
218 # partitions, possibly migrating virtual disks of them, with checks to see if
219 # it's safe and options to force it anyway
220 #
221 # vd_create() : should have an optional argument specifying a physical
222 # disk preference - useful to allocate for guest doms to do RAID
223 #
224 # vd_undelete() : add ability to "best effort" undelete as much of a
225 # vdisk as is left in the case that some of it has already been
226 # reallocated. Some people might still be able to recover some of
227 # their data this way, even if some of the disk has disappeared.
228 #
229 # It'd be nice if we could wipe virtual disks for security purposes -
230 # should be easy to do this using dev if=/dev/{zero,random} on each
231 # extent in turn. There could be another optional flag to vd_create
232 # in order to allow this.
233 #
234 # Error codes could be more expressive - i.e. actually tell why the
235 # error occurred rather than "it broke". Currently the code avoids
236 # using exceptions to make control scripting simpler and more
237 # accessible to beginners - therefore probably should just use more
238 # return codes.
239 #
240 # Enhancements / additions to the example scripts are also welcome:
241 # some people will interact with this code mostly through those
242 # scripts.
243 #
244 # More documentation of how this stuff should be used is always nice -
245 # if you have a novel configuration that you feel isn't discussed
246 # enough in the HOWTO (which is currently a work in progress), feel
247 # free to contribute a walkthrough, or something more substantial.
248 #
251 def __vd_no_database():
252 """Called when no database found - exits with an error
253 """
254 print >> sys.stderr, "ERROR: Could not locate the database file at " + VD_DB_FILE
255 sys.exit(1)
258 def vd_format(partition, extent_size_mb):
259 """Format a partition or drive for use a virtual disk storage.
260 partition [string]: device file representing the partition
261 extent_size_mb [string]: extent size in megabytes to use on this disk
262 """
264 if not os.path.isfile(VD_DB_FILE):
265 vd_init_db(VD_DB_FILE)
267 if not re.match( '/dev/', partition ):
268 partition = '/dev/' + partition
270 cx = sqlite.connect(VD_DB_FILE)
271 cu = cx.cursor()
273 cu.execute("select * from vdisk_part where partition = \'"
274 + partition + "\'")
275 row = cu.fetchone()
277 extent_size = extent_size_mb * 2048 # convert megabytes to sectors
279 if not row:
280 part_info = lookup_raw_partn(partition)[0]
282 cu.execute("INSERT INTO vdisk_part(partition, part_id, extent_size) " +
283 "VALUES ( \'" + partition + "\', "
284 + str(blkdev_name_to_number(partition))
285 + ", " + str(extent_size) + ")")
288 cu.execute("SELECT max(vdisk_extent_no) FROM vdisk_extents "
289 + "WHERE vdisk_id = 0")
291 max_id, = cu.fetchone()
293 if max_id != None:
294 new_id = max_id + 1
295 else:
296 new_id = 0
298 num_extents = part_info['nr_sectors'] / extent_size
300 for i in range(num_extents):
301 sql ="""INSERT INTO vdisk_extents(vdisk_extent_no, vdisk_id,
302 part_id, part_extent_no)
303 VALUES ("""+ str(new_id + i) + ", 0, "\
304 + str(blkdev_name_to_number(partition))\
305 + ", " + str(num_extents - (i + 1)) + ")"
306 cu.execute(sql)
308 cx.commit()
309 cx.close()
310 return 0
313 def vd_create(size_mb, expiry):
314 """Create a new virtual disk.
315 size_mb [int]: size in megabytes for the new virtual disk
316 expiry [int]: expiry time in seconds from now
317 """
319 if not os.path.isfile(VD_DB_FILE):
320 __vd_no_database()
322 cx = sqlite.connect(VD_DB_FILE)
323 cu = cx.cursor()
325 size = size_mb * 2048
327 cu.execute("SELECT max(vdisk_id) FROM vdisks")
328 max_id, = cu.fetchone()
329 new_id = int(max_id) + 1
331 # fetch a list of extents from the expired disks, along with information
332 # about their size
333 cu.execute("""SELECT vdisks.vdisk_id, vdisk_extent_no, part_extent_no,
334 vdisk_extents.part_id, extent_size
335 FROM vdisks NATURAL JOIN vdisk_extents
336 NATURAL JOIN vdisk_part
337 WHERE expires AND expiry_time <= datetime('now')
338 ORDER BY expiry_time ASC, vdisk_extent_no DESC
339 """) # aims to reuse the last extents
340 # from the longest-expired disks first
342 allocated = 0
344 if expiry:
345 expiry_ts = "datetime('now', '" + str(expiry) + " seconds')"
346 expires = 1
347 else:
348 expiry_ts = "NULL"
349 expires = 0
351 # we'll use this to build the SQL statement we want
352 building_sql = "INSERT INTO vdisks(vdisk_id, size, expires, expiry_time)" \
353 +" VALUES ("+str(new_id)+", "+str(size)+ ", " \
354 + str(expires) + ", " + expiry_ts + "); "
356 counter = 0
358 while allocated < size:
359 row = cu.fetchone()
360 if not row:
361 print "ran out of space, having allocated %d meg of %d" % (allocated, size)
362 cx.close()
363 return -1
366 (vdisk_id, vdisk_extent_no, part_extent_no, part_id, extent_size) = row
367 allocated += extent_size
368 building_sql += "UPDATE vdisk_extents SET vdisk_id = " + str(new_id) \
369 + ", " + "vdisk_extent_no = " + str(counter) \
370 + " WHERE vdisk_extent_no = " + str(vdisk_extent_no) \
371 + " AND vdisk_id = " + str(vdisk_id) + "; "
373 counter += 1
376 # this will execute the SQL query we build to store details of the new
377 # virtual disk and allocate space to it print building_sql
378 cu.execute(building_sql)
380 cx.commit()
381 cx.close()
382 return str(new_id)
385 def vd_lookup(id):
386 """Lookup a Virtual Disk by ID.
387 id [string]: a virtual disk identifier
388 Returns [list of dicts]: a list of extents as dicts, containing fields:
389 device : Linux device number of host disk
390 start_sector : within the device
391 nr_sectors : size of this extent
392 type : set to \'VD Extent\'
394 part_device : Linux device no of host partition
395 part_start_sector : within the partition
396 """
398 if not os.path.isfile(VD_DB_FILE):
399 __vd_no_database()
401 cx = sqlite.connect(VD_DB_FILE)
402 cu = cx.cursor()
404 cu.execute("-- types int")
405 cu.execute("""SELECT COUNT(*)
406 FROM vdisks
407 WHERE (expiry_time > datetime('now') OR NOT expires)
408 AND vdisk_id = """ + id)
409 count, = cu.fetchone()
411 if not count:
412 cx.close()
413 return None
415 cu.execute("SELECT size from vdisks WHERE vdisk_id = " + id)
416 real_size, = cu.fetchone()
418 # This query tells PySQLite how to convert the data returned from the
419 # following query - the use of the multiplication confuses it otherwise ;-)
420 # This row is significant to PySQLite but is syntactically an SQL comment.
422 cu.execute("-- types str, int, int, int")
424 # This SQL statement is designed so that when the results are fetched they
425 # will be in the right format to return immediately.
426 cu.execute("""SELECT partition, vdisk_part.part_id,
427 round(part_extent_no * extent_size) as start,
428 extent_size
430 FROM vdisks NATURAL JOIN vdisk_extents
431 NATURAL JOIN vdisk_part
433 WHERE vdisk_extents.vdisk_id = """ + id
434 + " ORDER BY vdisk_extents.vdisk_extent_no ASC"
435 )
437 extent_tuples = cu.fetchall()
439 # use this function to map the results from the database into a dict
440 # list of extents, for consistency with the rest of the code
441 def transform ((partition, part_device, part_offset, nr_sectors)):
442 return {
443 # the disk device this extent is on - for passing to Xen
444 'device' : lookup_raw_partn(partition)[0]['device'],
445 # the offset of this extent within the disk - for passing to Xen
446 'start_sector' : long(part_offset + lookup_raw_partn(partition)[0]['start_sector']),
447 # extent size, in sectors
448 'nr_sectors' : nr_sectors,
449 # partition device this extent is on (useful to know for xenctl.utils fns)
450 'part_device' : part_device,
451 # start sector within this partition (useful to know for xenctl.utils fns)
452 'part_start_sector' : part_offset,
453 # type of this extent - handy to know
454 'type' : 'VD Extent' }
456 cx.commit()
457 cx.close()
459 extent_dicts = map(transform, extent_tuples)
461 # calculate the over-allocation in sectors (happens because
462 # we allocate whole extents)
463 allocated_size = 0
464 for i in extent_dicts:
465 allocated_size += i['nr_sectors']
467 over_allocation = allocated_size - real_size
469 # trim down the last extent's length so the resulting VBD will be the
470 # size requested, rather than being rounded up to the nearest extent
471 extent_dicts[len(extent_dicts) - 1]['nr_sectors'] -= over_allocation
473 return extent_dicts
476 def vd_enlarge(vdisk_id, extra_size_mb):
477 """Create a new virtual disk.
478 vdisk_id [string] : ID of the virtual disk to enlarge
479 extra_size_mb [int]: size in megabytes to increase the allocation by
480 returns [int] : 0 on success, otherwise non-zero
481 """
483 if not os.path.isfile(VD_DB_FILE):
484 __vd_no_database()
486 cx = sqlite.connect(VD_DB_FILE)
487 cu = cx.cursor()
489 extra_size = extra_size_mb * 2048
491 cu.execute("-- types int")
492 cu.execute("SELECT COUNT(*) FROM vdisks WHERE vdisk_id = " + vdisk_id
493 + " AND (expiry_time > datetime('now') OR NOT expires)")
494 count, = cu.fetchone()
496 if not count: # no such vdisk
497 cx.close()
498 return -1
500 cu.execute("-- types int")
501 cu.execute("""SELECT SUM(extent_size)
502 FROM vdisks NATURAL JOIN vdisk_extents
503 NATURAL JOIN vdisk_part
504 WHERE vdisks.vdisk_id = """ + vdisk_id)
506 real_size, = cu.fetchone() # get the true allocated size
508 cu.execute("-- types int")
509 cu.execute("SELECT size FROM vdisks WHERE vdisk_id = " + vdisk_id)
511 old_size, = cu.fetchone()
514 cu.execute("--- types int")
515 cu.execute("""SELECT MAX(vdisk_extent_no)
516 FROM vdisk_extents
517 WHERE vdisk_id = """ + vdisk_id)
519 counter = cu.fetchone()[0] + 1 # this stores the extent numbers
522 # because of the extent-based allocation, the VD may already have more
523 # allocated space than they asked for. Find out how much we really
524 # need to add.
525 add_size = extra_size + old_size - real_size
527 # fetch a list of extents from the expired disks, along with information
528 # about their size
529 cu.execute("""SELECT vdisks.vdisk_id, vdisk_extent_no, part_extent_no,
530 vdisk_extents.part_id, extent_size
531 FROM vdisks NATURAL JOIN vdisk_extents
532 NATURAL JOIN vdisk_part
533 WHERE expires AND expiry_time <= datetime('now')
534 ORDER BY expiry_time ASC, vdisk_extent_no DESC
535 """) # aims to reuse the last extents
536 # from the longest-expired disks first
538 allocated = 0
540 building_sql = "UPDATE vdisks SET size = " + str(old_size + extra_size)\
541 + " WHERE vdisk_id = " + vdisk_id + "; "
543 while allocated < add_size:
544 row = cu.fetchone()
545 if not row:
546 cx.close()
547 return -1
549 (dead_vd_id, vdisk_extent_no, part_extent_no, part_id, extent_size) = row
550 allocated += extent_size
551 building_sql += "UPDATE vdisk_extents SET vdisk_id = " + vdisk_id \
552 + ", " + "vdisk_extent_no = " + str(counter) \
553 + " WHERE vdisk_extent_no = " + str(vdisk_extent_no) \
554 + " AND vdisk_id = " + str(dead_vd_id) + "; "
556 counter += 1
559 # this will execute the SQL query we build to store details of the new
560 # virtual disk and allocate space to it print building_sql
561 cu.execute(building_sql)
563 cx.commit()
564 cx.close()
565 return 0
568 def vd_undelete(vdisk_id, expiry_time):
569 """Create a new virtual disk.
570 vdisk_id [int]: size in megabytes for the new virtual disk
571 expiry_time [int]: expiry time, in seconds from now
572 returns [int]: zero on success, non-zero on failure
573 """
575 if not os.path.isfile(VD_DB_FILE):
576 __vd_no_database()
578 if vdisk_id == '0': # undeleting vdisk 0 isn't sane!
579 return -1
581 cx = sqlite.connect(VD_DB_FILE)
582 cu = cx.cursor()
584 cu.execute("-- types int")
585 cu.execute("SELECT COUNT(*) FROM vdisks WHERE vdisk_id = " + vdisk_id)
586 count, = cu.fetchone()
588 if not count:
589 cx.close()
590 return -1
592 cu.execute("-- types int")
593 cu.execute("""SELECT SUM(extent_size)
594 FROM vdisks NATURAL JOIN vdisk_extents
595 NATURAL JOIN vdisk_part
596 WHERE vdisks.vdisk_id = """ + vdisk_id)
598 real_size, = cu.fetchone() # get the true allocated size
601 cu.execute("-- types int")
602 cu.execute("SELECT size FROM vdisks WHERE vdisk_id = " + vdisk_id)
604 old_size, = cu.fetchone()
606 if real_size < old_size:
607 cx.close()
608 return -1
610 if expiry_time == 0:
611 expires = '0'
612 else:
613 expires = '1'
615 # this will execute the SQL query we build to store details of the new
616 # virtual disk and allocate space to it print building_sql
617 cu.execute("UPDATE vdisks SET expiry_time = datetime('now','"
618 + str(expiry_time) + " seconds'), expires = " + expires
619 + " WHERE vdisk_id = " + vdisk_id)
621 cx.commit()
622 cx.close()
623 return 0
628 def vd_list():
629 """Lists all the virtual disks registered in the system.
630 returns [list of dicts]
631 """
633 if not os.path.isfile(VD_DB_FILE):
634 __vd_no_database()
636 cx = sqlite.connect(VD_DB_FILE)
637 cu = cx.cursor()
639 cu.execute("""SELECT vdisk_id, size, expires, expiry_time
640 FROM vdisks
641 WHERE (NOT expires) OR expiry_time > datetime('now')
642 """)
644 ret = cu.fetchall()
646 cx.close()
648 def makedicts((vdisk_id, size, expires, expiry_time)):
649 return { 'vdisk_id' : str(vdisk_id), 'size': size,
650 'expires' : expires, 'expiry_time' : expiry_time }
652 return map(makedicts, ret)
655 def vd_refresh(id, expiry):
656 """Change the expiry time of a virtual disk.
657 id [string] : a virtual disk identifier
658 expiry [int] : expiry time in seconds from now (0 = never expire)
659 returns [int]: zero on success, non-zero on failure
660 """
662 if not os.path.isfile(VD_DB_FILE):
663 __vd_no_database()
665 cx = sqlite.connect(VD_DB_FILE)
666 cu = cx.cursor()
668 cu.execute("-- types int")
669 cu.execute("SELECT COUNT(*) FROM vdisks WHERE vdisk_id = " + id
670 + " AND (expiry_time > datetime('now') OR NOT expires)")
671 count, = cu.fetchone()
673 if not count:
674 cx.close()
675 return -1
677 if expiry:
678 expires = 1
679 expiry_ts = "datetime('now', '" + str(expiry) + " seconds')"
680 else:
681 expires = 0
682 expiry_ts = "NULL"
684 cu.execute("UPDATE vdisks SET expires = " + str(expires)
685 + ", expiry_time = " + expiry_ts
686 + " WHERE (expiry_time > datetime('now') OR NOT expires)"
687 + " AND vdisk_id = " + id)
689 cx.commit()
690 cx.close()
692 return 0
695 def vd_delete(id):
696 """Deletes a Virtual Disk, making its extents available for future VDs.
697 id [string] : identifier for the virtual disk to delete
698 returns [int] : 0 on success, -1 on failure (VD not found
699 or already deleted)
700 """
702 if not os.path.isfile(VD_DB_FILE):
703 __vd_no_database()
705 cx = sqlite.connect(VD_DB_FILE)
706 cu = cx.cursor()
708 cu.execute("-- types int")
709 cu.execute("SELECT COUNT(*) FROM vdisks WHERE vdisk_id = " + id
710 + " AND (expiry_time > datetime('now') OR NOT expires)")
711 count, = cu.fetchone()
713 if not count:
714 cx.close()
715 return -1
717 cu.execute("UPDATE vdisks SET expires = 1, expiry_time = datetime('now')"
718 + " WHERE vdisk_id = " + id)
720 cx.commit()
721 cx.close()
723 return 0
726 def vd_freespace():
727 """Returns the amount of free space available for new virtual disks, in MB
728 returns [int] : free space for VDs in MB
729 """
731 if not os.path.isfile(VD_DB_FILE):
732 __vd_no_database()
734 cx = sqlite.connect(VD_DB_FILE)
735 cu = cx.cursor()
737 cu.execute("-- types int")
739 cu.execute("""SELECT SUM(extent_size)
740 FROM vdisks NATURAL JOIN vdisk_extents
741 NATURAL JOIN vdisk_part
742 WHERE expiry_time <= datetime('now') AND expires""")
744 sum, = cu.fetchone()
746 cx.close()
748 return sum / 2048
751 def vd_init_db(path):
752 """Initialise the VD SQLite database
753 path [string]: path to the SQLite database file
754 """
756 cx = sqlite.connect(path)
757 cu = cx.cursor()
759 cu.execute(
760 """CREATE TABLE vdisk_extents
761 ( vdisk_extent_no INT,
762 vdisk_id INT,
763 part_id INT,
764 part_extent_no INT )
765 """)
767 cu.execute(
768 """CREATE TABLE vdisk_part
769 ( part_id INT,
770 partition VARCHAR,
771 extent_size INT )
772 """)
774 cu.execute(
775 """CREATE TABLE vdisks
776 ( vdisk_id INT,
777 size INT,
778 expires BOOLEAN,
779 expiry_time TIMESTAMP )
780 """)
783 cu.execute(
784 """INSERT INTO vdisks ( vdisk_id, size, expires, expiry_time )
785 VALUES ( 0, 0, 1, datetime('now') )
786 """)
788 cx.commit()
789 cx.close()
791 VD_DB_FILE = path
795 def vd_cp_to_file(vdisk_id,filename):
796 """Writes the contents of a specified vdisk out into a disk file, leaving
797 the original copy in the virtual disk pool."""
799 cx = sqlite.connect(VD_DB_FILE)
800 cu = cx.cursor()
802 extents = vd_lookup(vdisk_id)
804 if not extents:
805 return -1
807 file_idx = 0 # index into source file, in sectors
809 for i in extents:
810 cu.execute("""SELECT partition, extent_size FROM vdisk_part
811 WHERE part_id = """ + str(i['part_device']))
813 (partition, extent_size) = cu.fetchone()
815 os.system("dd bs=1b if=" + partition + " of=" + filename
816 + " skip=" + str(i['part_start_sector'])
817 + " seek=" + str(file_idx)
818 + " count=" + str(i['nr_sectors'])
819 + " > /dev/null")
821 file_idx += i['nr_sectors']
823 cx.close()
825 return 0 # should return -1 if something breaks
828 def vd_mv_to_file(vdisk_id,filename):
829 """Writes a vdisk out into a disk file and frees the space originally
830 taken within the virtual disk pool.
831 vdisk_id [string]: ID of the vdisk to write out
832 filename [string]: file to write vdisk contents out to
833 returns [int]: zero on success, nonzero on failure
834 """
836 if vd_cp_to_file(vdisk_id,filename):
837 return -1
839 if vd_delete(vdisk_id):
840 return -1
842 return 0
845 def vd_read_from_file(filename,expiry):
846 """Reads the contents of a file directly into a vdisk, which is
847 automatically allocated to fit.
848 filename [string]: file to read disk contents from
849 returns [string] : vdisk ID for the destination vdisk
850 """
852 size_bytes = os.stat(filename).st_size
854 (size_mb,leftover) = divmod(size_bytes,1048580) # size in megabytes
855 if leftover > 0: size_mb += 1 # round up if not an exact number of MB
857 vdisk_id = vd_create(size_mb, expiry)
859 if vdisk_id < 0:
860 return -1
862 cx = sqlite.connect(VD_DB_FILE)
863 cu = cx.cursor()
865 cu.execute("""SELECT partition, extent_size, part_extent_no
866 FROM vdisk_part NATURAL JOIN vdisk_extents
867 WHERE vdisk_id = """ + vdisk_id + """
868 ORDER BY vdisk_extent_no ASC""")
870 extents = cu.fetchall()
872 size_sectors = size_mb * 2048 # for feeding to dd
874 file_idx = 0 # index into source file, in sectors
876 def write_extent_to_vd((partition, extent_size, part_extent_no),
877 file_idx, filename):
878 """Write an extent out to disk and update file_idx"""
880 os.system("dd bs=512 if=" + filename + " of=" + partition
881 + " skip=" + str(file_idx)
882 + " seek=" + str(part_extent_no * extent_size)
883 + " count=" + str(min(extent_size, size_sectors - file_idx))
884 + " > /dev/null")
886 return extent_size
888 for i in extents:
889 file_idx += write_extent_to_vd(i, file_idx, filename)
891 cx.close()
893 return vdisk_id
898 def vd_extents_validate(new_extents,new_writeable):
899 """Validate the extents against the existing extents.
900 Complains if the list supplied clashes against the extents that
901 are already in use in the system.
902 new_extents [list of dicts]: list of new extents, as dicts
903 new_writeable [int]: 1 if they are to be writeable, 0 otherwise
904 returns [int]: either the expertise level of the mapping if it doesn't
905 exceed VBD_EXPERT_MODE or -1 if it does (error)
906 """
908 import Xc # this is only needed in this function
910 xc = Xc.new()
912 ##### Probe for explicitly created virtual disks and build a list
913 ##### of extents for comparison with the ones that are being added
915 probe = xc.vbd_probe()
917 old_extents = [] # this will hold a list of all existing extents and
918 # their writeable status, as a list of (device,
919 # start, size, writeable?) tuples
921 for vbd in probe:
922 this_vbd_extents = xc.vbd_getextents(vbd['dom'],vbd['vbd'])
923 for vbd_ext in this_vbd_extents:
924 vbd_ext['writeable'] = vbd['writeable']
925 old_extents.append(vbd_ext)
927 ##### Now scan /proc/mounts for compile a list of extents corresponding to
928 ##### any devices mounted in DOM0. This list is added on to old_extents
930 regexp = re.compile("/dev/(\S*) \S* \S* (..).*")
931 fd = open('/proc/mounts', "r")
933 while True:
934 line = fd.readline()
935 if not line: # if we've run out of lines then stop reading
936 break
938 m = regexp.match(line)
940 # if the regexp didn't match then it's probably a line we don't
941 # care about - skip to next line
942 if not m:
943 continue
945 # lookup the device
946 ext_list = lookup_raw_partn(m.group(1))
948 # if lookup failed, skip to next mounted device
949 if not ext_list:
950 continue
952 # set a writeable flag as appropriate
953 for ext in ext_list:
954 ext['writeable'] = m.group(2) == 'rw'
956 # now we've got here, the contents of ext_list are in a
957 # suitable format to be added onto the old_extents list, ready
958 # for checking against the new extents
960 old_extents.extend(ext_list)
962 fd.close() # close /proc/mounts
964 ##### By this point, old_extents contains a list of extents, in
965 ##### dictionary format corresponding to every extent of physical
966 ##### disk that's either part of an explicitly created VBD, or is
967 ##### mounted under DOM0. We now check these extents against the
968 ##### proposed additions in new_extents, to see if a conflict will
969 ##### happen if they are added with write status new_writeable
971 level = 0 # this'll accumulate the max warning level
973 # Search for clashes between the new extents and the old ones
974 # Takes time O(len(new_extents) * len(old_extents))
975 for new_ext in new_extents:
976 for old_ext in old_extents:
977 if(new_ext['device'] == old_ext['device']):
979 new_ext_start = new_ext['start_sector']
980 new_ext_end = new_ext_start + new_ext['nr_sectors'] - 1
982 old_ext_start = old_ext['start_sector']
983 old_ext_end = old_ext_start + old_ext['nr_sectors'] - 1
985 if((old_ext_start <= new_ext_start <= old_ext_end) or
986 (old_ext_start <= new_ext_end <= old_ext_end)):
987 if (not old_ext['writeable']) and new_writeable:
988 level = max(1,level)
989 elif old_ext['writeable'] and (not new_writeable):
990 level = max(1,level)
991 elif old_ext['writeable'] and new_writeable:
992 level = max(2,level)
995 ##### level now holds the warning level incurred by the current
996 ##### VBD setup and we complain appropriately to the user
999 if level == 1:
1000 print >> sys.stderr, """Warning: one or more hard disk extents
1001 writeable by one domain are also readable by another."""
1002 elif level == 2:
1003 print >> sys.stderr, """Warning: one or more hard disk extents are
1004 writeable by two or more domains simultaneously."""
1006 if level > VBD_EXPERT_MODE:
1007 print >> sys.stderr, """ERROR: This kind of disk sharing is not allowed
1008 at the current safety level (%d).""" % VBD_EXPERT_MODE
1009 level = -1
1011 return level