Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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,7 @@ 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"
EXTENSION_PROPERTIES_PATH="/opt/hivemq/extensions/hivemq-azure-cluster-discovery-extension/conf/config.properties"

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