diff --git a/ranger-discovery-bundle/perf/results/io.appform.ranger.discovery.bundle.id.IdGeneratorPerfTest.testGenerate.json b/ranger-discovery-bundle/perf/results/io.appform.ranger.discovery.bundle.id.IdGeneratorPerfTest.testGenerate.json index 5b64c35a..fa8f94a8 100644 --- a/ranger-discovery-bundle/perf/results/io.appform.ranger.discovery.bundle.id.IdGeneratorPerfTest.testGenerate.json +++ b/ranger-discovery-bundle/perf/results/io.appform.ranger.discovery.bundle.id.IdGeneratorPerfTest.testGenerate.json @@ -4,5 +4,5 @@ "iterations" : 4, "threads" : 1, "forks" : 3, - "mean_ops" : 823635.7718335792 + "mean_ops" : 806565.5933954978 } \ No newline at end of file diff --git a/ranger-discovery-bundle/perf/results/io.appform.ranger.discovery.bundle.id.IdGeneratorPerfTest.testGenerateBase36.json b/ranger-discovery-bundle/perf/results/io.appform.ranger.discovery.bundle.id.IdGeneratorPerfTest.testGenerateBase36.json index fe461876..8cc3b275 100644 --- a/ranger-discovery-bundle/perf/results/io.appform.ranger.discovery.bundle.id.IdGeneratorPerfTest.testGenerateBase36.json +++ b/ranger-discovery-bundle/perf/results/io.appform.ranger.discovery.bundle.id.IdGeneratorPerfTest.testGenerateBase36.json @@ -4,5 +4,5 @@ "iterations" : 4, "threads" : 1, "forks" : 3, - "mean_ops" : 655010.8023043653 + "mean_ops" : 618403.982977282 } \ No newline at end of file diff --git a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/ServiceDiscoveryBundle.java b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/ServiceDiscoveryBundle.java index 1c7a54f0..49d3b896 100644 --- a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/ServiceDiscoveryBundle.java +++ b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/ServiceDiscoveryBundle.java @@ -50,6 +50,7 @@ import io.appform.ranger.discovery.bundle.rotationstatus.RotationStatus; import io.appform.ranger.discovery.bundle.selectors.HierarchicalEnvironmentAwareShardSelector; import io.appform.ranger.discovery.bundle.util.ConfigurationUtils; +import io.appform.ranger.discovery.bundle.util.NodeUtils; import io.appform.ranger.zookeeper.ServiceProviderBuilders; import io.appform.ranger.zookeeper.serde.ZkNodeDataSerializer; import io.dropwizard.Configuration; @@ -317,7 +318,8 @@ public void start() { serviceProvider.start(); serviceDiscoveryClient.start(); val nodeIdManager = new NodeIdManager(curator, serviceName); - IdGenerator.initialize(nodeIdManager.fixNodeId(), globalIdConstraints, Collections.emptyMap()); + NodeUtils.setNode(nodeIdManager.fixNodeId()); + IdGenerator.initialize(globalIdConstraints, Collections.emptyMap()); log.debug("Discovery manager has been successfully started."); } diff --git a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/Id.java b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/Id.java index 575b43bb..6d9d14e2 100644 --- a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/Id.java +++ b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/Id.java @@ -30,6 +30,8 @@ @ToString public class Id { private String id; + private String prefix; + private String suffix; private Date generatedDate; private int node; private int exponent; diff --git a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/IdGenerator.java b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/IdGenerator.java index 4c4c29e6..21321b61 100644 --- a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/IdGenerator.java +++ b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/IdGenerator.java @@ -24,6 +24,7 @@ import io.appform.ranger.discovery.bundle.id.generator.DefaultIdGenerator; import io.appform.ranger.discovery.bundle.id.generator.IdGeneratorBase; import io.appform.ranger.discovery.bundle.id.request.IdGenerationRequest; +import io.dropwizard.logback.shaded.guava.annotations.VisibleForTesting; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; @@ -37,6 +38,7 @@ public class IdGenerator { private static final IdGeneratorBase baseGenerator = new DefaultIdGenerator(); + @VisibleForTesting public static void initialize(int node) { baseGenerator.setNodeId(node); } @@ -46,9 +48,8 @@ public static synchronized void cleanUp() { } public static synchronized void initialize( - int node, List globalConstraints, + List globalConstraints, Map> domainSpecificConstraints) { - initialize(node); if(null != globalConstraints && !globalConstraints.isEmpty() ) { baseGenerator.registerGlobalConstraints(globalConstraints); } diff --git a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdFormatters.java b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdFormatters.java index b0ea164f..de0d65f4 100644 --- a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdFormatters.java +++ b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdFormatters.java @@ -22,6 +22,7 @@ public class IdFormatters { private static final IdFormatter originalIdFormatter = new DefaultIdFormatter(); private static final IdFormatter base36IdFormatter = new Base36IdFormatter(originalIdFormatter); + private static final IdFormatter suffixIdFormatter = new SuffixIdFormatter(); public static IdFormatter original() { return originalIdFormatter; @@ -31,4 +32,7 @@ public static IdFormatter base36() { return base36IdFormatter; } + public static IdFormatter suffix() { + return suffixIdFormatter; + } } diff --git a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdParserType.java b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdParserType.java index 89038f79..81339caa 100644 --- a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdParserType.java +++ b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdParserType.java @@ -4,7 +4,9 @@ @Getter public enum IdParserType { - DEFAULT (0); + DEFAULT (00), + SUFFIX (01), + BASE36_SUFFIX(02); private final int value; diff --git a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdParsers.java b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdParsers.java index ef77fcce..c5e9a32c 100644 --- a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdParsers.java +++ b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdParsers.java @@ -32,7 +32,8 @@ public class IdParsers { private static final Pattern PATTERN = Pattern.compile("([A-Za-z]*)([0-9]{22})([0-9]{2})?(.*)"); private final Map parserRegistry = Map.of( - IdFormatters.original().getType().getValue(), IdFormatters.original() + IdFormatters.original().getType().getValue(), IdFormatters.original(), + IdFormatters.suffix().getType().getValue(), IdFormatters.suffix() ); /** diff --git a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/SuffixIdFormatter.java b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/SuffixIdFormatter.java new file mode 100644 index 00000000..5b93b3ed --- /dev/null +++ b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/SuffixIdFormatter.java @@ -0,0 +1,58 @@ +/* + * Copyright 2024 Authors, Flipkart Internet Pvt. Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.appform.ranger.discovery.bundle.id.formatter; + +import io.appform.ranger.discovery.bundle.id.Id; +import lombok.val; +import org.joda.time.DateTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; + +import java.util.Optional; +import java.util.regex.Pattern; + +public class SuffixIdFormatter implements IdFormatter { + private static final Pattern PATTERN = Pattern.compile("([A-Za-z]*)([0-9]{15})([0-9]{4})([0-9]{3})([0-9]{2})([0-9]*)"); + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormat.forPattern("yyMMddHHmmssSSS"); + + @Override + public IdParserType getType() { + return IdParserType.SUFFIX; + } + + @Override + public String format(final DateTime dateTime, + final int nodeId, + final int randomNonce) { + return String.format("%s%04d%03d%02d", DATE_TIME_FORMATTER.print(dateTime), nodeId, randomNonce, getType().getValue()); + } + + @Override + public Optional parse(final String idString) { + val matcher = PATTERN.matcher(idString); + if (!matcher.find()) { + return Optional.empty(); + } + return Optional.of(Id.builder() + .id(idString) + .prefix(matcher.group(1)) + .suffix(matcher.group(6)) + .node(Integer.parseInt(matcher.group(3))) + .exponent(Integer.parseInt(matcher.group(4))) + .generatedDate(DATE_TIME_FORMATTER.parseDateTime(matcher.group(2)).toDate()) + .build()); + } +} \ No newline at end of file diff --git a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/generator/DefaultIdGenerator.java b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/generator/DefaultIdGenerator.java index c66b5929..56266c6b 100644 --- a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/generator/DefaultIdGenerator.java +++ b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/generator/DefaultIdGenerator.java @@ -1,5 +1,6 @@ package io.appform.ranger.discovery.bundle.id.generator; +import io.appform.ranger.discovery.bundle.id.formatter.IdFormatter; import io.appform.ranger.discovery.bundle.id.formatter.IdFormatters; import io.appform.ranger.discovery.bundle.id.nonce.RandomNonceGenerator; @@ -8,4 +9,8 @@ public class DefaultIdGenerator extends IdGeneratorBase { public DefaultIdGenerator() { super(IdFormatters.original(), new RandomNonceGenerator()); } + + public DefaultIdGenerator(final IdFormatter idFormatter) { + super(idFormatter, new RandomNonceGenerator()); + } } diff --git a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/generator/IdGeneratorBase.java b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/generator/IdGeneratorBase.java index a91d91ca..cfc481b3 100644 --- a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/generator/IdGeneratorBase.java +++ b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/generator/IdGeneratorBase.java @@ -11,10 +11,10 @@ import io.appform.ranger.discovery.bundle.id.IdValidationState; import io.appform.ranger.discovery.bundle.id.constraints.IdValidationConstraint; import io.appform.ranger.discovery.bundle.id.formatter.IdFormatter; -import io.appform.ranger.discovery.bundle.id.formatter.IdFormatters; import io.appform.ranger.discovery.bundle.id.nonce.NonceGenerator; import io.appform.ranger.discovery.bundle.id.request.IdGenerationInput; import io.appform.ranger.discovery.bundle.id.request.IdGenerationRequest; +import io.appform.ranger.discovery.bundle.util.NodeUtils; import lombok.Getter; import lombok.val; import org.joda.time.DateTime; @@ -53,6 +53,7 @@ protected IdGeneratorBase(final IdFormatter idFormatter, .handleResultIf(generationResult -> generationResult.getState() == IdValidationState.INVALID_RETRYABLE) .onRetry(nonceGenerator::retryEventListener) .build(); + this.nodeId = NodeUtils.getNode(); this.retryer = Failsafe.with(Collections.singletonList(retryPolicy)); } @@ -78,7 +79,7 @@ public final synchronized void registerDomainSpecificConstraints( registeredDomains.computeIfAbsent(domain, key -> Domain.builder() .domain(domain) .constraints(validationConstraints) - .idFormatter(IdFormatters.original()) + .idFormatter(idFormatter) .resolution(TimeUnit.MILLISECONDS) .build()); } @@ -94,10 +95,25 @@ public final Id getIdFromIdInfo(final NonceInfo nonceInfo, final String namespac .build(); } + public final Id getIdFromIdInfo(final NonceInfo nonceInfo, final String namespace, final String suffix, final IdFormatter idFormatter) { + val dateTime = new DateTime(nonceInfo.getTime()); + val id = String.format("%s%s%s", namespace, idFormatter.format(dateTime, getNodeId(), nonceInfo.getExponent()), suffix != null ? suffix : ""); + return Id.builder() + .id(id) + .exponent(nonceInfo.getExponent()) + .generatedDate(dateTime.toDate()) + .node(getNodeId()) + .build(); + } + public final Id getIdFromIdInfo(final NonceInfo nonceInfo, final String namespace) { return getIdFromIdInfo(nonceInfo, namespace, idFormatter); } + public final Id getIdFromIdInfo(final NonceInfo nonceInfo, final String namespace, final String suffix) { + return getIdFromIdInfo(nonceInfo, namespace, suffix, idFormatter); + } + public final IdValidationState validateId(final List inConstraints, final Id id, final boolean skipGlobal) { // First evaluate global constraints val failedGlobalConstraint @@ -139,6 +155,10 @@ public final Id generate(final String namespace) { return getIdFromIdInfo(idInfo, namespace); } + public final Id generate(final String namespace, final String suffix) { + val idInfo = nonceGenerator.generate(namespace); + return getIdFromIdInfo(idInfo, namespace, suffix); + } public final Id generate(final String namespace, final IdFormatter idFormatter) { val idInfo = nonceGenerator.generate(namespace); @@ -167,23 +187,37 @@ public final Optional generateWithConstraints(final String namespace, final return generateWithConstraints(request); } + public Optional generateWithConstraints( + String namespace, + String suffix, + final List inConstraints) { + return generateWithConstraints(IdGenerationRequest.builder() + .prefix(namespace) + .suffix(suffix) + .constraints(inConstraints) + .skipGlobal(false) + .idFormatter(idFormatter) + .build()); + } + public final Optional generateWithConstraints(final IdGenerationRequest request) { val domain = request.getDomain() != null ? registeredDomains.getOrDefault(request.getDomain(), Domain.DEFAULT) : Domain.DEFAULT; val idGenerationInput = IdGenerationInput.builder() .prefix(request.getPrefix()) + .suffix(request.getSuffix()) .domain(domain) .build(); return Optional.ofNullable(retryer.get( () -> { val idInfoOptional = nonceGenerator.generateWithConstraints(idGenerationInput); - val id = getIdFromIdInfo(idInfoOptional, request.getPrefix(), request.getIdFormatter()); + val id = getIdFromIdInfo(idInfoOptional, request.getPrefix(), request.getSuffix(), request.getIdFormatter()); return new GenerationResult( idInfoOptional, validateId(request.getConstraints(), id, request.isSkipGlobal()), domain); })) .filter(generationResult -> generationResult.getState() == IdValidationState.VALID) - .map(generationResult -> this.getIdFromIdInfo(generationResult.getNonceInfo(), request.getPrefix(), request.getIdFormatter())); + .map(generationResult -> this.getIdFromIdInfo(generationResult.getNonceInfo(), request.getPrefix(), request.getSuffix(), request.getIdFormatter())); } public final void setNodeId(int nodeId) { diff --git a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/request/IdGenerationInput.java b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/request/IdGenerationInput.java index 4a251748..d3e10585 100644 --- a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/request/IdGenerationInput.java +++ b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/request/IdGenerationInput.java @@ -8,6 +8,6 @@ @Builder public class IdGenerationInput { String prefix; + String suffix; Domain domain; - } diff --git a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/request/IdGenerationRequest.java b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/request/IdGenerationRequest.java index a4affc21..8ad36159 100644 --- a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/request/IdGenerationRequest.java +++ b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/request/IdGenerationRequest.java @@ -27,6 +27,7 @@ public class IdGenerationRequest { String prefix; + String suffix; String domain; boolean skipGlobal; List constraints; diff --git a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/util/NodeUtils.java b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/util/NodeUtils.java new file mode 100644 index 00000000..f99b6e1c --- /dev/null +++ b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/util/NodeUtils.java @@ -0,0 +1,13 @@ +package io.appform.ranger.discovery.bundle.util; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.UtilityClass; + +@UtilityClass +public class NodeUtils { + + @Getter + @Setter + private int node; +} diff --git a/ranger-discovery-bundle/src/test/java/io/appform/ranger/discovery/bundle/id/IdParsersTest.java b/ranger-discovery-bundle/src/test/java/io/appform/ranger/discovery/bundle/id/IdParsersTest.java index 0d1d0e06..537bbc28 100644 --- a/ranger-discovery-bundle/src/test/java/io/appform/ranger/discovery/bundle/id/IdParsersTest.java +++ b/ranger-discovery-bundle/src/test/java/io/appform/ranger/discovery/bundle/id/IdParsersTest.java @@ -1,13 +1,17 @@ package io.appform.ranger.discovery.bundle.id; +import io.appform.ranger.discovery.bundle.id.formatter.IdFormatters; import io.appform.ranger.discovery.bundle.id.formatter.IdParsers; +import io.appform.ranger.discovery.bundle.id.generator.DefaultIdGenerator; import lombok.val; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.Collections; import java.util.Date; +import java.util.Optional; public class IdParsersTest { @@ -22,6 +26,45 @@ void testDefaultId() throws ParseException { assertDate("240710123233616", parsedId.getGeneratedDate()); } + @Test + void testParseSuccessAfterGenerationWithSuffix() { + val idGenerator = new DefaultIdGenerator(IdFormatters.suffix()); + val prefix = "TEST"; + val suffix = "007"; + val generatedId = idGenerator.generate(prefix, suffix); + val parsedId = IdGenerator.parse(generatedId.getId()).orElse(null); + Assertions.assertNotNull(parsedId); + Assertions.assertEquals(prefix, parsedId.getPrefix()); + Assertions.assertEquals(suffix, parsedId.getSuffix()); + Assertions.assertEquals(parsedId.getId(), generatedId.getId()); + Assertions.assertEquals(parsedId.getExponent(), generatedId.getExponent()); + Assertions.assertEquals(parsedId.getNode(), generatedId.getNode()); + Assertions.assertEquals(parsedId.getGeneratedDate(), generatedId.getGeneratedDate()); + } + + @Test + void testParseSuccessAfterGenerationWithConstraintsSuffix() { + val idGenerator = new DefaultIdGenerator(IdFormatters.suffix()); + val prefix = "TEST"; + val suffix = "007"; + val domain = "TEST"; + + idGenerator.registerDomainSpecificConstraints(domain, Collections.singletonList(id -> true)); + Optional id = idGenerator.generateWithConstraints(prefix, suffix, Collections.emptyList()); + + Assertions.assertTrue(id.isPresent()); + Assertions.assertEquals(31, id.get().getId().length()); + + val parsedId = IdGenerator.parse(id.get().getId()).orElse(null); + Assertions.assertNotNull(parsedId); + Assertions.assertEquals(prefix, parsedId.getPrefix()); + Assertions.assertEquals(suffix, parsedId.getSuffix()); + Assertions.assertEquals(parsedId.getId(), id.get().getId()); + Assertions.assertEquals(parsedId.getExponent(), id.get().getExponent()); + Assertions.assertEquals(parsedId.getNode(), id.get().getNode()); + Assertions.assertEquals(parsedId.getGeneratedDate(), id.get().getGeneratedDate()); + } + private void assertDate(final String dateString, final Date date) throws ParseException { Assertions.assertEquals(new SimpleDateFormat("yyMMddHHmmssSSS").parse(dateString), date); }