watcher to support empty response (#313)
* Add a WatchAsync method. * proposal for watcher async * fix and add testcase for async watch * try fix build
This commit is contained in:
committed by
Kubernetes Prow Robot
parent
8ab95b6b92
commit
a95400bd50
@@ -13,8 +13,8 @@ namespace watch
|
|||||||
|
|
||||||
IKubernetes client = new Kubernetes(config);
|
IKubernetes client = new Kubernetes(config);
|
||||||
|
|
||||||
var podlistResp = client.ListNamespacedPodWithHttpMessagesAsync("default", watch: true).Result;
|
var podlistResp = client.ListNamespacedPodWithHttpMessagesAsync("default", watch: true);
|
||||||
using (podlistResp.Watch<V1Pod>((type, item) =>
|
using (podlistResp.Watch<V1Pod, V1PodList>((type, item) =>
|
||||||
{
|
{
|
||||||
Console.WriteLine("==on watch event==");
|
Console.WriteLine("==on watch event==");
|
||||||
Console.WriteLine(type);
|
Console.WriteLine(type);
|
||||||
|
|||||||
@@ -149,10 +149,12 @@ namespace k8s
|
|||||||
throw ex;
|
throw ex;
|
||||||
}
|
}
|
||||||
|
|
||||||
var stream = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
return new Watcher<T>(async () => {
|
||||||
StreamReader reader = new StreamReader(stream);
|
var stream = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||||
|
StreamReader reader = new StreamReader(stream);
|
||||||
|
|
||||||
return new Watcher<T>(reader, onEvent, onError, onClosed);
|
return reader;
|
||||||
|
}, onEvent, onError, onClosed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ namespace k8s
|
|||||||
public bool Watching { get; private set; }
|
public bool Watching { get; private set; }
|
||||||
|
|
||||||
private readonly CancellationTokenSource _cts;
|
private readonly CancellationTokenSource _cts;
|
||||||
private readonly StreamReader _streamReader;
|
private readonly Func<Task<StreamReader>> _streamReaderCreator;
|
||||||
|
|
||||||
|
private StreamReader _streamReader;
|
||||||
private readonly Task _watcherLoop;
|
private readonly Task _watcherLoop;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -47,9 +49,9 @@ namespace k8s
|
|||||||
/// <param name="onClosed">
|
/// <param name="onClosed">
|
||||||
/// The action to invoke when the server closes the connection.
|
/// The action to invoke when the server closes the connection.
|
||||||
/// </param>
|
/// </param>
|
||||||
public Watcher(StreamReader streamReader, Action<WatchEventType, T> onEvent, Action<Exception> onError, Action onClosed = null)
|
public Watcher(Func<Task<StreamReader>> streamReaderCreator, Action<WatchEventType, T> onEvent, Action<Exception> onError, Action onClosed = null)
|
||||||
{
|
{
|
||||||
_streamReader = streamReader;
|
_streamReaderCreator = streamReaderCreator;
|
||||||
OnEvent += onEvent;
|
OnEvent += onEvent;
|
||||||
OnError += onError;
|
OnError += onError;
|
||||||
OnClosed += onClosed;
|
OnClosed += onClosed;
|
||||||
@@ -62,7 +64,7 @@ namespace k8s
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_cts.Cancel();
|
_cts.Cancel();
|
||||||
_streamReader.Dispose();
|
_streamReader?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -96,9 +98,10 @@ namespace k8s
|
|||||||
{
|
{
|
||||||
Watching = true;
|
Watching = true;
|
||||||
string line;
|
string line;
|
||||||
|
_streamReader = await _streamReaderCreator();
|
||||||
|
|
||||||
// ReadLineAsync will return null when we've reached the end of the stream.
|
// ReadLineAsync will return null when we've reached the end of the stream.
|
||||||
while ((line = await this._streamReader.ReadLineAsync().ConfigureAwait(false)) != null)
|
while ((line = await _streamReader.ReadLineAsync().ConfigureAwait(false)) != null)
|
||||||
{
|
{
|
||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@@ -147,6 +150,7 @@ namespace k8s
|
|||||||
/// create a watch object from a call to api server with watch=true
|
/// create a watch object from a call to api server with watch=true
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">type of the event object</typeparam>
|
/// <typeparam name="T">type of the event object</typeparam>
|
||||||
|
/// <typeparam name="L">type of the HttpOperationResponse object</typeparam>
|
||||||
/// <param name="response">the api response</param>
|
/// <param name="response">the api response</param>
|
||||||
/// <param name="onEvent">a callback when any event raised from api server</param>
|
/// <param name="onEvent">a callback when any event raised from api server</param>
|
||||||
/// <param name="onError">a callbak when any exception was caught during watching</param>
|
/// <param name="onError">a callbak when any exception was caught during watching</param>
|
||||||
@@ -154,23 +158,28 @@ namespace k8s
|
|||||||
/// The action to invoke when the server closes the connection.
|
/// The action to invoke when the server closes the connection.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <returns>a watch object</returns>
|
/// <returns>a watch object</returns>
|
||||||
public static Watcher<T> Watch<T>(this HttpOperationResponse response,
|
public static Watcher<T> Watch<T, L>(this Task<HttpOperationResponse<L>> responseTask,
|
||||||
Action<WatchEventType, T> onEvent,
|
Action<WatchEventType, T> onEvent,
|
||||||
Action<Exception> onError = null,
|
Action<Exception> onError = null,
|
||||||
Action onClosed = null)
|
Action onClosed = null)
|
||||||
{
|
{
|
||||||
if (!(response.Response.Content is WatcherDelegatingHandler.LineSeparatedHttpContent content))
|
return new Watcher<T>(async () => {
|
||||||
{
|
var response = await responseTask;
|
||||||
throw new KubernetesClientException("not a watchable request or failed response");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Watcher<T>(content.StreamReader, onEvent, onError, onClosed);
|
if (!(response.Response.Content is WatcherDelegatingHandler.LineSeparatedHttpContent content))
|
||||||
|
{
|
||||||
|
throw new KubernetesClientException("not a watchable request or failed response");
|
||||||
|
}
|
||||||
|
|
||||||
|
return content.StreamReader;
|
||||||
|
} , onEvent, onError, onClosed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// create a watch object from a call to api server with watch=true
|
/// create a watch object from a call to api server with watch=true
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">type of the event object</typeparam>
|
/// <typeparam name="T">type of the event object</typeparam>
|
||||||
|
/// <typeparam name="L">type of the HttpOperationResponse object</typeparam>
|
||||||
/// <param name="response">the api response</param>
|
/// <param name="response">the api response</param>
|
||||||
/// <param name="onEvent">a callback when any event raised from api server</param>
|
/// <param name="onEvent">a callback when any event raised from api server</param>
|
||||||
/// <param name="onError">a callbak when any exception was caught during watching</param>
|
/// <param name="onError">a callbak when any exception was caught during watching</param>
|
||||||
@@ -178,12 +187,12 @@ namespace k8s
|
|||||||
/// The action to invoke when the server closes the connection.
|
/// The action to invoke when the server closes the connection.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <returns>a watch object</returns>
|
/// <returns>a watch object</returns>
|
||||||
public static Watcher<T> Watch<T>(this HttpOperationResponse<T> response,
|
public static Watcher<T> Watch<T, L>(this HttpOperationResponse<L> response,
|
||||||
Action<WatchEventType, T> onEvent,
|
Action<WatchEventType, T> onEvent,
|
||||||
Action<Exception> onError = null,
|
Action<Exception> onError = null,
|
||||||
Action onClosed = null)
|
Action onClosed = null)
|
||||||
{
|
{
|
||||||
return Watch((HttpOperationResponse)response, onEvent, onError, onClosed);
|
return Watch(Task.FromResult(response), onEvent, onError, onClosed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,11 +66,16 @@ namespace k8s.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
// did not pass watch param
|
// did not pass watch param
|
||||||
var listTask = await client.ListNamespacedPodWithHttpMessagesAsync("default");
|
var listTask = client.ListNamespacedPodWithHttpMessagesAsync("default");
|
||||||
Assert.ThrowsAny<KubernetesClientException>(() =>
|
var onErrorCalled = false;
|
||||||
{
|
|
||||||
listTask.Watch<V1Pod>((type, item) => { });
|
using (listTask.Watch<V1Pod, V1PodList>((type, item) => { }, e => {
|
||||||
});
|
onErrorCalled = true;
|
||||||
|
})) { }
|
||||||
|
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(1)); // delay for onerror to be called
|
||||||
|
Assert.True(onErrorCalled);
|
||||||
|
|
||||||
|
|
||||||
// server did not response line by line
|
// server did not response line by line
|
||||||
await Assert.ThrowsAnyAsync<Exception>(() =>
|
await Assert.ThrowsAnyAsync<Exception>(() =>
|
||||||
@@ -83,6 +88,41 @@ namespace k8s.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AsyncWatcher()
|
||||||
|
{
|
||||||
|
var created = new AsyncManualResetEvent(false);
|
||||||
|
var eventsReceived = new AsyncManualResetEvent(false);
|
||||||
|
|
||||||
|
using (var server = new MockKubeApiServer(testOutput, async httpContext =>
|
||||||
|
{
|
||||||
|
// block until reponse watcher obj created
|
||||||
|
await created.WaitAsync();
|
||||||
|
await WriteStreamLine(httpContext, MockAddedEventStreamLine);
|
||||||
|
return false;
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
var client = new Kubernetes(new KubernetesClientConfiguration
|
||||||
|
{
|
||||||
|
Host = server.Uri.ToString()
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var listTask = client.ListNamespacedPodWithHttpMessagesAsync("default", watch: true);
|
||||||
|
using (listTask.Watch<V1Pod, V1PodList>((type, item) => {
|
||||||
|
eventsReceived.Set();
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
// here watcher is ready to use, but http server has not responsed yet.
|
||||||
|
created.Set();
|
||||||
|
await Task.WhenAny(eventsReceived.WaitAsync(), Task.Delay(TestTimeout));
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.True(eventsReceived.IsSet);
|
||||||
|
Assert.True(created.IsSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task SuriveBadLine()
|
public async Task SuriveBadLine()
|
||||||
{
|
{
|
||||||
@@ -119,7 +159,7 @@ namespace k8s.Tests
|
|||||||
var events = new HashSet<WatchEventType>();
|
var events = new HashSet<WatchEventType>();
|
||||||
var errors = 0;
|
var errors = 0;
|
||||||
|
|
||||||
var watcher = listTask.Watch<V1Pod>(
|
var watcher = listTask.Watch<V1Pod, V1PodList>(
|
||||||
(type, item) =>
|
(type, item) =>
|
||||||
{
|
{
|
||||||
testOutput.WriteLine($"Watcher received '{type}' event.");
|
testOutput.WriteLine($"Watcher received '{type}' event.");
|
||||||
@@ -188,7 +228,7 @@ namespace k8s.Tests
|
|||||||
|
|
||||||
var events = new HashSet<WatchEventType>();
|
var events = new HashSet<WatchEventType>();
|
||||||
|
|
||||||
var watcher = listTask.Watch<V1Pod>(
|
var watcher = listTask.Watch<V1Pod, V1PodList>(
|
||||||
(type, item) =>
|
(type, item) =>
|
||||||
{
|
{
|
||||||
events.Add(type);
|
events.Add(type);
|
||||||
@@ -256,7 +296,7 @@ namespace k8s.Tests
|
|||||||
var events = new HashSet<WatchEventType>();
|
var events = new HashSet<WatchEventType>();
|
||||||
var errors = 0;
|
var errors = 0;
|
||||||
|
|
||||||
var watcher = listTask.Watch<V1Pod>(
|
var watcher = listTask.Watch<V1Pod, V1PodList>(
|
||||||
(type, item) =>
|
(type, item) =>
|
||||||
{
|
{
|
||||||
testOutput.WriteLine($"Watcher received '{type}' event.");
|
testOutput.WriteLine($"Watcher received '{type}' event.");
|
||||||
@@ -330,7 +370,7 @@ namespace k8s.Tests
|
|||||||
var events = new HashSet<WatchEventType>();
|
var events = new HashSet<WatchEventType>();
|
||||||
var errors = 0;
|
var errors = 0;
|
||||||
|
|
||||||
var watcher = listTask.Watch<V1Pod>(
|
var watcher = listTask.Watch<V1Pod, V1PodList>(
|
||||||
(type, item) =>
|
(type, item) =>
|
||||||
{
|
{
|
||||||
testOutput.WriteLine($"Watcher received '{type}' event.");
|
testOutput.WriteLine($"Watcher received '{type}' event.");
|
||||||
@@ -396,7 +436,7 @@ namespace k8s.Tests
|
|||||||
|
|
||||||
waitForException.Set();
|
waitForException.Set();
|
||||||
Watcher<V1Pod> watcher;
|
Watcher<V1Pod> watcher;
|
||||||
watcher = listTask.Watch<V1Pod>(
|
watcher = listTask.Watch<V1Pod, V1PodList>(
|
||||||
onEvent: (type, item) => { },
|
onEvent: (type, item) => { },
|
||||||
onError: e =>
|
onError: e =>
|
||||||
{
|
{
|
||||||
@@ -463,7 +503,7 @@ namespace k8s.Tests
|
|||||||
|
|
||||||
var events = new HashSet<WatchEventType>();
|
var events = new HashSet<WatchEventType>();
|
||||||
|
|
||||||
var watcher = listTask.Watch<V1Pod>(
|
var watcher = listTask.Watch<V1Pod, V1PodList>(
|
||||||
(type, item) =>
|
(type, item) =>
|
||||||
{
|
{
|
||||||
events.Add(type);
|
events.Add(type);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace k8s.Tests
|
namespace k8s.Tests
|
||||||
@@ -21,7 +22,7 @@ namespace k8s.Tests
|
|||||||
ManualResetEvent mre = new ManualResetEvent(false);
|
ManualResetEvent mre = new ManualResetEvent(false);
|
||||||
|
|
||||||
Watcher<V1Pod> watcher = new Watcher<V1Pod>(
|
Watcher<V1Pod> watcher = new Watcher<V1Pod>(
|
||||||
reader,
|
() => Task.FromResult(reader),
|
||||||
null,
|
null,
|
||||||
(exception) =>
|
(exception) =>
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user