Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rollout config #719

Merged
merged 14 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/androidTest/java/helper/TestableSplitConfigBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.lang.reflect.Constructor;

import io.split.android.client.RolloutCacheConfiguration;
import io.split.android.client.ServiceEndpoints;
import io.split.android.client.SplitClientConfig;
import io.split.android.client.SyncConfig;
Expand Down Expand Up @@ -64,6 +65,7 @@ public class TestableSplitConfigBuilder {
private String mPrefix = "";
private CertificatePinningConfiguration mCertificatePinningConfiguration;
private long mImpressionsDedupeTimeInterval = ServiceConstants.DEFAULT_IMPRESSIONS_DEDUPE_TIME_INTERVAL;
private RolloutCacheConfiguration mRolloutCacheConfiguration = RolloutCacheConfiguration.builder().build();

public TestableSplitConfigBuilder() {
mServiceEndpoints = ServiceEndpoints.builder().build();
Expand Down Expand Up @@ -274,6 +276,11 @@ public TestableSplitConfigBuilder impressionsDedupeTimeInterval(long impressions
return this;
}

public TestableSplitConfigBuilder rolloutCacheConfiguration(RolloutCacheConfiguration rolloutCacheConfiguration) {
this.mRolloutCacheConfiguration = rolloutCacheConfiguration;
return this;
}

public SplitClientConfig build() {
Constructor constructor = SplitClientConfig.class.getDeclaredConstructors()[0];
constructor.setAccessible(true);
Expand Down Expand Up @@ -329,7 +336,8 @@ public SplitClientConfig build() {
mPrefix,
mObserverCacheExpirationPeriod,
mCertificatePinningConfiguration,
mImpressionsDedupeTimeInterval);
mImpressionsDedupeTimeInterval,
mRolloutCacheConfiguration);

Logger.instance().setLevel(mLogLevel);
return config;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import helper.IntegrationHelper;
import helper.SplitEventTaskHelper;
import helper.TestableSplitConfigBuilder;
import io.split.android.client.ServiceEndpoints;
import io.split.android.client.SplitClient;
import io.split.android.client.SplitClientConfig;
import io.split.android.client.SplitFactory;
Expand All @@ -32,15 +33,21 @@
import io.split.android.client.storage.db.SplitRoomDatabase;
import io.split.android.client.storage.db.attributes.AttributesEntity;
import io.split.android.client.utils.Json;
import okhttp3.mockwebserver.Dispatcher;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;

public class AttributesIntegrationTest {

private Context mContext;
private SplitRoomDatabase mRoomDb;
private SplitFactory mSplitFactory;
private MockWebServer mWebServer;

@Before
public void setup() {
setupServer();
mContext = InstrumentationRegistry.getInstrumentation().getContext();
mRoomDb = DatabaseHelper.getTestDatabase(mContext);
mRoomDb.clearAllTables();
Expand All @@ -51,7 +58,7 @@ public void testPersistentAttributes() throws InterruptedException {
insertSplitsFromFileIntoDB();
CountDownLatch readyLatch = new CountDownLatch(1);
SplitClient client = getSplitClient(readyLatch, true, null);
readyLatch.await(5, TimeUnit.SECONDS);
readyLatch.await(10, TimeUnit.SECONDS);

// 1. Evaluate without attrs
Assert.assertEquals("on", client.getTreatment("workm"));
Expand Down Expand Up @@ -224,8 +231,12 @@ private void setAttributesInClientAndEvaluate(SplitClient client) {

private SplitClient getSplitClient(CountDownLatch readyLatch, boolean persistenceEnabled, String matchingKey) {
if (mSplitFactory == null) {
final String url = mWebServer.url("/").url().toString();
ServiceEndpoints endpoints = ServiceEndpoints.builder()
.apiEndpoint(url).eventsEndpoint(url).build();
SplitClientConfig config = new TestableSplitConfigBuilder()
.enableDebug()
.serviceEndpoints(endpoints)
.featuresRefreshRate(9999)
.segmentsRefreshRate(9999)
.impressionsRefreshRate(9999)
Expand Down Expand Up @@ -272,4 +283,24 @@ private List<Split> getSplitListFromJson() {

return changes.splits;
}

private void setupServer() {
mWebServer = new MockWebServer();

final Dispatcher dispatcher = new Dispatcher() {

@Override
public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
if (request.getPath().contains("/" + IntegrationHelper.ServicePath.MEMBERSHIPS)) {
return new MockResponse().setResponseCode(200).setBody(IntegrationHelper.dummyAllSegments());
} else if (request.getPath().contains("/splitChanges")) {
return new MockResponse().setResponseCode(200)
.setBody(IntegrationHelper.emptySplitChanges(-1, 10000));
} else {
return new MockResponse().setResponseCode(404);
}
}
};
mWebServer.setDispatcher(dispatcher);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package io.split.android.client;

import io.split.android.client.service.ServiceConstants;
import io.split.android.client.utils.logger.Logger;

public class RolloutCacheConfiguration {

private final int mExpiration;
private final boolean mClearOnInit;

private RolloutCacheConfiguration(int expiration, boolean clearOnInit) {
mExpiration = expiration;
mClearOnInit = clearOnInit;
}

public int getExpiration() {
return mExpiration;
}

public boolean clearOnInit() {
return mClearOnInit;
}

public static Builder builder() {
return new Builder();
}

public static class Builder {

private static final int MIN_EXPIRATION_DAYS = 1;

private int mExpiration = ServiceConstants.DEFAULT_ROLLOUT_CACHE_EXPIRATION;
private boolean mClearOnInit = false;

private Builder() {

}

/**
* Set the expiration time for the rollout definitions cache, in days. Default is 10 days.
* @param expiration in days
* @return This builder
*/
public Builder expiration(int expiration) {
if (expiration < MIN_EXPIRATION_DAYS) {
Logger.w("Cache expiration must be at least 1 day. Using default value.");
mExpiration = ServiceConstants.DEFAULT_ROLLOUT_CACHE_EXPIRATION;
} else {
mExpiration = expiration;
}

return this;
}

/**
* Set if the rollout definitions cache should be cleared on initialization. Default is false.
* @param clearOnInit whether to clear cache on initialization.
* @return This builder
*/
public Builder clearOnInit(boolean clearOnInit) {
mClearOnInit = clearOnInit;
return this;
}

public RolloutCacheConfiguration build() {
return new RolloutCacheConfiguration(mExpiration, mClearOnInit);
}
}
}
31 changes: 27 additions & 4 deletions src/main/java/io/split/android/client/SplitClientConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ public class SplitClientConfig {
private final long mObserverCacheExpirationPeriod;
private final CertificatePinningConfiguration mCertificatePinningConfiguration;
private final long mImpressionsDedupeTimeInterval;
@NonNull
private final RolloutCacheConfiguration mRolloutCacheConfiguration;

public static Builder builder() {
return new Builder();
Expand Down Expand Up @@ -185,7 +187,8 @@ private SplitClientConfig(String endpoint,
String prefix,
long observerCacheExpirationPeriod,
CertificatePinningConfiguration certificatePinningConfiguration,
long impressionsDedupeTimeInterval) {
long impressionsDedupeTimeInterval,
RolloutCacheConfiguration rolloutCacheConfiguration) {
mEndpoint = endpoint;
mEventsEndpoint = eventsEndpoint;
mTelemetryEndpoint = telemetryEndpoint;
Expand Down Expand Up @@ -243,6 +246,7 @@ private SplitClientConfig(String endpoint,
mObserverCacheExpirationPeriod = observerCacheExpirationPeriod;
mCertificatePinningConfiguration = certificatePinningConfiguration;
mImpressionsDedupeTimeInterval = impressionsDedupeTimeInterval;
mRolloutCacheConfiguration = rolloutCacheConfiguration;
}

public String trafficType() {
Expand Down Expand Up @@ -486,8 +490,8 @@ public long impressionsDedupeTimeInterval() {
return mImpressionsDedupeTimeInterval;
}

public boolean clearOnInit() {
return false; // TODO: to be implemented in the future
public RolloutCacheConfiguration rolloutCacheConfiguration() {
return mRolloutCacheConfiguration;
}

public static final class Builder {
Expand Down Expand Up @@ -566,6 +570,8 @@ public static final class Builder {

private long mImpressionsDedupeTimeInterval = ServiceConstants.DEFAULT_IMPRESSIONS_DEDUPE_TIME_INTERVAL;

private RolloutCacheConfiguration mRolloutCacheConfiguration = RolloutCacheConfiguration.builder().build();

public Builder() {
mServiceEndpoints = ServiceEndpoints.builder().build();
}
Expand Down Expand Up @@ -1106,6 +1112,22 @@ public Builder impressionsDedupeTimeInterval(long impressionsDedupeTimeInterval)
return this;
}

/**
* Configuration for rollout definitions cache.
*
* @param rolloutCacheConfiguration Configuration object
* @return This builder
*/
public Builder rolloutCacheConfiguration(@NonNull RolloutCacheConfiguration rolloutCacheConfiguration) {
if (rolloutCacheConfiguration == null) {
Logger.w("Rollout cache configuration is null. Setting to default value.");
mRolloutCacheConfiguration = RolloutCacheConfiguration.builder().build();
} else {
mRolloutCacheConfiguration = rolloutCacheConfiguration;
}
return this;
}

public SplitClientConfig build() {
Logger.instance().setLevel(mLogLevel);

Expand Down Expand Up @@ -1237,7 +1259,8 @@ public SplitClientConfig build() {
mPrefix,
mObserverCacheExpirationPeriod,
mCertificatePinningConfiguration,
mImpressionsDedupeTimeInterval);
mImpressionsDedupeTimeInterval,
mRolloutCacheConfiguration);
}

private HttpProxy parseProxyHost(String proxyUri) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public class ServiceConstants {
public static final long MIN_INITIAL_DELAY = 5L;
public static final int DEFAULT_RECORDS_PER_PUSH = 100;
public static final long DEFAULT_SPLITS_CACHE_EXPIRATION_IN_SECONDS = TimeUnit.DAYS.toSeconds(10); // 10 days
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are DEFAULT_SPLITS_CACHE_EXPIRATION_IN_SECONDS and DEFAULT_ROLLOUT_CACHE_EXPIRATION constants related? in that case, should we have only one, or derive one from the other?

public static final int DEFAULT_ROLLOUT_CACHE_EXPIRATION = 10; // 10 days

public static final int MAX_ROWS_PER_QUERY = 100;

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import java.util.concurrent.TimeUnit;

import io.split.android.client.RolloutCacheConfiguration;
import io.split.android.client.SplitClientConfig;
import io.split.android.client.service.CleanUpDatabaseTask;
import io.split.android.client.service.executor.SplitTask;
Expand All @@ -27,19 +28,20 @@ public class RolloutCacheManagerImpl implements RolloutCacheManager, SplitTask {
@NonNull
private final GeneralInfoStorage mGeneralInfoStorage;
@NonNull
private final RolloutCacheManagerConfig mConfig;
private final RolloutCacheConfiguration mConfig;
@NonNull
private final RolloutDefinitionsCache[] mStorages;
@NonNull
private final CleanUpDatabaseTask mCleanUpDatabaseTask;
@NonNull
private final EncryptionMigrationTask mEncryptionMigrationTask;

public RolloutCacheManagerImpl(@NonNull SplitClientConfig splitClientConfig, @NonNull SplitStorageContainer storageContainer,
public RolloutCacheManagerImpl(@NonNull SplitClientConfig splitClientConfig,
@NonNull SplitStorageContainer storageContainer,
@NonNull CleanUpDatabaseTask cleanUpDatabaseTask,
@NonNull EncryptionMigrationTask encryptionMigrationTask) {
this(storageContainer.getGeneralInfoStorage(),
RolloutCacheManagerConfig.from(splitClientConfig),
splitClientConfig.rolloutCacheConfiguration(),
cleanUpDatabaseTask,
encryptionMigrationTask,
storageContainer.getSplitsStorage(),
Expand All @@ -49,12 +51,12 @@ public RolloutCacheManagerImpl(@NonNull SplitClientConfig splitClientConfig, @No

@VisibleForTesting
RolloutCacheManagerImpl(@NonNull GeneralInfoStorage generalInfoStorage,
@NonNull RolloutCacheManagerConfig config,
@NonNull CleanUpDatabaseTask clean,
@NonNull RolloutCacheConfiguration config,
@NonNull CleanUpDatabaseTask cleanUpDatabaseTask,
@NonNull EncryptionMigrationTask encryptionMigrationTask,
@NonNull RolloutDefinitionsCache... storages) {
mGeneralInfoStorage = checkNotNull(generalInfoStorage);
mCleanUpDatabaseTask = checkNotNull(clean);
mCleanUpDatabaseTask = checkNotNull(cleanUpDatabaseTask);
mEncryptionMigrationTask = checkNotNull(encryptionMigrationTask);
mStorages = checkNotNull(storages);
mConfig = checkNotNull(config);
Expand All @@ -70,7 +72,7 @@ public void validateCache(SplitTaskExecutionListener listener) {
execute();
Logger.v("Rollout cache manager: Migrating encryption");
mEncryptionMigrationTask.execute();
Logger.v("Rollout cache manager: validation finished");
Logger.v("Rollout cache manager: Validation finished");
listener.taskExecuted(SplitTaskExecutionInfo.success(SplitTaskType.GENERIC_TASK));
} catch (Exception ex) {
Logger.e("Error occurred validating cache: " + ex.getMessage());
Expand All @@ -95,15 +97,18 @@ public SplitTaskExecutionInfo execute() {
}

private boolean validateExpiration() {
// calculate elapsed time since last update
long lastUpdateTimestamp = mGeneralInfoStorage.getSplitsUpdateTimestamp();
// calculate elapsed time since last update
long daysSinceLastUpdate = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - lastUpdateTimestamp);

if (daysSinceLastUpdate > mConfig.getCacheExpirationInDays()) {
if (lastUpdateTimestamp > 0 && daysSinceLastUpdate > mConfig.getExpiration()) {
Logger.v("Clearing rollout definitions cache due to expiration");
return true;
} else if (mConfig.isClearOnInit()) {
} else if (mConfig.clearOnInit()) {
long lastCacheClearTimestamp = mGeneralInfoStorage.getRolloutCacheLastClearTimestamp();
if (lastCacheClearTimestamp < 1) {
return true;
}
long daysSinceCacheClear = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - lastCacheClearTimestamp);

// don't clear too soon
Expand Down
Loading
Loading