Ported GenericKubernetesApi from java along with other utilities (#682)
* Ported GenericKubernetesApi from java along with other utilities * Replace DeleteOptions for V1DeleteOptions * Clean up and add clear() * Clean up * Removed TweakApiHandler * Rename methods to follow "async" pattern * Fix method naming * Remove unneeded json property * Rearrange httpsuccess logic * Simplify dispose pattern * Treat MockKubeServerFlags as flags * Clean up flags logic * Remove unneeded json properties * Fix cs formatting * Remove unused variable * Move MockApi server options to seperate class and revert MockApi back to original * Remove IRunnable * Refactor config constants to use existing service account path
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using k8s.Models;
|
||||
using k8s.Tests.Mock;
|
||||
using k8s.Util.Common;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace k8s.Tests.Util.Common.Generic
|
||||
{
|
||||
public class GenericKubernetesApiTest
|
||||
{
|
||||
private readonly ITestOutputHelper _outputHelper;
|
||||
|
||||
public GenericKubernetesApiTest(ITestOutputHelper outputHelper)
|
||||
{
|
||||
_outputHelper = outputHelper;
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Create constructor success")]
|
||||
public void CreateConstSuccess()
|
||||
{
|
||||
using var server = new MockKubeApiServer(_outputHelper);
|
||||
var genericApi = Helpers.BuildGenericApi(server.Uri);
|
||||
genericApi.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Get namespaced object success")]
|
||||
public async Task GetNamespacedObject()
|
||||
{
|
||||
var serverOptions = new MockKubeApiServerOptions(MockKubeServerFlags.GetPod);
|
||||
using var server = new MockKubeApiServer(_outputHelper, serverOptions.ShouldNext);
|
||||
var podName = "nginx-1493591563-xb2v4";
|
||||
var genericApi = Helpers.BuildGenericApi(server.Uri);
|
||||
|
||||
var resp = await genericApi.GetAsync<V1Pod>(Namespaces.NamespaceDefault, podName).ConfigureAwait(false);
|
||||
|
||||
resp.Should().NotBeNull();
|
||||
resp.Metadata.Name.Should().Be(podName);
|
||||
resp.Metadata.NamespaceProperty.Should().Be(Namespaces.NamespaceDefault);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "List namespaced object success")]
|
||||
public async Task ListNamespacedObject()
|
||||
{
|
||||
var serverOptions = new MockKubeApiServerOptions(MockKubeServerFlags.ListPods);
|
||||
using var server = new MockKubeApiServer(_outputHelper, serverOptions.ShouldNext);
|
||||
var genericApi = Helpers.BuildGenericApi(server.Uri);
|
||||
|
||||
var resp = await genericApi.ListAsync<V1PodList>(Namespaces.NamespaceDefault).ConfigureAwait(false);
|
||||
|
||||
resp.Should().NotBeNull();
|
||||
resp.Items.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Patch namespaced object success")]
|
||||
public async Task PatchNamespacedObject()
|
||||
{
|
||||
using var server = new MockKubeApiServer(_outputHelper);
|
||||
var podName = "nginx-1493591563-xb2v4";
|
||||
var genericApi = Helpers.BuildGenericApi(server.Uri);
|
||||
|
||||
var resp = await genericApi.PatchAsync<V1Pod>(Namespaces.NamespaceDefault, podName).ConfigureAwait(false);
|
||||
|
||||
resp.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Update object success")]
|
||||
public async Task UpdateObject()
|
||||
{
|
||||
using var server = new MockKubeApiServer(_outputHelper);
|
||||
var pod = Helpers.CreatePods(1).First();
|
||||
var genericApi = Helpers.BuildGenericApi(server.Uri);
|
||||
|
||||
var resp = await genericApi.UpdateAsync(pod).ConfigureAwait(false);
|
||||
|
||||
resp.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Delete namespaced object success")]
|
||||
public async Task DeleteNamespacedObject()
|
||||
{
|
||||
using var server = new MockKubeApiServer(_outputHelper);
|
||||
var podName = "nginx-1493591563-xb2v4";
|
||||
var genericApi = Helpers.BuildGenericApi(server.Uri);
|
||||
|
||||
var resp = await genericApi.DeleteAsync<V1Pod>(Namespaces.NamespaceDefault, podName).ConfigureAwait(false);
|
||||
|
||||
resp.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Watch namespaced object success")]
|
||||
public void WatchNamespacedObject()
|
||||
{
|
||||
using var cts = new CancellationTokenSource();
|
||||
var serverOptions = new MockKubeApiServerOptions(MockKubeServerFlags.ModifiedPod);
|
||||
using var server = new MockKubeApiServer(_outputHelper, serverOptions.ShouldNext);
|
||||
var genericApi = Helpers.BuildGenericApi(server.Uri);
|
||||
|
||||
using var resp = genericApi.Watch<V1Pod>(Namespaces.NamespaceDefault, (actionType, pod) => { }, exception => { }, () => { }, cts.Token);
|
||||
|
||||
resp.Should().NotBeNull();
|
||||
cts.CancelAfter(1000);
|
||||
serverOptions.ServerShutdown?.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
75
tests/KubernetesClient.Tests/Util/Helpers.cs
Normal file
75
tests/KubernetesClient.Tests/Util/Helpers.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using k8s.Models;
|
||||
using k8s.Tests.Mock;
|
||||
using k8s.Util.Common.Generic;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Nito.AsyncEx;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace k8s.Tests.Util
|
||||
{
|
||||
internal static class Helpers
|
||||
{
|
||||
public static IEnumerable<V1Pod> CreatePods(int cnt)
|
||||
{
|
||||
var pods = new List<V1Pod>();
|
||||
for (var i = 0; i < cnt; i++)
|
||||
{
|
||||
pods.Add(new V1Pod()
|
||||
{
|
||||
ApiVersion = "Pod/V1",
|
||||
Kind = "Pod",
|
||||
Metadata = new V1ObjectMeta()
|
||||
{
|
||||
Name = Guid.NewGuid().ToString(),
|
||||
NamespaceProperty = "the-namespace",
|
||||
ResourceVersion = DateTime.Now.Ticks.ToString(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return pods;
|
||||
}
|
||||
|
||||
public static V1PodList CreatePodList(int cnt)
|
||||
{
|
||||
return new V1PodList()
|
||||
{
|
||||
ApiVersion = "Pod/V1",
|
||||
Kind = "Pod",
|
||||
Metadata = new V1ListMeta()
|
||||
{
|
||||
ResourceVersion = DateTime.Now.Ticks.ToString(),
|
||||
},
|
||||
Items = CreatePods(cnt).ToList(),
|
||||
};
|
||||
}
|
||||
|
||||
public static Kubernetes BuildApiClient(Uri hostAddress)
|
||||
{
|
||||
return new Kubernetes(new KubernetesClientConfiguration { Host = hostAddress.ToString() })
|
||||
{
|
||||
HttpClient =
|
||||
{
|
||||
Timeout = Timeout.InfiniteTimeSpan,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public static GenericKubernetesApi BuildGenericApi(Uri hostAddress)
|
||||
{
|
||||
return new GenericKubernetesApi(
|
||||
apiGroup: "pod",
|
||||
apiVersion: "v1",
|
||||
resourcePlural: "pods",
|
||||
apiClient: BuildApiClient(hostAddress));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using k8s.Util.Informer.Cache;
|
||||
using k8s.Models;
|
||||
using k8s.Util.Informer.Cache;
|
||||
using Xunit;
|
||||
|
||||
namespace k8s.Tests.Util.Informer.Cache
|
||||
@@ -16,13 +16,12 @@ namespace k8s.Tests.Util.Informer.Cache
|
||||
var cache = new Cache<V1Node>();
|
||||
cache.Should().NotBeNull();
|
||||
cache.GetIndexers().ContainsKey(Caches.NamespaceIndex).Should().BeTrue();
|
||||
// Todo: validate all defaults gor set up
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Add cache item success")]
|
||||
private void AddCacheItemSuccess()
|
||||
{
|
||||
var aPod = Util.CreatePods(1).First();
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
cache.Add(aPod);
|
||||
@@ -33,7 +32,7 @@ namespace k8s.Tests.Util.Informer.Cache
|
||||
[Fact(DisplayName = "Update cache item success")]
|
||||
private void UpdateCacheItemSuccess()
|
||||
{
|
||||
var aPod = Util.CreatePods(1).First();
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
@@ -47,7 +46,7 @@ namespace k8s.Tests.Util.Informer.Cache
|
||||
[Fact(DisplayName = "Delete cache item success")]
|
||||
private void DeleteCacheItemSuccess()
|
||||
{
|
||||
var aPod = Util.CreatePods(1).First();
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
@@ -61,7 +60,7 @@ namespace k8s.Tests.Util.Informer.Cache
|
||||
[Fact(DisplayName = "Replace cache items success")]
|
||||
private void ReplaceCacheItemsSuccess()
|
||||
{
|
||||
var pods = Util.CreatePods(3);
|
||||
var pods = Helpers.CreatePods(3);
|
||||
var aPod = pods.First();
|
||||
var anotherPod = pods.Skip(1).First();
|
||||
var yetAnotherPod = pods.Skip(2).First();
|
||||
@@ -79,7 +78,7 @@ namespace k8s.Tests.Util.Informer.Cache
|
||||
[Fact(DisplayName = "List item keys success")]
|
||||
public void ListItemKeysSuccess()
|
||||
{
|
||||
var pods = Util.CreatePods(3);
|
||||
var pods = Helpers.CreatePods(3);
|
||||
var aPod = pods.First();
|
||||
var anotherPod = pods.Skip(1).First();
|
||||
var cache = new Cache<V1Pod>();
|
||||
@@ -96,7 +95,7 @@ namespace k8s.Tests.Util.Informer.Cache
|
||||
[Fact(DisplayName = "Get item doesn't exist")]
|
||||
public void GetItemNotExist()
|
||||
{
|
||||
var aPod = Util.CreatePods(1).First();
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
var item = cache.Get(aPod);
|
||||
@@ -106,7 +105,7 @@ namespace k8s.Tests.Util.Informer.Cache
|
||||
[Fact(DisplayName = "Get item success")]
|
||||
public void GetItemSuccess()
|
||||
{
|
||||
var aPod = Util.CreatePods(1).First();
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
cache.Add(aPod);
|
||||
@@ -117,7 +116,7 @@ namespace k8s.Tests.Util.Informer.Cache
|
||||
[Fact(DisplayName = "List items success")]
|
||||
public void ListItemSuccess()
|
||||
{
|
||||
var pods = Util.CreatePods(3);
|
||||
var pods = Helpers.CreatePods(3);
|
||||
var aPod = pods.First();
|
||||
var anotherPod = pods.Skip(1).First();
|
||||
var yetAnotherPod = pods.Skip(2).First();
|
||||
@@ -138,7 +137,7 @@ namespace k8s.Tests.Util.Informer.Cache
|
||||
[Fact(DisplayName = "Get item by key success")]
|
||||
public void GetItemByKeySuccess()
|
||||
{
|
||||
var pod = Util.CreatePods(1).First();
|
||||
var pod = Helpers.CreatePods(1).First();
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
cache.Add(pod);
|
||||
@@ -149,7 +148,7 @@ namespace k8s.Tests.Util.Informer.Cache
|
||||
[Fact(DisplayName = "Index items no index")]
|
||||
public void IndexItemsNoIndex()
|
||||
{
|
||||
var pod = Util.CreatePods(1).First();
|
||||
var pod = Helpers.CreatePods(1).First();
|
||||
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
@@ -161,7 +160,7 @@ namespace k8s.Tests.Util.Informer.Cache
|
||||
[Fact(DisplayName = "Index items success")]
|
||||
public void IndexItemsSuccess()
|
||||
{
|
||||
var pod = Util.CreatePods(1).First();
|
||||
var pod = Helpers.CreatePods(1).First();
|
||||
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
@@ -191,7 +190,7 @@ namespace k8s.Tests.Util.Informer.Cache
|
||||
[Fact(DisplayName = "Get index keys success")]
|
||||
public void GetIndexKeysSuccess()
|
||||
{
|
||||
var pod = Util.CreatePods(1).First();
|
||||
var pod = Helpers.CreatePods(1).First();
|
||||
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
@@ -221,7 +220,7 @@ namespace k8s.Tests.Util.Informer.Cache
|
||||
[Fact(DisplayName = "List by index success")]
|
||||
public void ListByIndexSuccess()
|
||||
{
|
||||
var pod = Util.CreatePods(1).First();
|
||||
var pod = Helpers.CreatePods(1).First();
|
||||
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
@@ -323,7 +322,7 @@ namespace k8s.Tests.Util.Informer.Cache
|
||||
},
|
||||
};
|
||||
var cache = new Cache<V1Pod>();
|
||||
var newFunc = new Func<V1Pod, string>((pod) => pod.Kind);
|
||||
var newFunc = new Func<IKubernetesObject<V1ObjectMeta>, string>((pod) => pod.Kind);
|
||||
var defaultReturnValue = newFunc(aPod);
|
||||
|
||||
cache.SetKeyFunc(newFunc);
|
||||
|
||||
@@ -12,14 +12,14 @@ namespace k8s.Tests.Util.Informer.Cache
|
||||
[Fact(DisplayName = "Check for default DeletedFinalStateUnknown")]
|
||||
public void CheckDefaultDeletedFinalStateUnknown()
|
||||
{
|
||||
var aPod = Util.CreatePods(1).First();
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
Caches.DeletionHandlingMetaNamespaceKeyFunc(aPod).Should().Be($"{aPod.Metadata.NamespaceProperty}/{aPod.Metadata.Name}");
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Check for obj DeletedFinalStateUnknown")]
|
||||
public void CheckObjDeletedFinalStateUnknown()
|
||||
{
|
||||
var aPod = Util.CreatePods(1).First();
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
var key = "a-key";
|
||||
var deletedPod = new DeletedFinalStateUnknown<V1Pod>(key, aPod);
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace k8s.Tests.Util.Informer.Cache
|
||||
[Fact(DisplayName = "Get default namespace key success")]
|
||||
public void GetDefaultNamespaceKeySuccess()
|
||||
{
|
||||
var aPod = Util.CreatePods(1).First();
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
Caches.MetaNamespaceKeyFunc(aPod).Should().Be($"{aPod.Metadata.NamespaceProperty}/{aPod.Metadata.Name}");
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace k8s.Tests.Util.Informer.Cache
|
||||
[Fact(DisplayName = "Get default namespace index success")]
|
||||
public void GetDefaultNamespaceIndexSuccess()
|
||||
{
|
||||
var aPod = Util.CreatePods(1).First();
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
var indexes = Caches.MetaNamespaceIndexFunc(aPod);
|
||||
|
||||
indexes.Should().NotBeNull();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using k8s.Models;
|
||||
using Xunit;
|
||||
using k8s.Util.Informer.Cache;
|
||||
using Xunit;
|
||||
|
||||
namespace k8s.Tests.Util.Informer.Cache
|
||||
{
|
||||
@@ -20,7 +20,7 @@ namespace k8s.Tests.Util.Informer.Cache
|
||||
[Fact(DisplayName = "List with null namespace success")]
|
||||
private void ListNullNamespaceSuccess()
|
||||
{
|
||||
var aPod = Util.CreatePods(1).First();
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
var cache = new Cache<V1Pod>();
|
||||
var lister = new Lister<V1Pod>(cache);
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace k8s.Tests.Util.Informer.Cache
|
||||
[Fact(DisplayName = "List with custom namespace success")]
|
||||
private void ListCustomNamespaceSuccess()
|
||||
{
|
||||
var aPod = Util.CreatePods(1).First();
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
var cache = new Cache<V1Pod>();
|
||||
var lister = new Lister<V1Pod>(cache, aPod.Metadata.NamespaceProperty);
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace k8s.Tests.Util.Informer.Cache
|
||||
[Fact(DisplayName = "Get with null namespace success")]
|
||||
private void GetNullNamespaceSuccess()
|
||||
{
|
||||
var aPod = Util.CreatePods(1).First();
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
var cache = new Cache<V1Pod>();
|
||||
var lister = new Lister<V1Pod>(cache);
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace k8s.Tests.Util.Informer.Cache
|
||||
[Fact(DisplayName = "Get with custom namespace success")]
|
||||
private void GetCustomNamespaceSuccess()
|
||||
{
|
||||
var aPod = Util.CreatePods(1).First();
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
var cache = new Cache<V1Pod>();
|
||||
var lister = new Lister<V1Pod>(cache, aPod.Metadata.NamespaceProperty);
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace k8s.Tests.Util.Informer.Cache
|
||||
[Fact(DisplayName = "Set custom namespace success")]
|
||||
private void SetCustomNamespaceSuccess()
|
||||
{
|
||||
var aPod = Util.CreatePods(1).First();
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
var cache = new Cache<V1Pod>();
|
||||
var lister = new Lister<V1Pod>(cache);
|
||||
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
using FluentAssertions;
|
||||
using k8s.Models;
|
||||
using k8s.Util.Informer.Cache;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace k8s.Tests.Util.Informer.Cache
|
||||
{
|
||||
public class ReflectorTest
|
||||
{
|
||||
private readonly ITestOutputHelper _ouputHelper;
|
||||
|
||||
public ReflectorTest(ITestOutputHelper outputHelper)
|
||||
{
|
||||
_ouputHelper = outputHelper;
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Create default reflector success")]
|
||||
public void CreateReflectorSuccess()
|
||||
{
|
||||
/*using var apiClient = new Kubernetes(_clientConfiguration);
|
||||
var cache = new Cache<V1Pod>();
|
||||
var queue = new DeltaFifo(Caches.MetaNamespaceKeyFunc, cache, _deltasLogger);
|
||||
var listerWatcher = new ListWatcher<V1Pod, V1PodList>(apiClient, ListAllPods);
|
||||
var logger = LoggerFactory.Create(builder => builder.AddXUnit(_ouputHelper).SetMinimumLevel(LogLevel.Trace)).CreateLogger<k8s.Util.Cache.Reflector>();
|
||||
var reflector = new k8s.Util.Cache.Reflector<V1Pod, V1PodList>(listerWatcher, queue, logger);
|
||||
|
||||
reflector.Should().NotBeNull();*/
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using k8s.Models;
|
||||
|
||||
namespace k8s.Tests.Util.Informer.Cache
|
||||
{
|
||||
internal static class Util
|
||||
{
|
||||
internal static IEnumerable<V1Pod> CreatePods(int cnt)
|
||||
{
|
||||
var pods = new List<V1Pod>();
|
||||
for (var i = 0; i < cnt; i++)
|
||||
{
|
||||
pods.Add(new V1Pod()
|
||||
{
|
||||
ApiVersion = "Pod/V1",
|
||||
Kind = "Pod",
|
||||
Metadata = new V1ObjectMeta()
|
||||
{
|
||||
Name = Guid.NewGuid().ToString(),
|
||||
NamespaceProperty = "the-namespace",
|
||||
ResourceVersion = "1",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return pods;
|
||||
}
|
||||
|
||||
internal static V1PodList CreatePostList(int cnt)
|
||||
{
|
||||
return new V1PodList()
|
||||
{
|
||||
ApiVersion = "Pod/V1",
|
||||
Kind = "Pod",
|
||||
Metadata = new V1ListMeta()
|
||||
{
|
||||
ResourceVersion = "1",
|
||||
},
|
||||
Items = CreatePods(cnt).ToList(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
171
tests/KubernetesClient.Tests/Util/MockKubeApiServerOptions.cs
Normal file
171
tests/KubernetesClient.Tests/Util/MockKubeApiServerOptions.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using k8s.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Nito.AsyncEx;
|
||||
|
||||
namespace k8s.Tests.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// Flags to configure how the server will respond to requests
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum MockKubeServerFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// No flag
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Include a response with malformed json
|
||||
/// </summary>
|
||||
BadJson = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Include a pod added response
|
||||
/// </summary>
|
||||
AddedPod = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Include a pod delete response
|
||||
/// </summary>
|
||||
DeletedPod = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Include a pod modified response
|
||||
/// </summary>
|
||||
ModifiedPod = 8,
|
||||
|
||||
/// <summary>
|
||||
/// Include a pod error response
|
||||
/// </summary>
|
||||
ErrorPod = 16,
|
||||
|
||||
/// <summary>
|
||||
/// Include a response of pod list
|
||||
/// </summary>
|
||||
ListPods = 32,
|
||||
|
||||
/// <summary>
|
||||
/// Include a reponse of get pod
|
||||
/// </summary>
|
||||
GetPod = 64,
|
||||
|
||||
/// <summary>
|
||||
/// Throw a 500 Http status code on any request
|
||||
/// </summary>
|
||||
Throw500 = 128,
|
||||
}
|
||||
|
||||
internal class MockKubeApiServerOptions
|
||||
{
|
||||
// paste from minikube /api/v1/namespaces/default/pods
|
||||
public const string MockPodResponse =
|
||||
"{\r\n \"kind\": \"PodList\",\r\n \"apiVersion\": \"v1\",\r\n \"metadata\": {\r\n \"selfLink\": \"/api/v1/namespaces/default/pods\",\r\n \"resourceVersion\": \"1762810\"\r\n },\r\n \"items\": [\r\n {\r\n \"metadata\": {\r\n \"name\": \"nginx-1493591563-xb2v4\",\r\n \"generateName\": \"nginx-1493591563-\",\r\n \"namespace\": \"default\",\r\n \"selfLink\": \"/api/v1/namespaces/default/pods/nginx-1493591563-xb2v4\",\r\n \"uid\": \"ac1abb94-9c58-11e7-aaf5-00155d744505\",\r\n \"resourceVersion\": \"1737928\",\r\n \"creationTimestamp\": \"2017-09-18T10:03:51Z\",\r\n \"labels\": {\r\n \"app\": \"nginx\",\r\n \"pod-template-hash\": \"1493591563\"\r\n },\r\n \"annotations\": {\r\n \"kubernetes.io/created-by\": \"{\\\"kind\\\":\\\"SerializedReference\\\",\\\"apiVersion\\\":\\\"v1\\\",\\\"reference\\\":{\\\"kind\\\":\\\"ReplicaSet\\\",\\\"namespace\\\":\\\"default\\\",\\\"name\\\":\\\"nginx-1493591563\\\",\\\"uid\\\":\\\"ac013b63-9c58-11e7-aaf5-00155d744505\\\",\\\"apiVersion\\\":\\\"extensions\\\",\\\"resourceVersion\\\":\\\"5306\\\"}}\\n\"\r\n },\r\n \"ownerReferences\": [\r\n {\r\n \"apiVersion\": \"extensions/v1beta1\",\r\n \"kind\": \"ReplicaSet\",\r\n \"name\": \"nginx-1493591563\",\r\n \"uid\": \"ac013b63-9c58-11e7-aaf5-00155d744505\",\r\n \"controller\": true,\r\n \"blockOwnerDeletion\": true\r\n }\r\n ]\r\n },\r\n \"spec\": {\r\n \"volumes\": [\r\n {\r\n \"name\": \"default-token-3zzcj\",\r\n \"secret\": {\r\n \"secretName\": \"default-token-3zzcj\",\r\n \"defaultMode\": 420\r\n }\r\n }\r\n ],\r\n \"containers\": [\r\n {\r\n \"name\": \"nginx\",\r\n \"image\": \"nginx\",\r\n \"resources\": {},\r\n \"volumeMounts\": [\r\n {\r\n \"name\": \"default-token-3zzcj\",\r\n \"readOnly\": true,\r\n \"mountPath\": \"/var/run/secrets/kubernetes.io/serviceaccount\"\r\n }\r\n ],\r\n \"terminationMessagePath\": \"/dev/termination-log\",\r\n \"terminationMessagePolicy\": \"File\",\r\n \"imagePullPolicy\": \"Always\"\r\n }\r\n ],\r\n \"restartPolicy\": \"Always\",\r\n \"terminationGracePeriodSeconds\": 30,\r\n \"dnsPolicy\": \"ClusterFirst\",\r\n \"serviceAccountName\": \"default\",\r\n \"serviceAccount\": \"default\",\r\n \"nodeName\": \"ubuntu\",\r\n \"securityContext\": {},\r\n \"schedulerName\": \"default-scheduler\"\r\n },\r\n \"status\": {\r\n \"phase\": \"Running\",\r\n \"conditions\": [\r\n {\r\n \"type\": \"Initialized\",\r\n \"status\": \"True\",\r\n \"lastProbeTime\": null,\r\n \"lastTransitionTime\": \"2017-09-18T10:03:51Z\"\r\n },\r\n {\r\n \"type\": \"Ready\",\r\n \"status\": \"True\",\r\n \"lastProbeTime\": null,\r\n \"lastTransitionTime\": \"2017-10-12T07:09:21Z\"\r\n },\r\n {\r\n \"type\": \"PodScheduled\",\r\n \"status\": \"True\",\r\n \"lastProbeTime\": null,\r\n \"lastTransitionTime\": \"2017-09-18T10:03:51Z\"\r\n }\r\n ],\r\n \"hostIP\": \"192.168.188.42\",\r\n \"podIP\": \"172.17.0.5\",\r\n \"startTime\": \"2017-09-18T10:03:51Z\",\r\n \"containerStatuses\": [\r\n {\r\n \"name\": \"nginx\",\r\n \"state\": {\r\n \"running\": {\r\n \"startedAt\": \"2017-10-12T07:09:20Z\"\r\n }\r\n },\r\n \"lastState\": {\r\n \"terminated\": {\r\n \"exitCode\": 0,\r\n \"reason\": \"Completed\",\r\n \"startedAt\": \"2017-10-10T21:35:51Z\",\r\n \"finishedAt\": \"2017-10-12T07:07:37Z\",\r\n \"containerID\": \"docker://94df3f3965807421ad6dc76618e00b76cb15d024919c4946f3eb46a92659c62a\"\r\n }\r\n },\r\n \"ready\": true,\r\n \"restartCount\": 7,\r\n \"image\": \"nginx:latest\",\r\n \"imageID\": \"docker-pullable://nginx@sha256:004ac1d5e791e705f12a17c80d7bb1e8f7f01aa7dca7deee6e65a03465392072\",\r\n \"containerID\": \"docker://fa11bdd48c9b7d3a6c4c3f9b6d7319743c3455ab8d00c57d59c083b319b88194\"\r\n }\r\n ],\r\n \"qosClass\": \"BestEffort\"\r\n }\r\n }\r\n ]\r\n}";
|
||||
|
||||
public AsyncManualResetEvent ServerShutdown { get; private set; }
|
||||
|
||||
private readonly MockKubeServerFlags _serverFlags;
|
||||
private readonly string _mockAddedEventStreamLine = BuildWatchEventStreamLine(WatchEventType.Added);
|
||||
private readonly string _mockDeletedStreamLine = BuildWatchEventStreamLine(WatchEventType.Deleted);
|
||||
private readonly string _mockModifiedStreamLine = BuildWatchEventStreamLine(WatchEventType.Modified);
|
||||
private readonly string _mockErrorStreamLine = BuildWatchEventStreamLine(WatchEventType.Error);
|
||||
private const string MockBadStreamLine = "bad json";
|
||||
|
||||
public MockKubeApiServerOptions(MockKubeServerFlags? serverFlags)
|
||||
{
|
||||
_serverFlags = serverFlags ?? MockKubeServerFlags.None;
|
||||
}
|
||||
|
||||
private static string BuildWatchEventStreamLine(WatchEventType eventType)
|
||||
{
|
||||
var corev1PodList = JsonConvert.DeserializeObject<V1PodList>(MockPodResponse);
|
||||
return JsonConvert.SerializeObject(
|
||||
new Watcher<V1Pod>.WatchEvent { Type = eventType, Object = corev1PodList.Items.First() },
|
||||
new StringEnumConverter());
|
||||
}
|
||||
|
||||
private async Task WriteStreamLine(HttpContext httpContext, string reponseLine)
|
||||
{
|
||||
const string crlf = "\r\n";
|
||||
await httpContext.Response.WriteAsync(reponseLine.Replace(crlf, "")).ConfigureAwait(false);
|
||||
await httpContext.Response.WriteAsync(crlf).ConfigureAwait(false);
|
||||
await httpContext.Response.Body.FlushAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<bool> ShouldNext(HttpContext httpContext)
|
||||
{
|
||||
var isWatch = (httpContext.Request.Query.ContainsKey("watch") && httpContext.Request.Query["watch"] == "true");
|
||||
var returnStatusCode = (_serverFlags.HasFlag(MockKubeServerFlags.Throw500) ? HttpStatusCode.InternalServerError : HttpStatusCode.OK);
|
||||
|
||||
httpContext.Response.StatusCode = (int)returnStatusCode;
|
||||
httpContext.Response.ContentLength = null;
|
||||
|
||||
if (isWatch)
|
||||
{
|
||||
ServerShutdown = new AsyncManualResetEvent();
|
||||
|
||||
foreach (Enum flag in Enum.GetValues(_serverFlags.GetType()))
|
||||
{
|
||||
if (!_serverFlags.HasFlag(flag))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (flag)
|
||||
{
|
||||
case MockKubeServerFlags.AddedPod:
|
||||
await WriteStreamLine(httpContext, _mockAddedEventStreamLine).ConfigureAwait(false);
|
||||
break;
|
||||
case MockKubeServerFlags.ErrorPod:
|
||||
await WriteStreamLine(httpContext, _mockErrorStreamLine).ConfigureAwait(false);
|
||||
break;
|
||||
case MockKubeServerFlags.DeletedPod:
|
||||
await WriteStreamLine(httpContext, _mockDeletedStreamLine).ConfigureAwait(false);
|
||||
break;
|
||||
case MockKubeServerFlags.ModifiedPod:
|
||||
await WriteStreamLine(httpContext, _mockModifiedStreamLine).ConfigureAwait(false);
|
||||
break;
|
||||
case MockKubeServerFlags.BadJson:
|
||||
await WriteStreamLine(httpContext, MockBadStreamLine).ConfigureAwait(false);
|
||||
break;
|
||||
case MockKubeServerFlags.Throw500:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// keep server connection open
|
||||
await ServerShutdown.WaitAsync().ConfigureAwait(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (Enum flag in Enum.GetValues(_serverFlags.GetType()))
|
||||
{
|
||||
if (!_serverFlags.HasFlag(flag))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (flag)
|
||||
{
|
||||
case MockKubeServerFlags.ListPods:
|
||||
await WriteStreamLine(httpContext, MockPodResponse).ConfigureAwait(false);
|
||||
break;
|
||||
case MockKubeServerFlags.GetPod:
|
||||
var corev1PodList = JsonConvert.DeserializeObject<V1PodList>(MockPodResponse);
|
||||
await WriteStreamLine(httpContext, JsonConvert.SerializeObject(corev1PodList.Items.First())).ConfigureAwait(false);
|
||||
break;
|
||||
case MockKubeServerFlags.Throw500:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user