Skip to content

Commit 2922627

Browse files
authored
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
1 parent 6d27bd9 commit 2922627

File tree

5 files changed

+211
-6
lines changed

5 files changed

+211
-6
lines changed

src/KubernetesClient/Models/IntOrString.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
namespace k8s.Models
22
{
33
[JsonConverter(typeof(IntOrStringJsonConverter))]
4-
public struct IntOrString
4+
public class IntOrString
55
{
6-
public string? Value { get; private init; }
6+
public string Value { get; private init; }
77

88
public static implicit operator IntOrString(int v)
99
{
@@ -17,7 +17,7 @@ public static implicit operator IntOrString(long v)
1717

1818
public static implicit operator string(IntOrString v)
1919
{
20-
return v.Value;
20+
return v?.Value;
2121
}
2222

2323
public static implicit operator IntOrString(string v)

src/KubernetesClient/Models/IntOrStringYamlConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeseria
3535
public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerializer serializer)
3636
{
3737
var obj = (IntOrString)value;
38-
emitter?.Emit(new YamlDotNet.Core.Events.Scalar(obj.Value));
38+
emitter?.Emit(new YamlDotNet.Core.Events.Scalar(obj?.Value));
3939
}
4040
}
4141
}

src/KubernetesClient/Models/ResourceQuantity.cs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ namespace k8s.Models
5454
/// cause implementors to also use a fixed point implementation.
5555
/// </summary>
5656
[JsonConverter(typeof(ResourceQuantityJsonConverter))]
57-
public struct ResourceQuantity
57+
public class ResourceQuantity
5858
{
5959
public enum SuffixFormat
6060
{
@@ -179,6 +179,46 @@ public static implicit operator ResourceQuantity(decimal v)
179179
return new ResourceQuantity(v, 0, SuffixFormat.DecimalExponent);
180180
}
181181

182+
public bool Equals(ResourceQuantity other)
183+
{
184+
if (ReferenceEquals(null, other))
185+
{
186+
return false;
187+
}
188+
189+
if (ReferenceEquals(this, other))
190+
{
191+
return true;
192+
}
193+
194+
return _unitlessValue.Equals(other._unitlessValue);
195+
}
196+
197+
public override bool Equals(object obj)
198+
{
199+
return Equals(obj as ResourceQuantity);
200+
}
201+
202+
public override int GetHashCode()
203+
{
204+
return _unitlessValue.GetHashCode();
205+
}
206+
207+
public static bool operator ==(ResourceQuantity left, ResourceQuantity right)
208+
{
209+
if (left is null)
210+
{
211+
return right is null;
212+
}
213+
214+
return left.Equals(right);
215+
}
216+
217+
public static bool operator !=(ResourceQuantity left, ResourceQuantity right)
218+
{
219+
return !(left == right);
220+
}
221+
182222
private sealed class Suffixer
183223
{
184224
private static readonly IReadOnlyDictionary<string, (int, int)> BinSuffixes =

src/KubernetesClient/Models/ResourceQuantityYamlConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeseria
3535
public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerializer serializer)
3636
{
3737
var obj = (ResourceQuantity)value;
38-
emitter?.Emit(new YamlDotNet.Core.Events.Scalar(obj.ToString()));
38+
emitter?.Emit(new YamlDotNet.Core.Events.Scalar(obj?.ToString()));
3939
}
4040
}
4141
}

tests/E2E.Tests/MinikubeTests.cs

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -958,6 +958,171 @@ async Task AssertMd5sumAsync(string file, byte[] orig)
958958
}
959959
}
960960

961+
[MinikubeFact]
962+
public async Task V2HorizontalPodAutoscalerTestAsync()
963+
{
964+
var namespaceParameter = "default";
965+
var deploymentName = "k8scsharp-e2e-hpa-deployment";
966+
var hpaName = "k8scsharp-e2e-hpa";
967+
968+
using var client = CreateClient();
969+
970+
async Task CleanupAsync()
971+
{
972+
var deleteOptions = new V1DeleteOptions { PropagationPolicy = "Foreground" };
973+
974+
try
975+
{
976+
await client.AutoscalingV2.DeleteNamespacedHorizontalPodAutoscalerAsync(hpaName, namespaceParameter, deleteOptions).ConfigureAwait(false);
977+
}
978+
catch (HttpOperationException e)
979+
{
980+
if (e.Response?.StatusCode != System.Net.HttpStatusCode.NotFound)
981+
{
982+
throw;
983+
}
984+
}
985+
986+
try
987+
{
988+
await client.AppsV1.DeleteNamespacedDeploymentAsync(deploymentName, namespaceParameter, deleteOptions).ConfigureAwait(false);
989+
}
990+
catch (HttpOperationException e)
991+
{
992+
if (e.Response?.StatusCode != System.Net.HttpStatusCode.NotFound)
993+
{
994+
throw;
995+
}
996+
}
997+
998+
var attempts = 10;
999+
while (attempts-- > 0)
1000+
{
1001+
var hpaList = await client.AutoscalingV2.ListNamespacedHorizontalPodAutoscalerAsync(namespaceParameter).ConfigureAwait(false);
1002+
var deploymentList = await client.AppsV1.ListNamespacedDeploymentAsync(namespaceParameter).ConfigureAwait(false);
1003+
if (hpaList.Items.All(item => item.Metadata.Name != hpaName) && deploymentList.Items.All(item => item.Metadata.Name != deploymentName))
1004+
{
1005+
break;
1006+
}
1007+
1008+
await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
1009+
}
1010+
}
1011+
1012+
try
1013+
{
1014+
await CleanupAsync().ConfigureAwait(false);
1015+
1016+
var labels = new Dictionary<string, string> { ["app"] = "k8scsharp-hpa" };
1017+
1018+
await client.AppsV1.CreateNamespacedDeploymentAsync(
1019+
new V1Deployment
1020+
{
1021+
Metadata = new V1ObjectMeta { Name = deploymentName, Labels = new Dictionary<string, string>(labels) },
1022+
Spec = new V1DeploymentSpec
1023+
{
1024+
Replicas = 1,
1025+
Selector = new V1LabelSelector { MatchLabels = new Dictionary<string, string>(labels) },
1026+
Template = new V1PodTemplateSpec
1027+
{
1028+
Metadata = new V1ObjectMeta { Labels = new Dictionary<string, string>(labels) },
1029+
Spec = new V1PodSpec
1030+
{
1031+
Containers = new[]
1032+
{
1033+
new V1Container
1034+
{
1035+
Name = "k8scsharp-hpa",
1036+
Image = "nginx",
1037+
Resources = new V1ResourceRequirements
1038+
{
1039+
Requests = new Dictionary<string, ResourceQuantity>
1040+
{
1041+
{ "cpu", new ResourceQuantity("100m") },
1042+
{ "memory", new ResourceQuantity("128Mi") },
1043+
},
1044+
Limits = new Dictionary<string, ResourceQuantity>
1045+
{
1046+
{ "cpu", new ResourceQuantity("200m") },
1047+
{ "memory", new ResourceQuantity("256Mi") },
1048+
},
1049+
},
1050+
},
1051+
},
1052+
},
1053+
},
1054+
},
1055+
},
1056+
namespaceParameter).ConfigureAwait(false);
1057+
1058+
var hpa = new V2HorizontalPodAutoscaler
1059+
{
1060+
Metadata = new V1ObjectMeta { Name = hpaName },
1061+
Spec = new V2HorizontalPodAutoscalerSpec
1062+
{
1063+
MinReplicas = 1,
1064+
MaxReplicas = 3,
1065+
ScaleTargetRef = new V2CrossVersionObjectReference
1066+
{
1067+
ApiVersion = "apps/v1",
1068+
Kind = "Deployment",
1069+
Name = deploymentName,
1070+
},
1071+
Metrics = new List<V2MetricSpec>
1072+
{
1073+
new V2MetricSpec
1074+
{
1075+
Type = "Resource",
1076+
Resource = new V2ResourceMetricSource
1077+
{
1078+
Name = "cpu",
1079+
Target = new V2MetricTarget
1080+
{
1081+
Type = "Utilization",
1082+
AverageUtilization = 50,
1083+
},
1084+
},
1085+
},
1086+
},
1087+
},
1088+
};
1089+
1090+
await client.AutoscalingV2.CreateNamespacedHorizontalPodAutoscalerAsync(hpa, namespaceParameter).ConfigureAwait(false);
1091+
1092+
var hpaList = await client.AutoscalingV2.ListNamespacedHorizontalPodAutoscalerAsync(namespaceParameter).ConfigureAwait(false);
1093+
Assert.Contains(hpaList.Items, item => item.Metadata.Name == hpaName);
1094+
1095+
var created = await client.AutoscalingV2.ReadNamespacedHorizontalPodAutoscalerAsync(hpaName, namespaceParameter).ConfigureAwait(false);
1096+
Assert.Equal(1, created.Spec.MinReplicas);
1097+
1098+
created.Spec.MinReplicas = 2;
1099+
await client.AutoscalingV2.ReplaceNamespacedHorizontalPodAutoscalerAsync(created, hpaName, namespaceParameter).ConfigureAwait(false);
1100+
1101+
var updated = await client.AutoscalingV2.ReadNamespacedHorizontalPodAutoscalerAsync(hpaName, namespaceParameter).ConfigureAwait(false);
1102+
Assert.Equal(2, updated.Spec.MinReplicas);
1103+
1104+
await client.AutoscalingV2.DeleteNamespacedHorizontalPodAutoscalerAsync(hpaName, namespaceParameter, new V1DeleteOptions { PropagationPolicy = "Foreground" }).ConfigureAwait(false);
1105+
1106+
var retries = 10;
1107+
while (retries-- > 0)
1108+
{
1109+
hpaList = await client.AutoscalingV2.ListNamespacedHorizontalPodAutoscalerAsync(namespaceParameter).ConfigureAwait(false);
1110+
if (hpaList.Items.All(item => item.Metadata.Name != hpaName))
1111+
{
1112+
break;
1113+
}
1114+
1115+
await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
1116+
}
1117+
1118+
Assert.DoesNotContain(hpaList.Items, item => item.Metadata.Name == hpaName);
1119+
}
1120+
finally
1121+
{
1122+
await CleanupAsync().ConfigureAwait(false);
1123+
}
1124+
}
1125+
9611126
public static IKubernetes CreateClient()
9621127
{
9631128
return new Kubernetes(KubernetesClientConfiguration.BuildDefaultConfig());

0 commit comments

Comments
 (0)