mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-20 22:38:05 +01:00
Add a dynamic configuration manager
This commit is contained in:
committed by
GitHub
parent
5a9c8e304c
commit
92f6a79e1f
@@ -9,6 +9,7 @@ import io.dropwizard.Configuration;
|
||||
import io.dropwizard.client.JerseyClientConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.AccountDatabaseCrawlerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.ApnConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.AppConfigConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.AwsAttachmentsConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.CdnConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.DatabaseConfiguration;
|
||||
@@ -216,6 +217,11 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
@JsonProperty
|
||||
private RemoteConfigConfiguration remoteConfig;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private AppConfigConfiguration appConfig;
|
||||
|
||||
private Map<String, String> transparentDataIndex = new HashMap<>();
|
||||
|
||||
public RecaptchaConfiguration getRecaptchaConfiguration() {
|
||||
@@ -371,4 +377,8 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
public RemoteConfigConfiguration getRemoteConfigConfiguration() {
|
||||
return remoteConfig;
|
||||
}
|
||||
|
||||
public AppConfigConfiguration getAppConfig() {
|
||||
return appConfig;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +116,7 @@ import org.whispersystems.textsecuregcm.storage.ActiveUserCounter;
|
||||
import org.whispersystems.textsecuregcm.storage.DirectoryManager;
|
||||
import org.whispersystems.textsecuregcm.storage.DirectoryReconciler;
|
||||
import org.whispersystems.textsecuregcm.storage.DirectoryReconciliationClient;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||
import org.whispersystems.textsecuregcm.storage.FaultTolerantDatabase;
|
||||
import org.whispersystems.textsecuregcm.storage.FeatureFlags;
|
||||
import org.whispersystems.textsecuregcm.storage.FeatureFlagsManager;
|
||||
@@ -328,6 +329,8 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
RateLimiters rateLimiters = new RateLimiters(config.getLimitsConfiguration(), cacheCluster);
|
||||
ProvisioningManager provisioningManager = new ProvisioningManager(pubSubManager);
|
||||
|
||||
DynamicConfigurationManager dynamicConfigurationManager = new DynamicConfigurationManager(config.getAppConfig().getApplication(), config.getAppConfig().getEnvironment(), config.getAppConfig().getConfigurationName());
|
||||
|
||||
AccountAuthenticator accountAuthenticator = new AccountAuthenticator(accountsManager);
|
||||
DisabledPermittedAccountAuthenticator disabledPermittedAccountAuthenticator = new DisabledPermittedAccountAuthenticator(accountsManager);
|
||||
|
||||
@@ -373,6 +376,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
environment.lifecycle().manage(messagePersister);
|
||||
environment.lifecycle().manage(clientPresenceManager);
|
||||
environment.lifecycle().manage(featureFlagsManager);
|
||||
environment.lifecycle().manage(dynamicConfigurationManager);
|
||||
|
||||
AWSCredentials credentials = new BasicAWSCredentials(config.getCdnConfiguration().getAccessKey(), config.getCdnConfiguration().getAccessSecret());
|
||||
AWSCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(credentials);
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
public class AppConfigConfiguration {
|
||||
|
||||
@JsonProperty
|
||||
@NotEmpty
|
||||
private String application;
|
||||
|
||||
@JsonProperty
|
||||
@NotEmpty
|
||||
private String environment;
|
||||
|
||||
@JsonProperty
|
||||
@NotEmpty
|
||||
private String configuration;
|
||||
|
||||
public String getApplication() {
|
||||
return application;
|
||||
}
|
||||
|
||||
public String getEnvironment() {
|
||||
return environment;
|
||||
}
|
||||
|
||||
public String getConfigurationName() {
|
||||
return configuration;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.whispersystems.textsecuregcm.configuration.dynamic;
|
||||
|
||||
public class DynamicConfiguration {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import com.amazonaws.ClientConfiguration;
|
||||
import com.amazonaws.services.appconfig.AmazonAppConfig;
|
||||
import com.amazonaws.services.appconfig.AmazonAppConfigClient;
|
||||
import com.amazonaws.services.appconfig.model.GetConfigurationRequest;
|
||||
import com.amazonaws.services.appconfig.model.GetConfigurationResult;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import io.dropwizard.lifecycle.Managed;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class DynamicConfigurationManager implements Managed {
|
||||
|
||||
private final String application;
|
||||
private final String environment;
|
||||
private final String configurationName;
|
||||
private final String clientId;
|
||||
private final AmazonAppConfig appConfigClient;
|
||||
|
||||
private final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()).configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
private final AtomicReference<DynamicConfiguration> configuration = new AtomicReference<>();
|
||||
private final AtomicBoolean running = new AtomicBoolean(true);
|
||||
private final Logger logger = LoggerFactory.getLogger(DynamicConfigurationManager.class);
|
||||
|
||||
private GetConfigurationResult lastConfigResult;
|
||||
|
||||
private boolean initialized = false;
|
||||
|
||||
public DynamicConfigurationManager(String application, String environment, String configurationName) {
|
||||
this(AmazonAppConfigClient.builder()
|
||||
.withClientConfiguration(new ClientConfiguration().withClientExecutionTimeout(10000).withRequestTimeout(10000))
|
||||
.build(),
|
||||
application, environment, configurationName, UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public DynamicConfigurationManager(AmazonAppConfig appConfigClient, String application, String environment, String configurationName, String clientId) {
|
||||
this.appConfigClient = appConfigClient;
|
||||
this.application = application;
|
||||
this.environment = environment;
|
||||
this.configurationName = configurationName;
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public DynamicConfiguration getConfiguration() {
|
||||
synchronized (this) {
|
||||
while (!initialized) Util.wait(this);
|
||||
}
|
||||
|
||||
return configuration.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
configuration.set(retrieveInitialDynamicConfiguration());
|
||||
|
||||
synchronized (this) {
|
||||
this.initialized = true;
|
||||
this.notifyAll();
|
||||
}
|
||||
|
||||
new Thread(() -> {
|
||||
while (running.get()) {
|
||||
try {
|
||||
retrieveDynamicConfiguration().ifPresent(configuration::set);
|
||||
} catch (Throwable t) {
|
||||
logger.warn("Error retrieving dynamic configuration", t);
|
||||
}
|
||||
|
||||
Util.sleep(5000);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
running.set(false);
|
||||
}
|
||||
|
||||
private Optional<DynamicConfiguration> retrieveDynamicConfiguration() throws JsonProcessingException {
|
||||
final String previousVersion = lastConfigResult != null ? lastConfigResult.getConfigurationVersion() : null;
|
||||
|
||||
lastConfigResult = appConfigClient.getConfiguration(new GetConfigurationRequest().withApplication(application)
|
||||
.withEnvironment(environment)
|
||||
.withConfiguration(configurationName)
|
||||
.withClientId(clientId)
|
||||
.withClientConfigurationVersion(previousVersion));
|
||||
|
||||
final Optional<DynamicConfiguration> maybeDynamicConfiguration;
|
||||
|
||||
if (!StringUtils.equals(lastConfigResult.getConfigurationVersion(), previousVersion)) {
|
||||
logger.info("Received new config version: {}", lastConfigResult.getConfigurationVersion());
|
||||
maybeDynamicConfiguration = Optional.of(mapper.readValue(StandardCharsets.UTF_8.decode(lastConfigResult.getContent().asReadOnlyBuffer()).toString(), DynamicConfiguration.class));
|
||||
} else {
|
||||
// No change since last version
|
||||
maybeDynamicConfiguration = Optional.empty();
|
||||
}
|
||||
|
||||
return maybeDynamicConfiguration;
|
||||
}
|
||||
|
||||
private DynamicConfiguration retrieveInitialDynamicConfiguration() {
|
||||
for (;;) {
|
||||
try {
|
||||
final Optional<DynamicConfiguration> maybeDynamicConfiguration = retrieveDynamicConfiguration();
|
||||
|
||||
if (maybeDynamicConfiguration.isPresent()) {
|
||||
return maybeDynamicConfiguration.get();
|
||||
} else {
|
||||
throw new IllegalStateException("No initial configuration available");
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
logger.warn("Error retrieving initial dynamic configuration", t);
|
||||
Util.sleep(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user