solution: upstream apis as a controller Publisher

This commit is contained in:
Igor Artamonov
2019-09-06 00:15:10 -04:00
parent cdd9e6bf5f
commit 706f472355
17 changed files with 443 additions and 203 deletions

View File

@@ -113,7 +113,7 @@ class NativeCallSpec extends Specification {
nativeCall.executeOnRemote(call).block(Duration.ofSeconds(2))
def delta = System.currentTimeMillis() - t1
then:
delta >= 200
delta >= 100
}
def "One call has no pause"() {

View File

@@ -0,0 +1,76 @@
/**
* Copyright (c) 2019 ETCDEV GmbH
*
* 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 io.emeraldpay.dshackle.test
import com.fasterxml.jackson.databind.ObjectMapper
import io.emeraldpay.dshackle.upstream.CallMethods
import io.emeraldpay.dshackle.upstream.DirectCallMethods
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
import io.infinitape.etherjar.rpc.Batch
import io.infinitape.etherjar.rpc.ExecutableBatch
import io.infinitape.etherjar.rpc.RpcCall
import io.infinitape.etherjar.rpc.RpcClient
import io.infinitape.etherjar.rpc.transport.BatchStatus
import java.util.concurrent.CompletableFuture
class EthereumApiStub extends DirectEthereumApi {
private String id
private static ObjectMapper objectMapper = TestingCommons.objectMapper()
private static RpcClient rpcClient = new RpcClientMock();
EthereumApiStub(Integer id) {
this(id.toString())
}
EthereumApiStub(String id) {
super(rpcClient, objectMapper, new DirectCallMethods())
this.id = id
}
@Override
String toString() {
return "API Stub $id"
}
static class RpcClientMock implements RpcClient {
@Override
CompletableFuture<BatchStatus> execute(Batch batch) {
return null
}
@Override
def <RES> CompletableFuture<RES> execute(RpcCall<?, RES> call) {
return null
}
@Override
ExecutableBatch batch() {
return null
}
@Override
EthCommands eth() {
return null
}
@Override
TraceCommands trace() {
return null
}
}
}

View File

@@ -0,0 +1,182 @@
/**
* Copyright (c) 2019 ETCDEV GmbH
*
* 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 io.emeraldpay.dshackle.upstream
import io.emeraldpay.dshackle.config.UpstreamsConfig
import io.emeraldpay.dshackle.test.EthereumApiStub
import io.emeraldpay.dshackle.test.TestingCommons
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
import io.emeraldpay.dshackle.upstream.ethereum.EthereumWs
import io.emeraldpay.grpc.Chain
import io.infinitape.etherjar.rpc.DefaultRpcClient
import io.infinitape.etherjar.rpc.RpcClient
import reactor.test.StepVerifier
import spock.lang.Retry
import spock.lang.Specification
import java.time.Duration
class FilteredApisSpec extends Specification {
def rpcClient = new DefaultRpcClient(null)
def objectMapper = TestingCommons.objectMapper()
def ethereumTargets = new QuorumBasedMethods(objectMapper, Chain.ETHEREUM)
def "Verifies labels"() {
setup:
List<EthereumUpstream> upstreams = [
[test: "foo"],
[test: "bar"],
[test: "foo", test2: "baz"],
[test: "foo"],
[test: "baz"]
].collect {
new EthereumUpstream(
"test",
Chain.ETHEREUM,
new DirectEthereumApi(rpcClient, objectMapper, ethereumTargets),
(EthereumWs) null,
new UpstreamsConfig.Options(),
new NodeDetailsList.NodeDetails(1, UpstreamsConfig.Labels.fromMap(it)),
ethereumTargets
)
}
def matcher = new Selector.LabelMatcher("test", ["foo"])
upstreams.forEach {
it.setLag(0)
it.setStatus(UpstreamAvailability.OK)
}
when:
def iter = new FilteredApis(upstreams, matcher, 0, 1, 0)
iter.request(10)
then:
StepVerifier.create(iter)
.expectNext(upstreams[0].api)
.expectNext(upstreams[2].api)
.expectNext(upstreams[3].api)
.expectComplete()
.verify(Duration.ofSeconds(1))
when:
iter = new FilteredApis(upstreams, matcher, 1, 1, 0)
iter.request(10)
then:
StepVerifier.create(iter)
.expectNext(upstreams[2].api)
.expectNext(upstreams[3].api)
.expectNext(upstreams[0].api)
.expectComplete()
.verify(Duration.ofSeconds(1))
when:
iter = new FilteredApis(upstreams, matcher, 1, 2, 0)
iter.request(10)
then:
StepVerifier.create(iter)
.expectNext(upstreams[2].api)
.expectNext(upstreams[3].api)
.expectNext(upstreams[0].api)
.expectNext(upstreams[2].api)
.expectNext(upstreams[3].api)
.expectNext(upstreams[0].api)
.expectComplete()
.verify(Duration.ofSeconds(1))
}
def "Exponential backoff"() {
setup:
def apis = new FilteredApis([], Selector.empty, 0, 1, 0)
expect:
wait == apis.waitDuration(n).toMillis() as Integer
where:
n | wait
0 | 100
1 | 100
2 | 400
3 | 900
4 | 1600
5 | 2500
6 | 3600
7 | 4900
8 | 5000
9 | 5000
10 | 5000
-1 | 100
}
@Retry
def "Backoff uses jitter"() {
setup:
def apis = new FilteredApis([], Selector.empty, 0, 1, 20)
when:
def act = apis.waitDuration(1).toMillis()
println act
then:
act >= 80
act <= 120
act != 100
when:
act = apis.waitDuration(3).toMillis()
println act
then:
act >= 900 - 9 * 20
act <= 900 + 9 * 20
act != 900
}
def "Makes pause between batches"() {
when:
def api1 = TestingCommons.api(Stub(RpcClient))
def api2 = TestingCommons.api(Stub(RpcClient))
def up1 = TestingCommons.upstream(api1)
def up2 = TestingCommons.upstream(api2)
then:
StepVerifier.withVirtualTime({
def apis = new FilteredApis([up1, up2], Selector.empty, 0, 4, 0)
apis.request(10)
return apis
})
.expectNext(api1, api2).as("Batch 1")
.expectNoEvent(Duration.ofMillis(100)).as("Wait 1")
.expectNext(api1, api2).as("Batch 2")
.expectNoEvent(Duration.ofMillis(400)).as("Wait 2")
.expectNext(api1, api2).as("Batch 3")
.expectNoEvent(Duration.ofMillis(900)).as("Wait 3")
.expectNext(api1, api2).as("Batch 4")
.expectComplete()
.verify(Duration.ofSeconds(10))
}
def "Starts with right position"() {
setup:
def apis = (0..5).collect {
new EthereumApiStub(it)
}
def ups = apis.collect {
TestingCommons.upstream(it)
}
when:
def act = new FilteredApis(ups, Selector.empty, 2, 1, 0)
act.request(10)
then:
StepVerifier.create(act)
.expectNext(apis[2], apis[3], apis[4], apis[5], apis[0], apis[1])
.expectComplete()
.verify(Duration.ofSeconds(1))
}
}

View File

@@ -1,96 +0,0 @@
/**
* Copyright (c) 2019 ETCDEV GmbH
*
* 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 io.emeraldpay.dshackle.upstream
import io.emeraldpay.dshackle.config.UpstreamsConfig
import io.emeraldpay.dshackle.test.TestingCommons
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
import io.emeraldpay.dshackle.upstream.ethereum.EthereumWs
import io.emeraldpay.grpc.Chain
import io.infinitape.etherjar.rpc.DefaultRpcClient
import spock.lang.Specification
class FilteringApiIteratorSpec extends Specification {
def rpcClient = new DefaultRpcClient(null)
def objectMapper = TestingCommons.objectMapper()
def ethereumTargets = new QuorumBasedMethods(objectMapper, Chain.ETHEREUM)
def "Verifies labels"() {
setup:
List<EthereumUpstream> upstreams = [
[test: "foo"],
[test: "bar"],
[test: "foo", test2: "baz"],
[test: "foo"],
[test: "baz"]
].collect {
new EthereumUpstream(
"test",
Chain.ETHEREUM,
new DirectEthereumApi(rpcClient, objectMapper, ethereumTargets),
(EthereumWs) null,
new UpstreamsConfig.Options(),
new NodeDetailsList.NodeDetails(1, UpstreamsConfig.Labels.fromMap(it)),
ethereumTargets
)
}
def matcher = new Selector.LabelMatcher("test", ["foo"])
upstreams.forEach {
it.setLag(0)
it.setStatus(UpstreamAvailability.OK)
}
when:
def iter = new FilteringApiIterator(upstreams, 0, matcher, 1)
then:
iter.hasNext()
iter.next() == upstreams[0].api
iter.hasNext()
iter.next() == upstreams[2].api
iter.hasNext()
iter.next() == upstreams[3].api
!iter.hasNext()
when:
iter = new FilteringApiIterator(upstreams, 1, matcher, 1)
then:
iter.hasNext()
iter.next() == upstreams[2].api
iter.hasNext()
iter.next() == upstreams[3].api
iter.hasNext()
iter.next() == upstreams[0].api
!iter.hasNext()
when:
iter = new FilteringApiIterator(upstreams, 1, matcher, 2)
then:
iter.hasNext()
iter.next() == upstreams[2].api
iter.hasNext()
iter.next() == upstreams[3].api
iter.hasNext()
iter.next() == upstreams[0].api
iter.hasNext()
iter.next() == upstreams[2].api
iter.hasNext()
iter.next() == upstreams[3].api
iter.hasNext()
iter.next() == upstreams[0].api
!iter.hasNext()
}
}