| 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 |