summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--README.adoc16
-rw-r--r--classes/image_types_ostree.bbclass58
-rw-r--r--classes/sdcard_image-rpi-ota.bbclass190
-rw-r--r--classes/sota.bbclass9
-rw-r--r--classes/sota_am335x-evm-wifi.bbclass2
-rw-r--r--classes/sota_m3ulcb.bbclass1
-rw-r--r--classes/sota_porter.bbclass1
-rw-r--r--classes/sota_raspberrypi.bbclass8
-rw-r--r--lib/oeqa/selftest/garage_push.py39
l---------lib/oeqa/selftest/qemucommand.py1
-rw-r--r--lib/oeqa/selftest/updater.py147
-rw-r--r--recipes-core/images/initramfs-ostree-image.bb1
-rw-r--r--recipes-sota/aktualizr/aktualizr-hsm-test-prov.bb6
-rw-r--r--recipes-sota/aktualizr/aktualizr_git.bb4
-rw-r--r--recipes-sota/aktualizr/files/sota_hsm_test.toml5
-rw-r--r--recipes-sota/garage-sign/garage-sign.bb6
-rw-r--r--recipes-support/ca-certificates/ca-certificates_%.bbappend1
-rw-r--r--recipes-support/libp11/libp11_0.4.7.bb37
-rw-r--r--scripts/lib/wic/plugins/source/otaimage.py32
-rw-r--r--scripts/qemucommand.py127
-rwxr-xr-xscripts/run-qemu-ota131
22 files changed, 431 insertions, 392 deletions
diff --git a/.gitignore b/.gitignore
index bee8a64..8d35cb3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
1__pycache__ 1__pycache__
2*.pyc
diff --git a/README.adoc b/README.adoc
index 47c0a2b..b4608d5 100644
--- a/README.adoc
+++ b/README.adoc
@@ -122,3 +122,19 @@ garage-push --repo=/path/to/ostree-repo --ref=mybranch --credentials=/path/to/cr
122.... 122....
123 123
124You can set SOTA_PACKED_CREDENTIALS in your local.conf to make your build results be automatically synchronized with a remote server. Credentials are stored in the JSON format described in the https://github.com/advancedtelematic/aktualizr/blob/master/README.sotatools.adoc[garage-push README]. This JSON file can be optionally stored inside a zip file, although if it is stored this way, the JSON file must be named treehub.json. 124You can set SOTA_PACKED_CREDENTIALS in your local.conf to make your build results be automatically synchronized with a remote server. Credentials are stored in the JSON format described in the https://github.com/advancedtelematic/aktualizr/blob/master/README.sotatools.adoc[garage-push README]. This JSON file can be optionally stored inside a zip file, although if it is stored this way, the JSON file must be named treehub.json.
125
126=== QA
127
128This layer relies on the test framework oe-selftest for quality assurance. Follow the steps below to run the tests:
129
130* Append the line below to conf/local.conf
131
132```
133SANITY_TESTED_DISTROS=""
134```
135
136* Run oe-selftest:
137
138```
139oe-selftest --run-tests updater
140```
diff --git a/classes/image_types_ostree.bbclass b/classes/image_types_ostree.bbclass
index 1f8e195..172f2c8 100644
--- a/classes/image_types_ostree.bbclass
+++ b/classes/image_types_ostree.bbclass
@@ -5,6 +5,7 @@ inherit image
5IMAGE_DEPENDS_ostree = "ostree-native:do_populate_sysroot \ 5IMAGE_DEPENDS_ostree = "ostree-native:do_populate_sysroot \
6 openssl-native:do_populate_sysroot \ 6 openssl-native:do_populate_sysroot \
7 coreutils-native:do_populate_sysroot \ 7 coreutils-native:do_populate_sysroot \
8 unzip-native:do_populate_sysroot \
8 virtual/kernel:do_deploy \ 9 virtual/kernel:do_deploy \
9 ${OSTREE_INITRAMFS_IMAGE}:do_image_complete" 10 ${OSTREE_INITRAMFS_IMAGE}:do_image_complete"
10 11
@@ -104,6 +105,7 @@ IMAGE_CMD_ostree () {
104 if [ -d root ] && [ ! -L root ]; then 105 if [ -d root ] && [ ! -L root ]; then
105 if [ "$(ls -A root)" ]; then 106 if [ "$(ls -A root)" ]; then
106 bberror "Data in /root directory is not preserved by OSTree." 107 bberror "Data in /root directory is not preserved by OSTree."
108 exit 1
107 fi 109 fi
108 110
109 if [ -n "$SYSTEMD_USED" ]; then 111 if [ -n "$SYSTEMD_USED" ]; then
@@ -159,7 +161,7 @@ IMAGE_CMD_ostree () {
159} 161}
160 162
161IMAGE_TYPEDEP_ostreepush = "ostree" 163IMAGE_TYPEDEP_ostreepush = "ostree"
162IMAGE_DEPENDS_ostreepush = "aktualizr-native:do_populate_sysroot" 164IMAGE_DEPENDS_ostreepush = "aktualizr-native:do_populate_sysroot ca-certificates-native:do_populate_sysroot "
163IMAGE_CMD_ostreepush () { 165IMAGE_CMD_ostreepush () {
164 # Print warnings if credetials are not set or if the file has not been found. 166 # Print warnings if credetials are not set or if the file has not been found.
165 if [ -n "${SOTA_PACKED_CREDENTIALS}" ]; then 167 if [ -n "${SOTA_PACKED_CREDENTIALS}" ]; then
@@ -176,4 +178,58 @@ IMAGE_CMD_ostreepush () {
176 fi 178 fi
177} 179}
178 180
181IMAGE_TYPEDEP_garagesign = "ostreepush"
182IMAGE_DEPENDS_garagesign = "garage-sign-native:do_populate_sysroot"
183IMAGE_CMD_garagesign () {
184 if [ -n "${SOTA_PACKED_CREDENTIALS}" ]; then
185 # if credentials are issued by a server that doesn't support offline signing, exit silently
186 unzip -p ${SOTA_PACKED_CREDENTIALS} root.json targets.pub targets.sec 2>&1 >/dev/null || exit 0
187
188 java_version=$( java -version 2>&1 | awk -F '"' '/version/ {print $2}' )
189 if [ "${java_version}" = "" ]; then
190 bberror "Java is required for synchronization with update backend, but is not installed on the host machine"
191 exit 1
192 elif [ "${java_version}" \< "1.8" ]; then
193 bberror "Java version >= 8 is required for synchronization with update backend"
194 exit 1
195 fi
196
197 if [ ! -d "${GARAGE_SIGN_REPO}" ]; then
198 garage-sign init --repo ${GARAGE_SIGN_REPO} --home-dir ${GARAGE_SIGN_REPO} --credentials ${SOTA_PACKED_CREDENTIALS}
199 fi
200
201 if [ -n "${GARAGE_SIGN_REPOSERVER}" ]; then
202 reposerver_args="--reposerver ${GARAGE_SIGN_REPOSERVER}"
203 else
204 reposerver_args=""
205 fi
206
207 ostree_target_hash=$(cat ${OSTREE_REPO}/refs/heads/${OSTREE_BRANCHNAME})
208
209 # Push may fail due to race condition when multiple build machines try to push simultaneously
210 # in which case targets.json should be pulled again and the whole procedure repeated
211 push_success=0
212 for push_retries in $( seq 3 ); do
213 garage-sign targets pull --repo ${GARAGE_SIGN_REPO} --home-dir ${GARAGE_SIGN_REPO} ${reposerver_args}
214 garage-sign targets add --repo ${GARAGE_SIGN_REPO} --home-dir ${GARAGE_SIGN_REPO} --name ${OSTREE_BRANCHNAME} --format OSTREE --version ${OSTREE_BRANCHNAME} --length 0 --url "https://example.com/" --sha256 ${ostree_target_hash} --hardwareids ${MACHINE}
215 garage-sign targets sign --repo ${GARAGE_SIGN_REPO} --home-dir ${GARAGE_SIGN_REPO} --key-name=targets
216 errcode=0
217 garage-sign targets push --repo ${GARAGE_SIGN_REPO} --home-dir ${GARAGE_SIGN_REPO} ${reposerver_args} || errcode=$?
218 if [ "$errcode" -eq "0" ]; then
219 push_success=1
220 break
221 else
222 bbwarn "Push to garage repository has failed, retrying"
223 fi
224 done
225
226 if [ "$push_success" -ne "1" ]; then
227 bberror "Couldn't push to garage repository"
228 exit 1
229 fi
230 else
231 bbwarn "SOTA_PACKED_CREDENTIALS not set. Please add SOTA_PACKED_CREDENTIALS."
232 fi
233}
234
179# vim:set ts=4 sw=4 sts=4 expandtab: 235# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/classes/sdcard_image-rpi-ota.bbclass b/classes/sdcard_image-rpi-ota.bbclass
deleted file mode 100644
index 9c859fe..0000000
--- a/classes/sdcard_image-rpi-ota.bbclass
+++ /dev/null
@@ -1,190 +0,0 @@
1inherit image_types
2inherit linux-raspberrypi-base
3
4#
5# Create an image that can by written onto a SD card using dd.
6#
7# The disk layout used is:
8#
9# 0 -> IMAGE_ROOTFS_ALIGNMENT - reserved for other data
10# IMAGE_ROOTFS_ALIGNMENT -> BOOT_SPACE - bootloader and kernel
11# BOOT_SPACE -> SDIMG_OTA_SIZE - rootfs
12#
13
14# Default Free space = 1.3x
15# Use IMAGE_OVERHEAD_FACTOR to add more space
16# <--------->
17# 4MiB 40MiB SDIMG_OTA_ROOTFS
18# <-----------------------> <----------> <---------------------->
19# ------------------------ ------------ ------------------------
20# | IMAGE_ROOTFS_ALIGNMENT | BOOT_SPACE | OTAROOT_SIZE |
21# ------------------------ ------------ ------------------------
22# ^ ^ ^ ^
23# | | | |
24# 0 4MiB 4MiB + 40MiB 4MiB + 40Mib + SDIMG_OTA_ROOTFS
25
26# This image depends on the rootfs image
27IMAGE_TYPEDEP_rpi-sdimg-ota = "${SDIMG_OTA_ROOTFS_TYPE}"
28
29# Set kernel and boot loader
30IMAGE_BOOTLOADER ?= "bcm2835-bootfiles"
31
32# Set initramfs extension
33KERNEL_INITRAMFS ?= ""
34
35# Kernel image name
36SDIMG_OTA_KERNELIMAGE_raspberrypi ?= "kernel.img"
37SDIMG_OTA_KERNELIMAGE_raspberrypi2 ?= "kernel7.img"
38SDIMG_OTA_KERNELIMAGE_raspberrypi3 ?= "kernel7.img"
39
40# Boot partition volume id
41BOOTDD_VOLUME_ID ?= "${MACHINE}"
42
43# Boot partition size [in KiB] (will be rounded up to IMAGE_ROOTFS_ALIGNMENT)
44BOOT_SPACE ?= "40960"
45
46# Set alignment to 4MB [in KiB]
47IMAGE_ROOTFS_ALIGNMENT = "4096"
48
49# Use an uncompressed ext3 by default as rootfs
50SDIMG_OTA_ROOTFS_TYPE ?= "otaimg"
51SDIMG_OTA_ROOTFS = "${DEPLOY_DIR_IMAGE}/${IMAGE_LINK_NAME}.${SDIMG_OTA_ROOTFS_TYPE}"
52
53IMAGE_DEPENDS_rpi-sdimg-ota = " \
54 parted-native \
55 mtools-native \
56 dosfstools-native \
57 virtual/kernel:do_deploy \
58 ${IMAGE_BOOTLOADER} \
59 u-boot \
60 "
61IMAGE_TYPEDEP_rpi-sdimg-ota = "otaimg"
62
63# SD card image name
64SDIMG_OTA = "${IMGDEPLOYDIR}/${IMAGE_NAME}.rootfs.rpi-sdimg-ota"
65
66# Compression method to apply to SDIMG_OTA after it has been created. Supported
67# compression formats are "gzip", "bzip2" or "xz". The original .rpi-sdimg-ota file
68# is kept and a new compressed file is created if one of these compression
69# formats is chosen. If SDIMG_OTA_COMPRESSION is set to any other value it is
70# silently ignored.
71#SDIMG_OTA_COMPRESSION ?= ""
72
73# Additional files and/or directories to be copied into the vfat partition from the IMAGE_ROOTFS.
74FATPAYLOAD ?= ""
75
76IMAGE_CMD_rpi-sdimg-ota () {
77
78 # Align partitions
79 OTAROOT_SIZE=`du -Lb ${SDIMG_OTA_ROOTFS} | cut -f1`
80 OTAROOT_SIZE=$(expr ${OTAROOT_SIZE} / 1024 + 1)
81 BOOT_SPACE_ALIGNED=$(expr ${BOOT_SPACE} + ${IMAGE_ROOTFS_ALIGNMENT} - 1)
82 BOOT_SPACE_ALIGNED=$(expr ${BOOT_SPACE_ALIGNED} - ${BOOT_SPACE_ALIGNED} % ${IMAGE_ROOTFS_ALIGNMENT})
83 SDIMG_OTA_SIZE=$(expr ${IMAGE_ROOTFS_ALIGNMENT} + ${BOOT_SPACE_ALIGNED} + $OTAROOT_SIZE)
84
85 echo "Creating filesystem with Boot partition ${BOOT_SPACE_ALIGNED} KiB and RootFS $OTAROOT_SIZE KiB"
86
87 # Check if we are building with device tree support
88 DTS="${@get_dts(d, None)}"
89
90 # Initialize sdcard image file
91 dd if=/dev/zero of=${SDIMG_OTA} bs=1024 count=0 seek=${SDIMG_OTA_SIZE}
92
93 # Create partition table
94 parted -s ${SDIMG_OTA} mklabel msdos
95 # Create boot partition and mark it as bootable
96 parted -s ${SDIMG_OTA} unit KiB mkpart primary fat32 ${IMAGE_ROOTFS_ALIGNMENT} $(expr ${BOOT_SPACE_ALIGNED} \+ ${IMAGE_ROOTFS_ALIGNMENT})
97 parted -s ${SDIMG_OTA} set 1 boot on
98 # Create rootfs partition to the end of disk
99 parted -s ${SDIMG_OTA} -- unit KiB mkpart primary ext2 $(expr ${BOOT_SPACE_ALIGNED} \+ ${IMAGE_ROOTFS_ALIGNMENT}) -1s
100 parted ${SDIMG_OTA} print
101
102 # Create a vfat image with boot files
103 BOOT_BLOCKS=$(LC_ALL=C parted -s ${SDIMG_OTA} unit b print | awk '/ 1 / { print substr($4, 1, length($4 -1)) / 512 /2 }')
104 rm -f ${WORKDIR}/boot.img
105 mkfs.vfat -n "${BOOTDD_VOLUME_ID}" -S 512 -C ${WORKDIR}/boot.img $BOOT_BLOCKS
106 sync
107
108 mcopy -i ${WORKDIR}/boot.img -s ${DEPLOY_DIR_IMAGE}/bcm2835-bootfiles/* ::/
109
110 if test -n "${DTS}"; then
111 # Device Tree Overlays are assumed to be suffixed by '-overlay.dtb' string and will be put in a dedicated folder
112 DT_OVERLAYS="${@split_overlays(d, 0)}"
113 DT_ROOT="${@split_overlays(d, 1)}"
114
115 # Copy board device trees to root folder
116 for DTB in ${DT_ROOT}; do
117 DTB_BASE_NAME=`basename ${DTB} .dtb`
118
119 mcopy -i ${WORKDIR}/boot.img -s ${DEPLOY_DIR_IMAGE}/${KERNEL_IMAGETYPE}-${DTB_BASE_NAME}.dtb ::${DTB_BASE_NAME}.dtb
120 done
121
122 # Copy device tree overlays to dedicated folder
123 mmd -i ${WORKDIR}/boot.img overlays
124 for DTB in ${DT_OVERLAYS}; do
125 DTB_EXT=${DTB##*.}
126 DTB_BASE_NAME=`basename ${DTB} ."${DTB_EXT}"`
127
128 mcopy -i ${WORKDIR}/boot.img -s ${DEPLOY_DIR_IMAGE}/${KERNEL_IMAGETYPE}-${DTB_BASE_NAME}.${DTB_EXT} ::overlays/${DTB_BASE_NAME}.${DTB_EXT}
129 done
130 fi
131
132 case "${KERNEL_IMAGETYPE}" in
133 "uImage")
134 mcopy -i ${WORKDIR}/boot.img -s ${DEPLOY_DIR_IMAGE}/u-boot.bin ::${SDIMG_OTA_KERNELIMAGE}
135 ;;
136 *)
137 bbfatal "Kernel uImage is required for OTA image. Please set KERNEL_IMAGETYPE to \"uImage\""
138 ;;
139 esac
140
141 if [ -n ${FATPAYLOAD} ] ; then
142 echo "Copying payload into VFAT"
143 for entry in ${FATPAYLOAD} ; do
144 # add the || true to stop aborting on vfat issues like not supporting .~lock files
145 mcopy -i ${WORKDIR}/boot.img -s -v ${IMAGE_ROOTFS}$entry :: || true
146 done
147 fi
148
149 # Add stamp file
150 echo "${IMAGE_NAME}" > ${WORKDIR}/image-version-info
151 mcopy -i ${WORKDIR}/boot.img -v ${WORKDIR}//image-version-info ::
152
153 # Burn Partitions
154 sync
155 dd if=${WORKDIR}/boot.img of=${SDIMG_OTA} conv=notrunc seek=1 bs=$(expr ${IMAGE_ROOTFS_ALIGNMENT} \* 1024) && sync && sync
156 # If SDIMG_OTA_ROOTFS_TYPE is a .xz file use xzcat
157 if echo "${SDIMG_OTA_ROOTFS_TYPE}" | egrep -q "*\.xz"
158 then
159 xzcat ${SDIMG_OTA_ROOTFS} | dd of=${SDIMG_OTA} conv=notrunc seek=1 bs=$(expr 1024 \* ${BOOT_SPACE_ALIGNED} + ${IMAGE_ROOTFS_ALIGNMENT} \* 1024) && sync && sync
160 else
161 dd if=${SDIMG_OTA_ROOTFS} of=${SDIMG_OTA} conv=notrunc seek=1 bs=$(expr 1024 \* ${BOOT_SPACE_ALIGNED} + ${IMAGE_ROOTFS_ALIGNMENT} \* 1024) && sync && sync
162 fi
163
164 # Optionally apply compression
165 case "${SDIMG_OTA_COMPRESSION}" in
166 "gzip")
167 gzip -k9 "${SDIMG_OTA}"
168 ;;
169 "bzip2")
170 bzip2 -k9 "${SDIMG_OTA}"
171 ;;
172 "xz")
173 xz -k "${SDIMG_OTA}"
174 ;;
175 esac
176}
177
178ROOTFS_POSTPROCESS_COMMAND += " rpi_generate_sysctl_config ; "
179
180rpi_generate_sysctl_config() {
181 # systemd sysctl config
182 test -d ${IMAGE_ROOTFS}${sysconfdir}/sysctl.d && \
183 echo "vm.min_free_kbytes = 8192" > ${IMAGE_ROOTFS}${sysconfdir}/sysctl.d/rpi-vm.conf
184
185 # sysv sysctl config
186 IMAGE_SYSCTL_CONF="${IMAGE_ROOTFS}${sysconfdir}/sysctl.conf"
187 test -e ${IMAGE_ROOTFS}${sysconfdir}/sysctl.conf && \
188 sed -e "/vm.min_free_kbytes/d" -i ${IMAGE_SYSCTL_CONF}
189 echo "" >> ${IMAGE_SYSCTL_CONF} && echo "vm.min_free_kbytes = 8192" >> ${IMAGE_SYSCTL_CONF}
190}
diff --git a/classes/sota.bbclass b/classes/sota.bbclass
index 1865356..f5a42c1 100644
--- a/classes/sota.bbclass
+++ b/classes/sota.bbclass
@@ -5,11 +5,13 @@ python __anonymous() {
5 5
6OVERRIDES .= "${@bb.utils.contains('DISTRO_FEATURES', 'sota', ':sota', '', d)}" 6OVERRIDES .= "${@bb.utils.contains('DISTRO_FEATURES', 'sota', ':sota', '', d)}"
7 7
8HOSTTOOLS_NONFATAL += "java"
9
8SOTA_CLIENT ??= "aktualizr" 10SOTA_CLIENT ??= "aktualizr"
9SOTA_CLIENT_PROV ??= "aktualizr-auto-prov" 11SOTA_CLIENT_PROV ??= "aktualizr-auto-prov"
10IMAGE_INSTALL_append_sota = " ostree os-release ${SOTA_CLIENT} ${SOTA_CLIENT_PROV}" 12IMAGE_INSTALL_append_sota = " ostree os-release ${SOTA_CLIENT} ${SOTA_CLIENT_PROV}"
11IMAGE_CLASSES += " image_types_ostree image_types_ota" 13IMAGE_CLASSES += " image_types_ostree image_types_ota"
12IMAGE_FSTYPES += "${@bb.utils.contains('DISTRO_FEATURES', 'sota', 'ostreepush otaimg wic', ' ', d)}" 14IMAGE_FSTYPES += "${@bb.utils.contains('DISTRO_FEATURES', 'sota', 'ostreepush garagesign otaimg wic', ' ', d)}"
13 15
14PACKAGECONFIG_append_pn-curl = "${@bb.utils.contains('SOTA_CLIENT_FEATURES', 'hsm', " ssl", " ", d)}" 16PACKAGECONFIG_append_pn-curl = "${@bb.utils.contains('SOTA_CLIENT_FEATURES', 'hsm', " ssl", " ", d)}"
15PACKAGECONFIG_remove_pn-curl = "${@bb.utils.contains('SOTA_CLIENT_FEATURES', 'hsm', " gnutls", " ", d)}" 17PACKAGECONFIG_remove_pn-curl = "${@bb.utils.contains('SOTA_CLIENT_FEATURES', 'hsm', " gnutls", " ", d)}"
@@ -25,6 +27,11 @@ OSTREE_BRANCHNAME ?= "${MACHINE}"
25OSTREE_OSNAME ?= "poky" 27OSTREE_OSNAME ?= "poky"
26OSTREE_INITRAMFS_IMAGE ?= "initramfs-ostree-image" 28OSTREE_INITRAMFS_IMAGE ?= "initramfs-ostree-image"
27 29
30
31GARAGE_SIGN_REPO ?= "${DEPLOY_DIR_IMAGE}/garage_sign_repo"
32GARAGE_SIGN_KEYNAME ?= "garage-key"
33GARAGE_TARGET_NAME ?= "${OSTREE_BRANCHNAME}"
34
28SOTA_MACHINE ??="none" 35SOTA_MACHINE ??="none"
29SOTA_MACHINE_raspberrypi2 ?= "raspberrypi" 36SOTA_MACHINE_raspberrypi2 ?= "raspberrypi"
30SOTA_MACHINE_raspberrypi3 ?= "raspberrypi" 37SOTA_MACHINE_raspberrypi3 ?= "raspberrypi"
diff --git a/classes/sota_am335x-evm-wifi.bbclass b/classes/sota_am335x-evm-wifi.bbclass
index 821e8fb..adefb47 100644
--- a/classes/sota_am335x-evm-wifi.bbclass
+++ b/classes/sota_am335x-evm-wifi.bbclass
@@ -1,5 +1,3 @@
1IMAGE_CLASSES += "image_types_uboot"
2
3KERNEL_IMAGETYPE_sota = "uImage" 1KERNEL_IMAGETYPE_sota = "uImage"
4 2
5OSTREE_BOOTLOADER ?= "u-boot" 3OSTREE_BOOTLOADER ?= "u-boot"
diff --git a/classes/sota_m3ulcb.bbclass b/classes/sota_m3ulcb.bbclass
index 21d04ba..6b63af4 100644
--- a/classes/sota_m3ulcb.bbclass
+++ b/classes/sota_m3ulcb.bbclass
@@ -2,7 +2,6 @@
2OSTREE_KERNEL = "Image" 2OSTREE_KERNEL = "Image"
3 3
4EXTRA_IMAGEDEPENDS_append_sota = " m3ulcb-ota-bootfiles" 4EXTRA_IMAGEDEPENDS_append_sota = " m3ulcb-ota-bootfiles"
5IMAGE_CLASSES_append_sota = " image_types_uboot "
6IMAGE_BOOT_FILES_sota += "m3ulcb-ota-bootfiles/*" 5IMAGE_BOOT_FILES_sota += "m3ulcb-ota-bootfiles/*"
7 6
8OSTREE_BOOTLOADER ?= "u-boot" 7OSTREE_BOOTLOADER ?= "u-boot"
diff --git a/classes/sota_porter.bbclass b/classes/sota_porter.bbclass
index a8f5ba1..75ae579 100644
--- a/classes/sota_porter.bbclass
+++ b/classes/sota_porter.bbclass
@@ -2,7 +2,6 @@
2OSTREE_KERNEL = "uImage+dtb" 2OSTREE_KERNEL = "uImage+dtb"
3 3
4EXTRA_IMAGEDEPENDS_append_sota = " porter-bootfiles" 4EXTRA_IMAGEDEPENDS_append_sota = " porter-bootfiles"
5IMAGE_CLASSES_append_sota = " image_types_uboot "
6IMAGE_BOOT_FILES_sota += "porter-bootfiles/*" 5IMAGE_BOOT_FILES_sota += "porter-bootfiles/*"
7 6
8OSTREE_BOOTLOADER ?= "u-boot" 7OSTREE_BOOTLOADER ?= "u-boot"
diff --git a/classes/sota_raspberrypi.bbclass b/classes/sota_raspberrypi.bbclass
index cc6b666..51d07b2 100644
--- a/classes/sota_raspberrypi.bbclass
+++ b/classes/sota_raspberrypi.bbclass
@@ -1,11 +1,9 @@
1IMAGE_CLASSES += "${@bb.utils.contains('DISTRO_FEATURES', 'sota', 'image_types_uboot sdcard_image-rpi-ota', '', d)}"
2IMAGE_FSTYPES += "${@bb.utils.contains('DISTRO_FEATURES', 'sota', 'rpi-sdimg-ota.xz', 'rpi-sdimg.xz', d)}"
3
4IMAGE_FSTYPES_remove = "${@bb.utils.contains('DISTRO_FEATURES', 'sota', 'wic rpi-sdimg rpi-sdimg.xz', '', d)}"
5
6KERNEL_IMAGETYPE_sota = "uImage" 1KERNEL_IMAGETYPE_sota = "uImage"
7PREFERRED_PROVIDER_virtual/bootloader_sota ?= "u-boot" 2PREFERRED_PROVIDER_virtual/bootloader_sota ?= "u-boot"
8UBOOT_MACHINE_raspberrypi2_sota ?= "rpi_2_defconfig" 3UBOOT_MACHINE_raspberrypi2_sota ?= "rpi_2_defconfig"
9UBOOT_MACHINE_raspberrypi3_sota ?= "rpi_3_32b_defconfig" 4UBOOT_MACHINE_raspberrypi3_sota ?= "rpi_3_32b_defconfig"
10 5
11OSTREE_BOOTLOADER ?= "u-boot" 6OSTREE_BOOTLOADER ?= "u-boot"
7
8# OSTree puts its own boot.scr to bcm2835-bootfiles
9IMAGE_BOOT_FILES_remove_sota += "boot.scr"
diff --git a/lib/oeqa/selftest/garage_push.py b/lib/oeqa/selftest/garage_push.py
deleted file mode 100644
index 3490de5..0000000
--- a/lib/oeqa/selftest/garage_push.py
+++ /dev/null
@@ -1,39 +0,0 @@
1import unittest
2import os
3import logging
4
5from oeqa.selftest.base import oeSelfTest
6from oeqa.utils.commands import runCmd, bitbake, get_bb_var
7
8class GaragePushTests(oeSelfTest):
9
10 @classmethod
11 def setUpClass(cls):
12 # Ensure we have the right data in pkgdata
13 logger = logging.getLogger("selftest")
14 logger.info('Running bitbake to build aktualizr-native tools')
15 bitbake('aktualizr-native garage-sign-native')
16
17 def test_help(self):
18 image_dir = get_bb_var("D", "aktualizr-native")
19 bin_dir = get_bb_var("bindir", "aktualizr-native")
20 gp_path = os.path.join(image_dir, bin_dir[1:], 'garage-push')
21 result = runCmd('%s --help' % gp_path, ignore_status=True)
22 self.assertEqual(result.status, 0, "Status not equal to 0. output: %s" % result.output)
23
24 def test_java(self):
25 result = runCmd('which java', ignore_status=True)
26 self.assertEqual(result.status, 0, "Java not found.")
27
28 def test_sign(self):
29 image_dir = get_bb_var("D", "garage-sign-native")
30 bin_dir = get_bb_var("bindir", "garage-sign-native")
31 gs_path = os.path.join(image_dir, bin_dir[1:], 'garage-sign')
32 result = runCmd('%s --help' % gs_path, ignore_status=True)
33 self.assertEqual(result.status, 0, "Status not equal to 0. output: %s" % result.output)
34
35 def test_push(self):
36 bitbake('core-image-minimal')
37 self.write_config('IMAGE_INSTALL_append = " man "')
38 bitbake('core-image-minimal')
39
diff --git a/lib/oeqa/selftest/qemucommand.py b/lib/oeqa/selftest/qemucommand.py
new file mode 120000
index 0000000..bc06dde
--- /dev/null
+++ b/lib/oeqa/selftest/qemucommand.py
@@ -0,0 +1 @@
../../../scripts/qemucommand.py \ No newline at end of file
diff --git a/lib/oeqa/selftest/updater.py b/lib/oeqa/selftest/updater.py
new file mode 100644
index 0000000..2723b4a
--- /dev/null
+++ b/lib/oeqa/selftest/updater.py
@@ -0,0 +1,147 @@
1import unittest
2import os
3import logging
4import subprocess
5import time
6
7from oeqa.selftest.base import oeSelfTest
8from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars
9from oeqa.selftest.qemucommand import QemuCommand
10
11
12class SotaToolsTests(oeSelfTest):
13
14 @classmethod
15 def setUpClass(cls):
16 logger = logging.getLogger("selftest")
17 logger.info('Running bitbake to build aktualizr-native tools')
18 bitbake('aktualizr-native')
19
20 def test_push_help(self):
21 bb_vars = get_bb_vars(['SYSROOT_DESTDIR', 'bindir'], 'aktualizr-native')
22 p = bb_vars['SYSROOT_DESTDIR'] + bb_vars['bindir'] + "/" + "garage-push"
23 self.assertTrue(os.path.isfile(p), msg = "No garage-push found (%s)" % p)
24 result = runCmd('%s --help' % p, ignore_status=True)
25 self.assertEqual(result.status, 0, "Status not equal to 0. output: %s" % result.output)
26
27 def test_deploy_help(self):
28 bb_vars = get_bb_vars(['SYSROOT_DESTDIR', 'bindir'], 'aktualizr-native')
29 p = bb_vars['SYSROOT_DESTDIR'] + bb_vars['bindir'] + "/" + "garage-deploy"
30 self.assertTrue(os.path.isfile(p), msg = "No garage-deploy found (%s)" % p)
31 result = runCmd('%s --help' % p, ignore_status=True)
32 self.assertEqual(result.status, 0, "Status not equal to 0. output: %s" % result.output)
33
34
35class GarageSignTests(oeSelfTest):
36
37 @classmethod
38 def setUpClass(cls):
39 logger = logging.getLogger("selftest")
40 logger.info('Running bitbake to build garage-sign-native')
41 bitbake('garage-sign-native')
42
43 def test_help(self):
44 bb_vars = get_bb_vars(['SYSROOT_DESTDIR', 'bindir'], 'garage-sign-native')
45 p = bb_vars['SYSROOT_DESTDIR'] + bb_vars['bindir'] + "/" + "garage-sign"
46 self.assertTrue(os.path.isfile(p), msg = "No garage-sign found (%s)" % p)
47 result = runCmd('%s --help' % p, ignore_status=True)
48 self.assertEqual(result.status, 0, "Status not equal to 0. output: %s" % result.output)
49
50
51class HsmTests(oeSelfTest):
52
53 def test_hsm(self):
54 self.write_config('SOTA_CLIENT_FEATURES="hsm hsm-test"')
55 bitbake('core-image-minimal')
56
57
58class GeneralTests(oeSelfTest):
59
60 def test_feature_sota(self):
61 result = get_bb_var('DISTRO_FEATURES').find('sota')
62 self.assertNotEqual(result, -1, 'Feature "sota" not set at DISTRO_FEATURES');
63
64 def test_feature_systemd(self):
65 result = get_bb_var('DISTRO_FEATURES').find('systemd')
66 self.assertNotEqual(result, -1, 'Feature "systemd" not set at DISTRO_FEATURES');
67
68 def test_credentials(self):
69 bitbake('core-image-minimal')
70 credentials = get_bb_var('SOTA_PACKED_CREDENTIALS')
71 # skip the test if the variable SOTA_PACKED_CREDENTIALS is not set
72 if credentials is None:
73 raise unittest.SkipTest("Variable 'SOTA_PACKED_CREDENTIALS' not set.")
74 # Check if the file exists
75 self.assertTrue(os.path.isfile(credentials), "File %s does not exist" % credentials)
76 deploydir = get_bb_var('DEPLOY_DIR_IMAGE')
77 imagename = get_bb_var('IMAGE_LINK_NAME', 'core-image-minimal')
78 # Check if the credentials are included in the output image
79 result = runCmd('tar -jtvf %s/%s.tar.bz2 | grep sota_provisioning_credentials.zip' % (deploydir, imagename), ignore_status=True)
80 self.assertEqual(result.status, 0, "Status not equal to 0. output: %s" % result.output)
81
82 def test_java(self):
83 result = runCmd('which java', ignore_status=True)
84 self.assertEqual(result.status, 0, "Java not found.")
85
86 def test_add_package(self):
87 print('')
88 deploydir = get_bb_var('DEPLOY_DIR_IMAGE')
89 imagename = get_bb_var('IMAGE_LINK_NAME', 'core-image-minimal')
90 image_path = deploydir + '/' + imagename + '.otaimg'
91 logger = logging.getLogger("selftest")
92
93 logger.info('Running bitbake with man in the image package list')
94 self.write_config('IMAGE_INSTALL_append = " man "')
95 bitbake('-c cleanall man')
96 bitbake('core-image-minimal')
97 result = runCmd('oe-pkgdata-util find-path /usr/bin/man')
98 self.assertEqual(result.output, 'man: /usr/bin/man')
99 path1 = os.path.realpath(image_path)
100 size1 = os.path.getsize(path1)
101 logger.info('First image %s has size %i' % (path1, size1))
102
103 logger.info('Running bitbake without man in the image package list')
104 self.write_config('IMAGE_INSTALL_remove = " man "')
105 bitbake('-c cleanall man')
106 bitbake('core-image-minimal')
107 result = runCmd('oe-pkgdata-util find-path /usr/bin/man', ignore_status=True)
108 self.assertEqual(result.status, 1, "Status different than 1. output: %s" % result.output)
109 self.assertEqual(result.output, 'ERROR: Unable to find any package producing path /usr/bin/man')
110 path2 = os.path.realpath(image_path)
111 size2 = os.path.getsize(path2)
112 logger.info('Second image %s has size %i' % (path2, size2))
113 self.assertNotEqual(path1, path2, "Image paths are identical; image was not rebuilt.")
114 self.assertNotEqual(size1, size2, "Image sizes are identical; image was not rebuilt.")
115
116 def test_qemu(self):
117 print('')
118 # Create empty object.
119 args = type('', (), {})()
120 args.imagename = 'core-image-minimal'
121 args.mac = None
122 # Could use DEPLOY_DIR_IMAGE here but it's already in the machine
123 # subdirectory.
124 args.dir = 'tmp/deploy/images'
125 args.efi = False
126 args.machine = None
127 args.kvm = None # Autodetect
128 args.no_gui = True
129 args.gdb = False
130 args.pcap = None
131 args.overlay = None
132 args.dry_run = False
133
134 qemu_command = QemuCommand(args)
135 cmdline = qemu_command.command_line()
136 print('Booting image with run-qemu-ota...')
137 s = subprocess.Popen(cmdline)
138 time.sleep(10)
139 print('Machine name (hostname) of device is:')
140 ssh_cmd = ['ssh', '-q', '-o', 'UserKnownHostsFile=/dev/null', '-o', 'StrictHostKeyChecking=no', 'root@localhost', '-p', str(qemu_command.ssh_port), 'hostname']
141 s2 = subprocess.Popen(ssh_cmd)
142 time.sleep(5)
143 try:
144 s.terminate()
145 except KeyboardInterrupt:
146 pass
147
diff --git a/recipes-core/images/initramfs-ostree-image.bb b/recipes-core/images/initramfs-ostree-image.bb
index 4870579..4ab9da8 100644
--- a/recipes-core/images/initramfs-ostree-image.bb
+++ b/recipes-core/images/initramfs-ostree-image.bb
@@ -15,7 +15,6 @@ LICENSE = "MIT"
15 15
16IMAGE_FSTYPES = "ext4.gz" 16IMAGE_FSTYPES = "ext4.gz"
17IMAGE_FSTYPES_append_arm = " ext4.gz.u-boot" 17IMAGE_FSTYPES_append_arm = " ext4.gz.u-boot"
18IMAGE_CLASSES_append_arm = " image_types_uboot"
19 18
20inherit core-image 19inherit core-image
21 20
diff --git a/recipes-sota/aktualizr/aktualizr-hsm-test-prov.bb b/recipes-sota/aktualizr/aktualizr-hsm-test-prov.bb
index 276c17e..c443c56 100644
--- a/recipes-sota/aktualizr/aktualizr-hsm-test-prov.bb
+++ b/recipes-sota/aktualizr/aktualizr-hsm-test-prov.bb
@@ -23,12 +23,12 @@ inherit systemd
23do_install() { 23do_install() {
24 install -d ${D}/${systemd_unitdir}/system 24 install -d ${D}/${systemd_unitdir}/system
25 install -m 0644 ${WORKDIR}/aktualizr-autoprovision.service ${D}/${systemd_unitdir}/system/aktualizr.service 25 install -m 0644 ${WORKDIR}/aktualizr-autoprovision.service ${D}/${systemd_unitdir}/system/aktualizr.service
26 install -d ${D}/usr/lib/sota 26 install -d ${D}${libdir}/sota
27 aktualizr_implicit_writer -c ${SOTA_PACKED_CREDENTIALS} --no-root-ca \ 27 aktualizr_implicit_writer -c ${SOTA_PACKED_CREDENTIALS} --no-root-ca \
28 -i ${WORKDIR}/sota_hsm_test.toml -o ${D}/usr/lib/sota/sota.toml -p ${D} 28 -i ${WORKDIR}/sota_hsm_test.toml -o ${D}${libdir}/sota/sota.toml -p ${D}
29} 29}
30 30
31FILES_${PN} = " \ 31FILES_${PN} = " \
32 ${systemd_unitdir}/system/aktualizr.service \ 32 ${systemd_unitdir}/system/aktualizr.service \
33 /usr/lib/sota/sota.toml \ 33 ${libdir}/sota/sota.toml \
34 " 34 "
diff --git a/recipes-sota/aktualizr/aktualizr_git.bb b/recipes-sota/aktualizr/aktualizr_git.bb
index 9a1a7a6..162065e 100644
--- a/recipes-sota/aktualizr/aktualizr_git.bb
+++ b/recipes-sota/aktualizr/aktualizr_git.bb
@@ -18,7 +18,7 @@ PR = "7"
18SRC_URI = " \ 18SRC_URI = " \
19 git://github.com/advancedtelematic/aktualizr;branch=${BRANCH} \ 19 git://github.com/advancedtelematic/aktualizr;branch=${BRANCH} \
20 " 20 "
21SRCREV = "67c4f44c4136d16871726449502e3926098e8524" 21SRCREV = "f043191ae622a96cf2f4d48f9073d5cfa9f16e3f"
22BRANCH ?= "master" 22BRANCH ?= "master"
23 23
24S = "${WORKDIR}/git" 24S = "${WORKDIR}/git"
@@ -33,7 +33,6 @@ EXTRA_OECMAKE_append_class-native = "-DBUILD_SOTA_TOOLS=ON -DBUILD_OSTREE=OFF "
33 33
34do_install_append () { 34do_install_append () {
35 rm -f ${D}${bindir}/aktualizr_cert_provider 35 rm -f ${D}${bindir}/aktualizr_cert_provider
36 rm -f ${D}${bindir}/garage-deploy
37} 36}
38do_install_append_class-target () { 37do_install_append_class-target () {
39 rm -f ${D}${bindir}/aktualizr_implicit_writer 38 rm -f ${D}${bindir}/aktualizr_implicit_writer
@@ -47,5 +46,6 @@ FILES_${PN}_class-target = " \
47 " 46 "
48FILES_${PN}_class-native = " \ 47FILES_${PN}_class-native = " \
49 ${bindir}/aktualizr_implicit_writer \ 48 ${bindir}/aktualizr_implicit_writer \
49 ${bindir}/garage-deploy \
50 ${bindir}/garage-push \ 50 ${bindir}/garage-push \
51 " 51 "
diff --git a/recipes-sota/aktualizr/files/sota_hsm_test.toml b/recipes-sota/aktualizr/files/sota_hsm_test.toml
index 1317914..28aefc2 100644
--- a/recipes-sota/aktualizr/files/sota_hsm_test.toml
+++ b/recipes-sota/aktualizr/files/sota_hsm_test.toml
@@ -12,6 +12,7 @@ pass = "1234"
12 12
13[uptane] 13[uptane]
14metadata_path = "/var/sota/metadata" 14metadata_path = "/var/sota/metadata"
15private_key_path = "ecukey.der" 15key_source = "pkcs11"
16public_key_path = "ecukey.pub" 16private_key_path = "03"
17public_key_path = "03"
17 18
diff --git a/recipes-sota/garage-sign/garage-sign.bb b/recipes-sota/garage-sign/garage-sign.bb
index ccd7299..d5388bc 100644
--- a/recipes-sota/garage-sign/garage-sign.bb
+++ b/recipes-sota/garage-sign/garage-sign.bb
@@ -6,14 +6,14 @@ LICENSE = "CLOSED"
6LIC_FILES_CHKSUM = "file://${S}/docs/LICENSE;md5=3025e77db7bd3f1d616b3ffd11d54c94" 6LIC_FILES_CHKSUM = "file://${S}/docs/LICENSE;md5=3025e77db7bd3f1d616b3ffd11d54c94"
7DEPENDS = "" 7DEPENDS = ""
8 8
9PV = "0.2.0-6-g6af6ecd" 9PV = "0.2.0-35-g0544c33"
10 10
11SRC_URI = " \ 11SRC_URI = " \
12 https://ats-tuf-cli-releases.s3-eu-central-1.amazonaws.com/cli-${PV}.tgz \ 12 https://ats-tuf-cli-releases.s3-eu-central-1.amazonaws.com/cli-${PV}.tgz \
13 " 13 "
14 14
15SRC_URI[md5sum] = "39941607ddef3a93476e267ad7bf6280" 15SRC_URI[md5sum] = "1546e06d1e747f67aee5ed7096bf1c74"
16SRC_URI[sha256sum] = "fbd2ea56f21341146844b02837377b08e63a3e361079e2c65142c2ed881c3b5d" 16SRC_URI[sha256sum] = "1432348bca8ca5ad75df1218f348f480d429d7509d6454deb6e16ff31c5e08fc"
17 17
18S = "${WORKDIR}/${BPN}" 18S = "${WORKDIR}/${BPN}"
19 19
diff --git a/recipes-support/ca-certificates/ca-certificates_%.bbappend b/recipes-support/ca-certificates/ca-certificates_%.bbappend
new file mode 100644
index 0000000..afaadfd
--- /dev/null
+++ b/recipes-support/ca-certificates/ca-certificates_%.bbappend
@@ -0,0 +1 @@
SYSROOT_DIRS += "/etc"
diff --git a/recipes-support/libp11/libp11_0.4.7.bb b/recipes-support/libp11/libp11_0.4.7.bb
new file mode 100644
index 0000000..7d77e90
--- /dev/null
+++ b/recipes-support/libp11/libp11_0.4.7.bb
@@ -0,0 +1,37 @@
1SUMMARY = "Library for using PKCS"
2DESCRIPTION = "\
3Libp11 is a library implementing a small layer on top of PKCS \
4make using PKCS"
5HOMEPAGE = "http://www.opensc-project.org/libp11"
6SECTION = "Development/Libraries"
7LICENSE = "LGPLv2+"
8LIC_FILES_CHKSUM = "file://COPYING;md5=fad9b3332be894bab9bc501572864b29"
9DEPENDS = "libtool openssl"
10
11SRC_URI = "git://github.com/OpenSC/libp11.git"
12SRCREV = "da725ab727342083478150a203a3c80c4551feb4"
13
14S = "${WORKDIR}/git"
15
16inherit autotools pkgconfig
17
18# Currently, Makefile dependencies are incorrectly defined which causes build errors
19# The number of jobs is high
20# See https://github.com/OpenSC/libp11/issues/94
21PARALLEL_MAKE = ""
22EXTRA_OECONF = "--disable-static"
23
24do_install_append () {
25 rm -rf ${D}${libdir}/*.la
26 rm -rf ${D}${docdir}/${BPN}
27}
28
29FILES_${PN} = "${libdir}/engines/pkcs11.so \
30 ${libdir}/engines/libpkcs11${SOLIBS} \
31 ${libdir}/libp11${SOLIBS}"
32
33FILES_${PN}-dev = " \
34 ${libdir}/engines/libpkcs11${SOLIBSDEV} \
35 ${libdir}/libp11${SOLIBSDEV} \
36 ${libdir}/pkgconfig/libp11.pc \
37 /usr/include"
diff --git a/scripts/lib/wic/plugins/source/otaimage.py b/scripts/lib/wic/plugins/source/otaimage.py
index eef0bb4..26cfb10 100644
--- a/scripts/lib/wic/plugins/source/otaimage.py
+++ b/scripts/lib/wic/plugins/source/otaimage.py
@@ -19,10 +19,12 @@ import logging
19import os 19import os
20import sys 20import sys
21 21
22from wic.pluginbase import SourcePlugin 22from wic.plugins.source.rawcopy import RawCopyPlugin
23from wic.utils.misc import get_bitbake_var 23from wic.utils.misc import get_bitbake_var
24 24
25class OTAImagePlugin(SourcePlugin): 25logger = logging.getLogger('wic')
26
27class OTAImagePlugin(RawCopyPlugin):
26 """ 28 """
27 Add an already existing filesystem image to the partition layout. 29 Add an already existing filesystem image to the partition layout.
28 """ 30 """
@@ -30,25 +32,6 @@ class OTAImagePlugin(SourcePlugin):
30 name = 'otaimage' 32 name = 'otaimage'
31 33
32 @classmethod 34 @classmethod
33 def do_install_disk(cls, disk, disk_name, cr, workdir, oe_builddir,
34 bootimg_dir, kernel_dir, native_sysroot):
35 """
36 Called after all partitions have been prepared and assembled into a
37 disk image. Do nothing.
38 """
39 pass
40
41 @classmethod
42 def do_configure_partition(cls, part, source_params, cr, cr_workdir,
43 oe_builddir, bootimg_dir, kernel_dir,
44 native_sysroot):
45 """
46 Called before do_prepare_partition(). Possibly prepare
47 configuration files of some sort.
48 """
49 pass
50
51 @classmethod
52 def do_prepare_partition(cls, part, source_params, cr, cr_workdir, 35 def do_prepare_partition(cls, part, source_params, cr, cr_workdir,
53 oe_builddir, bootimg_dir, kernel_dir, 36 oe_builddir, bootimg_dir, kernel_dir,
54 rootfs_dir, native_sysroot): 37 rootfs_dir, native_sysroot):
@@ -65,5 +48,10 @@ class OTAImagePlugin(SourcePlugin):
65 src = bootimg_dir + "/" + get_bitbake_var("IMAGE_LINK_NAME") + ".otaimg" 48 src = bootimg_dir + "/" + get_bitbake_var("IMAGE_LINK_NAME") + ".otaimg"
66 49
67 logger.debug('Preparing partition using image %s' % (src)) 50 logger.debug('Preparing partition using image %s' % (src))
68 part.prepare_rootfs_from_fs_image(cr_workdir, src, "") 51 source_params['file'] = src
52
53 super(OTAImagePlugin, cls).do_prepare_partition(part, source_params,
54 cr, cr_workdir, oe_builddir,
55 bootimg_dir, kernel_dir,
56 rootfs_dir, native_sysroot)
69 57
diff --git a/scripts/qemucommand.py b/scripts/qemucommand.py
new file mode 100644
index 0000000..82a9540
--- /dev/null
+++ b/scripts/qemucommand.py
@@ -0,0 +1,127 @@
1from os.path import exists, join, realpath, abspath
2from os import listdir
3import random
4import socket
5from subprocess import check_output, CalledProcessError
6
7EXTENSIONS = {
8 'intel-corei7-64': 'wic',
9 'qemux86-64': 'otaimg'
10}
11
12
13def find_local_port(start_port):
14 """"
15 Find the next free TCP port after 'start_port'.
16 """
17
18 for port in range(start_port, start_port + 10):
19 try:
20 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
21 s.bind(('', port))
22 return port
23 except socket.error:
24 print("Skipping port %d" % port)
25 finally:
26 s.close()
27 raise Exception("Could not find a free TCP port")
28
29
30def random_mac():
31 """Return a random Ethernet MAC address
32 @link https://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml#ethernet-numbers-2
33 """
34 head = "ca:fe:"
35 hex_digits = '0123456789abcdef'
36 tail = ':'.join([random.choice(hex_digits) + random.choice(hex_digits) for _ in range(4)])
37 return head + tail
38
39
40class QemuCommand(object):
41 def __init__(self, args):
42 if args.machine:
43 self.machine = args.machine
44 else:
45 machines = listdir(args.dir)
46 if len(machines) == 1:
47 self.machine = machines[0]
48 else:
49 raise ValueError("Could not autodetect machine type from %s" % args.dir)
50 if args.efi:
51 self.bios = 'OVMF.fd'
52 else:
53 uboot = abspath(join(args.dir, self.machine, 'u-boot-qemux86-64.rom'))
54 if not exists(uboot):
55 raise ValueError("U-Boot image %s does not exist" % uboot)
56 self.bios = uboot
57 if exists(args.imagename):
58 image = args.imagename
59 else:
60 ext = EXTENSIONS.get(self.machine, 'wic')
61 image = join(args.dir, self.machine, '%s-%s.%s' % (args.imagename, self.machine, ext))
62 self.image = realpath(image)
63 if not exists(self.image):
64 raise ValueError("OS image %s does not exist" % self.image)
65 if args.mac:
66 self.mac_address = args.mac
67 else:
68 self.mac_address = random_mac()
69 self.serial_port = find_local_port(8990)
70 self.ssh_port = find_local_port(2222)
71 if args.kvm is None:
72 # Autodetect KVM using 'kvm-ok'
73 try:
74 check_output(['kvm-ok'])
75 self.kvm = True
76 except CalledProcessError:
77 self.kvm = False
78 else:
79 self.kvm = args.kvm
80 self.gui = not args.no_gui
81 self.gdb = args.gdb
82 self.pcap = args.pcap
83 self.overlay = args.overlay
84
85 def command_line(self):
86 netuser = 'user,hostfwd=tcp:0.0.0.0:%d-:22,restrict=off' % self.ssh_port
87 if self.gdb:
88 netuser += ',hostfwd=tcp:0.0.0.0:2159-:2159'
89 cmdline = [
90 "qemu-system-x86_64",
91 "-bios", self.bios
92 ]
93 if not self.overlay:
94 cmdline += ["-drive", "file=%s,if=ide,format=raw,snapshot=on" % self.image]
95 cmdline += [
96 "-serial", "tcp:127.0.0.1:%d,server,nowait" % self.serial_port,
97 "-m", "1G",
98 "-usb",
99 "-usbdevice", "tablet",
100 "-show-cursor",
101 "-vga", "std",
102 "-net", netuser,
103 "-net", "nic,macaddr=%s" % self.mac_address
104 ]
105 if self.pcap:
106 cmdline += ['-net', 'dump,file=' + self.pcap]
107 if self.gui:
108 cmdline += ["-serial", "stdio"]
109 else:
110 cmdline.append('-nographic')
111 if self.kvm:
112 cmdline.append('-enable-kvm')
113 else:
114 cmdline += ['-cpu', 'Haswell']
115 if self.overlay:
116 cmdline.append(self.overlay)
117 return cmdline
118
119 def img_command_line(self):
120 cmdline = [
121 "qemu-img", "create",
122 "-o", "backing_file=%s" % self.image,
123 "-f", "qcow2",
124 self.overlay]
125 return cmdline
126
127
diff --git a/scripts/run-qemu-ota b/scripts/run-qemu-ota
index 5334814..56e4fbc 100755
--- a/scripts/run-qemu-ota
+++ b/scripts/run-qemu-ota
@@ -2,126 +2,12 @@
2 2
3from argparse import ArgumentParser 3from argparse import ArgumentParser
4from subprocess import Popen 4from subprocess import Popen
5from os.path import exists, join, realpath 5from os.path import exists
6from os import listdir
7import random
8import sys 6import sys
9import socket 7from qemucommand import QemuCommand
10 8
11DEFAULT_DIR = 'tmp/deploy/images' 9DEFAULT_DIR = 'tmp/deploy/images'
12 10
13EXTENSIONS = {
14 'intel-corei7-64': 'wic',
15 'qemux86-64': 'otaimg'
16}
17
18
19def find_local_port(start_port):
20 """"
21 Find the next free TCP port after 'start_port'.
22 """
23
24 for port in range(start_port, start_port + 10):
25 try:
26 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
27 s.bind(('', port))
28 return port
29 except socket.error:
30 print("Skipping port %d" % port)
31 finally:
32 s.close()
33 raise Exception("Could not find a free TCP port")
34
35
36def random_mac():
37 """Return a random Ethernet MAC address
38 @link https://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml#ethernet-numbers-2
39 """
40 head = "ca:fe:"
41 hex_digits = '0123456789abcdef'
42 tail = ':'.join([random.choice(hex_digits) + random.choice(hex_digits) for _ in range(4)])
43 return head + tail
44
45
46class QemuCommand(object):
47 def __init__(self, args):
48 if args.machine:
49 self.machine = args.machine
50 else:
51 machines = listdir(args.dir)
52 if len(machines) == 1:
53 self.machine = machines[0]
54 else:
55 raise ValueError("Could not autodetect machine type from %s" % args.dir)
56 if args.efi:
57 self.bios = 'OVMF.fd'
58 else:
59 uboot = join(args.dir, self.machine, 'u-boot-qemux86-64.rom')
60 if not exists(uboot):
61 raise ValueError("U-Boot image %s does not exist" % uboot)
62 self.bios = uboot
63 if exists(args.imagename):
64 image = args.imagename
65 else:
66 ext = EXTENSIONS.get(self.machine, 'wic')
67 image = join(args.dir, self.machine, '%s-%s.%s' % (args.imagename, self.machine, ext))
68 self.image = realpath(image)
69 if not exists(self.image):
70 raise ValueError("OS image %s does not exist" % self.image)
71 if args.mac:
72 self.mac_address = args.mac
73 else:
74 self.mac_address = random_mac()
75 self.serial_port = find_local_port(8990)
76 self.ssh_port = find_local_port(2222)
77 self.kvm = not args.no_kvm
78 self.gui = not args.no_gui
79 self.gdb = args.gdb
80 self.pcap = args.pcap
81 self.overlay = args.overlay
82
83 def command_line(self):
84 netuser = 'user,hostfwd=tcp:0.0.0.0:%d-:22,restrict=off' % self.ssh_port
85 if self.gdb:
86 netuser += ',hostfwd=tcp:0.0.0.0:2159-:2159'
87 cmdline = [
88 "qemu-system-x86_64",
89 "-bios", self.bios
90 ]
91 if not self.overlay:
92 cmdline += ["-drive", "file=%s,if=ide,format=raw,snapshot=on" % self.image]
93 cmdline += [
94 "-serial", "tcp:127.0.0.1:%d,server,nowait" % self.serial_port,
95 "-m", "1G",
96 "-usb",
97 "-usbdevice", "tablet",
98 "-show-cursor",
99 "-vga", "std",
100 "-net", netuser,
101 "-net", "nic,macaddr=%s" % self.mac_address
102 ]
103 if self.pcap:
104 cmdline += ['-net', 'dump,file=' + self.pcap]
105 if self.gui:
106 cmdline += ["-serial", "stdio"]
107 else:
108 cmdline.append('-nographic')
109 if self.kvm:
110 cmdline.append('-enable-kvm')
111 else:
112 cmdline += ['-cpu', 'Haswell']
113 if self.overlay:
114 cmdline.append(self.overlay)
115 return cmdline
116
117 def img_command_line(self):
118 cmdline = [
119 "qemu-img", "create",
120 "-o", "backing_file=%s" % self.image,
121 "-f", "qcow2",
122 self.overlay]
123 return cmdline
124
125 11
126def main(): 12def main():
127 parser = ArgumentParser(description='Run meta-updater image in qemu') 13 parser = ArgumentParser(description='Run meta-updater image in qemu')
@@ -135,11 +21,18 @@ def main():
135 'OSTREE_BOOTLOADER = "grub" and OVMF.fd firmware to be installed (try "apt install ovmf")', 21 'OSTREE_BOOTLOADER = "grub" and OVMF.fd firmware to be installed (try "apt install ovmf")',
136 action='store_true') 22 action='store_true')
137 parser.add_argument('--machine', default=None, help="Target MACHINE") 23 parser.add_argument('--machine', default=None, help="Target MACHINE")
138 parser.add_argument('--no-kvm', help='Disable KVM in QEMU', action='store_true') 24 kvm_group = parser.add_argument_group()
25 kvm_group.add_argument('--force-kvm', help='Force use of KVM (default is to autodetect)',
26 dest='kvm', action='store_true', default=None)
27 kvm_group.add_argument('--no-kvm', help='Disable KVM in QEMU',
28 dest='kvm', action='store_false')
139 parser.add_argument('--no-gui', help='Disable GUI', action='store_true') 29 parser.add_argument('--no-gui', help='Disable GUI', action='store_true')
140 parser.add_argument('--gdb', help='Export gdbserver port 2159 from the image', action='store_true') 30 parser.add_argument('--gdb', help='Export gdbserver port 2159 from the image', action='store_true')
141 parser.add_argument('--pcap', default=None, help='Dump all network traffic') 31 parser.add_argument('--pcap', default=None, help='Dump all network traffic')
142 parser.add_argument('-o', '--overlay', type=str, metavar='file.cow', help='Use an overlay storage image file. Will be created if it does not exist. This option lets you have a persistent image without modifying the underlying image file, permitting multiple different persistent machines.') 32 parser.add_argument('-o', '--overlay', type=str, metavar='file.cow',
33 help='Use an overlay storage image file. Will be created if it does not exist. ' +
34 'This option lets you have a persistent image without modifying the underlying image ' +
35 'file, permitting multiple different persistent machines.')
143 parser.add_argument('-n', '--dry-run', help='Print qemu command line rather then run it', action='store_true') 36 parser.add_argument('-n', '--dry-run', help='Print qemu command line rather then run it', action='store_true')
144 args = parser.parse_args() 37 args = parser.parse_args()
145 try: 38 try:
@@ -161,7 +54,7 @@ def main():
161 if args.dry_run: 54 if args.dry_run:
162 print(" ".join(img_cmdline)) 55 print(" ".join(img_cmdline))
163 else: 56 else:
164 Popen(img_cmdline) 57 Popen(img_cmdline).wait()
165 58
166 if args.dry_run: 59 if args.dry_run:
167 print(" ".join(cmdline)) 60 print(" ".join(cmdline))