Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 724b6028 authored by Mohammad Samiul Islam's avatar Mohammad Samiul Islam Committed by Android (Google) Code Review
Browse files

Merge changes from topic "rollback-all"

* changes:
  Allow handling of all pending staged rollback sessions before rebooting
  Add test for verifying all available rollbacks are triggered during native crash
parents 0ec08df9 2f9fa5c9
Loading
Loading
Loading
Loading
+28 −1
Original line number Diff line number Diff line
@@ -280,7 +280,6 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
                            .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED,
                            WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN,
                            "");
                    mContext.getSystemService(PowerManager.class).reboot("Rollback staged install");
                } else if (sessionInfo.isStagedSessionFailed()
                        && markStagedSessionHandled(rollbackId)) {
                    logEvent(moduleMetadataPackage,
@@ -291,6 +290,11 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
                }
            }
        }

        // Wait for all pending staged sessions to get handled before rebooting.
        if (isPendingStagedSessionsEmpty()) {
            mContext.getSystemService(PowerManager.class).reboot("Rollback staged install");
        }
    }

    /**
@@ -303,6 +307,16 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
        }
    }

    /**
     * Returns {@code true} if all pending staged rollback sessions were marked as handled,
     * {@code false} if there is any left.
     */
    private boolean isPendingStagedSessionsEmpty() {
        synchronized (mPendingStagedRollbackIds) {
            return mPendingStagedRollbackIds.isEmpty();
        }
    }

    private void saveLastStagedRollbackId(int stagedRollbackId) {
        try {
            FileOutputStream fos = new FileOutputStream(mLastStagedRollbackIdFile);
@@ -414,6 +428,9 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
                            reasonToLog, failedPackageToLog);
                }
            } else {
                if (rollback.isStaged()) {
                    markStagedSessionHandled(rollback.getRollbackId());
                }
                logEvent(logPackage,
                        StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
                        reasonToLog, failedPackageToLog);
@@ -431,6 +448,16 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
        RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
        List<RollbackInfo> rollbacks = rollbackManager.getAvailableRollbacks();

        // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all
        // pending staged rollbacks are handled.
        synchronized (mPendingStagedRollbackIds) {
            for (RollbackInfo rollback : rollbacks) {
                if (rollback.isStaged()) {
                    mPendingStagedRollbackIds.add(rollback.getRollbackId());
                }
            }
        }

        for (RollbackInfo rollback : rollbacks) {
            VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom();
            rollbackPackage(rollback, sample, PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
+68 −6
Original line number Diff line number Diff line
@@ -23,12 +23,14 @@ import static com.google.common.truth.Truth.assertThat;

import android.Manifest;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
import android.os.ParcelFileDescriptor;
import android.os.storage.StorageManager;
import android.provider.DeviceConfig;

import androidx.test.platform.app.InstrumentationRegistry;
@@ -185,12 +187,6 @@ public class StagedRollbackTest {
     */
    @Test
    public void testNativeWatchdogTriggersRollback_Phase1() throws Exception {
        // When multiple staged sessions are installed on a device which doesn't support checkpoint,
        // only the 1st one will prevail. We have to check no other rollbacks available to ensure
        // TestApp.A is always the 1st and the only one to commit so rollback can work as intended.
        // If there are leftover rollbacks from previous tests, this assertion will fail.
        assertThat(RollbackUtils.getRollbackManager().getAvailableRollbacks()).isEmpty();

        Uninstall.packages(TestApp.A);
        Install.single(TestApp.A1).commit();
        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
@@ -220,6 +216,64 @@ public class StagedRollbackTest {
                TestApp.A)).isNotNull();
    }

    /**
     * Stage install an apk with rollback that will be later triggered by unattributable crash.
     */
    @Test
    public void testNativeWatchdogTriggersRollbackForAll_Phase1() throws Exception {
        Uninstall.packages(TestApp.A);
        Install.single(TestApp.A1).commit();
        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);

        Install.single(TestApp.A2).setEnableRollback().setStaged().commit();
    }

    /**
     * Verify the rollback is available and then install another package with rollback.
     */
    @Test
    public void testNativeWatchdogTriggersRollbackForAll_Phase2() throws Exception {
        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
        RollbackManager rm = RollbackUtils.getRollbackManager();
        assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
                TestApp.A)).isNotNull();

        // Install another package with rollback
        Uninstall.packages(TestApp.B);
        Install.single(TestApp.B1).commit();
        assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);

        Install.single(TestApp.B2).setEnableRollback().setStaged().commit();
    }

    /**
     * Verify the rollbacks are available.
     */
    @Test
    public void testNativeWatchdogTriggersRollbackForAll_Phase3() throws Exception {
        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
        assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
        RollbackManager rm = RollbackUtils.getRollbackManager();
        assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
                TestApp.A)).isNotNull();
        assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
                TestApp.B)).isNotNull();
    }

    /**
     * Verify the rollbacks are committed after crashing.
     */
    @Test
    public void testNativeWatchdogTriggersRollbackForAll_Phase4() throws Exception {
        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
        assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
        RollbackManager rm = RollbackUtils.getRollbackManager();
        assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
                TestApp.A)).isNotNull();
        assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
                TestApp.B)).isNotNull();
    }

    @Test
    public void testNetworkFailedRollback_Phase1() throws Exception {
        // Remove available rollbacks and uninstall NetworkStack on /data/
@@ -438,6 +492,7 @@ public class StagedRollbackTest {
        RollbackManager rm = RollbackUtils.getRollbackManager();
        rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream())
                .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage);
        assertThat(RollbackUtils.getRollbackManager().getAvailableRollbacks()).isEmpty();
    }

    private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
@@ -498,4 +553,11 @@ public class StagedRollbackTest {
                .executeShellCommand(cmd);
        IoUtils.closeQuietly(pfd);
    }

    @Test
    public void isCheckpointSupported() {
        Context context = InstrumentationRegistry.getInstrumentation().getContext();
        StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
        assertThat(sm.isCheckpointSupported()).isTrue();
    }
}
+45 −1
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.tests.rollback.host;

import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import static org.testng.Assert.assertThrows;

import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
@@ -62,6 +63,7 @@ public class StagedRollbackTest extends BaseHostJUnit4Test {
                "rm -f /system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex "
                        + "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex");
        getDevice().reboot();
        runPhase("testCleanUp");
    }

    @After
@@ -95,7 +97,6 @@ public class StagedRollbackTest extends BaseHostJUnit4Test {

    @Test
    public void testNativeWatchdogTriggersRollback() throws Exception {
        //Stage install ModuleMetadata package - this simulates a Mainline module update
        runPhase("testNativeWatchdogTriggersRollback_Phase1");

        // Reboot device to activate staged package
@@ -121,6 +122,40 @@ public class StagedRollbackTest extends BaseHostJUnit4Test {
        runPhase("testNativeWatchdogTriggersRollback_Phase3");
    }

    @Test
    public void testNativeWatchdogTriggersRollbackForAll() throws Exception {
        // This test requires committing multiple staged rollbacks
        assumeTrue(isCheckpointSupported());

        // Install a package with rollback enabled.
        runPhase("testNativeWatchdogTriggersRollbackForAll_Phase1");
        getDevice().reboot();

        // Once previous staged install is applied, install another package
        runPhase("testNativeWatchdogTriggersRollbackForAll_Phase2");
        getDevice().reboot();

        // Verify the new staged install has also been applied successfully.
        runPhase("testNativeWatchdogTriggersRollbackForAll_Phase3");

        // crash system_server enough times to trigger a rollback
        crashProcess("system_server", NATIVE_CRASHES_THRESHOLD);

        // Rollback should be committed automatically now.
        // Give time for rollback to be committed. This could take a while,
        // because we need all of the following to happen:
        // 1. system_server comes back up and boot completes.
        // 2. Rollback health observer detects updatable crashing signal.
        // 3. Staged rollback session becomes ready.
        // 4. Device actually reboots.
        // So we give a generous timeout here.
        assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5)));
        getDevice().waitForDeviceAvailable();

        // verify all available rollbacks have been committed
        runPhase("testNativeWatchdogTriggersRollbackForAll_Phase4");
    }

    /**
     * Tests failed network health check triggers watchdog staged rollbacks.
     */
@@ -244,4 +279,13 @@ public class StagedRollbackTest extends BaseHostJUnit4Test {
        // Find the NetworkStack path (can be NetworkStack.apk or NetworkStackNext.apk)
        return getDevice().executeShellCommand("ls /system/priv-app/NetworkStack*/*.apk");
    }

    private boolean isCheckpointSupported() throws Exception {
        try {
            runPhase("isCheckpointSupported");
            return true;
        } catch (AssertionError ignore) {
            return false;
        }
    }
}