Skip to content
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Additionally, every broker updates its own file on a regular basis to prevent th
the [HiveMQ Website](https://www.hivemq.com/releases/extensions/hivemq-azure-cluster-discovery-extension-1.1.0.zip) or
from the [GitHub Releases Page](https://github.com/hivemq/hivemq-azure-cluster-discovery-extension/releases/latest).
* Copy the content of the zip file to the `extensions` folder of your HiveMQ nodes.
* Modify the `azDiscovery.properties` file for your needs.
* Modify the `conf/config.properties` file for your needs.
* Change the [Discovery Mechanism](https://www.hivemq.com/docs/latest/hivemq/cluster.html#discovery) of HiveMQ
to `extension`.

Expand All @@ -34,7 +34,7 @@ The ip-address and port are taken from the `external-address` and `external-port
cluster `transport` (config.xml).
If they are not set, the `bind-address` and `bind-port` will be used.

The `azDiscovery.properties` can be reloaded during runtime.
The `conf/config.properties` can be reloaded during runtime.

### General Configuration

Expand All @@ -60,7 +60,7 @@ update-interval=60

* Create an Azure Storage Account.
* Get your Connection String for the Storage Account.
* Place the Connection String into the `azDiscovery.properties` file of your HiveMQ nodes.
* Place the Connection String into the `conf/config.properties` file of your HiveMQ nodes.
* Start your HiveMQ nodes and verify the discovery.

## Need Help?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,21 @@ EXTENSION_VERSION=$4

HIVEMQ_DOWNLOAD_LINK="https://www.hivemq.com/releases/hivemq-${HIVEMQ_VERSION}.zip"
EXTENSION_DOWNLOAD_LINK="https://github.com/hivemq/hivemq-azure-cluster-discovery-extension/releases/download/$EXTENSION_VERSION/hivemq-azure-cluster-discovery-extension-$EXTENSION_VERSION.zip"
EXTENSION_PROPERTIES_PATH="/opt/hivemq/extensions/hivemq-azure-cluster-discovery-extension/azDiscovery.properties"

# Determine config path based on extension version
# Version 1.2.8+ uses new path: conf/config.properties
# Version 1.2.7 and earlier uses legacy path: azDiscovery.properties
version_gte() {
[ "$(printf '%s\n' "$1" "$2" | sort -V | tail -n1)" = "$1" ]
}

if version_gte "$EXTENSION_VERSION" "1.2.8"; then
# New path for versions >= 1.2.8
EXTENSION_PROPERTIES_PATH="/opt/hivemq/extensions/hivemq-azure-cluster-discovery-extension/conf/config.properties"
else
# Legacy path for versions < 1.2.8
EXTENSION_PROPERTIES_PATH="/opt/hivemq/extensions/hivemq-azure-cluster-discovery-extension/azDiscovery.properties"
fi

sudo apt-get update -y
sudo apt-get install -y openjdk-21-jdk
Expand Down
26 changes: 26 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent

plugins {
alias(libs.plugins.hivemq.extension)
alias(libs.plugins.defaults)
Expand Down Expand Up @@ -66,6 +69,10 @@ oci {
permissions("opt/hivemq/", 0b111_111_101)
permissions("opt/hivemq/extensions/", 0b111_111_101)
into("opt/hivemq/extensions") {
permissions("*/", 0b111_111_101)
permissions("*/hivemq-extension.xml", 0b110_110_100)
permissions("*/conf/", 0b111_111_101)
permissions("*/conf/config.properties", 0b110_110_100)
from(zipTree(tasks.hivemqExtensionZip.flatMap { it.archiveFile }))
}
}
Expand All @@ -85,6 +92,25 @@ testing {
compileOnly(libs.jetbrains.annotations)
implementation(libs.assertj)
implementation(libs.mockito)
implementation(libs.logback.classic)
}
targets.configureEach {
testTask {
testLogging {
events = setOf(
TestLogEvent.STARTED,
TestLogEvent.PASSED,
TestLogEvent.SKIPPED,
TestLogEvent.FAILED,
TestLogEvent.STANDARD_ERROR,
)
exceptionFormat = TestExceptionFormat.FULL
showStandardStreams = true
}
reports {
junitXml.isOutputPerTestCase = true
}
}
}
}
"integrationTest"(JvmTestSuite::class) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ void wrongConnectionString_reloadRightConnectionString_clusterCreated() throws E
normalNode.start();

reloadingNode.copyFileToContainer(Transferable.of(createConfig(createDockerAzuriteConnectionString()).getBytes()),
"/opt/hivemq/extensions/hivemq-azure-cluster-discovery-extension/azDiscovery.properties");
"/opt/hivemq/extensions/hivemq-azure-cluster-discovery-extension/conf/config.properties");

consumer.waitUntil(frame -> frame.getUtf8String().contains("Cluster size = 2"), 90, SECONDS);
}
Expand Down Expand Up @@ -245,6 +245,20 @@ void containerExisting_nodeStarted_containerUsed() {
}
}

@Test
void configAtLegacyLocation_nodeStartsSuccessfully() throws Exception {
final var consumer = new WaitingConsumer();

final var node = createHiveMQNodeWithLegacyConfig().withLogConsumer(consumer);
try (node) {
node.start();
// Wait for HiveMQ to fully start and the extension to initialize with the legacy config
consumer.waitUntil(frame -> frame.getUtf8String().contains("Started HiveMQ"), 30, SECONDS);
// Verify the legacy config warning is logged (proves the legacy config was used)
consumer.waitUntil(frame -> frame.getUtf8String().contains("is placed at the legacy location"), 5, SECONDS);
}
}

private @NotNull String createHostAzuriteConnectionString() {
return createAzuriteConnectionString("127.0.0.1", azuriteContainer.getMappedPort(AZURITE_PORT));
}
Expand All @@ -270,6 +284,16 @@ void containerExisting_nodeStarted_containerUsed() {
.asCompatibleSubstituteFor("hivemq/hivemq4")) //
.withHiveMQConfig(MountableFile.forClasspathResource("config.xml"))
.withCopyToContainer(Transferable.of(createConfig(connectionString)),
"/opt/hivemq/extensions/hivemq-azure-cluster-discovery-extension/conf/config.properties")
.withEnv("HIVEMQ_DISABLE_STATISTICS", "true")
.withNetwork(network);
}

private @NotNull HiveMQContainer createHiveMQNodeWithLegacyConfig() {
return new HiveMQContainer(OciImages.getImageName("hivemq/extensions/hivemq-azure-cluster-discovery-extension")
.asCompatibleSubstituteFor("hivemq/hivemq4")) //
.withHiveMQConfig(MountableFile.forClasspathResource("config.xml"))
.withCopyToContainer(Transferable.of(createConfig(createDockerAzuriteConnectionString())),
"/opt/hivemq/extensions/hivemq-azure-cluster-discovery-extension/azDiscovery.properties")
.withEnv("HIVEMQ_DISABLE_STATISTICS", "true")
.withNetwork(network);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,18 @@

public class ConfigReader {

public static final @NotNull String STORAGE_FILE = "azDiscovery.properties";
public static final @NotNull String CONFIG_PATH = "conf/config.properties";
public static final @NotNull String LEGACY_CONFIG_PATH = "azDiscovery.properties";

private static final @NotNull Logger logger = LoggerFactory.getLogger(ConfigReader.class);

private final @NotNull File extensionHomeFolder;
private final @NotNull ConfigResolver configResolver;

public ConfigReader(final @NotNull ExtensionInformation extensionInformation) {
extensionHomeFolder = extensionInformation.getExtensionHomeFolder();
configResolver = new ConfigResolver(extensionInformation.getExtensionHomeFolder().toPath(),
"Azure Cluster Discovery Extension",
CONFIG_PATH,
LEGACY_CONFIG_PATH);
}

private static boolean isValid(final @NotNull AzureDiscoveryConfig azureDiscoveryConfig) {
Expand Down Expand Up @@ -100,11 +104,11 @@ public static boolean isNullOrBlank(final @Nullable String value) {
}

public @Nullable AzureDiscoveryConfig readConfiguration() {
final var propertiesFile = new File(extensionHomeFolder, STORAGE_FILE);
final var propertiesFile = configResolver.get().toFile();
if (!propertiesFile.exists()) {
logger.warn("Could not find '{}'. Please verify that the properties file is located under '{}'.",
STORAGE_FILE,
extensionHomeFolder);
propertiesFile.getName(),
propertiesFile.getParentFile());
return null;
}
if (!propertiesFile.canRead()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2021-present HiveMQ GmbH
*
* 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 com.hivemq.extensions.cluster.discovery.azure.config;

import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;

public class ConfigResolver implements Supplier<Path> {

private static final @NotNull Logger LOG = LoggerFactory.getLogger(ConfigResolver.class);

private final @NotNull AtomicBoolean legacyWarningAlreadyLogged = new AtomicBoolean();
private final @NotNull Path extensionHome;
private final @NotNull String extensionName;
private final @NotNull String configLocation;
private final @NotNull String legacyConfigLocation;

public ConfigResolver(
final @NotNull Path extensionHome,
final @NotNull String extensionName,
final @NotNull String configLocation,
final @NotNull String legacyConfigLocation) {
this.extensionHome = extensionHome;
this.extensionName = extensionName;
this.configLocation = configLocation;
this.legacyConfigLocation = legacyConfigLocation;
}

@Override
public @NotNull Path get() {
final Path configPath = extensionHome.resolve(configLocation);
final Path legacyPath = extensionHome.resolve(legacyConfigLocation);

// if config is present at the legacy location, use it (with warning)
// the only way it could be there is when deliberately placed
if (legacyPath.toFile().exists()) {
if (!legacyWarningAlreadyLogged.getAndSet(true)) {
LOG.warn("{}: The configuration file '{}' is placed at the legacy location. " +
"Please move the configuration file to '{}'. " +
"Support for the legacy location will be removed in a future release.",
extensionName,
legacyPath,
configPath);
}
return legacyPath;
}
return configPath;
}
}
Loading