revert change to structs from classes for IntOrString and ResourceQuantity, and handle null values in YAML converters (#1673)
* feat: add V2HorizontalPodAutoscaler integration test * fix: change structs to classes for IntOrString and ResourceQuantity, and handle null values in YAML converters * feat: implement equality members for ResourceQuantity class
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
namespace k8s.Models
|
namespace k8s.Models
|
||||||
{
|
{
|
||||||
[JsonConverter(typeof(IntOrStringJsonConverter))]
|
[JsonConverter(typeof(IntOrStringJsonConverter))]
|
||||||
public struct IntOrString
|
public class IntOrString
|
||||||
{
|
{
|
||||||
public string? Value { get; private init; }
|
public string Value { get; private init; }
|
||||||
|
|
||||||
public static implicit operator IntOrString(int v)
|
public static implicit operator IntOrString(int v)
|
||||||
{
|
{
|
||||||
@@ -17,7 +17,7 @@ namespace k8s.Models
|
|||||||
|
|
||||||
public static implicit operator string(IntOrString v)
|
public static implicit operator string(IntOrString v)
|
||||||
{
|
{
|
||||||
return v.Value;
|
return v?.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static implicit operator IntOrString(string v)
|
public static implicit operator IntOrString(string v)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ namespace k8s.Models
|
|||||||
public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerializer serializer)
|
public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerializer serializer)
|
||||||
{
|
{
|
||||||
var obj = (IntOrString)value;
|
var obj = (IntOrString)value;
|
||||||
emitter?.Emit(new YamlDotNet.Core.Events.Scalar(obj.Value));
|
emitter?.Emit(new YamlDotNet.Core.Events.Scalar(obj?.Value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ namespace k8s.Models
|
|||||||
/// cause implementors to also use a fixed point implementation.
|
/// cause implementors to also use a fixed point implementation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonConverter(typeof(ResourceQuantityJsonConverter))]
|
[JsonConverter(typeof(ResourceQuantityJsonConverter))]
|
||||||
public struct ResourceQuantity
|
public class ResourceQuantity
|
||||||
{
|
{
|
||||||
public enum SuffixFormat
|
public enum SuffixFormat
|
||||||
{
|
{
|
||||||
@@ -179,6 +179,46 @@ namespace k8s.Models
|
|||||||
return new ResourceQuantity(v, 0, SuffixFormat.DecimalExponent);
|
return new ResourceQuantity(v, 0, SuffixFormat.DecimalExponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool Equals(ResourceQuantity other)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(null, other))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(this, other))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _unitlessValue.Equals(other._unitlessValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return Equals(obj as ResourceQuantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return _unitlessValue.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(ResourceQuantity left, ResourceQuantity right)
|
||||||
|
{
|
||||||
|
if (left is null)
|
||||||
|
{
|
||||||
|
return right is null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(ResourceQuantity left, ResourceQuantity right)
|
||||||
|
{
|
||||||
|
return !(left == right);
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class Suffixer
|
private sealed class Suffixer
|
||||||
{
|
{
|
||||||
private static readonly IReadOnlyDictionary<string, (int, int)> BinSuffixes =
|
private static readonly IReadOnlyDictionary<string, (int, int)> BinSuffixes =
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ namespace k8s.Models
|
|||||||
public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerializer serializer)
|
public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerializer serializer)
|
||||||
{
|
{
|
||||||
var obj = (ResourceQuantity)value;
|
var obj = (ResourceQuantity)value;
|
||||||
emitter?.Emit(new YamlDotNet.Core.Events.Scalar(obj.ToString()));
|
emitter?.Emit(new YamlDotNet.Core.Events.Scalar(obj?.ToString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -958,6 +958,171 @@ namespace k8s.E2E
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MinikubeFact]
|
||||||
|
public async Task V2HorizontalPodAutoscalerTestAsync()
|
||||||
|
{
|
||||||
|
var namespaceParameter = "default";
|
||||||
|
var deploymentName = "k8scsharp-e2e-hpa-deployment";
|
||||||
|
var hpaName = "k8scsharp-e2e-hpa";
|
||||||
|
|
||||||
|
using var client = CreateClient();
|
||||||
|
|
||||||
|
async Task CleanupAsync()
|
||||||
|
{
|
||||||
|
var deleteOptions = new V1DeleteOptions { PropagationPolicy = "Foreground" };
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await client.AutoscalingV2.DeleteNamespacedHorizontalPodAutoscalerAsync(hpaName, namespaceParameter, deleteOptions).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (HttpOperationException e)
|
||||||
|
{
|
||||||
|
if (e.Response?.StatusCode != System.Net.HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await client.AppsV1.DeleteNamespacedDeploymentAsync(deploymentName, namespaceParameter, deleteOptions).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (HttpOperationException e)
|
||||||
|
{
|
||||||
|
if (e.Response?.StatusCode != System.Net.HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var attempts = 10;
|
||||||
|
while (attempts-- > 0)
|
||||||
|
{
|
||||||
|
var hpaList = await client.AutoscalingV2.ListNamespacedHorizontalPodAutoscalerAsync(namespaceParameter).ConfigureAwait(false);
|
||||||
|
var deploymentList = await client.AppsV1.ListNamespacedDeploymentAsync(namespaceParameter).ConfigureAwait(false);
|
||||||
|
if (hpaList.Items.All(item => item.Metadata.Name != hpaName) && deploymentList.Items.All(item => item.Metadata.Name != deploymentName))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await CleanupAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
var labels = new Dictionary<string, string> { ["app"] = "k8scsharp-hpa" };
|
||||||
|
|
||||||
|
await client.AppsV1.CreateNamespacedDeploymentAsync(
|
||||||
|
new V1Deployment
|
||||||
|
{
|
||||||
|
Metadata = new V1ObjectMeta { Name = deploymentName, Labels = new Dictionary<string, string>(labels) },
|
||||||
|
Spec = new V1DeploymentSpec
|
||||||
|
{
|
||||||
|
Replicas = 1,
|
||||||
|
Selector = new V1LabelSelector { MatchLabels = new Dictionary<string, string>(labels) },
|
||||||
|
Template = new V1PodTemplateSpec
|
||||||
|
{
|
||||||
|
Metadata = new V1ObjectMeta { Labels = new Dictionary<string, string>(labels) },
|
||||||
|
Spec = new V1PodSpec
|
||||||
|
{
|
||||||
|
Containers = new[]
|
||||||
|
{
|
||||||
|
new V1Container
|
||||||
|
{
|
||||||
|
Name = "k8scsharp-hpa",
|
||||||
|
Image = "nginx",
|
||||||
|
Resources = new V1ResourceRequirements
|
||||||
|
{
|
||||||
|
Requests = new Dictionary<string, ResourceQuantity>
|
||||||
|
{
|
||||||
|
{ "cpu", new ResourceQuantity("100m") },
|
||||||
|
{ "memory", new ResourceQuantity("128Mi") },
|
||||||
|
},
|
||||||
|
Limits = new Dictionary<string, ResourceQuantity>
|
||||||
|
{
|
||||||
|
{ "cpu", new ResourceQuantity("200m") },
|
||||||
|
{ "memory", new ResourceQuantity("256Mi") },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
namespaceParameter).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var hpa = new V2HorizontalPodAutoscaler
|
||||||
|
{
|
||||||
|
Metadata = new V1ObjectMeta { Name = hpaName },
|
||||||
|
Spec = new V2HorizontalPodAutoscalerSpec
|
||||||
|
{
|
||||||
|
MinReplicas = 1,
|
||||||
|
MaxReplicas = 3,
|
||||||
|
ScaleTargetRef = new V2CrossVersionObjectReference
|
||||||
|
{
|
||||||
|
ApiVersion = "apps/v1",
|
||||||
|
Kind = "Deployment",
|
||||||
|
Name = deploymentName,
|
||||||
|
},
|
||||||
|
Metrics = new List<V2MetricSpec>
|
||||||
|
{
|
||||||
|
new V2MetricSpec
|
||||||
|
{
|
||||||
|
Type = "Resource",
|
||||||
|
Resource = new V2ResourceMetricSource
|
||||||
|
{
|
||||||
|
Name = "cpu",
|
||||||
|
Target = new V2MetricTarget
|
||||||
|
{
|
||||||
|
Type = "Utilization",
|
||||||
|
AverageUtilization = 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await client.AutoscalingV2.CreateNamespacedHorizontalPodAutoscalerAsync(hpa, namespaceParameter).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var hpaList = await client.AutoscalingV2.ListNamespacedHorizontalPodAutoscalerAsync(namespaceParameter).ConfigureAwait(false);
|
||||||
|
Assert.Contains(hpaList.Items, item => item.Metadata.Name == hpaName);
|
||||||
|
|
||||||
|
var created = await client.AutoscalingV2.ReadNamespacedHorizontalPodAutoscalerAsync(hpaName, namespaceParameter).ConfigureAwait(false);
|
||||||
|
Assert.Equal(1, created.Spec.MinReplicas);
|
||||||
|
|
||||||
|
created.Spec.MinReplicas = 2;
|
||||||
|
await client.AutoscalingV2.ReplaceNamespacedHorizontalPodAutoscalerAsync(created, hpaName, namespaceParameter).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var updated = await client.AutoscalingV2.ReadNamespacedHorizontalPodAutoscalerAsync(hpaName, namespaceParameter).ConfigureAwait(false);
|
||||||
|
Assert.Equal(2, updated.Spec.MinReplicas);
|
||||||
|
|
||||||
|
await client.AutoscalingV2.DeleteNamespacedHorizontalPodAutoscalerAsync(hpaName, namespaceParameter, new V1DeleteOptions { PropagationPolicy = "Foreground" }).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var retries = 10;
|
||||||
|
while (retries-- > 0)
|
||||||
|
{
|
||||||
|
hpaList = await client.AutoscalingV2.ListNamespacedHorizontalPodAutoscalerAsync(namespaceParameter).ConfigureAwait(false);
|
||||||
|
if (hpaList.Items.All(item => item.Metadata.Name != hpaName))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.DoesNotContain(hpaList.Items, item => item.Metadata.Name == hpaName);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await CleanupAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static IKubernetes CreateClient()
|
public static IKubernetes CreateClient()
|
||||||
{
|
{
|
||||||
return new Kubernetes(KubernetesClientConfiguration.BuildDefaultConfig());
|
return new Kubernetes(KubernetesClientConfiguration.BuildDefaultConfig());
|
||||||
|
|||||||
Reference in New Issue
Block a user