Fix YAML serialization of ResourceQuantity values (#126)

* Fix YAML serialization of ResourceQuantity values

* Address PR feedback
This commit is contained in:
Frederik Carlier
2018-03-31 07:02:29 +02:00
committed by Brendan Burns
parent 3f2f2696d9
commit 801455c4d8
4 changed files with 168 additions and 34 deletions

View File

@@ -4,14 +4,17 @@ using System.Linq;
using System.Numerics;
using Fractions;
using Newtonsoft.Json;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace k8s.Models
{
internal class QuantityConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var q = (ResourceQuantity) value;
var q = (ResourceQuantity)value;
if (q != null)
{
@@ -83,8 +86,8 @@ namespace k8s.Models
/// writing some sort of special handling code in the hopes that that will
/// cause implementors to also use a fixed point implementation.
/// </summary>
[JsonConverter(typeof(QuantityConverter))]
public partial class ResourceQuantity
[JsonConverter(typeof(QuantityConverter))]
public partial class ResourceQuantity : IYamlConvertible
{
public enum SuffixFormat
{
@@ -175,8 +178,16 @@ namespace k8s.Models
// ctor
partial void CustomInit()
{
var value = (Value ?? "").Trim();
{
if (Value == null)
{
// No value has been defined, initialize to 0.
_unitlessValue = new Fraction(0);
Format = SuffixFormat.BinarySI;
return;
}
var value = Value.Trim();
var si = value.IndexOfAny(SuffixChars);
if (si == -1)
@@ -204,8 +215,30 @@ namespace k8s.Models
}
return BigInteger.Remainder(value.Numerator, value.Denominator) > 0;
}
}
/// <inheritdoc/>
public void Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer)
{
if (expectedType != typeof(ResourceQuantity))
{
throw new ArgumentOutOfRangeException(nameof(expectedType));
}
if (parser.Current is Scalar)
{
Value = ((Scalar)parser.Current).Value;
parser.MoveNext();
CustomInit();
}
}
/// <inheritdoc/>
public void Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer)
{
emitter.Emit(new Scalar(this.ToString()));
}
public static implicit operator decimal(ResourceQuantity v)
{
return v._unitlessValue.ToDecimal();
@@ -298,32 +331,32 @@ namespace k8s.Models
switch (format)
{
case SuffixFormat.DecimalExponent:
{
var minE = -9;
var lastv = Roundup(value * Fraction.Pow(10, -minE));
for (var exp = minE;; exp += 3)
{
var v = value * Fraction.Pow(10, -exp);
if (HasMantissa(v))
{
break;
}
minE = exp;
lastv = v;
case SuffixFormat.DecimalExponent:
{
var minE = -9;
var lastv = Roundup(value * Fraction.Pow(10, -minE));
for (var exp = minE;; exp += 3)
{
var v = value * Fraction.Pow(10, -exp);
if (HasMantissa(v))
{
break;
}
minE = exp;
lastv = v;
}
if (minE == 0)
{
return $"{(decimal) lastv}";
}
return $"{(decimal) lastv}e{minE}";
}
if (minE == 0)
{
return $"{(decimal) lastv}";
}
return $"{(decimal) lastv}e{minE}";
}
case SuffixFormat.BinarySI:
return AppendMaxSuffix(value, BinSuffixes);
case SuffixFormat.DecimalSI:

View File

@@ -1,5 +1,8 @@
using System.IO;
using System.Text;
using System.Threading.Tasks;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
@@ -17,18 +20,35 @@ namespace k8s
}
public static async Task<T> LoadFromFileAsync<T> (string file) {
using (FileStream fs = File.OpenRead(file)) {
using (FileStream fs = File.OpenRead(file)) {
return await LoadFromStreamAsync<T>(fs);
}
}
public static T LoadFromString<T>(string content) {
var deserializer =
var deserializer =
new DeserializerBuilder()
.WithNamingConvention(new CamelCaseNamingConvention())
.Build();
var obj = deserializer.Deserialize<T>(content);
return obj;
}
public static string SaveToString<T>(T value)
{
var stringBuilder = new StringBuilder();
var writer = new StringWriter(stringBuilder);
var emitter = new Emitter(writer);
var serializer =
new SerializerBuilder()
.WithNamingConvention(new CamelCaseNamingConvention())
.BuildValueSerializer();
emitter.Emit(new StreamStart());
emitter.Emit(new DocumentStart());
serializer.SerializeValue(emitter, value, typeof(T));
return stringBuilder.ToString();
}
}
}

View File

@@ -164,6 +164,12 @@ namespace k8s.Tests
Assert.ThrowsAny<Exception>(() => { new ResourceQuantity(s); });
}
}
[Fact]
public void ConstructorTest()
{
Assert.Throws<FormatException>(() => new ResourceQuantity(string.Empty));
}
[Fact]
public void QuantityString()
@@ -233,6 +239,20 @@ namespace k8s.Tests
ResourceQuantity quantity = 12000;
Assert.Equal("\"12e3\"", JsonConvert.SerializeObject(quantity));
}
}
[Fact]
public void DeserializeYaml()
{
var value = Yaml.LoadFromString<ResourceQuantity>("\"1\"");
Assert.Equal(new ResourceQuantity(1, 0, DecimalSI), value);
}
[Fact]
public void SerializeYaml()
{
var value = Yaml.SaveToString(new ResourceQuantity(1, -1, DecimalSI));
Assert.Equal("100m", value);
}
}
}

View File

@@ -35,5 +35,66 @@ metadata:
Assert.Equal("foo", obj.Metadata.Name);
}
}
[Fact]
public void WriteToString()
{
var pod = new V1Pod()
{
ApiVersion = "v1",
Kind = "Pod",
Metadata = new V1ObjectMeta()
{
Name = "foo"
}
};
var yaml = Yaml.SaveToString(pod);
Assert.Equal(@"apiVersion: v1
kind: Pod
metadata:
name: foo", yaml);
}
[Fact]
public void CpuRequestAndLimitFromString()
{
// Taken from https://raw.githubusercontent.com/kubernetes/website/master/docs/tasks/configure-pod-container/cpu-request-limit.yaml, although
// the 'namespace' property on 'metadata' was removed since it was rejected by the C# client.
var content = @"apiVersion: v1
kind: Pod
metadata:
name: cpu-demo
spec:
containers:
- name: cpu-demo-ctr
image: vish/stress
resources:
limits:
cpu: ""1""
requests:
cpu: ""0.5""
args:
- -cpus
- ""2""";
var obj = Yaml.LoadFromString<V1Pod>(content);
Assert.NotNull(obj?.Spec?.Containers);
var container = Assert.Single(obj.Spec.Containers);
Assert.NotNull(container.Resources);
Assert.NotNull(container.Resources.Limits);
Assert.NotNull(container.Resources.Requests);
var cpuLimit = Assert.Single(container.Resources.Limits);
var cpuRequest = Assert.Single(container.Resources.Requests);
Assert.Equal("cpu", cpuLimit.Key);
Assert.Equal("1", cpuLimit.Value.ToString());
Assert.Equal("cpu", cpuRequest.Key);
Assert.Equal("500m", cpuRequest.Value.ToString());
}
}
}