Remove @author tag from non third-party source files in console-proxy folder and repalce tab indention with 4 spaces

This commit is contained in:
Mice Xia 2012-08-13 15:26:03 +08:00
parent 89015cbf3c
commit b25122bd4d
8 changed files with 1567 additions and 1576 deletions

View File

@ -14,487 +14,486 @@
// KIND, either express or implied. See the License for the // KIND, either express or implied. See the License for the
// specific language governing permissions and limitations // specific language governing permissions and limitations
// under the License. // under the License.
package com.cloud.consoleproxy; package com.cloud.consoleproxy;
import java.io.File; import java.io.File;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import org.apache.axis.encoding.Base64;
import org.apache.log4j.xml.DOMConfigurator;
import com.cloud.consoleproxy.util.Logger;
import com.google.gson.Gson;
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
*/
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<String, ConsoleProxyClient> connectionMap = new Hashtable<String, ConsoleProxyClient>();
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;
static String encryptorPassword = genDefaultEncryptorPassword();
private static String genDefaultEncryptorPassword() {
try {
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
byte[] randomBytes = new byte[16];
random.nextBytes(randomBytes);
return Base64.encode(randomBytes);
} catch (NoSuchAlgorithmException e) {
s_logger.error("Unexpected exception ", e);
assert(false);
}
return "Dummy";
}
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 ConsoleProxyAuthenticationResult authenticateConsoleAccess(ConsoleProxyClientParam param, boolean reauthentication) {
ConsoleProxyAuthenticationResult authResult = new ConsoleProxyAuthenticationResult(); import org.apache.axis.encoding.Base64;
authResult.setSuccess(true); import org.apache.log4j.xml.DOMConfigurator;
authResult.setReauthentication(reauthentication);
authResult.setHost(param.getClientHostAddress()); import com.cloud.consoleproxy.util.Logger;
authResult.setPort(param.getClientHostPort()); import com.google.gson.Gson;
import com.sun.net.httpserver.HttpServer;
if(standaloneStart) {
return authResult; /**
} *
* ConsoleProxy, singleton class that manages overall activities in console proxy process. To make legacy code work, we still
if(authMethod != null) { */
Object result; public class ConsoleProxy {
try { private static final Logger s_logger = Logger.getLogger(ConsoleProxy.class);
result = authMethod.invoke(ConsoleProxy.context,
param.getClientHostAddress(), public static final int KEYBOARD_RAW = 0;
String.valueOf(param.getClientHostPort()), public static final int KEYBOARD_COOKED = 1;
param.getClientTag(),
param.getClientHostPassword(), public static int VIEWER_LINGER_SECONDS = 180;
param.getTicket(),
new Boolean(reauthentication)); public static Object context;
} catch (IllegalAccessException e) {
s_logger.error("Unable to invoke authenticateConsoleAccess due to IllegalAccessException" + " for vm: " + param.getClientTag(), e); // this has become more ugly, to store keystore info passed from management server (we now use management server managed keystore to support
authResult.setSuccess(false); // dynamically changing to customer supplied certificate)
return authResult; public static byte[] ksBits;
} catch (InvocationTargetException e) { public static String ksPassword;
s_logger.error("Unable to invoke authenticateConsoleAccess due to InvocationTargetException " + " for vm: " + param.getClientTag(), e);
authResult.setSuccess(false); public static Method authMethod;
return authResult; public static Method reportMethod;
} public static Method ensureRouteMethod;
if(result != null && result instanceof String) { static Hashtable<String, ConsoleProxyClient> connectionMap = new Hashtable<String, ConsoleProxyClient>();
authResult = new Gson().fromJson((String)result, ConsoleProxyAuthenticationResult.class); static int httpListenPort = 80;
} else { static int httpCmdListenPort = 8001;
s_logger.error("Invalid authentication return object " + result + " for vm: " + param.getClientTag() + ", decline the access"); static int reconnectMaxRetry = 5;
authResult.setSuccess(false); static int readTimeoutSeconds = 90;
} static int keyboardType = KEYBOARD_RAW;
} else { static String factoryClzName;
s_logger.warn("Private channel towards management server is not setup. Switch to offline mode and allow access to vm: " + param.getClientTag()); static boolean standaloneStart = false;
}
static String encryptorPassword = genDefaultEncryptorPassword();
return authResult;
} private static String genDefaultEncryptorPassword() {
try {
public static void reportLoadInfo(String gsonLoadInfo) { SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
if(reportMethod != null) {
try { byte[] randomBytes = new byte[16];
reportMethod.invoke(ConsoleProxy.context, gsonLoadInfo); random.nextBytes(randomBytes);
} catch (IllegalAccessException e) { return Base64.encode(randomBytes);
s_logger.error("Unable to invoke reportLoadInfo due to " + e.getMessage()); } catch (NoSuchAlgorithmException e) {
} catch (InvocationTargetException e) { s_logger.error("Unexpected exception ", e);
s_logger.error("Unable to invoke reportLoadInfo due to " + e.getMessage()); assert(false);
} }
} else {
s_logger.warn("Private channel towards management server is not setup. Switch to offline mode and ignore load report"); return "Dummy";
} }
}
private static void configLog4j() {
public static void ensureRoute(String address) { URL configUrl = System.class.getResource("/conf/log4j-cloud.xml");
if(ensureRouteMethod != null) { if(configUrl == null)
try { configUrl = ClassLoader.getSystemResource("log4j-cloud.xml");
ensureRouteMethod.invoke(ConsoleProxy.context, address);
} catch (IllegalAccessException e) { if(configUrl == null)
s_logger.error("Unable to invoke ensureRoute due to " + e.getMessage()); configUrl = ClassLoader.getSystemResource("conf/log4j-cloud.xml");
} catch (InvocationTargetException e) {
s_logger.error("Unable to invoke ensureRoute due to " + e.getMessage()); if(configUrl != null) {
} try {
} else { System.out.println("Configure log4j using " + configUrl.toURI().toString());
s_logger.warn("Unable to find ensureRoute method, console proxy agent is not up to date"); } catch (URISyntaxException e1) {
} e1.printStackTrace();
} }
public static void startWithContext(Properties conf, Object context, byte[] ksBits, String ksPassword) { try {
s_logger.info("Start console proxy with context"); File file = new File(configUrl.toURI());
if(conf != null) {
for(Object key : conf.keySet()) { System.out.println("Log4j configuration from : " + file.getAbsolutePath());
s_logger.info("Context property " + (String)key + ": " + conf.getProperty((String)key)); DOMConfigurator.configureAndWatch(file.getAbsolutePath(), 10000);
} } catch (URISyntaxException e) {
} System.out.println("Unable to convert log4j configuration Url to URI");
}
configLog4j(); // DOMConfigurator.configure(configUrl);
Logger.setFactory(new ConsoleProxyLoggerFactory()); } else {
System.out.println("Configure log4j with default properties");
// Using reflection to setup private/secure communication channel towards management server }
ConsoleProxy.context = context; }
ConsoleProxy.ksBits = ksBits;
ConsoleProxy.ksPassword = ksPassword; private static void configProxy(Properties conf) {
try { s_logger.info("Configure console proxy...");
Class<?> contextClazz = Class.forName("com.cloud.agent.resource.consoleproxy.ConsoleProxyResource"); for(Object key : conf.keySet()) {
authMethod = contextClazz.getDeclaredMethod("authenticateConsoleAccess", String.class, String.class, String.class, String.class, String.class, Boolean.class); s_logger.info("Property " + (String)key + ": " + conf.getProperty((String)key));
reportMethod = contextClazz.getDeclaredMethod("reportLoadInfo", String.class); }
ensureRouteMethod = contextClazz.getDeclaredMethod("ensureRoute", String.class);
} catch (SecurityException e) { String s = conf.getProperty("consoleproxy.httpListenPort");
s_logger.error("Unable to setup private channel due to SecurityException", e); if (s!=null) {
} catch (NoSuchMethodException e) { httpListenPort = Integer.parseInt(s);
s_logger.error("Unable to setup private channel due to NoSuchMethodException", e); s_logger.info("Setting httpListenPort=" + s);
} catch (IllegalArgumentException e) { }
s_logger.error("Unable to setup private channel due to IllegalArgumentException", e);
} catch(ClassNotFoundException e) { s = conf.getProperty("premium");
s_logger.error("Unable to setup private channel due to ClassNotFoundException", e); if(s != null && s.equalsIgnoreCase("true")) {
} s_logger.info("Premium setting will override settings from consoleproxy.properties, listen at port 443");
httpListenPort = 443;
// merge properties from conf file factoryClzName = "com.cloud.consoleproxy.ConsoleProxySecureServerFactoryImpl";
InputStream confs = ConsoleProxy.class.getResourceAsStream("/conf/consoleproxy.properties"); } else {
Properties props = new Properties(); factoryClzName = ConsoleProxyBaseServerFactoryImpl.class.getName();
if (confs == null) { }
s_logger.info("Can't load consoleproxy.properties from classpath, will use default configuration");
} else { s = conf.getProperty("consoleproxy.httpCmdListenPort");
try { if (s!=null) {
props.load(confs); httpCmdListenPort = Integer.parseInt(s);
s_logger.info("Setting httpCmdListenPort=" + s);
for(Object key : props.keySet()) { }
// give properties passed via context high priority, treat properties from consoleproxy.properties
// as default values s = conf.getProperty("consoleproxy.reconnectMaxRetry");
if(conf.get(key) == null) if (s!=null) {
conf.put(key, props.get(key)); reconnectMaxRetry = Integer.parseInt(s);
} s_logger.info("Setting reconnectMaxRetry=" + reconnectMaxRetry);
} catch (Exception e) { }
s_logger.error(e.toString(), e);
} s = conf.getProperty("consoleproxy.readTimeoutSeconds");
} if (s!=null) {
readTimeoutSeconds = Integer.parseInt(s);
start(conf); s_logger.info("Setting readTimeoutSeconds=" + readTimeoutSeconds);
} }
}
public static void start(Properties conf) {
System.setProperty("java.awt.headless", "true"); public static ConsoleProxyServerFactory getHttpServerFactory() {
try {
configProxy(conf); Class<?> clz = Class.forName(factoryClzName);
try {
ConsoleProxyServerFactory factory = getHttpServerFactory(); ConsoleProxyServerFactory factory = (ConsoleProxyServerFactory)clz.newInstance();
if(factory == null) { factory.init(ConsoleProxy.ksBits, ConsoleProxy.ksPassword);
s_logger.error("Unable to load console proxy server factory"); return factory;
System.exit(1); } catch (InstantiationException e) {
} s_logger.error(e.getMessage(), e);
return null;
if(httpListenPort != 0) { } catch (IllegalAccessException e) {
startupHttpMain(); s_logger.error(e.getMessage(), e);
} else { return null;
s_logger.error("A valid HTTP server port is required to be specified, please check your consoleproxy.httpListenPort settings"); }
System.exit(1); } catch (ClassNotFoundException e) {
} s_logger.warn("Unable to find http server factory class: " + factoryClzName);
return new ConsoleProxyBaseServerFactoryImpl();
if(httpCmdListenPort > 0) { }
startupHttpCmdPort(); }
} else {
s_logger.info("HTTP command port is disabled"); public static ConsoleProxyAuthenticationResult authenticateConsoleAccess(ConsoleProxyClientParam param, boolean reauthentication) {
}
ConsoleProxyAuthenticationResult authResult = new ConsoleProxyAuthenticationResult();
ConsoleProxyGCThread cthread = new ConsoleProxyGCThread(connectionMap); authResult.setSuccess(true);
cthread.setName("Console Proxy GC Thread"); authResult.setReauthentication(reauthentication);
cthread.start(); authResult.setHost(param.getClientHostAddress());
} authResult.setPort(param.getClientHostPort());
private static void startupHttpMain() { if(standaloneStart) {
try { return authResult;
ConsoleProxyServerFactory factory = getHttpServerFactory(); }
if(factory == null) {
s_logger.error("Unable to load HTTP server factory"); if(authMethod != null) {
System.exit(1); Object result;
} try {
result = authMethod.invoke(ConsoleProxy.context,
HttpServer server = factory.createHttpServerInstance(httpListenPort); param.getClientHostAddress(),
server.createContext("/getscreen", new ConsoleProxyThumbnailHandler()); String.valueOf(param.getClientHostPort()),
server.createContext("/resource/", new ConsoleProxyResourceHandler()); param.getClientTag(),
server.createContext("/ajax", new ConsoleProxyAjaxHandler()); param.getClientHostPassword(),
server.createContext("/ajaximg", new ConsoleProxyAjaxImageHandler()); param.getTicket(),
server.setExecutor(new ThreadExecutor()); // creates a default executor new Boolean(reauthentication));
server.start(); } catch (IllegalAccessException e) {
} catch(Exception e) { s_logger.error("Unable to invoke authenticateConsoleAccess due to IllegalAccessException" + " for vm: " + param.getClientTag(), e);
s_logger.error(e.getMessage(), e); authResult.setSuccess(false);
System.exit(1); return authResult;
} } catch (InvocationTargetException e) {
} s_logger.error("Unable to invoke authenticateConsoleAccess due to InvocationTargetException " + " for vm: " + param.getClientTag(), e);
authResult.setSuccess(false);
private static void startupHttpCmdPort() { return authResult;
try { }
s_logger.info("Listening for HTTP CMDs on port " + httpCmdListenPort);
HttpServer cmdServer = HttpServer.create(new InetSocketAddress(httpCmdListenPort), 2); if(result != null && result instanceof String) {
cmdServer.createContext("/cmd", new ConsoleProxyCmdHandler()); authResult = new Gson().fromJson((String)result, ConsoleProxyAuthenticationResult.class);
cmdServer.setExecutor(new ThreadExecutor()); // creates a default executor } else {
cmdServer.start(); s_logger.error("Invalid authentication return object " + result + " for vm: " + param.getClientTag() + ", decline the access");
} catch(Exception e) { authResult.setSuccess(false);
s_logger.error(e.getMessage(), e); }
System.exit(1); } else {
} s_logger.warn("Private channel towards management server is not setup. Switch to offline mode and allow access to vm: " + param.getClientTag());
} }
public static void main(String[] argv) { return authResult;
standaloneStart = true; }
configLog4j();
Logger.setFactory(new ConsoleProxyLoggerFactory()); public static void reportLoadInfo(String gsonLoadInfo) {
if(reportMethod != null) {
InputStream confs = ConsoleProxy.class.getResourceAsStream("/conf/consoleproxy.properties"); try {
Properties conf = new Properties(); reportMethod.invoke(ConsoleProxy.context, gsonLoadInfo);
if (confs == null) { } catch (IllegalAccessException e) {
s_logger.info("Can't load consoleproxy.properties from classpath, will use default configuration"); s_logger.error("Unable to invoke reportLoadInfo due to " + e.getMessage());
} else { } catch (InvocationTargetException e) {
try { s_logger.error("Unable to invoke reportLoadInfo due to " + e.getMessage());
conf.load(confs); }
} catch (Exception e) { } else {
s_logger.error(e.toString(), e); s_logger.warn("Private channel towards management server is not setup. Switch to offline mode and ignore load report");
} }
} }
start(conf);
} public static void ensureRoute(String address) {
if(ensureRouteMethod != null) {
public static ConsoleProxyClient getVncViewer(ConsoleProxyClientParam param) throws Exception { try {
ConsoleProxyClient viewer = null; ensureRouteMethod.invoke(ConsoleProxy.context, address);
} catch (IllegalAccessException e) {
boolean reportLoadChange = false; s_logger.error("Unable to invoke ensureRoute due to " + e.getMessage());
String clientKey = param.getClientMapKey(); } catch (InvocationTargetException e) {
synchronized (connectionMap) { s_logger.error("Unable to invoke ensureRoute due to " + e.getMessage());
viewer = connectionMap.get(clientKey); }
if (viewer == null) { } else {
viewer = new ConsoleProxyVncClient(); s_logger.warn("Unable to find ensureRoute method, console proxy agent is not up to date");
viewer.initClient(param); }
connectionMap.put(clientKey, viewer); }
s_logger.info("Added viewer object " + viewer);
public static void startWithContext(Properties conf, Object context, byte[] ksBits, String ksPassword) {
reportLoadChange = true; s_logger.info("Start console proxy with context");
} else if (!viewer.isFrontEndAlive()) { if(conf != null) {
s_logger.info("The rfb thread died, reinitializing the viewer " + viewer); for(Object key : conf.keySet()) {
viewer.initClient(param); s_logger.info("Context property " + (String)key + ": " + conf.getProperty((String)key));
} else if (!param.getClientHostPassword().equals(viewer.getClientHostPassword())) { }
s_logger.warn("Bad sid detected(VNC port may be reused). sid in session: " + viewer.getClientHostPassword() }
+ ", sid in request: " + param.getClientHostPassword());
viewer.initClient(param); configLog4j();
} Logger.setFactory(new ConsoleProxyLoggerFactory());
}
// Using reflection to setup private/secure communication channel towards management server
if(reportLoadChange) { ConsoleProxy.context = context;
ConsoleProxyClientStatsCollector statsCollector = getStatsCollector(); ConsoleProxy.ksBits = ksBits;
String loadInfo = statsCollector.getStatsReport(); ConsoleProxy.ksPassword = ksPassword;
reportLoadInfo(loadInfo); try {
if(s_logger.isDebugEnabled()) Class<?> contextClazz = Class.forName("com.cloud.agent.resource.consoleproxy.ConsoleProxyResource");
s_logger.debug("Report load change : " + loadInfo); authMethod = contextClazz.getDeclaredMethod("authenticateConsoleAccess", String.class, String.class, String.class, String.class, String.class, Boolean.class);
} reportMethod = contextClazz.getDeclaredMethod("reportLoadInfo", String.class);
ensureRouteMethod = contextClazz.getDeclaredMethod("ensureRoute", String.class);
return viewer; } catch (SecurityException e) {
} s_logger.error("Unable to setup private channel due to SecurityException", e);
} catch (NoSuchMethodException e) {
public static ConsoleProxyClient getAjaxVncViewer(ConsoleProxyClientParam param, String ajaxSession) throws Exception { s_logger.error("Unable to setup private channel due to NoSuchMethodException", e);
} catch (IllegalArgumentException e) {
boolean reportLoadChange = false; s_logger.error("Unable to setup private channel due to IllegalArgumentException", e);
String clientKey = param.getClientMapKey(); } catch(ClassNotFoundException e) {
synchronized (connectionMap) { s_logger.error("Unable to setup private channel due to ClassNotFoundException", e);
ConsoleProxyClient viewer = connectionMap.get(clientKey); }
if (viewer == null) {
viewer = new ConsoleProxyVncClient(); // merge properties from conf file
viewer.initClient(param); InputStream confs = ConsoleProxy.class.getResourceAsStream("/conf/consoleproxy.properties");
Properties props = new Properties();
connectionMap.put(clientKey, viewer); if (confs == null) {
s_logger.info("Added viewer object " + viewer); s_logger.info("Can't load consoleproxy.properties from classpath, will use default configuration");
reportLoadChange = true; } else {
} else if (!viewer.isFrontEndAlive()) { try {
s_logger.info("The rfb thread died, reinitializing the viewer " + viewer); props.load(confs);
viewer.initClient(param);
} else if (!param.getClientHostPassword().equals(viewer.getClientHostPassword())) { for(Object key : props.keySet()) {
s_logger.warn("Bad sid detected(VNC port may be reused). sid in session: " // give properties passed via context high priority, treat properties from consoleproxy.properties
+ viewer.getClientHostPassword() + ", sid in request: " + param.getClientHostPassword()); // as default values
viewer.initClient(param); if(conf.get(key) == null)
} else { conf.put(key, props.get(key));
if(ajaxSession == null || ajaxSession.isEmpty()) }
authenticationExternally(param); } catch (Exception e) {
} s_logger.error(e.toString(), e);
}
if(reportLoadChange) { }
ConsoleProxyClientStatsCollector statsCollector = getStatsCollector();
String loadInfo = statsCollector.getStatsReport(); start(conf);
reportLoadInfo(loadInfo); }
if(s_logger.isDebugEnabled())
s_logger.debug("Report load change : " + loadInfo); public static void start(Properties conf) {
} System.setProperty("java.awt.headless", "true");
return viewer;
} configProxy(conf);
}
ConsoleProxyServerFactory factory = getHttpServerFactory();
public static void removeViewer(ConsoleProxyClient viewer) { if(factory == null) {
synchronized (connectionMap) { s_logger.error("Unable to load console proxy server factory");
for(Map.Entry<String, ConsoleProxyClient> entry : connectionMap.entrySet()) { System.exit(1);
if(entry.getValue() == viewer) { }
connectionMap.remove(entry.getKey());
return; 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);
}
public static ConsoleProxyClientStatsCollector getStatsCollector() {
return new ConsoleProxyClientStatsCollector(connectionMap); if(httpCmdListenPort > 0) {
} startupHttpCmdPort();
} else {
public static void authenticationExternally(ConsoleProxyClientParam param) throws AuthenticationException { s_logger.info("HTTP command port is disabled");
ConsoleProxyAuthenticationResult authResult = authenticateConsoleAccess(param, false); }
if(authResult == null || !authResult.isSuccess()) { ConsoleProxyGCThread cthread = new ConsoleProxyGCThread(connectionMap);
s_logger.warn("External authenticator failed authencation request for vm " + param.getClientTag() + " with sid " + param.getClientHostPassword()); cthread.setName("Console Proxy GC Thread");
cthread.start();
throw new AuthenticationException("External authenticator failed request for vm " + param.getClientTag() + " with sid " + param.getClientHostPassword()); }
}
} private static void startupHttpMain() {
try {
public static ConsoleProxyAuthenticationResult reAuthenticationExternally(ConsoleProxyClientParam param) { ConsoleProxyServerFactory factory = getHttpServerFactory();
return authenticateConsoleAccess(param, true); if(factory == null) {
} s_logger.error("Unable to load HTTP server factory");
System.exit(1);
public static String getEncryptorPassword() { }
return encryptorPassword;
} HttpServer server = factory.createHttpServerInstance(httpListenPort);
server.createContext("/getscreen", new ConsoleProxyThumbnailHandler());
public static void setEncryptorPassword(String password) { server.createContext("/resource/", new ConsoleProxyResourceHandler());
encryptorPassword = password; server.createContext("/ajax", new ConsoleProxyAjaxHandler());
} server.createContext("/ajaximg", new ConsoleProxyAjaxImageHandler());
server.setExecutor(new ThreadExecutor()); // creates a default executor
static class ThreadExecutor implements Executor { server.start();
public void execute(Runnable r) { } catch(Exception e) {
new Thread(r).start(); 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(ConsoleProxyClientParam param) throws Exception {
ConsoleProxyClient viewer = null;
boolean reportLoadChange = false;
String clientKey = param.getClientMapKey();
synchronized (connectionMap) {
viewer = connectionMap.get(clientKey);
if (viewer == null) {
viewer = new ConsoleProxyVncClient();
viewer.initClient(param);
connectionMap.put(clientKey, 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(param);
} else if (!param.getClientHostPassword().equals(viewer.getClientHostPassword())) {
s_logger.warn("Bad sid detected(VNC port may be reused). sid in session: " + viewer.getClientHostPassword()
+ ", sid in request: " + param.getClientHostPassword());
viewer.initClient(param);
}
}
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(ConsoleProxyClientParam param, String ajaxSession) throws Exception {
boolean reportLoadChange = false;
String clientKey = param.getClientMapKey();
synchronized (connectionMap) {
ConsoleProxyClient viewer = connectionMap.get(clientKey);
if (viewer == null) {
viewer = new ConsoleProxyVncClient();
viewer.initClient(param);
connectionMap.put(clientKey, 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(param);
} else if (!param.getClientHostPassword().equals(viewer.getClientHostPassword())) {
s_logger.warn("Bad sid detected(VNC port may be reused). sid in session: "
+ viewer.getClientHostPassword() + ", sid in request: " + param.getClientHostPassword());
viewer.initClient(param);
} else {
if(ajaxSession == null || ajaxSession.isEmpty())
authenticationExternally(param);
}
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<String, ConsoleProxyClient> entry : connectionMap.entrySet()) {
if(entry.getValue() == viewer) {
connectionMap.remove(entry.getKey());
return;
}
}
}
}
public static ConsoleProxyClientStatsCollector getStatsCollector() {
return new ConsoleProxyClientStatsCollector(connectionMap);
}
public static void authenticationExternally(ConsoleProxyClientParam param) throws AuthenticationException {
ConsoleProxyAuthenticationResult authResult = authenticateConsoleAccess(param, false);
if(authResult == null || !authResult.isSuccess()) {
s_logger.warn("External authenticator failed authencation request for vm " + param.getClientTag() + " with sid " + param.getClientHostPassword());
throw new AuthenticationException("External authenticator failed request for vm " + param.getClientTag() + " with sid " + param.getClientHostPassword());
}
}
public static ConsoleProxyAuthenticationResult reAuthenticationExternally(ConsoleProxyClientParam param) {
return authenticateConsoleAccess(param, true);
}
public static String getEncryptorPassword() {
return encryptorPassword;
}
public static void setEncryptorPassword(String password) {
encryptorPassword = password;
}
static class ThreadExecutor implements Executor {
public void execute(Runnable r) {
new Thread(r).start();
}
}
}

View File

@ -14,57 +14,56 @@
// KIND, either express or implied. See the License for the // KIND, either express or implied. See the License for the
// specific language governing permissions and limitations // specific language governing permissions and limitations
// under the License. // under the License.
package com.cloud.consoleproxy; package com.cloud.consoleproxy;
import java.awt.Image; import java.awt.Image;
import java.util.List; import java.util.List;
/** /**
* @author Kelven Yang * ConsoleProxyClient defines an standard interface that a console client should implement,
* ConsoleProxyClient defines an standard interface that a console client should implement, *
* * ConsoleProxyClient maintains a session towards the target host, it glues the session
* ConsoleProxyClient maintains a session towards the target host, it glues the session * to a AJAX front-end viewer
* to a AJAX front-end viewer */
*/ public interface ConsoleProxyClient {
public interface ConsoleProxyClient { int getClientId();
int getClientId();
//
// // Quick status
// Quick status //
// boolean isHostConnected();
boolean isHostConnected(); boolean isFrontEndAlive();
boolean isFrontEndAlive();
//
// // AJAX viewer
// AJAX viewer //
// long getAjaxSessionId();
long getAjaxSessionId(); AjaxFIFOImageCache getAjaxImageCache();
AjaxFIFOImageCache getAjaxImageCache(); Image getClientScaledImage(int width, int height); // client thumbnail support
Image getClientScaledImage(int width, int height); // client thumbnail support
String onAjaxClientStart(String title, List<String> languages, String guest);
String onAjaxClientStart(String title, List<String> languages, String guest); String onAjaxClientUpdate();
String onAjaxClientUpdate(); String onAjaxClientKickoff();
String onAjaxClientKickoff();
//
// // Input handling
// Input handling //
// void sendClientRawKeyboardEvent(InputEventType event, int code, int modifiers);
void sendClientRawKeyboardEvent(InputEventType event, int code, int modifiers); void sendClientMouseEvent(InputEventType event, int x, int y, int code, int modifiers);
void sendClientMouseEvent(InputEventType event, int x, int y, int code, int modifiers);
//
// // Info/Stats
// Info/Stats //
// long getClientCreateTime();
long getClientCreateTime(); long getClientLastFrontEndActivityTime();
long getClientLastFrontEndActivityTime(); String getClientHostAddress();
String getClientHostAddress(); int getClientHostPort();
int getClientHostPort(); String getClientHostPassword();
String getClientHostPassword(); String getClientTag();
String getClientTag();
//
// // Setup/house-keeping
// Setup/house-keeping //
// void initClient(ConsoleProxyClientParam param);
void initClient(ConsoleProxyClientParam param); void closeClient();
void closeClient(); }
}

View File

@ -14,445 +14,443 @@
// KIND, either express or implied. See the License for the // KIND, either express or implied. See the License for the
// specific language governing permissions and limitations // specific language governing permissions and limitations
// under the License. // under the License.
package com.cloud.consoleproxy; package com.cloud.consoleproxy;
import java.awt.Image;
import java.awt.Rectangle;
import java.util.List;
import org.apache.log4j.Logger;
import com.cloud.consoleproxy.util.TileInfo;
import com.cloud.consoleproxy.util.TileTracker;
import com.cloud.consoleproxy.vnc.FrameBufferCanvas;
/**
*
* @author Kelven Yang
*
* an instance of specialized console protocol implementation, such as VNC or RDP
*
* It mainly implements the features needed by front-end AJAX viewer
*
*/
public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, ConsoleProxyClientListener {
private static final Logger s_logger = Logger.getLogger(ConsoleProxyClientBase.class);
private static int s_nextClientId = 0;
protected int clientId = getNextClientId();
protected long ajaxSessionId = 0;
protected boolean dirtyFlag = false;
protected Object tileDirtyEvent = new Object();
protected TileTracker tracker;
protected AjaxFIFOImageCache ajaxImageCache = new AjaxFIFOImageCache(2);
protected ConsoleProxyClientParam clientParam; import java.awt.Image;
protected String clientToken; import java.awt.Rectangle;
import java.util.List;
protected long createTime = System.currentTimeMillis();
protected long lastFrontEndActivityTime = System.currentTimeMillis();
protected boolean framebufferResized = false;
protected int resizedFramebufferWidth;
protected int resizedFramebufferHeight;
public ConsoleProxyClientBase() {
tracker = new TileTracker();
tracker.initTracking(64, 64, 800, 600);
}
//
// interface ConsoleProxyClient
//
@Override
public int getClientId() {
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;
}
@Override
public long getClientLastFrontEndActivityTime() {
return lastFrontEndActivityTime;
}
@Override
public String getClientHostAddress() {
return clientParam.getClientHostAddress();
}
@Override
public int getClientHostPort() {
return clientParam.getClientHostPort();
}
@Override
public String getClientHostPassword() {
return clientParam.getClientHostPassword();
}
@Override
public String getClientTag() {
if(clientParam.getClientTag() != null)
return clientParam.getClientTag();
return "";
}
@Override
public abstract void initClient(ConsoleProxyClientParam param);
@Override
public abstract void closeClient();
//
// interface FrameBufferEventListener
//
@Override
public void onFramebufferSizeChange(int w, int h) {
tracker.resize(w, h);
synchronized(this) {
framebufferResized = true;
resizedFramebufferWidth = w;
resizedFramebufferHeight = h;
}
signalTileDirtyEvent();
}
@Override
public void onFramebufferUpdate(int x, int y, int w, int h) {
if(s_logger.isTraceEnabled())
s_logger.trace("Frame buffer update {" + x + "," + y + "," + w + "," + h + "}");
tracker.invalidate(new Rectangle(x, y, w, h));
signalTileDirtyEvent();
}
//
// AJAX Image manipulation
//
public byte[] getFrameBufferJpeg() {
FrameBufferCanvas canvas = getFrameBufferCavas();
if(canvas != null)
return canvas.getFrameBufferJpeg();
return null;
}
public byte[] getTilesMergedJpeg(List<TileInfo> tileList, int tileWidth, int tileHeight) {
FrameBufferCanvas canvas = getFrameBufferCavas();
if(canvas != null)
return canvas.getTilesMergedJpeg(tileList, tileWidth, tileHeight);
return null;
}
private String prepareAjaxImage(List<TileInfo> tiles, boolean init) {
byte[] imgBits;
if(init)
imgBits = getFrameBufferJpeg();
else
imgBits = getTilesMergedJpeg(tiles, tracker.getTileWidth(), tracker.getTileHeight());
if(imgBits == null) {
s_logger.warn("Unable to generate jpeg image");
} else {
if(s_logger.isTraceEnabled())
s_logger.trace("Generated jpeg image size: " + imgBits.length);
}
int key = ajaxImageCache.putImage(imgBits);
StringBuffer sb = new StringBuffer();
sb.append("/ajaximg?token=").append(clientToken);
sb.append("&key=").append(key);
sb.append("&ts=").append(System.currentTimeMillis());
return sb.toString();
}
private String prepareAjaxSession(boolean init) {
if(init) {
synchronized(this) {
ajaxSessionId++;
}
}
StringBuffer sb = new StringBuffer();
sb.append("/ajax?token=").append(clientToken).append("&sess=").append(ajaxSessionId);
return sb.toString();
}
@Override
public String onAjaxClientKickoff() {
return "onKickoff();";
}
private boolean waitForViewerReady() {
long startTick = System.currentTimeMillis();
while(System.currentTimeMillis() - startTick < 5000) {
if(getFrameBufferCavas() != null)
return true;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
return false;
}
private String onAjaxClientConnectFailed() {
return "<html><head></head><body><div id=\"main_panel\" tabindex=\"1\"><p>" +
"Unable to start console session as connection is refused by the machine you are accessing" +
"</p></div></body></html>";
}
@Override
public String onAjaxClientStart(String title, List<String> languages, String guest) {
updateFrontEndActivityTime();
if(!waitForViewerReady())
return onAjaxClientConnectFailed();
synchronized(this) {
ajaxSessionId++;
framebufferResized = false;
}
int tileWidth = tracker.getTileWidth();
int tileHeight = tracker.getTileHeight();
int width = tracker.getTrackWidth();
int height = tracker.getTrackHeight();
if(s_logger.isTraceEnabled())
s_logger.trace("Ajax client start, frame buffer w: " + width + ", " + height);
int retry = 0;
tracker.initCoverageTest();
while(!tracker.hasFullCoverage() && retry < 10) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
retry++;
}
List<TileInfo> tiles = tracker.scan(true);
String imgUrl = prepareAjaxImage(tiles, true);
String updateUrl = prepareAjaxSession(true);
StringBuffer sbTileSequence = new StringBuffer();
int i = 0;
for(TileInfo tile : tiles) {
sbTileSequence.append("[").append(tile.getRow()).append(",").append(tile.getCol()).append("]");
if(i < tiles.size() - 1)
sbTileSequence.append(",");
i++;
}
return getAjaxViewerPageContent(sbTileSequence.toString(), imgUrl,
updateUrl, width, height, tileWidth, tileHeight, title,
ConsoleProxy.keyboardType == ConsoleProxy.KEYBOARD_RAW,
languages, guest);
}
private String getAjaxViewerPageContent(String tileSequence, String imgUrl, String updateUrl, int width,
int height, int tileWidth, int tileHeight, String title, boolean rawKeyboard, List<String> languages, String guest) {
StringBuffer sbLanguages = new StringBuffer("");
if(languages != null) {
for(String lang : languages) {
if(sbLanguages.length() > 0) {
sbLanguages.append(",");
}
sbLanguages.append(lang);
}
}
String[] content = new String[] {
"<html>",
"<head>",
"<script type=\"text/javascript\" language=\"javascript\" src=\"/resource/js/jquery.js\"></script>",
"<script type=\"text/javascript\" language=\"javascript\" src=\"/resource/js/cloud.logger.js\"></script>",
"<script type=\"text/javascript\" language=\"javascript\" src=\"/resource/js/ajaxviewer.js\"></script>",
"<script type=\"text/javascript\" language=\"javascript\" src=\"/resource/js/handler.js\"></script>",
"<link rel=\"stylesheet\" type=\"text/css\" href=\"/resource/css/ajaxviewer.css\"></link>",
"<link rel=\"stylesheet\" type=\"text/css\" href=\"/resource/css/logger.css\"></link>",
"<title>" + title + "</title>",
"</head>",
"<body>",
"<div id=\"toolbar\">",
"<ul>",
"<li>",
"<a href=\"#\" cmd=\"sendCtrlAltDel\">",
"<span><img align=\"left\" src=\"/resource/images/cad.gif\" alt=\"Ctrl-Alt-Del\" />Ctrl-Alt-Del</span>",
"</a>",
"</li>",
"<li>",
"<a href=\"#\" cmd=\"sendCtrlEsc\">",
"<span><img align=\"left\" src=\"/resource/images/winlog.png\" alt=\"Ctrl-Esc\" style=\"width:16px;height:16px\"/>Ctrl-Esc</span>",
"</a>",
"</li>",
"<li class=\"pulldown\">",
"<a href=\"#\">",
"<span><img align=\"left\" src=\"/resource/images/winlog.png\" alt=\"Keyboard\" style=\"width:16px;height:16px\"/>Keyboard</span>",
"</a>",
"<ul>",
"<li><a href=\"#\" cmd=\"keyboard_us\"><span>Standard (US) keyboard</span></a></li>",
"<li><a href=\"#\" cmd=\"keyboard_jp\"><span>Japanese keyboard</span></a></li>",
"</ul>",
"</li>",
"</ul>",
"<span id=\"light\" class=\"dark\" cmd=\"toggle_logwin\"></span>",
"</div>",
"<div id=\"main_panel\" tabindex=\"1\"></div>",
"<script language=\"javascript\">",
"var acceptLanguages = '" + sbLanguages.toString() + "';",
"var tileMap = [ " + tileSequence + " ];",
"var ajaxViewer = new AjaxViewer('main_panel', '" + imgUrl + "', '" + updateUrl + "', tileMap, ",
String.valueOf(width) + ", " + String.valueOf(height) + ", " + String.valueOf(tileWidth) + ", " + String.valueOf(tileHeight) + ");",
"$(function() {",
"ajaxViewer.start();",
"});",
"</script>",
"</body>",
"</html>"
};
StringBuffer sb = new StringBuffer();
for(int i = 0; i < content.length; i++)
sb.append(content[i]);
return sb.toString();
}
public String onAjaxClientDisconnected() {
return "onDisconnect();";
}
@Override
public String onAjaxClientUpdate() {
updateFrontEndActivityTime();
if(!waitForViewerReady())
return onAjaxClientDisconnected();
synchronized(tileDirtyEvent) {
if(!dirtyFlag) {
try {
tileDirtyEvent.wait(3000);
} catch(InterruptedException e) {
}
}
}
boolean doResize = false;
synchronized(this) {
if(framebufferResized) {
framebufferResized = false;
doResize = true;
}
}
List<TileInfo> tiles;
if(doResize)
tiles = tracker.scan(true);
else
tiles = tracker.scan(false);
dirtyFlag = false;
String imgUrl = prepareAjaxImage(tiles, false);
StringBuffer sbTileSequence = new StringBuffer();
int i = 0;
for(TileInfo tile : tiles) {
sbTileSequence.append("[").append(tile.getRow()).append(",").append(tile.getCol()).append("]");
if(i < tiles.size() - 1)
sbTileSequence.append(",");
i++;
}
return getAjaxViewerUpdatePageContent(sbTileSequence.toString(), imgUrl, doResize,
resizedFramebufferWidth, resizedFramebufferHeight,
tracker.getTileWidth(), tracker.getTileHeight());
}
private String getAjaxViewerUpdatePageContent(String tileSequence, String imgUrl, boolean resized, int width,
int height, int tileWidth, int tileHeight) {
String[] content = new String[] {
"tileMap = [ " + tileSequence + " ];",
resized ? "ajaxViewer.resize('main_panel', " + width + ", " + height + " , " + tileWidth + ", " + tileHeight + ");" : "",
"ajaxViewer.refresh('" + imgUrl + "', tileMap, false);"
};
StringBuffer sb = new StringBuffer();
for(int i = 0; i < content.length; i++)
sb.append(content[i]);
return sb.toString();
}
//
// Helpers
//
private synchronized static int getNextClientId() {
return ++s_nextClientId;
}
private void signalTileDirtyEvent() {
synchronized(tileDirtyEvent) {
dirtyFlag = true;
tileDirtyEvent.notifyAll();
}
}
public void updateFrontEndActivityTime() {
lastFrontEndActivityTime = System.currentTimeMillis();
}
protected abstract FrameBufferCanvas getFrameBufferCavas();
public ConsoleProxyClientParam getClientParam() { import org.apache.log4j.Logger;
return clientParam;
}
public void setClientParam(ConsoleProxyClientParam clientParam) { import com.cloud.consoleproxy.util.TileInfo;
this.clientParam = clientParam; import com.cloud.consoleproxy.util.TileTracker;
ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(ConsoleProxy.getEncryptorPassword()); import com.cloud.consoleproxy.vnc.FrameBufferCanvas;
this.clientToken = encryptor.encryptObject(ConsoleProxyClientParam.class, clientParam);
} /**
} *
* an instance of specialized console protocol implementation, such as VNC or RDP
*
* It mainly implements the features needed by front-end AJAX viewer
*
*/
public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, ConsoleProxyClientListener {
private static final Logger s_logger = Logger.getLogger(ConsoleProxyClientBase.class);
private static int s_nextClientId = 0;
protected int clientId = getNextClientId();
protected long ajaxSessionId = 0;
protected boolean dirtyFlag = false;
protected Object tileDirtyEvent = new Object();
protected TileTracker tracker;
protected AjaxFIFOImageCache ajaxImageCache = new AjaxFIFOImageCache(2);
protected ConsoleProxyClientParam clientParam;
protected String clientToken;
protected long createTime = System.currentTimeMillis();
protected long lastFrontEndActivityTime = System.currentTimeMillis();
protected boolean framebufferResized = false;
protected int resizedFramebufferWidth;
protected int resizedFramebufferHeight;
public ConsoleProxyClientBase() {
tracker = new TileTracker();
tracker.initTracking(64, 64, 800, 600);
}
//
// interface ConsoleProxyClient
//
@Override
public int getClientId() {
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;
}
@Override
public long getClientLastFrontEndActivityTime() {
return lastFrontEndActivityTime;
}
@Override
public String getClientHostAddress() {
return clientParam.getClientHostAddress();
}
@Override
public int getClientHostPort() {
return clientParam.getClientHostPort();
}
@Override
public String getClientHostPassword() {
return clientParam.getClientHostPassword();
}
@Override
public String getClientTag() {
if(clientParam.getClientTag() != null)
return clientParam.getClientTag();
return "";
}
@Override
public abstract void initClient(ConsoleProxyClientParam param);
@Override
public abstract void closeClient();
//
// interface FrameBufferEventListener
//
@Override
public void onFramebufferSizeChange(int w, int h) {
tracker.resize(w, h);
synchronized(this) {
framebufferResized = true;
resizedFramebufferWidth = w;
resizedFramebufferHeight = h;
}
signalTileDirtyEvent();
}
@Override
public void onFramebufferUpdate(int x, int y, int w, int h) {
if(s_logger.isTraceEnabled())
s_logger.trace("Frame buffer update {" + x + "," + y + "," + w + "," + h + "}");
tracker.invalidate(new Rectangle(x, y, w, h));
signalTileDirtyEvent();
}
//
// AJAX Image manipulation
//
public byte[] getFrameBufferJpeg() {
FrameBufferCanvas canvas = getFrameBufferCavas();
if(canvas != null)
return canvas.getFrameBufferJpeg();
return null;
}
public byte[] getTilesMergedJpeg(List<TileInfo> tileList, int tileWidth, int tileHeight) {
FrameBufferCanvas canvas = getFrameBufferCavas();
if(canvas != null)
return canvas.getTilesMergedJpeg(tileList, tileWidth, tileHeight);
return null;
}
private String prepareAjaxImage(List<TileInfo> tiles, boolean init) {
byte[] imgBits;
if(init)
imgBits = getFrameBufferJpeg();
else
imgBits = getTilesMergedJpeg(tiles, tracker.getTileWidth(), tracker.getTileHeight());
if(imgBits == null) {
s_logger.warn("Unable to generate jpeg image");
} else {
if(s_logger.isTraceEnabled())
s_logger.trace("Generated jpeg image size: " + imgBits.length);
}
int key = ajaxImageCache.putImage(imgBits);
StringBuffer sb = new StringBuffer();
sb.append("/ajaximg?token=").append(clientToken);
sb.append("&key=").append(key);
sb.append("&ts=").append(System.currentTimeMillis());
return sb.toString();
}
private String prepareAjaxSession(boolean init) {
if(init) {
synchronized(this) {
ajaxSessionId++;
}
}
StringBuffer sb = new StringBuffer();
sb.append("/ajax?token=").append(clientToken).append("&sess=").append(ajaxSessionId);
return sb.toString();
}
@Override
public String onAjaxClientKickoff() {
return "onKickoff();";
}
private boolean waitForViewerReady() {
long startTick = System.currentTimeMillis();
while(System.currentTimeMillis() - startTick < 5000) {
if(getFrameBufferCavas() != null)
return true;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
return false;
}
private String onAjaxClientConnectFailed() {
return "<html><head></head><body><div id=\"main_panel\" tabindex=\"1\"><p>" +
"Unable to start console session as connection is refused by the machine you are accessing" +
"</p></div></body></html>";
}
@Override
public String onAjaxClientStart(String title, List<String> languages, String guest) {
updateFrontEndActivityTime();
if(!waitForViewerReady())
return onAjaxClientConnectFailed();
synchronized(this) {
ajaxSessionId++;
framebufferResized = false;
}
int tileWidth = tracker.getTileWidth();
int tileHeight = tracker.getTileHeight();
int width = tracker.getTrackWidth();
int height = tracker.getTrackHeight();
if(s_logger.isTraceEnabled())
s_logger.trace("Ajax client start, frame buffer w: " + width + ", " + height);
int retry = 0;
tracker.initCoverageTest();
while(!tracker.hasFullCoverage() && retry < 10) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
retry++;
}
List<TileInfo> tiles = tracker.scan(true);
String imgUrl = prepareAjaxImage(tiles, true);
String updateUrl = prepareAjaxSession(true);
StringBuffer sbTileSequence = new StringBuffer();
int i = 0;
for(TileInfo tile : tiles) {
sbTileSequence.append("[").append(tile.getRow()).append(",").append(tile.getCol()).append("]");
if(i < tiles.size() - 1)
sbTileSequence.append(",");
i++;
}
return getAjaxViewerPageContent(sbTileSequence.toString(), imgUrl,
updateUrl, width, height, tileWidth, tileHeight, title,
ConsoleProxy.keyboardType == ConsoleProxy.KEYBOARD_RAW,
languages, guest);
}
private String getAjaxViewerPageContent(String tileSequence, String imgUrl, String updateUrl, int width,
int height, int tileWidth, int tileHeight, String title, boolean rawKeyboard, List<String> languages, String guest) {
StringBuffer sbLanguages = new StringBuffer("");
if(languages != null) {
for(String lang : languages) {
if(sbLanguages.length() > 0) {
sbLanguages.append(",");
}
sbLanguages.append(lang);
}
}
String[] content = new String[] {
"<html>",
"<head>",
"<script type=\"text/javascript\" language=\"javascript\" src=\"/resource/js/jquery.js\"></script>",
"<script type=\"text/javascript\" language=\"javascript\" src=\"/resource/js/cloud.logger.js\"></script>",
"<script type=\"text/javascript\" language=\"javascript\" src=\"/resource/js/ajaxviewer.js\"></script>",
"<script type=\"text/javascript\" language=\"javascript\" src=\"/resource/js/handler.js\"></script>",
"<link rel=\"stylesheet\" type=\"text/css\" href=\"/resource/css/ajaxviewer.css\"></link>",
"<link rel=\"stylesheet\" type=\"text/css\" href=\"/resource/css/logger.css\"></link>",
"<title>" + title + "</title>",
"</head>",
"<body>",
"<div id=\"toolbar\">",
"<ul>",
"<li>",
"<a href=\"#\" cmd=\"sendCtrlAltDel\">",
"<span><img align=\"left\" src=\"/resource/images/cad.gif\" alt=\"Ctrl-Alt-Del\" />Ctrl-Alt-Del</span>",
"</a>",
"</li>",
"<li>",
"<a href=\"#\" cmd=\"sendCtrlEsc\">",
"<span><img align=\"left\" src=\"/resource/images/winlog.png\" alt=\"Ctrl-Esc\" style=\"width:16px;height:16px\"/>Ctrl-Esc</span>",
"</a>",
"</li>",
"<li class=\"pulldown\">",
"<a href=\"#\">",
"<span><img align=\"left\" src=\"/resource/images/winlog.png\" alt=\"Keyboard\" style=\"width:16px;height:16px\"/>Keyboard</span>",
"</a>",
"<ul>",
"<li><a href=\"#\" cmd=\"keyboard_us\"><span>Standard (US) keyboard</span></a></li>",
"<li><a href=\"#\" cmd=\"keyboard_jp\"><span>Japanese keyboard</span></a></li>",
"</ul>",
"</li>",
"</ul>",
"<span id=\"light\" class=\"dark\" cmd=\"toggle_logwin\"></span>",
"</div>",
"<div id=\"main_panel\" tabindex=\"1\"></div>",
"<script language=\"javascript\">",
"var acceptLanguages = '" + sbLanguages.toString() + "';",
"var tileMap = [ " + tileSequence + " ];",
"var ajaxViewer = new AjaxViewer('main_panel', '" + imgUrl + "', '" + updateUrl + "', tileMap, ",
String.valueOf(width) + ", " + String.valueOf(height) + ", " + String.valueOf(tileWidth) + ", " + String.valueOf(tileHeight) + ");",
"$(function() {",
"ajaxViewer.start();",
"});",
"</script>",
"</body>",
"</html>"
};
StringBuffer sb = new StringBuffer();
for(int i = 0; i < content.length; i++)
sb.append(content[i]);
return sb.toString();
}
public String onAjaxClientDisconnected() {
return "onDisconnect();";
}
@Override
public String onAjaxClientUpdate() {
updateFrontEndActivityTime();
if(!waitForViewerReady())
return onAjaxClientDisconnected();
synchronized(tileDirtyEvent) {
if(!dirtyFlag) {
try {
tileDirtyEvent.wait(3000);
} catch(InterruptedException e) {
}
}
}
boolean doResize = false;
synchronized(this) {
if(framebufferResized) {
framebufferResized = false;
doResize = true;
}
}
List<TileInfo> tiles;
if(doResize)
tiles = tracker.scan(true);
else
tiles = tracker.scan(false);
dirtyFlag = false;
String imgUrl = prepareAjaxImage(tiles, false);
StringBuffer sbTileSequence = new StringBuffer();
int i = 0;
for(TileInfo tile : tiles) {
sbTileSequence.append("[").append(tile.getRow()).append(",").append(tile.getCol()).append("]");
if(i < tiles.size() - 1)
sbTileSequence.append(",");
i++;
}
return getAjaxViewerUpdatePageContent(sbTileSequence.toString(), imgUrl, doResize,
resizedFramebufferWidth, resizedFramebufferHeight,
tracker.getTileWidth(), tracker.getTileHeight());
}
private String getAjaxViewerUpdatePageContent(String tileSequence, String imgUrl, boolean resized, int width,
int height, int tileWidth, int tileHeight) {
String[] content = new String[] {
"tileMap = [ " + tileSequence + " ];",
resized ? "ajaxViewer.resize('main_panel', " + width + ", " + height + " , " + tileWidth + ", " + tileHeight + ");" : "",
"ajaxViewer.refresh('" + imgUrl + "', tileMap, false);"
};
StringBuffer sb = new StringBuffer();
for(int i = 0; i < content.length; i++)
sb.append(content[i]);
return sb.toString();
}
//
// Helpers
//
private synchronized static int getNextClientId() {
return ++s_nextClientId;
}
private void signalTileDirtyEvent() {
synchronized(tileDirtyEvent) {
dirtyFlag = true;
tileDirtyEvent.notifyAll();
}
}
public void updateFrontEndActivityTime() {
lastFrontEndActivityTime = System.currentTimeMillis();
}
protected abstract FrameBufferCanvas getFrameBufferCavas();
public ConsoleProxyClientParam getClientParam() {
return clientParam;
}
public void setClientParam(ConsoleProxyClientParam clientParam) {
this.clientParam = clientParam;
ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(ConsoleProxy.getEncryptorPassword());
this.clientToken = encryptor.encryptObject(ConsoleProxyClientParam.class, clientParam);
}
}

View File

@ -14,98 +14,97 @@
// KIND, either express or implied. See the License for the // KIND, either express or implied. See the License for the
// specific language governing permissions and limitations // specific language governing permissions and limitations
// under the License. // under the License.
package com.cloud.consoleproxy; package com.cloud.consoleproxy;
/** /**
* *
* @author Kelven Yang * Data object to store parameter info needed by client to connect to its host
* Data object to store parameter info needed by client to connect to its host */
*/ public class ConsoleProxyClientParam {
public class ConsoleProxyClientParam {
private String clientHostAddress;
private String clientHostAddress; private int clientHostPort;
private int clientHostPort; private String clientHostPassword;
private String clientHostPassword; private String clientTag;
private String clientTag; private String ticket;
private String ticket;
private String clientTunnelUrl;
private String clientTunnelUrl; private String clientTunnelSession;
private String clientTunnelSession;
private String ajaxSessionId;
private String ajaxSessionId;
public ConsoleProxyClientParam() {
public ConsoleProxyClientParam() { clientHostPort = 0;
clientHostPort = 0; }
}
public String getClientHostAddress() {
public String getClientHostAddress() { return clientHostAddress;
return clientHostAddress; }
}
public void setClientHostAddress(String clientHostAddress) {
public void setClientHostAddress(String clientHostAddress) { this.clientHostAddress = clientHostAddress;
this.clientHostAddress = clientHostAddress; }
}
public int getClientHostPort() {
public int getClientHostPort() { return clientHostPort;
return clientHostPort; }
}
public void setClientHostPort(int clientHostPort) {
public void setClientHostPort(int clientHostPort) { this.clientHostPort = clientHostPort;
this.clientHostPort = clientHostPort; }
}
public String getClientHostPassword() {
public String getClientHostPassword() { return clientHostPassword;
return clientHostPassword; }
}
public void setClientHostPassword(String clientHostPassword) {
public void setClientHostPassword(String clientHostPassword) { this.clientHostPassword = clientHostPassword;
this.clientHostPassword = clientHostPassword; }
}
public String getClientTag() {
public String getClientTag() { return clientTag;
return clientTag; }
}
public void setClientTag(String clientTag) {
public void setClientTag(String clientTag) { this.clientTag = clientTag;
this.clientTag = clientTag; }
}
public String getTicket() {
public String getTicket() { return ticket;
return ticket; }
}
public void setTicket(String ticket) {
public void setTicket(String ticket) { this.ticket = ticket;
this.ticket = ticket; }
}
public String getClientTunnelUrl() {
public String getClientTunnelUrl() { return clientTunnelUrl;
return clientTunnelUrl; }
}
public void setClientTunnelUrl(String clientTunnelUrl) {
public void setClientTunnelUrl(String clientTunnelUrl) { this.clientTunnelUrl = clientTunnelUrl;
this.clientTunnelUrl = clientTunnelUrl; }
}
public String getClientTunnelSession() {
public String getClientTunnelSession() { return clientTunnelSession;
return clientTunnelSession; }
}
public void setClientTunnelSession(String clientTunnelSession) {
public void setClientTunnelSession(String clientTunnelSession) { this.clientTunnelSession = clientTunnelSession;
this.clientTunnelSession = clientTunnelSession; }
}
public String getAjaxSessionId() {
public String getAjaxSessionId() { return this.ajaxSessionId;
return this.ajaxSessionId; }
}
public void setAjaxSessionId(String ajaxSessionId) {
public void setAjaxSessionId(String ajaxSessionId) { this.ajaxSessionId = ajaxSessionId;
this.ajaxSessionId = ajaxSessionId; }
}
public String getClientMapKey() {
public String getClientMapKey() { if(clientTag != null && !clientTag.isEmpty())
if(clientTag != null && !clientTag.isEmpty()) return clientTag;
return clientTag;
return clientHostAddress + ":" + clientHostPort;
return clientHostAddress + ":" + clientHostPort; }
} }
}

View File

@ -14,76 +14,75 @@
// KIND, either express or implied. See the License for the // KIND, either express or implied. See the License for the
// specific language governing permissions and limitations // specific language governing permissions and limitations
// under the License. // under the License.
package com.cloud.consoleproxy; package com.cloud.consoleproxy;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.Hashtable; import java.util.Hashtable;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
/** /**
* *
* @author Kelven Yang * ConsoleProxyClientStatsCollector collects client stats for console proxy agent to report
* ConsoleProxyClientStatsCollector collects client stats for console proxy agent to report */
*/ public class ConsoleProxyClientStatsCollector {
public class ConsoleProxyClientStatsCollector {
ArrayList<ConsoleProxyConnection> connections;
ArrayList<ConsoleProxyConnection> connections;
public ConsoleProxyClientStatsCollector() {
public ConsoleProxyClientStatsCollector() { }
}
public ConsoleProxyClientStatsCollector(Hashtable<String, ConsoleProxyClient> connMap) {
public ConsoleProxyClientStatsCollector(Hashtable<String, ConsoleProxyClient> connMap) { setConnections(connMap);
setConnections(connMap); }
}
public String getStatsReport() {
public String getStatsReport() { Gson gson = new GsonBuilder().setPrettyPrinting().create();
Gson gson = new GsonBuilder().setPrettyPrinting().create(); return gson.toJson(this);
return gson.toJson(this); }
}
public void getStatsReport(OutputStreamWriter os) {
public void getStatsReport(OutputStreamWriter os) { Gson gson = new GsonBuilder().setPrettyPrinting().create();
Gson gson = new GsonBuilder().setPrettyPrinting().create(); gson.toJson(this, os);
gson.toJson(this, os); }
}
private void setConnections(Hashtable<String, ConsoleProxyClient> connMap) {
private void setConnections(Hashtable<String, ConsoleProxyClient> connMap) {
ArrayList<ConsoleProxyConnection> conns = new ArrayList<ConsoleProxyConnection>();
ArrayList<ConsoleProxyConnection> conns = new ArrayList<ConsoleProxyConnection>(); Enumeration<String> e = connMap.keys();
Enumeration<String> e = connMap.keys(); while (e.hasMoreElements()) {
while (e.hasMoreElements()) { synchronized (connMap) {
synchronized (connMap) { String key = e.nextElement();
String key = e.nextElement(); ConsoleProxyClient client = connMap.get(key);
ConsoleProxyClient client = connMap.get(key);
ConsoleProxyConnection conn = new ConsoleProxyConnection();
ConsoleProxyConnection conn = new ConsoleProxyConnection();
conn.id = client.getClientId();
conn.id = client.getClientId(); conn.clientInfo = "";
conn.clientInfo = ""; conn.host = client.getClientHostAddress();
conn.host = client.getClientHostAddress(); conn.port = client.getClientHostPort();
conn.port = client.getClientHostPort(); conn.tag = client.getClientTag();
conn.tag = client.getClientTag(); conn.createTime = client.getClientCreateTime();
conn.createTime = client.getClientCreateTime(); conn.lastUsedTime = client.getClientLastFrontEndActivityTime();
conn.lastUsedTime = client.getClientLastFrontEndActivityTime(); conns.add(conn);
conns.add(conn); }
} }
} connections = conns;
connections = conns; }
}
public static class ConsoleProxyConnection {
public static class ConsoleProxyConnection { public int id;
public int id; public String clientInfo;
public String clientInfo; public String host;
public String host; public int port;
public int port; public String tag;
public String tag; public long createTime;
public long createTime; public long lastUsedTime;
public long lastUsedTime;
public ConsoleProxyConnection() {
public ConsoleProxyConnection() { }
} }
} }
}

View File

@ -14,96 +14,95 @@
// KIND, either express or implied. See the License for the // KIND, either express or implied. See the License for the
// specific language governing permissions and limitations // specific language governing permissions and limitations
// under the License. // under the License.
package com.cloud.consoleproxy; package com.cloud.consoleproxy;
import java.io.File; import java.io.File;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.Hashtable; import java.util.Hashtable;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
/** /**
* *
* @author Kelven Yang * ConsoleProxyGCThread does house-keeping work for the process, it helps cleanup log files,
* ConsoleProxyGCThread does house-keeping work for the process, it helps cleanup log files, * recycle idle client sessions without front-end activities and report client stats to external
* recycle idle client sessions without front-end activities and report client stats to external * management software
* management software */
*/ public class ConsoleProxyGCThread extends Thread {
public class ConsoleProxyGCThread extends Thread { private static final Logger s_logger = Logger.getLogger(ConsoleProxyGCThread.class);
private static final Logger s_logger = Logger.getLogger(ConsoleProxyGCThread.class);
private final static int MAX_SESSION_IDLE_SECONDS = 180;
private final static int MAX_SESSION_IDLE_SECONDS = 180;
private Hashtable<String, ConsoleProxyClient> connMap;
private Hashtable<String, ConsoleProxyClient> connMap; private long lastLogScan = 0;
private long lastLogScan = 0;
public ConsoleProxyGCThread(Hashtable<String, ConsoleProxyClient> connMap) {
public ConsoleProxyGCThread(Hashtable<String, ConsoleProxyClient> connMap) { this.connMap = connMap;
this.connMap = connMap; }
}
private void cleanupLogging() {
private void cleanupLogging() { if(lastLogScan != 0 && System.currentTimeMillis() - lastLogScan < 3600000)
if(lastLogScan != 0 && System.currentTimeMillis() - lastLogScan < 3600000) return;
return;
lastLogScan = System.currentTimeMillis();
lastLogScan = System.currentTimeMillis();
File logDir = new File("./logs");
File logDir = new File("./logs"); File files[] = logDir.listFiles();
File files[] = logDir.listFiles(); if(files != null) {
if(files != null) { for(File file : files) {
for(File file : files) { if(System.currentTimeMillis() - file.lastModified() >= 86400000L) {
if(System.currentTimeMillis() - file.lastModified() >= 86400000L) { try {
try { file.delete();
file.delete(); } catch(Throwable e) {
} catch(Throwable e) { }
} }
} }
} }
} }
}
@Override
@Override
public void run() { public void run() {
boolean bReportLoad = false; boolean bReportLoad = false;
while (true) { while (true) {
cleanupLogging(); cleanupLogging();
bReportLoad = false; bReportLoad = false;
if(s_logger.isDebugEnabled()) if(s_logger.isDebugEnabled())
s_logger.debug("connMap=" + connMap); s_logger.debug("connMap=" + connMap);
Enumeration<String> e = connMap.keys(); Enumeration<String> e = connMap.keys();
while (e.hasMoreElements()) { while (e.hasMoreElements()) {
String key; String key;
ConsoleProxyClient client; ConsoleProxyClient client;
synchronized (connMap) { synchronized (connMap) {
key = e.nextElement(); key = e.nextElement();
client = connMap.get(key); client = connMap.get(key);
} }
long seconds_unused = (System.currentTimeMillis() - client.getClientLastFrontEndActivityTime()) / 1000; long seconds_unused = (System.currentTimeMillis() - client.getClientLastFrontEndActivityTime()) / 1000;
if (seconds_unused < MAX_SESSION_IDLE_SECONDS) { if (seconds_unused < MAX_SESSION_IDLE_SECONDS) {
continue; continue;
} }
synchronized (connMap) { synchronized (connMap) {
connMap.remove(key); connMap.remove(key);
} }
// close the server connection // close the server connection
s_logger.info("Dropping " + client + " which has not been used for " + seconds_unused + " seconds"); s_logger.info("Dropping " + client + " which has not been used for " + seconds_unused + " seconds");
client.closeClient(); client.closeClient();
} }
if(bReportLoad) { if(bReportLoad) {
// report load changes // report load changes
String loadInfo = new ConsoleProxyClientStatsCollector(connMap).getStatsReport(); String loadInfo = new ConsoleProxyClientStatsCollector(connMap).getStatsReport();
ConsoleProxy.reportLoadInfo(loadInfo); ConsoleProxy.reportLoadInfo(loadInfo);
if(s_logger.isDebugEnabled()) if(s_logger.isDebugEnabled())
s_logger.debug("Report load change : " + loadInfo); s_logger.debug("Report load change : " + loadInfo);
} }
try { Thread.sleep(5000); } catch (InterruptedException ex) {} try { Thread.sleep(5000); } catch (InterruptedException ex) {}
} }
} }
} }

View File

@ -14,130 +14,129 @@
// KIND, either express or implied. See the License for the // KIND, either express or implied. See the License for the
// specific language governing permissions and limitations // specific language governing permissions and limitations
// under the License. // under the License.
package com.cloud.consoleproxy; package com.cloud.consoleproxy;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException; import javax.crypto.BadPaddingException;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException; import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException; import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
/** /**
* *
* @author Kelven Yang * A simple password based encyrptor based on DES. It can serialize simple POJO object into URL safe string
* A simple password based encyrptor based on DES. It can serialize simple POJO object into URL safe string * and deserialize it back.
* and deserialize it back. *
* */
*/ public class ConsoleProxyPasswordBasedEncryptor {
public class ConsoleProxyPasswordBasedEncryptor { private static final Logger s_logger = Logger.getLogger(ConsoleProxyPasswordBasedEncryptor.class);
private static final Logger s_logger = Logger.getLogger(ConsoleProxyPasswordBasedEncryptor.class);
private String password;
private String password; private Gson gson;
private Gson gson;
public ConsoleProxyPasswordBasedEncryptor(String password) {
public ConsoleProxyPasswordBasedEncryptor(String password) { this.password = password;
this.password = password; gson = new GsonBuilder().create();
gson = new GsonBuilder().create(); }
}
public String encryptText(String text) {
public String encryptText(String text) { if(text == null || text.isEmpty())
if(text == null || text.isEmpty()) return text;
return text;
assert(password != null);
assert(password != null); assert(!password.isEmpty());
assert(!password.isEmpty());
try {
try { Cipher cipher = Cipher.getInstance("DES");
Cipher cipher = Cipher.getInstance("DES"); int maxKeySize = 8;
int maxKeySize = 8; SecretKeySpec keySpec = new SecretKeySpec(normalizeKey(password.getBytes(), maxKeySize), "DES");
SecretKeySpec keySpec = new SecretKeySpec(normalizeKey(password.getBytes(), maxKeySize), "DES"); cipher.init(Cipher.ENCRYPT_MODE, keySpec);
cipher.init(Cipher.ENCRYPT_MODE, keySpec); byte[] encryptedBytes = cipher.doFinal(text.getBytes());
byte[] encryptedBytes = cipher.doFinal(text.getBytes()); return Base64.encodeBase64URLSafeString(encryptedBytes);
return Base64.encodeBase64URLSafeString(encryptedBytes); } catch (NoSuchAlgorithmException e) {
} catch (NoSuchAlgorithmException e) { s_logger.error("Unexpected exception ", e);
s_logger.error("Unexpected exception ", e); return null;
return null; } catch (NoSuchPaddingException e) {
} catch (NoSuchPaddingException e) { s_logger.error("Unexpected exception ", e);
s_logger.error("Unexpected exception ", e); return null;
return null; } catch (IllegalBlockSizeException e) {
} catch (IllegalBlockSizeException e) { s_logger.error("Unexpected exception ", e);
s_logger.error("Unexpected exception ", e); return null;
return null; } catch (BadPaddingException e) {
} catch (BadPaddingException e) { s_logger.error("Unexpected exception ", e);
s_logger.error("Unexpected exception ", e); return null;
return null; } catch (InvalidKeyException e) {
} catch (InvalidKeyException e) { s_logger.error("Unexpected exception ", e);
s_logger.error("Unexpected exception ", e); return null;
return null; }
} }
}
public String decryptText(String encryptedText) {
public String decryptText(String encryptedText) { if(encryptedText == null || encryptedText.isEmpty())
if(encryptedText == null || encryptedText.isEmpty()) return encryptedText;
return encryptedText;
assert(password != null);
assert(password != null); assert(!password.isEmpty());
assert(!password.isEmpty());
try {
try { Cipher cipher = Cipher.getInstance("DES");
Cipher cipher = Cipher.getInstance("DES"); int maxKeySize = 8;
int maxKeySize = 8; SecretKeySpec keySpec = new SecretKeySpec(normalizeKey(password.getBytes(), maxKeySize), "DES");
SecretKeySpec keySpec = new SecretKeySpec(normalizeKey(password.getBytes(), maxKeySize), "DES"); cipher.init(Cipher.DECRYPT_MODE, keySpec);
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] encryptedBytes = Base64.decodeBase64(encryptedText);
byte[] encryptedBytes = Base64.decodeBase64(encryptedText); return new String(cipher.doFinal(encryptedBytes));
return new String(cipher.doFinal(encryptedBytes)); } catch (NoSuchAlgorithmException e) {
} catch (NoSuchAlgorithmException e) { s_logger.error("Unexpected exception ", e);
s_logger.error("Unexpected exception ", e); return null;
return null; } catch (NoSuchPaddingException e) {
} catch (NoSuchPaddingException e) { s_logger.error("Unexpected exception ", e);
s_logger.error("Unexpected exception ", e); return null;
return null; } catch (IllegalBlockSizeException e) {
} catch (IllegalBlockSizeException e) { s_logger.error("Unexpected exception ", e);
s_logger.error("Unexpected exception ", e); return null;
return null; } catch (BadPaddingException e) {
} catch (BadPaddingException e) { s_logger.error("Unexpected exception ", e);
s_logger.error("Unexpected exception ", e); return null;
return null; } catch (InvalidKeyException e) {
} catch (InvalidKeyException e) { s_logger.error("Unexpected exception ", e);
s_logger.error("Unexpected exception ", e); return null;
return null; }
} }
}
public <T> String encryptObject(Class<?> clz, T obj) {
public <T> String encryptObject(Class<?> clz, T obj) { if(obj == null)
if(obj == null) return null;
return null;
String json = gson.toJson(obj);
String json = gson.toJson(obj); return encryptText(json);
return encryptText(json); }
}
@SuppressWarnings("unchecked")
@SuppressWarnings("unchecked") public <T> T decryptObject(Class<?> clz, String encrypted) {
public <T> T decryptObject(Class<?> clz, String encrypted) { if(encrypted == null || encrypted.isEmpty())
if(encrypted == null || encrypted.isEmpty()) return null;
return null;
String json = decryptText(encrypted);
String json = decryptText(encrypted); return (T)gson.fromJson(json, clz);
return (T)gson.fromJson(json, clz); }
}
private static byte[] normalizeKey(byte[] keyBytes, int keySize) {
private static byte[] normalizeKey(byte[] keyBytes, int keySize) { assert(keySize > 0);
assert(keySize > 0); byte[] key = new byte[keySize];
byte[] key = new byte[keySize];
for(int i = 0; i < keyBytes.length; i++)
for(int i = 0; i < keyBytes.length; i++) key[i%keySize] ^= keyBytes[i];
key[i%keySize] ^= keyBytes[i];
return key;
return key; }
} }
}

View File

@ -14,223 +14,222 @@
// KIND, either express or implied. See the License for the // KIND, either express or implied. See the License for the
// specific language governing permissions and limitations // specific language governing permissions and limitations
// under the License. // under the License.
package com.cloud.consoleproxy; package com.cloud.consoleproxy;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.UnknownHostException; 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;
/**
*
* @author Kelven Yang
* ConsoleProxyVncClient bridges a VNC engine with the front-end AJAX viewer
*
*/
public class ConsoleProxyVncClient extends ConsoleProxyClientBase {
private static final Logger s_logger = Logger.getLogger(ConsoleProxyVncClient.class);
private static final int SHIFT_KEY_MASK = 64; import org.apache.log4j.Logger;
private static final int CTRL_KEY_MASK = 128;
private static final int META_KEY_MASK = 256;
private static final int ALT_KEY_MASK = 512;
private static final int X11_KEY_SHIFT = 0xffe1;
private static final int X11_KEY_CTRL = 0xffe3;
private static final int X11_KEY_ALT = 0xffe9;
private static final int X11_KEY_META = 0xffe7;
private VncClient client;
private Thread worker;
private boolean workerDone = false;
private int lastModifierStates = 0;
private int lastPointerMask = 0;
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) {
s_logger.info("Front end has been idle for too long");
return false;
}
return true;
}
@Override
public void initClient(ConsoleProxyClientParam param) {
setClientParam(param);
client = new VncClient(this);
worker = new Thread(new Runnable() {
public void run() {
String tunnelUrl = getClientParam().getClientTunnelUrl();
String tunnelSession = getClientParam().getClientTunnelSession();
for(int i = 0; i < 15; i++) {
try {
if(tunnelUrl != null && !tunnelUrl.isEmpty() && tunnelSession != null && !tunnelSession.isEmpty()) {
URI uri = new URI(tunnelUrl);
s_logger.info("Connect to VNC server via tunnel. url: " + tunnelUrl + ", session: " + tunnelSession);
ConsoleProxy.ensureRoute(uri.getHost());
client.connectTo(
uri.getHost(), uri.getPort(),
uri.getPath() + "?" + uri.getQuery(),
tunnelSession, "https".equalsIgnoreCase(uri.getScheme()),
getClientHostPassword());
} else {
s_logger.info("Connect to VNC server directly. host: " + getClientHostAddress() + ", port: " + getClientHostPort());
ConsoleProxy.ensureRoute(getClientHostAddress());
client.connectTo(getClientHostAddress(), getClientHostPort(), getClientHostPassword());
}
} catch (UnknownHostException e) {
s_logger.error("Unexpected exception (will retry until timeout)", e);
} catch (IOException e) {
s_logger.error("Unexpected exception (will retry until timeout) ", e);
} catch (Throwable e) {
s_logger.error("Unexpected exception (will retry until timeout) ", e);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
if(tunnelUrl != null && !tunnelUrl.isEmpty() && tunnelSession != null && !tunnelSession.isEmpty()) { import com.cloud.consoleproxy.vnc.FrameBufferCanvas;
ConsoleProxyAuthenticationResult authResult = ConsoleProxy.reAuthenticationExternally(getClientParam()); import com.cloud.consoleproxy.vnc.RfbConstants;
if(authResult != null && authResult.isSuccess()) { import com.cloud.consoleproxy.vnc.VncClient;
if(authResult.getTunnelUrl() != null && !authResult.getTunnelUrl().isEmpty() &&
authResult.getTunnelSession() != null && !authResult.getTunnelSession().isEmpty()) {
tunnelUrl = authResult.getTunnelUrl();
tunnelSession = authResult.getTunnelSession();
s_logger.info("Reset XAPI session. url: " + tunnelUrl + ", session: " + tunnelSession);
}
}
}
}
s_logger.info("Receiver thread stopped."); /**
workerDone = true; *
client.getClientListener().onClientClose(); * ConsoleProxyVncClient bridges a VNC engine with the front-end AJAX viewer
} *
}); */
public class ConsoleProxyVncClient extends ConsoleProxyClientBase {
worker.setDaemon(true); private static final Logger s_logger = Logger.getLogger(ConsoleProxyVncClient.class);
worker.start();
}
@Override
public void closeClient() {
if(client != null)
client.shutdown();
}
@Override
public void onClientConnected() {
}
public void onClientClose() {
s_logger.info("Received client close indication. remove viewer from map.");
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 :
sendModifierEvents(modifiers);
client.sendClientKeyboardEvent(RfbConstants.KEY_DOWN, code, 0);
break;
case KEY_UP :
client.sendClientKeyboardEvent(RfbConstants.KEY_UP, code, 0);
sendModifierEvents(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();
if (event == InputEventType.MOUSE_DOWN) {
if (code == 2) {
lastPointerMask |= 4;
} else if (code == 0) {
lastPointerMask |= 1;
}
}
if (event == InputEventType.MOUSE_UP) {
if (code == 2) {
lastPointerMask ^= 4;
} else if (code == 0) {
lastPointerMask ^= 1;
}
}
sendModifierEvents(modifiers);
client.sendClientMouseEvent(lastPointerMask, x, y, code, modifiers);
if(lastPointerMask == 0)
sendModifierEvents(0);
}
@Override
protected FrameBufferCanvas getFrameBufferCavas() {
if(client != null)
return client.getFrameBufferCanvas();
return null;
}
private void sendModifierEvents(int modifiers) {
if((modifiers & SHIFT_KEY_MASK) != (lastModifierStates & SHIFT_KEY_MASK))
client.sendClientKeyboardEvent((modifiers & SHIFT_KEY_MASK) != 0 ? RfbConstants.KEY_DOWN : RfbConstants.KEY_UP, X11_KEY_SHIFT, 0);
if((modifiers & CTRL_KEY_MASK) != (lastModifierStates & CTRL_KEY_MASK))
client.sendClientKeyboardEvent((modifiers & CTRL_KEY_MASK) != 0 ? RfbConstants.KEY_DOWN : RfbConstants.KEY_UP, X11_KEY_CTRL, 0);
if((modifiers & META_KEY_MASK) != (lastModifierStates & META_KEY_MASK)) private static final int SHIFT_KEY_MASK = 64;
client.sendClientKeyboardEvent((modifiers & META_KEY_MASK) != 0 ? RfbConstants.KEY_DOWN : RfbConstants.KEY_UP, X11_KEY_META, 0); private static final int CTRL_KEY_MASK = 128;
private static final int META_KEY_MASK = 256;
if((modifiers & ALT_KEY_MASK) != (lastModifierStates & ALT_KEY_MASK)) private static final int ALT_KEY_MASK = 512;
client.sendClientKeyboardEvent((modifiers & ALT_KEY_MASK) != 0 ? RfbConstants.KEY_DOWN : RfbConstants.KEY_UP, X11_KEY_ALT, 0);
private static final int X11_KEY_SHIFT = 0xffe1;
lastModifierStates = modifiers; private static final int X11_KEY_CTRL = 0xffe3;
} private static final int X11_KEY_ALT = 0xffe9;
} private static final int X11_KEY_META = 0xffe7;
private VncClient client;
private Thread worker;
private boolean workerDone = false;
private int lastModifierStates = 0;
private int lastPointerMask = 0;
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) {
s_logger.info("Front end has been idle for too long");
return false;
}
return true;
}
@Override
public void initClient(ConsoleProxyClientParam param) {
setClientParam(param);
client = new VncClient(this);
worker = new Thread(new Runnable() {
public void run() {
String tunnelUrl = getClientParam().getClientTunnelUrl();
String tunnelSession = getClientParam().getClientTunnelSession();
for(int i = 0; i < 15; i++) {
try {
if(tunnelUrl != null && !tunnelUrl.isEmpty() && tunnelSession != null && !tunnelSession.isEmpty()) {
URI uri = new URI(tunnelUrl);
s_logger.info("Connect to VNC server via tunnel. url: " + tunnelUrl + ", session: " + tunnelSession);
ConsoleProxy.ensureRoute(uri.getHost());
client.connectTo(
uri.getHost(), uri.getPort(),
uri.getPath() + "?" + uri.getQuery(),
tunnelSession, "https".equalsIgnoreCase(uri.getScheme()),
getClientHostPassword());
} else {
s_logger.info("Connect to VNC server directly. host: " + getClientHostAddress() + ", port: " + getClientHostPort());
ConsoleProxy.ensureRoute(getClientHostAddress());
client.connectTo(getClientHostAddress(), getClientHostPort(), getClientHostPassword());
}
} catch (UnknownHostException e) {
s_logger.error("Unexpected exception (will retry until timeout)", e);
} catch (IOException e) {
s_logger.error("Unexpected exception (will retry until timeout) ", e);
} catch (Throwable e) {
s_logger.error("Unexpected exception (will retry until timeout) ", e);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
if(tunnelUrl != null && !tunnelUrl.isEmpty() && tunnelSession != null && !tunnelSession.isEmpty()) {
ConsoleProxyAuthenticationResult authResult = ConsoleProxy.reAuthenticationExternally(getClientParam());
if(authResult != null && authResult.isSuccess()) {
if(authResult.getTunnelUrl() != null && !authResult.getTunnelUrl().isEmpty() &&
authResult.getTunnelSession() != null && !authResult.getTunnelSession().isEmpty()) {
tunnelUrl = authResult.getTunnelUrl();
tunnelSession = authResult.getTunnelSession();
s_logger.info("Reset XAPI session. url: " + tunnelUrl + ", session: " + tunnelSession);
}
}
}
}
s_logger.info("Receiver thread stopped.");
workerDone = true;
client.getClientListener().onClientClose();
}
});
worker.setDaemon(true);
worker.start();
}
@Override
public void closeClient() {
if(client != null)
client.shutdown();
}
@Override
public void onClientConnected() {
}
public void onClientClose() {
s_logger.info("Received client close indication. remove viewer from map.");
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 :
sendModifierEvents(modifiers);
client.sendClientKeyboardEvent(RfbConstants.KEY_DOWN, code, 0);
break;
case KEY_UP :
client.sendClientKeyboardEvent(RfbConstants.KEY_UP, code, 0);
sendModifierEvents(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();
if (event == InputEventType.MOUSE_DOWN) {
if (code == 2) {
lastPointerMask |= 4;
} else if (code == 0) {
lastPointerMask |= 1;
}
}
if (event == InputEventType.MOUSE_UP) {
if (code == 2) {
lastPointerMask ^= 4;
} else if (code == 0) {
lastPointerMask ^= 1;
}
}
sendModifierEvents(modifiers);
client.sendClientMouseEvent(lastPointerMask, x, y, code, modifiers);
if(lastPointerMask == 0)
sendModifierEvents(0);
}
@Override
protected FrameBufferCanvas getFrameBufferCavas() {
if(client != null)
return client.getFrameBufferCanvas();
return null;
}
private void sendModifierEvents(int modifiers) {
if((modifiers & SHIFT_KEY_MASK) != (lastModifierStates & SHIFT_KEY_MASK))
client.sendClientKeyboardEvent((modifiers & SHIFT_KEY_MASK) != 0 ? RfbConstants.KEY_DOWN : RfbConstants.KEY_UP, X11_KEY_SHIFT, 0);
if((modifiers & CTRL_KEY_MASK) != (lastModifierStates & CTRL_KEY_MASK))
client.sendClientKeyboardEvent((modifiers & CTRL_KEY_MASK) != 0 ? RfbConstants.KEY_DOWN : RfbConstants.KEY_UP, X11_KEY_CTRL, 0);
if((modifiers & META_KEY_MASK) != (lastModifierStates & META_KEY_MASK))
client.sendClientKeyboardEvent((modifiers & META_KEY_MASK) != 0 ? RfbConstants.KEY_DOWN : RfbConstants.KEY_UP, X11_KEY_META, 0);
if((modifiers & ALT_KEY_MASK) != (lastModifierStates & ALT_KEY_MASK))
client.sendClientKeyboardEvent((modifiers & ALT_KEY_MASK) != 0 ? RfbConstants.KEY_DOWN : RfbConstants.KEY_UP, X11_KEY_ALT, 0);
lastModifierStates = modifiers;
}
}