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

Commit 3c8fe2f1 authored by Dennis Shen's avatar Dennis Shen
Browse files

system_server: bulk sync between old storage and new aconfig storage

message.

Do a bulk flag overrides sync when settings provider is initilized. At
the core, old storage data is stored inside SettingsState class. As the
last step of SettingState constructor, we handle bulk sync request. The
entry point of bulk sync is "handleBulkSyncToNewStorage" call in
SettingsState constructor.

When handling bulk sync requests, we use a setting entry in mSettings as
a marker to indicate if bulk sync has been performed. There are four
possibilities in each boot:

(1) enableAconfigStorageDaemon is on, but marker is false or no there (a setting entry in
mSettings) showing that bulk sync has been done. In this case, do a
bulkd sync, and then writes marker to mSettings and writes to xml file,
so that the marker persists.

(2) enableAconfigStorageDaemon is on, and marker is true. It means we have bulk
synced already, no need to do it again. Nothing to do in this case.

(3) enableAconfigStorageDaemon is off, and maker is true. Then we should clear the
marker from mSettings and trigger a write to xml file.

(4) enableAconfigStorageDaemon is off, and marker is false. Nothing to do in this case.

For bulk sync logic, it does the following things:

(1) write a storage reset request to aconfigd, ensure aconfigd is in
clean state before sync over any flags.

(2) loop over each setting and send over flag override requests.

Bug: b/312444587
Test: m and avd, tested with both server and local overrides
Change-Id: I1e0370da7516cd4cfaec94d428943d8e840b725e
parent c75b268e
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -32,6 +32,8 @@ android_library {
        "unsupportedappusage",
    ],
    static_libs: [
        "aconfig_new_storage_flags_lib",
        "aconfigd_java_utils",
        "aconfig_demo_flags_java_lib",
        "device_config_service_flags_java",
        "libaconfig_java_proto_lite",
+152 −0
Original line number Diff line number Diff line
@@ -82,6 +82,18 @@ import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;

// FOR ACONFIGD TEST MISSION AND ROLLOUT
import java.io.DataInputStream;
import java.io.DataOutputStream;
import android.net.LocalSocketAddress;
import android.net.LocalSocket;
import android.util.proto.ProtoInputStream;
import android.aconfigd.Aconfigd.StorageRequestMessage;
import android.aconfigd.Aconfigd.StorageRequestMessages;
import android.aconfigd.Aconfigd.StorageReturnMessage;
import android.aconfigd.Aconfigd.StorageReturnMessages;
import android.aconfigd.AconfigdJavaUtils;
import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
/**
 * This class contains the state for one type of settings. It is responsible
 * for saving the state asynchronously to an XML file after a mutation and
@@ -346,6 +358,7 @@ final class SettingsState {

        mNamespaceDefaults = new HashMap<>();

        ProtoOutputStream requests = null;
        synchronized (mLock) {
            readStateSyncLocked();

@@ -361,7 +374,146 @@ final class SettingsState {
                    loadAconfigDefaultValuesLocked(apexProtoPaths);
                }
            }

            if (isConfigSettingsKey(mKey)) {
                requests = handleBulkSyncToNewStorage();
            }
        }

        if (requests != null) {
            LocalSocket client = new LocalSocket();
            try{
                client.connect(new LocalSocketAddress(
                    "aconfigd", LocalSocketAddress.Namespace.RESERVED));
                Slog.d(LOG_TAG, "connected to aconfigd socket");
            } catch (IOException ioe) {
                Slog.e(LOG_TAG, "failed to connect to aconfigd socket", ioe);
                return;
            }
            AconfigdJavaUtils.sendAconfigdRequests(client, requests);
        }
    }

    // TODO(b/341764371): migrate aconfig flag push to GMS core
    public static class FlagOverrideToSync {
        public String packageName;
        public String flagName;
        public String flagValue;
        public boolean isLocal;
    }

    // TODO(b/341764371): migrate aconfig flag push to GMS core
    @VisibleForTesting
    @GuardedBy("mLock")
    public FlagOverrideToSync getFlagOverrideToSync(String name, String value) {
        int slashIdx = name.indexOf("/");
        if (slashIdx <= 0 || slashIdx >= name.length()-1) {
            Slog.e(LOG_TAG, "invalid flag name " + name);
            return null;
        }

        String namespace = name.substring(0, slashIdx);
        String fullFlagName = name.substring(slashIdx + 1);
        boolean isLocal = false;

        // get actual fully qualified flag name <package>.<flag>, note this is done
        // after staged flag is applied, so no need to check staged flags
        if (namespace.equals("device_config_overrides")) {
            int colonIdx = fullFlagName.indexOf(":");
            if (colonIdx == -1) {
                Slog.e(LOG_TAG, "invalid local override flag name " + name);
                return null;
            }
            namespace = fullFlagName.substring(0, colonIdx);
            fullFlagName = fullFlagName.substring(colonIdx + 1);
            isLocal = true;
        }

        String aconfigName = namespace + "/" + fullFlagName;
        boolean isAconfig = mNamespaceDefaults.containsKey(namespace)
                            && mNamespaceDefaults.get(namespace).containsKey(aconfigName);
        if (!isAconfig) {
            return null;
        }

        // get package name and flag name
        int dotIdx = fullFlagName.lastIndexOf(".");
        if (dotIdx == -1) {
            Slog.e(LOG_TAG, "invalid override flag name " + name);
            return null;
        }

        FlagOverrideToSync flag = new FlagOverrideToSync();
        flag.packageName = fullFlagName.substring(0, dotIdx);
        flag.flagName = fullFlagName.substring(dotIdx + 1);
        flag.isLocal = isLocal;
        flag.flagValue = value;
        return flag;
    }


    // TODO(b/341764371): migrate aconfig flag push to GMS core
    @VisibleForTesting
    @GuardedBy("mLock")
    public ProtoOutputStream handleBulkSyncToNewStorage() {
        // get marker or add marker if it does not exist
        final String bulkSyncMarkerName = new String("aconfigd_marker/bulk_synced");
        Setting markerSetting = mSettings.get(bulkSyncMarkerName);
        if (markerSetting == null) {
            markerSetting = new Setting(
                bulkSyncMarkerName, "false", false, "aconfig", "aconfig");
            mSettings.put(bulkSyncMarkerName, markerSetting);
        }

        if (enableAconfigStorageDaemon()) {
            if (markerSetting.value.equals("true")) {
                // CASE 1, flag is on, bulk sync marker true, nothing to do
                return null;
            } else {
                // CASE 2, flag is on, bulk sync marker false. Do following two tasks
                // (1) Do bulk sync here.
                // (2) After bulk sync, set marker to true.

                // first add storage reset request
                ProtoOutputStream requests = new ProtoOutputStream();
                AconfigdJavaUtils.writeResetStorageRequest(requests);

                // loop over all settings and add flag override requests
                final int numSettings = mSettings.size();
                int num_requests = 0;
                for (int i = 0; i < numSettings; i++) {
                    String name = mSettings.keyAt(i);
                    Setting setting = mSettings.valueAt(i);
                    FlagOverrideToSync flag =
                            getFlagOverrideToSync(name, setting.getValue());
                    if (flag == null) {
                        continue;
                    }
                    ++num_requests;
                    AconfigdJavaUtils.writeFlagOverrideRequest(
                        requests, flag.packageName, flag.flagName, flag.flagValue,
                        flag.isLocal);
                }

                Slog.i(LOG_TAG, num_requests + " flag override requests created");

                // mark sync has been done
                markerSetting.value = "true";
                scheduleWriteIfNeededLocked();
                return requests;
            }
        } else {
            if (markerSetting.value.equals("true")) {
                // CASE 3, flag is off, bulk sync marker true, clear the marker
                markerSetting.value = "false";
                scheduleWriteIfNeededLocked();
                return null;
            } else {
                // CASE 4, flag is off, bulk sync marker false, nothing to do
                return null;
            }
        }

    }

    @GuardedBy("mLock")
+117 −0
Original line number Diff line number Diff line
@@ -29,12 +29,18 @@ import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
import com.android.providers.settings.SettingsState.FlagOverrideToSync;

import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;

import com.android.modules.utils.TypedXmlSerializer;

import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.flag.junit.SetFlagsRule;

import com.google.common.base.Strings;

import java.io.ByteArrayOutputStream;
@@ -947,4 +953,115 @@ public class SettingsStateTest {
                + testValue1.length() /* size for default */) * Character.BYTES;
        assertEquals(expectedMemUsageForPackage2, settingsState.getMemoryUsage(package2));
    }

    @Test
    public void testGetFlagOverrideToSync() {
        int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
        Object lock = new Object();
        SettingsState settingsState = new SettingsState(
                InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
        parsed_flags flags = parsed_flags
                .newBuilder()
                .addParsedFlag(parsed_flag
                    .newBuilder()
                        .setPackage("com.android.flags")
                        .setName("flag1")
                        .setNamespace("test_namespace")
                        .setDescription("test flag")
                        .addBug("12345678")
                        .setState(Aconfig.flag_state.DISABLED)
                        .setPermission(Aconfig.flag_permission.READ_WRITE))
                .build();

        synchronized (lock) {
            Map<String, Map<String, String>> defaults = new HashMap<>();
            settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults);
            Map<String, String> namespaceDefaults = defaults.get("test_namespace");
            assertEquals(1, namespaceDefaults.keySet().size());
            settingsState.addAconfigDefaultValuesFromMap(defaults);
        }

        // invalid flag name
        assertTrue(settingsState.getFlagOverrideToSync(
            "invalid_flag", "false") == null);

        // non aconfig flag
        assertTrue(settingsState.getFlagOverrideToSync(
            "some_namespace/some_flag", "false") == null);

        // server override
        FlagOverrideToSync flag = settingsState.getFlagOverrideToSync(
            "test_namespace/com.android.flags.flag1", "false");
        assertTrue(flag != null);
        assertEquals(flag.packageName, "com.android.flags");
        assertEquals(flag.flagName, "flag1");
        assertEquals(flag.flagValue, "false");
        assertEquals(flag.isLocal, false);

        // local override
        flag = settingsState.getFlagOverrideToSync(
            "device_config_overrides/test_namespace:com.android.flags.flag1", "false");
        assertTrue(flag != null);
        assertEquals(flag.packageName, "com.android.flags");
        assertEquals(flag.flagName, "flag1");
        assertEquals(flag.flagValue, "false");
        assertEquals(flag.isLocal, true);
    }

    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();

    @Test
    @EnableFlags(com.android.aconfig_new_storage.Flags.FLAG_ENABLE_ACONFIG_STORAGE_DAEMON)
    public void testHandleBulkSyncWithAconfigdEnabled() {
        int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
        Object lock = new Object();
        SettingsState settingsState = new SettingsState(
                InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());

        synchronized (lock) {
            settingsState.insertSettingLocked("aconfigd_marker/bulk_synced",
                    "false", null, false, "aconfig");

            // first bulk sync
            ProtoOutputStream requests = settingsState.handleBulkSyncToNewStorage();
            assertTrue(requests != null);
            String value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue();
            assertEquals("true", value);

            // send time should no longer bulk sync
            requests = settingsState.handleBulkSyncToNewStorage();
            assertTrue(requests == null);
            value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue();
            assertEquals("true", value);
        }
    }

    @Test
    @DisableFlags(com.android.aconfig_new_storage.Flags.FLAG_ENABLE_ACONFIG_STORAGE_DAEMON)
    public void testHandleBulkSyncWithAconfigdDisabled() {
        int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
        Object lock = new Object();
        SettingsState settingsState = new SettingsState(
                InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());

        synchronized (lock) {
            settingsState.insertSettingLocked("aconfigd_marker/bulk_synced",
                    "true", null, false, "aconfig");

            // when aconfigd is off, should change the marker to false
            ProtoOutputStream requests = settingsState.handleBulkSyncToNewStorage();
            assertTrue(requests == null);
            String value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue();
            assertEquals("false", value);

            // marker started with false value, after call, it should remain false
            requests = settingsState.handleBulkSyncToNewStorage();
            assertTrue(requests == null);
            value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue();
            assertEquals("false", value);
        }
    }
}