960f72612d83192760dc16691e2da51a9b62b51c
[invirt/packages/invirt-autoinstaller.git] / files / usr / sbin / sipb-xen-lvcopy
1 #!/usr/bin/env python2.5
2
3 import sys
4 import os
5 import shutil
6 import tempfile
7 import time
8 from subprocess import call, check_call, Popen, PIPE
9
10 # TODO:
11 # . pick a loop device sanely
12 # x adapt size of partition to fit volume
13 # . fix hostname, root password
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])
34     else:
35       raise RuntimeError('out of loop devices for copying VM image: too many at once?')
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):
43   '''OBSOLETE: duplicate boot record, filesystem from LV source to LV dest
44   
45   source, dest should be device filenames.
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.
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'])
60   tmptree = tempfile.mkdtemp('', 'auto-install.dup.', '/tmp')#yes, args backward
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
80 def frob_copy(target, *args):
81   '''UNUSED: maybe we'll use this someday; it does isolate the frobber.'''
82   # 1. prepare arguments volume
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
88   # 2. invoke frobber vm
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
96   # XXX should check_call(['/sbin/lvremove', '-f', 'xenvg/'+args_volume])
97
98 def frob_copy_simple(target, hostname, rootpw):
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])
131   os.rmdir(mntdir)
132   call(['losetup', '-d', fs])
133
134 def duplicate_by_vm(source, target, rootpw, nodd=False):
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'
140   if not nodd:
141     check_call(['/bin/dd', 'bs=1M', 'conv=nocreat',
142                 'if='+source_device, 'of='+target_device])
143   # 2. frob target
144   frob_copy_simple(target_device, target, rootpw)
145
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
161 if __name__ == '__main__':
162   sys.exit(main(*sys.argv))