def test_image_update_broken_kernel( self, bitbake_variables, connection, latest_mender_image, http_server, board_type, use_s3, s3_address, ): """Test that an update with a broken kernel rolls back correctly. This is distinct from the test_broken_image_update test, which corrupts the filesystem. When grub.d integration is enabled, these two scenarios trigger very different code paths.""" file_flag = Helpers.get_file_flag(bitbake_variables) (active_before, passive_before) = determine_active_passive_part( bitbake_variables, connection) image_type = bitbake_variables["MENDER_DEVICE_TYPE"] temp_artifact = "temporary_artifact.mender" try: shutil.copyfile(latest_mender_image, temp_artifact) # Assume that artifact has the same kernel names as the currently # running image. kernels = connection.run( "find /boot/ -maxdepth 1 -name '*linu[xz]*' -o -name '*Image'" ).stdout.split() for kernel in kernels: # Inefficient, but there shouldn't be too many kernels. subprocess.check_call( ["mender-artifact", "rm", f"{temp_artifact}:{kernel}"]) Helpers.install_update( temp_artifact, connection, http_server, board_type, use_s3, s3_address, ) reboot(connection) # Now qemu is auto-rebooted twice; once to boot the dummy image, # where it fails, and the boot loader auto-reboots a second time # into the original partition. output = run_after_connect("mount", connection) # The update should have reverted to the original active partition, # since the kernel was missing. assert output.find(active_before) >= 0 assert output.find(passive_before) < 0 finally: os.remove(temp_artifact)
def test_broken_image_update(self, bitbake_variables, connection): """Test that an update with a broken filesystem rolls back correctly.""" file_flag = Helpers.get_file_flag(bitbake_variables) (active_before, passive_before) = determine_active_passive_part( bitbake_variables, connection) image_type = bitbake_variables["MENDER_DEVICE_TYPE"] try: # Make a dummy/broken update retcode = subprocess.call( "dd if=/dev/zero of=image.dat bs=1M count=0 seek=16", shell=True) if retcode != 0: raise Exception("error creating dummy image") retcode = subprocess.call( "mender-artifact write rootfs-image -t %s -n test-update %s image.dat -o image.mender" % (image_type, file_flag), shell=True, ) if retcode != 0: raise Exception( "error writing mender artifact using command: mender-artifact write rootfs-image -t %s -n test-update %s image.dat -o image.mender" % (image_type, file_flag)) put_no_sftp("image.mender", connection, remote="/var/tmp/image.mender") connection.run("mender install /var/tmp/image.mender") reboot(connection) # Now qemu is auto-rebooted twice; once to boot the dummy image, # where it fails, and the boot loader auto-reboots a second time # into the original partition. output = run_after_connect("mount", connection) # The update should have reverted to the original active partition, # since the image was bogus. assert output.find(active_before) >= 0 assert output.find(passive_before) < 0 finally: # Cleanup. if os.path.exists("image.mender"): os.remove("image.mender") if os.path.exists("image.dat"): os.remove("image.dat")
def do_install_mender_binary_delta( self, request, prepared_test_build, bitbake_variables, bitbake_image, connection, http_server, board_type, use_s3, s3_address, ): build_image( prepared_test_build["build_dir"], prepared_test_build["bitbake_corebase"], bitbake_image, ['IMAGE_INSTALL_append = " mender-binary-delta"'], [ 'BBLAYERS_append = " %s/../meta-mender-commercial"' % bitbake_variables["LAYERDIR_MENDER"] ], ) image = latest_build_artifact(request, prepared_test_build["build_dir"], "core-image*.mender") Helpers.install_update(image, connection, http_server, board_type, use_s3, s3_address) reboot(connection) run_after_connect("true", connection) connection.run("mender -commit") return image
def test_perform_update( self, request, setup_board, prepared_test_build, bitbake_variables, bitbake_image, connection, http_server, board_type, use_s3, s3_address, ): """Perform a delta update. """ if ("read-only-rootfs" not in bitbake_variables["IMAGE_FEATURES"].strip().split()): pytest.skip("Only works when using read-only-rootfs IMAGE_FEATURE") if distutils.spawn.find_executable( "mender-binary-delta-generator") is None: pytest.fail("mender-binary-delta-generator not found in PATH") built_artifact = self.do_install_mender_binary_delta( request, prepared_test_build, bitbake_variables, bitbake_image, connection, http_server, board_type, use_s3, s3_address, ) with make_tempdir() as tmpdir: # Copy previous build artifact_from = os.path.join(tmpdir, "artifact_from.mender") shutil.copyfile(built_artifact, artifact_from) # Create new image installing some extra software build_image( prepared_test_build["build_dir"], prepared_test_build["bitbake_corebase"], bitbake_image, ['IMAGE_INSTALL_append = " nano"'], ) built_artifact = latest_build_artifact( request, prepared_test_build["build_dir"], "core-image*.mender") artifact_to = os.path.join(tmpdir, "artifact_to.mender") shutil.copyfile(built_artifact, artifact_to) # Create delta Artifact using mender-binary-delta-generator artifact_delta = os.path.join(tmpdir, "artifact_delta.mender") subprocess.check_call( f"mender-binary-delta-generator -n v2.0-deltafrom-v1.0 {artifact_from} {artifact_to} -o {artifact_delta}", shell=True, ) # Verbose provides/depends of the different Artifacts and the client (when supported) connection.run("mender show-provides", warn=True) subprocess.check_call( "mender-artifact read %s" % artifact_from, shell=True, ) subprocess.check_call( "mender-artifact read %s" % artifact_to, shell=True, ) subprocess.check_call( "mender-artifact read %s" % artifact_delta, shell=True, ) # Install Artifact, verify partitions and commit (active, passive) = determine_active_passive_part(bitbake_variables, connection) Helpers.install_update(artifact_delta, connection, http_server, board_type, use_s3, s3_address) reboot(connection) run_after_connect("true", connection) (new_active, new_passive) = determine_active_passive_part( bitbake_variables, connection) assert new_active == passive assert new_passive == active connection.run("mender -commit")
def test_redundant_grub_env(self, successful_image_update_mender, bitbake_variables, connection): """This tests pretty much the same thing as the test_redundant_uboot_env above, but the details differ. U-Boot maintains a counter in each environment, and then only updates one of them. However, the GRUB variant we have implemented in the GRUB scripting language, where we cannot do this, so instead we update both, and use the validity of the variables instead as a crude checksum.""" (active, passive) = determine_active_passive_part(bitbake_variables, connection) # Corrupt the passive partition. connection.run("dd if=/dev/zero of=%s bs=1024 count=1024" % passive) if ("mender-bios" in bitbake_variables.get("MENDER_FEATURES", "").split() or "mender-bios" in bitbake_variables.get( "DISTRO_FEATURES", "").split()): env_dir = "/boot/grub/grub-mender-grubenv" else: env_dir = "/boot/efi/grub-mender-grubenv" # Now try to corrupt the environment, and make sure it doesn't get booted into. for env_num in [1, 2]: # Make a copy of the two environments. connection.run( "cp %s/{mender_grubenv1/env,mender_grubenv1/env.backup}" % env_dir) connection.run( "cp %s/{mender_grubenv1/lock,mender_grubenv1/lock.backup}" % env_dir) connection.run( "cp %s/{mender_grubenv2/env,mender_grubenv2/env.backup}" % env_dir) connection.run( "cp %s/{mender_grubenv2/lock,mender_grubenv2/lock.backup}" % env_dir) try: env_file = "%s/mender_grubenv%d/env" % (env_dir, env_num) lock_file = "%s/mender_grubenv%d/lock" % (env_dir, env_num) connection.run('sed -e "s/editing=.*/editing=1/" %s' % lock_file) connection.run( 'sed -e "s/mender_boot_part=.*/mender_boot_part=%s/" %s' % (passive[-1], lock_file)) reboot(connection) run_after_connect("true", connection) (new_active, new_passive) = determine_active_passive_part( bitbake_variables, connection) assert new_active == active assert new_passive == passive finally: # Restore the two environments. connection.run( "mv %s/{mender_grubenv1/env.backup,mender_grubenv1/env}" % env_dir) connection.run( "mv %s/{mender_grubenv1/lock.backup,mender_grubenv1/lock}" % env_dir) connection.run( "mv %s/{mender_grubenv2/env.backup,mender_grubenv2/env}" % env_dir) connection.run( "mv %s/{mender_grubenv2/lock.backup,mender_grubenv2/lock}" % env_dir)
def test_network_based_image_update( self, successful_image_update_mender, bitbake_variables, connection, http_server, board_type, use_s3, s3_address, ): (active_before, passive_before) = determine_active_passive_part( bitbake_variables, connection) Helpers.install_update( successful_image_update_mender, connection, http_server, board_type, use_s3, s3_address, ) bootenv_print, _ = bootenv_tools(connection) output = connection.run(f"{bootenv_print} bootcount").stdout assert output.rstrip("\n") == "bootcount=0" output = connection.run(f"{bootenv_print} upgrade_available").stdout assert output.rstrip("\n") == "upgrade_available=1" output = connection.run(f"{bootenv_print} mender_boot_part").stdout assert output.rstrip("\n") == "mender_boot_part=" + passive_before[-1:] # Delete kernel and associated files from currently running partition, # so that the boot will fail if U-Boot for any reason tries to grab the # kernel from the wrong place. connection.run("rm -f /boot/* || true") reboot(connection) run_after_connect("true", connection) (active_after, passive_after) = determine_active_passive_part( bitbake_variables, connection) # The OS should have moved to a new partition, since the image was fine. assert active_after == passive_before assert passive_after == active_before output = connection.run(f"{bootenv_print} bootcount").stdout assert output.rstrip("\n") == "bootcount=1" output = connection.run(f"{bootenv_print} upgrade_available").stdout assert output.rstrip("\n") == "upgrade_available=1" output = connection.run(f"{bootenv_print} mender_boot_part").stdout assert output.rstrip("\n") == "mender_boot_part=" + active_after[-1:] connection.run("mender commit") output = connection.run(f"{bootenv_print} upgrade_available").stdout assert output.rstrip("\n") == "upgrade_available=0" output = connection.run(f"{bootenv_print} mender_boot_part").stdout assert output.rstrip("\n") == "mender_boot_part=" + active_after[-1:] active_before = active_after passive_before = passive_after reboot(connection) run_after_connect("true", connection) (active_after, passive_after) = determine_active_passive_part( bitbake_variables, connection) # The OS should have stayed on the same partition, since we committed. assert active_after == active_before assert passive_after == passive_before