Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/main/java/com/uid2/admin/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.uid2.admin.legacy.RotatingLegacyClientKeyProvider;
import com.uid2.admin.managers.KeysetManager;
import com.uid2.admin.monitoring.DataStoreMetrics;
import com.uid2.admin.monitoring.SaltRotationMetrics;
import com.uid2.admin.salt.SaltRotation;
import com.uid2.admin.secret.*;
import com.uid2.admin.store.*;
Expand Down Expand Up @@ -329,6 +330,7 @@ public void run() {
DataStoreMetrics.addDataStoreMetrics("service_link", serviceLinkProvider);
DataStoreMetrics.addDataStoreServiceLinkEntryCount("snowflake", serviceLinkProvider, serviceProvider);

SaltRotationMetrics.register(Metrics.globalRegistry);

ReplaceSharingTypesWithSitesJob replaceSharingTypesWithSitesJob = new ReplaceSharingTypesWithSitesJob(config, writeLock, adminKeysetProvider, keysetProvider, keysetStoreWriter, siteProvider);
jobDispatcher.enqueue(replaceSharingTypesWithSitesJob);
Expand Down
29 changes: 29 additions & 0 deletions src/main/java/com/uid2/admin/monitoring/SaltRotationMetrics.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.uid2.admin.monitoring;

import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;

import java.util.concurrent.atomic.AtomicLong;

public final class SaltRotationMetrics {
private static final AtomicLong lastRotatedSaltCount = new AtomicLong(-1);

public static void register(MeterRegistry registry) {
// Reports NaN until the first rotation completes, so the alert does not fire on cold start.
Gauge.builder("uid2_salts_rotated_last_cycle", lastRotatedSaltCount, SaltRotationMetrics::asGaugeValue)
.description("Number of salts rotated in the most recent successful salt rotation cycle")
.strongReference(true)
.register(registry);
}

public static void recordRotated(int count) {
lastRotatedSaltCount.set(count);
}

private static double asGaugeValue(AtomicLong ref) {
long value = ref.get();
return value < 0 ? Double.NaN : (double) value;
}

private SaltRotationMetrics() {}
}
2 changes: 2 additions & 0 deletions src/main/java/com/uid2/admin/salt/SaltRotation.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.uid2.admin.salt;

import com.uid2.admin.AdminConst;
import com.uid2.admin.monitoring.SaltRotationMetrics;
import com.uid2.shared.model.SaltEntry;
import com.uid2.shared.secret.IKeyGenerator;

Expand Down Expand Up @@ -63,6 +64,7 @@ public Result rotateSalts(
logSaltAges("rotated-salts", targetDate, saltsToRotate);
logSaltAges("total-salts", targetDate, Arrays.asList(postRotationSalts));
logBucketFormatCount(targetDate, postRotationSalts);
SaltRotationMetrics.recordRotated(saltsToRotate.size());

var nextSnapshot = new SaltSnapshot(
nextEffective,
Expand Down
57 changes: 57 additions & 0 deletions src/test/java/com/uid2/admin/salt/SaltRotationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import com.uid2.admin.AdminConst;
import com.uid2.admin.monitoring.SaltRotationMetrics;
import com.uid2.admin.salt.helper.SaltBuilder;
import com.uid2.admin.salt.helper.SaltSnapshotBuilder;
import com.uid2.shared.model.SaltEntry;
import com.uid2.shared.secret.IKeyGenerator;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.vertx.core.json.JsonObject;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
Expand Down Expand Up @@ -440,6 +442,61 @@ private int countEntriesWithLastUpdated(SaltEntry[] entries, Instant lastUpdated
return (int) Arrays.stream(entries).filter(e -> e.lastUpdated() == lastUpdated.toEpochMilli()).count();
}

@Test
void testRotateSaltsRecordsLastCycleMetric() throws Exception {
var registry = new SimpleMeterRegistry();
SaltRotationMetrics.register(registry);
SaltRotationMetrics.recordRotated(-1);

final Duration[] minAges = {
Duration.ofDays(1),
Duration.ofDays(2),
};
var lastSnapshot = SaltSnapshotBuilder.start()
.entries(10, daysEarlier(10), targetDate())
.build();

var result = saltRotation.rotateSalts(lastSnapshot, minAges, 0.2, targetDate());
assertTrue(result.hasSnapshot());

var rotatedCount = countEntriesWithLastUpdated(result.getSnapshot().getAllRotatingSalts(), result.getSnapshot().getEffective());
var gauge = registry.find("uid2_salts_rotated_last_cycle").gauge();
assertThat(gauge).isNotNull();
assertThat(gauge.value()).isEqualTo((double) rotatedCount);
}

@Test
void testRotateSaltsNoSnapshotLeavesMetricUnchanged() throws Exception {
var registry = new SimpleMeterRegistry();
SaltRotationMetrics.register(registry);
SaltRotationMetrics.recordRotated(42);

final Duration[] minAges = {
Duration.ofDays(1),
Duration.ofDays(2),
};
var lastSnapshot = SaltSnapshotBuilder.start()
.entries(10, targetDate(), targetDate())
.build();

var result = saltRotation.rotateSalts(lastSnapshot, minAges, 0.2, targetDate());
assertFalse(result.hasSnapshot());

var gauge = registry.find("uid2_salts_rotated_last_cycle").gauge();
assertThat(gauge.value()).isEqualTo(42.0);
}

@Test
void testSaltRotationMetricReportsNaNBeforeFirstRotation() {
var registry = new SimpleMeterRegistry();
SaltRotationMetrics.register(registry);
SaltRotationMetrics.recordRotated(-1);

var gauge = registry.find("uid2_salts_rotated_last_cycle").gauge();
assertThat(gauge).isNotNull();
assertThat(Double.isNaN(gauge.value())).isTrue();
}

@Test
void testRotateSaltsZeroDoesntRotateSaltsButUpdatesRefreshFrom() throws Exception {
var lastSnapshot = SaltSnapshotBuilder.start()
Expand Down
Loading