├── .gitignore
├── Grpc.Gcp
├── Common.csproj.include
├── Grpc.Gcp.Benchmark
│ ├── BigtableBenchmark.cs
│ ├── Grpc.Gcp.Benchmark.csproj
│ └── Program.cs
├── Grpc.Gcp.IntegrationTest
│ ├── BigtableTest.cs
│ ├── Grpc.Gcp.IntegrationTest.csproj
│ ├── LocalServiceTest.cs
│ ├── SpannerTest.cs
│ ├── TestService.cs
│ ├── TestServiceGrpc.cs
│ └── spanner.grpc.config
├── Grpc.Gcp.sln
├── Grpc.Gcp
│ ├── AssemblyInfo.cs
│ ├── ChannelRef.cs
│ ├── GcpCallInvoker.cs
│ ├── GcpClientResponseStream.cs
│ ├── Grpc.Gcp.csproj
│ └── GrpcGcp.cs
└── keys
│ ├── Grpc.Gcp.public.snk
│ └── Grpc.Gcp.snk
├── LICENSE
├── README.md
├── cloudprober
├── bins
│ ├── .DS_Store
│ └── opt
│ │ ├── .DS_Store
│ │ └── grpc_csharp_plugin
├── cloudprober.cfg
├── codegen.sh
└── grpc_gcp_prober
│ ├── firestore_probes.cs
│ ├── grpc_gcp_prober.csproj
│ ├── prober.cs
│ ├── probetests_base.cs
│ ├── spanner_probes.cs
│ └── stackdriver_util.cs
├── codegen.bat
├── doc
├── grpc-client-user-guide.md
└── grpc-firestore-example.md
├── firestore
└── examples
│ └── end2end
│ ├── doc
│ └── .gitignore
│ └── src
│ ├── .gitignore
│ ├── BatchGetDocuments.cs
│ ├── BeginTransaction.cs
│ ├── CommitTransaction.cs
│ ├── CreateDocument.cs
│ ├── CreateIndex.cs
│ ├── DeleteDocument.cs
│ ├── DeleteIndex.cs
│ ├── FSWrite.cs
│ ├── GetDocument.cs
│ ├── GetIndex.cs
│ ├── ListCollectionIds.cs
│ ├── ListDocuments.cs
│ ├── ListIndexes.cs
│ ├── Program.cs
│ ├── Rollback.cs
│ ├── RunQuery.cs
│ ├── UpdateDocument.cs
│ ├── Utils.cs
│ └── Write.cs
└── protos
├── grpc_gcp.proto
└── test_service.proto
/.gitignore:
--------------------------------------------------------------------------------
1 | .vs
2 | .vscode
3 | packages
4 | bin
5 | obj
6 |
--------------------------------------------------------------------------------
/Grpc.Gcp/Common.csproj.include:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | /usr/lib/mono/4.5-api
6 | /usr/local/lib/mono/4.5-api
7 | /Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.5-api
8 |
9 |
--------------------------------------------------------------------------------
/Grpc.Gcp/Grpc.Gcp.Benchmark/BigtableBenchmark.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading;
4 | using Google.Apis.Auth.OAuth2;
5 | using Google.Cloud.Bigtable.V2;
6 | using Google.Protobuf;
7 | using Grpc.Auth;
8 | using Grpc.Core;
9 | using Microsoft.Extensions.CommandLineUtils;
10 |
11 | namespace Grpc.Gcp.Benchmark
12 | {
13 | class BigtableBenchmark
14 | {
15 | private const string Target = "bigtable.googleapis.com";
16 | private const string TableName = "projects/grpc-gcp/instances/test-instance/tables/test-table";
17 | private const string RowKey = "test-row";
18 | private const string TestValue = "test-value";
19 | private const string ColumnFamily = "test-cf";
20 | private const string ColumnQualifier = "test-cq";
21 | private const string LargeRowKey = "large-row";
22 | private const Int32 PayloadBytes = 10000000;
23 | private const Int32 DefaultMaxChannelsPerTarget = 10;
24 | private ApiConfig config = new ApiConfig();
25 | private Bigtable.BigtableClient client;
26 | private int numStreamCalls;
27 | private bool useGcp;
28 |
29 | public BigtableBenchmark(int numStreamCalls, bool gcp) {
30 | this.numStreamCalls = numStreamCalls;
31 | this.useGcp = gcp;
32 | if (gcp) {
33 | InitGcpClient();
34 | } else {
35 | InitDefaultClient();
36 | }
37 | }
38 |
39 | private void InitGcpClient()
40 | {
41 | InitApiConfig(100, 10);
42 | GoogleCredential credential = GoogleCredential.GetApplicationDefault();
43 | IList options = new List() {
44 | new ChannelOption(GcpCallInvoker.ApiConfigChannelArg, config.ToString()) };
45 | var invoker = new GcpCallInvoker(Target, credential.ToChannelCredentials(), options);
46 | client = new Bigtable.BigtableClient(invoker);
47 | }
48 |
49 | private void InitDefaultClient()
50 | {
51 | GoogleCredential credential = GoogleCredential.GetApplicationDefault();
52 | var channel = new Channel(Target, credential.ToChannelCredentials());
53 | client = new Bigtable.BigtableClient(channel);
54 | }
55 |
56 | private void InitApiConfig(uint maxConcurrentStreams, uint maxSize)
57 | {
58 | config.ChannelPool = new ChannelPoolConfig();
59 | config.ChannelPool.MaxConcurrentStreamsLowWatermark = maxConcurrentStreams;
60 | config.ChannelPool.MaxSize = maxSize;
61 | }
62 |
63 | private void PrepareTestData()
64 | {
65 | MutateRowRequest mutateRowRequest = new MutateRowRequest
66 | {
67 | TableName = TableName,
68 | RowKey = ByteString.CopyFromUtf8(LargeRowKey)
69 | };
70 |
71 | string largeValue = new string('x', PayloadBytes);
72 |
73 | Mutation mutation = new Mutation
74 | {
75 | SetCell = new Mutation.Types.SetCell
76 | {
77 | FamilyName = ColumnFamily,
78 | ColumnQualifier = ByteString.CopyFromUtf8(ColumnQualifier),
79 | Value = ByteString.CopyFromUtf8(largeValue),
80 | }
81 | };
82 |
83 | mutateRowRequest.Mutations.Add(mutation);
84 | client.MutateRow(mutateRowRequest);
85 | }
86 |
87 | public void RunMaxConcurrentStreams()
88 | {
89 | PrepareTestData();
90 |
91 | var calls = new List>();
92 |
93 | for (int i = 0; i < numStreamCalls; i++)
94 | {
95 | var streamingCall = client.ReadRows(
96 | new ReadRowsRequest
97 | {
98 | TableName = TableName,
99 | Rows = new RowSet
100 | {
101 | RowKeys = { ByteString.CopyFromUtf8("large-row") }
102 | }
103 | });
104 | calls.Add(streamingCall);
105 | }
106 | Console.WriteLine(String.Format("Created {0} streaming calls.", numStreamCalls));
107 |
108 | CancellationTokenSource tokenSource = new CancellationTokenSource();
109 | CancellationToken token = tokenSource.Token;
110 |
111 | Console.WriteLine("Starting UnaryUnary blocking call..");
112 | var watch = System.Diagnostics.Stopwatch.StartNew();
113 | MutateRowRequest mutateRowRequest = new MutateRowRequest
114 | {
115 | TableName = TableName,
116 | RowKey = ByteString.CopyFromUtf8(RowKey)
117 | };
118 |
119 | Mutation mutation = new Mutation
120 | {
121 | SetCell = new Mutation.Types.SetCell
122 | {
123 | FamilyName = ColumnFamily,
124 | ColumnQualifier = ByteString.CopyFromUtf8(ColumnQualifier),
125 | Value = ByteString.CopyFromUtf8(TestValue),
126 | }
127 | };
128 |
129 | mutateRowRequest.Mutations.Add(mutation);
130 |
131 | // Set 5 sec time out for the blocking call.
132 | client.MutateRow(mutateRowRequest, null, DateTime.UtcNow.AddSeconds(5));
133 |
134 | watch.Stop();
135 | var elapsedMs = watch.ElapsedMilliseconds;
136 | Console.WriteLine("Elapsed time for another call (ms): " + elapsedMs);
137 |
138 | }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/Grpc.Gcp/Grpc.Gcp.Benchmark/Grpc.Gcp.Benchmark.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp2.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Grpc.Gcp/Grpc.Gcp.Benchmark/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.CommandLineUtils;
2 | using System;
3 |
4 | namespace Grpc.Gcp.Benchmark
5 | {
6 | class Program
7 | {
8 | static void Main(string[] args)
9 | {
10 | CommandLineApplication app =
11 | new CommandLineApplication(throwOnUnexpectedArg: false);
12 | CommandOption numStreamCallsOption = app.Option(
13 | "-s |--num_stream_calls ",
14 | "The number of active streams to establish.",
15 | CommandOptionType.SingleValue);
16 | CommandOption gcpOption = app.Option(
17 | "-g | --gcp", "Use Grpc.Gcp call invoker feature.",
18 | CommandOptionType.NoValue);
19 | app.OnExecute(() =>
20 | {
21 | int numStreamCalls = 1;
22 | if (numStreamCallsOption.HasValue()) {
23 | numStreamCalls = Int32.Parse(numStreamCallsOption.Value());
24 | }
25 | BigtableBenchmark benchmark = new BigtableBenchmark(numStreamCalls, gcpOption.HasValue());
26 | benchmark.RunMaxConcurrentStreams();
27 | return 0;
28 | });
29 | app.Execute(args);
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/Grpc.Gcp/Grpc.Gcp.IntegrationTest/BigtableTest.cs:
--------------------------------------------------------------------------------
1 | using Google.Apis.Auth.OAuth2;
2 | using Google.Cloud.Bigtable.V2;
3 | using Google.Protobuf;
4 | using Grpc.Auth;
5 | using Grpc.Core;
6 | using Microsoft.VisualStudio.TestTools.UnitTesting;
7 | using System;
8 | using System.Collections.Generic;
9 | using System.Threading;
10 |
11 | namespace Grpc.Gcp.IntegrationTest
12 | {
13 | [TestClass]
14 | public class BigtableTest
15 | {
16 | private const string Target = "bigtable.googleapis.com";
17 | private const string TableName = "projects/grpc-gcp/instances/test-instance/tables/test-table";
18 | private const string RowKey = "test-row";
19 | private const string TestValue = "test-value";
20 | private const string ColumnFamily = "test-cf";
21 | private const string ColumnQualifier = "test-cq";
22 | private const Int32 DefaultMaxChannelsPerTarget = 10;
23 | private ApiConfig config;
24 | private GcpCallInvoker invoker;
25 | private Bigtable.BigtableClient client;
26 |
27 | [TestInitialize]
28 | public void SetUp()
29 | {
30 | InitApiConfig(1, 10);
31 | InitClient();
32 | }
33 |
34 | private void InitClient()
35 | {
36 | GoogleCredential credential = GoogleCredential.GetApplicationDefault();
37 | IList options = new List() {
38 | new ChannelOption(GcpCallInvoker.ApiConfigChannelArg, config.ToString()) };
39 | invoker = new GcpCallInvoker(Target, credential.ToChannelCredentials(), options);
40 | client = new Bigtable.BigtableClient(invoker);
41 | }
42 |
43 | private void InitApiConfig(uint maxConcurrentStreams, uint maxSize)
44 | {
45 | config = new ApiConfig
46 | {
47 | ChannelPool = new ChannelPoolConfig
48 | {
49 | MaxConcurrentStreamsLowWatermark = maxConcurrentStreams,
50 | MaxSize = maxSize,
51 | }
52 | };
53 | }
54 |
55 | [TestMethod]
56 | public void MutateRow()
57 | {
58 | MutateRowRequest mutateRowRequest = new MutateRowRequest
59 | {
60 | TableName = TableName,
61 | RowKey = ByteString.CopyFromUtf8(RowKey)
62 | };
63 |
64 | Mutation mutation = new Mutation
65 | {
66 | SetCell = new Mutation.Types.SetCell
67 | {
68 | FamilyName = ColumnFamily,
69 | ColumnQualifier = ByteString.CopyFromUtf8(ColumnQualifier),
70 | Value = ByteString.CopyFromUtf8(TestValue),
71 | }
72 | };
73 |
74 | mutateRowRequest.Mutations.Add(mutation);
75 |
76 | client.MutateRow(mutateRowRequest);
77 | Assert.AreEqual(1, invoker.GetChannelRefsForTest().Count);
78 | }
79 |
80 | [TestMethod]
81 | public void MutateRowAsync()
82 | {
83 | MutateRowRequest mutateRowRequest = new MutateRowRequest
84 | {
85 | TableName = TableName,
86 | RowKey = ByteString.CopyFromUtf8(RowKey),
87 | };
88 |
89 | Mutation mutation = new Mutation
90 | {
91 | SetCell = new Mutation.Types.SetCell
92 | {
93 | FamilyName = ColumnFamily,
94 | ColumnQualifier = ByteString.CopyFromUtf8(ColumnQualifier),
95 | Value = ByteString.CopyFromUtf8(TestValue),
96 | }
97 | };
98 |
99 | mutateRowRequest.Mutations.Add(mutation);
100 |
101 | AsyncUnaryCall call = client.MutateRowAsync(mutateRowRequest);
102 | var channelRefs = invoker.GetChannelRefsForTest();
103 | Assert.AreEqual(1, channelRefs.Count);
104 | Assert.AreEqual(1, channelRefs[0].ActiveStreamCount);
105 |
106 | MutateRowResponse response = call.ResponseAsync.Result;
107 | channelRefs = invoker.GetChannelRefsForTest();
108 | Assert.AreEqual(0, channelRefs[0].ActiveStreamCount);
109 | }
110 |
111 | [TestMethod]
112 | public void ReadRows()
113 | {
114 | ReadRowsRequest readRowsRequest = new ReadRowsRequest
115 | {
116 | TableName = TableName,
117 | Rows = new RowSet
118 | {
119 | RowKeys = { ByteString.CopyFromUtf8(RowKey) }
120 | }
121 | };
122 | var streamingCall = client.ReadRows(readRowsRequest);
123 | var channelRefs = invoker.GetChannelRefsForTest();
124 | Assert.AreEqual(1, channelRefs.Count);
125 | Assert.AreEqual(1, channelRefs[0].ActiveStreamCount);
126 | Assert.ThrowsException(() => streamingCall.GetStatus());
127 |
128 | CancellationTokenSource tokenSource = new CancellationTokenSource();
129 | CancellationToken token = tokenSource.Token;
130 | var responseStream = streamingCall.ResponseStream;
131 | ReadRowsResponse firstResponse = null;
132 | while (responseStream.MoveNext(token).Result)
133 | {
134 | if (firstResponse == null) firstResponse = responseStream.Current;
135 | }
136 | Assert.AreEqual("test-value", firstResponse.Chunks[0].Value.ToStringUtf8());
137 |
138 | channelRefs = invoker.GetChannelRefsForTest();
139 | Assert.AreEqual(1, channelRefs.Count);
140 | Assert.AreEqual(0, channelRefs[0].ActiveStreamCount);
141 | Assert.AreEqual(StatusCode.OK, streamingCall.GetStatus().StatusCode);
142 | }
143 |
144 | [TestMethod]
145 | public void ConcurrentStreams()
146 | {
147 | config = new ApiConfig();
148 | int lowWatermark = 5;
149 | InitApiConfig((uint)lowWatermark, 10);
150 | InitClient();
151 |
152 | var calls = new List>();
153 |
154 | IList channelRefs;
155 | for (int i = 0; i < lowWatermark; i++)
156 | {
157 | var streamingCall = client.ReadRows(
158 | new ReadRowsRequest
159 | {
160 | TableName = TableName,
161 | Rows = new RowSet
162 | {
163 | RowKeys = { ByteString.CopyFromUtf8(RowKey) }
164 | }
165 | });
166 | channelRefs = invoker.GetChannelRefsForTest();
167 | Assert.AreEqual(1, channelRefs.Count);
168 | Assert.AreEqual(i + 1, channelRefs[0].ActiveStreamCount);
169 | calls.Add(streamingCall);
170 | }
171 |
172 | // When number of active streams reaches the lowWaterMark,
173 | // New channel should be created.
174 | var anotherStreamingCall = client.ReadRows(
175 | new ReadRowsRequest
176 | {
177 | TableName = TableName,
178 | Rows = new RowSet
179 | {
180 | RowKeys = { ByteString.CopyFromUtf8(RowKey) }
181 | }
182 | });
183 |
184 | channelRefs = invoker.GetChannelRefsForTest();
185 | Assert.AreEqual(2, channelRefs.Count);
186 | Assert.AreEqual(lowWatermark, channelRefs[0].ActiveStreamCount);
187 | Assert.AreEqual(1, channelRefs[1].ActiveStreamCount);
188 | calls.Add(anotherStreamingCall);
189 |
190 | // Clean open streams.
191 | CancellationTokenSource tokenSource = new CancellationTokenSource();
192 | CancellationToken token = tokenSource.Token;
193 | for (int i = 0; i < calls.Count; i++)
194 | {
195 | var responseStream = calls[i].ResponseStream;
196 | while (responseStream.MoveNext(token).Result) { };
197 | }
198 |
199 | // Check channel references again.
200 | channelRefs = invoker.GetChannelRefsForTest();
201 | Assert.AreEqual(2, channelRefs.Count);
202 | Assert.AreEqual(0, channelRefs[0].ActiveStreamCount);
203 | Assert.AreEqual(0, channelRefs[1].ActiveStreamCount);
204 | }
205 |
206 | [TestMethod]
207 | public void AsyncCallsWithNewChannels()
208 | {
209 | var calls = new List>();
210 |
211 | for (int i = 0; i < DefaultMaxChannelsPerTarget; i++)
212 | {
213 | var streamingCall = client.ReadRows(
214 | new ReadRowsRequest
215 | {
216 | TableName = TableName,
217 | Rows = new RowSet
218 | {
219 | RowKeys = { ByteString.CopyFromUtf8(RowKey) }
220 | }
221 | });
222 | Assert.AreEqual(i + 1, invoker.GetChannelRefsForTest().Count);
223 | calls.Add(streamingCall);
224 | }
225 |
226 | // When number of channels reaches the max, old channels will be reused,
227 | // even when the number of active streams is higher than the watermark.
228 | for (int i = 0; i < DefaultMaxChannelsPerTarget; i++)
229 | {
230 | var streamingCall = client.ReadRows(
231 | new ReadRowsRequest
232 | {
233 | TableName = TableName,
234 | Rows = new RowSet
235 | {
236 | RowKeys = { ByteString.CopyFromUtf8(RowKey) }
237 | }
238 | });
239 | Assert.AreEqual(DefaultMaxChannelsPerTarget, invoker.GetChannelRefsForTest().Count);
240 | calls.Add(streamingCall);
241 | }
242 |
243 | // Clean open streams.
244 | CancellationTokenSource tokenSource = new CancellationTokenSource();
245 | CancellationToken token = tokenSource.Token;
246 | for (int i = 0; i < calls.Count; i++)
247 | {
248 | var responseStream = calls[i].ResponseStream;
249 | while (responseStream.MoveNext(token).Result) { };
250 | }
251 | Assert.AreEqual(DefaultMaxChannelsPerTarget, invoker.GetChannelRefsForTest().Count);
252 |
253 | var channelRefs = invoker.GetChannelRefsForTest();
254 | for (int i = 0; i < channelRefs.Count; i++)
255 | {
256 | var channel = channelRefs[i].Channel;
257 | var state = channel.State;
258 | Assert.AreEqual(ChannelState.Ready, channel.State);
259 | }
260 |
261 | // Shutdown all channels in the channel pool.
262 | invoker.ShutdownAsync().Wait();
263 |
264 | for (int i = 0; i < channelRefs.Count; i++)
265 | {
266 | var channel = channelRefs[i].Channel;
267 | Assert.AreEqual(ChannelState.Shutdown, channel.State);
268 | }
269 | }
270 |
271 | [TestMethod]
272 | public void CreateClientWithEmptyOptions()
273 | {
274 | GoogleCredential credential = GoogleCredential.GetApplicationDefault();
275 | invoker = new GcpCallInvoker(Target, credential.ToChannelCredentials());
276 | client = new Bigtable.BigtableClient(invoker);
277 |
278 | MutateRowRequest mutateRowRequest = new MutateRowRequest
279 | {
280 | TableName = TableName,
281 | RowKey = ByteString.CopyFromUtf8(RowKey)
282 | };
283 |
284 | Mutation mutation = new Mutation
285 | {
286 | SetCell = new Mutation.Types.SetCell
287 | {
288 | FamilyName = ColumnFamily,
289 | ColumnQualifier = ByteString.CopyFromUtf8(ColumnQualifier),
290 | Value = ByteString.CopyFromUtf8(TestValue),
291 | }
292 | };
293 |
294 | mutateRowRequest.Mutations.Add(mutation);
295 |
296 | client.MutateRow(mutateRowRequest);
297 | Assert.AreEqual(1, invoker.GetChannelRefsForTest().Count);
298 | }
299 | }
300 | }
301 |
--------------------------------------------------------------------------------
/Grpc.Gcp/Grpc.Gcp.IntegrationTest/Grpc.Gcp.IntegrationTest.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | netcoreapp2.0;net45
7 | netcoreapp2.0
8 | false
9 | true
10 | ../keys/Grpc.Gcp.snk
11 | true
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | mscorlib
29 |
30 |
31 | System
32 |
33 |
34 | System.Core
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | Always
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/Grpc.Gcp/Grpc.Gcp.IntegrationTest/LocalServiceTest.cs:
--------------------------------------------------------------------------------
1 | using Grpc.Core;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using static Grpc.Gcp.AffinityConfig.Types;
8 |
9 | namespace Grpc.Gcp.IntegrationTest
10 | {
11 | [TestClass]
12 | public class LocalServiceTest
13 | {
14 | [TestMethod]
15 | public async Task ExceptionPropagation()
16 | {
17 | await RunWithServer(
18 | new ThrowingService(),
19 | null,
20 | async (invoker, client) =>
21 | {
22 | await Assert.ThrowsExceptionAsync(async () => await client.DoSimpleAsync(new SimpleRequest()));
23 | AssertNoActiveStreams(invoker);
24 | },
25 | (invoker, client) =>
26 | {
27 | Assert.ThrowsException(() => client.DoSimple(new SimpleRequest()));
28 | AssertNoActiveStreams(invoker);
29 | });
30 |
31 | void AssertNoActiveStreams(GcpCallInvoker invoker)
32 | {
33 | var channelRefs = invoker.GetChannelRefsForTest();
34 | Assert.AreEqual(0, channelRefs.Sum(cr => cr.ActiveStreamCount));
35 | }
36 | }
37 |
38 | [TestMethod]
39 | public void NoChannelPoolConfig()
40 | {
41 | var config = new ApiConfig();
42 | var options = new ChannelOption[] { new ChannelOption(GcpCallInvoker.ApiConfigChannelArg, config.ToString()) };
43 | Assert.ThrowsException(() => new GcpCallInvoker("localhost", 12345, ChannelCredentials.Insecure, options));
44 | }
45 |
46 | public static IEnumerable