Skip to content

Commit 32a08ce

Browse files
feat: add upsert all support (#72)
* feat: add upsert all support * fix: reset creation timestamp after delete
1 parent 1fc8b19 commit 32a08ce

File tree

18 files changed

+571
-299
lines changed

18 files changed

+571
-299
lines changed

config-service-api/src/main/proto/org/hypertrace/config/service/v1/config_service.proto

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ service ConfigService {
1919

2020
// Deletes the config for the specified request
2121
rpc DeleteConfig(DeleteConfigRequest) returns (DeleteConfigResponse) {}
22+
23+
rpc UpsertAllConfigs(UpsertAllConfigsRequest) returns (UpsertAllConfigsResponse) {}
2224
}
2325

2426
message UpsertConfigRequest {
@@ -110,3 +112,26 @@ message DeleteConfigRequest {
110112
message DeleteConfigResponse {
111113
ContextSpecificConfig deleted_config = 1;
112114
}
115+
116+
message UpsertAllConfigsRequest {
117+
repeated ConfigToUpsert configs = 1;
118+
119+
message ConfigToUpsert {
120+
string resource_name = 1;
121+
string resource_namespace = 2;
122+
string context = 3;
123+
google.protobuf.Value config = 4;
124+
}
125+
}
126+
127+
message UpsertAllConfigsResponse {
128+
repeated UpsertedConfig upserted_configs = 1;
129+
130+
message UpsertedConfig {
131+
string context = 1;
132+
google.protobuf.Value config = 2;
133+
int64 creation_timestamp = 3;
134+
int64 update_timestamp = 4;
135+
optional google.protobuf.Value prev_config = 5;
136+
}
137+
}

config-service-api/src/testFixtures/java/org/hypertrace/config/service/test/MockGenericConfigService.java

Lines changed: 88 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.grpc.inprocess.InProcessServerBuilder;
2424
import io.grpc.stub.StreamObserver;
2525
import java.io.IOException;
26+
import java.time.Clock;
2627
import java.util.LinkedHashMap;
2728
import java.util.List;
2829
import java.util.Objects;
@@ -37,6 +38,9 @@
3738
import org.hypertrace.config.service.v1.GetAllConfigsResponse;
3839
import org.hypertrace.config.service.v1.GetConfigRequest;
3940
import org.hypertrace.config.service.v1.GetConfigResponse;
41+
import org.hypertrace.config.service.v1.UpsertAllConfigsRequest;
42+
import org.hypertrace.config.service.v1.UpsertAllConfigsResponse;
43+
import org.hypertrace.config.service.v1.UpsertAllConfigsResponse.UpsertedConfig;
4044
import org.hypertrace.config.service.v1.UpsertConfigRequest;
4145
import org.hypertrace.config.service.v1.UpsertConfigResponse;
4246
import org.hypertrace.core.grpcutils.context.RequestContext;
@@ -51,6 +55,7 @@
5155
public class MockGenericConfigService {
5256

5357
private Server grpcServer;
58+
private Clock clock = Clock.systemUTC();
5459
private final InProcessServerBuilder serverBuilder;
5560
private final ManagedChannel configChannel;
5661
private final RequestContext context = RequestContext.forTenantId("default tenant");
@@ -102,35 +107,34 @@ public void shutdown() {
102107
this.configChannel.shutdownNow();
103108
}
104109

110+
public MockGenericConfigService withClock(Clock clock) {
111+
this.clock = clock;
112+
return this;
113+
}
114+
105115
@SuppressWarnings("unchecked")
106116
public MockGenericConfigService mockUpsert() {
107117
Mockito.doAnswer(
108118
invocation -> {
109119
UpsertConfigRequest request = invocation.getArgument(0, UpsertConfigRequest.class);
110120
StreamObserver<UpsertConfigResponse> responseStreamObserver =
111121
invocation.getArgument(1, StreamObserver.class);
112-
ResourceType resourceType =
113-
ResourceType.of(request.getResourceNamespace(), request.getResourceName());
114-
String configContext = configContextOrDefault(request.getContext());
115-
ContextSpecificConfig existingConfig = currentValues.get(resourceType, configContext);
116-
long updateTimestamp = System.currentTimeMillis();
117-
long creationTimestamp =
118-
existingConfig == null ? updateTimestamp : existingConfig.getCreationTimestamp();
119-
currentValues.put(
120-
resourceType,
121-
configContext,
122-
ContextSpecificConfig.newBuilder()
123-
.setContext(configContext)
124-
.setConfig(request.getConfig())
125-
.setCreationTimestamp(creationTimestamp)
126-
.setUpdateTimestamp(updateTimestamp)
127-
.build());
128-
responseStreamObserver.onNext(
122+
UpsertedConfig upsertedConfig =
123+
this.writeToMap(
124+
request.getResourceNamespace(),
125+
request.getResourceName(),
126+
request.getContext(),
127+
request.getConfig());
128+
UpsertConfigResponse.Builder responseBuilder =
129129
UpsertConfigResponse.newBuilder()
130-
.setConfig(request.getConfig())
131-
.setCreationTimestamp(creationTimestamp)
132-
.setUpdateTimestamp(updateTimestamp)
133-
.build());
130+
.setConfig(upsertedConfig.getConfig())
131+
.setCreationTimestamp(upsertedConfig.getCreationTimestamp())
132+
.setUpdateTimestamp(upsertedConfig.getUpdateTimestamp());
133+
if (upsertedConfig.hasPrevConfig()) {
134+
responseBuilder.setPrevConfig(upsertedConfig.getPrevConfig());
135+
}
136+
137+
responseStreamObserver.onNext(responseBuilder.build());
134138
responseStreamObserver.onCompleted();
135139
return null;
136140
})
@@ -243,6 +247,35 @@ public MockGenericConfigService mockGet() {
243247
return this;
244248
}
245249

250+
public MockGenericConfigService mockUpsertAll() {
251+
Mockito.doAnswer(
252+
invocation -> {
253+
UpsertAllConfigsRequest request =
254+
invocation.getArgument(0, UpsertAllConfigsRequest.class);
255+
StreamObserver<UpsertAllConfigsResponse> responseStreamObserver =
256+
invocation.getArgument(1, StreamObserver.class);
257+
258+
List<UpsertedConfig> configs =
259+
request.getConfigsList().stream()
260+
.map(
261+
configToUpsert ->
262+
this.writeToMap(
263+
configToUpsert.getResourceNamespace(),
264+
configToUpsert.getResourceName(),
265+
configToUpsert.getContext(),
266+
configToUpsert.getConfig()))
267+
.collect(Collectors.toUnmodifiableList());
268+
responseStreamObserver.onNext(
269+
UpsertAllConfigsResponse.newBuilder().addAllUpsertedConfigs(configs).build());
270+
responseStreamObserver.onCompleted();
271+
return null;
272+
})
273+
.when(this.mockConfigService)
274+
.upsertConfig(ArgumentMatchers.any(), ArgumentMatchers.any());
275+
276+
return this;
277+
}
278+
246279
private Optional<Value> mergeValues(List<Value> values) {
247280
if (values.isEmpty()) {
248281
return Optional.empty();
@@ -259,6 +292,40 @@ private boolean isValidValue(Optional<Value> value) {
259292
return value.isPresent() && value.get().getKindCase() != Value.KindCase.NULL_VALUE;
260293
}
261294

295+
private UpsertedConfig writeToMap(String namespace, String name, String context, Value config) {
296+
ResourceType resourceType = ResourceType.of(namespace, name);
297+
String configContext = configContextOrDefault(context);
298+
ContextSpecificConfig existingConfig = currentValues.get(resourceType, configContext);
299+
long updateTimestamp = clock.millis();
300+
long creationTimestamp =
301+
Optional.ofNullable(existingConfig)
302+
.map(ContextSpecificConfig::getCreationTimestamp)
303+
.orElse(updateTimestamp);
304+
Optional<Value> previousConfig =
305+
Optional.ofNullable(
306+
currentValues.put(
307+
resourceType,
308+
configContext,
309+
ContextSpecificConfig.newBuilder()
310+
.setContext(configContext)
311+
.setConfig(config)
312+
.setCreationTimestamp(creationTimestamp)
313+
.setUpdateTimestamp(updateTimestamp)
314+
.build()))
315+
.map(ContextSpecificConfig::getConfig);
316+
317+
UpsertedConfig.Builder resultBuilder =
318+
UpsertedConfig.newBuilder()
319+
.setConfig(config)
320+
.setContext(configContext)
321+
.setCreationTimestamp(creationTimestamp)
322+
.setUpdateTimestamp(updateTimestamp);
323+
324+
previousConfig.ifPresent(resultBuilder::setPrevConfig);
325+
326+
return resultBuilder.build();
327+
}
328+
262329
private class TestInterceptor implements ServerInterceptor {
263330
@Override
264331
public <ReqT, RespT> Listener<ReqT> interceptCall(

config-service-impl/src/main/java/org/hypertrace/config/service/ConfigResource.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,12 @@
33
import lombok.Value;
44

55
/**
6-
* Identifies the configuration resource which you want to deal with. A single config resource can
7-
* have multiple versions of config values associated with it.
6+
* Identifies the configuration resource which you want to deal with. Multiple contexts may exist
7+
* for this resource, each potentially with multiple versions.
88
*/
99
@Value
1010
public class ConfigResource {
11-
1211
String resourceName;
1312
String resourceNamespace;
1413
String tenantId;
15-
String context;
1614
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.hypertrace.config.service;
2+
3+
import com.google.common.base.Strings;
4+
import javax.annotation.Nonnull;
5+
import javax.annotation.Nullable;
6+
import lombok.Value;
7+
8+
/** A specific context within a configuration resource. */
9+
@Value
10+
public class ConfigResourceContext {
11+
private static final String DEFAULT_CONTEXT = "DEFAULT-CONTEXT";
12+
ConfigResource configResource;
13+
String context;
14+
15+
public ConfigResourceContext(@Nonnull ConfigResource configResource) {
16+
this(configResource, null);
17+
}
18+
19+
public ConfigResourceContext(@Nonnull ConfigResource configResource, @Nullable String context) {
20+
this.context = Strings.isNullOrEmpty(context) ? DEFAULT_CONTEXT : context;
21+
this.configResource = configResource;
22+
}
23+
}

0 commit comments

Comments
 (0)