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"));