diff --git a/hadoop-client-modules/hadoop-client-minicluster/pom.xml b/hadoop-client-modules/hadoop-client-minicluster/pom.xml index 70fca8a89c8..bd81e7d5bc6 100644 --- a/hadoop-client-modules/hadoop-client-minicluster/pom.xml +++ b/hadoop-client-modules/hadoop-client-minicluster/pom.xml @@ -666,6 +666,10 @@ junit:junit com.google.code.findbugs:jsr305 log4j:log4j + org.eclipse.jetty.websocket:* + javax.websocket:javax.websocket-api + javax.annotation:javax.annotation-api + org.eclipse.jetty:jetty-jndi @@ -777,6 +781,12 @@ ehcache-core.xsd + + org.eclipse.jetty.websocket:javax-websocket-server-impl + + * + + diff --git a/hadoop-client-modules/hadoop-client-runtime/pom.xml b/hadoop-client-modules/hadoop-client-runtime/pom.xml index bfa6c152449..47ce9350770 100644 --- a/hadoop-client-modules/hadoop-client-runtime/pom.xml +++ b/hadoop-client-modules/hadoop-client-runtime/pom.xml @@ -158,6 +158,10 @@ com.google.code.findbugs:jsr305 io.dropwizard.metrics:metrics-core + org.eclipse.jetty.websocket:* + org.eclipse.jetty:jetty-servlet + org.eclipse.jetty:jetty-security + org.ow2.asm:* diff --git a/hadoop-project/pom.xml b/hadoop-project/pom.xml index e7baee003cb..f7e817b811f 100644 --- a/hadoop-project/pom.xml +++ b/hadoop-project/pom.xml @@ -804,6 +804,21 @@ jetty-util-ajax ${jetty.version} + + org.eclipse.jetty.websocket + javax-websocket-server-impl + ${jetty.version} + + + org.ow2.asm + asm + + + org.eclipse.jetty + jetty-webapp + + + javax.servlet.jsp jsp-api diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/pom.xml b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/pom.xml index 74be4da8da4..005af0f62bc 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/pom.xml +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/pom.xml @@ -83,6 +83,10 @@ org.eclipse.jetty jetty-util + + org.eclipse.jetty.websocket + javax-websocket-server-impl + com.google.guava guava diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/ContainerShellClientSocket.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/ContainerShellClientSocket.java new file mode 100644 index 00000000000..f8269c17572 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/ContainerShellClientSocket.java @@ -0,0 +1,79 @@ +/** + * 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.hadoop.yarn.server.nodemanager.webapp; + +import com.google.common.annotations.VisibleForTesting; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.WebSocketAdapter; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import java.io.IOException; +import java.util.concurrent.CountDownLatch; + +@WebSocket +public class ContainerShellClientSocket extends WebSocketAdapter { + + private Session session; + + private String responseMsg; + + CountDownLatch latch= new CountDownLatch(1); + + @Override + public void onWebSocketText(String message) { + System.out.println("Message received from server:" + message); + } + + @Override + public void onWebSocketConnect(Session session) { + System.out.println("Connected to server"); + this.session=session; + latch.countDown(); + } + + @Override + public void onWebSocketClose(int statusCode, String reason) { + session.close(); + } + + @Override + public void onWebSocketError(Throwable cause) { + super.onWebSocketError(cause); + cause.printStackTrace(System.err); + } + + public void sendMessage(String str) { + try { + session.getRemote().sendString(str); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public CountDownLatch getLatch() { + return latch; + } + + @VisibleForTesting + public String getResponseMsg() { + return responseMsg; + } + +} + diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/ContainerShellWebSocket.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/ContainerShellWebSocket.java new file mode 100644 index 00000000000..8ca575e7ea8 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/ContainerShellWebSocket.java @@ -0,0 +1,67 @@ +/** + * 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.hadoop.yarn.server.nodemanager.webapp; + +import java.io.IOException; +import java.net.URI; +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; + +/** + * Web socket for establishing interactive command shell connection through + * Node Manage to container executor + */ +@InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce", "YARN"}) +@InterfaceStability.Unstable + +@WebSocket +public class ContainerShellWebSocket { + + @OnWebSocketMessage + public void onText(Session session, String message) throws IOException { + System.out.println("Message received:" + message); + if (session.isOpen()) { + String response = message.toUpperCase(); + session.getRemote().sendString(response); + } + } + + @OnWebSocketConnect + public void onConnect(Session session) { + System.out.println( + session.getRemoteAddress().getHostString() + " connected!"); + URI containerURI = session.getUpgradeRequest().getRequestURI(); + String[] containerPath = containerURI.getPath().split("/"); + String cId = containerPath[2]; + System.out.println( + "Making interactive connection to running docker container with ID:" + + cId); + } + + @OnWebSocketClose + public void onClose(Session session, int status, String reason) { + System.out.println(session.getRemoteAddress().getHostString() + " closed!"); + } + +} \ No newline at end of file diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/ContainerShellWebSocketServlet.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/ContainerShellWebSocketServlet.java new file mode 100644 index 00000000000..31fc97bc7c4 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/ContainerShellWebSocketServlet.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.hadoop.yarn.server.nodemanager.webapp; + +import javax.servlet.annotation.WebServlet; + +import org.eclipse.jetty.websocket.servlet.WebSocketServlet; +import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; + +/** + * Container shell web socket interface + */ +@WebServlet(urlPatterns="/container/$containerId") +public class ContainerShellWebSocketServlet extends WebSocketServlet{ + + @Override + public void configure(WebSocketServletFactory factory) { + factory.register(ContainerShellWebSocket.class); + } +} diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/WebServer.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/WebServer.java index 813ba142111..3476aeb3271 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/WebServer.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/WebServer.java @@ -41,7 +41,9 @@ import com.sun.jersey.guice.spi.container.servlet.GuiceContainer; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class WebServer extends AbstractService { @@ -64,6 +66,7 @@ public WebServer(Context nmContext, ResourceView resourceView, @Override protected void serviceStart() throws Exception { Configuration conf = getConfig(); + Map params = new HashMap(); String bindAddress = WebAppUtils.getWebAppBindURL(conf, YarnConfiguration.NM_BIND_HOST, WebAppUtils.getNMWebAppURLWithoutScheme(conf)); @@ -102,6 +105,8 @@ protected void serviceStart() throws Exception { WebApps .$for("node", Context.class, this.nmContext, "ws") .at(bindAddress) + .withServlet("ContainerShellWebSocket", "/container/*", + ContainerShellWebSocketServlet.class, params, false) .with(conf) .withHttpSpnegoPrincipalKey( YarnConfiguration.NM_WEBAPP_SPNEGO_USER_NAME_KEY) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/webapp/TestNMWebServer.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/webapp/TestNMWebServer.java index 0a71a9179bb..31eb96184a5 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/webapp/TestNMWebServer.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/webapp/TestNMWebServer.java @@ -25,6 +25,8 @@ import java.io.FileWriter; import java.io.IOException; import java.io.Writer; +import java.net.URI; +import java.util.concurrent.Future; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileUtil; @@ -55,6 +57,10 @@ import org.apache.hadoop.yarn.server.security.ApplicationACLsManager; import org.apache.hadoop.yarn.server.utils.BuilderUtils; import org.apache.hadoop.yarn.util.ConverterUtils; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; +import org.eclipse.jetty.websocket.client.WebSocketClient; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -124,8 +130,8 @@ public boolean isPmemCheckEnabled() { server.start(); return server.getPort(); } finally { - server.stop(); - healthChecker.stop(); + //server.stop(); + //healthChecker.stop(); } } @@ -239,6 +245,34 @@ public ContainerState getContainerState() { // Thread.sleep(1000000); } + @Test + public void testWebServerWithServlet() { + int port = startNMWebAppServer("0.0.0.0"); + System.out.println("bind to port: " + port); + StringBuilder sb = new StringBuilder(); + sb.append("ws://localhost:").append(port).append("/container/abc/"); + String dest = sb.toString(); + WebSocketClient client = new WebSocketClient(); + try { + ContainerShellClientSocket socket = new ContainerShellClientSocket(); + client.start(); + URI echoUri = new URI(dest); + Future future = client.connect(socket, echoUri); + Session session = future.get(); + session.getRemote().sendString("hello world"); + session.close(); + client.stop(); + } catch (Throwable t) { + t.printStackTrace(); + } finally { + try { + client.stop(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + private void writeContainerLogs(Context nmContext, ContainerId containerId, LocalDirsHandlerService dirsHandler) throws IOException, YarnException {