xref: /qemu/tests/vm/centos.aarch64 (revision ca61e750)
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