~ubuntu-branches/ubuntu/utopic/xen/utopic

« back to all changes in this revision

Viewing changes to tools/xc/py/XenoUtil.py

  • Committer: Bazaar Package Importer
  • Author(s): Bastian Blank
  • Date: 2010-05-06 15:47:38 UTC
  • mto: (1.3.1) (15.1.1 sid) (4.1.1 experimental)
  • mto: This revision was merged to the branch mainline in revision 3.
  • Revision ID: james.westby@ubuntu.com-20100506154738-agoz0rlafrh1fnq7
Tags: upstream-4.0.0
ImportĀ upstreamĀ versionĀ 4.0.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
import string, re, os, sys
2
 
 
3
 
##### Module variables
4
 
 
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"
9
 
 
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
18
 
 
19
 
##### Module initialisation
20
 
 
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
27
 
 
28
 
 
29
 
##### Networking-related functions
30
 
 
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
43
 
 
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
56
 
 
57
 
def get_current_ipgw(dev='eth0'):
58
 
    """Return a string containing the IP gateway for the given
59
 
    network interface (default 'eth0').
60
 
    """
61
 
    fd = os.popen( '/sbin/route -n' )
62
 
    lines = fd.readlines()
63
 
    for line in lines:
64
 
        m = re.search( '^\S+\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)' +
65
 
                       '\s+\S+\s+\S*G.*' + dev + '.*', line )
66
 
        if m:
67
 
            return m.group(1)
68
 
    return None
69
 
 
70
 
def setup_vfr_rules_for_vif(dom,vif,addr):
71
 
    """Takes a tuple ( domain-id, vif-id, ip-addr ), where the ip-addr
72
 
    is expressed as a textual dotted quad, and set up appropriate routing
73
 
    rules in Xen. No return value.
74
 
    """
75
 
    fd = os.open( '/proc/xeno/vfr', os.O_WRONLY )
76
 
    if ( re.search( '169\.254', addr) ):
77
 
        os.write( fd, 'ADD ACCEPT srcaddr=' + addr +
78
 
                  ' srcaddrmask=255.255.255.255' +
79
 
                  ' srcdom=' + str(dom) + ' srcidx=' + str(vif) +
80
 
                  ' dstdom=0 dstidx=0 proto=any\n' )
81
 
    else:
82
 
        os.write( fd, 'ADD ACCEPT srcaddr=' + addr +
83
 
                  ' srcaddrmask=255.255.255.255' +
84
 
                  ' srcdom=' + str(dom) + ' srcidx=' + str(vif) +
85
 
                  ' dst=PHYS proto=any\n' )
86
 
    os.write( fd, 'ADD ACCEPT dstaddr=' + addr +
87
 
              ' dstaddrmask=255.255.255.255' +
88
 
              ' src=ANY' +
89
 
              ' dstdom=' + str(dom) + ' dstidx=' + str(vif) +
90
 
              ' proto=any\n' )
91
 
    os.close( fd )
92
 
    return None
93
 
 
94
 
def add_offset_to_ip( ip, off ):
95
 
    l = string.split(ip,'.')
96
 
    a = ( (string.atoi(l[0])<<24) | (string.atoi(l[1])<<16) | 
97
 
          (string.atoi(l[2])<<8)  | string.atoi(l[3]) ) + off
98
 
    
99
 
    return '%d.%d.%d.%d' % ( ((a>>24)&0xff), ((a>>16)&0xff),
100
 
                             ((a>>8)&0xff), (a&0xff) )
101
 
 
102
 
def check_subnet( ip, network, netmask ):
103
 
    l = string.split(ip,'.')
104
 
    n_ip = ( (string.atoi(l[0])<<24) | (string.atoi(l[1])<<16) | 
105
 
           (string.atoi(l[2])<<8)  | string.atoi(l[3]) ) 
106
 
 
107
 
    l = string.split(network,'.')
108
 
    n_net = ( (string.atoi(l[0])<<24) | (string.atoi(l[1])<<16) | 
109
 
           (string.atoi(l[2])<<8)  | string.atoi(l[3]) )
110
 
 
111
 
    l = string.split(netmask,'.')
112
 
    n_mask = ( (string.atoi(l[0])<<24) | (string.atoi(l[1])<<16) | 
113
 
           (string.atoi(l[2])<<8)  | string.atoi(l[3]) )
114
 
    
115
 
    return (n_ip&n_mask)==(n_net&n_mask)
116
 
 
117
 
 
118
 
##### VBD-related Functions
119
 
 
120
 
def blkdev_name_to_number(name):
121
 
    """Take the given textual block-device name (e.g., '/dev/sda1',
122
 
    'hda') and return the device number used by the OS. """
123
 
 
124
 
    if not re.match( '/dev/', name ):
125
 
        name = '/dev/' + name
126
 
        
127
 
    return os.stat(name).st_rdev
128
 
 
129
 
# lookup_blkdev_partn_info( '/dev/sda3' )
130
 
def lookup_raw_partn(partition):
131
 
    """Take the given block-device name (e.g., '/dev/sda1', 'hda')
132
 
    and return a dictionary { device, start_sector,
133
 
    nr_sectors, type }
134
 
        device:       Device number of the given partition
135
 
        start_sector: Index of first sector of the partition
136
 
        nr_sectors:   Number of sectors comprising this partition
137
 
        type:         'Disk' or identifying name for partition type
138
 
    """
139
 
 
140
 
    if not re.match( '/dev/', partition ):
141
 
        partition = '/dev/' + partition
142
 
 
143
 
    drive = re.split( '[0-9]', partition )[0]
144
 
 
145
 
    if drive == partition:
146
 
        fd = os.popen( '/sbin/sfdisk -s ' + drive + ' 2>/dev/null' )
147
 
        line = fd.readline()
148
 
        if line:
149
 
            return [ { 'device' : blkdev_name_to_number(drive),
150
 
                       'start_sector' : long(0),
151
 
                       'nr_sectors' : long(line) * 2,
152
 
                       'type' : 'Disk' } ]
153
 
        return None
154
 
 
155
 
    # determine position on disk
156
 
    fd = os.popen( '/sbin/sfdisk -d ' + drive + ' 2>/dev/null' )
157
 
 
158
 
    #['/dev/sda3 : start= 16948575, size=16836120, Id=83, bootable\012']
159
 
    lines = fd.readlines()
160
 
    for line in lines:
161
 
        m = re.search( '^' + partition + '\s*: start=\s*([0-9]+), ' +
162
 
                       'size=\s*([0-9]+), Id=\s*(\S+).*$', line)
163
 
        if m:
164
 
            return [ { 'device' : blkdev_name_to_number(drive),
165
 
                       'start_sector' : long(m.group(1)),
166
 
                       'nr_sectors' : long(m.group(2)),
167
 
                       'type' : m.group(3) } ]
168
 
    
169
 
    return None
170
 
 
171
 
def lookup_disk_uname( uname ):
172
 
    """Lookup a list of segments for either a physical or a virtual device.
173
 
    uname [string]:  name of the device in the format \'vd:id\' for a virtual
174
 
                     disk, or \'phy:dev\' for a physical device
175
 
    returns [list of dicts]: list of extents that make up the named device
176
 
    """
177
 
    ( type, d_name ) = string.split( uname, ':' )
178
 
 
179
 
    if type == "phy":
180
 
        segments = lookup_raw_partn( d_name )
181
 
    elif type == "vd":
182
 
        segments = vd_lookup( d_name )
183
 
 
184
 
    return segments
185
 
 
186
 
 
187
 
 
188
 
##### VD Management-related functions
189
 
 
190
 
##### By Mark Williamson, <mark.a.williamson@intel.com>
191
 
##### (C) Intel Research Cambridge
192
 
 
193
 
# TODO:
194
 
#
195
 
# Plenty of room for enhancement to this functionality (contributions
196
 
# welcome - and then you get to have your name in the source ;-)...
197
 
#
198
 
# vd_unformat() : want facilities to unallocate virtual disk
199
 
# partitions, possibly migrating virtual disks of them, with checks to see if
200
 
# it's safe and options to force it anyway
201
 
#
202
 
# vd_create() : should have an optional argument specifying a physical
203
 
# disk preference - useful to allocate for guest doms to do RAID
204
 
#
205
 
# vd_undelete() : add ability to "best effort" undelete as much of a
206
 
# vdisk as is left in the case that some of it has already been
207
 
# reallocated.  Some people might still be able to recover some of
208
 
# their data this way, even if some of the disk has disappeared.
209
 
#
210
 
# It'd be nice if we could wipe virtual disks for security purposes -
211
 
# should be easy to do this using dev if=/dev/{zero,random} on each
212
 
# extent in turn.  There could be another optional flag to vd_create
213
 
# in order to allow this.
214
 
#
215
 
# Error codes could be more expressive - i.e. actually tell why the
216
 
# error occurred rather than "it broke".  Currently the code avoids
217
 
# using exceptions to make control scripting simpler and more
218
 
# accessible to beginners - therefore probably should just use more
219
 
# return codes.
220
 
#
221
 
# Enhancements / additions to the example scripts are also welcome:
222
 
# some people will interact with this code mostly through those
223
 
# scripts.
224
 
#
225
 
# More documentation of how this stuff should be used is always nice -
226
 
# if you have a novel configuration that you feel isn't discussed
227
 
# enough in the HOWTO (which is currently a work in progress), feel
228
 
# free to contribute a walkthrough, or something more substantial.
229
 
#
230
 
 
231
 
 
232
 
def __vd_no_database():
233
 
    """Called when no database found - exits with an error
234
 
    """
235
 
    print >> sys.stderr, "ERROR: Could not locate the database file at " + VD_DB_FILE
236
 
    sys.exit(1)
237
 
 
238
 
 
239
 
def vd_format(partition, extent_size_mb):
240
 
    """Format a partition or drive for use a virtual disk storage.
241
 
    partition [string]: device file representing the partition
242
 
    extent_size_mb [string]: extent size in megabytes to use on this disk
243
 
    """
244
 
 
245
 
    if not os.path.isfile(VD_DB_FILE):
246
 
        vd_init_db(VD_DB_FILE)
247
 
    
248
 
    if not re.match( '/dev/', partition ):
249
 
        partition = '/dev/' + partition
250
 
 
251
 
    cx = sqlite.connect(VD_DB_FILE)
252
 
    cu = cx.cursor()
253
 
 
254
 
    cu.execute("select * from vdisk_part where partition = \'"
255
 
               + partition + "\'")
256
 
    row = cu.fetchone()
257
 
 
258
 
    extent_size = extent_size_mb * 2048 # convert megabytes to sectors
259
 
    
260
 
    if not row:
261
 
        part_info = lookup_raw_partn(partition)[0]
262
 
        
263
 
        cu.execute("INSERT INTO vdisk_part(partition, part_id, extent_size) " +
264
 
                   "VALUES ( \'" + partition + "\', "
265
 
                   + str(blkdev_name_to_number(partition))
266
 
                   + ", " + str(extent_size) + ")")
267
 
 
268
 
 
269
 
        cu.execute("SELECT max(vdisk_extent_no) FROM vdisk_extents "
270
 
                   + "WHERE vdisk_id = 0")
271
 
        
272
 
        max_id, = cu.fetchone()
273
 
 
274
 
        if max_id != None:
275
 
            new_id = max_id + 1
276
 
        else:
277
 
            new_id = 0
278
 
 
279
 
        num_extents = part_info['nr_sectors'] / extent_size
280
 
 
281
 
        for i in range(num_extents):
282
 
            sql ="""INSERT INTO vdisk_extents(vdisk_extent_no, vdisk_id,
283
 
                                              part_id, part_extent_no)
284
 
                    VALUES ("""+ str(new_id + i) + ", 0, "\
285
 
                               + str(blkdev_name_to_number(partition))\
286
 
                               + ", " + str(num_extents - (i + 1)) + ")"
287
 
            cu.execute(sql)
288
 
 
289
 
    cx.commit()
290
 
    cx.close()
291
 
    return 0
292
 
 
293
 
 
294
 
def vd_create(size_mb, expiry):
295
 
    """Create a new virtual disk.
296
 
    size_mb [int]: size in megabytes for the new virtual disk
297
 
    expiry [int]: expiry time in seconds from now
298
 
    """
299
 
 
300
 
    if not os.path.isfile(VD_DB_FILE):
301
 
        __vd_no_database()
302
 
 
303
 
    cx = sqlite.connect(VD_DB_FILE)
304
 
    cu = cx.cursor()
305
 
 
306
 
    size = size_mb * 2048
307
 
 
308
 
    cu.execute("SELECT max(vdisk_id) FROM vdisks")
309
 
    max_id, = cu.fetchone()
310
 
    new_id = int(max_id) + 1
311
 
 
312
 
    # fetch a list of extents from the expired disks, along with information
313
 
    # about their size
314
 
    cu.execute("""SELECT vdisks.vdisk_id, vdisk_extent_no, part_extent_no,
315
 
                         vdisk_extents.part_id, extent_size
316
 
                  FROM vdisks NATURAL JOIN vdisk_extents
317
 
                                                  NATURAL JOIN vdisk_part
318
 
                  WHERE expires AND expiry_time <= datetime('now')
319
 
                  ORDER BY expiry_time ASC, vdisk_extent_no DESC
320
 
               """)  # aims to reuse the last extents
321
 
                     # from the longest-expired disks first
322
 
 
323
 
    allocated = 0
324
 
 
325
 
    if expiry:
326
 
        expiry_ts = "datetime('now', '" + str(expiry) + " seconds')"
327
 
        expires = 1
328
 
    else:
329
 
        expiry_ts = "NULL"
330
 
        expires = 0
331
 
 
332
 
    # we'll use this to build the SQL statement we want
333
 
    building_sql = "INSERT INTO vdisks(vdisk_id, size, expires, expiry_time)" \
334
 
                   +" VALUES ("+str(new_id)+", "+str(size)+ ", "              \
335
 
                   + str(expires) + ", " + expiry_ts + "); "
336
 
 
337
 
    counter = 0
338
 
 
339
 
    while allocated < size:
340
 
        row = cu.fetchone()
341
 
        if not row:
342
 
            print "ran out of space, having allocated %d meg of %d" % (allocated, size)
343
 
            cx.close()
344
 
            return -1
345
 
        
346
 
 
347
 
        (vdisk_id, vdisk_extent_no, part_extent_no, part_id, extent_size) = row
348
 
        allocated += extent_size
349
 
        building_sql += "UPDATE vdisk_extents SET vdisk_id = " + str(new_id) \
350
 
                        + ", " + "vdisk_extent_no = " + str(counter)         \
351
 
                        + " WHERE vdisk_extent_no = " + str(vdisk_extent_no) \
352
 
                        + " AND vdisk_id = " + str(vdisk_id) + "; "
353
 
 
354
 
        counter += 1
355
 
        
356
 
 
357
 
    # this will execute the SQL query we build to store details of the new
358
 
    # virtual disk and allocate space to it print building_sql
359
 
    cu.execute(building_sql)
360
 
    
361
 
    cx.commit()
362
 
    cx.close()
363
 
    return str(new_id)
364
 
 
365
 
 
366
 
def vd_lookup(id):
367
 
    """Lookup a Virtual Disk by ID.
368
 
    id [string]: a virtual disk identifier
369
 
    Returns [list of dicts]: a list of extents as dicts, containing fields:
370
 
                             device : Linux device number of host disk
371
 
                             start_sector : within the device
372
 
                             nr_sectors : size of this extent
373
 
                             type : set to \'VD Extent\'
374
 
                             
375
 
                             part_device : Linux device no of host partition
376
 
                             part_start_sector : within the partition
377
 
    """
378
 
 
379
 
    if not os.path.isfile(VD_DB_FILE):
380
 
        __vd_no_database()
381
 
 
382
 
    cx = sqlite.connect(VD_DB_FILE)
383
 
    cu = cx.cursor()
384
 
 
385
 
    cu.execute("-- types int")
386
 
    cu.execute("""SELECT COUNT(*)
387
 
                  FROM vdisks
388
 
                  WHERE (expiry_time > datetime('now') OR NOT expires)
389
 
                              AND vdisk_id = """ + id)
390
 
    count, = cu.fetchone()
391
 
 
392
 
    if not count:
393
 
        cx.close()
394
 
        return None
395
 
 
396
 
    cu.execute("SELECT size from vdisks WHERE vdisk_id = " + id)
397
 
    real_size, = cu.fetchone()
398
 
  
399
 
    # This query tells PySQLite how to convert the data returned from the
400
 
    # following query - the use of the multiplication confuses it otherwise ;-)
401
 
    # This row is significant to PySQLite but is syntactically an SQL comment.
402
 
 
403
 
    cu.execute("-- types str, int, int, int")
404
 
 
405
 
    # This SQL statement is designed so that when the results are fetched they
406
 
    # will be in the right format to return immediately.
407
 
    cu.execute("""SELECT partition, vdisk_part.part_id,
408
 
                         round(part_extent_no * extent_size) as start,
409
 
                         extent_size
410
 
                         
411
 
                  FROM vdisks NATURAL JOIN vdisk_extents
412
 
                                             NATURAL JOIN vdisk_part
413
 
                                                
414
 
                  WHERE vdisk_extents.vdisk_id = """ + id
415
 
               + " ORDER BY vdisk_extents.vdisk_extent_no ASC"
416
 
               )
417
 
 
418
 
    extent_tuples = cu.fetchall()
419
 
 
420
 
    # use this function to map the results from the database into a dict
421
 
    # list of extents, for consistency with the rest of the code
422
 
    def transform ((partition, part_device, part_offset, nr_sectors)):
423
 
        return {
424
 
                 # the disk device this extent is on - for passing to Xen
425
 
                 'device' : lookup_raw_partn(partition)[0]['device'],
426
 
                 # the offset of this extent within the disk - for passing to Xen
427
 
                 'start_sector' : long(part_offset + lookup_raw_partn(partition)[0]['start_sector']),
428
 
                 # extent size, in sectors
429
 
                 'nr_sectors' : nr_sectors,
430
 
                 # partition device this extent is on (useful to know for XenoUtil fns)
431
 
                 'part_device' : part_device,
432
 
                 # start sector within this partition (useful to know for XenoUtil fns)
433
 
                 'part_start_sector' : part_offset,
434
 
                 # type of this extent - handy to know
435
 
                 'type' : 'VD Extent' }
436
 
 
437
 
    cx.commit()
438
 
    cx.close()
439
 
 
440
 
    extent_dicts = map(transform, extent_tuples)
441
 
 
442
 
    # calculate the over-allocation in sectors (happens because
443
 
    # we allocate whole extents)
444
 
    allocated_size = 0
445
 
    for i in extent_dicts:
446
 
        allocated_size += i['nr_sectors']
447
 
 
448
 
    over_allocation = allocated_size - real_size
449
 
 
450
 
    # trim down the last extent's length so the resulting VBD will be the
451
 
    # size requested, rather than being rounded up to the nearest extent
452
 
    extent_dicts[len(extent_dicts) - 1]['nr_sectors'] -= over_allocation
453
 
 
454
 
    return extent_dicts
455
 
 
456
 
 
457
 
def vd_enlarge(vdisk_id, extra_size_mb):
458
 
    """Create a new virtual disk.
459
 
    vdisk_id [string]   :    ID of the virtual disk to enlarge
460
 
    extra_size_mb  [int]:    size in megabytes to increase the allocation by
461
 
    returns  [int]      :    0 on success, otherwise non-zero
462
 
    """
463
 
 
464
 
    if not os.path.isfile(VD_DB_FILE):
465
 
        __vd_no_database()
466
 
 
467
 
    cx = sqlite.connect(VD_DB_FILE)
468
 
    cu = cx.cursor()
469
 
 
470
 
    extra_size = extra_size_mb * 2048
471
 
 
472
 
    cu.execute("-- types int")
473
 
    cu.execute("SELECT COUNT(*) FROM vdisks WHERE vdisk_id = " + vdisk_id
474
 
               + " AND (expiry_time > datetime('now') OR NOT expires)")
475
 
    count, = cu.fetchone()
476
 
 
477
 
    if not count: # no such vdisk
478
 
        cx.close()
479
 
        return -1
480
 
 
481
 
    cu.execute("-- types int")
482
 
    cu.execute("""SELECT SUM(extent_size)
483
 
                  FROM vdisks NATURAL JOIN vdisk_extents
484
 
                                         NATURAL JOIN vdisk_part
485
 
                  WHERE vdisks.vdisk_id = """ + vdisk_id)
486
 
 
487
 
    real_size, = cu.fetchone() # get the true allocated size
488
 
 
489
 
    cu.execute("-- types int")
490
 
    cu.execute("SELECT size FROM vdisks WHERE vdisk_id = " + vdisk_id)
491
 
 
492
 
    old_size, = cu.fetchone()
493
 
 
494
 
 
495
 
    cu.execute("--- types int")
496
 
    cu.execute("""SELECT MAX(vdisk_extent_no)
497
 
                  FROM vdisk_extents
498
 
                  WHERE vdisk_id = """ + vdisk_id)
499
 
 
500
 
    counter = cu.fetchone()[0] + 1 # this stores the extent numbers
501
 
 
502
 
 
503
 
    # because of the extent-based allocation, the VD may already have more
504
 
    # allocated space than they asked for.  Find out how much we really
505
 
    # need to add.
506
 
    add_size = extra_size + old_size - real_size
507
 
 
508
 
    # fetch a list of extents from the expired disks, along with information
509
 
    # about their size
510
 
    cu.execute("""SELECT vdisks.vdisk_id, vdisk_extent_no, part_extent_no,
511
 
                         vdisk_extents.part_id, extent_size
512
 
                  FROM vdisks NATURAL JOIN vdisk_extents
513
 
                                                  NATURAL JOIN vdisk_part
514
 
                  WHERE expires AND expiry_time <= datetime('now')
515
 
                  ORDER BY expiry_time ASC, vdisk_extent_no DESC
516
 
               """)  # aims to reuse the last extents
517
 
                     # from the longest-expired disks first
518
 
 
519
 
    allocated = 0
520
 
 
521
 
    building_sql = "UPDATE vdisks SET size = " + str(old_size + extra_size)\
522
 
                   + " WHERE vdisk_id = " + vdisk_id + "; "
523
 
 
524
 
    while allocated < add_size:
525
 
        row = cu.fetchone()
526
 
        if not row:
527
 
            cx.close()
528
 
            return -1
529
 
 
530
 
        (dead_vd_id, vdisk_extent_no, part_extent_no, part_id, extent_size) = row
531
 
        allocated += extent_size
532
 
        building_sql += "UPDATE vdisk_extents SET vdisk_id = " + vdisk_id    \
533
 
                        + ", " + "vdisk_extent_no = " + str(counter)         \
534
 
                        + " WHERE vdisk_extent_no = " + str(vdisk_extent_no) \
535
 
                        + " AND vdisk_id = " + str(dead_vd_id) + "; "
536
 
 
537
 
        counter += 1
538
 
        
539
 
 
540
 
    # this will execute the SQL query we build to store details of the new
541
 
    # virtual disk and allocate space to it print building_sql
542
 
    cu.execute(building_sql)
543
 
    
544
 
    cx.commit()
545
 
    cx.close()
546
 
    return 0
547
 
 
548
 
 
549
 
def vd_undelete(vdisk_id, expiry_time):
550
 
    """Create a new virtual disk.
551
 
    vdisk_id      [int]: size in megabytes for the new virtual disk
552
 
    expiry_time   [int]: expiry time, in seconds from now
553
 
    returns       [int]: zero on success, non-zero on failure
554
 
    """
555
 
 
556
 
    if not os.path.isfile(VD_DB_FILE):
557
 
        __vd_no_database()
558
 
 
559
 
    if vdisk_id == '0': #  undeleting vdisk 0 isn't sane!
560
 
        return -1
561
 
 
562
 
    cx = sqlite.connect(VD_DB_FILE)
563
 
    cu = cx.cursor()
564
 
 
565
 
    cu.execute("-- types int")
566
 
    cu.execute("SELECT COUNT(*) FROM vdisks WHERE vdisk_id = " + vdisk_id)
567
 
    count, = cu.fetchone()
568
 
 
569
 
    if not count:
570
 
        cx.close()
571
 
        return -1
572
 
 
573
 
    cu.execute("-- types int")
574
 
    cu.execute("""SELECT SUM(extent_size)
575
 
                  FROM vdisks NATURAL JOIN vdisk_extents
576
 
                                         NATURAL JOIN vdisk_part
577
 
                  WHERE vdisks.vdisk_id = """ + vdisk_id)
578
 
 
579
 
    real_size, = cu.fetchone() # get the true allocated size
580
 
 
581
 
 
582
 
    cu.execute("-- types int")
583
 
    cu.execute("SELECT size FROM vdisks WHERE vdisk_id = " + vdisk_id)
584
 
 
585
 
    old_size, = cu.fetchone()
586
 
 
587
 
    if real_size < old_size:
588
 
        cx.close()
589
 
        return -1
590
 
 
591
 
    if expiry_time == 0:
592
 
        expires = '0'
593
 
    else:
594
 
        expires = '1'
595
 
 
596
 
    # this will execute the SQL query we build to store details of the new
597
 
    # virtual disk and allocate space to it print building_sql
598
 
    cu.execute("UPDATE vdisks SET expiry_time = datetime('now','"
599
 
               + str(expiry_time) + " seconds'), expires = " + expires
600
 
               + " WHERE vdisk_id = " + vdisk_id)
601
 
    
602
 
    cx.commit()
603
 
    cx.close()
604
 
    return 0
605
 
 
606
 
 
607
 
 
608
 
 
609
 
def vd_list():
610
 
    """Lists all the virtual disks registered in the system.
611
 
    returns [list of dicts]
612
 
    """
613
 
    
614
 
    if not os.path.isfile(VD_DB_FILE):
615
 
        __vd_no_database()
616
 
 
617
 
    cx = sqlite.connect(VD_DB_FILE)
618
 
    cu = cx.cursor()
619
 
 
620
 
    cu.execute("""SELECT vdisk_id, size, expires, expiry_time
621
 
                  FROM vdisks
622
 
                  WHERE (NOT expires) OR expiry_time > datetime('now')
623
 
               """)
624
 
 
625
 
    ret = cu.fetchall()
626
 
 
627
 
    cx.close()
628
 
 
629
 
    def makedicts((vdisk_id, size, expires, expiry_time)):
630
 
        return { 'vdisk_id' : str(vdisk_id), 'size': size,
631
 
                 'expires' : expires, 'expiry_time' : expiry_time }
632
 
 
633
 
    return map(makedicts, ret)
634
 
 
635
 
 
636
 
def vd_refresh(id, expiry):
637
 
    """Change the expiry time of a virtual disk.
638
 
    id [string]  : a virtual disk identifier
639
 
    expiry [int] : expiry time in seconds from now (0 = never expire)
640
 
    returns [int]: zero on success, non-zero on failure
641
 
    """
642
 
 
643
 
    if not os.path.isfile(VD_DB_FILE):
644
 
        __vd_no_database()
645
 
    
646
 
    cx = sqlite.connect(VD_DB_FILE)
647
 
    cu = cx.cursor()
648
 
 
649
 
    cu.execute("-- types int")
650
 
    cu.execute("SELECT COUNT(*) FROM vdisks WHERE vdisk_id = " + id
651
 
               + " AND (expiry_time > datetime('now') OR NOT expires)")
652
 
    count, = cu.fetchone()
653
 
 
654
 
    if not count:
655
 
        cx.close()
656
 
        return -1
657
 
 
658
 
    if expiry:
659
 
        expires = 1
660
 
        expiry_ts = "datetime('now', '" + str(expiry) + " seconds')"
661
 
    else:
662
 
        expires = 0
663
 
        expiry_ts = "NULL"
664
 
 
665
 
    cu.execute("UPDATE vdisks SET expires = " + str(expires)
666
 
               + ", expiry_time = " + expiry_ts
667
 
               + " WHERE (expiry_time > datetime('now') OR NOT expires)"
668
 
               + " AND vdisk_id = " + id)
669
 
 
670
 
    cx.commit()
671
 
    cx.close()
672
 
    
673
 
    return 0
674
 
 
675
 
 
676
 
def vd_delete(id):
677
 
    """Deletes a Virtual Disk, making its extents available for future VDs.
678
 
       id [string]   : identifier for the virtual disk to delete
679
 
       returns [int] : 0 on success, -1 on failure (VD not found
680
 
                       or already deleted)
681
 
    """
682
 
 
683
 
    if not os.path.isfile(VD_DB_FILE):
684
 
        __vd_no_database()
685
 
    
686
 
    cx = sqlite.connect(VD_DB_FILE)
687
 
    cu = cx.cursor()
688
 
 
689
 
    cu.execute("-- types int")
690
 
    cu.execute("SELECT COUNT(*) FROM vdisks WHERE vdisk_id = " + id
691
 
               + " AND (expiry_time > datetime('now') OR NOT expires)")
692
 
    count, = cu.fetchone()
693
 
 
694
 
    if not count:
695
 
        cx.close()
696
 
        return -1
697
 
 
698
 
    cu.execute("UPDATE vdisks SET expires = 1, expiry_time = datetime('now')"
699
 
               + " WHERE vdisk_id = " + id)
700
 
 
701
 
    cx.commit()
702
 
    cx.close()
703
 
    
704
 
    return 0
705
 
 
706
 
 
707
 
def vd_freespace():
708
 
    """Returns the amount of free space available for new virtual disks, in MB
709
 
    returns [int] : free space for VDs in MB
710
 
    """
711
 
 
712
 
    if not os.path.isfile(VD_DB_FILE):
713
 
        __vd_no_database()
714
 
 
715
 
    cx = sqlite.connect(VD_DB_FILE)
716
 
    cu = cx.cursor()
717
 
 
718
 
    cu.execute("-- types int")
719
 
 
720
 
    cu.execute("""SELECT SUM(extent_size)
721
 
                  FROM vdisks NATURAL JOIN vdisk_extents
722
 
                                           NATURAL JOIN vdisk_part
723
 
                  WHERE expiry_time <= datetime('now') AND expires""")
724
 
 
725
 
    sum, = cu.fetchone()
726
 
 
727
 
    cx.close()
728
 
 
729
 
    return sum / 2048
730
 
 
731
 
 
732
 
def vd_init_db(path):
733
 
    """Initialise the VD SQLite database
734
 
    path [string]: path to the SQLite database file
735
 
    """
736
 
 
737
 
    cx = sqlite.connect(path)
738
 
    cu = cx.cursor()
739
 
 
740
 
    cu.execute(
741
 
        """CREATE TABLE vdisk_extents
742
 
                           ( vdisk_extent_no INT,
743
 
                             vdisk_id INT,
744
 
                             part_id INT,
745
 
                             part_extent_no INT )
746
 
        """)
747
 
 
748
 
    cu.execute(
749
 
        """CREATE TABLE vdisk_part
750
 
                           ( part_id INT,
751
 
                             partition VARCHAR,
752
 
                             extent_size INT )
753
 
        """)
754
 
 
755
 
    cu.execute(
756
 
        """CREATE TABLE vdisks
757
 
                           ( vdisk_id INT,
758
 
                             size INT,
759
 
                             expires BOOLEAN,
760
 
                             expiry_time TIMESTAMP )
761
 
        """)
762
 
 
763
 
 
764
 
    cu.execute(
765
 
        """INSERT INTO vdisks ( vdisk_id, size, expires, expiry_time )
766
 
                       VALUES ( 0,        0,    1,       datetime('now') )
767
 
        """)
768
 
 
769
 
    cx.commit()
770
 
    cx.close()
771
 
 
772
 
    VD_DB_FILE = path
773
 
 
774
 
 
775
 
 
776
 
def vd_cp_to_file(vdisk_id,filename):
777
 
    """Writes the contents of a specified vdisk out into a disk file, leaving
778
 
    the original copy in the virtual disk pool."""
779
 
 
780
 
    cx = sqlite.connect(VD_DB_FILE)
781
 
    cu = cx.cursor()
782
 
 
783
 
    extents = vd_lookup(vdisk_id)
784
 
 
785
 
    if not extents:
786
 
        return -1
787
 
    
788
 
    file_idx = 0 # index into source file, in sectors
789
 
 
790
 
    for i in extents:
791
 
        cu.execute("""SELECT partition, extent_size FROM vdisk_part
792
 
                      WHERE part_id =  """ + str(i['part_device']))
793
 
 
794
 
        (partition, extent_size) = cu.fetchone()
795
 
 
796
 
        os.system("dd bs=1b if=" + partition + " of=" + filename
797
 
                  + " skip=" + str(i['part_start_sector'])
798
 
                  + " seek=" + str(file_idx)
799
 
                  + " count=" + str(i['nr_sectors'])
800
 
                  + " > /dev/null")
801
 
 
802
 
        file_idx += i['nr_sectors']
803
 
 
804
 
    cx.close()
805
 
 
806
 
    return 0 # should return -1 if something breaks
807
 
    
808
 
 
809
 
def vd_mv_to_file(vdisk_id,filename):
810
 
    """Writes a vdisk out into a disk file and frees the space originally
811
 
    taken within the virtual disk pool.
812
 
    vdisk_id [string]: ID of the vdisk to write out
813
 
    filename [string]: file to write vdisk contents out to
814
 
    returns [int]: zero on success, nonzero on failure
815
 
    """
816
 
 
817
 
    if vd_cp_to_file(vdisk_id,filename):
818
 
        return -1
819
 
 
820
 
    if vd_delete(vdisk_id):
821
 
        return -1
822
 
 
823
 
    return 0
824
 
 
825
 
 
826
 
def vd_read_from_file(filename,expiry):
827
 
    """Reads the contents of a file directly into a vdisk, which is
828
 
    automatically allocated to fit.
829
 
    filename [string]: file to read disk contents from
830
 
    returns [string] : vdisk ID for the destination vdisk
831
 
    """
832
 
 
833
 
    size_bytes = os.stat(filename).st_size
834
 
 
835
 
    (size_mb,leftover) =  divmod(size_bytes,1048580) # size in megabytes
836
 
    if leftover > 0: size_mb += 1 # round up if not an exact number of MB
837
 
 
838
 
    vdisk_id = vd_create(size_mb, expiry)
839
 
 
840
 
    if vdisk_id < 0:
841
 
        return -1
842
 
 
843
 
    cx = sqlite.connect(VD_DB_FILE)
844
 
    cu = cx.cursor()
845
 
 
846
 
    cu.execute("""SELECT partition, extent_size, part_extent_no
847
 
                  FROM vdisk_part NATURAL JOIN vdisk_extents
848
 
                  WHERE vdisk_id =  """ + vdisk_id + """
849
 
                  ORDER BY vdisk_extent_no ASC""")
850
 
 
851
 
    extents = cu.fetchall()
852
 
 
853
 
    size_sectors = size_mb * 2048 # for feeding to dd
854
 
 
855
 
    file_idx = 0 # index into source file, in sectors
856
 
 
857
 
    def write_extent_to_vd((partition, extent_size, part_extent_no),
858
 
                           file_idx, filename):
859
 
        """Write an extent out to disk and update file_idx"""
860
 
 
861
 
        os.system("dd bs=512 if=" + filename + " of=" + partition
862
 
                  + " skip=" + str(file_idx)
863
 
                  + " seek=" + str(part_extent_no * extent_size)
864
 
                  + " count=" + str(min(extent_size, size_sectors - file_idx))
865
 
                  + " > /dev/null")
866
 
 
867
 
        return extent_size
868
 
 
869
 
    for i in extents:
870
 
        file_idx += write_extent_to_vd(i, file_idx, filename)
871
 
 
872
 
    cx.close()
873
 
 
874
 
    return vdisk_id
875
 
    
876
 
 
877
 
 
878
 
 
879
 
def vd_extents_validate(new_extents,new_writeable):
880
 
    """Validate the extents against the existing extents.
881
 
    Complains if the list supplied clashes against the extents that
882
 
    are already in use in the system.
883
 
    new_extents [list of dicts]: list of new extents, as dicts
884
 
    new_writeable [int]: 1 if they are to be writeable, 0 otherwise
885
 
    returns [int]: either the expertise level of the mapping if it doesn't
886
 
                   exceed VBD_EXPERT_MODE or -1 if it does (error)
887
 
    """
888
 
 
889
 
    import Xc # this is only needed in this function
890
 
 
891
 
    xc = Xc.new()
892
 
 
893
 
    ##### Probe for explicitly created virtual disks and build a list
894
 
    ##### of extents for comparison with the ones that are being added
895
 
 
896
 
    probe = xc.vbd_probe()
897
 
 
898
 
    old_extents = [] # this will hold a list of all existing extents and
899
 
                     # their writeable status, as a list of (device,
900
 
                     # start, size, writeable?) tuples
901
 
 
902
 
    for vbd in probe:
903
 
        this_vbd_extents = xc.vbd_getextents(vbd['dom'],vbd['vbd'])
904
 
        for vbd_ext in this_vbd_extents:
905
 
            vbd_ext['writeable'] = vbd['writeable']
906
 
            old_extents.append(vbd_ext)
907
 
            
908
 
    ##### Now scan /proc/mounts for compile a list of extents corresponding to
909
 
    ##### any devices mounted in DOM0.  This list is added on to old_extents
910
 
 
911
 
    regexp = re.compile("/dev/(\S*) \S* \S* (..).*")
912
 
    fd = open('/proc/mounts', "r")
913
 
 
914
 
    while True:
915
 
        line = fd.readline()
916
 
        if not line: # if we've run out of lines then stop reading
917
 
            break
918
 
        
919
 
        m = regexp.match(line)
920
 
 
921
 
        # if the regexp didn't match then it's probably a line we don't
922
 
        # care about - skip to next line
923
 
        if not m:
924
 
            continue
925
 
 
926
 
        # lookup the device
927
 
        ext_list = lookup_raw_partn(m.group(1))
928
 
 
929
 
        # if lookup failed, skip to next mounted device
930
 
        if not ext_list:
931
 
            continue
932
 
 
933
 
        # set a writeable flag as appropriate
934
 
        for ext in ext_list:
935
 
            ext['writeable'] = m.group(2) == 'rw'
936
 
 
937
 
        # now we've got here, the contents of ext_list are in a
938
 
        # suitable format to be added onto the old_extents list, ready
939
 
        # for checking against the new extents
940
 
 
941
 
        old_extents.extend(ext_list)
942
 
 
943
 
    fd.close() # close /proc/mounts
944
 
 
945
 
    ##### By this point, old_extents contains a list of extents, in
946
 
    ##### dictionary format corresponding to every extent of physical
947
 
    ##### disk that's either part of an explicitly created VBD, or is
948
 
    ##### mounted under DOM0.  We now check these extents against the
949
 
    ##### proposed additions in new_extents, to see if a conflict will
950
 
    ##### happen if they are added with write status new_writeable
951
 
 
952
 
    level = 0 # this'll accumulate the max warning level
953
 
 
954
 
    # Search for clashes between the new extents and the old ones
955
 
    # Takes time O(len(new_extents) * len(old_extents))
956
 
    for new_ext in new_extents:
957
 
        for old_ext in old_extents:
958
 
            if(new_ext['device'] == old_ext['device']):
959
 
 
960
 
                new_ext_start = new_ext['start_sector']
961
 
                new_ext_end = new_ext_start + new_ext['nr_sectors'] - 1
962
 
                
963
 
                old_ext_start = old_ext['start_sector']
964
 
                old_ext_end = old_ext_start + old_ext['nr_sectors'] - 1
965
 
                
966
 
                if((old_ext_start <= new_ext_start <= old_ext_end) or
967
 
                   (old_ext_start <= new_ext_end <= old_ext_end)):
968
 
                    if (not old_ext['writeable']) and new_writeable:
969
 
                        level = max(1,level)
970
 
                    elif old_ext['writeable'] and (not new_writeable):
971
 
                        level = max(1,level)
972
 
                    elif old_ext['writeable'] and new_writeable:
973
 
                        level = max(2,level)
974
 
 
975
 
 
976
 
    ##### level now holds the warning level incurred by the current
977
 
    ##### VBD setup and we complain appropriately to the user
978
 
 
979
 
 
980
 
    if level == 1:
981
 
        print >> sys.stderr, """Warning: one or more hard disk extents
982
 
         writeable by one domain are also readable by another."""
983
 
    elif level == 2:
984
 
        print >> sys.stderr, """Warning: one or more hard disk extents are
985
 
         writeable by two or more domains simultaneously."""
986
 
 
987
 
    if level > VBD_EXPERT_MODE:
988
 
        print >> sys.stderr, """ERROR: This kind of disk sharing is not allowed
989
 
        at the current safety level (%d).""" % VBD_EXPERT_MODE
990
 
        level = -1
991
 
 
992
 
    return level
993