Skip to content

Commit d8496d7

Browse files
authored
Fix MQTT eventing and broker tests (#1035)
Unify MQTT topics, upgrade Moquette, and guard Docker publishing.
1 parent cd90e46 commit d8496d7

62 files changed

Lines changed: 1737 additions & 1167 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/docker-milestone-release.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,37 @@ on:
77
env:
88
DOCKER_NAMESPACE: eclipsebasyx
99

10+
permissions: {}
11+
1012
jobs:
13+
guard:
14+
runs-on: ubuntu-latest
15+
permissions:
16+
contents: read
17+
outputs:
18+
should-run: ${{ steps.check.outputs.should-run }}
19+
steps:
20+
- name: Checkout Code
21+
uses: actions/checkout@v7
22+
23+
- id: check
24+
env:
25+
EVENT_REPOSITORY_FORK: ${{ github.event.repository.fork }}
26+
run: |
27+
should_run=$(bash .github/workflows/scripts/check_docker_publication.sh "${GITHUB_REPOSITORY}" "${EVENT_REPOSITORY_FORK}")
28+
echo "should-run=${should_run}" >> "$GITHUB_OUTPUT"
29+
if [[ "${should_run}" == "true" ]]; then
30+
echo "Running Docker publication in the upstream repository"
31+
else
32+
echo "Skipping Docker publication for ${GITHUB_REPOSITORY}"
33+
fi
34+
1135
build-and-push-milestone-release:
36+
needs: guard
37+
if: ${{ needs.guard.outputs.should-run == 'true' }}
1238
runs-on: ubuntu-latest
39+
permissions:
40+
contents: read
1341
strategy:
1442
matrix:
1543
include:
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# /*******************************************************************************
2+
# * Copyright (C) 2026 the Eclipse BaSyx Authors
3+
# *
4+
# * Permission is hereby granted, free of charge, to any person obtaining
5+
# * a copy of this software and associated documentation files (the
6+
# * "Software"), to deal in the Software without restriction, including
7+
# * without limitation the rights to use, copy, modify, merge, publish,
8+
# * distribute, sublicense, and/or sell copies of the Software, and to
9+
# * permit persons to whom the Software is furnished to do so, subject to
10+
# * the following conditions:
11+
# *
12+
# * The above copyright notice and this permission notice shall be
13+
# * included in all copies or substantial portions of the Software.
14+
# *
15+
# * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16+
# * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17+
# * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18+
# * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19+
# * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20+
# * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21+
# * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22+
# *
23+
# * SPDX-License-Identifier: MIT
24+
# ******************************************************************************/
25+
26+
name: Test Docker Publication Guard
27+
28+
on:
29+
pull_request:
30+
branches: [ main ]
31+
paths:
32+
- '.github/workflows/docker-milestone-release.yml'
33+
- '.github/workflows/docker-snapshot-release.yml'
34+
- '.github/workflows/docker-publication-guard-test.yml'
35+
- '.github/workflows/scripts/check_docker_publication.sh'
36+
- '.github/workflows/scripts/test_docker_publication_guard.sh'
37+
push:
38+
branches: [ main ]
39+
paths:
40+
- '.github/workflows/docker-milestone-release.yml'
41+
- '.github/workflows/docker-snapshot-release.yml'
42+
- '.github/workflows/docker-publication-guard-test.yml'
43+
- '.github/workflows/scripts/check_docker_publication.sh'
44+
- '.github/workflows/scripts/test_docker_publication_guard.sh'
45+
46+
permissions: {}
47+
48+
jobs:
49+
test-docker-publication-guard:
50+
runs-on: ubuntu-latest
51+
permissions:
52+
contents: read
53+
steps:
54+
- name: Checkout Code
55+
uses: actions/checkout@v7
56+
57+
- name: Test publication guard
58+
run: bash .github/workflows/scripts/test_docker_publication_guard.sh

.github/workflows/docker-snapshot-release.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,37 @@ on:
2323
env:
2424
DOCKER_NAMESPACE: eclipsebasyx
2525

26+
permissions: {}
27+
2628
jobs:
29+
guard:
30+
runs-on: ubuntu-latest
31+
permissions:
32+
contents: read
33+
outputs:
34+
should-run: ${{ steps.check.outputs.should-run }}
35+
steps:
36+
- name: Checkout Code
37+
uses: actions/checkout@v7
38+
39+
- id: check
40+
env:
41+
EVENT_REPOSITORY_FORK: ${{ github.event.repository.fork }}
42+
run: |
43+
should_run=$(bash .github/workflows/scripts/check_docker_publication.sh "${GITHUB_REPOSITORY}" "${EVENT_REPOSITORY_FORK}")
44+
echo "should-run=${should_run}" >> "$GITHUB_OUTPUT"
45+
if [[ "${should_run}" == "true" ]]; then
46+
echo "Running Docker publication in the upstream repository"
47+
else
48+
echo "Skipping Docker publication for ${GITHUB_REPOSITORY}"
49+
fi
50+
2751
build-and-push-prerelease:
52+
needs: guard
53+
if: ${{ needs.guard.outputs.should-run == 'true' }}
2854
runs-on: ubuntu-latest
55+
permissions:
56+
contents: read
2957
strategy:
3058
matrix:
3159
include:
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/usr/bin/env bash
2+
3+
# /*******************************************************************************
4+
# * Copyright (C) 2026 the Eclipse BaSyx Authors
5+
# *
6+
# * Permission is hereby granted, free of charge, to any person obtaining
7+
# * a copy of this software and associated documentation files (the
8+
# * "Software"), to deal in the Software without restriction, including
9+
# * without limitation the rights to use, copy, modify, merge, publish,
10+
# * distribute, sublicense, and/or sell copies of the Software, and to
11+
# * permit persons to whom the Software is furnished to do so, subject to
12+
# * the following conditions:
13+
# *
14+
# * The above copyright notice and this permission notice shall be
15+
# * included in all copies or substantial portions of the Software.
16+
# *
17+
# * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18+
# * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19+
# * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20+
# * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21+
# * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22+
# * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23+
# * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24+
# *
25+
# * SPDX-License-Identifier: MIT
26+
# ******************************************************************************/
27+
28+
set -euo pipefail
29+
30+
readonly UPSTREAM_REPOSITORY="eclipse-basyx/basyx-java-server-sdk"
31+
32+
repository="${1:-}"
33+
is_fork="${2:-}"
34+
35+
if [[ "${repository}" == "${UPSTREAM_REPOSITORY}" && "${is_fork}" == "false" ]]; then
36+
printf 'true\n'
37+
else
38+
printf 'false\n'
39+
fi
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/usr/bin/env bash
2+
3+
# /*******************************************************************************
4+
# * Copyright (C) 2026 the Eclipse BaSyx Authors
5+
# *
6+
# * Permission is hereby granted, free of charge, to any person obtaining
7+
# * a copy of this software and associated documentation files (the
8+
# * "Software"), to deal in the Software without restriction, including
9+
# * without limitation the rights to use, copy, modify, merge, publish,
10+
# * distribute, sublicense, and/or sell copies of the Software, and to
11+
# * permit persons to whom the Software is furnished to do so, subject to
12+
# * the following conditions:
13+
# *
14+
# * The above copyright notice and this permission notice shall be
15+
# * included in all copies or substantial portions of the Software.
16+
# *
17+
# * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18+
# * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19+
# * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20+
# * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21+
# * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22+
# * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23+
# * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24+
# *
25+
# * SPDX-License-Identifier: MIT
26+
# ******************************************************************************/
27+
28+
set -euo pipefail
29+
30+
readonly SCRIPT_DIRECTORY="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
31+
readonly GUARD_SCRIPT="${SCRIPT_DIRECTORY}/check_docker_publication.sh"
32+
readonly WORKFLOW_DIRECTORY="${SCRIPT_DIRECTORY}/.."
33+
34+
assert_guard_result() {
35+
local description="$1"
36+
local repository="$2"
37+
local is_fork="$3"
38+
local expected="$4"
39+
local actual
40+
41+
actual="$(bash "${GUARD_SCRIPT}" "${repository}" "${is_fork}")"
42+
if [[ "${actual}" != "${expected}" ]]; then
43+
echo "${description}: expected ${expected}, got ${actual}" >&2
44+
exit 1
45+
fi
46+
}
47+
48+
assert_workflow_uses_guard() {
49+
local workflow="$1"
50+
51+
if [[ "$(grep -c 'check_docker_publication.sh' "${workflow}")" -ne 1 ]]; then
52+
echo "${workflow} must invoke the shared Docker publication guard exactly once" >&2
53+
exit 1
54+
fi
55+
}
56+
57+
# Release workflow cases
58+
assert_guard_result "release in upstream" "eclipse-basyx/basyx-java-server-sdk" "false" "true"
59+
assert_guard_result "release in upstream marked as fork" "eclipse-basyx/basyx-java-server-sdk" "true" "false"
60+
assert_guard_result "release in fork" "contributor/basyx-java-server-sdk" "true" "false"
61+
assert_guard_result "release in renamed repository" "eclipse-basyx/basyx-java-server-sdk-renamed" "false" "false"
62+
63+
# Snapshot push workflow cases
64+
assert_guard_result "push in upstream" "eclipse-basyx/basyx-java-server-sdk" "false" "true"
65+
assert_guard_result "push in upstream marked as fork" "eclipse-basyx/basyx-java-server-sdk" "true" "false"
66+
assert_guard_result "push in fork" "contributor/basyx-java-server-sdk" "true" "false"
67+
assert_guard_result "push in renamed repository" "eclipse-basyx/basyx-java-server-sdk-renamed" "false" "false"
68+
69+
# Missing event metadata must fail closed.
70+
assert_guard_result "missing fork metadata" "eclipse-basyx/basyx-java-server-sdk" "" "false"
71+
assert_guard_result "missing repository metadata" "" "false" "false"
72+
73+
assert_workflow_uses_guard "${WORKFLOW_DIRECTORY}/docker-milestone-release.yml"
74+
assert_workflow_uses_guard "${WORKFLOW_DIRECTORY}/docker-snapshot-release.yml"
75+
76+
echo "Docker publication guard tests passed"

basyx.aasrepository/basyx.aasrepository-feature-mqtt/Readme.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,9 @@ This feature provides hierarchical MQTT eventing for a multitude of events:
66
| AAS Created | aas-repository/\$repoId/shells/created| Created AAS JSON |
77
| AAS Updated | aas-repository/\$repoId/shells/updated| Updated AAS JSON|
88
| AAS Deleted | aas-repository/\$repoId/shells/deleted| Deleted AAS JSON|
9-
| Submodel Reference Created | aas-repository/\$repoId/shells/submodels/\$submodelId/created| Created AAS JSON |
10-
| Submodel Reference Deleted | aas-repository/\$repoId/shells/submodels/\$submodelId/deleted| Deleted AAS JSON|
9+
| Submodel Reference Created | aas-repository/\$repoId/shells/submodels/\$submodelIdBase64Url/created| Created AAS JSON |
10+
| Submodel Reference Deleted | aas-repository/\$repoId/shells/submodels/\$submodelIdBase64Url/deleted| Deleted AAS JSON|
11+
12+
Identifier segments use unpadded UTF-8 Base64URL encoding. Repository names are not encoded.
13+
14+
> **Breaking topic migration:** Submodel-reference events are published only on the encoded canonical topics. Existing subscribers using raw submodel IDs must update their subscriptions.

basyx.aasrepository/basyx.aasrepository-feature-mqtt/pom.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@
2121
<groupId>org.eclipse.digitaltwin.basyx</groupId>
2222
<artifactId>basyx.mqttcore</artifactId>
2323
</dependency>
24+
<dependency>
25+
<groupId>org.eclipse.digitaltwin.basyx</groupId>
26+
<artifactId>basyx.mqttcore</artifactId>
27+
<version>${revision}</version>
28+
<classifier>tests</classifier>
29+
<scope>test</scope>
30+
</dependency>
2431
<dependency>
2532
<groupId>org.eclipse.digitaltwin.basyx</groupId>
2633
<artifactId>basyx.aasrepository-core</artifactId>
@@ -55,4 +62,4 @@
5562
<scope>test</scope>
5663
</dependency>
5764
</dependencies>
58-
</project>
65+
</project>

basyx.aasrepository/basyx.aasrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/mqtt/MqttAasRepository.java

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,12 @@
3535
import org.eclipse.digitaltwin.aas4j.v3.model.Reference;
3636
import org.eclipse.digitaltwin.aas4j.v3.model.SpecificAssetId;
3737
import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository;
38+
import org.eclipse.digitaltwin.basyx.common.mqttcore.MqttEventPublisher;
3839
import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException;
3940
import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException;
4041
import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult;
4142
import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo;
4243
import org.eclipse.paho.client.mqttv3.IMqttClient;
43-
import org.eclipse.paho.client.mqttv3.MqttException;
44-
import org.eclipse.paho.client.mqttv3.MqttMessage;
45-
import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
4644
import org.slf4j.Logger;
4745
import org.slf4j.LoggerFactory;
4846

@@ -170,24 +168,7 @@ private String serializePayload(AssetAdministrationShell shell) {
170168
* the actual message
171169
*/
172170
private void sendMqttMessage(String topic, String payload) {
173-
MqttMessage msg = createMqttMessage(payload);
174-
175-
try {
176-
logger.debug("Send MQTT message to " + topic + ": " + payload);
177-
mqttClient.publish(topic, msg);
178-
} catch (MqttPersistenceException e) {
179-
logger.error("Could not persist mqtt message", e);
180-
} catch (MqttException e) {
181-
logger.error("Could not send mqtt message", e);
182-
}
183-
}
184-
185-
private MqttMessage createMqttMessage(String payload) {
186-
if (payload == null) {
187-
return new MqttMessage();
188-
} else {
189-
return new MqttMessage(payload.getBytes());
190-
}
171+
MqttEventPublisher.publish(mqttClient, topic, payload, logger);
191172
}
192173

193174
@Override

basyx.aasrepository/basyx.aasrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/mqtt/MqttAasRepositoryConfiguration.java

Lines changed: 2 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -26,54 +26,11 @@
2626

2727
package org.eclipse.digitaltwin.basyx.aasrepository.feature.mqtt;
2828

29-
import org.eclipse.paho.client.mqttv3.IMqttClient;
30-
import org.eclipse.paho.client.mqttv3.MqttClient;
31-
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
32-
import org.eclipse.paho.client.mqttv3.MqttException;
33-
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
34-
import org.springframework.beans.factory.annotation.Value;
29+
import org.eclipse.digitaltwin.basyx.common.mqttcore.MqttClientConfiguration;
3530
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
36-
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
37-
import org.springframework.boot.context.properties.ConfigurationProperties;
38-
import org.springframework.context.annotation.Bean;
3931
import org.springframework.context.annotation.Configuration;
4032

4133
@ConditionalOnExpression("#{${" + MqttAasRepositoryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.mqtt.enabled:false}}")
4234
@Configuration
43-
public class MqttAasRepositoryConfiguration {
44-
45-
@ConditionalOnMissingBean
46-
@Bean
47-
public IMqttClient mqttClient(
48-
@Value("${mqtt.clientId}") String clientId,
49-
@Value("${mqtt.hostname}") String hostname,
50-
@Value("${mqtt.port}") int port,
51-
@Value("${mqtt.protocol:tcp}") String protocol,
52-
MqttConnectOptions mqttConnectOptions)
53-
throws MqttException {
54-
IMqttClient mqttClient = new MqttClient(protocol + "://" + hostname + ":" + port, clientId,
55-
new MemoryPersistence());
56-
57-
mqttClient.connect(mqttConnectOptions);
58-
59-
return mqttClient;
60-
}
61-
62-
@ConditionalOnMissingBean
63-
@Bean
64-
@ConfigurationProperties(prefix = "mqtt")
65-
public MqttConnectOptions mqttConnectOptions(
66-
@Value("${mqtt.username:}") String username,
67-
@Value("${mqtt.password:}") String password) {
68-
MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
69-
mqttConnectOptions.setAutomaticReconnect(true);
70-
71-
if (username.isBlank() || password.isBlank())
72-
return mqttConnectOptions;
73-
74-
mqttConnectOptions.setUserName(username);
75-
mqttConnectOptions.setPassword(password.toCharArray());
76-
77-
return mqttConnectOptions;
78-
}
35+
public class MqttAasRepositoryConfiguration extends MqttClientConfiguration {
7936
}

0 commit comments

Comments
 (0)