OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 """ | 2 """ |
3 Simple script to setup unattended installs on KVM guests. | 3 Simple script to setup unattended installs on KVM guests. |
4 """ | 4 """ |
5 # -*- coding: utf-8 -*- | 5 # -*- coding: utf-8 -*- |
6 import os, sys, shutil, tempfile, re, ConfigParser, glob, inspect | 6 import os, sys, shutil, tempfile, re |
7 import common | 7 import common |
8 | 8 |
9 | 9 |
10 SCRIPT_DIR = os.path.dirname(sys.modules[__name__].__file__) | |
11 KVM_TEST_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "..")) | |
12 | |
13 | |
14 class SetupError(Exception): | 10 class SetupError(Exception): |
15 """ | 11 """ |
16 Simple wrapper for the builtin Exception class. | 12 Simple wrapper for the builtin Exception class. |
17 """ | 13 """ |
18 pass | 14 pass |
19 | 15 |
20 | 16 |
21 def find_command(cmd): | |
22 """ | |
23 Searches for a command on common paths, error if it can't find it. | |
24 | |
25 @param cmd: Command to be found. | |
26 """ | |
27 for dir in ["/usr/local/sbin", "/usr/local/bin", | |
28 "/usr/sbin", "/usr/bin", "/sbin", "/bin"]: | |
29 file = os.path.join(dir, cmd) | |
30 if os.path.exists(file): | |
31 return file | |
32 raise ValueError('Missing command: %s' % cmd) | |
33 | |
34 | |
35 def run(cmd, info=None): | |
36 """ | |
37 Run a command and throw an exception if it fails. | |
38 Optionally, you can provide additional contextual info. | |
39 | |
40 @param cmd: Command string. | |
41 @param reason: Optional string that explains the context of the failure. | |
42 | |
43 @raise: SetupError if command fails. | |
44 """ | |
45 print "Running '%s'" % cmd | |
46 cmd_name = cmd.split(' ')[0] | |
47 find_command(cmd_name) | |
48 if os.system(cmd): | |
49 e_msg = 'Command failed: %s' % cmd | |
50 if info is not None: | |
51 e_msg += '. %s' % info | |
52 raise SetupError(e_msg) | |
53 | |
54 | |
55 def cleanup(dir): | |
56 """ | |
57 If dir is a mountpoint, do what is possible to unmount it. Afterwards, | |
58 try to remove it. | |
59 | |
60 @param dir: Directory to be cleaned up. | |
61 """ | |
62 print "Cleaning up directory %s" % dir | |
63 if os.path.ismount(dir): | |
64 os.system('fuser -k %s' % dir) | |
65 run('umount %s' % dir, info='Could not unmount %s' % dir) | |
66 if os.path.isdir(dir): | |
67 shutil.rmtree(dir) | |
68 | |
69 | |
70 def clean_old_image(image): | |
71 """ | |
72 Clean a leftover image file from previous processes. If it contains a | |
73 mounted file system, do the proper cleanup procedures. | |
74 | |
75 @param image: Path to image to be cleaned up. | |
76 """ | |
77 if os.path.exists(image): | |
78 mtab = open('/etc/mtab', 'r') | |
79 mtab_contents = mtab.read() | |
80 mtab.close() | |
81 if image in mtab_contents: | |
82 os.system('fuser -k %s' % image) | |
83 os.system('umount %s' % image) | |
84 os.remove(image) | |
85 | |
86 | |
87 class Disk(object): | |
88 """ | |
89 Abstract class for Disk objects, with the common methods implemented. | |
90 """ | |
91 def __init__(self): | |
92 self.path = None | |
93 | |
94 | |
95 def setup_answer_file(self, filename, contents): | |
96 answer_file = open(os.path.join(self.mount, filename), 'w') | |
97 answer_file.write(contents) | |
98 answer_file.close() | |
99 | |
100 | |
101 def copy_to(self, src): | |
102 dst = os.path.join(self.mount, os.path.basename(src)) | |
103 if os.path.isdir(src): | |
104 shutil.copytree(src, dst) | |
105 elif os.path.isfile(src): | |
106 shutil.copyfile(src, dst) | |
107 | |
108 | |
109 def close(self): | |
110 os.chmod(self.path, 0755) | |
111 cleanup(self.mount) | |
112 print "Disk %s successfuly set" % self.path | |
113 | |
114 | |
115 class FloppyDisk(Disk): | |
116 """ | |
117 Represents a 1.44 MB floppy disk. We can copy files to it, and setup it in | |
118 convenient ways. | |
119 """ | |
120 def __init__(self, path): | |
121 print "Creating floppy unattended image %s" % path | |
122 try: | |
123 qemu_img_binary = os.environ['KVM_TEST_qemu_img_binary'] | |
124 except KeyError: | |
125 qemu_img_binary = os.path.join(KVM_TEST_DIR, qemu_img_binary) | |
126 if not os.path.exists(qemu_img_binary): | |
127 raise SetupError('The qemu-img binary that is supposed to be used ' | |
128 '(%s) does not exist. Please verify your ' | |
129 'configuration' % qemu_img_binary) | |
130 | |
131 self.mount = tempfile.mkdtemp(prefix='floppy_', dir='/tmp') | |
132 self.virtio_mount = None | |
133 self.path = path | |
134 clean_old_image(path) | |
135 if not os.path.isdir(os.path.dirname(path)): | |
136 os.makedirs(os.path.dirname(path)) | |
137 | |
138 try: | |
139 c_cmd = '%s create -f raw %s 1440k' % (qemu_img_binary, path) | |
140 run(c_cmd, info='Could not create floppy image') | |
141 f_cmd = 'mkfs.msdos -s 1 %s' % path | |
142 run(f_cmd, info='Error formatting floppy image') | |
143 m_cmd = 'mount -o loop,rw %s %s' % (path, self.mount) | |
144 run(m_cmd, info='Could not mount floppy image') | |
145 except: | |
146 cleanup(self.mount) | |
147 | |
148 | |
149 def _copy_virtio_drivers(self, virtio_floppy): | |
150 """ | |
151 Copy the virtio drivers on the virtio floppy to the install floppy. | |
152 | |
153 1) Mount the floppy containing the viostor drivers | |
154 2) Copy its contents to the root of the install floppy | |
155 """ | |
156 virtio_mount = tempfile.mkdtemp(prefix='virtio_floppy_', dir='/tmp') | |
157 | |
158 pwd = os.getcwd() | |
159 try: | |
160 m_cmd = 'mount -o loop %s %s' % (virtio_floppy, virtio_mount) | |
161 run(m_cmd, info='Could not mount virtio floppy driver') | |
162 os.chdir(virtio_mount) | |
163 path_list = glob.glob('*') | |
164 for path in path_list: | |
165 self.copy_to(path) | |
166 finally: | |
167 os.chdir(pwd) | |
168 cleanup(virtio_mount) | |
169 | |
170 | |
171 def setup_virtio_win2003(self, virtio_floppy, virtio_oemsetup_id): | |
172 """ | |
173 Setup the install floppy with the virtio storage drivers, win2003 style. | |
174 | |
175 Win2003 and WinXP depend on the file txtsetup.oem file to install | |
176 the virtio drivers from the floppy, which is a .ini file. | |
177 Process: | |
178 | |
179 1) Copy the virtio drivers on the virtio floppy to the install floppy | |
180 2) Parse the ini file with config parser | |
181 3) Modify the identifier of the default session that is going to be | |
182 executed on the config parser object | |
183 4) Re-write the config file to the disk | |
184 """ | |
185 self._copy_virtio_drivers(virtio_floppy) | |
186 txtsetup_oem = os.path.join(self.mount, 'txtsetup.oem') | |
187 if not os.path.isfile(txtsetup_oem): | |
188 raise SetupError('File txtsetup.oem not found on the install ' | |
189 'floppy. Please verify if your floppy virtio ' | |
190 'driver image has this file') | |
191 parser = ConfigParser.ConfigParser() | |
192 parser.read(txtsetup_oem) | |
193 if not parser.has_section('Defaults'): | |
194 raise SetupError('File txtsetup.oem does not have the session ' | |
195 '"Defaults". Please check txtsetup.oem') | |
196 default_driver = parser.get('Defaults', 'SCSI') | |
197 if default_driver != virtio_oemsetup_id: | |
198 parser.set('Defaults', 'SCSI', virtio_oemsetup_id) | |
199 fp = open(txtsetup_oem, 'w') | |
200 parser.write(fp) | |
201 fp.close() | |
202 | |
203 | |
204 def setup_virtio_win2008(self, virtio_floppy): | |
205 """ | |
206 Setup the install floppy with the virtio storage drivers, win2008 style. | |
207 | |
208 Win2008, Vista and 7 require people to point out the path to the drivers | |
209 on the unattended file, so we just need to copy the drivers to the | |
210 driver floppy disk. | |
211 Process: | |
212 | |
213 1) Copy the virtio drivers on the virtio floppy to the install floppy | |
214 """ | |
215 self._copy_virtio_drivers(virtio_floppy) | |
216 | |
217 | |
218 class CdromDisk(Disk): | |
219 """ | |
220 Represents a CDROM disk that we can master according to our needs. | |
221 """ | |
222 def __init__(self, path): | |
223 print "Creating ISO unattended image %s" % path | |
224 self.mount = tempfile.mkdtemp(prefix='cdrom_unattended_', dir='/tmp') | |
225 self.path = path | |
226 clean_old_image(path) | |
227 if not os.path.isdir(os.path.dirname(path)): | |
228 os.makedirs(os.path.dirname(path)) | |
229 | |
230 | |
231 def close(self): | |
232 g_cmd = ('mkisofs -o %s -max-iso9660-filenames ' | |
233 '-relaxed-filenames -D --input-charset iso8859-1 ' | |
234 '%s' % (self.path, self.mount)) | |
235 run(g_cmd, info='Could not generate iso with answer file') | |
236 | |
237 os.chmod(self.path, 0755) | |
238 cleanup(self.mount) | |
239 print "Disk %s successfuly set" % self.path | |
240 | |
241 | |
242 class UnattendedInstall(object): | 17 class UnattendedInstall(object): |
243 """ | 18 """ |
244 Creates a floppy disk image that will contain a config file for unattended | 19 Creates a floppy disk image that will contain a config file for unattended |
245 OS install. Optionally, sets up a PXE install server using qemu built in | 20 OS install. Optionally, sets up a PXE install server using qemu built in |
246 TFTP and DHCP servers to install a particular operating system. The | 21 TFTP and DHCP servers to install a particular operating system. The |
247 parameters to the script are retrieved from environment variables. | 22 parameters to the script are retrieved from environment variables. |
248 """ | 23 """ |
249 def __init__(self): | 24 def __init__(self): |
250 """ | 25 """ |
251 Gets params from environment variables and sets class attributes. | 26 Gets params from environment variables and sets class attributes. |
252 """ | 27 """ |
253 images_dir = os.path.join(KVM_TEST_DIR, 'images') | 28 script_dir = os.path.dirname(sys.modules[__name__].__file__) |
254 self.deps_dir = os.path.join(KVM_TEST_DIR, 'deps') | 29 kvm_test_dir = os.path.abspath(os.path.join(script_dir, "..")) |
255 self.unattended_dir = os.path.join(KVM_TEST_DIR, 'unattended') | 30 images_dir = os.path.join(kvm_test_dir, 'images') |
| 31 self.deps_dir = os.path.join(kvm_test_dir, 'deps') |
| 32 self.unattended_dir = os.path.join(kvm_test_dir, 'unattended') |
256 | 33 |
257 attributes = ['kernel_args', 'finish_program', 'cdrom_cd1', | 34 tftp_root = os.environ.get('KVM_TEST_tftp', '') |
258 'unattended_file', 'medium', 'url', 'kernel', 'initrd', | 35 if tftp_root: |
259 'nfs_server', 'nfs_dir', 'pxe_dir', 'pxe_image', | 36 self.tftp_root = os.path.join(kvm_test_dir, tftp_root) |
260 'pxe_initrd', 'install_virtio', 'tftp', | 37 if not os.path.isdir(self.tftp_root): |
261 'floppy', 'cdrom_unattended'] | 38 os.makedirs(self.tftp_root) |
262 for a in attributes: | 39 else: |
263 self._setattr(a) | 40 self.tftp_root = tftp_root |
264 | 41 |
265 if self.install_virtio == 'yes': | 42 self.kernel_args = os.environ.get('KVM_TEST_kernel_args', '') |
266 v_attributes = ['virtio_floppy', 'virtio_storage_path', | 43 self.finish_program= os.environ.get('KVM_TEST_finish_program', '') |
267 'virtio_network_path', 'virtio_oemsetup_id', | 44 cdrom_iso = os.environ.get('KVM_TEST_cdrom_cd1') |
268 'virtio_network_installer'] | 45 self.unattended_file = os.environ.get('KVM_TEST_unattended_file') |
269 for va in v_attributes: | |
270 self._setattr(va) | |
271 | 46 |
272 # Silly attribution just to calm pylint down... | 47 self.qemu_img_bin = os.environ.get('KVM_TEST_qemu_img_binary') |
273 self.tftp = self.tftp | 48 if not os.path.isabs(self.qemu_img_bin): |
274 if self.tftp: | 49 self.qemu_img_bin = os.path.join(kvm_test_dir, self.qemu_img_bin) |
275 self.tftp = os.path.join(KVM_TEST_DIR, self.tftp) | 50 self.cdrom_iso = os.path.join(kvm_test_dir, cdrom_iso) |
276 if not os.path.isdir(self.tftp): | 51 self.floppy_mount = tempfile.mkdtemp(prefix='floppy_', dir='/tmp') |
277 os.makedirs(self.tftp) | 52 self.cdrom_mount = tempfile.mkdtemp(prefix='cdrom_', dir='/tmp') |
| 53 self.nfs_mount = tempfile.mkdtemp(prefix='nfs_', dir='/tmp') |
| 54 floppy_name = os.environ['KVM_TEST_floppy'] |
| 55 self.floppy_img = os.path.join(kvm_test_dir, floppy_name) |
| 56 floppy_dir = os.path.dirname(self.floppy_img) |
| 57 if not os.path.isdir(floppy_dir): |
| 58 os.makedirs(floppy_dir) |
278 | 59 |
279 self.cdrom_cd1 = os.path.join(KVM_TEST_DIR, self.cdrom_cd1) | 60 self.pxe_dir = os.environ.get('KVM_TEST_pxe_dir', '') |
280 self.cdrom_cd1_mount = tempfile.mkdtemp(prefix='cdrom_cd1_', dir='/tmp') | 61 self.pxe_image = os.environ.get('KVM_TEST_pxe_image', '') |
281 if self.medium == 'nfs': | 62 self.pxe_initrd = os.environ.get('KVM_TEST_pxe_initrd', '') |
282 self.nfs_mount = tempfile.mkdtemp(prefix='nfs_', dir='/tmp') | |
283 | 63 |
284 self.floppy = os.path.join(KVM_TEST_DIR, self.floppy) | 64 self.medium = os.environ.get('KVM_TEST_medium', '') |
285 if not os.path.isdir(os.path.dirname(self.floppy)): | 65 self.url = os.environ.get('KVM_TEST_url', '') |
286 os.makedirs(os.path.dirname(self.floppy)) | 66 self.kernel = os.environ.get('KVM_TEST_kernel', '') |
287 | 67 self.initrd = os.environ.get('KVM_TEST_initrd', '') |
288 self.image_path = KVM_TEST_DIR | 68 self.nfs_server = os.environ.get('KVM_TEST_nfs_server', '') |
| 69 self.nfs_dir = os.environ.get('KVM_TEST_nfs_dir', '') |
| 70 self.image_path = kvm_test_dir |
289 self.kernel_path = os.path.join(self.image_path, self.kernel) | 71 self.kernel_path = os.path.join(self.image_path, self.kernel) |
290 self.initrd_path = os.path.join(self.image_path, self.initrd) | 72 self.initrd_path = os.path.join(self.image_path, self.initrd) |
291 | 73 |
292 | 74 |
293 def _setattr(self, key): | 75 def create_boot_floppy(self): |
294 """ | 76 """ |
295 Populate class attributes with contents of environment variables. | 77 Prepares a boot floppy by creating a floppy image file, mounting it and |
| 78 copying an answer file (kickstarts for RH based distros, answer files |
| 79 for windows) to it. After that the image is umounted. |
| 80 """ |
| 81 print "Creating boot floppy" |
296 | 82 |
297 Example: KVM_TEST_medium will populate self.medium. | 83 if os.path.exists(self.floppy_img): |
| 84 os.remove(self.floppy_img) |
298 | 85 |
299 @param key: Name of the class attribute we desire to have. | 86 c_cmd = '%s create -f raw %s 1440k' % (self.qemu_img_bin, |
300 """ | 87 self.floppy_img) |
301 env_name = 'KVM_TEST_%s' % key | 88 if os.system(c_cmd): |
302 value = os.environ.get(env_name, '') | 89 raise SetupError('Could not create floppy image.') |
303 setattr(self, key, value) | |
304 | 90 |
| 91 f_cmd = 'mkfs.msdos -s 1 %s' % self.floppy_img |
| 92 if os.system(f_cmd): |
| 93 raise SetupError('Error formatting floppy image.') |
305 | 94 |
306 def render_answer_file(self): | 95 try: |
307 # Replace KVM_TEST_CDKEY (in the unattended file) with the cdkey | 96 m_cmd = 'mount -o loop %s %s' % (self.floppy_img, self.floppy_mount) |
308 # provided for this test and replace the KVM_TEST_MEDIUM with | 97 if os.system(m_cmd): |
309 # the tree url or nfs address provided for this test. | 98 raise SetupError('Could not mount floppy image.') |
310 unattended_contents = open(self.unattended_file).read() | 99 |
311 dummy_cdkey_re = r'\bKVM_TEST_CDKEY\b' | 100 if self.unattended_file.endswith('.sif'): |
312 real_cdkey = os.environ.get('KVM_TEST_cdkey') | 101 dest_fname = 'winnt.sif' |
313 if re.search(dummy_cdkey_re, unattended_contents): | 102 setup_file = 'winnt.bat' |
314 if real_cdkey: | 103 setup_file_path = os.path.join(self.unattended_dir, setup_file) |
315 unattended_contents = re.sub(dummy_cdkey_re, real_cdkey, | 104 setup_file_dest = os.path.join(self.floppy_mount, setup_file) |
316 unattended_contents) | 105 shutil.copyfile(setup_file_path, setup_file_dest) |
| 106 elif self.unattended_file.endswith('.ks'): |
| 107 # Red Hat kickstart install |
| 108 dest_fname = 'ks.cfg' |
| 109 elif self.unattended_file.endswith('.xml'): |
| 110 if self.tftp_root is '': |
| 111 # Windows unattended install |
| 112 dest_fname = "autounattend.xml" |
| 113 else: |
| 114 # SUSE autoyast install |
| 115 dest_fname = "autoinst.xml" |
| 116 |
| 117 dest = os.path.join(self.floppy_mount, dest_fname) |
| 118 |
| 119 # Replace KVM_TEST_CDKEY (in the unattended file) with the cdkey |
| 120 # provided for this test and replace the KVM_TEST_MEDIUM with |
| 121 # the tree url or nfs address provided for this test. |
| 122 unattended_contents = open(self.unattended_file).read() |
| 123 dummy_cdkey_re = r'\bKVM_TEST_CDKEY\b' |
| 124 real_cdkey = os.environ.get('KVM_TEST_cdkey') |
| 125 if re.search(dummy_cdkey_re, unattended_contents): |
| 126 if real_cdkey: |
| 127 unattended_contents = re.sub(dummy_cdkey_re, real_cdkey, |
| 128 unattended_contents) |
| 129 else: |
| 130 print ("WARNING: 'cdkey' required but not specified for " |
| 131 "this unattended installation") |
| 132 |
| 133 dummy_re = r'\bKVM_TEST_MEDIUM\b' |
| 134 if self.medium == "cdrom": |
| 135 content = "cdrom" |
| 136 elif self.medium == "url": |
| 137 content = "url --url %s" % self.url |
| 138 elif self.medium == "nfs": |
| 139 content = "nfs --server=%s --dir=%s" % (self.nfs_server, self.nf
s_dir) |
317 else: | 140 else: |
318 print ("WARNING: 'cdkey' required but not specified for " | 141 raise SetupError("Unexpected installation medium %s" % self.url) |
319 "this unattended installation") | |
320 | 142 |
321 dummy_medium_re = r'\bKVM_TEST_MEDIUM\b' | 143 unattended_contents = re.sub(dummy_re, content, unattended_contents) |
322 if self.medium == "cdrom": | |
323 content = "cdrom" | |
324 elif self.medium == "url": | |
325 content = "url --url %s" % self.url | |
326 elif self.medium == "nfs": | |
327 content = "nfs --server=%s --dir=%s" % (self.nfs_server, | |
328 self.nfs_dir) | |
329 else: | |
330 raise SetupError("Unexpected installation medium %s" % self.url) | |
331 | 144 |
332 unattended_contents = re.sub(dummy_medium_re, content, | 145 print |
333 unattended_contents) | 146 print "Unattended install %s contents:" % dest_fname |
| 147 print unattended_contents |
| 148 # Write the unattended file contents to 'dest' |
| 149 open(dest, 'w').write(unattended_contents) |
334 | 150 |
335 def replace_virtio_key(contents, dummy_re, env): | 151 if self.finish_program: |
336 """ | 152 dest_fname = os.path.basename(self.finish_program) |
337 Replace a virtio dummy string with contents. | 153 dest = os.path.join(self.floppy_mount, dest_fname) |
| 154 shutil.copyfile(self.finish_program, dest) |
338 | 155 |
339 If install_virtio is not set, replace it with a dummy string. | 156 finally: |
| 157 u_cmd = 'umount %s' % self.floppy_mount |
| 158 if os.system(u_cmd): |
| 159 raise SetupError('Could not unmount floppy at %s.' % |
| 160 self.floppy_mount) |
| 161 self.cleanup(self.floppy_mount) |
340 | 162 |
341 @param contents: Contents of the unattended file | 163 os.chmod(self.floppy_img, 0755) |
342 @param dummy_re: Regular expression used to search on the. | |
343 unattended file contents. | |
344 @param env: Name of the environment variable. | |
345 """ | |
346 dummy_path = "C:" | |
347 driver = os.environ.get(env, '') | |
348 | 164 |
349 if re.search(dummy_re, contents): | 165 print "Boot floppy created successfuly" |
350 if self.install_virtio == "yes": | |
351 if driver.endswith("msi"): | |
352 driver = 'msiexec /passive /package ' + driver | |
353 else: | |
354 try: | |
355 # Let's escape windows style paths properly | |
356 drive, path = driver.split(":") | |
357 driver = drive + ":" + re.escape(path) | |
358 except: | |
359 pass | |
360 contents = re.sub(dummy_re, driver, contents) | |
361 else: | |
362 contents = re.sub(dummy_re, dummy_path, contents) | |
363 return contents | |
364 | |
365 vdict = {r'\bKVM_TEST_STORAGE_DRIVER_PATH\b': | |
366 'KVM_TEST_virtio_storage_path', | |
367 r'\bKVM_TEST_NETWORK_DRIVER_PATH\b': | |
368 'KVM_TEST_virtio_network_path', | |
369 r'\bKVM_TEST_VIRTIO_NETWORK_INSTALLER\b': | |
370 'KVM_TEST_virtio_network_installer_path'} | |
371 | |
372 for vkey in vdict: | |
373 unattended_contents = replace_virtio_key(unattended_contents, | |
374 vkey, vdict[vkey]) | |
375 | |
376 print "Unattended install contents:" | |
377 print unattended_contents | |
378 return unattended_contents | |
379 | |
380 | |
381 def setup_boot_disk(self): | |
382 answer_contents = self.render_answer_file() | |
383 | |
384 if self.unattended_file.endswith('.sif'): | |
385 dest_fname = 'winnt.sif' | |
386 setup_file = 'winnt.bat' | |
387 boot_disk = FloppyDisk(self.floppy) | |
388 boot_disk.setup_answer_file(dest_fname, answer_contents) | |
389 setup_file_path = os.path.join(self.unattended_dir, setup_file) | |
390 boot_disk.copy_to(setup_file_path) | |
391 if self.install_virtio == "yes": | |
392 boot_disk.setup_virtio_win2003(self.virtio_floppy, | |
393 self.virtio_oemsetup_id) | |
394 boot_disk.copy_to(self.finish_program) | |
395 | |
396 elif self.unattended_file.endswith('.ks'): | |
397 # Red Hat kickstart install | |
398 dest_fname = 'ks.cfg' | |
399 if self.cdrom_unattended: | |
400 boot_disk = CdromDisk(self.cdrom_unattended) | |
401 elif self.floppy: | |
402 boot_disk = FloppyDisk(self.floppy) | |
403 else: | |
404 raise SetupError("Neither cdrom_unattended nor floppy set " | |
405 "on the config file, please verify") | |
406 boot_disk.setup_answer_file(dest_fname, answer_contents) | |
407 | |
408 elif self.unattended_file.endswith('.xml'): | |
409 if self.tftp: | |
410 # SUSE autoyast install | |
411 dest_fname = "autoinst.xml" | |
412 if self.cdrom_unattended: | |
413 boot_disk = CdromDisk(self.cdrom_unattended) | |
414 elif self.floppy: | |
415 boot_disk = FloppyDisk(self.floppy) | |
416 else: | |
417 raise SetupError("Neither cdrom_unattended nor floppy set " | |
418 "on the config file, please verify") | |
419 boot_disk.setup_answer_file(dest_fname, answer_contents) | |
420 | |
421 else: | |
422 # Windows unattended install | |
423 dest_fname = "autounattend.xml" | |
424 boot_disk = FloppyDisk(self.floppy) | |
425 boot_disk.setup_answer_file(dest_fname, answer_contents) | |
426 if self.install_virtio == "yes": | |
427 boot_disk.setup_virtio_win2008(self.virtio_floppy) | |
428 boot_disk.copy_to(self.finish_program) | |
429 | |
430 else: | |
431 raise SetupError('Unknown answer file %s' % | |
432 self.unattended_file) | |
433 | |
434 boot_disk.close() | |
435 | 166 |
436 | 167 |
437 def setup_pxe_boot(self): | 168 def setup_pxe_boot(self): |
438 """ | 169 """ |
439 Sets up a PXE boot environment using the built in qemu TFTP server. | 170 Sets up a PXE boot environment using the built in qemu TFTP server. |
440 Copies the PXE Linux bootloader pxelinux.0 from the host (needs the | 171 Copies the PXE Linux bootloader pxelinux.0 from the host (needs the |
441 pxelinux package or equivalent for your distro), and vmlinuz and | 172 pxelinux package or equivalent for your distro), and vmlinuz and |
442 initrd.img files from the CD to a directory that qemu will serve trough | 173 initrd.img files from the CD to a directory that qemu will serve trough |
443 TFTP to the VM. | 174 TFTP to the VM. |
444 """ | 175 """ |
445 print "Setting up PXE boot using TFTP root %s" % self.tftp | 176 print "Setting up PXE boot using TFTP root %s" % self.tftp_root |
446 | 177 |
447 pxe_file = None | 178 pxe_file = None |
448 pxe_paths = ['/usr/lib/syslinux/pxelinux.0', | 179 pxe_paths = ['/usr/lib/syslinux/pxelinux.0', |
449 '/usr/share/syslinux/pxelinux.0'] | 180 '/usr/share/syslinux/pxelinux.0'] |
450 for path in pxe_paths: | 181 for path in pxe_paths: |
451 if os.path.isfile(path): | 182 if os.path.isfile(path): |
452 pxe_file = path | 183 pxe_file = path |
453 break | 184 break |
454 | 185 |
455 if not pxe_file: | 186 if not pxe_file: |
456 raise SetupError('Cannot find PXE boot loader pxelinux.0. Make ' | 187 raise SetupError('Cannot find PXE boot loader pxelinux.0. Make ' |
457 'sure pxelinux or equivalent package for your ' | 188 'sure pxelinux or equivalent package for your ' |
458 'distro is installed.') | 189 'distro is installed.') |
459 | 190 |
460 pxe_dest = os.path.join(self.tftp, 'pxelinux.0') | 191 pxe_dest = os.path.join(self.tftp_root, 'pxelinux.0') |
461 shutil.copyfile(pxe_file, pxe_dest) | 192 shutil.copyfile(pxe_file, pxe_dest) |
462 | 193 |
463 try: | 194 try: |
464 m_cmd = ('mount -t iso9660 -v -o loop,ro %s %s' % | 195 m_cmd = 'mount -t iso9660 -v -o loop,ro %s %s' % (self.cdrom_iso, |
465 (self.cdrom_cd1, self.cdrom_cd1_mount)) | 196 self.cdrom_mount) |
466 run(m_cmd, info='Could not mount CD image %s.' % self.cdrom_cd1) | 197 if os.system(m_cmd): |
| 198 raise SetupError('Could not mount CD image %s.' % |
| 199 self.cdrom_iso) |
467 | 200 |
468 pxe_dir = os.path.join(self.cdrom_cd1_mount, self.pxe_dir) | 201 pxe_dir = os.path.join(self.cdrom_mount, self.pxe_dir) |
469 pxe_image = os.path.join(pxe_dir, self.pxe_image) | 202 pxe_image = os.path.join(pxe_dir, self.pxe_image) |
470 pxe_initrd = os.path.join(pxe_dir, self.pxe_initrd) | 203 pxe_initrd = os.path.join(pxe_dir, self.pxe_initrd) |
471 | 204 |
472 if not os.path.isdir(pxe_dir): | 205 if not os.path.isdir(pxe_dir): |
473 raise SetupError('The ISO image does not have a %s dir. The ' | 206 raise SetupError('The ISO image does not have a %s dir. The ' |
474 'script assumes that the cd has a %s dir ' | 207 'script assumes that the cd has a %s dir ' |
475 'where to search for the vmlinuz image.' % | 208 'where to search for the vmlinuz image.' % |
476 (self.pxe_dir, self.pxe_dir)) | 209 (self.pxe_dir, self.pxe_dir)) |
477 | 210 |
478 if not os.path.isfile(pxe_image) or not os.path.isfile(pxe_initrd): | 211 if not os.path.isfile(pxe_image) or not os.path.isfile(pxe_initrd): |
479 raise SetupError('The location %s is lacking either a vmlinuz ' | 212 raise SetupError('The location %s is lacking either a vmlinuz ' |
480 'or a initrd.img file. Cannot find a PXE ' | 213 'or a initrd.img file. Cannot find a PXE ' |
481 'image to proceed.' % self.pxe_dir) | 214 'image to proceed.' % self.pxe_dir) |
482 | 215 |
483 tftp_image = os.path.join(self.tftp, 'vmlinuz') | 216 tftp_image = os.path.join(self.tftp_root, 'vmlinuz') |
484 tftp_initrd = os.path.join(self.tftp, 'initrd.img') | 217 tftp_initrd = os.path.join(self.tftp_root, 'initrd.img') |
485 shutil.copyfile(pxe_image, tftp_image) | 218 shutil.copyfile(pxe_image, tftp_image) |
486 shutil.copyfile(pxe_initrd, tftp_initrd) | 219 shutil.copyfile(pxe_initrd, tftp_initrd) |
487 | 220 |
488 finally: | 221 finally: |
489 cleanup(self.cdrom_cd1_mount) | 222 u_cmd = 'umount %s' % self.cdrom_mount |
| 223 if os.system(u_cmd): |
| 224 raise SetupError('Could not unmount CD at %s.' % |
| 225 self.cdrom_mount) |
| 226 self.cleanup(self.cdrom_mount) |
490 | 227 |
491 pxe_config_dir = os.path.join(self.tftp, 'pxelinux.cfg') | 228 pxe_config_dir = os.path.join(self.tftp_root, 'pxelinux.cfg') |
492 if not os.path.isdir(pxe_config_dir): | 229 if not os.path.isdir(pxe_config_dir): |
493 os.makedirs(pxe_config_dir) | 230 os.makedirs(pxe_config_dir) |
494 pxe_config_path = os.path.join(pxe_config_dir, 'default') | 231 pxe_config_path = os.path.join(pxe_config_dir, 'default') |
495 | 232 |
496 pxe_config = open(pxe_config_path, 'w') | 233 pxe_config = open(pxe_config_path, 'w') |
497 pxe_config.write('DEFAULT pxeboot\n') | 234 pxe_config.write('DEFAULT pxeboot\n') |
498 pxe_config.write('TIMEOUT 20\n') | 235 pxe_config.write('TIMEOUT 20\n') |
499 pxe_config.write('PROMPT 0\n') | 236 pxe_config.write('PROMPT 0\n') |
500 pxe_config.write('LABEL pxeboot\n') | 237 pxe_config.write('LABEL pxeboot\n') |
501 pxe_config.write(' KERNEL vmlinuz\n') | 238 pxe_config.write(' KERNEL vmlinuz\n') |
502 pxe_config.write(' APPEND initrd=initrd.img %s\n' % | 239 pxe_config.write(' APPEND initrd=initrd.img %s\n' % |
503 self.kernel_args) | 240 self.kernel_args) |
504 pxe_config.close() | 241 pxe_config.close() |
505 | 242 |
506 print "PXE boot successfuly set" | 243 print "PXE boot successfuly set" |
507 | 244 |
508 | 245 |
509 def setup_url(self): | 246 def setup_url(self): |
510 """ | 247 """ |
511 Download the vmlinuz and initrd.img from URL. | 248 Download the vmlinuz and initrd.img from URL |
512 """ | 249 """ |
513 print "Downloading the vmlinuz and initrd.img" | 250 print "Downloading the vmlinuz and initrd.img" |
514 os.chdir(self.image_path) | 251 os.chdir(self.image_path) |
515 | 252 |
516 kernel_fetch_cmd = "wget -q %s/isolinux/%s" % (self.url, self.kernel) | 253 kernel_fetch_cmd = "wget -q %s/isolinux/%s" % (self.url, self.kernel) |
517 initrd_fetch_cmd = "wget -q %s/isolinux/%s" % (self.url, self.initrd) | 254 initrd_fetch_cmd = "wget -q %s/isolinux/%s" % (self.url, self.initrd) |
518 | 255 |
519 if os.path.exists(self.kernel): | 256 if os.path.exists(self.kernel): |
520 os.unlink(self.kernel) | 257 os.unlink(self.kernel) |
521 if os.path.exists(self.initrd): | 258 if os.path.exists(self.initrd): |
522 os.unlink(self.initrd) | 259 os.unlink(self.initrd) |
523 | 260 |
524 run(kernel_fetch_cmd, info="Could not fetch vmlinuz from %s" % self.url) | 261 if os.system(kernel_fetch_cmd) != 0: |
525 run(initrd_fetch_cmd, info=("Could not fetch initrd.img from %s" % | 262 raise SetupError("Could not fetch vmlinuz from %s" % self.url) |
526 self.url)) | 263 if os.system(initrd_fetch_cmd) != 0: |
527 print "Download of vmlinuz and initrd.img finished" | 264 raise SetupError("Could not fetch initrd.img from %s" % self.url) |
528 | 265 |
| 266 print "Downloading finish" |
529 | 267 |
530 def setup_nfs(self): | 268 def setup_nfs(self): |
531 """ | 269 """ |
532 Copy the vmlinuz and initrd.img from nfs. | 270 Copy the vmlinuz and initrd.img from nfs. |
533 """ | 271 """ |
534 print "Copying the vmlinuz and initrd.img from nfs" | 272 print "Copying the vmlinuz and initrd.img from nfs" |
535 | 273 |
536 m_cmd = ("mount %s:%s %s -o ro" % | 274 m_cmd = "mount %s:%s %s -o ro" % (self.nfs_server, self.nfs_dir, self.nf
s_mount) |
537 (self.nfs_server, self.nfs_dir, self.nfs_mount)) | 275 if os.system(m_cmd): |
538 run(m_cmd, info='Could not mount nfs server') | 276 raise SetupError('Could not mount nfs server.') |
| 277 |
| 278 kernel_fetch_cmd = "cp %s/isolinux/%s %s" % (self.nfs_mount, |
| 279 self.kernel, |
| 280 self.image_path) |
| 281 initrd_fetch_cmd = "cp %s/isolinux/%s %s" % (self.nfs_mount, |
| 282 self.initrd, |
| 283 self.image_path) |
539 | 284 |
540 try: | 285 try: |
541 kernel_fetch_cmd = ("cp %s/isolinux/%s %s" % | 286 if os.system(kernel_fetch_cmd): |
542 (self.nfs_mount, self.kernel, self.image_path)) | 287 raise SetupError("Could not copy the vmlinuz from %s" % |
543 run(kernel_fetch_cmd, info=("Could not copy the vmlinuz from %s" % | 288 self.nfs_mount) |
544 self.nfs_mount)) | 289 if os.system(initrd_fetch_cmd): |
545 initrd_fetch_cmd = ("cp %s/isolinux/%s %s" % | 290 raise SetupError("Could not copy the initrd.img from %s" % |
546 (self.nfs_mount, self.initrd, self.image_path)) | 291 self.nfs_mount) |
547 run(initrd_fetch_cmd, info=("Could not copy the initrd.img from " | |
548 "%s" % self.nfs_mount)) | |
549 finally: | 292 finally: |
550 cleanup(self.nfs_mount) | 293 u_cmd = "umount %s" % self.nfs_mount |
| 294 if os.system(u_cmd): |
| 295 raise SetupError("Could not unmont nfs at %s" % self.nfs_mount) |
| 296 self.cleanup(self.nfs_mount) |
| 297 |
| 298 def cleanup(self, mount): |
| 299 """ |
| 300 Clean up a previously used mountpoint. |
| 301 |
| 302 @param mount: Mountpoint to be cleaned up. |
| 303 """ |
| 304 if os.path.isdir(mount): |
| 305 if os.path.ismount(mount): |
| 306 print "Path %s is still mounted, please verify" % mount |
| 307 else: |
| 308 print "Removing mount point %s" % mount |
| 309 os.rmdir(mount) |
551 | 310 |
552 | 311 |
553 def setup(self): | 312 def setup(self): |
554 """ | |
555 Configure the environment for unattended install. | |
556 | |
557 Uses an appropriate strategy according to each install model. | |
558 """ | |
559 print "Starting unattended install setup" | 313 print "Starting unattended install setup" |
560 print | |
561 | 314 |
562 print "Variables set:" | 315 print "Variables set:" |
563 for member in inspect.getmembers(self): | 316 print " medium: " + str(self.medium) |
564 name, value = member | 317 print " qemu_img_bin: " + str(self.qemu_img_bin) |
565 attribute = getattr(self, name) | 318 print " cdrom iso: " + str(self.cdrom_iso) |
566 if not (name.startswith("__") or callable(attribute) or not value): | 319 print " unattended_file: " + str(self.unattended_file) |
567 print " %s: %s" % (name, value) | 320 print " kernel_args: " + str(self.kernel_args) |
568 print | 321 print " tftp_root: " + str(self.tftp_root) |
| 322 print " floppy_mount: " + str(self.floppy_mount) |
| 323 print " floppy_img: " + str(self.floppy_img) |
| 324 print " finish_program: " + str(self.finish_program) |
| 325 print " pxe_dir: " + str(self.pxe_dir) |
| 326 print " pxe_image: " + str(self.pxe_image) |
| 327 print " pxe_initrd: " + str(self.pxe_initrd) |
| 328 print " url: " + str(self.url) |
| 329 print " kernel: " + str(self.kernel) |
| 330 print " initrd: " + str(self.initrd) |
| 331 print " nfs_server: " + str(self.nfs_server) |
| 332 print " nfs_dir: " + str(self.nfs_dir) |
| 333 print " nfs_mount: " + str(self.nfs_mount) |
569 | 334 |
570 if self.unattended_file and (self.floppy or self.cdrom_unattended): | 335 if self.unattended_file and self.floppy_img is not None: |
571 self.setup_boot_disk() | 336 self.create_boot_floppy() |
572 if self.medium == "cdrom": | 337 if self.medium == "cdrom": |
573 if self.tftp: | 338 if self.tftp_root: |
574 self.setup_pxe_boot() | 339 self.setup_pxe_boot() |
575 elif self.medium == "url": | 340 elif self.medium == "url": |
576 self.setup_url() | 341 self.setup_url() |
577 elif self.medium == "nfs": | 342 elif self.medium == "nfs": |
578 self.setup_nfs() | 343 self.setup_nfs() |
579 else: | 344 else: |
580 raise SetupError("Unexpected installation method %s" % | 345 raise SetupError("Unexpected installation method %s" % |
581 self.medium) | 346 self.medium) |
582 print "Unattended install setup finished successfuly" | 347 print "Unattended install setup finished successfuly" |
583 | 348 |
584 | 349 |
585 if __name__ == "__main__": | 350 if __name__ == "__main__": |
586 os_install = UnattendedInstall() | 351 os_install = UnattendedInstall() |
587 os_install.setup() | 352 os_install.setup() |
OLD | NEW |