Skip to content

Commit 729c5be

Browse files
committed
Switch opcua tests to test containers.
Relates to #1410. Checked in MiloTestContainer which creates on-the-fly container based on static configuration. Signed-off-by: Łukasz Dywicki <[email protected]>
1 parent 9410635 commit 729c5be

File tree

6 files changed

+189
-24
lines changed

6 files changed

+189
-24
lines changed

plc4j/drivers/opcua/Dockerfile.test

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one or more
2+
# contributor license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright ownership.
4+
# The ASF licenses this file to You under the Apache License, Version 2.0
5+
# (the "License"); you may not use this file except in compliance with
6+
# the License. You may obtain a copy of the License at
7+
#
8+
# https://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
FROM eclipse-temurin:17
17+
18+
ADD "target/milo-testcontainer/*.jar" "/opt/milo/"
19+
ADD "target/test-classes/org/eclipse/milo" "/opt/milo/org/eclipse/milo"
20+
21+
CMD java -cp '/opt/milo/*:/opt/milo/' org.eclipse.milo.examples.server.TestMiloServer

plc4j/drivers/opcua/pom.xml

+41
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,23 @@
6969
</execution>
7070
</executions>
7171
</plugin>
72+
<plugin>
73+
<groupId>org.apache.maven.plugins</groupId>
74+
<artifactId>maven-dependency-plugin</artifactId>
75+
<executions>
76+
<execution>
77+
<id>copy-milo-dependencies</id>
78+
<goals>
79+
<goal>copy-dependencies</goal>
80+
</goals>
81+
<phase>test-compile</phase>
82+
<configuration>
83+
<includeArtifactIds>server-examples</includeArtifactIds>
84+
<outputDirectory>${project.build.directory}/milo-testcontainer</outputDirectory>
85+
</configuration>
86+
</execution>
87+
</executions>
88+
</plugin>
7289
<plugin>
7390
<groupId>org.apache.felix</groupId>
7491
<artifactId>maven-bundle-plugin</artifactId>
@@ -186,6 +203,30 @@
186203
<type>test-jar</type>
187204
<scope>test</scope>
188205
</dependency>
206+
<dependency>
207+
<groupId>com.github.docker-java</groupId>
208+
<artifactId>docker-java-api</artifactId>
209+
<version>3.4.0</version>
210+
<scope>test</scope>
211+
</dependency>
212+
<dependency>
213+
<groupId>org.testcontainers</groupId>
214+
<artifactId>testcontainers</artifactId>
215+
<version>1.20.0</version>
216+
<scope>test</scope>
217+
</dependency>
218+
<dependency>
219+
<groupId>org.testcontainers</groupId>
220+
<artifactId>junit-jupiter</artifactId>
221+
<version>1.20.0</version>
222+
<scope>test</scope>
223+
</dependency>
224+
<dependency>
225+
<groupId>com.google.cloud.tools</groupId>
226+
<artifactId>jib-core</artifactId>
227+
<version>0.22.0</version>
228+
<scope>test</scope>
229+
</dependency>
189230
</dependencies>
190231

191232
<dependencyManagement>

plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaProtocolLogic.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ public Map<String, ResponseItem<PlcValue>> readResponse(LinkedHashSet<String> ta
412412
} else {
413413
StatusCode statusCode = results.get(count).getStatusCode();
414414
responseCode = mapOpcStatusCode(statusCode.getStatusCode(), PlcResponseCode.UNSUPPORTED);
415-
LOGGER.error("Error while reading value from OPC UA server error code:- " + results.get(count).getStatusCode().toString());
415+
LOGGER.error("Error while reading value from OPC UA server error code: {}", results.get(count).getStatusCode().toString());
416416
}
417417
count++;
418418
response.put(tagName, new ResponseItem<>(responseCode, value));
@@ -446,7 +446,7 @@ private static PlcResponseCode mapOpcStatusCode(long opcStatusCode, PlcResponseC
446446

447447
private Variant fromPlcValue(String tagName, OpcuaTag tag, PlcWriteRequest request) {
448448
PlcList valueObject;
449-
if (request.getPlcValue(tagName).getObject() instanceof ArrayList) {
449+
if (request.getPlcValue(tagName).getObject() instanceof List) {
450450
valueObject = (PlcList) request.getPlcValue(tagName);
451451
} else {
452452
List<PlcValue> list = new ArrayList<>();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.plc4x.java.opcua;
21+
22+
import java.nio.file.Path;
23+
import java.nio.file.Paths;
24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
26+
import org.testcontainers.containers.GenericContainer;
27+
import org.testcontainers.containers.wait.strategy.Wait;
28+
import org.testcontainers.images.builder.ImageFromDockerfile;
29+
30+
public class MiloTestContainer extends GenericContainer<MiloTestContainer> {
31+
32+
private final static Logger logger = LoggerFactory.getLogger(MiloTestContainer.class);
33+
34+
public MiloTestContainer() {
35+
super(inlineImage());
36+
37+
waitingFor(Wait.forLogMessage("Server started\\s*", 1));
38+
addFixedExposedPort(12686, 12686);
39+
}
40+
41+
private static ImageFromDockerfile inlineImage() {
42+
Path absolutePath = Paths.get(".").toAbsolutePath();
43+
logger.info("Building milo server image from {}", absolutePath);
44+
return new ImageFromDockerfile("plc4x-milo-test", false)
45+
.withBuildImageCmdModifier(cmd -> cmd.withNoCache(true))
46+
.withDockerfile(absolutePath.resolve("Dockerfile.test"));
47+
}
48+
49+
}

plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/OpcuaPlcDriverTest.java

+43-22
Original file line numberDiff line numberDiff line change
@@ -62,17 +62,32 @@
6262
import java.nio.file.Paths;
6363
import java.util.concurrent.ExecutionException;
6464
import java.util.stream.Stream;
65+
import org.testcontainers.containers.BindMode;
66+
import org.testcontainers.containers.GenericContainer;
67+
import org.testcontainers.containers.output.Slf4jLogConsumer;
68+
import org.testcontainers.containers.wait.strategy.Wait;
69+
import org.testcontainers.images.builder.ImageFromDockerfile;
70+
import org.testcontainers.jib.JibImage;
71+
import org.testcontainers.junit.jupiter.Container;
72+
import org.testcontainers.junit.jupiter.Testcontainers;
6573

6674
import static java.util.Map.entry;
6775
import static org.assertj.core.api.Assertions.assertThat;
6876
import static org.assertj.core.api.Assertions.fail;
6977

78+
@Testcontainers(disabledWithoutDocker = true)
7079
@DisableOnJenkinsFlag
71-
@Disabled("This test regularly fails the build, it needs to be generally fixed.")
7280
public class OpcuaPlcDriverTest {
7381

7482
private static final Logger LOGGER = LoggerFactory.getLogger(OpcuaPlcDriverTest.class);
7583

84+
@Container
85+
public static final GenericContainer milo = new MiloTestContainer()
86+
//.withCreateContainerCmdModifier(cmd -> cmd.withHostName("test-opcua-server"))
87+
.withReuse(true)
88+
.withLogConsumer(new Slf4jLogConsumer(LOGGER))
89+
.withFileSystemBind("target/tmp/server/security", "/tmp/server/security", BindMode.READ_WRITE);
90+
7691
// Read only variables of milo example server of version 3.6
7792
private static final String BOOL_IDENTIFIER_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/Boolean";
7893
private static final String BYTE_IDENTIFIER_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/Byte";
@@ -123,50 +138,55 @@ public class OpcuaPlcDriverTest {
123138
//Restricted
124139
public static final String STRING_IDENTIFIER_ONLY_ADMIN_READ_WRITE = "ns=2;s=HelloWorld/OnlyAdminCanRead/String";
125140

126-
// Address of local milo server
127-
private final String miloLocalAddress = "127.0.0.1:12686/milo";
141+
// Address of local milo server, since it comes from test container its hostname and port is not static
142+
private final String miloLocalAddress = "%s:%d/milo";
128143
//Tcp pattern of OPC UA
129144
private final String opcPattern = "opcua:tcp://";
130145

131146
private final String paramSectionDivider = "?";
132147
private final String paramDivider = "&";
133148

134-
private final String tcpConnectionAddress = opcPattern + miloLocalAddress;
135-
136-
private final List<String> connectionStringValidSet = List.of(tcpConnectionAddress);
137-
private final List<String> connectionStringCorruptedSet = List.of();
138-
139149
private final String discoveryValidParamTrue = "discovery=true";
140150
private final String discoveryValidParamFalse = "discovery=false";
141151
private final String discoveryCorruptedParamWrongValueNum = "discovery=1";
142152
private final String discoveryCorruptedParamWrongName = "diskovery=false";
143153

154+
private String tcpConnectionAddress;
155+
private List<String> connectionStringValidSet;
156+
144157
final List<String> discoveryParamValidSet = List.of(discoveryValidParamTrue, discoveryValidParamFalse);
145158
List<String> discoveryParamCorruptedSet = List.of(discoveryCorruptedParamWrongValueNum, discoveryCorruptedParamWrongName);
146159

147160
private static TestMiloServer exampleServer;
148161

162+
@BeforeEach
163+
public void startUp() {
164+
//System.out.println(milo.getMappedPort(12686));
165+
tcpConnectionAddress = String.format(opcPattern + miloLocalAddress, milo.getHost(), milo.getMappedPort(12686));
166+
connectionStringValidSet = List.of(tcpConnectionAddress);
167+
}
168+
149169
@BeforeAll
150170
public static void setup() throws Exception {
151171
// When switching JDK versions from a newer to an older version,
152172
// this can cause the server to not start correctly.
153173
// Deleting the directory makes sure the key-store is initialized correctly.
154-
Path securityBaseDir = Paths.get(System.getProperty("java.io.tmpdir"), "server", "security");
155-
try {
156-
Files.delete(securityBaseDir);
157-
} catch (Exception e) {
158-
// Ignore this ...
159-
}
160-
161-
exampleServer = new TestMiloServer();
162-
exampleServer.startup().get();
174+
// Path securityBaseDir = Paths.get(System.getProperty("java.io.tmpdir"), "server", "security");
175+
// try {
176+
// Files.delete(securityBaseDir);
177+
// } catch (Exception e) {
178+
// // Ignore this ...
179+
// }
180+
//
181+
// exampleServer = new TestMiloServer();
182+
// exampleServer.startup().get();
163183
}
164184

165185
@AfterAll
166186
public static void tearDown() throws Exception {
167-
if (exampleServer != null) {
168-
exampleServer.shutdown().get();
169-
}
187+
// if (exampleServer != null) {
188+
// exampleServer.shutdown().get();
189+
// }
170190
}
171191

172192
@Nested
@@ -400,7 +420,7 @@ public void writeVariables(SecurityPolicy policy, MessageSecurity messageSecurit
400420

401421
PlcWriteRequest.Builder builder = opcuaConnection.writeRequestBuilder();
402422
tags.forEach((tagName, tagEntry) -> {
403-
System.out.println("Write tag " + tagName);
423+
System.out.println("Write tag " + tagName + " " + tagEntry);
404424
try {
405425
Object value = tagEntry.getValue();
406426
if (value.getClass().isArray()) {
@@ -526,7 +546,8 @@ private String getConnectionString(SecurityPolicy policy, MessageSecurity messag
526546
case Basic256Sha256:
527547
case Aes128_Sha256_RsaOaep:
528548
case Aes256_Sha256_RsaPss:
529-
Path keyStoreFile = Paths.get(System.getProperty("java.io.tmpdir"), "server", "security", "example-server.pfx");
549+
// this file and its contents should be populated by milo container
550+
Path keyStoreFile = Paths.get("target/tmp/server/security/example-server.pfx");
530551
String connectionParams = Stream.of(
531552
entry("key-store-file", keyStoreFile.toAbsolutePath().toString().replace("\\", "/")), // handle windows paths
532553
entry("key-store-password", "password"),

plc4j/drivers/opcua/src/test/java/org/eclipse/milo/examples/server/TestMiloServer.java

+33
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,17 @@
2828
import java.nio.file.Path;
2929
import java.nio.file.Paths;
3030
import java.security.KeyPair;
31+
import java.security.Security;
3132
import java.security.cert.X509Certificate;
3233
import java.util.ArrayList;
3334
import java.util.LinkedHashSet;
3435
import java.util.List;
3536
import java.util.Set;
3637
import java.util.concurrent.CompletableFuture;
38+
import java.util.concurrent.ExecutionException;
39+
import java.util.concurrent.TimeUnit;
40+
import java.util.concurrent.TimeoutException;
41+
import org.bouncycastle.jce.provider.BouncyCastleProvider;
3742
import org.eclipse.milo.opcua.sdk.server.OpcUaServer;
3843
import org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig;
3944
import org.eclipse.milo.opcua.sdk.server.identity.CompositeValidator;
@@ -52,6 +57,7 @@
5257
import org.eclipse.milo.opcua.stack.core.types.enumerated.MessageSecurityMode;
5358
import org.eclipse.milo.opcua.stack.core.types.structured.BuildInfo;
5459
import org.eclipse.milo.opcua.stack.core.util.CertificateUtil;
60+
import org.eclipse.milo.opcua.stack.core.util.NonceUtil;
5561
import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateGenerator;
5662
import org.eclipse.milo.opcua.stack.core.util.SelfSignedHttpsCertificateBuilder;
5763
import org.eclipse.milo.opcua.stack.server.EndpointConfiguration;
@@ -69,6 +75,32 @@ public class TestMiloServer {
6975
private final OpcUaServer server;
7076
private final ExampleNamespace exampleNamespace;
7177

78+
static {
79+
// Required for SecurityPolicy.Aes256_Sha256_RsaPss
80+
Security.addProvider(new BouncyCastleProvider());
81+
82+
try {
83+
NonceUtil.blockUntilSecureRandomSeeded(10, TimeUnit.SECONDS);
84+
} catch (ExecutionException | InterruptedException | TimeoutException e) {
85+
e.printStackTrace();
86+
System.exit(-1);
87+
}
88+
}
89+
90+
public static void main(String[] args) throws Exception {
91+
TestMiloServer server = new TestMiloServer();
92+
93+
server.startup().thenAccept(srv -> {
94+
System.out.println("Server started");
95+
}).get();
96+
97+
final CompletableFuture<Void> future = new CompletableFuture<>();
98+
99+
Runtime.getRuntime().addShutdownHook(new Thread(() -> future.complete(null)));
100+
101+
future.get();
102+
}
103+
72104
public TestMiloServer() throws Exception {
73105
Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "server", "security");
74106
Files.createDirectories(securityTempDir);
@@ -168,6 +200,7 @@ private Set<EndpointConfiguration> createEndpointConfigurations(X509Certificate
168200
bindAddresses.add("0.0.0.0");
169201

170202
Set<String> hostnames = new LinkedHashSet<>();
203+
hostnames.add("localhost");
171204
hostnames.add(HostnameUtil.getHostname());
172205
hostnames.addAll(HostnameUtil.getHostnames("0.0.0.0"));
173206

0 commit comments

Comments
 (0)