diff --git a/dev/dependencyList b/dev/dependencyList index 9dce44b28..ebe2659ed 100644 --- a/dev/dependencyList +++ b/dev/dependencyList @@ -33,6 +33,7 @@ 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-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.annotation-api/1.3.5//jakarta.annotation-api-1.3.5.jar @@ -46,8 +47,10 @@ jcl-over-slf4j/1.7.30//jcl-over-slf4j-1.7.30.jar jersey-client/2.30//jersey-client-2.30.jar jersey-common/2.30//jersey-common-2.30.jar jersey-container-servlet-core/2.30//jersey-container-servlet-core-2.30.jar +jersey-entity-filtering/2.30//jersey-entity-filtering-2.30.jar jersey-hk2/2.30//jersey-hk2-2.30.jar jersey-media-jaxb/2.30//jersey-media-jaxb-2.30.jar +jersey-media-json-jackson/2.30//jersey-media-json-jackson-2.30.jar jersey-server/2.30//jersey-server-2.30.jar jetty-http/9.4.41.v20210516//jetty-http-9.4.41.v20210516.jar jetty-io/9.4.41.v20210516//jetty-io-9.4.41.v20210516.jar diff --git a/kyuubi-server/pom.xml b/kyuubi-server/pom.xml index 150f0cda6..dbc1aec25 100644 --- a/kyuubi-server/pom.xml +++ b/kyuubi-server/pom.xml @@ -81,6 +81,11 @@ jersey-hk2 + + org.glassfish.jersey.media + jersey-media-json-jackson + + org.apache.kyuubi kyuubi-common_${scala.binary.version} diff --git a/kyuubi-server/src/main/java/org/apache/kyuubi/server/api/v1/dto/SessionOpenedCount.java b/kyuubi-server/src/main/java/org/apache/kyuubi/server/api/v1/dto/SessionOpenedCount.java new file mode 100644 index 000000000..914793b3a --- /dev/null +++ b/kyuubi-server/src/main/java/org/apache/kyuubi/server/api/v1/dto/SessionOpenedCount.java @@ -0,0 +1,36 @@ +/* + * 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.v1.dto; + +import java.io.Serializable; + +public class SessionOpenedCount implements Serializable { + + private int openSessionCount; + + public SessionOpenedCount() { + } + + public int getOpenSessionCount() { + return openSessionCount; + } + + public void setOpenSessionCount(int openSessionCount) { + this.openSessionCount = openSessionCount; + } +} diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/RestFrontendService.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/RestFrontendService.scala index b84c34aad..857219624 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/RestFrontendService.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/RestFrontendService.scala @@ -113,6 +113,16 @@ private[server] class RestFrontendService private(name: String, be: BackendServi private def stopHttpServer(): Unit = { if (jettyServer != null) { + try { + connector.stop() + info("Rest frontend service server connector has stopped.") + } catch { + case err: Exception => + error("Cannot safely stop rest frontend service server connector", err) + } finally { + connector = null + } + try { jettyServer.stop() info("Rest frontend service jetty server has stopped.") 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 9313fe220..862163cf0 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 @@ -71,5 +71,3 @@ private[server] object ApiUtils { } } - - 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 e915026ba..49acabbc8 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 @@ -30,4 +30,7 @@ private[v1] class ApiRootResource extends ApiRequestContext { @Produces(Array(MediaType.TEXT_PLAIN)) def ping(): String = "pong" + @Path("sessions") + def sessions: Class[SessionsResource] = classOf[SessionsResource] + } 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 new file mode 100644 index 000000000..9fcc9dd1f --- /dev/null +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/SessionsResource.scala @@ -0,0 +1,39 @@ +/* + * 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.v1 + +import javax.ws.rs.{GET, Path, Produces} +import javax.ws.rs.core.MediaType + +import org.apache.kyuubi.server.api.ApiRequestContext +import org.apache.kyuubi.server.api.v1.dto.SessionOpenedCount + +@Produces(Array(MediaType.APPLICATION_JSON)) +private[v1] class SessionsResource extends ApiRequestContext { + + @GET + @Path("count") + def sessionCount(): SessionOpenedCount = { + val sessionOpenedCount = new SessionOpenedCount() + sessionOpenedCount.setOpenSessionCount( + backendService.sessionManager.getOpenSessionCount + ) + sessionOpenedCount + } + +} diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/RestFrontendServiceSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/RestFrontendServiceSuite.scala index f453a2e5e..531bda6ed 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/RestFrontendServiceSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/RestFrontendServiceSuite.scala @@ -19,13 +19,16 @@ package org.apache.kyuubi.server import java.util.Locale +import com.fasterxml.jackson.databind.ObjectMapper import org.scalatest.time.SpanSugar._ import scala.io.Source import org.apache.kyuubi.KyuubiFunSuite import org.apache.kyuubi.config.KyuubiConf -import org.apache.kyuubi.service.NoopServer +import org.apache.kyuubi.server.api.v1.dto.SessionOpenedCount +import org.apache.kyuubi.service.{NoopBackendService, NoopServer} import org.apache.kyuubi.service.ServiceState._ +import org.apache.kyuubi.session.{NoopSessionImpl, NoopSessionManager, SessionManager} class RestFrontendServiceSuite extends KyuubiFunSuite{ @@ -61,29 +64,63 @@ class RestFrontendServiceSuite extends KyuubiFunSuite{ } test("kyuubi rest frontend service http basic") { + withKyuubiRestServer { + (_, host, port) => + eventually(timeout(10.seconds), interval(50.milliseconds)) { + val html = Source.fromURL(s"http://$host:$port/api/v1/ping").mkString + assert(html.toLowerCase(Locale.ROOT).equals("pong")) + } + } + } + + test("kyuubi rest frontend service for sessions resource") { + withKyuubiRestServer { + (_, host, port) => + val expectedCount = new SessionOpenedCount() + expectedCount.setOpenSessionCount(1) + val expectedStr = new ObjectMapper().writeValueAsString(expectedCount) + + eventually(timeout(10.seconds), interval(50.milliseconds)) { + val html = Source.fromURL(s"http://$host:$port/api/v1/sessions/count").mkString + assert(html.toLowerCase(Locale.ROOT).equalsIgnoreCase(expectedStr)) + } + } + } + + def withKyuubiRestServer(f: (RestFrontendService, String, Int) => Unit): Unit = { val server = new RestNoopServer() server.stop() val conf = KyuubiConf() conf.set(KyuubiConf.FRONTEND_REST_BIND_HOST, "localhost") server.initialize(conf) - val frontendService = server.getServices(0).asInstanceOf[RestFrontendService] server.start() - assert(server.getServiceState === STARTED) - assert(frontendService.getServiceState == STARTED) - eventually(timeout(10.seconds), interval(50.milliseconds)) { - val html = Source.fromURL("http://localhost:10099/api/v1/ping").mkString - assert(html.toLowerCase(Locale.ROOT).equals("pong")) + val frontendService = server.getServices(0).asInstanceOf[RestFrontendService] + + try { + f(frontendService, conf.get(KyuubiConf.FRONTEND_REST_BIND_HOST).get, + conf.get(KyuubiConf.FRONTEND_REST_BIND_PORT)) + } finally { + server.stop() } - - server.stop() } class RestNoopServer extends NoopServer { - + override val backendService: NoopBackendService = new RestMockedBeService override val frontendService = new RestFrontendService(backendService) + } + class RestMockedBeService extends NoopBackendService { + override val sessionManager: SessionManager = new RestMockedSessionManager() + } + + class RestMockedSessionManager extends NoopSessionManager { + // It's a ugly and temporally implementation will replace it via creation rest API. + var session = new NoopSessionImpl(null, null, null, null, Map(), this) + setSession(session.handle, session) + + override protected def isServer: Boolean = true } } diff --git a/pom.xml b/pom.xml index f9fb1e0cf..c663977c9 100644 --- a/pom.xml +++ b/pom.xml @@ -1185,6 +1185,12 @@ jersey-hk2 ${jersey.version} + + + org.glassfish.jersey.media + jersey-media-json-jackson + ${jersey.version} +