diff --git a/plc4j/drivers/opcua/Dockerfile.test b/plc4j/drivers/opcua/Dockerfile.test
new file mode 100644
index 00000000000..5aa1aa79af6
--- /dev/null
+++ b/plc4j/drivers/opcua/Dockerfile.test
@@ -0,0 +1,6 @@
+FROM eclipse-temurin:17
+
+ADD "target/milo-testcontainer/*.jar" "/opt/milo/"
+ADD "target/test-classes/org/eclipse/milo" "/opt/milo/org/eclipse/milo"
+
+CMD java -cp '/opt/milo/*:/opt/milo/' org.eclipse.milo.examples.server.TestMiloServer
\ No newline at end of file
diff --git a/plc4j/drivers/opcua/pom.xml b/plc4j/drivers/opcua/pom.xml
index 85e38681834..79999ee96ef 100644
--- a/plc4j/drivers/opcua/pom.xml
+++ b/plc4j/drivers/opcua/pom.xml
@@ -69,6 +69,23 @@
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-milo-dependencies
+
+ copy-dependencies
+
+ test-compile
+
+ server-examples
+ ${project.build.directory}/milo-testcontainer
+
+
+
+
org.apache.felix
maven-bundle-plugin
@@ -186,6 +203,24 @@
test-jar
test
+
+ org.testcontainers
+ testcontainers
+ 1.20.0
+ test
+
+
+ org.testcontainers
+ junit-jupiter
+ 1.20.0
+ test
+
+
+ com.google.cloud.tools
+ jib-core
+ 0.22.0
+ test
+
diff --git a/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/MiloTestContainer.java b/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/MiloTestContainer.java
new file mode 100644
index 00000000000..d22f448bf4c
--- /dev/null
+++ b/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/MiloTestContainer.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.plc4x.java.opcua;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.images.builder.ImageFromDockerfile;
+
+public class MiloTestContainer extends GenericContainer {
+
+ private final static Logger logger = LoggerFactory.getLogger(MiloTestContainer.class);
+
+ public MiloTestContainer() {
+ super(inlineImage());
+
+ waitingFor(Wait.forLogMessage("Server started\\s*", 1));
+ addFixedExposedPort(12686, 12686);
+ }
+
+ private static ImageFromDockerfile inlineImage() {
+ Path absolutePath = Paths.get(".").toAbsolutePath();
+ logger.info("Building milo server image from {}", absolutePath);
+ return new ImageFromDockerfile("plc4x-milo-test", false)
+ .withBuildImageCmdModifier(cmd -> cmd.withNoCache(true))
+ .withDockerfile(absolutePath.resolve("Dockerfile.test"));
+ }
+
+}
diff --git a/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/OpcuaPlcDriverTest.java b/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/OpcuaPlcDriverTest.java
index 2b32e12cd6c..a963b74306c 100644
--- a/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/OpcuaPlcDriverTest.java
+++ b/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/OpcuaPlcDriverTest.java
@@ -62,17 +62,31 @@
import java.nio.file.Paths;
import java.util.concurrent.ExecutionException;
import java.util.stream.Stream;
+import org.testcontainers.containers.BindMode;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.output.Slf4jLogConsumer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.images.builder.ImageFromDockerfile;
+import org.testcontainers.jib.JibImage;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
import static java.util.Map.entry;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
+@Testcontainers(disabledWithoutDocker = true)
@DisableOnJenkinsFlag
-@Disabled("This test regularly fails the build, it needs to be generally fixed.")
public class OpcuaPlcDriverTest {
private static final Logger LOGGER = LoggerFactory.getLogger(OpcuaPlcDriverTest.class);
+ @Container
+ public static final GenericContainer milo = new MiloTestContainer()
+ //.withCreateContainerCmdModifier(cmd -> cmd.withHostName("test-opcua-server"))
+ .withReuse(true)
+ .withFileSystemBind("target/tmp/server/security", "/tmp/server/security", BindMode.READ_WRITE);
+
// Read only variables of milo example server of version 3.6
private static final String BOOL_IDENTIFIER_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/Boolean";
private static final String BYTE_IDENTIFIER_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/Byte";
@@ -123,50 +137,55 @@ public class OpcuaPlcDriverTest {
//Restricted
public static final String STRING_IDENTIFIER_ONLY_ADMIN_READ_WRITE = "ns=2;s=HelloWorld/OnlyAdminCanRead/String";
- // Address of local milo server
- private final String miloLocalAddress = "127.0.0.1:12686/milo";
+ // Address of local milo server, since it comes from test container its hostname and port is not static
+ private final String miloLocalAddress = "%s:%d/milo";
//Tcp pattern of OPC UA
private final String opcPattern = "opcua:tcp://";
private final String paramSectionDivider = "?";
private final String paramDivider = "&";
- private final String tcpConnectionAddress = opcPattern + miloLocalAddress;
-
- private final List connectionStringValidSet = List.of(tcpConnectionAddress);
- private final List connectionStringCorruptedSet = List.of();
-
private final String discoveryValidParamTrue = "discovery=true";
private final String discoveryValidParamFalse = "discovery=false";
private final String discoveryCorruptedParamWrongValueNum = "discovery=1";
private final String discoveryCorruptedParamWrongName = "diskovery=false";
+ private String tcpConnectionAddress;
+ private List connectionStringValidSet;
+
final List discoveryParamValidSet = List.of(discoveryValidParamTrue, discoveryValidParamFalse);
List discoveryParamCorruptedSet = List.of(discoveryCorruptedParamWrongValueNum, discoveryCorruptedParamWrongName);
private static TestMiloServer exampleServer;
+ @BeforeEach
+ public void startUp() {
+ //System.out.println(milo.getMappedPort(12686));
+ tcpConnectionAddress = String.format(opcPattern + miloLocalAddress, milo.getHost(), milo.getMappedPort(12686));
+ connectionStringValidSet = List.of(tcpConnectionAddress);
+ }
+
@BeforeAll
public static void setup() throws Exception {
// When switching JDK versions from a newer to an older version,
// this can cause the server to not start correctly.
// Deleting the directory makes sure the key-store is initialized correctly.
- Path securityBaseDir = Paths.get(System.getProperty("java.io.tmpdir"), "server", "security");
- try {
- Files.delete(securityBaseDir);
- } catch (Exception e) {
- // Ignore this ...
- }
-
- exampleServer = new TestMiloServer();
- exampleServer.startup().get();
+// Path securityBaseDir = Paths.get(System.getProperty("java.io.tmpdir"), "server", "security");
+// try {
+// Files.delete(securityBaseDir);
+// } catch (Exception e) {
+// // Ignore this ...
+// }
+//
+// exampleServer = new TestMiloServer();
+// exampleServer.startup().get();
}
@AfterAll
public static void tearDown() throws Exception {
- if (exampleServer != null) {
- exampleServer.shutdown().get();
- }
+// if (exampleServer != null) {
+// exampleServer.shutdown().get();
+// }
}
@Nested
@@ -526,7 +545,8 @@ private String getConnectionString(SecurityPolicy policy, MessageSecurity messag
case Basic256Sha256:
case Aes128_Sha256_RsaOaep:
case Aes256_Sha256_RsaPss:
- Path keyStoreFile = Paths.get(System.getProperty("java.io.tmpdir"), "server", "security", "example-server.pfx");
+ // this file and its contents should be populated by milo container
+ Path keyStoreFile = Paths.get("target/tmp/server/security/example-server.pfx");
String connectionParams = Stream.of(
entry("key-store-file", keyStoreFile.toAbsolutePath().toString().replace("\\", "/")), // handle windows paths
entry("key-store-password", "password"),
diff --git a/plc4j/drivers/opcua/src/test/java/org/eclipse/milo/examples/server/TestMiloServer.java b/plc4j/drivers/opcua/src/test/java/org/eclipse/milo/examples/server/TestMiloServer.java
index 39258661406..872afbfb0fd 100644
--- a/plc4j/drivers/opcua/src/test/java/org/eclipse/milo/examples/server/TestMiloServer.java
+++ b/plc4j/drivers/opcua/src/test/java/org/eclipse/milo/examples/server/TestMiloServer.java
@@ -28,12 +28,17 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyPair;
+import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.eclipse.milo.opcua.sdk.server.OpcUaServer;
import org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig;
import org.eclipse.milo.opcua.sdk.server.identity.CompositeValidator;
@@ -52,6 +57,7 @@
import org.eclipse.milo.opcua.stack.core.types.enumerated.MessageSecurityMode;
import org.eclipse.milo.opcua.stack.core.types.structured.BuildInfo;
import org.eclipse.milo.opcua.stack.core.util.CertificateUtil;
+import org.eclipse.milo.opcua.stack.core.util.NonceUtil;
import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateGenerator;
import org.eclipse.milo.opcua.stack.core.util.SelfSignedHttpsCertificateBuilder;
import org.eclipse.milo.opcua.stack.server.EndpointConfiguration;
@@ -69,6 +75,32 @@ public class TestMiloServer {
private final OpcUaServer server;
private final ExampleNamespace exampleNamespace;
+ static {
+ // Required for SecurityPolicy.Aes256_Sha256_RsaPss
+ Security.addProvider(new BouncyCastleProvider());
+
+ try {
+ NonceUtil.blockUntilSecureRandomSeeded(10, TimeUnit.SECONDS);
+ } catch (ExecutionException | InterruptedException | TimeoutException e) {
+ e.printStackTrace();
+ System.exit(-1);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ TestMiloServer server = new TestMiloServer();
+
+ server.startup().thenAccept(srv -> {
+ System.out.println("Server started");
+ }).get();
+
+ final CompletableFuture future = new CompletableFuture<>();
+
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> future.complete(null)));
+
+ future.get();
+ }
+
public TestMiloServer() throws Exception {
Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "server", "security");
Files.createDirectories(securityTempDir);
@@ -168,6 +200,7 @@ private Set createEndpointConfigurations(X509Certificate
bindAddresses.add("0.0.0.0");
Set hostnames = new LinkedHashSet<>();
+ hostnames.add("localhost");
hostnames.add(HostnameUtil.getHostname());
hostnames.addAll(HostnameUtil.getHostnames("0.0.0.0"));