diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/config/OpcuaConfiguration.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/config/OpcuaConfiguration.java index 74beb746e91..65488540096 100644 --- a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/config/OpcuaConfiguration.java +++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/config/OpcuaConfiguration.java @@ -135,6 +135,14 @@ public class OpcuaConfiguration implements PlcConnectionConfiguration { @Description("TCP encoding options") private Limits limits; + @ConfigurationParameter("endpoint-host") + @Description("Endpoint host used to establish secure channel.") + private String endpointHost; + + @ConfigurationParameter("endpoint-port") + @Description("Endpoint port used to establish secure channel") + private Integer endpointPort; + public String getProtocolCode() { return protocolCode; } @@ -228,6 +236,14 @@ public long getNegotiationTimeout() { return negotiationTimeout; } + public String getEndpointHost() { + return endpointHost; + } + + public Integer getEndpointPort() { + return endpointPort; + } + @Override public String toString() { return "OpcuaConfiguration{" + @@ -240,5 +256,6 @@ public String toString() { ", limits=" + limits + '}'; } + } diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/OpcuaDriverContext.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/OpcuaDriverContext.java index fd8862fc42f..2d05af99871 100644 --- a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/OpcuaDriverContext.java +++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/OpcuaDriverContext.java @@ -111,10 +111,6 @@ public String getHost() { return host; } - public void setHost(String host) { - this.host = host; - } - public String getPort() { return port; } @@ -126,10 +122,6 @@ public String getEndpoint() { public String getTransportEndpoint() { return transportEndpoint; } - - public void setTransportEndpoint(String transportEndpoint) { - this.transportEndpoint = transportEndpoint; - } public X509Certificate getServerCertificate() { return serverCertificate; @@ -147,6 +139,13 @@ public void setConfiguration(OpcuaConfiguration configuration) { port = matcher.group("transportPort"); transportEndpoint = matcher.group("transportEndpoint"); + if (configuration.getEndpointHost() != null) { + host = configuration.getEndpointHost(); + } + if (configuration.getEndpointPort() != null) { + port = String.valueOf(configuration.getEndpointPort()); + } + String portAddition = port != null ? ":" + port : ""; endpoint = "opc." + code + "://" + host + portAddition + transportEndpoint; diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/SecureChannel.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/SecureChannel.java index 44427db25a1..1cc9a6f1632 100644 --- a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/SecureChannel.java +++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/SecureChannel.java @@ -18,6 +18,7 @@ */ package org.apache.plc4x.java.opcua.context; +import static java.util.Map.entry; import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; import static org.apache.plc4x.java.opcua.readwrite.ChunkType.*; @@ -27,18 +28,24 @@ import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Map.Entry; +import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Collectors; import org.apache.commons.lang3.RandomStringUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.plc4x.java.api.authentication.PlcAuthentication; import org.apache.plc4x.java.api.authentication.PlcUsernamePasswordAuthentication; import org.apache.plc4x.java.api.exceptions.PlcRuntimeException; import org.apache.plc4x.java.opcua.config.OpcuaConfiguration; import org.apache.plc4x.java.opcua.readwrite.*; +import org.apache.plc4x.java.opcua.security.MessageSecurity; import org.apache.plc4x.java.opcua.security.SecurityPolicy; import org.apache.plc4x.java.opcua.security.SecurityPolicy.SignatureAlgorithm; import org.apache.plc4x.java.spi.generation.*; @@ -54,9 +61,7 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; -import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Stream; import static java.util.concurrent.Executors.newSingleThreadExecutor; @@ -92,7 +97,7 @@ public class SecureChannel { private final OpcuaDriverContext driverContext; private final Conversation conversation; private ScheduledFuture keepAlive; - private final List endpoints = new ArrayList<>(); + private final Set endpoints = new HashSet<>(); private double sessionTimeout; private long revisedLifetime; @@ -118,9 +123,9 @@ public SecureChannel(Conversation conversation, RequestTransactionManager tm, Op // Generate a list of endpoints we can use. try { InetAddress address = InetAddress.getByName(driverContext.getHost()); - this.endpoints.add(address.getHostAddress()); - this.endpoints.add(address.getHostName()); - this.endpoints.add(address.getCanonicalHostName()); + this.endpoints.add("opc.tcp://" + address.getHostAddress() + ":" + driverContext.getPort() + driverContext.getTransportEndpoint()); + this.endpoints.add("opc.tcp://" + address.getHostName() + ":" + driverContext.getPort() + driverContext.getTransportEndpoint()); + this.endpoints.add("opc.tcp://" + address.getCanonicalHostName() + ":" + driverContext.getPort() + driverContext.getTransportEndpoint()); } catch (UnknownHostException e) { LOGGER.warn("Unable to resolve host name. Using original host from connection string which may cause issues connecting to server"); this.endpoints.add(driverContext.getHost()); @@ -314,21 +319,22 @@ private CompletableFuture onConnectActivateSessionReque conversation.setRemoteCertificate(getX509Certificate(sessionResponse.getServerCertificate().getStringValue())); conversation.setRemoteNonce(sessionResponse.getServerNonce().getStringValue()); - String[] endpoints = new String[3]; + List contactPoints = new ArrayList<>(3); try { InetAddress address = InetAddress.getByName(driverContext.getHost()); - endpoints[0] = "opc.tcp://" + address.getHostAddress() + ":" + driverContext.getPort() + driverContext.getTransportEndpoint(); - endpoints[1] = "opc.tcp://" + address.getHostName() + ":" + driverContext.getPort() + driverContext.getTransportEndpoint(); - endpoints[2] = "opc.tcp://" + address.getCanonicalHostName() + ":" + driverContext.getPort() + driverContext.getTransportEndpoint(); + contactPoints.add("opc.tcp://" + address.getHostAddress() + ":" + driverContext.getPort() + driverContext.getTransportEndpoint()); + contactPoints.add("opc.tcp://" + address.getHostName() + ":" + driverContext.getPort() + driverContext.getTransportEndpoint()); + contactPoints.add("opc.tcp://" + address.getCanonicalHostName() + ":" + driverContext.getPort() + driverContext.getTransportEndpoint()); } catch (UnknownHostException e) { LOGGER.debug("error getting host", e); } - selectEndpoint(sessionResponse); - - if (this.policyId == null) { - throw new PlcRuntimeException("Unable to find endpoint - " + endpoints[1]); + List> selectedEndpoints = selectEndpoint(sessionResponse.getServerEndpoints(), contactPoints, configuration.getSecurityPolicy(), configuration.getMessageSecurity()); + if (selectedEndpoints.isEmpty()) { + throw new PlcRuntimeException("Unable to find endpoint matching - " + contactPoints.get(1)); } + this.policyId = selectedEndpoints.get(0).getValue().getPolicyId(); + this.tokenType = selectedEndpoints.get(0).getValue().getTokenType(); ExtensionObject userIdentityToken = getIdentityToken(this.tokenType, policyId.getStringValue()); RequestHeader requestHeader = conversation.createRequestHeader(); @@ -421,27 +427,42 @@ public CompletableFuture onDiscoverGetEndpointsRequest() { return conversation.submit(endpointsRequest, GetEndpointsResponse.class).thenApply(response -> { List endpoints = response.getEndpoints(); - MessageSecurityMode effectiveMode = this.configuration.getSecurityPolicy() == SecurityPolicy.NONE ? MessageSecurityMode.messageSecurityModeNone : this.configuration.getMessageSecurity().getMode(); - for (ExtensionObjectDefinition endpoint : endpoints) { - EndpointDescription endpointDescription = (EndpointDescription) endpoint; - - boolean urlMatch = endpointDescription.getEndpointUrl().getStringValue().equals(this.endpoint.getStringValue()); - boolean policyMatch = endpointDescription.getSecurityPolicyUri().getStringValue().equals(this.configuration.getSecurityPolicy().getSecurityPolicyUri()); - boolean msgSecurityMatch = endpointDescription.getSecurityMode().equals(effectiveMode); - - LOGGER.debug("Validate OPC UA endpoint {} during discovery phase." - + "Expected {}. Endpoint policy {} looking for {}. Message security {}, looking for {}", endpointDescription.getEndpointUrl().getStringValue(), this.endpoint.getStringValue(), - endpointDescription.getSecurityPolicyUri().getStringValue(), configuration.getSecurityPolicy().getSecurityPolicyUri(), - endpointDescription.getSecurityMode(), configuration.getMessageSecurity().getMode()); - - if (urlMatch && policyMatch && msgSecurityMatch) { - LOGGER.info("Found OPC UA endpoint {}", this.endpoint.getStringValue()); - return endpointDescription; - } + List> entries = selectEndpoint(response.getEndpoints(), this.endpoints, + this.configuration.getSecurityPolicy(), this.configuration.getMessageSecurity()); +// MessageSecurityMode effectiveMode = this.configuration.getSecurityPolicy() == SecurityPolicy.NONE ? MessageSecurityMode.messageSecurityModeNone : this.configuration.getMessageSecurity().getMode(); +// for (ExtensionObjectDefinition endpoint : endpoints) { +// EndpointDescription endpointDescription = (EndpointDescription) endpoint; +// +// boolean urlMatch = endpointDescription.getEndpointUrl().getStringValue().equals(this.endpoint.getStringValue()); +// if (!urlMatch && configuration.isIgnoreEndpointUri()) { +// urlMatch = true; +// } +// boolean policyMatch = endpointDescription.getSecurityPolicyUri().getStringValue().equals(this.configuration.getSecurityPolicy().getSecurityPolicyUri()); +// boolean msgSecurityMatch = endpointDescription.getSecurityMode().equals(effectiveMode); +// +// System.out.println(endpointDescription); +// +// LOGGER.debug("Validate OPC UA endpoint {} during discovery phase." +// + "Expected {}. Endpoint policy {} looking for {}. Message security {}, looking for {}", endpointDescription.getEndpointUrl().getStringValue(), this.endpoint.getStringValue(), +// endpointDescription.getSecurityPolicyUri().getStringValue(), configuration.getSecurityPolicy().getSecurityPolicyUri(), +// endpointDescription.getSecurityMode(), configuration.getMessageSecurity().getMode()); +// +// if (urlMatch && policyMatch && msgSecurityMatch) { +// LOGGER.info("Found OPC UA endpoint {}", this.endpoint.getStringValue()); +// return endpointDescription; +// } +// } + if (entries.isEmpty()) { + Set endpointUris = endpoints.stream() + .filter(EndpointDescription.class::isInstance) + .map(EndpointDescription.class::cast) + .map(EndpointDescription::getEndpointUrl) + .map(PascalString::getStringValue) + .collect(Collectors.toSet()); + throw new IllegalArgumentException("Could not find endpoint matching client configuration. Tested " + endpointUris + ". " + + "Was looking for " + this.endpoint.getStringValue() + " " + this.configuration.getSecurityPolicy().getSecurityPolicyUri() + " " + this.configuration.getMessageSecurity().getMode()); } - - throw new IllegalArgumentException("Could not find endpoint matching client configuration. Tested " + endpoints.size() + " endpoints. " - + "None matched " + this.endpoint.getStringValue() + " " + this.configuration.getSecurityPolicy().getSecurityPolicyUri() + " " + this.configuration.getMessageSecurity().getMode()); + return entries.get(0).getKey(); }); } @@ -501,89 +522,78 @@ private static ReadBufferByteBased toBuffer(Supplier supplier) { } /** - * Selects the endpoint to use based on the connection string provided. - * If Discovery is disabled it will use the host address return from the server + * Selects the endpoint to use based on the connection string provided. If Discovery is disabled + * it will use the host address return from the server * - * @param sessionResponse - The CreateSessionResponse message returned by the server - * @throws PlcRuntimeException - If no endpoint with a compatible policy is found raise and error. + * @param extensionObjects - The list containing extension objects, including endpoint + * descriptions. */ - private void selectEndpoint(CreateSessionResponse sessionResponse) throws PlcRuntimeException { + private List> selectEndpoint(List extensionObjects, Collection contactPoints, + SecurityPolicy securityPolicy, MessageSecurity messageSecurity) throws PlcRuntimeException { // Get a list of the endpoints which match ours. - Stream filteredEndpoints = sessionResponse.getServerEndpoints().stream() - .map(e -> (EndpointDescription) e) - .filter(this::isEndpoint); - - //Determine if the requested security policy is included in the endpoint - filteredEndpoints.forEach(endpoint -> hasIdentity( - endpoint.getUserIdentityTokens().stream() - .map(p -> (UserTokenPolicy) p) - .toArray(UserTokenPolicy[]::new) - )); - - if (this.policyId == null) { - throw new PlcRuntimeException("Unable to find endpoint - " + this.endpoints.get(0)); - } - if (this.tokenType == null) { - throw new PlcRuntimeException("Unable to find Security Policy for endpoint - " + this.endpoints.get(0)); + MessageSecurityMode effectiveMessageSecurity = SecurityPolicy.NONE == securityPolicy ? MessageSecurityMode.messageSecurityModeNone : messageSecurity.getMode(); + List> serverEndpoints = new ArrayList<>(); + for (ExtensionObjectDefinition extensionObject : extensionObjects) { + if (!(extensionObject instanceof EndpointDescription)) { + continue; + } + + EndpointDescription endpointDescription = (EndpointDescription) extensionObject; + if (isMatchingEndpoint(endpointDescription, contactPoints)) { + boolean policyMatch = endpointDescription.getSecurityPolicyUri().getStringValue().equals(securityPolicy.getSecurityPolicyUri()); + boolean msgSecurityMatch = endpointDescription.getSecurityMode().equals(effectiveMessageSecurity); + + if (!policyMatch && !msgSecurityMatch) { + continue; + } + + for (ExtensionObjectDefinition objectDefinition : endpointDescription.getUserIdentityTokens()) { + if (objectDefinition instanceof UserTokenPolicy) { + UserTokenPolicy userTokenPolicy = (UserTokenPolicy) objectDefinition; + if (canCommunicate(userTokenPolicy, this.username) != null) { + serverEndpoints.add(entry(endpointDescription, userTokenPolicy)); + } + } + } + } } + serverEndpoints.sort(Comparator.comparing(e -> e.getKey().getSecurityLevel())); + return serverEndpoints; } /** - * Checks each component of the return endpoint description against the connection string. - * If all are correct then return true. + * Checks each component of the return endpoint description against the connection string. If + * all are correct then return true. * * @param endpoint - EndpointDescription returned from server * @return true if this endpoint matches our configuration - * @throws PlcRuntimeException - If the returned endpoint string doesn't match the format expected + * @throws PlcRuntimeException - If the returned endpoint string doesn't match the format + * expected */ - private boolean isEndpoint(EndpointDescription endpoint) throws PlcRuntimeException { + private static boolean isMatchingEndpoint(EndpointDescription endpoint, Collection contactPoints) throws PlcRuntimeException { // Split up the connection string into it's individual segments. - String endpointUri = endpoint.getEndpointUrl().getStringValue(); - Matcher matcher = URI_PATTERN.matcher(endpointUri); - if (!matcher.matches()) { - throw new PlcRuntimeException( - "Endpoint " + endpointUri + " returned from the server doesn't match the format '{protocol-code}:({transport-code})?//{transport-host}(:{transport-port})(/{transport-endpoint})'"); - } - LOGGER.trace("Using Endpoint {} {} {}", matcher.group("transportHost"), matcher.group("transportPort"), matcher.group("transportEndpoint")); - - //When the parameter discovery=false is configured, prefer using the custom address. If the transportEndpoint is empty, - // directly replace it with the TransportEndpoint returned by the server. - if (!configuration.isDiscovery() && StringUtils.isBlank(driverContext.getTransportEndpoint())) { - driverContext.setTransportEndpoint(matcher.group("transportEndpoint")); - return true; - } - - if (configuration.isDiscovery() && !this.endpoints.contains(matcher.group("transportHost"))) { - return false; - } - - if (!driverContext.getPort().equals(matcher.group("transportPort"))) { - return false; - } - - if (!driverContext.getTransportEndpoint().equals(matcher.group("transportEndpoint"))) { - return false; + for (String contactPoint : contactPoints) { + if (endpoint.getEndpointUrl().getStringValue().startsWith(contactPoint)) { + return true; + } } - - return true; + return false; } /** * Confirms that a policy that matches the connection string is available from * the returned endpoints. It sets the policyId and tokenType for the policy to use. * - * @param policies - A list of policies returned with the endpoint description. + * @param identityToken - Identity token supported by server. + * @return True if token can be used by driver. */ - private void hasIdentity(UserTokenPolicy[] policies) { - for (UserTokenPolicy identityToken : policies) { - if ((identityToken.getTokenType() == UserTokenType.userTokenTypeAnonymous) && (this.username == null)) { - policyId = identityToken.getPolicyId(); - tokenType = identityToken.getTokenType(); - } else if ((identityToken.getTokenType() == UserTokenType.userTokenTypeUserName) && (this.username != null)) { - policyId = identityToken.getPolicyId(); - tokenType = identityToken.getTokenType(); - } + private static UserTokenType canCommunicate(UserTokenPolicy identityToken, String username) { + if (identityToken.getTokenType() == UserTokenType.userTokenTypeAnonymous && username == null) { + return UserTokenType.userTokenTypeAnonymous; + } else if (identityToken.getTokenType() == UserTokenType.userTokenTypeUserName && username != null) { + return UserTokenType.userTokenTypeUserName; } + return null; } /** 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 index d22f448bf4c..db13a936aee 100644 --- 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 @@ -31,11 +31,13 @@ public class MiloTestContainer extends GenericContainer { private final static Logger logger = LoggerFactory.getLogger(MiloTestContainer.class); + private final static ImageFromDockerfile IMAGE = inlineImage(); + public MiloTestContainer() { - super(inlineImage()); + super(IMAGE); waitingFor(Wait.forLogMessage("Server started\\s*", 1)); - addFixedExposedPort(12686, 12686); + addExposedPort(12686); } private static ImageFromDockerfile inlineImage() { 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 2bb0bd79ae9..e39e2ab32a8 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 @@ -57,6 +57,7 @@ import org.slf4j.LoggerFactory; import java.math.BigInteger; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.concurrent.ExecutionException; @@ -64,6 +65,9 @@ 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; @@ -72,15 +76,13 @@ import static org.assertj.core.api.Assertions.fail; @Testcontainers(disabledWithoutDocker = true) -@DisableOnJenkinsFlag public class OpcuaPlcDriverTest { private static final Logger LOGGER = LoggerFactory.getLogger(OpcuaPlcDriverTest.class); @Container - public static final GenericContainer milo = new MiloTestContainer() + public final GenericContainer milo = new MiloTestContainer() //.withCreateContainerCmdModifier(cmd -> cmd.withHostName("test-opcua-server")) - .withReuse(true) .withLogConsumer(new Slf4jLogConsumer(LOGGER)) .withFileSystemBind("target/tmp/server/security", "/tmp/server/security", BindMode.READ_WRITE); @@ -135,17 +137,17 @@ public class OpcuaPlcDriverTest { public static final String STRING_IDENTIFIER_ONLY_ADMIN_READ_WRITE = "ns=2;s=HelloWorld/OnlyAdminCanRead/String"; // Address of local milo server, since it comes from test container its hostname and port is not static - private static final String miloLocalAddress = "%s:%d/milo"; + private final String miloLocalAddress = "%s:%d/milo"; //Tcp pattern of OPC UA - private static final String opcPattern = "opcua:tcp://"; + private final String opcPattern = "opcua:tcp://"; - private static final String paramSectionDivider = "?"; - private static final String paramDivider = "&"; + private final String paramSectionDivider = "?"; + private final String paramDivider = "&"; - private static final String discoveryValidParamTrue = "discovery=true"; - private static final String discoveryValidParamFalse = "discovery=false"; - private static final String discoveryCorruptedParamWrongValueNum = "discovery=1"; - private static final String discoveryCorruptedParamWrongName = "diskovery=false"; + 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; @@ -153,12 +155,10 @@ public class OpcuaPlcDriverTest { 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)); + tcpConnectionAddress = String.format(opcPattern + miloLocalAddress, milo.getHost(), milo.getMappedPort(12686)) + "?endpoint-port=12686"; connectionStringValidSet = List.of(tcpConnectionAddress); } @@ -275,7 +275,7 @@ Stream connectionWithDiscoveryParam() throws Exception { return connectionStringValidSet.stream() .map(connectionAddress -> DynamicContainer.dynamicContainer(connectionAddress, () -> discoveryParamValidSet.stream().map(discoveryParam -> DynamicTest.dynamicTest(discoveryParam, () -> { - String connectionString = connectionAddress + paramSectionDivider + discoveryParam; + String connectionString = connectionAddress + paramDivider + discoveryParam; PlcConnection opcuaConnection = new DefaultPlcDriverManager().getConnection(connectionString); Condition is_connected = new Condition<>(PlcConnection::isConnected, "is connected"); assertThat(opcuaConnection).is(is_connected); @@ -290,7 +290,7 @@ Stream connectionWithDiscoveryParam() throws Exception { @Test void connectionWithUrlAuthentication() throws Exception { DefaultPlcDriverManager driverManager = new DefaultPlcDriverManager(); - try (PlcConnection opcuaConnection = driverManager.getConnection(tcpConnectionAddress + "?username=admin&password=password2")) { + try (PlcConnection opcuaConnection = driverManager.getConnection(tcpConnectionAddress + "&username=admin&password=password2")) { Condition is_connected = new Condition<>(PlcConnection::isConnected, "is connected"); assertThat(opcuaConnection).is(is_connected); @@ -325,7 +325,7 @@ void connectionWithPlcAuthentication() throws Exception { @Test void connectionWithPlcAuthenticationOverridesUrlParam() throws Exception { DefaultPlcDriverManager driverManager = new DefaultPlcDriverManager(); - try (PlcConnection opcuaConnection = driverManager.getConnection(tcpConnectionAddress + "?username=user&password=password1", + try (PlcConnection opcuaConnection = driverManager.getConnection(tcpConnectionAddress + "&username=user&password=password1", new PlcUsernamePasswordAuthentication("admin", "password2"))) { Condition is_connected = new Condition<>(PlcConnection::isConnected, "is connected"); assertThat(opcuaConnection).is(is_connected); @@ -459,7 +459,6 @@ public void writeVariables(SecurityPolicy policy, MessageSecurity messageSecurit Test added to test the synchronized TransactionHandler. (This was disabled before being enabled again so it might be a candidate for those tests not running properly on different platforms) */ @Test - @Disabled("Disabled flaky test. Tracking issue at https://github.com/apache/plc4x/issues/1764") public void multipleThreads() throws Exception { class ReadWorker extends Thread { private final PlcConnection connection; @@ -554,7 +553,7 @@ private String getConnectionString(SecurityPolicy policy, MessageSecurity messag .map(tuple -> tuple.getKey() + "=" + URLEncoder.encode(tuple.getValue(), Charset.defaultCharset())) .collect(Collectors.joining(paramDivider)); - return tcpConnectionAddress + paramSectionDivider + connectionParams; + return tcpConnectionAddress + paramDivider + connectionParams; default: throw new IllegalStateException(); } @@ -565,19 +564,19 @@ private static Stream getConnectionSecurityPolicies() { Arguments.of(SecurityPolicy.NONE, MessageSecurity.NONE), Arguments.of(SecurityPolicy.NONE, MessageSecurity.SIGN), Arguments.of(SecurityPolicy.NONE, MessageSecurity.SIGN_ENCRYPT), - Arguments.of(SecurityPolicy.Basic256Sha256, MessageSecurity.NONE), + //Arguments.of(SecurityPolicy.Basic256Sha256, MessageSecurity.NONE), Arguments.of(SecurityPolicy.Basic256Sha256, MessageSecurity.SIGN), Arguments.of(SecurityPolicy.Basic256Sha256, MessageSecurity.SIGN_ENCRYPT), - Arguments.of(SecurityPolicy.Basic256, MessageSecurity.NONE), + //Arguments.of(SecurityPolicy.Basic256, MessageSecurity.NONE), Arguments.of(SecurityPolicy.Basic256, MessageSecurity.SIGN), Arguments.of(SecurityPolicy.Basic256, MessageSecurity.SIGN_ENCRYPT), - Arguments.of(SecurityPolicy.Basic128Rsa15, MessageSecurity.NONE), + //Arguments.of(SecurityPolicy.Basic128Rsa15, MessageSecurity.NONE), Arguments.of(SecurityPolicy.Basic128Rsa15, MessageSecurity.SIGN), Arguments.of(SecurityPolicy.Basic128Rsa15, MessageSecurity.SIGN_ENCRYPT), - Arguments.of(SecurityPolicy.Aes128_Sha256_RsaOaep, MessageSecurity.NONE), + //Arguments.of(SecurityPolicy.Aes128_Sha256_RsaOaep, MessageSecurity.NONE), Arguments.of(SecurityPolicy.Aes128_Sha256_RsaOaep, MessageSecurity.SIGN), Arguments.of(SecurityPolicy.Aes128_Sha256_RsaOaep, MessageSecurity.SIGN_ENCRYPT), - Arguments.of(SecurityPolicy.Aes256_Sha256_RsaPss, MessageSecurity.NONE), + //Arguments.of(SecurityPolicy.Aes256_Sha256_RsaPss, MessageSecurity.NONE), Arguments.of(SecurityPolicy.Aes256_Sha256_RsaPss, MessageSecurity.SIGN), Arguments.of(SecurityPolicy.Aes256_Sha256_RsaPss, MessageSecurity.SIGN_ENCRYPT) ); diff --git a/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/protocol/OpcuaSubscriptionHandleTest.java b/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/protocol/OpcuaSubscriptionHandleTest.java index 1845472f4ef..5b853fd74c2 100644 --- a/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/protocol/OpcuaSubscriptionHandleTest.java +++ b/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/protocol/OpcuaSubscriptionHandleTest.java @@ -25,11 +25,10 @@ import org.apache.plc4x.java.api.messages.PlcSubscriptionRequest; import org.apache.plc4x.java.api.messages.PlcSubscriptionResponse; import org.apache.plc4x.java.api.types.PlcResponseCode; +import org.apache.plc4x.java.opcua.MiloTestContainer; import org.apache.plc4x.java.opcua.OpcuaPlcDriverTest; -import org.apache.plc4x.test.DisableInDockerFlag; import org.apache.plc4x.test.DisableOnJenkinsFlag; import org.apache.plc4x.test.DisableOnParallelsVmFlag; -import org.eclipse.milo.examples.server.ExampleServer; import org.junit.jupiter.api.*; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -41,6 +40,11 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.concurrent.TimeUnit; +import org.testcontainers.containers.BindMode; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -52,26 +56,25 @@ // cdutz: I have done way more than my fair share on tracking down this issue and am simply giving up on it. // I tracked it down into the core of Milo several times now, but got lost in there. // It's not a big issue as the GitHub runners and the Apache Jenkins still run the test. -@DisableOnParallelsVmFlag -@DisableInDockerFlag -@DisableOnJenkinsFlag -@Disabled("This test seems to randomly fail on ANY CI platform") +@Testcontainers(disabledWithoutDocker = true) public class OpcuaSubscriptionHandleTest { private static final Logger LOGGER = LoggerFactory.getLogger(OpcuaPlcDriverTest.class); - private static ExampleServer exampleServer; + @Container + public final GenericContainer milo = new MiloTestContainer() + //.withCreateContainerCmdModifier(cmd -> cmd.withHostName("test-opcua-server")) + .withLogConsumer(new Slf4jLogConsumer(LOGGER)) + .withFileSystemBind("target/tmp/server/security", "/tmp/server/security", BindMode.READ_WRITE); // Address of local milo server - private static final String miloLocalAddress = "127.0.0.1:12686/milo"; + private static final String miloLocalAddress = "%s:%d/milo"; //Tcp pattern of OPC UA private static final String opcPattern = "opcua:tcp://"; private final String paramSectionDivider = "?"; private final String paramDivider = "&"; - private static final String tcpConnectionAddress = opcPattern + miloLocalAddress; - // 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"; @@ -89,14 +92,17 @@ public class OpcuaSubscriptionHandleTest { private static final String UINTEGER_IDENTIFIER_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/UInteger"; private static final String DOES_NOT_EXIST_IDENTIFIER_READ_WRITE = "ns=2;i=12512623"; - private static PlcConnection opcuaConnection; + private PlcConnection opcuaConnection; // ! If this test fails, see comment at the top of the class before investigating. - @BeforeAll - public static void setup() throws Exception { + @BeforeEach + public 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. + + String tcpConnectionAddress = String.format(opcPattern + miloLocalAddress, milo.getHost(), milo.getMappedPort(12686)) + "?endpoint-port=12686"; + Path securityBaseDir = Paths.get(System.getProperty("java.io.tmpdir"), "server", "security"); try { Files.delete(securityBaseDir); @@ -104,20 +110,16 @@ public static void setup() throws Exception { // Ignore this ... } - exampleServer = new ExampleServer(); - exampleServer.startup().get(); //Connect opcuaConnection = new DefaultPlcDriverManager().getConnection(tcpConnectionAddress); assertThat(opcuaConnection).extracting(PlcConnection::isConnected).isEqualTo(true); } - @AfterAll - public static void tearDown() throws Exception { + @AfterEach + public void tearDown() throws Exception { // Close Connection opcuaConnection.close(); assertThat(opcuaConnection).extracting(PlcConnection::isConnected).isEqualTo(false); - - exampleServer.shutdown().get(); } // ! If this test fails, see comment at the top of the class before investigating. diff --git a/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/protocol/chunk/ChunkFactoryTest.java b/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/protocol/chunk/ChunkFactoryTest.java index 89c44b75332..a77e6f1fd04 100644 --- a/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/protocol/chunk/ChunkFactoryTest.java +++ b/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/protocol/chunk/ChunkFactoryTest.java @@ -37,8 +37,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvFileSource; -@DisableOnJenkinsFlag -@Disabled("Disabled flaky test. Tracking issue at https://github.com/apache/plc4x/issues/1764") class ChunkFactoryTest { public static final Map> CERTIFICATES = new ConcurrentHashMap<>();