Skip to content

Commit 801455c

Browse files
qmfrederikbrendandburns
authored andcommitted
Fix YAML serialization of ResourceQuantity values (#126)
* Fix YAML serialization of ResourceQuantity values * Address PR feedback
1 parent 3f2f269 commit 801455c

File tree

4 files changed

+168
-34
lines changed

4 files changed

+168
-34
lines changed

src/ResourceQuantity.cs

Lines changed: 65 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@
44
using System.Numerics;
55
using Fractions;
66
using Newtonsoft.Json;
7-
7+
using YamlDotNet.Core;
8+
using YamlDotNet.Core.Events;
9+
using YamlDotNet.Serialization;
10+
811
namespace k8s.Models
912
{
1013
internal class QuantityConverter : JsonConverter
1114
{
1215
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
1316
{
14-
var q = (ResourceQuantity) value;
17+
var q = (ResourceQuantity)value;
1518

1619
if (q != null)
1720
{
@@ -83,8 +86,8 @@ public override bool CanConvert(Type objectType)
8386
/// writing some sort of special handling code in the hopes that that will
8487
/// cause implementors to also use a fixed point implementation.
8588
/// </summary>
86-
[JsonConverter(typeof(QuantityConverter))]
87-
public partial class ResourceQuantity
89+
[JsonConverter(typeof(QuantityConverter))]
90+
public partial class ResourceQuantity : IYamlConvertible
8891
{
8992
public enum SuffixFormat
9093
{
@@ -175,8 +178,16 @@ public string CanonicalizeString(SuffixFormat suffixFormat)
175178

176179
// ctor
177180
partial void CustomInit()
178-
{
179-
var value = (Value ?? "").Trim();
181+
{
182+
if (Value == null)
183+
{
184+
// No value has been defined, initialize to 0.
185+
_unitlessValue = new Fraction(0);
186+
Format = SuffixFormat.BinarySI;
187+
return;
188+
}
189+
190+
var value = Value.Trim();
180191

181192
var si = value.IndexOfAny(SuffixChars);
182193
if (si == -1)
@@ -204,8 +215,30 @@ private static bool HasMantissa(Fraction value)
204215
}
205216

206217
return BigInteger.Remainder(value.Numerator, value.Denominator) > 0;
207-
}
208-
218+
}
219+
220+
/// <inheritdoc/>
221+
public void Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer)
222+
{
223+
if (expectedType != typeof(ResourceQuantity))
224+
{
225+
throw new ArgumentOutOfRangeException(nameof(expectedType));
226+
}
227+
228+
if (parser.Current is Scalar)
229+
{
230+
Value = ((Scalar)parser.Current).Value;
231+
parser.MoveNext();
232+
CustomInit();
233+
}
234+
}
235+
236+
/// <inheritdoc/>
237+
public void Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer)
238+
{
239+
emitter.Emit(new Scalar(this.ToString()));
240+
}
241+
209242
public static implicit operator decimal(ResourceQuantity v)
210243
{
211244
return v._unitlessValue.ToDecimal();
@@ -298,32 +331,32 @@ public static string AppendMaxSuffix(Fraction value, SuffixFormat format)
298331

299332
switch (format)
300333
{
301-
case SuffixFormat.DecimalExponent:
302-
{
303-
var minE = -9;
304-
var lastv = Roundup(value * Fraction.Pow(10, -minE));
305-
306-
for (var exp = minE;; exp += 3)
307-
{
308-
var v = value * Fraction.Pow(10, -exp);
309-
if (HasMantissa(v))
310-
{
311-
break;
312-
}
313-
314-
minE = exp;
315-
lastv = v;
334+
case SuffixFormat.DecimalExponent:
335+
{
336+
var minE = -9;
337+
var lastv = Roundup(value * Fraction.Pow(10, -minE));
338+
339+
for (var exp = minE;; exp += 3)
340+
{
341+
var v = value * Fraction.Pow(10, -exp);
342+
if (HasMantissa(v))
343+
{
344+
break;
345+
}
346+
347+
minE = exp;
348+
lastv = v;
349+
}
350+
351+
352+
if (minE == 0)
353+
{
354+
return $"{(decimal) lastv}";
355+
}
356+
357+
return $"{(decimal) lastv}e{minE}";
316358
}
317359

318-
319-
if (minE == 0)
320-
{
321-
return $"{(decimal) lastv}";
322-
}
323-
324-
return $"{(decimal) lastv}e{minE}";
325-
}
326-
327360
case SuffixFormat.BinarySI:
328361
return AppendMaxSuffix(value, BinSuffixes);
329362
case SuffixFormat.DecimalSI:

src/Yaml.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
using System.IO;
2+
using System.Text;
23
using System.Threading.Tasks;
4+
using YamlDotNet.Core;
5+
using YamlDotNet.Core.Events;
36
using YamlDotNet.Serialization;
47
using YamlDotNet.Serialization.NamingConventions;
58

@@ -17,18 +20,35 @@ public static async Task<T> LoadFromStreamAsync<T>(Stream stream) {
1720
}
1821

1922
public static async Task<T> LoadFromFileAsync<T> (string file) {
20-
using (FileStream fs = File.OpenRead(file)) {
23+
using (FileStream fs = File.OpenRead(file)) {
2124
return await LoadFromStreamAsync<T>(fs);
2225
}
2326
}
2427

2528
public static T LoadFromString<T>(string content) {
26-
var deserializer =
29+
var deserializer =
2730
new DeserializerBuilder()
2831
.WithNamingConvention(new CamelCaseNamingConvention())
2932
.Build();
3033
var obj = deserializer.Deserialize<T>(content);
3134
return obj;
3235
}
36+
37+
public static string SaveToString<T>(T value)
38+
{
39+
var stringBuilder = new StringBuilder();
40+
var writer = new StringWriter(stringBuilder);
41+
var emitter = new Emitter(writer);
42+
43+
var serializer =
44+
new SerializerBuilder()
45+
.WithNamingConvention(new CamelCaseNamingConvention())
46+
.BuildValueSerializer();
47+
emitter.Emit(new StreamStart());
48+
emitter.Emit(new DocumentStart());
49+
serializer.SerializeValue(emitter, value, typeof(T));
50+
51+
return stringBuilder.ToString();
52+
}
3353
}
3454
}

tests/QuantityValueTests.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,12 @@ public void Parse()
164164
Assert.ThrowsAny<Exception>(() => { new ResourceQuantity(s); });
165165
}
166166
}
167+
168+
[Fact]
169+
public void ConstructorTest()
170+
{
171+
Assert.Throws<FormatException>(() => new ResourceQuantity(string.Empty));
172+
}
167173

168174
[Fact]
169175
public void QuantityString()
@@ -233,6 +239,20 @@ public void Serialize()
233239
ResourceQuantity quantity = 12000;
234240
Assert.Equal("\"12e3\"", JsonConvert.SerializeObject(quantity));
235241
}
242+
}
243+
244+
[Fact]
245+
public void DeserializeYaml()
246+
{
247+
var value = Yaml.LoadFromString<ResourceQuantity>("\"1\"");
248+
Assert.Equal(new ResourceQuantity(1, 0, DecimalSI), value);
249+
}
250+
251+
[Fact]
252+
public void SerializeYaml()
253+
{
254+
var value = Yaml.SaveToString(new ResourceQuantity(1, -1, DecimalSI));
255+
Assert.Equal("100m", value);
236256
}
237257
}
238258
}

tests/YamlTests.cs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,66 @@ public void LoadFromStream()
3535
Assert.Equal("foo", obj.Metadata.Name);
3636
}
3737
}
38+
39+
[Fact]
40+
public void WriteToString()
41+
{
42+
var pod = new V1Pod()
43+
{
44+
ApiVersion = "v1",
45+
Kind = "Pod",
46+
Metadata = new V1ObjectMeta()
47+
{
48+
Name = "foo"
49+
}
50+
};
51+
52+
var yaml = Yaml.SaveToString(pod);
53+
Assert.Equal(@"apiVersion: v1
54+
kind: Pod
55+
metadata:
56+
name: foo", yaml);
57+
}
58+
59+
[Fact]
60+
public void CpuRequestAndLimitFromString()
61+
{
62+
// Taken from https://raw.githubusercontent.com/kubernetes/website/master/docs/tasks/configure-pod-container/cpu-request-limit.yaml, although
63+
// the 'namespace' property on 'metadata' was removed since it was rejected by the C# client.
64+
var content = @"apiVersion: v1
65+
kind: Pod
66+
metadata:
67+
name: cpu-demo
68+
spec:
69+
containers:
70+
- name: cpu-demo-ctr
71+
image: vish/stress
72+
resources:
73+
limits:
74+
cpu: ""1""
75+
requests:
76+
cpu: ""0.5""
77+
args:
78+
- -cpus
79+
- ""2""";
80+
81+
var obj = Yaml.LoadFromString<V1Pod>(content);
82+
83+
Assert.NotNull(obj?.Spec?.Containers);
84+
var container = Assert.Single(obj.Spec.Containers);
85+
86+
Assert.NotNull(container.Resources);
87+
Assert.NotNull(container.Resources.Limits);
88+
Assert.NotNull(container.Resources.Requests);
89+
90+
var cpuLimit = Assert.Single(container.Resources.Limits);
91+
var cpuRequest = Assert.Single(container.Resources.Requests);
92+
93+
Assert.Equal("cpu", cpuLimit.Key);
94+
Assert.Equal("1", cpuLimit.Value.ToString());
95+
96+
Assert.Equal("cpu", cpuRequest.Key);
97+
Assert.Equal("500m", cpuRequest.Value.ToString());
98+
}
3899
}
39100
}

0 commit comments

Comments
 (0)