diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxy.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxy.java index b05badc917e..658cfe17d2a 100644 --- a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxy.java +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxy.java @@ -1,688 +1,442 @@ -/** - * Copyright (C) 2010 Cloud.com, Inc. All rights reserved. - * - * This software is licensed under the GNU General Public License v3 or later. - * - * It is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -package com.cloud.consoleproxy; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Enumeration; -import java.util.Hashtable; -import java.util.Map; -import java.util.Properties; -import java.util.concurrent.Executor; - -import javax.net.ssl.SSLServerSocket; - -import org.apache.log4j.xml.DOMConfigurator; - -import com.cloud.console.AuthenticationException; -import com.cloud.console.Logger; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.sun.net.httpserver.HttpServer; - -public class ConsoleProxy { - private static final Logger s_logger = Logger.getLogger(ConsoleProxy.class); - - public static final int KEYBOARD_RAW = 0; - public static final int KEYBOARD_COOKED = 1; - - public static Object context; - - // this has become more ugly, to store keystore info passed from management server (we now use management server managed keystore to support - // dynamically changing to customer supplied certificate) - public static byte[] ksBits; - public static String ksPassword; - - public static Method authMethod; - public static Method reportMethod; - public static Method ensureRouteMethod; - - static Hashtable connectionMap = new Hashtable(); - static int tcpListenPort = 5999; - static int httpListenPort = 80; - static int httpCmdListenPort = 8001; - static String jarDir = "./applet/"; - static boolean compressServerMessage = true; - static int viewerLinger = 180; - static int reconnectMaxRetry = 5; - static int readTimeoutSeconds = 90; - static int keyboardType = KEYBOARD_RAW; - static String factoryClzName; - static boolean standaloneStart = false; - - private static void configLog4j() { - URL configUrl = System.class.getResource("/conf/log4j-cloud.xml"); - if(configUrl == null) - configUrl = System.class.getClassLoader().getSystemResource("log4j-cloud.xml"); - - if(configUrl == null) - configUrl = System.class.getClassLoader().getSystemResource("conf/log4j-cloud.xml"); - - if(configUrl != null) { - try { - System.out.println("Configure log4j using " + configUrl.toURI().toString()); - } catch (URISyntaxException e1) { - e1.printStackTrace(); - } - - try { - File file = new File(configUrl.toURI()); - - System.out.println("Log4j configuration from : " + file.getAbsolutePath()); - DOMConfigurator.configureAndWatch(file.getAbsolutePath(), 10000); - } catch (URISyntaxException e) { - System.out.println("Unable to convert log4j configuration Url to URI"); - } - // DOMConfigurator.configure(configUrl); - } else { - System.out.println("Configure log4j with default properties"); - } - } - - private static void configProxy(Properties conf) { - s_logger.info("Configure console proxy..."); - for(Object key : conf.keySet()) { - s_logger.info("Property " + (String)key + ": " + conf.getProperty((String)key)); - } - - String s = conf.getProperty("consoleproxy.tcpListenPort"); - if (s!=null) { - tcpListenPort = Integer.parseInt(s); - s_logger.info("Setting tcpListenPort=" + s); - } - - s = conf.getProperty("consoleproxy.httpListenPort"); - if (s!=null) { - httpListenPort = Integer.parseInt(s); - s_logger.info("Setting httpListenPort=" + s); - } - - s = conf.getProperty("premium"); - if(s != null && s.equalsIgnoreCase("true")) { - s_logger.info("Premium setting will override settings from consoleproxy.properties, listen at port 443"); - httpListenPort = 443; - factoryClzName = "com.cloud.consoleproxy.ConsoleProxySecureServerFactoryImpl"; - } else { - factoryClzName = ConsoleProxyBaseServerFactoryImpl.class.getName(); - } - - s = conf.getProperty("consoleproxy.httpCmdListenPort"); - if (s!=null) { - httpCmdListenPort = Integer.parseInt(s); - s_logger.info("Setting httpCmdListenPort=" + s); - } - s = conf.getProperty("consoleproxy.jarDir"); - if (s!=null) { - jarDir = s; - s_logger.info("Setting jarDir=" + s); - } - s = conf.getProperty("consoleproxy.viewerLinger"); - if (s!=null) { - viewerLinger = Integer.parseInt(s); - s_logger.info("Setting viewerLinger=" + s); - } - s = conf.getProperty("consoleproxy.compressServerMessage"); - if (s!=null) { - compressServerMessage = Boolean.parseBoolean(s); - s_logger.info("Setting compressServerMessage=" + s); - } - - s = conf.getProperty("consoleproxy.reconnectMaxRetry"); - if (s!=null) { - reconnectMaxRetry = Integer.parseInt(s); - s_logger.info("Setting reconnectMaxRetry=" + reconnectMaxRetry); - } - - s = conf.getProperty("consoleproxy.readTimeoutSeconds"); - if (s!=null) { - readTimeoutSeconds = Integer.parseInt(s); - s_logger.info("Setting readTimeoutSeconds=" + readTimeoutSeconds); - } - } - - public static ConsoleProxyServerFactory getHttpServerFactory() { - try { - Class clz = Class.forName(factoryClzName); - try { - ConsoleProxyServerFactory factory = (ConsoleProxyServerFactory)clz.newInstance(); - factory.init(ConsoleProxy.ksBits, ConsoleProxy.ksPassword); - return factory; - } catch (InstantiationException e) { - s_logger.error(e.getMessage(), e); - return null; - } catch (IllegalAccessException e) { - s_logger.error(e.getMessage(), e); - return null; - } - } catch (ClassNotFoundException e) { - s_logger.warn("Unable to find http server factory class: " + factoryClzName); - return new ConsoleProxyBaseServerFactoryImpl(); - } - } - - public static boolean authenticateConsoleAccess(String host, String port, String vmId, String sid, String ticket) { - if(standaloneStart) - return true; - - if(authMethod != null) { - Object result; - try { - result = authMethod.invoke(ConsoleProxy.context, host, port, vmId, sid, ticket); - } catch (IllegalAccessException e) { - s_logger.error("Unable to invoke authenticateConsoleAccess due to IllegalAccessException" + " for vm: " + vmId, e); - return false; - } catch (InvocationTargetException e) { - s_logger.error("Unable to invoke authenticateConsoleAccess due to InvocationTargetException " + " for vm: " + vmId, e); - return false; - } - - if(result != null && result instanceof Boolean) { - return ((Boolean)result).booleanValue(); - } else { - s_logger.error("Invalid authentication return object " + result + " for vm: " + vmId + ", decline the access"); - return false; - } - - } else { - s_logger.warn("Private channel towards management server is not setup. Switch to offline mode and allow access to vm: " + vmId); - return true; - } - } - - public static void reportLoadInfo(String gsonLoadInfo) { - if(reportMethod != null) { - try { - reportMethod.invoke(ConsoleProxy.context, gsonLoadInfo); - } catch (IllegalAccessException e) { - s_logger.error("Unable to invoke reportLoadInfo due to " + e.getMessage()); - } catch (InvocationTargetException e) { - s_logger.error("Unable to invoke reportLoadInfo due to " + e.getMessage()); - } - } else { - s_logger.warn("Private channel towards management server is not setup. Switch to offline mode and ignore load report"); - } - } - - public static void ensureRoute(String address) { - if(ensureRouteMethod != null) { - try { - ensureRouteMethod.invoke(ConsoleProxy.context, address); - } catch (IllegalAccessException e) { - s_logger.error("Unable to invoke ensureRoute due to " + e.getMessage()); - } catch (InvocationTargetException e) { - s_logger.error("Unable to invoke ensureRoute due to " + e.getMessage()); - } - } else { - s_logger.warn("Unable to find ensureRoute method, console proxy agent is not up to date"); - } - } - - public static void startWithContext(Properties conf, Object context, byte[] ksBits, String ksPassword) { - s_logger.info("Start console proxy with context"); - if(conf != null) { - for(Object key : conf.keySet()) { - s_logger.info("Context property " + (String)key + ": " + conf.getProperty((String)key)); - } - } - - configLog4j(); - Logger.setFactory(new ConsoleProxyLoggerFactory()); - - // Using reflection to setup private/secure communication channel towards management server - ConsoleProxy.context = context; - ConsoleProxy.ksBits = ksBits; - ConsoleProxy.ksPassword = ksPassword; - try { - Class contextClazz = Class.forName("com.cloud.agent.resource.consoleproxy.ConsoleProxyResource"); - authMethod = contextClazz.getDeclaredMethod("authenticateConsoleAccess", String.class, String.class, String.class, String.class, String.class); - reportMethod = contextClazz.getDeclaredMethod("reportLoadInfo", String.class); - ensureRouteMethod = contextClazz.getDeclaredMethod("ensureRoute", String.class); - } catch (SecurityException e) { - s_logger.error("Unable to setup private channel due to SecurityException", e); - } catch (NoSuchMethodException e) { - s_logger.error("Unable to setup private channel due to NoSuchMethodException", e); - } catch (IllegalArgumentException e) { - s_logger.error("Unable to setup private channel due to IllegalArgumentException", e); - } catch(ClassNotFoundException e) { - s_logger.error("Unable to setup private channel due to ClassNotFoundException", e); - } - - // merge properties from conf file - InputStream confs = ConsoleProxy.class.getResourceAsStream("/conf/consoleproxy.properties"); - Properties props = new Properties(); - if (confs == null) { - s_logger.info("Can't load consoleproxy.properties from classpath, will use default configuration"); - } else { - try { - props.load(confs); - - for(Object key : props.keySet()) { - // give properties passed via context high priority, treat properties from consoleproxy.properties - // as default values - if(conf.get(key) == null) - conf.put(key, props.get(key)); - } - } catch (Exception e) { - s_logger.error(e.toString(), e); - } - } - - start(conf); - } - - public static void start(Properties conf) { - System.setProperty("java.awt.headless", "true"); - - configProxy(conf); - - ConsoleProxyServerFactory factory = getHttpServerFactory(); - if(factory == null) { - s_logger.error("Unable to load console proxy server factory"); - System.exit(1); - } - - if(httpListenPort != 0) { - startupHttpMain(); - } else { - s_logger.error("A valid HTTP server port is required to be specified, please check your consoleproxy.httpListenPort settings"); - System.exit(1); - } - - if(httpCmdListenPort > 0) { - startupHttpCmdPort(); - } else { - s_logger.info("HTTP command port is disabled"); - } - - ViewerGCThread cthread = new ViewerGCThread(connectionMap); - cthread.setName("Viewer GC Thread"); - cthread.start(); - - if(tcpListenPort > 0) { - SSLServerSocket srvSock = null; - try { - srvSock = factory.createSSLServerSocket(tcpListenPort); - s_logger.info("Listening for TCP on port " + tcpListenPort); - } catch (IOException ioe) { - s_logger.error(ioe.toString(), ioe); - System.exit(1); - } - - if(srvSock != null) { - while (true) { - Socket conn = null; - try { - conn = srvSock.accept(); - String srcinfo = conn.getInetAddress().getHostAddress() + ":" + conn.getPort(); - s_logger.info("Accepted connection from " + srcinfo); - conn.setSoLinger(false,0); - ConsoleProxyClientHandler worker = new ConsoleProxyClientHandler(conn); - worker.setName("Proxy Thread " + worker.getId() + " <" + srcinfo); - worker.start(); - } catch (IOException ioe2) { - s_logger.error(ioe2.toString(), ioe2); - try { - if (conn != null) { - conn.close(); - } - } catch (IOException ioe) {} - } catch (Throwable e) { - // Something really bad happened - // Terminate the program - s_logger.error(e.toString(), e); - System.exit(1); - } - } - } else { - s_logger.warn("TCP port is enabled in configuration but we are not able to instantiate server socket."); - } - } else { - s_logger.info("TCP port is disabled for applet viewers"); - } - } - - private static void startupHttpMain() { - try { - ConsoleProxyServerFactory factory = getHttpServerFactory(); - if(factory == null) { - s_logger.error("Unable to load HTTP server factory"); - System.exit(1); - } - - HttpServer server = factory.createHttpServerInstance(httpListenPort); - server.createContext("/getscreen", new ConsoleProxyThumbnailHandler()); - server.createContext("/resource/", new ConsoleProxyResourceHandler()); - server.createContext("/ajax", new ConsoleProxyAjaxHandler()); - server.createContext("/ajaximg", new ConsoleProxyAjaxImageHandler()); - server.setExecutor(new ThreadExecutor()); // creates a default executor - server.start(); - } catch(Exception e) { - s_logger.error(e.getMessage(), e); - System.exit(1); - } - } - - private static void startupHttpCmdPort() { - try { - s_logger.info("Listening for HTTP CMDs on port " + httpCmdListenPort); - HttpServer cmdServer = HttpServer.create(new InetSocketAddress(httpCmdListenPort), 2); - cmdServer.createContext("/cmd", new ConsoleProxyCmdHandler()); - cmdServer.setExecutor(new ThreadExecutor()); // creates a default executor - cmdServer.start(); - } catch(Exception e) { - s_logger.error(e.getMessage(), e); - System.exit(1); - } - } - - public static void main(String[] argv) { - standaloneStart = true; - configLog4j(); - Logger.setFactory(new ConsoleProxyLoggerFactory()); - - InputStream confs = ConsoleProxy.class.getResourceAsStream("/conf/consoleproxy.properties"); - Properties conf = new Properties(); - if (confs == null) { - s_logger.info("Can't load consoleproxy.properties from classpath, will use default configuration"); - } else { - try { - conf.load(confs); - } catch (Exception e) { - s_logger.error(e.toString(), e); - } - } - start(conf); - } - - static ConsoleProxyViewer createViewer() { - ConsoleProxyViewer viewer = new ConsoleProxyViewer(); - viewer.compressServerMessage = compressServerMessage; - return viewer; - } - - static void initViewer(ConsoleProxyViewer viewer, String host, int port, String tag, String sid, String ticket) throws AuthenticationException { - ConsoleProxyViewer.authenticationExternally(host, String.valueOf(port), tag, sid, ticket); - - viewer.host = host; - viewer.port = port; - viewer.tag = tag; - viewer.passwordParam = sid; - - viewer.init(); - } - - static ConsoleProxyViewer getVncViewer(String host, int port, String sid, String tag, String ticket) throws Exception { - ConsoleProxyViewer viewer = null; - - boolean reportLoadChange = false; - synchronized (connectionMap) { - viewer = connectionMap.get(host + ":" + port); - if (viewer == null) { - viewer = createViewer(); - initViewer(viewer, host, port, tag, sid, ticket); - connectionMap.put(host + ":" + port, viewer); - s_logger.info("Added viewer object " + viewer); - - reportLoadChange = true; - } else if (!viewer.rfbThread.isAlive()) { - s_logger.info("The rfb thread died, reinitializing the viewer " + - viewer); - initViewer(viewer, host, port, tag, sid, ticket); - - reportLoadChange = true; - } else if (!sid.equals(viewer.passwordParam)) { - s_logger.warn("Bad sid detected(VNC port may be reused). sid in session: " + viewer.passwordParam + ", sid in request: " + sid); - initViewer(viewer, host, port, tag, sid, ticket); - - reportLoadChange = true; - - /* - throw new AuthenticationException ("Cannot use the existing viewer " + - viewer + ": bad sid"); - */ - } - } - - if(viewer != null) { - if (viewer.status == ConsoleProxyViewer.STATUS_NORMAL_OPERATION) { - // Do not update lastUsedTime if the viewer is in the process of starting up - // or if it failed to authenticate. - viewer.lastUsedTime = System.currentTimeMillis(); - } - } - - if(reportLoadChange) { - ConsoleProxyStatus status = new ConsoleProxyStatus(); - status.setConnections(ConsoleProxy.connectionMap); - Gson gson = new GsonBuilder().setPrettyPrinting().create(); - String loadInfo = gson.toJson(status); - - ConsoleProxy.reportLoadInfo(loadInfo); - if(s_logger.isDebugEnabled()) - s_logger.debug("Report load change : " + loadInfo); - } - - return viewer; - } - - static ConsoleProxyViewer getAjaxVncViewer(String host, int port, String sid, String tag, String ticket, String ajaxSession) throws Exception { - boolean reportLoadChange = false; - synchronized (connectionMap) { - ConsoleProxyViewer viewer = connectionMap.get(host + ":" + port); -// s_logger.info("view lookup " + host + ":" + port + " = " + viewer); - - if (viewer == null) { - viewer = createViewer(); - viewer.ajaxViewer = true; - - initViewer(viewer, host, port, tag, sid, ticket); - connectionMap.put(host + ":" + port, viewer); - s_logger.info("Added viewer object " + viewer); - reportLoadChange = true; - } else if (!viewer.rfbThread.isAlive()) { - s_logger.info("The rfb thread died, reinitializing the viewer " + - viewer); - initViewer(viewer, host, port, tag, sid, ticket); - reportLoadChange = true; - } else if (!sid.equals(viewer.passwordParam)) { - s_logger.warn("Bad sid detected(VNC port may be reused). sid in session: " + viewer.passwordParam + ", sid in request: " + sid); - initViewer(viewer, host, port, tag, sid, ticket); - reportLoadChange = true; - - /* - throw new AuthenticationException ("Cannot use the existing viewer " + - viewer + ": bad sid"); - */ - } else { - if(ajaxSession == null || ajaxSession.isEmpty()) - ConsoleProxyViewer.authenticationExternally(host, String.valueOf(port), tag, sid, ticket); - } - - if (viewer.status == ConsoleProxyViewer.STATUS_NORMAL_OPERATION) { - // Do not update lastUsedTime if the viewer is in the process of starting up - // or if it failed to authenticate. - viewer.lastUsedTime = System.currentTimeMillis(); - } - - if(reportLoadChange) { - ConsoleProxyStatus status = new ConsoleProxyStatus(); - status.setConnections(ConsoleProxy.connectionMap); - Gson gson = new GsonBuilder().setPrettyPrinting().create(); - String loadInfo = gson.toJson(status); - - ConsoleProxy.reportLoadInfo(loadInfo); - if(s_logger.isDebugEnabled()) - s_logger.debug("Report load change : " + loadInfo); - } - return viewer; - } - } - - public static void removeViewer(ConsoleProxyViewer viewer) { - synchronized (connectionMap) { - for(Map.Entry entry : connectionMap.entrySet()) { - if(entry.getValue() == viewer) { - connectionMap.remove(entry.getKey()); - return; - } - } - } - } - - static void waitForViewerToStart(ConsoleProxyViewer viewer) throws Exception { - if (viewer.status == ConsoleProxyViewer.STATUS_NORMAL_OPERATION) { - return; - } - - Long startTime = System.currentTimeMillis(); - int delay = 500; - - while (System.currentTimeMillis() < startTime + 30000 && - viewer.status != ConsoleProxyViewer.STATUS_NORMAL_OPERATION) { - if (viewer.status == ConsoleProxyViewer.STATUS_AUTHENTICATION_FAILURE) { - throw new Exception ("Authentication failure"); - } - try { - Thread.sleep(delay); - } catch (InterruptedException e) { - // ignore - } - delay = (int)(delay * 1.5); - } - - if (viewer.status != ConsoleProxyViewer.STATUS_NORMAL_OPERATION) { - throw new Exception ("Cannot start VncViewer"); - } - - s_logger.info("Waited " + - (System.currentTimeMillis() - startTime) + "ms for VncViewer to start"); - } - - static class ThreadExecutor implements Executor { - public void execute(Runnable r) { - new Thread(r).start(); - } - } - - static class ViewerGCThread extends Thread { - Hashtable connMap; - long lastLogScan = 0L; - - public ViewerGCThread(Hashtable connMap) { - this.connMap = connMap; - } - - private void cleanupLogging() { - if(lastLogScan != 0 && System.currentTimeMillis() - lastLogScan < 3600000) - return; - lastLogScan = System.currentTimeMillis(); - - File logDir = new File("./logs"); - File files[] = logDir.listFiles(); - if(files != null) { - for(File file : files) { - if(System.currentTimeMillis() - file.lastModified() >= 86400000L) { - try { - file.delete(); - } catch(Throwable e) { - } - } - } - } - } - - @Override - public void run() { - while (true) { - cleanupLogging(); - - s_logger.info("connMap=" + connMap); - Enumeration e = connMap.keys(); - while (e.hasMoreElements()) { - String key; - ConsoleProxyViewer viewer; - - synchronized (connMap) { - key = e.nextElement(); - viewer = connMap.get(key); - } - - long seconds_unused = (System.currentTimeMillis() - viewer.lastUsedTime) / 1000; - - if (seconds_unused > viewerLinger / 2 && viewer.clientStream != null) { - s_logger.info("Pinging client for " + viewer + - " which has not been used for " + seconds_unused + "sec"); - byte[] bs = new byte[2]; - bs[0] = (byte)250; - bs[1] = 3; - viewer.writeToClientStream(bs); - } - - if (seconds_unused < viewerLinger) { - continue; - } - - synchronized (connMap) { - connMap.remove(key); - } - // close the server connection - s_logger.info("Dropping " + viewer + - " which has not been used for " + - seconds_unused + " seconds"); - viewer.dropMe = true; - synchronized (viewer) { - if (viewer.clientStream != null) { - try { - viewer.clientStream.close(); - } catch (IOException ioe) { - // ignored - } - viewer.clientStream = null; - viewer.clientStreamInfo = null; - } - if (viewer.rfb != null) { - viewer.rfb.close(); - } - } - - // report load change for removal of the viewer - ConsoleProxyStatus status = new ConsoleProxyStatus(); - status.setConnections(ConsoleProxy.connectionMap); - Gson gson = new GsonBuilder().setPrettyPrinting().create(); - String loadInfo = gson.toJson(status); - - ConsoleProxy.reportLoadInfo(loadInfo); - if(s_logger.isDebugEnabled()) - s_logger.debug("Report load change : " + loadInfo); - } - try { - Thread.sleep(30000); - } catch (InterruptedException exp) { - // Ignore - } - } - } - } -} +package com.cloud.consoleproxy; + +import java.io.File; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Hashtable; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.Executor; + +import org.apache.log4j.xml.DOMConfigurator; + +import com.cloud.console.AuthenticationException; +import com.cloud.console.Logger; +import com.sun.net.httpserver.HttpServer; + +/** + * + * @author Kelven Yang + * ConsoleProxy, singleton class that manages overall activities in console proxy process. To make legacy code work, we still + * keep a lot of static memebers in this class + */ +public class ConsoleProxy { + private static final Logger s_logger = Logger.getLogger(ConsoleProxy.class); + + public static final int KEYBOARD_RAW = 0; + public static final int KEYBOARD_COOKED = 1; + + public static int VIEWER_LINGER_SECONDS = 180; + + public static Object context; + + // this has become more ugly, to store keystore info passed from management server (we now use management server managed keystore to support + // dynamically changing to customer supplied certificate) + public static byte[] ksBits; + public static String ksPassword; + + public static Method authMethod; + public static Method reportMethod; + public static Method ensureRouteMethod; + + static Hashtable connectionMap = new Hashtable(); + static int httpListenPort = 80; + static int httpCmdListenPort = 8001; + static int reconnectMaxRetry = 5; + static int readTimeoutSeconds = 90; + static int keyboardType = KEYBOARD_RAW; + static String factoryClzName; + static boolean standaloneStart = false; + + private static void configLog4j() { + URL configUrl = System.class.getResource("/conf/log4j-cloud.xml"); + if(configUrl == null) + configUrl = ClassLoader.getSystemResource("log4j-cloud.xml"); + + if(configUrl == null) + configUrl = ClassLoader.getSystemResource("conf/log4j-cloud.xml"); + + if(configUrl != null) { + try { + System.out.println("Configure log4j using " + configUrl.toURI().toString()); + } catch (URISyntaxException e1) { + e1.printStackTrace(); + } + + try { + File file = new File(configUrl.toURI()); + + System.out.println("Log4j configuration from : " + file.getAbsolutePath()); + DOMConfigurator.configureAndWatch(file.getAbsolutePath(), 10000); + } catch (URISyntaxException e) { + System.out.println("Unable to convert log4j configuration Url to URI"); + } + // DOMConfigurator.configure(configUrl); + } else { + System.out.println("Configure log4j with default properties"); + } + } + + private static void configProxy(Properties conf) { + s_logger.info("Configure console proxy..."); + for(Object key : conf.keySet()) { + s_logger.info("Property " + (String)key + ": " + conf.getProperty((String)key)); + } + + String s = conf.getProperty("consoleproxy.httpListenPort"); + if (s!=null) { + httpListenPort = Integer.parseInt(s); + s_logger.info("Setting httpListenPort=" + s); + } + + s = conf.getProperty("premium"); + if(s != null && s.equalsIgnoreCase("true")) { + s_logger.info("Premium setting will override settings from consoleproxy.properties, listen at port 443"); + httpListenPort = 443; + factoryClzName = "com.cloud.consoleproxy.ConsoleProxySecureServerFactoryImpl"; + } else { + factoryClzName = ConsoleProxyBaseServerFactoryImpl.class.getName(); + } + + s = conf.getProperty("consoleproxy.httpCmdListenPort"); + if (s!=null) { + httpCmdListenPort = Integer.parseInt(s); + s_logger.info("Setting httpCmdListenPort=" + s); + } + + s = conf.getProperty("consoleproxy.reconnectMaxRetry"); + if (s!=null) { + reconnectMaxRetry = Integer.parseInt(s); + s_logger.info("Setting reconnectMaxRetry=" + reconnectMaxRetry); + } + + s = conf.getProperty("consoleproxy.readTimeoutSeconds"); + if (s!=null) { + readTimeoutSeconds = Integer.parseInt(s); + s_logger.info("Setting readTimeoutSeconds=" + readTimeoutSeconds); + } + } + + public static ConsoleProxyServerFactory getHttpServerFactory() { + try { + Class clz = Class.forName(factoryClzName); + try { + ConsoleProxyServerFactory factory = (ConsoleProxyServerFactory)clz.newInstance(); + factory.init(ConsoleProxy.ksBits, ConsoleProxy.ksPassword); + return factory; + } catch (InstantiationException e) { + s_logger.error(e.getMessage(), e); + return null; + } catch (IllegalAccessException e) { + s_logger.error(e.getMessage(), e); + return null; + } + } catch (ClassNotFoundException e) { + s_logger.warn("Unable to find http server factory class: " + factoryClzName); + return new ConsoleProxyBaseServerFactoryImpl(); + } + } + + public static boolean authenticateConsoleAccess(String host, String port, String vmId, String sid, String ticket) { + if(standaloneStart) + return true; + + if(authMethod != null) { + Object result; + try { + result = authMethod.invoke(ConsoleProxy.context, host, port, vmId, sid, ticket); + } catch (IllegalAccessException e) { + s_logger.error("Unable to invoke authenticateConsoleAccess due to IllegalAccessException" + " for vm: " + vmId, e); + return false; + } catch (InvocationTargetException e) { + s_logger.error("Unable to invoke authenticateConsoleAccess due to InvocationTargetException " + " for vm: " + vmId, e); + return false; + } + + if(result != null && result instanceof Boolean) { + return ((Boolean)result).booleanValue(); + } else { + s_logger.error("Invalid authentication return object " + result + " for vm: " + vmId + ", decline the access"); + return false; + } + + } else { + s_logger.warn("Private channel towards management server is not setup. Switch to offline mode and allow access to vm: " + vmId); + return true; + } + } + + public static void reportLoadInfo(String gsonLoadInfo) { + if(reportMethod != null) { + try { + reportMethod.invoke(ConsoleProxy.context, gsonLoadInfo); + } catch (IllegalAccessException e) { + s_logger.error("Unable to invoke reportLoadInfo due to " + e.getMessage()); + } catch (InvocationTargetException e) { + s_logger.error("Unable to invoke reportLoadInfo due to " + e.getMessage()); + } + } else { + s_logger.warn("Private channel towards management server is not setup. Switch to offline mode and ignore load report"); + } + } + + public static void ensureRoute(String address) { + if(ensureRouteMethod != null) { + try { + ensureRouteMethod.invoke(ConsoleProxy.context, address); + } catch (IllegalAccessException e) { + s_logger.error("Unable to invoke ensureRoute due to " + e.getMessage()); + } catch (InvocationTargetException e) { + s_logger.error("Unable to invoke ensureRoute due to " + e.getMessage()); + } + } else { + s_logger.warn("Unable to find ensureRoute method, console proxy agent is not up to date"); + } + } + + public static void startWithContext(Properties conf, Object context, byte[] ksBits, String ksPassword) { + s_logger.info("Start console proxy with context"); + if(conf != null) { + for(Object key : conf.keySet()) { + s_logger.info("Context property " + (String)key + ": " + conf.getProperty((String)key)); + } + } + + configLog4j(); + Logger.setFactory(new ConsoleProxyLoggerFactory()); + + // Using reflection to setup private/secure communication channel towards management server + ConsoleProxy.context = context; + ConsoleProxy.ksBits = ksBits; + ConsoleProxy.ksPassword = ksPassword; + try { + Class contextClazz = Class.forName("com.cloud.agent.resource.consoleproxy.ConsoleProxyResource"); + authMethod = contextClazz.getDeclaredMethod("authenticateConsoleAccess", String.class, String.class, String.class, String.class, String.class); + reportMethod = contextClazz.getDeclaredMethod("reportLoadInfo", String.class); + ensureRouteMethod = contextClazz.getDeclaredMethod("ensureRoute", String.class); + } catch (SecurityException e) { + s_logger.error("Unable to setup private channel due to SecurityException", e); + } catch (NoSuchMethodException e) { + s_logger.error("Unable to setup private channel due to NoSuchMethodException", e); + } catch (IllegalArgumentException e) { + s_logger.error("Unable to setup private channel due to IllegalArgumentException", e); + } catch(ClassNotFoundException e) { + s_logger.error("Unable to setup private channel due to ClassNotFoundException", e); + } + + // merge properties from conf file + InputStream confs = ConsoleProxy.class.getResourceAsStream("/conf/consoleproxy.properties"); + Properties props = new Properties(); + if (confs == null) { + s_logger.info("Can't load consoleproxy.properties from classpath, will use default configuration"); + } else { + try { + props.load(confs); + + for(Object key : props.keySet()) { + // give properties passed via context high priority, treat properties from consoleproxy.properties + // as default values + if(conf.get(key) == null) + conf.put(key, props.get(key)); + } + } catch (Exception e) { + s_logger.error(e.toString(), e); + } + } + + start(conf); + } + + public static void start(Properties conf) { + System.setProperty("java.awt.headless", "true"); + + configProxy(conf); + + ConsoleProxyServerFactory factory = getHttpServerFactory(); + if(factory == null) { + s_logger.error("Unable to load console proxy server factory"); + System.exit(1); + } + + if(httpListenPort != 0) { + startupHttpMain(); + } else { + s_logger.error("A valid HTTP server port is required to be specified, please check your consoleproxy.httpListenPort settings"); + System.exit(1); + } + + if(httpCmdListenPort > 0) { + startupHttpCmdPort(); + } else { + s_logger.info("HTTP command port is disabled"); + } + + ConsoleProxyGCThread cthread = new ConsoleProxyGCThread(connectionMap); + cthread.setName("Console Proxy GC Thread"); + cthread.start(); + } + + private static void startupHttpMain() { + try { + ConsoleProxyServerFactory factory = getHttpServerFactory(); + if(factory == null) { + s_logger.error("Unable to load HTTP server factory"); + System.exit(1); + } + + HttpServer server = factory.createHttpServerInstance(httpListenPort); + server.createContext("/getscreen", new ConsoleProxyThumbnailHandler()); + server.createContext("/resource/", new ConsoleProxyResourceHandler()); + server.createContext("/ajax", new ConsoleProxyAjaxHandler()); + server.createContext("/ajaximg", new ConsoleProxyAjaxImageHandler()); + server.setExecutor(new ThreadExecutor()); // creates a default executor + server.start(); + } catch(Exception e) { + s_logger.error(e.getMessage(), e); + System.exit(1); + } + } + + private static void startupHttpCmdPort() { + try { + s_logger.info("Listening for HTTP CMDs on port " + httpCmdListenPort); + HttpServer cmdServer = HttpServer.create(new InetSocketAddress(httpCmdListenPort), 2); + cmdServer.createContext("/cmd", new ConsoleProxyCmdHandler()); + cmdServer.setExecutor(new ThreadExecutor()); // creates a default executor + cmdServer.start(); + } catch(Exception e) { + s_logger.error(e.getMessage(), e); + System.exit(1); + } + } + + public static void main(String[] argv) { + standaloneStart = true; + configLog4j(); + Logger.setFactory(new ConsoleProxyLoggerFactory()); + + InputStream confs = ConsoleProxy.class.getResourceAsStream("/conf/consoleproxy.properties"); + Properties conf = new Properties(); + if (confs == null) { + s_logger.info("Can't load consoleproxy.properties from classpath, will use default configuration"); + } else { + try { + conf.load(confs); + } catch (Exception e) { + s_logger.error(e.toString(), e); + } + } + start(conf); + } + + public static ConsoleProxyClient getVncViewer(String host, int port, String sid, String tag, String ticket) throws Exception { + ConsoleProxyClient viewer = null; + + boolean reportLoadChange = false; + synchronized (connectionMap) { + viewer = connectionMap.get(host + ":" + port); + if (viewer == null) { + viewer = new ConsoleProxyVncClient(); + viewer.initClient(host, port, sid, tag, ticket); + connectionMap.put(host + ":" + port, viewer); + s_logger.info("Added viewer object " + viewer); + + reportLoadChange = true; + } else if (!viewer.isFrontEndAlive()) { + s_logger.info("The rfb thread died, reinitializing the viewer " + + viewer); + viewer.initClient(host, port, sid, tag, ticket); + + reportLoadChange = true; + } else if (!sid.equals(viewer.getClientHostPassword())) { + s_logger.warn("Bad sid detected(VNC port may be reused). sid in session: " + viewer.getClientHostPassword() + + ", sid in request: " + sid); + viewer.initClient(host, port, sid, tag, ticket); + + reportLoadChange = true; + } + } + + if(reportLoadChange) { + ConsoleProxyClientStatsCollector statsCollector = getStatsCollector(); + String loadInfo = statsCollector.getStatsReport(); + reportLoadInfo(loadInfo); + if(s_logger.isDebugEnabled()) + s_logger.debug("Report load change : " + loadInfo); + } + + return viewer; + } + + public static ConsoleProxyClient getAjaxVncViewer(String host, int port, String sid, String tag, + String ticket, String ajaxSession) throws Exception { + + boolean reportLoadChange = false; + synchronized (connectionMap) { + ConsoleProxyClient viewer = connectionMap.get(host + ":" + port); + if (viewer == null) { + viewer = new ConsoleProxyVncClient(); + viewer.initClient(host, port, sid, tag, ticket); + + connectionMap.put(host + ":" + port, viewer); + s_logger.info("Added viewer object " + viewer); + reportLoadChange = true; + } else if (!viewer.isFrontEndAlive()) { + s_logger.info("The rfb thread died, reinitializing the viewer " + + viewer); + viewer.initClient(host, port, sid, tag, ticket); + reportLoadChange = true; + } else if (!sid.equals(viewer.getClientHostPassword())) { + s_logger.warn("Bad sid detected(VNC port may be reused). sid in session: " + + viewer.getClientHostPassword() + ", sid in request: " + sid); + viewer.initClient(host, port, sid, tag, ticket); + reportLoadChange = true; + } else { + if(ajaxSession == null || ajaxSession.isEmpty()) + authenticationExternally(host, String.valueOf(port), tag, sid, ticket); + } + + if(reportLoadChange) { + ConsoleProxyClientStatsCollector statsCollector = getStatsCollector(); + String loadInfo = statsCollector.getStatsReport(); + reportLoadInfo(loadInfo); + if(s_logger.isDebugEnabled()) + s_logger.debug("Report load change : " + loadInfo); + } + return viewer; + } + } + + public static void removeViewer(ConsoleProxyClient viewer) { + synchronized (connectionMap) { + for(Map.Entry entry : connectionMap.entrySet()) { + if(entry.getValue() == viewer) { + connectionMap.remove(entry.getKey()); + return; + } + } + } + } + + public static ConsoleProxyClientStatsCollector getStatsCollector() { + return new ConsoleProxyClientStatsCollector(connectionMap); + } + + public static void authenticationExternally(String host, String port, String tag, String sid, String ticket) throws AuthenticationException { + if(!authenticateConsoleAccess(host, port, tag, sid, ticket)) { + s_logger.warn("External authenticator failed authencation request for vm " + tag + " with sid " + sid); + + throw new AuthenticationException("External authenticator failed request for vm " + tag + " with sid " + sid); + } + } + + static class ThreadExecutor implements Executor { + public void execute(Runnable r) { + new Thread(r).start(); + } + } +} diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyAjaxHandler.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyAjaxHandler.java index ddfdb88a55f..3c9e18c0985 100644 --- a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyAjaxHandler.java +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyAjaxHandler.java @@ -114,22 +114,11 @@ public class ConsoleProxyAjaxHandler implements HttpHandler { } } - ConsoleProxyViewer viewer = null; + ConsoleProxyClient viewer = null; try { viewer = ConsoleProxy.getAjaxVncViewer(host, port, sid, tag, ticket, ajaxSessionIdStr); } catch(Exception e) { -/* - StringWriter writer = new StringWriter(); - try { - ConsoleProxy.processTemplate("viewer-bad-sid.ftl", null, writer); - } catch (IOException ex) { - s_logger.warn("Unexpected exception in processing template.", e); - } catch (TemplateException ex) { - s_logger.warn("Unexpected exception in processing template.", e); - } - StringBuffer sb = writer.getBuffer(); -*/ s_logger.warn("Failed to create viwer due to " + e.getMessage(), e); String[] content = new String[] { @@ -236,7 +225,8 @@ public class ConsoleProxyAjaxHandler implements HttpHandler { } } - private void handleClientEventBag(ConsoleProxyViewer viewer, String requestData) { + @SuppressWarnings("deprecation") + private void handleClientEventBag(ConsoleProxyClient viewer, String requestData) { if(s_logger.isTraceEnabled()) s_logger.trace("Handle event bag, event bag: " + requestData); @@ -294,7 +284,7 @@ public class ConsoleProxyAjaxHandler implements HttpHandler { } } - private void handleClientEvent(ConsoleProxyViewer viewer, int event, Map queryMap) { + private void handleClientEvent(ConsoleProxyClient viewer, int event, Map queryMap) { int code = 0; int x = 0, y = 0; int modifiers = 0; @@ -347,7 +337,7 @@ public class ConsoleProxyAjaxHandler implements HttpHandler { if(s_logger.isTraceEnabled()) s_logger.trace("Handle client mouse move event. x: " + x + ", y: " + y); } - viewer.sendClientMouseEvent(event, x, y, code, modifiers); + viewer.sendClientMouseEvent(InputEventType.fromEventCode(event), x, y, code, modifiers); break; case 4: // key press @@ -371,7 +361,7 @@ public class ConsoleProxyAjaxHandler implements HttpHandler { if(s_logger.isDebugEnabled()) s_logger.debug("Handle client keyboard event. event: " + event + ", code: " + code + ", modifier: " + modifiers); - viewer.sendClientRawKeyboardEvent(event, code, modifiers); + viewer.sendClientRawKeyboardEvent(InputEventType.fromEventCode(event), code, modifiers); break; default : @@ -379,7 +369,7 @@ public class ConsoleProxyAjaxHandler implements HttpHandler { } } - private void handleClientKickoff(HttpExchange t, ConsoleProxyViewer viewer) throws IOException { + private void handleClientKickoff(HttpExchange t, ConsoleProxyClient viewer) throws IOException { String response = viewer.onAjaxClientKickoff(); t.sendResponseHeaders(200, response.length()); OutputStream os = t.getResponseBody(); @@ -390,7 +380,7 @@ public class ConsoleProxyAjaxHandler implements HttpHandler { } } - private void handleClientStart(HttpExchange t, ConsoleProxyViewer viewer, String title, String guest) throws IOException { + private void handleClientStart(HttpExchange t, ConsoleProxyClient viewer, String title, String guest) throws IOException { List languages = t.getRequestHeaders().get("Accept-Language"); String response = viewer.onAjaxClientStart(title, languages, guest); @@ -408,7 +398,7 @@ public class ConsoleProxyAjaxHandler implements HttpHandler { } } - private void handleClientUpdate(HttpExchange t, ConsoleProxyViewer viewer) throws IOException { + private void handleClientUpdate(HttpExchange t, ConsoleProxyClient viewer) throws IOException { String response = viewer.onAjaxClientUpdate(); Headers hds = t.getResponseHeaders(); diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyAjaxImageHandler.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyAjaxImageHandler.java index 812c4b4cb94..f151b6e4470 100644 --- a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyAjaxImageHandler.java +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyAjaxImageHandler.java @@ -91,7 +91,7 @@ public class ConsoleProxyAjaxImageHandler implements HttpHandler { throw new IllegalArgumentException(e); } - ConsoleProxyViewer viewer = ConsoleProxy.getVncViewer(host, port, sid, tag, ticket); + ConsoleProxyClient viewer = ConsoleProxy.getVncViewer(host, port, sid, tag, ticket); byte[] img = viewer.getAjaxImageCache().getImage(key); if(img != null) { Headers hds = t.getResponseHeaders(); diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClient.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClient.java index 69e964ed51e..f83a4b06122 100644 --- a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClient.java +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClient.java @@ -1,25 +1,56 @@ package com.cloud.consoleproxy; +import java.awt.Image; +import java.util.List; + /** * @author Kelven Yang * ConsoleProxyClient defines an standard interface that a console client should implement, * example of such console clients could be a VNC client or a RDP client * * ConsoleProxyClient maintains a session towards the target host, it glues the session - * to a AJAX frontend viewer + * to a AJAX front-end viewer */ public interface ConsoleProxyClient { int getClientId(); + // + // Quick status + // + boolean isHostConnected(); + boolean isFrontEndAlive(); + + // + // AJAX viewer + // + long getAjaxSessionId(); + AjaxFIFOImageCache getAjaxImageCache(); + Image getClientScaledImage(int width, int height); // client thumbnail support + + String onAjaxClientStart(String title, List languages, String guest); + String onAjaxClientUpdate(); + String onAjaxClientKickoff(); + + // + // Input handling + // + void sendClientRawKeyboardEvent(InputEventType event, int code, int modifiers); + void sendClientMouseEvent(InputEventType event, int x, int y, int code, int modifiers); + + // + // Info/Stats + // long getClientCreateTime(); long getClientLastFrontEndActivityTime(); - String getClientHostAddress(); int getClientHostPort(); String getClientHostPassword(); String getClientTag(); + // + // Setup/house-keeping + // void initClient(String clientHostAddress, int clientHostPort, - String clientHostPassword, String clientTag); + String clientHostPassword, String clientTag, String ticket); void closeClient(); } diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClientBase.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClientBase.java index 53315a87f8d..b66213e68eb 100644 --- a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClientBase.java +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClientBase.java @@ -1,5 +1,6 @@ package com.cloud.consoleproxy; +import java.awt.Image; import java.awt.Rectangle; import java.util.List; @@ -8,7 +9,6 @@ import org.apache.log4j.Logger; import com.cloud.console.TileInfo; import com.cloud.console.TileTracker; import com.cloud.consoleproxy.vnc.FrameBufferCanvas; -import com.cloud.consoleproxy.vnc.FrameBufferEventListener; /** * @@ -20,7 +20,7 @@ import com.cloud.consoleproxy.vnc.FrameBufferEventListener; * It mainly implements the features needed by front-end AJAX viewer * */ -public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, FrameBufferEventListener { +public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, ConsoleProxyClientListener { private static final Logger s_logger = Logger.getLogger(ConsoleProxyClientBase.class); private static int s_nextClientId = 0; @@ -37,6 +37,7 @@ public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, Fram protected int port; protected String passwordParam; protected String tag = ""; + protected String ticket = ""; protected long createTime = System.currentTimeMillis(); protected long lastFrontEndActivityTime = System.currentTimeMillis(); @@ -45,6 +46,8 @@ public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, Fram protected int resizedFramebufferHeight; public ConsoleProxyClientBase() { + tracker = new TileTracker(); + tracker.initTracking(64, 64, 800, 600); } // @@ -55,6 +58,30 @@ public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, Fram return clientId; } + public abstract boolean isHostConnected(); + public abstract boolean isFrontEndAlive(); + + @Override + public long getAjaxSessionId() { + return this.ajaxSessionId; + } + + @Override + public AjaxFIFOImageCache getAjaxImageCache() { + return ajaxImageCache; + } + + public Image getClientScaledImage(int width, int height) { + FrameBufferCanvas canvas = getFrameBufferCavas(); + if(canvas != null) + return canvas.getFrameBufferScaledImage(width, height); + + return null; + } + + public abstract void sendClientRawKeyboardEvent(InputEventType event, int code, int modifiers); + public abstract void sendClientMouseEvent(InputEventType event, int x, int y, int code, int modifiers); + @Override public long getClientCreateTime() { return createTime; @@ -87,8 +114,8 @@ public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, Fram @Override public abstract void initClient(String clientHostAddress, int clientHostPort, - String clientHostPassword, String clientTag); - + String clientHostPassword, String clientTag, String ticket); + @Override public abstract void closeClient(); @@ -170,6 +197,7 @@ public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, Fram return sb.toString(); } + @Override public String onAjaxClientKickoff() { return "onKickoff();"; } @@ -193,8 +221,11 @@ public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, Fram "Unable to start console session as connection is refused by the machine you are accessing" + "

"; } - + + @Override public String onAjaxClientStart(String title, List languages, String guest) { + updateFrontEndActivityTime(); + if(!waitForViewerReady()) return onAjaxClientConnectFailed(); @@ -317,8 +348,10 @@ public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, Fram public String onAjaxClientDisconnected() { return "onDisconnect();"; } - + + @Override public String onAjaxClientUpdate() { + updateFrontEndActivityTime(); if(!waitForViewerReady()) return onAjaxClientDisconnected(); @@ -386,14 +419,6 @@ public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, Fram return ++s_nextClientId; } - public long getAjaxSessionId() { - return this.ajaxSessionId; - } - - public AjaxFIFOImageCache getAjaxImageCache() { - return ajaxImageCache; - } - private void signalTileDirtyEvent() { synchronized(tileDirtyEvent) { dirtyFlag = true; diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClientHandler.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClientHandler.java index 23543a706c4..fee5fe4c360 100644 --- a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClientHandler.java +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClientHandler.java @@ -28,7 +28,8 @@ import java.util.StringTokenizer; import com.cloud.console.Logger; import com.cloud.console.RfbProto; -public class ConsoleProxyClientHandler extends Thread { +public class ConsoleProxyClientHandler extends Thread { +/* private static final Logger s_logger = Logger.getLogger(ConsoleProxyClientHandler.class); Socket clientSocket = null; @@ -274,5 +275,6 @@ public class ConsoleProxyClientHandler extends Thread { } finally { viewer.lastUsedTime = System.currentTimeMillis(); } - } + } +*/ } diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClientListener.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClientListener.java new file mode 100644 index 00000000000..bef18631259 --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClientListener.java @@ -0,0 +1,9 @@ +package com.cloud.consoleproxy; + +public interface ConsoleProxyClientListener { + void onFramebufferSizeChange(int w, int h); + void onFramebufferUpdate(int x, int y, int w, int h); + + void onClientConnected(); + void onClientClose(); +} diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClientStatsCollector.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClientStatsCollector.java index 2fc181368e1..ffcf795ab4c 100644 --- a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClientStatsCollector.java +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClientStatsCollector.java @@ -1,5 +1,6 @@ package com.cloud.consoleproxy; +import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.Enumeration; import java.util.Hashtable; @@ -28,6 +29,11 @@ public class ConsoleProxyClientStatsCollector { Gson gson = new GsonBuilder().setPrettyPrinting().create(); return gson.toJson(this); } + + public void getStatsReport(OutputStreamWriter os) { + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + gson.toJson(this, os); + } private void setConnections(Hashtable connMap) { diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyCmdHandler.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyCmdHandler.java index 27e9be49c55..38ef1dbda9a 100644 --- a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyCmdHandler.java +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyCmdHandler.java @@ -23,8 +23,6 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import com.cloud.console.Logger; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; @@ -60,15 +58,14 @@ public class ConsoleProxyCmdHandler implements HttpHandler { int i = path.indexOf("/", 1); String cmd = path.substring(i + 1); s_logger.info("Get CMD request for " + cmd); - if (cmd.equals("getstatus")) { - ConsoleProxyStatus status = new ConsoleProxyStatus(); - status.setConnections(ConsoleProxy.connectionMap); + if (cmd.equals("getstatus")) { + ConsoleProxyClientStatsCollector statsCollector = ConsoleProxy.getStatsCollector(); + Headers hds = t.getResponseHeaders(); hds.set("Content-Type", "text/plain"); t.sendResponseHeaders(200, 0); - OutputStreamWriter os = new OutputStreamWriter(t.getResponseBody()); - Gson gson = new GsonBuilder().setPrettyPrinting().create(); - gson.toJson(status, os); + OutputStreamWriter os = new OutputStreamWriter(t.getResponseBody()); + statsCollector.getStatsReport(os); os.close(); } } diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyGCThread.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyGCThread.java index f42f688ea50..81a3b66ff84 100644 --- a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyGCThread.java +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyGCThread.java @@ -81,7 +81,7 @@ public class ConsoleProxyGCThread extends Thread { s_logger.debug("Report load change : " + loadInfo); } - try { Thread.sleep(30000); } catch (InterruptedException ex) {} + try { Thread.sleep(1000); } catch (InterruptedException ex) {} } } } diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyMonitor.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyMonitor.java index 45bfd111108..7d8f624e961 100644 --- a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyMonitor.java +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyMonitor.java @@ -123,10 +123,10 @@ public class ConsoleProxyMonitor { private static void configLog4j() { URL configUrl = System.class.getResource("/conf/log4j-cloud.xml"); if(configUrl == null) - configUrl = System.class.getClassLoader().getSystemResource("log4j-cloud.xml"); + configUrl = ClassLoader.getSystemResource("log4j-cloud.xml"); if(configUrl == null) - configUrl = System.class.getClassLoader().getSystemResource("conf/log4j-cloud.xml"); + configUrl = ClassLoader.getSystemResource("conf/log4j-cloud.xml"); if(configUrl != null) { try { @@ -150,7 +150,6 @@ public class ConsoleProxyMonitor { public static void main(String[] argv) { configLog4j(); - (new ConsoleProxyMonitor(argv)).run(); } } diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyStatus.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyStatus.java index 75a106097fb..6a6e56d6353 100644 --- a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyStatus.java +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyStatus.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Enumeration; import java.util.Hashtable; +/* public class ConsoleProxyStatus { ArrayList connections; public ConsoleProxyStatus() { @@ -59,3 +60,4 @@ public class ConsoleProxyStatus { } } } +*/ \ No newline at end of file diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyThumbnailHandler.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyThumbnailHandler.java index 690490c3c91..393fe0facc8 100644 --- a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyThumbnailHandler.java +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyThumbnailHandler.java @@ -93,32 +93,6 @@ public class ConsoleProxyThumbnailHandler implements HttpHandler { OutputStream os = t.getResponseBody(); os.write(bs); os.close(); - -/* - // Send back a dummy image - File f = new File ("./images/cannotconnect.jpg"); - long length = f.length(); - FileInputStream fis = new FileInputStream(f); - Headers hds = t.getResponseHeaders(); - hds.set("Content-Type", "image/jpeg"); - hds.set("Cache-Control", "no-cache"); - hds.set("Cache-Control", "no-store"); - t.sendResponseHeaders(200, length); - OutputStream os = t.getResponseBody(); - try { - while (true) { - byte[] b = new byte[8192]; - int n = fis.read(b); - if (n < 0) { - break; - } - os.write(b, 0, n); - } - } finally { - os.close(); - fis.close(); - } -*/ s_logger.error("Cannot get console, sent error JPG response for " + t.getRequestURI()); return; } finally { @@ -126,8 +100,7 @@ public class ConsoleProxyThumbnailHandler implements HttpHandler { } } - private void doHandle(HttpExchange t) throws Exception, - IllegalArgumentException { + private void doHandle(HttpExchange t) throws Exception, IllegalArgumentException { String queries = t.getRequestURI().getQuery(); Map queryMap = getQueryMap(queries); int width = 0; @@ -154,9 +127,9 @@ public class ConsoleProxyThumbnailHandler implements HttpHandler { throw new IllegalArgumentException(e); } - ConsoleProxyViewer viewer = ConsoleProxy.getVncViewer(host, port, sid, tag, ticket); + ConsoleProxyClient viewer = ConsoleProxy.getVncViewer(host, port, sid, tag, ticket); - if (viewer.status != ConsoleProxyViewer.STATUS_NORMAL_OPERATION) { + if (!viewer.isHostConnected()) { // use generated image instead of static BufferedImage img = generateTextImage(width, height, "Connecting"); ByteArrayOutputStream bos = new ByteArrayOutputStream(8196); @@ -171,54 +144,13 @@ public class ConsoleProxyThumbnailHandler implements HttpHandler { os.write(bs); os.close(); -/* - // Send back a dummy image - File f = new File ("./images/notready.jpg"); - long length = f.length(); - FileInputStream fis = new FileInputStream(f); - Headers hds = t.getResponseHeaders(); - hds.set("Content-Type", "image/jpeg"); - hds.set("Cache-Control", "no-cache"); - hds.set("Cache-Control", "no-store"); - t.sendResponseHeaders(200, length); - - OutputStream os = t.getResponseBody(); - try { - while (true) { - byte[] b = new byte[8192]; - int n = fis.read(b); - if (n < 0) { - break; - } - os.write(b, 0, n); - } - } finally { - os.close(); - fis.close(); - } -*/ if(s_logger.isInfoEnabled()) - s_logger.info("Console not ready, sent dummy JPG response, viewer status : " + viewer.status); + s_logger.info("Console not ready, sent dummy JPG response"); return; - } -/* - if (viewer.status == ConsoleViewer.STATUS_AUTHENTICATION_FAILURE) { - String response = "Authentication failed"; - t.sendResponseHeaders(200, response.length()); - OutputStream os = t.getResponseBody(); - os.write(response.getBytes()); - os.close(); - } else if (viewer.vc == null || viewer.vc.memImage == null) { - String response = "Server not ready"; - t.sendResponseHeaders(200, response.length()); - OutputStream os = t.getResponseBody(); - os.write(response.getBytes()); - os.close(); - } else -*/ + } + { - Image scaledImage = viewer.vc.memImage.getScaledInstance(width, - height, Image.SCALE_DEFAULT); + Image scaledImage = viewer.getClientScaledImage(width, height); BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR); Graphics2D bufImageGraphics = bufferedImage.createGraphics(); diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyViewer.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyViewer.java index 0f936234884..ed6174110c4 100644 --- a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyViewer.java +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyViewer.java @@ -48,7 +48,8 @@ import com.cloud.console.RfbProtoAdapter; import com.cloud.console.RfbViewer; import com.cloud.console.TileInfo; import com.cloud.console.TileTracker; - + +/* public class ConsoleProxyViewer implements java.lang.Runnable, RfbViewer, RfbProtoAdapter, ITileScanListener { private static final Logger s_logger = Logger.getLogger(ConsoleProxyViewer.class); @@ -317,42 +318,6 @@ public class ConsoleProxyViewer implements java.lang.Runnable, RfbViewer, RfbPro } static void authenticationExternally(String host, String port, String tag, String sid, String ticket) throws AuthenticationException { -/* - if(ConsoleProxy.management_host != null) { - try { - boolean success = false; - URL url = new URL(ConsoleProxy.management_host + "/console?cmd=auth&vm=" + getTag() + "&sid=" + passwordParam); - - URLConnection conn = url.openConnection(); - - // setting TIMEOUTs to avoid possible waiting until death situations - conn.setConnectTimeout(5000); - conn.setReadTimeout(5000); - - BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); - String inputLine; - if ((inputLine = in.readLine()) != null) { - if(inputLine.equals("success")) - success = true; - } - in.close(); - - if(!success) { - if(s_logger.isInfoEnabled()) - s_logger.info("External authenticator failed authencation request for vm " + getTag() + " with sid " + passwordParam); - - throw new AuthenticationException("Unable to contact external authentication source " + ConsoleProxy.management_host); - } - } catch (MalformedURLException e) { - s_logger.error("Unexpected exception " + e.getMessage(), e); - } catch(IOException e) { - s_logger.error("Unable to contact external authentication source due to " + e.getMessage(), e); - throw new AuthenticationException("Unable to contact external authentication source " + ConsoleProxy.management_host); - } - } else { - s_logger.warn("No external authentication source being setup."); - } -*/ if(!ConsoleProxy.authenticateConsoleAccess(host, port, tag, sid, ticket)) { s_logger.warn("External authenticator failed authencation request for vm " + tag + " with sid " + sid); @@ -978,31 +943,6 @@ public class ConsoleProxyViewer implements java.lang.Runnable, RfbViewer, RfbPro i++; } -/* - SimpleHash model = new SimpleHash(); - model.put("tileSequence", sbTileSequence.toString()); - model.put("imgUrl", imgUrl); - model.put("updateUrl", updateUrl); - model.put("width", String.valueOf(width)); - model.put("height", String.valueOf(height)); - model.put("tileWidth", String.valueOf(tileWidth)); - model.put("tileHeight", String.valueOf(tileHeight)); - model.put("title", title); - model.put("rawKeyboard", ConsoleProxy.keyboardType == ConsoleProxy.KEYBOARD_RAW ? "true" : "false"); - - StringWriter writer = new StringWriter(); - try { - ConsoleProxy.processTemplate("viewer.ftl", model, writer); - } catch (IOException e) { - s_logger.warn("Unexpected exception in processing template.", e); - } catch (TemplateException e) { - s_logger.warn("Unexpected exception in processing template.", e); - } - StringBuffer sb = writer.getBuffer(); - if(s_logger.isTraceEnabled()) - s_logger.trace("onAjaxClientStart response: " + sb.toString()); - return sb.toString(); - */ return getAjaxViewerPageContent(sbTileSequence.toString(), imgUrl, updateUrl, width, height, tileWidth, tileHeight, title, ConsoleProxy.keyboardType == ConsoleProxy.KEYBOARD_RAW, languages, guest); @@ -1129,31 +1069,6 @@ public class ConsoleProxyViewer implements java.lang.Runnable, RfbViewer, RfbPro i++; } -/* - SimpleHash model = new SimpleHash(); - model.put("tileSequence", sbTileSequence.toString()); - model.put("resized", doResize); - model.put("imgUrl", imgUrl); - model.put("width", String.valueOf(resizedFramebufferWidth)); - model.put("height", String.valueOf(resizedFramebufferHeight)); - model.put("tileWidth", String.valueOf(tracker.getTileWidth())); - model.put("tileHeight", String.valueOf(tracker.getTileHeight())); - - StringWriter writer = new StringWriter(); - try { - ConsoleProxy.processTemplate("viewer-update.ftl", model, writer); - } catch (IOException e) { - s_logger.warn("Unexpected exception in processing template.", e); - } catch (TemplateException e) { - s_logger.warn("Unexpected exception in processing template.", e); - } - StringBuffer sb = writer.getBuffer(); - - if(s_logger.isTraceEnabled()) - s_logger.trace("onAjaxClientUpdate response: " + sb.toString()); - - return sb.toString(); -*/ return getAjaxViewerUpdatePageContent(sbTileSequence.toString(), imgUrl, doResize, resizedFramebufferWidth, resizedFramebufferHeight, tracker.getTileWidth(), tracker.getTileHeight()); } @@ -1304,5 +1219,7 @@ public class ConsoleProxyViewer implements java.lang.Runnable, RfbViewer, RfbPro } } } - } -} + } +} + +*/ diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyVncClient.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyVncClient.java index 310ee655085..5cd6fe8cb10 100644 --- a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyVncClient.java +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyVncClient.java @@ -1,8 +1,12 @@ package com.cloud.consoleproxy; +import java.io.IOException; +import java.net.UnknownHostException; + import org.apache.log4j.Logger; import com.cloud.consoleproxy.vnc.FrameBufferCanvas; +import com.cloud.consoleproxy.vnc.RfbConstants; import com.cloud.consoleproxy.vnc.VncClient; /** @@ -15,19 +19,124 @@ public class ConsoleProxyVncClient extends ConsoleProxyClientBase { private static final Logger s_logger = Logger.getLogger(ConsoleProxyVncClient.class); private VncClient client; - + private Thread worker; + private boolean workerDone = false; + public ConsoleProxyVncClient() { } + + public boolean isHostConnected() { + if(client != null) + return client.isHostConnected(); + + return false; + } + + @Override + public boolean isFrontEndAlive() { + if(workerDone || System.currentTimeMillis() - getClientLastFrontEndActivityTime() > ConsoleProxy.VIEWER_LINGER_SECONDS*1000) + return false; + return true; + } - public void initClient(String clientHostAddress, int clientHostPort, - String clientHostPassword, String clientTag) { - // TODO + @Override + public void initClient(final String clientHostAddress, final int clientHostPort, + final String clientHostPassword, final String clientTag, final String ticket) { + this.host = clientHostAddress; + this.port = clientHostPort; + this.passwordParam = clientHostPassword; + this.tag = clientTag; + this.ticket = ticket; + + client = new VncClient(this); + worker = new Thread(new Runnable() { + public void run() { + long startTick = System.currentTimeMillis(); + while(System.currentTimeMillis() - startTick < 7000) { + try { + client.connectTo(clientHostAddress, clientHostPort, clientHostPassword); + } catch (UnknownHostException e) { + s_logger.error("Unexpected exception: ", e); + } catch (IOException e) { + s_logger.error("Unexpected exception: ", e); + } + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + } + } + + workerDone = true; + } + }); + + worker.setDaemon(true); + worker.start(); } + @Override public void closeClient() { - // TODO + if(client != null) + client.shutdown(); } + @Override + public void onClientConnected() { + } + + public void onClientClose() { + ConsoleProxy.removeViewer(this); + } + + @Override + public void onFramebufferUpdate(int x, int y, int w, int h) { + super.onFramebufferUpdate(x, y, w, h); + client.requestUpdate(false); + } + + public void sendClientRawKeyboardEvent(InputEventType event, int code, int modifiers) { + if(client == null) + return; + + updateFrontEndActivityTime(); + + switch(event) { + case KEY_DOWN : + client.sendClientKeyboardEvent(RfbConstants.KEY_DOWN, code, 0); + break; + + case KEY_UP : + client.sendClientKeyboardEvent(RfbConstants.KEY_UP, code, 0); + break; + + case KEY_PRESS : + break; + + default : + assert(false); + break; + } + } + + public void sendClientMouseEvent(InputEventType event, int x, int y, int code, int modifiers) { + if(client == null) + return; + + updateFrontEndActivityTime(); + + int pointerMask = 0; + int mask = 1; + if(code == 2) + mask = 4; + if(event == InputEventType.MOUSE_DOWN) { + pointerMask = mask; + } + + client.sendClientMouseEvent(pointerMask, x, y, code, modifiers); + } + + @Override protected FrameBufferCanvas getFrameBufferCavas() { if(client != null) return client.getFrameBufferCanvas(); diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxy_obsolete.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxy_obsolete.java new file mode 100644 index 00000000000..1168e64186b --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxy_obsolete.java @@ -0,0 +1,680 @@ +/** + * Copyright (C) 2010 Cloud.com, Inc. All rights reserved. + * + * This software is licensed under the GNU General Public License v3 or later. + * + * It is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +package com.cloud.consoleproxy; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.Executor; + +import javax.net.ssl.SSLServerSocket; + +import org.apache.log4j.xml.DOMConfigurator; + +import com.cloud.console.AuthenticationException; +import com.cloud.console.Logger; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.sun.net.httpserver.HttpServer; + +/* +public class ConsoleProxy_obsolete { + private static final Logger s_logger = Logger.getLogger(ConsoleProxy_obsolete.class); + + public static final int KEYBOARD_RAW = 0; + public static final int KEYBOARD_COOKED = 1; + + public static Object context; + + // this has become more ugly, to store keystore info passed from management server (we now use management server managed keystore to support + // dynamically changing to customer supplied certificate) + public static byte[] ksBits; + public static String ksPassword; + + public static Method authMethod; + public static Method reportMethod; + public static Method ensureRouteMethod; + + static Hashtable connectionMap = new Hashtable(); + static int tcpListenPort = 5999; + static int httpListenPort = 80; + static int httpCmdListenPort = 8001; + static String jarDir = "./applet/"; + static boolean compressServerMessage = true; + static int viewerLinger = 180; + static int reconnectMaxRetry = 5; + static int readTimeoutSeconds = 90; + static int keyboardType = KEYBOARD_RAW; + static String factoryClzName; + static boolean standaloneStart = false; + + private static void configLog4j() { + URL configUrl = System.class.getResource("/conf/log4j-cloud.xml"); + if(configUrl == null) + configUrl = System.class.getClassLoader().getSystemResource("log4j-cloud.xml"); + + if(configUrl == null) + configUrl = System.class.getClassLoader().getSystemResource("conf/log4j-cloud.xml"); + + if(configUrl != null) { + try { + System.out.println("Configure log4j using " + configUrl.toURI().toString()); + } catch (URISyntaxException e1) { + e1.printStackTrace(); + } + + try { + File file = new File(configUrl.toURI()); + + System.out.println("Log4j configuration from : " + file.getAbsolutePath()); + DOMConfigurator.configureAndWatch(file.getAbsolutePath(), 10000); + } catch (URISyntaxException e) { + System.out.println("Unable to convert log4j configuration Url to URI"); + } + // DOMConfigurator.configure(configUrl); + } else { + System.out.println("Configure log4j with default properties"); + } + } + + private static void configProxy(Properties conf) { + s_logger.info("Configure console proxy..."); + for(Object key : conf.keySet()) { + s_logger.info("Property " + (String)key + ": " + conf.getProperty((String)key)); + } + + String s = conf.getProperty("consoleproxy.tcpListenPort"); + if (s!=null) { + tcpListenPort = Integer.parseInt(s); + s_logger.info("Setting tcpListenPort=" + s); + } + + s = conf.getProperty("consoleproxy.httpListenPort"); + if (s!=null) { + httpListenPort = Integer.parseInt(s); + s_logger.info("Setting httpListenPort=" + s); + } + + s = conf.getProperty("premium"); + if(s != null && s.equalsIgnoreCase("true")) { + s_logger.info("Premium setting will override settings from consoleproxy.properties, listen at port 443"); + httpListenPort = 443; + factoryClzName = "com.cloud.consoleproxy.ConsoleProxySecureServerFactoryImpl"; + } else { + factoryClzName = ConsoleProxyBaseServerFactoryImpl.class.getName(); + } + + s = conf.getProperty("consoleproxy.httpCmdListenPort"); + if (s!=null) { + httpCmdListenPort = Integer.parseInt(s); + s_logger.info("Setting httpCmdListenPort=" + s); + } + s = conf.getProperty("consoleproxy.jarDir"); + if (s!=null) { + jarDir = s; + s_logger.info("Setting jarDir=" + s); + } + s = conf.getProperty("consoleproxy.viewerLinger"); + if (s!=null) { + viewerLinger = Integer.parseInt(s); + s_logger.info("Setting viewerLinger=" + s); + } + s = conf.getProperty("consoleproxy.compressServerMessage"); + if (s!=null) { + compressServerMessage = Boolean.parseBoolean(s); + s_logger.info("Setting compressServerMessage=" + s); + } + + s = conf.getProperty("consoleproxy.reconnectMaxRetry"); + if (s!=null) { + reconnectMaxRetry = Integer.parseInt(s); + s_logger.info("Setting reconnectMaxRetry=" + reconnectMaxRetry); + } + + s = conf.getProperty("consoleproxy.readTimeoutSeconds"); + if (s!=null) { + readTimeoutSeconds = Integer.parseInt(s); + s_logger.info("Setting readTimeoutSeconds=" + readTimeoutSeconds); + } + } + + public static ConsoleProxyServerFactory getHttpServerFactory() { + try { + Class clz = Class.forName(factoryClzName); + try { + ConsoleProxyServerFactory factory = (ConsoleProxyServerFactory)clz.newInstance(); + factory.init(ConsoleProxy_obsolete.ksBits, ConsoleProxy_obsolete.ksPassword); + return factory; + } catch (InstantiationException e) { + s_logger.error(e.getMessage(), e); + return null; + } catch (IllegalAccessException e) { + s_logger.error(e.getMessage(), e); + return null; + } + } catch (ClassNotFoundException e) { + s_logger.warn("Unable to find http server factory class: " + factoryClzName); + return new ConsoleProxyBaseServerFactoryImpl(); + } + } + + public static boolean authenticateConsoleAccess(String host, String port, String vmId, String sid, String ticket) { + if(standaloneStart) + return true; + + if(authMethod != null) { + Object result; + try { + result = authMethod.invoke(ConsoleProxy_obsolete.context, host, port, vmId, sid, ticket); + } catch (IllegalAccessException e) { + s_logger.error("Unable to invoke authenticateConsoleAccess due to IllegalAccessException" + " for vm: " + vmId, e); + return false; + } catch (InvocationTargetException e) { + s_logger.error("Unable to invoke authenticateConsoleAccess due to InvocationTargetException " + " for vm: " + vmId, e); + return false; + } + + if(result != null && result instanceof Boolean) { + return ((Boolean)result).booleanValue(); + } else { + s_logger.error("Invalid authentication return object " + result + " for vm: " + vmId + ", decline the access"); + return false; + } + + } else { + s_logger.warn("Private channel towards management server is not setup. Switch to offline mode and allow access to vm: " + vmId); + return true; + } + } + + public static void reportLoadInfo(String gsonLoadInfo) { + if(reportMethod != null) { + try { + reportMethod.invoke(ConsoleProxy_obsolete.context, gsonLoadInfo); + } catch (IllegalAccessException e) { + s_logger.error("Unable to invoke reportLoadInfo due to " + e.getMessage()); + } catch (InvocationTargetException e) { + s_logger.error("Unable to invoke reportLoadInfo due to " + e.getMessage()); + } + } else { + s_logger.warn("Private channel towards management server is not setup. Switch to offline mode and ignore load report"); + } + } + + public static void ensureRoute(String address) { + if(ensureRouteMethod != null) { + try { + ensureRouteMethod.invoke(ConsoleProxy_obsolete.context, address); + } catch (IllegalAccessException e) { + s_logger.error("Unable to invoke ensureRoute due to " + e.getMessage()); + } catch (InvocationTargetException e) { + s_logger.error("Unable to invoke ensureRoute due to " + e.getMessage()); + } + } else { + s_logger.warn("Unable to find ensureRoute method, console proxy agent is not up to date"); + } + } + + public static void startWithContext(Properties conf, Object context, byte[] ksBits, String ksPassword) { + s_logger.info("Start console proxy with context"); + if(conf != null) { + for(Object key : conf.keySet()) { + s_logger.info("Context property " + (String)key + ": " + conf.getProperty((String)key)); + } + } + + configLog4j(); + Logger.setFactory(new ConsoleProxyLoggerFactory()); + + // Using reflection to setup private/secure communication channel towards management server + ConsoleProxy_obsolete.context = context; + ConsoleProxy_obsolete.ksBits = ksBits; + ConsoleProxy_obsolete.ksPassword = ksPassword; + try { + Class contextClazz = Class.forName("com.cloud.agent.resource.consoleproxy.ConsoleProxyResource"); + authMethod = contextClazz.getDeclaredMethod("authenticateConsoleAccess", String.class, String.class, String.class, String.class, String.class); + reportMethod = contextClazz.getDeclaredMethod("reportLoadInfo", String.class); + ensureRouteMethod = contextClazz.getDeclaredMethod("ensureRoute", String.class); + } catch (SecurityException e) { + s_logger.error("Unable to setup private channel due to SecurityException", e); + } catch (NoSuchMethodException e) { + s_logger.error("Unable to setup private channel due to NoSuchMethodException", e); + } catch (IllegalArgumentException e) { + s_logger.error("Unable to setup private channel due to IllegalArgumentException", e); + } catch(ClassNotFoundException e) { + s_logger.error("Unable to setup private channel due to ClassNotFoundException", e); + } + + // merge properties from conf file + InputStream confs = ConsoleProxy_obsolete.class.getResourceAsStream("/conf/consoleproxy.properties"); + Properties props = new Properties(); + if (confs == null) { + s_logger.info("Can't load consoleproxy.properties from classpath, will use default configuration"); + } else { + try { + props.load(confs); + + for(Object key : props.keySet()) { + // give properties passed via context high priority, treat properties from consoleproxy.properties + // as default values + if(conf.get(key) == null) + conf.put(key, props.get(key)); + } + } catch (Exception e) { + s_logger.error(e.toString(), e); + } + } + + start(conf); + } + + public static void start(Properties conf) { + System.setProperty("java.awt.headless", "true"); + + configProxy(conf); + + ConsoleProxyServerFactory factory = getHttpServerFactory(); + if(factory == null) { + s_logger.error("Unable to load console proxy server factory"); + System.exit(1); + } + + if(httpListenPort != 0) { + startupHttpMain(); + } else { + s_logger.error("A valid HTTP server port is required to be specified, please check your consoleproxy.httpListenPort settings"); + System.exit(1); + } + + if(httpCmdListenPort > 0) { + startupHttpCmdPort(); + } else { + s_logger.info("HTTP command port is disabled"); + } + + ViewerGCThread cthread = new ViewerGCThread(connectionMap); + cthread.setName("Viewer GC Thread"); + cthread.start(); + + if(tcpListenPort > 0) { + SSLServerSocket srvSock = null; + try { + srvSock = factory.createSSLServerSocket(tcpListenPort); + s_logger.info("Listening for TCP on port " + tcpListenPort); + } catch (IOException ioe) { + s_logger.error(ioe.toString(), ioe); + System.exit(1); + } + + if(srvSock != null) { + while (true) { + Socket conn = null; + try { + conn = srvSock.accept(); + String srcinfo = conn.getInetAddress().getHostAddress() + ":" + conn.getPort(); + s_logger.info("Accepted connection from " + srcinfo); + conn.setSoLinger(false,0); + ConsoleProxyClientHandler worker = new ConsoleProxyClientHandler(conn); + worker.setName("Proxy Thread " + worker.getId() + " <" + srcinfo); + worker.start(); + } catch (IOException ioe2) { + s_logger.error(ioe2.toString(), ioe2); + try { + if (conn != null) { + conn.close(); + } + } catch (IOException ioe) {} + } catch (Throwable e) { + // Something really bad happened + // Terminate the program + s_logger.error(e.toString(), e); + System.exit(1); + } + } + } else { + s_logger.warn("TCP port is enabled in configuration but we are not able to instantiate server socket."); + } + } else { + s_logger.info("TCP port is disabled for applet viewers"); + } + } + + private static void startupHttpMain() { + try { + ConsoleProxyServerFactory factory = getHttpServerFactory(); + if(factory == null) { + s_logger.error("Unable to load HTTP server factory"); + System.exit(1); + } + + HttpServer server = factory.createHttpServerInstance(httpListenPort); + server.createContext("/getscreen", new ConsoleProxyThumbnailHandler()); + server.createContext("/resource/", new ConsoleProxyResourceHandler()); + server.createContext("/ajax", new ConsoleProxyAjaxHandler()); + server.createContext("/ajaximg", new ConsoleProxyAjaxImageHandler()); + server.setExecutor(new ThreadExecutor()); // creates a default executor + server.start(); + } catch(Exception e) { + s_logger.error(e.getMessage(), e); + System.exit(1); + } + } + + private static void startupHttpCmdPort() { + try { + s_logger.info("Listening for HTTP CMDs on port " + httpCmdListenPort); + HttpServer cmdServer = HttpServer.create(new InetSocketAddress(httpCmdListenPort), 2); + cmdServer.createContext("/cmd", new ConsoleProxyCmdHandler()); + cmdServer.setExecutor(new ThreadExecutor()); // creates a default executor + cmdServer.start(); + } catch(Exception e) { + s_logger.error(e.getMessage(), e); + System.exit(1); + } + } + + public static void main(String[] argv) { + standaloneStart = true; + configLog4j(); + Logger.setFactory(new ConsoleProxyLoggerFactory()); + + InputStream confs = ConsoleProxy_obsolete.class.getResourceAsStream("/conf/consoleproxy.properties"); + Properties conf = new Properties(); + if (confs == null) { + s_logger.info("Can't load consoleproxy.properties from classpath, will use default configuration"); + } else { + try { + conf.load(confs); + } catch (Exception e) { + s_logger.error(e.toString(), e); + } + } + start(conf); + } + + static ConsoleProxyViewer createViewer() { + ConsoleProxyViewer viewer = new ConsoleProxyViewer(); + viewer.compressServerMessage = compressServerMessage; + return viewer; + } + + static void initViewer(ConsoleProxyViewer viewer, String host, int port, String tag, String sid, String ticket) throws AuthenticationException { + ConsoleProxyViewer.authenticationExternally(host, String.valueOf(port), tag, sid, ticket); + + viewer.host = host; + viewer.port = port; + viewer.tag = tag; + viewer.passwordParam = sid; + + viewer.init(); + } + + static ConsoleProxyViewer getVncViewer(String host, int port, String sid, String tag, String ticket) throws Exception { + ConsoleProxyViewer viewer = null; + + boolean reportLoadChange = false; + synchronized (connectionMap) { + viewer = connectionMap.get(host + ":" + port); + if (viewer == null) { + viewer = createViewer(); + initViewer(viewer, host, port, tag, sid, ticket); + connectionMap.put(host + ":" + port, viewer); + s_logger.info("Added viewer object " + viewer); + + reportLoadChange = true; + } else if (!viewer.rfbThread.isAlive()) { + s_logger.info("The rfb thread died, reinitializing the viewer " + + viewer); + initViewer(viewer, host, port, tag, sid, ticket); + + reportLoadChange = true; + } else if (!sid.equals(viewer.passwordParam)) { + s_logger.warn("Bad sid detected(VNC port may be reused). sid in session: " + viewer.passwordParam + ", sid in request: " + sid); + initViewer(viewer, host, port, tag, sid, ticket); + + reportLoadChange = true; + } + } + + if(viewer != null) { + if (viewer.status == ConsoleProxyViewer.STATUS_NORMAL_OPERATION) { + // Do not update lastUsedTime if the viewer is in the process of starting up + // or if it failed to authenticate. + viewer.lastUsedTime = System.currentTimeMillis(); + } + } + + if(reportLoadChange) { + ConsoleProxyStatus status = new ConsoleProxyStatus(); + status.setConnections(ConsoleProxy_obsolete.connectionMap); + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + String loadInfo = gson.toJson(status); + + ConsoleProxy_obsolete.reportLoadInfo(loadInfo); + if(s_logger.isDebugEnabled()) + s_logger.debug("Report load change : " + loadInfo); + } + + return viewer; + } + + static ConsoleProxyViewer getAjaxVncViewer(String host, int port, String sid, String tag, String ticket, String ajaxSession) throws Exception { + boolean reportLoadChange = false; + synchronized (connectionMap) { + ConsoleProxyViewer viewer = connectionMap.get(host + ":" + port); +// s_logger.info("view lookup " + host + ":" + port + " = " + viewer); + + if (viewer == null) { + viewer = createViewer(); + viewer.ajaxViewer = true; + + initViewer(viewer, host, port, tag, sid, ticket); + connectionMap.put(host + ":" + port, viewer); + s_logger.info("Added viewer object " + viewer); + reportLoadChange = true; + } else if (!viewer.rfbThread.isAlive()) { + s_logger.info("The rfb thread died, reinitializing the viewer " + + viewer); + initViewer(viewer, host, port, tag, sid, ticket); + reportLoadChange = true; + } else if (!sid.equals(viewer.passwordParam)) { + s_logger.warn("Bad sid detected(VNC port may be reused). sid in session: " + viewer.passwordParam + ", sid in request: " + sid); + initViewer(viewer, host, port, tag, sid, ticket); + reportLoadChange = true; + } else { + if(ajaxSession == null || ajaxSession.isEmpty()) + ConsoleProxyViewer.authenticationExternally(host, String.valueOf(port), tag, sid, ticket); + } + + if (viewer.status == ConsoleProxyViewer.STATUS_NORMAL_OPERATION) { + // Do not update lastUsedTime if the viewer is in the process of starting up + // or if it failed to authenticate. + viewer.lastUsedTime = System.currentTimeMillis(); + } + + if(reportLoadChange) { + ConsoleProxyStatus status = new ConsoleProxyStatus(); + status.setConnections(ConsoleProxy_obsolete.connectionMap); + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + String loadInfo = gson.toJson(status); + + ConsoleProxy_obsolete.reportLoadInfo(loadInfo); + if(s_logger.isDebugEnabled()) + s_logger.debug("Report load change : " + loadInfo); + } + return viewer; + } + } + + public static void removeViewer(ConsoleProxyViewer viewer) { + synchronized (connectionMap) { + for(Map.Entry entry : connectionMap.entrySet()) { + if(entry.getValue() == viewer) { + connectionMap.remove(entry.getKey()); + return; + } + } + } + } + + static void waitForViewerToStart(ConsoleProxyViewer viewer) throws Exception { + if (viewer.status == ConsoleProxyViewer.STATUS_NORMAL_OPERATION) { + return; + } + + Long startTime = System.currentTimeMillis(); + int delay = 500; + + while (System.currentTimeMillis() < startTime + 30000 && + viewer.status != ConsoleProxyViewer.STATUS_NORMAL_OPERATION) { + if (viewer.status == ConsoleProxyViewer.STATUS_AUTHENTICATION_FAILURE) { + throw new Exception ("Authentication failure"); + } + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + // ignore + } + delay = (int)(delay * 1.5); + } + + if (viewer.status != ConsoleProxyViewer.STATUS_NORMAL_OPERATION) { + throw new Exception ("Cannot start VncViewer"); + } + + s_logger.info("Waited " + + (System.currentTimeMillis() - startTime) + "ms for VncViewer to start"); + } + + static class ThreadExecutor implements Executor { + public void execute(Runnable r) { + new Thread(r).start(); + } + } + + static class ViewerGCThread extends Thread { + Hashtable connMap; + long lastLogScan = 0L; + + public ViewerGCThread(Hashtable connMap) { + this.connMap = connMap; + } + + private void cleanupLogging() { + if(lastLogScan != 0 && System.currentTimeMillis() - lastLogScan < 3600000) + return; + lastLogScan = System.currentTimeMillis(); + + File logDir = new File("./logs"); + File files[] = logDir.listFiles(); + if(files != null) { + for(File file : files) { + if(System.currentTimeMillis() - file.lastModified() >= 86400000L) { + try { + file.delete(); + } catch(Throwable e) { + } + } + } + } + } + + @Override + public void run() { + while (true) { + cleanupLogging(); + + s_logger.info("connMap=" + connMap); + Enumeration e = connMap.keys(); + while (e.hasMoreElements()) { + String key; + ConsoleProxyViewer viewer; + + synchronized (connMap) { + key = e.nextElement(); + viewer = connMap.get(key); + } + + long seconds_unused = (System.currentTimeMillis() - viewer.lastUsedTime) / 1000; + + if (seconds_unused > viewerLinger / 2 && viewer.clientStream != null) { + s_logger.info("Pinging client for " + viewer + + " which has not been used for " + seconds_unused + "sec"); + byte[] bs = new byte[2]; + bs[0] = (byte)250; + bs[1] = 3; + viewer.writeToClientStream(bs); + } + + if (seconds_unused < viewerLinger) { + continue; + } + + synchronized (connMap) { + connMap.remove(key); + } + // close the server connection + s_logger.info("Dropping " + viewer + + " which has not been used for " + + seconds_unused + " seconds"); + viewer.dropMe = true; + synchronized (viewer) { + if (viewer.clientStream != null) { + try { + viewer.clientStream.close(); + } catch (IOException ioe) { + // ignored + } + viewer.clientStream = null; + viewer.clientStreamInfo = null; + } + if (viewer.rfb != null) { + viewer.rfb.close(); + } + } + + // report load change for removal of the viewer + ConsoleProxyStatus status = new ConsoleProxyStatus(); + status.setConnections(ConsoleProxy_obsolete.connectionMap); + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + String loadInfo = gson.toJson(status); + + ConsoleProxy_obsolete.reportLoadInfo(loadInfo); + if(s_logger.isDebugEnabled()) + s_logger.debug("Report load change : " + loadInfo); + } + try { + Thread.sleep(30000); + } catch (InterruptedException exp) { + // Ignore + } + } + } + } +} +*/ diff --git a/console-proxy/src/com/cloud/consoleproxy/InputEventType.java b/console-proxy/src/com/cloud/consoleproxy/InputEventType.java new file mode 100644 index 00000000000..1122121e18e --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/InputEventType.java @@ -0,0 +1,42 @@ +package com.cloud.consoleproxy; + +public enum InputEventType { + MOUSE_MOVE(1), + MOUSE_DOWN(2), + MOUSE_UP(3), + KEY_PRESS(4), + KEY_DOWN(5), + KEY_UP(6), + MOUSE_DBLCLICK(8); + + int eventCode; + private InputEventType(int eventCode) { + this.eventCode = eventCode; + } + + public int getEventCode() { + return eventCode; + } + + public static InputEventType fromEventCode(int eventCode) { + switch(eventCode) { + case 1 : + return MOUSE_MOVE; + case 2 : + return MOUSE_DOWN; + case 3 : + return MOUSE_UP; + case 4 : + return KEY_PRESS; + case 5 : + return KEY_DOWN; + case 6 : + return KEY_UP; + case 8 : + return MOUSE_DBLCLICK; + default : + break; + } + throw new IllegalArgumentException("Unsupport event code: " + eventCode); + } +} diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/BufferedImageCanvas.java b/console-proxy/src/com/cloud/consoleproxy/vnc/BufferedImageCanvas.java index 44663557882..3633eb1a1d8 100644 --- a/console-proxy/src/com/cloud/consoleproxy/vnc/BufferedImageCanvas.java +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/BufferedImageCanvas.java @@ -4,6 +4,7 @@ import java.awt.Canvas; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; +import java.awt.Image; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.IOException; @@ -80,6 +81,13 @@ public class BufferedImageCanvas extends Canvas implements FrameBufferCanvas { } } + @Override + public Image getFrameBufferScaledImage(int width, int height) { + if(offlineImage != null) + return offlineImage.getScaledInstance(width, height, Image.SCALE_DEFAULT); + return null; + } + @Override public byte[] getFrameBufferJpeg() { int width = 800; diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/FrameBufferCanvas.java b/console-proxy/src/com/cloud/consoleproxy/vnc/FrameBufferCanvas.java index 912ad088a5f..66b463cd09d 100644 --- a/console-proxy/src/com/cloud/consoleproxy/vnc/FrameBufferCanvas.java +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/FrameBufferCanvas.java @@ -1,10 +1,12 @@ package com.cloud.consoleproxy.vnc; +import java.awt.Image; import java.util.List; import com.cloud.console.TileInfo; public interface FrameBufferCanvas { + Image getFrameBufferScaledImage(int width, int height); public byte[] getFrameBufferJpeg(); public byte[] getTilesMergedJpeg(List tileList, int tileWidth, int tileHeight); } diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/FrameBufferEventListener.java b/console-proxy/src/com/cloud/consoleproxy/vnc/FrameBufferEventListener.java deleted file mode 100644 index 3d17592c689..00000000000 --- a/console-proxy/src/com/cloud/consoleproxy/vnc/FrameBufferEventListener.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.cloud.consoleproxy.vnc; - -public interface FrameBufferEventListener { - void onFramebufferSizeChange(int w, int h); - void onFramebufferUpdate(int x, int y, int w, int h); -} diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/SimpleLogger.java b/console-proxy/src/com/cloud/consoleproxy/vnc/SimpleLogger.java deleted file mode 100644 index 2a6ae4d351e..00000000000 --- a/console-proxy/src/com/cloud/consoleproxy/vnc/SimpleLogger.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.cloud.consoleproxy.vnc; - -public class SimpleLogger { - - public static void log(String message) { - System.out.println(getPrefix(1) + " LOG: " + message); - } - - public static void log(int skipFrames, String message) { - System.out.println(getPrefix(1+skipFrames) + " LOG: " + message); - } - - public static void debug(String message) { - System.out.println(getPrefix(1) + " DEBUG: " + message); - } - - public static void info(String message) { - System.out.println(getPrefix(1) + " INFO: " + message); - } - - public static void warn(String message) { - System.err.println(getPrefix(1) + " WARN: " + message); - } - - public static void error(String message) { - System.err.println(getPrefix(1) + " ERROR: " + message); - } - - private static String getPrefix(int skipFrames) { - StackTraceElement frame; - try { - throw new RuntimeException(); - } catch (Exception e) { - frame = e.getStackTrace()[1+skipFrames]; - } - - return "(" + frame.getFileName() + ":" + frame.getLineNumber() + ") " + frame.getMethodName() + "()"; - } - -} diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/VncClient.java b/console-proxy/src/com/cloud/consoleproxy/vnc/VncClient.java index 93258421612..236703fa5b2 100644 --- a/console-proxy/src/com/cloud/consoleproxy/vnc/VncClient.java +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/VncClient.java @@ -16,7 +16,13 @@ import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESKeySpec; +import com.cloud.console.Logger; +import com.cloud.consoleproxy.ConsoleProxyClientListener; +import com.cloud.consoleproxy.vnc.packet.client.KeyboardEventPacket; +import com.cloud.consoleproxy.vnc.packet.client.MouseEventPacket; + public class VncClient { + private static final Logger s_logger = Logger.getLogger(VncClient.class); private Socket socket; private DataInputStream is; @@ -28,7 +34,7 @@ public class VncClient { private VncServerPacketReceiver receiver; private boolean noUI = false; - private FrameBufferEventListener clientListener = null; + private ConsoleProxyClientListener clientListener = null; public static void main(String args[]) { if (args.length < 3) { @@ -43,26 +49,31 @@ public class VncClient { try { new VncClient(host, Integer.parseInt(port), password, false, null); } catch (NumberFormatException e) { - SimpleLogger.error("Incorrect VNC server port number: " + port + "."); + s_logger.error("Incorrect VNC server port number: " + port + "."); System.exit(1); } catch (UnknownHostException e) { - SimpleLogger.error("Incorrect VNC server host name: " + host + "."); + s_logger.error("Incorrect VNC server host name: " + host + "."); System.exit(1); } catch (IOException e) { - SimpleLogger.error("Cannot communicate with VNC server: " + e.getMessage()); + s_logger.error("Cannot communicate with VNC server: " + e.getMessage()); System.exit(1); } catch (Throwable e) { - SimpleLogger.error("An error happened: " + e.getMessage()); - System.exit(1); + s_logger.error("An error happened: " + e.getMessage()); + System.exit(1); } System.exit(0); } private static void printHelpMessage() { - /* LOG */SimpleLogger.info("Usage: HOST PORT PASSWORD."); + /* LOG */s_logger.info("Usage: HOST PORT PASSWORD."); + } + + public VncClient(ConsoleProxyClientListener clientListener) { + this.noUI = true; + this.clientListener = clientListener; } - public VncClient(String host, int port, String password, boolean noUI, FrameBufferEventListener clientListener) + public VncClient(String host, int port, String password, boolean noUI, ConsoleProxyClientListener clientListener) throws UnknownHostException, IOException { this.noUI = noUI; @@ -70,33 +81,40 @@ public class VncClient { connectTo(host, port, password); } - void shutdown() { - sender.closeConnection(); - receiver.closeConnection(); + public void shutdown() { + if(sender != null) + sender.closeConnection(); + + if(receiver != null) + receiver.closeConnection(); - try { - is.close(); - } catch (Throwable e) { - } + if(is != null) { + try { + is.close(); + } catch (Throwable e) { + } + } - try { - os.close(); - } catch (Throwable e) { - } - - try { - socket.close(); - } catch (Throwable e) { - } + if(os != null) { + try { + os.close(); + } catch (Throwable e) { + } + } + + if(socket != null) { + try { + socket.close(); + } catch (Throwable e) { + } + } + + clientListener.onClientClose(); } public void connectTo(String host, int port, String password) throws UnknownHostException, IOException { - // If port number is too small, then interpret it as display number. - if (port < 100) - port += 5900; - // Connect to server - SimpleLogger.info("Connecting to VNC server " + host + ":" + port + "..."); + s_logger.info("Connecting to VNC server " + host + ":" + port + "..."); this.socket = new Socket(host, port); is = new DataInputStream(socket.getInputStream()); os = new DataOutputStream(socket.getOutputStream()); @@ -322,6 +340,8 @@ public class VncClient { int framebufferWidth = is.readUnsignedShort(); int framebufferHeight = is.readUnsignedShort(); screen.setFramebufferSize(framebufferWidth, framebufferHeight); + if(clientListener != null) + clientListener.onFramebufferSizeChange(framebufferWidth, framebufferHeight); } // Read pixel format @@ -362,4 +382,23 @@ public class VncClient { return null; } + + public void requestUpdate(boolean fullUpdate) { + if(fullUpdate) + sender.requestFullScreenUpdate(); + else + sender.imagePaintedOnScreen(); + } + + public void sendClientKeyboardEvent(int event, int code, int modifiers) { + sender.sendClientPacket(new KeyboardEventPacket(event, code)); + } + + public void sendClientMouseEvent(int event, int x, int y, int code, int modifiers) { + sender.sendClientPacket(new MouseEventPacket(event, x, y)); + } + + public boolean isHostConnected() { + return receiver != null && receiver.isConnectionAlive(); + } } diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/VncClientPacketSender.java b/console-proxy/src/com/cloud/consoleproxy/vnc/VncClientPacketSender.java index c6569deb0d1..bdbfc7ea21e 100644 --- a/console-proxy/src/com/cloud/consoleproxy/vnc/VncClientPacketSender.java +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/VncClientPacketSender.java @@ -40,6 +40,10 @@ public class VncClientPacketSender implements Runnable, PaintNotificationListene sendSetEncodings(); requestFullScreenUpdate(); } + + public void sendClientPacket(ClientPacket packet) { + queue.add(packet); + } @Override public void run() { diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/VncServerPacketReceiver.java b/console-proxy/src/com/cloud/consoleproxy/vnc/VncServerPacketReceiver.java index 1e8fb912dac..64adc96f4ad 100644 --- a/console-proxy/src/com/cloud/consoleproxy/vnc/VncServerPacketReceiver.java +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/VncServerPacketReceiver.java @@ -5,10 +5,13 @@ import java.awt.datatransfer.StringSelection; import java.io.DataInputStream; import java.io.IOException; +import com.cloud.console.Logger; +import com.cloud.consoleproxy.ConsoleProxyClientListener; import com.cloud.consoleproxy.vnc.packet.server.FramebufferUpdatePacket; import com.cloud.consoleproxy.vnc.packet.server.ServerCutText; public class VncServerPacketReceiver implements Runnable { + private static final Logger s_logger = Logger.getLogger(VncServerPacketReceiver.class); private final VncScreenDescription screen; private BufferedImageCanvas canvas; @@ -17,10 +20,10 @@ public class VncServerPacketReceiver implements Runnable { private boolean connectionAlive = true; private VncClient vncConnection; private final FrameBufferUpdateListener fburListener; - private final FrameBufferEventListener clientListener; + private final ConsoleProxyClientListener clientListener; public VncServerPacketReceiver(DataInputStream is, BufferedImageCanvas canvas, VncScreenDescription screen, VncClient vncConnection, - FrameBufferUpdateListener fburListener, FrameBufferEventListener clientListener) { + FrameBufferUpdateListener fburListener, ConsoleProxyClientListener clientListener) { this.screen = screen; this.canvas = canvas; this.is = is; @@ -66,9 +69,9 @@ public class VncServerPacketReceiver implements Runnable { default: throw new RuntimeException("Unknown server packet type: " + messageType + "."); } - } } catch (Throwable e) { + if (connectionAlive) { closeConnection(); vncConnection.shutdown(); @@ -79,6 +82,10 @@ public class VncServerPacketReceiver implements Runnable { public void closeConnection() { connectionAlive = false; } + + public boolean isConnectionAlive() { + return connectionAlive; + } /** * Handle server bell packet. @@ -95,6 +102,6 @@ public class VncServerPacketReceiver implements Runnable { StringSelection contents = new StringSelection(clipboardContent.getContent()); Toolkit.getDefaultToolkit().getSystemClipboard().setContents(contents, null); - SimpleLogger.info("Server clipboard buffer: "+clipboardContent.getContent()); + s_logger.info("Server clipboard buffer: "+clipboardContent.getContent()); } } diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/packet/server/FramebufferUpdatePacket.java b/console-proxy/src/com/cloud/consoleproxy/vnc/packet/server/FramebufferUpdatePacket.java index b0ed4fdff5e..c213047c2b9 100644 --- a/console-proxy/src/com/cloud/consoleproxy/vnc/packet/server/FramebufferUpdatePacket.java +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/packet/server/FramebufferUpdatePacket.java @@ -3,9 +3,9 @@ package com.cloud.consoleproxy.vnc.packet.server; import java.io.DataInputStream; import java.io.IOException; +import com.cloud.consoleproxy.ConsoleProxyClientListener; import com.cloud.consoleproxy.vnc.BufferedImageCanvas; import com.cloud.consoleproxy.vnc.RfbConstants; -import com.cloud.consoleproxy.vnc.FrameBufferEventListener; import com.cloud.consoleproxy.vnc.VncScreenDescription; import com.cloud.consoleproxy.vnc.packet.server.CopyRect; import com.cloud.consoleproxy.vnc.packet.server.RawRect; @@ -15,10 +15,10 @@ public class FramebufferUpdatePacket { private final VncScreenDescription screen; private final BufferedImageCanvas canvas; - private final FrameBufferEventListener clientListener; + private final ConsoleProxyClientListener clientListener; public FramebufferUpdatePacket(BufferedImageCanvas canvas, VncScreenDescription screen, DataInputStream is, - FrameBufferEventListener clientListener) throws IOException { + ConsoleProxyClientListener clientListener) throws IOException { this.screen = screen; this.canvas = canvas; diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/packet/server/ServerCutText.java b/console-proxy/src/com/cloud/consoleproxy/vnc/packet/server/ServerCutText.java index 473f87509ad..6eed8ba1a0b 100644 --- a/console-proxy/src/com/cloud/consoleproxy/vnc/packet/server/ServerCutText.java +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/packet/server/ServerCutText.java @@ -3,10 +3,11 @@ package com.cloud.consoleproxy.vnc.packet.server; import java.io.DataInputStream; import java.io.IOException; +import com.cloud.console.Logger; import com.cloud.consoleproxy.vnc.RfbConstants; -import com.cloud.consoleproxy.vnc.SimpleLogger; public class ServerCutText { + private static final Logger s_logger = Logger.getLogger(ServerCutText.class); private String content; @@ -26,7 +27,7 @@ public class ServerCutText { content = new String(buf, RfbConstants.CHARSET); - /* LOG */SimpleLogger.log("Clippboard content: " + content); + /* LOG */s_logger.info("Clippboard content: " + content); } }