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