261 lines
12 KiB
C#
261 lines
12 KiB
C#
#region Copyright notice and license
|
|
// Copyright 2015 gRPC authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
#endregion
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
using Grpc.Core;
|
|
using Grpc.Health.V1;
|
|
using NUnit.Framework;
|
|
|
|
namespace Grpc.HealthCheck.Tests
|
|
{
|
|
/// <summary>
|
|
/// Tests for HealthCheckServiceImpl
|
|
/// </summary>
|
|
public class HealthServiceImplTest
|
|
{
|
|
[Test]
|
|
public void SetStatus()
|
|
{
|
|
var impl = new HealthServiceImpl();
|
|
impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.Serving);
|
|
Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Serving, GetStatusHelper(impl, ""));
|
|
|
|
impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.NotServing);
|
|
Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.NotServing, GetStatusHelper(impl, ""));
|
|
|
|
impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.Unknown);
|
|
Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Unknown, GetStatusHelper(impl, ""));
|
|
|
|
impl.SetStatus("grpc.test.TestService", HealthCheckResponse.Types.ServingStatus.Serving);
|
|
Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Serving, GetStatusHelper(impl, "grpc.test.TestService"));
|
|
}
|
|
|
|
[Test]
|
|
public void ClearStatus()
|
|
{
|
|
var impl = new HealthServiceImpl();
|
|
impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.Serving);
|
|
impl.SetStatus("grpc.test.TestService", HealthCheckResponse.Types.ServingStatus.Unknown);
|
|
|
|
impl.ClearStatus("");
|
|
|
|
var ex = Assert.Throws<RpcException>(() => GetStatusHelper(impl, ""));
|
|
Assert.AreEqual(StatusCode.NotFound, ex.Status.StatusCode);
|
|
Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Unknown, GetStatusHelper(impl, "grpc.test.TestService"));
|
|
}
|
|
|
|
[Test]
|
|
public void ClearAll()
|
|
{
|
|
var impl = new HealthServiceImpl();
|
|
impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.Serving);
|
|
impl.SetStatus("grpc.test.TestService", HealthCheckResponse.Types.ServingStatus.Unknown);
|
|
|
|
impl.ClearAll();
|
|
Assert.Throws(typeof(RpcException), () => GetStatusHelper(impl, ""));
|
|
Assert.Throws(typeof(RpcException), () => GetStatusHelper(impl, "grpc.test.TestService"));
|
|
}
|
|
|
|
[Test]
|
|
public void NullsRejected()
|
|
{
|
|
var impl = new HealthServiceImpl();
|
|
Assert.Throws(typeof(ArgumentNullException), () => impl.SetStatus(null, HealthCheckResponse.Types.ServingStatus.Serving));
|
|
|
|
Assert.Throws(typeof(ArgumentNullException), () => impl.ClearStatus(null));
|
|
}
|
|
|
|
#if GRPC_SUPPORT_WATCH
|
|
[Test]
|
|
public async Task Watch()
|
|
{
|
|
var cts = new CancellationTokenSource();
|
|
var context = new TestServerCallContext(cts.Token);
|
|
var writer = new TestResponseStreamWriter();
|
|
|
|
var impl = new HealthServiceImpl();
|
|
var callTask = impl.Watch(new HealthCheckRequest { Service = "" }, writer, context);
|
|
|
|
// Calling Watch on a service that doesn't have a value set will initially return ServiceUnknown
|
|
var nextWriteTask = writer.WrittenMessagesReader.ReadAsync();
|
|
Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask).Status);
|
|
|
|
nextWriteTask = writer.WrittenMessagesReader.ReadAsync();
|
|
impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.Serving);
|
|
Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Serving, (await nextWriteTask).Status);
|
|
|
|
nextWriteTask = writer.WrittenMessagesReader.ReadAsync();
|
|
impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.NotServing);
|
|
Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.NotServing, (await nextWriteTask).Status);
|
|
|
|
nextWriteTask = writer.WrittenMessagesReader.ReadAsync();
|
|
impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.Unknown);
|
|
Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Unknown, (await nextWriteTask).Status);
|
|
|
|
// Setting status for a different service name will not update Watch results
|
|
nextWriteTask = writer.WrittenMessagesReader.ReadAsync();
|
|
impl.SetStatus("grpc.test.TestService", HealthCheckResponse.Types.ServingStatus.Serving);
|
|
Assert.IsFalse(nextWriteTask.IsCompleted);
|
|
|
|
impl.ClearStatus("");
|
|
Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask).Status);
|
|
|
|
Assert.IsFalse(callTask.IsCompleted);
|
|
cts.Cancel();
|
|
await callTask;
|
|
}
|
|
|
|
[Test]
|
|
public async Task Watch_MultipleWatchesForSameService()
|
|
{
|
|
var cts = new CancellationTokenSource();
|
|
var context = new TestServerCallContext(cts.Token);
|
|
var writer1 = new TestResponseStreamWriter();
|
|
var writer2 = new TestResponseStreamWriter();
|
|
|
|
var impl = new HealthServiceImpl();
|
|
var callTask1 = impl.Watch(new HealthCheckRequest { Service = "" }, writer1, context);
|
|
var callTask2 = impl.Watch(new HealthCheckRequest { Service = "" }, writer2, context);
|
|
|
|
// Calling Watch on a service that doesn't have a value set will initially return ServiceUnknown
|
|
var nextWriteTask1 = writer1.WrittenMessagesReader.ReadAsync();
|
|
var nextWriteTask2 = writer2.WrittenMessagesReader.ReadAsync();
|
|
Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask1).Status);
|
|
Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask2).Status);
|
|
|
|
nextWriteTask1 = writer1.WrittenMessagesReader.ReadAsync();
|
|
nextWriteTask2 = writer2.WrittenMessagesReader.ReadAsync();
|
|
impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.Serving);
|
|
Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Serving, (await nextWriteTask1).Status);
|
|
Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Serving, (await nextWriteTask2).Status);
|
|
|
|
nextWriteTask1 = writer1.WrittenMessagesReader.ReadAsync();
|
|
nextWriteTask2 = writer2.WrittenMessagesReader.ReadAsync();
|
|
impl.ClearStatus("");
|
|
Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask1).Status);
|
|
Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask2).Status);
|
|
|
|
cts.Cancel();
|
|
await callTask1;
|
|
await callTask2;
|
|
}
|
|
|
|
[Test]
|
|
public async Task Watch_MultipleWatchesForDifferentServices()
|
|
{
|
|
var cts = new CancellationTokenSource();
|
|
var context = new TestServerCallContext(cts.Token);
|
|
var writer1 = new TestResponseStreamWriter();
|
|
var writer2 = new TestResponseStreamWriter();
|
|
|
|
var impl = new HealthServiceImpl();
|
|
var callTask1 = impl.Watch(new HealthCheckRequest { Service = "One" }, writer1, context);
|
|
var callTask2 = impl.Watch(new HealthCheckRequest { Service = "Two" }, writer2, context);
|
|
|
|
// Calling Watch on a service that doesn't have a value set will initially return ServiceUnknown
|
|
var nextWriteTask1 = writer1.WrittenMessagesReader.ReadAsync();
|
|
var nextWriteTask2 = writer2.WrittenMessagesReader.ReadAsync();
|
|
Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask1).Status);
|
|
Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask2).Status);
|
|
|
|
nextWriteTask1 = writer1.WrittenMessagesReader.ReadAsync();
|
|
nextWriteTask2 = writer2.WrittenMessagesReader.ReadAsync();
|
|
impl.SetStatus("One", HealthCheckResponse.Types.ServingStatus.Serving);
|
|
impl.SetStatus("Two", HealthCheckResponse.Types.ServingStatus.NotServing);
|
|
Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Serving, (await nextWriteTask1).Status);
|
|
Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.NotServing, (await nextWriteTask2).Status);
|
|
|
|
nextWriteTask1 = writer1.WrittenMessagesReader.ReadAsync();
|
|
nextWriteTask2 = writer2.WrittenMessagesReader.ReadAsync();
|
|
impl.ClearAll();
|
|
Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask1).Status);
|
|
Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask2).Status);
|
|
|
|
cts.Cancel();
|
|
await callTask1;
|
|
await callTask2;
|
|
}
|
|
|
|
[Test]
|
|
public async Task Watch_ExceedMaximumCapacitySize_DiscardOldValues()
|
|
{
|
|
var cts = new CancellationTokenSource();
|
|
var context = new TestServerCallContext(cts.Token);
|
|
var writer = new TestResponseStreamWriter(started: false);
|
|
|
|
var impl = new HealthServiceImpl();
|
|
var callTask = impl.Watch(new HealthCheckRequest { Service = "" }, writer, context);
|
|
|
|
// Write new statuses. Only last statuses will be returned when we read them from watch writer
|
|
for (var i = 0; i < HealthServiceImpl.MaxStatusBufferSize * 2; i++)
|
|
{
|
|
// These statuses aren't "valid" but it is useful for testing to have an incrementing number
|
|
impl.SetStatus("", (HealthCheckResponse.Types.ServingStatus)i + 10);
|
|
}
|
|
|
|
// Start reading responses now that statuses have been queued up
|
|
// This is to keep the test non-flakey
|
|
writer.Start();
|
|
|
|
// Read messages in a background task
|
|
var statuses = new List<HealthCheckResponse.Types.ServingStatus>();
|
|
var readStatusesTask = Task.Run(async () => {
|
|
while (await writer.WrittenMessagesReader.WaitToReadAsync())
|
|
{
|
|
if (writer.WrittenMessagesReader.TryRead(out var response))
|
|
{
|
|
statuses.Add(response.Status);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Tell server we're done watching and it can write what it has left and then exit
|
|
cts.Cancel();
|
|
await callTask;
|
|
|
|
// Ensure we've read all the queued statuses
|
|
writer.Complete();
|
|
await readStatusesTask;
|
|
|
|
// Collection will contain initial written message (ServiceUnknown) plus 5 queued messages
|
|
Assert.AreEqual(HealthServiceImpl.MaxStatusBufferSize + 1, statuses.Count);
|
|
|
|
// Initial written message
|
|
Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, statuses[0]);
|
|
|
|
// Last 5 queued messages
|
|
Assert.AreEqual((HealthCheckResponse.Types.ServingStatus)15, statuses[statuses.Count - 5]);
|
|
Assert.AreEqual((HealthCheckResponse.Types.ServingStatus)16, statuses[statuses.Count - 4]);
|
|
Assert.AreEqual((HealthCheckResponse.Types.ServingStatus)17, statuses[statuses.Count - 3]);
|
|
Assert.AreEqual((HealthCheckResponse.Types.ServingStatus)18, statuses[statuses.Count - 2]);
|
|
Assert.AreEqual((HealthCheckResponse.Types.ServingStatus)19, statuses[statuses.Count - 1]);
|
|
}
|
|
#endif
|
|
|
|
private static HealthCheckResponse.Types.ServingStatus GetStatusHelper(HealthServiceImpl impl, string service)
|
|
{
|
|
return impl.Check(new HealthCheckRequest { Service = service }, null).Result.Status;
|
|
}
|
|
}
|
|
}
|