1
1
using System . Reactive . Linq ;
2
+ using System . Runtime . Serialization ;
3
+ using System . Text . Json ;
2
4
using FluentAssertions ;
3
5
using k8s ;
4
6
using k8s . Models ;
@@ -32,22 +34,7 @@ public async Task Should_Restart_Watcher_On_Exception()
32
34
{
33
35
var settings = new OperatorSettings ( ) ;
34
36
35
- Action < Exception > ? onError = null ;
36
-
37
- _client . Setup (
38
- c => c . Watch (
39
- It . IsAny < TimeSpan > ( ) ,
40
- It . IsAny < Action < WatchEventType , TestResource > > ( ) ,
41
- It . IsAny < Action < Exception > ? > ( ) ,
42
- It . IsAny < Action > ( ) ,
43
- null ,
44
- It . IsAny < CancellationToken > ( ) ,
45
- It . IsAny < string ? > ( ) ) )
46
- . Callback < TimeSpan , Action < WatchEventType , TestResource > , Action < Exception > ? , Action ? , string ? ,
47
- CancellationToken , string ? > (
48
- ( _ , _ , onErrorArg , _ , _ , _ , _ ) => { onError = onErrorArg ; } )
49
- . Returns ( Task . FromResult ( CreateFakeWatcher ( ) ) )
50
- . Verifiable ( ) ;
37
+ Func < Action < Exception > ? > getOnError = SetupKubernetesClientWatch ( ) ;
51
38
52
39
_metrics . Setup ( c => c . Running ) . Returns ( Mock . Of < IGauge > ( ) ) ;
53
40
_metrics . Setup ( c => c . WatcherExceptions ) . Returns ( Mock . Of < ICounter > ( ) ) ;
@@ -63,7 +50,7 @@ public async Task Should_Restart_Watcher_On_Exception()
63
50
64
51
await resourceWatcher . StartAsync ( ) ;
65
52
66
- onError ? . Invoke ( new Exception ( ) ) ;
53
+ getOnError ( ) ? . Invoke ( new Exception ( ) ) ;
67
54
68
55
var backoff = settings . ErrorBackoffStrategy ( 1 ) ;
69
56
@@ -72,11 +59,8 @@ public async Task Should_Restart_Watcher_On_Exception()
72
59
VerifyWatch ( 2 ) ;
73
60
}
74
61
75
- [ Fact ]
76
- public async Task Should_Not_Throw_Overflow_Exception ( )
62
+ private Func < Action < Exception > ? > SetupKubernetesClientWatch ( )
77
63
{
78
- var settings = new OperatorSettings ( ) ;
79
-
80
64
Action < Exception > ? onError = null ;
81
65
82
66
_client . Setup (
@@ -88,12 +72,25 @@ public async Task Should_Not_Throw_Overflow_Exception()
88
72
null ,
89
73
It . IsAny < CancellationToken > ( ) ,
90
74
It . IsAny < string ? > ( ) ) )
91
- . Callback < TimeSpan , Action < WatchEventType , TestResource > , Action < Exception > ? , Action ? , string ? ,
92
- CancellationToken , string ? > (
93
- ( _ , _ , onErrorArg , _ , _ , _ , _ ) => { onError = onErrorArg ; } )
75
+ . Callback < TimeSpan , Action < WatchEventType , TestResource > , Action < Exception > ? , Action ? , string ? , CancellationToken , string ? > (
76
+ ( _ , _ , onErrorArg , _ , _ , _ , _ ) =>
77
+ {
78
+ onError = onErrorArg ;
79
+ } )
94
80
. Returns ( Task . FromResult ( CreateFakeWatcher ( ) ) )
95
81
. Verifiable ( ) ;
96
82
83
+
84
+ return ( ) => onError ;
85
+ }
86
+
87
+ [ Fact ]
88
+ public async Task Should_Not_Throw_Overflow_Exception ( )
89
+ {
90
+ var settings = new OperatorSettings ( ) ;
91
+
92
+ Func < Action < Exception > ? > getOnError = SetupKubernetesClientWatch ( ) ;
93
+
97
94
_metrics . Setup ( c => c . Running ) . Returns ( Mock . Of < IGauge > ( ) ) ;
98
95
_metrics . Setup ( c => c . WatcherExceptions ) . Returns ( Mock . Of < ICounter > ( ) ) ;
99
96
@@ -111,7 +108,7 @@ public async Task Should_Not_Throw_Overflow_Exception()
111
108
const int numberOfRetries = 40 ;
112
109
for ( int reconnectAttempts = 1 ; reconnectAttempts <= numberOfRetries ; reconnectAttempts ++ )
113
110
{
114
- onError ? . Invoke ( new Exception ( ) ) ;
111
+ getOnError ( ) ? . Invoke ( new Exception ( ) ) ;
115
112
116
113
var backoff = settings . ErrorBackoffStrategy ( reconnectAttempts > 39 ? 39 : reconnectAttempts ) ;
117
114
if ( backoff . TotalSeconds > settings . WatcherMaxRetrySeconds )
@@ -122,24 +119,15 @@ public async Task Should_Not_Throw_Overflow_Exception()
122
119
testScheduler . AdvanceBy ( backoff . Add ( TimeSpan . FromSeconds ( 1 ) ) . Ticks ) ;
123
120
}
124
121
125
- VerifyWatch ( numberOfRetries + 1 ) ;
122
+ VerifyWatch ( numberOfRetries + 1 ) ;
126
123
}
127
124
128
125
[ Fact ]
129
126
public async Task Should_Not_Dispose_Reconnect_Subject_Or_Throw_Exception_After_Restarts ( )
130
127
{
131
128
var settings = new OperatorSettings ( ) ;
132
129
133
- Action < Exception > ? onError = null ;
134
-
135
- _client . Setup ( c => c . Watch ( It . IsAny < TimeSpan > ( ) , It . IsAny < Action < WatchEventType , TestResource > > ( ) , It . IsAny < Action < Exception > ? > ( ) , It . IsAny < Action > ( ) , null , It . IsAny < CancellationToken > ( ) , It . IsAny < string ? > ( ) ) )
136
- . Callback < TimeSpan , Action < WatchEventType , TestResource > , Action < Exception > ? , Action ? , string ? , CancellationToken , string ? > (
137
- ( _ , _ , onErrorArg , _ , _ , _ , _ ) =>
138
- {
139
- onError = onErrorArg ;
140
- } )
141
- . Returns ( Task . FromResult ( CreateFakeWatcher ( ) ) )
142
- . Verifiable ( ) ;
130
+ Func < Action < Exception > ? > getOnError = SetupKubernetesClientWatch ( ) ;
143
131
144
132
_metrics . Setup ( c => c . Running ) . Returns ( Mock . Of < IGauge > ( ) ) ;
145
133
_metrics . Setup ( c => c . WatcherExceptions ) . Returns ( Mock . Of < ICounter > ( ) ) ;
@@ -154,13 +142,62 @@ public async Task Should_Not_Dispose_Reconnect_Subject_Or_Throw_Exception_After_
154
142
155
143
var kubernetesException = new KubernetesException ( new V1Status ( ) ) ;
156
144
157
- onError ? . Invoke ( kubernetesException ) ;
145
+ getOnError ( ) ? . Invoke ( kubernetesException ) ;
158
146
159
147
resourceWatcher . WatchEvents . Should ( ) . NotBeNull ( ) ;
160
148
161
149
VerifyWatch ( 2 ) ;
162
150
}
163
151
152
+ [ Fact ]
153
+ public async Task Should_Not_Restart_On_Serialization_Exception ( )
154
+ {
155
+ var settings = new OperatorSettings ( ) ;
156
+
157
+ Func < Action < Exception > ? > getOnError = SetupKubernetesClientWatch ( ) ;
158
+
159
+ _metrics . Setup ( c => c . Running ) . Returns ( Mock . Of < IGauge > ( ) ) ;
160
+ _metrics . Setup ( c => c . WatcherExceptions ) . Returns ( Mock . Of < ICounter > ( ) ) ;
161
+
162
+ using var resourceWatcher = new ResourceWatcher < TestResource > ( _client . Object , new NullLogger < ResourceWatcher < TestResource > > ( ) , _metrics . Object , settings ) ;
163
+
164
+ await resourceWatcher . StartAsync ( ) ;
165
+
166
+ var serializationException = new SerializationException ( string . Empty , new JsonException ( "The input does not contain any JSON tokens" ) ) ;
167
+
168
+ getOnError ( ) ? . Invoke ( serializationException ) ;
169
+
170
+ resourceWatcher . WatchEvents . Should ( ) . NotBeNull ( ) ;
171
+
172
+ VerifyWatch ( 1 ) ;
173
+ }
174
+
175
+ [ Fact ]
176
+ public async Task Should_Be_Restarted_After_TaskCanceledException_IOException ( )
177
+ {
178
+ var settings = new OperatorSettings ( ) ;
179
+
180
+ Func < Action < Exception > ? > getOnError = SetupKubernetesClientWatch ( ) ;
181
+
182
+ SetupResourceWatcherMetrics ( ) ;
183
+
184
+ using var resourceWatcher = new ResourceWatcher < TestResource > ( _client . Object , new NullLogger < ResourceWatcher < TestResource > > ( ) , _metrics . Object , settings ) ;
185
+
186
+ await resourceWatcher . StartAsync ( ) ;
187
+
188
+ var serializationException = new TaskCanceledException ( string . Empty , new IOException ( $@ "Either the server or the client did close the connection on watcher for resource ""{ typeof ( TestResource ) } "". Restart.") ) ;
189
+
190
+ getOnError ( ) ? . Invoke ( serializationException ) ;
191
+
192
+ VerifyWatch ( 2 ) ;
193
+ }
194
+
195
+ private void SetupResourceWatcherMetrics ( )
196
+ {
197
+ _metrics . Setup ( c => c . Running ) . Returns ( Mock . Of < IGauge > ( ) ) ;
198
+ _metrics . Setup ( c => c . WatcherExceptions ) . Returns ( Mock . Of < ICounter > ( ) ) ;
199
+ }
200
+
164
201
[ Fact ]
165
202
public async Task Should_Publish_On_Watcher_Event ( )
166
203
{
0 commit comments