11using System ;
2+ using System . Collections . Generic ;
23using System . Linq ;
4+ using System . Text ;
35using System . Threading ;
46using System . Threading . Tasks ;
57using Docker . DotNet . Models ;
@@ -10,11 +12,18 @@ namespace Docker.DotNet.Tests
1012 [ Collection ( nameof ( TestCollection ) ) ]
1113 public class ISwarmOperationsTests
1214 {
15+ private readonly CancellationTokenSource _cts ;
16+
1317 private readonly DockerClient _dockerClient ;
1418 private readonly string _imageId ;
1519
1620 public ISwarmOperationsTests ( TestFixture testFixture )
1721 {
22+ // Do not wait forever in case it gets stuck
23+ _cts = CancellationTokenSource . CreateLinkedTokenSource ( testFixture . Cts . Token ) ;
24+ _cts . CancelAfter ( TimeSpan . FromMinutes ( 5 ) ) ;
25+ _cts . Token . Register ( ( ) => throw new TimeoutException ( "SwarmOperationTests timeout" ) ) ;
26+
1827 _dockerClient = testFixture . DockerClient ;
1928 _imageId = testFixture . Image . ID ;
2029 }
@@ -148,5 +157,103 @@ public async Task GetServices_Succeeds()
148157 await _dockerClient . Swarm . RemoveServiceAsync ( secondServiceId , default ) ;
149158 await _dockerClient . Swarm . RemoveServiceAsync ( thirdServiceid , default ) ;
150159 }
160+
161+ [ Fact ]
162+ public async Task GetServiceLogs_Succeeds ( )
163+ {
164+ var cts = new CancellationTokenSource ( ) ;
165+ var linkedCts = CancellationTokenSource . CreateLinkedTokenSource ( _cts . Token , cts . Token ) ;
166+
167+ var serviceName = $ "service-withLogs-{ Guid . NewGuid ( ) . ToString ( ) . Substring ( 1 , 10 ) } ";
168+ var serviceId = _dockerClient . Swarm . CreateServiceAsync ( new ServiceCreateParameters
169+ {
170+ Service = new ServiceSpec
171+ {
172+ Name = serviceName ,
173+ TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _imageId } }
174+ }
175+ } ) . Result . ID ;
176+
177+ var _stream = await _dockerClient . Swarm . GetServiceLogsAsync ( serviceName , false , new ServiceLogsParameters
178+ {
179+ Follow = true ,
180+ ShowStdout = true ,
181+ ShowStderr = true
182+ } ) ;
183+
184+ int maxRetries = 3 ;
185+ int currentRetry = 0 ;
186+ TimeSpan delayBetweenRetries = TimeSpan . FromSeconds ( 5 ) ;
187+ List < string > logLines = null ;
188+
189+ while ( currentRetry < maxRetries && ! linkedCts . IsCancellationRequested )
190+ {
191+ logLines = new List < string > ( ) ;
192+ TimeSpan delay = TimeSpan . FromSeconds ( 10 ) ;
193+ cts . CancelAfter ( delay ) ;
194+
195+ bool cancelRequested = false ; // Add a flag to indicate cancellation
196+
197+ while ( ! linkedCts . IsCancellationRequested && ! cancelRequested )
198+ {
199+ var line = new List < byte > ( ) ;
200+ var buffer = new byte [ 4096 ] ;
201+
202+ try
203+ {
204+ while ( true )
205+ {
206+ var res = await _stream . ReadOutputAsync ( buffer , 0 , buffer . Length , linkedCts . Token ) ;
207+
208+ if ( res . Count == 0 )
209+ {
210+ continue ;
211+ }
212+
213+ int newlineIndex = Array . IndexOf ( buffer , ( byte ) '\n ' , 0 , res . Count ) ;
214+
215+ if ( newlineIndex != - 1 )
216+ {
217+ line . AddRange ( buffer . Take ( newlineIndex ) ) ;
218+ break ;
219+ }
220+ else
221+ {
222+ line . AddRange ( buffer . Take ( res . Count ) ) ;
223+ }
224+ }
225+
226+ logLines . Add ( Encoding . UTF8 . GetString ( line . ToArray ( ) ) ) ;
227+ }
228+ catch ( OperationCanceledException )
229+ {
230+ cancelRequested = true ; // Set the flag when cancellation is requested
231+
232+ // Reset the CancellationTokenSource for the next attempt
233+ cts = new CancellationTokenSource ( ) ;
234+ linkedCts = CancellationTokenSource . CreateLinkedTokenSource ( _cts . Token , cts . Token ) ;
235+ cts . CancelAfter ( delay ) ;
236+ }
237+ }
238+
239+ if ( logLines . Any ( ) && logLines . First ( ) . Contains ( "[INF]" ) )
240+ {
241+ break ;
242+ }
243+ else
244+ {
245+ currentRetry ++ ;
246+ if ( currentRetry < maxRetries )
247+ {
248+ await Task . Delay ( delayBetweenRetries ) ;
249+ }
250+ }
251+ }
252+
253+ Assert . True ( logLines . Any ( ) ) ;
254+ Assert . Contains ( "[INF]" , logLines . First ( ) ) ;
255+
256+ await _dockerClient . Swarm . RemoveServiceAsync ( serviceId , default ) ;
257+ }
151258 }
152259}
0 commit comments