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 | 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]) |
---|
25 | else: |
---|
26 | raise RuntimeError('out of loop devices for copying VM image: too many at once?') |
---|
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): |
---|
34 | '''OBSOLETE: duplicate boot record, filesystem from LV source to LV dest |
---|
35 | |
---|
36 | source, dest should be device filenames. |
---|
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. |
---|
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']) |
---|
51 | tmptree = tempfile.mkdtemp('', 'auto-install.dup.', '/tmp')#yes, args backward |
---|
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 | |
---|
71 | def frob_copy(target, *args): |
---|
72 | '''UNUSED: maybe we'll use this someday; it does isolate the frobber.''' |
---|
73 | # 1. prepare arguments volume |
---|
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 | |
---|
79 | # 2. invoke frobber vm |
---|
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 | |
---|
87 | # XXX should check_call(['/sbin/lvremove', '-f', 'xenvg/'+args_volume]) |
---|
88 | |
---|
89 | def frob_copy_simple(target, hostname, rootpw): |
---|
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]) |
---|
122 | os.rmdir(mntdir) |
---|
123 | call(['losetup', '-d', fs]) |
---|
124 | |
---|
125 | def duplicate_by_vm(source, target, rootpw, nodd=False): |
---|
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' |
---|
131 | if not nodd: |
---|
132 | check_call(['/bin/dd', 'bs=1M', 'conv=nocreat', |
---|
133 | 'if='+source_device, 'of='+target_device]) |
---|
134 | # 2. frob target |
---|
135 | frob_copy_simple(target_device, target, rootpw) |
---|
136 | |
---|
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 | |
---|
152 | if __name__ == '__main__': |
---|
153 | sys.exit(main(*sys.argv)) |
---|