Skip to content

Commit c986372

Browse files
committed
test: Add kuksa.val.v2 tests
1 parent 24ac58a commit c986372

File tree

9 files changed

+788
-7
lines changed

9 files changed

+788
-7
lines changed

kuksa-java-sdk/src/main/kotlin/org/eclipse/kuksa/connectivity/databroker/v2/DataBrokerConnectionV2.kt

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ package org.eclipse.kuksa.connectivity.databroker.v2
2121

2222
import io.grpc.ConnectivityState
2323
import io.grpc.ManagedChannel
24+
import io.grpc.stub.StreamObserver
2425
import kotlinx.coroutines.flow.Flow
2526
import org.eclipse.kuksa.connectivity.authentication.JsonWebToken
27+
import org.eclipse.kuksa.connectivity.databroker.DataBrokerException
2628
import org.eclipse.kuksa.connectivity.databroker.DisconnectListener
2729
import org.eclipse.kuksa.connectivity.databroker.v2.request.ActuateRequestV2
2830
import org.eclipse.kuksa.connectivity.databroker.v2.request.BatchActuateRequestV2
@@ -38,6 +40,11 @@ import org.eclipse.kuksa.proto.v2.KuksaValV2
3840
import java.util.logging.Logger
3941
import kotlin.properties.Delegates
4042

43+
/**
44+
* The DataBrokerConnection holds an active connection to the DataBroker. The Connection can be use to interact with the
45+
* DataBroker.
46+
*/
47+
@Suppress("TooManyFunctions") // most methods are simply exposed from transporter layer
4148
class DataBrokerConnectionV2 internal constructor(
4249
private val managedChannel: ManagedChannel,
4350
private val dataBrokerTransporter: DataBrokerTransporterV2 = DataBrokerTransporterV2(managedChannel),
@@ -77,6 +84,8 @@ class DataBrokerConnectionV2 internal constructor(
7784
* The server might respond with the following GRPC error codes:
7885
* NOT_FOUND if the requested signal doesn't exist
7986
* PERMISSION_DENIED if access is denied
87+
*
88+
* @throws DataBrokerException when an error occurs
8089
*/
8190
suspend fun fetchValue(request: FetchValueRequestV2): KuksaValV2.GetValueResponse {
8291
return dataBrokerTransporter.fetchValue(request.signalId)
@@ -89,6 +98,9 @@ class DataBrokerConnectionV2 internal constructor(
8998
* The server might respond with the following GRPC error codes:
9099
* NOT_FOUND if any of the requested signals doesn't exist.
91100
* PERMISSION_DENIED if access is denied for any of the requested signals.
101+
*
102+
* @throws DataBrokerException when an error occurs
103+
*
92104
*/
93105
suspend fun fetchValues(request: FetchValuesRequestV2): KuksaValV2.GetValuesResponse {
94106
return dataBrokerTransporter.fetchValues(request.signalIds)
@@ -99,6 +111,8 @@ class DataBrokerConnectionV2 internal constructor(
99111
* Returns (GRPC error code):
100112
* NOT_FOUND if any of the signals are non-existent.
101113
* PERMISSION_DENIED if access is denied for any of the signals.
114+
*
115+
* @throws DataBrokerException when an error occurs
102116
*/
103117
fun subscribeById(
104118
request: SubscribeByIdRequestV2,
@@ -115,6 +129,8 @@ class DataBrokerConnectionV2 internal constructor(
115129
* When subscribing the Broker shall immediately return the value for all
116130
* subscribed entries. If no value is available when subscribing a DataPoint
117131
* with value None shall be returned.
132+
*
133+
* @throws DataBrokerException when an error occurs
118134
*/
119135
fun subscribe(
120136
request: SubscribeRequestV2,
@@ -132,6 +148,8 @@ class DataBrokerConnectionV2 internal constructor(
132148
* INVALID_ARGUMENT
133149
* - if the data type used in the request does not match the data type of the addressed signal
134150
* - if the requested value is not accepted, e.g. if sending an unsupported enum value
151+
*
152+
* @throws DataBrokerException when an error occurs
135153
*/
136154
suspend fun actuate(request: ActuateRequestV2): KuksaValV2.ActuateResponse {
137155
return dataBrokerTransporter.actuate(request.signalId, request.value)
@@ -150,6 +168,7 @@ class DataBrokerConnectionV2 internal constructor(
150168
* - if the data type used in the request does not match the data type of the addressed signal
151169
* - if the requested value is not accepted, e.g. if sending an unsupported enum value
152170
*
171+
* @throws DataBrokerException when an error occurs
153172
*/
154173
suspend fun batchActuate(request: BatchActuateRequestV2): KuksaValV2.BatchActuateResponse {
155174
return dataBrokerTransporter.batchActuate(request.signalIds, request.value)
@@ -162,6 +181,8 @@ class DataBrokerConnectionV2 internal constructor(
162181
*
163182
* The server might respond with the following GRPC error codes:
164183
* NOT_FOUND if the specified root branch does not exist.
184+
*
185+
* @throws DataBrokerException when an error occurs
165186
*/
166187
suspend fun listMetadata(request: ListMetadataRequestV2): KuksaValV2.ListMetadataResponse {
167188
return dataBrokerTransporter.listMetadata(request.root, request.filter)
@@ -178,6 +199,8 @@ class DataBrokerConnectionV2 internal constructor(
178199
* INVALID_ARGUMENT
179200
* - if the data type used in the request does not match the data type of the addressed signal
180201
* - if the published value is not accepted e.g. if sending an unsupported enum value
202+
*
203+
* @throws DataBrokerException when an error occurs
181204
*/
182205
suspend fun publishValue(
183206
request: PublishValueRequestV2,
@@ -193,17 +216,29 @@ class DataBrokerConnectionV2 internal constructor(
193216
* The open stream is used for request / response type communication between the
194217
* provider and server (where the initiator of a request can vary).
195218
* Errors are communicated as messages in the stream.
219+
*
220+
* @throws DataBrokerException when an error occurs
196221
*/
197222
fun openProviderStream(
198-
streamRequestFlow: Flow<KuksaValV2.OpenProviderStreamRequest>,
199-
): Flow<KuksaValV2.OpenProviderStreamResponse> {
200-
return dataBrokerTransporter.openProviderStream(streamRequestFlow)
223+
responseStream: StreamObserver<KuksaValV2.OpenProviderStreamResponse>,
224+
): StreamObserver<KuksaValV2.OpenProviderStreamRequest> {
225+
return dataBrokerTransporter.openProviderStream(responseStream)
201226
}
202227

203228
/**
204229
* Gets the server information.
230+
*
231+
* @throws DataBrokerException when an error occurs
205232
*/
206233
suspend fun fetchServerInfo(): KuksaValV2.GetServerInfoResponse {
207234
return dataBrokerTransporter.fetchServerInfo()
208235
}
236+
237+
/**
238+
* Disconnect from the DataBroker.
239+
*/
240+
fun disconnect() {
241+
logger.finer("disconnect() called")
242+
managedChannel.shutdownNow()
243+
}
209244
}

kuksa-java-sdk/src/main/kotlin/org/eclipse/kuksa/connectivity/databroker/v2/DataBrokerTransporterV2.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ package org.eclipse.kuksa.connectivity.databroker.v2
2222
import io.grpc.ConnectivityState
2323
import io.grpc.ManagedChannel
2424
import io.grpc.StatusException
25+
import io.grpc.stub.StreamObserver
2526
import kotlinx.coroutines.flow.Flow
2627
import org.eclipse.kuksa.connectivity.authentication.JsonWebToken
2728
import org.eclipse.kuksa.connectivity.databroker.DataBrokerException
2829
import org.eclipse.kuksa.connectivity.databroker.v2.extension.withAuthenticationInterceptor
2930
import org.eclipse.kuksa.proto.v2.KuksaValV2
3031
import org.eclipse.kuksa.proto.v2.Types
3132
import org.eclipse.kuksa.proto.v2.Types.Value
33+
import org.eclipse.kuksa.proto.v2.VALGrpc
3234
import org.eclipse.kuksa.proto.v2.VALGrpcKt
3335
import org.eclipse.kuksa.proto.v2.actuateRequest
3436
import org.eclipse.kuksa.proto.v2.batchActuateRequest
@@ -63,6 +65,7 @@ internal class DataBrokerTransporterV2(
6365
var jsonWebToken: JsonWebToken? = null
6466

6567
private val coroutineStub: VALGrpcKt.VALCoroutineStub = VALGrpcKt.VALCoroutineStub(managedChannel)
68+
private val asyncStub: VALGrpc.VALStub = VALGrpc.newStub(managedChannel)
6669

6770
/**
6871
* Gets the latest value of a [signalId].
@@ -281,12 +284,12 @@ internal class DataBrokerTransporterV2(
281284
* Errors are communicated as messages in the stream.
282285
*/
283286
fun openProviderStream(
284-
streamRequestFlow: Flow<KuksaValV2.OpenProviderStreamRequest>,
285-
): Flow<KuksaValV2.OpenProviderStreamResponse> {
287+
responseStream: StreamObserver<KuksaValV2.OpenProviderStreamResponse>,
288+
): StreamObserver<KuksaValV2.OpenProviderStreamRequest> {
286289
return try {
287-
coroutineStub
290+
asyncStub
288291
.withAuthenticationInterceptor(jsonWebToken)
289-
.openProviderStream(streamRequestFlow)
292+
.openProviderStream(responseStream)
290293
} catch (e: StatusException) {
291294
throw DataBrokerException(e.message, e)
292295
}

kuksa-java-sdk/src/main/kotlin/org/eclipse/kuksa/connectivity/databroker/v2/extension/VALStubExtension.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import io.grpc.ClientInterceptor
2424
import io.grpc.Metadata
2525
import io.grpc.stub.MetadataUtils
2626
import org.eclipse.kuksa.connectivity.authentication.JsonWebToken
27+
import org.eclipse.kuksa.proto.v2.VALGrpc
2728
import org.eclipse.kuksa.proto.v2.VALGrpcKt
2829

2930
internal fun VALGrpcKt.VALCoroutineStub.withAuthenticationInterceptor(
@@ -35,6 +36,15 @@ internal fun VALGrpcKt.VALCoroutineStub.withAuthenticationInterceptor(
3536
return withInterceptors(authenticationInterceptor)
3637
}
3738

39+
internal fun VALGrpc.VALStub.withAuthenticationInterceptor(
40+
jsonWebToken: JsonWebToken?,
41+
): VALGrpc.VALStub {
42+
if (jsonWebToken == null) return this
43+
44+
val authenticationInterceptor = clientInterceptor(jsonWebToken)
45+
return withInterceptors(authenticationInterceptor)
46+
}
47+
3848
private fun clientInterceptor(jsonWebToken: JsonWebToken): ClientInterceptor? {
3949
val authorizationHeader = Metadata.Key.of(HttpHeaders.AUTHORIZATION, Metadata.ASCII_STRING_MARSHALLER)
4050

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright (c) 2023 - 2025 Contributors to the Eclipse Foundation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://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+
* SPDX-License-Identifier: Apache-2.0
17+
*
18+
*/
19+
20+
package org.eclipse.kuksa.connectivity.databroker.provider
21+
22+
import io.grpc.ChannelCredentials
23+
import io.grpc.Grpc
24+
import io.grpc.ManagedChannel
25+
import io.grpc.ManagedChannelBuilder
26+
import io.grpc.TlsChannelCredentials
27+
import org.eclipse.kuksa.connectivity.authentication.JsonWebToken
28+
import org.eclipse.kuksa.connectivity.databroker.DATABROKER_HOST
29+
import org.eclipse.kuksa.connectivity.databroker.DATABROKER_TIMEOUT_SECONDS
30+
import org.eclipse.kuksa.connectivity.databroker.DATABROKER_TIMEOUT_UNIT
31+
import org.eclipse.kuksa.connectivity.databroker.docker.DEFAULT_PORT_INSECURE
32+
import org.eclipse.kuksa.connectivity.databroker.docker.DEFAULT_PORT_SECURE
33+
import org.eclipse.kuksa.connectivity.databroker.v2.DataBrokerConnectorV2
34+
import org.eclipse.kuksa.mocking.JwtType
35+
import org.eclipse.kuksa.model.TimeoutConfig
36+
import org.eclipse.kuksa.test.TestResourceFile
37+
import java.io.IOException
38+
import java.io.InputStream
39+
40+
class DataBrokerConnectorV2Provider {
41+
lateinit var managedChannel: ManagedChannel
42+
43+
fun createInsecure(
44+
host: String = DATABROKER_HOST,
45+
port: Int = DEFAULT_PORT_INSECURE,
46+
jwtFileStream: InputStream? = null,
47+
): DataBrokerConnectorV2 {
48+
managedChannel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build()
49+
50+
val jsonWebToken = jwtFileStream?.let {
51+
val token = it.reader().readText()
52+
JsonWebToken(token)
53+
}
54+
55+
return DataBrokerConnectorV2(
56+
managedChannel,
57+
jsonWebToken,
58+
).apply {
59+
timeoutConfig = TimeoutConfig(DATABROKER_TIMEOUT_SECONDS, DATABROKER_TIMEOUT_UNIT)
60+
}
61+
}
62+
63+
fun createSecure(
64+
host: String = DATABROKER_HOST,
65+
port: Int = DEFAULT_PORT_SECURE,
66+
overrideAuthority: String = "",
67+
rootCertFileStream: InputStream = TestResourceFile("tls/CA.pem").inputStream(),
68+
jwtFileStream: InputStream? = JwtType.READ_WRITE_ALL.asInputStream(),
69+
): DataBrokerConnectorV2 {
70+
val tlsCredentials: ChannelCredentials
71+
try {
72+
tlsCredentials = TlsChannelCredentials.newBuilder()
73+
.trustManager(rootCertFileStream)
74+
.build()
75+
} catch (_: IOException) {
76+
// Handle error
77+
throw IOException("Could not create TLS credentials")
78+
}
79+
80+
val channelBuilder = Grpc
81+
.newChannelBuilderForAddress(host, port, tlsCredentials)
82+
83+
val hasOverrideAuthority = overrideAuthority.isNotEmpty()
84+
if (hasOverrideAuthority) {
85+
channelBuilder.overrideAuthority(overrideAuthority)
86+
}
87+
88+
managedChannel = channelBuilder.build()
89+
90+
val jsonWebToken = jwtFileStream?.let {
91+
val token = it.reader().readText()
92+
JsonWebToken(token)
93+
}
94+
95+
return DataBrokerConnectorV2(
96+
managedChannel,
97+
jsonWebToken,
98+
).apply {
99+
timeoutConfig = TimeoutConfig(DATABROKER_TIMEOUT_SECONDS, DATABROKER_TIMEOUT_UNIT)
100+
}
101+
}
102+
}

0 commit comments

Comments
 (0)