Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parsing error when decoding bookmark #70

Closed
MOZGIII opened this issue Jun 29, 2020 · 11 comments
Closed

Parsing error when decoding bookmark #70

MOZGIII opened this issue Jun 29, 2020 · 11 comments

Comments

@MOZGIII
Copy link
Contributor

MOZGIII commented Jun 29, 2020

I'm getting an error

Json(Error(\"invalid type: null, expected a sequence\", line: 2, column: 163))

when parsing this data:

\n{\"type\":\"BOOKMARK\",\"object\":{\"kind\":\"Pod\",\"apiVersion\":\"v1\",\"metadata\":{\"resourceVersion\":\"3845\",\"creationTimestamp\":null},\"spec\":{\"containers\":null},\"status\":{}}}\n

with Pod::try_from_parts.

I get this set of bytes from the K8s API as part of the watch request stream.

Expected output:

WatchEvent::Bookmark(Pod {
  metadata: Some(ObjectMeta {
      resource_version: Some("3845".into()),
      creation_timestamp: None,
      ..ObjectMeta::default()
  }),
  spec: Some(PodSpec {
      containers: vec![],
      ..PodSpec::default()
  }),
  ..Pod::default()
});

Versions:

  • serde 1.0.104
  • serde_json 1.0.52
  • k8s-openapi 0.7.1
@Arnavion
Copy link
Owner

The openapi spec says that PodSpec::containers is not optional, so it's emitted as containers: Vec<...>. In your JSON, it's null, hence the error.

On that basis, the JSON sent by the API server is violating the spec and it would count as an API server bug or a spec bug. (Yes, the spec says it's "required", not that it's non-nullable, but historically the generator that Kubernetes uses uses the former to mean the latter.)

But that said, it seems more logical to me to consider that bookmark events will only have kind, apiVersion and metadata.resourceVersion set to useful values anyway, since everything else is unnecessary. So it would probably make sense to make a custom type with just those properties instead of using the original T that the WatchEvent is parameterized on.

@MOZGIII
Copy link
Contributor Author

MOZGIII commented Jun 29, 2020

On that basis, the JSON sent by the API server is violating the spec and it would count as an API server bug or a spec bug. (Yes, the spec says it's "required", not that it's non-nullable, but historically the generator that Kubernetes uses uses the former to mean the latter.)

😦 I suspected this would be the issue.

One of the more generic workarounds would be to generally relax serde deserializer strictness to be more like Go decoder. It seems like it's something that could be tweaked for the generic case, and some other potential issues with similar effect.

But that said, it seems more logical to me to consider that bookmark events will only have kind, apiVersion and metadata.resourceVersion set to useful values anyway, since everything else is unnecessary. So it would probably make sense to make a custom type with just those properties instead of using the original T that the WatchEvent is parameterized on.

The type of the event is now known upfront, so it'd have to be something like PatchedWatchEvent<T> or WatchEvent<Wrapped<T>> for it to work.

@clux
Copy link

clux commented Jun 29, 2020

Just noting that people have also reported this a similar issue with kind: kube-rs/kube#216

@Arnavion
Copy link
Owner

One of the more generic workarounds would be to generally relax serde deserializer strictness to be more like Go decoder. It seems like it's something that could be tweaked for the generic case, and some other potential issues with similar effect.

It's not a matter of "relaxing". A Vec fundamentally cannot be deserialized from a null. The thing that needs to change is not the deserializer but the deserialized type, namely to either make PodSpec::containers an Option<Vec> or to make it default to an empty Vec, both of which are bad solutions. The former is bad because that means every field in the whole crate that is not an Option today will need to be an Option, because we're ignoring "required"-ness from the openapi spec. The latter is bad because it's a lie.

to be more like Go decoder

Golang is able to deserialize this type not because the decoder is special, but because golang is not type-safe enough to have non-nullable types. PodSpec.Containers in golang is a slice, and golang slices can be null, so there's no deserialization failure.

The type of the event is now known upfront, so it'd have to be something like PatchedWatchEvent<T> or WatchEvent<Wrapped<T>> for it to work.

I'm saying that we can change the definition from:

enum WatchEvent<T> {
    ...,
    Bookmark(T),
}

to:

enum WatchEvent<T> {
    ...,
    Bookmark(Bookmark),
}

struct Bookmark {
    kind: String,
    api_version: String,
    metadata: ObjectMeta,
}

@Arnavion
Copy link
Owner

@clux Your issue I'm more inclined to consider is an openapi spec bug regarding the nullability of ContainerImage::names. Unlike OP's case there is no special consideration like "it's a bookmark event so it's expected to have fewer fields set". So your issue should be reported to github.com/kubernetes/kubernetes (and can be fixup'd in this repo's codegen in the mean time).

@MOZGIII Just to confirm, do all pod watch bookmark events have object.spec.containers set to null ? Or do they generally have the field set?

@MOZGIII
Copy link
Contributor Author

MOZGIII commented Jun 29, 2020

@MOZGIII Just to confirm, do all pod watch bookmark events have object.spec.containers set to null ? Or do they generally have the field set?

The ones I saw are like that, and I haven't seen other bookmarks, so I guess it's safe to assume they're all like that (with null).

@Arnavion
Copy link
Owner

Actually, kind and api_version would presumably always match the T, so they probably don't need to be there either. And ObjectMeta::resource_version is optional, but the bookmark is always going to have it. Furthermore none of the other fields in ObjectMeta are going to be set.

So I'm inclined to make it Bookmark { resource_version: String }

This could become problematic in the future if bookmark events start getting more fields outside the flattened ObjectMeta, but I don't think it's particularly likely. If it does happen we can worry about it then.

Arnavion added a commit that referenced this issue Jun 29, 2020
…instead of the full object.

In some cases, the API server sends a BOOKMARK watch event that can't be
successfully deserialized as the resource type. The example in #70 is of
a watch event for pods where the `spec.containers` field is `null`, even though
the spec requires that `containers` be non-null.

Since the only point of bookmark events is to carry a resource version,
this change makes it so that the deserializer only looks for
the `metadata.resourceVersion` field, in addition to the `apiVersion`
and `kind` fields like before.

Apart from changing the definition of the `WatchEvent::Bookmark` variant,
this change also adds a bound to the type parameter of `WatchEvent` -
it must now also implement `k8s_openapi::Resource` since its API_VERSION
and KIND are still needed to validate the bookmark object.

Fixes #70
@Arnavion
Copy link
Owner

@MOZGIII Can you test with https://github.com/Arnavion/k8s-openapi/compare/fix-70 ?

@MOZGIII
Copy link
Contributor Author

MOZGIII commented Jun 29, 2020

Unit test I have at the parsing module on our end now passes! I'll run our E2E suite tomorrow to be sure, but so far looks good. 👍

@MOZGIII
Copy link
Contributor Author

MOZGIII commented Jun 30, 2020

This bug no longer appears at our E2E tests. 👍

@MOZGIII
Copy link
Contributor Author

MOZGIII commented Jun 30, 2020

Are there any plans to ship this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants