Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Th2 5107] User can't change gRPC retry configuration via schema #282

Merged
merged 9 commits into from
Oct 30, 2023
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# th2 common library (Java) (5.6.0)
# th2 common library (Java) (5.7.0)

## Usage

Expand Down Expand Up @@ -192,12 +192,22 @@ The `CommonFactory` reads a gRPC router configuration from the `grpc_router.json
* maxMessageSize - this option enables endpoint message filtering based on message size (message with size larger than
option value will be skipped). By default, it has a value of `4 MB`. The unit of measurement of the value is number of
bytes.
* retryConfiguration - this settings aria is responsible for how a component executes gRPC retries before gives up with exception.
Component executes request attempts with growing timeout between them until success or attempts over
* maxAttempts - number of attempts before give up
* minMethodRetriesTimeout - minimal timeout between retry in milliseconds
* maxMethodRetriesTimeout - maximum timeout between retry in milliseconds

```json
{
"enableSizeMeasuring": false,
"keepAliveInterval": 60,
"maxMessageSize": 4194304
"maxMessageSize": 4194304,
"retryConfiguration": {
"maxAttempts": 60,
"minMethodRetriesTimeout": 100,
"maxMethodRetriesTimeout": 120000
}
}
```

Expand Down Expand Up @@ -491,6 +501,15 @@ dependencies {

## Release notes

### 5.7.0-dev

#### Fix:
+ gRPC `retryConfiguration` has been moved from grpc.json to grpc_router.json
+ the whole default gRPC retry interval is about 1 minute

#### Updated:
+ grpc-service-generator: `3.5.0`

### 5.6.0-dev

#### Added:
Expand Down
42 changes: 39 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,28 @@ plugins {
id 'maven-publish'
id "io.github.gradle-nexus.publish-plugin" version "1.0.0"
id 'signing'
id 'com.google.protobuf' version '0.8.8' apply false
id 'org.jetbrains.kotlin.jvm' version "${kotlin_version}"
id 'org.jetbrains.kotlin.kapt' version "${kotlin_version}"
id "org.owasp.dependencycheck" version "8.3.1"
id "me.champeau.jmh" version "0.6.8"
id "com.gorylenko.gradle-git-properties" version "2.4.1"
id 'com.github.jk1.dependency-license-report' version '2.5'
id "de.undercouch.download" version "5.4.0"
id "com.google.protobuf" version "0.9.4"
}

group = 'com.exactpro.th2'
version = release_version

ext {
grpcVersion = '1.56.0'
protobufVersion = '3.23.2' // The protoc:3.23.3 https://github.com/protocolbuffers/protobuf/issues/13070
serviceGeneratorVersion = '3.5.0'

cradleVersion = '5.1.1-dev'
junitVersion = '5.10.0'

genBaseDir = file("${buildDir}/generated/source/proto")
}

repositories {
Expand Down Expand Up @@ -93,7 +99,7 @@ tasks.withType(Sign).configureEach {
}
// disable running task 'initializeSonatypeStagingRepository' on a gitlab
tasks.configureEach { task ->
if (task.name.equals('initializeSonatypeStagingRepository') &&
if (task.name == 'initializeSonatypeStagingRepository' &&
!(project.hasProperty('sonatypeUsername') && project.hasProperty('sonatypePassword'))
) {
task.enabled = false
Expand Down Expand Up @@ -189,7 +195,7 @@ dependencies {
jmh 'org.openjdk.jmh:jmh-generator-annprocess:0.9'

implementation 'com.google.protobuf:protobuf-java-util'
implementation 'com.exactpro.th2:grpc-service-generator:3.4.0'
implementation "com.exactpro.th2:grpc-service-generator:${serviceGeneratorVersion}"
implementation "com.exactpro.th2:cradle-cassandra:${cradleVersion}"

def autoValueVersion = '1.10.1'
Expand Down Expand Up @@ -253,6 +259,7 @@ dependencies {

implementation 'io.github.microutils:kotlin-logging:3.0.0' // The last version bases on kotlin 1.6.0

testImplementation 'javax.annotation:javax.annotation-api:1.3.2'
testImplementation "org.junit.jupiter:junit-jupiter:${junitVersion}"
testImplementation 'org.mockito.kotlin:mockito-kotlin:4.0.0'
testImplementation 'org.jetbrains.kotlin:kotlin-test-junit5'
Expand Down Expand Up @@ -282,8 +289,37 @@ jar {

sourceSets {
main.kotlin.srcDirs += "src/main/kotlin"
test.resources.srcDirs += "$genBaseDir/test/services/java/resources"
}

protobuf {
protoc {
artifact = "com.google.protobuf:protoc:${protobufVersion}"
}
plugins {
grpc {
artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}"
}
services {
artifact = "com.exactpro.th2:grpc-service-generator:${serviceGeneratorVersion}:all@jar"
}
}
generateProtoTasks {
all()*.plugins {
grpc {}
services {
option 'javaInterfacesPath=./java/src'
option 'javaInterfacesImplPath=./java/src'
option 'javaMetaInfPath=./java/resources'
}
}
ofSourceSet('test')
}
}

compileTestJava.dependsOn.add('generateTestProto')
processTestResources.dependsOn.add('generateTestProto')

tasks.withType(KotlinCompile).configureEach {
kotlinOptions.jvmTarget = "11"
kotlinOptions.freeCompilerArgs += "-Xjvm-default=all"
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
release_version=5.6.0
release_version=5.7.0
description='th2 common library (Java)'
vcs_url=https://github.com/th2-net/th2-common-j
kapt.include.compile.classpath=false
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public <T> T getService(@NotNull Class<T> cls) {
try {
return th2ImplClass.getConstructor(RetryPolicy.class, StubStorage.class)
.newInstance(
configuration.getRetryConfiguration(),
routerConfiguration.getRetryConfiguration(),
stubsStorages.computeIfAbsent(cls, key ->
new DefaultStubStorage<>(getServiceConfig(key),
createGetMetric(GRPC_INVOKE_CALL_TOTAL, GRPC_INVOKE_CALL_MAP),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2022 Exactpro (Exactpro Systems Limited)
* Copyright 2020-2023 Exactpro (Exactpro Systems Limited)
* Licensed 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
Expand All @@ -18,15 +18,13 @@ package com.exactpro.th2.common.schema.grpc.configuration
import com.exactpro.th2.common.schema.configuration.Configuration
import com.exactpro.th2.common.schema.message.configuration.FieldFilterConfiguration
import com.exactpro.th2.common.schema.strategy.route.RoutingStrategy
import com.exactpro.th2.service.RetryPolicy
import com.fasterxml.jackson.annotation.JsonProperty
import io.grpc.internal.GrpcUtil

data class GrpcConfiguration(
@JsonProperty var services: Map<String, GrpcServiceConfiguration> = emptyMap(),
@JsonProperty(value = "server") var serverConfiguration: GrpcServerConfiguration = GrpcServerConfiguration(),
@JsonProperty var maxMessageSize: Int = GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE,
@JsonProperty var retryConfiguration: GrpcRetryConfiguration = GrpcRetryConfiguration(),
) : Configuration()

data class GrpcServiceConfiguration(
Expand All @@ -46,20 +44,6 @@ data class GrpcEndpointConfiguration(
var attributes: List<String?> = emptyList(),
) : Configuration()

data class GrpcRetryConfiguration(
private var maxAttempts: Int = 5,
var minMethodRetriesTimeout: Long = 100,
var maxMethodRetriesTimeout: Long = 2000
) : Configuration(), RetryPolicy {
override fun getDelay(index: Int): Long =
(minMethodRetriesTimeout + if (maxAttempts > 1) (maxMethodRetriesTimeout - minMethodRetriesTimeout) / (maxAttempts - 1) * index else 0)

override fun getMaxAttempts(): Int = maxAttempts
fun setMaxAttempts(maxAttempts: Int) {
this.maxAttempts = maxAttempts
}
}

data class GrpcServerConfiguration(
var host: String? = "localhost",
@JsonProperty(required = true) var port: Int = 8080,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2020-2023 Exactpro (Exactpro Systems Limited)
* Licensed 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 com.exactpro.th2.common.schema.grpc.configuration

import com.exactpro.th2.common.schema.configuration.Configuration
import com.exactpro.th2.service.RetryPolicy

data class GrpcRetryConfiguration(
private var maxAttempts: Int = 15,
var minMethodRetriesTimeout: Long = 100,
var maxMethodRetriesTimeout: Long = 7000,
var retryInterruptedTransaction: Boolean = false,
) : Configuration(), RetryPolicy {

init {
require(maxAttempts >= 0) {
"'max attempts' of ${javaClass.simpleName} class must be 0 or positive"
}
require(minMethodRetriesTimeout >= 0) {
"'min method retries timeout' of ${javaClass.simpleName} class must be 0 or positive"
}
require(maxMethodRetriesTimeout >= 0) {
"'max method retries timeout' of ${javaClass.simpleName} class must be 0 or positive"
}
require(maxMethodRetriesTimeout >= minMethodRetriesTimeout) {
"'max method retries timeout' of ${javaClass.simpleName} class must be greater of equal 'min method retries timeout'"
}
}

override fun getDelay(index: Int): Long {
val attempt = if (index > 0) {
if (index > maxAttempts) maxAttempts else index
} else { 0 }
var increment = 0L
if (maxAttempts > 1) {
increment = (maxMethodRetriesTimeout - minMethodRetriesTimeout) / (maxAttempts - 1) * attempt
}

return minMethodRetriesTimeout + increment
}

override fun getMaxAttempts(): Int = maxAttempts

override fun retryInterruptedTransaction(): Boolean = retryInterruptedTransaction
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ data class GrpcRouterConfiguration(
var enableSizeMeasuring: Boolean = false,
var keepAliveInterval: Long = 60L,
var maxMessageSize: Int = GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE,
var retryConfiguration: GrpcRetryConfiguration = GrpcRetryConfiguration(),
) : Configuration()
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ class TestJsonConfiguration {
testDeserialize(GRPC_CONF_JSON, GRPC_CONF)
}

@Test
fun `test grpc router json configuration deserialize`() {
testDeserialize(GRPC_ROUTER_CONF_JSON, GRPC_ROUTER_CONF)
}

@Test
fun `test grpc json configuration serialize and deserialize`() {
testSerializeAndDeserialize(GRPC_CONF)
Expand Down Expand Up @@ -143,6 +148,18 @@ class TestJsonConfiguration {
GrpcServerConfiguration("host123", 1234, 58),
)

private val GRPC_ROUTER_CONF_JSON = loadConfJson("grpc_router")
private val GRPC_ROUTER_CONF = GrpcRouterConfiguration(
true,
61,
4194305,
GrpcRetryConfiguration(
61,
101,
120001
)
)

private val RABBITMQ_CONF_JSON = loadConfJson("rabbitMq")
private val RABBITMQ_CONF = RabbitMQConfiguration(
"host",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2023 Exactpro (Exactpro Systems Limited)
* Licensed 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 com.exactpro.th2.common.schema.grpc.configuration

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.CsvSource

class GrpcRetryConfigurationTest {

@ParameterizedTest
@CsvSource(
"0,0,0",
"0,0,100",
"0,100,100",
"0,100,2000",
"1,0,0",
"1,0,100",
"1,100,100",
"1,100,2000",
"100,0,0",
"100,0,100",
"100,100,100",
"100,100,2000",
)
fun `get delay test`(maxAttempts: String, minTimeout: String, maxTimeout: String) {
val retryPolicy = GrpcRetryConfiguration(maxAttempts.toInt(), minTimeout.toLong(), maxTimeout.toLong())

for (attempt in -1..retryPolicy.getMaxAttempts() + 1) {
val delay = retryPolicy.getDelay(attempt)
when {
attempt <= 0 -> {
assertEquals(retryPolicy.minMethodRetriesTimeout, delay) {
"Check minimum equality, delay: $delay, attempt: $attempt"
}
}
attempt >= retryPolicy.maxMethodRetriesTimeout -> {
assertEquals(retryPolicy.maxMethodRetriesTimeout, delay) {
"Check maximum equality, delay: $delay, attempt: $attempt"
}
}
else -> {
assertTrue(delay >= retryPolicy.minMethodRetriesTimeout) {
"Check minimum limit, delay: $delay, attempt: $attempt"
}
assertTrue(delay <= retryPolicy.maxMethodRetriesTimeout) {
"Check maximum limit, delay: $delay, attempt: $attempt"
}
}
}
}
}

@ParameterizedTest
@CsvSource(
"-1,0,0",
"0,-1,0",
"0,0,-1",
"0,1,0",
)
fun `negative test`(maxAttempts: String, minTimeout: String, maxTimeout: String) {
assertThrows(IllegalArgumentException::class.java) {
GrpcRetryConfiguration(maxAttempts.toInt(), minTimeout.toLong(), maxTimeout.toLong())
}
}
}
Loading
Loading