From 6a3608277da1beb9b6273ed5bff74371902b8dc3 Mon Sep 17 00:00:00 2001 From: AntonRoskvist Date: Fri, 31 May 2024 15:22:27 +0200 Subject: [PATCH] ARTEMIS-4545 Allow node ID to be configured --- .../artemis/core/config/Configuration.java | 10 +++++ .../core/config/impl/ConfigurationImpl.java | 35 ++++++++++++++++ .../impl/FileConfigurationParser.java | 2 + .../artemis/core/server/NodeManager.java | 15 +++++-- .../core/server/impl/ActiveMQServerImpl.java | 5 +++ .../server/impl/FileBasedNodeManager.java | 4 +- .../impl/ReplicationBackupActivation.java | 2 +- .../schema/artemis-configuration.xsd | 9 +++++ .../ConfigurationTest-full-config.xml | 1 + .../ConfigurationTest-xinclude-config.xml | 1 + docs/user-manual/_book.adoc | 1 + docs/user-manual/node-id.adoc | 40 +++++++++++++++++++ .../artemis/tests/util/ActiveMQTestBase.java | 1 + .../SharedNothingReplicationTest.java | 2 + 14 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 docs/user-manual/node-id.adoc diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java index 1df52bd0bc1f..7ed51e3b48d3 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java @@ -70,6 +70,16 @@ public interface Configuration { */ Configuration setName(String name); + /** + * Returns the ID of the node. + */ + String getNodeID(); + + /** + * Sets the ID of the node. If not set, a UUID generated on first startup will be used instead. + */ + Configuration setNodeID(String nodeID); + /** * We use Bean-utils to pass in System.properties that start with {@link #setSystemPropertyPrefix(String)}. * The default should be 'brokerconfig.' (Including the "."). diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java index 0b78fab0c436..fe2d676e83ae 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java @@ -153,6 +153,8 @@ public class ConfigurationImpl implements Configuration, Serializable { private String name = "localhost"; + private String nodeID = null; + private boolean persistenceEnabled = ActiveMQDefaultConfiguration.isDefaultPersistenceEnabled(); private int maxRedeliveryRecords = ActiveMQDefaultConfiguration.getDefaultMaxRedeliveryRecords(); @@ -2559,6 +2561,17 @@ public ConfigurationImpl setName(String name) { return this; } + @Override + public String getNodeID() { + return nodeID; + } + + @Override + public ConfigurationImpl setNodeID(String nodeID) { + this.nodeID = toCompatibleNodeID(nodeID); + return this; + } + @Override public ConfigurationImpl setResolveProtocols(boolean resolveProtocols) { this.resolveProtocols = resolveProtocols; @@ -3665,6 +3678,28 @@ public String getProperty(final String expression) { } } + private String toCompatibleNodeID(String nodeID) { + if (nodeID == null) { + return null; + } + + final int len = nodeID.length(); + + if (!(len > 0)) { + return null; + } + + if (len >= 16) { + nodeID = nodeID.substring(0, 16); + } else if (len % 2 != 0) { + // must be even for conversion to uuid, extend to next even + nodeID = nodeID + "+"; + } + + return nodeID.replace('-', '.'); + + } + public static class InsertionOrderedProperties extends Properties { final LinkedHashMap orderedMap = new LinkedHashMap<>(); diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java index 9f6ab681e6a0..0c25e5ce661b 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java @@ -424,6 +424,8 @@ public void parseMainConfig(final Element e, final Configuration config) throws config.setName(getString(e, "name", config.getName(), NO_CHECK)); + config.setNodeID(getString(e, "node-id", config.getNodeID(), NO_CHECK)); + config.setSystemPropertyPrefix(getString(e, "system-property-prefix", config.getSystemPropertyPrefix(), NOT_NULL_OR_EMPTY)); NodeList haPolicyNodes = e.getElementsByTagName("ha-policy"); diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/NodeManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/NodeManager.java index 28baf1edf8fd..7a0b1d2613ab 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/NodeManager.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/NodeManager.java @@ -83,6 +83,15 @@ public SimpleString getNodeId() { } } + /** + * Returns the converted form of a given nodeID + * + * @param nodeID + */ + public String getConvertedNodeId(String nodeID) { + return new UUID(UUID.TYPE_TIME_BASED, UUID.stringToBytes(nodeID)).toString(); + } + public long readNodeActivationSequence() throws NodeManagerException { // TODO make it abstract throw new UnsupportedOperationException("TODO"); @@ -117,16 +126,14 @@ public UUID getUUID() { } /** - * Sets the nodeID. - *

- * Only used by replicating backups. + * Sets the nodeID * * @param nodeID */ public void setNodeID(String nodeID) { synchronized (nodeIDGuard) { - this.nodeID = new SimpleString(nodeID); this.uuid = new UUID(UUID.TYPE_TIME_BASED, UUID.stringToBytes(nodeID)); + this.nodeID = new SimpleString(uuid.toString()); } } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java index 6877d7bc3311..dc3b5b6ab3fa 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java @@ -617,6 +617,11 @@ protected NodeManager createNodeManager(final File directory, boolean replicatin } else { manager = new FileLockNodeManager(directory, replicatingBackup, configuration.getJournalLockAcquisitionTimeout(), scheduledPool); } + + if (!replicatingBackup && configuration.getNodeID() != null) { + manager.setNodeID(configuration.getNodeID()); + } + return manager; } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/FileBasedNodeManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/FileBasedNodeManager.java index 99adbdfc640a..fc9b36a2256c 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/FileBasedNodeManager.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/FileBasedNodeManager.java @@ -208,7 +208,9 @@ protected final synchronized void createNodeId() throws IOException { channel.write(id, 3); channel.force(true); } else if (read != 16) { - setUUID(UUIDGenerator.getInstance().generateUUID()); + if (getUUID() == null) { + setUUID(UUIDGenerator.getInstance().generateUUID()); + } id.put(getUUID().asBytes(), 0, 16); id.position(0); channel.write(id, 3); diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ReplicationBackupActivation.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ReplicationBackupActivation.java index 44a93837d3a4..b4c3c8c42a1a 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ReplicationBackupActivation.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ReplicationBackupActivation.java @@ -80,7 +80,7 @@ public ReplicationBackupActivation(final ActiveMQServerImpl activeMQServer, // patch expectedNodeID final String coordinationId = policy.getPrimaryPolicy().getCoordinationId(); if (coordinationId != null) { - expectedNodeID = coordinationId; + expectedNodeID = activeMQServer.getNodeManager().getConvertedNodeId(coordinationId); } else { final SimpleString serverNodeID = activeMQServer.getNodeID(); if (serverNodeID == null || serverNodeID.isEmpty()) { diff --git a/artemis-server/src/main/resources/schema/artemis-configuration.xsd b/artemis-server/src/main/resources/schema/artemis-configuration.xsd index fc4e50e57863..511a893cc488 100644 --- a/artemis-server/src/main/resources/schema/artemis-configuration.xsd +++ b/artemis-server/src/main/resources/schema/artemis-configuration.xsd @@ -36,6 +36,15 @@ + + + + Node ID. If set, it will be used as an identifier when running in a cluster. + Has to be unique within the cluster + + + + diff --git a/artemis-server/src/test/resources/ConfigurationTest-full-config.xml b/artemis-server/src/test/resources/ConfigurationTest-full-config.xml index 456a7bf272fc..217997e83c1f 100644 --- a/artemis-server/src/test/resources/ConfigurationTest-full-config.xml +++ b/artemis-server/src/test/resources/ConfigurationTest-full-config.xml @@ -20,6 +20,7 @@ xsi:schemaLocation="urn:activemq ../../../../activemq-server/src/main/resources/schema/artemis-server.xsd"> SomeNameForUseOnTheApplicationServer + AUniqueIDForThisBroker false false 12345 diff --git a/artemis-server/src/test/resources/ConfigurationTest-xinclude-config.xml b/artemis-server/src/test/resources/ConfigurationTest-xinclude-config.xml index 436d2fb8191f..48b7efd0f2c3 100644 --- a/artemis-server/src/test/resources/ConfigurationTest-xinclude-config.xml +++ b/artemis-server/src/test/resources/ConfigurationTest-xinclude-config.xml @@ -21,6 +21,7 @@ xsi:schemaLocation="urn:activemq ../../../../activemq-server/src/main/resources/schema/artemis-server.xsd"> SomeNameForUseOnTheApplicationServer + AUniqueIDForThisBroker false false 12345 diff --git a/docs/user-manual/_book.adoc b/docs/user-manual/_book.adoc index b3a383cbf0d3..a1c67ae7665e 100644 --- a/docs/user-manual/_book.adoc +++ b/docs/user-manual/_book.adoc @@ -57,6 +57,7 @@ include::metrics.adoc[leveloffset=1] //== Broker-to-Broker Connectivity +include::node-id.adoc[leveloffset=1] include::core-bridges.adoc[leveloffset=1] include::clusters.adoc[leveloffset=1] include::federation.adoc[leveloffset=1] diff --git a/docs/user-manual/node-id.adoc b/docs/user-manual/node-id.adoc new file mode 100644 index 000000000000..48af44e45361 --- /dev/null +++ b/docs/user-manual/node-id.adoc @@ -0,0 +1,40 @@ += Node ID +:idprefix: +:idseparator: - + +When connecting multiple Artemis brokers together they can cooperate to solve a common task +like xref:clusters.adoc[clustering] or xref:ha.adoc[high availability] for example. + +Implementation of coordination and control over which broker does what will vary between the different "Broker-to-broker Connectivity" +variants, but one important aspect is the use of a unique identifier called `node-id`. + +By default, the brokers `node-id` will get generated the first time it starts up. The value is then +persisted into the `journal`, on a file called `server.lock` + +This id _must_ be unique among all brokers to ensure proper functionality. + + +== Manually setting the brokers ID + +In certain cases, being able to manually set a `node-id` rather than generating one can be preferable. Brokers running in +an environment where their storage is ephemeral would be one such case. This is because any time a broker is restarted, moved +or upgraded it could end up with a new disk and therefore also a new journal. Starting this broker up, while being configured +identiaclly, would still give it a new `node-id`. In some configurations, this means the broker won't be able to resume +whatever role it orinially had because the other broker it was connected to prior will look for the original `node-id`. + +To handle these scenarios `node-id` can be set in the `broker.xml` configuration file. +[,xml] +---- +myUniqueID +---- + +The selected nodeID will get converted internally into a 16-byte UUID. Therefore it might not be recognizeable to +someone looking for it in logs or the console. + +The `node-id` has to be set before first starting the broker as this value will get persisted in the brokers `journal`. +Once persisted, this is the value that the broker will use regardless of configuration. + +[WARNING] +==== +If you choose to set `node-id` manually, it's uniqueness among other brokers are _UTMOST_ important. +==== \ No newline at end of file diff --git a/tests/artemis-test-support/src/main/java/org/apache/activemq/artemis/tests/util/ActiveMQTestBase.java b/tests/artemis-test-support/src/main/java/org/apache/activemq/artemis/tests/util/ActiveMQTestBase.java index 05659bd21e48..7df4f215ee0d 100644 --- a/tests/artemis-test-support/src/main/java/org/apache/activemq/artemis/tests/util/ActiveMQTestBase.java +++ b/tests/artemis-test-support/src/main/java/org/apache/activemq/artemis/tests/util/ActiveMQTestBase.java @@ -455,6 +455,7 @@ protected ConfigurationImpl createBasicConfig() throws Exception { protected ConfigurationImpl createBasicConfig(final int serverID) { ConfigurationImpl configuration = new ConfigurationImpl().setSecurityEnabled(false).setJournalMinFiles(2).setJournalFileSize(100 * 1024).setJournalType(getDefaultJournalType()).setJournalDirectory(getJournalDir(serverID, false)).setBindingsDirectory(getBindingsDir(serverID, false)).setPagingDirectory(getPageDir(serverID, false)).setLargeMessagesDirectory(getLargeMessagesDir(serverID, false)).setJournalCompactMinFiles(0).setJournalCompactPercentage(0).setClusterPassword(CLUSTER_PASSWORD).setJournalDatasync(false); + configuration.setNodeID("node-" + serverID); // When it comes to the testsuite, we don't need any batching, I will leave some minimal batching to exercise the codebase configuration.setJournalBufferTimeout_AIO(100).setJournalBufferTimeout_NIO(100); diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/replication/SharedNothingReplicationTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/replication/SharedNothingReplicationTest.java index a21843de0995..e4a8e06583f8 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/replication/SharedNothingReplicationTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/replication/SharedNothingReplicationTest.java @@ -269,6 +269,7 @@ protected Configuration createPrimaryConfiguration() throws Exception { conf.addClusterConfiguration(ccconf); conf.setSecurityEnabled(false).setJMXManagementEnabled(false).setJournalType(JournalType.MAPPED).setJournalFileSize(1024 * 512).setConnectionTTLOverride(60_000L); + conf.setNodeID("localhost::primary"); return conf; } @@ -300,6 +301,7 @@ protected Configuration createBackupConfiguration() throws Exception { conf.addClusterConfiguration(ccconf); conf.setSecurityEnabled(false).setJMXManagementEnabled(false).setJournalType(JournalType.MAPPED).setJournalFileSize(1024 * 512).setConnectionTTLOverride(60_000L); + conf.setNodeID("localhost::backup"); return conf; }