| [245] | 1 | #!/usr/bin/env python2.5 | 
|---|
| [231] | 2 |  | 
|---|
|  | 3 | import sys | 
|---|
|  | 4 | import os | 
|---|
|  | 5 | import shutil | 
|---|
|  | 6 | import tempfile | 
|---|
| [336] | 7 | import time | 
|---|
|  | 8 | from subprocess import call, check_call, Popen, PIPE | 
|---|
| [231] | 9 |  | 
|---|
|  | 10 | def losetup(source, offset=0): | 
|---|
|  | 11 | # XXX we avoid colliding with other instances of ourself, | 
|---|
|  | 12 | #     but when it comes to other loop-device users we just | 
|---|
|  | 13 | #     pick a range things don't seem to use and hope... | 
|---|
|  | 14 | lockfilename = '/tmp/losetup.lock' | 
|---|
|  | 15 | os.close(os.open(lockfilename, os.O_CREAT+os.O_EXCL)) #lock | 
|---|
|  | 16 | try: | 
|---|
|  | 17 | loopdevice = None | 
|---|
|  | 18 | for i in xrange(32,60): # totally arbitrary, just looks to be unused on black-mesa | 
|---|
|  | 19 | filename = '/dev/loop%d'%i | 
|---|
|  | 20 | if 0 == len(file(filename).read(1)): | 
|---|
|  | 21 | loopdevice = filename # it's empty | 
|---|
|  | 22 | break | 
|---|
|  | 23 | if loopdevice is not None: | 
|---|
|  | 24 | call(['losetup', '-o', str(offset), loopdevice, source]) | 
|---|
| [336] | 25 | else: | 
|---|
|  | 26 | raise RuntimeError('out of loop devices for copying VM image: too many at once?') | 
|---|
| [231] | 27 | finally: | 
|---|
|  | 28 | os.unlink(lockfilename) #unlock | 
|---|
|  | 29 | return loopdevice | 
|---|
|  | 30 | # XX this means we can duplicate 9 at once.  since it takes around 30 seconds, | 
|---|
|  | 31 | # this seems like an adequate capacity. | 
|---|
|  | 32 |  | 
|---|
|  | 33 | def duplicate_lv(source, dest): | 
|---|
| [336] | 34 | '''OBSOLETE: duplicate boot record, filesystem from LV source to LV dest | 
|---|
| [231] | 35 |  | 
|---|
|  | 36 | source, dest should be device filenames. | 
|---|
| [336] | 37 |  | 
|---|
|  | 38 | Now we just dd.  Doesn't support resizing, but it's easier, | 
|---|
|  | 39 | especially if we allow the partition table to vary between images. | 
|---|
| [231] | 40 | ''' | 
|---|
|  | 41 | # XXX this is very specific to what the etch sipb-xen-make-iso installer does. | 
|---|
|  | 42 | # XXXX also, it's specific to four gigs. | 
|---|
|  | 43 | # step 1: copy the MBR | 
|---|
|  | 44 | call(['dd', 'if='+source, 'bs=512', 'count=63', 'of='+dest]) | 
|---|
|  | 45 | # step 2: fix up partition table | 
|---|
|  | 46 | # XX actually do this; this is our opportunity to resize the filesystem | 
|---|
|  | 47 | # step 3: make the filesystem, and copy its contents in | 
|---|
|  | 48 | sourcefs = losetup(source, 32256) | 
|---|
|  | 49 | destfs = losetup(dest, 32256) | 
|---|
|  | 50 | call(['mkfs.ext3', '-b', '4096', destfs, '987989']) | 
|---|
| [336] | 51 | tmptree = tempfile.mkdtemp('', 'auto-install.dup.', '/tmp')#yes, args backward | 
|---|
| [231] | 52 | os.mkdir(tmptree+"/source") | 
|---|
|  | 53 | call(['mount', '-t', 'ext3', '-o', 'ro', sourcefs, tmptree+"/source"]) | 
|---|
|  | 54 | os.mkdir(tmptree+"/dest") | 
|---|
|  | 55 | call(['mount', '-t', 'ext3', destfs, tmptree+"/dest"]) | 
|---|
|  | 56 | call(['cp', '-aT', tmptree+"/source", tmptree+"/dest"]) | 
|---|
|  | 57 | # step 4: twiddle what we want to twiddle | 
|---|
|  | 58 | # XX do this | 
|---|
|  | 59 | # step 5: make the swap area | 
|---|
|  | 60 | swapfs = losetup(dest, 4046870016) | 
|---|
|  | 61 | call(['mkswap', swapfs, str(240943)]) | 
|---|
|  | 62 | # call(['losetup', '-d', swapfs]) | 
|---|
|  | 63 | print 'losetup -d '+swapfs | 
|---|
|  | 64 | # step 6: clean up. | 
|---|
|  | 65 | # XX actually unmount etc (leaving it is for debugging) | 
|---|
|  | 66 | print 'umount '+tmptree+'/source' | 
|---|
|  | 67 | call(['umount', tmptree+"/dest"]) # needed to flush writes | 
|---|
|  | 68 | print 'losetup -d '+sourcefs | 
|---|
|  | 69 | print 'losetup -d '+destfs | 
|---|
|  | 70 |  | 
|---|
| [336] | 71 | def frob_copy(target, *args): | 
|---|
| [338] | 72 | '''UNUSED: maybe we'll use this someday; it does isolate the frobber.''' | 
|---|
| [336] | 73 | # 1. prepare arguments volume | 
|---|
| [245] | 74 | args_volume = prefix+target+'_args' | 
|---|
|  | 75 | args_device = '/dev/xenvg/' + args_volume | 
|---|
|  | 76 | check_call(['/sbin/lvcreate', 'xenvg', '--name', args_volume, '--size', '4K']) | 
|---|
|  | 77 | file(args_device, 'w').write('\n'.join(args) + '\n') | 
|---|
|  | 78 |  | 
|---|
| [336] | 79 | # 2. invoke frobber vm | 
|---|
| [245] | 80 | copier_device = '/dev/xenvg/d_wert_hda' | 
|---|
|  | 81 | check_call(['/usr/sbin/xm', 'create', 'sipb-database', | 
|---|
|  | 82 | 'machine_name='+target, | 
|---|
|  | 83 | 'disks=' + ' '.join(['phy:'+copier_device+',hda,w', | 
|---|
|  | 84 | 'phy:'+target_device+',hdc,w', | 
|---|
|  | 85 | 'phy:'+args_device+',hdd,w'])]) | 
|---|
|  | 86 |  | 
|---|
| [336] | 87 | # XXX should check_call(['/sbin/lvremove', '-f', 'xenvg/'+args_volume]) | 
|---|
| [245] | 88 |  | 
|---|
| [338] | 89 | def frob_copy_simple(target, hostname, rootpw): | 
|---|
| [336] | 90 | """target should be an LV device filename | 
|---|
|  | 91 |  | 
|---|
|  | 92 | This is highly specific to the etch install produced by sipb-xen-make-iso. | 
|---|
|  | 93 | Generalizing and customizing to other distros is left to the future... | 
|---|
|  | 94 | """ | 
|---|
|  | 95 | # 1: mount filesystem | 
|---|
|  | 96 | fs = losetup(target, 32256) | 
|---|
|  | 97 | mntdir = tempfile.mkdtemp('', 'auto-install.frob.', '/tmp') | 
|---|
|  | 98 | call(['mount', '-t', 'ext3', fs, mntdir]) | 
|---|
|  | 99 | # 2: do frobbing | 
|---|
|  | 100 | # 2a:  (printf "%s\n" "$ROOTPW"; sleep .3; printf "%s\n" "$ROOTPW") | 
|---|
|  | 101 | #      | /usr/sbin/chroot "$TARGET" /usr/bin/passwd root | 
|---|
|  | 102 | p = Popen(['/usr/sbin/chroot', mntdir, '/usr/bin/passwd', 'root'], stdin=PIPE) | 
|---|
|  | 103 | p.stdin.write(rootpw+'\n') | 
|---|
|  | 104 | time.sleep(1) | 
|---|
|  | 105 | p.stdin.write(rootpw+'\n') | 
|---|
|  | 106 | p.stdin.close() | 
|---|
|  | 107 | p.wait() | 
|---|
|  | 108 | os.chdir(mntdir) | 
|---|
|  | 109 | # 2b: clear generated file that has eth0's old MAC address | 
|---|
|  | 110 | #      rm $TARGET/etc/udev/rules.d/z25_persistent-net.rules | 
|---|
|  | 111 | os.unlink('etc/udev/rules.d/z25_persistent-net.rules') | 
|---|
|  | 112 | # 2c: hostname. | 
|---|
|  | 113 | # XX Use nullmailer in image, not exim4.  (Fewer copies of hostname.) | 
|---|
|  | 114 | # 2c1: rm /var/lib/dhcp3/dhclient.eth0.leases | 
|---|
|  | 115 | os.unlink('var/lib/dhcp3/dhclient.eth0.leases') | 
|---|
|  | 116 | # 2c2: /etc/hosts, /etc/hostname; /etc/motd? /etc/mailname? | 
|---|
|  | 117 | call(['perl', '-i', '-pe', 's/ice3.servers.csail.mit.edu/'+hostname+'/g', | 
|---|
|  | 118 | 'etc/hosts', 'etc/hostname', 'etc/motd', 'etc/mailname']) | 
|---|
|  | 119 | # 3: clean up | 
|---|
|  | 120 | os.chdir('/') | 
|---|
|  | 121 | call(['umount', mntdir]) | 
|---|
| [338] | 122 | os.rmdir(mntdir) | 
|---|
| [336] | 123 | call(['losetup', '-d', fs]) | 
|---|
|  | 124 |  | 
|---|
| [338] | 125 | def duplicate_by_vm(source, target, rootpw, nodd=False): | 
|---|
| [336] | 126 | # source, target should be machine names | 
|---|
|  | 127 | prefix = 'd_' | 
|---|
|  | 128 | # 1. copy (by dd) source to target | 
|---|
|  | 129 | source_device = '/dev/xenvg/' + prefix + source + '_hda' | 
|---|
|  | 130 | target_device = '/dev/xenvg/' + prefix + target + '_hda' | 
|---|
| [338] | 131 | if not nodd: | 
|---|
|  | 132 | check_call(['/bin/dd', 'bs=1M', 'conv=nocreat', | 
|---|
|  | 133 | 'if='+source_device, 'of='+target_device]) | 
|---|
| [336] | 134 | # 2. frob target | 
|---|
| [338] | 135 | frob_copy_simple(target_device, target, rootpw) | 
|---|
| [336] | 136 |  | 
|---|
| [338] | 137 | def main(*argv): | 
|---|
|  | 138 | subcommand = argv[1] | 
|---|
|  | 139 | args = argv[2:] | 
|---|
|  | 140 | os.environ['PATH'] = '/usr/sbin:/usr/bin:/sbin:/bin' | 
|---|
|  | 141 | if subcommand == 'lvcopy': | 
|---|
|  | 142 | kwargs = {} | 
|---|
|  | 143 | if args[0] == '--nodd': | 
|---|
|  | 144 | args = args[1:] | 
|---|
|  | 145 | kwargs['nodd'] = True | 
|---|
|  | 146 | duplicate_by_vm(*args, **kwargs) | 
|---|
|  | 147 | else: | 
|---|
|  | 148 | print >>sys.stderr, argv[0]+": unknown subcommand: "+subcommand | 
|---|
|  | 149 | return 2 | 
|---|
|  | 150 | return 0 | 
|---|
|  | 151 |  | 
|---|
| [231] | 152 | if __name__ == '__main__': | 
|---|
| [338] | 153 | sys.exit(main(*sys.argv)) | 
|---|