Skip to content

Timestamp deserialization fails on well-formatted timestamps depending on number of nanosecond digits #1350

@anekdoti

Description

@anekdoti

Describe the bug
Timestamp deserialization is more limited than Kubernetes API conventions allow. I.e., there are timestamps that are fine according to Kubernetes API conventions (e.g., lastTransitionTimestamp in status conditions) that can not be deserialized using the C# Kubernetes client but instead raise an exception like shown below.

More precisely, the C# Kubernetes client only allows either exactly 6 digits for nanoseconds or none at all, whereas an arbitrary number < 10 is actually possible.

Kubernetes C# SDK Client Version
Observed on 10.0.31, but according to the implementation of KubernetesDateTimeOffsetConverter on the master branch we expect the bug to occur on master as well.

Server Kubernetes Version
1.24.14

Dotnet Runtime Version
net6

To Reproduce
Create a Kubernetes resource with a status condition where the lastTransitionTimestamp has e.g. 9 nanosecond positions.
Attempt to read the resource with the generic client of the C# Kubernetes Client. The exception shown below will be thrown.

Expected behavior
The Kubernetes resource should be read properly, including the lastTransitionTimestamp of the status condition.

Where do you run your app with Kubernetes SDK (please complete the following information):
The Kubernetes SDK is used in a container running the image mcr.microsoft.com/dotnet/sdk:6.0-alpine3.16 in Kubernetes 1.24.14.

Additional context
According to the Kubernetes API conventions, the lastTransitionTimestamp should be deserializable as a Golang Time struct. The deserialization of a string into such a struct follows the conventions of RFC3339 and its implementation in particular allows an arbitrary number of nanosecond digits from 0 to 9.

We actually faced this problem in practice trying to read a KafkaConnector resource by the Strimzi operator that contained a status condition with the lastTransitionTimestamp as shown in the exception below. According to the Kubernetes API conventions, this timestamp is well-formatted and should be deserialized correctly.

The KubernetesDateTimeOffsetConverter in the C# Kubernetes Client however only allows either 0 or 6 nanosecond digits.

Exception

System.FormatException: String '2023-07-26T11:17:34.255606088Z' was not recognized as a valid DateTime.
   at System.DateTimeParse.ParseExactMultiple(ReadOnlySpan`1 s, String[] formats, DateTimeFormatInfo dtfi, DateTimeStyles style, TimeSpan& offset)
   at System.DateTimeOffset.ParseExact(String input, String[] formats, IFormatProvider formatProvider, DateTimeStyles styles)
   at k8s.KubernetesJson.KubernetesDateTimeConverter.Read(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.ReadJsonAndSetMember(Object obj, ReadStack& state, Utf8JsonReader& reader)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonCollectionConverter`2.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, TCollection& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.ReadJsonAndSetMember(Object obj, ReadStack& state, Utf8JsonReader& reader)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.ReadJsonAndSetMember(Object obj, ReadStack& state, Utf8JsonReader& reader)
   at System.Text.Json.Serialization.Converters.ObjectWithParameterizedConstructorConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 utf8Json, JsonTypeInfo jsonTypeInfo, Nullable`1 actualByteCount)
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 json, JsonTypeInfo jsonTypeInfo)
   at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
   at k8s.GenericClient.ReadNamespacedAsync[T](String ns, String name, CancellationToken cancel)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions