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