Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure the Transaction Manager node name is less than or equal to 28 bytes #40613

Merged
merged 1 commit into from
Oct 22, 2024
Merged
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
7 changes: 7 additions & 0 deletions docs/src/main/asciidoc/transaction.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,13 @@ The node name identifier needs to be unique per transaction manager deployment.
And the node identifier needs to be stable over the transaction manager restarts.

The node name identifier may be configured via the property `quarkus.transaction-manager.node-name`.
[NOTE]
====
The node name cannot be longer than 28 bytes.
To automatically shorten names longer than 28 bytes, set `quarkus.transaction-manager.shorten-node-name-if-necessary` to `true`.

Shortening is implemented by hashing the node name, encoding the hash to Base64 and then truncating the result. As with all hashes, the resulting shortened node name could potentially conflict with another shortened node name, but it is https://github.com/quarkusio/quarkus/issues/30491#issuecomment-1537247764[very unlikely].
====

[[transaction-scope]]
== Using `@TransactionScoped` to bind CDI beans to the transaction lifecycle
Expand Down
11 changes: 11 additions & 0 deletions extensions/narayana-jta/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,17 @@
<groupId>org.jboss.narayana.jts</groupId>
<artifactId>narayana-jts-integration</artifactId>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -60,7 +61,15 @@ private static void shortenNodeName(TransactionManagerConfiguration transactions
HASH_ALGORITHM_FOR_SHORTENING);
final byte[] nodeNameAsBytes = originalNodeName.getBytes();
MessageDigest messageDigest224 = MessageDigest.getInstance(HASH_ALGORITHM_FOR_SHORTENING);
transactions.nodeName = new String(messageDigest224.digest(nodeNameAsBytes), StandardCharsets.UTF_8);
yrodiere marked this conversation as resolved.
Show resolved Hide resolved
byte[] hashedByteArray = messageDigest224.digest(nodeNameAsBytes);

//Encode the byte array in Base64
//encoding the array might result in a longer array
byte[] base64Result = Base64.getEncoder().encode(hashedByteArray);
//truncate the array
byte[] slice = Arrays.copyOfRange(base64Result, 0, 28);

transactions.nodeName = new String(slice, StandardCharsets.UTF_8);
log.warnf("New node name is \"%s\"", transactions.nodeName);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public final class TransactionManagerConfiguration {
* Whether the node name should be shortened if necessary.
* The node name must not exceed a length of 28 bytes. If this property is set to {@code true}, and the node name exceeds 28
* bytes, the node name is shortened by calculating the <a href="https://en.wikipedia.org/wiki/SHA-2">SHA-224</a> hash,
* which has a length of 28 bytes.
* which has a length of 28 bytes, encoded to Base64 format and then shortened to 28 bytes.
*
* @see #nodeName
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package io.quarkus.narayana.jta.runtime;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.nio.charset.StandardCharsets;

import org.junit.jupiter.api.Test;

public class NarayanaJtaRecorderTest {

//this string has been chosen as when hashed and Base64 encoded the resulted byte array will have a length > 28, so it will be trimmed too.
public static final String NODE_NAME_TO_SHORTEN = "dfe2420d-b12e-4ec3-92c0-ee7c4";

@Test
void testByteLengthWithLongerString() {
TransactionManagerConfiguration transactions = new TransactionManagerConfiguration();
transactions.shortenNodeNameIfNecessary = true;
// create nodeNames larger than 28 bytes
assertTrue(NODE_NAME_TO_SHORTEN.getBytes(StandardCharsets.UTF_8).length > 28);
NarayanaJtaRecorder r = new NarayanaJtaRecorder();
transactions.nodeName = NODE_NAME_TO_SHORTEN;
r.setNodeName(transactions);
int numberOfBytes = transactions.nodeName.getBytes(StandardCharsets.UTF_8).length;
assertEquals(28, numberOfBytes,
"node name bytes was not 28 bytes limit, number of bytes is " + numberOfBytes);
}

@Test
void testPredictableConversion() {
TransactionManagerConfiguration transactions = new TransactionManagerConfiguration();
transactions.shortenNodeNameIfNecessary = true;
assertTrue(NODE_NAME_TO_SHORTEN.getBytes(StandardCharsets.UTF_8).length > 28);
NarayanaJtaRecorder r = new NarayanaJtaRecorder();
transactions.nodeName = NODE_NAME_TO_SHORTEN;
r.setNodeName(transactions);
int numberOfBytes = transactions.nodeName.getBytes(StandardCharsets.UTF_8).length;
assertEquals(28, numberOfBytes,
"node name bytes was not 28 bytes limit, number of bytes is " + numberOfBytes);
String firstConversion = transactions.nodeName;
transactions.nodeName = NODE_NAME_TO_SHORTEN;
r.setNodeName(transactions);
String secondConversion = transactions.nodeName;
numberOfBytes = transactions.nodeName.getBytes(StandardCharsets.UTF_8).length;
assertEquals(28, numberOfBytes,
"node name bytes was not 28 bytes limit, number of bytes is " + numberOfBytes);
assertEquals(firstConversion, secondConversion,
"Node names were shortened differently: " + firstConversion + " " + secondConversion);
}
}
Loading