You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@submarine.apache.org by pi...@apache.org on 2022/01/15 17:24:31 UTC

[submarine] branch master updated: SUBMARINE-1134. Connect API for CLI Environment

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

pingsutw 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 2ed83e2  SUBMARINE-1134. Connect API for CLI Environment
2ed83e2 is described below

commit 2ed83e277aa3466959a06aa44ebea1ecd5ec9415
Author: atosystem <at...@hotmail.com>
AuthorDate: Thu Jan 13 11:27:44 2022 +0800

    SUBMARINE-1134. Connect API for CLI Environment
    
    ### What is this PR for?
    Implement the following commands for submarine cli
    * `submarine list environment`
    * `submarine get environment <name>`
    * `submarine delete environment <name>`
    
    Some java API code is fixed
    
    ### What type of PR is it?
    [Feature]
    
    ### Todos
    
    None
    
    ### What is the Jira issue?
    https://issues.apache.org/jira/projects/SUBMARINE/issues/SUBMARINE-1134
    
    ### How should this be tested?
    e2e test is implemented
    
    ### Screenshots (if appropriate)
    none
    
    ### Questions:
    * Do the license files need updating? No
    * Are there breaking changes for older versions? No
    * Does this need new documentation? Yes
    
    Author: atosystem <at...@hotmail.com>
    
    Signed-off-by: Kevin <pi...@apache.org>
    
    Closes #867 from atosystem/SUBMARINE-1134 and squashes the following commits:
    
    702e8ed8 [atosystem] SUBMARINE-1134. fix github action
    40eb5339 [atosystem] SUBMARINE-1134. lint
    a06dcd38 [atosystem] SUBMARINE-1134. implement environment cli
---
 .github/workflows/python.yml                       |   2 +-
 dev-support/pysubmarine/swagger_config.json        |   4 +-
 .../submarine/cli/environment/command.py           | 119 ++++++-
 .../pysubmarine/submarine/client/__init__.py       |   3 -
 .../pysubmarine/submarine/client/api/__init__.py   |   1 -
 .../submarine/client/api/environment_api.py        | 344 ++++++++++++++++++-
 .../submarine/client/api/environment_client.py     | 136 ++++++++
 .../submarine/client/api/environments_api.py       | 382 ---------------------
 .../submarine/client/models/__init__.py            |   2 -
 .../submarine/client/models/environment_id.py      | 158 ---------
 .../models/{environment.py => serve_request.py}    |  98 ++++--
 .../pysubmarine/tests/cli/test_environment.py      |  67 +++-
 .../tests/environment/test_environment_client.py   |  42 +++
 .../submarine/server/rest/EnvironmentRestApi.java  |  16 +-
 14 files changed, 754 insertions(+), 620 deletions(-)

diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml
index b7ac163..f2369ff 100644
--- a/.github/workflows/python.yml
+++ b/.github/workflows/python.yml
@@ -125,4 +125,4 @@ jobs:
           kubectl describe nodes
           kubectl get pods
           kubectl -n default get events --sort-by='{.lastTimestamp}'
-        if: ${{ failure() }}
+        if: ${{ failure() }}
\ No newline at end of file
diff --git a/dev-support/pysubmarine/swagger_config.json b/dev-support/pysubmarine/swagger_config.json
index 3e16d23..f462645 100644
--- a/dev-support/pysubmarine/swagger_config.json
+++ b/dev-support/pysubmarine/swagger_config.json
@@ -1,5 +1,5 @@
 {
-  "packageName" : "submarine.experiment",
-  "projectName" : "submarine.experiment",
+  "packageName" : "submarine.client",
+  "projectName" : "submarine.client",
   "packageVersion": "0.7.0-SNAPSHOT"
 }
diff --git a/submarine-sdk/pysubmarine/submarine/cli/environment/command.py b/submarine-sdk/pysubmarine/submarine/cli/environment/command.py
index 85c620f..a545e98 100644
--- a/submarine-sdk/pysubmarine/submarine/cli/environment/command.py
+++ b/submarine-sdk/pysubmarine/submarine/cli/environment/command.py
@@ -15,24 +15,129 @@
  under the License.
 """
 
+import json
+import time
+
 import click
+from rich.console import Console
+from rich.json import JSON as richJSON
+from rich.panel import Panel
+from rich.table import Table
+
+from submarine.cli.config.config import loadConfig
+from submarine.client.api.environment_client import EnvironmentClient
+from submarine.client.exceptions import ApiException
+
+submarineCliConfig = loadConfig()
+if submarineCliConfig is None:
+    exit(1)
+environmentClient = EnvironmentClient(
+    host="http://{}:{}".format(
+        submarineCliConfig.connection.hostname, submarineCliConfig.connection.port
+    )
+)
+
+POLLING_INTERVAL = 1  # sec
+TIMEOUT = 30  # sec
 
 
 @click.command("environment")
 def list_environment():
     """List environment"""
-    click.echo("list environment!")
+    COLS_TO_SHOW = ["Name", "Id", "dockerImage"]
+    console = Console()
+    try:
+        thread = environmentClient.list_environments_async()
+        timeout = time.time() + TIMEOUT
+        with console.status("[bold green] Fetching Environments..."):
+            while not thread.ready():
+                time.sleep(POLLING_INTERVAL)
+                if time.time() > timeout:
+                    console.print("[bold red] Timeout!")
+                    return
+
+        result = thread.get()
+        results = result.result
+
+        results = list(
+            map(
+                lambda r: [
+                    r["environmentSpec"]["name"],
+                    r["environmentId"],
+                    r["environmentSpec"]["dockerImage"],
+                ],
+                results,
+            )
+        )
+
+        table = Table(title="List of Environments")
+
+        for col in COLS_TO_SHOW:
+            table.add_column(col, overflow="fold")
+        for res in results:
+            table.add_row(*res)
+
+        console.print(table)
+
+    except ApiException as err:
+        if err.body is not None:
+            errbody = json.loads(err.body)
+            click.echo("[Api Error] {}".format(errbody["message"]))
+        else:
+            click.echo("[Api Error] {}".format(err))
 
 
 @click.command("environment")
-@click.argument("id")
-def get_environment(id):
+@click.argument("name")
+def get_environment(name):
     """Get environment"""
-    click.echo("get environment! id={}".format(id))
+    console = Console()
+    try:
+        thread = environmentClient.get_environment_async(name)
+        timeout = time.time() + TIMEOUT
+        with console.status("[bold green] Fetching Environment(name = {} )...".format(name)):
+            while not thread.ready():
+                time.sleep(POLLING_INTERVAL)
+                if time.time() > timeout:
+                    console.print("[bold red] Timeout!")
+                    return
+
+        result = thread.get()
+        result = result.result
+
+        json_data = richJSON.from_data(result)
+        console.print(Panel(json_data, title="Environment(name = {} )".format(name)))
+    except ApiException as err:
+        if err.body is not None:
+            errbody = json.loads(err.body)
+            click.echo("[Api Error] {}".format(errbody["message"]))
+        else:
+            click.echo("[Api Error] {}".format(err))
 
 
 @click.command("environment")
-@click.argument("id")
-def delete_environment(id):
+@click.argument("name")
+def delete_environment(name):
     """Delete environment"""
-    click.echo("delete environment! id={}".format(id))
+    console = Console()
+    try:
+        thread = environmentClient.delete_environment_async(name)
+        timeout = time.time() + TIMEOUT
+        with console.status("[bold green] Deleting Environment(name = {} )...".format(name)):
+            while not thread.ready():
+                time.sleep(POLLING_INTERVAL)
+                if time.time() > timeout:
+                    console.print("[bold red] Timeout!")
+                    return
+
+        result = thread.get()
+        result = result.result
+
+        console.print("[bold green] Environment(name = {} ) deleted".format(name))
+
+    except ApiException as err:
+        if err.body is not None:
+            errbody = json.loads(err.body)
+            click.echo("[Api Error] {}".format(errbody["message"]))
+        else:
+            click.echo("[Api Error] {}".format(err))
diff --git a/submarine-sdk/pysubmarine/submarine/client/__init__.py b/submarine-sdk/pysubmarine/submarine/client/__init__.py
index c14fa11..47834d0 100644
--- a/submarine-sdk/pysubmarine/submarine/client/__init__.py
+++ b/submarine-sdk/pysubmarine/submarine/client/__init__.py
@@ -34,7 +34,6 @@ __version__ = "0.7.0-SNAPSHOT"
 
 # import apis into sdk package
 from submarine.client.api.environment_api import EnvironmentApi
-from submarine.client.api.environments_api import EnvironmentsApi
 from submarine.client.api.experiment_api import ExperimentApi
 from submarine.client.api.notebook_api import NotebookApi
 
@@ -51,8 +50,6 @@ from submarine.client.exceptions import (
 
 # import models into sdk package
 from submarine.client.models.code_spec import CodeSpec
-from submarine.client.models.environment import Environment
-from submarine.client.models.environment_id import EnvironmentId
 from submarine.client.models.environment_spec import EnvironmentSpec
 from submarine.client.models.experiment_meta import ExperimentMeta
 from submarine.client.models.experiment_spec import ExperimentSpec
diff --git a/submarine-sdk/pysubmarine/submarine/client/api/__init__.py b/submarine-sdk/pysubmarine/submarine/client/api/__init__.py
index 09104dd..19defd6 100644
--- a/submarine-sdk/pysubmarine/submarine/client/api/__init__.py
+++ b/submarine-sdk/pysubmarine/submarine/client/api/__init__.py
@@ -17,7 +17,6 @@ from __future__ import absolute_import
 
 # import apis into api package
 from submarine.client.api.environment_api import EnvironmentApi
-from submarine.client.api.environments_api import EnvironmentsApi
 from submarine.client.api.experiment_api import ExperimentApi
 from submarine.client.api.notebook_api import NotebookApi
 
diff --git a/submarine-sdk/pysubmarine/submarine/client/api/environment_api.py b/submarine-sdk/pysubmarine/submarine/client/api/environment_api.py
index 533c1ad..609b534 100644
--- a/submarine-sdk/pysubmarine/submarine/client/api/environment_api.py
+++ b/submarine-sdk/pysubmarine/submarine/client/api/environment_api.py
@@ -66,7 +66,7 @@ class EnvironmentApi(object):
                                  number provided, it will be total request
                                  timeout. It can also be a pair (tuple) of
                                  (connection, read) timeouts.
-        :return: Environment
+        :return: JsonResponse
                  If the method is called asynchronously,
                  returns the request thread.
         """
@@ -92,7 +92,7 @@ class EnvironmentApi(object):
                                  number provided, it will be total request
                                  timeout. It can also be a pair (tuple) of
                                  (connection, read) timeouts.
-        :return: tuple(Environment, status_code(int), headers(HTTPHeaderDict))
+        :return: tuple(JsonResponse, status_code(int), headers(HTTPHeaderDict))
                  If the method is called asynchronously,
                  returns the request thread.
         """
@@ -148,7 +148,117 @@ class EnvironmentApi(object):
             body=body_params,
             post_params=form_params,
             files=local_var_files,
-            response_type="Environment",  # noqa: E501
+            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_environment(self, id, **kwargs):  # noqa: E501
+        """Delete the environment  # 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_environment(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_environment_with_http_info(id, **kwargs)  # noqa: E501
+
+    def delete_environment_with_http_info(self, id, **kwargs):  # noqa: E501
+        """Delete the environment  # 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_environment_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_environment" % 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 local_var_params["id"] is None  # noqa: E501
+        ):  # noqa: E501
+            raise ApiValueError(
+                "Missing the required parameter `id` when calling `delete_environment`"
+            )  # 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/environment/{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
@@ -174,7 +284,7 @@ class EnvironmentApi(object):
                                  number provided, it will be total request
                                  timeout. It can also be a pair (tuple) of
                                  (connection, read) timeouts.
-        :return: Environment
+        :return: JsonResponse
                  If the method is called asynchronously,
                  returns the request thread.
         """
@@ -200,7 +310,7 @@ class EnvironmentApi(object):
                                  number provided, it will be total request
                                  timeout. It can also be a pair (tuple) of
                                  (connection, read) timeouts.
-        :return: tuple(Environment, status_code(int), headers(HTTPHeaderDict))
+        :return: tuple(JsonResponse, status_code(int), headers(HTTPHeaderDict))
                  If the method is called asynchronously,
                  returns the request thread.
         """
@@ -258,7 +368,229 @@ class EnvironmentApi(object):
             body=body_params,
             post_params=form_params,
             files=local_var_files,
-            response_type="Environment",  # noqa: E501
+            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_environment(self, **kwargs):  # noqa: E501
+        """List of Environments  # 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_environment(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_environment_with_http_info(**kwargs)  # noqa: E501
+
+    def list_environment_with_http_info(self, **kwargs):  # noqa: E501
+        """List of Environments  # 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_environment_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_environment" % 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/environment",
+            "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 update_environment(self, id, **kwargs):  # noqa: E501
+        """Update the environment 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.update_environment(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool: execute request asynchronously
+        :param str id: (required)
+        :param EnvironmentSpec environment_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.update_environment_with_http_info(id, **kwargs)  # noqa: E501
+
+    def update_environment_with_http_info(self, id, **kwargs):  # noqa: E501
+        """Update the environment 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.update_environment_with_http_info(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool: execute request asynchronously
+        :param str id: (required)
+        :param EnvironmentSpec environment_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", "environment_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 update_environment" % 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 local_var_params["id"] is None  # noqa: E501
+        ):  # noqa: E501
+            raise ApiValueError(
+                "Missing the required parameter `id` when calling `update_environment`"
+            )  # 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 "environment_spec" in local_var_params:
+            body_params = local_var_params["environment_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/environment/{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
diff --git a/submarine-sdk/pysubmarine/submarine/client/api/environment_client.py b/submarine-sdk/pysubmarine/submarine/client/api/environment_client.py
new file mode 100644
index 0000000..3a12dff
--- /dev/null
+++ b/submarine-sdk/pysubmarine/submarine/client/api/environment_client.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.
+
+import logging
+import os
+
+from submarine.client.api.environment_api import EnvironmentApi
+from submarine.client.api_client import ApiClient
+from submarine.client.configuration import Configuration
+
+logger = logging.getLogger(__name__)
+logging.basicConfig(format="%(message)s")
+logging.getLogger().setLevel(logging.INFO)
+
+
+def generate_host():
+    """
+    Generate submarine host
+    :return: submarine host
+    """
+    submarine_server_dns_name = str(os.environ.get("SUBMARINE_SERVER_DNS_NAME"))
+    submarine_server_port = str(os.environ.get("SUBMARINE_SERVER_PORT"))
+    host = submarine_server_dns_name + ":" + submarine_server_port
+    return host
+
+
+class EnvironmentClient:
+    def __init__(self, host: str = generate_host()):
+        """
+        Submarine environment client constructor
+        :param host: An HTTP URI like http://submarine-server:8080.
+        """
+        # TODO(pingsutw): support authentication for talking to the submarine server
+        self.host = host
+        configuration = Configuration()
+        configuration.host = host + "/api"
+        api_client = ApiClient(configuration=configuration)
+        self.environment_api = EnvironmentApi(api_client=api_client)
+
+    def create_environment(self, environment_spec):
+        """
+        Create an environment
+        :param environment_spec: submarine environment spec
+        :return: submarine environment
+        """
+        response = self.environment_api.create_environment(environment_spec=environment_spec)
+        return response.result
+
+    def create_environment_async(self, environment_spec):
+        """
+        Create an environment (async)
+        :param environment_spec: submarine environment spec
+        :return: thread
+        """
+        thread = self.environment_api.create_environment(
+            environment_spec=environment_spec, async_req=True
+        )
+        return thread
+
+    def update_environment(self, name, environment_spec):
+        """
+        Update an environment
+        :param name: submarine environment name
+        :param environment_spec: submarine environment spec
+        :return: submarine environment
+        """
+        response = self.environment_api.update_environment(
+            id=name, environment_spec=environment_spec
+        )
+        return response.result
+
+    def get_environment(self, name):
+        """
+        Get the environment's detailed info by name
+        :param name: submarine environment name
+        :return: submarine environment
+        """
+        response = self.environment_api.get_environment(id=name)
+        return response.result
+
+    def get_environment_async(self, name):
+        """
+        Get the environment's detailed info by name (async)
+        :param name: submarine environment name
+        :return: thread
+        """
+        thread = self.environment_api.get_environment(id=name, async_req=True)
+        return thread
+
+    def list_environments(self, status=None):
+        """
+        List all environments for the user
+        :param status:
+        :return: List of submarine environments
+        """
+        response = self.environment_api.list_environment(status=status)
+        return response.result
+
+    def list_environments_async(self, status=None):
+        """
+        List all environments for the user (async)
+        :param status:
+        :return: thread
+        """
+        thread = self.environment_api.list_environment(status=status, async_req=True)
+        return thread
+
+    def delete_environment(self, name):
+        """
+        Delete the Submarine environment
+        :param name: Submarine environment name
+        :return: The detailed info about deleted submarine environment
+        """
+        response = self.environment_api.delete_environment(name)
+        return response.result
+
+    def delete_environment_async(self, name):
+        """
+        Delete the Submarine environment (async)
+        :param name: Submarine environment name
+        :return: thread
+        """
+        thread = self.environment_api.delete_environment(name, async_req=True)
+        return thread
diff --git a/submarine-sdk/pysubmarine/submarine/client/api/environments_api.py b/submarine-sdk/pysubmarine/submarine/client/api/environments_api.py
deleted file mode 100644
index d5d02a0..0000000
--- a/submarine-sdk/pysubmarine/submarine/client/api/environments_api.py
+++ /dev/null
@@ -1,382 +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 API
-
-    The Submarine REST API allows you to access Submarine resources such as,  experiments, environments and notebooks. The  API is hosted under the /v1 path 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.7.0-SNAPSHOT
-    Contact: 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.client.api_client import ApiClient
-from submarine.client.exceptions import ApiTypeError, ApiValueError  # noqa: F401
-
-
-class EnvironmentsApi(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 delete_environment(self, id, **kwargs):  # noqa: E501
-        """Delete the environment  # 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_environment(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: Environment
-                 If the method is called asynchronously,
-                 returns the request thread.
-        """
-        kwargs["_return_http_data_only"] = True
-        return self.delete_environment_with_http_info(id, **kwargs)  # noqa: E501
-
-    def delete_environment_with_http_info(self, id, **kwargs):  # noqa: E501
-        """Delete the environment  # 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_environment_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(Environment, 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_environment" % 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 local_var_params["id"] is None  # noqa: E501
-        ):  # noqa: E501
-            raise ApiValueError(
-                "Missing the required parameter `id` when calling `delete_environment`"
-            )  # 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/environment/{id}",
-            "DELETE",
-            path_params,
-            query_params,
-            header_params,
-            body=body_params,
-            post_params=form_params,
-            files=local_var_files,
-            response_type="Environment",  # 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_environment(self, **kwargs):  # noqa: E501
-        """List of Environments  # 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_environment(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: Environment
-                 If the method is called asynchronously,
-                 returns the request thread.
-        """
-        kwargs["_return_http_data_only"] = True
-        return self.list_environment_with_http_info(**kwargs)  # noqa: E501
-
-    def list_environment_with_http_info(self, **kwargs):  # noqa: E501
-        """List of Environments  # 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_environment_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(Environment, 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_environment" % 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/environment",
-            "GET",
-            path_params,
-            query_params,
-            header_params,
-            body=body_params,
-            post_params=form_params,
-            files=local_var_files,
-            response_type="Environment",  # 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 update_environment(self, id, **kwargs):  # noqa: E501
-        """Update the environment 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.update_environment(id, async_req=True)
-        >>> result = thread.get()
-
-        :param async_req bool: execute request asynchronously
-        :param str id: (required)
-        :param EnvironmentSpec environment_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: Environment
-                 If the method is called asynchronously,
-                 returns the request thread.
-        """
-        kwargs["_return_http_data_only"] = True
-        return self.update_environment_with_http_info(id, **kwargs)  # noqa: E501
-
-    def update_environment_with_http_info(self, id, **kwargs):  # noqa: E501
-        """Update the environment 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.update_environment_with_http_info(id, async_req=True)
-        >>> result = thread.get()
-
-        :param async_req bool: execute request asynchronously
-        :param str id: (required)
-        :param EnvironmentSpec environment_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(Environment, status_code(int), headers(HTTPHeaderDict))
-                 If the method is called asynchronously,
-                 returns the request thread.
-        """
-
-        local_var_params = locals()
-
-        all_params = ["id", "environment_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 update_environment" % 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 local_var_params["id"] is None  # noqa: E501
-        ):  # noqa: E501
-            raise ApiValueError(
-                "Missing the required parameter `id` when calling `update_environment`"
-            )  # 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 "environment_spec" in local_var_params:
-            body_params = local_var_params["environment_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/environment/{id}",
-            "PATCH",
-            path_params,
-            query_params,
-            header_params,
-            body=body_params,
-            post_params=form_params,
-            files=local_var_files,
-            response_type="Environment",  # 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/client/models/__init__.py b/submarine-sdk/pysubmarine/submarine/client/models/__init__.py
index 75be16c..005b99b 100644
--- a/submarine-sdk/pysubmarine/submarine/client/models/__init__.py
+++ b/submarine-sdk/pysubmarine/submarine/client/models/__init__.py
@@ -31,8 +31,6 @@ from __future__ import absolute_import
 
 # import models into model package
 from submarine.client.models.code_spec import CodeSpec
-from submarine.client.models.environment import Environment
-from submarine.client.models.environment_id import EnvironmentId
 from submarine.client.models.environment_spec import EnvironmentSpec
 from submarine.client.models.experiment_meta import ExperimentMeta
 from submarine.client.models.experiment_spec import ExperimentSpec
diff --git a/submarine-sdk/pysubmarine/submarine/client/models/environment_id.py b/submarine-sdk/pysubmarine/submarine/client/models/environment_id.py
deleted file mode 100644
index f4d9756..0000000
--- a/submarine-sdk/pysubmarine/submarine/client/models/environment_id.py
+++ /dev/null
@@ -1,158 +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 API
-
-    The Submarine REST API allows you to access Submarine resources such as,  experiments, environments and notebooks. The  API is hosted under the /v1 path 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.7.0-SNAPSHOT
-    Contact: dev@submarine.apache.org
-    Generated by: https://openapi-generator.tech
-"""
-
-
-import pprint
-import re  # noqa: F401
-
-import six
-
-from submarine.client.configuration import Configuration
-
-
-class EnvironmentId(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 = {"id": "int", "server_timestamp": "int"}
-
-    attribute_map = {"id": "id", "server_timestamp": "serverTimestamp"}
-
-    def __init__(self, id=None, server_timestamp=None, local_vars_configuration=None):  # noqa: E501
-        """EnvironmentId - 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._id = None
-        self._server_timestamp = None
-        self.discriminator = None
-
-        if id is not None:
-            self.id = id
-        if server_timestamp is not None:
-            self.server_timestamp = server_timestamp
-
-    @property
-    def id(self):
-        """Gets the id of this EnvironmentId.  # noqa: E501
-
-
-        :return: The id of this EnvironmentId.  # noqa: E501
-        :rtype: int
-        """
-        return self._id
-
-    @id.setter
-    def id(self, id):
-        """Sets the id of this EnvironmentId.
-
-
-        :param id: The id of this EnvironmentId.  # noqa: E501
-        :type: int
-        """
-
-        self._id = id
-
-    @property
-    def server_timestamp(self):
-        """Gets the server_timestamp of this EnvironmentId.  # noqa: E501
-
-
-        :return: The server_timestamp of this EnvironmentId.  # noqa: E501
-        :rtype: int
-        """
-        return self._server_timestamp
-
-    @server_timestamp.setter
-    def server_timestamp(self, server_timestamp):
-        """Sets the server_timestamp of this EnvironmentId.
-
-
-        :param server_timestamp: The server_timestamp of this EnvironmentId.  # noqa: E501
-        :type: int
-        """
-
-        self._server_timestamp = server_timestamp
-
-    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, EnvironmentId):
-            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, EnvironmentId):
-            return True
-
-        return self.to_dict() != other.to_dict()
diff --git a/submarine-sdk/pysubmarine/submarine/client/models/environment.py b/submarine-sdk/pysubmarine/submarine/client/models/serve_request.py
similarity index 61%
rename from submarine-sdk/pysubmarine/submarine/client/models/environment.py
rename to submarine-sdk/pysubmarine/submarine/client/models/serve_request.py
index d047e46..9a4313e 100644
--- a/submarine-sdk/pysubmarine/submarine/client/models/environment.py
+++ b/submarine-sdk/pysubmarine/submarine/client/models/serve_request.py
@@ -34,7 +34,7 @@ import six
 from submarine.client.configuration import Configuration
 
 
-class Environment(object):
+class ServeRequest(object):
     """NOTE: This class is auto generated by OpenAPI Generator.
     Ref: https://openapi-generator.tech
 
@@ -48,68 +48,96 @@ class Environment(object):
       attribute_map (dict): The key is attribute name
                             and the value is json key in definition.
     """
-    openapi_types = {"environment_id": "EnvironmentId", "environment_spec": "EnvironmentSpec"}
+    openapi_types = {"model_name": "str", "model_version": "str", "namespace": "str"}
 
-    attribute_map = {"environment_id": "environmentId", "environment_spec": "environmentSpec"}
+    attribute_map = {
+        "model_name": "modelName",
+        "model_version": "modelVersion",
+        "namespace": "namespace",
+    }
 
     def __init__(
-        self, environment_id=None, environment_spec=None, local_vars_configuration=None
+        self, model_name=None, model_version=None, namespace=None, local_vars_configuration=None
     ):  # noqa: E501
-        """Environment - a model defined in OpenAPI"""  # noqa: E501
+        """ServeRequest - 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._environment_id = None
-        self._environment_spec = None
+        self._model_name = None
+        self._model_version = None
+        self._namespace = None
         self.discriminator = None
 
-        if environment_id is not None:
-            self.environment_id = environment_id
-        if environment_spec is not None:
-            self.environment_spec = environment_spec
+        if model_name is not None:
+            self.model_name = model_name
+        if model_version is not None:
+            self.model_version = model_version
+        if namespace is not None:
+            self.namespace = namespace
 
     @property
-    def environment_id(self):
-        """Gets the environment_id of this Environment.  # noqa: E501
+    def model_name(self):
+        """Gets the model_name of this ServeRequest.  # noqa: E501
 
 
-        :return: The environment_id of this Environment.  # noqa: E501
-        :rtype: EnvironmentId
+        :return: The model_name of this ServeRequest.  # noqa: E501
+        :rtype: str
         """
-        return self._environment_id
+        return self._model_name
 
-    @environment_id.setter
-    def environment_id(self, environment_id):
-        """Sets the environment_id of this Environment.
+    @model_name.setter
+    def model_name(self, model_name):
+        """Sets the model_name of this ServeRequest.
 
 
-        :param environment_id: The environment_id of this Environment.  # noqa: E501
-        :type: EnvironmentId
+        :param model_name: The model_name of this ServeRequest.  # noqa: E501
+        :type: str
         """
 
-        self._environment_id = environment_id
+        self._model_name = model_name
 
     @property
-    def environment_spec(self):
-        """Gets the environment_spec of this Environment.  # noqa: E501
+    def model_version(self):
+        """Gets the model_version of this ServeRequest.  # noqa: E501
 
 
-        :return: The environment_spec of this Environment.  # noqa: E501
-        :rtype: EnvironmentSpec
+        :return: The model_version of this ServeRequest.  # noqa: E501
+        :rtype: str
         """
-        return self._environment_spec
+        return self._model_version
 
-    @environment_spec.setter
-    def environment_spec(self, environment_spec):
-        """Sets the environment_spec of this Environment.
+    @model_version.setter
+    def model_version(self, model_version):
+        """Sets the model_version of this ServeRequest.
 
 
-        :param environment_spec: The environment_spec of this Environment.  # noqa: E501
-        :type: EnvironmentSpec
+        :param model_version: The model_version of this ServeRequest.  # noqa: E501
+        :type: str
         """
 
-        self._environment_spec = environment_spec
+        self._model_version = model_version
+
+    @property
+    def namespace(self):
+        """Gets the namespace of this ServeRequest.  # noqa: E501
+
+
+        :return: The namespace of this ServeRequest.  # noqa: E501
+        :rtype: str
+        """
+        return self._namespace
+
+    @namespace.setter
+    def namespace(self, namespace):
+        """Sets the namespace of this ServeRequest.
+
+
+        :param namespace: The namespace of this ServeRequest.  # noqa: E501
+        :type: str
+        """
+
+        self._namespace = namespace
 
     def to_dict(self):
         """Returns the model properties as a dict"""
@@ -147,14 +175,14 @@ class Environment(object):
 
     def __eq__(self, other):
         """Returns true if both objects are equal"""
-        if not isinstance(other, Environment):
+        if not isinstance(other, ServeRequest):
             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):
+        if not isinstance(other, ServeRequest):
             return True
 
         return self.to_dict() != other.to_dict()
diff --git a/submarine-sdk/pysubmarine/tests/cli/test_environment.py b/submarine-sdk/pysubmarine/tests/cli/test_environment.py
index 7240bf1..3ebcf1f 100644
--- a/submarine-sdk/pysubmarine/tests/cli/test_environment.py
+++ b/submarine-sdk/pysubmarine/tests/cli/test_environment.py
@@ -13,29 +13,66 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import pytest
 from click.testing import CliRunner
 
 from submarine.cli import main
+from submarine.client.api.environment_client import EnvironmentClient
+from submarine.client.models.environment_spec import EnvironmentSpec
+from submarine.client.models.kernel_spec import KernelSpec
 
+TEST_CONSOLE_WIDTH = 191
 
-def test_list_environment():
-    runner = CliRunner()
-    result = runner.invoke(main.entry_point, ["list", "environment"])
+
+@pytest.mark.e2e
+def test_all_environment_e2e():
+    """E2E Test for using submarine CLI to access submarine environment
+    To run this test, you should first set
+        your submarine CLI config `port` to 8080 and `hostname` to localhost
+    i.e. please execute the commands in your terminal:
+        submarine config set connection.hostname localhost
+        submarine config set connection.port 8080
+    """
+    # set env to display full table
+    runner = CliRunner(env={"COLUMNS": str(TEST_CONSOLE_WIDTH)})
+    # check if cli config is correct for testing
+    result = runner.invoke(main.entry_point, ["config", "get", "connection.port"])
     assert result.exit_code == 0
-    assert "list environment!" in result.output
+    assert "connection.port={}".format(8080) in result.output
+
+    submarine_client = EnvironmentClient(host="http://localhost:8080")
+    kernel_spec = KernelSpec(
+        name="submarine_jupyter_py3",
+        channels=["defaults"],
+        conda_dependencies=[],
+        pip_dependencies=[],
+    )
+    environment_spec = EnvironmentSpec(
+        name="mytest",
+        kernel_spec=kernel_spec,
+        docker_image="apache/submarine:jupyter-notebook-gpu-0.7.0-SNAPSHOT",
+    )
 
+    environment = submarine_client.create_environment(environment_spec=environment_spec)
+    environment_name = environment["environmentSpec"]["name"]
 
-def test_get_environment():
-    mock_environment_id = "0"
-    runner = CliRunner()
-    result = runner.invoke(main.entry_point, ["get", "environment", mock_environment_id])
+    # test list environment
+    result = runner.invoke(main.entry_point, ["list", "environment"])
     assert result.exit_code == 0
-    assert "get environment! id={}".format(mock_environment_id) in result.output
+    assert "List of Environments" in result.output
+    assert environment["environmentSpec"]["name"] in result.output
+    assert environment["environmentSpec"]["dockerImage"] in result.output
+    assert environment["environmentId"] in result.output
 
+    # test get environment
+    result = runner.invoke(main.entry_point, ["get", "environment", environment_name])
+    assert "Environment(name = {} )".format(environment_name) in result.output
+    assert environment["environmentSpec"]["name"] in result.output
 
-def test_delete_environment():
-    mock_environment_id = "0"
-    runner = CliRunner()
-    result = runner.invoke(main.entry_point, ["delete", "environment", mock_environment_id])
-    assert result.exit_code == 0
-    assert "delete environment! id={}".format(mock_environment_id) in result.output
+    # test delete environment
+    result = runner.invoke(main.entry_point, ["delete", "environment", environment_name])
+    assert "Environment(name = {} ) deleted".format(environment_name) in result.output
+
+    # test get environment fail after delete
+    result = runner.invoke(main.entry_point, ["get", "environment", environment_name])
+    assert "[Api Error] Environment not found." in result.output
diff --git a/submarine-sdk/pysubmarine/tests/environment/test_environment_client.py b/submarine-sdk/pysubmarine/tests/environment/test_environment_client.py
new file mode 100644
index 0000000..a735235
--- /dev/null
+++ b/submarine-sdk/pysubmarine/tests/environment/test_environment_client.py
@@ -0,0 +1,42 @@
+# 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.
+
+import pytest
+
+from submarine.client.api.environment_client import EnvironmentClient
+from submarine.client.models.environment_spec import EnvironmentSpec
+from submarine.client.models.kernel_spec import KernelSpec
+
+
+@pytest.mark.e2e
+def test_environment_e2e():
+    submarine_client = EnvironmentClient(host="http://localhost:8080")
+    kernel_spec = KernelSpec(
+        name="submarine_jupyter_py3",
+        channels=["defaults"],
+        conda_dependencies=[],
+        pip_dependencies=[],
+    )
+    environment_spec = EnvironmentSpec(
+        name="mytest",
+        kernel_spec=kernel_spec,
+        docker_image="apache/submarine:jupyter-notebook-gpu-0.7.0-SNAPSHOT",
+    )
+
+    environment = submarine_client.create_environment(environment_spec=environment_spec)
+    environment_name = environment["environmentSpec"]["name"]
+    submarine_client.get_environment(environment_name)
+    submarine_client.list_environments()
+    submarine_client.delete_environment(environment_name)
diff --git a/submarine-server/server-core/src/main/java/org/apache/submarine/server/rest/EnvironmentRestApi.java b/submarine-server/server-core/src/main/java/org/apache/submarine/server/rest/EnvironmentRestApi.java
index 6e381c7..ba56383 100644
--- a/submarine-server/server-core/src/main/java/org/apache/submarine/server/rest/EnvironmentRestApi.java
+++ b/submarine-server/server-core/src/main/java/org/apache/submarine/server/rest/EnvironmentRestApi.java
@@ -67,7 +67,7 @@ public class EnvironmentRestApi {
                   @ApiResponse(description = "successful operation", 
                       content = @Content(
                           schema = @Schema(
-                              implementation = Environment.class)))})
+                              implementation = JsonResponse.class)))})
   public Response createEnvironment(EnvironmentSpec spec) {
     try {
       Environment environment = environmentManager.createEnvironment(spec);
@@ -88,12 +88,12 @@ public class EnvironmentRestApi {
   @Path("/{id}")
   @Consumes({RestConstants.MEDIA_TYPE_YAML, MediaType.APPLICATION_JSON})
   @Operation(summary = "Update the environment with job spec",
-          tags = {"environments"},
+          tags = {"environment"},
           responses = {
                   @ApiResponse(description = "successful operation", 
                       content = @Content(
                           schema = @Schema(
-                              implementation = Environment.class))),
+                              implementation = JsonResponse.class))),
                   @ApiResponse(
                       responseCode = "404", 
                       description = "Environment not found")})
@@ -118,11 +118,11 @@ public class EnvironmentRestApi {
   @DELETE
   @Path("/{id}")
   @Operation(summary = "Delete the environment",
-          tags = {"environments"},
+          tags = {"environment"},
           responses = {
                   @ApiResponse(description = "successful operation", 
                       content = @Content(
-                          schema = @Schema(implementation = Environment.class))),
+                          schema = @Schema(implementation = JsonResponse.class))),
                   @ApiResponse(
                       responseCode = "404", description = "Environment not found")})
   public Response deleteEnvironment(
@@ -142,12 +142,12 @@ public class EnvironmentRestApi {
    */
   @GET
   @Operation(summary = "List of Environments",
-          tags = {"environments"},
+          tags = {"environment"},
           responses = {
                   @ApiResponse(description = "successful operation", 
                       content = @Content(
                           schema = @Schema(
-                              implementation = Environment.class)))})
+                              implementation = JsonResponse.class)))})
   public Response listEnvironment(@QueryParam("status") String status) {
     try {
       List<Environment> environmentList =
@@ -171,7 +171,7 @@ public class EnvironmentRestApi {
           responses = {
                   @ApiResponse(description = "successful operation", 
                       content = @Content(
-                          schema = @Schema(implementation = Environment.class))),
+                          schema = @Schema(implementation = JsonResponse.class))),
                   @ApiResponse(
                       responseCode = "404", 
                       description = "Environment not found")})

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