From 9a2d28e924bc6cc4e3e658134a01cbeb5564bb10 Mon Sep 17 00:00:00 2001 From: Benjamin Graf Date: Tue, 9 Jul 2024 20:29:54 +0200 Subject: [PATCH] Shorten node identifier if necessary --- README.md | 4 ++- .../core/properties/NarayanaProperties.java | 33 ++++++++++++++++--- .../NarayanaPropertiesInitializer.java | 25 +++++++++++--- .../NarayanaPropertiesInitializerTests.java | 16 ++++++++- 4 files changed, 67 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 067a3d1d..8a4f2ffc 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,9 @@ Narayana project [documentation](http://narayana.io/docs/project/index.html). > To ensure that multiple transaction managers can safely coordinate the same resource managers, each Narayana instance must be configured with a unique ID. By default, this ID is set to 1. To ensure uniqueness in production, you should -configure the `narayana.transaction-manager-id` property with a different value for each instance of your application. +configure the `narayana.node-identifier` property with a different value for each instance of your application. This value +must not exceed a length of 28 bytes. To ensure that the value is shorten to a valid length by hashing with SHA-224, +configure `narayana.shorten-node-identifier-if-necessary` property to true. # Using databases diff --git a/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/properties/NarayanaProperties.java b/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/properties/NarayanaProperties.java index 40f8f72c..0048a539 100644 --- a/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/properties/NarayanaProperties.java +++ b/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/properties/NarayanaProperties.java @@ -41,9 +41,14 @@ public class NarayanaProperties { private String logDir; /** - * Unique transaction manager id. + * Unique node identifier. */ - private String transactionManagerId = "1"; + private String nodeIdentifier = "1"; + + /** + * Shorten node identifier if exceed a length of 28 bytes. + */ + private boolean shortenNodeIdentifierIfNecessary = false; /** * Enable one phase commit optimization. @@ -138,12 +143,30 @@ public void setLogDir(String logDir) { this.logDir = logDir; } + @Deprecated(forRemoval = true) public String getTransactionManagerId() { - return this.transactionManagerId; + return getNodeIdentifier(); + } + + @Deprecated(forRemoval = true) + public void setTransactionManagerId(String nodeIdentifier) { + setNodeIdentifier(nodeIdentifier); + } + + public String getNodeIdentifier() { + return this.nodeIdentifier; + } + + public void setNodeIdentifier(String nodeIdentifier) { + this.nodeIdentifier = nodeIdentifier; + } + + public boolean isShortenNodeIdentifierIfNecessary() { + return this.shortenNodeIdentifierIfNecessary; } - public void setTransactionManagerId(String transactionManagerId) { - this.transactionManagerId = transactionManagerId; + public void setShortenNodeIdentifierIfNecessary(boolean shortenNodeIdentifierIfNecessary) { + this.shortenNodeIdentifierIfNecessary = shortenNodeIdentifierIfNecessary; } public boolean isOnePhaseCommit() { diff --git a/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/properties/NarayanaPropertiesInitializer.java b/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/properties/NarayanaPropertiesInitializer.java index 9b535812..6b415fea 100644 --- a/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/properties/NarayanaPropertiesInitializer.java +++ b/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/properties/NarayanaPropertiesInitializer.java @@ -16,6 +16,9 @@ package dev.snowdrop.boot.narayana.core.properties; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.List; import com.arjuna.ats.arjuna.common.CoordinatorEnvironmentBean; @@ -34,6 +37,8 @@ */ public class NarayanaPropertiesInitializer implements InitializingBean { + private static final String HASH_ALGORITHM_FOR_SHORTENING = "SHA-224"; + private final NarayanaProperties properties; public NarayanaPropertiesInitializer(NarayanaProperties narayanaProperties) { @@ -42,7 +47,7 @@ public NarayanaPropertiesInitializer(NarayanaProperties narayanaProperties) { @Override public void afterPropertiesSet() { - setNodeIdentifier(this.properties.getTransactionManagerId()); + setNodeIdentifier(this.properties.getNodeIdentifier(), this.properties.isShortenNodeIdentifierIfNecessary()); setXARecoveryNodes(this.properties.getXaRecoveryNodes()); setObjectStoreDir(this.properties.getLogDir()); setCommitOnePhase(this.properties.isOnePhaseCommit()); @@ -58,14 +63,26 @@ public void afterPropertiesSet() { setExpiryScanners(this.properties.getExpiryScanners()); } - private void setNodeIdentifier(String nodeIdentifier) { + private void setNodeIdentifier(String nodeIdentifier, boolean shortenNodeIdentifierIfNecessary) { try { - getPopulator(CoreEnvironmentBean.class).setNodeIdentifier(nodeIdentifier); - } catch (CoreEnvironmentBeanException e) { + if (nodeIdentifier != null + && nodeIdentifier.getBytes(StandardCharsets.UTF_8).length > 28 + && shortenNodeIdentifierIfNecessary) { + getPopulator(CoreEnvironmentBean.class).setNodeIdentifier(shortenNodeIdentifier(nodeIdentifier)); + } else { + getPopulator(CoreEnvironmentBean.class).setNodeIdentifier(nodeIdentifier); + } + } catch (CoreEnvironmentBeanException | NoSuchAlgorithmException e) { throw new IllegalArgumentException(e); } } + private byte[] shortenNodeIdentifier(String nodeIdentifier) throws NoSuchAlgorithmException { + byte[] nodeIdentifierAsBytes = nodeIdentifier.getBytes(StandardCharsets.UTF_8); + MessageDigest messageDigest224 = MessageDigest.getInstance(HASH_ALGORITHM_FOR_SHORTENING); + return messageDigest224.digest(nodeIdentifierAsBytes); + } + private void setXARecoveryNodes(List xaRecoveryNodes) { if (xaRecoveryNodes.isEmpty()) { xaRecoveryNodes = List.of(getPopulator(CoreEnvironmentBean.class).getNodeIdentifier()); diff --git a/narayana-spring-boot-core/src/test/java/dev/snowdrop/boot/narayana/core/properties/NarayanaPropertiesInitializerTests.java b/narayana-spring-boot-core/src/test/java/dev/snowdrop/boot/narayana/core/properties/NarayanaPropertiesInitializerTests.java index c32fb8ea..0fdaab68 100644 --- a/narayana-spring-boot-core/src/test/java/dev/snowdrop/boot/narayana/core/properties/NarayanaPropertiesInitializerTests.java +++ b/narayana-spring-boot-core/src/test/java/dev/snowdrop/boot/narayana/core/properties/NarayanaPropertiesInitializerTests.java @@ -116,7 +116,7 @@ void shouldSetDefaultProperties() { @Test void shouldSetModifiedProperties() { NarayanaProperties narayanaProperties = new NarayanaProperties(); - narayanaProperties.setTransactionManagerId("test-id-1"); + narayanaProperties.setNodeIdentifier("test-id-1"); narayanaProperties.setXaRecoveryNodes(List.of("test-id-1", "test-id-2")); narayanaProperties.setLogDir("test-dir"); narayanaProperties.setDefaultTimeout(1); @@ -161,4 +161,18 @@ void shouldSetModifiedProperties() { .getExpiryScannerClassNames()) .isEqualTo(List.of("test-scanner-1", "test-scanner-2")); } + + @Test + void shouldSetShortenNodeIdentifier() { + NarayanaProperties narayanaProperties = new NarayanaProperties(); + narayanaProperties.setNodeIdentifier("x".repeat(30)); + + narayanaProperties.setShortenNodeIdentifierIfNecessary(true); + NarayanaPropertiesInitializer narayanaPropertiesInitializer = + new NarayanaPropertiesInitializer(narayanaProperties); + narayanaPropertiesInitializer.afterPropertiesSet(); + + assertThat(BeanPopulator.getDefaultInstance(CoreEnvironmentBean.class) + .getNodeIdentifierBytes().length).isEqualTo(28); + } }