remove unused util code (#1042)
This commit is contained in:
@@ -1,42 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<LangVersion>9.0</LangVersion>
|
||||
<Authors>The Kubernetes Project Authors</Authors>
|
||||
<Copyright>2017 The Kubernetes Project Authors</Copyright>
|
||||
<Description>Supprting utilities for the kubernetes open source container orchestrator client library.</Description>
|
||||
|
||||
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
|
||||
<PackageProjectUrl>https://github.com/kubernetes-client/csharp</PackageProjectUrl>
|
||||
<PackageIconUrl>https://raw.githubusercontent.com/kubernetes/kubernetes/master/logo/logo.png</PackageIconUrl>
|
||||
<PackageTags>kubernetes;docker;containers;</PackageTags>
|
||||
|
||||
<TargetFrameworks>netstandard2.1;net5.0</TargetFrameworks>
|
||||
<RootNamespace>k8s.Util</RootNamespace>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
|
||||
<!-- Publish the repository URL in the built .nupkg (in the NuSpec <Repository> element) -->
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
|
||||
<!-- Build symbol package (.snupkg) to distribute the PDB containing Source Link -->
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\KubernetesClient\KubernetesClient.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Text.Json" Version="5.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Utils" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -5,13 +5,6 @@
|
||||
<RootNamespace>k8s</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!--TODO merge util folder -->
|
||||
<Compile Remove="Util\**" />
|
||||
<EmbeddedResource Remove="Util\**" />
|
||||
<None Remove="Util\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="prometheus-net" Version="7.0.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.25.1" />
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
namespace k8s.Util.Common
|
||||
{
|
||||
public class BadNotificationException : Exception
|
||||
{
|
||||
public BadNotificationException()
|
||||
{
|
||||
}
|
||||
|
||||
public BadNotificationException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
namespace k8s.Util.Common
|
||||
{
|
||||
public class CallGeneratorParams
|
||||
{
|
||||
public bool Watch { get; }
|
||||
public string ResourceVersion { get; }
|
||||
public int? TimeoutSeconds { get; }
|
||||
|
||||
public CallGeneratorParams(bool watch, string resourceVersion, int? timeoutSeconds)
|
||||
{
|
||||
Watch = watch;
|
||||
ResourceVersion = resourceVersion;
|
||||
TimeoutSeconds = timeoutSeconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
namespace k8s.Util.Common
|
||||
{
|
||||
internal static class CollectionsExtensions
|
||||
{
|
||||
public static void AddRange<T>(this HashSet<T> hashSet, ICollection<T> items)
|
||||
{
|
||||
if (items == null || hashSet == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
hashSet.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
internal static TValue ComputeIfAbsent<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, Func<TKey, TValue> mappingFunction)
|
||||
{
|
||||
if (dictionary is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dictionary));
|
||||
}
|
||||
|
||||
if (dictionary.TryGetValue(key, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
if (mappingFunction == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(mappingFunction));
|
||||
}
|
||||
|
||||
var newKey = mappingFunction(key);
|
||||
dictionary[key] = newKey;
|
||||
return newKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
namespace k8s.Util.Common
|
||||
{
|
||||
public static class Config
|
||||
{
|
||||
public static string ServiceAccountCaPath => KubernetesClientConfiguration.ServiceAccountPath + "/ca.crt";
|
||||
public static string ServiceAccountTokenPath => KubernetesClientConfiguration.ServiceAccountPath + "/token";
|
||||
public static string ServiceAccountNamespacePath => KubernetesClientConfiguration.ServiceAccountPath + "/namespace";
|
||||
public static string EnvKubeconfig => "KUBECONFIG";
|
||||
public static string EnvServiceHost => "KUBERNETES_SERVICE_HOST";
|
||||
public static string EnvServicePort => "KUBERNETES_SERVICE_PORT";
|
||||
|
||||
// The last resort host to try
|
||||
public static string DefaultFallbackHost => "http://localhost:8080";
|
||||
}
|
||||
}
|
||||
@@ -1,647 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using k8s.Models;
|
||||
using k8s.Util.Common.Generic.Options;
|
||||
using k8s.Autorest;
|
||||
|
||||
namespace k8s.Util.Common.Generic
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// The Generic kubernetes api provides a unified client interface for not only the non-core-group
|
||||
/// built-in resources from kubernetes but also the custom-resources models meet the following
|
||||
/// requirements:
|
||||
///
|
||||
/// 1. there's a `V1ObjectMeta` field in the model along with its getter/setter. 2. there's a
|
||||
/// `V1ListMeta` field in the list model along with its getter/setter. - supports Json
|
||||
/// serialization/deserialization. 3. the generic kubernetes api covers all the basic operations over
|
||||
/// the custom resources including {get, list, watch, create, update, patch, delete}.
|
||||
///
|
||||
/// - For kubernetes-defined failures, the server will return a {@link V1Status} with 4xx/5xx
|
||||
/// code. The status object will be nested in {@link KubernetesApiResponse#getStatus()} - For the
|
||||
/// other unknown reason (including network, JVM..), throws an unchecked exception.
|
||||
/// </summary>
|
||||
public class GenericKubernetesApi
|
||||
{
|
||||
private readonly string _apiGroup;
|
||||
private readonly string _apiVersion;
|
||||
private readonly string _resourcePlural;
|
||||
private readonly IKubernetes _client;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GenericKubernetesApi"/> class.
|
||||
/// </summary>
|
||||
/// <param name="apiGroup"> the api group"></param>
|
||||
/// <param name="apiVersion"> the api version"></param>
|
||||
/// <param name="resourcePlural"> the resource plural, e.g. "jobs""></param>
|
||||
/// <param name="apiClient"> optional client"></param>
|
||||
public GenericKubernetesApi(string apiGroup = default, string apiVersion = default, string resourcePlural = default, IKubernetes apiClient = default)
|
||||
{
|
||||
_apiGroup = apiGroup ?? throw new ArgumentNullException(nameof(apiGroup));
|
||||
_apiVersion = apiVersion ?? throw new ArgumentNullException(nameof(apiVersion));
|
||||
_resourcePlural = resourcePlural ?? throw new ArgumentNullException(nameof(resourcePlural));
|
||||
_client = apiClient ?? new Kubernetes(KubernetesClientConfiguration.BuildDefaultConfig());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get kubernetes object.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <param name="name">the object name </param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <returns>The object</returns>
|
||||
public Task<T> GetAsync<T>(string name, CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
return GetAsync<T>(name, new GetOptions(), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get kubernetes object under the namespaceProperty.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <param name="namespaceProperty"> the namespaceProperty</param>
|
||||
/// <param name="name"> the name</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <returns>the kubernetes object</returns>
|
||||
public Task<T> GetAsync<T>(string namespaceProperty, string name, CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
return GetAsync<T>(namespaceProperty, name, new GetOptions(), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List kubernetes object cluster-scoped.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <returns>the kubernetes object</returns>
|
||||
public Task<T> ListAsync<T>(CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ListMeta>
|
||||
{
|
||||
return ListAsync<T>(new ListOptions(), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List kubernetes object under the namespaceProperty.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <param name="namespaceProperty"> the namespace</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <returns>the kubernetes object</returns>
|
||||
public Task<T> ListAsync<T>(string namespaceProperty, CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ListMeta>
|
||||
{
|
||||
return ListAsync<T>(namespaceProperty, new ListOptions(), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create kubernetes object, if the namespaceProperty in the object is present, it will send a
|
||||
/// namespaceProperty-scoped requests, vice versa.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <param name="obj"> the object</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <returns>the kubernetes object</returns>
|
||||
public Task<T> CreateAsync<T>(T obj, CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
return CreateAsync(obj, new CreateOptions(), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create kubernetes object, if the namespaceProperty in the object is present, it will send a
|
||||
/// namespaceProperty-scoped requests, vice versa.
|
||||
/// </summary>
|
||||
/// <param name="obj"> the object</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <returns>the kubernetes object</returns>
|
||||
public Task<T> UpdateAsync<T>(T obj, CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
return UpdateAsync(obj, new UpdateOptions(), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Patch kubernetes object.
|
||||
/// </summary>
|
||||
/// <param name="name"> the name</param>
|
||||
/// <param name="patch"> the string patch content</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <returns>the kubernetes object</returns>
|
||||
public Task<T> PatchAsync<T>(string name, object patch, CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
return PatchAsync<T>(name, patch, new PatchOptions(), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Patch kubernetes object under the namespaceProperty.
|
||||
/// </summary>
|
||||
/// <param name="namespaceProperty"> the namespaceProperty</param>
|
||||
/// <param name="name"> the name</param>
|
||||
/// <param name="patch"> the string patch content</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <returns>the kubernetes object</returns>
|
||||
public Task<T> PatchAsync<T>(string namespaceProperty, string name, object patch, CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
return PatchAsync<T>(namespaceProperty, name, patch, new PatchOptions(), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete kubernetes object.
|
||||
/// </summary>
|
||||
/// <param name="name"> the name</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <returns>the kubernetes object</returns>
|
||||
public Task<T> DeleteAsync<T>(string name, CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
return DeleteAsync<T>(name, new V1DeleteOptions(), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete kubernetes object under the namespaceProperty.
|
||||
/// </summary>
|
||||
/// <param name="namespaceProperty"> the namespaceProperty</param>
|
||||
/// <param name="name"> the name</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <returns>the kubernetes object</returns>
|
||||
public Task<T> DeleteAsync<T>(string namespaceProperty, string name, CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
return DeleteAsync<T>(namespaceProperty, name, new V1DeleteOptions(), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a cluster-scoped Watch on the resource.
|
||||
/// </summary>
|
||||
/// <param name="onEvent">action on event</param>
|
||||
/// <param name="onError">action on error</param>
|
||||
/// <param name="onClosed">action on closed</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <returns>the watchable</returns>
|
||||
public Watcher<T> Watch<T>(Action<WatchEventType, T> onEvent, Action<Exception> onError = default, Action onClosed = default, CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
return Watch(new ListOptions(), onEvent, onError, onClosed, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a namespaceProperty-scoped Watch on the resource.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <param name="namespaceProperty"> the namespaceProperty</param>
|
||||
/// <param name="onEvent">action on event</param>
|
||||
/// <param name="onError">action on error</param>
|
||||
/// <param name="onClosed">action on closed</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <returns>the watchable</returns>
|
||||
public Watcher<T> Watch<T>(string namespaceProperty, Action<WatchEventType, T> onEvent, Action<Exception> onError = default, Action onClosed = default,
|
||||
CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
return Watch(namespaceProperty, new ListOptions(), onEvent, onError, onClosed, cancellationToken);
|
||||
}
|
||||
|
||||
// TODO(yue9944882): watch one resource?
|
||||
|
||||
/// <summary>
|
||||
/// Get kubernetes object.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <param name="name"> the name</param>
|
||||
/// <param name="getOptions">the get options</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <returns>the kubernetes object</returns>
|
||||
public async Task<T> GetAsync<T>(string name, GetOptions getOptions, CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
var resp = await _client.CustomObjects.GetClusterCustomObjectWithHttpMessagesAsync(group: _apiGroup, plural: _resourcePlural, version: _apiVersion, name: name, cancellationToken: cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
return KubernetesJson.Deserialize<T>(resp.Body.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get kubernetes object.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <param name="namespaceProperty"> the namespaceProperty</param>
|
||||
/// <param name="name"> the name</param>
|
||||
/// <param name="getOptions">the get options</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <returns>the kubernetes object</returns>
|
||||
public async Task<T> GetAsync<T>(string namespaceProperty, string name, GetOptions getOptions, CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(namespaceProperty))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(namespaceProperty));
|
||||
}
|
||||
|
||||
var resp = await _client.CustomObjects.GetNamespacedCustomObjectWithHttpMessagesAsync(group: _apiGroup, plural: _resourcePlural, version: _apiVersion, name: name, namespaceParameter: namespaceProperty,
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
return KubernetesJson.Deserialize<T>(resp.Body.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List kubernetes object.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <param name="listOptions">the list options</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <returns>the kubernetes object</returns>
|
||||
public async Task<T> ListAsync<T>(ListOptions listOptions, CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ListMeta>
|
||||
{
|
||||
if (listOptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(listOptions));
|
||||
}
|
||||
|
||||
var resp = await _client.CustomObjects.ListClusterCustomObjectWithHttpMessagesAsync(group: _apiGroup, plural: _resourcePlural, version: _apiVersion, resourceVersion: listOptions.ResourceVersion,
|
||||
continueParameter: listOptions.Continue, fieldSelector: listOptions.FieldSelector, labelSelector: listOptions.LabelSelector, limit: listOptions.Limit,
|
||||
timeoutSeconds: listOptions.TimeoutSeconds, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
return KubernetesJson.Deserialize<T>(resp.Body.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List kubernetes object.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <param name="namespaceProperty"> the namespaceProperty</param>
|
||||
/// <param name="listOptions">the list options</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <returns>the kubernetes object</returns>
|
||||
public async Task<T> ListAsync<T>(string namespaceProperty, ListOptions listOptions, CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ListMeta>
|
||||
{
|
||||
if (listOptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(listOptions));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(namespaceProperty))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(namespaceProperty));
|
||||
}
|
||||
|
||||
var resp = await _client.CustomObjects.ListNamespacedCustomObjectWithHttpMessagesAsync(group: _apiGroup, plural: _resourcePlural, version: _apiVersion, resourceVersion: listOptions.ResourceVersion,
|
||||
continueParameter: listOptions.Continue, fieldSelector: listOptions.FieldSelector, labelSelector: listOptions.LabelSelector, limit: listOptions.Limit,
|
||||
timeoutSeconds: listOptions.TimeoutSeconds, namespaceParameter: namespaceProperty, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return KubernetesJson.Deserialize<T>(resp.Body.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create kubernetes object.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <param name="obj">the object</param>
|
||||
/// <param name="createOptions">the create options</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <returns>the kubernetes object</returns>
|
||||
public async Task<T> CreateAsync<T>(T obj, CreateOptions createOptions, CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
if (createOptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(createOptions));
|
||||
}
|
||||
|
||||
V1ObjectMeta objectMeta = obj.Metadata;
|
||||
|
||||
var isNamespaced = !string.IsNullOrEmpty(objectMeta.NamespaceProperty);
|
||||
if (isNamespaced)
|
||||
{
|
||||
return await CreateAsync(objectMeta.NamespaceProperty, obj, createOptions, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var resp = await _client.CustomObjects.CreateClusterCustomObjectWithHttpMessagesAsync(body: obj, group: _apiGroup, plural: _resourcePlural, version: _apiVersion, dryRun: createOptions.DryRun,
|
||||
fieldManager: createOptions.FieldManager, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return KubernetesJson.Deserialize<T>(resp.Body.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create namespaced kubernetes object.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <param name="namespaceProperty">the namespace</param>
|
||||
/// <param name="obj">the object</param>
|
||||
/// <param name="createOptions">the create options</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <returns>the kubernetes object</returns>
|
||||
public async Task<T> CreateAsync<T>(string namespaceProperty, T obj, CreateOptions createOptions, CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
if (createOptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(createOptions));
|
||||
}
|
||||
|
||||
var resp = await _client.CustomObjects.CreateNamespacedCustomObjectWithHttpMessagesAsync(body: obj, group: _apiGroup, plural: _resourcePlural, version: _apiVersion,
|
||||
namespaceParameter: namespaceProperty, dryRun: createOptions.DryRun, fieldManager: createOptions.FieldManager, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return KubernetesJson.Deserialize<T>(resp.Body.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update kubernetes object.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <param name="obj">the object</param>
|
||||
/// <param name="updateOptions">the update options</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <returns>the kubernetes object</returns>
|
||||
public async Task<T> UpdateAsync<T>(T obj, UpdateOptions updateOptions, CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
if (updateOptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(updateOptions));
|
||||
}
|
||||
|
||||
V1ObjectMeta objectMeta = obj.Metadata;
|
||||
|
||||
var isNamespaced = !string.IsNullOrEmpty(objectMeta.NamespaceProperty);
|
||||
HttpOperationResponse<object> resp;
|
||||
if (isNamespaced)
|
||||
{
|
||||
resp = await _client.CustomObjects.ReplaceNamespacedCustomObjectWithHttpMessagesAsync(body: obj, name: objectMeta.Name, group: _apiGroup, plural: _resourcePlural, version: _apiVersion,
|
||||
namespaceParameter: objectMeta.NamespaceProperty, dryRun: updateOptions.DryRun, fieldManager: updateOptions.FieldManager, cancellationToken: cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
resp = await _client.CustomObjects.ReplaceClusterCustomObjectWithHttpMessagesAsync(body: obj, name: objectMeta.Name, group: _apiGroup ?? obj.ApiGroup(), plural: _resourcePlural,
|
||||
version: _apiVersion, dryRun: updateOptions.DryRun, fieldManager: updateOptions.FieldManager, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return KubernetesJson.Deserialize<T>(resp.Body.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create kubernetes object, if the namespaceProperty in the object is present, it will send a
|
||||
/// namespaceProperty-scoped requests, vice versa.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <param name="obj"> the object</param>
|
||||
/// <param name="status"> function to extract the status from the object</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <returns>the kubernetes object</returns>
|
||||
public Task<T> UpdateStatusAsync<T>(T obj, Func<T, object> status, CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
return UpdateStatusAsync(obj, status, new UpdateOptions(), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update status of kubernetes object.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <param name="obj"> the object</param>
|
||||
/// <param name="status"> function to extract the status from the object</param>
|
||||
/// <param name="updateOptions">the update options</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <returns>the kubernetes object</returns>
|
||||
public async Task<T> UpdateStatusAsync<T>(T obj, Func<T, object> status, UpdateOptions updateOptions, CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
if (updateOptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(updateOptions));
|
||||
}
|
||||
|
||||
V1ObjectMeta objectMeta = obj.Metadata;
|
||||
HttpOperationResponse<object> resp;
|
||||
var isNamespaced = !string.IsNullOrEmpty(objectMeta.NamespaceProperty);
|
||||
if (isNamespaced)
|
||||
{
|
||||
resp = await _client.CustomObjects.PatchNamespacedCustomObjectStatusWithHttpMessagesAsync(body: obj, group: _apiGroup, version: _apiVersion, namespaceParameter: objectMeta.NamespaceProperty,
|
||||
plural: _resourcePlural, name: objectMeta.Name, dryRun: updateOptions.DryRun, fieldManager: updateOptions.FieldManager, force: updateOptions.Force,
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
resp = await _client.CustomObjects.PatchClusterCustomObjectStatusWithHttpMessagesAsync(body: obj, group: _apiGroup, version: _apiVersion, plural: _resourcePlural, name: objectMeta.Name,
|
||||
dryRun: updateOptions.DryRun, fieldManager: updateOptions.FieldManager, force: updateOptions.Force, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return KubernetesJson.Deserialize<T>(resp.Body.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Patch kubernetes object.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <param name="name"> the name</param>
|
||||
/// <param name="obj"> the object</param>
|
||||
/// <param name="patchOptions">the patch options</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <returns>the kubernetes object</returns>
|
||||
public async Task<T> PatchAsync<T>(string name, object obj, PatchOptions patchOptions, CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
if (patchOptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(patchOptions));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
var resp = await _client.CustomObjects.PatchClusterCustomObjectWithHttpMessagesAsync(body: obj, group: _apiGroup, version: _apiVersion, plural: _resourcePlural, name: name, dryRun: patchOptions.DryRun,
|
||||
fieldManager: patchOptions.FieldManager, force: patchOptions.Force, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
return KubernetesJson.Deserialize<T>(resp.Body.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Patch kubernetes object.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <param name="namespaceProperty"> the namespaceProperty</param>
|
||||
/// <param name="name"> the name</param>
|
||||
/// <param name="obj"> the object</param>
|
||||
/// <param name="patchOptions">the patch options</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <returns>the kubernetes object</returns>
|
||||
public async Task<T> PatchAsync<T>(string namespaceProperty, string name, object obj, PatchOptions patchOptions, CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
if (string.IsNullOrEmpty(namespaceProperty))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(namespaceProperty));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (obj == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
if (patchOptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(patchOptions));
|
||||
}
|
||||
|
||||
var resp = await _client.CustomObjects.PatchNamespacedCustomObjectWithHttpMessagesAsync(body: obj, group: _apiGroup, version: _apiVersion, namespaceParameter: namespaceProperty, plural: _resourcePlural,
|
||||
name: name, dryRun: patchOptions.DryRun, fieldManager: patchOptions.FieldManager, force: patchOptions.Force, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
return KubernetesJson.Deserialize<T>(resp.Body.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete kubernetes object.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <param name="name"> the name</param>
|
||||
/// <param name="deleteOptions">the delete options</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <returns>the kubernetes object</returns>
|
||||
public async Task<T> DeleteAsync<T>(string name, V1DeleteOptions deleteOptions, CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
var resp = await _client.CustomObjects.DeleteClusterCustomObjectWithHttpMessagesAsync(
|
||||
group: _apiGroup, version: _apiVersion, plural: _resourcePlural, name: name, body: deleteOptions, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
return KubernetesJson.Deserialize<T>(resp.Body.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete kubernetes object.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <param name="namespaceProperty"> the namespaceProperty</param>
|
||||
/// <param name="name"> the name</param>
|
||||
/// <param name="deleteOptions">the delete options</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <returns>the kubernetes object</returns>
|
||||
public async Task<T> DeleteAsync<T>(string namespaceProperty, string name, V1DeleteOptions deleteOptions, CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
if (string.IsNullOrEmpty(namespaceProperty))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(namespaceProperty));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
var resp = await _client.CustomObjects.DeleteNamespacedCustomObjectWithHttpMessagesAsync(group: _apiGroup, version: _apiVersion, namespaceParameter: namespaceProperty, plural: _resourcePlural,
|
||||
name: name, body: deleteOptions, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
return KubernetesJson.Deserialize<T>(resp.Body.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Watch watchable.
|
||||
/// </summary>
|
||||
/// <param name="listOptions">the list options</param>
|
||||
/// <param name="onEvent">action on event</param>
|
||||
/// <param name="onError">action on error</param>
|
||||
/// <param name="onClosed">action on closed</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <returns>the watchable</returns>
|
||||
public Watcher<T> Watch<T>(ListOptions listOptions, Action<WatchEventType, T> onEvent, Action<Exception> onError = default, Action onClosed = default,
|
||||
CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
if (listOptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(listOptions));
|
||||
}
|
||||
|
||||
var resp = _client.CustomObjects.ListClusterCustomObjectWithHttpMessagesAsync(group: _apiGroup, version: _apiVersion, plural: _resourcePlural, continueParameter: listOptions.Continue,
|
||||
fieldSelector: listOptions.FieldSelector, labelSelector: listOptions.LabelSelector, limit: listOptions.Limit, resourceVersion: listOptions.ResourceVersion,
|
||||
timeoutSeconds: listOptions.TimeoutSeconds, watch: true, cancellationToken: cancellationToken);
|
||||
|
||||
return resp.Watch(onEvent, onError, onClosed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Watch watchable.
|
||||
/// </summary>
|
||||
/// <param name="namespaceProperty"> the namespaceProperty</param>
|
||||
/// <param name="listOptions">the list options</param>
|
||||
/// <param name="onEvent">action on event</param>
|
||||
/// <param name="onError">action on error</param>
|
||||
/// <param name="onClosed">action on closed</param>
|
||||
/// <param name="cancellationToken">the token </param>
|
||||
/// <typeparam name="T">the object type</typeparam>
|
||||
/// <returns>the watchable</returns>
|
||||
public Watcher<T> Watch<T>(string namespaceProperty, ListOptions listOptions, Action<WatchEventType, T> onEvent, Action<Exception> onError = default, Action onClosed = default,
|
||||
CancellationToken cancellationToken = default)
|
||||
where T : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
if (listOptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(listOptions));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(namespaceProperty))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(namespaceProperty));
|
||||
}
|
||||
|
||||
var resp = _client.CustomObjects.ListNamespacedCustomObjectWithHttpMessagesAsync(group: _apiGroup, version: _apiVersion, namespaceParameter: namespaceProperty, plural: _resourcePlural,
|
||||
continueParameter: listOptions.Continue, fieldSelector: listOptions.FieldSelector, labelSelector: listOptions.LabelSelector, limit: listOptions.Limit,
|
||||
resourceVersion: listOptions.ResourceVersion, timeoutSeconds: listOptions.TimeoutSeconds, watch: true, cancellationToken: cancellationToken);
|
||||
|
||||
return resp.Watch(onEvent, onError, onClosed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
using System.Net;
|
||||
using k8s.Models;
|
||||
|
||||
namespace k8s.Util.Common.Generic
|
||||
{
|
||||
public class KubernetesApiResponse<TDataType>
|
||||
where TDataType : IKubernetesObject
|
||||
{
|
||||
public KubernetesApiResponse(TDataType @object)
|
||||
{
|
||||
Object = @object;
|
||||
Status = null;
|
||||
HttpStatusCode = HttpStatusCode.OK; // 200
|
||||
}
|
||||
|
||||
public KubernetesApiResponse(V1Status status, HttpStatusCode httpStatusCode)
|
||||
{
|
||||
Object = default(TDataType);
|
||||
Status = status;
|
||||
HttpStatusCode = httpStatusCode;
|
||||
}
|
||||
|
||||
public TDataType Object { get; }
|
||||
|
||||
public V1Status Status { get; }
|
||||
|
||||
public HttpStatusCode HttpStatusCode { get; }
|
||||
|
||||
public bool IsSuccess => ((int)HttpStatusCode > 199 && (int)HttpStatusCode < 300); // 400
|
||||
|
||||
/// <summary>
|
||||
/// Throws api exception kubernetes api response on failure. This is the recommended approach to
|
||||
/// deal with errors returned from server.
|
||||
/// </summary>
|
||||
/// <returns>the kubernetes api response</returns>
|
||||
/// <exception cref="HttpListenerException">the api exception</exception>
|
||||
public KubernetesApiResponse<TDataType> ThrowsApiException()
|
||||
{
|
||||
return OnFailure(new ErrorStatusHandler());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calling errorStatusHandler upon errors from server
|
||||
/// </summary>
|
||||
/// <param name="errorStatusHandler">the error status handler</param>
|
||||
/// <returns>the kubernetes api response</returns>
|
||||
public KubernetesApiResponse<TDataType> OnFailure(ErrorStatusHandler errorStatusHandler)
|
||||
{
|
||||
if (!IsSuccess && errorStatusHandler != null)
|
||||
{
|
||||
errorStatusHandler.Handle((int)HttpStatusCode, Status);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public class ErrorStatusHandler
|
||||
{
|
||||
public void Handle(int code, V1Status errorStatus)
|
||||
{
|
||||
throw new HttpListenerException(code, errorStatus?.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
namespace k8s.Util.Common.Generic.Options
|
||||
{
|
||||
public class CreateOptions
|
||||
{
|
||||
public string DryRun { get; private set; }
|
||||
|
||||
public string FieldManager { get; private set; }
|
||||
|
||||
public CreateOptions(string dryRun = default, string fieldManager = default)
|
||||
{
|
||||
DryRun = dryRun;
|
||||
FieldManager = fieldManager;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace k8s.Util.Common.Generic.Options
|
||||
{
|
||||
public class GetOptions
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
namespace k8s.Util.Common.Generic.Options
|
||||
{
|
||||
public class ListOptions
|
||||
{
|
||||
public int? TimeoutSeconds { get; private set; }
|
||||
|
||||
public int Limit { get; private set; }
|
||||
|
||||
public string FieldSelector { get; private set; }
|
||||
|
||||
public string LabelSelector { get; private set; }
|
||||
|
||||
public string ResourceVersion { get; private set; }
|
||||
|
||||
public string Continue { get; private set; }
|
||||
|
||||
public ListOptions(int? timeoutSeconds = default, int limit = default, string fieldSelector = default, string labelSelector = default, string resourceVersion = default,
|
||||
string @continue = default)
|
||||
{
|
||||
TimeoutSeconds = timeoutSeconds;
|
||||
Limit = limit;
|
||||
FieldSelector = fieldSelector;
|
||||
LabelSelector = labelSelector;
|
||||
ResourceVersion = resourceVersion;
|
||||
Continue = @continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
namespace k8s.Util.Common.Generic.Options
|
||||
{
|
||||
public class PatchOptions
|
||||
{
|
||||
public string DryRun { get; private set; }
|
||||
|
||||
public bool Force { get; private set; }
|
||||
|
||||
public string FieldManager { get; private set; }
|
||||
|
||||
public PatchOptions(string dryRun = default, bool force = false, string fieldManager = default)
|
||||
{
|
||||
DryRun = dryRun;
|
||||
Force = force;
|
||||
FieldManager = fieldManager;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
namespace k8s.Util.Common.Generic.Options
|
||||
{
|
||||
public class UpdateOptions
|
||||
{
|
||||
public string DryRun { get; private set; }
|
||||
|
||||
public bool Force { get; private set; }
|
||||
|
||||
public string FieldManager { get; private set; }
|
||||
|
||||
public UpdateOptions(string dryRun = default, bool force = false, string fieldManager = default)
|
||||
{
|
||||
DryRun = dryRun;
|
||||
Force = force;
|
||||
FieldManager = fieldManager;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace k8s.Util.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Namespaces provides a set of helpers for operating namespaces.
|
||||
/// </summary>
|
||||
public class Namespaces
|
||||
{
|
||||
public const string NamespaceAll = "";
|
||||
|
||||
public const string NamespaceDefault = "default";
|
||||
|
||||
public const string NamespaceKubeSystem = "kube-system";
|
||||
|
||||
public static string GetPodNamespace()
|
||||
{
|
||||
return File.ReadAllText(Config.ServiceAccountNamespacePath, Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,441 +0,0 @@
|
||||
using k8s.Models;
|
||||
using k8s.Util.Common;
|
||||
|
||||
namespace k8s.Util.Informer.Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// Cache is a C# port of Java's Cache which is a port of k/client-go's ThreadSafeStore. It basically saves and indexes all the entries.
|
||||
/// </summary>
|
||||
/// <typeparam name="TApiType">The type of K8s object to save</typeparam>
|
||||
public class Cache<TApiType> : IIndexer<TApiType>
|
||||
where TApiType : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
/// <summary>
|
||||
/// keyFunc defines how to map index objects into indices
|
||||
/// </summary>
|
||||
private Func<IKubernetesObject<V1ObjectMeta>, string> _keyFunc;
|
||||
|
||||
/// <summary>
|
||||
/// indexers stores index functions by their names
|
||||
/// </summary>
|
||||
/// <remarks>The indexer name(string) is a label marking the different ways it can be calculated.
|
||||
/// The default label is "namespace". The default func is to look in the object's metadata and combine the
|
||||
/// namespace and name values, as namespace/name.
|
||||
/// </remarks>
|
||||
private readonly Dictionary<string, Func<TApiType, List<string>>> _indexers = new Dictionary<string, Func<TApiType, List<string>>>();
|
||||
|
||||
/// <summary>
|
||||
/// indices stores objects' keys by their indices
|
||||
/// </summary>
|
||||
/// <remarks>Similar to 'indexers', an indice has the same label as its corresponding indexer except it's value
|
||||
/// is the result of the func.
|
||||
/// if the indexer func is to calculate the namespace and name values as namespace/name, then the indice HashSet
|
||||
/// holds those values.
|
||||
/// </remarks>
|
||||
private Dictionary<string, Dictionary<string, HashSet<string>>> _indices = new Dictionary<string, Dictionary<string, HashSet<string>>>();
|
||||
|
||||
/// <summary>
|
||||
/// items stores object instances
|
||||
/// </summary>
|
||||
/// <remarks>Indices hold the HashSet of calculated keys (namespace/name) for a given resource and items map each of
|
||||
/// those keys to actual K8s object that was originally returned.</remarks>
|
||||
private Dictionary<string, TApiType> _items = new Dictionary<string, TApiType>();
|
||||
|
||||
/// <summary>
|
||||
/// object used to track locking
|
||||
/// </summary>
|
||||
/// <remarks>methods interacting with the store need to lock to secure the thread for race conditions,
|
||||
/// learn more: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/lock-statement</remarks>
|
||||
private readonly object _lock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Cache{TApiType}"/> class. Uses an object's namespace as the key.
|
||||
/// </summary>
|
||||
public Cache()
|
||||
: this(Caches.NamespaceIndex, Caches.MetaNamespaceIndexFunc, Caches.DeletionHandlingMetaNamespaceKeyFunc)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Cache{TApiType}"/> class.
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="indexName">the index name, an unique name representing the index</param>
|
||||
/// <param name="indexFunc">the index func by which we map multiple object to an index for querying</param>
|
||||
/// <param name="keyFunc">the key func by which we map one object to an unique key for storing</param>
|
||||
public Cache(string indexName, Func<TApiType, List<string>> indexFunc, Func<IKubernetesObject<V1ObjectMeta>, string> keyFunc)
|
||||
{
|
||||
_indexers[indexName] = indexFunc;
|
||||
_keyFunc = keyFunc;
|
||||
_indices[indexName] = new Dictionary<string, HashSet<string>>();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_items?.Clear();
|
||||
_indices?.Clear();
|
||||
_indexers?.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add objects.
|
||||
/// </summary>
|
||||
/// <param name="obj">the obj</param>
|
||||
public void Add(TApiType obj)
|
||||
{
|
||||
var key = _keyFunc(obj);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
var oldObj = _items.GetValueOrDefault(key);
|
||||
_items[key] = obj;
|
||||
UpdateIndices(oldObj, obj, key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the object.
|
||||
/// </summary>
|
||||
/// <param name="obj">the obj</param>
|
||||
public void Update(TApiType obj)
|
||||
{
|
||||
var key = _keyFunc(obj);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
var oldObj = _items.GetValueOrDefault(key);
|
||||
_items[key] = obj;
|
||||
UpdateIndices(oldObj, obj, key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete the object.
|
||||
/// </summary>
|
||||
/// <param name="obj">the obj</param>
|
||||
public void Delete(TApiType obj)
|
||||
{
|
||||
var key = _keyFunc(obj);
|
||||
lock (_lock)
|
||||
{
|
||||
if (!_items.TryGetValue(key, out var value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DeleteFromIndices(value, key);
|
||||
_items.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace the content in the cache completely.
|
||||
/// </summary>
|
||||
/// <param name="list">the list</param>
|
||||
/// <param name="resourceVersion">optional, unused param from interface</param>
|
||||
/// <exception cref="ArgumentNullException">list is null</exception>
|
||||
public void Replace(IEnumerable<TApiType> list, string resourceVersion = default)
|
||||
{
|
||||
if (list is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(list));
|
||||
}
|
||||
|
||||
var newItems = new Dictionary<string, TApiType>();
|
||||
foreach (var item in list)
|
||||
{
|
||||
var key = _keyFunc(item);
|
||||
newItems[key] = item;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_items = newItems;
|
||||
|
||||
// rebuild any index
|
||||
_indices = new Dictionary<string, Dictionary<string, HashSet<string>>>();
|
||||
foreach (var (key, value) in _items)
|
||||
{
|
||||
UpdateIndices(default, value, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resync.
|
||||
/// </summary>
|
||||
public void Resync()
|
||||
{
|
||||
// Do nothing by default
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List keys.
|
||||
/// </summary>
|
||||
/// <returns>the list</returns>
|
||||
public IEnumerable<string> ListKeys()
|
||||
{
|
||||
return _items.Select(item => item.Key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get object t.
|
||||
/// </summary>
|
||||
/// <param name="obj">the obj</param>
|
||||
/// <returns>the t</returns>
|
||||
public TApiType Get(TApiType obj)
|
||||
{
|
||||
var key = _keyFunc(obj);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
// Todo: to make this lock striped or reader/writer (or use ConcurrentDictionary)
|
||||
return _items.GetValueOrDefault(key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List all objects in the cache.
|
||||
/// </summary>
|
||||
/// <returns>all items</returns>
|
||||
public IEnumerable<TApiType> List()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _items.Select(item => item.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get object t.
|
||||
/// </summary>
|
||||
/// <param name="key">the key</param>
|
||||
/// <returns>the get by key</returns>
|
||||
public TApiType GetByKey(string key)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_items.TryGetValue(key, out var value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get objects.
|
||||
/// </summary>
|
||||
/// <param name="indexName">the index name</param>
|
||||
/// <param name="obj">the obj</param>
|
||||
/// <returns>the list</returns>
|
||||
/// <exception cref="ArgumentException">indexers does not contain the provided index name</exception>
|
||||
public IEnumerable<TApiType> Index(string indexName, TApiType obj)
|
||||
{
|
||||
if (!_indexers.ContainsKey(indexName))
|
||||
{
|
||||
throw new ArgumentException($"index {indexName} doesn't exist!", nameof(indexName));
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
var indexFunc = _indexers[indexName];
|
||||
var indexKeys = indexFunc(obj);
|
||||
var index = _indices.GetValueOrDefault(indexName);
|
||||
if (index is null || index.Count == 0)
|
||||
{
|
||||
return new List<TApiType>();
|
||||
}
|
||||
|
||||
var returnKeySet = new HashSet<string>();
|
||||
foreach (var set in indexKeys.Select(indexKey => index.GetValueOrDefault(indexKey)).Where(set => set != null && set.Count != 0))
|
||||
{
|
||||
returnKeySet.AddRange(set);
|
||||
}
|
||||
|
||||
var items = new List<TApiType>(returnKeySet.Count);
|
||||
items.AddRange(returnKeySet.Select(absoluteKey => _items[absoluteKey]));
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Index keys list.
|
||||
/// </summary>
|
||||
/// <param name="indexName">the index name</param>
|
||||
/// <param name="indexKey">the index key</param>
|
||||
/// <returns>the list</returns>
|
||||
/// <exception cref="ArgumentException">indexers does not contain the provided index name</exception>
|
||||
/// <exception cref="KeyNotFoundException">indices collection does not contain the provided index name</exception>
|
||||
public IEnumerable<string> IndexKeys(string indexName, string indexKey)
|
||||
{
|
||||
if (!_indexers.ContainsKey(indexName))
|
||||
{
|
||||
throw new ArgumentException($"index {indexName} doesn't exist!", nameof(indexName));
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
var index = _indices.GetValueOrDefault(indexName);
|
||||
|
||||
if (index is null)
|
||||
{
|
||||
throw new KeyNotFoundException($"no value could be found for name '{indexName}'");
|
||||
}
|
||||
|
||||
return index[indexKey];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// By index list.
|
||||
/// </summary>
|
||||
/// <param name="indexName">the index name</param>
|
||||
/// <param name="indexKey">the index key</param>
|
||||
/// <returns>the list</returns>
|
||||
/// <exception cref="ArgumentException">indexers does not contain the provided index name</exception>
|
||||
/// <exception cref="KeyNotFoundException">indices collection does not contain the provided index name</exception>
|
||||
public IEnumerable<TApiType> ByIndex(string indexName, string indexKey)
|
||||
{
|
||||
if (!_indexers.ContainsKey(indexName))
|
||||
{
|
||||
throw new ArgumentException($"index {indexName} doesn't exist!", nameof(indexName));
|
||||
}
|
||||
|
||||
var index = _indices.GetValueOrDefault(indexName);
|
||||
|
||||
if (index is null)
|
||||
{
|
||||
throw new KeyNotFoundException($"no value could be found for name '{indexName}'");
|
||||
}
|
||||
|
||||
var set = index[indexKey];
|
||||
return set is null ? new List<TApiType>() : set.Select(key => _items[key]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the indexers registered with the cache.
|
||||
/// </summary>
|
||||
/// <returns>registered indexers</returns>
|
||||
public IDictionary<string, Func<TApiType, List<string>>> GetIndexers() => _indexers;
|
||||
|
||||
/// <summary>
|
||||
/// Add additional indexers to the cache.
|
||||
/// </summary>
|
||||
/// <param name="newIndexers">indexers to add</param>
|
||||
/// <exception cref="ArgumentNullException">newIndexers is null</exception>
|
||||
/// <exception cref="InvalidOperationException">items collection is not empty</exception>
|
||||
/// <exception cref="ArgumentException">conflict between keys in existing index and new indexers provided</exception>
|
||||
public void AddIndexers(IDictionary<string, Func<TApiType, List<string>>> newIndexers)
|
||||
{
|
||||
if (newIndexers is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(newIndexers));
|
||||
}
|
||||
|
||||
if (_items.Any())
|
||||
{
|
||||
throw new InvalidOperationException("cannot add indexers to a non-empty cache");
|
||||
}
|
||||
|
||||
var oldKeys = _indexers.Keys;
|
||||
var newKeys = newIndexers.Keys;
|
||||
var intersection = oldKeys.Intersect(newKeys);
|
||||
|
||||
if (intersection.Any())
|
||||
{
|
||||
throw new ArgumentException("indexer conflict: " + intersection);
|
||||
}
|
||||
|
||||
foreach (var (key, value) in newIndexers)
|
||||
{
|
||||
AddIndexFunc(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UpdateIndices modifies the objects location in the managed indexes, if this is an update, you
|
||||
/// must provide an oldObj.
|
||||
/// </summary>
|
||||
/// <remarks>UpdateIndices must be called from a function that already has a lock on the cache.</remarks>
|
||||
/// <param name="oldObj"> the old obj</param>
|
||||
/// <param name="newObj"> the new obj</param>
|
||||
/// <param name="key">the key</param>
|
||||
private void UpdateIndices(TApiType oldObj, TApiType newObj, string key)
|
||||
{
|
||||
// if we got an old object, we need to remove it before we can add
|
||||
// it again.
|
||||
if (oldObj != null)
|
||||
{
|
||||
DeleteFromIndices(oldObj, key);
|
||||
}
|
||||
|
||||
foreach (var (indexName, indexFunc) in _indexers)
|
||||
{
|
||||
var indexValues = indexFunc(newObj);
|
||||
if (indexValues is null || indexValues.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var index = _indices.ComputeIfAbsent(indexName, _ => new Dictionary<string, HashSet<string>>());
|
||||
|
||||
foreach (var indexValue in indexValues)
|
||||
{
|
||||
HashSet<string> indexSet = index.ComputeIfAbsent(indexValue, k => new HashSet<string>());
|
||||
indexSet.Add(key);
|
||||
|
||||
index[indexValue] = indexSet;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DeleteFromIndices removes the object from each of the managed indexes.
|
||||
/// </summary>
|
||||
/// <remarks>It is intended to be called from a function that already has a lock on the cache.</remarks>
|
||||
/// <param name="oldObj">the old obj</param>
|
||||
/// <param name="key">the key</param>
|
||||
private void DeleteFromIndices(TApiType oldObj, string key)
|
||||
{
|
||||
foreach (var (s, indexFunc) in _indexers)
|
||||
{
|
||||
var indexValues = indexFunc(oldObj);
|
||||
if (indexValues is null || indexValues.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var index = _indices.GetValueOrDefault(s);
|
||||
if (index is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var indexSet in indexValues.Select(indexValue => index[indexValue]))
|
||||
{
|
||||
indexSet?.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add index func.
|
||||
/// </summary>
|
||||
/// <param name="indexName">the index name</param>
|
||||
/// <param name="indexFunc">the index func</param>
|
||||
public void AddIndexFunc(string indexName, Func<TApiType, List<string>> indexFunc)
|
||||
{
|
||||
_indices[indexName] = new Dictionary<string, HashSet<string>>();
|
||||
_indexers[indexName] = indexFunc;
|
||||
}
|
||||
|
||||
public Func<IKubernetesObject<V1ObjectMeta>, string> KeyFunc => _keyFunc;
|
||||
|
||||
public void SetKeyFunc(Func<IKubernetesObject<V1ObjectMeta>, string> keyFunc)
|
||||
{
|
||||
_keyFunc = keyFunc;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
using k8s.Models;
|
||||
|
||||
namespace k8s.Util.Informer.Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// A set of helper utilities for constructing a cache.
|
||||
/// </summary>
|
||||
public static class Caches
|
||||
{
|
||||
/// <summary>
|
||||
/// NamespaceIndex is the default index function for caching objects
|
||||
/// </summary>
|
||||
public const string NamespaceIndex = "namespace";
|
||||
|
||||
/// <summary>
|
||||
/// deletionHandlingMetaNamespaceKeyFunc checks for DeletedFinalStateUnknown objects before calling
|
||||
/// metaNamespaceKeyFunc.
|
||||
/// </summary>
|
||||
/// <param name="obj">specific object</param>
|
||||
/// <typeparam name="TApiType">the type parameter</typeparam>
|
||||
/// <exception cref="ArgumentNullException">if obj is null</exception>
|
||||
/// <returns>the key</returns>
|
||||
public static string DeletionHandlingMetaNamespaceKeyFunc<TApiType>(TApiType obj)
|
||||
where TApiType : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
if (obj.GetType() == typeof(DeletedFinalStateUnknown<TApiType>))
|
||||
{
|
||||
var deleteObj = obj as DeletedFinalStateUnknown<TApiType>;
|
||||
return deleteObj.GetKey();
|
||||
}
|
||||
|
||||
return MetaNamespaceKeyFunc(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MetaNamespaceKeyFunc is a convenient default KeyFunc which knows how to make keys for API
|
||||
/// objects which implement V1ObjectMeta Interface. The key uses the format <namespace>/<name>
|
||||
/// unless <namespace> is empty, then it's just <name>.
|
||||
/// </summary>
|
||||
/// <param name="obj">specific object</param>
|
||||
/// <returns>the key</returns>
|
||||
/// <exception cref="ArgumentNullException">if obj is null</exception>
|
||||
/// <exception cref="InvalidOperationException">if metadata can't be found on obj</exception>
|
||||
public static string MetaNamespaceKeyFunc(IKubernetesObject<V1ObjectMeta> obj)
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(obj.Metadata.NamespaceProperty))
|
||||
{
|
||||
return obj.Metadata.NamespaceProperty + "/" + obj.Metadata.Name;
|
||||
}
|
||||
|
||||
return obj.Metadata.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MetaNamespaceIndexFunc is a default index function that indexes based on an object's namespace.
|
||||
/// </summary>
|
||||
/// <param name="obj">specific object</param>
|
||||
/// <typeparam name="TApiType">the type parameter</typeparam>
|
||||
/// <returns>the indexed value</returns>
|
||||
/// <exception cref="ArgumentNullException">if obj is null</exception>
|
||||
/// <exception cref="InvalidOperationException">if metadata can't be found on obj</exception>
|
||||
public static List<string> MetaNamespaceIndexFunc<TApiType>(TApiType obj)
|
||||
where TApiType : IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
return obj.Metadata is null ? new List<string>() : new List<string>() { obj.Metadata.NamespaceProperty };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
using k8s.Models;
|
||||
|
||||
namespace k8s.Util.Informer.Cache
|
||||
{
|
||||
// DeletedFinalStateUnknown is placed into a DeltaFIFO in the case where
|
||||
// an object was deleted but the watch deletion event was missed. In this
|
||||
// case we don't know the final "resting" state of the object, so there's
|
||||
// a chance the included `Obj` is stale.
|
||||
public class DeletedFinalStateUnknown<TApi> : IKubernetesObject<V1ObjectMeta>
|
||||
where TApi : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
private readonly string _key;
|
||||
private readonly TApi _obj;
|
||||
|
||||
public DeletedFinalStateUnknown(string key, TApi obj)
|
||||
{
|
||||
_key = key;
|
||||
_obj = obj;
|
||||
}
|
||||
|
||||
public string GetKey() => _key;
|
||||
|
||||
/// <summary>
|
||||
/// Gets get obj.
|
||||
/// </summary>
|
||||
/// <returns>the get obj</returns>
|
||||
public TApi GetObj() => _obj;
|
||||
|
||||
public V1ObjectMeta Metadata
|
||||
{
|
||||
get => _obj.Metadata;
|
||||
set => _obj.Metadata = value;
|
||||
}
|
||||
|
||||
public string ApiVersion
|
||||
{
|
||||
get => _obj.ApiVersion;
|
||||
set => _obj.ApiVersion = value;
|
||||
}
|
||||
|
||||
public string Kind
|
||||
{
|
||||
get => _obj.Kind;
|
||||
set => _obj.Kind = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
namespace k8s.Util.Informer.Cache
|
||||
{
|
||||
public enum DeltaType
|
||||
{
|
||||
/// <summary>
|
||||
/// Item added
|
||||
/// </summary>
|
||||
Added,
|
||||
|
||||
/// <summary>
|
||||
/// Item updated
|
||||
/// </summary>
|
||||
Updated,
|
||||
|
||||
/// <summary>
|
||||
/// Item deleted
|
||||
/// </summary>
|
||||
Deleted,
|
||||
|
||||
/// <summary>
|
||||
/// Item synchronized
|
||||
/// </summary>
|
||||
Sync,
|
||||
|
||||
/// <summary>
|
||||
/// Item replaced
|
||||
/// </summary>
|
||||
Replaced,
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using k8s.Models;
|
||||
|
||||
namespace k8s.Util.Informer.Cache
|
||||
{
|
||||
public interface IIndexer<TApiType> : IStore<TApiType>
|
||||
where TApiType : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieve list of objects that match on the named indexing function.
|
||||
/// </summary>
|
||||
/// <param name="indexName">specific indexing function</param>
|
||||
/// <param name="obj"> . </param>
|
||||
/// <returns>matched objects</returns>
|
||||
IEnumerable<TApiType> Index(string indexName, TApiType obj);
|
||||
|
||||
/// <summary>
|
||||
/// IndexKeys returns the set of keys that match on the named indexing function.
|
||||
/// </summary>
|
||||
/// <param name="indexName">specific indexing function</param>
|
||||
/// <param name="indexKey">specific index key</param>
|
||||
/// <returns>matched keys</returns>
|
||||
IEnumerable<string> IndexKeys(string indexName, string indexKey);
|
||||
|
||||
/// <summary>
|
||||
/// ByIndex lists object that match on the named indexing function with the exact key.
|
||||
/// </summary>
|
||||
/// <param name="indexName">specific indexing function</param>
|
||||
/// <param name="indexKey">specific index key</param>
|
||||
/// <returns>matched objects</returns>
|
||||
IEnumerable<TApiType> ByIndex(string indexName, string indexKey);
|
||||
|
||||
/// <summary>
|
||||
/// Return the indexers registered with the store.
|
||||
/// </summary>
|
||||
/// <returns>registered indexers</returns>
|
||||
IDictionary<string, Func<TApiType, List<string>>> GetIndexers();
|
||||
|
||||
/// <summary>
|
||||
/// Add additional indexers to the store.
|
||||
/// </summary>
|
||||
/// <param name="indexers">indexers to add</param>
|
||||
void AddIndexers(IDictionary<string, Func<TApiType, List<string>>> indexers);
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
using k8s.Models;
|
||||
|
||||
namespace k8s.Util.Informer.Cache
|
||||
{
|
||||
public interface IStore<TApiType>
|
||||
where TApiType : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
/// <summary>
|
||||
/// add inserts an item into the store.
|
||||
/// </summary>
|
||||
/// <param name="obj">specific obj</param>
|
||||
void Add(TApiType obj);
|
||||
|
||||
/// <summary>
|
||||
/// update sets an item in the store to its updated state.
|
||||
/// </summary>
|
||||
/// <param name="obj">specific obj</param>
|
||||
void Update(TApiType obj);
|
||||
|
||||
/// <summary>
|
||||
/// delete removes an item from the store.
|
||||
/// </summary>
|
||||
/// <param name="obj">specific obj</param>
|
||||
void Delete(TApiType obj);
|
||||
|
||||
/// <summary>
|
||||
/// Replace will delete the contents of 'c', using instead the given list.
|
||||
/// </summary>
|
||||
/// <param name="list">list of objects</param>
|
||||
/// <param name="resourceVersion">specific resource version</param>
|
||||
void Replace(IEnumerable<TApiType> list, string resourceVersion);
|
||||
|
||||
/// <summary>
|
||||
/// resync will send a resync event for each item.
|
||||
/// </summary>
|
||||
void Resync();
|
||||
|
||||
/// <summary>
|
||||
/// listKeys returns a list of all the keys of the object currently in the store.
|
||||
/// </summary>
|
||||
/// <returns>list of all keys</returns>
|
||||
IEnumerable<string> ListKeys();
|
||||
|
||||
/// <summary>
|
||||
/// get returns the requested item.
|
||||
/// </summary>
|
||||
/// <param name="obj">specific obj</param>
|
||||
/// <returns>the requested item if exist</returns>
|
||||
TApiType Get(TApiType obj);
|
||||
|
||||
/// <summary>
|
||||
/// getByKey returns the request item with specific key.
|
||||
/// </summary>
|
||||
/// <param name="key">specific key</param>
|
||||
/// <returns>the request item</returns>
|
||||
TApiType GetByKey(string key);
|
||||
|
||||
/// <summary>
|
||||
/// list returns a list of all the items.
|
||||
/// </summary>
|
||||
/// <returns>list of all the items</returns>
|
||||
IEnumerable<TApiType> List();
|
||||
|
||||
/// <summary>
|
||||
/// Empty the store
|
||||
/// </summary>
|
||||
void Clear();
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using k8s.Models;
|
||||
|
||||
namespace k8s.Util.Informer.Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// Lister interface is used to list cached items from a running informer.
|
||||
/// </summary>
|
||||
/// <typeparam name="TApiType">the type</typeparam>
|
||||
public class Lister<TApiType>
|
||||
where TApiType : class, IKubernetesObject<V1ObjectMeta>
|
||||
{
|
||||
private readonly string _namespace;
|
||||
private readonly string _indexName;
|
||||
private readonly IIndexer<TApiType> _indexer;
|
||||
|
||||
public Lister(IIndexer<TApiType> indexer, string @namespace = default, string indexName = Caches.NamespaceIndex)
|
||||
{
|
||||
_indexer = indexer;
|
||||
_namespace = @namespace;
|
||||
_indexName = indexName;
|
||||
}
|
||||
|
||||
public IEnumerable<TApiType> List()
|
||||
{
|
||||
return string.IsNullOrEmpty(_namespace) ? _indexer.List() : _indexer.ByIndex(_indexName, _namespace);
|
||||
}
|
||||
|
||||
public TApiType Get(string name)
|
||||
{
|
||||
var key = name;
|
||||
if (!string.IsNullOrEmpty(_namespace))
|
||||
{
|
||||
key = _namespace + "/" + name;
|
||||
}
|
||||
|
||||
return _indexer.GetByKey(key);
|
||||
}
|
||||
|
||||
public Lister<TApiType> Namespace(string @namespace)
|
||||
{
|
||||
return new Lister<TApiType>(_indexer, @namespace, Caches.NamespaceIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
namespace k8s.Util.Informer.Cache
|
||||
{
|
||||
public class MutablePair<TLeft, TRight>
|
||||
{
|
||||
protected bool Equals(MutablePair<TLeft, TRight> other)
|
||||
{
|
||||
if (other is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(other));
|
||||
}
|
||||
|
||||
return EqualityComparer<TLeft>.Default.Equals(Left, other.Left) && EqualityComparer<TRight>.Default.Equals(Right, other.Right);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(this, obj))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return obj.GetType() == this.GetType() && Equals((MutablePair<TLeft, TRight>)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (EqualityComparer<TLeft>.Default.GetHashCode(Left) * 397) ^ EqualityComparer<TRight>.Default.GetHashCode(Right);
|
||||
}
|
||||
}
|
||||
|
||||
public TRight Right { get; }
|
||||
|
||||
public TLeft Left { get; }
|
||||
|
||||
public MutablePair()
|
||||
{
|
||||
}
|
||||
|
||||
public MutablePair(TLeft left, TRight right)
|
||||
{
|
||||
Left = left;
|
||||
Right = right;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,13 +38,6 @@
|
||||
<None Include="assets/*" CopyToOutputDirectory="Always" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Util\**" />
|
||||
<Content Remove="Util\**" />
|
||||
<EmbeddedResource Remove="Util\**" />
|
||||
<None Remove="Util\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\KubernetesClient\KubernetesClient.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using k8s.Models;
|
||||
using k8s.Util.Common.Generic;
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,335 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using k8s.Models;
|
||||
using k8s.Util.Informer.Cache;
|
||||
using Xunit;
|
||||
|
||||
namespace k8s.Tests.Util.Informer.Cache
|
||||
{
|
||||
public class CacheTest
|
||||
{
|
||||
[Fact(DisplayName = "Create default cache success")]
|
||||
public void CreateCacheSuccess()
|
||||
{
|
||||
var cache = new Cache<V1Node>();
|
||||
cache.Should().NotBeNull();
|
||||
cache.GetIndexers().ContainsKey(Caches.NamespaceIndex).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Add cache item success")]
|
||||
public void AddCacheItemSuccess()
|
||||
{
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
cache.Add(aPod);
|
||||
|
||||
cache.Get(aPod).Equals(aPod).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Update cache item success")]
|
||||
public void UpdateCacheItemSuccess()
|
||||
{
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
cache.Add(aPod);
|
||||
aPod.Kind = "another-kind";
|
||||
cache.Update(aPod);
|
||||
|
||||
cache.Get(aPod).Kind.Equals(aPod.Kind).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Delete cache item success")]
|
||||
public void DeleteCacheItemSuccess()
|
||||
{
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
cache.Add(aPod);
|
||||
cache.Delete(aPod);
|
||||
|
||||
// Todo: check indices for removed item
|
||||
cache.Get(aPod).Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Replace cache items success")]
|
||||
public void ReplaceCacheItemsSuccess()
|
||||
{
|
||||
var pods = Helpers.CreatePods(3);
|
||||
var aPod = pods.First();
|
||||
var anotherPod = pods.Skip(1).First();
|
||||
var yetAnotherPod = pods.Skip(2).First();
|
||||
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
cache.Add(aPod);
|
||||
cache.Replace(new[] { anotherPod, yetAnotherPod });
|
||||
|
||||
// Todo: check indices for replaced items
|
||||
cache.Get(anotherPod).Should().NotBeNull();
|
||||
cache.Get(yetAnotherPod).Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "List item keys success")]
|
||||
public void ListItemKeysSuccess()
|
||||
{
|
||||
var pods = Helpers.CreatePods(3);
|
||||
var aPod = pods.First();
|
||||
var anotherPod = pods.Skip(1).First();
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
cache.Add(aPod);
|
||||
cache.Add(anotherPod);
|
||||
|
||||
var keys = cache.ListKeys();
|
||||
|
||||
keys.Should().Contain($"{aPod.Metadata.NamespaceProperty}/{aPod.Metadata.Name}");
|
||||
keys.Should().Contain($"{anotherPod.Metadata.NamespaceProperty}/{anotherPod.Metadata.Name}");
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Get item doesn't exist")]
|
||||
public void GetItemNotExist()
|
||||
{
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
var item = cache.Get(aPod);
|
||||
item.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Get item success")]
|
||||
public void GetItemSuccess()
|
||||
{
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
cache.Add(aPod);
|
||||
var item = cache.Get(aPod);
|
||||
item.Equals(aPod).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "List items success")]
|
||||
public void ListItemSuccess()
|
||||
{
|
||||
var pods = Helpers.CreatePods(3);
|
||||
var aPod = pods.First();
|
||||
var anotherPod = pods.Skip(1).First();
|
||||
var yetAnotherPod = pods.Skip(2).First();
|
||||
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
cache.Add(aPod);
|
||||
cache.Add(anotherPod);
|
||||
cache.Add(yetAnotherPod);
|
||||
|
||||
var items = cache.List();
|
||||
items.Should().HaveCount(3);
|
||||
items.Should().Contain(aPod);
|
||||
items.Should().Contain(anotherPod);
|
||||
items.Should().Contain(yetAnotherPod);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Get item by key success")]
|
||||
public void GetItemByKeySuccess()
|
||||
{
|
||||
var pod = Helpers.CreatePods(1).First();
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
cache.Add(pod);
|
||||
var item = cache.GetByKey($"{pod.Metadata.NamespaceProperty}/{pod.Metadata.Name}");
|
||||
item.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Index items no index")]
|
||||
public void IndexItemsNoIndex()
|
||||
{
|
||||
var pod = Helpers.CreatePods(1).First();
|
||||
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
cache.Add(pod);
|
||||
|
||||
Assert.Throws<ArgumentException>(() => { cache.Index("asdf", pod); });
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Index items success")]
|
||||
public void IndexItemsSuccess()
|
||||
{
|
||||
var pod = Helpers.CreatePods(1).First();
|
||||
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
cache.Add(pod);
|
||||
|
||||
var items = cache.Index("namespace", pod);
|
||||
|
||||
items.Should().Contain(pod);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Get index keys no index")]
|
||||
public void GetIndexKeysNoIndex()
|
||||
{
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
Assert.Throws<ArgumentException>(() => { cache.IndexKeys("a", "b"); });
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Get index keys no indice item")]
|
||||
public void GetIndexKeysNoIndiceItem()
|
||||
{
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
Assert.Throws<KeyNotFoundException>(() => { cache.IndexKeys("namespace", "b"); });
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Get index keys success")]
|
||||
public void GetIndexKeysSuccess()
|
||||
{
|
||||
var pod = Helpers.CreatePods(1).First();
|
||||
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
cache.Add(pod);
|
||||
var keys = cache.IndexKeys("namespace", pod.Metadata.NamespaceProperty);
|
||||
|
||||
keys.Should().NotBeNull();
|
||||
keys.Should().Contain(Caches.MetaNamespaceKeyFunc(pod));
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "List by index no index")]
|
||||
public void ListByIndexNoIndex()
|
||||
{
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
Assert.Throws<ArgumentException>(() => { cache.ByIndex("a", "b"); });
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "List by index no indice item")]
|
||||
public void ListByIndexNoIndiceItem()
|
||||
{
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
Assert.Throws<KeyNotFoundException>(() => { cache.ByIndex("namespace", "b"); });
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "List by index success")]
|
||||
public void ListByIndexSuccess()
|
||||
{
|
||||
var pod = Helpers.CreatePods(1).First();
|
||||
|
||||
var cache = new Cache<V1Pod>();
|
||||
|
||||
cache.Add(pod);
|
||||
var items = cache.ByIndex("namespace", pod.Metadata.NamespaceProperty);
|
||||
|
||||
items.Should().Contain(pod);
|
||||
}
|
||||
|
||||
/* Add Indexers */
|
||||
[Fact(DisplayName = "Add null indexers")]
|
||||
public void AddNullIndexers()
|
||||
{
|
||||
var cache = new Cache<V1Pod>();
|
||||
Assert.Throws<ArgumentNullException>(() => { cache.AddIndexers(null); });
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Add indexers with conflict")]
|
||||
public void AddIndexersConflict()
|
||||
{
|
||||
var cache = new Cache<V1Pod>();
|
||||
Dictionary<string, Func<V1Pod, List<string>>> initialIndexers = new Dictionary<string, Func<V1Pod, List<string>>>()
|
||||
{
|
||||
{ "1", pod => new List<string>() },
|
||||
{ "2", pod => new List<string>() },
|
||||
};
|
||||
Dictionary<string, Func<V1Pod, List<string>>> conflictIndexers = new Dictionary<string, Func<V1Pod, List<string>>>()
|
||||
{
|
||||
{ "1", pod => new List<string>() },
|
||||
};
|
||||
|
||||
cache.AddIndexers(initialIndexers);
|
||||
Assert.Throws<ArgumentException>(() => { cache.AddIndexers(conflictIndexers); });
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Add indexers success")]
|
||||
public void AddIndexersSuccess()
|
||||
{
|
||||
var cache = new Cache<V1Pod>();
|
||||
Dictionary<string, Func<V1Pod, List<string>>> indexers = new Dictionary<string, Func<V1Pod, List<string>>>()
|
||||
{
|
||||
{ "2", pod => new List<string>() { pod.Name() } },
|
||||
{ "3", pod => new List<string>() { pod.Name() } },
|
||||
};
|
||||
|
||||
cache.AddIndexers(indexers);
|
||||
|
||||
var savedIndexers = cache.GetIndexers();
|
||||
savedIndexers.Should().HaveCount(indexers.Count + 1); // blank cache constructor will add a default index
|
||||
savedIndexers.Should().Contain(indexers);
|
||||
|
||||
// Todo: check indicies collection for new indexname keys
|
||||
}
|
||||
|
||||
/* Add Index Function */
|
||||
[Fact(DisplayName = "Add index function success")]
|
||||
public void AddIndexFuncSuccess()
|
||||
{
|
||||
var cache = new Cache<V1Pod>();
|
||||
cache.AddIndexFunc("1", pod => new List<string>() { pod.Name() });
|
||||
|
||||
var savedIndexers = cache.GetIndexers();
|
||||
savedIndexers.Should().HaveCount(2);
|
||||
|
||||
// Todo: check indicies collection for new indexname keys
|
||||
}
|
||||
|
||||
/* Get Key Function */
|
||||
[Fact(DisplayName = "Get default key function success")]
|
||||
public void GetDefaultKeyFuncSuccess()
|
||||
{
|
||||
var pod = new V1Pod()
|
||||
{
|
||||
Metadata = new V1ObjectMeta()
|
||||
{
|
||||
Name = "a-name",
|
||||
NamespaceProperty = "the-namespace",
|
||||
},
|
||||
};
|
||||
var cache = new Cache<V1Pod>();
|
||||
var defaultReturnValue = Caches.DeletionHandlingMetaNamespaceKeyFunc(pod);
|
||||
|
||||
var funcReturnValue = cache.KeyFunc(pod);
|
||||
|
||||
Assert.True(defaultReturnValue.Equals(funcReturnValue));
|
||||
}
|
||||
|
||||
/* Set Key Function */
|
||||
[Fact(DisplayName = "Set key function success")]
|
||||
public void SetKeyFuncSuccess()
|
||||
{
|
||||
var aPod = new V1Pod()
|
||||
{
|
||||
Kind = "some-kind",
|
||||
Metadata = new V1ObjectMeta()
|
||||
{
|
||||
Name = "a-name",
|
||||
NamespaceProperty = "the-namespace",
|
||||
},
|
||||
};
|
||||
var cache = new Cache<V1Pod>();
|
||||
var newFunc = new Func<IKubernetesObject<V1ObjectMeta>, string>((pod) => pod.Kind);
|
||||
var defaultReturnValue = newFunc(aPod);
|
||||
|
||||
cache.SetKeyFunc(newFunc);
|
||||
|
||||
var funcReturnValue = cache.KeyFunc(aPod);
|
||||
|
||||
Assert.True(defaultReturnValue.Equals(funcReturnValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using k8s.Models;
|
||||
using Xunit;
|
||||
using k8s.Util.Informer.Cache;
|
||||
|
||||
namespace k8s.Tests.Util.Informer.Cache
|
||||
{
|
||||
public class CachesTest
|
||||
{
|
||||
[Fact(DisplayName = "Check for default DeletedFinalStateUnknown")]
|
||||
public void CheckDefaultDeletedFinalStateUnknown()
|
||||
{
|
||||
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 = Helpers.CreatePods(1).First();
|
||||
var key = "a-key";
|
||||
var deletedPod = new DeletedFinalStateUnknown<V1Pod>(key, aPod);
|
||||
|
||||
var returnKey = Caches.DeletionHandlingMetaNamespaceKeyFunc(deletedPod);
|
||||
|
||||
// returnKey.Should().Be(key);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Get default namespace key null")]
|
||||
public void GetDefaultNamespaceKeyNull()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => { Caches.MetaNamespaceKeyFunc(null); });
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Get default namespace key success")]
|
||||
public void GetDefaultNamespaceKeySuccess()
|
||||
{
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
Caches.MetaNamespaceKeyFunc(aPod).Should().Be($"{aPod.Metadata.NamespaceProperty}/{aPod.Metadata.Name}");
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Get default namespace index null")]
|
||||
public void GetDefaultNamespaceIndexNull()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => { Caches.MetaNamespaceIndexFunc<V1Pod>(null); });
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Get default namespace index success")]
|
||||
public void GetDefaultNamespaceIndexSuccess()
|
||||
{
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
var indexes = Caches.MetaNamespaceIndexFunc(aPod);
|
||||
|
||||
indexes.Should().NotBeNull();
|
||||
indexes.Should().Contain(aPod.Metadata.NamespaceProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using k8s.Models;
|
||||
using k8s.Util.Informer.Cache;
|
||||
using Xunit;
|
||||
|
||||
namespace k8s.Tests.Util.Informer.Cache
|
||||
{
|
||||
public class ListerTest
|
||||
{
|
||||
[Fact(DisplayName = "Create default lister success")]
|
||||
public void CreateListerDefaultsSuccess()
|
||||
{
|
||||
var cache = new Cache<V1Pod>();
|
||||
var lister = new Lister<V1Pod>(cache);
|
||||
|
||||
lister.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "List with null namespace success")]
|
||||
public void ListNullNamespaceSuccess()
|
||||
{
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
var cache = new Cache<V1Pod>();
|
||||
var lister = new Lister<V1Pod>(cache);
|
||||
|
||||
cache.Add(aPod);
|
||||
var pods = lister.List();
|
||||
|
||||
pods.Should().HaveCount(1);
|
||||
pods.Should().Contain(aPod);
|
||||
// Can't 'Get' the pod due to no namespace specified in Lister constructor
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "List with custom namespace success")]
|
||||
public void ListCustomNamespaceSuccess()
|
||||
{
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
var cache = new Cache<V1Pod>();
|
||||
var lister = new Lister<V1Pod>(cache, aPod.Metadata.NamespaceProperty);
|
||||
|
||||
cache.Add(aPod);
|
||||
var pods = lister.List();
|
||||
|
||||
pods.Should().HaveCount(1);
|
||||
pods.Should().Contain(aPod);
|
||||
lister.Get(aPod.Metadata.Name).Should().Be(aPod);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Get with null namespace success")]
|
||||
public void GetNullNamespaceSuccess()
|
||||
{
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
var cache = new Cache<V1Pod>();
|
||||
var lister = new Lister<V1Pod>(cache);
|
||||
|
||||
cache.Add(aPod);
|
||||
var pod = lister.Get(aPod.Metadata.Name);
|
||||
|
||||
// it's null because the namespace was not set in Lister constructor, but the pod did have a namespace.
|
||||
// So it can't build the right key name for lookup in Cache
|
||||
pod.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Get with custom namespace success")]
|
||||
public void GetCustomNamespaceSuccess()
|
||||
{
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
var cache = new Cache<V1Pod>();
|
||||
var lister = new Lister<V1Pod>(cache, aPod.Metadata.NamespaceProperty);
|
||||
|
||||
cache.Add(aPod);
|
||||
var pod = lister.Get(aPod.Metadata.Name);
|
||||
|
||||
pod.Should().Be(aPod);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Set custom namespace success")]
|
||||
public void SetCustomNamespaceSuccess()
|
||||
{
|
||||
var aPod = Helpers.CreatePods(1).First();
|
||||
var cache = new Cache<V1Pod>();
|
||||
var lister = new Lister<V1Pod>(cache);
|
||||
|
||||
cache.Add(aPod);
|
||||
var pod = lister.Get(aPod.Metadata.Name);
|
||||
pod.Should().BeNull();
|
||||
|
||||
lister = lister.Namespace(aPod.Metadata.NamespaceProperty);
|
||||
|
||||
pod = lister.Get(aPod.Metadata.Name);
|
||||
pod.Should().Be(aPod);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using k8s.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
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 = KubernetesJson.Deserialize<V1PodList>(MockPodResponse);
|
||||
return KubernetesJson.Serialize(new Watcher<V1Pod>.WatchEvent { Type = eventType, Object = corev1PodList.Items.First() });
|
||||
}
|
||||
|
||||
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 = KubernetesJson.Deserialize<V1PodList>(MockPodResponse);
|
||||
await WriteStreamLine(httpContext, KubernetesJson.Serialize(corev1PodList.Items.First())).ConfigureAwait(false);
|
||||
break;
|
||||
case MockKubeServerFlags.Throw500:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MartinCostello.Logging.XUnit" Version="0.1.1" />
|
||||
<PackageReference Include="FluentAssertions" Version="5.10.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="1.3.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\KubernetesClient\KubernetesClient.csproj" />
|
||||
<ProjectReference Include="..\..\src\KubernetesClient.Util\KubernetesClient.Util.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Cache" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user