mirror of
https://github.com/apache/cloudstack.git
synced 2025-12-17 02:53:18 +01:00
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:
parent
89015cbf3c
commit
b25122bd4d
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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();
|
}
|
||||||
}
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@ -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() {
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@ -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) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user