mirror of
https://github.com/apache/cloudstack.git
synced 2025-11-03 04:12:31 +01:00
690 lines
23 KiB
Java
690 lines
23 KiB
Java
/**
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
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<String, ConsoleProxyViewer> connectionMap = new Hashtable<String, ConsoleProxyViewer>();
|
|
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<String, ConsoleProxyViewer> 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<String, ConsoleProxyViewer> connMap;
|
|
long lastLogScan = 0L;
|
|
|
|
public ViewerGCThread(Hashtable<String, ConsoleProxyViewer> 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<String> 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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|