Skip to content

Commit e74511b

Browse files
committed
Add support for multiple configurations per product
1 parent d003966 commit e74511b

5 files changed

Lines changed: 134 additions & 59 deletions

File tree

dd-java-agent/appsec/src/main/java/com/datadog/appsec/config/AppSecConfigServiceImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ private void subscribeConfigurationPoller() {
4747
this.configurationPoller.addListener(
4848
Product.ASM_DD,
4949
AppSecConfigDeserializer.INSTANCE,
50-
(newConfig, hinter) -> {
50+
(configKey, newConfig, hinter) -> {
5151
if (newConfig == null) {
5252
// TODO: disable appsec
5353
return true;
@@ -61,7 +61,7 @@ private void subscribeConfigurationPoller() {
6161
this.configurationPoller.addFeaturesListener(
6262
"asm",
6363
AppSecFeaturesDeserializer.INSTANCE,
64-
(newConfig, hinter) -> {
64+
(product, newConfig, hinter) -> {
6565
// TODO: disable appsec
6666
return true;
6767
});

dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/config/AppSecConfigServiceImplSpecification.groovy

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,11 @@ class AppSecConfigServiceImplSpecification extends DDSpecification {
105105

106106
when:
107107
savedConfChangesListener.accept(
108+
_ as String,
108109
savedConfDeserializer.deserialize(
109110
'{"version": "2.0"}, "foo": {"version": "1.0"}'.bytes), null)
110111
savedFeaturesListener.accept(
112+
'asm',
111113
savedFeaturesDeserializer.deserialize(
112114
'{"enabled": true}'.bytes), null)
113115

@@ -136,6 +138,7 @@ class AppSecConfigServiceImplSpecification extends DDSpecification {
136138

137139
when:
138140
savedConfChangesListener.accept(
141+
_ as String,
139142
savedConfDeserializer.deserialize(
140143
'{"version": "1.1"}, "foo": {"version": "2.0"}'.bytes), null)
141144

remote-config/src/main/java/datadog/remoteconfig/ConfigurationChangesListener.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
public interface ConfigurationChangesListener<T> {
77
boolean accept(
8+
String configKey,
89
@Nullable T configuration, // null to "unapply" the configuration
910
PollingRateHinter pollingRateHinter);
1011

remote-config/src/main/java/datadog/remoteconfig/ConfigurationPoller.java

Lines changed: 41 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,11 @@
2626
import java.util.List;
2727
import java.util.Map;
2828
import java.util.Optional;
29-
import java.util.Set;
3029
import java.util.concurrent.TimeUnit;
3130
import java.util.concurrent.atomic.AtomicInteger;
3231
import java.util.function.Consumer;
3332
import java.util.regex.Matcher;
3433
import java.util.regex.Pattern;
35-
import java.util.stream.Collectors;
3634
import okhttp3.Call;
3735
import okhttp3.OkHttpClient;
3836
import okhttp3.Request;
@@ -138,7 +136,7 @@ public <T> void addFeaturesListener(
138136

139137
if (productFeaturesByteArray != null) {
140138
try {
141-
dl.deserializeAndAccept(productFeaturesByteArray, PollingRateHinter.NOOP);
139+
dl.deserializeAndAccept(name, productFeaturesByteArray, PollingRateHinter.NOOP);
142140
} catch (RuntimeException | IOException e) {
143141
log.warn("Error applying features for {}", name, e);
144142
}
@@ -292,12 +290,6 @@ private void handleAgentResponse(ResponseBody body) {
292290
}
293291
}
294292

295-
try {
296-
unapplyConfigs(configsToApply, this);
297-
} catch (ReportableException e) {
298-
errorMessage = e.getMessage();
299-
}
300-
301293
updateNextState(fleetResponse, inspectedConfigurationKeys, errorMessage);
302294

303295
if (successes == 0 && failures > 0) {
@@ -308,35 +300,7 @@ private void handleAgentResponse(ResponseBody body) {
308300
rescheduleBaseOnConfiguration(this.durationHint);
309301
}
310302

311-
private void unapplyConfigs(List<String> configsToApply, PollingRateHinter hinter) {
312-
Set<String> activeProducts =
313-
configsToApply.stream()
314-
.map(ConfigurationPoller::extractProductFromKey)
315-
.collect(Collectors.toSet());
316-
// it WILL not unapply configurations for products that we're subscribed to
317-
// but for which we see no configurations
318-
for (ConfigState configState : this.nextClientState.configStates) {
319-
String previousProduct = configState.product;
320-
if (!activeProducts.contains(previousProduct)) {
321-
DeserializerAndListener<?> dl = this.listeners.get(previousProduct);
322-
if (dl != null) {
323-
log.info("Unapplying configuration for {}", previousProduct);
324-
try {
325-
dl.listener.accept(null, hinter);
326-
} catch (Exception e) {
327-
ratelimitedLogger.warn(
328-
"Error unapplying configuration for " + previousProduct + ": " + e.getMessage());
329-
}
330-
}
331-
}
332-
}
333-
}
334-
335-
private boolean processConfigKey(
336-
RemoteConfigResponse fleetResponse,
337-
String configKey,
338-
List<String> inspectedConfigurationKeys,
339-
PollingRateHinter pollingRateHinter) {
303+
private DeserializerAndListener<?> extractDeserializerAndListenerFromKey(String configKey) {
340304
String productName = extractProductFromKey(configKey);
341305
if (productName == null) {
342306
throw new ReportableException("Cannot extract product from key " + configKey);
@@ -352,6 +316,23 @@ private boolean processConfigKey(
352316
+ " is not being handled");
353317
}
354318

319+
return dl;
320+
}
321+
322+
private boolean notifyConfigurationKeyRemoved(
323+
String configKey, PollingRateHinter pollingRateHinter) throws IOException {
324+
return extractDeserializerAndListenerFromKey(configKey)
325+
.deserializeAndAccept(configKey, null, pollingRateHinter);
326+
}
327+
328+
private boolean processConfigKey(
329+
RemoteConfigResponse fleetResponse,
330+
String configKey,
331+
List<String> inspectedConfigurationKeys,
332+
PollingRateHinter pollingRateHinter) {
333+
// find right product from configKey
334+
DeserializerAndListener<?> dl = extractDeserializerAndListenerFromKey(configKey);
335+
355336
// check if the hash of this configuration file actually changed
356337
CachedTargetFile cachedTargetFile = this.cachedTargetFiles.get(configKey);
357338
RemoteConfigResponse.Targets.ConfigTarget target = fleetResponse.getTarget(configKey);
@@ -389,8 +370,7 @@ private boolean processConfigKey(
389370

390371
try {
391372
log.debug("Applying configuration for {}", configKey);
392-
boolean result = dl.deserializeAndAccept(fileContent, pollingRateHinter);
393-
return result;
373+
return dl.deserializeAndAccept(configKey, fileContent, pollingRateHinter);
394374
} catch (IOException | RuntimeException ex) {
395375
ratelimitedLogger.warn("Error handling configuration for " + configKey, ex);
396376
return false;
@@ -484,6 +464,12 @@ private void updateNextState(
484464
String configKey = cachedConfigKeysIter.next();
485465
if (!inspectedConfigKeys.contains(configKey)) {
486466
cachedConfigKeysIter.remove();
467+
try {
468+
log.debug("Removing configuration for {}", configKey);
469+
notifyConfigurationKeyRemoved(configKey, this);
470+
} catch (IOException | RuntimeException ex) {
471+
ratelimitedLogger.warn("Error handling configuration for " + configKey, ex);
472+
}
487473
}
488474
}
489475
}
@@ -524,7 +510,7 @@ private void loadFromFile(File file, DeserializerAndListener<?> deserializerAndL
524510

525511
boolean res =
526512
deserializerAndListener.deserializeAndAccept(
527-
outputStream.toByteArray(), PollingRateHinter.NOOP);
513+
file.getAbsolutePath(), outputStream.toByteArray(), PollingRateHinter.NOOP);
528514
if (!res) {
529515
ratelimitedLogger.warn("Failed reading or applying configuration from {}", file);
530516
} else {
@@ -539,7 +525,7 @@ private void loadFromFile(File file, DeserializerAndListener<?> deserializerAndL
539525
// marked as synchronized only to satisfy spotbugs,
540526
// because this method is only called from synchronized methods anyway
541527
private synchronized boolean featuresChangeListener(
542-
FeaturesConfig fconfig, PollingRateHinter hinter) {
528+
String configKey, FeaturesConfig fconfig, PollingRateHinter hinter) {
543529
if (fconfig == null) {
544530
log.warn("Features configuration was pulled, which is unexpected");
545531
return true;
@@ -556,7 +542,7 @@ private synchronized boolean featuresChangeListener(
556542

557543
try {
558544
byte[] productFeaturesByteArray = fconfig.getProductFeaturesByteArray(product);
559-
dl.deserializeAndAccept(productFeaturesByteArray, hinter);
545+
dl.deserializeAndAccept(product, productFeaturesByteArray, hinter);
560546
} catch (IOException | RuntimeException e) {
561547
ratelimitedLogger.warn("Error processing features for {}", product);
562548
}
@@ -583,12 +569,19 @@ private static class DeserializerAndListener<T> {
583569
this.listener = listener;
584570
}
585571

586-
boolean deserializeAndAccept(byte[] bytes, PollingRateHinter hinter) throws IOException {
587-
T configuration = this.deserializer.deserialize(bytes);
588-
if (configuration == null) {
589-
return false;
572+
boolean deserializeAndAccept(String configKey, byte[] bytes, PollingRateHinter hinter)
573+
throws IOException {
574+
T configuration = null;
575+
576+
if (bytes != null) {
577+
configuration = this.deserializer.deserialize(bytes);
578+
// ensure deserializer return a value.
579+
if (configuration == null) {
580+
throw new RuntimeException("Configuration deserializer didn't provide a configuration");
581+
}
590582
}
591-
return this.listener.accept(configuration, hinter);
583+
584+
return this.listener.accept(configKey, configuration, hinter);
592585
}
593586
}
594587

0 commit comments

Comments
 (0)