Imported Upstream version 1.27.0
[platform/upstream/grpc.git] / src / csharp / Grpc.HealthCheck.Tests / HealthServiceImplTest.cs
1 #region Copyright notice and license
2 // Copyright 2015 gRPC authors.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //     http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 #endregion
16
17 using System;
18 using System.Collections.Generic;
19 using System.Diagnostics;
20 using System.Linq;
21 using System.Text;
22 using System.Threading;
23 using System.Threading.Tasks;
24
25 using Grpc.Core;
26 using Grpc.Health.V1;
27 using NUnit.Framework;
28
29 namespace Grpc.HealthCheck.Tests
30 {
31     /// <summary>
32     /// Tests for HealthCheckServiceImpl
33     /// </summary>
34     public class HealthServiceImplTest
35     {
36         [Test]
37         public void SetStatus()
38         {
39             var impl = new HealthServiceImpl();
40             impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.Serving);
41             Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Serving, GetStatusHelper(impl, ""));
42
43             impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.NotServing);
44             Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.NotServing, GetStatusHelper(impl, ""));
45
46             impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.Unknown);
47             Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Unknown, GetStatusHelper(impl, ""));
48
49             impl.SetStatus("grpc.test.TestService", HealthCheckResponse.Types.ServingStatus.Serving);
50             Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Serving, GetStatusHelper(impl, "grpc.test.TestService"));
51         }
52
53         [Test]
54         public void ClearStatus()
55         {
56             var impl = new HealthServiceImpl();
57             impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.Serving);
58             impl.SetStatus("grpc.test.TestService", HealthCheckResponse.Types.ServingStatus.Unknown);
59
60             impl.ClearStatus("");
61
62             var ex = Assert.Throws<RpcException>(() => GetStatusHelper(impl, ""));
63             Assert.AreEqual(StatusCode.NotFound, ex.Status.StatusCode);
64             Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Unknown, GetStatusHelper(impl, "grpc.test.TestService"));
65         }
66
67         [Test]
68         public void ClearAll()
69         {
70             var impl = new HealthServiceImpl();
71             impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.Serving);
72             impl.SetStatus("grpc.test.TestService", HealthCheckResponse.Types.ServingStatus.Unknown);
73
74             impl.ClearAll();
75             Assert.Throws(typeof(RpcException), () => GetStatusHelper(impl, ""));
76             Assert.Throws(typeof(RpcException), () => GetStatusHelper(impl, "grpc.test.TestService"));
77         }
78
79         [Test]
80         public void NullsRejected()
81         {
82             var impl = new HealthServiceImpl();
83             Assert.Throws(typeof(ArgumentNullException), () => impl.SetStatus(null, HealthCheckResponse.Types.ServingStatus.Serving));
84
85             Assert.Throws(typeof(ArgumentNullException), () => impl.ClearStatus(null));
86         }
87
88 #if GRPC_SUPPORT_WATCH
89         [Test]
90         public async Task Watch()
91         {
92             var cts = new CancellationTokenSource();
93             var context = new TestServerCallContext(cts.Token);
94             var writer = new TestResponseStreamWriter();
95
96             var impl = new HealthServiceImpl();
97             var callTask = impl.Watch(new HealthCheckRequest { Service = "" }, writer, context);
98
99             // Calling Watch on a service that doesn't have a value set will initially return ServiceUnknown
100             var nextWriteTask = writer.WrittenMessagesReader.ReadAsync();
101             Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask).Status);
102
103             nextWriteTask = writer.WrittenMessagesReader.ReadAsync();
104             impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.Serving);
105             Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Serving, (await nextWriteTask).Status);
106
107             nextWriteTask = writer.WrittenMessagesReader.ReadAsync();
108             impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.NotServing);
109             Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.NotServing, (await nextWriteTask).Status);
110
111             nextWriteTask = writer.WrittenMessagesReader.ReadAsync();
112             impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.Unknown);
113             Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Unknown, (await nextWriteTask).Status);
114
115             // Setting status for a different service name will not update Watch results
116             nextWriteTask = writer.WrittenMessagesReader.ReadAsync();
117             impl.SetStatus("grpc.test.TestService", HealthCheckResponse.Types.ServingStatus.Serving);
118             Assert.IsFalse(nextWriteTask.IsCompleted);
119
120             impl.ClearStatus("");
121             Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask).Status);
122
123             Assert.IsFalse(callTask.IsCompleted);
124             cts.Cancel();
125             await callTask;
126         }
127
128         [Test]
129         public async Task Watch_MultipleWatchesForSameService()
130         {
131             var cts = new CancellationTokenSource();
132             var context = new TestServerCallContext(cts.Token);
133             var writer1 = new TestResponseStreamWriter();
134             var writer2 = new TestResponseStreamWriter();
135
136             var impl = new HealthServiceImpl();
137             var callTask1 = impl.Watch(new HealthCheckRequest { Service = "" }, writer1, context);
138             var callTask2 = impl.Watch(new HealthCheckRequest { Service = "" }, writer2, context);
139
140             // Calling Watch on a service that doesn't have a value set will initially return ServiceUnknown
141             var nextWriteTask1 = writer1.WrittenMessagesReader.ReadAsync();
142             var nextWriteTask2 = writer2.WrittenMessagesReader.ReadAsync();
143             Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask1).Status);
144             Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask2).Status);
145
146             nextWriteTask1 = writer1.WrittenMessagesReader.ReadAsync();
147             nextWriteTask2 = writer2.WrittenMessagesReader.ReadAsync();
148             impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.Serving);
149             Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Serving, (await nextWriteTask1).Status);
150             Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Serving, (await nextWriteTask2).Status);
151
152             nextWriteTask1 = writer1.WrittenMessagesReader.ReadAsync();
153             nextWriteTask2 = writer2.WrittenMessagesReader.ReadAsync();
154             impl.ClearStatus("");
155             Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask1).Status);
156             Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask2).Status);
157
158             cts.Cancel();
159             await callTask1;
160             await callTask2;
161         }
162
163         [Test]
164         public async Task Watch_MultipleWatchesForDifferentServices()
165         {
166             var cts = new CancellationTokenSource();
167             var context = new TestServerCallContext(cts.Token);
168             var writer1 = new TestResponseStreamWriter();
169             var writer2 = new TestResponseStreamWriter();
170
171             var impl = new HealthServiceImpl();
172             var callTask1 = impl.Watch(new HealthCheckRequest { Service = "One" }, writer1, context);
173             var callTask2 = impl.Watch(new HealthCheckRequest { Service = "Two" }, writer2, context);
174
175             // Calling Watch on a service that doesn't have a value set will initially return ServiceUnknown
176             var nextWriteTask1 = writer1.WrittenMessagesReader.ReadAsync();
177             var nextWriteTask2 = writer2.WrittenMessagesReader.ReadAsync();
178             Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask1).Status);
179             Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask2).Status);
180
181             nextWriteTask1 = writer1.WrittenMessagesReader.ReadAsync();
182             nextWriteTask2 = writer2.WrittenMessagesReader.ReadAsync();
183             impl.SetStatus("One", HealthCheckResponse.Types.ServingStatus.Serving);
184             impl.SetStatus("Two", HealthCheckResponse.Types.ServingStatus.NotServing);
185             Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Serving, (await nextWriteTask1).Status);
186             Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.NotServing, (await nextWriteTask2).Status);
187
188             nextWriteTask1 = writer1.WrittenMessagesReader.ReadAsync();
189             nextWriteTask2 = writer2.WrittenMessagesReader.ReadAsync();
190             impl.ClearAll();
191             Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask1).Status);
192             Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask2).Status);
193
194             cts.Cancel();
195             await callTask1;
196             await callTask2;
197         }
198
199         [Test]
200         public async Task Watch_ExceedMaximumCapacitySize_DiscardOldValues()
201         {
202             var cts = new CancellationTokenSource();
203             var context = new TestServerCallContext(cts.Token);
204             var writer = new TestResponseStreamWriter(started: false);
205
206             var impl = new HealthServiceImpl();
207             var callTask = impl.Watch(new HealthCheckRequest { Service = "" }, writer, context);
208
209             // Write new statuses. Only last statuses will be returned when we read them from watch writer
210             for (var i = 0; i < HealthServiceImpl.MaxStatusBufferSize * 2; i++)
211             {
212                 // These statuses aren't "valid" but it is useful for testing to have an incrementing number
213                 impl.SetStatus("", (HealthCheckResponse.Types.ServingStatus)i + 10);
214             }
215
216             // Start reading responses now that statuses have been queued up
217             // This is to keep the test non-flakey
218             writer.Start();
219
220             // Read messages in a background task
221             var statuses = new List<HealthCheckResponse.Types.ServingStatus>();
222             var readStatusesTask = Task.Run(async () => {
223                 while (await writer.WrittenMessagesReader.WaitToReadAsync())
224                 {
225                     if (writer.WrittenMessagesReader.TryRead(out var response))
226                     {
227                         statuses.Add(response.Status);
228                     }
229                 }
230             });
231
232             // Tell server we're done watching and it can write what it has left and then exit
233             cts.Cancel();
234             await callTask;
235
236             // Ensure we've read all the queued statuses
237             writer.Complete();
238             await readStatusesTask;
239
240             // Collection will contain initial written message (ServiceUnknown) plus 5 queued messages
241             Assert.AreEqual(HealthServiceImpl.MaxStatusBufferSize + 1, statuses.Count);
242
243             // Initial written message
244             Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, statuses[0]);
245
246             // Last 5 queued messages
247             Assert.AreEqual((HealthCheckResponse.Types.ServingStatus)15, statuses[statuses.Count - 5]);
248             Assert.AreEqual((HealthCheckResponse.Types.ServingStatus)16, statuses[statuses.Count - 4]);
249             Assert.AreEqual((HealthCheckResponse.Types.ServingStatus)17, statuses[statuses.Count - 3]);
250             Assert.AreEqual((HealthCheckResponse.Types.ServingStatus)18, statuses[statuses.Count - 2]);
251             Assert.AreEqual((HealthCheckResponse.Types.ServingStatus)19, statuses[statuses.Count - 1]);
252         }
253 #endif
254
255         private static HealthCheckResponse.Types.ServingStatus GetStatusHelper(HealthServiceImpl impl, string service)
256         {
257             return impl.Check(new HealthCheckRequest { Service = service }, null).Result.Status;
258         }
259     }
260 }