From d6d3c34ae9d24bbe68444fb28391a5f96f5376ab Mon Sep 17 00:00:00 2001 From: saxenakshitiz Date: Fri, 30 Aug 2024 23:55:28 +0530 Subject: [PATCH] chore: add support for system label application rules --- ...LabelApplicationRuleConfigServiceImpl.java | 83 +++++++++++++++---- ...lApplicationRuleConfigServiceImplTest.java | 57 +++++++++++-- 2 files changed, 117 insertions(+), 23 deletions(-) diff --git a/label-application-rule-config-service-impl/src/main/java/org/hypertrace/label/application/rule/config/service/LabelApplicationRuleConfigServiceImpl.java b/label-application-rule-config-service-impl/src/main/java/org/hypertrace/label/application/rule/config/service/LabelApplicationRuleConfigServiceImpl.java index 321bf4b3..e5da9d7b 100644 --- a/label-application-rule-config-service-impl/src/main/java/org/hypertrace/label/application/rule/config/service/LabelApplicationRuleConfigServiceImpl.java +++ b/label-application-rule-config-service-impl/src/main/java/org/hypertrace/label/application/rule/config/service/LabelApplicationRuleConfigServiceImpl.java @@ -1,12 +1,22 @@ package org.hypertrace.label.application.rule.config.service; +import static java.util.function.Function.identity; + +import com.google.protobuf.util.JsonFormat; import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; import io.grpc.Channel; import io.grpc.Status; +import io.grpc.StatusRuntimeException; import io.grpc.stub.StreamObserver; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; +import lombok.SneakyThrows; import org.hypertrace.config.objectstore.ConfigObject; import org.hypertrace.config.objectstore.IdentifiedObjectStore; import org.hypertrace.config.service.change.event.api.ConfigChangeEventGenerator; @@ -27,29 +37,42 @@ public class LabelApplicationRuleConfigServiceImpl extends LabelApplicationRuleConfigServiceGrpc.LabelApplicationRuleConfigServiceImplBase { - static final String LABEL_APPLICATION_RULE_CONFIG_SERVICE_CONFIG = + private static final String LABEL_APPLICATION_RULE_CONFIG_SERVICE_CONFIG = "label.application.rule.config.service"; - static final String MAX_DYNAMIC_LABEL_APPLICATION_RULES_PER_TENANT = + private static final String MAX_DYNAMIC_LABEL_APPLICATION_RULES_PER_TENANT = "max.dynamic.label.application.rules.per.tenant"; - static final int DEFAULT_MAX_DYNAMIC_LABEL_APPLICATION_RULES_PER_TENANT = 100; + private static final String SYSTEM_LABEL_APPLICATION_RULES = "system.label.application.rules"; + private static final int DEFAULT_MAX_DYNAMIC_LABEL_APPLICATION_RULES_PER_TENANT = 100; + private final IdentifiedObjectStore labelApplicationRuleStore; private final LabelApplicationRuleValidator requestValidator; private final int maxDynamicLabelApplicationRulesAllowed; + private final List systemLabelApplicationRules; + private final Map systemLabelApplicationRulesMap; public LabelApplicationRuleConfigServiceImpl( Channel configChannel, Config config, ConfigChangeEventGenerator configChangeEventGenerator) { - int maxDynamicRules = DEFAULT_MAX_DYNAMIC_LABEL_APPLICATION_RULES_PER_TENANT; - if (config.hasPath(LABEL_APPLICATION_RULE_CONFIG_SERVICE_CONFIG)) { - Config labelApplicationRuleConfig = - config.getConfig(LABEL_APPLICATION_RULE_CONFIG_SERVICE_CONFIG); - if (labelApplicationRuleConfig.hasPath(MAX_DYNAMIC_LABEL_APPLICATION_RULES_PER_TENANT)) { - maxDynamicRules = - labelApplicationRuleConfig.getInt(MAX_DYNAMIC_LABEL_APPLICATION_RULES_PER_TENANT); - } + Config labelApplicationRuleConfig = + config.hasPath(LABEL_APPLICATION_RULE_CONFIG_SERVICE_CONFIG) + ? config.getConfig(LABEL_APPLICATION_RULE_CONFIG_SERVICE_CONFIG) + : ConfigFactory.empty(); + this.maxDynamicLabelApplicationRulesAllowed = + labelApplicationRuleConfig.hasPath(MAX_DYNAMIC_LABEL_APPLICATION_RULES_PER_TENANT) + ? labelApplicationRuleConfig.getInt(MAX_DYNAMIC_LABEL_APPLICATION_RULES_PER_TENANT) + : DEFAULT_MAX_DYNAMIC_LABEL_APPLICATION_RULES_PER_TENANT; + if (labelApplicationRuleConfig.hasPath(SYSTEM_LABEL_APPLICATION_RULES)) { + final List systemLabelApplicationRulesObjectList = + labelApplicationRuleConfig.getObjectList(SYSTEM_LABEL_APPLICATION_RULES); + this.systemLabelApplicationRules = + buildSystemLabelApplicationRuleList(systemLabelApplicationRulesObjectList); + this.systemLabelApplicationRulesMap = + this.systemLabelApplicationRules.stream() + .collect(Collectors.toUnmodifiableMap(LabelApplicationRule::getId, identity())); + } else { + this.systemLabelApplicationRules = Collections.emptyList(); + this.systemLabelApplicationRulesMap = Collections.emptyMap(); } - this.maxDynamicLabelApplicationRulesAllowed = maxDynamicRules; - - ConfigServiceBlockingStub configServiceBlockingStub = + final ConfigServiceBlockingStub configServiceBlockingStub = ConfigServiceGrpc.newBlockingStub(configChannel) .withCallCredentials( RequestContextClientCallCredsProviderFactory.getClientCallCredsProvider().get()); @@ -96,9 +119,18 @@ public void getLabelApplicationRules( this.labelApplicationRuleStore.getAllObjects(requestContext).stream() .map(ConfigObject::getData) .collect(Collectors.toUnmodifiableList()); + Set labelApplicationRuleIds = + labelApplicationRules.stream() + .map(LabelApplicationRule::getId) + .collect(Collectors.toUnmodifiableSet()); + List filteredSystemLabelApplicationRules = + systemLabelApplicationRules.stream() + .filter(rule -> !labelApplicationRuleIds.contains(rule.getId())) + .collect(Collectors.toUnmodifiableList()); responseObserver.onNext( GetLabelApplicationRulesResponse.newBuilder() .addAllLabelApplicationRules(labelApplicationRules) + .addAllLabelApplicationRules(filteredSystemLabelApplicationRules) .build()); responseObserver.onCompleted(); } catch (Exception e) { @@ -116,6 +148,7 @@ public void updateLabelApplicationRule( LabelApplicationRule existingRule = this.labelApplicationRuleStore .getData(requestContext, request.getId()) + .or(() -> Optional.ofNullable(systemLabelApplicationRulesMap.get(request.getId()))) .orElseThrow(Status.NOT_FOUND::asRuntimeException); LabelApplicationRule updateLabelApplicationRule = existingRule.toBuilder().setData(request.getData()).build(); @@ -140,6 +173,12 @@ public void deleteLabelApplicationRule( try { RequestContext requestContext = RequestContext.CURRENT.get(); this.requestValidator.validateOrThrow(requestContext, request); + String labelApplicationRuleId = request.getId(); + if (systemLabelApplicationRulesMap.containsKey(labelApplicationRuleId)) { + // Deleting a system label application rule is not allowed + responseObserver.onError(new StatusRuntimeException(Status.INVALID_ARGUMENT)); + return; + } this.labelApplicationRuleStore .deleteObject(requestContext, request.getId()) .orElseThrow(Status.NOT_FOUND::asRuntimeException); @@ -166,4 +205,20 @@ private void checkRequestForDynamicLabelsLimit( } } } + + private List buildSystemLabelApplicationRuleList( + List configObjectList) { + return configObjectList.stream() + .map(LabelApplicationRuleConfigServiceImpl::buildLabelApplicationRuleFromConfig) + .collect(Collectors.toUnmodifiableList()); + } + + @SneakyThrows + private static LabelApplicationRule buildLabelApplicationRuleFromConfig( + com.typesafe.config.ConfigObject configObject) { + String jsonString = configObject.render(); + LabelApplicationRule.Builder builder = LabelApplicationRule.newBuilder(); + JsonFormat.parser().merge(jsonString, builder); + return builder.build(); + } } diff --git a/label-application-rule-config-service-impl/src/test/java/org/hypertrace/label/application/rule/config/service/LabelApplicationRuleConfigServiceImplTest.java b/label-application-rule-config-service-impl/src/test/java/org/hypertrace/label/application/rule/config/service/LabelApplicationRuleConfigServiceImplTest.java index d3e8dac7..a4385a99 100644 --- a/label-application-rule-config-service-impl/src/test/java/org/hypertrace/label/application/rule/config/service/LabelApplicationRuleConfigServiceImplTest.java +++ b/label-application-rule-config-service-impl/src/test/java/org/hypertrace/label/application/rule/config/service/LabelApplicationRuleConfigServiceImplTest.java @@ -1,12 +1,12 @@ package org.hypertrace.label.application.rule.config.service; -import static org.hypertrace.label.application.rule.config.service.LabelApplicationRuleConfigServiceImpl.LABEL_APPLICATION_RULE_CONFIG_SERVICE_CONFIG; -import static org.hypertrace.label.application.rule.config.service.LabelApplicationRuleConfigServiceImpl.MAX_DYNAMIC_LABEL_APPLICATION_RULES_PER_TENANT; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import io.grpc.Channel; @@ -44,20 +44,29 @@ import org.junit.jupiter.api.Test; public class LabelApplicationRuleConfigServiceImplTest { + private static final String SYSTEM_LABEL_APPLICATION_RULE_STR = + "{\"id\":\"system-label-application-rule-1\",\"data\":{\"name\":\"SystemLabelApplicationRule1\",\"matching_condition\":{\"leaf_condition\":{\"key_condition\":{\"operator\":\"OPERATOR_EQUALS\",\"value\":\"test.key\"},\"unary_condition\":{\"operator\":\"OPERATOR_EXISTS\"}}},\"label_action\":{\"entity_types\":[\"API\"],\"operation\":\"OPERATION_MERGE\",\"static_labels\":{\"ids\":[\"static-label-id-1\"]}},\"enabled\":false}}"; MockGenericConfigService mockGenericConfigService; LabelApplicationRuleConfigServiceBlockingStub labelApplicationRuleConfigServiceBlockingStub; + LabelApplicationRule systemLabelApplicationRule; @BeforeEach - void setUp() { + void setUp() throws InvalidProtocolBufferException { mockGenericConfigService = new MockGenericConfigService().mockUpsert().mockGet().mockGetAll().mockDelete(); Channel channel = mockGenericConfigService.channel(); ConfigChangeEventGenerator configChangeEventGenerator = mock(ConfigChangeEventGenerator.class); - Config config = - ConfigFactory.parseMap( - Map.of( - LABEL_APPLICATION_RULE_CONFIG_SERVICE_CONFIG, - Map.of(MAX_DYNAMIC_LABEL_APPLICATION_RULES_PER_TENANT, 2))); + String configStr = + "label.application.rule.config.service {\n" + + "max.dynamic.label.application.rules.per.tenant = 2\n" + + "system.label.application.rules = [\n" + + SYSTEM_LABEL_APPLICATION_RULE_STR + + "\n]\n" + + "}\n"; + Config config = ConfigFactory.parseString(configStr); + LabelApplicationRule.Builder builder = LabelApplicationRule.newBuilder().clear(); + JsonFormat.parser().merge(SYSTEM_LABEL_APPLICATION_RULE_STR, builder); + systemLabelApplicationRule = builder.build(); mockGenericConfigService .addService( new LabelApplicationRuleConfigServiceImpl(channel, config, configChangeEventGenerator)) @@ -109,7 +118,8 @@ void createLabelApplicationRuleWithDynamicLabelApplicationRulesLimitReached() { void getLabelApplicationRules() { LabelApplicationRule simpleRule = createSimpleRule("auth", "valid"); LabelApplicationRule compositeRule = createCompositeRule(); - Set expectedRules = Set.of(simpleRule, compositeRule); + Set expectedRules = + Set.of(simpleRule, compositeRule, systemLabelApplicationRule); GetLabelApplicationRulesResponse response = labelApplicationRuleConfigServiceBlockingStub.getLabelApplicationRules( GetLabelApplicationRulesRequest.getDefaultInstance()); @@ -133,6 +143,20 @@ void updateLabelApplicationRule() { assertEquals(expectedData, response.getLabelApplicationRule().getData()); } + @Test + void updateSystemLabelApplicationRule() { + LabelApplicationRuleData expectedData = buildSimpleRuleData("auth", "not-valid"); + UpdateLabelApplicationRuleRequest request = + UpdateLabelApplicationRuleRequest.newBuilder() + .setId(systemLabelApplicationRule.getId()) + .setData(expectedData) + .build(); + UpdateLabelApplicationRuleResponse response = + labelApplicationRuleConfigServiceBlockingStub.updateLabelApplicationRule(request); + assertEquals(systemLabelApplicationRule.getId(), response.getLabelApplicationRule().getId()); + assertEquals(expectedData, response.getLabelApplicationRule().getData()); + } + @Test void updateLabelApplicationRuleError() { LabelApplicationRule simpleRule = createSimpleRule("auth", "valid"); @@ -179,6 +203,21 @@ void deleteApplicationRuleError() { assertEquals(Status.NOT_FOUND, Status.fromThrowable(exception)); } + @Test + void deleteSystemApplicationRuleError() { + DeleteLabelApplicationRuleRequest request = + DeleteLabelApplicationRuleRequest.newBuilder() + .setId(systemLabelApplicationRule.getId()) + .build(); + Throwable exception = + assertThrows( + StatusRuntimeException.class, + () -> { + labelApplicationRuleConfigServiceBlockingStub.deleteLabelApplicationRule(request); + }); + assertEquals(Status.INVALID_ARGUMENT, Status.fromThrowable(exception)); + } + private LabelApplicationRuleData buildCompositeRuleData() { // This condition implies foo(key) exists AND foo(key) = bar(value) AND // req.http.headers.auth(key) = valid(value)