public void RequestProxied_FailureRateLimitExceeded_MarkDestinationUnhealthy()
        {
            var options = Options.Create(
                new TransportFailureRateHealthPolicyOptions {
                DefaultFailureRateLimit = 0.5, DetectionWindowSize = TimeSpan.FromSeconds(30), MinimalTotalCountThreshold = 1
            });
            var clock         = new ManualClock(TimeSpan.FromMilliseconds(10000));
            var healthUpdater = new Mock <IDestinationHealthUpdater>();
            var policy        = new TransportFailureRateHealthPolicy(options, clock, healthUpdater.Object);

            Assert.Equal(HealthCheckConstants.PassivePolicy.TransportFailureRate, policy.Name);

            var reactivationPeriod0 = TimeSpan.FromSeconds(60);
            var reactivationPeriod1 = TimeSpan.FromSeconds(100);
            var cluster0            = GetClusterInfo("cluster0", destinationCount: 2);
            var cluster1            = GetClusterInfo("cluster1", destinationCount: 2, failureRateLimit: 0.61, reactivationPeriod1);

            // Initial state
            Assert.All(cluster0.Destinations.Values, d => Assert.Equal(DestinationHealth.Unknown, d.Health.Passive));
            Assert.All(cluster1.Destinations.Values, d => Assert.Equal(DestinationHealth.Unknown, d.Health.Passive));

            // Successful requests
            for (var i = 0; i < 3; i++)
            {
                policy.RequestProxied(cluster0, cluster0.Destinations.Values.First(), new DefaultHttpContext());
                policy.RequestProxied(cluster0, cluster0.Destinations.Values.Skip(1).First(), new DefaultHttpContext());
                policy.RequestProxied(cluster1, cluster1.Destinations.Values.First(), new DefaultHttpContext());
                policy.RequestProxied(cluster1, cluster1.Destinations.Values.Skip(1).First(), new DefaultHttpContext());
                clock.AdvanceClockBy(TimeSpan.FromMilliseconds(4000));
            }

            healthUpdater.Verify(u => u.SetPassive(cluster0, cluster0.Destinations.Values.First(), DestinationHealth.Healthy, reactivationPeriod0), Times.Exactly(3));
            healthUpdater.Verify(u => u.SetPassive(cluster0, cluster0.Destinations.Values.Skip(1).First(), DestinationHealth.Healthy, reactivationPeriod0), Times.Exactly(3));
            healthUpdater.Verify(u => u.SetPassive(cluster1, cluster1.Destinations.Values.First(), DestinationHealth.Healthy, reactivationPeriod1), Times.Exactly(3));
            healthUpdater.Verify(u => u.SetPassive(cluster1, cluster1.Destinations.Values.Skip(1).First(), DestinationHealth.Healthy, reactivationPeriod1), Times.Exactly(3));
            healthUpdater.VerifyNoOtherCalls();

            // Failed requests
            for (var i = 0; i < 3; i++)
            {
                policy.RequestProxied(cluster0, cluster0.Destinations.Values.Skip(1).First(), GetFailedRequestContext(ProxyError.RequestTimedOut));
                policy.RequestProxied(cluster1, cluster1.Destinations.Values.First(), GetFailedRequestContext(ProxyError.Request));
                clock.AdvanceClockBy(TimeSpan.FromMilliseconds(4000));
            }

            healthUpdater.Verify(u => u.SetPassive(cluster0, cluster0.Destinations.Values.Skip(1).First(), DestinationHealth.Healthy, reactivationPeriod0), Times.Exactly(5));
            healthUpdater.Verify(u => u.SetPassive(cluster0, cluster0.Destinations.Values.Skip(1).First(), DestinationHealth.Unhealthy, reactivationPeriod0), Times.Once);
            healthUpdater.Verify(u => u.SetPassive(cluster1, cluster1.Destinations.Values.First(), DestinationHealth.Healthy, reactivationPeriod1), Times.Exactly(6));
            healthUpdater.VerifyNoOtherCalls();

            // Two more failed requests
            policy.RequestProxied(cluster1, cluster1.Destinations.Values.First(), GetFailedRequestContext(ProxyError.Request));
            // End of the detection window
            clock.AdvanceClockBy(TimeSpan.FromMilliseconds(6000));
            policy.RequestProxied(cluster1, cluster1.Destinations.Values.First(), GetFailedRequestContext(ProxyError.Request));

            healthUpdater.Verify(u => u.SetPassive(cluster1, cluster1.Destinations.Values.First(), DestinationHealth.Healthy, reactivationPeriod1), Times.Exactly(7));
            healthUpdater.Verify(u => u.SetPassive(cluster1, cluster1.Destinations.Values.First(), DestinationHealth.Unhealthy, reactivationPeriod1), Times.Once);
            healthUpdater.VerifyNoOtherCalls();
        }
        public void RequestProxied_FailureMovedOutOfDetectionWindow_MarkDestinationHealthy()
        {
            var options = Options.Create(
                new TransportFailureRateHealthPolicyOptions {
                DefaultFailureRateLimit = 0.5, DetectionWindowSize = TimeSpan.FromSeconds(30), MinimalTotalCountThreshold = 1
            });
            var clock         = new ManualClock(TimeSpan.FromMilliseconds(10000));
            var healthUpdater = new Mock <IDestinationHealthUpdater>();
            var policy        = new TransportFailureRateHealthPolicy(options, clock, healthUpdater.Object);

            var cluster = GetClusterInfo("cluster0", destinationCount: 2);

            // Initial failed requests
            for (var i = 0; i < 2; i++)
            {
                policy.RequestProxied(cluster, cluster.Destinations.Values.Skip(1).First(), GetFailedRequestContext(ProxyError.RequestTimedOut));
                clock.AdvanceClockBy(TimeSpan.FromMilliseconds(1000));
            }

            healthUpdater.Verify(u => u.SetPassive(cluster, cluster.Destinations.Values.Skip(1).First(), DestinationHealth.Unhealthy, TimeSpan.FromSeconds(60)), Times.Exactly(2));
            healthUpdater.VerifyNoOtherCalls();

            // Successful requests
            for (var i = 0; i < 4; i++)
            {
                policy.RequestProxied(cluster, cluster.Destinations.Values.First(), new DefaultHttpContext());
                policy.RequestProxied(cluster, cluster.Destinations.Values.Skip(1).First(), new DefaultHttpContext());
                clock.AdvanceClockBy(TimeSpan.FromMilliseconds(5000));
            }

            healthUpdater.Verify(u => u.SetPassive(cluster, cluster.Destinations.Values.First(), DestinationHealth.Healthy, TimeSpan.FromSeconds(60)), Times.Exactly(4));
            healthUpdater.Verify(u => u.SetPassive(cluster, cluster.Destinations.Values.Skip(1).First(), DestinationHealth.Healthy, TimeSpan.FromSeconds(60)), Times.Exactly(2));
            healthUpdater.Verify(u => u.SetPassive(cluster, cluster.Destinations.Values.Skip(1).First(), DestinationHealth.Unhealthy, TimeSpan.FromSeconds(60)), Times.Exactly(4));
            healthUpdater.VerifyNoOtherCalls();

            // Failed requests
            for (var i = 0; i < 2; i++)
            {
                policy.RequestProxied(cluster, cluster.Destinations.Values.Skip(1).First(), GetFailedRequestContext(ProxyError.RequestTimedOut));
                clock.AdvanceClockBy(TimeSpan.FromMilliseconds(1));
            }

            healthUpdater.Verify(u => u.SetPassive(cluster, cluster.Destinations.Values.Skip(1).First(), DestinationHealth.Healthy, TimeSpan.FromSeconds(60)), Times.Exactly(3));
            healthUpdater.Verify(u => u.SetPassive(cluster, cluster.Destinations.Values.Skip(1).First(), DestinationHealth.Unhealthy, TimeSpan.FromSeconds(60)), Times.Exactly(5));
            healthUpdater.VerifyNoOtherCalls();

            // Shift the detection window to the future
            clock.AdvanceClockBy(TimeSpan.FromMilliseconds(10998));

            // New failed request, but 2 oldest failures have moved out of the detection window
            policy.RequestProxied(cluster, cluster.Destinations.Values.Skip(1).First(), GetFailedRequestContext(ProxyError.RequestTimedOut));

            healthUpdater.Verify(u => u.SetPassive(cluster, cluster.Destinations.Values.Skip(1).First(), DestinationHealth.Healthy, TimeSpan.FromSeconds(60)), Times.Exactly(4));
            healthUpdater.VerifyNoOtherCalls();
        }
        public void RequestProxied_MultipleConcurrentRequests_MarkDestinationUnhealthyAndHealthyAgain()
        {
            var options = Options.Create(
                new TransportFailureRateHealthPolicyOptions {
                DefaultFailureRateLimit = 0.5, DetectionWindowSize = TimeSpan.FromSeconds(30), MinimalTotalCountThreshold = 1
            });
            var clock              = new ManualClock(TimeSpan.FromMilliseconds(10000));
            var healthUpdater      = new Mock <IDestinationHealthUpdater>();
            var reactivationPeriod = TimeSpan.FromSeconds(15);
            var policy             = new TransportFailureRateHealthPolicy(options, clock, healthUpdater.Object);

            var cluster = GetClusterInfo("cluster0", destinationCount: 2, reactivationPeriod: reactivationPeriod);

            // Initial state
            Assert.All(cluster.Destinations.Values, d => Assert.Equal(DestinationHealth.Unknown, d.Health.Passive));

            // Initial sucessful requests
            for (var i = 0; i < 2; i++)
            {
                policy.RequestProxied(cluster, cluster.Destinations.Values.Skip(1).First(), new DefaultHttpContext());
            }

            healthUpdater.Verify(u => u.SetPassive(cluster, cluster.Destinations.Values.Skip(1).First(), DestinationHealth.Healthy, reactivationPeriod), Times.Exactly(2));
            healthUpdater.VerifyNoOtherCalls();

            // Concurrent failed requests.
            // They are 'concurrent' because the clock is not updated.
            for (var i = 0; i < 2; i++)
            {
                policy.RequestProxied(cluster, cluster.Destinations.Values.Skip(1).First(), GetFailedRequestContext(ProxyError.RequestTimedOut));
            }

            healthUpdater.Verify(u => u.SetPassive(cluster, cluster.Destinations.Values.Skip(1).First(), DestinationHealth.Healthy, reactivationPeriod), Times.Exactly(3));
            healthUpdater.Verify(u => u.SetPassive(cluster, cluster.Destinations.Values.Skip(1).First(), DestinationHealth.Unhealthy, reactivationPeriod), Times.Once);
            healthUpdater.VerifyNoOtherCalls();

            // More successful requests
            for (var i = 0; i < 2; i++)
            {
                policy.RequestProxied(cluster, cluster.Destinations.Values.Skip(1).First(), new DefaultHttpContext());
                clock.AdvanceClockBy(TimeSpan.FromMilliseconds(100));
            }

            healthUpdater.Verify(u => u.SetPassive(cluster, cluster.Destinations.Values.Skip(1).First(), DestinationHealth.Healthy, reactivationPeriod), Times.Exactly(5));
            healthUpdater.Verify(u => u.SetPassive(cluster, cluster.Destinations.Values.Skip(1).First(), DestinationHealth.Unhealthy, reactivationPeriod), Times.Once);
            healthUpdater.VerifyNoOtherCalls();

            // More failed requests
            for (var i = 0; i < 2; i++)
            {
                policy.RequestProxied(cluster, cluster.Destinations.Values.Skip(1).First(), GetFailedRequestContext(ProxyError.RequestTimedOut));
                clock.AdvanceClockBy(TimeSpan.FromMilliseconds(100));
            }

            healthUpdater.Verify(u => u.SetPassive(cluster, cluster.Destinations.Values.Skip(1).First(), DestinationHealth.Healthy, reactivationPeriod), Times.Exactly(6));
            healthUpdater.Verify(u => u.SetPassive(cluster, cluster.Destinations.Values.Skip(1).First(), DestinationHealth.Unhealthy, reactivationPeriod), Times.Exactly(2));
            healthUpdater.VerifyNoOtherCalls();

            policy.RequestProxied(cluster, cluster.Destinations.Values.First(), new DefaultHttpContext());

            healthUpdater.Verify(u => u.SetPassive(cluster, cluster.Destinations.Values.First(), DestinationHealth.Healthy, reactivationPeriod), Times.Once);
            healthUpdater.VerifyNoOtherCalls();
        }