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