You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@submarine.apache.org by zt...@apache.org on 2020/06/18 05:46:23 UTC

[submarine] branch master updated: SUBMARINE-516. Automatically generate python API from swagger

This is an automated email from the ASF dual-hosted git repository.

ztang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/submarine.git


The following commit(s) were added to refs/heads/master by this push:
     new 0f281ca  SUBMARINE-516. Automatically generate python API from swagger
0f281ca is described below

commit 0f281cad18ed5c1549b1286a9358a1eceba5991e
Author: pingsutw <pi...@gmail.com>
AuthorDate: Thu Jun 18 09:06:30 2020 +0800

    SUBMARINE-516. Automatically generate python API from swagger
    
    ### What is this PR for?
    Generate Python SDK in one command `/dev-support/pysubmarine/gen-sdk.sh`
    
    1. could we merge https://github.com/apache/submarine/pull/312 firstly, the code generated by swagger has some code style issues.
    2. I will add an abstract class for current client API in the next JIRA so that users can more easily use this API.
    
    Here is a [Jupyter example](https://github.com/pingsutw/hadoop-submarine/blob/SUBMARINE-516/submarine-sdk/pysubmarine/example/submarine_experiment_sdk.ipynb)
    
    ### What type of PR is it?
    [Improvement]
    
    ### Todos
    * [ ] - Task
    
    ### What is the Jira issue?
    https://issues.apache.org/jira/browse/SUBMARINE-516
    
    ### How should this be tested?
    https://travis-ci.org/github/pingsutw/hadoop-submarine/builds/699154337
    
    ### Screenshots (if appropriate)
    
    ### Questions:
    * Does the licenses files need update? No
    * Is there breaking changes for older versions? No
    * Does this needs documentation? No
    
    Author: pingsutw <pi...@gmail.com>
    Author: HUAN-PING SU <pi...@gmail.com>
    
    Closes #314 from pingsutw/SUBMARINE-516 and squashes the following commits:
    
    407d7a1 [pingsutw] Rename file
    7c8bcec [pingsutw] update iphnb
    c128a07 [HUAN-PING SU] Update __init__.py
    065a530 [pingsutw] Update ipynb
    5f58830 [pingsutw] Update
    3aa7a79 [pingsutw] Fix code style
    bbb3974 [pingsutw] Fix code style
    5fbbafd [pingsutw] update
    ead12d6 [pingsutw] update
    57a3cc4 [HUAN-PING SU] Update gen-sdk.sh
    fc12f68 [HUAN-PING SU] Delete generate_api.md
    19dd882 [HUAN-PING SU] Update README.md
    098c5cf [pingsutw] SUBMARINE-516. Automatically generate python API from swagger
---
 .gitignore                                         |   1 +
 dev-support/pysubmarine/gen-sdk.sh                 |  66 ++
 .../pysubmarine/license-header.txt                 |  12 +-
 dev-support/pysubmarine/openapi.json               | 363 ++++++++
 dev-support/pysubmarine/swagger_config.json        |   5 +
 docs/submarine-sdk/pysubmarine/README.md           |   6 +-
 docs/submarine-sdk/pysubmarine/generate_api.md     |  31 -
 docs/submarine-sdk/pysubmarine/openapi.yaml        | 244 ------
 .../example/submarine_experiment_sdk.ipynb         | 289 +++++++
 .../pysubmarine/example/submarine_job_sdk.ipynb    | 608 -------------
 submarine-sdk/pysubmarine/github-actions/lint.sh   |   2 +-
 submarine-sdk/pysubmarine/submarine/__init__.py    |  12 +-
 .../pysubmarine/submarine/experiment/__init__.py   |  46 +
 .../submarine/{job => experiment}/api/__init__.py  |  12 +-
 .../submarine/experiment/api/experiment_api.py     | 943 +++++++++++++++++++++
 .../submarine/{job => experiment}/api_client.py    | 215 +++--
 .../submarine/experiment/configuration.py          | 391 +++++++++
 .../pysubmarine/submarine/experiment/exceptions.py | 139 +++
 .../{job => experiment}/models/__init__.py         |  22 +-
 .../submarine/experiment/models/environment.py     | 136 +++
 .../submarine/experiment/models/experiment_meta.py | 240 ++++++
 .../submarine/experiment/models/experiment_spec.py | 188 ++++
 .../models/experiment_task_spec.py}                | 235 ++---
 .../{job => experiment}/models/json_response.py    |  66 +-
 .../submarine/{job => experiment}/rest.py          |  60 +-
 .../pysubmarine/submarine/job/__init__.py          |  40 -
 .../pysubmarine/submarine/job/api/jobs_api.py      | 783 -----------------
 .../pysubmarine/submarine/job/configuration.py     | 257 ------
 .../submarine/job/models/job_library_spec.py       | 233 -----
 .../pysubmarine/submarine/job/models/job_spec.py   | 233 -----
 .../org/apache/submarine/server/Bootstrap.java     |   8 +-
 31 files changed, 3140 insertions(+), 2746 deletions(-)

diff --git a/.gitignore b/.gitignore
index af83984..c4c7f50 100644
--- a/.gitignore
+++ b/.gitignore
@@ -58,6 +58,7 @@ venv.bak/
 *.jar
 target
 dev-support/mini-submarine/database
+dev-support/pysubmarine/sdk
 submarine-workbench/workbench-web/node
 submarine-workbench/workbench-web/node_modules
 submarine-workbench/workbench-web/dist
diff --git a/dev-support/pysubmarine/gen-sdk.sh b/dev-support/pysubmarine/gen-sdk.sh
new file mode 100755
index 0000000..3afc89a
--- /dev/null
+++ b/dev-support/pysubmarine/gen-sdk.sh
@@ -0,0 +1,66 @@
+#!/bin/bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+FWDIR="$(cd "$(dirname "$0")"; pwd)"
+cd "$FWDIR"
+
+SUBMARINE_PROJECT_PATH="$FWDIR/../.."
+SWAGGER_JAR_URL="https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/4.3.1/openapi-generator-cli-4.3.1.jar"
+SWAGGER_CODEGEN_JAR="openapi-generator-cli.jar"
+SWAGGER_CODEGEN_CONF="swagger_config.json"
+SWAGGER_CODEGEN_FILE="openapi.json"
+SDK_OUTPUT_PATH="sdk/python"
+
+submarine_dist_exists=$(find -L "${SUBMARINE_PROJECT_PATH}/submarine-dist/target" -name "submarine-dist-*.tar.gz")
+# Build source code if the package doesn't exist.
+if [[ -z "${submarine_dist_exists}" ]]; then
+  cd "${SUBMARINE_PROJECT_PATH}"
+  mvn clean package -DskipTests
+fi
+
+echo "Generating openAPI 3.0 definition file ..."
+# TODO(pingsutw): generate openapi.json without starting submarine server
+bash ${SUBMARINE_PROJECT_PATH}/submarine-dist/target//submarine-dist-*/submarine-dist-*/bin/submarine-daemon.sh start getMysqlJar
+sleep 5
+rm openapi.json
+wget http://localhost:8080/v1/openapi.json
+bash ${SUBMARINE_PROJECT_PATH}/submarine-dist/target//submarine-dist-*/submarine-dist-*/bin/submarine-daemon.sh stop
+
+openapi_generator_cli_exists=$(find -L "${FWDIR}" -name "openapi-generator-cli*")
+if [[ -z "${openapi_generator_cli_exists}" ]]; then
+  echo "Downloading the swagger-codegen JAR package ..."
+  wget -O "${SWAGGER_CODEGEN_JAR}" "${SWAGGER_JAR_URL}"
+fi
+
+echo "Generating Python SDK for Submarine ..."
+rm -r sdk/
+java -jar ${SWAGGER_CODEGEN_JAR} generate \
+     -i "${SWAGGER_CODEGEN_FILE}" \
+     -g python \
+     -o ${SDK_OUTPUT_PATH} \
+     -c ${SWAGGER_CODEGEN_CONF}
+
+echo "Insert apache license at the top of file ..."
+for filename in $(find ${SDK_OUTPUT_PATH}/submarine/experiment -type f); do
+  echo "$filename"
+  sed -i -e '1 e cat license-header.txt' "$filename"
+done
+
+echo "Move Experiment API to pysubmarine"
+cp -r sdk/python/submarine/experiment ${SUBMARINE_PROJECT_PATH}/submarine-sdk/pysubmarine/submarine/
+
+echo "Fix Python SDK code style"
+${SUBMARINE_PROJECT_PATH}/submarine-sdk/pysubmarine/github-actions/auto-format.sh
diff --git a/submarine-sdk/pysubmarine/submarine/job/api/__init__.py b/dev-support/pysubmarine/license-header.txt
similarity index 66%
copy from submarine-sdk/pysubmarine/submarine/job/api/__init__.py
copy to dev-support/pysubmarine/license-header.txt
index 952d741..09697dc 100644
--- a/submarine-sdk/pysubmarine/submarine/job/api/__init__.py
+++ b/dev-support/pysubmarine/license-header.txt
@@ -1,11 +1,11 @@
 # Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
+# contributor license agreements.  See the NOTICE file distributed with
 # this work for additional information regarding copyright ownership.
 # The ASF licenses this file to You under the Apache License, Version 2.0
 # (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
+# the License.  You may obtain a copy of the License at
 #
-# http://www.apache.org/licenses/LICENSE-2.0
+#     http://www.apache.org/licenses/LICENSE-2.0
 #
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,9 +13,3 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from __future__ import absolute_import
-
-# import apis into api package
-from submarine.job.api.jobs_api import JobsApi
-
-# flake8: noqa
diff --git a/dev-support/pysubmarine/openapi.json b/dev-support/pysubmarine/openapi.json
new file mode 100644
index 0000000..9f4074b
--- /dev/null
+++ b/dev-support/pysubmarine/openapi.json
@@ -0,0 +1,363 @@
+{
+  "openapi" : "3.0.1",
+  "info" : {
+    "title" : "Submarine Experiment API",
+    "description" : "The Submarine REST API allows you to create, list, and get experiments. The\nAPI is hosted under the /v1/experiment route on the Submarine server. For example,\nto list experiments on a server hosted at http://localhost:8080, access\nhttp://localhost:8080/api/v1/experiment/",
+    "termsOfService" : "http://swagger.io/terms/",
+    "contact" : {
+      "email" : "submarine-dev@submarine.apache.org"
+    },
+    "license" : {
+      "name" : "Apache 2.0",
+      "url" : "http://www.apache.org/licenses/LICENSE-2.0.html"
+    },
+    "version" : "0.4.0-SNAPSHOT"
+  },
+  "servers" : [ {
+    "url" : "/api"
+  } ],
+  "paths" : {
+    "/v1/experiment/{id}" : {
+      "get" : {
+        "tags" : [ "experiment" ],
+        "summary" : "Get the experiment's detailed info by id",
+        "operationId" : "getExperiment",
+        "parameters" : [ {
+          "name" : "id",
+          "in" : "path",
+          "required" : true,
+          "schema" : {
+            "type" : "string"
+          }
+        } ],
+        "responses" : {
+          "default" : {
+            "description" : "successful operation",
+            "content" : {
+              "application/json; charset=utf-8" : {
+                "schema" : {
+                  "$ref" : "#/components/schemas/JsonResponse"
+                }
+              }
+            }
+          },
+          "404" : {
+            "description" : "Experiment not found"
+          }
+        }
+      },
+      "delete" : {
+        "tags" : [ "experiment" ],
+        "summary" : "Delete the experiment",
+        "operationId" : "deleteExperiment",
+        "parameters" : [ {
+          "name" : "id",
+          "in" : "path",
+          "required" : true,
+          "schema" : {
+            "type" : "string"
+          }
+        } ],
+        "responses" : {
+          "default" : {
+            "description" : "successful operation",
+            "content" : {
+              "application/json; charset=utf-8" : {
+                "schema" : {
+                  "$ref" : "#/components/schemas/JsonResponse"
+                }
+              }
+            }
+          },
+          "404" : {
+            "description" : "Experiment not found"
+          }
+        }
+      },
+      "patch" : {
+        "tags" : [ "experiment" ],
+        "summary" : "Update the experiment in the submarine server with spec",
+        "operationId" : "patchExperiment",
+        "parameters" : [ {
+          "name" : "id",
+          "in" : "path",
+          "required" : true,
+          "schema" : {
+            "type" : "string"
+          }
+        } ],
+        "requestBody" : {
+          "content" : {
+            "application/yaml" : {
+              "schema" : {
+                "$ref" : "#/components/schemas/ExperimentSpec"
+              }
+            },
+            "application/json" : {
+              "schema" : {
+                "$ref" : "#/components/schemas/ExperimentSpec"
+              }
+            }
+          }
+        },
+        "responses" : {
+          "default" : {
+            "description" : "successful operation",
+            "content" : {
+              "application/json; charset=utf-8" : {
+                "schema" : {
+                  "$ref" : "#/components/schemas/JsonResponse"
+                }
+              }
+            }
+          },
+          "404" : {
+            "description" : "Experiment not found"
+          }
+        }
+      }
+    },
+    "/v1/experiment/logs" : {
+      "get" : {
+        "tags" : [ "experiment" ],
+        "summary" : "List experiment's log",
+        "operationId" : "listLog",
+        "parameters" : [ {
+          "name" : "status",
+          "in" : "query",
+          "schema" : {
+            "type" : "string"
+          }
+        } ],
+        "responses" : {
+          "default" : {
+            "description" : "successful operation",
+            "content" : {
+              "application/json; charset=utf-8" : {
+                "schema" : {
+                  "$ref" : "#/components/schemas/JsonResponse"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/v1/experiment" : {
+      "get" : {
+        "tags" : [ "experiment" ],
+        "summary" : "List experiments",
+        "operationId" : "listExperiments",
+        "parameters" : [ {
+          "name" : "status",
+          "in" : "query",
+          "schema" : {
+            "type" : "string"
+          }
+        } ],
+        "responses" : {
+          "default" : {
+            "description" : "successful operation",
+            "content" : {
+              "application/json; charset=utf-8" : {
+                "schema" : {
+                  "$ref" : "#/components/schemas/JsonResponse"
+                }
+              }
+            }
+          }
+        }
+      },
+      "post" : {
+        "tags" : [ "experiment" ],
+        "summary" : "Create an experiment",
+        "operationId" : "createExperiment",
+        "requestBody" : {
+          "content" : {
+            "application/yaml" : {
+              "schema" : {
+                "$ref" : "#/components/schemas/ExperimentSpec"
+              }
+            },
+            "application/json" : {
+              "schema" : {
+                "$ref" : "#/components/schemas/ExperimentSpec"
+              }
+            }
+          }
+        },
+        "responses" : {
+          "default" : {
+            "description" : "successful operation",
+            "content" : {
+              "application/json; charset=utf-8" : {
+                "schema" : {
+                  "$ref" : "#/components/schemas/JsonResponse"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/v1/experiment/logs/{id}" : {
+      "get" : {
+        "tags" : [ "experiment" ],
+        "summary" : "Log experiment by id",
+        "operationId" : "getLog",
+        "parameters" : [ {
+          "name" : "id",
+          "in" : "path",
+          "required" : true,
+          "schema" : {
+            "type" : "string"
+          }
+        } ],
+        "responses" : {
+          "default" : {
+            "description" : "successful operation",
+            "content" : {
+              "application/json; charset=utf-8" : {
+                "schema" : {
+                  "$ref" : "#/components/schemas/JsonResponse"
+                }
+              }
+            }
+          },
+          "404" : {
+            "description" : "Experiment not found"
+          }
+        }
+      }
+    },
+    "/v1/experiment/ping" : {
+      "get" : {
+        "tags" : [ "experiment" ],
+        "summary" : "Ping submarine server",
+        "description" : "Return the Pong message for test the connectivity",
+        "operationId" : "ping",
+        "responses" : {
+          "200" : {
+            "description" : "successful operation",
+            "content" : {
+              "application/json; charset=utf-8" : {
+                "schema" : {
+                  "type" : "string"
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  },
+  "components" : {
+    "schemas" : {
+      "JsonResponse" : {
+        "type" : "object",
+        "properties" : {
+          "code" : {
+            "type" : "integer",
+            "format" : "int32"
+          },
+          "success" : {
+            "type" : "boolean"
+          },
+          "result" : {
+            "type" : "object"
+          },
+          "attributes" : {
+            "type" : "object",
+            "additionalProperties" : {
+              "type" : "object"
+            }
+          }
+        }
+      },
+      "Environment" : {
+        "type" : "object",
+        "properties" : {
+          "image" : {
+            "type" : "string"
+          }
+        }
+      },
+      "ExperimentMeta" : {
+        "type" : "object",
+        "properties" : {
+          "name" : {
+            "type" : "string"
+          },
+          "namespace" : {
+            "type" : "string"
+          },
+          "framework" : {
+            "type" : "string"
+          },
+          "cmd" : {
+            "type" : "string"
+          },
+          "envVars" : {
+            "type" : "object",
+            "additionalProperties" : {
+              "type" : "string"
+            }
+          }
+        }
+      },
+      "ExperimentSpec" : {
+        "type" : "object",
+        "properties" : {
+          "meta" : {
+            "$ref" : "#/components/schemas/ExperimentMeta"
+          },
+          "environment" : {
+            "$ref" : "#/components/schemas/Environment"
+          },
+          "spec" : {
+            "type" : "object",
+            "additionalProperties" : {
+              "$ref" : "#/components/schemas/ExperimentTaskSpec"
+            }
+          }
+        }
+      },
+      "ExperimentTaskSpec" : {
+        "type" : "object",
+        "properties" : {
+          "replicas" : {
+            "type" : "integer",
+            "format" : "int32"
+          },
+          "resources" : {
+            "type" : "string"
+          },
+          "name" : {
+            "type" : "string"
+          },
+          "image" : {
+            "type" : "string"
+          },
+          "cmd" : {
+            "type" : "string"
+          },
+          "envVars" : {
+            "type" : "object",
+            "additionalProperties" : {
+              "type" : "string"
+            }
+          },
+          "cpu" : {
+            "type" : "string"
+          },
+          "gpu" : {
+            "type" : "string"
+          },
+          "memory" : {
+            "type" : "string"
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/dev-support/pysubmarine/swagger_config.json b/dev-support/pysubmarine/swagger_config.json
new file mode 100644
index 0000000..be5877c
--- /dev/null
+++ b/dev-support/pysubmarine/swagger_config.json
@@ -0,0 +1,5 @@
+{
+  "packageName" : "submarine.experiment",
+  "projectName" : "submarine.experiment",
+  "packageVersion": "0.4.0-SNAPSHOT"
+}
diff --git a/docs/submarine-sdk/pysubmarine/README.md b/docs/submarine-sdk/pysubmarine/README.md
index a401ef6..0e7441b 100644
--- a/docs/submarine-sdk/pysubmarine/README.md
+++ b/docs/submarine-sdk/pysubmarine/README.md
@@ -43,7 +43,9 @@ pytest --cov=submarine -vs
 ./submarine-sdk/pysubmarine/github-actions/lint.sh
 ```
 ## How to generate REST SDK from swagger
-- [Generate REST SDK from swagger](./generate_api.md)
+```bash
+./dev-support/pysubmarine/gen-sdk.sh
+```
 
 ## Easy-to-use model trainers
 - [FM](../../../submarine-sdk/pysubmarine/example/deepfm)
@@ -51,7 +53,7 @@ pytest --cov=submarine -vs
 
 ## Submarine experiment management
 Makes it easy to run distributed or non-distributed TensorFlow, PyTorch experiments on Kubernetes.
-- [mnist example](../../../submarine-sdk/pysubmarine/example/submarine_job_sdk.ipynb)
+- [mnist example](../../../submarine-sdk/pysubmarine/example/submarine_experiment_sdk.ipynb)
 
 ## PySubmarine API Reference
 - [Tracking](tracking.md)
diff --git a/docs/submarine-sdk/pysubmarine/generate_api.md b/docs/submarine-sdk/pysubmarine/generate_api.md
deleted file mode 100644
index c331a42..0000000
--- a/docs/submarine-sdk/pysubmarine/generate_api.md
+++ /dev/null
@@ -1,31 +0,0 @@
-<!---
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-   http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License. See accompanying LICENSE file.
--->
-
-# How to generate Experiment API
-
-### Build Submarine project
-- git clone git@github.com:apache/submarine.git
-- mvn clean package -DskipTests
-### Start Submarine server
-- cd submarine-dist/target/submarine-dist-0.4.0-SNAPSHOT-hadoop-2.9/submarine-dist-0.4.0-SNAPSHOT-hadoop-2.9/
-- ./bin/submarine-daemon.sh start getMysqlJar
-### Generate experiment API
-- open localhost:8080/v1/openapi.json
-- copy `openapi.json` file to [swagger editor](https://editor.swagger.io/)
-- click *generate client* -> *python* to generate experiment API archive
-### Add experiment API to pysubmarine
-- mv `./python-client-generated/swagger-client/*` to `pysubmarine/submarine/job`
-- rename all `swagger_client` in `submarine/job/*.py` to `submarine.job`.
-    - e.g. `from swagger_client.models.job_task_spec import JobTaskSpec` -> `from submarine.job.models.job_task_spec import JobTaskSpec`
-- import experiment API in [\_\_init__.py](../__init__.py)
diff --git a/docs/submarine-sdk/pysubmarine/openapi.yaml b/docs/submarine-sdk/pysubmarine/openapi.yaml
deleted file mode 100644
index d9264eb..0000000
--- a/docs/submarine-sdk/pysubmarine/openapi.yaml
+++ /dev/null
@@ -1,244 +0,0 @@
-openapi: 3.0.1
-info:
-  title: Submarine Experiment API
-  description: 'The Submarine REST API allows you to create, list, and get experiments. TheAPI is hosted under the /v1/jobs route on the Submarine server. For example,to list experiments on a server hosted at http://localhost:8080, accesshttp://localhost:8080/api/v1/jobs/'
-  termsOfService: 'http://swagger.io/terms/'
-  contact:
-    email: submarine-dev@submarine.apache.org
-  license:
-    name: Apache 2.0
-    url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
-  version: 0.4.0-SNAPSHOT
-servers:
-  - url: /api
-paths:
-  /v1/jobs:
-    get:
-      tags:
-        - jobs
-      summary: List jobs
-      operationId: listJob
-      parameters:
-        - name: status
-          in: query
-          schema:
-            type: string
-      responses:
-        default:
-          description: successful operation
-          content:
-            application/json; charset=utf-8:
-              schema:
-                $ref: '#/components/schemas/JsonResponse'
-    post:
-      tags:
-        - jobs
-      summary: Create a job
-      operationId: createJob
-      requestBody:
-        content:
-          application/yaml:
-            schema:
-              $ref: '#/components/schemas/JobSpec'
-          application/json:
-            schema:
-              $ref: '#/components/schemas/JobSpec'
-      responses:
-        default:
-          description: successful operation
-          content:
-            application/json; charset=utf-8:
-              schema:
-                $ref: '#/components/schemas/JsonResponse'
-  '/v1/jobs/{id}':
-    get:
-      tags:
-        - jobs
-      summary: Find job by id
-      operationId: getJob
-      parameters:
-        - name: id
-          in: path
-          required: true
-          schema:
-            type: string
-      responses:
-        '404':
-          description: Job not found
-        default:
-          description: successful operation
-          content:
-            application/json; charset=utf-8:
-              schema:
-                $ref: '#/components/schemas/JsonResponse'
-    delete:
-      tags:
-        - jobs
-      summary: Delete the job
-      operationId: deleteJob
-      parameters:
-        - name: id
-          in: path
-          required: true
-          schema:
-            type: string
-      responses:
-        '404':
-          description: Job not found
-        default:
-          description: successful operation
-          content:
-            application/json; charset=utf-8:
-              schema:
-                $ref: '#/components/schemas/JsonResponse'
-    patch:
-      tags:
-        - jobs
-      summary: Update the job in the submarine server with job spec
-      operationId: patchJob
-      parameters:
-        - name: id
-          in: path
-          required: true
-          schema:
-            type: string
-      requestBody:
-        content:
-          application/yaml:
-            schema:
-              $ref: '#/components/schemas/JobSpec'
-          application/json:
-            schema:
-              $ref: '#/components/schemas/JobSpec'
-      responses:
-        '404':
-          description: Job not found
-        default:
-          description: successful operation
-          content:
-            application/json; charset=utf-8:
-              schema:
-                $ref: '#/components/schemas/JsonResponse'
-  /v1/jobs/logs:
-    get:
-      tags:
-        - jobs
-      summary: Log jobs
-      operationId: listLog
-      parameters:
-        - name: status
-          in: query
-          schema:
-            type: string
-      responses:
-        default:
-          description: successful operation
-          content:
-            application/json; charset=utf-8:
-              schema:
-                $ref: '#/components/schemas/JsonResponse'
-  '/v1/jobs/logs/{id}':
-    get:
-      tags:
-        - jobs
-      summary: Log job by id
-      operationId: getLog
-      parameters:
-        - name: id
-          in: path
-          required: true
-          schema:
-            type: string
-      responses:
-        '404':
-          description: Job not found
-        default:
-          description: successful operation
-          content:
-            application/json; charset=utf-8:
-              schema:
-                $ref: '#/components/schemas/JsonResponse'
-  /v1/jobs/ping:
-    get:
-      tags:
-        - jobs
-      summary: Ping submarine server
-      description: Return the Pong message for test the connectivity
-      operationId: ping
-      responses:
-        '200':
-          description: successful operation
-          content:
-            application/json; charset=utf-8:
-              schema:
-                type: string
-components:
-  schemas:
-    JsonResponse:
-      type: object
-      properties:
-        code:
-          type: integer
-          format: int32
-        success:
-          type: boolean
-        result:
-          type: object
-        attributes:
-          type: object
-          additionalProperties:
-            type: object
-    JobLibrarySpec:
-      type: object
-      properties:
-        name:
-          type: string
-        version:
-          type: string
-        image:
-          type: string
-        cmd:
-          type: string
-        envVars:
-          type: object
-          additionalProperties:
-            type: string
-    JobSpec:
-      type: object
-      properties:
-        name:
-          type: string
-        namespace:
-          type: string
-        librarySpec:
-          $ref: '#/components/schemas/JobLibrarySpec'
-        taskSpecs:
-          type: object
-          additionalProperties:
-            $ref: '#/components/schemas/JobTaskSpec'
-        projects:
-          type: string
-    JobTaskSpec:
-      type: object
-      properties:
-        name:
-          type: string
-        image:
-          type: string
-        cmd:
-          type: string
-        envVars:
-          type: object
-          additionalProperties:
-            type: string
-        resources:
-          type: string
-        replicas:
-          type: integer
-          format: int32
-        cpu:
-          type: string
-        gpu:
-          type: string
-        memory:
-          type: string
diff --git a/submarine-sdk/pysubmarine/example/submarine_experiment_sdk.ipynb b/submarine-sdk/pysubmarine/example/submarine_experiment_sdk.ipynb
new file mode 100644
index 0000000..9c1f5e2
--- /dev/null
+++ b/submarine-sdk/pysubmarine/example/submarine_experiment_sdk.ipynb
@@ -0,0 +1,289 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Sample for Submarine Experiment SDK\n",
+    "\n",
+    "The notebook shows how to use Submarine Experiment SDK to create, get, list, log, delete Submarine Experiment."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 21,
+   "metadata": {
+    "pycharm": {
+     "is_executing": false
+    }
+   },
+   "outputs": [],
+   "source": [
+    "from __future__ import print_function\n",
+    "from submarine.experiment.configuration import Configuration\n",
+    "from submarine.experiment.api_client import ApiClient\n",
+    "from submarine.experiment.api.experiment_api import ExperimentApi\n",
+    "from submarine.experiment.models.environment import Environment\n",
+    "from submarine.experiment.models.experiment_spec import ExperimentSpec\n",
+    "from submarine.experiment.models.experiment_task_spec import ExperimentTaskSpec\n",
+    "from submarine.experiment.models.experiment_meta import ExperimentMeta"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Create Submarine Client"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 22,
+   "metadata": {
+    "pycharm": {
+     "is_executing": false,
+     "name": "#%%\n"
+    }
+   },
+   "outputs": [],
+   "source": [
+    "configuration = Configuration()\n",
+    "configuration.host = 'http://192.168.103.9:8080/api'\n",
+    "api_client = ApiClient(configuration=configuration)\n",
+    "submarine_client = ExperimentApi(api_client=api_client)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "pycharm": {
+     "name": "#%% md\n"
+    }
+   },
+   "source": [
+    "### Define TensorFlow experiment spec¶\n",
+    "Define Submarine spec¶\n",
+    "The demo only creates a worker of TF experiment to run mnist sample."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 23,
+   "metadata": {
+    "pycharm": {
+     "is_executing": false,
+     "name": "#%%\n"
+    }
+   },
+   "outputs": [],
+   "source": [
+    "experiment_meta = ExperimentMeta(name='mnist',\n",
+    "                                 namespace='submarine',\n",
+    "                                 framework='Tensorflow',\n",
+    "                                 cmd='python /var/tf_mnist/mnist_with_summaries.py'\n",
+    "                                    ' --log_dir=/train/log --learning_rate=0.01'\n",
+    "                                    ' --batch_size=150',\n",
+    "                                 env_vars={'ENV1': 'ENV1'})\n",
+    "\n",
+    "worker_spec = ExperimentTaskSpec(resources='cpu=4,memory=2048M',\n",
+    "                                 replicas=1)\n",
+    "\n",
+    "environment = Environment(image='gcr.io/kubeflow-ci/tf-mnist-with-summaries:1.0')\n",
+    "\n",
+    "experiment_spec = ExperimentSpec(meta=experiment_meta,\n",
+    "                                 environment=environment,\n",
+    "                                 spec={'Worker': worker_spec}) \n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Create experiment"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 24,
+   "metadata": {
+    "pycharm": {
+     "name": "#%%\n",
+     "is_executing": false
+    },
+    "scrolled": true
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": "{'attributes': {},\n 'code': 200,\n 'result': {'acceptedTime': '2020-06-18T08:25:14.000+08:00',\n            'createdTime': None,\n            'experimentId': 'experiment_1592409071112_0002',\n            'finishedTime': None,\n            'name': 'mnist',\n            'runningTime': None,\n            'spec': {'environment': {'image': 'gcr.io/kubeflow-ci/tf-mnist-with-summaries:1.0'},\n                     'meta': {'cmd': 'python '\n                                  [...]
+     },
+     "metadata": {},
+     "output_type": "execute_result",
+     "execution_count": 24
+    }
+   ],
+   "source": [
+    "submarine_client.create_experiment(experiment_spec=experiment_spec)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Get the created experiment"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 26,
+   "metadata": {
+    "pycharm": {
+     "name": "#%%\n",
+     "is_executing": false
+    }
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": "{'attributes': {},\n 'code': 200,\n 'result': {'acceptedTime': '2020-06-18T08:25:14.000+08:00',\n            'createdTime': '2020-06-18T08:25:14.000+08:00',\n            'experimentId': 'experiment_1592409071112_0002',\n            'finishedTime': None,\n            'name': 'mnist',\n            'runningTime': '2020-06-18T08:25:16.000+08:00',\n            'spec': {'environment': {'image': 'gcr.io/kubeflow-ci/tf-mnist-with-summaries:1.0'},\n                     'meta' [...]
+     },
+     "metadata": {},
+     "output_type": "execute_result",
+     "execution_count": 26
+    }
+   ],
+   "source": [
+    "id = 'experiment_1592409071112_0002'\n",
+    "submarine_client.get_experiment(id)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### List all running experiments"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 27,
+   "metadata": {
+    "pycharm": {
+     "name": "#%%\n",
+     "is_executing": false
+    }
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": "{'attributes': {},\n 'code': 200,\n 'result': [{'acceptedTime': '2020-06-18T08:25:14.000+08:00',\n             'createdTime': '2020-06-18T08:25:14.000+08:00',\n             'experimentId': 'experiment_1592409071112_0002',\n             'finishedTime': None,\n             'name': 'mnist',\n             'runningTime': '2020-06-18T08:25:16.000+08:00',\n             'spec': {'environment': {'image': 'gcr.io/kubeflow-ci/tf-mnist-with-summaries:1.0'},\n                     [...]
+     },
+     "metadata": {},
+     "output_type": "execute_result",
+     "execution_count": 27
+    }
+   ],
+   "source": [
+    "status = 'running'\n",
+    "submarine_client.list_experiments(status=status)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Get specific experiment training log "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 32,
+   "metadata": {
+    "pycharm": {
+     "name": "#%%\n",
+     "is_executing": false
+    }
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": "{'attributes': {},\n 'code': 200,\n 'result': {'experimentId': 'experiment_1592409071112_0002',\n            'logContent': [{'podLog': ['WARNING:tensorflow:From '\n                                       '/var/tf_mnist/mnist_with_summaries.py:39: '\n                                       'read_data_sets (from '\n                                       'tensorflow.contrib.learn.python.learn.datasets.mnist) '\n                                       'is deprecated and wil [...]
+     },
+     "metadata": {},
+     "output_type": "execute_result",
+     "execution_count": 32
+    }
+   ],
+   "source": [
+    "submarine_client.get_log(id)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Delete the experiment"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 33,
+   "metadata": {
+    "pycharm": {
+     "name": "#%%\n",
+     "is_executing": false
+    }
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": "{'attributes': {},\n 'code': 200,\n 'result': {'acceptedTime': '2020-06-18T08:25:14.000+08:00',\n            'createdTime': '2020-06-18T08:25:14.000+08:00',\n            'experimentId': 'experiment_1592409071112_0002',\n            'finishedTime': '2020-06-18T08:29:02.000+08:00',\n            'name': 'mnist',\n            'runningTime': '2020-06-18T08:25:16.000+08:00',\n            'spec': {'environment': {'image': 'gcr.io/kubeflow-ci/tf-mnist-with-summaries:1.0'},\n [...]
+     },
+     "metadata": {},
+     "output_type": "execute_result",
+     "execution_count": 33
+    }
+   ],
+   "source": [
+    "submarine_client.delete_experiment(id)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.7.6"
+  },
+  "pycharm": {
+   "stem_cell": {
+    "cell_type": "raw",
+    "source": [],
+    "metadata": {
+     "collapsed": false
+    }
+   }
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 1
+}
diff --git a/submarine-sdk/pysubmarine/example/submarine_job_sdk.ipynb b/submarine-sdk/pysubmarine/example/submarine_job_sdk.ipynb
deleted file mode 100644
index 9ff9550..0000000
--- a/submarine-sdk/pysubmarine/example/submarine_job_sdk.ipynb
+++ /dev/null
@@ -1,608 +0,0 @@
-{
- "cells": [
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "## Sample for Submarine Experiment SDK\n",
-    "\n",
-    "The notebook shows how to use Subamrine Experiment SDK to create, get, list, log, delete Submarine Experiment."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 2,
-   "metadata": {
-    "pycharm": {
-     "is_executing": false
-    }
-   },
-   "outputs": [],
-   "source": [
-    "from __future__ import print_function\n",
-    "import submarine\n",
-    "\n",
-    "from submarine import JobLibrarySpec\n",
-    "from submarine import JobTaskSpec\n",
-    "from submarine import JobSpec"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "### Create Submarine Client"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 7,
-   "metadata": {
-    "pycharm": {
-     "is_executing": false,
-     "name": "#%%\n"
-    }
-   },
-   "outputs": [],
-   "source": [
-    "configuration = submarine.Configuration()\n",
-    "configuration.host = 'http://192.168.103.9:8080/api'\n",
-    "api_client = submarine.ApiClient(configuration=configuration)\n",
-    "submarine_client = submarine.JobsApi(api_client=api_client)"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {
-    "pycharm": {
-     "name": "#%% md\n"
-    }
-   },
-   "source": [
-    "### Define TFJob¶\n",
-    "Define Submarine spec¶\n",
-    "The demo only creates a worker of TFJob to run mnist sample."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 8,
-   "metadata": {
-    "pycharm": {
-     "is_executing": false,
-     "name": "#%%\n"
-    }
-   },
-   "outputs": [],
-   "source": [
-    "jobLibrarySpec = JobLibrarySpec(name='tensorflow',\n",
-    "                                version='2.1.0',\n",
-    "                                image='gcr.io/kubeflow-ci/tf-mnist-with-summaries:1.0',\n",
-    "                                cmd='python /var/tf_mnist/mnist_with_summaries.py'\n",
-    "                                    ' --log_dir=/train/log --learning_rate=0.01'\n",
-    "                                    ' --batch_size=150',\n",
-    "                                env_vars={'ENV1': 'ENV1'})\n",
-    "\n",
-    "worker = JobTaskSpec(name='tensorlfow',\n",
-    "                     image=None,\n",
-    "                     cmd=None,\n",
-    "                     env_vars=None,\n",
-    "                     resources='cpu=4,memory=2048M',\n",
-    "                     replicas=1)\n",
-    "\n",
-    "body = JobSpec(name='mnist',\n",
-    "               namespace='submarine',\n",
-    "               library_spec=jobLibrarySpec,\n",
-    "               task_specs={\"Worker\": worker})"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "### Create experiment"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 19,
-   "metadata": {
-    "pycharm": {
-     "name": "#%%\n"
-    },
-    "scrolled": true
-   },
-   "outputs": [
-    {
-     "data": {
-      "text/plain": [
-       "{'attributes': {},\n",
-       " 'code': 200,\n",
-       " 'result': {'acceptedTime': '2020-05-21T18:23:09.000+08:00',\n",
-       "            'createdTime': None,\n",
-       "            'finishedTime': None,\n",
-       "            'jobId': 'job_1590056548552_0001',\n",
-       "            'name': 'mnist',\n",
-       "            'runningTime': None,\n",
-       "            'spec': {'librarySpec': {'cmd': 'python '\n",
-       "                                            '/var/tf_mnist/mnist_with_summaries.py '\n",
-       "                                            '--log_dir=/train/log '\n",
-       "                                            '--learning_rate=0.01 '\n",
-       "                                            '--batch_size=150',\n",
-       "                                     'envVars': {'ENV1': 'ENV1'},\n",
-       "                                     'image': 'gcr.io/kubeflow-ci/tf-mnist-with-summaries:1.0',\n",
-       "                                     'name': 'tensorflow',\n",
-       "                                     'version': '2.1.0'},\n",
-       "                     'name': 'mnist',\n",
-       "                     'namespace': 'submarine',\n",
-       "                     'projects': None,\n",
-       "                     'taskSpecs': {'Worker': {'cmd': None,\n",
-       "                                              'envVars': None,\n",
-       "                                              'image': None,\n",
-       "                                              'name': 'tensorlfow',\n",
-       "                                              'replicas': 1,\n",
-       "                                              'resourceMap': {'cpu': '4',\n",
-       "                                                              'memory': '2048M'},\n",
-       "                                              'resources': 'cpu=4,memory=2048M'}}},\n",
-       "            'status': 'Accepted',\n",
-       "            'uid': '10b226b9-3710-40e9-9f13-9322e03dcb8d'},\n",
-       " 'success': True}"
-      ]
-     },
-     "execution_count": 19,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "submarine_client.create_job(body=body)"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "### Get the created experiment"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 20,
-   "metadata": {
-    "pycharm": {
-     "name": "#%%\n"
-    }
-   },
-   "outputs": [
-    {
-     "data": {
-      "text/plain": [
-       "{'attributes': {},\n",
-       " 'code': 200,\n",
-       " 'result': {'acceptedTime': '2020-05-21T18:23:09.000+08:00',\n",
-       "            'createdTime': '2020-05-21T18:23:09.000+08:00',\n",
-       "            'finishedTime': None,\n",
-       "            'jobId': 'job_1590056548552_0001',\n",
-       "            'name': 'mnist',\n",
-       "            'runningTime': '2020-05-21T18:23:11.000+08:00',\n",
-       "            'spec': {'librarySpec': {'cmd': 'python '\n",
-       "                                            '/var/tf_mnist/mnist_with_summaries.py '\n",
-       "                                            '--log_dir=/train/log '\n",
-       "                                            '--learning_rate=0.01 '\n",
-       "                                            '--batch_size=150',\n",
-       "                                     'envVars': {'ENV1': 'ENV1'},\n",
-       "                                     'image': 'gcr.io/kubeflow-ci/tf-mnist-with-summaries:1.0',\n",
-       "                                     'name': 'tensorflow',\n",
-       "                                     'version': '2.1.0'},\n",
-       "                     'name': 'mnist',\n",
-       "                     'namespace': 'submarine',\n",
-       "                     'projects': None,\n",
-       "                     'taskSpecs': {'Worker': {'cmd': None,\n",
-       "                                              'envVars': None,\n",
-       "                                              'image': None,\n",
-       "                                              'name': 'tensorlfow',\n",
-       "                                              'replicas': 1,\n",
-       "                                              'resourceMap': {'cpu': '4',\n",
-       "                                                              'memory': '2048M'},\n",
-       "                                              'resources': 'cpu=4,memory=2048M'}}},\n",
-       "            'status': 'Running',\n",
-       "            'uid': '10b226b9-3710-40e9-9f13-9322e03dcb8d'},\n",
-       " 'success': True}"
-      ]
-     },
-     "execution_count": 20,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "id = 'job_1590056548552_0001'\n",
-    "submarine_client.get_job(id)"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "### List all running experiments"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 21,
-   "metadata": {
-    "pycharm": {
-     "name": "#%%\n"
-    }
-   },
-   "outputs": [
-    {
-     "data": {
-      "text/plain": [
-       "{'attributes': {},\n",
-       " 'code': 200,\n",
-       " 'result': [{'acceptedTime': '2020-05-21T18:23:09.000+08:00',\n",
-       "             'createdTime': '2020-05-21T18:23:09.000+08:00',\n",
-       "             'finishedTime': None,\n",
-       "             'jobId': 'job_1590056548552_0001',\n",
-       "             'name': 'mnist',\n",
-       "             'runningTime': '2020-05-21T18:23:11.000+08:00',\n",
-       "             'spec': {'librarySpec': {'cmd': 'python '\n",
-       "                                             '/var/tf_mnist/mnist_with_summaries.py '\n",
-       "                                             '--log_dir=/train/log '\n",
-       "                                             '--learning_rate=0.01 '\n",
-       "                                             '--batch_size=150',\n",
-       "                                      'envVars': {'ENV1': 'ENV1'},\n",
-       "                                      'image': 'gcr.io/kubeflow-ci/tf-mnist-with-summaries:1.0',\n",
-       "                                      'name': 'tensorflow',\n",
-       "                                      'version': '2.1.0'},\n",
-       "                      'name': 'mnist',\n",
-       "                      'namespace': 'submarine',\n",
-       "                      'projects': None,\n",
-       "                      'taskSpecs': {'Worker': {'cmd': None,\n",
-       "                                               'envVars': None,\n",
-       "                                               'image': None,\n",
-       "                                               'name': 'tensorlfow',\n",
-       "                                               'replicas': 1,\n",
-       "                                               'resourceMap': {'cpu': '4',\n",
-       "                                                               'memory': '2048M'},\n",
-       "                                               'resources': 'cpu=4,memory=2048M'}}},\n",
-       "             'status': 'Running',\n",
-       "             'uid': '10b226b9-3710-40e9-9f13-9322e03dcb8d'}],\n",
-       " 'success': True}"
-      ]
-     },
-     "execution_count": 21,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "status = 'running'\n",
-    "submarine_client.list_job(status=status)"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "### Get specific experiment training log "
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 24,
-   "metadata": {
-    "pycharm": {
-     "name": "#%%\n"
-    }
-   },
-   "outputs": [
-    {
-     "data": {
-      "text/plain": [
-       "{'attributes': {},\n",
-       " 'code': 200,\n",
-       " 'result': {'jobId': 'job_1590056548552_0001',\n",
-       "            'logContent': [{'podLog': 'WARNING:tensorflow:From '\n",
-       "                                      '/var/tf_mnist/mnist_with_summaries.py:39: '\n",
-       "                                      'read_data_sets (from '\n",
-       "                                      'tensorflow.contrib.learn.python.learn.datasets.mnist) '\n",
-       "                                      'is deprecated and will be removed in a '\n",
-       "                                      'future version.\\n'\n",
-       "                                      'Instructions for updating:\\n'\n",
-       "                                      'Please use alternatives such as '\n",
-       "                                      'official/mnist/dataset.py from '\n",
-       "                                      'tensorflow/models.\\n'\n",
-       "                                      'WARNING:tensorflow:From '\n",
-       "                                      '/usr/local/lib/python2.7/dist-packages/tensorflow/contrib/learn/python/learn/datasets/mnist.py:260: '\n",
-       "                                      'maybe_download (from '\n",
-       "                                      'tensorflow.contrib.learn.python.learn.datasets.base) '\n",
-       "                                      'is deprecated and will be removed in a '\n",
-       "                                      'future version.\\n'\n",
-       "                                      'Instructions for updating:\\n'\n",
-       "                                      'Please write your own downloading '\n",
-       "                                      'logic.\\n'\n",
-       "                                      'WARNING:tensorflow:From '\n",
-       "                                      '/usr/local/lib/python2.7/dist-packages/tensorflow/contrib/learn/python/learn/datasets/base.py:252: '\n",
-       "                                      'wrapped_fn (from '\n",
-       "                                      'tensorflow.contrib.learn.python.learn.datasets.base) '\n",
-       "                                      'is deprecated and will be removed in a '\n",
-       "                                      'future version.\\n'\n",
-       "                                      'Instructions for updating:\\n'\n",
-       "                                      'Please use urllib or similar directly.\\n'\n",
-       "                                      'WARNING:tensorflow:From '\n",
-       "                                      '/usr/local/lib/python2.7/dist-packages/tensorflow/contrib/learn/python/learn/datasets/mnist.py:262: '\n",
-       "                                      'extract_images (from '\n",
-       "                                      'tensorflow.contrib.learn.python.learn.datasets.mnist) '\n",
-       "                                      'is deprecated and will be removed in a '\n",
-       "                                      'future version.\\n'\n",
-       "                                      'Instructions for updating:\\n'\n",
-       "                                      'Please use tf.data to implement this '\n",
-       "                                      'functionality.\\n'\n",
-       "                                      'WARNING:tensorflow:From '\n",
-       "                                      '/usr/local/lib/python2.7/dist-packages/tensorflow/contrib/learn/python/learn/datasets/mnist.py:267: '\n",
-       "                                      'extract_labels (from '\n",
-       "                                      'tensorflow.contrib.learn.python.learn.datasets.mnist) '\n",
-       "                                      'is deprecated and will be removed in a '\n",
-       "                                      'future version.\\n'\n",
-       "                                      'Instructions for updating:\\n'\n",
-       "                                      'Please use tf.data to implement this '\n",
-       "                                      'functionality.\\n'\n",
-       "                                      'WARNING:tensorflow:From '\n",
-       "                                      '/usr/local/lib/python2.7/dist-packages/tensorflow/contrib/learn/python/learn/datasets/mnist.py:290: '\n",
-       "                                      '__init__ (from '\n",
-       "                                      'tensorflow.contrib.learn.python.learn.datasets.mnist) '\n",
-       "                                      'is deprecated and will be removed in a '\n",
-       "                                      'future version.\\n'\n",
-       "                                      'Instructions for updating:\\n'\n",
-       "                                      'Please use alternatives such as '\n",
-       "                                      'official/mnist/dataset.py from '\n",
-       "                                      'tensorflow/models.\\n'\n",
-       "                                      '2020-05-21 10:23:16.050386: I '\n",
-       "                                      'tensorflow/core/platform/cpu_feature_guard.cc:141] '\n",
-       "                                      'Your CPU supports instructions that '\n",
-       "                                      'this TensorFlow binary was not compiled '\n",
-       "                                      'to use: AVX2 FMA\\n'\n",
-       "                                      'Successfully downloaded '\n",
-       "                                      'train-images-idx3-ubyte.gz 9912422 '\n",
-       "                                      'bytes.\\n'\n",
-       "                                      'Extracting '\n",
-       "                                      '/tmp/tensorflow/mnist/input_data/train-images-idx3-ubyte.gz\\n'\n",
-       "                                      'Successfully downloaded '\n",
-       "                                      'train-labels-idx1-ubyte.gz 28881 '\n",
-       "                                      'bytes.\\n'\n",
-       "                                      'Extracting '\n",
-       "                                      '/tmp/tensorflow/mnist/input_data/train-labels-idx1-ubyte.gz\\n'\n",
-       "                                      'Successfully downloaded '\n",
-       "                                      't10k-images-idx3-ubyte.gz 1648877 '\n",
-       "                                      'bytes.\\n'\n",
-       "                                      'Extracting '\n",
-       "                                      '/tmp/tensorflow/mnist/input_data/t10k-images-idx3-ubyte.gz\\n'\n",
-       "                                      'Successfully downloaded '\n",
-       "                                      't10k-labels-idx1-ubyte.gz 4542 bytes.\\n'\n",
-       "                                      'Extracting '\n",
-       "                                      '/tmp/tensorflow/mnist/input_data/t10k-labels-idx1-ubyte.gz\\n'\n",
-       "                                      'Accuracy at step 0: 0.1102\\n'\n",
-       "                                      'Accuracy at step 10: 0.6304\\n'\n",
-       "                                      'Accuracy at step 20: 0.8477\\n'\n",
-       "                                      'Accuracy at step 30: 0.886\\n'\n",
-       "                                      'Accuracy at step 40: 0.9034\\n'\n",
-       "                                      'Accuracy at step 50: 0.9165\\n'\n",
-       "                                      'Accuracy at step 60: 0.9248\\n'\n",
-       "                                      'Accuracy at step 70: 0.9211\\n'\n",
-       "                                      'Accuracy at step 80: 0.9304\\n'\n",
-       "                                      'Accuracy at step 90: 0.9333\\n'\n",
-       "                                      'Adding run metadata for 99\\n'\n",
-       "                                      'Accuracy at step 100: 0.9388\\n'\n",
-       "                                      'Accuracy at step 110: 0.9345\\n'\n",
-       "                                      'Accuracy at step 120: 0.9427\\n'\n",
-       "                                      'Accuracy at step 130: 0.9452\\n'\n",
-       "                                      'Accuracy at step 140: 0.9393\\n'\n",
-       "                                      'Accuracy at step 150: 0.9474\\n'\n",
-       "                                      'Accuracy at step 160: 0.9506\\n'\n",
-       "                                      'Accuracy at step 170: 0.9539\\n'\n",
-       "                                      'Accuracy at step 180: 0.9491\\n'\n",
-       "                                      'Accuracy at step 190: 0.946\\n'\n",
-       "                                      'Adding run metadata for 199\\n'\n",
-       "                                      'Accuracy at step 200: 0.9524\\n'\n",
-       "                                      'Accuracy at step 210: 0.9466\\n'\n",
-       "                                      'Accuracy at step 220: 0.9466\\n'\n",
-       "                                      'Accuracy at step 230: 0.9516\\n'\n",
-       "                                      'Accuracy at step 240: 0.9521\\n'\n",
-       "                                      'Accuracy at step 250: 0.9516\\n'\n",
-       "                                      'Accuracy at step 260: 0.9511\\n'\n",
-       "                                      'Accuracy at step 270: 0.9515\\n'\n",
-       "                                      'Accuracy at step 280: 0.9535\\n'\n",
-       "                                      'Accuracy at step 290: 0.956\\n'\n",
-       "                                      'Adding run metadata for 299\\n'\n",
-       "                                      'Accuracy at step 300: 0.9594\\n'\n",
-       "                                      'Accuracy at step 310: 0.9584\\n'\n",
-       "                                      'Accuracy at step 320: 0.9595\\n'\n",
-       "                                      'Accuracy at step 330: 0.9584\\n'\n",
-       "                                      'Accuracy at step 340: 0.9605\\n'\n",
-       "                                      'Accuracy at step 350: 0.9593\\n'\n",
-       "                                      'Accuracy at step 360: 0.9637\\n'\n",
-       "                                      'Accuracy at step 370: 0.9598\\n'\n",
-       "                                      'Accuracy at step 380: 0.96\\n'\n",
-       "                                      'Accuracy at step 390: 0.9576\\n'\n",
-       "                                      'Adding run metadata for 399\\n'\n",
-       "                                      'Accuracy at step 400: 0.9589\\n'\n",
-       "                                      'Accuracy at step 410: 0.9587\\n'\n",
-       "                                      'Accuracy at step 420: 0.9643\\n'\n",
-       "                                      'Accuracy at step 430: 0.9626\\n'\n",
-       "                                      'Accuracy at step 440: 0.964\\n'\n",
-       "                                      'Accuracy at step 450: 0.9641\\n'\n",
-       "                                      'Accuracy at step 460: 0.9622\\n'\n",
-       "                                      'Accuracy at step 470: 0.9634\\n'\n",
-       "                                      'Accuracy at step 480: 0.9669\\n'\n",
-       "                                      'Accuracy at step 490: 0.9675\\n'\n",
-       "                                      'Adding run metadata for 499\\n'\n",
-       "                                      'Accuracy at step 500: 0.9599\\n'\n",
-       "                                      'Accuracy at step 510: 0.9653\\n'\n",
-       "                                      'Accuracy at step 520: 0.9665\\n'\n",
-       "                                      'Accuracy at step 530: 0.9643\\n'\n",
-       "                                      'Accuracy at step 540: 0.96\\n'\n",
-       "                                      'Accuracy at step 550: 0.9582\\n'\n",
-       "                                      'Accuracy at step 560: 0.9644\\n'\n",
-       "                                      'Accuracy at step 570: 0.9587\\n'\n",
-       "                                      'Accuracy at step 580: 0.9662\\n'\n",
-       "                                      'Accuracy at step 590: 0.9612\\n'\n",
-       "                                      'Adding run metadata for 599\\n'\n",
-       "                                      'Accuracy at step 600: 0.9574\\n'\n",
-       "                                      'Accuracy at step 610: 0.9643\\n'\n",
-       "                                      'Accuracy at step 620: 0.9665\\n'\n",
-       "                                      'Accuracy at step 630: 0.9657\\n'\n",
-       "                                      'Accuracy at step 640: 0.9659\\n'\n",
-       "                                      'Accuracy at step 650: 0.9644\\n'\n",
-       "                                      'Accuracy at step 660: 0.9683\\n'\n",
-       "                                      'Accuracy at step 670: 0.969\\n'\n",
-       "                                      'Accuracy at step 680: 0.9618\\n'\n",
-       "                                      'Accuracy at step 690: 0.968\\n'\n",
-       "                                      'Adding run metadata for 699\\n'\n",
-       "                                      'Accuracy at step 700: 0.9641\\n'\n",
-       "                                      'Accuracy at step 710: 0.9681\\n'\n",
-       "                                      'Accuracy at step 720: 0.967\\n'\n",
-       "                                      'Accuracy at step 730: 0.9661\\n'\n",
-       "                                      'Accuracy at step 740: 0.9679\\n'\n",
-       "                                      'Accuracy at step 750: 0.9676\\n'\n",
-       "                                      'Accuracy at step 760: 0.9681\\n'\n",
-       "                                      'Accuracy at step 770: 0.9668\\n'\n",
-       "                                      'Accuracy at step 780: 0.9657\\n'\n",
-       "                                      'Accuracy at step 790: 0.9597\\n'\n",
-       "                                      'Adding run metadata for 799\\n'\n",
-       "                                      'Accuracy at step 800: 0.964\\n'\n",
-       "                                      'Accuracy at step 810: 0.9655\\n'\n",
-       "                                      'Accuracy at step 820: 0.966\\n'\n",
-       "                                      'Accuracy at step 830: 0.9706\\n'\n",
-       "                                      'Accuracy at step 840: 0.9702\\n'\n",
-       "                                      'Accuracy at step 850: 0.969\\n'\n",
-       "                                      'Accuracy at step 860: 0.97\\n'\n",
-       "                                      'Accuracy at step 870: 0.9712\\n'\n",
-       "                                      'Accuracy at step 880: 0.9677\\n'\n",
-       "                                      'Accuracy at step 890: 0.9655\\n'\n",
-       "                                      'Adding run metadata for 899\\n'\n",
-       "                                      'Accuracy at step 900: 0.9696\\n'\n",
-       "                                      'Accuracy at step 910: 0.9718\\n'\n",
-       "                                      'Accuracy at step 920: 0.9708\\n'\n",
-       "                                      'Accuracy at step 930: 0.971\\n'\n",
-       "                                      'Accuracy at step 940: 0.9697\\n'\n",
-       "                                      'Accuracy at step 950: 0.9698\\n'\n",
-       "                                      'Accuracy at step 960: 0.969\\n'\n",
-       "                                      'Accuracy at step 970: 0.9685\\n'\n",
-       "                                      'Accuracy at step 980: 0.968\\n'\n",
-       "                                      'Accuracy at step 990: 0.9682\\n'\n",
-       "                                      'Adding run metadata for 999\\n',\n",
-       "                            'podName': 'mnist-worker-0'}]},\n",
-       " 'success': True}"
-      ]
-     },
-     "execution_count": 24,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "submarine_client.get_log(id)"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "### Delete the experiment"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 25,
-   "metadata": {
-    "pycharm": {
-     "name": "#%%\n"
-    }
-   },
-   "outputs": [
-    {
-     "data": {
-      "text/plain": [
-       "{'attributes': {},\n",
-       " 'code': 200,\n",
-       " 'result': {'acceptedTime': '2020-05-21T18:23:09.000+08:00',\n",
-       "            'createdTime': '2020-05-21T18:23:09.000+08:00',\n",
-       "            'finishedTime': '2020-05-21T18:35:44.000+08:00',\n",
-       "            'jobId': 'job_1590056548552_0001',\n",
-       "            'name': 'mnist',\n",
-       "            'runningTime': '2020-05-21T18:23:11.000+08:00',\n",
-       "            'spec': {'librarySpec': {'cmd': 'python '\n",
-       "                                            '/var/tf_mnist/mnist_with_summaries.py '\n",
-       "                                            '--log_dir=/train/log '\n",
-       "                                            '--learning_rate=0.01 '\n",
-       "                                            '--batch_size=150',\n",
-       "                                     'envVars': {'ENV1': 'ENV1'},\n",
-       "                                     'image': 'gcr.io/kubeflow-ci/tf-mnist-with-summaries:1.0',\n",
-       "                                     'name': 'tensorflow',\n",
-       "                                     'version': '2.1.0'},\n",
-       "                     'name': 'mnist',\n",
-       "                     'namespace': 'submarine',\n",
-       "                     'projects': None,\n",
-       "                     'taskSpecs': {'Worker': {'cmd': None,\n",
-       "                                              'envVars': None,\n",
-       "                                              'image': None,\n",
-       "                                              'name': 'tensorlfow',\n",
-       "                                              'replicas': 1,\n",
-       "                                              'resourceMap': {'cpu': '4',\n",
-       "                                                              'memory': '2048M'},\n",
-       "                                              'resources': 'cpu=4,memory=2048M'}}},\n",
-       "            'status': 'Deleted',\n",
-       "            'uid': '10b226b9-3710-40e9-9f13-9322e03dcb8d'},\n",
-       " 'success': True}"
-      ]
-     },
-     "execution_count": 25,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "submarine_client.delete_job(id)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": []
-  }
- ],
- "metadata": {
-  "kernelspec": {
-   "display_name": "Python 3",
-   "language": "python",
-   "name": "python3"
-  },
-  "language_info": {
-   "codemirror_mode": {
-    "name": "ipython",
-    "version": 3
-   },
-   "file_extension": ".py",
-   "mimetype": "text/x-python",
-   "name": "python",
-   "nbconvert_exporter": "python",
-   "pygments_lexer": "ipython3",
-   "version": "3.7.6"
-  },
-  "pycharm": {
-   "stem_cell": {
-    "cell_type": "raw",
-    "source": [],
-    "metadata": {
-     "collapsed": false
-    }
-   }
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 1
-}
diff --git a/submarine-sdk/pysubmarine/github-actions/lint.sh b/submarine-sdk/pysubmarine/github-actions/lint.sh
index 83a7fac..07d92c0 100755
--- a/submarine-sdk/pysubmarine/github-actions/lint.sh
+++ b/submarine-sdk/pysubmarine/github-actions/lint.sh
@@ -21,7 +21,7 @@ cd "$FWDIR"
 cd ..
 
 pycodestyle --max-line-length=100  -- submarine tests
-pylint --ignore job --msg-template="{path} ({line},{column}): [{msg_id} {symbol}] {msg}" --rcfile=pylintrc -- submarine tests
+pylint --ignore experiment --msg-template="{path} ({line},{column}): [{msg_id} {symbol}] {msg}" --rcfile=pylintrc -- submarine tests
 ./github-actions/auto-format.sh
 
 GIT_STATUS="$(git status --porcelain)"
diff --git a/submarine-sdk/pysubmarine/submarine/__init__.py b/submarine-sdk/pysubmarine/submarine/__init__.py
index 5484d8b..2f11da9 100644
--- a/submarine-sdk/pysubmarine/submarine/__init__.py
+++ b/submarine-sdk/pysubmarine/submarine/__init__.py
@@ -15,16 +15,14 @@
 
 import submarine.tracking as tracking
 import submarine.tracking.fluent
-from submarine.job import (ApiClient, Configuration, JobLibrarySpec, JobsApi,
-                           JobSpec, JobTaskSpec)
 
 log_param = submarine.tracking.fluent.log_param
 log_metric = submarine.tracking.fluent.log_metric
 set_tracking_uri = tracking.set_tracking_uri
 get_tracking_uri = tracking.get_tracking_uri
 
-__all__ = [
-    "log_metric", "log_param", "set_tracking_uri", "get_tracking_uri",
-    "ApiClient", "JobLibrarySpec", "JobSpec", "JobTaskSpec", "Configuration",
-    "JobsApi"
-]
+__all__ = ["log_metric",
+           "log_param",
+           "set_tracking_uri",
+           "get_tracking_uri"
+           ]
diff --git a/submarine-sdk/pysubmarine/submarine/experiment/__init__.py b/submarine-sdk/pysubmarine/submarine/experiment/__init__.py
new file mode 100644
index 0000000..2f3973c
--- /dev/null
+++ b/submarine-sdk/pysubmarine/submarine/experiment/__init__.py
@@ -0,0 +1,46 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# coding: utf-8
+
+# flake8: noqa
+"""
+    Submarine Experiment API
+
+    The Submarine REST API allows you to create, list, and get experiments. The API is hosted under the /v1/experiment route on the Submarine server. For example, to list experiments on a server hosted at http://localhost:8080, access http://localhost:8080/api/v1/experiment/  # noqa: E501
+
+    The version of the OpenAPI document: 0.4.0-SNAPSHOT
+    Contact: submarine-dev@submarine.apache.org
+    Generated by: https://openapi-generator.tech
+"""
+
+from __future__ import absolute_import
+
+# import apis into sdk package
+from submarine.experiment.api.experiment_api import ExperimentApi
+# import ApiClient
+from submarine.experiment.api_client import ApiClient
+from submarine.experiment.configuration import Configuration
+from submarine.experiment.exceptions import (ApiException, ApiKeyError,
+                                             ApiTypeError, ApiValueError,
+                                             OpenApiException)
+# import models into sdk package
+from submarine.experiment.models.environment import Environment
+from submarine.experiment.models.experiment_meta import ExperimentMeta
+from submarine.experiment.models.experiment_spec import ExperimentSpec
+from submarine.experiment.models.experiment_task_spec import ExperimentTaskSpec
+from submarine.experiment.models.json_response import JsonResponse
+
+__version__ = "0.4.0-SNAPSHOT"
diff --git a/submarine-sdk/pysubmarine/submarine/job/api/__init__.py b/submarine-sdk/pysubmarine/submarine/experiment/api/__init__.py
similarity index 74%
rename from submarine-sdk/pysubmarine/submarine/job/api/__init__.py
rename to submarine-sdk/pysubmarine/submarine/experiment/api/__init__.py
index 952d741..62dc60e 100644
--- a/submarine-sdk/pysubmarine/submarine/job/api/__init__.py
+++ b/submarine-sdk/pysubmarine/submarine/experiment/api/__init__.py
@@ -1,11 +1,11 @@
 # Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
+# contributor license agreements.  See the NOTICE file distributed with
 # this work for additional information regarding copyright ownership.
 # The ASF licenses this file to You under the Apache License, Version 2.0
 # (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
+# the License.  You may obtain a copy of the License at
 #
-# http://www.apache.org/licenses/LICENSE-2.0
+#     http://www.apache.org/licenses/LICENSE-2.0
 #
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
@@ -15,7 +15,7 @@
 
 from __future__ import absolute_import
 
-# import apis into api package
-from submarine.job.api.jobs_api import JobsApi
-
 # flake8: noqa
+
+# import apis into api package
+from submarine.experiment.api.experiment_api import ExperimentApi
diff --git a/submarine-sdk/pysubmarine/submarine/experiment/api/experiment_api.py b/submarine-sdk/pysubmarine/submarine/experiment/api/experiment_api.py
new file mode 100644
index 0000000..b23180c
--- /dev/null
+++ b/submarine-sdk/pysubmarine/submarine/experiment/api/experiment_api.py
@@ -0,0 +1,943 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# coding: utf-8
+
+"""
+    Submarine Experiment API
+
+    The Submarine REST API allows you to create, list, and get experiments. The API is hosted under the /v1/experiment route on the Submarine server. For example, to list experiments on a server hosted at http://localhost:8080, access http://localhost:8080/api/v1/experiment/  # noqa: E501
+
+    The version of the OpenAPI document: 0.4.0-SNAPSHOT
+    Contact: submarine-dev@submarine.apache.org
+    Generated by: https://openapi-generator.tech
+"""
+
+
+from __future__ import absolute_import
+
+import re  # noqa: F401
+
+# python 2 and python 3 compatibility library
+import six
+
+from submarine.experiment.api_client import ApiClient
+from submarine.experiment.exceptions import (  # noqa: F401
+    ApiTypeError,
+    ApiValueError
+)
+
+
+class ExperimentApi(object):
+    """NOTE: This class is auto generated by OpenAPI Generator
+    Ref: https://openapi-generator.tech
+
+    Do not edit the class manually.
+    """
+
+    def __init__(self, api_client=None):
+        if api_client is None:
+            api_client = ApiClient()
+        self.api_client = api_client
+
+    def create_experiment(self, **kwargs):  # noqa: E501
+        """Create an experiment  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.create_experiment(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool: execute request asynchronously
+        :param ExperimentSpec experiment_spec:
+        :param _preload_content: if False, the urllib3.HTTPResponse object will
+                                 be returned without reading/decoding response
+                                 data. Default is True.
+        :param _request_timeout: timeout setting for this request. If one
+                                 number provided, it will be total request
+                                 timeout. It can also be a pair (tuple) of
+                                 (connection, read) timeouts.
+        :return: JsonResponse
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs['_return_http_data_only'] = True
+        return self.create_experiment_with_http_info(**kwargs)  # noqa: E501
+
+    def create_experiment_with_http_info(self, **kwargs):  # noqa: E501
+        """Create an experiment  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.create_experiment_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool: execute request asynchronously
+        :param ExperimentSpec experiment_spec:
+        :param _return_http_data_only: response data without head status code
+                                       and headers
+        :param _preload_content: if False, the urllib3.HTTPResponse object will
+                                 be returned without reading/decoding response
+                                 data. Default is True.
+        :param _request_timeout: timeout setting for this request. If one
+                                 number provided, it will be total request
+                                 timeout. It can also be a pair (tuple) of
+                                 (connection, read) timeouts.
+        :return: tuple(JsonResponse, status_code(int), headers(HTTPHeaderDict))
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        local_var_params = locals()
+
+        all_params = [
+            'experiment_spec'
+        ]
+        all_params.extend(
+            [
+                'async_req',
+                '_return_http_data_only',
+                '_preload_content',
+                '_request_timeout'
+            ]
+        )
+
+        for key, val in six.iteritems(local_var_params['kwargs']):
+            if key not in all_params:
+                raise ApiTypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method create_experiment" % key
+                )
+            local_var_params[key] = val
+        del local_var_params['kwargs']
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if 'experiment_spec' in local_var_params:
+            body_params = local_var_params['experiment_spec']
+        # HTTP header `Accept`
+        header_params['Accept'] = self.api_client.select_header_accept(
+            ['application/json; charset=utf-8'])  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params['Content-Type'] = self.api_client.select_header_content_type(  # noqa: E501
+            ['application/yaml', 'application/json'])  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            '/v1/experiment', 'POST',
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type='JsonResponse',  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=local_var_params.get('async_req'),
+            _return_http_data_only=local_var_params.get('_return_http_data_only'),  # noqa: E501
+            _preload_content=local_var_params.get('_preload_content', True),
+            _request_timeout=local_var_params.get('_request_timeout'),
+            collection_formats=collection_formats)
+
+    def delete_experiment(self, id, **kwargs):  # noqa: E501
+        """Delete the experiment  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_experiment(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool: execute request asynchronously
+        :param str id: (required)
+        :param _preload_content: if False, the urllib3.HTTPResponse object will
+                                 be returned without reading/decoding response
+                                 data. Default is True.
+        :param _request_timeout: timeout setting for this request. If one
+                                 number provided, it will be total request
+                                 timeout. It can also be a pair (tuple) of
+                                 (connection, read) timeouts.
+        :return: JsonResponse
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs['_return_http_data_only'] = True
+        return self.delete_experiment_with_http_info(id, **kwargs)  # noqa: E501
+
+    def delete_experiment_with_http_info(self, id, **kwargs):  # noqa: E501
+        """Delete the experiment  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_experiment_with_http_info(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool: execute request asynchronously
+        :param str id: (required)
+        :param _return_http_data_only: response data without head status code
+                                       and headers
+        :param _preload_content: if False, the urllib3.HTTPResponse object will
+                                 be returned without reading/decoding response
+                                 data. Default is True.
+        :param _request_timeout: timeout setting for this request. If one
+                                 number provided, it will be total request
+                                 timeout. It can also be a pair (tuple) of
+                                 (connection, read) timeouts.
+        :return: tuple(JsonResponse, status_code(int), headers(HTTPHeaderDict))
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        local_var_params = locals()
+
+        all_params = [
+            'id'
+        ]
+        all_params.extend(
+            [
+                'async_req',
+                '_return_http_data_only',
+                '_preload_content',
+                '_request_timeout'
+            ]
+        )
+
+        for key, val in six.iteritems(local_var_params['kwargs']):
+            if key not in all_params:
+                raise ApiTypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method delete_experiment" % key
+                )
+            local_var_params[key] = val
+        del local_var_params['kwargs']
+        # verify the required parameter 'id' is set
+        if self.api_client.client_side_validation and ('id' not in local_var_params or  # noqa: E501
+                                                        local_var_params['id'] is None):  # noqa: E501
+            raise ApiValueError("Missing the required parameter `id` when calling `delete_experiment`")  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if 'id' in local_var_params:
+            path_params['id'] = local_var_params['id']  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params['Accept'] = self.api_client.select_header_accept(
+            ['application/json; charset=utf-8'])  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            '/v1/experiment/{id}', 'DELETE',
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type='JsonResponse',  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=local_var_params.get('async_req'),
+            _return_http_data_only=local_var_params.get('_return_http_data_only'),  # noqa: E501
+            _preload_content=local_var_params.get('_preload_content', True),
+            _request_timeout=local_var_params.get('_request_timeout'),
+            collection_formats=collection_formats)
+
+    def get_experiment(self, id, **kwargs):  # noqa: E501
+        """Get the experiment's detailed info by id  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_experiment(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool: execute request asynchronously
+        :param str id: (required)
+        :param _preload_content: if False, the urllib3.HTTPResponse object will
+                                 be returned without reading/decoding response
+                                 data. Default is True.
+        :param _request_timeout: timeout setting for this request. If one
+                                 number provided, it will be total request
+                                 timeout. It can also be a pair (tuple) of
+                                 (connection, read) timeouts.
+        :return: JsonResponse
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs['_return_http_data_only'] = True
+        return self.get_experiment_with_http_info(id, **kwargs)  # noqa: E501
+
+    def get_experiment_with_http_info(self, id, **kwargs):  # noqa: E501
+        """Get the experiment's detailed info by id  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_experiment_with_http_info(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool: execute request asynchronously
+        :param str id: (required)
+        :param _return_http_data_only: response data without head status code
+                                       and headers
+        :param _preload_content: if False, the urllib3.HTTPResponse object will
+                                 be returned without reading/decoding response
+                                 data. Default is True.
+        :param _request_timeout: timeout setting for this request. If one
+                                 number provided, it will be total request
+                                 timeout. It can also be a pair (tuple) of
+                                 (connection, read) timeouts.
+        :return: tuple(JsonResponse, status_code(int), headers(HTTPHeaderDict))
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        local_var_params = locals()
+
+        all_params = [
+            'id'
+        ]
+        all_params.extend(
+            [
+                'async_req',
+                '_return_http_data_only',
+                '_preload_content',
+                '_request_timeout'
+            ]
+        )
+
+        for key, val in six.iteritems(local_var_params['kwargs']):
+            if key not in all_params:
+                raise ApiTypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_experiment" % key
+                )
+            local_var_params[key] = val
+        del local_var_params['kwargs']
+        # verify the required parameter 'id' is set
+        if self.api_client.client_side_validation and ('id' not in local_var_params or  # noqa: E501
+                                                        local_var_params['id'] is None):  # noqa: E501
+            raise ApiValueError("Missing the required parameter `id` when calling `get_experiment`")  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if 'id' in local_var_params:
+            path_params['id'] = local_var_params['id']  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params['Accept'] = self.api_client.select_header_accept(
+            ['application/json; charset=utf-8'])  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            '/v1/experiment/{id}', 'GET',
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type='JsonResponse',  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=local_var_params.get('async_req'),
+            _return_http_data_only=local_var_params.get('_return_http_data_only'),  # noqa: E501
+            _preload_content=local_var_params.get('_preload_content', True),
+            _request_timeout=local_var_params.get('_request_timeout'),
+            collection_formats=collection_formats)
+
+    def get_log(self, id, **kwargs):  # noqa: E501
+        """Log experiment by id  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_log(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool: execute request asynchronously
+        :param str id: (required)
+        :param _preload_content: if False, the urllib3.HTTPResponse object will
+                                 be returned without reading/decoding response
+                                 data. Default is True.
+        :param _request_timeout: timeout setting for this request. If one
+                                 number provided, it will be total request
+                                 timeout. It can also be a pair (tuple) of
+                                 (connection, read) timeouts.
+        :return: JsonResponse
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs['_return_http_data_only'] = True
+        return self.get_log_with_http_info(id, **kwargs)  # noqa: E501
+
+    def get_log_with_http_info(self, id, **kwargs):  # noqa: E501
+        """Log experiment by id  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_log_with_http_info(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool: execute request asynchronously
+        :param str id: (required)
+        :param _return_http_data_only: response data without head status code
+                                       and headers
+        :param _preload_content: if False, the urllib3.HTTPResponse object will
+                                 be returned without reading/decoding response
+                                 data. Default is True.
+        :param _request_timeout: timeout setting for this request. If one
+                                 number provided, it will be total request
+                                 timeout. It can also be a pair (tuple) of
+                                 (connection, read) timeouts.
+        :return: tuple(JsonResponse, status_code(int), headers(HTTPHeaderDict))
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        local_var_params = locals()
+
+        all_params = [
+            'id'
+        ]
+        all_params.extend(
+            [
+                'async_req',
+                '_return_http_data_only',
+                '_preload_content',
+                '_request_timeout'
+            ]
+        )
+
+        for key, val in six.iteritems(local_var_params['kwargs']):
+            if key not in all_params:
+                raise ApiTypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_log" % key
+                )
+            local_var_params[key] = val
+        del local_var_params['kwargs']
+        # verify the required parameter 'id' is set
+        if self.api_client.client_side_validation and ('id' not in local_var_params or  # noqa: E501
+                                                        local_var_params['id'] is None):  # noqa: E501
+            raise ApiValueError("Missing the required parameter `id` when calling `get_log`")  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if 'id' in local_var_params:
+            path_params['id'] = local_var_params['id']  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params['Accept'] = self.api_client.select_header_accept(
+            ['application/json; charset=utf-8'])  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            '/v1/experiment/logs/{id}', 'GET',
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type='JsonResponse',  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=local_var_params.get('async_req'),
+            _return_http_data_only=local_var_params.get('_return_http_data_only'),  # noqa: E501
+            _preload_content=local_var_params.get('_preload_content', True),
+            _request_timeout=local_var_params.get('_request_timeout'),
+            collection_formats=collection_formats)
+
+    def list_experiments(self, **kwargs):  # noqa: E501
+        """List experiments  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.list_experiments(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool: execute request asynchronously
+        :param str status:
+        :param _preload_content: if False, the urllib3.HTTPResponse object will
+                                 be returned without reading/decoding response
+                                 data. Default is True.
+        :param _request_timeout: timeout setting for this request. If one
+                                 number provided, it will be total request
+                                 timeout. It can also be a pair (tuple) of
+                                 (connection, read) timeouts.
+        :return: JsonResponse
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs['_return_http_data_only'] = True
+        return self.list_experiments_with_http_info(**kwargs)  # noqa: E501
+
+    def list_experiments_with_http_info(self, **kwargs):  # noqa: E501
+        """List experiments  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.list_experiments_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool: execute request asynchronously
+        :param str status:
+        :param _return_http_data_only: response data without head status code
+                                       and headers
+        :param _preload_content: if False, the urllib3.HTTPResponse object will
+                                 be returned without reading/decoding response
+                                 data. Default is True.
+        :param _request_timeout: timeout setting for this request. If one
+                                 number provided, it will be total request
+                                 timeout. It can also be a pair (tuple) of
+                                 (connection, read) timeouts.
+        :return: tuple(JsonResponse, status_code(int), headers(HTTPHeaderDict))
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        local_var_params = locals()
+
+        all_params = [
+            'status'
+        ]
+        all_params.extend(
+            [
+                'async_req',
+                '_return_http_data_only',
+                '_preload_content',
+                '_request_timeout'
+            ]
+        )
+
+        for key, val in six.iteritems(local_var_params['kwargs']):
+            if key not in all_params:
+                raise ApiTypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method list_experiments" % key
+                )
+            local_var_params[key] = val
+        del local_var_params['kwargs']
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+        if 'status' in local_var_params and local_var_params['status'] is not None:  # noqa: E501
+            query_params.append(('status', local_var_params['status']))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params['Accept'] = self.api_client.select_header_accept(
+            ['application/json; charset=utf-8'])  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            '/v1/experiment', 'GET',
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type='JsonResponse',  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=local_var_params.get('async_req'),
+            _return_http_data_only=local_var_params.get('_return_http_data_only'),  # noqa: E501
+            _preload_content=local_var_params.get('_preload_content', True),
+            _request_timeout=local_var_params.get('_request_timeout'),
+            collection_formats=collection_formats)
+
+    def list_log(self, **kwargs):  # noqa: E501
+        """List experiment's log  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.list_log(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool: execute request asynchronously
+        :param str status:
+        :param _preload_content: if False, the urllib3.HTTPResponse object will
+                                 be returned without reading/decoding response
+                                 data. Default is True.
+        :param _request_timeout: timeout setting for this request. If one
+                                 number provided, it will be total request
+                                 timeout. It can also be a pair (tuple) of
+                                 (connection, read) timeouts.
+        :return: JsonResponse
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs['_return_http_data_only'] = True
+        return self.list_log_with_http_info(**kwargs)  # noqa: E501
+
+    def list_log_with_http_info(self, **kwargs):  # noqa: E501
+        """List experiment's log  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.list_log_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool: execute request asynchronously
+        :param str status:
+        :param _return_http_data_only: response data without head status code
+                                       and headers
+        :param _preload_content: if False, the urllib3.HTTPResponse object will
+                                 be returned without reading/decoding response
+                                 data. Default is True.
+        :param _request_timeout: timeout setting for this request. If one
+                                 number provided, it will be total request
+                                 timeout. It can also be a pair (tuple) of
+                                 (connection, read) timeouts.
+        :return: tuple(JsonResponse, status_code(int), headers(HTTPHeaderDict))
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        local_var_params = locals()
+
+        all_params = [
+            'status'
+        ]
+        all_params.extend(
+            [
+                'async_req',
+                '_return_http_data_only',
+                '_preload_content',
+                '_request_timeout'
+            ]
+        )
+
+        for key, val in six.iteritems(local_var_params['kwargs']):
+            if key not in all_params:
+                raise ApiTypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method list_log" % key
+                )
+            local_var_params[key] = val
+        del local_var_params['kwargs']
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+        if 'status' in local_var_params and local_var_params['status'] is not None:  # noqa: E501
+            query_params.append(('status', local_var_params['status']))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params['Accept'] = self.api_client.select_header_accept(
+            ['application/json; charset=utf-8'])  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            '/v1/experiment/logs', 'GET',
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type='JsonResponse',  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=local_var_params.get('async_req'),
+            _return_http_data_only=local_var_params.get('_return_http_data_only'),  # noqa: E501
+            _preload_content=local_var_params.get('_preload_content', True),
+            _request_timeout=local_var_params.get('_request_timeout'),
+            collection_formats=collection_formats)
+
+    def patch_experiment(self, id, **kwargs):  # noqa: E501
+        """Update the experiment in the submarine server with spec  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.patch_experiment(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool: execute request asynchronously
+        :param str id: (required)
+        :param ExperimentSpec experiment_spec:
+        :param _preload_content: if False, the urllib3.HTTPResponse object will
+                                 be returned without reading/decoding response
+                                 data. Default is True.
+        :param _request_timeout: timeout setting for this request. If one
+                                 number provided, it will be total request
+                                 timeout. It can also be a pair (tuple) of
+                                 (connection, read) timeouts.
+        :return: JsonResponse
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs['_return_http_data_only'] = True
+        return self.patch_experiment_with_http_info(id, **kwargs)  # noqa: E501
+
+    def patch_experiment_with_http_info(self, id, **kwargs):  # noqa: E501
+        """Update the experiment in the submarine server with spec  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.patch_experiment_with_http_info(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool: execute request asynchronously
+        :param str id: (required)
+        :param ExperimentSpec experiment_spec:
+        :param _return_http_data_only: response data without head status code
+                                       and headers
+        :param _preload_content: if False, the urllib3.HTTPResponse object will
+                                 be returned without reading/decoding response
+                                 data. Default is True.
+        :param _request_timeout: timeout setting for this request. If one
+                                 number provided, it will be total request
+                                 timeout. It can also be a pair (tuple) of
+                                 (connection, read) timeouts.
+        :return: tuple(JsonResponse, status_code(int), headers(HTTPHeaderDict))
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        local_var_params = locals()
+
+        all_params = [
+            'id',
+            'experiment_spec'
+        ]
+        all_params.extend(
+            [
+                'async_req',
+                '_return_http_data_only',
+                '_preload_content',
+                '_request_timeout'
+            ]
+        )
+
+        for key, val in six.iteritems(local_var_params['kwargs']):
+            if key not in all_params:
+                raise ApiTypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method patch_experiment" % key
+                )
+            local_var_params[key] = val
+        del local_var_params['kwargs']
+        # verify the required parameter 'id' is set
+        if self.api_client.client_side_validation and ('id' not in local_var_params or  # noqa: E501
+                                                        local_var_params['id'] is None):  # noqa: E501
+            raise ApiValueError("Missing the required parameter `id` when calling `patch_experiment`")  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if 'id' in local_var_params:
+            path_params['id'] = local_var_params['id']  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if 'experiment_spec' in local_var_params:
+            body_params = local_var_params['experiment_spec']
+        # HTTP header `Accept`
+        header_params['Accept'] = self.api_client.select_header_accept(
+            ['application/json; charset=utf-8'])  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params['Content-Type'] = self.api_client.select_header_content_type(  # noqa: E501
+            ['application/yaml', 'application/json'])  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            '/v1/experiment/{id}', 'PATCH',
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type='JsonResponse',  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=local_var_params.get('async_req'),
+            _return_http_data_only=local_var_params.get('_return_http_data_only'),  # noqa: E501
+            _preload_content=local_var_params.get('_preload_content', True),
+            _request_timeout=local_var_params.get('_request_timeout'),
+            collection_formats=collection_formats)
+
+    def ping(self, **kwargs):  # noqa: E501
+        """Ping submarine server  # noqa: E501
+
+        Return the Pong message for test the connectivity  # noqa: E501
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.ping(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool: execute request asynchronously
+        :param _preload_content: if False, the urllib3.HTTPResponse object will
+                                 be returned without reading/decoding response
+                                 data. Default is True.
+        :param _request_timeout: timeout setting for this request. If one
+                                 number provided, it will be total request
+                                 timeout. It can also be a pair (tuple) of
+                                 (connection, read) timeouts.
+        :return: str
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs['_return_http_data_only'] = True
+        return self.ping_with_http_info(**kwargs)  # noqa: E501
+
+    def ping_with_http_info(self, **kwargs):  # noqa: E501
+        """Ping submarine server  # noqa: E501
+
+        Return the Pong message for test the connectivity  # noqa: E501
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.ping_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool: execute request asynchronously
+        :param _return_http_data_only: response data without head status code
+                                       and headers
+        :param _preload_content: if False, the urllib3.HTTPResponse object will
+                                 be returned without reading/decoding response
+                                 data. Default is True.
+        :param _request_timeout: timeout setting for this request. If one
+                                 number provided, it will be total request
+                                 timeout. It can also be a pair (tuple) of
+                                 (connection, read) timeouts.
+        :return: tuple(str, status_code(int), headers(HTTPHeaderDict))
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        local_var_params = locals()
+
+        all_params = [
+        ]
+        all_params.extend(
+            [
+                'async_req',
+                '_return_http_data_only',
+                '_preload_content',
+                '_request_timeout'
+            ]
+        )
+
+        for key, val in six.iteritems(local_var_params['kwargs']):
+            if key not in all_params:
+                raise ApiTypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method ping" % key
+                )
+            local_var_params[key] = val
+        del local_var_params['kwargs']
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params['Accept'] = self.api_client.select_header_accept(
+            ['application/json; charset=utf-8'])  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            '/v1/experiment/ping', 'GET',
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type='str',  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=local_var_params.get('async_req'),
+            _return_http_data_only=local_var_params.get('_return_http_data_only'),  # noqa: E501
+            _preload_content=local_var_params.get('_preload_content', True),
+            _request_timeout=local_var_params.get('_request_timeout'),
+            collection_formats=collection_formats)
diff --git a/submarine-sdk/pysubmarine/submarine/job/api_client.py b/submarine-sdk/pysubmarine/submarine/experiment/api_client.py
similarity index 79%
rename from submarine-sdk/pysubmarine/submarine/job/api_client.py
rename to submarine-sdk/pysubmarine/submarine/experiment/api_client.py
index 3267628..46a45e3 100644
--- a/submarine-sdk/pysubmarine/submarine/job/api_client.py
+++ b/submarine-sdk/pysubmarine/submarine/experiment/api_client.py
@@ -1,11 +1,11 @@
 # Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
+# contributor license agreements.  See the NOTICE file distributed with
 # this work for additional information regarding copyright ownership.
 # The ASF licenses this file to You under the Apache License, Version 2.0
 # (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
+# the License.  You may obtain a copy of the License at
 #
-# http://www.apache.org/licenses/LICENSE-2.0
+#     http://www.apache.org/licenses/LICENSE-2.0
 #
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
@@ -17,14 +17,16 @@
 """
     Submarine Experiment API
 
-    The Submarine REST API allows you to create, list, and get experiments. TheAPI is hosted under the /v1/jobs route on the Submarine server. For example,to list experiments on a server hosted at http://localhost:8080, accesshttp://localhost:8080/api/v1/jobs/  # noqa: E501
+    The Submarine REST API allows you to create, list, and get experiments. The API is hosted under the /v1/experiment route on the Submarine server. For example, to list experiments on a server hosted at http://localhost:8080, access http://localhost:8080/api/v1/experiment/  # noqa: E501
 
-    OpenAPI spec version: 0.4.0-SNAPSHOT
+    The version of the OpenAPI document: 0.4.0-SNAPSHOT
     Contact: submarine-dev@submarine.apache.org
-    Generated by: https://github.com/swagger-api/swagger-codegen.git
+    Generated by: https://openapi-generator.tech
 """
+
 from __future__ import absolute_import
 
+import atexit
 import datetime
 import json
 import mimetypes
@@ -35,23 +37,25 @@ from multiprocessing.pool import ThreadPool
 
 # python 2 and python 3 compatibility library
 import six
+from dateutil.parser import parse
 from six.moves.urllib.parse import quote
 
-import submarine.job.models
-from submarine.job import rest
-from submarine.job.configuration import Configuration
+import submarine.experiment.models
+from submarine.experiment import rest
+from submarine.experiment.configuration import Configuration
+from submarine.experiment.exceptions import ApiException, ApiValueError
 
 
 class ApiClient(object):
-    """Generic API client for Swagger client library builds.
+    """Generic API client for OpenAPI client library builds.
 
-    Swagger generic API client. This client handles the client-
+    OpenAPI generic API client. This client handles the client-
     server communication, and is invariant across implementations. Specifics of
-    the methods and models for each application are generated from the Swagger
+    the methods and models for each application are generated from the OpenAPI
     templates.
 
-    NOTE: This class is auto generated by the swagger code generator program.
-    Ref: https://github.com/swagger-api/swagger-codegen
+    NOTE: This class is auto generated by OpenAPI Generator.
+    Ref: https://openapi-generator.tech
     Do not edit the class manually.
 
     :param configuration: .Configuration object for this client
@@ -60,6 +64,8 @@ class ApiClient(object):
         the API.
     :param cookie: a cookie to include in the header when making calls
         to the API
+    :param pool_threads: The number of threads to use for async requests
+        to the API. More threads means more concurrent API requests.
     """
 
     PRIMITIVE_TYPES = (float, bool, bytes, six.text_type) + six.integer_types
@@ -73,28 +79,51 @@ class ApiClient(object):
         'datetime': datetime.datetime,
         'object': object,
     }
+    _pool = None
 
     def __init__(self,
                  configuration=None,
                  header_name=None,
                  header_value=None,
-                 cookie=None):
+                 cookie=None,
+                 pool_threads=1):
         if configuration is None:
-            configuration = Configuration()
+            configuration = Configuration.get_default_copy()
         self.configuration = configuration
+        self.pool_threads = pool_threads
 
-        self.pool = ThreadPool()
         self.rest_client = rest.RESTClientObject(configuration)
         self.default_headers = {}
         if header_name is not None:
             self.default_headers[header_name] = header_value
         self.cookie = cookie
         # Set default User-Agent.
-        self.user_agent = 'Swagger-Codegen/1.0.0/python'
+        self.user_agent = 'OpenAPI-Generator/0.4.0-SNAPSHOT/python'
+        self.client_side_validation = configuration.client_side_validation
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        self.close()
 
-    def __del__(self):
-        self.pool.close()
-        self.pool.join()
+    def close(self):
+        if self._pool:
+            self._pool.close()
+            self._pool.join()
+            self._pool = None
+            if hasattr(atexit, 'unregister'):
+                atexit.unregister(self.close)
+
+    @property
+    def pool(self):
+        """Create thread pool on first request
+         avoids instantiating unused threadpool for blocking clients.
+        """
+        if self._pool is None:
+            atexit.register(self.close)
+            self._pool = ThreadPool(self.pool_threads)
+        return self._pool
 
     @property
     def user_agent(self):
@@ -122,7 +151,8 @@ class ApiClient(object):
                    _return_http_data_only=None,
                    collection_formats=None,
                    _preload_content=True,
-                   _request_timeout=None):
+                   _request_timeout=None,
+                   _host=None):
 
         config = self.configuration
 
@@ -155,10 +185,11 @@ class ApiClient(object):
 
         # post parameters
         if post_params or files:
-            post_params = self.prepare_post_parameters(post_params, files)
+            post_params = post_params if post_params else []
             post_params = self.sanitize_for_serialization(post_params)
             post_params = self.parameters_to_tuples(post_params,
                                                     collection_formats)
+            post_params.extend(self.files_parameters(files))
 
         # auth setting
         self.update_params_for_auth(header_params, query_params, auth_settings)
@@ -168,27 +199,48 @@ class ApiClient(object):
             body = self.sanitize_for_serialization(body)
 
         # request url
-        url = self.configuration.host + resource_path
-
-        # perform request and return response
-        response_data = self.request(method,
-                                     url,
-                                     query_params=query_params,
-                                     headers=header_params,
-                                     post_params=post_params,
-                                     body=body,
-                                     _preload_content=_preload_content,
-                                     _request_timeout=_request_timeout)
+        if _host is None:
+            url = self.configuration.host + resource_path
+        else:
+            # use server/host defined in path or operation instead
+            url = _host + resource_path
+
+        try:
+            # perform request and return response
+            response_data = self.request(method,
+                                         url,
+                                         query_params=query_params,
+                                         headers=header_params,
+                                         post_params=post_params,
+                                         body=body,
+                                         _preload_content=_preload_content,
+                                         _request_timeout=_request_timeout)
+        except ApiException as e:
+            e.body = e.body.decode('utf-8') if six.PY3 else e.body
+            raise e
+
+        content_type = response_data.getheader('content-type')
 
         self.last_response = response_data
 
         return_data = response_data
-        if _preload_content:
-            # deserialize response data
-            if response_type:
-                return_data = self.deserialize(response_data, response_type)
-            else:
-                return_data = None
+
+        if not _preload_content:
+            return return_data
+
+        if six.PY3 and response_type not in ["file", "bytes"]:
+            match = None
+            if content_type is not None:
+                match = re.search(r"charset=([a-zA-Z\-\d]+)[\s\;]?",
+                                  content_type)
+            encoding = match.group(1) if match else "utf-8"
+            response_data.data = response_data.data.decode(encoding)
+
+        # deserialize response data
+        if response_type:
+            return_data = self.deserialize(response_data, response_type)
+        else:
+            return_data = None
 
         if _return_http_data_only:
             return (return_data)
@@ -205,7 +257,7 @@ class ApiClient(object):
             convert to string in iso8601 format.
         If obj is list, sanitize each element in the list.
         If obj is dict, return the dict.
-        If obj is swagger model, return the properties dict.
+        If obj is OpenAPI model, return the properties dict.
 
         :param obj: The data to serialize.
         :return: The serialized form of data.
@@ -226,13 +278,13 @@ class ApiClient(object):
             obj_dict = obj
         else:
             # Convert model obj to dict except
-            # attributes `swagger_types`, `attribute_map`
+            # attributes `openapi_types`, `attribute_map`
             # and attributes which value is not None.
             # Convert attribute name to json key in
             # model definition for request.
             obj_dict = {
                 obj.attribute_map[attr]: getattr(obj, attr)
-                for attr, _ in six.iteritems(obj.swagger_types)
+                for attr, _ in six.iteritems(obj.openapi_types)
                 if getattr(obj, attr) is not None
             }
 
@@ -292,7 +344,7 @@ class ApiClient(object):
             if klass in self.NATIVE_TYPES_MAPPING:
                 klass = self.NATIVE_TYPES_MAPPING[klass]
             else:
-                klass = getattr(submarine.job.models, klass)
+                klass = getattr(submarine.experiment.models, klass)
 
         if klass in self.PRIMITIVE_TYPES:
             return self.__deserialize_primitive(data, klass)
@@ -301,7 +353,7 @@ class ApiClient(object):
         elif klass == datetime.date:
             return self.__deserialize_date(data)
         elif klass == datetime.datetime:
-            return self.__deserialize_datatime(data)
+            return self.__deserialize_datetime(data)
         else:
             return self.__deserialize_model(data, klass)
 
@@ -320,10 +372,11 @@ class ApiClient(object):
                  _return_http_data_only=None,
                  collection_formats=None,
                  _preload_content=True,
-                 _request_timeout=None):
+                 _request_timeout=None,
+                 _host=None):
         """Makes the HTTP request (synchronous) and returns deserialized data.
 
-        To make an async request, set the async_req parameter.
+        To make an async_req request, set the async_req parameter.
 
         :param resource_path: Path to method endpoint.
         :param method: Method to call.
@@ -363,15 +416,14 @@ class ApiClient(object):
                                    post_params, files, response_type,
                                    auth_settings, _return_http_data_only,
                                    collection_formats, _preload_content,
-                                   _request_timeout)
-        else:
-            thread = self.pool.apply_async(
-                self.__call_api,
-                (resource_path, method, path_params, query_params,
-                 header_params, body, post_params, files, response_type,
-                 auth_settings, _return_http_data_only, collection_formats,
-                 _preload_content, _request_timeout))
-        return thread
+                                   _request_timeout, _host)
+
+        return self.pool.apply_async(
+            self.__call_api,
+            (resource_path, method, path_params, query_params, header_params,
+             body, post_params, files, response_type, auth_settings,
+             _return_http_data_only, collection_formats, _preload_content,
+             _request_timeout, _host))
 
     def request(self,
                 method,
@@ -399,10 +451,8 @@ class ApiClient(object):
             return self.rest_client.OPTIONS(url,
                                             query_params=query_params,
                                             headers=headers,
-                                            post_params=post_params,
                                             _preload_content=_preload_content,
-                                            _request_timeout=_request_timeout,
-                                            body=body)
+                                            _request_timeout=_request_timeout)
         elif method == "POST":
             return self.rest_client.POST(url,
                                          query_params=query_params,
@@ -435,8 +485,8 @@ class ApiClient(object):
                                            _request_timeout=_request_timeout,
                                            body=body)
         else:
-            raise ValueError("http method must be `GET`, `HEAD`, `OPTIONS`,"
-                             " `POST`, `PATCH`, `PUT` or `DELETE`.")
+            raise ApiValueError("http method must be `GET`, `HEAD`, `OPTIONS`,"
+                                " `POST`, `PATCH`, `PUT` or `DELETE`.")
 
     def parameters_to_tuples(self, params, collection_formats):
         """Get parameters as list of tuples, formatting collections.
@@ -469,18 +519,14 @@ class ApiClient(object):
                 new_params.append((k, v))
         return new_params
 
-    def prepare_post_parameters(self, post_params=None, files=None):
+    def files_parameters(self, files=None):
         """Builds form parameters.
 
-        :param post_params: Normal form parameters.
         :param files: File parameters.
         :return: Form parameters with files.
         """
         params = []
 
-        if post_params:
-            params = post_params
-
         if files:
             for k, v in six.iteritems(files):
                 if not v:
@@ -542,14 +588,14 @@ class ApiClient(object):
         for auth in auth_settings:
             auth_setting = self.configuration.auth_settings().get(auth)
             if auth_setting:
-                if not auth_setting['value']:
-                    continue
+                if auth_setting['in'] == 'cookie':
+                    headers['Cookie'] = auth_setting['value']
                 elif auth_setting['in'] == 'header':
                     headers[auth_setting['key']] = auth_setting['value']
                 elif auth_setting['in'] == 'query':
                     querys.append((auth_setting['key'], auth_setting['value']))
                 else:
-                    raise ValueError(
+                    raise ApiValueError(
                         'Authentication token must be in `query` or `header`')
 
     def __deserialize_file(self, response):
@@ -592,7 +638,7 @@ class ApiClient(object):
             return data
 
     def __deserialize_object(self, value):
-        """Return a original value.
+        """Return an original value.
 
         :return: object.
         """
@@ -605,7 +651,6 @@ class ApiClient(object):
         :return: date.
         """
         try:
-            from dateutil.parser import parse
             return parse(string).date()
         except ImportError:
             return string
@@ -614,7 +659,7 @@ class ApiClient(object):
                 status=0,
                 reason="Failed to parse `{0}` as date object".format(string))
 
-    def __deserialize_datatime(self, string):
+    def __deserialize_datetime(self, string):
         """Deserializes string to datetime.
 
         The string should be in iso8601 datetime format.
@@ -623,7 +668,6 @@ class ApiClient(object):
         :return: datetime.
         """
         try:
-            from dateutil.parser import parse
             return parse(string)
         except ImportError:
             return string
@@ -633,9 +677,6 @@ class ApiClient(object):
                 reason=(
                     "Failed to parse `{0}` as datetime object".format(string)))
 
-    def __hasattr(self, object, name):
-        return name in object.__class__.__dict__
-
     def __deserialize_model(self, data, klass):
         """Deserializes list or dict to model.
 
@@ -643,27 +684,25 @@ class ApiClient(object):
         :param klass: class literal.
         :return: model object.
         """
+        has_discriminator = False
+        if (hasattr(klass, 'get_real_child_model') and
+                klass.discriminator_value_class_map):
+            has_discriminator = True
 
-        if not klass.swagger_types and not self.__hasattr(
-                klass, 'get_real_child_model'):
+        if not klass.openapi_types and has_discriminator is False:
             return data
 
         kwargs = {}
-        if klass.swagger_types is not None:
-            for attr, attr_type in six.iteritems(klass.swagger_types):
-                if (data is not None and klass.attribute_map[attr] in data and
-                        isinstance(data, (list, dict))):
+        if (data is not None and klass.openapi_types is not None and
+                isinstance(data, (list, dict))):
+            for attr, attr_type in six.iteritems(klass.openapi_types):
+                if klass.attribute_map[attr] in data:
                     value = data[klass.attribute_map[attr]]
                     kwargs[attr] = self.__deserialize(value, attr_type)
 
         instance = klass(**kwargs)
 
-        if (isinstance(instance, dict) and klass.swagger_types is not None and
-                isinstance(data, dict)):
-            for key, value in data.items():
-                if key not in klass.swagger_types:
-                    instance[key] = value
-        if self.__hasattr(instance, 'get_real_child_model'):
+        if has_discriminator:
             klass_name = instance.get_real_child_model(data)
             if klass_name:
                 instance = self.__deserialize(data, klass_name)
diff --git a/submarine-sdk/pysubmarine/submarine/experiment/configuration.py b/submarine-sdk/pysubmarine/submarine/experiment/configuration.py
new file mode 100644
index 0000000..ee281ac
--- /dev/null
+++ b/submarine-sdk/pysubmarine/submarine/experiment/configuration.py
@@ -0,0 +1,391 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# coding: utf-8
+"""
+    Submarine Experiment API
+
+    The Submarine REST API allows you to create, list, and get experiments. The API is hosted under the /v1/experiment route on the Submarine server. For example, to list experiments on a server hosted at http://localhost:8080, access http://localhost:8080/api/v1/experiment/  # noqa: E501
+
+    The version of the OpenAPI document: 0.4.0-SNAPSHOT
+    Contact: submarine-dev@submarine.apache.org
+    Generated by: https://openapi-generator.tech
+"""
+
+from __future__ import absolute_import
+
+import copy
+import logging
+import multiprocessing
+import sys
+
+import six
+import urllib3
+from six.moves import http_client as httplib
+
+
+class Configuration(object):
+    """NOTE: This class is auto generated by OpenAPI Generator
+
+    Ref: https://openapi-generator.tech
+    Do not edit the class manually.
+
+    :param host: Base url
+    :param api_key: Dict to store API key(s).
+      Each entry in the dict specifies an API key.
+      The dict key is the name of the security scheme in the OAS specification.
+      The dict value is the API key secret.
+    :param api_key_prefix: Dict to store API prefix (e.g. Bearer)
+      The dict key is the name of the security scheme in the OAS specification.
+      The dict value is an API key prefix when generating the auth data.
+    :param username: Username for HTTP basic authentication
+    :param password: Password for HTTP basic authentication
+    :param discard_unknown_keys: Boolean value indicating whether to discard
+      unknown properties. A server may send a response that includes additional
+      properties that are not known by the client in the following scenarios:
+      1. The OpenAPI document is incomplete, i.e. it does not match the server
+         implementation.
+      2. The client was generated using an older version of the OpenAPI document
+         and the server has been upgraded since then.
+      If a schema in the OpenAPI document defines the additionalProperties attribute,
+      then all undeclared properties received by the server are injected into the
+      additional properties map. In that case, there are undeclared properties, and
+      nothing to discard.
+
+    """
+
+    _default = None
+
+    def __init__(
+        self,
+        host="http://localhost/api",
+        api_key=None,
+        api_key_prefix=None,
+        username=None,
+        password=None,
+        discard_unknown_keys=False,
+    ):
+        """Constructor
+        """
+        self.host = host
+        """Default Base url
+        """
+        self.temp_folder_path = None
+        """Temp file folder for downloading files
+        """
+        # Authentication Settings
+        self.api_key = {}
+        if api_key:
+            self.api_key = api_key
+        """dict to store API key(s)
+        """
+        self.api_key_prefix = {}
+        if api_key_prefix:
+            self.api_key_prefix = api_key_prefix
+        """dict to store API prefix (e.g. Bearer)
+        """
+        self.refresh_api_key_hook = None
+        """function hook to refresh API key if expired
+        """
+        self.username = username
+        """Username for HTTP basic authentication
+        """
+        self.password = password
+        """Password for HTTP basic authentication
+        """
+        self.discard_unknown_keys = discard_unknown_keys
+        self.logger = {}
+        """Logging Settings
+        """
+        self.logger["package_logger"] = logging.getLogger(
+            "submarine.experiment")
+        self.logger["urllib3_logger"] = logging.getLogger("urllib3")
+        self.logger_format = '%(asctime)s %(levelname)s %(message)s'
+        """Log format
+        """
+        self.logger_stream_handler = None
+        """Log stream handler
+        """
+        self.logger_file_handler = None
+        """Log file handler
+        """
+        self.logger_file = None
+        """Debug file location
+        """
+        self.debug = False
+        """Debug switch
+        """
+
+        self.verify_ssl = True
+        """SSL/TLS verification
+           Set this to false to skip verifying SSL certificate when calling API
+           from https server.
+        """
+        self.ssl_ca_cert = None
+        """Set this to customize the certificate file to verify the peer.
+        """
+        self.cert_file = None
+        """client certificate file
+        """
+        self.key_file = None
+        """client key file
+        """
+        self.assert_hostname = None
+        """Set this to True/False to enable/disable SSL hostname verification.
+        """
+
+        self.connection_pool_maxsize = multiprocessing.cpu_count() * 5
+        """urllib3 connection pool's maximum number of connections saved
+           per pool. urllib3 uses 1 connection as default value, but this is
+           not the best value when you are making a lot of possibly parallel
+           requests to the same host, which is often the case here.
+           cpu_count * 5 is used as default value to increase performance.
+        """
+
+        self.proxy = None
+        """Proxy URL
+        """
+        self.proxy_headers = None
+        """Proxy headers
+        """
+        self.safe_chars_for_path_param = ''
+        """Safe chars for path_param
+        """
+        self.retries = None
+        """Adding retries to override urllib3 default value 3
+        """
+        # Disable client side validation
+        self.client_side_validation = True
+
+    def __deepcopy__(self, memo):
+        cls = self.__class__
+        result = cls.__new__(cls)
+        memo[id(self)] = result
+        for k, v in self.__dict__.items():
+            if k not in ('logger', 'logger_file_handler'):
+                setattr(result, k, copy.deepcopy(v, memo))
+        # shallow copy of loggers
+        result.logger = copy.copy(self.logger)
+        # use setters to configure loggers
+        result.logger_file = self.logger_file
+        result.debug = self.debug
+        return result
+
+    def __setattr__(self, name, value):
+        object.__setattr__(self, name, value)
+
+    @classmethod
+    def set_default(cls, default):
+        """Set default instance of configuration.
+
+        It stores default configuration, which can be
+        returned by get_default_copy method.
+
+        :param default: object of Configuration
+        """
+        cls._default = copy.deepcopy(default)
+
+    @classmethod
+    def get_default_copy(cls):
+        """Return new instance of configuration.
+
+        This method returns newly created, based on default constructor,
+        object of Configuration class or returns a copy of default
+        configuration passed by the set_default method.
+
+        :return: The configuration object.
+        """
+        if cls._default is not None:
+            return copy.deepcopy(cls._default)
+        return Configuration()
+
+    @property
+    def logger_file(self):
+        """The logger file.
+
+        If the logger_file is None, then add stream handler and remove file
+        handler. Otherwise, add file handler and remove stream handler.
+
+        :param value: The logger_file path.
+        :type: str
+        """
+        return self.__logger_file
+
+    @logger_file.setter
+    def logger_file(self, value):
+        """The logger file.
+
+        If the logger_file is None, then add stream handler and remove file
+        handler. Otherwise, add file handler and remove stream handler.
+
+        :param value: The logger_file path.
+        :type: str
+        """
+        self.__logger_file = value
+        if self.__logger_file:
+            # If set logging file,
+            # then add file handler and remove stream handler.
+            self.logger_file_handler = logging.FileHandler(self.__logger_file)
+            self.logger_file_handler.setFormatter(self.logger_formatter)
+            for _, logger in six.iteritems(self.logger):
+                logger.addHandler(self.logger_file_handler)
+
+    @property
+    def debug(self):
+        """Debug status
+
+        :param value: The debug status, True or False.
+        :type: bool
+        """
+        return self.__debug
+
+    @debug.setter
+    def debug(self, value):
+        """Debug status
+
+        :param value: The debug status, True or False.
+        :type: bool
+        """
+        self.__debug = value
+        if self.__debug:
+            # if debug status is True, turn on debug logging
+            for _, logger in six.iteritems(self.logger):
+                logger.setLevel(logging.DEBUG)
+            # turn on httplib debug
+            httplib.HTTPConnection.debuglevel = 1
+        else:
+            # if debug status is False, turn off debug logging,
+            # setting log level to default `logging.WARNING`
+            for _, logger in six.iteritems(self.logger):
+                logger.setLevel(logging.WARNING)
+            # turn off httplib debug
+            httplib.HTTPConnection.debuglevel = 0
+
+    @property
+    def logger_format(self):
+        """The logger format.
+
+        The logger_formatter will be updated when sets logger_format.
+
+        :param value: The format string.
+        :type: str
+        """
+        return self.__logger_format
+
+    @logger_format.setter
+    def logger_format(self, value):
+        """The logger format.
+
+        The logger_formatter will be updated when sets logger_format.
+
+        :param value: The format string.
+        :type: str
+        """
+        self.__logger_format = value
+        self.logger_formatter = logging.Formatter(self.__logger_format)
+
+    def get_api_key_with_prefix(self, identifier):
+        """Gets API key (with prefix if set).
+
+        :param identifier: The identifier of apiKey.
+        :return: The token for api key authentication.
+        """
+        if self.refresh_api_key_hook is not None:
+            self.refresh_api_key_hook(self)
+        key = self.api_key.get(identifier)
+        if key:
+            prefix = self.api_key_prefix.get(identifier)
+            if prefix:
+                return "%s %s" % (prefix, key)
+            else:
+                return key
+
+    def get_basic_auth_token(self):
+        """Gets HTTP basic authentication header (string).
+
+        :return: The token for basic HTTP authentication.
+        """
+        username = ""
+        if self.username is not None:
+            username = self.username
+        password = ""
+        if self.password is not None:
+            password = self.password
+        return urllib3.util.make_headers(basic_auth=username + ':' +
+                                         password).get('authorization')
+
+    def auth_settings(self):
+        """Gets Auth Settings dict for api client.
+
+        :return: The Auth Settings information dict.
+        """
+        auth = {}
+        return auth
+
+    def to_debug_report(self):
+        """Gets the essential information for debugging.
+
+        :return: The report for debugging.
+        """
+        return "Python SDK Debug Report:\n"\
+               "OS: {env}\n"\
+               "Python Version: {pyversion}\n"\
+               "Version of the API: 0.4.0-SNAPSHOT\n"\
+               "SDK Package Version: 0.4.0-SNAPSHOT".\
+               format(env=sys.platform, pyversion=sys.version)
+
+    def get_host_settings(self):
+        """Gets an array of host settings
+
+        :return: An array of host settings
+        """
+        return [{
+            'url': "/api",
+            'description': "No description provided",
+        }]
+
+    def get_host_from_settings(self, index, variables=None):
+        """Gets host URL based on the index and variables
+        :param index: array index of the host settings
+        :param variables: hash of variable and the corresponding value
+        :return: URL based on host settings
+        """
+        variables = {} if variables is None else variables
+        servers = self.get_host_settings()
+
+        try:
+            server = servers[index]
+        except IndexError:
+            raise ValueError(
+                "Invalid index {0} when selecting the host settings. "
+                "Must be less than {1}".format(index, len(servers)))
+
+        url = server['url']
+
+        # go through variables and replace placeholders
+        for variable_name, variable in server['variables'].items():
+            used_value = variables.get(variable_name, variable['default_value'])
+
+            if 'enum_values' in variable \
+                    and used_value not in variable['enum_values']:
+                raise ValueError(
+                    "The variable `{0}` in the host URL has invalid value "
+                    "{1}. Must be {2}.".format(variable_name,
+                                               variables[variable_name],
+                                               variable['enum_values']))
+
+            url = url.replace("{" + variable_name + "}", used_value)
+
+        return url
diff --git a/submarine-sdk/pysubmarine/submarine/experiment/exceptions.py b/submarine-sdk/pysubmarine/submarine/experiment/exceptions.py
new file mode 100644
index 0000000..dc56965
--- /dev/null
+++ b/submarine-sdk/pysubmarine/submarine/experiment/exceptions.py
@@ -0,0 +1,139 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# coding: utf-8
+"""
+    Submarine Experiment API
+
+    The Submarine REST API allows you to create, list, and get experiments. The API is hosted under the /v1/experiment route on the Submarine server. For example, to list experiments on a server hosted at http://localhost:8080, access http://localhost:8080/api/v1/experiment/  # noqa: E501
+
+    The version of the OpenAPI document: 0.4.0-SNAPSHOT
+    Contact: submarine-dev@submarine.apache.org
+    Generated by: https://openapi-generator.tech
+"""
+
+import six
+
+
+class OpenApiException(Exception):
+    """The base exception class for all OpenAPIExceptions"""
+
+
+class ApiTypeError(OpenApiException, TypeError):
+
+    def __init__(self,
+                 msg,
+                 path_to_item=None,
+                 valid_classes=None,
+                 key_type=None):
+        """ Raises an exception for TypeErrors
+
+        Args:
+            msg (str): the exception message
+
+        Keyword Args:
+            path_to_item (list): a list of keys an indices to get to the
+                                 current_item
+                                 None if unset
+            valid_classes (tuple): the primitive classes that current item
+                                   should be an instance of
+                                   None if unset
+            key_type (bool): False if our value is a value in a dict
+                             True if it is a key in a dict
+                             False if our item is an item in a list
+                             None if unset
+        """
+        self.path_to_item = path_to_item
+        self.valid_classes = valid_classes
+        self.key_type = key_type
+        full_msg = msg
+        if path_to_item:
+            full_msg = "{0} at {1}".format(msg, render_path(path_to_item))
+        super(ApiTypeError, self).__init__(full_msg)
+
+
+class ApiValueError(OpenApiException, ValueError):
+
+    def __init__(self, msg, path_to_item=None):
+        """
+        Args:
+            msg (str): the exception message
+
+        Keyword Args:
+            path_to_item (list) the path to the exception in the
+                received_data dict. None if unset
+        """
+
+        self.path_to_item = path_to_item
+        full_msg = msg
+        if path_to_item:
+            full_msg = "{0} at {1}".format(msg, render_path(path_to_item))
+        super(ApiValueError, self).__init__(full_msg)
+
+
+class ApiKeyError(OpenApiException, KeyError):
+
+    def __init__(self, msg, path_to_item=None):
+        """
+        Args:
+            msg (str): the exception message
+
+        Keyword Args:
+            path_to_item (None/list) the path to the exception in the
+                received_data dict
+        """
+        self.path_to_item = path_to_item
+        full_msg = msg
+        if path_to_item:
+            full_msg = "{0} at {1}".format(msg, render_path(path_to_item))
+        super(ApiKeyError, self).__init__(full_msg)
+
+
+class ApiException(OpenApiException):
+
+    def __init__(self, status=None, reason=None, http_resp=None):
+        if http_resp:
+            self.status = http_resp.status
+            self.reason = http_resp.reason
+            self.body = http_resp.data
+            self.headers = http_resp.getheaders()
+        else:
+            self.status = status
+            self.reason = reason
+            self.body = None
+            self.headers = None
+
+    def __str__(self):
+        """Custom error messages for exception"""
+        error_message = "({0})\n"\
+                        "Reason: {1}\n".format(self.status, self.reason)
+        if self.headers:
+            error_message += "HTTP response headers: {0}\n".format(self.headers)
+
+        if self.body:
+            error_message += "HTTP response body: {0}\n".format(self.body)
+
+        return error_message
+
+
+def render_path(path_to_item):
+    """Returns a string representation of a path"""
+    result = ""
+    for pth in path_to_item:
+        if isinstance(pth, six.integer_types):
+            result += "[{0}]".format(pth)
+        else:
+            result += "['{0}']".format(pth)
+    return result
diff --git a/submarine-sdk/pysubmarine/submarine/job/models/__init__.py b/submarine-sdk/pysubmarine/submarine/experiment/models/__init__.py
similarity index 50%
rename from submarine-sdk/pysubmarine/submarine/job/models/__init__.py
rename to submarine-sdk/pysubmarine/submarine/experiment/models/__init__.py
index c827ebe..4976022 100644
--- a/submarine-sdk/pysubmarine/submarine/job/models/__init__.py
+++ b/submarine-sdk/pysubmarine/submarine/experiment/models/__init__.py
@@ -1,11 +1,11 @@
 # Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
+# contributor license agreements.  See the NOTICE file distributed with
 # this work for additional information regarding copyright ownership.
 # The ASF licenses this file to You under the Apache License, Version 2.0
 # (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
+# the License.  You may obtain a copy of the License at
 #
-# http://www.apache.org/licenses/LICENSE-2.0
+#     http://www.apache.org/licenses/LICENSE-2.0
 #
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
@@ -19,17 +19,19 @@
 """
     Submarine Experiment API
 
-    The Submarine REST API allows you to create, list, and get experiments. TheAPI is hosted under the /v1/jobs route on the Submarine server. For example,to list experiments on a server hosted at http://localhost:8080, accesshttp://localhost:8080/api/v1/jobs/  # noqa: E501
+    The Submarine REST API allows you to create, list, and get experiments. The API is hosted under the /v1/experiment route on the Submarine server. For example, to list experiments on a server hosted at http://localhost:8080, access http://localhost:8080/api/v1/experiment/  # noqa: E501
 
-    OpenAPI spec version: 0.4.0-SNAPSHOT
+    The version of the OpenAPI document: 0.4.0-SNAPSHOT
     Contact: submarine-dev@submarine.apache.org
-    Generated by: https://github.com/swagger-api/swagger-codegen.git
+    Generated by: https://openapi-generator.tech
 """
 
+
 from __future__ import absolute_import
 
 # import models into model package
-from submarine.job.models.job_library_spec import JobLibrarySpec
-from submarine.job.models.job_spec import JobSpec
-from submarine.job.models.job_task_spec import JobTaskSpec
-from submarine.job.models.json_response import JsonResponse
+from submarine.experiment.models.environment import Environment
+from submarine.experiment.models.experiment_meta import ExperimentMeta
+from submarine.experiment.models.experiment_spec import ExperimentSpec
+from submarine.experiment.models.experiment_task_spec import ExperimentTaskSpec
+from submarine.experiment.models.json_response import JsonResponse
diff --git a/submarine-sdk/pysubmarine/submarine/experiment/models/environment.py b/submarine-sdk/pysubmarine/submarine/experiment/models/environment.py
new file mode 100644
index 0000000..d362902
--- /dev/null
+++ b/submarine-sdk/pysubmarine/submarine/experiment/models/environment.py
@@ -0,0 +1,136 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# coding: utf-8
+
+"""
+    Submarine Experiment API
+
+    The Submarine REST API allows you to create, list, and get experiments. The API is hosted under the /v1/experiment route on the Submarine server. For example, to list experiments on a server hosted at http://localhost:8080, access http://localhost:8080/api/v1/experiment/  # noqa: E501
+
+    The version of the OpenAPI document: 0.4.0-SNAPSHOT
+    Contact: submarine-dev@submarine.apache.org
+    Generated by: https://openapi-generator.tech
+"""
+
+
+import pprint
+import re  # noqa: F401
+
+import six
+
+from submarine.experiment.configuration import Configuration
+
+
+class Environment(object):
+    """NOTE: This class is auto generated by OpenAPI Generator.
+    Ref: https://openapi-generator.tech
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      openapi_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    openapi_types = {
+        'image': 'str'
+    }
+
+    attribute_map = {
+        'image': 'image'
+    }
+
+    def __init__(self, image=None, local_vars_configuration=None):  # noqa: E501
+        """Environment - a model defined in OpenAPI"""  # noqa: E501
+        if local_vars_configuration is None:
+            local_vars_configuration = Configuration()
+        self.local_vars_configuration = local_vars_configuration
+
+        self._image = None
+        self.discriminator = None
+
+        if image is not None:
+            self.image = image
+
+    @property
+    def image(self):
+        """Gets the image of this Environment.  # noqa: E501
+
+
+        :return: The image of this Environment.  # noqa: E501
+        :rtype: str
+        """
+        return self._image
+
+    @image.setter
+    def image(self, image):
+        """Sets the image of this Environment.
+
+
+        :param image: The image of this Environment.  # noqa: E501
+        :type: str
+        """
+
+        self._image = image
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.openapi_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(map(
+                    lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
+                    value
+                ))
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(map(
+                    lambda item: (item[0], item[1].to_dict())
+                    if hasattr(item[1], "to_dict") else item,
+                    value.items()
+                ))
+            else:
+                result[attr] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, Environment):
+            return False
+
+        return self.to_dict() == other.to_dict()
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        if not isinstance(other, Environment):
+            return True
+
+        return self.to_dict() != other.to_dict()
diff --git a/submarine-sdk/pysubmarine/submarine/experiment/models/experiment_meta.py b/submarine-sdk/pysubmarine/submarine/experiment/models/experiment_meta.py
new file mode 100644
index 0000000..b5cbe9c
--- /dev/null
+++ b/submarine-sdk/pysubmarine/submarine/experiment/models/experiment_meta.py
@@ -0,0 +1,240 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# coding: utf-8
+
+"""
+    Submarine Experiment API
+
+    The Submarine REST API allows you to create, list, and get experiments. The API is hosted under the /v1/experiment route on the Submarine server. For example, to list experiments on a server hosted at http://localhost:8080, access http://localhost:8080/api/v1/experiment/  # noqa: E501
+
+    The version of the OpenAPI document: 0.4.0-SNAPSHOT
+    Contact: submarine-dev@submarine.apache.org
+    Generated by: https://openapi-generator.tech
+"""
+
+
+import pprint
+import re  # noqa: F401
+
+import six
+
+from submarine.experiment.configuration import Configuration
+
+
+class ExperimentMeta(object):
+    """NOTE: This class is auto generated by OpenAPI Generator.
+    Ref: https://openapi-generator.tech
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      openapi_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    openapi_types = {
+        'name': 'str',
+        'namespace': 'str',
+        'framework': 'str',
+        'cmd': 'str',
+        'env_vars': 'dict(str, str)'
+    }
+
+    attribute_map = {
+        'name': 'name',
+        'namespace': 'namespace',
+        'framework': 'framework',
+        'cmd': 'cmd',
+        'env_vars': 'envVars'
+    }
+
+    def __init__(self, name=None, namespace=None, framework=None, cmd=None, env_vars=None, local_vars_configuration=None):  # noqa: E501
+        """ExperimentMeta - a model defined in OpenAPI"""  # noqa: E501
+        if local_vars_configuration is None:
+            local_vars_configuration = Configuration()
+        self.local_vars_configuration = local_vars_configuration
+
+        self._name = None
+        self._namespace = None
+        self._framework = None
+        self._cmd = None
+        self._env_vars = None
+        self.discriminator = None
+
+        if name is not None:
+            self.name = name
+        if namespace is not None:
+            self.namespace = namespace
+        if framework is not None:
+            self.framework = framework
+        if cmd is not None:
+            self.cmd = cmd
+        if env_vars is not None:
+            self.env_vars = env_vars
+
+    @property
+    def name(self):
+        """Gets the name of this ExperimentMeta.  # noqa: E501
+
+
+        :return: The name of this ExperimentMeta.  # noqa: E501
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        """Sets the name of this ExperimentMeta.
+
+
+        :param name: The name of this ExperimentMeta.  # noqa: E501
+        :type: str
+        """
+
+        self._name = name
+
+    @property
+    def namespace(self):
+        """Gets the namespace of this ExperimentMeta.  # noqa: E501
+
+
+        :return: The namespace of this ExperimentMeta.  # noqa: E501
+        :rtype: str
+        """
+        return self._namespace
+
+    @namespace.setter
+    def namespace(self, namespace):
+        """Sets the namespace of this ExperimentMeta.
+
+
+        :param namespace: The namespace of this ExperimentMeta.  # noqa: E501
+        :type: str
+        """
+
+        self._namespace = namespace
+
+    @property
+    def framework(self):
+        """Gets the framework of this ExperimentMeta.  # noqa: E501
+
+
+        :return: The framework of this ExperimentMeta.  # noqa: E501
+        :rtype: str
+        """
+        return self._framework
+
+    @framework.setter
+    def framework(self, framework):
+        """Sets the framework of this ExperimentMeta.
+
+
+        :param framework: The framework of this ExperimentMeta.  # noqa: E501
+        :type: str
+        """
+
+        self._framework = framework
+
+    @property
+    def cmd(self):
+        """Gets the cmd of this ExperimentMeta.  # noqa: E501
+
+
+        :return: The cmd of this ExperimentMeta.  # noqa: E501
+        :rtype: str
+        """
+        return self._cmd
+
+    @cmd.setter
+    def cmd(self, cmd):
+        """Sets the cmd of this ExperimentMeta.
+
+
+        :param cmd: The cmd of this ExperimentMeta.  # noqa: E501
+        :type: str
+        """
+
+        self._cmd = cmd
+
+    @property
+    def env_vars(self):
+        """Gets the env_vars of this ExperimentMeta.  # noqa: E501
+
+
+        :return: The env_vars of this ExperimentMeta.  # noqa: E501
+        :rtype: dict(str, str)
+        """
+        return self._env_vars
+
+    @env_vars.setter
+    def env_vars(self, env_vars):
+        """Sets the env_vars of this ExperimentMeta.
+
+
+        :param env_vars: The env_vars of this ExperimentMeta.  # noqa: E501
+        :type: dict(str, str)
+        """
+
+        self._env_vars = env_vars
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.openapi_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(map(
+                    lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
+                    value
+                ))
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(map(
+                    lambda item: (item[0], item[1].to_dict())
+                    if hasattr(item[1], "to_dict") else item,
+                    value.items()
+                ))
+            else:
+                result[attr] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, ExperimentMeta):
+            return False
+
+        return self.to_dict() == other.to_dict()
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        if not isinstance(other, ExperimentMeta):
+            return True
+
+        return self.to_dict() != other.to_dict()
diff --git a/submarine-sdk/pysubmarine/submarine/experiment/models/experiment_spec.py b/submarine-sdk/pysubmarine/submarine/experiment/models/experiment_spec.py
new file mode 100644
index 0000000..47bb9a5
--- /dev/null
+++ b/submarine-sdk/pysubmarine/submarine/experiment/models/experiment_spec.py
@@ -0,0 +1,188 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# coding: utf-8
+
+"""
+    Submarine Experiment API
+
+    The Submarine REST API allows you to create, list, and get experiments. The API is hosted under the /v1/experiment route on the Submarine server. For example, to list experiments on a server hosted at http://localhost:8080, access http://localhost:8080/api/v1/experiment/  # noqa: E501
+
+    The version of the OpenAPI document: 0.4.0-SNAPSHOT
+    Contact: submarine-dev@submarine.apache.org
+    Generated by: https://openapi-generator.tech
+"""
+
+
+import pprint
+import re  # noqa: F401
+
+import six
+
+from submarine.experiment.configuration import Configuration
+
+
+class ExperimentSpec(object):
+    """NOTE: This class is auto generated by OpenAPI Generator.
+    Ref: https://openapi-generator.tech
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      openapi_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    openapi_types = {
+        'meta': 'ExperimentMeta',
+        'environment': 'Environment',
+        'spec': 'dict(str, ExperimentTaskSpec)'
+    }
+
+    attribute_map = {
+        'meta': 'meta',
+        'environment': 'environment',
+        'spec': 'spec'
+    }
+
+    def __init__(self, meta=None, environment=None, spec=None, local_vars_configuration=None):  # noqa: E501
+        """ExperimentSpec - a model defined in OpenAPI"""  # noqa: E501
+        if local_vars_configuration is None:
+            local_vars_configuration = Configuration()
+        self.local_vars_configuration = local_vars_configuration
+
+        self._meta = None
+        self._environment = None
+        self._spec = None
+        self.discriminator = None
+
+        if meta is not None:
+            self.meta = meta
+        if environment is not None:
+            self.environment = environment
+        if spec is not None:
+            self.spec = spec
+
+    @property
+    def meta(self):
+        """Gets the meta of this ExperimentSpec.  # noqa: E501
+
+
+        :return: The meta of this ExperimentSpec.  # noqa: E501
+        :rtype: ExperimentMeta
+        """
+        return self._meta
+
+    @meta.setter
+    def meta(self, meta):
+        """Sets the meta of this ExperimentSpec.
+
+
+        :param meta: The meta of this ExperimentSpec.  # noqa: E501
+        :type: ExperimentMeta
+        """
+
+        self._meta = meta
+
+    @property
+    def environment(self):
+        """Gets the environment of this ExperimentSpec.  # noqa: E501
+
+
+        :return: The environment of this ExperimentSpec.  # noqa: E501
+        :rtype: Environment
+        """
+        return self._environment
+
+    @environment.setter
+    def environment(self, environment):
+        """Sets the environment of this ExperimentSpec.
+
+
+        :param environment: The environment of this ExperimentSpec.  # noqa: E501
+        :type: Environment
+        """
+
+        self._environment = environment
+
+    @property
+    def spec(self):
+        """Gets the spec of this ExperimentSpec.  # noqa: E501
+
+
+        :return: The spec of this ExperimentSpec.  # noqa: E501
+        :rtype: dict(str, ExperimentTaskSpec)
+        """
+        return self._spec
+
+    @spec.setter
+    def spec(self, spec):
+        """Sets the spec of this ExperimentSpec.
+
+
+        :param spec: The spec of this ExperimentSpec.  # noqa: E501
+        :type: dict(str, ExperimentTaskSpec)
+        """
+
+        self._spec = spec
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.openapi_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(map(
+                    lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
+                    value
+                ))
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(map(
+                    lambda item: (item[0], item[1].to_dict())
+                    if hasattr(item[1], "to_dict") else item,
+                    value.items()
+                ))
+            else:
+                result[attr] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, ExperimentSpec):
+            return False
+
+        return self.to_dict() == other.to_dict()
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        if not isinstance(other, ExperimentSpec):
+            return True
+
+        return self.to_dict() != other.to_dict()
diff --git a/submarine-sdk/pysubmarine/submarine/job/models/job_task_spec.py b/submarine-sdk/pysubmarine/submarine/experiment/models/experiment_task_spec.py
similarity index 54%
rename from submarine-sdk/pysubmarine/submarine/job/models/job_task_spec.py
rename to submarine-sdk/pysubmarine/submarine/experiment/models/experiment_task_spec.py
index 2d5ac5b..44e5fa0 100644
--- a/submarine-sdk/pysubmarine/submarine/job/models/job_task_spec.py
+++ b/submarine-sdk/pysubmarine/submarine/experiment/models/experiment_task_spec.py
@@ -1,11 +1,11 @@
 # Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
+# contributor license agreements.  See the NOTICE file distributed with
 # this work for additional information regarding copyright ownership.
 # The ASF licenses this file to You under the Apache License, Version 2.0
 # (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
+# the License.  You may obtain a copy of the License at
 #
-# http://www.apache.org/licenses/LICENSE-2.0
+#     http://www.apache.org/licenses/LICENSE-2.0
 #
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,79 +14,85 @@
 # limitations under the License.
 
 # coding: utf-8
+
 """
     Submarine Experiment API
 
-    The Submarine REST API allows you to create, list, and get experiments. TheAPI is hosted under the /v1/jobs route on the Submarine server. For example,to list experiments on a server hosted at http://localhost:8080, accesshttp://localhost:8080/api/v1/jobs/  # noqa: E501
+    The Submarine REST API allows you to create, list, and get experiments. The API is hosted under the /v1/experiment route on the Submarine server. For example, to list experiments on a server hosted at http://localhost:8080, access http://localhost:8080/api/v1/experiment/  # noqa: E501
 
-    OpenAPI spec version: 0.4.0-SNAPSHOT
+    The version of the OpenAPI document: 0.4.0-SNAPSHOT
     Contact: submarine-dev@submarine.apache.org
-    Generated by: https://github.com/swagger-api/swagger-codegen.git
+    Generated by: https://openapi-generator.tech
 """
 
+
 import pprint
 import re  # noqa: F401
 
 import six
 
+from submarine.experiment.configuration import Configuration
+
 
-class JobTaskSpec(object):
-    """NOTE: This class is auto generated by the swagger code generator program.
+class ExperimentTaskSpec(object):
+    """NOTE: This class is auto generated by OpenAPI Generator.
+    Ref: https://openapi-generator.tech
 
     Do not edit the class manually.
     """
+
     """
     Attributes:
-      swagger_types (dict): The key is attribute name
+      openapi_types (dict): The key is attribute name
                             and the value is attribute type.
       attribute_map (dict): The key is attribute name
                             and the value is json key in definition.
     """
-    swagger_types = {
+    openapi_types = {
+        'replicas': 'int',
+        'resources': 'str',
         'name': 'str',
         'image': 'str',
         'cmd': 'str',
         'env_vars': 'dict(str, str)',
-        'resources': 'str',
-        'replicas': 'int',
         'cpu': 'str',
         'gpu': 'str',
         'memory': 'str'
     }
 
     attribute_map = {
+        'replicas': 'replicas',
+        'resources': 'resources',
         'name': 'name',
         'image': 'image',
         'cmd': 'cmd',
         'env_vars': 'envVars',
-        'resources': 'resources',
-        'replicas': 'replicas',
         'cpu': 'cpu',
         'gpu': 'gpu',
         'memory': 'memory'
     }
 
-    def __init__(self,
-                 name=None,
-                 image=None,
-                 cmd=None,
-                 env_vars=None,
-                 resources=None,
-                 replicas=None,
-                 cpu=None,
-                 gpu=None,
-                 memory=None):  # noqa: E501
-        """JobTaskSpec - a model defined in Swagger"""  # noqa: E501
+    def __init__(self, replicas=None, resources=None, name=None, image=None, cmd=None, env_vars=None, cpu=None, gpu=None, memory=None, local_vars_configuration=None):  # noqa: E501
+        """ExperimentTaskSpec - a model defined in OpenAPI"""  # noqa: E501
+        if local_vars_configuration is None:
+            local_vars_configuration = Configuration()
+        self.local_vars_configuration = local_vars_configuration
+
+        self._replicas = None
+        self._resources = None
         self._name = None
         self._image = None
         self._cmd = None
         self._env_vars = None
-        self._resources = None
-        self._replicas = None
         self._cpu = None
         self._gpu = None
         self._memory = None
         self.discriminator = None
+
+        if replicas is not None:
+            self.replicas = replicas
+        if resources is not None:
+            self.resources = resources
         if name is not None:
             self.name = name
         if image is not None:
@@ -95,10 +101,6 @@ class JobTaskSpec(object):
             self.cmd = cmd
         if env_vars is not None:
             self.env_vars = env_vars
-        if resources is not None:
-            self.resources = resources
-        if replicas is not None:
-            self.replicas = replicas
         if cpu is not None:
             self.cpu = cpu
         if gpu is not None:
@@ -107,21 +109,63 @@ class JobTaskSpec(object):
             self.memory = memory
 
     @property
+    def replicas(self):
+        """Gets the replicas of this ExperimentTaskSpec.  # noqa: E501
+
+
+        :return: The replicas of this ExperimentTaskSpec.  # noqa: E501
+        :rtype: int
+        """
+        return self._replicas
+
+    @replicas.setter
+    def replicas(self, replicas):
+        """Sets the replicas of this ExperimentTaskSpec.
+
+
+        :param replicas: The replicas of this ExperimentTaskSpec.  # noqa: E501
+        :type: int
+        """
+
+        self._replicas = replicas
+
+    @property
+    def resources(self):
+        """Gets the resources of this ExperimentTaskSpec.  # noqa: E501
+
+
+        :return: The resources of this ExperimentTaskSpec.  # noqa: E501
+        :rtype: str
+        """
+        return self._resources
+
+    @resources.setter
+    def resources(self, resources):
+        """Sets the resources of this ExperimentTaskSpec.
+
+
+        :param resources: The resources of this ExperimentTaskSpec.  # noqa: E501
+        :type: str
+        """
+
+        self._resources = resources
+
+    @property
     def name(self):
-        """Gets the name of this JobTaskSpec.  # noqa: E501
+        """Gets the name of this ExperimentTaskSpec.  # noqa: E501
 
 
-        :return: The name of this JobTaskSpec.  # noqa: E501
+        :return: The name of this ExperimentTaskSpec.  # noqa: E501
         :rtype: str
         """
         return self._name
 
     @name.setter
     def name(self, name):
-        """Sets the name of this JobTaskSpec.
+        """Sets the name of this ExperimentTaskSpec.
 
 
-        :param name: The name of this JobTaskSpec.  # noqa: E501
+        :param name: The name of this ExperimentTaskSpec.  # noqa: E501
         :type: str
         """
 
@@ -129,20 +173,20 @@ class JobTaskSpec(object):
 
     @property
     def image(self):
-        """Gets the image of this JobTaskSpec.  # noqa: E501
+        """Gets the image of this ExperimentTaskSpec.  # noqa: E501
 
 
-        :return: The image of this JobTaskSpec.  # noqa: E501
+        :return: The image of this ExperimentTaskSpec.  # noqa: E501
         :rtype: str
         """
         return self._image
 
     @image.setter
     def image(self, image):
-        """Sets the image of this JobTaskSpec.
+        """Sets the image of this ExperimentTaskSpec.
 
 
-        :param image: The image of this JobTaskSpec.  # noqa: E501
+        :param image: The image of this ExperimentTaskSpec.  # noqa: E501
         :type: str
         """
 
@@ -150,20 +194,20 @@ class JobTaskSpec(object):
 
     @property
     def cmd(self):
-        """Gets the cmd of this JobTaskSpec.  # noqa: E501
+        """Gets the cmd of this ExperimentTaskSpec.  # noqa: E501
 
 
-        :return: The cmd of this JobTaskSpec.  # noqa: E501
+        :return: The cmd of this ExperimentTaskSpec.  # noqa: E501
         :rtype: str
         """
         return self._cmd
 
     @cmd.setter
     def cmd(self, cmd):
-        """Sets the cmd of this JobTaskSpec.
+        """Sets the cmd of this ExperimentTaskSpec.
 
 
-        :param cmd: The cmd of this JobTaskSpec.  # noqa: E501
+        :param cmd: The cmd of this ExperimentTaskSpec.  # noqa: E501
         :type: str
         """
 
@@ -171,83 +215,41 @@ class JobTaskSpec(object):
 
     @property
     def env_vars(self):
-        """Gets the env_vars of this JobTaskSpec.  # noqa: E501
+        """Gets the env_vars of this ExperimentTaskSpec.  # noqa: E501
 
 
-        :return: The env_vars of this JobTaskSpec.  # noqa: E501
+        :return: The env_vars of this ExperimentTaskSpec.  # noqa: E501
         :rtype: dict(str, str)
         """
         return self._env_vars
 
     @env_vars.setter
     def env_vars(self, env_vars):
-        """Sets the env_vars of this JobTaskSpec.
+        """Sets the env_vars of this ExperimentTaskSpec.
 
 
-        :param env_vars: The env_vars of this JobTaskSpec.  # noqa: E501
+        :param env_vars: The env_vars of this ExperimentTaskSpec.  # noqa: E501
         :type: dict(str, str)
         """
 
         self._env_vars = env_vars
 
     @property
-    def resources(self):
-        """Gets the resources of this JobTaskSpec.  # noqa: E501
-
-
-        :return: The resources of this JobTaskSpec.  # noqa: E501
-        :rtype: str
-        """
-        return self._resources
-
-    @resources.setter
-    def resources(self, resources):
-        """Sets the resources of this JobTaskSpec.
-
-
-        :param resources: The resources of this JobTaskSpec.  # noqa: E501
-        :type: str
-        """
-
-        self._resources = resources
-
-    @property
-    def replicas(self):
-        """Gets the replicas of this JobTaskSpec.  # noqa: E501
-
-
-        :return: The replicas of this JobTaskSpec.  # noqa: E501
-        :rtype: int
-        """
-        return self._replicas
-
-    @replicas.setter
-    def replicas(self, replicas):
-        """Sets the replicas of this JobTaskSpec.
-
-
-        :param replicas: The replicas of this JobTaskSpec.  # noqa: E501
-        :type: int
-        """
-
-        self._replicas = replicas
-
-    @property
     def cpu(self):
-        """Gets the cpu of this JobTaskSpec.  # noqa: E501
+        """Gets the cpu of this ExperimentTaskSpec.  # noqa: E501
 
 
-        :return: The cpu of this JobTaskSpec.  # noqa: E501
+        :return: The cpu of this ExperimentTaskSpec.  # noqa: E501
         :rtype: str
         """
         return self._cpu
 
     @cpu.setter
     def cpu(self, cpu):
-        """Sets the cpu of this JobTaskSpec.
+        """Sets the cpu of this ExperimentTaskSpec.
 
 
-        :param cpu: The cpu of this JobTaskSpec.  # noqa: E501
+        :param cpu: The cpu of this ExperimentTaskSpec.  # noqa: E501
         :type: str
         """
 
@@ -255,20 +257,20 @@ class JobTaskSpec(object):
 
     @property
     def gpu(self):
-        """Gets the gpu of this JobTaskSpec.  # noqa: E501
+        """Gets the gpu of this ExperimentTaskSpec.  # noqa: E501
 
 
-        :return: The gpu of this JobTaskSpec.  # noqa: E501
+        :return: The gpu of this ExperimentTaskSpec.  # noqa: E501
         :rtype: str
         """
         return self._gpu
 
     @gpu.setter
     def gpu(self, gpu):
-        """Sets the gpu of this JobTaskSpec.
+        """Sets the gpu of this ExperimentTaskSpec.
 
 
-        :param gpu: The gpu of this JobTaskSpec.  # noqa: E501
+        :param gpu: The gpu of this ExperimentTaskSpec.  # noqa: E501
         :type: str
         """
 
@@ -276,20 +278,20 @@ class JobTaskSpec(object):
 
     @property
     def memory(self):
-        """Gets the memory of this JobTaskSpec.  # noqa: E501
+        """Gets the memory of this ExperimentTaskSpec.  # noqa: E501
 
 
-        :return: The memory of this JobTaskSpec.  # noqa: E501
+        :return: The memory of this ExperimentTaskSpec.  # noqa: E501
         :rtype: str
         """
         return self._memory
 
     @memory.setter
     def memory(self, memory):
-        """Sets the memory of this JobTaskSpec.
+        """Sets the memory of this ExperimentTaskSpec.
 
 
-        :param memory: The memory of this JobTaskSpec.  # noqa: E501
+        :param memory: The memory of this ExperimentTaskSpec.  # noqa: E501
         :type: str
         """
 
@@ -299,25 +301,23 @@ class JobTaskSpec(object):
         """Returns the model properties as a dict"""
         result = {}
 
-        for attr, _ in six.iteritems(self.swagger_types):
+        for attr, _ in six.iteritems(self.openapi_types):
             value = getattr(self, attr)
             if isinstance(value, list):
-                result[attr] = list(
-                    map(lambda x: x.to_dict()
-                        if hasattr(x, "to_dict") else x, value))
+                result[attr] = list(map(
+                    lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
+                    value
+                ))
             elif hasattr(value, "to_dict"):
                 result[attr] = value.to_dict()
             elif isinstance(value, dict):
-                result[attr] = dict(
-                    map(
-                        lambda item: (item[0], item[1].to_dict())
-                        if hasattr(item[1], "to_dict") else item,
-                        value.items()))
+                result[attr] = dict(map(
+                    lambda item: (item[0], item[1].to_dict())
+                    if hasattr(item[1], "to_dict") else item,
+                    value.items()
+                ))
             else:
                 result[attr] = value
-        if issubclass(JobTaskSpec, dict):
-            for key, value in self.items():
-                result[key] = value
 
         return result
 
@@ -331,11 +331,14 @@ class JobTaskSpec(object):
 
     def __eq__(self, other):
         """Returns true if both objects are equal"""
-        if not isinstance(other, JobTaskSpec):
+        if not isinstance(other, ExperimentTaskSpec):
             return False
 
-        return self.__dict__ == other.__dict__
+        return self.to_dict() == other.to_dict()
 
     def __ne__(self, other):
         """Returns true if both objects are not equal"""
-        return not self == other
+        if not isinstance(other, ExperimentTaskSpec):
+            return True
+
+        return self.to_dict() != other.to_dict()
diff --git a/submarine-sdk/pysubmarine/submarine/job/models/json_response.py b/submarine-sdk/pysubmarine/submarine/experiment/models/json_response.py
similarity index 72%
rename from submarine-sdk/pysubmarine/submarine/job/models/json_response.py
rename to submarine-sdk/pysubmarine/submarine/experiment/models/json_response.py
index 9a3c94d..3e01803 100644
--- a/submarine-sdk/pysubmarine/submarine/job/models/json_response.py
+++ b/submarine-sdk/pysubmarine/submarine/experiment/models/json_response.py
@@ -1,11 +1,11 @@
 # Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
+# contributor license agreements.  See the NOTICE file distributed with
 # this work for additional information regarding copyright ownership.
 # The ASF licenses this file to You under the Apache License, Version 2.0
 # (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
+# the License.  You may obtain a copy of the License at
 #
-# http://www.apache.org/licenses/LICENSE-2.0
+#     http://www.apache.org/licenses/LICENSE-2.0
 #
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,35 +14,41 @@
 # limitations under the License.
 
 # coding: utf-8
+
 """
     Submarine Experiment API
 
-    The Submarine REST API allows you to create, list, and get experiments. TheAPI is hosted under the /v1/jobs route on the Submarine server. For example,to list experiments on a server hosted at http://localhost:8080, accesshttp://localhost:8080/api/v1/jobs/  # noqa: E501
+    The Submarine REST API allows you to create, list, and get experiments. The API is hosted under the /v1/experiment route on the Submarine server. For example, to list experiments on a server hosted at http://localhost:8080, access http://localhost:8080/api/v1/experiment/  # noqa: E501
 
-    OpenAPI spec version: 0.4.0-SNAPSHOT
+    The version of the OpenAPI document: 0.4.0-SNAPSHOT
     Contact: submarine-dev@submarine.apache.org
-    Generated by: https://github.com/swagger-api/swagger-codegen.git
+    Generated by: https://openapi-generator.tech
 """
 
+
 import pprint
 import re  # noqa: F401
 
 import six
 
+from submarine.experiment.configuration import Configuration
+
 
 class JsonResponse(object):
-    """NOTE: This class is auto generated by the swagger code generator program.
+    """NOTE: This class is auto generated by OpenAPI Generator.
+    Ref: https://openapi-generator.tech
 
     Do not edit the class manually.
     """
+
     """
     Attributes:
-      swagger_types (dict): The key is attribute name
+      openapi_types (dict): The key is attribute name
                             and the value is attribute type.
       attribute_map (dict): The key is attribute name
                             and the value is json key in definition.
     """
-    swagger_types = {
+    openapi_types = {
         'code': 'int',
         'success': 'bool',
         'result': 'object',
@@ -56,17 +62,18 @@ class JsonResponse(object):
         'attributes': 'attributes'
     }
 
-    def __init__(self,
-                 code=None,
-                 success=None,
-                 result=None,
-                 attributes=None):  # noqa: E501
-        """JsonResponse - a model defined in Swagger"""  # noqa: E501
+    def __init__(self, code=None, success=None, result=None, attributes=None, local_vars_configuration=None):  # noqa: E501
+        """JsonResponse - a model defined in OpenAPI"""  # noqa: E501
+        if local_vars_configuration is None:
+            local_vars_configuration = Configuration()
+        self.local_vars_configuration = local_vars_configuration
+
         self._code = None
         self._success = None
         self._result = None
         self._attributes = None
         self.discriminator = None
+
         if code is not None:
             self.code = code
         if success is not None:
@@ -164,25 +171,23 @@ class JsonResponse(object):
         """Returns the model properties as a dict"""
         result = {}
 
-        for attr, _ in six.iteritems(self.swagger_types):
+        for attr, _ in six.iteritems(self.openapi_types):
             value = getattr(self, attr)
             if isinstance(value, list):
-                result[attr] = list(
-                    map(lambda x: x.to_dict()
-                        if hasattr(x, "to_dict") else x, value))
+                result[attr] = list(map(
+                    lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
+                    value
+                ))
             elif hasattr(value, "to_dict"):
                 result[attr] = value.to_dict()
             elif isinstance(value, dict):
-                result[attr] = dict(
-                    map(
-                        lambda item: (item[0], item[1].to_dict())
-                        if hasattr(item[1], "to_dict") else item,
-                        value.items()))
+                result[attr] = dict(map(
+                    lambda item: (item[0], item[1].to_dict())
+                    if hasattr(item[1], "to_dict") else item,
+                    value.items()
+                ))
             else:
                 result[attr] = value
-        if issubclass(JsonResponse, dict):
-            for key, value in self.items():
-                result[key] = value
 
         return result
 
@@ -199,8 +204,11 @@ class JsonResponse(object):
         if not isinstance(other, JsonResponse):
             return False
 
-        return self.__dict__ == other.__dict__
+        return self.to_dict() == other.to_dict()
 
     def __ne__(self, other):
         """Returns true if both objects are not equal"""
-        return not self == other
+        if not isinstance(other, JsonResponse):
+            return True
+
+        return self.to_dict() != other.to_dict()
diff --git a/submarine-sdk/pysubmarine/submarine/job/rest.py b/submarine-sdk/pysubmarine/submarine/experiment/rest.py
similarity index 88%
rename from submarine-sdk/pysubmarine/submarine/job/rest.py
rename to submarine-sdk/pysubmarine/submarine/experiment/rest.py
index 94f0288..46be388 100644
--- a/submarine-sdk/pysubmarine/submarine/job/rest.py
+++ b/submarine-sdk/pysubmarine/submarine/experiment/rest.py
@@ -1,11 +1,11 @@
 # Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
+# contributor license agreements.  See the NOTICE file distributed with
 # this work for additional information regarding copyright ownership.
 # The ASF licenses this file to You under the Apache License, Version 2.0
 # (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
+# the License.  You may obtain a copy of the License at
 #
-# http://www.apache.org/licenses/LICENSE-2.0
+#     http://www.apache.org/licenses/LICENSE-2.0
 #
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
@@ -17,11 +17,11 @@
 """
     Submarine Experiment API
 
-    The Submarine REST API allows you to create, list, and get experiments. TheAPI is hosted under the /v1/jobs route on the Submarine server. For example,to list experiments on a server hosted at http://localhost:8080, accesshttp://localhost:8080/api/v1/jobs/  # noqa: E501
+    The Submarine REST API allows you to create, list, and get experiments. The API is hosted under the /v1/experiment route on the Submarine server. For example, to list experiments on a server hosted at http://localhost:8080, access http://localhost:8080/api/v1/experiment/  # noqa: E501
 
-    OpenAPI spec version: 0.4.0-SNAPSHOT
+    The version of the OpenAPI document: 0.4.0-SNAPSHOT
     Contact: submarine-dev@submarine.apache.org
-    Generated by: https://github.com/swagger-api/swagger-codegen.git
+    Generated by: https://openapi-generator.tech
 """
 
 from __future__ import absolute_import
@@ -35,12 +35,10 @@ import ssl
 import certifi
 # python 2 and python 3 compatibility library
 import six
+import urllib3
 from six.moves.urllib.parse import urlencode
 
-try:
-    import urllib3
-except ImportError:
-    raise ImportError('Swagger python client requires urllib3.')
+from submarine.experiment.exceptions import ApiException, ApiValueError
 
 logger = logging.getLogger(__name__)
 
@@ -89,6 +87,9 @@ class RESTClientObject(object):
             addition_pool_args[
                 'assert_hostname'] = configuration.assert_hostname  # noqa: E501
 
+        if configuration.retries is not None:
+            addition_pool_args['retries'] = configuration.retries
+
         if maxsize is None:
             if configuration.connection_pool_maxsize is not None:
                 maxsize = configuration.connection_pool_maxsize
@@ -105,6 +106,7 @@ class RESTClientObject(object):
                 cert_file=configuration.cert_file,
                 key_file=configuration.key_file,
                 proxy_url=configuration.proxy,
+                proxy_headers=configuration.proxy_headers,
                 **addition_pool_args)
         else:
             self.pool_manager = urllib3.PoolManager(
@@ -149,7 +151,7 @@ class RESTClientObject(object):
         ]
 
         if post_params and body:
-            raise ValueError(
+            raise ApiValueError(
                 "body parameter cannot be used with post_params parameter.")
 
         post_params = post_params or {}
@@ -174,7 +176,7 @@ class RESTClientObject(object):
                 if query_params:
                     url += '?' + urlencode(query_params)
                 if re.search('json', headers['Content-Type'], re.IGNORECASE):
-                    request_body = '{}'
+                    request_body = None
                     if body is not None:
                         request_body = json.dumps(body)
                     r = self.pool_manager.request(
@@ -210,7 +212,7 @@ class RESTClientObject(object):
                 # Pass a `string` parameter directly in the body to support
                 # other content types than Json when `body` argument is
                 # provided in serialized form
-                elif isinstance(body, str):
+                elif isinstance(body, str) or isinstance(body, bytes):
                     request_body = body
                     r = self.pool_manager.request(
                         method,
@@ -240,11 +242,6 @@ class RESTClientObject(object):
         if _preload_content:
             r = RESTResponse(r)
 
-            # In the python 3, the response.data is bytes.
-            # we need to decode it to string.
-            if six.PY3:
-                r.data = r.data.decode('utf8')
-
             # log response body
             logger.debug("response body: %s", r.data)
 
@@ -361,30 +358,3 @@ class RESTClientObject(object):
                             _preload_content=_preload_content,
                             _request_timeout=_request_timeout,
                             body=body)
-
-
-class ApiException(Exception):
-
-    def __init__(self, status=None, reason=None, http_resp=None):
-        if http_resp:
-            self.status = http_resp.status
-            self.reason = http_resp.reason
-            self.body = http_resp.data
-            self.headers = http_resp.getheaders()
-        else:
-            self.status = status
-            self.reason = reason
-            self.body = None
-            self.headers = None
-
-    def __str__(self):
-        """Custom error messages for exception"""
-        error_message = "({0})\n"\
-                        "Reason: {1}\n".format(self.status, self.reason)
-        if self.headers:
-            error_message += "HTTP response headers: {0}\n".format(self.headers)
-
-        if self.body:
-            error_message += "HTTP response body: {0}\n".format(self.body)
-
-        return error_message
diff --git a/submarine-sdk/pysubmarine/submarine/job/__init__.py b/submarine-sdk/pysubmarine/submarine/job/__init__.py
deleted file mode 100644
index ae4d323..0000000
--- a/submarine-sdk/pysubmarine/submarine/job/__init__.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# coding: utf-8
-
-# flake8: noqa
-"""
-    Submarine Experiment API
-
-    The Submarine REST API allows you to create, list, and get experiments. TheAPI is hosted under the /v1/jobs route on the Submarine server. For example,to list experiments on a server hosted at http://localhost:8080, accesshttp://localhost:8080/api/v1/jobs/  # noqa: E501
-
-    OpenAPI spec version: 0.4.0-SNAPSHOT
-    Contact: submarine-dev@submarine.apache.org
-    Generated by: https://github.com/swagger-api/swagger-codegen.git
-"""
-
-from __future__ import absolute_import
-
-# import apis into sdk package
-from submarine.job.api.jobs_api import JobsApi
-# import ApiClient
-from submarine.job.api_client import ApiClient
-from submarine.job.configuration import Configuration
-# import models into sdk package
-from submarine.job.models.job_library_spec import JobLibrarySpec
-from submarine.job.models.job_spec import JobSpec
-from submarine.job.models.job_task_spec import JobTaskSpec
-from submarine.job.models.json_response import JsonResponse
diff --git a/submarine-sdk/pysubmarine/submarine/job/api/jobs_api.py b/submarine-sdk/pysubmarine/submarine/job/api/jobs_api.py
deleted file mode 100644
index 05195b9..0000000
--- a/submarine-sdk/pysubmarine/submarine/job/api/jobs_api.py
+++ /dev/null
@@ -1,783 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# coding: utf-8
-"""
-    Submarine Experiment API
-
-    The Submarine REST API allows you to create, list, and get experiments. TheAPI is hosted under the /v1/jobs route on the Submarine server. For example,to list experiments on a server hosted at http://localhost:8080, accesshttp://localhost:8080/api/v1/jobs/  # noqa: E501
-
-    OpenAPI spec version: 0.4.0-SNAPSHOT
-    Contact: submarine-dev@submarine.apache.org
-    Generated by: https://github.com/swagger-api/swagger-codegen.git
-"""
-
-from __future__ import absolute_import
-
-import re  # noqa: F401
-
-# python 2 and python 3 compatibility library
-import six
-
-from submarine.job.api_client import ApiClient
-
-
-class JobsApi(object):
-    """NOTE: This class is auto generated by the swagger code generator program.
-
-    Do not edit the class manually.
-    Ref: https://github.com/swagger-api/swagger-codegen
-    """
-
-    def __init__(self, api_client=None):
-        if api_client is None:
-            api_client = ApiClient()
-        self.api_client = api_client
-
-    def create_job(self, **kwargs):  # noqa: E501
-        """Create a job  # noqa: E501
-
-        This method makes a synchronous HTTP request by default. To make an
-        asynchronous HTTP request, please pass async_req=True
-        >>> thread = api.create_job(async_req=True)
-        >>> result = thread.get()
-
-        :param async_req bool
-        :param JobSpec body:
-        :return: JsonResponse
-                 If the method is called asynchronously,
-                 returns the request thread.
-        """
-        kwargs['_return_http_data_only'] = True
-        if kwargs.get('async_req'):
-            return self.create_job_with_http_info(**kwargs)  # noqa: E501
-        else:
-            (data) = self.create_job_with_http_info(**kwargs)  # noqa: E501
-            return data
-
-    def create_job_with_http_info(self, **kwargs):  # noqa: E501
-        """Create a job  # noqa: E501
-
-        This method makes a synchronous HTTP request by default. To make an
-        asynchronous HTTP request, please pass async_req=True
-        >>> thread = api.create_job_with_http_info(async_req=True)
-        >>> result = thread.get()
-
-        :param async_req bool
-        :param JobSpec body:
-        :return: JsonResponse
-                 If the method is called asynchronously,
-                 returns the request thread.
-        """
-
-        all_params = ['body']  # noqa: E501
-        all_params.append('async_req')
-        all_params.append('_return_http_data_only')
-        all_params.append('_preload_content')
-        all_params.append('_request_timeout')
-
-        params = locals()
-        for key, val in six.iteritems(params['kwargs']):
-            if key not in all_params:
-                raise TypeError("Got an unexpected keyword argument '%s'"
-                                " to method create_job" % key)
-            params[key] = val
-        del params['kwargs']
-
-        collection_formats = {}
-
-        path_params = {}
-
-        query_params = []
-
-        header_params = {}
-
-        form_params = []
-        local_var_files = {}
-
-        body_params = None
-        if 'body' in params:
-            body_params = params['body']
-        # HTTP header `Accept`
-        header_params['Accept'] = self.api_client.select_header_accept(
-            ['application/json; charset=utf-8'])  # noqa: E501
-
-        # HTTP header `Content-Type`
-        header_params[
-            'Content-Type'] = self.api_client.select_header_content_type(  # noqa: E501
-                ['application/yaml', 'application/json'])  # noqa: E501
-
-        # Authentication setting
-        auth_settings = []  # noqa: E501
-
-        return self.api_client.call_api(
-            '/v1/jobs',
-            'POST',
-            path_params,
-            query_params,
-            header_params,
-            body=body_params,
-            post_params=form_params,
-            files=local_var_files,
-            response_type='JsonResponse',  # noqa: E501
-            auth_settings=auth_settings,
-            async_req=params.get('async_req'),
-            _return_http_data_only=params.get('_return_http_data_only'),
-            _preload_content=params.get('_preload_content', True),
-            _request_timeout=params.get('_request_timeout'),
-            collection_formats=collection_formats)
-
-    def delete_job(self, id, **kwargs):  # noqa: E501
-        """Delete the job  # noqa: E501
-
-        This method makes a synchronous HTTP request by default. To make an
-        asynchronous HTTP request, please pass async_req=True
-        >>> thread = api.delete_job(id, async_req=True)
-        >>> result = thread.get()
-
-        :param async_req bool
-        :param str id: (required)
-        :return: JsonResponse
-                 If the method is called asynchronously,
-                 returns the request thread.
-        """
-        kwargs['_return_http_data_only'] = True
-        if kwargs.get('async_req'):
-            return self.delete_job_with_http_info(id, **kwargs)  # noqa: E501
-        else:
-            (data) = self.delete_job_with_http_info(id, **kwargs)  # noqa: E501
-            return data
-
-    def delete_job_with_http_info(self, id, **kwargs):  # noqa: E501
-        """Delete the job  # noqa: E501
-
-        This method makes a synchronous HTTP request by default. To make an
-        asynchronous HTTP request, please pass async_req=True
-        >>> thread = api.delete_job_with_http_info(id, async_req=True)
-        >>> result = thread.get()
-
-        :param async_req bool
-        :param str id: (required)
-        :return: JsonResponse
-                 If the method is called asynchronously,
-                 returns the request thread.
-        """
-
-        all_params = ['id']  # noqa: E501
-        all_params.append('async_req')
-        all_params.append('_return_http_data_only')
-        all_params.append('_preload_content')
-        all_params.append('_request_timeout')
-
-        params = locals()
-        for key, val in six.iteritems(params['kwargs']):
-            if key not in all_params:
-                raise TypeError("Got an unexpected keyword argument '%s'"
-                                " to method delete_job" % key)
-            params[key] = val
-        del params['kwargs']
-        # verify the required parameter 'id' is set
-        if ('id' not in params or params['id'] is None):
-            raise ValueError(
-                "Missing the required parameter `id` when calling `delete_job`"
-            )  # noqa: E501
-
-        collection_formats = {}
-
-        path_params = {}
-        if 'id' in params:
-            path_params['id'] = params['id']  # noqa: E501
-
-        query_params = []
-
-        header_params = {}
-
-        form_params = []
-        local_var_files = {}
-
-        body_params = None
-        # HTTP header `Accept`
-        header_params['Accept'] = self.api_client.select_header_accept(
-            ['application/json; charset=utf-8'])  # noqa: E501
-
-        # Authentication setting
-        auth_settings = []  # noqa: E501
-
-        return self.api_client.call_api(
-            '/v1/jobs/{id}',
-            'DELETE',
-            path_params,
-            query_params,
-            header_params,
-            body=body_params,
-            post_params=form_params,
-            files=local_var_files,
-            response_type='JsonResponse',  # noqa: E501
-            auth_settings=auth_settings,
-            async_req=params.get('async_req'),
-            _return_http_data_only=params.get('_return_http_data_only'),
-            _preload_content=params.get('_preload_content', True),
-            _request_timeout=params.get('_request_timeout'),
-            collection_formats=collection_formats)
-
-    def get_job(self, id, **kwargs):  # noqa: E501
-        """Find job by id  # noqa: E501
-
-        This method makes a synchronous HTTP request by default. To make an
-        asynchronous HTTP request, please pass async_req=True
-        >>> thread = api.get_job(id, async_req=True)
-        >>> result = thread.get()
-
-        :param async_req bool
-        :param str id: (required)
-        :return: JsonResponse
-                 If the method is called asynchronously,
-                 returns the request thread.
-        """
-        kwargs['_return_http_data_only'] = True
-        if kwargs.get('async_req'):
-            return self.get_job_with_http_info(id, **kwargs)  # noqa: E501
-        else:
-            (data) = self.get_job_with_http_info(id, **kwargs)  # noqa: E501
-            return data
-
-    def get_job_with_http_info(self, id, **kwargs):  # noqa: E501
-        """Find job by id  # noqa: E501
-
-        This method makes a synchronous HTTP request by default. To make an
-        asynchronous HTTP request, please pass async_req=True
-        >>> thread = api.get_job_with_http_info(id, async_req=True)
-        >>> result = thread.get()
-
-        :param async_req bool
-        :param str id: (required)
-        :return: JsonResponse
-                 If the method is called asynchronously,
-                 returns the request thread.
-        """
-
-        all_params = ['id']  # noqa: E501
-        all_params.append('async_req')
-        all_params.append('_return_http_data_only')
-        all_params.append('_preload_content')
-        all_params.append('_request_timeout')
-
-        params = locals()
-        for key, val in six.iteritems(params['kwargs']):
-            if key not in all_params:
-                raise TypeError("Got an unexpected keyword argument '%s'"
-                                " to method get_job" % key)
-            params[key] = val
-        del params['kwargs']
-        # verify the required parameter 'id' is set
-        if ('id' not in params or params['id'] is None):
-            raise ValueError(
-                "Missing the required parameter `id` when calling `get_job`"
-            )  # noqa: E501
-
-        collection_formats = {}
-
-        path_params = {}
-        if 'id' in params:
-            path_params['id'] = params['id']  # noqa: E501
-
-        query_params = []
-
-        header_params = {}
-
-        form_params = []
-        local_var_files = {}
-
-        body_params = None
-        # HTTP header `Accept`
-        header_params['Accept'] = self.api_client.select_header_accept(
-            ['application/json; charset=utf-8'])  # noqa: E501
-
-        # Authentication setting
-        auth_settings = []  # noqa: E501
-
-        return self.api_client.call_api(
-            '/v1/jobs/{id}',
-            'GET',
-            path_params,
-            query_params,
-            header_params,
-            body=body_params,
-            post_params=form_params,
-            files=local_var_files,
-            response_type='JsonResponse',  # noqa: E501
-            auth_settings=auth_settings,
-            async_req=params.get('async_req'),
-            _return_http_data_only=params.get('_return_http_data_only'),
-            _preload_content=params.get('_preload_content', True),
-            _request_timeout=params.get('_request_timeout'),
-            collection_formats=collection_formats)
-
-    def get_log(self, id, **kwargs):  # noqa: E501
-        """Log job by id  # noqa: E501
-
-        This method makes a synchronous HTTP request by default. To make an
-        asynchronous HTTP request, please pass async_req=True
-        >>> thread = api.get_log(id, async_req=True)
-        >>> result = thread.get()
-
-        :param async_req bool
-        :param str id: (required)
-        :return: JsonResponse
-                 If the method is called asynchronously,
-                 returns the request thread.
-        """
-        kwargs['_return_http_data_only'] = True
-        if kwargs.get('async_req'):
-            return self.get_log_with_http_info(id, **kwargs)  # noqa: E501
-        else:
-            (data) = self.get_log_with_http_info(id, **kwargs)  # noqa: E501
-            return data
-
-    def get_log_with_http_info(self, id, **kwargs):  # noqa: E501
-        """Log job by id  # noqa: E501
-
-        This method makes a synchronous HTTP request by default. To make an
-        asynchronous HTTP request, please pass async_req=True
-        >>> thread = api.get_log_with_http_info(id, async_req=True)
-        >>> result = thread.get()
-
-        :param async_req bool
-        :param str id: (required)
-        :return: JsonResponse
-                 If the method is called asynchronously,
-                 returns the request thread.
-        """
-
-        all_params = ['id']  # noqa: E501
-        all_params.append('async_req')
-        all_params.append('_return_http_data_only')
-        all_params.append('_preload_content')
-        all_params.append('_request_timeout')
-
-        params = locals()
-        for key, val in six.iteritems(params['kwargs']):
-            if key not in all_params:
-                raise TypeError("Got an unexpected keyword argument '%s'"
-                                " to method get_log" % key)
-            params[key] = val
-        del params['kwargs']
-        # verify the required parameter 'id' is set
-        if ('id' not in params or params['id'] is None):
-            raise ValueError(
-                "Missing the required parameter `id` when calling `get_log`"
-            )  # noqa: E501
-
-        collection_formats = {}
-
-        path_params = {}
-        if 'id' in params:
-            path_params['id'] = params['id']  # noqa: E501
-
-        query_params = []
-
-        header_params = {}
-
-        form_params = []
-        local_var_files = {}
-
-        body_params = None
-        # HTTP header `Accept`
-        header_params['Accept'] = self.api_client.select_header_accept(
-            ['application/json; charset=utf-8'])  # noqa: E501
-
-        # Authentication setting
-        auth_settings = []  # noqa: E501
-
-        return self.api_client.call_api(
-            '/v1/jobs/logs/{id}',
-            'GET',
-            path_params,
-            query_params,
-            header_params,
-            body=body_params,
-            post_params=form_params,
-            files=local_var_files,
-            response_type='JsonResponse',  # noqa: E501
-            auth_settings=auth_settings,
-            async_req=params.get('async_req'),
-            _return_http_data_only=params.get('_return_http_data_only'),
-            _preload_content=params.get('_preload_content', True),
-            _request_timeout=params.get('_request_timeout'),
-            collection_formats=collection_formats)
-
-    def list_job(self, **kwargs):  # noqa: E501
-        """List jobs  # noqa: E501
-
-        This method makes a synchronous HTTP request by default. To make an
-        asynchronous HTTP request, please pass async_req=True
-        >>> thread = api.list_job(async_req=True)
-        >>> result = thread.get()
-
-        :param async_req bool
-        :param str status:
-        :return: JsonResponse
-                 If the method is called asynchronously,
-                 returns the request thread.
-        """
-        kwargs['_return_http_data_only'] = True
-        if kwargs.get('async_req'):
-            return self.list_job_with_http_info(**kwargs)  # noqa: E501
-        else:
-            (data) = self.list_job_with_http_info(**kwargs)  # noqa: E501
-            return data
-
-    def list_job_with_http_info(self, **kwargs):  # noqa: E501
-        """List jobs  # noqa: E501
-
-        This method makes a synchronous HTTP request by default. To make an
-        asynchronous HTTP request, please pass async_req=True
-        >>> thread = api.list_job_with_http_info(async_req=True)
-        >>> result = thread.get()
-
-        :param async_req bool
-        :param str status:
-        :return: JsonResponse
-                 If the method is called asynchronously,
-                 returns the request thread.
-        """
-
-        all_params = ['status']  # noqa: E501
-        all_params.append('async_req')
-        all_params.append('_return_http_data_only')
-        all_params.append('_preload_content')
-        all_params.append('_request_timeout')
-
-        params = locals()
-        for key, val in six.iteritems(params['kwargs']):
-            if key not in all_params:
-                raise TypeError("Got an unexpected keyword argument '%s'"
-                                " to method list_job" % key)
-            params[key] = val
-        del params['kwargs']
-
-        collection_formats = {}
-
-        path_params = {}
-
-        query_params = []
-        if 'status' in params:
-            query_params.append(('status', params['status']))  # noqa: E501
-
-        header_params = {}
-
-        form_params = []
-        local_var_files = {}
-
-        body_params = None
-        # HTTP header `Accept`
-        header_params['Accept'] = self.api_client.select_header_accept(
-            ['application/json; charset=utf-8'])  # noqa: E501
-
-        # Authentication setting
-        auth_settings = []  # noqa: E501
-
-        return self.api_client.call_api(
-            '/v1/jobs',
-            'GET',
-            path_params,
-            query_params,
-            header_params,
-            body=body_params,
-            post_params=form_params,
-            files=local_var_files,
-            response_type='JsonResponse',  # noqa: E501
-            auth_settings=auth_settings,
-            async_req=params.get('async_req'),
-            _return_http_data_only=params.get('_return_http_data_only'),
-            _preload_content=params.get('_preload_content', True),
-            _request_timeout=params.get('_request_timeout'),
-            collection_formats=collection_formats)
-
-    def list_log(self, **kwargs):  # noqa: E501
-        """Log jobs  # noqa: E501
-
-        This method makes a synchronous HTTP request by default. To make an
-        asynchronous HTTP request, please pass async_req=True
-        >>> thread = api.list_log(async_req=True)
-        >>> result = thread.get()
-
-        :param async_req bool
-        :param str status:
-        :return: JsonResponse
-                 If the method is called asynchronously,
-                 returns the request thread.
-        """
-        kwargs['_return_http_data_only'] = True
-        if kwargs.get('async_req'):
-            return self.list_log_with_http_info(**kwargs)  # noqa: E501
-        else:
-            (data) = self.list_log_with_http_info(**kwargs)  # noqa: E501
-            return data
-
-    def list_log_with_http_info(self, **kwargs):  # noqa: E501
-        """Log jobs  # noqa: E501
-
-        This method makes a synchronous HTTP request by default. To make an
-        asynchronous HTTP request, please pass async_req=True
-        >>> thread = api.list_log_with_http_info(async_req=True)
-        >>> result = thread.get()
-
-        :param async_req bool
-        :param str status:
-        :return: JsonResponse
-                 If the method is called asynchronously,
-                 returns the request thread.
-        """
-
-        all_params = ['status']  # noqa: E501
-        all_params.append('async_req')
-        all_params.append('_return_http_data_only')
-        all_params.append('_preload_content')
-        all_params.append('_request_timeout')
-
-        params = locals()
-        for key, val in six.iteritems(params['kwargs']):
-            if key not in all_params:
-                raise TypeError("Got an unexpected keyword argument '%s'"
-                                " to method list_log" % key)
-            params[key] = val
-        del params['kwargs']
-
-        collection_formats = {}
-
-        path_params = {}
-
-        query_params = []
-        if 'status' in params:
-            query_params.append(('status', params['status']))  # noqa: E501
-
-        header_params = {}
-
-        form_params = []
-        local_var_files = {}
-
-        body_params = None
-        # HTTP header `Accept`
-        header_params['Accept'] = self.api_client.select_header_accept(
-            ['application/json; charset=utf-8'])  # noqa: E501
-
-        # Authentication setting
-        auth_settings = []  # noqa: E501
-
-        return self.api_client.call_api(
-            '/v1/jobs/logs',
-            'GET',
-            path_params,
-            query_params,
-            header_params,
-            body=body_params,
-            post_params=form_params,
-            files=local_var_files,
-            response_type='JsonResponse',  # noqa: E501
-            auth_settings=auth_settings,
-            async_req=params.get('async_req'),
-            _return_http_data_only=params.get('_return_http_data_only'),
-            _preload_content=params.get('_preload_content', True),
-            _request_timeout=params.get('_request_timeout'),
-            collection_formats=collection_formats)
-
-    def patch_job(self, id, **kwargs):  # noqa: E501
-        """Update the job in the submarine server with job spec  # noqa: E501
-
-        This method makes a synchronous HTTP request by default. To make an
-        asynchronous HTTP request, please pass async_req=True
-        >>> thread = api.patch_job(id, async_req=True)
-        >>> result = thread.get()
-
-        :param async_req bool
-        :param str id: (required)
-        :param JobSpec body:
-        :return: JsonResponse
-                 If the method is called asynchronously,
-                 returns the request thread.
-        """
-        kwargs['_return_http_data_only'] = True
-        if kwargs.get('async_req'):
-            return self.patch_job_with_http_info(id, **kwargs)  # noqa: E501
-        else:
-            (data) = self.patch_job_with_http_info(id, **kwargs)  # noqa: E501
-            return data
-
-    def patch_job_with_http_info(self, id, **kwargs):  # noqa: E501
-        """Update the job in the submarine server with job spec  # noqa: E501
-
-        This method makes a synchronous HTTP request by default. To make an
-        asynchronous HTTP request, please pass async_req=True
-        >>> thread = api.patch_job_with_http_info(id, async_req=True)
-        >>> result = thread.get()
-
-        :param async_req bool
-        :param str id: (required)
-        :param JobSpec body:
-        :return: JsonResponse
-                 If the method is called asynchronously,
-                 returns the request thread.
-        """
-
-        all_params = ['id', 'body']  # noqa: E501
-        all_params.append('async_req')
-        all_params.append('_return_http_data_only')
-        all_params.append('_preload_content')
-        all_params.append('_request_timeout')
-
-        params = locals()
-        for key, val in six.iteritems(params['kwargs']):
-            if key not in all_params:
-                raise TypeError("Got an unexpected keyword argument '%s'"
-                                " to method patch_job" % key)
-            params[key] = val
-        del params['kwargs']
-        # verify the required parameter 'id' is set
-        if ('id' not in params or params['id'] is None):
-            raise ValueError(
-                "Missing the required parameter `id` when calling `patch_job`"
-            )  # noqa: E501
-
-        collection_formats = {}
-
-        path_params = {}
-        if 'id' in params:
-            path_params['id'] = params['id']  # noqa: E501
-
-        query_params = []
-
-        header_params = {}
-
-        form_params = []
-        local_var_files = {}
-
-        body_params = None
-        if 'body' in params:
-            body_params = params['body']
-        # HTTP header `Accept`
-        header_params['Accept'] = self.api_client.select_header_accept(
-            ['application/json; charset=utf-8'])  # noqa: E501
-
-        # HTTP header `Content-Type`
-        header_params[
-            'Content-Type'] = self.api_client.select_header_content_type(  # noqa: E501
-                ['application/yaml', 'application/json'])  # noqa: E501
-
-        # Authentication setting
-        auth_settings = []  # noqa: E501
-
-        return self.api_client.call_api(
-            '/v1/jobs/{id}',
-            'PATCH',
-            path_params,
-            query_params,
-            header_params,
-            body=body_params,
-            post_params=form_params,
-            files=local_var_files,
-            response_type='JsonResponse',  # noqa: E501
-            auth_settings=auth_settings,
-            async_req=params.get('async_req'),
-            _return_http_data_only=params.get('_return_http_data_only'),
-            _preload_content=params.get('_preload_content', True),
-            _request_timeout=params.get('_request_timeout'),
-            collection_formats=collection_formats)
-
-    def ping(self, **kwargs):  # noqa: E501
-        """Ping submarine server  # noqa: E501
-
-        Return the Pong message for test the connectivity  # noqa: E501
-        This method makes a synchronous HTTP request by default. To make an
-        asynchronous HTTP request, please pass async_req=True
-        >>> thread = api.ping(async_req=True)
-        >>> result = thread.get()
-
-        :param async_req bool
-        :return: str
-                 If the method is called asynchronously,
-                 returns the request thread.
-        """
-        kwargs['_return_http_data_only'] = True
-        if kwargs.get('async_req'):
-            return self.ping_with_http_info(**kwargs)  # noqa: E501
-        else:
-            (data) = self.ping_with_http_info(**kwargs)  # noqa: E501
-            return data
-
-    def ping_with_http_info(self, **kwargs):  # noqa: E501
-        """Ping submarine server  # noqa: E501
-
-        Return the Pong message for test the connectivity  # noqa: E501
-        This method makes a synchronous HTTP request by default. To make an
-        asynchronous HTTP request, please pass async_req=True
-        >>> thread = api.ping_with_http_info(async_req=True)
-        >>> result = thread.get()
-
-        :param async_req bool
-        :return: str
-                 If the method is called asynchronously,
-                 returns the request thread.
-        """
-
-        all_params = []  # noqa: E501
-        all_params.append('async_req')
-        all_params.append('_return_http_data_only')
-        all_params.append('_preload_content')
-        all_params.append('_request_timeout')
-
-        params = locals()
-        for key, val in six.iteritems(params['kwargs']):
-            if key not in all_params:
-                raise TypeError("Got an unexpected keyword argument '%s'"
-                                " to method ping" % key)
-            params[key] = val
-        del params['kwargs']
-
-        collection_formats = {}
-
-        path_params = {}
-
-        query_params = []
-
-        header_params = {}
-
-        form_params = []
-        local_var_files = {}
-
-        body_params = None
-        # HTTP header `Accept`
-        header_params['Accept'] = self.api_client.select_header_accept(
-            ['application/json; charset=utf-8'])  # noqa: E501
-
-        # Authentication setting
-        auth_settings = []  # noqa: E501
-
-        return self.api_client.call_api(
-            '/v1/jobs/ping',
-            'GET',
-            path_params,
-            query_params,
-            header_params,
-            body=body_params,
-            post_params=form_params,
-            files=local_var_files,
-            response_type='str',  # noqa: E501
-            auth_settings=auth_settings,
-            async_req=params.get('async_req'),
-            _return_http_data_only=params.get('_return_http_data_only'),
-            _preload_content=params.get('_preload_content', True),
-            _request_timeout=params.get('_request_timeout'),
-            collection_formats=collection_formats)
diff --git a/submarine-sdk/pysubmarine/submarine/job/configuration.py b/submarine-sdk/pysubmarine/submarine/job/configuration.py
deleted file mode 100644
index f8be11a..0000000
--- a/submarine-sdk/pysubmarine/submarine/job/configuration.py
+++ /dev/null
@@ -1,257 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# coding: utf-8
-"""
-    Submarine Experiment API
-
-    The Submarine REST API allows you to create, list, and get experiments. TheAPI is hosted under the /v1/jobs route on the Submarine server. For example,to list experiments on a server hosted at http://localhost:8080, accesshttp://localhost:8080/api/v1/jobs/  # noqa: E501
-
-    OpenAPI spec version: 0.4.0-SNAPSHOT
-    Contact: submarine-dev@submarine.apache.org
-    Generated by: https://github.com/swagger-api/swagger-codegen.git
-"""
-
-from __future__ import absolute_import
-
-import copy
-import logging
-import multiprocessing
-import sys
-
-import six
-import urllib3
-from six.moves import http_client as httplib
-
-
-class TypeWithDefault(type):
-
-    def __init__(cls, name, bases, dct):
-        super(TypeWithDefault, cls).__init__(name, bases, dct)
-        cls._default = None
-
-    def __call__(cls):
-        if cls._default is None:
-            cls._default = type.__call__(cls)
-        return copy.copy(cls._default)
-
-    def set_default(cls, default):
-        cls._default = copy.copy(default)
-
-
-class Configuration(six.with_metaclass(TypeWithDefault, object)):
-    """NOTE: This class is auto generated by the swagger code generator program.
-
-    Ref: https://github.com/swagger-api/swagger-codegen
-    Do not edit the class manually.
-    """
-
-    def __init__(self):
-        """Constructor"""
-        # Default Base url
-        self.host = "/api"
-        # Temp file folder for downloading files
-        self.temp_folder_path = None
-
-        # Authentication Settings
-        # dict to store API key(s)
-        self.api_key = {}
-        # dict to store API prefix (e.g. Bearer)
-        self.api_key_prefix = {}
-        # function to refresh API key if expired
-        self.refresh_api_key_hook = None
-        # Username for HTTP basic authentication
-        self.username = ""
-        # Password for HTTP basic authentication
-        self.password = ""
-        # Logging Settings
-        self.logger = {}
-        self.logger["package_logger"] = logging.getLogger("submarine.job")
-        self.logger["urllib3_logger"] = logging.getLogger("urllib3")
-        # Log format
-        self.logger_format = '%(asctime)s %(levelname)s %(message)s'
-        # Log stream handler
-        self.logger_stream_handler = None
-        # Log file handler
-        self.logger_file_handler = None
-        # Debug file location
-        self.logger_file = None
-        # Debug switch
-        self.debug = False
-
-        # SSL/TLS verification
-        # Set this to false to skip verifying SSL certificate when calling API
-        # from https server.
-        self.verify_ssl = True
-        # Set this to customize the certificate file to verify the peer.
-        self.ssl_ca_cert = None
-        # client certificate file
-        self.cert_file = None
-        # client key file
-        self.key_file = None
-        # Set this to True/False to enable/disable SSL hostname verification.
-        self.assert_hostname = None
-
-        # urllib3 connection pool's maximum number of connections saved
-        # per pool. urllib3 uses 1 connection as default value, but this is
-        # not the best value when you are making a lot of possibly parallel
-        # requests to the same host, which is often the case here.
-        # cpu_count * 5 is used as default value to increase performance.
-        self.connection_pool_maxsize = multiprocessing.cpu_count() * 5
-
-        # Proxy URL
-        self.proxy = None
-        # Safe chars for path_param
-        self.safe_chars_for_path_param = ''
-
-    @property
-    def logger_file(self):
-        """The logger file.
-
-        If the logger_file is None, then add stream handler and remove file
-        handler. Otherwise, add file handler and remove stream handler.
-
-        :param value: The logger_file path.
-        :type: str
-        """
-        return self.__logger_file
-
-    @logger_file.setter
-    def logger_file(self, value):
-        """The logger file.
-
-        If the logger_file is None, then add stream handler and remove file
-        handler. Otherwise, add file handler and remove stream handler.
-
-        :param value: The logger_file path.
-        :type: str
-        """
-        self.__logger_file = value
-        if self.__logger_file:
-            # If set logging file,
-            # then add file handler and remove stream handler.
-            self.logger_file_handler = logging.FileHandler(self.__logger_file)
-            self.logger_file_handler.setFormatter(self.logger_formatter)
-            for _, logger in six.iteritems(self.logger):
-                logger.addHandler(self.logger_file_handler)
-                if self.logger_stream_handler:
-                    logger.removeHandler(self.logger_stream_handler)
-        else:
-            # If not set logging file,
-            # then add stream handler and remove file handler.
-            self.logger_stream_handler = logging.StreamHandler()
-            self.logger_stream_handler.setFormatter(self.logger_formatter)
-            for _, logger in six.iteritems(self.logger):
-                logger.addHandler(self.logger_stream_handler)
-                if self.logger_file_handler:
-                    logger.removeHandler(self.logger_file_handler)
-
-    @property
-    def debug(self):
-        """Debug status
-
-        :param value: The debug status, True or False.
-        :type: bool
-        """
-        return self.__debug
-
-    @debug.setter
-    def debug(self, value):
-        """Debug status
-
-        :param value: The debug status, True or False.
-        :type: bool
-        """
-        self.__debug = value
-        if self.__debug:
-            # if debug status is True, turn on debug logging
-            for _, logger in six.iteritems(self.logger):
-                logger.setLevel(logging.DEBUG)
-            # turn on httplib debug
-            httplib.HTTPConnection.debuglevel = 1
-        else:
-            # if debug status is False, turn off debug logging,
-            # setting log level to default `logging.WARNING`
-            for _, logger in six.iteritems(self.logger):
-                logger.setLevel(logging.WARNING)
-            # turn off httplib debug
-            httplib.HTTPConnection.debuglevel = 0
-
-    @property
-    def logger_format(self):
-        """The logger format.
-
-        The logger_formatter will be updated when sets logger_format.
-
-        :param value: The format string.
-        :type: str
-        """
-        return self.__logger_format
-
-    @logger_format.setter
-    def logger_format(self, value):
-        """The logger format.
-
-        The logger_formatter will be updated when sets logger_format.
-
-        :param value: The format string.
-        :type: str
-        """
-        self.__logger_format = value
-        self.logger_formatter = logging.Formatter(self.__logger_format)
-
-    def get_api_key_with_prefix(self, identifier):
-        """Gets API key (with prefix if set).
-
-        :param identifier: The identifier of apiKey.
-        :return: The token for api key authentication.
-        """
-        if self.refresh_api_key_hook:
-            self.refresh_api_key_hook(self)
-
-        key = self.api_key.get(identifier)
-        if key:
-            prefix = self.api_key_prefix.get(identifier)
-            if prefix:
-                return "%s %s" % (prefix, key)
-            else:
-                return key
-
-    def get_basic_auth_token(self):
-        """Gets HTTP basic authentication header (string).
-
-        :return: The token for basic HTTP authentication.
-        """
-        return urllib3.util.make_headers(basic_auth=self.username + ':' +
-                                         self.password).get('authorization')
-
-    def auth_settings(self):
-        """Gets Auth Settings dict for api client.
-
-        :return: The Auth Settings information dict.
-        """
-        return {}
-
-    def to_debug_report(self):
-        """Gets the essential information for debugging.
-
-        :return: The report for debugging.
-        """
-        return "Python SDK Debug Report:\n"\
-               "OS: {env}\n"\
-               "Python Version: {pyversion}\n"\
-               "Version of the API: 0.4.0-SNAPSHOT\n"\
-               "SDK Package Version: 1.0.0".\
-               format(env=sys.platform, pyversion=sys.version)
diff --git a/submarine-sdk/pysubmarine/submarine/job/models/job_library_spec.py b/submarine-sdk/pysubmarine/submarine/job/models/job_library_spec.py
deleted file mode 100644
index 8d017f3..0000000
--- a/submarine-sdk/pysubmarine/submarine/job/models/job_library_spec.py
+++ /dev/null
@@ -1,233 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# coding: utf-8
-"""
-    Submarine Experiment API
-
-    The Submarine REST API allows you to create, list, and get experiments. TheAPI is hosted under the /v1/jobs route on the Submarine server. For example,to list experiments on a server hosted at http://localhost:8080, accesshttp://localhost:8080/api/v1/jobs/  # noqa: E501
-
-    OpenAPI spec version: 0.4.0-SNAPSHOT
-    Contact: submarine-dev@submarine.apache.org
-    Generated by: https://github.com/swagger-api/swagger-codegen.git
-"""
-
-import pprint
-import re  # noqa: F401
-
-import six
-
-
-class JobLibrarySpec(object):
-    """NOTE: This class is auto generated by the swagger code generator program.
-
-    Do not edit the class manually.
-    """
-    """
-    Attributes:
-      swagger_types (dict): The key is attribute name
-                            and the value is attribute type.
-      attribute_map (dict): The key is attribute name
-                            and the value is json key in definition.
-    """
-    swagger_types = {
-        'name': 'str',
-        'version': 'str',
-        'image': 'str',
-        'cmd': 'str',
-        'env_vars': 'dict(str, str)'
-    }
-
-    attribute_map = {
-        'name': 'name',
-        'version': 'version',
-        'image': 'image',
-        'cmd': 'cmd',
-        'env_vars': 'envVars'
-    }
-
-    def __init__(self,
-                 name=None,
-                 version=None,
-                 image=None,
-                 cmd=None,
-                 env_vars=None):  # noqa: E501
-        """JobLibrarySpec - a model defined in Swagger"""  # noqa: E501
-        self._name = None
-        self._version = None
-        self._image = None
-        self._cmd = None
-        self._env_vars = None
-        self.discriminator = None
-        if name is not None:
-            self.name = name
-        if version is not None:
-            self.version = version
-        if image is not None:
-            self.image = image
-        if cmd is not None:
-            self.cmd = cmd
-        if env_vars is not None:
-            self.env_vars = env_vars
-
-    @property
-    def name(self):
-        """Gets the name of this JobLibrarySpec.  # noqa: E501
-
-
-        :return: The name of this JobLibrarySpec.  # noqa: E501
-        :rtype: str
-        """
-        return self._name
-
-    @name.setter
-    def name(self, name):
-        """Sets the name of this JobLibrarySpec.
-
-
-        :param name: The name of this JobLibrarySpec.  # noqa: E501
-        :type: str
-        """
-
-        self._name = name
-
-    @property
-    def version(self):
-        """Gets the version of this JobLibrarySpec.  # noqa: E501
-
-
-        :return: The version of this JobLibrarySpec.  # noqa: E501
-        :rtype: str
-        """
-        return self._version
-
-    @version.setter
-    def version(self, version):
-        """Sets the version of this JobLibrarySpec.
-
-
-        :param version: The version of this JobLibrarySpec.  # noqa: E501
-        :type: str
-        """
-
-        self._version = version
-
-    @property
-    def image(self):
-        """Gets the image of this JobLibrarySpec.  # noqa: E501
-
-
-        :return: The image of this JobLibrarySpec.  # noqa: E501
-        :rtype: str
-        """
-        return self._image
-
-    @image.setter
-    def image(self, image):
-        """Sets the image of this JobLibrarySpec.
-
-
-        :param image: The image of this JobLibrarySpec.  # noqa: E501
-        :type: str
-        """
-
-        self._image = image
-
-    @property
-    def cmd(self):
-        """Gets the cmd of this JobLibrarySpec.  # noqa: E501
-
-
-        :return: The cmd of this JobLibrarySpec.  # noqa: E501
-        :rtype: str
-        """
-        return self._cmd
-
-    @cmd.setter
-    def cmd(self, cmd):
-        """Sets the cmd of this JobLibrarySpec.
-
-
-        :param cmd: The cmd of this JobLibrarySpec.  # noqa: E501
-        :type: str
-        """
-
-        self._cmd = cmd
-
-    @property
-    def env_vars(self):
-        """Gets the env_vars of this JobLibrarySpec.  # noqa: E501
-
-
-        :return: The env_vars of this JobLibrarySpec.  # noqa: E501
-        :rtype: dict(str, str)
-        """
-        return self._env_vars
-
-    @env_vars.setter
-    def env_vars(self, env_vars):
-        """Sets the env_vars of this JobLibrarySpec.
-
-
-        :param env_vars: The env_vars of this JobLibrarySpec.  # noqa: E501
-        :type: dict(str, str)
-        """
-
-        self._env_vars = env_vars
-
-    def to_dict(self):
-        """Returns the model properties as a dict"""
-        result = {}
-
-        for attr, _ in six.iteritems(self.swagger_types):
-            value = getattr(self, attr)
-            if isinstance(value, list):
-                result[attr] = list(
-                    map(lambda x: x.to_dict()
-                        if hasattr(x, "to_dict") else x, value))
-            elif hasattr(value, "to_dict"):
-                result[attr] = value.to_dict()
-            elif isinstance(value, dict):
-                result[attr] = dict(
-                    map(
-                        lambda item: (item[0], item[1].to_dict())
-                        if hasattr(item[1], "to_dict") else item,
-                        value.items()))
-            else:
-                result[attr] = value
-        if issubclass(JobLibrarySpec, dict):
-            for key, value in self.items():
-                result[key] = value
-
-        return result
-
-    def to_str(self):
-        """Returns the string representation of the model"""
-        return pprint.pformat(self.to_dict())
-
-    def __repr__(self):
-        """For `print` and `pprint`"""
-        return self.to_str()
-
-    def __eq__(self, other):
-        """Returns true if both objects are equal"""
-        if not isinstance(other, JobLibrarySpec):
-            return False
-
-        return self.__dict__ == other.__dict__
-
-    def __ne__(self, other):
-        """Returns true if both objects are not equal"""
-        return not self == other
diff --git a/submarine-sdk/pysubmarine/submarine/job/models/job_spec.py b/submarine-sdk/pysubmarine/submarine/job/models/job_spec.py
deleted file mode 100644
index f038d23..0000000
--- a/submarine-sdk/pysubmarine/submarine/job/models/job_spec.py
+++ /dev/null
@@ -1,233 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# coding: utf-8
-"""
-    Submarine Experiment API
-
-    The Submarine REST API allows you to create, list, and get experiments. TheAPI is hosted under the /v1/jobs route on the Submarine server. For example,to list experiments on a server hosted at http://localhost:8080, accesshttp://localhost:8080/api/v1/jobs/  # noqa: E501
-
-    OpenAPI spec version: 0.4.0-SNAPSHOT
-    Contact: submarine-dev@submarine.apache.org
-    Generated by: https://github.com/swagger-api/swagger-codegen.git
-"""
-
-import pprint
-import re  # noqa: F401
-
-import six
-
-
-class JobSpec(object):
-    """NOTE: This class is auto generated by the swagger code generator program.
-
-    Do not edit the class manually.
-    """
-    """
-    Attributes:
-      swagger_types (dict): The key is attribute name
-                            and the value is attribute type.
-      attribute_map (dict): The key is attribute name
-                            and the value is json key in definition.
-    """
-    swagger_types = {
-        'name': 'str',
-        'namespace': 'str',
-        'library_spec': 'JobLibrarySpec',
-        'task_specs': 'dict(str, JobTaskSpec)',
-        'projects': 'str'
-    }
-
-    attribute_map = {
-        'name': 'name',
-        'namespace': 'namespace',
-        'library_spec': 'librarySpec',
-        'task_specs': 'taskSpecs',
-        'projects': 'projects'
-    }
-
-    def __init__(self,
-                 name=None,
-                 namespace=None,
-                 library_spec=None,
-                 task_specs=None,
-                 projects=None):  # noqa: E501
-        """JobSpec - a model defined in Swagger"""  # noqa: E501
-        self._name = None
-        self._namespace = None
-        self._library_spec = None
-        self._task_specs = None
-        self._projects = None
-        self.discriminator = None
-        if name is not None:
-            self.name = name
-        if namespace is not None:
-            self.namespace = namespace
-        if library_spec is not None:
-            self.library_spec = library_spec
-        if task_specs is not None:
-            self.task_specs = task_specs
-        if projects is not None:
-            self.projects = projects
-
-    @property
-    def name(self):
-        """Gets the name of this JobSpec.  # noqa: E501
-
-
-        :return: The name of this JobSpec.  # noqa: E501
-        :rtype: str
-        """
-        return self._name
-
-    @name.setter
-    def name(self, name):
-        """Sets the name of this JobSpec.
-
-
-        :param name: The name of this JobSpec.  # noqa: E501
-        :type: str
-        """
-
-        self._name = name
-
-    @property
-    def namespace(self):
-        """Gets the namespace of this JobSpec.  # noqa: E501
-
-
-        :return: The namespace of this JobSpec.  # noqa: E501
-        :rtype: str
-        """
-        return self._namespace
-
-    @namespace.setter
-    def namespace(self, namespace):
-        """Sets the namespace of this JobSpec.
-
-
-        :param namespace: The namespace of this JobSpec.  # noqa: E501
-        :type: str
-        """
-
-        self._namespace = namespace
-
-    @property
-    def library_spec(self):
-        """Gets the library_spec of this JobSpec.  # noqa: E501
-
-
-        :return: The library_spec of this JobSpec.  # noqa: E501
-        :rtype: JobLibrarySpec
-        """
-        return self._library_spec
-
-    @library_spec.setter
-    def library_spec(self, library_spec):
-        """Sets the library_spec of this JobSpec.
-
-
-        :param library_spec: The library_spec of this JobSpec.  # noqa: E501
-        :type: JobLibrarySpec
-        """
-
-        self._library_spec = library_spec
-
-    @property
-    def task_specs(self):
-        """Gets the task_specs of this JobSpec.  # noqa: E501
-
-
-        :return: The task_specs of this JobSpec.  # noqa: E501
-        :rtype: dict(str, JobTaskSpec)
-        """
-        return self._task_specs
-
-    @task_specs.setter
-    def task_specs(self, task_specs):
-        """Sets the task_specs of this JobSpec.
-
-
-        :param task_specs: The task_specs of this JobSpec.  # noqa: E501
-        :type: dict(str, JobTaskSpec)
-        """
-
-        self._task_specs = task_specs
-
-    @property
-    def projects(self):
-        """Gets the projects of this JobSpec.  # noqa: E501
-
-
-        :return: The projects of this JobSpec.  # noqa: E501
-        :rtype: str
-        """
-        return self._projects
-
-    @projects.setter
-    def projects(self, projects):
-        """Sets the projects of this JobSpec.
-
-
-        :param projects: The projects of this JobSpec.  # noqa: E501
-        :type: str
-        """
-
-        self._projects = projects
-
-    def to_dict(self):
-        """Returns the model properties as a dict"""
-        result = {}
-
-        for attr, _ in six.iteritems(self.swagger_types):
-            value = getattr(self, attr)
-            if isinstance(value, list):
-                result[attr] = list(
-                    map(lambda x: x.to_dict()
-                        if hasattr(x, "to_dict") else x, value))
-            elif hasattr(value, "to_dict"):
-                result[attr] = value.to_dict()
-            elif isinstance(value, dict):
-                result[attr] = dict(
-                    map(
-                        lambda item: (item[0], item[1].to_dict())
-                        if hasattr(item[1], "to_dict") else item,
-                        value.items()))
-            else:
-                result[attr] = value
-        if issubclass(JobSpec, dict):
-            for key, value in self.items():
-                result[key] = value
-
-        return result
-
-    def to_str(self):
-        """Returns the string representation of the model"""
-        return pprint.pformat(self.to_dict())
-
-    def __repr__(self):
-        """For `print` and `pprint`"""
-        return self.to_str()
-
-    def __eq__(self, other):
-        """Returns true if both objects are equal"""
-        if not isinstance(other, JobSpec):
-            return False
-
-        return self.__dict__ == other.__dict__
-
-    def __ne__(self, other):
-        """Returns true if both objects are not equal"""
-        return not self == other
diff --git a/submarine-server/server-core/src/main/java/org/apache/submarine/server/Bootstrap.java b/submarine-server/server-core/src/main/java/org/apache/submarine/server/Bootstrap.java
index c4bee19..e8abd45 100644
--- a/submarine-server/server-core/src/main/java/org/apache/submarine/server/Bootstrap.java
+++ b/submarine-server/server-core/src/main/java/org/apache/submarine/server/Bootstrap.java
@@ -42,10 +42,10 @@ public class Bootstrap extends HttpServlet {
     OpenAPI oas = new OpenAPI();
     Info info = new Info()
              .title("Submarine Experiment API")
-             .description("The Submarine REST API allows you to create, list, and get experiments. The" +
-                     "API is hosted under the /v1/jobs route on the Submarine server. For example," +
-                     "to list experiments on a server hosted at http://localhost:8080, access" +
-                     "http://localhost:8080/api/v1/jobs/")
+             .description("The Submarine REST API allows you to create, list, and get experiments. The\n" +
+                     "API is hosted under the /v1/experiment route on the Submarine server. For example,\n" +
+                     "to list experiments on a server hosted at http://localhost:8080, access\n" +
+                     "http://localhost:8080/api/v1/experiment/")
              .termsOfService("http://swagger.io/terms/")
              .contact(new Contact()
              .email("submarine-dev@submarine.apache.org"))


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@submarine.apache.org
For additional commands, e-mail: dev-help@submarine.apache.org