Merge pull request #23 from medici/litmus-executor
Litmus executor with fixed thread pool
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
FROM 193044292705.dkr.ecr.ap-south-1.amazonaws.com/common/maven:3.8.3-openjdk-17-slim as builder
|
||||
ARG ARTIFACT_VERSION=2.0.5-RELEASE
|
||||
ARG ARTIFACT_VERSION=2.0.6-RELEASE
|
||||
RUN mkdir -p /build
|
||||
WORKDIR /build
|
||||
COPY . /build
|
||||
@@ -7,7 +7,7 @@ RUN mvn clean install
|
||||
RUN mvn clean verify -DskipTests -Dartifact.version=${ARTIFACT_VERSION}
|
||||
|
||||
FROM 193044292705.dkr.ecr.ap-south-1.amazonaws.com/common/openjdk:17-slim-bullseye
|
||||
ARG ARTIFACT_VERSION=2.0.5-RELEASE
|
||||
ARG ARTIFACT_VERSION=2.0.6-RELEASE
|
||||
RUN mkdir -p /usr/local
|
||||
RUN apt-get update -y && apt-get -y install fontconfig libpng-dev
|
||||
WORKDIR /usr/local/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
FROM 193044292705.dkr.ecr.ap-south-1.amazonaws.com/common/maven:3.8.3-openjdk-17-slim as builder
|
||||
ARG ARTIFACT_VERSION=2.0.5-RELEASE
|
||||
ARG ARTIFACT_VERSION=2.0.6-RELEASE
|
||||
RUN mkdir -p /build
|
||||
WORKDIR /build
|
||||
COPY . /build
|
||||
@@ -7,7 +7,7 @@ RUN mvn clean install -DskipTests
|
||||
RUN mvn clean verify -DskipTests -Dartifact.version=${ARTIFACT_VERSION}
|
||||
|
||||
FROM 193044292705.dkr.ecr.ap-south-1.amazonaws.com/common/openjdk:17-slim-bullseye
|
||||
ARG ARTIFACT_VERSION=2.0.5-RELEASE
|
||||
ARG ARTIFACT_VERSION=2.0.6-RELEASE
|
||||
RUN mkdir -p /usr/local
|
||||
RUN apt-get update -y && apt-get -y install fontconfig libpng-dev
|
||||
WORKDIR /usr/local/
|
||||
|
||||
26
docker-compose.yaml
Normal file
26
docker-compose.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
version: '3.0'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: "postgres:11"
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
- POSTGRES_DB=communication-service-test
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=admin
|
||||
|
||||
spring-boot-app:
|
||||
depends_on:
|
||||
- db
|
||||
image: "maven:3.8.3-openjdk-17-slim"
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
- DATASOURCE_URL=jdbc:postgresql://db:5432/litmus
|
||||
- DATASOURCE_USERNAME=litmus-test
|
||||
- DATASOURCE_PASSWORD=litmus-test
|
||||
volumes:
|
||||
- .:/app
|
||||
working_dir: /app
|
||||
command: mvn clean verify
|
||||
@@ -4,11 +4,11 @@
|
||||
<parent>
|
||||
<artifactId>litmus</artifactId>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>litmus-cache</artifactId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>litmus-cache</name>
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
<parent>
|
||||
<artifactId>litmus</artifactId>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>litmus-client</artifactId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>litmus-client</name>
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<dependency>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<artifactId>litmus-model</artifactId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -3,51 +3,40 @@ package com.navi.medici.client;
|
||||
|
||||
import com.navi.medici.clickstream.ClickStreamPayload;
|
||||
import com.navi.medici.config.LitmusConfig;
|
||||
import com.navi.medici.response.LitmusExperimentResponse;
|
||||
import com.navi.medici.util.JacksonUtils;
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import io.micrometer.core.instrument.Counter;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
import static com.navi.medici.util.MetricsUtils.counterMetrics;
|
||||
|
||||
@Log4j2
|
||||
public class ClickStreamClient {
|
||||
public static final MediaType JSON
|
||||
= MediaType.parse("application/json; charset=utf-8");
|
||||
|
||||
private final String clickStreamAPI;
|
||||
private final String appName;
|
||||
private final String vertical;
|
||||
private final MeterRegistry meterRegistry;
|
||||
private final LitmusConfig litmusConfig;
|
||||
|
||||
public ClickStreamClient(String clickStreamAPI, String appName,
|
||||
String vertical, MeterRegistry meterRegistry) {
|
||||
this.clickStreamAPI = clickStreamAPI;
|
||||
this.appName = appName;
|
||||
this.vertical = vertical;
|
||||
this.meterRegistry = meterRegistry;
|
||||
public ClickStreamClient(LitmusConfig litmusConfig) {
|
||||
this.litmusConfig = litmusConfig;
|
||||
}
|
||||
|
||||
@Timed(value = "litmus_client_click_stream_publish_latency", percentiles = {0.90, 0.95, 0.99})
|
||||
public <T> void publish(String experimentName, ClickStreamPayload<T> payload) {
|
||||
String requestBody = JacksonUtils.objectToString(payload);
|
||||
Request request = new Request.Builder()
|
||||
.url(clickStreamAPI)
|
||||
.url(litmusConfig.getClickStreamAPI())
|
||||
.post(RequestBody.create(JSON, requestBody))
|
||||
.build();
|
||||
|
||||
try(Response response = OkHttpClientContainer.getInstance().newCall(request).execute()) {
|
||||
Counter.builder("litmus_client_click_stream_event_ingestion")
|
||||
.tag("vertical", vertical)
|
||||
counterMetrics(litmusConfig, "litmus_client_click_stream_event_ingestion")
|
||||
.tag("status", String.valueOf(response.code()))
|
||||
.tag("experiment_name", experimentName)
|
||||
.tag("app_name", appName)
|
||||
.register(meterRegistry)
|
||||
.register(litmusConfig.getMeterRegistry())
|
||||
.increment();
|
||||
if (response.code() < 300) {
|
||||
var responseBody = response.body();
|
||||
@@ -59,12 +48,10 @@ public class ClickStreamClient {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("clickstream event ingestion failed. ", e);
|
||||
Counter.builder("litmus_client_click_stream_event_ingestion_failed")
|
||||
.tag("vertical", vertical)
|
||||
counterMetrics(litmusConfig, "litmus_client_click_stream_event_ingestion_failed")
|
||||
.tag("experiment_name", experimentName)
|
||||
.tag("app_name", appName)
|
||||
.tag("exception", e.getMessage())
|
||||
.register(meterRegistry)
|
||||
.register(litmusConfig.getMeterRegistry())
|
||||
.increment();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,15 +5,12 @@ import com.navi.medici.exception.LitmusException;
|
||||
import com.navi.medici.request.v1.LitmusExperiment;
|
||||
import com.navi.medici.response.LitmusExperimentCollection;
|
||||
import com.navi.medici.util.JacksonUtils;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.core.util.IOUtils;
|
||||
@@ -42,7 +39,7 @@ public class ExperimentBackupHandlerFile implements ExperimentBackupHandler {
|
||||
return JacksonUtils.stringToObject(data, LitmusExperimentCollection.class);
|
||||
} catch (FileNotFoundException e) {
|
||||
log.info(
|
||||
" Litmus could not find the backup-file '"
|
||||
"Litmus could not find the backup-file '"
|
||||
+ backupFile
|
||||
+ ". This is expected behavior the first time litmus runs in a new environment.");
|
||||
} catch (IOException | IllegalStateException e) {
|
||||
|
||||
@@ -6,54 +6,34 @@ import com.navi.medici.response.LitmusExperimentCollection;
|
||||
import com.navi.medici.response.LitmusExperimentResponse;
|
||||
import com.navi.medici.response.LitmusResponse;
|
||||
import com.navi.medici.util.JacksonUtils;
|
||||
import com.navi.medici.util.LitmusURLs;
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import io.micrometer.core.instrument.Counter;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import java.net.URL;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
import static com.navi.medici.util.MetricsUtils.counterMetrics;
|
||||
|
||||
@Log4j2
|
||||
public class HttpExperimentFetcher implements ExperimentFetcher {
|
||||
private final LitmusConfig litmusConfig;
|
||||
|
||||
private final LitmusURLs litmusURLs;
|
||||
private final String appName;
|
||||
private final String projectNamePrefix;
|
||||
private final String projectName;
|
||||
private final String vertical;
|
||||
private final MeterRegistry meterRegistry;
|
||||
|
||||
public HttpExperimentFetcher(LitmusURLs litmusURLs,
|
||||
String appName,
|
||||
String projectNamePrefix,
|
||||
String projectName,
|
||||
String vertical,
|
||||
MeterRegistry meterRegistry) {
|
||||
this.litmusURLs = litmusURLs;
|
||||
this.appName = appName;
|
||||
this.projectNamePrefix = projectNamePrefix;
|
||||
this.projectName = projectName;
|
||||
this.vertical = vertical;
|
||||
this.meterRegistry = meterRegistry;
|
||||
public HttpExperimentFetcher(LitmusConfig litmusConfig) {
|
||||
this.litmusConfig = litmusConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Timed(value = "litmus_client_fetch_experiments_latency", percentiles = {0.90, 0.95, 0.99})
|
||||
public LitmusExperimentResponse fetchExperiments(String vertical, Long pollingTime) throws LitmusException {
|
||||
URL experimentUrl = litmusURLs
|
||||
.getLitmusExperimentsURL(projectName, projectNamePrefix);
|
||||
URL experimentUrl = litmusConfig.getLitmusURLs()
|
||||
.getLitmusExperimentsURL(litmusConfig.getProjectName(), litmusConfig.getNamePrefix());
|
||||
Request request = new Request.Builder()
|
||||
.url(String.format("%s/vertical?vertical=%s&polling_time=%s", experimentUrl, vertical, pollingTime))
|
||||
.build();
|
||||
|
||||
try (Response response = OkHttpClientContainer.getInstance().newCall(request).execute()) {
|
||||
Counter.builder("litmus_client_fetch_experiment_polling")
|
||||
.tag("vertical", this.vertical)
|
||||
.tag("app_name", this.appName)
|
||||
.register(this.meterRegistry)
|
||||
counterMetrics(litmusConfig, "litmus_client_fetch_experiment_polling")
|
||||
.register(litmusConfig.getMeterRegistry())
|
||||
.increment();
|
||||
|
||||
if (response.code() < 300) {
|
||||
@@ -62,41 +42,34 @@ public class HttpExperimentFetcher implements ExperimentFetcher {
|
||||
|
||||
LitmusExperimentCollection experiments = JacksonUtils.stringToObject(responseBody.string(), LitmusExperimentCollection.class);
|
||||
|
||||
Counter.builder("litmus_client_fetch_experiment_polling_total_request")
|
||||
.tag("vertical", vertical)
|
||||
counterMetrics(litmusConfig, "litmus_client_fetch_experiment_polling_total_request")
|
||||
.tag("status", String.valueOf(response.code()))
|
||||
.tag("experiment_state", LitmusExperimentResponse.Status.CHANGED.name())
|
||||
.tag("app_name", this.appName)
|
||||
.register(this.meterRegistry)
|
||||
.register(litmusConfig.getMeterRegistry())
|
||||
.increment();
|
||||
return new LitmusExperimentResponse(LitmusExperimentResponse.Status.CHANGED, experiments);
|
||||
} else if (response.code() == 304) {
|
||||
Counter.builder("litmus_client_fetch_experiment_polling_request_status")
|
||||
.tag("vertical", vertical)
|
||||
.tag("app_name", this.appName)
|
||||
counterMetrics(litmusConfig, "litmus_client_fetch_experiment_polling_request_status")
|
||||
.tag("status", String.valueOf(response.code()))
|
||||
.tag("experiment_state", LitmusExperimentResponse.Status.NOT_CHANGED.name())
|
||||
.register(this.meterRegistry)
|
||||
.register(litmusConfig.getMeterRegistry())
|
||||
.increment();
|
||||
|
||||
return new LitmusExperimentResponse(LitmusExperimentResponse.Status.NOT_CHANGED, response.code());
|
||||
} else {
|
||||
Counter.builder("litmus_client_fetch_experiment_polling_request_status")
|
||||
.tag("vertical", this.vertical)
|
||||
.tag("app_name", this.appName)
|
||||
counterMetrics(litmusConfig, "litmus_client_fetch_experiment_polling_request_status")
|
||||
.tag("status", String.valueOf(response.code()))
|
||||
.tag("experiment_state", LitmusExperimentResponse.Status.NOT_CHANGED.name())
|
||||
.register(this.meterRegistry)
|
||||
.register(litmusConfig.getMeterRegistry())
|
||||
.increment();
|
||||
|
||||
return new LitmusExperimentResponse(LitmusExperimentResponse.Status.UNAVAILABLE, response.code());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Counter.builder("litmus_client_fetch_experiment_polling_request_failed")
|
||||
.tag("vertical", this.vertical)
|
||||
.tag("app_name", this.appName)
|
||||
.register(this.meterRegistry)
|
||||
counterMetrics(litmusConfig, "litmus_client_fetch_experiment_polling_request_failed")
|
||||
.tag("exception", e.getMessage())
|
||||
.register(litmusConfig.getMeterRegistry())
|
||||
.increment();
|
||||
throw new LitmusException("fetch experiments failed.", e);
|
||||
}
|
||||
@@ -104,7 +77,7 @@ public class HttpExperimentFetcher implements ExperimentFetcher {
|
||||
|
||||
@Timed(value = "litmus_client_segment_id_exists_latency", percentiles = {0.90, 0.95, 0.99})
|
||||
public LitmusResponse<Boolean> segmentIdExists(String segmentName, String id) {
|
||||
URL segmentIdUrl = this.litmusURLs.getSegmentIdURL();
|
||||
URL segmentIdUrl = litmusConfig.getLitmusURLs().getSegmentIdURL();
|
||||
Request request = new Request.Builder()
|
||||
.url(String.format("%s?segment_name=%s&id=%s", segmentIdUrl, segmentName, id))
|
||||
.build();
|
||||
@@ -113,20 +86,16 @@ public class HttpExperimentFetcher implements ExperimentFetcher {
|
||||
var responseBody = response.body();
|
||||
assert responseBody != null;
|
||||
|
||||
Counter.builder("litmus_client_segment_id_exist_request")
|
||||
.tag("vertical", this.vertical)
|
||||
.tag("app_name", this.appName)
|
||||
.tag("status", String.valueOf(response.code()))
|
||||
.register(this.meterRegistry)
|
||||
.increment();
|
||||
counterMetrics(litmusConfig, "litmus_client_segment_id_exist_request")
|
||||
.tag("status", String.valueOf(response.code()))
|
||||
.register(litmusConfig.getMeterRegistry())
|
||||
.increment();
|
||||
return JacksonUtils.stringToObject(responseBody.string(), LitmusResponse.class);
|
||||
} catch (Exception e) {
|
||||
Counter.builder("litmus_client_segment_id_exist_request_failed")
|
||||
.tag("vertical", this.vertical)
|
||||
counterMetrics(litmusConfig, "litmus_client_segment_id_exist_request_failed")
|
||||
.tag("segment_name", segmentName)
|
||||
.tag("app_name", this.appName)
|
||||
.tag("exception", e.getMessage())
|
||||
.register(this.meterRegistry)
|
||||
.register(litmusConfig.getMeterRegistry())
|
||||
.increment();
|
||||
|
||||
throw new LitmusException("segment name to id matching doesn't exists.", e);
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.navi.medici.scheduler.LitmusScheduledExecutor;
|
||||
import com.navi.medici.scheduler.LitmusScheduledExecutorImpl;
|
||||
import com.navi.medici.strategy.Strategy;
|
||||
import com.navi.medici.strategy.UnknownStrategy;
|
||||
import com.navi.medici.util.LitmusProperties;
|
||||
import com.navi.medici.util.LitmusURLs;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import java.io.File;
|
||||
@@ -15,6 +16,13 @@ import java.net.URI;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import lombok.Setter;
|
||||
|
||||
public class LitmusConfig {
|
||||
@@ -41,7 +49,11 @@ public class LitmusConfig {
|
||||
private final String clickStreamAPI;
|
||||
private final String vertical;
|
||||
private final MeterRegistry meterRegistry;
|
||||
private final ExecutorService executorService;
|
||||
|
||||
private final String litmusClientVersion;
|
||||
|
||||
private final boolean enableClickStream;
|
||||
|
||||
private LitmusConfig(
|
||||
URI litmusAPI,
|
||||
@@ -58,7 +70,10 @@ public class LitmusConfig {
|
||||
LitmusExperimentBootstrapProvider litmusBootstrapProvider,
|
||||
String clickStreamAPI,
|
||||
String vertical,
|
||||
MeterRegistry meterRegistry) {
|
||||
MeterRegistry meterRegistry,
|
||||
ExecutorService executorService,
|
||||
String litmusClientVersion,
|
||||
boolean enableClickStream) {
|
||||
|
||||
if (appName == null) {
|
||||
throw new IllegalStateException("You are required to specify the litmus appName");
|
||||
@@ -88,6 +103,13 @@ public class LitmusConfig {
|
||||
throw new IllegalStateException("You are required to specify meter registry");
|
||||
}
|
||||
|
||||
if (executorService == null) {
|
||||
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
|
||||
executorService = new ThreadPoolExecutor(20, 20,
|
||||
5000L, TimeUnit.MILLISECONDS,
|
||||
new ArrayBlockingQueue<Runnable>(1000, true), handler);
|
||||
}
|
||||
|
||||
|
||||
this.fallbackStrategy = Objects.requireNonNullElseGet(fallbackStrategy, UnknownStrategy::new);
|
||||
|
||||
@@ -106,6 +128,9 @@ public class LitmusConfig {
|
||||
this.clickStreamAPI = clickStreamAPI;
|
||||
this.vertical = vertical;
|
||||
this.meterRegistry = meterRegistry;
|
||||
this.executorService = executorService;
|
||||
this.litmusClientVersion = LitmusProperties.getProperty("litmus.client.version");
|
||||
this.enableClickStream = enableClickStream;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
@@ -125,6 +150,7 @@ public class LitmusConfig {
|
||||
return instanceId;
|
||||
}
|
||||
|
||||
//schedule periodic experiment poll interval in seconds
|
||||
public long getFetchLitmusExperimentsInterval() {
|
||||
return fetchLitmusExperimentsInterval;
|
||||
}
|
||||
@@ -177,6 +203,17 @@ public class LitmusConfig {
|
||||
return meterRegistry;
|
||||
}
|
||||
|
||||
public ExecutorService getExecutorService() {
|
||||
return executorService;
|
||||
}
|
||||
|
||||
public String getLitmusClientVersion() {
|
||||
return litmusClientVersion;
|
||||
}
|
||||
|
||||
public boolean isEnableClickStream() {
|
||||
return enableClickStream;
|
||||
}
|
||||
public static class Builder {
|
||||
|
||||
private URI litmusAPI;
|
||||
@@ -195,6 +232,12 @@ public class LitmusConfig {
|
||||
private String vertical;
|
||||
private MeterRegistry meterRegistry;
|
||||
|
||||
private ExecutorService executorService;
|
||||
|
||||
private String litmusClientVersion;
|
||||
|
||||
private boolean enableClickStream;
|
||||
|
||||
private static String getHostname() {
|
||||
String hostName = System.getProperty("hostname");
|
||||
if (hostName == null || hostName.length() == 0) {
|
||||
@@ -291,6 +334,20 @@ public class LitmusConfig {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder executorService(ExecutorService executorService) {
|
||||
this.executorService = executorService;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder getLitmusClient(String litmusClientVersion) {
|
||||
this.litmusClientVersion = litmusClientVersion;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder enableClickStream(boolean enableClickStream) {
|
||||
this.enableClickStream = enableClickStream;
|
||||
return this;
|
||||
}
|
||||
|
||||
private String getBackupFile() {
|
||||
if (backupFile != null) {
|
||||
@@ -319,14 +376,10 @@ public class LitmusConfig {
|
||||
litmusExperimentBootstrapProvider,
|
||||
clickStreamAPI,
|
||||
vertical,
|
||||
meterRegistry);
|
||||
}
|
||||
|
||||
public String getDefaultSdkVersion() {
|
||||
String version =
|
||||
Optional.ofNullable(getClass().getPackage().getImplementationVersion())
|
||||
.orElse("development");
|
||||
return "litmus-client:" + version;
|
||||
meterRegistry,
|
||||
executorService,
|
||||
litmusClientVersion,
|
||||
enableClickStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,7 @@ public class EventDispatcher {
|
||||
private final LitmusConfig litmusConfig;
|
||||
|
||||
public EventDispatcher(LitmusConfig litmusConfig) {
|
||||
this.clickStreamClient = new ClickStreamClient(litmusConfig.getClickStreamAPI(), litmusConfig.getAppName(),
|
||||
litmusConfig.getVertical(), litmusConfig.getMeterRegistry());
|
||||
this.clickStreamClient = new ClickStreamClient(litmusConfig);
|
||||
this.litmusConfig = litmusConfig;
|
||||
}
|
||||
|
||||
@@ -56,14 +55,14 @@ public class EventDispatcher {
|
||||
clickStreamPayload.setClickStreamEvent(List.of(event));
|
||||
clickStreamPayload.setSource("Litmus");
|
||||
clickStreamPayload.setUser(User.builder()
|
||||
.customerReferenceId(litmusContext.getUserId().orElse(""))
|
||||
.customerReferenceId(litmusContext.getUserId()
|
||||
.orElse(""))
|
||||
.build());
|
||||
|
||||
CompletableFuture.supplyAsync(() -> {
|
||||
CompletableFuture.runAsync(() -> {
|
||||
litmusConfig.getMeterRegistry().timer("litmus_client_click_stream_publish_latency")
|
||||
.record(() -> clickStreamClient.publish(litmusExperiment.getName(), clickStreamPayload));
|
||||
return null;
|
||||
});
|
||||
}, litmusConfig.getExecutorService());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.navi.medici.litmus;
|
||||
|
||||
|
||||
import com.navi.medici.annotation.Nullable;
|
||||
import com.navi.medici.client.ClickStreamClient;
|
||||
import com.navi.medici.client.ExperimentBackupHandlerFile;
|
||||
import com.navi.medici.client.HttpExperimentFetcher;
|
||||
import com.navi.medici.config.LitmusConfig;
|
||||
@@ -13,6 +12,7 @@ import com.navi.medici.provider.LitmusContextProvider;
|
||||
import com.navi.medici.repository.ExperimentRepository;
|
||||
import com.navi.medici.repository.LitmusExperimentRepository;
|
||||
import com.navi.medici.request.v1.LitmusExperiment;
|
||||
import com.navi.medici.strategy.CustomParameterWithIdStrategy;
|
||||
import com.navi.medici.strategy.DefaultStrategy;
|
||||
import com.navi.medici.strategy.DeviceWithIdStrategy;
|
||||
import com.navi.medici.strategy.FlexibleRolloutStrategy;
|
||||
@@ -23,21 +23,16 @@ import com.navi.medici.strategy.UserWithIdStrategy;
|
||||
import com.navi.medici.util.VariantUtil;
|
||||
import com.navi.medici.variants.Variant;
|
||||
|
||||
import io.micrometer.core.instrument.Counter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import static com.navi.medici.util.MetricsUtils.counterMetrics;
|
||||
|
||||
@Log4j2
|
||||
public class DefaultLitmus implements Litmus {
|
||||
|
||||
@@ -53,8 +48,7 @@ public class DefaultLitmus implements Litmus {
|
||||
private static ExperimentRepository defaultExperimentRepository(LitmusConfig litmusConfig) {
|
||||
return new LitmusExperimentRepository(
|
||||
litmusConfig,
|
||||
new HttpExperimentFetcher(litmusConfig.getLitmusURLs(), litmusConfig.getAppName(), litmusConfig.getNamePrefix(),
|
||||
litmusConfig.getProjectName(), litmusConfig.getVertical(), litmusConfig.getMeterRegistry()),
|
||||
new HttpExperimentFetcher(litmusConfig),
|
||||
new ExperimentBackupHandlerFile(litmusConfig)) {
|
||||
};
|
||||
}
|
||||
@@ -146,8 +140,7 @@ public class DefaultLitmus implements Litmus {
|
||||
}
|
||||
|
||||
} else {
|
||||
enabled =
|
||||
litmusExperiment.getStrategies().stream()
|
||||
enabled = litmusExperiment.getStrategies().stream()
|
||||
.anyMatch(
|
||||
strategy -> {
|
||||
Strategy configuredStrategy =
|
||||
@@ -157,20 +150,23 @@ public class DefaultLitmus implements Litmus {
|
||||
experimentName, strategy.getName());
|
||||
}
|
||||
|
||||
return configuredStrategy.isEnabled(
|
||||
strategy.getParameters(),
|
||||
context,
|
||||
strategy.getConstraints());
|
||||
boolean result = configuredStrategy.isEnabled(strategy.getParameters(),
|
||||
context, strategy.getConstraints());
|
||||
log.debug("experiment_name: {}, strategy: {}, result: {}",
|
||||
litmusExperiment.getName(), configuredStrategy.getName(), result);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
Counter.builder("litmus_client_experiment_check_enabled")
|
||||
counterMetrics(config, "litmus_client_experiment_check_enabled")
|
||||
.tag("experiment_name", experimentName)
|
||||
.tag("result", String.valueOf(enabled))
|
||||
.register(this.config.getMeterRegistry())
|
||||
.increment();
|
||||
|
||||
this.eventDispatcher.publish(context, litmusExperiment, enabled, null);
|
||||
if (config.isEnableClickStream()) {
|
||||
this.eventDispatcher.publish(context, litmusExperiment, enabled, null);
|
||||
}
|
||||
|
||||
log.info("experiment_name: {}, result: {}", experimentName, enabled);
|
||||
return enabled;
|
||||
@@ -183,14 +179,13 @@ public class DefaultLitmus implements Litmus {
|
||||
Arrays.asList(
|
||||
new DefaultStrategy(),
|
||||
|
||||
new UserWithIdStrategy(new HttpExperimentFetcher(litmusConfig.getLitmusURLs(), litmusConfig.getAppName(), litmusConfig.getNamePrefix(),
|
||||
litmusConfig.getProjectName(), litmusConfig.getVertical(), litmusConfig.getMeterRegistry())),
|
||||
new UserWithIdStrategy(new HttpExperimentFetcher(litmusConfig)),
|
||||
|
||||
new DeviceWithIdStrategy(new HttpExperimentFetcher(litmusConfig.getLitmusURLs(), litmusConfig.getAppName(), litmusConfig.getNamePrefix(),
|
||||
litmusConfig.getProjectName(), litmusConfig.getVertical(), litmusConfig.getMeterRegistry())),
|
||||
new DeviceWithIdStrategy(new HttpExperimentFetcher(litmusConfig)),
|
||||
|
||||
new PhoneNumberStrategy(new HttpExperimentFetcher(litmusConfig.getLitmusURLs(), litmusConfig.getAppName(), litmusConfig.getNamePrefix(),
|
||||
litmusConfig.getProjectName(), litmusConfig.getVertical(), litmusConfig.getMeterRegistry())),
|
||||
new PhoneNumberStrategy(new HttpExperimentFetcher(litmusConfig)),
|
||||
|
||||
new CustomParameterWithIdStrategy(new HttpExperimentFetcher(litmusConfig)),
|
||||
|
||||
new FlexibleRolloutStrategy());
|
||||
|
||||
@@ -228,10 +223,13 @@ public class DefaultLitmus implements Litmus {
|
||||
? VariantUtil.selectVariant(litmusExperiment, context, defaultValue)
|
||||
: defaultValue;
|
||||
|
||||
this.eventDispatcher.publish(context, litmusExperiment, enabled, variant);
|
||||
if (config.isEnableClickStream()) {
|
||||
this.eventDispatcher.publish(context, litmusExperiment, enabled, variant);
|
||||
}
|
||||
|
||||
log.info("experiment_name: {}, result: {}", experimentName, variant.toString());
|
||||
|
||||
Counter.builder("litmus_client_experiment_variant")
|
||||
counterMetrics(config, "litmus_client_experiment_variant")
|
||||
.tag("experiment_name", experimentName)
|
||||
.tag("result", String.valueOf(enabled))
|
||||
.tag("variant", variant.getName())
|
||||
@@ -257,13 +255,9 @@ public class DefaultLitmus implements Litmus {
|
||||
* Emit litmus version to prometheus to track litmus-client version used by services
|
||||
*/
|
||||
private void trackVersion(LitmusConfig litmusConfig) {
|
||||
Counter.builder("litmus_client_version")
|
||||
.tag("vertical", litmusConfig.getVertical())
|
||||
.tag("app_name", litmusConfig.getAppName())
|
||||
.tag("version", "2.0.5-RELEASE")
|
||||
counterMetrics(config, "litmus_client_version")
|
||||
.register(litmusConfig.getMeterRegistry())
|
||||
.increment();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.navi.medici.strategy;
|
||||
|
||||
import com.navi.medici.client.HttpExperimentFetcher;
|
||||
import com.navi.medici.context.LitmusContext;
|
||||
import com.navi.medici.util.StrategyUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static com.navi.medici.strategy.FlexibleRolloutStrategy.GROUP_ID;
|
||||
import static com.navi.medici.strategy.FlexibleRolloutStrategy.PERCENTAGE;
|
||||
|
||||
public class CustomParameterWithIdStrategy implements Strategy {
|
||||
private static final String STRATEGY_NAME = "customWithId";
|
||||
|
||||
private static final String SEGMENT = "segment";
|
||||
|
||||
private static final String CUSTOM_PARAMETER_NAME = "customParameter";
|
||||
|
||||
private final HttpExperimentFetcher experimentFetcher;
|
||||
|
||||
public CustomParameterWithIdStrategy(HttpExperimentFetcher experimentFetcher) {
|
||||
this.experimentFetcher = experimentFetcher;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return STRATEGY_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(Map<String, String> parameters) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(Map<String, String> parameters, LitmusContext litmusContext) {
|
||||
if (StringUtils.isBlank(parameters.get(PERCENTAGE))) {
|
||||
return customWithIdSegmentCheck(parameters, litmusContext);
|
||||
} else {
|
||||
return segmentCheckWithRollout(parameters, litmusContext);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean customWithIdSegmentCheck(Map<String, String> parameters, LitmusContext litmusContext) {
|
||||
var customParameterId = litmusContext.getProperties().get(CUSTOM_PARAMETER_NAME);
|
||||
var segmentName = parameters.get(SEGMENT);
|
||||
var result = experimentFetcher.segmentIdExists(segmentName, customParameterId);
|
||||
|
||||
return result.getData() != null && (Boolean) result.getData();
|
||||
}
|
||||
|
||||
private boolean segmentCheckWithRollout(Map<String, String> parameters, LitmusContext litmusContext) {
|
||||
var customParameterId = litmusContext.getProperties().get(CUSTOM_PARAMETER_NAME);
|
||||
var percentage = StrategyUtils.getPercentage(parameters.get(PERCENTAGE));
|
||||
var groupId = parameters.getOrDefault(GROUP_ID, "");
|
||||
|
||||
var norm = StrategyUtils.getNormalizedNumber(customParameterId, groupId);
|
||||
|
||||
return Optional.of(customWithIdSegmentCheck(parameters, litmusContext))
|
||||
.map(r -> r && percentage > 0 && norm <= percentage)
|
||||
.orElse(false);
|
||||
}
|
||||
}
|
||||
@@ -41,19 +41,19 @@ public class PhoneNumberStrategy implements Strategy {
|
||||
}
|
||||
|
||||
private boolean phoneNumberSegmentCheck(Map<String, String> parameters, LitmusContext litmusContext) {
|
||||
var userId = litmusContext.getUserId().orElse(null);
|
||||
var phoneNumber = litmusContext.getPhoneNumber().orElse(null);
|
||||
var segmentName = parameters.get(SEGMENT);
|
||||
var result = experimentFetcher.segmentIdExists(segmentName, userId);
|
||||
var result = experimentFetcher.segmentIdExists(segmentName, phoneNumber);
|
||||
|
||||
return result.getData() != null && (Boolean) result.getData();
|
||||
}
|
||||
|
||||
private boolean segmentCheckWithRollout(Map<String, String> parameters, LitmusContext litmusContext) {
|
||||
var userId = litmusContext.getUserId().orElse(null);
|
||||
var phoneNumber = litmusContext.getPhoneNumber().orElse(null);
|
||||
var percentage = StrategyUtils.getPercentage(parameters.get(PERCENTAGE));
|
||||
var groupId = parameters.getOrDefault(GROUP_ID, "");
|
||||
|
||||
var norm = StrategyUtils.getNormalizedNumber(userId, groupId);
|
||||
var norm = StrategyUtils.getNormalizedNumber(phoneNumber, groupId);
|
||||
|
||||
return Optional.of(phoneNumberSegmentCheck(parameters, litmusContext))
|
||||
.map(r -> r && percentage > 0 && norm <= percentage)
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.navi.medici.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
public class LitmusProperties {
|
||||
static Properties appProperties;
|
||||
|
||||
static {
|
||||
try (InputStream is = LitmusProperties.class.getClassLoader().getResourceAsStream("app.properties")) {
|
||||
appProperties = new Properties();
|
||||
appProperties.load(is);
|
||||
} catch (IOException ioException) {
|
||||
appProperties = new Properties();
|
||||
appProperties.setProperty("litmus.client.version", "0.0.0-default");
|
||||
}
|
||||
}
|
||||
|
||||
public static String getProperty(String propName) {
|
||||
return appProperties.getProperty(propName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.navi.medici.util;
|
||||
|
||||
import com.navi.medici.config.LitmusConfig;
|
||||
import io.micrometer.core.instrument.Counter;
|
||||
|
||||
public class MetricsUtils {
|
||||
public static Counter.Builder counterMetrics(LitmusConfig litmusConfig, String name) {
|
||||
return Counter.builder(name)
|
||||
.tag("vertical", litmusConfig.getVertical())
|
||||
.tag("app_name", litmusConfig.getAppName())
|
||||
.tag("litmus_client_version", litmusConfig.getLitmusClientVersion());
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
Implementation-Version: 2.0.5-RELEASE
|
||||
1
litmus-client/src/main/resources/app.properties
Normal file
1
litmus-client/src/main/resources/app.properties
Normal file
@@ -0,0 +1 @@
|
||||
litmus.client.version=2.0.6-RELEASE
|
||||
@@ -53,8 +53,7 @@ public class ClickStreamClientTest extends WireMockRules {
|
||||
.build();
|
||||
|
||||
var clickStreamPayload = clickStreamPayload();
|
||||
client = new ClickStreamClient(litmusConfig.getClickStreamAPI(), litmusConfig.getAppName(),
|
||||
litmusConfig.getVertical(), litmusConfig.getMeterRegistry());
|
||||
client = new ClickStreamClient(litmusConfig);
|
||||
|
||||
wireMockRule.stubFor(post("/events/json").withRequestBody(equalToJson(new ObjectMapper().writeValueAsString(clickStreamPayload)))
|
||||
.willReturn(aResponse()
|
||||
@@ -74,8 +73,7 @@ public class ClickStreamClientTest extends WireMockRules {
|
||||
.build();
|
||||
|
||||
var clickStreamPayload = clickStreamPayload();
|
||||
client = new ClickStreamClient(litmusConfig.getClickStreamAPI(), litmusConfig.getAppName(),
|
||||
litmusConfig.getVertical(), litmusConfig.getMeterRegistry());
|
||||
client = new ClickStreamClient(litmusConfig);
|
||||
|
||||
wireMockRule.stubFor(post("/events/json").withRequestBody(equalToJson(new ObjectMapper().writeValueAsString(clickStreamPayload)))
|
||||
.willReturn(aResponse()
|
||||
|
||||
@@ -35,15 +35,14 @@ public class HttpExperimentFetcherTest extends WireMockRules {
|
||||
File file =
|
||||
new File(getClass().getClassLoader().getResource("unleash-repo-v1.json").toURI());
|
||||
String response = TestUtils.fileToString(file);
|
||||
wireMockRule.stubFor(get( "/litmus-core/v1/experiments?vertical=test-experiment&polling_time=20")
|
||||
wireMockRule.stubFor(get( "/litmus-core/v1/experiments/vertical?vertical=test-experiment&polling_time=20")
|
||||
.willReturn(aResponse()
|
||||
.withHeader("Content-Type", "application/json")
|
||||
.withStatus(200)
|
||||
.withBody(response)));
|
||||
|
||||
|
||||
httpExperimentFetcher = new HttpExperimentFetcher(litmusConfig.getLitmusURLs(), litmusConfig.getAppName(), litmusConfig.getNamePrefix(),
|
||||
litmusConfig.getProjectName(), litmusConfig.getVertical(), litmusConfig.getMeterRegistry());
|
||||
httpExperimentFetcher = new HttpExperimentFetcher(litmusConfig);
|
||||
|
||||
var litmusResponse = httpExperimentFetcher.fetchExperiments("test-experiment", 20L );
|
||||
assertEquals(litmusResponse.getExperimentCollection().getLitmusExperiments().size(), 3);
|
||||
@@ -61,13 +60,12 @@ public class HttpExperimentFetcherTest extends WireMockRules {
|
||||
.litmusAPI("http://localhost:" + wireMockRule.port() + "/litmus-core/v1")
|
||||
.build();
|
||||
|
||||
stubFor(get("/litmus-core/v1/experiments?vertical=test-experiment&polling_time=20")
|
||||
stubFor(get("/litmus-core/v1/experiments/vertical?vertical=test-experiment&polling_time=20")
|
||||
.willReturn(aResponse()
|
||||
.withHeader("Content-Type", "application/json")
|
||||
.withStatus(304)));
|
||||
|
||||
httpExperimentFetcher = new HttpExperimentFetcher(litmusConfig.getLitmusURLs(), litmusConfig.getAppName(), litmusConfig.getNamePrefix(),
|
||||
litmusConfig.getProjectName(), litmusConfig.getVertical(), litmusConfig.getMeterRegistry());
|
||||
httpExperimentFetcher = new HttpExperimentFetcher(litmusConfig);
|
||||
|
||||
var litmusResponse = httpExperimentFetcher.fetchExperiments("test-experiment", 20L );
|
||||
assertEquals(litmusResponse.getExperimentCollection().getLitmusExperiments().size(), 0);
|
||||
@@ -85,18 +83,17 @@ public class HttpExperimentFetcherTest extends WireMockRules {
|
||||
.litmusAPI("http://localhost:" + wireMockRule.port() + "/litmus-core/v1")
|
||||
.build();
|
||||
|
||||
stubFor(get("/litmus-core/v1/experiments?vertical=test-experiment&polling_time=20")
|
||||
stubFor(get("/litmus-core/v1/experiments/vertical?vertical=test-experiment&polling_time=20")
|
||||
.willReturn(aResponse()
|
||||
.withHeader("Content-Type", "application/json")
|
||||
.withStatus(500)));
|
||||
|
||||
httpExperimentFetcher = new HttpExperimentFetcher(litmusConfig.getLitmusURLs(), litmusConfig.getAppName(), litmusConfig.getNamePrefix(),
|
||||
litmusConfig.getProjectName(), litmusConfig.getVertical(), litmusConfig.getMeterRegistry());
|
||||
httpExperimentFetcher = new HttpExperimentFetcher(litmusConfig);
|
||||
|
||||
var litmusResponse = httpExperimentFetcher.fetchExperiments("test-experiment", 20L );
|
||||
assertEquals(litmusResponse.getExperimentCollection().getLitmusExperiments().size(), 0);
|
||||
assertEquals(litmusResponse.getHttpStatusCode(), 500);
|
||||
assertEquals(litmusResponse.getStatus(), Status.UNAVAILABLE);
|
||||
assertEquals(500, litmusResponse.getHttpStatusCode());
|
||||
assertEquals(Status.UNAVAILABLE, litmusResponse.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -120,8 +117,7 @@ public class HttpExperimentFetcherTest extends WireMockRules {
|
||||
.withStatus(200)
|
||||
.withBody(response)));
|
||||
|
||||
httpExperimentFetcher = new HttpExperimentFetcher(litmusConfig.getLitmusURLs(), litmusConfig.getAppName(), litmusConfig.getNamePrefix(),
|
||||
litmusConfig.getProjectName(), litmusConfig.getVertical(), litmusConfig.getMeterRegistry());
|
||||
httpExperimentFetcher = new HttpExperimentFetcher(litmusConfig);
|
||||
|
||||
var result = httpExperimentFetcher.segmentIdExists(segmentName, id);
|
||||
assertTrue(result.getData());
|
||||
@@ -144,8 +140,7 @@ public class HttpExperimentFetcherTest extends WireMockRules {
|
||||
.willReturn(aResponse()
|
||||
.withStatus(200)));
|
||||
|
||||
httpExperimentFetcher = new HttpExperimentFetcher(litmusConfig.getLitmusURLs(), litmusConfig.getAppName(), litmusConfig.getNamePrefix(),
|
||||
litmusConfig.getProjectName(), litmusConfig.getVertical(), litmusConfig.getMeterRegistry());
|
||||
httpExperimentFetcher = new HttpExperimentFetcher(litmusConfig);
|
||||
|
||||
Exception e = assertThrows(LitmusException.class, () ->
|
||||
httpExperimentFetcher.segmentIdExists(segmentName, id)
|
||||
|
||||
@@ -15,6 +15,8 @@ import com.navi.medici.context.LitmusContext;
|
||||
import com.navi.medici.enums.ExperimentType;
|
||||
import com.navi.medici.request.v1.LitmusExperiment;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@@ -36,6 +38,7 @@ public class EventDispatcherTest {
|
||||
|
||||
@Test
|
||||
public void testEventDispatchWithoutClickStreamPayload() {
|
||||
when(litmusConfig.getExecutorService()).thenReturn(Executors.newFixedThreadPool(1));
|
||||
LitmusContext litmusContext = LitmusContext.builder()
|
||||
.appName("test-app")
|
||||
.userId("test-user-id")
|
||||
@@ -58,7 +61,7 @@ public class EventDispatcherTest {
|
||||
|
||||
@Test
|
||||
public void testEventDispatchWithClickStreamPayload() {
|
||||
|
||||
when(litmusConfig.getExecutorService()).thenReturn(Executors.newFixedThreadPool(1));
|
||||
LitmusContext litmusContext = buildLitmusContext();
|
||||
|
||||
LitmusExperiment litmusExperiment = LitmusExperiment.builder()
|
||||
|
||||
@@ -3,8 +3,12 @@ package com.navi.medici.litmus;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyMap;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.navi.medici.context.LitmusContext;
|
||||
@@ -71,7 +75,7 @@ public class DefaultLitmusTest {
|
||||
.deviceId("test-device-id")
|
||||
.build());
|
||||
var result = litmus.isEnabled("experiment-1");
|
||||
assertTrue(result);
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -156,6 +160,8 @@ public class DefaultLitmusTest {
|
||||
public void testExperimentPresentWithStrategy100RolloutVariant() {
|
||||
var litmusConfigBuilder = TestUtils.buildLitmusConfig();
|
||||
litmusConfigBuilder.litmusContextProvider(litmusContextProvider);
|
||||
litmusConfigBuilder.enableClickStream(true);
|
||||
|
||||
litmus = new DefaultLitmus(litmusConfigBuilder.build(), litmusExperimentRepository);
|
||||
|
||||
when(litmusExperimentRepository.getLitmusExperiment(anyString())).thenReturn(LitmusExperiment.builder()
|
||||
@@ -184,4 +190,38 @@ public class DefaultLitmusTest {
|
||||
assertTrue(result.isEnabled());
|
||||
assertEquals(result.getName(), "variant-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClickStreamKillSwitch() {
|
||||
var litmusConfigBuilder = TestUtils.buildLitmusConfig();
|
||||
litmusConfigBuilder.litmusContextProvider(litmusContextProvider);
|
||||
litmus = new DefaultLitmus(litmusConfigBuilder.build(), litmusExperimentRepository);
|
||||
|
||||
when(litmusExperimentRepository.getLitmusExperiment(anyString())).thenReturn(LitmusExperiment.builder()
|
||||
.name("experiment-1")
|
||||
.enabled(Boolean.TRUE)
|
||||
.vertical("PL")
|
||||
.experimentId("test-experiment-id")
|
||||
.type(ExperimentType.EXPERIMENT)
|
||||
.strategies(List.of(ActivationStrategy.builder()
|
||||
.name("flexibleRollout")
|
||||
.parameters(Map.of("rollout", "100", "stickiness", "userId", "groupId", "test-group-id"))
|
||||
.build()))
|
||||
.variants(List.of(VariantDefinition.builder()
|
||||
.name("variant-1")
|
||||
.weight(100)
|
||||
.stickiness("userId")
|
||||
.overrides(List.of())
|
||||
.build()))
|
||||
.build());
|
||||
|
||||
when(litmusContextProvider.getContext()).thenReturn(LitmusContext.builder()
|
||||
.userId("test-user-id")
|
||||
.deviceId("test-device-id")
|
||||
.build());
|
||||
var result = litmus.getVariant("experiment-1");
|
||||
assertTrue(result.isEnabled());
|
||||
assertEquals(result.getName(), "variant-1");
|
||||
verify(eventDispatcher, times(0)).publish(any(), any(), any(), any());
|
||||
}
|
||||
}
|
||||
@@ -46,8 +46,7 @@ public class LitmusExperimentRepositoryTest {
|
||||
public void noBackUpAndNoRepositoryAvailable() {
|
||||
LitmusConfig litmusConfig = TestUtils.buildLitmusConfig().build();
|
||||
|
||||
ExperimentFetcher experimentFetcher = new HttpExperimentFetcher(litmusConfig.getLitmusURLs(), litmusConfig.getAppName(), litmusConfig.getNamePrefix(),
|
||||
litmusConfig.getProjectName(), litmusConfig.getVertical(), litmusConfig.getMeterRegistry());
|
||||
ExperimentFetcher experimentFetcher = new HttpExperimentFetcher(litmusConfig);
|
||||
ExperimentBackupHandler experimentBackupHandler = new ExperimentBackupHandlerFile(litmusConfig);
|
||||
|
||||
LitmusExperimentRepository litmusExperimentRepository = new LitmusExperimentRepository(litmusConfig, experimentFetcher, experimentBackupHandler);
|
||||
@@ -63,8 +62,7 @@ public class LitmusExperimentRepositoryTest {
|
||||
ExperimentBackupHandler experimentBackupHandler = mock(ExperimentBackupHandlerFile.class);
|
||||
when(experimentBackupHandler.read()).thenReturn(new LitmusExperimentCollection(Collections.emptyList(), 1));
|
||||
|
||||
ExperimentFetcher experimentFetcher = new HttpExperimentFetcher(litmusConfig.getLitmusURLs(), litmusConfig.getAppName(), litmusConfig.getNamePrefix(),
|
||||
litmusConfig.getProjectName(), litmusConfig.getVertical(), litmusConfig.getMeterRegistry());
|
||||
ExperimentFetcher experimentFetcher = new HttpExperimentFetcher(litmusConfig);
|
||||
|
||||
LitmusScheduledExecutor litmusScheduledExecutor = mock(LitmusScheduledExecutor.class);
|
||||
|
||||
@@ -141,12 +139,12 @@ public class LitmusExperimentRepositoryTest {
|
||||
LitmusExperimentRepository experimentRepository = new LitmusExperimentRepository(config, executor, experimentFetcher, experimentBackupHandler);
|
||||
|
||||
verify(executor).setInterval(runnableArgumentCaptor.capture(), anyLong(), anyLong());
|
||||
verify(experimentFetcher, times(0)).fetchExperiments(anyString(), anyLong());
|
||||
verify(experimentFetcher, times(1)).fetchExperiments(anyString(), anyLong());
|
||||
|
||||
runnableArgumentCaptor.getValue().run();
|
||||
|
||||
verify(experimentBackupHandler, times(1)).read();
|
||||
verify(experimentFetcher, times(1)).fetchExperiments(anyString(), anyLong());
|
||||
verify(experimentFetcher, times(2)).fetchExperiments(anyString(), anyLong());
|
||||
assertTrue(experimentRepository.getLitmusExperiment("experiment-name1").isEnabled());
|
||||
}
|
||||
|
||||
|
||||
@@ -21,10 +21,15 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class DeviceWithIdStrategyTest {
|
||||
|
||||
@Spy
|
||||
ObjectMapper objectMapper;
|
||||
|
||||
@Mock
|
||||
HttpExperimentFetcher httpExperimentFetcher;
|
||||
|
||||
@@ -39,7 +44,7 @@ public class DeviceWithIdStrategyTest {
|
||||
@Test
|
||||
public void testWithoutContext() throws Exception {
|
||||
String parameters = " {\"groupId\": \"new-home-page-group-id\", \"rollout\": \"100\", \"segment\": \"test-segment\"}";
|
||||
HashMap<String, String> params = new ObjectMapper().readValue(parameters, HashMap.class);
|
||||
HashMap<String, String> params = objectMapper.readValue(parameters, HashMap.class);
|
||||
|
||||
assertFalse(deviceWithIdStrategy.isEnabled(params));
|
||||
}
|
||||
@@ -47,7 +52,7 @@ public class DeviceWithIdStrategyTest {
|
||||
@Test
|
||||
public void testStrategyWithRollout() throws Exception {
|
||||
String parameters = " {\"groupId\": \"new-home-page-group-id\", \"rollout\": \"100\", \"segment\": \"test-segment\"}";
|
||||
HashMap<String, String> params = new ObjectMapper().readValue(parameters, HashMap.class);
|
||||
HashMap<String, String> params = objectMapper.readValue(parameters, HashMap.class);
|
||||
var context = TestUtils.buildLitmusContext();
|
||||
|
||||
when(httpExperimentFetcher.segmentIdExists(anyString(), anyString()))
|
||||
@@ -60,7 +65,7 @@ public class DeviceWithIdStrategyTest {
|
||||
@Test
|
||||
public void testStrategyWithoutRollout() throws Exception {
|
||||
String parameters = " {\"groupId\": \"new-home-page-group-id\", \"segment\": \"test-segment\"}";
|
||||
HashMap<String, String> params = new ObjectMapper().readValue(parameters, HashMap.class);
|
||||
HashMap<String, String> params = objectMapper.readValue(parameters, HashMap.class);
|
||||
var context = TestUtils.buildLitmusContext();
|
||||
|
||||
when(httpExperimentFetcher.segmentIdExists(anyString(), anyString()))
|
||||
|
||||
@@ -15,7 +15,8 @@ public class TestUtils {
|
||||
.clickStreamAPI("http://localhost")
|
||||
.vertical("PL")
|
||||
.appName("test-app")
|
||||
.litmusAPI("http://localhost:8080/litmus-core/v1");
|
||||
.litmusAPI("http://localhost:8080/litmus-core/v1")
|
||||
.synchronousFetchOnInitialisation(true);
|
||||
}
|
||||
|
||||
public static LitmusContext buildLitmusContext() {
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
<parent>
|
||||
<artifactId>litmus</artifactId>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>litmus-core</artifactId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>litmus-core</name>
|
||||
|
||||
@@ -31,25 +31,25 @@
|
||||
<dependency>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<artifactId>litmus-model</artifactId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<artifactId>litmus-db</artifactId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<artifactId>litmus-cache</artifactId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<artifactId>litmus-util</artifactId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.navi.medici.controller.v1;
|
||||
|
||||
import com.navi.medici.entity.ExperimentEntity;
|
||||
import com.navi.medici.request.v1.LitmusExperiment;
|
||||
import com.navi.medici.request.v1.LitmusExperimentStrategyUpdate;
|
||||
import com.navi.medici.request.v1.VerticalUpdateRequest;
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
<parent>
|
||||
<artifactId>litmus</artifactId>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>litmus-db</artifactId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>litmus-db</name>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<dependency>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<artifactId>litmus-model</artifactId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
<parent>
|
||||
<artifactId>litmus</artifactId>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>litmus-liquibase</artifactId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>litmus-liquibase</name>
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
<parent>
|
||||
<artifactId>litmus</artifactId>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>litmus-mock</artifactId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>litmus-mock</name>
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
<dependency>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<artifactId>litmus-model</artifactId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<artifactId>litmus-client</artifactId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -15,9 +15,9 @@ public class CustomLitmusContextProvider implements LitmusContextProvider {
|
||||
@Override
|
||||
public LitmusContext getContext() {
|
||||
return LitmusContext.builder()
|
||||
.userId(new Random().nextInt() + "")
|
||||
.deviceId(new Random().nextInt() + "")
|
||||
.clickStreamPayload("{\"app\":{\"name\":\"NaviDebug\",\"version\":\"77\",\"version_name\":\"1.9.3-cug-1-debug\"},\"client_ts\":\"1633768839907\",\"device\":{\"advertising_id\":\"8835d2b0-5615-412e-b133-8f8dc28e228e\",\"device_id\":\"9a14136215e76324\",\"manufacturer\":\"vivo\",\"model\":\"V2040\",\"os\":\"Android\",\"os_version\":\"30\"},\"events\":[{\"event_name\":\"splash\",\"timestamp\":1633768835058},{\"event_name\":\"home_activity\",\"timestamp\":1633768839791},{\"event_name\":\"home\",\"timestamp\":1633768839860},{\"event_name\":\"full_page_loader_shown\",\"timestamp\":1633768839868}],\"location\":{\"latitude\":\"null\",\"longitude\":\"null\"},\"network\":{\"carrier\":\"\",\"type\":\"Wifi\"},\"source\":\"SyncTimer\",\"user\":{}}")
|
||||
.addProperty("loanOfferReferenceId", "873a0877-51a7-4acf-96f6-66eb6ac80652")
|
||||
// .deviceId(new Random().nextInt() + "")
|
||||
// .clickStreamPayload("{\"app\":{\"name\":\"NaviDebug\",\"version\":\"77\",\"version_name\":\"1.9.3-cug-1-debug\"},\"client_ts\":\"1633768839907\",\"device\":{\"advertising_id\":\"8835d2b0-5615-412e-b133-8f8dc28e228e\",\"device_id\":\"9a14136215e76324\",\"manufacturer\":\"vivo\",\"model\":\"V2040\",\"os\":\"Android\",\"os_version\":\"30\"},\"events\":[{\"event_name\":\"splash\",\"timestamp\":1633768835058},{\"event_name\":\"home_activity\",\"timestamp\":1633768839791},{\"event_name\":\"home\",\"timestamp\":1633768839860},{\"event_name\":\"full_page_loader_shown\",\"timestamp\":1633768839868}],\"location\":{\"latitude\":\"null\",\"longitude\":\"null\"},\"network\":{\"carrier\":\"\",\"type\":\"Wifi\"},\"source\":\"SyncTimer\",\"user\":{}}")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ public class MockContainer {
|
||||
.instanceId("test-instance")
|
||||
.litmusContextProvider(new CustomLitmusContextProvider())
|
||||
.clickStreamAPI("https://dev-janus.np.navi-tech.in/events/json")
|
||||
.vertical("PL")
|
||||
.vertical("SA")
|
||||
.meterRegistry(meterRegistry)
|
||||
.synchronousFetchOnInitialisation(true)
|
||||
.build();
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
<parent>
|
||||
<artifactId>litmus</artifactId>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>litmus-model</artifactId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>litmus-model</name>
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
<parent>
|
||||
<artifactId>litmus</artifactId>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>litmus-proxy</artifactId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>litmus-proxy</name>
|
||||
|
||||
@@ -17,19 +17,19 @@
|
||||
<dependency>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<artifactId>litmus-model</artifactId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<artifactId>litmus-client</artifactId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<artifactId>litmus-util</artifactId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
<parent>
|
||||
<artifactId>litmus</artifactId>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>litmus-util</artifactId>
|
||||
<version>2.0.5-RELEASE</version>
|
||||
<version>2.0.6-RELEASE</version>
|
||||
|
||||
<name>litmus-util</name>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user