├── .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 GetInvalidAffinityData () 47 | { 48 | return AppendCommand("not_a_field") 49 | .Concat(AppendCommand("name.not_a_field")) 50 | .Concat(AppendCommand("number")) 51 | .Concat(AppendCommand("inner")) 52 | .Concat(AppendCommand("inner.not_a_field")) 53 | .Concat(AppendCommand("inner.name.not_a_field")) 54 | .Concat(AppendCommand("inner.number")) 55 | .Concat(AppendCommand("inner.nested_inner")) 56 | .Concat(AppendCommand("inner.nested_inner.number")) 57 | .Concat(AppendCommand("inner.nested_inner.nested_inner")) 58 | .Concat(AppendCommand("inner.nested_inners")) 59 | .Concat(AppendCommand("inner.nested_inners.not_a_field")) 60 | .Concat(AppendCommand("inner.nested_inners.number")) 61 | .Concat(AppendCommand("inner.nested_inners.nested_inner")); 62 | 63 | IEnumerable AppendCommand(string affinityKey) 64 | { 65 | yield return new object[] { affinityKey, Command.Bind }; 66 | yield return new object[] { affinityKey, Command.Unbind }; 67 | yield return new object[] { affinityKey, Command.Bound }; 68 | } 69 | } 70 | 71 | [DataTestMethod] 72 | [DynamicData(nameof(GetInvalidAffinityData), DynamicDataSourceType.Method)] 73 | public async Task InvalidAffinity(string affinityKey, Command command) 74 | { 75 | ComplexRequest request = new ComplexRequest 76 | { 77 | Name = "name", 78 | Number = 10, 79 | Inner = new ComplexInner 80 | { 81 | Name = "name", 82 | Number = 10, 83 | NestedInner = new ComplexInner { NestedInner = new ComplexInner() }, 84 | NestedInners = 85 | { 86 | new ComplexInner 87 | { 88 | Name = "inner name", 89 | Number = 5, 90 | NestedInner = new ComplexInner { } 91 | }, 92 | new ComplexInner 93 | { 94 | Name = "another inner name", 95 | Number = 15, 96 | NestedInner = new ComplexInner { } 97 | } 98 | } 99 | } 100 | }; 101 | 102 | var config = CreateApiConfig(affinityKey, command); 103 | await RunWithServer( 104 | new CopyService(), 105 | config, 106 | async (invoker, client) => 107 | await Assert.ThrowsExceptionAsync(async () => await client.DoComplexAsync(request)), 108 | (invoker, client) => 109 | Assert.ThrowsException(() => client.DoComplex(request))); 110 | } 111 | 112 | public static IEnumerable GetNullOrEmptyAffinityData() 113 | { 114 | return AppendCommand("name", new ComplexRequest()) 115 | .Concat(AppendCommand("inner.name", new ComplexRequest())) 116 | .Concat(AppendCommand("inner.nested_inner.name", new ComplexRequest())) 117 | .Concat(AppendCommand("inner.nested_inner.name", new ComplexRequest { Inner = new ComplexInner() })) 118 | .Concat(AppendCommand("inner.nested_inners", new ComplexRequest { Inner = new ComplexInner() })) 119 | .Concat(AppendCommand("inner.nested_inners.name", new ComplexRequest { Inner = new ComplexInner { NestedInners = { new ComplexInner() }}})) 120 | // This one only for Bind, since Bound and Unbind don't accept multiple affinity keys. This is tested elsewhere. 121 | .Concat(new object[][] { new object[] { "inner.nested_inners.name", new ComplexRequest { Inner = new ComplexInner { NestedInners = { new ComplexInner(), new ComplexInner() }}}, Command.Bind }}); 122 | 123 | IEnumerable AppendCommand(string affinityKey, ComplexRequest request) 124 | { 125 | yield return new object[] { affinityKey, request, Command.Bind }; 126 | yield return new object[] { affinityKey, request, Command.Unbind }; 127 | yield return new object[] { affinityKey, request, Command.Bound }; 128 | } 129 | } 130 | 131 | [DataTestMethod] 132 | [DynamicData(nameof(GetNullOrEmptyAffinityData), DynamicDataSourceType.Method)] 133 | public async Task NullOrEmptyAffinity(string affinityKey, ComplexRequest request, Command command) 134 | { 135 | var config = CreateApiConfig(affinityKey, command); 136 | await RunWithServer( 137 | new CopyService(), 138 | config, 139 | async (invoker, client) => 140 | { 141 | await client.DoComplexAsync(request); 142 | AssertNoAffinity(invoker); 143 | }, 144 | (invoker, client) => 145 | { 146 | client.DoComplex(request); 147 | AssertNoAffinity(invoker); 148 | }); 149 | } 150 | 151 | public static IEnumerable SingleAffinityData => new object[][] 152 | { 153 | new object[] 154 | { 155 | "name", 156 | new ComplexRequest 157 | { 158 | Name = "affinityKey1" 159 | }, 160 | "affinityKey1" 161 | }, 162 | new object[] 163 | { 164 | "inner.name", 165 | new ComplexRequest 166 | { 167 | Name = "affinityKey1", 168 | Inner = new ComplexInner 169 | { 170 | Name = "affinityKey2" 171 | } 172 | }, 173 | "affinityKey2" 174 | }, 175 | new object[] 176 | { 177 | "inner.nested_inner.name", 178 | new ComplexRequest 179 | { 180 | Name = "affinityKey1", 181 | Inner = new ComplexInner 182 | { 183 | Name = "affinityKey2", 184 | NestedInner = new ComplexInner 185 | { 186 | Name = "affinityKey3" 187 | } 188 | } 189 | }, 190 | "affinityKey3" 191 | }, 192 | new object[] 193 | { 194 | "inner.nested_inners.name", 195 | new ComplexRequest 196 | { 197 | Name = "affinityKey1", 198 | Inner = new ComplexInner 199 | { 200 | Name = "affinityKey2", 201 | NestedInner = new ComplexInner 202 | { 203 | Name = "affinityKey3" 204 | }, 205 | NestedInners = 206 | { 207 | new ComplexInner 208 | { 209 | Name = "affinityKey4" 210 | } 211 | } 212 | } 213 | }, 214 | "affinityKey4" 215 | } 216 | }; 217 | 218 | public static IEnumerable BatchAffinityData => new object[][] 219 | { 220 | new object[] 221 | { 222 | "inner.nested_inners.name", 223 | new ComplexRequest 224 | { 225 | Inner = new ComplexInner 226 | { 227 | NestedInners = 228 | { 229 | new ComplexInner { Name = "nested1"}, 230 | new ComplexInner { Name = "nested2"}, 231 | new ComplexInner { Name = "nested3"}, 232 | new ComplexInner { Name = "nested4"}, 233 | } 234 | } 235 | } 236 | }, 237 | new object[] 238 | { 239 | "inner.nested_inners.nested_inners.name", 240 | new ComplexRequest 241 | { 242 | Inner = new ComplexInner 243 | { 244 | NestedInners = 245 | { 246 | new ComplexInner 247 | { 248 | NestedInners = 249 | { 250 | new ComplexInner { Name = "nested1"}, 251 | new ComplexInner { Name = "nested2"}, 252 | } 253 | }, 254 | new ComplexInner 255 | { 256 | NestedInners = 257 | { 258 | new ComplexInner { Name = "nested3"}, 259 | new ComplexInner { Name = "nested4"}, 260 | } 261 | }, 262 | } 263 | } 264 | } 265 | }, 266 | }; 267 | 268 | [DataTestMethod] 269 | [DynamicData(nameof(SingleAffinityData), DynamicDataSourceType.Property)] 270 | public async Task SingleAffinity_Bind(string affinityKey, ComplexRequest request, string expectedAffinityKey) 271 | { 272 | var config = CreateApiConfig(affinityKey, Command.Bind); 273 | await RunWithServer( 274 | new CopyService(), 275 | config, 276 | async (invoker, client) => 277 | { 278 | await client.DoComplexAsync(request); 279 | AssertAffinity(invoker, 1); 280 | }, 281 | (invoker, client) => 282 | { 283 | client.DoComplex(request); 284 | // Testing here with 2 due to how the tests are set up. 285 | // We use the same test server to test identical sync and async calls. 286 | // In relation to affinity and batch affinity binding, two calls will probably never 287 | // be the same in reality, because binding should occur on a unique identifier for the 288 | // object that is being bound to the channel, on creation of said object. 289 | // There's a TODO on the CallInvoker code to check that this actually never happens. 290 | // In the meantime, it's not even a bad thing to have these tests as are and this explanation here. 291 | AssertAffinity(invoker, 2); 292 | }); 293 | 294 | void AssertAffinity(GcpCallInvoker invoker, int expetedAffinityCount) 295 | { 296 | var channelRefs = invoker.GetChannelRefsForTest(); 297 | Assert.AreEqual(expetedAffinityCount, channelRefs.Sum(cr => cr.AffinityCount)); 298 | 299 | var affinityKeys = invoker.GetChannelRefsByAffinityKeyForTest().Keys; 300 | Assert.AreEqual(1, affinityKeys.Count); 301 | Assert.IsTrue(affinityKeys.Contains(expectedAffinityKey)); 302 | } 303 | } 304 | 305 | [DataTestMethod] 306 | [DynamicData(nameof(SingleAffinityData), DynamicDataSourceType.Property)] 307 | public async Task SingleAffinity_Unbind(string affinityKey, ComplexRequest request, string _) 308 | { 309 | var service = new CopyService(); 310 | var bindConfig = CreateApiConfig(affinityKey, Command.Bind); 311 | var unbindConfig = CreateApiConfig(affinityKey, Command.Unbind); 312 | 313 | // First we need to Bind. 314 | // Bind works, we test elsewhere. 315 | Bind(); 316 | 317 | // Test for Unbind async 318 | await RunWithServer( 319 | service, 320 | unbindConfig, 321 | async (invoker, client) => 322 | { 323 | await client.DoComplexAsync(request); 324 | AssertNoAffinity(invoker); 325 | }, 326 | (invoker, client) => 327 | { 328 | // No op, we are testing unbind async here. 329 | // Can't unbind twice. 330 | }); 331 | 332 | // Bind again so we can test sync unbind 333 | Bind(); 334 | 335 | // Test for Unbind sync 336 | await RunWithServer( 337 | service, 338 | unbindConfig, 339 | async (invoker, client) => 340 | { 341 | // No op, we are testing unbind sync here. 342 | // Can't unbind twice. 343 | await Task.FromResult(0); 344 | }, 345 | (invoker, client) => 346 | { 347 | client.DoComplex(request); 348 | AssertNoAffinity(invoker); 349 | }); 350 | 351 | async void Bind() 352 | { 353 | await RunWithServer( 354 | service, 355 | bindConfig, 356 | async (invoker, client) => 357 | { 358 | await client.DoComplexAsync(request); 359 | }, 360 | (invoker, client) => 361 | { 362 | // No op, we are just binding to test unbind after. 363 | // We are not testing Bind here. 364 | }); 365 | } 366 | } 367 | 368 | [DataTestMethod] 369 | [DynamicData(nameof(BatchAffinityData), DynamicDataSourceType.Property)] 370 | public async Task BatchAffinity_Bind(string affinityKey, ComplexRequest request) 371 | { 372 | var config = CreateApiConfig(affinityKey, Command.Bind); 373 | await RunWithServer( 374 | new CopyService(), 375 | config, 376 | async (invoker, client) => 377 | { 378 | await client.DoComplexAsync(request); 379 | AssertBatchAffinity(invoker, 4); 380 | }, 381 | (invoker, client) => 382 | { 383 | client.DoComplex(request); 384 | // Testing here with 8 due to how the tests are set up. 385 | // We use the same test server to test identical sync and async calls. 386 | // In relation to affinity and batch affinity binding, two calls will probably never 387 | // be the same in reality, because binding should occur on a unique identifier for the 388 | // object that is being bound to the channel, on creation of said object. 389 | // There's a TODO on the CallInvoker code to check that this actually never happens. 390 | // In the meantime, it's not even a bad thing to have these tests as are and this explanation here. 391 | AssertBatchAffinity(invoker, 8); 392 | }); 393 | 394 | void AssertBatchAffinity(GcpCallInvoker invoker, int expectedAffinityCount) 395 | { 396 | var channelRefs = invoker.GetChannelRefsForTest(); 397 | Assert.AreEqual(expectedAffinityCount, channelRefs.Sum(cr => cr.AffinityCount)); 398 | 399 | var affinityKeys = invoker.GetChannelRefsByAffinityKeyForTest().Keys; 400 | Assert.AreEqual(4, affinityKeys.Count); 401 | Assert.IsTrue(affinityKeys.Contains("nested1")); 402 | Assert.IsTrue(affinityKeys.Contains("nested2")); 403 | Assert.IsTrue(affinityKeys.Contains("nested3")); 404 | Assert.IsTrue(affinityKeys.Contains("nested4")); 405 | } 406 | } 407 | 408 | [DataTestMethod] 409 | [DynamicData(nameof(BatchAffinityData), DynamicDataSourceType.Property)] 410 | public async Task BatchAffinity_Unbind(string affinityKey, ComplexRequest request) 411 | { 412 | var service = new CopyService(); 413 | var bindConfig = CreateApiConfig(affinityKey, Command.Bind); 414 | var unbindConfig = CreateApiConfig(affinityKey, Command.Unbind); 415 | 416 | // First we need to Bind. 417 | // Bind works, we test elsewhere. 418 | Bind(); 419 | 420 | // Test for Unbind async 421 | await RunWithServer( 422 | service, 423 | unbindConfig, 424 | async (invoker, client) => 425 | { 426 | await client.DoComplexAsync(request); 427 | AssertNoAffinity(invoker); 428 | }, 429 | (invoker, client) => 430 | { 431 | // No op, we are testing unbind async here. 432 | // Can't unbind twice. 433 | }); 434 | 435 | // Bind again so we can test sync unbind 436 | Bind(); 437 | 438 | // Test for Unbind sync 439 | await RunWithServer( 440 | service, 441 | unbindConfig, 442 | async (invoker, client) => 443 | { 444 | // No op, we are testing unbind sync here. 445 | // Can't unbind twice. 446 | await Task.FromResult(0); 447 | }, 448 | (invoker, client) => 449 | { 450 | client.DoComplex(request); 451 | AssertNoAffinity(invoker); 452 | }); 453 | 454 | async void Bind() 455 | { 456 | await RunWithServer( 457 | service, 458 | bindConfig, 459 | async (invoker, client) => 460 | { 461 | await client.DoComplexAsync(request); 462 | }, 463 | (invoker, client) => 464 | { 465 | // No op, we are just binding to test unbind after. 466 | // We are not testing Bind here. 467 | }); 468 | } 469 | } 470 | 471 | [DataTestMethod] 472 | [DynamicData(nameof(BatchAffinityData), DynamicDataSourceType.Property)] 473 | public async Task BatchAffinity_Bound(string affinityKey, ComplexRequest request) 474 | { 475 | var config = CreateApiConfig(affinityKey, Command.Bound); 476 | await RunWithServer( 477 | new CopyService(), 478 | config, 479 | async (invoker, client) => 480 | await Assert.ThrowsExceptionAsync(async () => await client.DoComplexAsync(request)), 481 | (invoker, client) => 482 | Assert.ThrowsException(() => client.DoComplex(request))); 483 | } 484 | 485 | private void AssertNoAffinity(GcpCallInvoker invoker) 486 | { 487 | var channelRefs = invoker.GetChannelRefsForTest(); 488 | Assert.AreEqual(0, channelRefs.Sum(cr => cr.AffinityCount)); 489 | Assert.AreEqual(0, invoker.GetChannelRefsByAffinityKeyForTest().Count); 490 | } 491 | 492 | private static ApiConfig CreateApiConfig(string complexAffinityKey, Command command) => new ApiConfig 493 | { 494 | ChannelPool = new ChannelPoolConfig { IdleTimeout = 1000, MaxConcurrentStreamsLowWatermark = 10, MaxSize = 10 }, 495 | Method = 496 | { 497 | new MethodConfig 498 | { 499 | Name = { "/grpc.gcp.integration_test.TestService/DoComplex" }, 500 | Affinity = new AffinityConfig { AffinityKey = complexAffinityKey, Command = command } 501 | } 502 | } 503 | }; 504 | 505 | /// 506 | /// Starts up a service, creates a call invoker and a client, using an optional API config, 507 | /// then executes the asynchronous and/or synchronous test methods provided. 508 | /// 509 | private static async Task RunWithServer( 510 | TestService.TestServiceBase serviceImpl, 511 | ApiConfig apiConfig, 512 | Func asyncTestAction, 513 | Action testAction) 514 | { 515 | var server = new Server 516 | { 517 | Services = { TestService.BindService(serviceImpl) }, 518 | Ports = { new ServerPort("localhost", 0, ServerCredentials.Insecure) } 519 | }; 520 | server.Start(); 521 | try 522 | { 523 | var port = server.Ports.First(); 524 | var options = new List(); 525 | if (apiConfig != null) 526 | { 527 | options.Add(new ChannelOption(GcpCallInvoker.ApiConfigChannelArg, apiConfig.ToString())); 528 | } 529 | var invoker = new GcpCallInvoker(port.Host, port.BoundPort, ChannelCredentials.Insecure, options); 530 | var client = new TestService.TestServiceClient(invoker); 531 | await (asyncTestAction?.Invoke(invoker, client) ?? Task.FromResult(0)); 532 | testAction?.Invoke(invoker, client); 533 | } 534 | finally 535 | { 536 | await server.ShutdownAsync(); 537 | } 538 | } 539 | 540 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously 541 | private class ThrowingService : TestService.TestServiceBase 542 | { 543 | public override async Task DoSimple(SimpleRequest request, ServerCallContext context) => 544 | throw new Exception("Bang"); 545 | 546 | public override async Task DoComplex(ComplexRequest request, ServerCallContext context) => 547 | throw new Exception("Bang"); 548 | } 549 | 550 | private class CopyService : TestService.TestServiceBase 551 | { 552 | public override async Task DoSimple(SimpleRequest request, ServerCallContext context) => 553 | new SimpleResponse { Name = request.Name }; 554 | public override async Task DoComplex(ComplexRequest request, ServerCallContext context) => 555 | new ComplexResponse 556 | { 557 | Name = request.Name, 558 | Number = request.Number, 559 | Inner = request.Inner 560 | }; 561 | } 562 | #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously 563 | } 564 | } 565 | -------------------------------------------------------------------------------- /Grpc.Gcp/Grpc.Gcp.IntegrationTest/SpannerTest.cs: -------------------------------------------------------------------------------- 1 | using Google.Apis.Auth.OAuth2; 2 | using Google.Cloud.Spanner.V1; 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.Linq; 10 | using System.Threading; 11 | 12 | namespace Grpc.Gcp.IntegrationTest 13 | { 14 | [TestClass] 15 | public class SpannerTest 16 | { 17 | private const string Target = "spanner.googleapis.com"; 18 | private const string DatabaseUrl = "projects/grpc-gcp/instances/sample/databases/benchmark"; 19 | private const string TableName = "storage"; 20 | private const string ColumnId = "payload"; 21 | private const Int32 DefaultMaxChannelsPerTarget = 10; 22 | 23 | private ApiConfig config = new ApiConfig(); 24 | private GcpCallInvoker invoker; 25 | private Spanner.SpannerClient client; 26 | 27 | [TestInitialize] 28 | public void SetUp() 29 | { 30 | //InitApiConfig(1, 10); 31 | InitApiConfigFromFile(); 32 | InitClient(); 33 | } 34 | 35 | private void InitClient() 36 | { 37 | GoogleCredential credential = GoogleCredential.GetApplicationDefault(); 38 | IList options = new List() { 39 | new ChannelOption(GcpCallInvoker.ApiConfigChannelArg, config.ToString()) }; 40 | invoker = new GcpCallInvoker(Target, credential.ToChannelCredentials(), options); 41 | client = new Spanner.SpannerClient(invoker); 42 | } 43 | 44 | private void InitApiConfigFromFile() 45 | { 46 | MessageParser parser = ApiConfig.Parser; 47 | string text = System.IO.File.ReadAllText(@"spanner.grpc.config"); 48 | config = parser.ParseJson(text); 49 | } 50 | 51 | private void InitApiConfig(int maxConcurrentStreams, int maxSize) 52 | { 53 | config.ChannelPool = new ChannelPoolConfig 54 | { 55 | MaxConcurrentStreamsLowWatermark = (uint)maxConcurrentStreams, 56 | MaxSize = (uint)maxSize 57 | }; 58 | AddMethod(config, "/google.spanner.v1.Spanner/CreateSession", AffinityConfig.Types.Command.Bind, "name"); 59 | AddMethod(config, "/google.spanner.v1.Spanner/GetSession", AffinityConfig.Types.Command.Bound, "name"); 60 | AddMethod(config, "/google.spanner.v1.Spanner/DeleteSession", AffinityConfig.Types.Command.Unbind, "name"); 61 | AddMethod(config, "/google.spanner.v1.Spanner/ExecuteSql", AffinityConfig.Types.Command.Bound, "session"); 62 | AddMethod(config, "/google.spanner.v1.Spanner/ExecuteStreamingSql", AffinityConfig.Types.Command.Bound, "session"); 63 | AddMethod(config, "/google.spanner.v1.Spanner/Read", AffinityConfig.Types.Command.Bound, "session"); 64 | AddMethod(config, "/google.spanner.v1.Spanner/StreamingRead", AffinityConfig.Types.Command.Bound, "session"); 65 | AddMethod(config, "/google.spanner.v1.Spanner/BeginTransaction", AffinityConfig.Types.Command.Bound, "session"); 66 | AddMethod(config, "/google.spanner.v1.Spanner/Commit", AffinityConfig.Types.Command.Bound, "session"); 67 | AddMethod(config, "/google.spanner.v1.Spanner/Rollback", AffinityConfig.Types.Command.Bound, "session"); 68 | AddMethod(config, "/google.spanner.v1.Spanner/PartitionQuery", AffinityConfig.Types.Command.Bound, "session"); 69 | AddMethod(config, "/google.spanner.v1.Spanner/PartitionRead", AffinityConfig.Types.Command.Bound, "session"); 70 | } 71 | 72 | private void AddMethod(ApiConfig config, string name, AffinityConfig.Types.Command command, string affinityKey) 73 | { 74 | MethodConfig method = new MethodConfig(); 75 | method.Name.Add(name); 76 | method.Affinity = new AffinityConfig 77 | { 78 | Command = command, 79 | AffinityKey = affinityKey 80 | }; 81 | config.Method.Add(method); 82 | } 83 | 84 | [TestMethod] 85 | public void CreateSessionWithNewChannel() 86 | { 87 | IList> calls = new List>(); 88 | 89 | for (int i = 0; i < DefaultMaxChannelsPerTarget; i++) 90 | { 91 | var call = client.CreateSessionAsync( 92 | new CreateSessionRequest { Database = DatabaseUrl }); 93 | calls.Add(call); 94 | Assert.AreEqual(i + 1, invoker.GetChannelRefsForTest().Count); 95 | } 96 | for (int i = 0; i < calls.Count; i++) 97 | { 98 | client.DeleteSession( 99 | new DeleteSessionRequest { Name = calls[i].ResponseAsync.Result.Name }); 100 | } 101 | 102 | calls.Clear(); 103 | 104 | for (int i = 0; i < DefaultMaxChannelsPerTarget; i++) 105 | { 106 | var call = client.CreateSessionAsync( 107 | new CreateSessionRequest { Database = DatabaseUrl }); 108 | calls.Add(call); 109 | Assert.AreEqual(DefaultMaxChannelsPerTarget, invoker.GetChannelRefsForTest().Count); 110 | } 111 | for (int i = 0; i < calls.Count; i++) 112 | { 113 | client.DeleteSession( 114 | new DeleteSessionRequest { Name = calls[i].ResponseAsync.Result.Name }); 115 | } 116 | } 117 | 118 | [TestMethod] 119 | public void CreateSessionWithReusedChannel() 120 | { 121 | for (int i = 0; i < DefaultMaxChannelsPerTarget * 2; i++) 122 | { 123 | Session session; 124 | session = client.CreateSession( 125 | new CreateSessionRequest { Database = DatabaseUrl }); 126 | 127 | Assert.IsNotNull(session); 128 | Assert.AreEqual(1, invoker.GetChannelRefsForTest().Count); 129 | 130 | client.DeleteSession(new DeleteSessionRequest { Name = session.Name }); 131 | } 132 | } 133 | 134 | [TestMethod] 135 | public void CreateListDeleteSession() 136 | { 137 | Session session; 138 | { 139 | CreateSessionRequest request = new CreateSessionRequest 140 | { 141 | Database = DatabaseUrl 142 | }; 143 | session = client.CreateSession(request); 144 | Assert.IsNotNull(session); 145 | AssertAffinityCount(1); 146 | } 147 | 148 | { 149 | ListSessionsRequest request = new ListSessionsRequest 150 | { 151 | Database = DatabaseUrl 152 | }; 153 | ListSessionsResponse response = client.ListSessions(request); 154 | Assert.IsNotNull(response); 155 | Assert.IsNotNull(response.Sessions); 156 | Assert.IsTrue(response.Sessions.Any(item => item.Name == session.Name)); 157 | AssertAffinityCount(1); 158 | } 159 | 160 | { 161 | DeleteSessionRequest request = new DeleteSessionRequest 162 | { 163 | Name = session.Name 164 | }; 165 | client.DeleteSession(request); 166 | AssertAffinityCount(0); 167 | } 168 | 169 | { 170 | ListSessionsRequest request = new ListSessionsRequest 171 | { 172 | Database = DatabaseUrl 173 | }; 174 | ListSessionsResponse response = client.ListSessions(request); 175 | Assert.IsNotNull(response); 176 | Assert.IsNotNull(response.Sessions); 177 | Assert.IsFalse(response.Sessions.Any(item => item.Name == session.Name)); 178 | AssertAffinityCount(0); 179 | } 180 | 181 | } 182 | 183 | [TestMethod] 184 | public void ExecuteSql() 185 | { 186 | Session session; 187 | { 188 | CreateSessionRequest request = new CreateSessionRequest 189 | { 190 | Database = DatabaseUrl 191 | }; 192 | session = client.CreateSession(request); 193 | Assert.IsNotNull(session); 194 | AssertAffinityCount(1); 195 | } 196 | { 197 | ExecuteSqlRequest request = new ExecuteSqlRequest 198 | { 199 | Session = session.Name, 200 | Sql = string.Format("select id, data from {0}", TableName) 201 | }; 202 | ResultSet resultSet = client.ExecuteSql(request); 203 | AssertAffinityCount(1); 204 | Assert.IsNotNull(resultSet); 205 | Assert.AreEqual(1, resultSet.Rows.Count); 206 | Assert.AreEqual(ColumnId, resultSet.Rows[0].Values[0].StringValue); 207 | } 208 | { 209 | DeleteSessionRequest request = new DeleteSessionRequest 210 | { 211 | Name = session.Name 212 | }; 213 | client.DeleteSession(request); 214 | AssertAffinityCount(0); 215 | } 216 | } 217 | 218 | [TestMethod] 219 | public void ExecuteStreamingSql() 220 | { 221 | Session session; 222 | 223 | session = client.CreateSession( 224 | new CreateSessionRequest { Database = DatabaseUrl }); 225 | Assert.IsNotNull(session); 226 | AssertAffinityCount(1); 227 | 228 | var streamingCall = client.ExecuteStreamingSql( 229 | new ExecuteSqlRequest 230 | { 231 | Session = session.Name, 232 | Sql = string.Format("select id, data from {0}", TableName) 233 | }); 234 | 235 | AssertAffinityCount(1, expectedActiveStreamCount: 1); 236 | 237 | CancellationTokenSource tokenSource = new CancellationTokenSource(); 238 | CancellationToken token = tokenSource.Token; 239 | var responseStream = streamingCall.ResponseStream; 240 | PartialResultSet firstResultSet = null; 241 | while (responseStream.MoveNext(token).Result) 242 | { 243 | if (firstResultSet == null) firstResultSet = responseStream.Current; 244 | } 245 | Assert.AreEqual(ColumnId, firstResultSet?.Values[0].StringValue); 246 | AssertAffinityCount(1); 247 | 248 | client.DeleteSession(new DeleteSessionRequest { Name = session.Name }); 249 | AssertAffinityCount(0); 250 | } 251 | 252 | [TestMethod] 253 | public void ExecuteSqlAsync() 254 | { 255 | Session session; 256 | 257 | session = client.CreateSession( 258 | new CreateSessionRequest { Database = DatabaseUrl }); 259 | Assert.IsNotNull(session); 260 | AssertAffinityCount(1); 261 | 262 | AsyncUnaryCall call = client.ExecuteSqlAsync( 263 | new ExecuteSqlRequest 264 | { 265 | Session = session.Name, 266 | Sql = string.Format("select id, data from {0}", TableName) 267 | }); 268 | AssertAffinityCount(1, expectedActiveStreamCount: 1); 269 | 270 | ResultSet resultSet = call.ResponseAsync.Result; 271 | 272 | AssertAffinityCount(1); 273 | 274 | Assert.IsNotNull(resultSet); 275 | Assert.AreEqual(1, resultSet.Rows.Count); 276 | Assert.AreEqual(ColumnId, resultSet.Rows[0].Values[0].StringValue); 277 | 278 | client.DeleteSession(new DeleteSessionRequest { Name = session.Name }); 279 | AssertAffinityCount(0); 280 | } 281 | 282 | [TestMethod] 283 | public void BoundUnbindInvalidAffinityKey() 284 | { 285 | GetSessionRequest getSessionRequest = new GetSessionRequest 286 | { 287 | Name = "random_name" 288 | }; 289 | Assert.ThrowsException(() => client.GetSession(getSessionRequest)); 290 | 291 | DeleteSessionRequest deleteSessionRequest = new DeleteSessionRequest 292 | { 293 | Name = "random_name" 294 | }; 295 | 296 | Assert.ThrowsException(() => client.DeleteSession(deleteSessionRequest)); 297 | } 298 | 299 | [TestMethod] 300 | public void BoundAfterUnbind() 301 | { 302 | CreateSessionRequest request = new CreateSessionRequest 303 | { 304 | Database = DatabaseUrl 305 | }; 306 | Session session = client.CreateSession(request); 307 | 308 | Assert.AreEqual(1, invoker.GetChannelRefsByAffinityKeyForTest().Count); 309 | 310 | DeleteSessionRequest deleteSessionRequest = new DeleteSessionRequest 311 | { 312 | Name = session.Name 313 | }; 314 | client.DeleteSession(deleteSessionRequest); 315 | 316 | Assert.AreEqual(0, invoker.GetChannelRefsByAffinityKeyForTest().Count); 317 | 318 | GetSessionRequest getSessionRequest = new GetSessionRequest(); 319 | getSessionRequest.Name = session.Name; 320 | Assert.ThrowsException(() => client.GetSession(getSessionRequest)); 321 | 322 | } 323 | 324 | [TestMethod] 325 | public void ConcurrentStreams() 326 | { 327 | config = new ApiConfig(); 328 | int lowWatermark = 5; 329 | InitApiConfig(lowWatermark, 10); 330 | InitClient(); 331 | 332 | var sessions = new List(); 333 | var calls = new List>(); 334 | 335 | for (int i = 0; i < lowWatermark; i++) 336 | { 337 | Session session = client.CreateSession( 338 | new CreateSessionRequest { Database = DatabaseUrl }); 339 | AssertAffinityCount(i + 1, expectedActiveStreamCount: i); 340 | Assert.IsNotNull(session); 341 | 342 | sessions.Add(session); 343 | 344 | var streamingCall = client.ExecuteStreamingSql( 345 | new ExecuteSqlRequest 346 | { 347 | Session = session.Name, 348 | Sql = string.Format("select id, data from {0}", TableName) 349 | }); 350 | AssertAffinityCount(i + 1, expectedActiveStreamCount: i + 1); 351 | calls.Add(streamingCall); 352 | } 353 | 354 | // When number of active streams reaches the lowWaterMark, 355 | // New channel should be created. 356 | 357 | Session anotherSession = client.CreateSession( 358 | new CreateSessionRequest { Database = DatabaseUrl }); 359 | var channelRefs = invoker.GetChannelRefsForTest(); 360 | Assert.AreEqual(2, channelRefs.Count); 361 | Assert.AreEqual(lowWatermark, channelRefs[0].AffinityCount); 362 | Assert.AreEqual(lowWatermark, channelRefs[0].ActiveStreamCount); 363 | Assert.AreEqual(1, channelRefs[1].AffinityCount); 364 | Assert.AreEqual(0, channelRefs[1].ActiveStreamCount); 365 | Assert.IsNotNull(anotherSession); 366 | 367 | sessions.Add(anotherSession); 368 | 369 | var anotherStreamingCall = client.ExecuteStreamingSql( 370 | new ExecuteSqlRequest 371 | { 372 | Session = anotherSession.Name, 373 | Sql = string.Format("select id, data from {0}", TableName) 374 | }); 375 | channelRefs = invoker.GetChannelRefsForTest(); 376 | Assert.AreEqual(2, channelRefs.Count); 377 | Assert.AreEqual(lowWatermark, channelRefs[0].AffinityCount); 378 | Assert.AreEqual(lowWatermark, channelRefs[0].ActiveStreamCount); 379 | Assert.AreEqual(1, channelRefs[1].AffinityCount); 380 | Assert.AreEqual(1, channelRefs[1].ActiveStreamCount); 381 | 382 | calls.Add(anotherStreamingCall); 383 | 384 | // Clean open streams. 385 | CancellationTokenSource tokenSource = new CancellationTokenSource(); 386 | CancellationToken token = tokenSource.Token; 387 | for (int i = 0; i < calls.Count; i++) 388 | { 389 | var responseStream = calls[i].ResponseStream; 390 | while (responseStream.MoveNext(token).Result) { }; 391 | } 392 | channelRefs = invoker.GetChannelRefsForTest(); 393 | Assert.AreEqual(2, channelRefs.Count); 394 | Assert.AreEqual(lowWatermark, channelRefs[0].AffinityCount); 395 | Assert.AreEqual(0, channelRefs[0].ActiveStreamCount); 396 | Assert.AreEqual(1, channelRefs[1].AffinityCount); 397 | Assert.AreEqual(0, channelRefs[1].ActiveStreamCount); 398 | 399 | // Delete all sessions to clean affinity. 400 | for (int i = 0; i < sessions.Count; i++) 401 | { 402 | client.DeleteSession(new DeleteSessionRequest { Name = sessions[i].Name }); 403 | } 404 | channelRefs = invoker.GetChannelRefsForTest(); 405 | Assert.AreEqual(2, channelRefs.Count); 406 | Assert.AreEqual(0, channelRefs[0].AffinityCount); 407 | Assert.AreEqual(0, channelRefs[0].ActiveStreamCount); 408 | Assert.AreEqual(0, channelRefs[1].AffinityCount); 409 | Assert.AreEqual(0, channelRefs[1].ActiveStreamCount); 410 | } 411 | 412 | [TestMethod] 413 | public void ShutdownChannels() 414 | { 415 | IList> calls = new List>(); 416 | 417 | for (int i = 0; i < DefaultMaxChannelsPerTarget; i++) 418 | { 419 | var call = client.CreateSessionAsync( 420 | new CreateSessionRequest { Database = DatabaseUrl }); 421 | calls.Add(call); 422 | Assert.AreEqual(i + 1, invoker.GetChannelRefsForTest().Count); 423 | } 424 | for (int i = 0; i < calls.Count; i++) 425 | { 426 | client.DeleteSession( 427 | new DeleteSessionRequest { Name = calls[i].ResponseAsync.Result.Name }); 428 | } 429 | 430 | var channelRefs = invoker.GetChannelRefsForTest(); 431 | for (int i = 0; i < channelRefs.Count; i++) 432 | { 433 | var channel = channelRefs[i].Channel; 434 | Assert.AreEqual(ChannelState.Ready, channel.State); 435 | } 436 | 437 | // Shutdown all channels in the channel pool. 438 | invoker.ShutdownAsync().Wait(); 439 | 440 | for (int i = 0; i < channelRefs.Count; i++) 441 | { 442 | var channel = channelRefs[i].Channel; 443 | Assert.AreEqual(ChannelState.Shutdown, channel.State); 444 | } 445 | } 446 | 447 | private void AssertAffinityCount(int expectedAffinityCount, int expectedActiveStreamCount = 0) 448 | { 449 | var channelRefs = invoker.GetChannelRefsForTest(); 450 | Assert.AreEqual(1, channelRefs.Count); 451 | Assert.AreEqual(expectedAffinityCount, channelRefs[0].AffinityCount); 452 | Assert.AreEqual(expectedActiveStreamCount, channelRefs[0].ActiveStreamCount); 453 | } 454 | } 455 | } 456 | -------------------------------------------------------------------------------- /Grpc.Gcp/Grpc.Gcp.IntegrationTest/TestServiceGrpc.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by the protocol buffer compiler. DO NOT EDIT! 3 | // source: test_service.proto 4 | // 5 | // Original file comments: 6 | // 7 | // Copyright 2018 Google Inc. All Rights Reserved. 8 | // Use of this source code is governed by a BSD-style 9 | // license that can be found in the LICENSE file or at 10 | // https://developers.google.com/open-source/licenses/bsd 11 | // 12 | #pragma warning disable 0414, 1591 13 | #region Designer generated code 14 | 15 | using grpc = global::Grpc.Core; 16 | 17 | namespace Grpc.Gcp.IntegrationTest { 18 | public static partial class TestService 19 | { 20 | static readonly string __ServiceName = "grpc.gcp.integration_test.TestService"; 21 | 22 | static readonly grpc::Marshaller __Marshaller_SimpleRequest = grpc::Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Grpc.Gcp.IntegrationTest.SimpleRequest.Parser.ParseFrom); 23 | static readonly grpc::Marshaller __Marshaller_SimpleResponse = grpc::Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Grpc.Gcp.IntegrationTest.SimpleResponse.Parser.ParseFrom); 24 | static readonly grpc::Marshaller __Marshaller_ComplexRequest = grpc::Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Grpc.Gcp.IntegrationTest.ComplexRequest.Parser.ParseFrom); 25 | static readonly grpc::Marshaller __Marshaller_ComplexResponse = grpc::Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Grpc.Gcp.IntegrationTest.ComplexResponse.Parser.ParseFrom); 26 | 27 | static readonly grpc::Method __Method_DoSimple = new grpc::Method( 28 | grpc::MethodType.Unary, 29 | __ServiceName, 30 | "DoSimple", 31 | __Marshaller_SimpleRequest, 32 | __Marshaller_SimpleResponse); 33 | 34 | static readonly grpc::Method __Method_DoComplex = new grpc::Method( 35 | grpc::MethodType.Unary, 36 | __ServiceName, 37 | "DoComplex", 38 | __Marshaller_ComplexRequest, 39 | __Marshaller_ComplexResponse); 40 | 41 | /// Service descriptor 42 | public static global::Google.Protobuf.Reflection.ServiceDescriptor Descriptor 43 | { 44 | get { return global::Grpc.Gcp.IntegrationTest.TestServiceReflection.Descriptor.Services[0]; } 45 | } 46 | 47 | /// Base class for server-side implementations of TestService 48 | public abstract partial class TestServiceBase 49 | { 50 | public virtual global::System.Threading.Tasks.Task DoSimple(global::Grpc.Gcp.IntegrationTest.SimpleRequest request, grpc::ServerCallContext context) 51 | { 52 | throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, "")); 53 | } 54 | 55 | public virtual global::System.Threading.Tasks.Task DoComplex(global::Grpc.Gcp.IntegrationTest.ComplexRequest request, grpc::ServerCallContext context) 56 | { 57 | throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, "")); 58 | } 59 | 60 | } 61 | 62 | /// Client for TestService 63 | public partial class TestServiceClient : grpc::ClientBase 64 | { 65 | /// Creates a new client for TestService 66 | /// The channel to use to make remote calls. 67 | public TestServiceClient(grpc::Channel channel) : base(channel) 68 | { 69 | } 70 | /// Creates a new client for TestService that uses a custom CallInvoker. 71 | /// The callInvoker to use to make remote calls. 72 | public TestServiceClient(grpc::CallInvoker callInvoker) : base(callInvoker) 73 | { 74 | } 75 | /// Protected parameterless constructor to allow creation of test doubles. 76 | protected TestServiceClient() : base() 77 | { 78 | } 79 | /// Protected constructor to allow creation of configured clients. 80 | /// The client configuration. 81 | protected TestServiceClient(ClientBaseConfiguration configuration) : base(configuration) 82 | { 83 | } 84 | 85 | public virtual global::Grpc.Gcp.IntegrationTest.SimpleResponse DoSimple(global::Grpc.Gcp.IntegrationTest.SimpleRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) 86 | { 87 | return DoSimple(request, new grpc::CallOptions(headers, deadline, cancellationToken)); 88 | } 89 | public virtual global::Grpc.Gcp.IntegrationTest.SimpleResponse DoSimple(global::Grpc.Gcp.IntegrationTest.SimpleRequest request, grpc::CallOptions options) 90 | { 91 | return CallInvoker.BlockingUnaryCall(__Method_DoSimple, null, options, request); 92 | } 93 | public virtual grpc::AsyncUnaryCall DoSimpleAsync(global::Grpc.Gcp.IntegrationTest.SimpleRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) 94 | { 95 | return DoSimpleAsync(request, new grpc::CallOptions(headers, deadline, cancellationToken)); 96 | } 97 | public virtual grpc::AsyncUnaryCall DoSimpleAsync(global::Grpc.Gcp.IntegrationTest.SimpleRequest request, grpc::CallOptions options) 98 | { 99 | return CallInvoker.AsyncUnaryCall(__Method_DoSimple, null, options, request); 100 | } 101 | public virtual global::Grpc.Gcp.IntegrationTest.ComplexResponse DoComplex(global::Grpc.Gcp.IntegrationTest.ComplexRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) 102 | { 103 | return DoComplex(request, new grpc::CallOptions(headers, deadline, cancellationToken)); 104 | } 105 | public virtual global::Grpc.Gcp.IntegrationTest.ComplexResponse DoComplex(global::Grpc.Gcp.IntegrationTest.ComplexRequest request, grpc::CallOptions options) 106 | { 107 | return CallInvoker.BlockingUnaryCall(__Method_DoComplex, null, options, request); 108 | } 109 | public virtual grpc::AsyncUnaryCall DoComplexAsync(global::Grpc.Gcp.IntegrationTest.ComplexRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) 110 | { 111 | return DoComplexAsync(request, new grpc::CallOptions(headers, deadline, cancellationToken)); 112 | } 113 | public virtual grpc::AsyncUnaryCall DoComplexAsync(global::Grpc.Gcp.IntegrationTest.ComplexRequest request, grpc::CallOptions options) 114 | { 115 | return CallInvoker.AsyncUnaryCall(__Method_DoComplex, null, options, request); 116 | } 117 | /// Creates a new instance of client from given ClientBaseConfiguration. 118 | protected override TestServiceClient NewInstance(ClientBaseConfiguration configuration) 119 | { 120 | return new TestServiceClient(configuration); 121 | } 122 | } 123 | 124 | /// Creates service definition that can be registered with a server 125 | /// An object implementing the server-side handling logic. 126 | public static grpc::ServerServiceDefinition BindService(TestServiceBase serviceImpl) 127 | { 128 | return grpc::ServerServiceDefinition.CreateBuilder() 129 | .AddMethod(__Method_DoSimple, serviceImpl.DoSimple) 130 | .AddMethod(__Method_DoComplex, serviceImpl.DoComplex).Build(); 131 | } 132 | 133 | } 134 | } 135 | #endregion 136 | -------------------------------------------------------------------------------- /Grpc.Gcp/Grpc.Gcp.IntegrationTest/spanner.grpc.config: -------------------------------------------------------------------------------- 1 | { 2 | "channelPool": { 3 | "maxSize": 10, 4 | "maxConcurrentStreamsLowWatermark": 1 5 | }, 6 | "method": [ 7 | { 8 | "name": [ "/google.spanner.v1.Spanner/CreateSession" ], 9 | "affinity": { 10 | "command": "BIND", 11 | "affinityKey": "name" 12 | } 13 | }, 14 | { 15 | "name": [ "/google.spanner.v1.Spanner/GetSession" ], 16 | "affinity": { 17 | "command": "BOUND", 18 | "affinityKey": "name" 19 | } 20 | }, 21 | { 22 | "name": [ "/google.spanner.v1.Spanner/DeleteSession" ], 23 | "affinity": { 24 | "command": "UNBIND", 25 | "affinityKey": "name" 26 | } 27 | }, 28 | { 29 | "name": [ "/google.spanner.v1.Spanner/ExecuteSql" ], 30 | "affinity": { 31 | "command": "BOUND", 32 | "affinityKey": "session" 33 | } 34 | }, 35 | { 36 | "name": [ "/google.spanner.v1.Spanner/ExecuteStreamingSql" ], 37 | "affinity": { 38 | "command": "BOUND", 39 | "affinityKey": "session" 40 | } 41 | }, 42 | { 43 | "name": [ "/google.spanner.v1.Spanner/Read" ], 44 | "affinity": { 45 | "command": "BOUND", 46 | "affinityKey": "session" 47 | } 48 | }, 49 | { 50 | "name": [ "/google.spanner.v1.Spanner/StreamingRead" ], 51 | "affinity": { 52 | "command": "BOUND", 53 | "affinityKey": "session" 54 | } 55 | }, 56 | { 57 | "name": [ "/google.spanner.v1.Spanner/BeginTransaction" ], 58 | "affinity": { 59 | "command": "BOUND", 60 | "affinityKey": "session" 61 | } 62 | }, 63 | { 64 | "name": [ "/google.spanner.v1.Spanner/Commit" ], 65 | "affinity": { 66 | "command": "BOUND", 67 | "affinityKey": "session" 68 | } 69 | }, 70 | { 71 | "name": [ "/google.spanner.v1.Spanner/Rollback" ], 72 | "affinity": { 73 | "command": "BOUND", 74 | "affinityKey": "session" 75 | } 76 | }, 77 | { 78 | "name": [ "/google.spanner.v1.Spanner/PartitionQuery" ], 79 | "affinity": { 80 | "command": "BOUND", 81 | "affinityKey": "session" 82 | } 83 | }, 84 | { 85 | "name": [ "/google.spanner.v1.Spanner/PartitionRead" ], 86 | "affinity": { 87 | "command": "BOUND", 88 | "affinityKey": "session" 89 | } 90 | } 91 | ] 92 | } 93 | -------------------------------------------------------------------------------- /Grpc.Gcp/Grpc.Gcp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2035 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.Gcp", "Grpc.Gcp\Grpc.Gcp.csproj", "{F4AFFF8E-FA15-4CBC-81E1-7BFCC1A12E7F}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.Gcp.IntegrationTest", "Grpc.Gcp.IntegrationTest\Grpc.Gcp.IntegrationTest.csproj", "{54006751-6500-4E42-BF57-CD7058A2D868}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.Gcp.Benchmark", "Grpc.Gcp.Benchmark\Grpc.Gcp.Benchmark.csproj", "{45762CDD-3C44-440A-B359-DE950AD6BE96}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {F4AFFF8E-FA15-4CBC-81E1-7BFCC1A12E7F}.Debug|Any CPU.ActiveCfg = Release|Any CPU 19 | {F4AFFF8E-FA15-4CBC-81E1-7BFCC1A12E7F}.Debug|Any CPU.Build.0 = Release|Any CPU 20 | {F4AFFF8E-FA15-4CBC-81E1-7BFCC1A12E7F}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {F4AFFF8E-FA15-4CBC-81E1-7BFCC1A12E7F}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {54006751-6500-4E42-BF57-CD7058A2D868}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {54006751-6500-4E42-BF57-CD7058A2D868}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {54006751-6500-4E42-BF57-CD7058A2D868}.Release|Any CPU.ActiveCfg = Debug|Any CPU 25 | {54006751-6500-4E42-BF57-CD7058A2D868}.Release|Any CPU.Build.0 = Debug|Any CPU 26 | {45762CDD-3C44-440A-B359-DE950AD6BE96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {45762CDD-3C44-440A-B359-DE950AD6BE96}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {45762CDD-3C44-440A-B359-DE950AD6BE96}.Release|Any CPU.ActiveCfg = Debug|Any CPU 29 | {45762CDD-3C44-440A-B359-DE950AD6BE96}.Release|Any CPU.Build.0 = Debug|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {7F949481-EAD3-42F4-8F74-EE43E0B120B0} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /Grpc.Gcp/Grpc.Gcp/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: InternalsVisibleTo("Grpc.Gcp.IntegrationTest,PublicKey=" + 6 | "002400000480000094000000060200000024000052534131000400000100010089c5252844e2c9" + 7 | "d323d0bd451849ee347c35f4c2a8da823b6a9a0bfa16595743db0139ef7d23a154bbb1a31ef82d" + 8 | "e01d1d52f578e2b1ba7ff546e012b5f1b89f34753925727c59b3399077e8b18f8bdba57d822075" + 9 | "2344b9e31cc48c4cd11f7809c65de3e7dc43cb4c535073956e1132da73ad6cb46e7f93f8ef3e2e" + 10 | "8cd344c3")] 11 | -------------------------------------------------------------------------------- /Grpc.Gcp/Grpc.Gcp/ChannelRef.cs: -------------------------------------------------------------------------------- 1 | using Grpc.Core; 2 | using System.Threading; 3 | 4 | namespace Grpc.Gcp 5 | { 6 | /// 7 | /// Keeps record of channel affinity and active streams. 8 | /// This class is thread-safe. 9 | /// 10 | internal sealed class ChannelRef 11 | { 12 | private int affinityCount; 13 | private int activeStreamCount; 14 | private int id; 15 | 16 | public ChannelRef(Channel channel, int id, int affinityCount = 0, int activeStreamCount = 0) 17 | { 18 | Channel = channel; 19 | this.id = id; 20 | this.affinityCount = affinityCount; 21 | this.activeStreamCount = activeStreamCount; 22 | } 23 | 24 | internal Channel Channel { get; } 25 | internal int AffinityCount => Interlocked.CompareExchange(ref affinityCount, 0, 0); 26 | internal int ActiveStreamCount => Interlocked.CompareExchange(ref activeStreamCount, 0, 0); 27 | 28 | internal int AffinityCountIncr() => Interlocked.Increment(ref affinityCount); 29 | internal int AffinityCountDecr() => Interlocked.Decrement(ref affinityCount); 30 | internal int ActiveStreamCountIncr() => Interlocked.Increment(ref activeStreamCount); 31 | internal int ActiveStreamCountDecr() => Interlocked.Decrement(ref activeStreamCount); 32 | 33 | internal ChannelRef Clone() => new ChannelRef(Channel, id, AffinityCount, ActiveStreamCount); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Grpc.Gcp/Grpc.Gcp/GcpCallInvoker.cs: -------------------------------------------------------------------------------- 1 | using Google.Protobuf; 2 | using Google.Protobuf.Collections; 3 | using Grpc.Core; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace Grpc.Gcp 11 | { 12 | /// 13 | /// Invokes client RPCs using . 14 | /// Calls are made through underlying gcp channel pool. 15 | /// 16 | public class GcpCallInvoker : CallInvoker 17 | { 18 | private static int clientChannelIdCounter; 19 | 20 | public const string ApiConfigChannelArg = "grpc_gcp.api_config"; 21 | private const string ClientChannelId = "grpc_gcp.client_channel.id"; 22 | private const Int32 DefaultChannelPoolSize = 10; 23 | private const Int32 DefaultMaxCurrentStreams = 100; 24 | 25 | // Lock to protect the channel reference collections, as they're not thread-safe. 26 | private readonly Object thisLock = new Object(); 27 | private readonly IDictionary channelRefByAffinityKey = new Dictionary(); 28 | private readonly IList channelRefs = new List(); 29 | 30 | // Access to these fields does not need to be protected by the lock: the objects are never modified. 31 | private readonly string target; 32 | private readonly ApiConfig apiConfig; 33 | private readonly IDictionary affinityByMethod; 34 | private readonly ChannelCredentials credentials; 35 | private readonly IEnumerable options; 36 | 37 | /// 38 | /// Initializes a new instance of the class. 39 | /// 40 | /// Target of the underlying grpc channels. 41 | /// Credentials to secure the underlying grpc channels. 42 | /// Channel options to be used by the underlying grpc channels. 43 | public GcpCallInvoker(string target, ChannelCredentials credentials, IEnumerable options = null) 44 | { 45 | this.target = target; 46 | this.credentials = credentials; 47 | this.apiConfig = InitDefaultApiConfig(); 48 | 49 | if (options != null) 50 | { 51 | ChannelOption option = options.FirstOrDefault(opt => opt.Name == ApiConfigChannelArg); 52 | if (option != null) 53 | { 54 | try 55 | { 56 | apiConfig = ApiConfig.Parser.ParseJson(option.StringValue); 57 | } 58 | catch (Exception ex) 59 | { 60 | if (ex is InvalidOperationException || ex is InvalidJsonException || ex is InvalidProtocolBufferException) 61 | { 62 | throw new ArgumentException("Invalid API config!", ex); 63 | } 64 | throw; 65 | } 66 | if (apiConfig.ChannelPool == null) 67 | { 68 | throw new ArgumentException("Invalid API config: no channel pool settings"); 69 | } 70 | } 71 | this.options = options.Where(o => o.Name != ApiConfigChannelArg).ToList(); 72 | } 73 | else 74 | { 75 | this.options = Enumerable.Empty(); 76 | } 77 | 78 | affinityByMethod = InitAffinityByMethodIndex(apiConfig); 79 | } 80 | 81 | /// 82 | /// Initializes a new instance of the class. 83 | /// 84 | /// Hostname of target. 85 | /// Port number of target 86 | /// Credentials to secure the underlying grpc channels. 87 | /// Channel options to be used by the underlying grpc channels. 88 | public GcpCallInvoker(string host, int port, ChannelCredentials credentials, IEnumerable options = null) : 89 | this($"{host}:{port}", credentials, options) 90 | { } 91 | 92 | private ApiConfig InitDefaultApiConfig() => 93 | new ApiConfig 94 | { 95 | ChannelPool = new ChannelPoolConfig 96 | { 97 | MaxConcurrentStreamsLowWatermark = (uint)DefaultMaxCurrentStreams, 98 | MaxSize = (uint)DefaultChannelPoolSize 99 | } 100 | }; 101 | 102 | private IDictionary InitAffinityByMethodIndex(ApiConfig config) 103 | { 104 | IDictionary index = new Dictionary(); 105 | if (config != null) 106 | { 107 | foreach (MethodConfig method in config.Method) 108 | { 109 | // TODO(fengli): supports wildcard in method selector. 110 | foreach (string name in method.Name) 111 | { 112 | index.Add(name, method.Affinity); 113 | } 114 | } 115 | } 116 | return index; 117 | } 118 | 119 | private ChannelRef GetChannelRef(string affinityKey = null) 120 | { 121 | // TODO(fengli): Supports load reporting. 122 | lock (thisLock) 123 | { 124 | if (!string.IsNullOrEmpty(affinityKey)) 125 | { 126 | // Finds the gRPC channel according to the affinity key. 127 | if (channelRefByAffinityKey.TryGetValue(affinityKey, out ChannelRef channelRef)) 128 | { 129 | return channelRef; 130 | } 131 | // TODO(fengli): Affinity key not found, log an error. 132 | } 133 | 134 | // TODO(fengli): Creates new gRPC channels on demand, depends on the load reporting. 135 | IOrderedEnumerable orderedChannelRefs = 136 | channelRefs.OrderBy(channelRef => channelRef.ActiveStreamCount); 137 | foreach (ChannelRef channelRef in orderedChannelRefs) 138 | { 139 | if (channelRef.ActiveStreamCount < apiConfig.ChannelPool.MaxConcurrentStreamsLowWatermark) 140 | { 141 | // If there's a free channel, use it. 142 | return channelRef; 143 | } 144 | else 145 | { 146 | // If all channels are busy, break. 147 | break; 148 | } 149 | } 150 | int count = channelRefs.Count; 151 | if (count < apiConfig.ChannelPool.MaxSize) 152 | { 153 | // Creates a new gRPC channel. 154 | GrpcEnvironment.Logger.Info("Grpc.Gcp creating new channel"); 155 | Channel channel = new Channel(target, credentials, 156 | options.Concat(new[] { new ChannelOption(ClientChannelId, Interlocked.Increment (ref clientChannelIdCounter)) })); 157 | ChannelRef channelRef = new ChannelRef(channel, count); 158 | channelRefs.Add(channelRef); 159 | return channelRef; 160 | } 161 | // If all channels are overloaded and the channel pool is full already, 162 | // return the channel with least active streams. 163 | return orderedChannelRefs.First(); 164 | } 165 | } 166 | 167 | private List GetAffinityKeysFromProto(string affinityKey, IMessage message) 168 | { 169 | List affinityKeyValues = new List(); 170 | if (!string.IsNullOrEmpty(affinityKey)) 171 | { 172 | string[] names = affinityKey.Split('.'); 173 | GetAffinityKeysFromProto(names, 0, message, affinityKeyValues); 174 | } 175 | return affinityKeyValues; 176 | } 177 | 178 | private void GetAffinityKeysFromProto(string[] names, int namesIndex, IMessage message, List affinityKeyValues) 179 | { 180 | if (namesIndex >= names.Length) 181 | { 182 | throw new InvalidOperationException($"Affinity key {string.Join(".", names)} missing field name for message {message.Descriptor.Name}."); 183 | } 184 | 185 | string name = names[namesIndex]; 186 | var field = message.Descriptor.FindFieldByName(name); 187 | if (field == null) 188 | { 189 | throw new InvalidOperationException($"Field {name} not present in message {message.Descriptor.Name}"); 190 | } 191 | var accessor = field.Accessor; 192 | if (accessor == null) 193 | { 194 | throw new InvalidOperationException($"Field {name} in message {message.Descriptor.Name} has no accessor"); 195 | } 196 | int lastIndex = names.Length - 1; 197 | switch (accessor.GetValue(message)) 198 | { 199 | case string text when namesIndex < lastIndex: 200 | case RepeatedField texts when namesIndex < lastIndex: 201 | throw new InvalidOperationException($"Field {name} in message {message.Descriptor.Name} is neither a message or repeated message field."); 202 | case string text: 203 | affinityKeyValues.Add(text); 204 | break; 205 | case RepeatedField texts: 206 | affinityKeyValues.AddRange(texts); 207 | break; 208 | case IMessage nestedMessage: 209 | GetAffinityKeysFromProto(names, namesIndex + 1, nestedMessage, affinityKeyValues); 210 | break; 211 | // We can't use RepeatedField because RepeatedField is not 212 | // covariant on T. But IEnumerable is covariant on T. 213 | // We can safely assume that any IEnumerable is really 214 | // a RepeatedField where T is IMessage. 215 | case IEnumerable nestedMessages: 216 | foreach (IMessage nestedMessage in nestedMessages) 217 | { 218 | GetAffinityKeysFromProto(names, namesIndex + 1, nestedMessage, affinityKeyValues); 219 | } 220 | break; 221 | case null: 222 | // Probably a nested message, but with no value. Just don't use an affinity key. 223 | break; 224 | default: 225 | throw new InvalidOperationException($"Field {name} in message {message.Descriptor.Name} is neither a string or repeated string field nor another message or repeated message field."); 226 | } 227 | } 228 | 229 | private void Bind(ChannelRef channelRef, string affinityKey) 230 | { 231 | if (!string.IsNullOrEmpty(affinityKey)) 232 | { 233 | lock (thisLock) 234 | { 235 | // TODO: What should we do if the dictionary already contains this key, but for a different channel ref? 236 | if (!channelRefByAffinityKey.Keys.Contains(affinityKey)) 237 | { 238 | channelRefByAffinityKey.Add(affinityKey, channelRef); 239 | } 240 | channelRefByAffinityKey[affinityKey].AffinityCountIncr(); 241 | } 242 | } 243 | } 244 | 245 | private void Unbind(string affinityKey) 246 | { 247 | if (!string.IsNullOrEmpty(affinityKey)) 248 | { 249 | lock (thisLock) 250 | { 251 | if (channelRefByAffinityKey.TryGetValue(affinityKey, out ChannelRef channelRef)) 252 | { 253 | int newCount = channelRef.AffinityCountDecr(); 254 | 255 | // We would expect it to be exactly 0, but it doesn't hurt to be cautious. 256 | if (newCount <= 0) 257 | { 258 | channelRefByAffinityKey.Remove(affinityKey); 259 | } 260 | } 261 | } 262 | } 263 | } 264 | 265 | private ChannelRef PreProcess(AffinityConfig affinityConfig, TRequest request) 266 | { 267 | // Gets the affinity bound key if required in the request method. 268 | string boundKey = null; 269 | if (affinityConfig != null && affinityConfig.Command == AffinityConfig.Types.Command.Bound) 270 | { 271 | boundKey = GetAffinityKeysFromProto(affinityConfig.AffinityKey, (IMessage)request).SingleOrDefault(); 272 | } 273 | 274 | ChannelRef channelRef = GetChannelRef(boundKey); 275 | channelRef.ActiveStreamCountIncr(); 276 | return channelRef; 277 | } 278 | 279 | // Note: response may be default(TResponse) in the case of a failure. We only expect to be called from 280 | // protobuf-based calls anyway, so it will always be a class type, and will never be null for success cases. 281 | // We can therefore check for nullity rather than having a separate "success" parameter. 282 | private void PostProcess(AffinityConfig affinityConfig, ChannelRef channelRef, TRequest request, TResponse response) 283 | { 284 | channelRef.ActiveStreamCountDecr(); 285 | // Process BIND or UNBIND if the method has affinity feature enabled, but only for successful calls. 286 | if (affinityConfig != null && response != null) 287 | { 288 | if (affinityConfig.Command == AffinityConfig.Types.Command.Bind) 289 | { 290 | foreach (string bindingKey in GetAffinityKeysFromProto(affinityConfig.AffinityKey, (IMessage)response)) 291 | { 292 | Bind(channelRef, bindingKey); 293 | } 294 | } 295 | else if (affinityConfig.Command == AffinityConfig.Types.Command.Unbind) 296 | { 297 | foreach (string unbindingKey in GetAffinityKeysFromProto(affinityConfig.AffinityKey, (IMessage)request)) 298 | { 299 | Unbind(unbindingKey); 300 | } 301 | } 302 | } 303 | } 304 | 305 | /// 306 | /// Invokes a client streaming call asynchronously. 307 | /// In client streaming scenario, client sends a stream of requests and server responds with a single response. 308 | /// 309 | public override AsyncClientStreamingCall 310 | AsyncClientStreamingCall(Method method, string host, CallOptions options) 311 | { 312 | // No channel affinity feature for client streaming call. 313 | ChannelRef channelRef = GetChannelRef(); 314 | var callDetails = new CallInvocationDetails(channelRef.Channel, method, host, options); 315 | var originalCall = Calls.AsyncClientStreamingCall(callDetails); 316 | 317 | // Decrease the active streams count once async response finishes. 318 | var gcpResponseAsync = DecrementCountAndPropagateResult(originalCall.ResponseAsync); 319 | 320 | // Create a wrapper of the original AsyncClientStreamingCall. 321 | return new AsyncClientStreamingCall( 322 | originalCall.RequestStream, 323 | gcpResponseAsync, 324 | originalCall.ResponseHeadersAsync, 325 | () => originalCall.GetStatus(), 326 | () => originalCall.GetTrailers(), 327 | () => originalCall.Dispose()); 328 | 329 | async Task DecrementCountAndPropagateResult(Task task) 330 | { 331 | try 332 | { 333 | return await task.ConfigureAwait(false); 334 | } 335 | finally 336 | { 337 | channelRef.ActiveStreamCountDecr(); 338 | } 339 | } 340 | } 341 | 342 | /// 343 | /// Invokes a duplex streaming call asynchronously. 344 | /// In duplex streaming scenario, client sends a stream of requests and server responds with a stream of responses. 345 | /// The response stream is completely independent and both side can be sending messages at the same time. 346 | /// 347 | public override AsyncDuplexStreamingCall 348 | AsyncDuplexStreamingCall(Method method, string host, CallOptions options) 349 | { 350 | // No channel affinity feature for duplex streaming call. 351 | ChannelRef channelRef = GetChannelRef(); 352 | var callDetails = new CallInvocationDetails(channelRef.Channel, method, host, options); 353 | var originalCall = Calls.AsyncDuplexStreamingCall(callDetails); 354 | 355 | // Decrease the active streams count once the streaming response finishes its final batch. 356 | var gcpResponseStream = new GcpClientResponseStream( 357 | originalCall.ResponseStream, 358 | (resp) => channelRef.ActiveStreamCountDecr()); 359 | 360 | // Create a wrapper of the original AsyncDuplexStreamingCall. 361 | return new AsyncDuplexStreamingCall( 362 | originalCall.RequestStream, 363 | gcpResponseStream, 364 | originalCall.ResponseHeadersAsync, 365 | () => originalCall.GetStatus(), 366 | () => originalCall.GetTrailers(), 367 | () => originalCall.Dispose()); 368 | } 369 | 370 | /// 371 | /// Invokes a server streaming call asynchronously. 372 | /// In server streaming scenario, client sends on request and server responds with a stream of responses. 373 | /// 374 | public override AsyncServerStreamingCall 375 | AsyncServerStreamingCall(Method method, string host, CallOptions options, TRequest request) 376 | { 377 | affinityByMethod.TryGetValue(method.FullName, out AffinityConfig affinityConfig); 378 | 379 | ChannelRef channelRef = PreProcess(affinityConfig, request); 380 | 381 | var callDetails = new CallInvocationDetails(channelRef.Channel, method, host, options); 382 | var originalCall = Calls.AsyncServerStreamingCall(callDetails, request); 383 | 384 | // Executes affinity postprocess once the streaming response finishes its final batch. 385 | var gcpResponseStream = new GcpClientResponseStream( 386 | originalCall.ResponseStream, 387 | (resp) => PostProcess(affinityConfig, channelRef, request, resp)); 388 | 389 | // Create a wrapper of the original AsyncServerStreamingCall. 390 | return new AsyncServerStreamingCall( 391 | gcpResponseStream, 392 | originalCall.ResponseHeadersAsync, 393 | () => originalCall.GetStatus(), 394 | () => originalCall.GetTrailers(), 395 | () => originalCall.Dispose()); 396 | 397 | } 398 | 399 | /// 400 | /// Invokes a simple remote call asynchronously. 401 | /// 402 | public override AsyncUnaryCall 403 | AsyncUnaryCall(Method method, string host, CallOptions options, TRequest request) 404 | { 405 | affinityByMethod.TryGetValue(method.FullName, out AffinityConfig affinityConfig); 406 | 407 | ChannelRef channelRef = PreProcess(affinityConfig, request); 408 | 409 | var callDetails = new CallInvocationDetails(channelRef.Channel, method, host, options); 410 | var originalCall = Calls.AsyncUnaryCall(callDetails, request); 411 | 412 | // Executes affinity postprocess once the async response finishes. 413 | var gcpResponseAsync = PostProcessPropagateResult(originalCall.ResponseAsync); 414 | 415 | // Create a wrapper of the original AsyncUnaryCall. 416 | return new AsyncUnaryCall( 417 | gcpResponseAsync, 418 | originalCall.ResponseHeadersAsync, 419 | () => originalCall.GetStatus(), 420 | () => originalCall.GetTrailers(), 421 | () => originalCall.Dispose()); 422 | 423 | async Task PostProcessPropagateResult(Task task) 424 | { 425 | TResponse response = default(TResponse); 426 | try 427 | { 428 | response = await task.ConfigureAwait(false); 429 | return response; 430 | } 431 | finally 432 | { 433 | PostProcess(affinityConfig, channelRef, request, response); 434 | } 435 | } 436 | } 437 | 438 | /// 439 | /// Invokes a simple remote call in a blocking fashion. 440 | /// 441 | public override TResponse 442 | BlockingUnaryCall(Method method, string host, CallOptions options, TRequest request) 443 | { 444 | affinityByMethod.TryGetValue(method.FullName, out AffinityConfig affinityConfig); 445 | 446 | ChannelRef channelRef = PreProcess(affinityConfig, request); 447 | 448 | var callDetails = new CallInvocationDetails(channelRef.Channel, method, host, options); 449 | TResponse response = default(TResponse); 450 | try 451 | { 452 | response = Calls.BlockingUnaryCall(callDetails, request); 453 | return response; 454 | } 455 | finally 456 | { 457 | PostProcess(affinityConfig, channelRef, request, response); 458 | } 459 | } 460 | 461 | /// 462 | /// Shuts down the all channels in the underlying channel pool cleanly. It is strongly 463 | /// recommended to shutdown all previously created channels before exiting from the process. 464 | /// 465 | public async Task ShutdownAsync() 466 | { 467 | for (int i = 0; i < channelRefs.Count; i++) 468 | { 469 | await channelRefs[i].Channel.ShutdownAsync(); 470 | } 471 | } 472 | 473 | // Test helper methods 474 | 475 | /// 476 | /// Returns a deep clone of the internal list of channel references. 477 | /// This method should only be used in tests. 478 | /// 479 | internal IList GetChannelRefsForTest() 480 | { 481 | lock (thisLock) 482 | { 483 | // Create an independent copy 484 | return channelRefs.Select(cr => cr.Clone()).ToList(); 485 | } 486 | } 487 | 488 | /// 489 | /// Returns a deep clone of the internal dictionary of channel references by affinity key. 490 | /// This method should only be used in tests. 491 | /// 492 | internal IDictionary GetChannelRefsByAffinityKeyForTest() 493 | { 494 | lock (thisLock) 495 | { 496 | // Create an independent copy 497 | return channelRefByAffinityKey.ToDictionary(pair => pair.Key, pair => pair.Value.Clone()); 498 | } 499 | } 500 | } 501 | } 502 | -------------------------------------------------------------------------------- /Grpc.Gcp/Grpc.Gcp/GcpClientResponseStream.cs: -------------------------------------------------------------------------------- 1 | using Grpc.Core; 2 | using System; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace Grpc.Gcp 7 | { 8 | /// 9 | /// A wrapper class for handling post process for server streaming responses. 10 | /// 11 | /// The type representing the request. 12 | /// The type representing the response. 13 | internal class GcpClientResponseStream : IAsyncStreamReader 14 | where TRequest : class 15 | where TResponse : class 16 | { 17 | bool callbackDone = false; 18 | readonly IAsyncStreamReader originalStreamReader; 19 | TResponse lastResponse; 20 | Action postProcess; 21 | 22 | public GcpClientResponseStream(IAsyncStreamReader originalStreamReader, Action postProcess) 23 | { 24 | this.originalStreamReader = originalStreamReader; 25 | this.postProcess = postProcess; 26 | } 27 | 28 | public TResponse Current 29 | { 30 | get 31 | { 32 | TResponse current = originalStreamReader.Current; 33 | // Record the last response. 34 | lastResponse = current; 35 | return current; 36 | } 37 | } 38 | 39 | public async Task MoveNext(CancellationToken token) 40 | { 41 | bool executeCallback = false; 42 | try 43 | { 44 | bool result = await originalStreamReader.MoveNext(token); 45 | // The last invocation of originalStreamReader.MoveNext returns false if it finishes successfully. 46 | if (!result) 47 | { 48 | executeCallback = true; 49 | } 50 | return result; 51 | } 52 | finally 53 | { 54 | // If stream is successfully processed or failed, execute the callback. 55 | // We ensure the callback is called only once. 56 | if (executeCallback && !callbackDone) 57 | { 58 | postProcess(lastResponse); 59 | callbackDone = true; 60 | } 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Grpc.Gcp/Grpc.Gcp/Grpc.Gcp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | net45;netstandard1.5 7 | Google Inc. 8 | Google Inc. 9 | Extension supporting Google Cloud Platform specific features for gRPC. 10 | Copyright 2018, Google Inc. 11 | https://www.apache.org/licenses/LICENSE-2.0 12 | https://github.com/GoogleCloudPlatform/grpc-gcp-csharp 13 | https://github.com/GoogleCloudPlatform/grpc-gcp-csharp 14 | git 15 | Google;Cloud;Grpc;GCP 16 | 2.0.0 17 | Grpc.Gcp v2.0.0 18 | - Upgrade gRPC version to v2.25.0 19 | true 20 | ../keys/Grpc.Gcp.snk 21 | 22 | 23 | 24 | 27 | 28 | netstandard1.5 29 | true 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Grpc.Gcp/keys/Grpc.Gcp.public.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleCloudPlatform/grpc-gcp-csharp/8ea25d92eb500d56207b0160343576c7a4ed7e04/Grpc.Gcp/keys/Grpc.Gcp.public.snk -------------------------------------------------------------------------------- /Grpc.Gcp/keys/Grpc.Gcp.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleCloudPlatform/grpc-gcp-csharp/8ea25d92eb500d56207b0160343576c7a4ed7e04/Grpc.Gcp/keys/Grpc.Gcp.snk -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gRPC for GCP extensions (C#) 2 | 3 | Copyright 2018 4 | [The gRPC Authors](https://github.com/grpc/grpc/blob/master/AUTHORS) 5 | 6 | ## About This Repository 7 | 8 | This repo is created to support GCP specific extensions for gRPC. To use the extension features, please refer to [Grpc.Gcp](Grpc.Gcp). 9 | 10 | This repo also contains supporting infrastructures such as end2end tests and benchmarks for accessing cloud APIs with gRPC client libraries. 11 | 12 | ## Testing 13 | 14 | ### Authentication 15 | 16 | Integration tests requires Google Cloud Platform credentials. See [Getting 17 | Started With 18 | Authentication](https://cloud.google.com/docs/authentication/getting-started). 19 | 20 | ```sh 21 | $ export GOOGLE_APPLICATION_CREDENTIALS=path/to/key.json 22 | ``` 23 | 24 | ### Run Tests 25 | 26 | Grpc.Gcp.IntegrationTest can be built for .NET Core or .NET Framework. 27 | 28 | If using Visual Studio 2017, open Grpc.Gcp.sln, and the tests will be loaded automatically under test explorer. 29 | 30 | For UNIX, use [dotnet 31 | cli](https://docs.microsoft.com/en-us/dotnet/core/tools/?tabs=netcore2x) to 32 | build and run tests. 33 | 34 | ```sh 35 | $ cd Grpc.Gcp/Grpc.Gcp.IntegrationTest 36 | $ dotnet build 37 | $ dotnet test 38 | ``` 39 | -------------------------------------------------------------------------------- /cloudprober/bins/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleCloudPlatform/grpc-gcp-csharp/8ea25d92eb500d56207b0160343576c7a4ed7e04/cloudprober/bins/.DS_Store -------------------------------------------------------------------------------- /cloudprober/bins/opt/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleCloudPlatform/grpc-gcp-csharp/8ea25d92eb500d56207b0160343576c7a4ed7e04/cloudprober/bins/opt/.DS_Store -------------------------------------------------------------------------------- /cloudprober/bins/opt/grpc_csharp_plugin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleCloudPlatform/grpc-gcp-csharp/8ea25d92eb500d56207b0160343576c7a4ed7e04/cloudprober/bins/opt/grpc_csharp_plugin -------------------------------------------------------------------------------- /cloudprober/cloudprober.cfg: -------------------------------------------------------------------------------- 1 | probe { 2 | type: EXTERNAL 3 | name: "spanner" 4 | interval_msec: 1800000 5 | timeout_msec: 30000 6 | targets { dummy_targets {} } # No targets for external probe 7 | external_probe { 8 | mode: ONCE 9 | command: "dotnet run --api spanner" 10 | } 11 | } 12 | 13 | probe { 14 | type: EXTERNAL 15 | name: "firestore" 16 | interval_msec: 1800000 17 | timeout_msec: 30000 18 | targets { dummy_targets {} } # No targets for external probe 19 | external_probe { 20 | mode: ONCE 21 | command: "dotnet run --api firestore" 22 | } 23 | } 24 | 25 | surfacer { 26 | type: STACKDRIVER 27 | name: "stackdriver" 28 | stackdriver_surfacer { 29 | monitoring_url: "custom.googleapis.com/cloudprober/" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cloudprober/codegen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd "$(dirname "$0")" 3 | 4 | rm -rf google 5 | 6 | for p in $(find ../third_party/googleapis/google -type f -name *.proto); do 7 | protoc \ 8 | --proto_path=../third_party/googleapis \ 9 | --csharp_out=./ \ 10 | --grpc_out=./output \ 11 | --plugin=protoc-gen-grpc=./bins/opt/grpc_csharp_plugin \ 12 | "$p" 13 | done 14 | -------------------------------------------------------------------------------- /cloudprober/grpc_gcp_prober/firestore_probes.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using Google.Cloud.Firestore.V1Beta1; 4 | using ProbeTestsBase; 5 | 6 | namespace FirestoreProbesTest 7 | { 8 | 9 | public class FirestoreProbesTestClass : ProbeTestsBaseClass 10 | { 11 | static private readonly Stopwatch stopwatch = new Stopwatch(); 12 | private static string _PARENT_RESOURCE = "projects/grpc-prober-testing/databases/(default)/documents"; 13 | private Dictionary probFunctions; 14 | 15 | public FirestoreProbesTestClass() 16 | { 17 | //Constructor 18 | this.probFunctions = new Dictionary(){ 19 | {"documents", "document"} 20 | }; 21 | } 22 | 23 | public static void document(Firestore.FirestoreClient client, ref Dictionary metrics){ 24 | 25 | ListDocumentsRequest list_document_request = new ListDocumentsRequest(); 26 | list_document_request.Parent = _PARENT_RESOURCE; 27 | 28 | stopwatch.Start(); 29 | client.ListDocuments(list_document_request); 30 | stopwatch.Stop(); 31 | 32 | 33 | long lantency = stopwatch.ElapsedMilliseconds; 34 | metrics.Add("list_documents_latency_ms", lantency); 35 | } 36 | 37 | public Dictionary GetProbFunctions() 38 | { 39 | return this.probFunctions; 40 | } 41 | } 42 | } 43 | 44 | 45 | -------------------------------------------------------------------------------- /cloudprober/grpc_gcp_prober/grpc_gcp_prober.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | false 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /cloudprober/grpc_gcp_prober/prober.cs: -------------------------------------------------------------------------------- 1 | using FirestoreProbesTest; 2 | using SpannerProbesTest; 3 | using ProbeTestsBase; 4 | using Google.Cloud.Firestore.V1Beta1; 5 | using Google.Cloud.Spanner.V1; 6 | using Google.Apis.Auth.OAuth2; 7 | using Grpc.Core; 8 | using Grpc.Gcp; 9 | using Grpc.Auth; 10 | using System; 11 | using System.Reflection; 12 | using StackdriverUtil; 13 | using System.Collections.Generic; 14 | 15 | namespace ProberTest 16 | { 17 | public class Prober 18 | { 19 | //private static string _OAUTH_SCOPE = "https://www.googleapis.com/auth/cloud-platform"; 20 | private static string _FIRESTORE_TARGET = "firestore.googleapis.com:443"; 21 | private static string _SPANNER_TARGET = "spanner.googleapis.com:443"; 22 | 23 | private static ApiConfig config = new ApiConfig(); 24 | 25 | static int Main(string[] args) 26 | { 27 | 28 | if (args.Length == 0){ 29 | Console.WriteLine("Please enter a numeric argument"); 30 | return 1; 31 | } 32 | 33 | if (args[0] != "--api") 34 | { 35 | Console.WriteLine("Type (--api api_name) to continue ..."); 36 | return 1; 37 | } 38 | 39 | executeProbes(args[1]); 40 | return 0; 41 | } 42 | 43 | public static void executeProbes(string api) 44 | { 45 | StackdriverUtilClass util = new StackdriverUtilClass(api); 46 | GoogleCredential auth = GoogleCredential.GetApplicationDefault(); 47 | ClientBase client; 48 | ProbeTestsBaseClass test; 49 | System.Type type; 50 | 51 | Dictionary probe_functions = new Dictionary(); 52 | 53 | if (api == "firestore") 54 | { 55 | Grpc.Core.Channel channel = new Grpc.Core.Channel(_FIRESTORE_TARGET, GoogleGrpcCredentials.ToChannelCredentials(auth)); 56 | client = new Firestore.FirestoreClient(channel); 57 | test = new FirestoreProbesTestClass(); 58 | probe_functions = (test as FirestoreProbesTestClass).GetProbFunctions(); 59 | type = typeof(FirestoreProbesTestClass); 60 | } 61 | else if(api == "spanner") 62 | { 63 | Grpc.Core.Channel channel = new Grpc.Core.Channel(_SPANNER_TARGET, GoogleGrpcCredentials.ToChannelCredentials(auth)); 64 | client = new Spanner.SpannerClient(channel); 65 | test = new SpannerProbesTestClass(); 66 | probe_functions = (test as SpannerProbesTestClass).GetProbFunctions(); 67 | type = typeof(SpannerProbesTestClass); 68 | } 69 | else 70 | { 71 | Console.WriteLine("grpc not implemented for {0}", api); 72 | return; 73 | } 74 | 75 | //object value = test.GetType().GetMethod("GetProbFunctions").Invoke(test,null); 76 | //probe_functions = value.GetType().GetProperties() 77 | 78 | int total = probe_functions.Count; 79 | int success = 0; 80 | Dictionary metrics = new Dictionary(); 81 | 82 | foreach (var probe in probe_functions){ 83 | MethodInfo fun = type.GetMethod(probe.Value); 84 | object[] parameters = new object[] { client, metrics }; 85 | 86 | try 87 | { 88 | if(api == "firestore") 89 | { 90 | fun.Invoke((test as FirestoreProbesTestClass), parameters); 91 | } 92 | else 93 | { 94 | fun.Invoke((test as SpannerProbesTestClass), parameters); 95 | } 96 | 97 | success++; 98 | } 99 | catch(Exception error) 100 | { 101 | Console.WriteLine("{0}", error); 102 | util.reportError(error); 103 | } 104 | } 105 | 106 | if(success == total){ 107 | util.setSuccess(true); 108 | } 109 | util.addMetrics(metrics); 110 | util.outputMetrics(); 111 | 112 | if(success != total){ 113 | return; 114 | } 115 | } 116 | 117 | } 118 | } 119 | 120 | -------------------------------------------------------------------------------- /cloudprober/grpc_gcp_prober/probetests_base.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace ProbeTestsBase 3 | { 4 | public class ProbeTestsBaseClass 5 | { 6 | public ProbeTestsBaseClass() 7 | { 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /cloudprober/grpc_gcp_prober/spanner_probes.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using Grpc.Core; 4 | using Google.Cloud.Spanner.V1; 5 | using ProbeTestsBase; 6 | 7 | namespace SpannerProbesTest 8 | { 9 | public class SpannerProbesTestClass : ProbeTestsBaseClass 10 | { 11 | private Dictionary probFunctions; 12 | private static string _DATABASE = "projects/grpc-prober-testing/instances/test-instance/databases/test-db"; 13 | //private static string _TEST_USERNAME = "test_username"; 14 | static private readonly Stopwatch stopwatch = new Stopwatch(); 15 | 16 | /* 17 | * Constructor of SpannerProbesTestClass 18 | * Initialize probFunctions 19 | */ 20 | 21 | public SpannerProbesTestClass() 22 | { 23 | this.probFunctions = new Dictionary() 24 | { 25 | {"session_management", "sessionManagement"}, 26 | {"execute_sql", "executeSql"}, 27 | {"read", "read"}, 28 | {"transaction", "transaction"}, 29 | {"partition", "partition"} 30 | }; 31 | } 32 | 33 | /* 34 | * Return probFunctions 35 | */ 36 | 37 | public Dictionary GetProbFunctions() 38 | { 39 | return this.probFunctions; 40 | } 41 | 42 | /* 43 | * Create a Session from client 44 | */ 45 | 46 | public static Session StartSession(Spanner.SpannerClient client) 47 | { 48 | CreateSessionRequest createSessionRequest = new CreateSessionRequest(); 49 | createSessionRequest.Database = _DATABASE; 50 | return client.CreateSession(createSessionRequest); 51 | } 52 | 53 | /* 54 | * Delete Current Session 55 | */ 56 | 57 | public static void EndSession(Spanner.SpannerClient client ,Session session) 58 | { 59 | DeleteSessionRequest deleteSessionRequest = new DeleteSessionRequest(); 60 | deleteSessionRequest.Name = session.Name; 61 | client.DeleteSession(deleteSessionRequest); 62 | return; 63 | } 64 | 65 | /* 66 | Probes to test session related grpc call from Spanner stub. 67 | 68 | Includes tests against CreateSession, GetSession, ListSessions, and 69 | DeleteSession of Spanner stub. 70 | 71 | Args: 72 | stub: An object of SpannerStub. 73 | metrics: A list of metrics. 74 | 75 | */ 76 | 77 | public static void sessionManagement(Spanner.SpannerClient client, ref Dictionary metrics) 78 | { 79 | long latency; 80 | CreateSessionRequest createSessionRequest = new CreateSessionRequest(); 81 | createSessionRequest.Database = _DATABASE; 82 | //Create Session test 83 | //Create 84 | stopwatch.Start(); 85 | Session session = client.CreateSession(createSessionRequest); 86 | stopwatch.Stop(); 87 | latency = stopwatch.ElapsedMilliseconds; 88 | metrics.Add("create_session_latency_ms", latency); 89 | 90 | //Get Session 91 | GetSessionRequest getSessionRequest = new GetSessionRequest(); 92 | getSessionRequest.Name = session.Name; 93 | stopwatch.Start(); 94 | client.GetSession(getSessionRequest); 95 | stopwatch.Stop(); 96 | latency = stopwatch.ElapsedMilliseconds; 97 | metrics.Add("get_session_latency_ms", latency); 98 | 99 | //List Session 100 | ListSessionsRequest listSessionsRequest = new ListSessionsRequest(); 101 | listSessionsRequest.Database = _DATABASE; 102 | stopwatch.Start(); 103 | client.ListSessions(listSessionsRequest); 104 | stopwatch.Stop(); 105 | latency = stopwatch.ElapsedMilliseconds; 106 | metrics.Add("list_sessions_latency_ms", latency); 107 | 108 | //Delete Session 109 | DeleteSessionRequest deleteSessionRequest = new DeleteSessionRequest(); 110 | deleteSessionRequest.Name = session.Name; 111 | stopwatch.Start(); 112 | client.DeleteSession(deleteSessionRequest); 113 | stopwatch.Stop(); 114 | latency = stopwatch.ElapsedMilliseconds; 115 | metrics.Add("delete_session_latency_ms", latency); 116 | } 117 | 118 | /* 119 | Probes to test ExecuteSql and ExecuteStreamingSql call from Spanner stub. 120 | 121 | Args: 122 | stub: An object of SpannerStub. 123 | metrics: A list of metrics. 124 | 125 | */ 126 | 127 | public static void executeSql(Spanner.SpannerClient client, ref Dictionarymetrics) 128 | { 129 | long latency; 130 | 131 | //Create Session 132 | Session session = StartSession(client); 133 | 134 | //Probing ExecuteSql Call 135 | stopwatch.Start(); 136 | ExecuteSqlRequest executeSqlRequest = new ExecuteSqlRequest(); 137 | executeSqlRequest.Session = session.Name; 138 | executeSqlRequest.Sql = "select * FROM users"; 139 | client.ExecuteSql(executeSqlRequest); 140 | stopwatch.Stop(); 141 | latency = stopwatch.ElapsedMilliseconds; 142 | metrics.Add("execute_sql_latency_ms", latency); 143 | 144 | //Probing ExecuteStreamingSql Call 145 | AsyncServerStreamingCall partial_result_set = client.ExecuteStreamingSql(executeSqlRequest); 146 | 147 | stopwatch.Start(); 148 | var header = partial_result_set.ResponseHeadersAsync; 149 | stopwatch.Stop(); 150 | latency = stopwatch.ElapsedMilliseconds; 151 | metrics.Add("execute_streaming_sql_latency_ms", latency); 152 | 153 | //Delete Session 154 | EndSession(client, session); 155 | } 156 | 157 | /* 158 | Probe to test Read and StreamingRead grpc call from Spanner stub. 159 | 160 | Args: 161 | stub: An object of SpannerStub. 162 | metrics: A list of metrics. 163 | */ 164 | 165 | public static void read(Spanner.SpannerClient client, ref Dictionary metrics) 166 | { 167 | long latency; 168 | 169 | //Create Session 170 | Session session = StartSession(client); 171 | 172 | //Probing Read Call 173 | stopwatch.Start(); 174 | ReadRequest readRequest = new ReadRequest(); 175 | readRequest.Session = session.Name; 176 | readRequest.Table = "users"; 177 | KeySet keyset = new KeySet(); 178 | keyset.All = true; 179 | readRequest.KeySet = keyset; 180 | client.Read(readRequest); 181 | stopwatch.Stop(); 182 | 183 | latency = stopwatch.ElapsedMilliseconds; 184 | metrics.Add("read_latency_ms", latency); 185 | 186 | //Probing StreamingRead Call 187 | AsyncServerStreamingCall result_set = client.StreamingRead(readRequest); 188 | stopwatch.Start(); 189 | var header = result_set.ResponseHeadersAsync; 190 | stopwatch.Stop(); 191 | latency = stopwatch.ElapsedMilliseconds; 192 | metrics.Add("streaming_read_latency_ms", latency); 193 | 194 | //Delete Session 195 | EndSession(client, session); 196 | } 197 | 198 | /* 199 | Probe to test BeginTransaction, Commit and Rollback grpc from Spanner stub. 200 | 201 | Args: 202 | stub: An object of SpannerStub. 203 | metrics: A list of metrics. 204 | */ 205 | 206 | public static void transaction(Spanner.SpannerClient client, ref Dictionary metrics) 207 | { 208 | long latency; 209 | 210 | //Start Session 211 | Session session = StartSession(client); 212 | 213 | TransactionOptions txn_options = new TransactionOptions(); 214 | TransactionOptions.Types.ReadWrite rw = new TransactionOptions.Types.ReadWrite(); 215 | txn_options.ReadWrite = rw; 216 | BeginTransactionRequest txn_request = new BeginTransactionRequest(); 217 | txn_request.Session = session.Name; 218 | txn_request.Options = txn_options; 219 | 220 | //Probing BeginTransaction Call 221 | stopwatch.Start(); 222 | Transaction txn = client.BeginTransaction(txn_request); 223 | stopwatch.Stop(); 224 | latency = stopwatch.ElapsedMilliseconds; 225 | metrics.Add("begin_transaction_latency_ms", latency); 226 | 227 | CommitRequest commitRequest = new CommitRequest(); 228 | commitRequest.Session = session.Name; 229 | commitRequest.TransactionId = txn.Id; 230 | 231 | //Probing Commit Call 232 | stopwatch.Start(); 233 | client.Commit(commitRequest); 234 | stopwatch.Stop(); 235 | latency = stopwatch.ElapsedMilliseconds; 236 | metrics.Add("commit_latency_ms", latency); 237 | 238 | txn = client.BeginTransaction(txn_request); 239 | RollbackRequest rollbackRequest = new RollbackRequest(); 240 | rollbackRequest.Session = session.Name; 241 | rollbackRequest.TransactionId = txn.Id; 242 | 243 | //Probing Rollback Call 244 | stopwatch.Start(); 245 | client.Rollback(rollbackRequest); 246 | stopwatch.Stop(); 247 | latency = stopwatch.ElapsedMilliseconds; 248 | metrics.Add("rollback_latency_ms", latency); 249 | 250 | //Delete Session 251 | EndSession(client, session); 252 | } 253 | 254 | /* 255 | Probe to test PartitionQuery and PartitionRead grpc call from Spanner stub. 256 | 257 | Args: 258 | stub: An object of SpannerStub. 259 | metrics: A list of metrics. 260 | */ 261 | 262 | public static void partition(Spanner.SpannerClient client, ref Dictionary metrics) 263 | { 264 | long latency; 265 | 266 | //Start Session 267 | Session session = StartSession(client); 268 | 269 | TransactionOptions txn_options = new TransactionOptions(); 270 | TransactionOptions.Types.ReadOnly ro = new TransactionOptions.Types.ReadOnly(); 271 | txn_options.ReadOnly = ro; 272 | TransactionSelector txn_selector = new TransactionSelector(); 273 | txn_selector.Begin = txn_options; 274 | 275 | //Probing PartitionQuery call 276 | PartitionQueryRequest ptn_query_request = new PartitionQueryRequest(); 277 | ptn_query_request.Session = session.Name; 278 | ptn_query_request.Sql = "select * FROM users"; 279 | ptn_query_request.Transaction = txn_selector; 280 | 281 | stopwatch.Start(); 282 | client.PartitionQuery(ptn_query_request); 283 | stopwatch.Stop(); 284 | latency = stopwatch.ElapsedMilliseconds; 285 | metrics.Add("partition_query_latency_ms", latency); 286 | 287 | //Probing PartitionRead Call 288 | PartitionReadRequest ptn_read_request = new PartitionReadRequest(); 289 | ptn_read_request.Session = session.Name; 290 | ptn_read_request.Table = "users"; 291 | ptn_read_request.Transaction = txn_selector; 292 | KeySet keyset = new KeySet(); 293 | keyset.All = true; 294 | ptn_read_request.KeySet = keyset; 295 | stopwatch.Start(); 296 | client.PartitionRead(ptn_read_request); 297 | stopwatch.Stop(); 298 | latency = stopwatch.ElapsedMilliseconds; 299 | metrics.Add("partition_read_latency_ms", latency); 300 | 301 | //Delete Session 302 | EndSession(client, session); 303 | 304 | } 305 | 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /cloudprober/grpc_gcp_prober/stackdriver_util.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Google.Cloud.ErrorReporting.V1Beta1; 5 | using Google.Api.Gax.ResourceNames; 6 | 7 | namespace StackdriverUtil 8 | { 9 | public class StackdriverUtilClass 10 | { 11 | private string api; 12 | private Dictionary metrics; 13 | private bool success; 14 | private ReportErrorsServiceClient err_client; 15 | 16 | public StackdriverUtilClass(string api) 17 | { 18 | this.api = api; 19 | this.metrics = new Dictionary(); 20 | this.success = false; 21 | this.err_client = ReportErrorsServiceClient.Create(); 22 | } 23 | 24 | public void addMetric(string key, long val) 25 | { 26 | this.metrics.Add(key, val); 27 | return; 28 | } 29 | 30 | public void addMetrics(Dictionary metrics) 31 | { 32 | metrics.ToList().ForEach(x => this.metrics.Add(x.Key, x.Value)); 33 | return; 34 | } 35 | 36 | public void setSuccess(bool result) 37 | { 38 | this.success = result; 39 | return; 40 | } 41 | 42 | public void outputMetrics() 43 | { 44 | if (this.success) 45 | { 46 | Console.WriteLine("{0}_success 1", this.api); 47 | } 48 | else 49 | { 50 | Console.WriteLine("{0}_success 0", this.api); 51 | } 52 | foreach(var ele in this.metrics) 53 | { 54 | Console.WriteLine("{0} {1}",ele.Key, ele.Value); 55 | } 56 | return; 57 | } 58 | 59 | public void reportError(Exception err) 60 | { 61 | string projectId = "434076015357"; 62 | ProjectName project_name = new ProjectName(projectId); 63 | string err_mssage = err.ToString(); 64 | 65 | ReportedErrorEvent error_event = new ReportedErrorEvent(); 66 | ErrorContext context = new ErrorContext(); 67 | SourceLocation location = new SourceLocation(); 68 | location.FunctionName = this.api; 69 | context.ReportLocation = location; 70 | error_event.Context = context; 71 | error_event.Message = "CSharpProbeFailure: fails on {$this.api} API. Details: {$err_message}"; 72 | 73 | this.err_client.ReportErrorEvent(project_name, error_event); 74 | return; 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /codegen.bat: -------------------------------------------------------------------------------- 1 | packages\Grpc.Tools.1.12.0\tools\windows_x86\protoc.exe -Iprotos --csharp_out Grpc.Gcp/Grpc.Gcp protos/grpc_gcp.proto 2 | packages\Grpc.Tools.1.12.0\tools\windows_x86\protoc.exe -Iprotos --grpc_out Grpc.Gcp/Grpc.Gcp.IntegrationTest --csharp_out Grpc.Gcp/Grpc.Gcp.IntegrationTest protos/test_service.proto --plugin=protoc-gen-grpc=packages\Grpc.Tools.1.12.0\tools\windows_x86\grpc_csharp_plugin.exe 3 | -------------------------------------------------------------------------------- /doc/grpc-client-user-guide.md: -------------------------------------------------------------------------------- 1 | # gRPC Client User Guide 2 | 3 | ## Overview 4 | 5 | Instructions for creating grpc client and make request to Google Cloud APIs. 6 | This can be used to test the functionality of the gRPC calls to the Cloud 7 | Services. For this instruction, we take Firestore API as an example. 8 | 9 | ## Prerequisites 10 | 11 | ### Install .NET Core SDK 12 | 13 | Install system components 14 | 15 | ```sh 16 | $ sudo apt-get update 17 | $ sudo apt-get install curl libunwind8 gettext apt-transport-https 18 | ``` 19 | 20 | Register the trusted Microsoft Product key 21 | 22 | ```sh 23 | $ curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg 24 | $ sudo mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg 25 | ``` 26 | 27 | Register Microsoft Product feed 28 | 29 | ```sh 30 | $ sudo sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-debian-stretch-prod stretch main" > /etc/apt/sources.list.d/dotnetdev.list' 31 | ``` 32 | 33 | Update the products available for installation, then install the .NET SDK 34 | 35 | ```sh 36 | $ sudo apt-get update 37 | $ sudo apt-get install dotnet-sdk-2.1.105 38 | ``` 39 | 40 | To test dotnet is installed successfully 41 | 42 | ```sh 43 | $ dotnet --version 44 | ``` 45 | 46 | ### Disable dotnet SDK telemetry 47 | 48 | dotnet telemetry is enabled by default. To disable, add this line to your 49 | ~/.bashrc file (and restart the shell): 50 | 51 | ```sh 52 | $ export DOTNET_CLI_TELEMETRY_OPTOUT=true 53 | ``` 54 | 55 | ## Main Steps 56 | 57 | ### Setup C# project for Cloud API 58 | 59 | Create a root directory to hold the projects we are going to create. For example 60 | we use a folder called grpc-client-test: 61 | 62 | ```sh 63 | $ mkdir ~/grpc-client-test 64 | $ cd ~/grpc-client-test 65 | ``` 66 | 67 | Create C# project for Firestore API: 68 | 69 | ```sh 70 | $ dotnet new console -o Google.Cloud.Firestore.V1Beta1 71 | ``` 72 | 73 | Install grpc and protobuf packages: 74 | 75 | ```sh 76 | $ dotnet add Google.Cloud.Firestore.V1Beta1 package Grpc 77 | $ dotnet add Google.Cloud.Firestore.V1Beta1 package Grpc.Tools 78 | $ dotnet add Google.Cloud.Firestore.V1Beta1 package Google.Protobuf 79 | $ dotnet add Google.Cloud.Firestore.V1Beta1 package Google.Protobuf.Tools 80 | $ dotnet add Google.Cloud.Firestore.V1Beta1 package Google.Api.Gax.Grpc 81 | ``` 82 | 83 | Alternatively, we can manually add the dependencies to the 84 | Google.Cloud.Firestore.V1Beta1.csproj file, and then use `dotnet restore` to 85 | install those packages: 86 | 87 | ```csproj 88 | 89 | 90 | ... 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | ``` 102 | 103 | These packages will be installed to the default location for global-packages. To 104 | check the default locations: 105 | 106 | ```sh 107 | $ dotnet nuget locals all --list 108 | ``` 109 | 110 | ### Generate grpc C# code for Google Cloud API 111 | 112 | Download .proto file from [googleapis](https://github.com/googleapis/googleapis) 113 | 114 | ```sh 115 | $ git clone https://github.com/googleapis/googleapis.git 116 | ``` 117 | 118 | Use protoc from Grpc.Tools to generate grpc client code based on the proto file 119 | we have just downloaded. You will need to specify not only the .proto file that 120 | you want to code-generate, but also the dependency proto libraries they share. 121 | For example, if foo.proto has dependency on bar.proto, you need to specify both 122 | foo.proto and bar.proto in the code generation command. 123 | 124 | In this example, we only need the .proto files under googleapis/google/firestore/v1beta1/ 125 | 126 | ```sh 127 | # Setup environment variables. 128 | PROTOC=$HOME/.nuget/packages/grpc.tools/1.11.0/tools/linux_x64/protoc 129 | GRPC_PLUGIN=$HOME/.nuget/packages/grpc.tools/1.11.0/tools/linux_x64/grpc_csharp_plugin 130 | PROTOBUF_TOOLS_DIR=$HOME/.nuget/packages/google.protobuf.tools/3.5.1/tools/ 131 | OUT_DIR=Google.Cloud.Firestore.V1Beta1/ 132 | 133 | # Generate csharp code. 134 | $PROTOC -I googleapis/ -I $PROTOBUF_TOOLS_DIR \ 135 | --csharp_out $OUT_DIR \ 136 | --grpc_out $OUT_DIR \ 137 | --plugin=protoc-gen-grpc=$GRPC_PLUGIN \ 138 | googleapis/google/firestore/v1beta1/*proto 139 | ``` 140 | 141 | ### Create C# project for gRPC client 142 | 143 | First we need to create a C# project for the test client: 144 | 145 | ```sh 146 | $ dotnet new console -o Google.Cloud.Firestore.V1Beta1.Test 147 | ``` 148 | 149 | In 150 | Google.Cloud.Firestore.V1Beta1.Test/Google.Cloud.Firestore.V1Beta1.Test.csproj 151 | add a ProjectReference to ItemGroup to include the API project we created 152 | before. 153 | 154 | ```csproj 155 | 156 | 157 | ... 158 | 159 | 160 | 161 | 162 | 163 | ``` 164 | 165 | In Google.Cloud.Firestore.V1Beta1.Test/Program.cs, write client code to make 166 | grpc calls to Firestore API. 167 | 168 | ```cs 169 | using System; 170 | using Google.Cloud.Firestore.V1Beta1; 171 | using sco = System.Collections.ObjectModel; 172 | using gaxgrpc = Google.Api.Gax.Grpc; 173 | using grpccore = Grpc.Core; 174 | 175 | namespace Google.Cloud.Firestore.V1Beta1.Test 176 | { 177 | class Program 178 | { 179 | static void Main(string[] args) 180 | { 181 | // Create a channel pool with scopes. 182 | string[] scopes = new string[] { 183 | "https://www.googleapis.com/auth/cloud-platform", 184 | }; 185 | gaxgrpc::ChannelPool channelPool = new gaxgrpc::ChannelPool(scopes); 186 | 187 | // Get a channel from channel pool for a specified endpoint. 188 | gaxgrpc::ServiceEndpoint endpoint = new gaxgrpc::ServiceEndpoint( 189 | "firestore.googleapis.com", 443); 190 | grpccore::Channel channel = ChannelPool.GetChannel(endpoint); 191 | 192 | // Initialize a grpc client of firestore using the channel just created. 193 | Firestore.FirestoreClient grpcClient = new Firestore.FirestoreClient(channel); 194 | 195 | // Make a ListDocumentsRequest to firestore API and check the response. 196 | ListDocumentsRequest request = new ListDocumentsRequest{ 197 | Parent = "projects//databases/(default)/documents" 198 | }; 199 | ListDocumentsResponse response = grpcClient.ListDocuments(request); 200 | Console.WriteLine(response); 201 | } 202 | } 203 | } 204 | ``` 205 | 206 | ### Authentication 207 | 208 | If running on Google Cloud Platform (GCP) then authentication is already setup. 209 | Otherwise, download a service account key file from your GCP project. See 210 | [Getting Started With Authentication](https://cloud.google.com/docs/authentication/getting-started) for more details. 211 | 212 | After you downloaded the JSON key file, set the following environment variable 213 | to refer to the file: 214 | 215 | ```sh 216 | $ export GOOGLE_APPLICATION_CREDENTIALS=path/to/key.json 217 | ``` 218 | 219 | ### Build and run application 220 | 221 | ```sh 222 | $ dotnet build Google.Cloud.Firestore.V1Beta1.Test 223 | $ dotnet run -p Google.Cloud.Firestore.V1Beta1.Test 224 | ``` 225 | 226 | -------------------------------------------------------------------------------- /doc/grpc-firestore-example.md: -------------------------------------------------------------------------------- 1 | gRPC Firestore Example (C#) 2 | =========================== 3 | 4 | This is an example of utilizing Cloud Firestore using gRPC in C#. 5 | 6 | BACKGROUND 7 | ------------- 8 | For this sample, it is assumed the C# code for access to Firestore from the gRPC proto files has already been done. 9 | 10 | PREREQUISITES 11 | ------------- 12 | 13 | - Mac OS X: Visual Studio for Mac Community 14 | 15 | BUILD 16 | ------- 17 | 18 | Download the source files from github. 19 | 20 | # Using Visual Studio 21 | 22 | * Create a project 23 | * Add the source files 24 | * Add the C# generated files from the gRPC Firestore proto files 25 | * Add the following packages: 26 | - Google.Apis 27 | - Google.Apis.Auth 28 | - Google.Apis.Core 29 | - Google.Apis.Storage.v1 30 | - Google.Cloud.Datastore.V1 31 | - Google.Cloud.DevTools.Common 32 | - Google.Cloud.Storage.V1 33 | - Google.LongRunning 34 | - Google.Protobuf 35 | - Grpc 36 | - Grpc.Auth 37 | - Grpc.Core 38 | - Grpc.Tools 39 | 40 | * Build the project 41 | 42 | Try it! 43 | ------- 44 | 45 | * Add the environment varable GOOGLE_APPLICATION_CREDENTIALS to the Project options pointing to your Application Default Credentials (ADC) file generated from the GCP Console. 46 | More info is here: https://cloud.google.com/docs/authentication/getting-started 47 | 48 | * Run the application from the Visual Studio IDE or from the command line with: 49 | ``` 50 | export GOOGLE_APPLICATION_CREDENTIALS="" 51 | mono /bin/Debug/.exe 52 | ``` 53 | -------------------------------------------------------------------------------- /firestore/examples/end2end/doc/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleCloudPlatform/grpc-gcp-csharp/8ea25d92eb500d56207b0160343576c7a4ed7e04/firestore/examples/end2end/doc/.gitignore -------------------------------------------------------------------------------- /firestore/examples/end2end/src/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleCloudPlatform/grpc-gcp-csharp/8ea25d92eb500d56207b0160343576c7a4ed7e04/firestore/examples/end2end/src/.gitignore -------------------------------------------------------------------------------- /firestore/examples/end2end/src/BatchGetDocuments.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using Google.Apis.Auth.OAuth2; 4 | using Google.Cloud.Storage.V1; 5 | using Google.Apis.Services; 6 | using Google.Type; 7 | using Google.Cloud.Firestore.Admin.V1Beta1; 8 | using Google.Cloud.Firestore.V1Beta1; 9 | using Grpc.Auth; 10 | 11 | namespace FirestoreTest 12 | { 13 | public partial class FirestoreTestClass 14 | { 15 | public async System.Threading.Tasks.Task FSBatchGetDocuments() 16 | { 17 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\n:: Batch Retrieve Documents ::\n"); 18 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, "\nAvailable Docments:\n"); 19 | ListDocuments(); 20 | 21 | var batchGetDocumentsRequest = new BatchGetDocumentsRequest(); 22 | batchGetDocumentsRequest.Database = Parent; 23 | 24 | while (true) 25 | { 26 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Yellow, "\n\nEnter Collection [cities]: "); 27 | string collectionId = Console.ReadLine(); 28 | if (collectionId == "") 29 | { 30 | collectionId = "cities"; 31 | } 32 | 33 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Yellow, "\nEnter Document Name (blank when finished): "); 34 | string documentName = Console.ReadLine(); 35 | if (documentName != "") 36 | { 37 | Document doc = null; 38 | try 39 | { 40 | doc = GetDocument(documentName, collectionId); 41 | } 42 | catch (Grpc.Core.RpcException e) 43 | { 44 | if (e.Status.StatusCode == Grpc.Core.StatusCode.NotFound) 45 | { 46 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Red, "ERROR: Document " + documentName + " not found, ignoring ..."); 47 | } 48 | continue; 49 | } 50 | batchGetDocumentsRequest.Documents.Add(doc.Name); 51 | } 52 | else 53 | { 54 | break; 55 | } 56 | } 57 | 58 | var batchGetDocumentsResponse = new BatchGetDocumentsResponse(); 59 | try 60 | { 61 | var ret = FsClient.BatchGetDocuments(batchGetDocumentsRequest); 62 | var responseStream = ret.ResponseStream; 63 | var cancellationToken = new System.Threading.CancellationToken(); 64 | while (await responseStream.MoveNext(cancellationToken)) 65 | { 66 | batchGetDocumentsResponse = responseStream.Current; 67 | Utils.ReadDocument(batchGetDocumentsResponse.Found); 68 | } 69 | } 70 | catch (Exception e) 71 | { 72 | Console.WriteLine("{0} Exception caught.", e); 73 | return; 74 | } 75 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\nSuccessfully batch retrieved documents!\n"); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /firestore/examples/end2end/src/BeginTransaction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Google.Apis.Auth.OAuth2; 3 | using Google.Cloud.Storage.V1; 4 | using Google.Apis.Services; 5 | using Google.Type; 6 | using Google.Cloud.Firestore.Admin.V1Beta1; 7 | using Google.Cloud.Firestore.V1Beta1; 8 | using Grpc.Auth; 9 | 10 | namespace FirestoreTest 11 | { 12 | public partial class FirestoreTestClass 13 | { 14 | public void FSBeginTransaction() 15 | { 16 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\n :: Starting New Transaction ::\n"); 17 | if (TransactionId != null) 18 | { 19 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Yellow, "Transaction in play, returning!\n"); 20 | return; 21 | } 22 | var options = new TransactionOptions(); 23 | var beginTransactionRequest = new BeginTransactionRequest(); 24 | beginTransactionRequest.Database = Parent; 25 | beginTransactionRequest.Options = options; 26 | var beginTransactionResponse = new BeginTransactionResponse(); 27 | try 28 | { 29 | beginTransactionResponse = FsClient.BeginTransaction(beginTransactionRequest); 30 | } 31 | catch (Exception e) 32 | { 33 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Red, "Exception caught\n" + e.Message); 34 | return; 35 | } 36 | TransactionId = beginTransactionResponse.Transaction; 37 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\n Successfully began new transaction '"); 38 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, TransactionId.ToBase64().ToString()); 39 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "'!"); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /firestore/examples/end2end/src/CommitTransaction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Google.Apis.Auth.OAuth2; 3 | using Google.Cloud.Storage.V1; 4 | using Google.Apis.Services; 5 | using Google.Type; 6 | using Google.Cloud.Firestore.Admin.V1Beta1; 7 | using Google.Cloud.Firestore.V1Beta1; 8 | using Grpc.Auth; 9 | 10 | namespace FirestoreTest 11 | { 12 | public partial class FirestoreTestClass 13 | { 14 | public void FSCommit() 15 | { 16 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\n :: Starting Commit ::\n"); 17 | if (TransactionId == null) 18 | { 19 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Yellow, "No transaction to commit, returning!\n"); 20 | return; 21 | } 22 | var commitRequest = new CommitRequest(); 23 | commitRequest.Database = Parent; 24 | commitRequest.Transaction = TransactionId; 25 | var commitResponse = new CommitResponse(); 26 | try 27 | { 28 | commitResponse = FsClient.Commit(commitRequest); 29 | } 30 | catch (Exception e) 31 | { 32 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Red, "Exception caught\n" + e.Message); 33 | return; 34 | } 35 | var timestamp = commitResponse.CommitTime; 36 | if (timestamp == null) 37 | { 38 | timestamp = new Google.Protobuf.WellKnownTypes.Timestamp(); 39 | // fix 40 | } 41 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\n Successfully commit at "); 42 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, timestamp.ToDateTime().ToString()); 43 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "!"); 44 | // clear transactionId! 45 | ClearTransactionId(); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /firestore/examples/end2end/src/CreateDocument.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Google.Apis.Auth.OAuth2; 3 | using Google.Cloud.Storage.V1; 4 | using Google.Apis.Services; 5 | using Google.Type; 6 | using Google.Cloud.Firestore.Admin.V1Beta1; 7 | using Google.Cloud.Firestore.V1Beta1; 8 | using Grpc.Auth; 9 | 10 | namespace FirestoreTest 11 | { 12 | public partial class FirestoreTestClass 13 | { 14 | public void FSCreateDocument() 15 | { 16 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\n:: Creating a new Document ::\n"); 17 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Yellow, "\nEnter Collection [cities]: "); 18 | string collectionId = Console.ReadLine(); 19 | if (collectionId == "") 20 | { 21 | collectionId = "cities"; 22 | } 23 | 24 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Yellow, "\nEnter Document: "); 25 | var docId = Console.ReadLine(); 26 | var createDocRequest = new CreateDocumentRequest(); 27 | createDocRequest.Parent = Parent; 28 | createDocRequest.DocumentId = docId; 29 | createDocRequest.CollectionId = collectionId; 30 | var fsDocument = new Document(); 31 | createDocRequest.Document = fsDocument; 32 | try 33 | { 34 | fsDocument = FsClient.CreateDocument(createDocRequest); 35 | } 36 | catch (Grpc.Core.RpcException e) 37 | { 38 | Grpc.Core.Status stat = e.Status; 39 | if (stat.StatusCode == Grpc.Core.StatusCode.AlreadyExists) 40 | { 41 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Red, "\nDocument already exists."); 42 | } 43 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Yellow, "\n" + stat.Detail); 44 | } 45 | catch (Exception e) 46 | { 47 | Console.WriteLine("{0} Exception caught.", e); 48 | } 49 | 50 | // get the document to ensure it was created 51 | Document retDoc; 52 | try 53 | { 54 | retDoc = GetDocument(docId, collectionId); 55 | } 56 | catch (Grpc.Core.RpcException e) 57 | { 58 | if (e.Status.StatusCode == Grpc.Core.StatusCode.NotFound) 59 | { 60 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Red, "\nDocument was not added."); 61 | } 62 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Red, "\n" + e.Status.Detail); 63 | return; 64 | } 65 | catch (Exception e) 66 | { 67 | Console.WriteLine("{0} Exception caught.", e); 68 | return; 69 | } 70 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\nSuccessfully created new document!\nName:" + retDoc.Name + "\n"); 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /firestore/examples/end2end/src/CreateIndex.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Google.Apis.Auth.OAuth2; 3 | using Google.Cloud.Storage.V1; 4 | using Google.Apis.Services; 5 | using Google.Type; 6 | using Google.Cloud.Firestore.Admin.V1Beta1; 7 | using Google.Cloud.Firestore.V1Beta1; 8 | using Google.LongRunning; 9 | using Grpc.Auth; 10 | 11 | namespace FirestoreTest 12 | { 13 | public partial class FirestoreTestClass 14 | { 15 | public void FSCreateIndex() 16 | { 17 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\n:: Creating a Index ::\n"); 18 | var index = new Index(); 19 | while (true) 20 | { 21 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, "\nEnter Field Name (blank when finished): "); 22 | string fieldName = Console.ReadLine(); 23 | if (fieldName != "") 24 | { 25 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, "\nEnter Mode (ASCENDING/DESCENDING) [ASCENDING]: "); 26 | string fieldMode = Console.ReadLine(); 27 | IndexField.Types.Mode indexFieldMode = IndexField.Types.Mode.Ascending; 28 | if (fieldMode == "" || fieldMode == "ASCENDING") { indexFieldMode = IndexField.Types.Mode.Ascending; } 29 | else 30 | if (fieldMode == "DESCENDING") { indexFieldMode = IndexField.Types.Mode.Descending; } 31 | else 32 | if (fieldMode != "ASCENDING" && fieldMode != "DESCENDING") 33 | { 34 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Red, "\nUnrecognized Mode - Choosing ASCENDING"); 35 | indexFieldMode = IndexField.Types.Mode.Ascending; 36 | } 37 | var indexField = new IndexField(); 38 | indexField.FieldPath = fieldName; 39 | indexField.Mode = indexFieldMode; 40 | index.Fields.Add(indexField); 41 | } 42 | else 43 | { 44 | var createIndexRequest = new CreateIndexRequest(); 45 | createIndexRequest.Parent = Parent; 46 | index.CollectionId = BaseCollectionId; 47 | createIndexRequest.Index = index; 48 | try 49 | { 50 | Operation operation = FsAdminClient.CreateIndex(createIndexRequest); 51 | } 52 | catch (Grpc.Core.RpcException e) 53 | { 54 | Grpc.Core.Status stat = e.Status; 55 | if (stat.StatusCode == Grpc.Core.StatusCode.AlreadyExists) 56 | { 57 | Console.WriteLine("\nIndex already exists."); 58 | } 59 | Console.WriteLine(stat.Detail); 60 | } 61 | catch (Exception e) 62 | { 63 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Red, "\nException caught\n" + e.Message); 64 | } 65 | break; 66 | } 67 | } 68 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\nSuccessfully created new index!\n"); 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /firestore/examples/end2end/src/DeleteDocument.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Google.Apis.Auth.OAuth2; 3 | using Google.Cloud.Storage.V1; 4 | using Google.Apis.Services; 5 | using Google.Type; 6 | using Google.Cloud.Firestore.Admin.V1Beta1; 7 | using Google.Cloud.Firestore.V1Beta1; 8 | using Grpc.Auth; 9 | 10 | namespace FirestoreTest 11 | { 12 | public partial class FirestoreTestClass 13 | { 14 | public void FSDeleteDocument() 15 | { 16 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\n:: Deleting a Document ::\n"); 17 | 18 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Yellow, "\nEnter Collection [cities]: "); 19 | string collectionId = Console.ReadLine(); 20 | if (collectionId == "") 21 | { 22 | collectionId = "cities"; 23 | } 24 | 25 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Yellow, "\nEnter Document ID: "); 26 | var docId = Console.ReadLine(); 27 | 28 | var deleteDocumentRequest = new DeleteDocumentRequest(); 29 | deleteDocumentRequest.Name = Parent + "/documents/" + collectionId + "/" + docId; 30 | try 31 | { 32 | FsClient.DeleteDocument(deleteDocumentRequest); 33 | } 34 | catch (Grpc.Core.RpcException e) 35 | { 36 | if (e.Status.StatusCode == Grpc.Core.StatusCode.NotFound) 37 | { 38 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Yellow, "\nDocument does not exists."); 39 | } 40 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Red, "\n" + e.Status.Detail); 41 | return; 42 | } 43 | catch (Exception e) 44 | { 45 | Console.WriteLine("{0} Exception caught.", e); 46 | return; 47 | } 48 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\nSuccessfully deleted document!"); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /firestore/examples/end2end/src/DeleteIndex.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Google.Apis.Auth.OAuth2; 3 | using Google.Cloud.Storage.V1; 4 | using Google.Apis.Services; 5 | using Google.Type; 6 | using Google.Cloud.Firestore.Admin.V1Beta1; 7 | using Google.Cloud.Firestore.V1Beta1; 8 | using Grpc.Auth; 9 | 10 | namespace FirestoreTest 11 | { 12 | public partial class FirestoreTestClass 13 | { 14 | public void FSDeleteIndex() 15 | { 16 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\n:: Delete an Index ::\n"); 17 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, "\nAvailable Indexes:\n"); 18 | ListIndexes(); 19 | 20 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, "\nEnter Index Id: "); 21 | var indexId = Console.ReadLine(); 22 | 23 | var deleteIndexRequest = new DeleteIndexRequest(); 24 | deleteIndexRequest.Name = indexId; 25 | try 26 | { 27 | FsAdminClient.DeleteIndex(deleteIndexRequest); 28 | } 29 | catch (Grpc.Core.RpcException e) 30 | { 31 | if ((e.Status.StatusCode == Grpc.Core.StatusCode.NotFound) || 32 | (e.Status.StatusCode == Grpc.Core.StatusCode.InvalidArgument)) 33 | { 34 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Red, "\nERROR: Index " + indexId + " not found!\n"); 35 | } 36 | else 37 | { 38 | Console.WriteLine("{0} Exception caught.", e); 39 | } 40 | return; 41 | } 42 | catch (Exception e) 43 | { 44 | Console.WriteLine("{0} Exception caught.", e); 45 | return; 46 | } 47 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\nSuccessfully deleted index!\n"); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /firestore/examples/end2end/src/FSWrite.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Google.Apis.Auth.OAuth2; 4 | using Google.Cloud.Storage.V1; 5 | using Google.Apis.Services; 6 | using Google.Type; 7 | using Google.Cloud.Firestore.Admin.V1Beta1; 8 | using Google.Cloud.Firestore.V1Beta1; 9 | using Grpc.Auth; 10 | using Google.Protobuf.Collections; 11 | using System.Threading.Tasks; 12 | 13 | namespace FirestoreTest 14 | { 15 | public partial class FirestoreTestClass 16 | { 17 | 18 | public async Task FSWrite() 19 | { 20 | try 21 | { 22 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\n:: Streaming Writes to a Document ::\n"); 23 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, "\nEnter Document Id: "); 24 | String docId = Console.ReadLine(); 25 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\nStreaming writes to "); 26 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, docId); 27 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "...\n"); 28 | 29 | Document updatedDoc = GetDocument(docId); 30 | MapField fields; 31 | if (updatedDoc == null) 32 | { 33 | updatedDoc = new Document(); 34 | updatedDoc.Name = Parent + "/documents/GrpcTestData/" + docId; 35 | fields = new MapField(); 36 | } 37 | else 38 | { 39 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, "\nAvailable fields in this document to update:\n"); 40 | Utils.ProcessFields(updatedDoc); 41 | fields = updatedDoc.Fields; 42 | } 43 | 44 | String streamId = ""; 45 | Google.Protobuf.ByteString streamToken = null; 46 | 47 | using (var writeCall = FsClient.Write()) 48 | { 49 | var writeRequest = new WriteRequest(); 50 | writeRequest.Database = Parent; 51 | await writeCall.RequestStream.WriteAsync(writeRequest); 52 | await writeCall.ResponseStream.MoveNext(); 53 | var responseStr = writeCall.ResponseStream.Current; 54 | streamId = responseStr.StreamId; 55 | streamToken = responseStr.StreamToken; 56 | 57 | var responseReaderTask = Task.Run(async () => 58 | { 59 | while (await writeCall.ResponseStream.MoveNext()) 60 | { 61 | var response = writeCall.ResponseStream.Current; 62 | var writeResults = response.WriteResults; 63 | foreach (var writeResult in writeResults) 64 | { 65 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, "\nWrite result:" + writeResult.ToString()); 66 | } 67 | } 68 | }); 69 | 70 | var currentWrite = new Write(); 71 | var docMask = new DocumentMask(); 72 | // update/create fields 73 | while (true) 74 | { 75 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, "\nEnter Field Name (blank when finished): "); 76 | string fieldName = Console.ReadLine(); 77 | if (fieldName == "") 78 | { 79 | break; 80 | } 81 | else 82 | { 83 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, "\nEnter Field Value: "); 84 | var fieldInValue = Console.ReadLine(); 85 | Utils.AddField(fields, fieldName, fieldInValue); 86 | docMask.FieldPaths.Add(fieldName); 87 | } 88 | } 89 | writeRequest.Database = Parent; 90 | currentWrite.Update = updatedDoc; 91 | currentWrite.UpdateMask = docMask; 92 | writeRequest.StreamId = streamId; 93 | writeRequest.StreamToken = streamToken; 94 | writeRequest.Writes.Add(currentWrite); 95 | await writeCall.RequestStream.WriteAsync(writeRequest); 96 | await writeCall.RequestStream.CompleteAsync(); 97 | await responseReaderTask; 98 | } 99 | } 100 | catch (Grpc.Core.RpcException e) 101 | { 102 | Console.WriteLine("RPC failed", e); 103 | throw; 104 | } 105 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\nSuccessfully streamed updates to the document!\n"); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /firestore/examples/end2end/src/GetDocument.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Google.Apis.Auth.OAuth2; 3 | using Google.Cloud.Storage.V1; 4 | using Google.Apis.Services; 5 | using Google.Type; 6 | using Google.Cloud.Firestore.Admin.V1Beta1; 7 | using Google.Cloud.Firestore.V1Beta1; 8 | using Grpc.Auth; 9 | 10 | namespace FirestoreTest 11 | { 12 | public partial class FirestoreTestClass 13 | { 14 | public Document GetDocument(String documentName, String collectionID = "GrpcTestData") 15 | { 16 | var getDocumentRequest = new GetDocumentRequest(); 17 | getDocumentRequest.Name = Parent + "/documents/" + collectionID + "/" + documentName; 18 | Document retDoc; 19 | try 20 | { 21 | retDoc = FsClient.GetDocument(getDocumentRequest); 22 | } 23 | catch (Grpc.Core.RpcException e) 24 | { 25 | if (e.Status.StatusCode == Grpc.Core.StatusCode.NotFound) 26 | { 27 | // FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Red, "ERROR: Document " + documentName + " not found!"); 28 | throw e; 29 | } 30 | else 31 | { 32 | Console.WriteLine("{0} Exception caught.", e); 33 | } 34 | throw e; 35 | } 36 | catch (Exception e) 37 | { 38 | Console.WriteLine("{0} Exception caught.", e); 39 | throw e; 40 | } 41 | return retDoc; 42 | } 43 | 44 | public void FSGetDocument() 45 | { 46 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\n:: Fetch a Specific Document ::\n"); 47 | 48 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Yellow, "\nEnter Collection [cities]: "); 49 | string collectionId = Console.ReadLine(); 50 | if (collectionId == "") 51 | { 52 | collectionId = "cities"; 53 | } 54 | 55 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Yellow, "\nEnter Document Id: "); 56 | String docId = Console.ReadLine(); 57 | 58 | Document retDoc = null; 59 | try 60 | { 61 | retDoc = GetDocument(docId, collectionId); 62 | } 63 | catch (Grpc.Core.RpcException e) 64 | { 65 | if (e.Status.StatusCode == Grpc.Core.StatusCode.NotFound) 66 | { 67 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Red, "\nERROR: Document " + docId + " not found!"); 68 | } 69 | } 70 | if (retDoc != null) 71 | Utils.ReadDocument(retDoc); 72 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\nFinished getting document!\n"); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /firestore/examples/end2end/src/GetIndex.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Google.Apis.Auth.OAuth2; 3 | using Google.Cloud.Storage.V1; 4 | using Google.Apis.Services; 5 | using Google.Type; 6 | using Google.Cloud.Firestore.Admin.V1Beta1; 7 | using Google.Cloud.Firestore.V1Beta1; 8 | using Grpc.Auth; 9 | 10 | namespace FirestoreTest 11 | { 12 | public partial class FirestoreTestClass 13 | { 14 | public void FSGetIndex() 15 | { 16 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\n:: Fetch a Specific Index ::\n"); 17 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, "\nAvailable Indexes:\n"); 18 | ListIndexes(); 19 | 20 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, "\nEnter Index Id: "); 21 | var indexId = Console.ReadLine(); 22 | 23 | var getIndexRequest = new GetIndexRequest(); 24 | getIndexRequest.Name = indexId; 25 | Index retIndex; 26 | try 27 | { 28 | retIndex = FsAdminClient.GetIndex(getIndexRequest); 29 | } 30 | catch (Grpc.Core.RpcException e) 31 | { 32 | if ((e.Status.StatusCode == Grpc.Core.StatusCode.NotFound) || 33 | (e.Status.StatusCode == Grpc.Core.StatusCode.InvalidArgument)) 34 | { 35 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Red, "\nERROR: Index " + indexId + " not found!\n"); 36 | } 37 | else 38 | { 39 | Console.WriteLine("{0} Exception caught.", e); 40 | } 41 | return; 42 | } 43 | catch (Exception e) 44 | { 45 | Console.WriteLine("{0} Exception caught.", e); 46 | return; 47 | } 48 | Utils.PrintIndex(retIndex); 49 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\nFinished getting index!\n"); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /firestore/examples/end2end/src/ListCollectionIds.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Google.Apis.Auth.OAuth2; 3 | using Google.Cloud.Storage.V1; 4 | using Google.Apis.Services; 5 | using Google.Type; 6 | using Google.Cloud.Firestore.Admin.V1Beta1; 7 | using Google.Cloud.Firestore.V1Beta1; 8 | using Grpc.Auth; 9 | 10 | namespace FirestoreTest 11 | { 12 | public partial class FirestoreTestClass 13 | { 14 | public void FSListCollectionIds() 15 | { 16 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green,"\n:: Listing all Collection Ids from Document or Database... ::\n"); 17 | var listCollectionIdsRequest = new ListCollectionIdsRequest(); 18 | listCollectionIdsRequest.Parent = Parent; 19 | 20 | var listCollectionIdsResponse = FsClient.ListCollectionIds(listCollectionIdsRequest); 21 | var cids = listCollectionIdsResponse.CollectionIds; 22 | int i = 0; 23 | foreach (var cid in cids) 24 | { 25 | i++; 26 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, "\n:: Collection Id " + i + ": " + cid.ToString()); 27 | } 28 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\nFinished listing Collection Ids from Document or Database."); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /firestore/examples/end2end/src/ListDocuments.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Google.Apis.Auth.OAuth2; 3 | using Google.Cloud.Storage.V1; 4 | using Google.Apis.Services; 5 | using Google.Type; 6 | using Google.Cloud.Firestore.Admin.V1Beta1; 7 | using Google.Cloud.Firestore.V1Beta1; 8 | using Grpc.Auth; 9 | 10 | namespace FirestoreTest 11 | { 12 | public partial class FirestoreTestClass 13 | { 14 | public void ListDocuments() 15 | { 16 | var listDocumentsRequest = new ListDocumentsRequest(); 17 | listDocumentsRequest.Parent = Parent; 18 | var listDocumentsResponse = FsClient.ListDocuments(listDocumentsRequest); 19 | var documents = listDocumentsResponse.Documents; 20 | int i = 0; 21 | foreach (var document in documents) 22 | { 23 | i++; 24 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, "\nDocument " + i + ": " + document.Name + ""); 25 | } 26 | } 27 | 28 | public void FSListDocuments() 29 | { 30 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\n:: Listing all documents from Firestore... ::\n"); 31 | ListDocuments(); 32 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\nFinished listing documents!\n"); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /firestore/examples/end2end/src/ListIndexes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Google.Apis.Auth.OAuth2; 3 | using Google.Cloud.Storage.V1; 4 | using Google.Apis.Services; 5 | using Google.Type; 6 | using Google.Cloud.Firestore.Admin.V1Beta1; 7 | using Google.Cloud.Firestore.V1Beta1; 8 | using Google.LongRunning; 9 | using Grpc.Auth; 10 | 11 | namespace FirestoreTest 12 | { 13 | public partial class FirestoreTestClass 14 | { 15 | 16 | public void ListIndexes() 17 | { 18 | var listIndexesRequest = new ListIndexesRequest(); 19 | listIndexesRequest.Parent = Parent; 20 | ListIndexesResponse listIndexesResponse = new ListIndexesResponse(); 21 | try 22 | { 23 | listIndexesResponse = FsAdminClient.ListIndexes(listIndexesRequest); 24 | } 25 | catch (Exception e) 26 | { 27 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Red, "Exception caught\n" + e.Message); 28 | } 29 | var indexes = listIndexesResponse.Indexes; 30 | var i = 0; 31 | foreach (var index in indexes) 32 | { 33 | i++; 34 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, "\nIndex " + i + ": " + index.Name); 35 | } 36 | } 37 | 38 | public void FSListIndexes() 39 | { 40 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\n:: Listing all Indexes ::\n"); 41 | ListIndexes(); 42 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\nFinished listing indexes!\n"); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /firestore/examples/end2end/src/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Google.Apis.Auth.OAuth2; 3 | using Google.Cloud.Storage.V1; 4 | using Google.Apis.Services; 5 | using Google.Type; 6 | using Google.Cloud.Firestore.Admin.V1Beta1; 7 | using Google.Cloud.Firestore.V1Beta1; 8 | using Grpc.Auth; 9 | 10 | namespace FirestoreTest 11 | { 12 | 13 | public partial class FirestoreTestClass 14 | { 15 | public String Parent; 16 | public String BaseCollectionId; 17 | public Google.Protobuf.ByteString TransactionId; 18 | public Firestore.FirestoreClient FsClient; 19 | public FirestoreAdmin.FirestoreAdminClient FsAdminClient; 20 | public FirestoreTestUtils Utils; 21 | 22 | public FirestoreTestClass(String parent, 23 | Firestore.FirestoreClient fsClient, 24 | FirestoreAdmin.FirestoreAdminClient fsAdminClient, 25 | String baseCollectionId) 26 | { 27 | Parent = parent; 28 | FsClient = fsClient; 29 | FsAdminClient = fsAdminClient; 30 | BaseCollectionId = baseCollectionId; 31 | Utils = new FirestoreTestUtils(); 32 | ClearTransactionId(); 33 | } 34 | 35 | public void ClearTransactionId() 36 | { 37 | TransactionId = null; 38 | } 39 | 40 | } 41 | 42 | 43 | class MainClass 44 | { 45 | public static FirestoreTestClass Ftc; 46 | 47 | public static void Main(string[] args) 48 | { 49 | Console.Title = typeof(MainClass).Name; 50 | var parent = "projects/mch-test-49bba/databases/(default)"; 51 | Console.Title = parent; 52 | 53 | Boolean debug = false; 54 | if (args.Length > 0 && args[0] == "--debug") 55 | { 56 | debug = true; 57 | } 58 | 59 | if (debug) 60 | { 61 | Console.WriteLine("GOOGLE_APPLICATION_CREDENTIALS: " + Environment.GetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS")); 62 | var storage = StorageClient.Create(); 63 | var buckets = storage.ListBuckets("mch-test-49bba"); 64 | foreach (var bucket in buckets) 65 | { 66 | Console.WriteLine(bucket.Name); 67 | } 68 | } 69 | 70 | GoogleCredential credential = GoogleCredential.FromFile(Environment.GetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS")); 71 | if (credential == null) 72 | { 73 | Console.WriteLine("Could not create credential from file."); 74 | Console.WriteLine("GOOGLE_APPLICATION_CREDENTIALS: " + Environment.GetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS")); 75 | } 76 | Grpc.Core.Channel channel = new Grpc.Core.Channel( 77 | "firestore.googleapis.com:443", 78 | GoogleGrpcCredentials.ToChannelCredentials(credential) 79 | ); 80 | 81 | Firestore.FirestoreClient fsClient = new Firestore.FirestoreClient(channel); 82 | FirestoreAdmin.FirestoreAdminClient fsAdminClient = new FirestoreAdmin.FirestoreAdminClient(channel); 83 | 84 | String baseCollectionId = "GrpcTestData"; 85 | Ftc = new FirestoreTestClass(parent, fsClient, fsAdminClient, baseCollectionId); 86 | 87 | Run(); 88 | 89 | } 90 | 91 | static void Run() 92 | { 93 | Ftc.Utils.LoadData(); 94 | while (true) 95 | { 96 | DrawMenu(); 97 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Yellow, "\nEnter an Option ('quit' to exit): "); // Prompt 98 | string line = Console.ReadLine(); // Get string from user 99 | line.Trim(); 100 | if (line.Contains("q")) // Check string 101 | { 102 | return; 103 | } 104 | ExecuteMenuEntry(line); 105 | } 106 | 107 | } 108 | 109 | public static void ExecuteMenuEntry(string menuEntry) 110 | { 111 | switch (menuEntry) 112 | { 113 | case "1": 114 | Ftc.FSBatchGetDocuments().Wait(); 115 | break; 116 | case "2": 117 | Ftc.FSBeginTransaction(); 118 | break; 119 | case "3": 120 | Ftc.FSCommit(); 121 | break; 122 | case "4": 123 | Ftc.FSCreateDocument(); 124 | break; 125 | case "5": 126 | Ftc.FSDeleteDocument(); 127 | break; 128 | case "6": 129 | Ftc.FSGetDocument(); 130 | break; 131 | case "7": 132 | Ftc.FSListCollectionIds(); 133 | break; 134 | case "8": 135 | Ftc.FSListDocuments(); 136 | break; 137 | case "9": 138 | Ftc.FSRollback(); 139 | break; 140 | case "10": 141 | Ftc.FSRunQuery().Wait(); 142 | break; 143 | case "11": 144 | Ftc.FSUpdateDocument(); 145 | break; 146 | case "12": 147 | Ftc.FSWrite().Wait(); 148 | break; 149 | case "13": 150 | Ftc.FSCreateIndex(); 151 | break; 152 | case "14": 153 | Ftc.FSDeleteIndex(); 154 | break; 155 | case "15": 156 | Ftc.FSGetIndex(); 157 | break; 158 | case "16": 159 | Ftc.FSListIndexes(); 160 | break; 161 | default: 162 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Red, "Not yet implemneted!\n"); 163 | break; 164 | } 165 | } 166 | 167 | public static void DrawMenu() 168 | { 169 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Yellow, "\n\n Google Firestore RPC Menu \n\n"); 170 | // draw.drawLineSeparator(); 171 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, " 1|batchgetdocuments ........ BatchGetDocuments\n"); 172 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, " 2|begintransaction ........ BeginTransaction\n"); 173 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, " 3|commit ................... Commit\n"); 174 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, " 4|createdocument ........... CreateDocument\n"); 175 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, " 5|deletedocument ........... DeleteDocument\n"); 176 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, " 6|getdocument .............. GetDocument\n"); 177 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, " 7|listcollectionids ........ ListCollectionIds\n"); 178 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, " 8|listdocuments ............ ListDocuments\n"); 179 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, " 9|rollback ................. Rollback\n"); 180 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, "10|runquery ................. RunQuery\n"); 181 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, "11|updatedocument ........... UpdateDocument\n"); 182 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, "12|write .................... Write\n\n"); 183 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Yellow, " Firestore Admin RPC's \n"); 184 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, "13|createindex .............. CreateIndex\n"); 185 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, "14|deleteindex .............. DeleteIndex\n"); 186 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, "15|getindex ................. GetIndex\n"); 187 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, "16|listindexes .............. ListIndex\n\n"); 188 | 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /firestore/examples/end2end/src/Rollback.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Google.Apis.Auth.OAuth2; 3 | using Google.Cloud.Storage.V1; 4 | using Google.Apis.Services; 5 | using Google.Type; 6 | using Google.Cloud.Firestore.Admin.V1Beta1; 7 | using Google.Cloud.Firestore.V1Beta1; 8 | using Grpc.Auth; 9 | 10 | namespace FirestoreTest 11 | { 12 | public partial class FirestoreTestClass 13 | { 14 | public void FSRollback() 15 | { 16 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\n :: Starting Rollback ::\n"); 17 | if (TransactionId == null) 18 | { 19 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Yellow, "\nNo transaction to rollback, returning!"); 20 | return; 21 | } 22 | var rollbackRequest = new RollbackRequest(); 23 | rollbackRequest.Database = Parent; 24 | rollbackRequest.Transaction = TransactionId; 25 | try 26 | { 27 | FsClient.Rollback(rollbackRequest); 28 | } 29 | catch (Exception e) 30 | { 31 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Red, "Exception caught\n" + e.Message); 32 | return; 33 | } 34 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\nSuccessfully rollback!"); 35 | // clear transactionId! 36 | ClearTransactionId(); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /firestore/examples/end2end/src/RunQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Google.Apis.Auth.OAuth2; 3 | using Google.Cloud.Storage.V1; 4 | using Google.Apis.Services; 5 | using Google.Type; 6 | using Google.Cloud.Firestore.Admin.V1Beta1; 7 | using Google.Cloud.Firestore.V1Beta1; 8 | using Grpc.Auth; 9 | 10 | namespace FirestoreTest 11 | { 12 | public partial class FirestoreTestClass 13 | { 14 | public async System.Threading.Tasks.Task FSRunQuery() 15 | { 16 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\n:: Running a query from Firestore... ::\n"); 17 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Yellow, "\nUSA Cities with populations larger than 1 millon\n"); 18 | 19 | var runQueryRequest = new RunQueryRequest(); 20 | runQueryRequest.Parent = "projects/mch-test-49bba/databases/(default)" + "/documents"; 21 | StructuredQuery sq = new StructuredQuery(); 22 | 23 | // just look at cities! 24 | var colSel = new StructuredQuery.Types.CollectionSelector(); 25 | colSel.CollectionId = "cities"; 26 | sq.From.Add(colSel); 27 | 28 | // only get USA cities with more than 1 million people 29 | // define USA filter 30 | StructuredQuery.Types.Filter countryFilter = new StructuredQuery.Types.Filter(); 31 | StructuredQuery.Types.FieldFilter fieldFilter = new StructuredQuery.Types.FieldFilter(); 32 | StructuredQuery.Types.FieldReference field = new StructuredQuery.Types.FieldReference 33 | { 34 | FieldPath = "country" 35 | }; 36 | fieldFilter.Field = field; 37 | fieldFilter.Op = StructuredQuery.Types.FieldFilter.Types.Operator.Equal; 38 | Value filterValue = new Value 39 | { 40 | StringValue = "USA" 41 | }; 42 | fieldFilter.Value = filterValue; 43 | countryFilter.FieldFilter = fieldFilter; 44 | // define 1 Million filer 45 | StructuredQuery.Types.Filter populationFilter = new StructuredQuery.Types.Filter(); 46 | fieldFilter = new StructuredQuery.Types.FieldFilter(); 47 | field = new StructuredQuery.Types.FieldReference 48 | { 49 | FieldPath = "population" 50 | }; 51 | fieldFilter.Field = field; 52 | fieldFilter.Op = StructuredQuery.Types.FieldFilter.Types.Operator.GreaterThanOrEqual; 53 | filterValue = new Value 54 | { 55 | IntegerValue = 1000000 56 | }; 57 | fieldFilter.Value = filterValue; 58 | populationFilter.FieldFilter = fieldFilter; 59 | 60 | StructuredQuery.Types.CompositeFilter compositeFilter = new StructuredQuery.Types.CompositeFilter(); 61 | compositeFilter.Filters.Add(countryFilter); 62 | compositeFilter.Filters.Add(populationFilter); 63 | compositeFilter.Op = StructuredQuery.Types.CompositeFilter.Types.Operator.And; 64 | var where = new StructuredQuery.Types.Filter(); 65 | where.CompositeFilter = compositeFilter; 66 | sq.Where = where; 67 | 68 | runQueryRequest.StructuredQuery = sq; 69 | 70 | var select = new StructuredQuery.Types.Projection(); 71 | var fields = select.Fields; 72 | var fieldRef = new StructuredQuery.Types.FieldReference 73 | { 74 | FieldPath = "name" 75 | }; 76 | fields.Add(fieldRef); 77 | fieldRef = new StructuredQuery.Types.FieldReference 78 | { 79 | FieldPath = "population" 80 | }; 81 | fields.Add(fieldRef); 82 | 83 | sq.Select = select; 84 | 85 | var runQueryResponse = new RunQueryResponse(); 86 | try 87 | { 88 | 89 | var ret = FsClient.RunQuery(runQueryRequest); 90 | var responseStream = ret.ResponseStream; 91 | var cancellationToken = new System.Threading.CancellationToken(); 92 | while (await responseStream.MoveNext(cancellationToken)) 93 | { 94 | runQueryResponse = responseStream.Current; 95 | Utils.ReadDocument(runQueryResponse.Document); 96 | } 97 | } 98 | catch (Exception e) 99 | { 100 | Console.WriteLine("{0} Exception caught.", e); 101 | return; 102 | } 103 | 104 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\nFinished running query!\n"); 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /firestore/examples/end2end/src/UpdateDocument.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Google.Apis.Auth.OAuth2; 4 | using Google.Cloud.Storage.V1; 5 | using Google.Apis.Services; 6 | using Google.Type; 7 | using Google.Cloud.Firestore.Admin.V1Beta1; 8 | using Google.Cloud.Firestore.V1Beta1; 9 | using Grpc.Auth; 10 | using Google.Protobuf.Collections; 11 | 12 | namespace FirestoreTest 13 | { 14 | public partial class FirestoreTestClass 15 | { 16 | public void FSUpdateDocument() 17 | { 18 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\n:: Update/Create a Document ::\n"); 19 | 20 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Yellow, "\nEnter Collection [cities]: "); 21 | string collectionId = Console.ReadLine(); 22 | if (collectionId == "") 23 | { 24 | collectionId = "cities"; 25 | } 26 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Yellow, "\nEnter Document Id: "); 27 | String docId = Console.ReadLine(); 28 | Document updatedDoc = GetDocument(docId,collectionId); 29 | MapField fields; 30 | if (updatedDoc == null) 31 | { 32 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Yellow, "\nDocument within " + collectionId + " does not exist, create it (y/n): "); 33 | String ynValue = Console.ReadLine(); 34 | if ((ynValue.ToLower() != "y") && 35 | (ynValue.ToLower() != "yes")) 36 | { 37 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Red, "\nDocument update aborted, returning"); 38 | return; 39 | } 40 | updatedDoc = new Document(); 41 | updatedDoc.Name = Parent + "/documents/" + collectionId + "/" + docId; 42 | fields = new MapField(); 43 | } 44 | else 45 | { 46 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.White, "\nAvailable fields in this document to update:\n"); 47 | Utils.ProcessFields(updatedDoc); 48 | fields = updatedDoc.Fields; 49 | } 50 | // update/create fields 51 | while (true) 52 | { 53 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Yellow, "\nEnter Field Name (blank when finished): "); 54 | string fieldName = Console.ReadLine(); 55 | if (fieldName != "") 56 | { 57 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Yellow, "\nEnter Field Value: "); 58 | string fieldInValue = Console.ReadLine(); 59 | // TODO must query for type and handle!!!! 60 | // TODO only handling string!!!! 61 | Value fieldValue = new Value(); 62 | fieldValue.StringValue = fieldInValue; 63 | if (fields.ContainsKey(fieldName)) 64 | { 65 | fields[fieldName].StringValue = fieldInValue; 66 | } 67 | else 68 | { 69 | fields.Add(fieldName,fieldValue); 70 | } 71 | } 72 | else 73 | { 74 | break; 75 | } 76 | 77 | } 78 | var updateDocumentRequest = new UpdateDocumentRequest(); 79 | updateDocumentRequest.Document = updatedDoc; 80 | Document returnDocument; 81 | try 82 | { 83 | returnDocument = FsClient.UpdateDocument(updateDocumentRequest); 84 | } 85 | catch (Exception e) 86 | { 87 | Console.WriteLine("{0} Exception caught.", e); 88 | } 89 | FirestoreTestUtils.ColoredConsoleWrite(ConsoleColor.Green, "\nSuccessfully updated document!\n"); 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /firestore/examples/end2end/src/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Google.Apis.Auth.OAuth2; 3 | using Google.Cloud.Storage.V1; 4 | using Google.Apis.Services; 5 | using Google.Type; 6 | using Google.Cloud.Firestore.Admin.V1Beta1; 7 | using Google.Cloud.Firestore.V1Beta1; 8 | using Grpc.Auth; 9 | using Google.Protobuf.Collections; 10 | 11 | 12 | namespace FirestoreTest 13 | { 14 | public class FirestoreTestUtils 15 | { 16 | public void ProcessFields(Document document) 17 | { 18 | var fields = document.Fields; 19 | var i = 0; 20 | foreach (var field in fields) 21 | { 22 | i++; 23 | ColoredConsoleWrite(ConsoleColor.White, "\tField " + i + ": "); 24 | ColoredConsoleWrite(ConsoleColor.White, "\tKey: " + field.Key); 25 | ColoredConsoleWrite(ConsoleColor.White, "\tValue: " + field.Value.ToString() + "\n"); 26 | // Console.WriteLine("\tValue: " + field.Value); 27 | // Console.WriteLine("\tToString: " + field.ToString()); 28 | // Console.WriteLine("\tType: " + field.GetType().ToString()); 29 | } 30 | } 31 | 32 | public void ReadDocument(Document document) 33 | { 34 | ColoredConsoleWrite(ConsoleColor.Yellow, "Document Name: " + document.Name + "\n"); 35 | ColoredConsoleWrite(ConsoleColor.Gray, " Created: " + document.CreateTime.ToString() + "\n"); 36 | ColoredConsoleWrite(ConsoleColor.Gray, " Updated: " + document.UpdateTime.ToString() + "\n"); 37 | ColoredConsoleWrite(ConsoleColor.Gray, " Document Fields:\n"); 38 | ProcessFields(document); 39 | } 40 | 41 | public void PrintIndex(Index index) 42 | { 43 | ColoredConsoleWrite(ConsoleColor.Yellow, "\n Index Name: " + index.Name); 44 | ColoredConsoleWrite(ConsoleColor.Yellow, "\nCollection Id: " + index.CollectionId); 45 | ColoredConsoleWrite(ConsoleColor.Yellow, "\n State: "); 46 | switch (index.State) 47 | { 48 | case Index.Types.State.Unspecified: 49 | ColoredConsoleWrite(ConsoleColor.Yellow, Index.Types.State.Unspecified.ToString()); 50 | break; 51 | case Index.Types.State.Creating: 52 | ColoredConsoleWrite(ConsoleColor.Yellow, Index.Types.State.Creating.ToString()); 53 | break; 54 | case Index.Types.State.Error: 55 | ColoredConsoleWrite(ConsoleColor.Yellow, Index.Types.State.Error.ToString()); 56 | break; 57 | case Index.Types.State.Ready: 58 | ColoredConsoleWrite(ConsoleColor.Yellow, Index.Types.State.Ready.ToString()); 59 | break; 60 | default: 61 | ColoredConsoleWrite(ConsoleColor.Red, "Not Defined!"); 62 | break; 63 | } 64 | var fields = index.Fields; 65 | var i = 0; 66 | foreach (var field in fields) 67 | { 68 | i++; 69 | ColoredConsoleWrite(ConsoleColor.Gray, "\n Field " + i + ": " + field.FieldPath); 70 | ColoredConsoleWrite(ConsoleColor.Gray, " Mode: "); 71 | IndexField.Types.Mode indexFieldMode = field.Mode; 72 | switch (field.Mode) 73 | { 74 | case IndexField.Types.Mode.Ascending: 75 | ColoredConsoleWrite(ConsoleColor.Gray, "Ascending"); 76 | break; 77 | case IndexField.Types.Mode.Descending: 78 | ColoredConsoleWrite(ConsoleColor.Gray, "Descending"); 79 | break; 80 | case IndexField.Types.Mode.Unspecified: 81 | ColoredConsoleWrite(ConsoleColor.Gray, "Unspecified"); 82 | break; 83 | default: 84 | ColoredConsoleWrite(ConsoleColor.Red, "Not Defined!"); 85 | break; 86 | } 87 | 88 | } 89 | } 90 | 91 | public void AddField(MapField fields, String fieldName, String fieldValue) 92 | { 93 | if (fields.ContainsKey(fieldName)) 94 | { 95 | fields[fieldName].StringValue = fieldValue; 96 | } 97 | else 98 | { 99 | Value strValue = new Value(); 100 | strValue.StringValue = fieldValue; 101 | fields.Add(fieldName, strValue); 102 | } 103 | } 104 | 105 | public void AddField(MapField fields, String fieldName, bool fieldValue) 106 | { 107 | if (fields.ContainsKey(fieldName)) 108 | { 109 | fields[fieldName].BooleanValue = fieldValue; 110 | } 111 | else 112 | { 113 | Value value = new Value(); 114 | value.BooleanValue = fieldValue; 115 | fields.Add(fieldName, value); 116 | } 117 | } 118 | 119 | public void AddField(MapField fields, String fieldName, long fieldValue) 120 | { 121 | if (fields.ContainsKey(fieldName)) 122 | { 123 | fields[fieldName].IntegerValue = fieldValue; 124 | } 125 | else 126 | { 127 | Value value = new Value(); 128 | value.IntegerValue = fieldValue; 129 | fields.Add(fieldName, value); 130 | } 131 | } 132 | 133 | 134 | public static void ColoredConsoleWrite(ConsoleColor color, string text) 135 | { 136 | ConsoleColor originalColor = Console.ForegroundColor; 137 | Console.ForegroundColor = color; 138 | Console.Write(text); 139 | Console.ForegroundColor = originalColor; 140 | } 141 | 142 | private void AddCityData(String cityAbbr, 143 | String name, 144 | String state, 145 | String country, 146 | Boolean capital, 147 | long population) 148 | { 149 | String collectionName = "cities"; 150 | 151 | String docName = cityAbbr; 152 | 153 | Document updatedDoc = null; 154 | try 155 | { 156 | updatedDoc = MainClass.Ftc.GetDocument(docName, collectionName); 157 | } 158 | catch (Grpc.Core.RpcException e) 159 | { 160 | if (e.Status.StatusCode == Grpc.Core.StatusCode.NotFound) 161 | { 162 | updatedDoc = new Document(); 163 | updatedDoc.Name = MainClass.Ftc.Parent + "/documents/" + collectionName + "/" + docName; 164 | } 165 | } 166 | MapField fields; 167 | 168 | fields = updatedDoc.Fields; 169 | // update/create fields 170 | AddField(fields, "name", name); 171 | AddField(fields, "state", state); 172 | AddField(fields, "country", country); 173 | AddField(fields, "capital", capital); 174 | AddField(fields, "population", population); 175 | 176 | var updateDocumentRequest = new UpdateDocumentRequest(); 177 | updateDocumentRequest.Document = updatedDoc; 178 | Document returnDocument; 179 | try 180 | { 181 | returnDocument = MainClass.Ftc.FsClient.UpdateDocument(updateDocumentRequest); 182 | } 183 | catch (Exception e) 184 | { 185 | Console.WriteLine("{0} Exception caught.", e); 186 | } 187 | 188 | } 189 | 190 | public void LoadData() 191 | { 192 | ColoredConsoleWrite(ConsoleColor.Green, "\n:: Loading Test Data ::\n"); 193 | 194 | AddCityData("SF", 195 | "San Francisco", 196 | "CA", 197 | "USA", 198 | false, 199 | 860000); 200 | AddCityData("LA", 201 | "Los Angeles", 202 | "CA", 203 | "USA", 204 | false, 205 | 3900000); 206 | AddCityData("DC", 207 | "Washington, D.C.", 208 | "", 209 | "USA", 210 | true, 211 | 680000); 212 | AddCityData("TOK", 213 | "Tokyo", 214 | "", 215 | "Japan", 216 | true, 217 | 9000000); 218 | AddCityData("BJ", 219 | "Beijing", 220 | "", 221 | "China", 222 | true, 223 | 21500000); 224 | AddCityData("NYC", 225 | "New York", 226 | "NY", 227 | "USA", 228 | false, 229 | 8550405); 230 | 231 | ColoredConsoleWrite(ConsoleColor.Green, "\nSuccessfully Loaded Data!\n"); 232 | 233 | } 234 | } 235 | } -------------------------------------------------------------------------------- /protos/grpc_gcp.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2018 gRPC authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package grpc.gcp; 18 | 19 | message ApiConfig { 20 | // The channel pool configurations. 21 | ChannelPoolConfig channel_pool = 2; 22 | 23 | // The method configurations. 24 | repeated MethodConfig method = 1001; 25 | } 26 | 27 | message ChannelPoolConfig { 28 | // The max number of channels in the pool. 29 | uint32 max_size = 1; 30 | // The idle timeout (seconds) of channels without bound affinity sessions. 31 | uint64 idle_timeout = 2; 32 | // The low watermark of max number of concurrent streams in a channel. 33 | // New channel will be created once it get hit, until we reach the max size 34 | // of the channel pool. 35 | uint32 max_concurrent_streams_low_watermark = 3; 36 | } 37 | 38 | message MethodConfig { 39 | // A fully qualified name of a gRPC method, or a wildcard pattern ending 40 | // with .*, such as foo.bar.A, foo.bar.*. Method configs are evaluated 41 | // sequentially, and the first one takes precedence. 42 | repeated string name = 1; 43 | 44 | // The channel affinity configurations. 45 | AffinityConfig affinity = 1001; 46 | } 47 | 48 | message AffinityConfig { 49 | enum Command { 50 | // The annotated method will be required to be bound to an existing session 51 | // to execute the RPC. The corresponding will be 52 | // used to find the affinity key from the request message. 53 | BOUND = 0; 54 | // The annotated method will establish the channel affinity with the channel 55 | // which is used to execute the RPC. The corresponding 56 | // will be used to find the affinity key from the 57 | // response message. 58 | BIND = 1; 59 | // The annotated method will remove the channel affinity with the channel 60 | // which is used to execute the RPC. The corresponding 61 | // will be used to find the affinity key from the 62 | // request message. 63 | UNBIND = 2; 64 | } 65 | // The affinity command applies on the selected gRPC methods. 66 | Command command = 2; 67 | // The field path of the affinity key in the request/response message. 68 | // For example: "f.a", "f.b.d", etc. 69 | string affinity_key = 3; 70 | } 71 | -------------------------------------------------------------------------------- /protos/test_service.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file or at 5 | * https://developers.google.com/open-source/licenses/bsd 6 | */ 7 | 8 | syntax = "proto3"; 9 | 10 | package grpc.gcp.integration_test; 11 | 12 | service TestService { 13 | rpc DoSimple(SimpleRequest) returns (SimpleResponse); 14 | rpc DoComplex(ComplexRequest) returns (ComplexResponse); 15 | } 16 | 17 | message SimpleRequest { 18 | string name = 1; 19 | } 20 | 21 | message SimpleResponse { 22 | string name = 2; 23 | } 24 | 25 | message ComplexInner { 26 | string name = 1; 27 | int32 number = 2; 28 | ComplexInner nested_inner = 3; 29 | repeated ComplexInner nested_inners = 4; 30 | } 31 | 32 | message ComplexRequest { 33 | string name = 1; 34 | int32 number = 2; 35 | ComplexInner inner = 3; 36 | } 37 | 38 | message ComplexResponse { 39 | string name = 1; 40 | int32 number = 2; 41 | ComplexInner inner = 3; 42 | } 43 | --------------------------------------------------------------------------------