Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
eb9577c
Added ConfigService interface and concrete class. Added required cons…
Dec 16, 2024
c985841
Add config to RoutingContext and implement ConfigService injection
Dec 16, 2024
83fe8d7
Updated ConfigService getConfig method to use ConfigRetriever getCach…
Dec 17, 2024
6c8aefb
Updated UIDOperatorVerticle to use ConfigService, Mocked ConfigServic…
Dec 17, 2024
c7a788f
Updated UIDOperatorService to get "identity_v3" config from UIDOperat…
Dec 17, 2024
cfd6a6a
Added ConfigValidatorUtil, Added configValidationHandler method to Co…
Dec 18, 2024
975f6ab
Updated UIDOperatorService to take config as method parameters, Updat…
Dec 18, 2024
491025b
Removed setMaxSharingLifetimeSeconds metho from ExtendedUIDOperatorVe…
Dec 18, 2024
8018196
Added ConfigValidatorUtilTest, Removed unused imports in UIDOperatorV…
Dec 18, 2024
cd23998
Updated ConfigValidatorUtil to handle null values, Updated ConfigVali…
Dec 19, 2024
bde83a5
Fixed httpStore in ConfigService
Dec 19, 2024
8478fd2
Added ConfigServiceTest
Dec 19, 2024
8aee5a3
Added ConfigRetrieverFactory to create vert.x ConfigRetriever
Dec 31, 2024
8f40ba2
Updated ConfigService, Main
Dec 31, 2024
c1fd6df
Updated ConfigServiceTest
Jan 2, 2025
fc1147a
Merge branch 'refs/heads/main' into bmz-UID2-4632-runtime-config-appl…
Jan 2, 2025
ab30054
Updated ConfigRetrieverFactory
Jan 2, 2025
7d2a16d
Updated ConfigService
Jan 2, 2025
67b05b1
Added ConfigServiceManager, StaticConfigService
Jan 2, 2025
25ad265
Update UIDOperatorVerticle, Main
Jan 2, 2025
6bd4a35
Update UIDOperatorVerticle
Jan 6, 2025
80b6b19
Add DelegatingConfigService
Jan 6, 2025
7ef6af6
Update ConfigServiceManager, Main
Jan 6, 2025
ed3141a
Update UIDOperatorVerticleTest
Jan 8, 2025
5cde2b2
Add static config to UIDOperatorVerticle constructor
Jan 8, 2025
91ecc1d
Update ConfigServiceTest to combine futures using compose
Jan 9, 2025
41e7489
Update feature flag functionality
Jan 14, 2025
272ac08
Add ConfigServiceManager tests
Jan 14, 2025
68530b4
Update ConfigServiceTest to remove unnecessary use of mock server
Jan 14, 2025
1dfdea3
Add authorisation header to config retriever
Jan 15, 2025
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
5 changes: 3 additions & 2 deletions conf/default-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"enclave_platform": null,
"failure_shutdown_wait_hours": 120,
"sharing_token_expiry_seconds": 2592000,
"operator_type": "public"

"operator_type": "public",
"core_config_path": "/operator/config",
"config_scan_period_ms": 300000
}
4 changes: 2 additions & 2 deletions conf/integ-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@
"optout_api_token": "test-operator-key",
"optout_api_uri": "http://localhost:8081/optout/replicate",
"salts_expired_shutdown_hours": 12,
"operator_type": "public"

"operator_type": "public",
"core_operator_config_path": "http://localhost:8088/operator/config"
}
3 changes: 2 additions & 1 deletion conf/local-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@
"key_sharing_endpoint_provide_app_names": true,
"client_side_token_generate_log_invalid_http_origins": true,
"salts_expired_shutdown_hours": 12,
"operator_type": "public"
"operator_type": "public",
"remote_config_feature_flag": true
}
3 changes: 2 additions & 1 deletion scripts/aws/conf/integ-uid2-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
"core_attest_url": "https://core-integ.uidapi.com/attest",
"optout_api_uri": "https://optout-integ.uidapi.com/optout/replicate",
"optout_s3_folder": "uid-optout-integ/",
"allow_legacy_api": false
"allow_legacy_api": false,
"core_operator_config_path": "https://core-integ.uidapi.com/operator/config"
}
7 changes: 7 additions & 0 deletions src/main/java/com/uid2/operator/Const.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,12 @@ public class Config extends com.uid2.shared.Const.Config {
public static final String OptOutStatusMaxRequestSize = "optout_status_max_request_size";
public static final String MaxInvalidPaths = "logging_limit_max_invalid_paths_per_interval";
public static final String MaxVersionBucketsPerSite = "logging_limit_max_version_buckets_per_site";

public static final String CoreConfigPath = "core_operator_config_path";
public static final String ConfigScanPeriodMs = "config_scan_period_ms";
public static final String Config = "config";
public static final String identityV3 = "identity_v3";
public static final String RemoteConfigFeatureFlag = "remote_config_feature_flag";
public static final String RemoteConfigFlagConfigMapPath = "remote_config_feature_flag_path";
}
}
112 changes: 78 additions & 34 deletions src/main/java/com/uid2/operator/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
import com.uid2.operator.monitoring.IStatsCollectorQueue;
import com.uid2.operator.monitoring.OperatorMetrics;
import com.uid2.operator.monitoring.StatsCollectorVerticle;
import com.uid2.operator.service.SecureLinkValidatorService;
import com.uid2.operator.service.ShutdownService;
import com.uid2.operator.service.*;
import com.uid2.operator.vertx.Endpoints;
import com.uid2.operator.vertx.OperatorShutdownHandler;
import com.uid2.operator.store.CloudSyncOptOutStore;
Expand Down Expand Up @@ -37,6 +36,7 @@
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import io.micrometer.prometheus.PrometheusRenameFilter;
import io.vertx.config.ConfigRetriever;
import io.vertx.core.*;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.http.impl.HttpUtils;
Expand Down Expand Up @@ -203,7 +203,9 @@ else if (!Utils.isProductionEnvironment()) {
}

Vertx vertx = createVertx();
VertxUtils.createConfigRetriever(vertx).getConfig(ar -> {
ConfigRetriever configRetriever = VertxUtils.createConfigRetriever(vertx);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

undo


configRetriever.getConfig(ar -> {
if (ar.failed()) {
LOGGER.error("Unable to read config: " + ar.cause().getMessage(), ar.cause());
return;
Expand Down Expand Up @@ -267,39 +269,81 @@ private ICloudStorage wrapCloudStorageForOptOut(ICloudStorage cloudStorage) {
private void run() throws Exception {
this.createVertxInstancesMetric();
this.createVertxEventLoopsMetric();
Supplier<Verticle> operatorVerticleSupplier = () -> {
UIDOperatorVerticle verticle = new UIDOperatorVerticle(config, this.clientSideTokenGenerate, siteProvider, clientKeyProvider, clientSideKeypairProvider, getKeyManager(), saltProvider, optOutStore, Clock.systemUTC(), _statsCollectorQueue, new SecureLinkValidatorService(this.serviceLinkProvider, this.serviceProvider), this.shutdownHandler::handleSaltRetrievalResponse);
return verticle;
};

DeploymentOptions options = new DeploymentOptions();
int svcInstances = this.config.getInteger(Const.Config.ServiceInstancesProp);
options.setInstances(svcInstances);

Promise<Void> compositePromise = Promise.promise();
List<Future> fs = new ArrayList<>();
fs.add(createAndDeployStatsCollector());
fs.add(createStoreVerticles());

CompositeFuture.all(fs).onComplete(ar -> {
if (ar.failed()) compositePromise.fail(new Exception(ar.cause()));
else compositePromise.complete();
});
ConfigRetrieverFactory configRetrieverFactory = new ConfigRetrieverFactory();
ConfigRetriever dynamicConfigRetriever = configRetrieverFactory.createRemoteConfigRetriever(vertx, config, this.createOperatorKeyRetriever().retrieve());
ConfigRetriever staticConfigRetriever = configRetrieverFactory.createJsonRetriever(vertx, config);

Future<ConfigService> dynamicConfigFuture = ConfigService.create(dynamicConfigRetriever);
Future<ConfigService> staticConfigFuture = ConfigService.create(staticConfigRetriever);

ConfigRetriever featureFlagConfigRetriever = configRetrieverFactory.createFileRetriever(
vertx,
config.getString(Const.Config.RemoteConfigFlagConfigMapPath, "conf/local-config.json")
);

Future<JsonObject> featureFlagFuture = featureFlagConfigRetriever.getConfig();

Future.all(dynamicConfigFuture, staticConfigFuture, featureFlagFuture)
.compose(configServiceManagerCompositeFuture -> {
ConfigService dynamicConfigService = configServiceManagerCompositeFuture.resultAt(0);
ConfigService staticConfigService = configServiceManagerCompositeFuture.resultAt(1);
JsonObject featureFlagConfig = configServiceManagerCompositeFuture.resultAt(2);

boolean featureFlag = featureFlagConfig.getBoolean(Const.Config.RemoteConfigFeatureFlag, true);

ConfigServiceManager configServiceManager = new ConfigServiceManager(vertx, dynamicConfigService, staticConfigService, featureFlag);

featureFlagConfigRetriever.listen(change -> {
JsonObject newConfig = change.getNewConfiguration();
boolean useDynamicConfig = newConfig.getBoolean(Const.Config.RemoteConfigFeatureFlag, true);
configServiceManager.updateConfigService(useDynamicConfig).onComplete(update -> {
if (update.succeeded()) {
LOGGER.info("Remote config feature flag toggled successfully");
} else {
LOGGER.error("Failed to toggle remote config feature flag: " + update.cause());
}
});
});

IConfigService configService = configServiceManager.getDelegatingConfigService();
Supplier<Verticle> operatorVerticleSupplier = () -> {
UIDOperatorVerticle verticle = new UIDOperatorVerticle(configService, config, this.clientSideTokenGenerate, siteProvider, clientKeyProvider, clientSideKeypairProvider, getKeyManager(), saltProvider, optOutStore, Clock.systemUTC(), _statsCollectorQueue, new SecureLinkValidatorService(this.serviceLinkProvider, this.serviceProvider), this.shutdownHandler::handleSaltRetrievalResponse);
return verticle;
};

DeploymentOptions options = new DeploymentOptions();
int svcInstances = this.config.getInteger(Const.Config.ServiceInstancesProp);
options.setInstances(svcInstances);

Promise<Void> compositePromise = Promise.promise();
List<Future> fs = new ArrayList<>();
fs.add(createAndDeployStatsCollector());
try {
fs.add(createStoreVerticles());
} catch (Exception e) {
throw new RuntimeException(e);
}

compositePromise.future()
.compose(v -> {
metrics.setup();
vertx.setPeriodic(60000, id -> metrics.update());

Promise<String> promise = Promise.promise();
vertx.deployVerticle(operatorVerticleSupplier, options, promise);
return promise.future();
})
.onFailure(t -> {
LOGGER.error("Failed to bootstrap operator: " + t.getMessage(), new Exception(t));
vertx.close();
System.exit(1);
});
CompositeFuture.all(fs).onComplete(ar -> {
if (ar.failed()) compositePromise.fail(new Exception(ar.cause()));
else compositePromise.complete();
});

return compositePromise.future()
.compose(v -> {
metrics.setup();
vertx.setPeriodic(60000, id -> metrics.update());
Promise<String> promise = Promise.promise();
vertx.deployVerticle(operatorVerticleSupplier, options, promise);
return promise.future();
})
.onFailure(t -> {
LOGGER.error("Failed to bootstrap operator: " + t.getMessage(), new Exception(t));
vertx.close();
System.exit(1);
});
});
}

private Future<Void> createStoreVerticles() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.uid2.operator.service;

import io.vertx.config.ConfigRetriever;
import io.vertx.config.ConfigRetrieverOptions;
import io.vertx.config.ConfigStoreOptions;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;

import java.net.URI;
import java.net.URISyntaxException;

import static com.uid2.operator.Const.Config.ConfigScanPeriodMs;
import static com.uid2.operator.Const.Config.CoreConfigPath;

public class ConfigRetrieverFactory {
public ConfigRetriever createRemoteConfigRetriever(Vertx vertx, JsonObject bootstrapConfig, String operatorKey) throws URISyntaxException {
String configPath = bootstrapConfig.getString(CoreConfigPath);
URI uri = new URI(configPath);

ConfigStoreOptions httpStore = new ConfigStoreOptions()
.setType("http")
.setOptional(true)
.setConfig(new JsonObject()
.put("host", uri.getHost())
.put("port", uri.getPort())
.put("path", uri.getPath())
.put("headers", new JsonObject()
.put("Authorization", "Bearer " + operatorKey)));

ConfigRetrieverOptions retrieverOptions = new ConfigRetrieverOptions()
.setScanPeriod(bootstrapConfig.getLong(ConfigScanPeriodMs))
.addStore(httpStore);

return ConfigRetriever.create(vertx, retrieverOptions);
}

public ConfigRetriever createJsonRetriever(Vertx vertx, JsonObject config) {
ConfigStoreOptions jsonStore = new ConfigStoreOptions()
.setType("json")
.setConfig(config);

ConfigRetrieverOptions retrieverOptions = new ConfigRetrieverOptions()
.setScanPeriod(-1)
.addStore(jsonStore);


return ConfigRetriever.create(vertx, retrieverOptions);
}

public ConfigRetriever createFileRetriever(Vertx vertx, String path) {
ConfigStoreOptions fileStore = new ConfigStoreOptions()
.setType("file")
.setConfig(new JsonObject()
.put("path", path)
.put("format", "json"));

ConfigRetrieverOptions retrieverOptions = new ConfigRetrieverOptions()
.addStore(fileStore);

return ConfigRetriever.create(vertx, retrieverOptions);
}
}
70 changes: 70 additions & 0 deletions src/main/java/com/uid2/operator/service/ConfigService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.uid2.operator.service;

import com.uid2.operator.Const;
import io.vertx.config.ConfigRetriever;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.uid2.operator.service.ConfigValidatorUtil.*;
import static com.uid2.operator.service.UIDOperatorService.*;

public class ConfigService implements IConfigService {

private final ConfigRetriever configRetriever;
private static final Logger logger = LoggerFactory.getLogger(ConfigService.class);

private ConfigService(ConfigRetriever configRetriever) {
this.configRetriever = configRetriever;
this.configRetriever.setConfigurationProcessor(this::configValidationHandler);
}

public static Future<ConfigService> create(ConfigRetriever configRetriever) {
Promise<ConfigService> promise = Promise.promise();

ConfigService instance = new ConfigService(configRetriever);

configRetriever.getConfig(ar -> {
if (ar.succeeded()) {
System.out.println("Successfully loaded config");
promise.complete(instance);
} else {
System.err.println("Failed to load config: " + ar.cause().getMessage());
logger.error("Failed to load config: {}", ar.cause().getMessage());
promise.fail(ar.cause());
}
});

return promise.future();
}

@Override
public JsonObject getConfig() {
return configRetriever.getCachedConfig();
}

private JsonObject configValidationHandler(JsonObject config) {
boolean isValid = true;
Integer identityExpiresAfter = config.getInteger(IDENTITY_TOKEN_EXPIRES_AFTER_SECONDS);
Integer refreshExpiresAfter = config.getInteger(REFRESH_TOKEN_EXPIRES_AFTER_SECONDS);
Integer refreshIdentityAfter = config.getInteger(REFRESH_IDENTITY_TOKEN_AFTER_SECONDS);
Integer maxBidstreamLifetimeSeconds = config.getInteger(Const.Config.MaxBidstreamLifetimeSecondsProp, identityExpiresAfter);

isValid &= validateIdentityRefreshTokens(identityExpiresAfter, refreshExpiresAfter, refreshIdentityAfter);

isValid &= validateBidstreamLifetime(maxBidstreamLifetimeSeconds, identityExpiresAfter);

if (!isValid) {
logger.error("Failed to update config");
JsonObject lastConfig = this.getConfig();
if (lastConfig == null || lastConfig.isEmpty()) {
throw new RuntimeException("Invalid config retrieved and no previous config to revert to");
}
return lastConfig;
}

return config;
}
}
56 changes: 56 additions & 0 deletions src/main/java/com/uid2/operator/service/ConfigServiceManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.uid2.operator.service;

import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import io.vertx.core.shareddata.Lock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ConfigServiceManager {
private final Vertx vertx;
private final DelegatingConfigService delegatingConfigService;
private final IConfigService dynamicConfigService;
private final IConfigService staticConfigService;
private static final Logger logger = LoggerFactory.getLogger(ConfigServiceManager.class);

public ConfigServiceManager(Vertx vertx, IConfigService dynamicConfigService, IConfigService staticConfigService, boolean useDynamicConfig) {
this.vertx = vertx;
this.dynamicConfigService = dynamicConfigService;
this.staticConfigService = staticConfigService;
this.delegatingConfigService = new DelegatingConfigService(useDynamicConfig ? dynamicConfigService : staticConfigService);
}

public Future<Void> updateConfigService(boolean useDynamicConfig) {
Promise<Void> promise = Promise.promise();
vertx.sharedData().getLocalLock("updateConfigServiceLock", lockAsyncResult -> {
if (lockAsyncResult.succeeded()) {
Lock lock = lockAsyncResult.result();
try {
if (useDynamicConfig) {
logger.info("Switching to DynamicConfigService");
delegatingConfigService.updateConfigService(dynamicConfigService);
} else {
logger.info("Switching to StaticConfigService");
delegatingConfigService.updateConfigService(staticConfigService);
}
promise.complete();
} catch (Exception e) {
promise.fail(e);
} finally {
lock.release();
}
} else {
logger.error("Failed to acquire lock for updating active ConfigService", lockAsyncResult.cause());
promise.fail(lockAsyncResult.cause());
}
});

return promise.future();
}

public IConfigService getDelegatingConfigService() {
return delegatingConfigService;
}

}
Loading
Loading