argo-workflows icon indicating copy to clipboard operation
argo-workflows copied to clipboard

Java client fails to deserialize WorkflowTemplate

Open jkldrr opened this issue 3 years ago • 7 comments

Checklist

  • [x] Double-checked my configuration.
  • [x] Tested using the latest version.
  • [x] Used the Emissary executor.

Summary

What happened/what you expected to happen? I'm using the java-client to get a WorkflowTemplate (any) form the server. This results in an IllegalStateException.

java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 165 path $.metadata.creationTimestamp
	at com.google.gson.stream.JsonReader.beginObject(JsonReader.java:385) ~[gson-2.9.0.jar:na]

the server returns a valid JSON with the following metadata (double checked that via cURL):

  "metadata": {
    "name": "********",
    "namespace": "argo",
    "uid": "9b1039c0-f710-458e-b510-945337dd4bac",
    "resourceVersion": "15460",
    "generation": 1,
    "creationTimestamp": "2022-06-21T12:57:34Z",
    "labels": {
      "kustomize.toolkit.fluxcd.io/name": "argo-pipelines",
      "kustomize.toolkit.fluxcd.io/namespace": "flux-system"
    }

I also checked the schema.json

"creationTimestamp": {
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time",
"description": "CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\n\nPopulated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata"
}
"io.k8s.apimachinery.pkg.apis.meta.v1.Time": {
"description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON.  Wrappers are provided for many of the factory methods that the time package offers.",
"format": "date-time",
"type": "string"
}

So GSON understandably expects a BEGIN_OBJECT for the creationTimestamp value.

How can I solve this issue?

What version are you running? Server: v3.3.1 Java Client: v3.3.8

Diagnostics

Paste the smallest workflow that reproduces the bug. We must be able to run the workflow. not applicable, relates to the client.


# Logs from the workflow controller:
kubectl logs -n argo deploy/workflow-controller | grep ${workflow} 

# If the workflow's pods have not been created, you can skip the rest of the diagnostics.

# The workflow's pods that are problematic:
kubectl get pod -o yaml -l workflows.argoproj.io/workflow=${workflow},workflow.argoproj.io/phase!=Succeeded

# Logs from in your workflow's wait container, something like:
kubectl logs -c wait -l workflows.argoproj.io/workflow=${workflow},workflow.argoproj.io/phase!=Succeeded

Message from the maintainers:

Impacted by this bug? Give it a 👍. We prioritise the issues with the most 👍.

jkldrr avatar Jul 14 '22 15:07 jkldrr

I also encountered this problem, looking forward to the official solution to it

sanqiuli avatar Jul 26 '22 06:07 sanqiuli

I also faced this issue. An easy way to reproduce it is to mock the response from a server and pass it through io.argoproj.workflow.JSON

My example:

import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import io.argoproj.workflow.JSON;
import io.argoproj.workflow.models.IoArgoprojWorkflowV1alpha1Workflow;

import java.io.StringReader;
import java.lang.reflect.Type;

public class Main {

    public static void main(String[] args) {
        JSON json = new JSON();
        String mockResponseBody = "{\"metadata\":{\"name\":\"test\",\"creationTimestamp\":\"2022-08-01T07:51:04Z\"}}";

        JsonReader jsonReader = new JsonReader(new StringReader(mockResponseBody));
        Type localVarReturnType = new TypeToken<IoArgoprojWorkflowV1alpha1Workflow>(){}.getType();

        json.getGson().fromJson(jsonReader, localVarReturnType);
    }
}

Despite the json is valid, the result is: Caused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 49 path $.metadata.creationTimestamp

mortemate avatar Aug 02 '22 07:08 mortemate

Just wondering which version this issue occurs from? Is there any working 3.3.x Argo Workflows Java SDK?

kulinskyvs avatar Aug 02 '22 08:08 kulinskyvs

I have the issue with 3.3.8 java SDK

vcarluer avatar Aug 02 '22 08:08 vcarluer

Can you try latest?

alexec avatar Aug 02 '22 17:08 alexec

It is maybe not the same case for me. I was migrated from an old argoproj-lab SDK client and no 3.x version works for me in fact. I have: Failed making field 'java.time.Instant#seconds' accessible; either change its visibility or write a custom TypeAdapter for its declaring type So it is related to Gson parsing and basic java type protection. I got the same error with "Expected BEGIN_OBJECT but was STRING" after an ugly patch to the jvm: "--add-opens", "java.base/java.time=ALL-UNNAMED",

But I don't know if it is the same issue I don't want to change this issue target. For information my server is version 3.3.8 if it helps.

vcarluer avatar Aug 02 '22 23:08 vcarluer

io.argoproj.workflow.models.IoArgoprojWorkflowV1alpha1Workflow has an attribute metadata which is an object of io.kubernetes.client.openapi.models.V1ObjectMeta In turn, V1ObjectMeta has creationTimestamp which is custom DateTime format org.joda.time.DateTime. So, the reason of the error is that io.argoproj.workflow.JSON can't deserialize this joda creationTimestamp, since there is no proper type adapter registered for it.

Proposing fix taken from here https://stackoverflow.com/questions/15972856/json-using-gson-library-error-expected-begin-object-but-was-string: In io.argoproj.workflow.JSON in constructor:

public JSON() {
    gson = createGson()
            .registerTypeAdapter(Date.class, dateTypeAdapter)
            .registerTypeAdapter(java.sql.Date.class, sqlDateTypeAdapter)
            .registerTypeAdapter(OffsetDateTime.class, offsetDateTimeTypeAdapter)
            .registerTypeAdapter(LocalDate.class, localDateTypeAdapter)
            .registerTypeAdapter(byte[].class, byteArrayAdapter)
            .registerTypeAdapter(DateTime.class, new JsonDeserializer<DateTime>() {
                @Override
                public DateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
                        throws JsonParseException {
                    return new DateTime(json.getAsString());
                }
            }).create();
}

mortemate avatar Aug 03 '22 07:08 mortemate

Version 3.3.9 solves the issue for me.

jkldrr avatar Aug 19 '22 12:08 jkldrr

It seems that it was fixed after I moved to v3.3.9 but I still have to use --add-opens java.base/java.time=ALL-UNNAMED Is there a way around that? I'm using Java 17.

sunheng avatar Sep 07 '22 15:09 sunheng

After I used v3.3.9, I found that there was still a serialization problem. It was not the original DateTime problem but the java.time.Instant problem. I created a new package io.argoproj.workflow in my project, which added JSON.java, then copy io.argoproj.workflow.JSON in the sdk source code and modify it in my JSON.java, and modify the JSON constructor as follows:

public class JSON {
//add
private InstantTypeAdapter instantTypeAdapter = new InstantTypeAdapter();
//edit
public JSON() {
        gson = createGson()
                .registerTypeAdapter(Date.class, dateTypeAdapter)
                .registerTypeAdapter(Instant.class, instantTypeAdapter)
                .registerTypeAdapter(java.sql.Date.class, sqlDateTypeAdapter)
                .registerTypeAdapter(OffsetDateTime.class, offsetDateTimeTypeAdapter)
                .registerTypeAdapter(LocalDate.class, localDateTypeAdapter)
                .registerTypeAdapter(byte[].class, byteArrayAdapter)
                .create();
    }
//add 
public static class InstantTypeAdapter extends TypeAdapter<Instant> {

        private Instant dateFormat;

        public InstantTypeAdapter() {
        }

        public InstantTypeAdapter(Instant instant) {
            this.dateFormat = dateFormat;
        }

        public void setFormat(Instant instant) {
            this.dateFormat = dateFormat;
        }

        @Override
        public void write(JsonWriter out, Instant date) throws IOException {
            if (date == null) {
                out.nullValue();
            } else {
                out.value(FORMATTER.format(date));
            }
        }

        @Override
        public Instant read(JsonReader in) throws IOException {
            try {
                switch (in.peek()) {
                    case NULL:
                        in.nextNull();
                        return null;
                    default:
                        String date = in.nextString();
                        if (date == null) {
                            return null;
                        }
                        return FORMATTER.parse(date, Instant::from);

                }
            } catch (IllegalArgumentException e) {
                throw new JsonParseException(e);
            }
        }
    }


}

}

sanqiuli avatar Sep 19 '22 04:09 sanqiuli

@alexec could the fix described by @sanqiuli be included in the next release? I can also open a new issue if needed.

kodjo-anipah avatar Sep 20 '22 08:09 kodjo-anipah