-
Notifications
You must be signed in to change notification settings - Fork 306
introduce watch api #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
language: csharp | ||
sudo: required | ||
sudo: false | ||
matrix: | ||
include: | ||
- dotnet: 2.0.0 | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
using System; | ||
using System.IO; | ||
using System.Runtime.Serialization; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using k8s.Exceptions; | ||
using Microsoft.Rest; | ||
using Microsoft.Rest.Serialization; | ||
|
||
namespace k8s | ||
{ | ||
public enum WatchEventType | ||
{ | ||
[EnumMember(Value = "ADDED")] Added, | ||
|
||
[EnumMember(Value = "MODIFIED")] Modified, | ||
|
||
[EnumMember(Value = "DELETED")] Deleted, | ||
|
||
[EnumMember(Value = "ERROR")] Error | ||
} | ||
|
||
public class Watcher<T> : IDisposable | ||
{ | ||
/// <summary> | ||
/// indicate if the watch object is alive | ||
/// </summary> | ||
public bool Watching { get; private set; } | ||
|
||
private readonly CancellationTokenSource _cts; | ||
private readonly StreamReader _streamReader; | ||
|
||
internal Watcher(StreamReader streamReader, Action<WatchEventType, T> onEvent, Action<Exception> onError) | ||
{ | ||
_streamReader = streamReader; | ||
OnEvent += onEvent; | ||
OnError += onError; | ||
|
||
_cts = new CancellationTokenSource(); | ||
|
||
var token = _cts.Token; | ||
|
||
Task.Run(async () => | ||
{ | ||
try | ||
{ | ||
Watching = true; | ||
|
||
while (!streamReader.EndOfStream) | ||
{ | ||
if (token.IsCancellationRequested) | ||
{ | ||
return; | ||
} | ||
|
||
var line = await streamReader.ReadLineAsync(); | ||
|
||
try | ||
{ | ||
var @event = SafeJsonConvert.DeserializeObject<WatchEvent>(line); | ||
OnEvent?.Invoke(@event.Type, @event.Object); | ||
} | ||
catch (Exception e) | ||
{ | ||
// error if deserialized failed or onevent throws | ||
OnError?.Invoke(e); | ||
} | ||
} | ||
} | ||
catch (Exception e) | ||
{ | ||
// error when transport error, IOException ect | ||
OnError?.Invoke(e); | ||
} | ||
finally | ||
{ | ||
Watching = false; | ||
} | ||
}, token); | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
_cts.Cancel(); | ||
_streamReader.Dispose(); | ||
} | ||
|
||
/// <summary> | ||
/// add/remove callbacks when any event raised from api server | ||
/// </summary> | ||
public event Action<WatchEventType, T> OnEvent; | ||
|
||
/// <summary> | ||
/// add/remove callbacks when any exception was caught during watching | ||
/// </summary> | ||
public event Action<Exception> OnError; | ||
|
||
public class WatchEvent | ||
{ | ||
public WatchEventType Type { get; set; } | ||
|
||
public T Object { get; set; } | ||
} | ||
} | ||
|
||
public static class WatcherExt | ||
{ | ||
/// <summary> | ||
/// create a watch object from a call to api server with watch=true | ||
/// </summary> | ||
/// <typeparam name="T">type of the event object</typeparam> | ||
/// <param name="response">the api response</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> | ||
/// <returns>a watch object</returns> | ||
public static Watcher<T> Watch<T>(this HttpOperationResponse response, | ||
Action<WatchEventType, T> onEvent, | ||
Action<Exception> onError = null) | ||
{ | ||
if (!(response.Response.Content is WatcherDelegatingHandler.LineSeparatedHttpContent content)) | ||
{ | ||
throw new KubernetesClientException("not a watchable request or failed response"); | ||
} | ||
|
||
return new Watcher<T>(content.StreamReader, onEvent, onError); | ||
} | ||
|
||
/// <summary> | ||
/// create a watch object from a call to api server with watch=true | ||
/// </summary> | ||
/// <typeparam name="T">type of the event object</typeparam> | ||
/// <param name="response">the api response</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> | ||
/// <returns>a watch object</returns> | ||
public static Watcher<T> Watch<T>(this HttpOperationResponse<T> response, | ||
Action<WatchEventType, T> onEvent, | ||
Action<Exception> onError = null) | ||
{ | ||
return Watch((HttpOperationResponse) response, onEvent, onError); | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
using System.IO; | ||
using System.Linq; | ||
using System.Net; | ||
using System.Net.Http; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.WebUtilities; | ||
|
||
namespace k8s | ||
{ | ||
/// <summary> | ||
/// This HttpDelegatingHandler is to rewrite the response and return first line to autorest client | ||
/// then use WatchExt to create a watch object which interact with the replaced http response to get watch works. | ||
/// </summary> | ||
internal class WatcherDelegatingHandler : DelegatingHandler | ||
{ | ||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, | ||
CancellationToken cancellationToken) | ||
{ | ||
var originResponse = await base.SendAsync(request, cancellationToken); | ||
|
||
if (originResponse.IsSuccessStatusCode) | ||
{ | ||
var query = QueryHelpers.ParseQuery(request.RequestUri.Query); | ||
|
||
if (query.TryGetValue("watch", out var values) && values.Any(v => v == "true")) | ||
{ | ||
originResponse.Content = new LineSeparatedHttpContent(originResponse.Content); | ||
} | ||
} | ||
return originResponse; | ||
} | ||
|
||
internal class LineSeparatedHttpContent : HttpContent | ||
{ | ||
private readonly HttpContent _originContent; | ||
private Stream _originStream; | ||
|
||
public LineSeparatedHttpContent(HttpContent originContent) | ||
{ | ||
_originContent = originContent; | ||
} | ||
|
||
internal StreamReader StreamReader { get; private set; } | ||
|
||
protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context) | ||
{ | ||
_originStream = await _originContent.ReadAsStreamAsync(); | ||
|
||
StreamReader = new StreamReader(_originStream); | ||
|
||
var firstLine = await StreamReader.ReadLineAsync(); | ||
var writer = new StreamWriter(stream); | ||
|
||
// using (writer) // leave open | ||
{ | ||
await writer.WriteAsync(firstLine); | ||
await writer.FlushAsync(); | ||
} | ||
} | ||
|
||
protected override bool TryComputeLength(out long length) | ||
{ | ||
length = 0; | ||
return false; | ||
} | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm definitely not a csharp style expert, but it feels weird to have newlines between these values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really love
go fmt
we might introduce something like https://github.com/dotnet/codeformatter and contribution guide. I will create another issue to track this.