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

@@ -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
}
}
```

@@ -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 hour
Nikita-Smirnov-Exactpro marked this conversation as resolved.
Show resolved Hide resolved

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

### 5.6.0-dev

#### Added:
42 changes: 39 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -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-TH2-5107-+'

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

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

repositories {
@@ -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
@@ -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'
@@ -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'
@@ -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"
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -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
@@ -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),
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
@@ -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(
@@ -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,
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* 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 = 60,
var minMethodRetriesTimeout: Long = 100,
var maxMethodRetriesTimeout: Long = 120_000,
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 {
return retryInterruptedTransaction
}
Nikita-Smirnov-Exactpro marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -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
@@ -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)
@@ -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",
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