You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kyuubi.apache.org by ul...@apache.org on 2021/11/17 04:18:10 UTC

[incubator-kyuubi] branch master updated: [KYUUBI #1354] Expose OpenAPI Specifications

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

ulyssesyou pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-kyuubi.git


The following commit(s) were added to refs/heads/master by this push:
     new a5ef94a  [KYUUBI #1354] Expose OpenAPI Specifications
a5ef94a is described below

commit a5ef94accc0b6e7557a085512dc8fe062d79375b
Author: Fu Chen <cf...@gmail.com>
AuthorDate: Wed Nov 17 12:17:58 2021 +0800

    [KYUUBI #1354] Expose OpenAPI Specifications
    
    <!--
    Thanks for sending a pull request!
    
    Here are some tips for you:
      1. If this is your first time, please read our contributor guidelines: https://kyuubi.readthedocs.io/en/latest/community/contributions.html
      2. If the PR is related to an issue in https://github.com/apache/incubator-kyuubi/issues, add '[KYUUBI #XXXX]' in your PR title, e.g., '[KYUUBI #XXXX] Your PR title ...'.
      3. If the PR is unfinished, add '[WIP]' in your PR title, e.g., '[WIP][KYUUBI #XXXX] Your PR title ...'.
    -->
    
    ### _Why are the changes needed?_
    <!--
    Please clarify why the changes are needed. For instance,
      1. If you add a feature, you can talk about the use case of it.
      2. If you fix a bug, you can clarify why it is a bug.
    -->
    
    1. add a new endpoint `/openapi.json` which describes the entire restful API of Kyuubi.
    2. add a static web page `/api/v1/swagger-ui`. see [swagger-ui](https://github.com/swagger-api/swagger-ui)
    
    ```
    curl http://localhost:10099/openapi.json | jq .
    ```
    
    ```json
    {
      "openapi": "3.0.1",
      "info": {
        "title": "Apache Kyuubi RESTful Api Documentation",
        "description": "Apache Kyuubi RESTful Api Documentation",
        "contact": {
          "name": "Apache Kyuubi Community",
          "url": "https://kyuubi.apache.org/issue_tracking.html",
          "email": "devkyuubi.apache.org"
        },
        "license": {
          "name": "Apache 2.0",
          "url": "https://www.apache.org/licenses/LICENSE-2.0.html"
        },
        "version": "1.4.0"
      },
      "tags": [
        {
          "name": "Session"
        }
      ],
      "paths": {
        "/api/v1/ping": {
          "get": {
            "operationId": "ping",
            "responses": {
              "default": {
                "description": "default response",
                "content": {
                  "text/plain": {
                    "schema": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "/api/v1/sessions/{sessionHandle}": {
          "get": {
            "tags": [
              "Session"
            ],
            "operationId": "sessionInfo",
            "parameters": [
              {
                "name": "sessionHandle",
                "in": "path",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            ],
            "responses": {
              "200": {
                "description": "get a session via session handle identifier",
                "content": {
                  "application/json": {}
                }
              }
            }
          },
          "delete": {
            "tags": [
              "Session"
            ],
            "operationId": "closeSession",
            "parameters": [
              {
                "name": "sessionHandle",
                "in": "path",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            ],
            "responses": {
              "200": {
                "description": "close a Session",
                "content": {
                  "application/json": {}
                }
              }
            }
          }
        },
        "/api/v1/sessions/execPool/statistic": {
          "get": {
            "tags": [
              "Session"
            ],
            "operationId": "execPoolStatistic",
            "responses": {
              "200": {
                "description": "get some statistic info of sessions",
                "content": {
                  "application/json": {}
                }
              }
            }
          }
        },
        "/api/v1/sessions/{sessionHandle}/info/{infoType}": {
          "get": {
            "tags": [
              "Session"
            ],
            "operationId": "getInfo",
            "parameters": [
              {
                "name": "sessionHandle",
                "in": "path",
                "required": true,
                "schema": {
                  "type": "string"
                }
              },
              {
                "name": "infoType",
                "in": "path",
                "required": true,
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            ],
            "responses": {
              "200": {
                "description": "get a information detail via session handle identifier and a specific information type",
                "content": {
                  "application/json": {}
                }
              }
            }
          }
        },
        "/api/v1/sessions": {
          "get": {
            "tags": [
              "Session"
            ],
            "operationId": "sessionInfoList",
            "responses": {
              "200": {
                "description": "get all the session list hosted in SessionManager",
                "content": {
                  "application/json": {}
                }
              }
            }
          },
          "post": {
            "tags": [
              "Session"
            ],
            "operationId": "openSession",
            "requestBody": {
              "content": {
                "application/json": {
                  "schema": {
                    "$ref": "#/components/schemas/SessionOpenRequest"
                  }
                }
              }
            },
            "responses": {
              "200": {
                "description": "Open(create) a Session",
                "content": {
                  "application/json": {}
                }
              }
            }
          }
        },
        "/api/v1/sessions/count": {
          "get": {
            "tags": [
              "Session"
            ],
            "operationId": "sessionCount",
            "responses": {
              "200": {
                "description": "get open session count",
                "content": {
                  "application/json": {}
                }
              }
            }
          }
        },
        "/api/v1/exception": {
          "get": {
            "operationId": "test",
            "responses": {
              "default": {
                "description": "default response",
                "content": {
                  "text/plain": {}
                }
              }
            }
          }
        },
        "/application.wadl/{path}": {
          "get": {
            "operationId": "getExternalGrammar",
            "parameters": [
              {
                "name": "path",
                "in": "path",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            ],
            "responses": {
              "default": {
                "description": "default response",
                "content": {
                  "application/xml": {}
                }
              }
            }
          }
        },
        "/application.wadl": {
          "get": {
            "operationId": "getWadl",
            "responses": {
              "default": {
                "description": "default response",
                "content": {
                  "application/vnd.sun.wadl+xml": {},
                  "application/xml": {}
                }
              }
            }
          }
        }
      },
      "components": {
        "schemas": {
          "ExecPoolStatistic": {
            "required": [
              "execPoolSize"
            ],
            "type": "object",
            "properties": {
              "execPoolSize": {
                "type": "integer",
                "format": "int32"
              },
              "execPoolActiveCount": {
                "type": "integer",
                "format": "int32"
              }
            }
          },
          "InfoDetail": {
            "required": [
              "infoType"
            ],
            "type": "object",
            "properties": {
              "infoType": {
                "type": "string"
              },
              "infoValue": {
                "type": "string"
              }
            }
          },
          "HandleIdentifier": {
            "required": [
              "publicId"
            ],
            "type": "object",
            "properties": {
              "publicId": {
                "type": "string",
                "format": "uuid"
              },
              "secretId": {
                "type": "string",
                "format": "uuid"
              }
            }
          },
          "SessionHandle": {
            "required": [
              "identifier",
              "protocol"
            ],
            "type": "object",
            "properties": {
              "identifier": {
                "$ref": "#/components/schemas/HandleIdentifier"
              },
              "protocol": {
                "type": "string",
                "enum": [
                  "HIVE_CLI_SERVICE_PROTOCOL_V1",
                  "HIVE_CLI_SERVICE_PROTOCOL_V2",
                  "HIVE_CLI_SERVICE_PROTOCOL_V3",
                  "HIVE_CLI_SERVICE_PROTOCOL_V4",
                  "HIVE_CLI_SERVICE_PROTOCOL_V5",
                  "HIVE_CLI_SERVICE_PROTOCOL_V6",
                  "HIVE_CLI_SERVICE_PROTOCOL_V7",
                  "HIVE_CLI_SERVICE_PROTOCOL_V8",
                  "HIVE_CLI_SERVICE_PROTOCOL_V9",
                  "HIVE_CLI_SERVICE_PROTOCOL_V10"
                ]
              }
            }
          },
          "MapStringString": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "SessionOpenRequest": {
            "required": [
              "configs",
              "protocolVersion",
              "user"
            ],
            "type": "object",
            "properties": {
              "protocolVersion": {
                "type": "integer",
                "format": "int32"
              },
              "user": {
                "type": "string"
              },
              "password": {
                "type": "string"
              },
              "ipAddr": {
                "type": "string"
              },
              "configs": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            }
          },
          "SessionOpenCount": {
            "required": [
              "openSessionCount"
            ],
            "type": "object",
            "properties": {
              "openSessionCount": {
                "type": "integer",
                "format": "int32"
              }
            }
          },
          "SessionDetail": {
            "required": [
              "configs",
              "createTime",
              "user"
            ],
            "type": "object",
            "properties": {
              "user": {
                "type": "string"
              },
              "ipAddr": {
                "type": "string"
              },
              "createTime": {
                "type": "integer",
                "format": "int64"
              },
              "sessionHandle": {
                "$ref": "#/components/schemas/SessionHandle"
              },
              "lastAccessTime": {
                "type": "integer",
                "format": "int64"
              },
              "lastIdleTime": {
                "type": "integer",
                "format": "int64"
              },
              "noOperationTime": {
                "type": "integer",
                "format": "int64"
              },
              "configs": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            }
          },
          "SeqSessionOverview": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SessionOverview"
            }
          },
          "SessionList": {
            "required": [
              "sessionList"
            ],
            "type": "object",
            "properties": {
              "sessionList": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/SessionOverview"
                }
              }
            }
          },
          "SessionOverview": {
            "required": [
              "createTime",
              "user"
            ],
            "type": "object",
            "properties": {
              "user": {
                "type": "string"
              },
              "ipAddr": {
                "type": "string"
              },
              "createTime": {
                "type": "integer",
                "format": "int64"
              },
              "sessionHandle": {
                "$ref": "#/components/schemas/SessionHandle"
              }
            }
          }
        }
      }
    }
    ```
    http://localhost:10099/api/v1/swagger-ui
    
    > Note that this request will redirect to http://localhost:10099/swagger-ui-redirected/index.html?url=http://localhost:10099/openapi.json
    
    ![截屏2021-11-09 上午10 43 49](https://user-images.githubusercontent.com/8537877/141281596-e47f5552-75b9-47d1-8a36-67337cb6eb3a.png)
    
    ### _How was this patch tested?_
    - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible
    
    - [ ] Add screenshots for manual tests if appropriate
    
    - [ ] [Run test](https://kyuubi.readthedocs.io/en/latest/develop_tools/testing.html#running-tests) locally before make a pull request
    
    Closes #1363 from cfmcgrady/kyuubi-1354.
    
    Closes #1354
    
    8895d7fc [Fu Chen] typo
    8f5dea9a [Fu Chen] addressed comment
    4d325a45 [Fu Chen] constant MediaType.APPLICATION_JSON
    0d96951e [Fu Chen] RESTful Api -> REST API
    be7d02dc [Fu Chen] add LIABILITY-edl.txt
    4ff1053e [Fu Chen] update dev/dependencyList
    b126f7e6 [Fu Chen] swagger-ui 4.1.0
    a35c2f29 [Fu Chen] addressed comment
    7bde0534 [Fu Chen] update LICENSE-binary
    38397dc2 [Fu Chen] align jackson version
    923f3e7e [Fu Chen] update dev/dependencyList
    7d5338c8 [Fu Chen] remove kyuubi-server static files
    5cb66b33 [Fu Chen] update dev/dependencyList
    e8a8c421 [Fu Chen] fix style
    5b13f69c [Fu Chen] add license and remove .map files
    c04d7a75 [Fu Chen] rebase master
    2388dae8 [Fu Chen] fix style
    8dcbd3db [Fu Chen] add static resource
    4b0d6d0a [Fu Chen] expose OpenAPI
    
    Authored-by: Fu Chen <cf...@gmail.com>
    Signed-off-by: ulysses-you <ul...@apache.org>
---
 LICENSE-binary                                     | 21 ++++++++
 dev/dependencyList                                 | 18 +++++++
 kyuubi-server/pom.xml                              | 15 ++++++
 .../apache/kyuubi/server/api/OpenAPIConfig.scala   | 45 +++++++++++++++++
 .../scala/org/apache/kyuubi/server/api/api.scala   | 24 +++++----
 .../kyuubi/server/api/v1/ApiRootResource.scala     | 17 ++++++-
 .../kyuubi/server/api/v1/SessionsResource.scala    | 57 +++++++++++++++++++++-
 .../org/apache/kyuubi/server/api/v1/dto.scala      |  2 +-
 licenses-binary/LICENSE-edl.txt                    | 13 +++++
 pom.xml                                            | 52 ++++++++++++++++++++
 10 files changed, 250 insertions(+), 14 deletions(-)

diff --git a/LICENSE-binary b/LICENSE-binary
index e0a3ef1..1ad1b30 100644
--- a/LICENSE-binary
+++ b/LICENSE-binary
@@ -232,6 +232,11 @@ org.apache.htrace:htrace-core4
 com.fasterxml.jackson.core:jackson-annotations
 com.fasterxml.jackson.core:jackson-core
 com.fasterxml.jackson.core:jackson-databind
+com.fasterxml.jackson.dataformat:jackson-dataformat-yaml
+com.fasterxml.jackson.datatype:jackson-datatype-jsr310
+com.fasterxml.jackson.jaxrs:jackson-jaxrs-base
+com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider
+com.github.swagger-akka-http:swagger-scala-module_*
 org.eclipse.jetty:jetty-http
 org.eclipse.jetty:jetty-io
 org.eclipse.jetty:jetty-security
@@ -241,6 +246,8 @@ org.eclipse.jetty:jetty-util-ajax
 org.eclipse.jetty:jetty-util
 org.apache.thrift:libfb303
 org.apache.thrift:libthrift
+org.webjars:swagger-ui
+org.yaml:snakeyaml
 log4j:log4j
 io.dropwizard.metrics:metrics-core
 io.dropwizard.metrics:metrics-jmx
@@ -251,12 +258,25 @@ io.prometheus:simpleclient
 io.prometheus:simpleclient_common
 io.prometheus:simpleclient_dropwizard
 io.prometheus:simpleclient_servlet
+io.swagger.core.v3:swagger-annotations
+io.swagger.core.v3:swagger-annotations-jakarta
+io.swagger.core.v3:swagger-core
+io.swagger.core.v3:swagger-core-jakarta
+io.swagger.core.v3:swagger-integration
+io.swagger.core.v3:swagger-jaxrs2
+io.swagger.core.v3:swagger-models
+io.swagger.core.v3:swagger-models-jakarta
 org.apache.zookeeper:zookeeper
 
 BSD 3-Clause
 ------------
 org.scala-lang:scala-library
 
+Eclipse Distribution License - v 1.0
+------------------------------------
+com.sun.activation:jakarta.activation
+jakarta.xml.bind:jakarta.xml.bind-api
+
 Eclipse Public License (EPL) 2.0
 --------------------------------
 jakarta.servlet:jakarta.servlet-api https://projects.eclipse.org/projects/ee4j.servlet
@@ -271,3 +291,4 @@ org.slf4j:slf4j-api
 org.slf4j:slf4j-log4j12
 org.slf4j:jcl-over-slf4j
 com.github.scopt:scopt_*
+io.github.classgraph:classgraph
diff --git a/dev/dependencyList b/dev/dependencyList
index 58d9a79..e070bf2 100644
--- a/dev/dependencyList
+++ b/dev/dependencyList
@@ -16,6 +16,7 @@
 #
 
 aopalliance-repackaged/2.6.1//aopalliance-repackaged-2.6.1.jar
+classgraph/4.8.95//classgraph-4.8.95.jar
 commons-codec/1.15//commons-codec-1.15.jar
 commons-lang/2.6//commons-lang-2.6.jar
 commons-lang3/3.10//commons-lang3-3.10.jar
@@ -39,14 +40,20 @@ htrace-core4/4.1.0-incubating//htrace-core4-4.1.0-incubating.jar
 jackson-annotations/2.11.4//jackson-annotations-2.11.4.jar
 jackson-core/2.11.4//jackson-core-2.11.4.jar
 jackson-databind/2.11.4//jackson-databind-2.11.4.jar
+jackson-dataformat-yaml/2.11.4//jackson-dataformat-yaml-2.11.4.jar
+jackson-datatype-jsr310/2.11.4//jackson-datatype-jsr310-2.11.4.jar
+jackson-jaxrs-base/2.11.4//jackson-jaxrs-base-2.11.4.jar
+jackson-jaxrs-json-provider/2.11.4//jackson-jaxrs-json-provider-2.11.4.jar
 jackson-module-jaxb-annotations/2.9.9//jackson-module-jaxb-annotations-2.9.9.jar
 jackson-module-paranamer/2.11.4//jackson-module-paranamer-2.11.4.jar
 jackson-module-scala_2.12/2.11.4//jackson-module-scala_2.12-2.11.4.jar
+jakarta.activation/2.0.0//jakarta.activation-2.0.0.jar
 jakarta.annotation-api/1.3.5//jakarta.annotation-api-1.3.5.jar
 jakarta.inject/2.6.1//jakarta.inject-2.6.1.jar
 jakarta.servlet-api/4.0.4//jakarta.servlet-api-4.0.4.jar
 jakarta.validation-api/2.0.2//jakarta.validation-api-2.0.2.jar
 jakarta.ws.rs-api/2.1.6//jakarta.ws.rs-api-2.1.6.jar
+jakarta.xml.bind-api/3.0.0//jakarta.xml.bind-api-3.0.0.jar
 javassist/3.25.0-GA//javassist-3.25.0-GA.jar
 jaxb-api/2.2.11//jaxb-api-2.2.11.jar
 jcl-over-slf4j/1.7.30//jcl-over-slf4j-1.7.30.jar
@@ -83,4 +90,15 @@ simpleclient_dropwizard/0.10.0//simpleclient_dropwizard-0.10.0.jar
 simpleclient_servlet/0.10.0//simpleclient_servlet-0.10.0.jar
 slf4j-api/1.7.30//slf4j-api-1.7.30.jar
 slf4j-log4j12/1.7.30//slf4j-log4j12-1.7.30.jar
+snakeyaml/1.26//snakeyaml-1.26.jar
+swagger-annotations-jakarta/2.1.11//swagger-annotations-jakarta-2.1.11.jar
+swagger-annotations/2.1.11//swagger-annotations-2.1.11.jar
+swagger-core-jakarta/2.1.11//swagger-core-jakarta-2.1.11.jar
+swagger-core/2.1.11//swagger-core-2.1.11.jar
+swagger-integration/2.1.11//swagger-integration-2.1.11.jar
+swagger-jaxrs2/2.1.11//swagger-jaxrs2-2.1.11.jar
+swagger-models-jakarta/2.1.11//swagger-models-jakarta-2.1.11.jar
+swagger-models/2.1.11//swagger-models-2.1.11.jar
+swagger-scala-module_2.12/2.5.2//swagger-scala-module_2.12-2.5.2.jar
+swagger-ui/4.1.0//swagger-ui-4.1.0.jar
 zookeeper/3.4.14//zookeeper-3.4.14.jar
diff --git a/kyuubi-server/pom.xml b/kyuubi-server/pom.xml
index ecf336b..d5b8a54 100644
--- a/kyuubi-server/pom.xml
+++ b/kyuubi-server/pom.xml
@@ -203,6 +203,16 @@
         </dependency>
 
         <dependency>
+            <groupId>io.swagger.core.v3</groupId>
+            <artifactId>swagger-jaxrs2</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.swagger-akka-http</groupId>
+            <artifactId>swagger-scala-module_${scala.binary.version}</artifactId>
+        </dependency>
+
+        <dependency>
             <groupId>org.glassfish.jersey.core</groupId>
             <artifactId>jersey-server</artifactId>
         </dependency>
@@ -433,6 +443,11 @@
             <artifactId>mysql-connector-java</artifactId>
             <scope>test</scope>
         </dependency>
+
+        <dependency>
+            <groupId>org.webjars</groupId>
+            <artifactId>swagger-ui</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/OpenAPIConfig.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/OpenAPIConfig.scala
new file mode 100644
index 0000000..46539e7
--- /dev/null
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/OpenAPIConfig.scala
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+package org.apache.kyuubi.server.api
+
+import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource
+import io.swagger.v3.oas.annotations.OpenAPIDefinition
+import io.swagger.v3.oas.annotations.info.{Contact, Info, License}
+import io.swagger.v3.oas.annotations.tags.Tag
+import org.glassfish.jersey.server.ResourceConfig
+
+@OpenAPIDefinition(
+  info = new Info(
+    title = "Apache Kyuubi REST API Documentation",
+    version = "1.4.0",
+    description = "Apache Kyuubi REST API Documentation",
+    contact = new Contact(
+      name = "Apache Kyuubi Community",
+      url = "https://kyuubi.apache.org/issue_tracking.html",
+      email = "dev@kyuubi.apache.org"),
+    license = new License(
+      name = "Apache 2.0",
+      url = "https://www.apache.org/licenses/LICENSE-2.0.html")
+  ),
+  tags = Array(new Tag(name = "Session"))
+)
+class OpenAPIConfig extends ResourceConfig {
+  packages("org.apache.kyuubi.server.api.v1")
+  register(classOf[OpenApiResource]);
+  register(classOf[KyuubiScalaObjectMapper])
+}
diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/api.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/api.scala
index 83068e3..6a9bddf 100644
--- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/api.scala
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/api.scala
@@ -22,8 +22,8 @@ import javax.servlet.http.HttpServletRequest
 import javax.ws.rs.core.Context
 
 import org.eclipse.jetty.server.handler.ContextHandler
-import org.eclipse.jetty.servlet.{ServletContextHandler, ServletHolder}
-import org.glassfish.jersey.server.ServerProperties
+import org.eclipse.jetty.servlet.{DefaultServlet, ServletContextHandler, ServletHolder}
+import org.glassfish.jersey.server.ResourceConfig
 import org.glassfish.jersey.servlet.ServletContainer
 
 import org.apache.kyuubi.service.BackendService
@@ -56,17 +56,21 @@ private[api] object BackendServiceProvider {
 private[server] object ApiUtils {
 
   def getServletHandler(backendService: BackendService): ServletContextHandler = {
-    val servlet = new ServletHolder(classOf[ServletContainer])
-    servlet.setInitParameter(
-      ServerProperties.PROVIDER_PACKAGES,
-      "org.apache.kyuubi.server.api.v1")
-    servlet.setInitParameter(
-      ServerProperties.PROVIDER_CLASSNAMES,
-      classOf[KyuubiScalaObjectMapper].getName)
+    val openapiConf: ResourceConfig = new OpenAPIConfig
+    val servlet = new ServletHolder(new ServletContainer(openapiConf))
     val handler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS)
     BackendServiceProvider.setBackendService(handler, backendService)
-    handler.setContextPath("/api")
     handler.addServlet(servlet, "/*")
+
+    // install swagger-ui, these static files are copied from
+    // https://github.com/swagger-api/swagger-ui/tree/master/dist
+    val swaggerUI = new ServletHolder("swagger-ui", classOf[DefaultServlet])
+    swaggerUI.setInitParameter("resourceBase",
+      getClass.getClassLoader()
+        .getResource("META-INF/resources/webjars/swagger-ui/4.1.0/")
+        .toExternalForm)
+    swaggerUI.setInitParameter("pathInfoOnly", "true")
+    handler.addServlet(swaggerUI, "/swagger-ui-redirected/*");
     handler
   }
 }
diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/ApiRootResource.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/ApiRootResource.scala
index 751de24..b41290b 100644
--- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/ApiRootResource.scala
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/ApiRootResource.scala
@@ -17,13 +17,16 @@
 
 package org.apache.kyuubi.server.api.v1
 
-import com.google.common.annotations.VisibleForTesting
+import java.net.URI
 import javax.ws.rs.{GET, Path, Produces}
 import javax.ws.rs.core.{MediaType, Response}
 
+import com.google.common.annotations.VisibleForTesting
+
+import org.apache.kyuubi.server.KyuubiServer
 import org.apache.kyuubi.server.api.ApiRequestContext
 
-@Path("/v1")
+@Path("/api/v1")
 private[v1] class ApiRootResource extends ApiRequestContext {
 
   @GET
@@ -43,4 +46,14 @@ private[v1] class ApiRootResource extends ApiRequestContext {
     Response.ok().build()
   }
 
+  @GET
+  @Path("swagger-ui")
+  @Produces(Array(MediaType.TEXT_HTML))
+  def swaggerUi(): Response = {
+    val serverIP = KyuubiServer.kyuubiServer.frontendServices.head.connectionUrl
+    val swaggerUi =
+      s"http://$serverIP/swagger-ui-redirected/index.html?url=http://$serverIP/openapi.json"
+    Response.temporaryRedirect(new URI(swaggerUi)).build()
+  }
+
 }
diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/SessionsResource.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/SessionsResource.scala
index 7ac06dc..30c5532 100644
--- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/SessionsResource.scala
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/SessionsResource.scala
@@ -19,11 +19,15 @@ package org.apache.kyuubi.server.api.v1
 
 import java.util.UUID
 import javax.ws.rs._
+import javax.ws.rs.{Consumes, DELETE, GET, Path, PathParam, POST, Produces}
 import javax.ws.rs.core.{MediaType, Response}
 
 import scala.collection.JavaConverters._
 import scala.util.control.NonFatal
 
+import io.swagger.v3.oas.annotations.media.Content
+import io.swagger.v3.oas.annotations.responses.ApiResponse
+import io.swagger.v3.oas.annotations.tags.Tag
 import org.apache.hive.service.rpc.thrift.{TGetInfoType, TProtocolVersion}
 
 import org.apache.kyuubi.Utils.error
@@ -32,19 +36,34 @@ import org.apache.kyuubi.operation.OperationHandle
 import org.apache.kyuubi.server.api.ApiRequestContext
 import org.apache.kyuubi.session.SessionHandle
 
+@Tag(name = "Session")
 @Produces(Array(MediaType.APPLICATION_JSON))
 private[v1] class SessionsResource extends ApiRequestContext {
 
+  @ApiResponse(
+    responseCode = "200",
+    content = Array(new Content(
+      mediaType = MediaType.APPLICATION_JSON
+    )),
+    description = "get all the session list hosted in SessionManager"
+  )
   @GET
   def sessionInfoList(): SessionList = {
     SessionList(
       backendService.sessionManager.getSessionList().asScala.map {
         case (handle, session) =>
           SessionOverview(session.user, session.ipAddress, session.createTime, handle)
-      }.toList
+      }.toSeq
     )
   }
 
+  @ApiResponse(
+    responseCode = "200",
+    content = Array(new Content(
+      mediaType = MediaType.APPLICATION_JSON
+    )),
+    description = "get a session via session handle identifier"
+  )
   @GET
   @Path("{sessionHandle}")
   def sessionInfo(@PathParam("sessionHandle") sessionHandleStr: String): SessionDetail = {
@@ -60,6 +79,14 @@ private[v1] class SessionsResource extends ApiRequestContext {
     }
   }
 
+  @ApiResponse(
+    responseCode = "200",
+    content = Array(new Content(
+      mediaType = MediaType.APPLICATION_JSON
+    )),
+    description =
+      "get a information detail via session handle identifier and a specific information type"
+  )
   @GET
   @Path("{sessionHandle}/info/{infoType}")
   def getInfo(@PathParam("sessionHandle") sessionHandleStr: String,
@@ -77,12 +104,26 @@ private[v1] class SessionsResource extends ApiRequestContext {
     }
   }
 
+  @ApiResponse(
+    responseCode = "200",
+    content = Array(new Content(
+      mediaType = MediaType.APPLICATION_JSON
+    )),
+    description = "Get the current open session count"
+  )
   @GET
   @Path("count")
   def sessionCount(): SessionOpenCount = {
     SessionOpenCount(backendService.sessionManager.getOpenSessionCount)
   }
 
+  @ApiResponse(
+    responseCode = "200",
+    content = Array(new Content(
+      mediaType = MediaType.APPLICATION_JSON
+    )),
+    description = "Get statistic info of background executors"
+  )
   @GET
   @Path("execPool/statistic")
   def execPoolStatistic(): ExecPoolStatistic = {
@@ -90,6 +131,13 @@ private[v1] class SessionsResource extends ApiRequestContext {
       backendService.sessionManager.getActiveCount)
   }
 
+  @ApiResponse(
+    responseCode = "200",
+    content = Array(new Content(
+      mediaType = MediaType.APPLICATION_JSON
+    )),
+    description = "Open(create) a session"
+  )
   @POST
   @Consumes(Array(MediaType.APPLICATION_JSON))
   def openSession(request: SessionOpenRequest): SessionHandle = {
@@ -101,6 +149,13 @@ private[v1] class SessionsResource extends ApiRequestContext {
       request.configs)
   }
 
+  @ApiResponse(
+    responseCode = "200",
+    content = Array(new Content(
+      mediaType = MediaType.APPLICATION_JSON
+    )),
+    description = "Close a session"
+  )
   @DELETE
   @Path("{sessionHandle}")
   def closeSession(@PathParam("sessionHandle") sessionHandleStr: String): Response = {
diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/dto.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/dto.scala
index 90fcde3..4abd17a 100644
--- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/dto.scala
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/dto.scala
@@ -23,7 +23,7 @@ case class SessionOpenCount(openSessionCount: Int)
 
 case class ExecPoolStatistic(execPoolSize: Int, execPoolActiveCount: Int)
 
-case class SessionList(sessionList: List[SessionOverview])
+case class SessionList(sessionList: Seq[SessionOverview])
 
 case class SessionOverview(
   user: String,
diff --git a/licenses-binary/LICENSE-edl.txt b/licenses-binary/LICENSE-edl.txt
new file mode 100644
index 0000000..6e96b02
--- /dev/null
+++ b/licenses-binary/LICENSE-edl.txt
@@ -0,0 +1,13 @@
+Eclipse Distribution License - v 1.0
+
+Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors.
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+- Neither the name of the Eclipse Foundation, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFI [...]
diff --git a/pom.xml b/pom.xml
index 87d80a9..c7eadb1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -129,6 +129,9 @@
         <spark.archive.name>spark-${spark.version}-bin-hadoop${hadoop.binary.version}.tgz</spark.archive.name>
         <spark.archive.mirror>https://archive.apache.org/dist/spark/spark-${spark.version}</spark.archive.mirror>
         <spark.archive.download.skip>false</spark.archive.download.skip>
+        <swagger.version>2.1.11</swagger.version>
+        <swagger.scala.module.version>2.5.2</swagger.scala.module.version>
+        <swagger-ui.version>4.1.0</swagger-ui.version>
         <zookeeper.version>3.4.14</zookeeper.version>
 
         <!-- only apply to kyuubi-hive-jdbc module -->
@@ -569,6 +572,30 @@
             </dependency>
 
             <dependency>
+                <groupId>com.fasterxml.jackson.dataformat</groupId>
+                <artifactId>jackson-dataformat-yaml</artifactId>
+                <version>${jackson.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.fasterxml.jackson.datatype</groupId>
+                <artifactId>jackson-datatype-jsr310</artifactId>
+                <version>${jackson.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.fasterxml.jackson.jaxrs</groupId>
+                <artifactId>jackson-jaxrs-base</artifactId>
+                <version>${jackson.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.fasterxml.jackson.jaxrs</groupId>
+                <artifactId>jackson-jaxrs-json-provider</artifactId>
+                <version>${jackson.version}</version>
+            </dependency>
+
+            <dependency>
                 <groupId>com.fasterxml.jackson.module</groupId>
                 <artifactId>jackson-module-scala_${scala.binary.version}</artifactId>
                 <version>${jackson.version}</version>
@@ -591,6 +618,19 @@
             </dependency>
 
             <dependency>
+                <groupId>io.swagger.core.v3</groupId>
+                <artifactId>swagger-jaxrs2</artifactId>
+                <version>${swagger.version}</version>
+            </dependency>
+
+            <!-- ref: https://github.com/swagger-akka-http/swagger-scala-module -->
+            <dependency>
+                <groupId>com.github.swagger-akka-http</groupId>
+                <artifactId>swagger-scala-module_${scala.binary.version}</artifactId>
+                <version>${swagger.scala.module.version}</version>
+            </dependency>
+
+            <dependency>
                 <groupId>io.netty</groupId>
                 <artifactId>netty-all</artifactId>
                 <version>${netty.version}</version>
@@ -897,6 +937,18 @@
                 <artifactId>mysql-connector-java</artifactId>
                 <version>${mysql.jdbc.version}</version>
             </dependency>
+
+            <!--
+              1. This library only contains swagger-ui static resource (.html/.css/.js/.png), for more detail, see
+                 https://github.com/swagger-api/swagger-ui/blob/master/dist/
+              2. Note that when trying to upgrade swagger-ui, we should also update the version in the file(
+                 kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/api.scala).
+            -->
+            <dependency>
+                <groupId>org.webjars</groupId>
+                <artifactId>swagger-ui</artifactId>
+                <version>${swagger-ui.version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>