diff --git a/.github/workflows/build-daily.yml b/.github/workflows/build-daily.yml index 0374e0e8..b328a56c 100644 --- a/.github/workflows/build-daily.yml +++ b/.github/workflows/build-daily.yml @@ -9,10 +9,10 @@ on: # implicitely set all other permissions to none permissions: - checks: write # test.yml - contents: read # debos.yml test.yml - packages: read # test.yml - pull-requests: write # test.yml + checks: write # lava-test.yml + contents: read # debos.yml lava-test.yml + packages: read # lava-test.yml + pull-requests: write # lava-test.yml jobs: build-daily: @@ -23,7 +23,7 @@ jobs: test-daily: # don't run cron from forks of the main repository or from other branches if: github.repository == 'qualcomm-linux/qcom-deb-images' && github.ref == 'refs/heads/main' - uses: ./.github/workflows/test.yml + uses: ./.github/workflows/lava-test.yml needs: build-daily secrets: inherit with: diff --git a/.github/workflows/build-on-pr.yml b/.github/workflows/build-on-pr.yml index 830f14b8..8321482f 100644 --- a/.github/workflows/build-on-pr.yml +++ b/.github/workflows/build-on-pr.yml @@ -5,10 +5,10 @@ on: # implicitely set all other permissions to none permissions: - checks: write # test.yml - contents: read # debos.yml lava-schema-check.yml test.yml - packages: read # test.yml - pull-requests: write # test.yml + checks: write # lava-test.yml + contents: read # debos.yml lava-schema-check.yml lava-test.yml + packages: read # lava-test.yml + pull-requests: write # lava-test.yml jobs: event-file: diff --git a/.github/workflows/build-on-push.yml b/.github/workflows/build-on-push.yml index 9399c80e..576bbd9f 100644 --- a/.github/workflows/build-on-push.yml +++ b/.github/workflows/build-on-push.yml @@ -6,10 +6,10 @@ on: # implicitely set all other permissions to none permissions: - checks: write # test.yml - contents: read # debos.yml lava-schema-check.yml test.yml - packages: read # test.yml - pull-requests: write # test.yml + checks: write # lava-test.yml + contents: read # debos.yml lava-schema-check.yml lava-test.yml + packages: read # lava-test.yml + pull-requests: write # lava-test.yml jobs: build-daily: @@ -17,7 +17,7 @@ jobs: schema-check: uses: ./.github/workflows/lava-schema-check.yml test: - uses: ./.github/workflows/test.yml + uses: ./.github/workflows/lava-test.yml needs: [build-daily, schema-check] secrets: inherit with: diff --git a/.github/workflows/debos.yml b/.github/workflows/debos.yml index 13e968fe..dd1cc8ca 100644 --- a/.github/workflows/debos.yml +++ b/.github/workflows/debos.yml @@ -61,8 +61,8 @@ jobs: run: cp -av "/fileserver-downloads/qcom-deb-images/u-boot-rb1-latest/rb1-boot.img" . # mtools is needed for the flash recipe - - name: Install debos and dependencies of the recipes - run: apt -y install debos mtools + - name: Install debos and dependencies of the recipes and local tests + run: apt -y install debos make mtools python3-pexpect python3-pytest qemu-efi-aarch64 qemu-system-arm - name: Setup local APT repo run: | @@ -210,3 +210,12 @@ jobs: with: name: build_url path: build_url + - name: Invoke test runner + run: | + # This is currently a clone of Makefile's "test" target to avoid any + # unexpected interactions with triggering depending build targets. + # The plan is to move this entire workflow to use make targets + # instead but all at once. See #74 and #159. + + # rootfs/ is a build artifact, so should not be scanned for tests + py.test-3 --ignore=rootfs diff --git a/.github/workflows/test.yml b/.github/workflows/lava-test.yml similarity index 100% rename from .github/workflows/test.yml rename to .github/workflows/lava-test.yml diff --git a/.github/workflows/test-pr.yml b/.github/workflows/test-pr.yml index 1eb9a32d..bc9567d6 100644 --- a/.github/workflows/test-pr.yml +++ b/.github/workflows/test-pr.yml @@ -10,10 +10,10 @@ on: # implicitely set all other permissions to none permissions: - checks: write # test.yml EnricoMi/publish-unit-test-result-action - contents: read # test.yml actions/checkout - packages: read # test.yml actions/download-artifact - # test.yml EnricoMi/publish-unit-test-result-action + checks: write # lava-test.yml EnricoMi/publish-unit-test-result-action + contents: read # lava-test.yml actions/checkout + packages: read # lava-test.yml actions/download-artifact + # lava-test.yml EnricoMi/publish-unit-test-result-action # thollander/actions-comment-pull-request pull-requests: write @@ -59,7 +59,7 @@ jobs: echo "url=${BUILD_URL}" >> $GITHUB_OUTPUT test: - uses: ./.github/workflows/test.yml + uses: ./.github/workflows/lava-test.yml secrets: inherit needs: retrieve-build-url with: diff --git a/Makefile b/Makefile index 58516bd7..012321dc 100644 --- a/Makefile +++ b/Makefile @@ -17,10 +17,14 @@ all: disk-ufs.img.gz disk-sdcard.img.gz rootfs.tar.gz: debos-recipes/qualcomm-linux-debian-rootfs.yaml $(DEBOS) $< -disk-ufs.img.gz: debos-recipes/qualcomm-linux-debian-image.yaml rootfs.tar.gz +disk-ufs.img disk-ufs.img.gz: debos-recipes/qualcomm-linux-debian-image.yaml rootfs.tar.gz $(DEBOS) $< disk-sdcard.img.gz: debos-recipes/qualcomm-linux-debian-image.yaml rootfs.tar.gz $(DEBOS) -t imagetype:sdcard $< -.PHONY: all +test: disk-ufs.img + # rootfs/ is a build artifact, so should not be scanned for tests + py.test-3 --ignore=rootfs + +.PHONY: all test diff --git a/README.md b/README.md index 1a6e2257..f3124143 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,76 @@ NB: It's also possible to run qdl from the host while the baord is not connected Want to join in the development? Changes welcome! See [CONTRIBUTING.md file](CONTRIBUTING.md) for step by step instructions. +### Test an image locally with qemu + +You can boot an image locally with qemu as follows: + +1. Install dependencies. `qemu-system-arm` is required together with +an aarch64 build of UEFI. On Debian and Ubuntu, this is provided by the +`qemu-system-arm` package which recommends `qemu-efi-aarch64`: + ```bash + sudo apt install qemu-system-arm qemu-efi-aarch64 + ``` + +1. As above under "Usage", build the disk image from the root filesystem + tarball if you haven't done this already: + ```bash + debos debos-recipes/qualcomm-linux-debian-image.yaml + ``` + +1. Run qemu as follows: + ```bash + # SCSI is required to present a device with a matching 4096 sector size + # inside the VM + qemu-system-aarch64 -cpu cortex-a57 -m 2048 -M virt -nographic \ + -device virtio-scsi-pci,id=scsi1 \ + -device scsi-hd,bus=scsi1.0,drive=disk1,physical_block_size=4096,logical_block_size=4096 \ + -drive if=none,file=disk-ufs.img,format=raw,id=disk1 \ + -bios /usr/share/AAVMF/AAVMF_CODE.fd + ``` + +#### Copy on write + +Instead of modifying `file-ufs.img`, you can arrange copy-on-write, for example +to reproduce the same first boot multiple times: + +1. Prepare a qcow file to contain the writes, backed by `disk-ufs.img`: + ```bash + qemu-img create -b disk-ufs.img -f qcow -F raw disk1.qcow + ``` + +1. Run qemu as follows: + ```bash + qemu-system-aarch64 -cpu cortex-a57 -m 2048 -M virt -nographic \ + -device virtio-scsi-pci,id=scsi1 \ + -device scsi-hd,bus=scsi1.0,drive=disk1,physical_block_size=4096,logical_block_size=4096 \ + -drive if=none,file=disk1.img,format=qcow,id=disk1 \ + -bios /usr/share/AAVMF/AAVMF_CODE.fd + ``` + +#### Direct kernel boot + +For debugging purposes, it is sometimes useful to boot the kernel directly, for +example to confirm that an issue in the image lies in the bootloader +installation. You can do this as follows: + +1. Extract the rootfs: + ```bash + mkdir rootfs + tar xzC rootfs -f rootfs.tar.gz + ``` + +2. Run qemu against the kernel and initrd present inside the rootfs directly: + ```bash + qemu-system-aarch64 -cpu cortex-a57 -m 2048 -M virt -nographic \ + -device virtio-scsi-pci,id=scsi1 \ + -device scsi-hd,bus=scsi1.0,drive=disk1,physical_block_size=4096,logical_block_size=4096 \ + -drive if=none,file=disk-ufs.img,format=raw,id=disk1 \ + -kernel rootfs/boot/vmlinuz-* + -initrd rootfs/boot/initrd.img-* + -append root=/dev/sda2 + ``` + ## Reporting Issues We'd love to hear if you run into issues or have ideas for improvements. [Report an Issue on GitHub](../../issues) to discuss, and try to include as much information as possible on your specific environment. diff --git a/ci/lava/qcs6490-rb3gen2-core-kit/boot.yaml b/ci/lava/qcs6490-rb3gen2-core-kit/boot.yaml index 80ba2511..dce40fc1 100644 --- a/ci/lava/qcs6490-rb3gen2-core-kit/boot.yaml +++ b/ci/lava/qcs6490-rb3gen2-core-kit/boot.yaml @@ -37,11 +37,17 @@ actions: password_prompt: 'Password' password: debian login_commands: + - "debian" + - "new password" + - "new password" - sudo su method: minimal prompts: - root@debian - debian@debian + - "Current password" + - "New password" + - "Retype new password" timeout: minutes: 3 - test: diff --git a/ci/lava/qrb2210-rb1/boot.yaml b/ci/lava/qrb2210-rb1/boot.yaml index d6c2f4ad..aee29db9 100644 --- a/ci/lava/qrb2210-rb1/boot.yaml +++ b/ci/lava/qrb2210-rb1/boot.yaml @@ -38,11 +38,17 @@ actions: password_prompt: 'Password' password: debian login_commands: + - "debian" + - "new password" + - "new password" - sudo su method: minimal prompts: - root@debian - debian@debian + - "Current password" + - "New password" + - "Retype new password" timeout: minutes: 3 - test: diff --git a/ci/qemu_test.py b/ci/qemu_test.py new file mode 100644 index 00000000..e54b3f2a --- /dev/null +++ b/ci/qemu_test.py @@ -0,0 +1,82 @@ +"""Tests that are entirely qemu based, so do not require test hardware""" + +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause + +import os +import signal +import subprocess +import tempfile + +import pexpect +import pytest + + +@pytest.fixture +def vm(): + """A pexpect.spawn object attached to the serial console of a VM freshly + booting with a CoW base of disk-ufs.img""" + with tempfile.TemporaryDirectory() as tmpdir: + qcow_path = os.path.join(tmpdir, "disk1.qcow") + subprocess.run( + [ + "qemu-img", + "create", + "-b", + os.path.join(os.getcwd(), "disk-ufs.img"), + "-f", + "qcow", + "-F", + "raw", + qcow_path, + ], + check=True, + ) + child = pexpect.spawn( + "qemu-system-aarch64", + [ + "-cpu", + "cortex-a57", + "-m", + "2048", + "-M", + "virt", + "-drive", + f"if=none,file={qcow_path},format=qcow,id=disk1", + "-device", + "virtio-scsi-pci,id=scsi1", + "-device", + "scsi-hd,bus=scsi1.0,drive=disk1,physical_block_size=4096,logical_block_size=4096", + "-nographic", + "-bios", + "/usr/share/AAVMF/AAVMF_CODE.fd", + ], + ) + yield child + + # No need to be nice; that would take time + child.kill(signal.SIGKILL) + + # If this blocks then we have a problem. Better to hang than build up + # excess qemu processes that won't die. + child.wait() + + +def test_password_reset_required(vm): + """On first login, there should be a mandatory reset password flow""" + # https://github.com/qualcomm-linux/qcom-deb-images/issues/69 + + # This takes a minute or two on a ThinkPad T14s Gen 6 Snapdragon + vm.expect_exact("debian login:", timeout=240) + + vm.send("debian\r\n") + vm.expect_exact("Password:") + vm.send("debian\r\n") + vm.expect_exact("You are required to change your password immediately") + vm.expect_exact("Current password:") + vm.send("debian\r\n") + vm.expect_exact("New password:") + vm.send("new password\r\n") + vm.expect_exact("Retype new password:") + vm.send("new password\r\n") + vm.expect_exact("debian@debian:~$") diff --git a/debos-recipes/qualcomm-linux-debian-image.yaml b/debos-recipes/qualcomm-linux-debian-image.yaml index f42cc55a..a1a3cfd8 100644 --- a/debos-recipes/qualcomm-linux-debian-image.yaml +++ b/debos-recipes/qualcomm-linux-debian-image.yaml @@ -138,9 +138,9 @@ actions: done - action: run - description: Compress image file + description: Add compressed image file postprocess: true - command: gzip -v -f "${ARTIFACTDIR}/{{ $image }}" + command: gzip -v -f -k "${ARTIFACTDIR}/{{ $image }}" # Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. # SPDX-License-Identifier: BSD-3-Clause diff --git a/debos-recipes/qualcomm-linux-debian-rootfs.yaml b/debos-recipes/qualcomm-linux-debian-rootfs.yaml index ed5f22d3..e66abad1 100644 --- a/debos-recipes/qualcomm-linux-debian-rootfs.yaml +++ b/debos-recipes/qualcomm-linux-debian-rootfs.yaml @@ -119,9 +119,10 @@ actions: # session has not updated ACLs to the device nodes useradd --create-home --shell /bin/bash --user-group \ --groups adm,audio,render,sudo,users,video debian - # password must be changed on first login; set it to "debian" - chage --lastday 0 debian + # set password to "debian" echo debian:debian | chpasswd + # password must be changed on first login + chage --lastday 0 debian # add to sudoers mkdir -v --mode 755 --parents /etc/sudoers.d # subshell to override umask