Skip to content

Commit 108240e

Browse files
authored
fix: Resource watcher re-connects should reset currentVersion (#792)
1 parent e8de144 commit 108240e

File tree

2 files changed

+71
-67
lines changed

2 files changed

+71
-67
lines changed

src/KubeOps.Operator/Watcher/ResourceWatcher{TEntity}.cs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -170,16 +170,15 @@ private async Task WatchClientEventsAsync(CancellationToken stoppingToken)
170170
{
171171
await OnEventAsync(type, entity, stoppingToken);
172172
}
173-
catch (KubernetesException e)
173+
catch (KubernetesException e) when (e.Status.Code is (int)HttpStatusCode.GatewayTimeout)
174174
{
175-
if (e.Status.Code is (int)HttpStatusCode.Gone or (int)HttpStatusCode.GatewayTimeout)
176-
{
177-
logger.LogDebug(e, "Watch restarting due to 410 HTTP Gone or 504 Gateway Timeout.");
178-
179-
break;
180-
}
181-
182-
LogReconciliationFailed(e);
175+
logger.LogDebug(e, "Watch restarting due to 504 Gateway Timeout.");
176+
break;
177+
}
178+
catch (KubernetesException e) when (e.Status.Code is (int)HttpStatusCode.Gone)
179+
{
180+
// Special handling when our resource version is outdated.
181+
throw;
183182
}
184183
catch (Exception e)
185184
{
@@ -202,6 +201,11 @@ void LogReconciliationFailed(Exception exception)
202201
// Don't throw if the cancellation was indeed requested.
203202
break;
204203
}
204+
catch (KubernetesException e) when (e.Status.Code is (int)HttpStatusCode.Gone)
205+
{
206+
logger.LogDebug(e, "Watch restarting with reset bookmark due to 410 HTTP Gone.");
207+
currentVersion = null;
208+
}
205209
catch (Exception e)
206210
{
207211
await OnWatchErrorAsync(e);
Lines changed: 58 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,58 @@
1-
using FluentAssertions;
2-
3-
using k8s.LeaderElection;
4-
5-
using KubeOps.Operator.LeaderElection;
6-
7-
using Microsoft.Extensions.Logging;
8-
9-
using Moq;
10-
11-
namespace KubeOps.Operator.Test.LeaderElector;
12-
13-
public sealed class LeaderElectionBackgroundServiceTest
14-
{
15-
[Fact]
16-
public async Task Elector_Throws_Should_Retry()
17-
{
18-
// Arrange.
19-
var logger = Mock.Of<ILogger<LeaderElectionBackgroundService>>();
20-
21-
var electionLock = Mock.Of<ILock>();
22-
23-
var electionLockSubsequentCallEvent = new AutoResetEvent(false);
24-
bool hasElectionLockThrown = false;
25-
Mock.Get(electionLock)
26-
.Setup(electionLock => electionLock.GetAsync(It.IsAny<CancellationToken>()))
27-
.Returns<CancellationToken>(
28-
async cancellationToken =>
29-
{
30-
if (hasElectionLockThrown)
31-
{
32-
// Signal to the test that a subsequent call has been made.
33-
electionLockSubsequentCallEvent.Set();
34-
35-
// Delay returning for a long time, allowing the test to stop the background service, in turn cancelling the cancellation token.
36-
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
37-
throw new InvalidOperationException();
38-
}
39-
40-
hasElectionLockThrown = true;
41-
throw new Exception("Unit test exception");
42-
});
43-
44-
var leaderElectionConfig = new LeaderElectionConfig(electionLock);
45-
var leaderElector = new k8s.LeaderElection.LeaderElector(leaderElectionConfig);
46-
47-
var leaderElectionBackgroundService = new LeaderElectionBackgroundService(logger, leaderElector);
48-
49-
// Act / Assert.
50-
await leaderElectionBackgroundService.StartAsync(CancellationToken.None);
51-
52-
// Starting the background service should result in the lock attempt throwing, and then a subsequent attempt being made.
53-
// Wait for the subsequent event to be signalled, if we time out the test fails. The retry delay requires us to wait at least 3 seconds.
54-
electionLockSubsequentCallEvent.WaitOne(TimeSpan.FromMilliseconds(3100)).Should().BeTrue();
55-
56-
await leaderElectionBackgroundService.StopAsync(CancellationToken.None);
57-
}
58-
}
1+
using FluentAssertions;
2+
3+
using k8s.LeaderElection;
4+
5+
using KubeOps.Operator.LeaderElection;
6+
7+
using Microsoft.Extensions.Logging;
8+
9+
using Moq;
10+
11+
namespace KubeOps.Operator.Test.LeaderElector;
12+
13+
public sealed class LeaderElectionBackgroundServiceTest
14+
{
15+
[Fact]
16+
public async Task Elector_Throws_Should_Retry()
17+
{
18+
// Arrange.
19+
var logger = Mock.Of<ILogger<LeaderElectionBackgroundService>>();
20+
21+
var electionLock = Mock.Of<ILock>();
22+
23+
var electionLockSubsequentCallEvent = new AutoResetEvent(false);
24+
bool hasElectionLockThrown = false;
25+
Mock.Get(electionLock)
26+
.Setup(electionLock => electionLock.GetAsync(It.IsAny<CancellationToken>()))
27+
.Returns<CancellationToken>(
28+
async cancellationToken =>
29+
{
30+
if (hasElectionLockThrown)
31+
{
32+
// Signal to the test that a subsequent call has been made.
33+
electionLockSubsequentCallEvent.Set();
34+
35+
// Delay returning for a long time, allowing the test to stop the background service, in turn cancelling the cancellation token.
36+
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
37+
throw new InvalidOperationException();
38+
}
39+
40+
hasElectionLockThrown = true;
41+
throw new Exception("Unit test exception");
42+
});
43+
44+
var leaderElectionConfig = new LeaderElectionConfig(electionLock);
45+
var leaderElector = new k8s.LeaderElection.LeaderElector(leaderElectionConfig);
46+
47+
var leaderElectionBackgroundService = new LeaderElectionBackgroundService(logger, leaderElector);
48+
49+
// Act / Assert.
50+
await leaderElectionBackgroundService.StartAsync(CancellationToken.None);
51+
52+
// Starting the background service should result in the lock attempt throwing, and then a subsequent attempt being made.
53+
// Wait for the subsequent event to be signalled, if we time out the test fails. The retry delay requires us to wait at least 3 seconds.
54+
electionLockSubsequentCallEvent.WaitOne(TimeSpan.FromMilliseconds(3100)).Should().BeTrue();
55+
56+
await leaderElectionBackgroundService.StopAsync(CancellationToken.None);
57+
}
58+
}

0 commit comments

Comments
 (0)