| /* |
| * Copyright 2023 CeresDB Project Authors. Licensed under Apache-2.0. |
| */ |
| package io.ceresdb; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.concurrent.CompletableFuture; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.ForkJoinPool; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| |
| import io.ceresdb.models.Point; |
| import io.ceresdb.models.RequestContext; |
| import io.ceresdb.models.Value; |
| import io.ceresdb.models.WriteRequest; |
| import io.ceresdb.proto.internal.Common; |
| import io.ceresdb.proto.internal.Storage; |
| import org.junit.After; |
| import org.junit.Assert; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.Mock; |
| import org.mockito.Mockito; |
| import org.mockito.runners.MockitoJUnitRunner; |
| |
| import io.ceresdb.common.Endpoint; |
| import io.ceresdb.common.util.Clock; |
| import io.ceresdb.models.Err; |
| import io.ceresdb.models.Result; |
| import io.ceresdb.models.WriteOk; |
| import io.ceresdb.options.WriteOptions; |
| import io.ceresdb.rpc.Context; |
| import io.ceresdb.rpc.Observer; |
| import io.ceresdb.util.StreamWriteBuf; |
| import io.ceresdb.util.TestUtil; |
| import io.ceresdb.util.Utils; |
| |
| @RunWith(value = MockitoJUnitRunner.class) |
| public class WriteClientTest { |
| |
| private WriteClient writeClient; |
| @Mock |
| private RouterClient routerClient; |
| |
| @Before |
| public void before() { |
| final WriteOptions writeOpts = new WriteOptions(); |
| writeOpts.setAsyncPool(ForkJoinPool.commonPool()); |
| writeOpts.setRoutedClient(this.routerClient); |
| writeOpts.setDatabase("public"); |
| |
| this.writeClient = new WriteClient(); |
| this.writeClient.init(writeOpts); |
| } |
| |
| @After |
| public void after() { |
| this.writeClient.shutdownGracefully(); |
| this.routerClient.shutdownGracefully(); |
| } |
| |
| @Test |
| public void writeAllSuccessTest() throws ExecutionException, InterruptedException { |
| final List<Point> data = TestUtil.newMultiTablePoints("write_client_test_table1", // |
| "write_client_test_table2", // |
| "write_client_test_table3"); |
| |
| final Endpoint ep1 = Endpoint.of("127.0.0.1", 8081); |
| final Endpoint ep2 = Endpoint.of("127.0.0.2", 8081); |
| final Endpoint ep3 = Endpoint.of("127.0.0.3", 8081); |
| |
| final Common.ResponseHeader header = Common.ResponseHeader.newBuilder() // |
| .setCode(Result.SUCCESS) // |
| .build(); |
| final Storage.WriteResponse resp = Storage.WriteResponse.newBuilder() // |
| .setHeader(header) // |
| .setSuccess(2) // |
| .build(); |
| Mockito.when(this.routerClient.invoke(Mockito.eq(ep1), Mockito.any(), Mockito.any())) // |
| .thenReturn(Utils.completedCf(resp)); |
| Mockito.when(this.routerClient.invoke(Mockito.eq(ep2), Mockito.any(), Mockito.any())) // |
| .thenReturn(Utils.completedCf(resp)); |
| Mockito.when(this.routerClient.invoke(Mockito.eq(ep3), Mockito.any(), Mockito.any())) // |
| .thenReturn(Utils.completedCf(resp)); |
| Mockito.when(this.routerClient.routeFor(Mockito.any(), Mockito.any())) // |
| .thenReturn(Utils.completedCf(new HashMap<String, Route>() { |
| private static final long serialVersionUID = 4694599978937545735L; |
| |
| { |
| put("write_client_test_table1", Route.of("write_client_test_table1", ep1)); |
| put("write_client_test_table2", Route.of("write_client_test_table2", ep2)); |
| put("write_client_test_table3", Route.of("write_client_test_table3", ep3)); |
| } |
| })); |
| |
| final CompletableFuture<Result<WriteOk, Err>> f = this.writeClient.write(new WriteRequest(data), |
| Context.newDefault()); |
| final Result<WriteOk, Err> ret = f.get(); |
| |
| Assert.assertTrue(ret.isOk()); |
| Assert.assertEquals(new Integer(6), ret.mapOr(0, WriteOk::getSuccess)); |
| Assert.assertEquals(new Integer(0), ret.mapOr(-1, WriteOk::getFailed)); |
| } |
| |
| @Test |
| public void writeSplitTest() throws ExecutionException, InterruptedException { |
| writeSplit(1, 1); |
| writeSplit(2, 2); |
| writeSplit(4, 4); |
| writeSplit(8, 8); |
| } |
| |
| private void writeSplit(final int maxWriteSize, final int partOfSuccess) |
| throws ExecutionException, InterruptedException { |
| // re-init |
| this.writeClient.shutdownGracefully(); |
| final WriteOptions writeOpts = new WriteOptions(); |
| writeOpts.setDatabase("public"); |
| writeOpts.setAsyncPool(ForkJoinPool.commonPool()); |
| writeOpts.setRoutedClient(this.routerClient); |
| writeOpts.setMaxWriteSize(maxWriteSize); |
| this.writeClient = new WriteClient(); |
| this.writeClient.init(writeOpts); |
| |
| final List<Point> data = TestUtil.newMultiTablePoints("write_client_test_table1", // |
| "write_client_test_table2", // |
| "write_client_test_table3", // |
| "write_client_test_table4", // |
| "write_client_test_table5", // |
| "write_client_test_table6", // |
| "write_client_test_table7", // |
| "write_client_test_table8"); |
| |
| final Endpoint ep = Endpoint.of("127.0.0.1", 8081); |
| |
| final Common.ResponseHeader header = Common.ResponseHeader.newBuilder() // |
| .setCode(Result.SUCCESS) // |
| .build(); |
| final Storage.WriteResponse resp = Storage.WriteResponse.newBuilder() // |
| .setHeader(header) // |
| .setSuccess(partOfSuccess) // |
| .build(); |
| Mockito.when(this.routerClient.invoke(Mockito.eq(ep), Mockito.any(), Mockito.any())) // |
| .thenReturn(Utils.completedCf(resp)); |
| Mockito.when(this.routerClient.routeFor(Mockito.any(), Mockito.any())) // |
| .thenReturn(Utils.completedCf(new HashMap<String, Route>() { |
| private static final long serialVersionUID = 4694599978937545735L; |
| |
| { |
| put("write_client_test_table1", Route.of("write_client_test_table1", ep)); |
| put("write_client_test_table2", Route.of("write_client_test_table2", ep)); |
| put("write_client_test_table3", Route.of("write_client_test_table3", ep)); |
| put("write_client_test_table4", Route.of("write_client_test_table4", ep)); |
| put("write_client_test_table5", Route.of("write_client_test_table5", ep)); |
| put("write_client_test_table6", Route.of("write_client_test_table6", ep)); |
| put("write_client_test_table7", Route.of("write_client_test_table7", ep)); |
| put("write_client_test_table8", Route.of("write_client_test_table8", ep)); |
| } |
| })); |
| |
| final CompletableFuture<Result<WriteOk, Err>> f = this.writeClient.write(new WriteRequest(data), |
| Context.newDefault()); |
| final Result<WriteOk, Err> ret = f.get(); |
| |
| Assert.assertTrue(ret.isOk()); |
| Assert.assertEquals(new Integer(16), ret.mapOr(0, WriteOk::getSuccess)); |
| Assert.assertEquals(new Integer(0), ret.mapOr(-1, WriteOk::getFailed)); |
| } |
| |
| @Test |
| public void streamWriteTest() { |
| final String testTable = "stream_table_test"; |
| final Endpoint ep = Endpoint.of("127.0.0.1", 8081); |
| Mockito.when(this.routerClient.routeFor(Mockito.any(), Mockito.eq(Collections.singleton(testTable)))) // |
| .thenReturn(Utils.completedCf(new HashMap<String, Route>() { |
| |
| private static final long serialVersionUID = 8473563130528272901L; |
| |
| { |
| put(testTable, Route.of(testTable, ep)); |
| } |
| })); |
| final CompletableFuture<WriteOk> f = new CompletableFuture<>(); |
| final AtomicInteger dataCount = new AtomicInteger(); |
| Mockito.when(this.routerClient.invokeClientStreaming(Mockito.any(), Mockito.any(Storage.WriteRequest.class), |
| Mockito.any(), Mockito.any())).thenReturn(new Observer<Storage.WriteRequest>() { |
| |
| @Override |
| public void onNext(final Storage.WriteRequest value) { |
| final int c = value.getTableRequestsList().stream() // |
| .flatMap(writeTableRequest -> writeTableRequest.getEntriesList().stream()) // |
| .map(Storage.WriteSeriesEntry::getFieldGroupsCount).reduce(0, Integer::sum); |
| dataCount.addAndGet(c); |
| } |
| |
| @Override |
| public void onError(final Throwable err) { |
| // ignored |
| } |
| |
| @Override |
| public void onCompleted() { |
| f.complete(WriteOk.ok(dataCount.get(), 0, null)); |
| } |
| }); |
| |
| final StreamWriteBuf<Point, WriteOk> writer = this.writeClient.streamWrite(testTable); |
| final CompletableFuture<WriteOk> ret = writer // |
| .write(TestUtil.newTableTwoPoints(testTable)) // |
| .write(TestUtil.newTableTwoPoints(testTable)) // |
| .write(TestUtil.newTableTwoPoints(testTable)) // |
| .flush() // |
| .write(TestUtil.newTableTwoPoints(testTable)) // |
| .flush() // |
| .writeAndFlush(TestUtil.newMultiTablePoints(testTable, testTable)) // |
| .completed(); |
| |
| f.whenComplete((r, e) -> { |
| if (e != null) { |
| ret.completeExceptionally(e); |
| } else { |
| ret.complete(r); |
| } |
| }); |
| |
| Assert.assertEquals(12, ret.join().getSuccess()); |
| } |
| |
| @Test |
| public void rowsToWriteProtoTest() { |
| List<Point> table1 = new ArrayList<>(); |
| table1.add(Point.newPointBuilder("table1") // |
| .setTimestamp(Clock.defaultClock().getTick()).addTag("t1_tag1", Value.withString("v1")) // |
| .addTag("t1_tag2", Value.withString("v2")) // |
| .addTag("t1_tag3", Value.withString("v3")) // |
| .addField("t1_field1", Value.withDouble(0.1)).addField("t1_field2", Value.withString("he")) |
| .addField("t1_field3", Value.withStringOrNull(null)).build()); |
| table1.add(Point.newPointBuilder("table1") // |
| .setTimestamp(Clock.defaultClock().getTick()).addTag("t1_tag1", Value.withString("v1")) // |
| .addTag("t1_tag2", Value.withString("v2")) // |
| .addTag("t1_tag3", Value.withString("v3")) // |
| .addField("t1_field1", Value.withDouble(0.1)).addField("t1_field2", Value.withString("he")) |
| .addField("t1_field3", Value.withStringOrNull("surprise!!!")).build()); |
| |
| List<Point> table2 = new ArrayList<>(); |
| table2.add(Point.newPointBuilder("table2") // |
| .setTimestamp(Clock.defaultClock().getTick()).addTag("t2_tag1", Value.withString("v1")) // |
| .addTag("t2_tag2", Value.withString("v2")) // |
| .addTag("t2_tag3", Value.withString("v3")) // |
| .addField("t2_field1", Value.withDouble(0.1)).addField("t2_field2", Value.withString("he")).build()); |
| table2.add(Point.newPointBuilder("table2").setTimestamp(Clock.defaultClock().getTick() + 10) |
| .addTag("t2_tag1", Value.withString("v1")) // |
| .addTag("t2_tag2", Value.withString("v2")) // |
| .addTag("t2_tag3", Value.withString("v3")) // |
| .addField("t2_field1", Value.withDouble(0.1)).addField("t2_field2", Value.withString("he")) |
| .addField("t2_field3", Value.withStringOrNull("surprise!!!")).build()); |
| |
| final List<Point> table3 = Collections.singletonList(Point.newPointBuilder("table1") |
| .setTimestamp(System.currentTimeMillis()).addTag("t1_tag1", Value.withString("vv1")) // |
| .addTag("t1_tag2", Value.withString("vv2")) // |
| .addField("t1_field1", Value.withDouble(0.1)).addField("t1_field2", Value.withString("he")).build()); |
| |
| RequestContext reqCtx = new RequestContext(); |
| reqCtx.setDatabase("public"); |
| |
| final Storage.WriteRequest writeReq = this.writeClient.toWriteRequestObj(reqCtx, |
| Stream.of(table1, table2, table3).flatMap(List::stream).collect(Collectors.toList()).stream()); |
| |
| Assert.assertNotNull(writeReq); |
| Assert.assertEquals(2, writeReq.getTableRequestsCount()); |
| final Set<String> tables = writeReq.getTableRequestsList() // |
| .stream() // |
| .map(Storage.WriteTableRequest::getTable) // |
| .collect(Collectors.toSet()); |
| Assert.assertEquals(new HashSet<>(Arrays.asList("table1", "table2")), tables); |
| |
| { |
| final Optional<Storage.WriteTableRequest> opl = writeReq.getTableRequestsList().stream() |
| .filter(wm -> wm.getTable().equals("table1")) // |
| .findFirst(); // |
| Assert.assertTrue(opl.isPresent()); |
| |
| final Storage.WriteTableRequest tableRequest = opl.get(); |
| Assert.assertEquals(3, tableRequest.getTagNamesCount()); |
| Assert.assertEquals(3, tableRequest.getFieldNamesCount()); |
| Assert.assertEquals(2, tableRequest.getEntriesCount()); |
| |
| { |
| final Optional<Storage.WriteSeriesEntry> opl2 = tableRequest.getEntriesList().stream().filter(w -> { |
| final Set<String> tagVSet = w.getTagsList().stream().map(tag -> tag.getValue().getStringValue()) |
| .collect(Collectors.toSet()); |
| |
| return tagVSet.contains("v1") && tagVSet.contains("v2") && tagVSet.contains("v3"); |
| }).findFirst(); |
| Assert.assertTrue(opl2.isPresent()); |
| Assert.assertEquals(0, opl2.get().getTags(0).getNameIndex()); |
| Assert.assertEquals(1, opl2.get().getTags(1).getNameIndex()); |
| Assert.assertEquals(2, opl2.get().getTags(2).getNameIndex()); |
| Assert.assertEquals(2, opl2.get().getFieldGroupsCount()); |
| Assert.assertEquals(2, opl2.get().getFieldGroups(0).getFieldsCount()); |
| Assert.assertEquals(3, opl2.get().getFieldGroups(1).getFieldsCount()); |
| } |
| } |
| |
| { |
| final Optional<Storage.WriteTableRequest> opl = writeReq.getTableRequestsList().stream() |
| .filter(wm -> wm.getTable().equals("table2")) // |
| .findFirst(); // |
| Assert.assertTrue(opl.isPresent()); |
| |
| final Storage.WriteTableRequest tableRequest = opl.get(); |
| Assert.assertEquals(3, tableRequest.getTagNamesCount()); |
| Assert.assertEquals(3, tableRequest.getFieldNamesCount()); |
| Assert.assertEquals(1, tableRequest.getEntriesCount()); |
| |
| { |
| final Optional<Storage.WriteSeriesEntry> opl2 = tableRequest.getEntriesList().stream().filter(w -> { |
| final Set<String> tagVSet = w.getTagsList().stream().map(tag -> tag.getValue().getStringValue()) |
| .collect(Collectors.toSet()); |
| |
| return tagVSet.contains("v1") && tagVSet.contains("v2") && tagVSet.contains("v3"); |
| }).findFirst(); |
| Assert.assertTrue(opl2.isPresent()); |
| Assert.assertEquals(0, opl2.get().getTags(0).getNameIndex()); |
| Assert.assertEquals(1, opl2.get().getTags(1).getNameIndex()); |
| Assert.assertEquals(2, opl2.get().getTags(2).getNameIndex()); |
| Assert.assertEquals(2, opl2.get().getFieldGroupsCount()); |
| Assert.assertEquals(2, opl2.get().getFieldGroups(0).getFieldsCount()); |
| Assert.assertEquals(3, opl2.get().getFieldGroups(1).getFieldsCount()); |
| } |
| } |
| } |
| } |