1#!/usr/bin/env python3 2# 3# Centos aarch64 image 4# 5# Copyright 2020 Linaro 6# 7# Authors: 8# Robert Foley <robert.foley@linaro.org> 9# Originally based on ubuntu.aarch64 10# 11# This code is licensed under the GPL version 2 or later. See 12# the COPYING file in the top-level directory. 13# 14 15import os 16import sys 17import subprocess 18import basevm 19import time 20import traceback 21import aarch64vm 22 23DEFAULT_CONFIG = { 24 'cpu' : "max", 25 'machine' : "virt,gic-version=max", 26 'install_cmds' : "yum install -y make ninja-build git python3 gcc gcc-c++ flex bison, "\ 27 "yum install -y glib2-devel pixman-devel zlib-devel, "\ 28 "yum install -y perl-Test-Harness, "\ 29 "alternatives --set python /usr/bin/python3, "\ 30 "sudo dnf config-manager "\ 31 "--add-repo=https://download.docker.com/linux/centos/docker-ce.repo,"\ 32 "sudo dnf install --nobest -y docker-ce.aarch64,"\ 33 "systemctl enable docker", 34 # We increase beyond the default time since during boot 35 # it can take some time (many seconds) to log into the VM. 36 'ssh_timeout' : 60, 37} 38 39class CentosAarch64VM(basevm.BaseVM): 40 name = "centos.aarch64" 41 arch = "aarch64" 42 login_prompt = "localhost login:" 43 prompt = '[root@localhost ~]#' 44 image_name = "CentOS-8-aarch64-1905-dvd1.iso" 45 image_link = "http://mirrors.usc.edu/pub/linux/distributions/centos/8.0.1905/isos/aarch64/" 46 image_link += image_name 47 BUILD_SCRIPT = """ 48 set -e; 49 cd $(mktemp -d); 50 sudo chmod a+r /dev/vdb; 51 tar --checkpoint=.10 -xf /dev/vdb; 52 ./configure {configure_opts}; 53 make --output-sync {target} -j{jobs} {verbose}; 54 """ 55 def set_key_perm(self): 56 """Set permissions properly on certain files to allow 57 ssh access.""" 58 self.console_wait_send(self.prompt, 59 "/usr/sbin/restorecon -R -v /root/.ssh\n") 60 self.console_wait_send(self.prompt, 61 "/usr/sbin/restorecon -R -v "\ 62 "/home/{}/.ssh\n".format(self._config["guest_user"])) 63 64 def create_kickstart(self): 65 """Generate the kickstart file used to generate the centos image.""" 66 # Start with the template for the kickstart. 67 ks_file = self._source_path + "/tests/vm/centos-8-aarch64.ks" 68 subprocess.check_call("cp {} ./ks.cfg".format(ks_file), shell=True) 69 # Append the ssh keys to the kickstart file 70 # as the post processing phase of installation. 71 with open("ks.cfg", "a") as f: 72 # Add in the root pw and guest user. 73 rootpw = "rootpw --plaintext {}\n" 74 f.write(rootpw.format(self._config["root_pass"])) 75 add_user = "user --groups=wheel --name={} "\ 76 "--password={} --plaintext\n" 77 f.write(add_user.format(self._config["guest_user"], 78 self._config["guest_pass"])) 79 # Add the ssh keys. 80 f.write("%post --log=/root/ks-post.log\n") 81 f.write("mkdir -p /root/.ssh\n") 82 addkey = 'echo "{}" >> /root/.ssh/authorized_keys\n' 83 addkey_cmd = addkey.format(self._config["ssh_pub_key"]) 84 f.write(addkey_cmd) 85 f.write('mkdir -p /home/{}/.ssh\n'.format(self._config["guest_user"])) 86 addkey = 'echo "{}" >> /home/{}/.ssh/authorized_keys\n' 87 addkey_cmd = addkey.format(self._config["ssh_pub_key"], 88 self._config["guest_user"]) 89 f.write(addkey_cmd) 90 f.write("%end\n") 91 # Take our kickstart file and create an .iso from it. 92 # The .iso will be provided to qemu as we boot 93 # from the install dvd. 94 # Anaconda will recognize the label "OEMDRV" and will 95 # start the automated installation. 96 gen_iso_img = 'genisoimage -output ks.iso -volid "OEMDRV" ks.cfg' 97 subprocess.check_call(gen_iso_img, shell=True) 98 99 def wait_for_shutdown(self): 100 """We wait for qemu to shutdown the VM and exit. 101 While this happens we display the console view 102 for easier debugging.""" 103 # The image creation is essentially done, 104 # so whether or not the wait is successful we want to 105 # wait for qemu to exit (the self.wait()) before we return. 106 try: 107 self.console_wait("reboot: Power down") 108 except Exception as e: 109 sys.stderr.write("Exception hit\n") 110 if isinstance(e, SystemExit) and e.code == 0: 111 return 0 112 traceback.print_exc() 113 finally: 114 self.wait() 115 116 def build_base_image(self, dest_img): 117 """Run through the centos installer to create 118 a base image with name dest_img.""" 119 # We create the temp image, and only rename 120 # to destination when we are done. 121 img = dest_img + ".tmp" 122 # Create an empty image. 123 # We will provide this as the install destination. 124 qemu_img_create = "qemu-img create {} 50G".format(img) 125 subprocess.check_call(qemu_img_create, shell=True) 126 127 # Create our kickstart file to be fed to the installer. 128 self.create_kickstart() 129 # Boot the install dvd with the params as our ks.iso 130 os_img = self._download_with_cache(self.image_link) 131 dvd_iso = "centos-8-dvd.iso" 132 subprocess.check_call(["cp", "-f", os_img, dvd_iso]) 133 extra_args = "-cdrom ks.iso" 134 extra_args += " -drive file={},if=none,id=drive1,cache=writeback" 135 extra_args += " -device virtio-blk,drive=drive1,bootindex=1" 136 extra_args = extra_args.format(dvd_iso).split(" ") 137 self.boot(img, extra_args=extra_args) 138 self.console_wait_send("change the selection", "\n") 139 # We seem to need to hit esc (chr(27)) twice to abort the 140 # media check, which takes a long time. 141 # Waiting a bit seems to be more reliable before hitting esc. 142 self.console_wait("Checking") 143 time.sleep(5) 144 self.console_wait_send("Checking", chr(27)) 145 time.sleep(5) 146 self.console_wait_send("Checking", chr(27)) 147 print("Found Checking") 148 # Give sufficient time for the installer to create the image. 149 self.console_init(timeout=7200) 150 self.wait_for_shutdown() 151 os.rename(img, dest_img) 152 print("Done with base image build: {}".format(dest_img)) 153 154 def check_create_base_img(self, img_base, img_dest): 155 """Create a base image using the installer. 156 We will use the base image if it exists. 157 This helps cut down on install time in case we 158 need to restart image creation, 159 since the base image creation can take a long time.""" 160 if not os.path.exists(img_base): 161 print("Generate new base image: {}".format(img_base)) 162 self.build_base_image(img_base); 163 else: 164 print("Use existing base image: {}".format(img_base)) 165 # Save a copy of the base image and copy it to dest. 166 # which we will use going forward. 167 subprocess.check_call(["cp", img_base, img_dest]) 168 169 def boot(self, img, extra_args=None): 170 aarch64vm.create_flash_images(self._tmpdir, self._efi_aarch64) 171 default_args = aarch64vm.get_pflash_args(self._tmpdir) 172 if extra_args: 173 extra_args.extend(default_args) 174 else: 175 extra_args = default_args 176 # We always add these performance tweaks 177 # because without them, we boot so slowly that we 178 # can time out finding the boot efi device. 179 if '-smp' not in extra_args and \ 180 '-smp' not in self._config['extra_args'] and \ 181 '-smp' not in self._args: 182 # Only add if not already there to give caller option to change it. 183 extra_args.extend(["-smp", "8"]) 184 # We have overridden boot() since aarch64 has additional parameters. 185 # Call down to the base class method. 186 super(CentosAarch64VM, self).boot(img, extra_args=extra_args) 187 188 def build_image(self, img): 189 img_tmp = img + ".tmp" 190 self.check_create_base_img(img + ".base", img_tmp) 191 192 # Boot the new image for the first time to finish installation. 193 self.boot(img_tmp) 194 self.console_init() 195 self.console_wait_send(self.login_prompt, "root\n") 196 self.console_wait_send("Password:", 197 "{}\n".format(self._config["root_pass"])) 198 199 self.set_key_perm() 200 self.console_wait_send(self.prompt, "rpm -q centos-release\n") 201 enable_adapter = "sed -i 's/ONBOOT=no/ONBOOT=yes/g'" \ 202 " /etc/sysconfig/network-scripts/ifcfg-enp0s1\n" 203 self.console_wait_send(self.prompt, enable_adapter) 204 self.console_wait_send(self.prompt, "ifup enp0s1\n") 205 self.console_wait_send(self.prompt, 206 'echo "qemu ALL=(ALL) NOPASSWD:ALL" | '\ 207 'sudo tee /etc/sudoers.d/qemu\n') 208 self.console_wait(self.prompt) 209 210 # Rest of the commands we issue through ssh. 211 self.wait_ssh(wait_root=True) 212 213 # If the user chooses *not* to do the second phase, 214 # then we will jump right to the graceful shutdown 215 if self._config['install_cmds'] != "": 216 install_cmds = self._config['install_cmds'].split(',') 217 for cmd in install_cmds: 218 self.ssh_root(cmd) 219 self.ssh_root("poweroff") 220 self.wait_for_shutdown() 221 os.rename(img_tmp, img) 222 print("image creation complete: {}".format(img)) 223 return 0 224 225if __name__ == "__main__": 226 defaults = aarch64vm.get_config_defaults(CentosAarch64VM, DEFAULT_CONFIG) 227 sys.exit(basevm.main(CentosAarch64VM, defaults)) 228