You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by bf...@apache.org on 2013/02/13 23:04:21 UTC

[23/50] [abbrv] Moved console-proxy into services

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/scripts/ssvm-check.sh
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/scripts/ssvm-check.sh b/services/console-proxy/server/scripts/ssvm-check.sh
new file mode 100644
index 0000000..a401164
--- /dev/null
+++ b/services/console-proxy/server/scripts/ssvm-check.sh
@@ -0,0 +1,136 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+ 
+
+# Health check script for the Secondary Storage VM
+
+# DNS server is specified.
+
+
+CMDLINE=/var/cache/cloud/cmdline
+for i in `cat $CMDLINE`
+do
+   key=`echo $i | cut -d= -f1`
+   value=`echo $i | cut -d= -f2`
+   case $key in
+      host)
+         MGMTSERVER=$value       
+         ;;
+   esac
+done
+
+
+# ping dns server
+echo ================================================
+DNSSERVER=`egrep '^nameserver' /etc/resolv.conf  | awk '{print $2}'| head -1`
+echo "First DNS server is " $DNSSERVER
+ping -c 2  $DNSSERVER
+if [ $? -eq 0 ]
+then
+    echo "Good: Can ping DNS server"
+else
+    echo "WARNING: cannot ping DNS server"
+    echo "route follows"
+    route -n
+fi
+
+
+# check dns resolve
+echo ================================================
+nslookup download.cloud.com 1> /tmp/dns 2>&1
+grep 'no servers could' /tmp/dns 1> /dev/null 2>&1
+if [ $? -eq 0 ]
+then
+    echo "ERROR: DNS not resolving download.cloud.com"
+    echo resolv.conf follows
+    cat /etc/resolv.conf
+    exit 2
+else
+    echo "Good: DNS resolves download.cloud.com"
+fi
+
+
+# check to see if we have the NFS volume mounted
+echo ================================================
+mount|grep -v sunrpc|grep nfs 1> /dev/null 2>&1
+if [ $? -eq 0 ]
+then
+    echo "NFS is currently mounted"
+    # check for write access
+    for MOUNTPT in `mount|grep -v sunrpc|grep nfs| awk '{print $3}'`
+    do
+        if [ $MOUNTPT != "/proc/xen" ] # mounted by xen
+        then
+            echo Mount point is $MOUNTPT
+            touch $MOUNTPT/foo
+            if [ $? -eq 0 ]
+            then
+                echo "Good: Can write to mount point"
+                rm $MOUNTPT/foo
+            else
+                echo "ERROR: Cannot write to mount point"
+                echo "You need to export with norootsquash"
+            fi
+        fi
+     done
+else
+    echo "ERROR: NFS is not currently mounted"
+    echo "Try manually mounting from inside the VM"
+    NFSSERVER=`awk '{print $17}' $CMDLINE|awk -F= '{print $2}'|awk -F: '{print $1}'`
+    echo "NFS server is " $NFSSERVER
+    ping -c 2  $NFSSERVER
+    if [ $? -eq 0 ]
+    then
+	echo "Good: Can ping NFS server"
+    else
+	echo "WARNING: cannot ping NFS server"
+	echo routing table follows
+	route -n
+    fi
+fi
+
+
+# check for connectivity to the management server
+echo ================================================
+echo Management server is $MGMTSERVER.  Checking connectivity.
+socatout=$(echo | socat - TCP:$MGMTSERVER:8250,connect-timeout=3 2>&1)
+if [ $? -eq 0 ]
+then
+    echo "Good: Can connect to management server port 8250"
+else
+    echo "ERROR: Cannot connect to $MGMTSERVER port 8250"
+    echo $socatout
+    exit 4
+fi
+
+
+# check for the java process running
+echo ================================================
+ps -eaf|grep -v grep|grep java 1> /dev/null 2>&1
+if [ $? -eq 0 ]
+then
+    echo "Good: Java process is running"
+else
+    echo "ERROR: Java process not running.  Try restarting the SSVM."
+    exit 3
+fi
+
+echo ================================================
+echo Tests Complete.  Look for ERROR or WARNING above.  
+
+exit 0

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/AjaxFIFOImageCache.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/AjaxFIFOImageCache.java b/services/console-proxy/server/src/com/cloud/consoleproxy/AjaxFIFOImageCache.java
new file mode 100644
index 0000000..cff00b3
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/AjaxFIFOImageCache.java
@@ -0,0 +1,83 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.cloud.consoleproxy.util.Logger;
+
+public class AjaxFIFOImageCache {
+    private static final Logger s_logger = Logger.getLogger(AjaxFIFOImageCache.class);
+    
+    private List<Integer> fifoQueue;
+    private Map<Integer, byte[]> cache;
+    private int cacheSize;
+    private int nextKey = 0;
+    
+    public AjaxFIFOImageCache(int cacheSize) {
+        this.cacheSize = cacheSize;
+        fifoQueue = new ArrayList<Integer>();
+        cache = new HashMap<Integer, byte[]>();
+    }
+    
+    public synchronized void clear() {
+        fifoQueue.clear();
+        cache.clear();
+    }
+    
+    public synchronized int putImage(byte[] image) {
+        while(cache.size() >= cacheSize) {
+            Integer keyToRemove = fifoQueue.remove(0);
+            cache.remove(keyToRemove);
+            
+            if(s_logger.isTraceEnabled())
+                s_logger.trace("Remove image from cache, key: " + keyToRemove);
+        }
+        
+        int key = getNextKey();
+        
+        if(s_logger.isTraceEnabled())
+            s_logger.trace("Add image to cache, key: " + key);
+        
+        cache.put(key, image);
+        fifoQueue.add(key);
+        return key;
+    }
+    
+    public synchronized byte[] getImage(int key) {
+        if (key == 0) {
+            key = nextKey;
+        }
+        if (cache.containsKey(key)) {
+            if (s_logger.isTraceEnabled())
+                s_logger.trace("Retrieve image from cache, key: " + key);
+
+            return cache.get(key);
+        }
+
+        if (s_logger.isTraceEnabled())
+            s_logger.trace("Image is no long in cache, key: " + key);
+        return null;
+    }
+
+    public synchronized int getNextKey() {
+        return ++nextKey;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/AuthenticationException.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/AuthenticationException.java b/services/console-proxy/server/src/com/cloud/consoleproxy/AuthenticationException.java
new file mode 100644
index 0000000..3fa2667
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/AuthenticationException.java
@@ -0,0 +1,33 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy;
+
+public class AuthenticationException extends Exception {
+    private static final long serialVersionUID = -393139302884898842L;
+    public AuthenticationException() {
+        super();
+    }
+    public AuthenticationException(String s) {
+        super(s);
+    }
+    public AuthenticationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+     public AuthenticationException(Throwable cause) {
+         super(cause);
+     }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxy.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxy.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxy.java
new file mode 100644
index 0000000..a722d83
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxy.java
@@ -0,0 +1,499 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy;
+
+import java.io.File;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetSocketAddress;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.Executor;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.log4j.xml.DOMConfigurator;
+
+import com.cloud.consoleproxy.util.Logger;
+import com.google.gson.Gson;
+import com.sun.net.httpserver.HttpServer;
+
+/**
+ * 
+ * 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.encodeBase64String(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();
+        authResult.setSuccess(true);
+        authResult.setReauthentication(reauthentication);
+        authResult.setHost(param.getClientHostAddress());
+        authResult.setPort(param.getClientHostPort());
+        
+        if(standaloneStart) {
+            return authResult;
+        }
+        
+        if(authMethod != null) {
+            Object result;
+            try {
+                result = authMethod.invoke(ConsoleProxy.context, 
+                    param.getClientHostAddress(), 
+                    String.valueOf(param.getClientHostPort()), 
+                    param.getClientTag(), 
+                    param.getClientHostPassword(), 
+                    param.getTicket(),
+                    new Boolean(reauthentication));
+            } catch (IllegalAccessException e) {
+                s_logger.error("Unable to invoke authenticateConsoleAccess due to IllegalAccessException" + " for vm: " + param.getClientTag(), e);
+                authResult.setSuccess(false);
+                return authResult;
+            } catch (InvocationTargetException e) {
+                s_logger.error("Unable to invoke authenticateConsoleAccess due to InvocationTargetException " + " for vm: " + param.getClientTag(), e);
+                authResult.setSuccess(false);
+                return authResult;
+            }
+            
+            if(result != null && result instanceof String) {
+                authResult = new Gson().fromJson((String)result, ConsoleProxyAuthenticationResult.class);
+            } else {
+                s_logger.error("Invalid authentication return object " + result + " for vm: " + param.getClientTag() + ", decline the access");
+                authResult.setSuccess(false);
+            }
+        } else {
+            s_logger.warn("Private channel towards management server is not setup. Switch to offline mode and allow access to vm: " + param.getClientTag());
+        }
+        
+        return authResult;
+    }
+    
+    public static void reportLoadInfo(String gsonLoadInfo) {
+        if(reportMethod != null) {
+            try {
+                reportMethod.invoke(ConsoleProxy.context, gsonLoadInfo);
+            } catch (IllegalAccessException e) {
+                s_logger.error("Unable to invoke reportLoadInfo due to " + e.getMessage());
+            } catch (InvocationTargetException e) {
+                s_logger.error("Unable to invoke reportLoadInfo due to " + e.getMessage());
+            }
+        } else {
+            s_logger.warn("Private channel towards management server is not setup. Switch to offline mode and ignore load report");
+        }
+    }
+    
+    public static void ensureRoute(String address) {
+        if(ensureRouteMethod != null) {
+            try {
+                ensureRouteMethod.invoke(ConsoleProxy.context, address);
+            } catch (IllegalAccessException e) {
+                s_logger.error("Unable to invoke ensureRoute due to " + e.getMessage());
+            } catch (InvocationTargetException e) {
+                s_logger.error("Unable to invoke ensureRoute due to " + e.getMessage());
+            }
+        } else {
+            s_logger.warn("Unable to find ensureRoute method, console proxy agent is not up to date");
+        }
+    }
+
+    public static void startWithContext(Properties conf, Object context, byte[] ksBits, String ksPassword) {
+        s_logger.info("Start console proxy with context");
+        if(conf != null) {
+            for(Object key : conf.keySet()) {
+                s_logger.info("Context property " + (String)key + ": " + conf.getProperty((String)key));
+            }
+        }
+        
+        configLog4j();
+        Logger.setFactory(new ConsoleProxyLoggerFactory());
+        
+        // Using reflection to setup private/secure communication channel towards management server
+        ConsoleProxy.context = context;
+        ConsoleProxy.ksBits = ksBits;
+        ConsoleProxy.ksPassword = ksPassword;
+        try {
+            Class<?> contextClazz = Class.forName("com.cloud.agent.resource.consoleproxy.ConsoleProxyResource");
+            authMethod = contextClazz.getDeclaredMethod("authenticateConsoleAccess", String.class, String.class, String.class, String.class, String.class, Boolean.class);
+            reportMethod = contextClazz.getDeclaredMethod("reportLoadInfo", String.class);
+            ensureRouteMethod = contextClazz.getDeclaredMethod("ensureRoute", String.class);
+        } catch (SecurityException e) {
+            s_logger.error("Unable to setup private channel due to SecurityException", e);
+        } catch (NoSuchMethodException e) {
+            s_logger.error("Unable to setup private channel due to NoSuchMethodException", e);
+        } catch (IllegalArgumentException e) {
+            s_logger.error("Unable to setup private channel due to IllegalArgumentException", e);
+        } catch(ClassNotFoundException e) {
+            s_logger.error("Unable to setup private channel due to ClassNotFoundException", e);
+        }
+        
+        // merge properties from conf file
+        InputStream confs = ConsoleProxy.class.getResourceAsStream("/conf/consoleproxy.properties");
+        Properties props = new Properties();
+        if (confs == null) {
+            s_logger.info("Can't load consoleproxy.properties from classpath, will use default configuration");
+        } else {
+            try {
+                props.load(confs);
+                
+                for(Object key : props.keySet()) {
+                    // give properties passed via context high priority, treat properties from consoleproxy.properties
+                    // as default values
+                    if(conf.get(key) == null)
+                        conf.put(key, props.get(key));
+                }
+            }  catch (Exception e) {
+                s_logger.error(e.toString(), e);
+            }
+        }
+        
+        start(conf);
+    }
+
+    public static void start(Properties conf) {
+        System.setProperty("java.awt.headless", "true");
+        
+        configProxy(conf);
+        
+        ConsoleProxyServerFactory factory = getHttpServerFactory();
+        if(factory == null) {
+            s_logger.error("Unable to load console proxy server factory");
+            System.exit(1);
+        }
+        
+        if(httpListenPort != 0) {
+            startupHttpMain();
+        } else {
+            s_logger.error("A valid HTTP server port is required to be specified, please check your consoleproxy.httpListenPort settings");
+            System.exit(1);
+        }
+        
+        if(httpCmdListenPort > 0) {
+            startupHttpCmdPort();
+        } else {
+            s_logger.info("HTTP command port is disabled");
+        }
+        
+        ConsoleProxyGCThread cthread = new ConsoleProxyGCThread(connectionMap);
+        cthread.setName("Console Proxy GC Thread");
+        cthread.start();
+    }
+    
+    private static void startupHttpMain() {
+        try {
+            ConsoleProxyServerFactory factory = getHttpServerFactory();
+            if(factory == null) {
+                s_logger.error("Unable to load HTTP server factory");
+                System.exit(1);
+            }
+            
+            HttpServer server = factory.createHttpServerInstance(httpListenPort);
+            server.createContext("/getscreen", new ConsoleProxyThumbnailHandler());
+            server.createContext("/resource/", new ConsoleProxyResourceHandler());
+            server.createContext("/ajax", new ConsoleProxyAjaxHandler());
+            server.createContext("/ajaximg", new ConsoleProxyAjaxImageHandler());
+            server.setExecutor(new ThreadExecutor()); // creates a default executor
+            server.start();
+        } catch(Exception e) {
+            s_logger.error(e.getMessage(), e);
+            System.exit(1);
+        }
+    }
+    
+    private static void startupHttpCmdPort() {
+        try {
+            s_logger.info("Listening for HTTP CMDs on port " + httpCmdListenPort);
+            HttpServer cmdServer = HttpServer.create(new InetSocketAddress(httpCmdListenPort), 2);
+            cmdServer.createContext("/cmd", new ConsoleProxyCmdHandler());
+            cmdServer.setExecutor(new ThreadExecutor()); // creates a default executor
+            cmdServer.start();
+        } catch(Exception e) {
+            s_logger.error(e.getMessage(), e);
+            System.exit(1);
+        }
+    }
+    
+    public static void main(String[] argv) {
+        standaloneStart = true;
+        configLog4j();
+        Logger.setFactory(new ConsoleProxyLoggerFactory());
+        
+        InputStream confs = ConsoleProxy.class.getResourceAsStream("/conf/consoleproxy.properties");
+        Properties conf = new Properties();
+        if (confs == null) {
+            s_logger.info("Can't load consoleproxy.properties from classpath, will use default configuration");
+        } else {
+            try {
+                conf.load(confs);
+            }  catch (Exception e) {
+                s_logger.error(e.toString(), e);
+            }
+        }
+        start(conf);
+    }
+    
+    public static ConsoleProxyClient getVncViewer(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();
+         }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyAjaxHandler.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyAjaxHandler.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyAjaxHandler.java
new file mode 100644
index 0000000..6cadeca
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyAjaxHandler.java
@@ -0,0 +1,406 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.URLDecoder;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.cloud.consoleproxy.util.Logger;
+import com.sun.net.httpserver.Headers;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+
+public class ConsoleProxyAjaxHandler implements HttpHandler {
+    private static final Logger s_logger = Logger.getLogger(ConsoleProxyAjaxHandler.class);
+    
+    public ConsoleProxyAjaxHandler() {
+    }
+    
+    public void handle(HttpExchange t) throws IOException {
+        try {
+            if(s_logger.isTraceEnabled())
+                s_logger.trace("AjaxHandler " + t.getRequestURI());
+            
+            long startTick = System.currentTimeMillis();
+            
+            doHandle(t);
+            
+            if(s_logger.isTraceEnabled())
+                s_logger.trace(t.getRequestURI() + " process time " + (System.currentTimeMillis() - startTick) + " ms");
+        } catch (IOException e) {
+            throw e;
+        } catch (IllegalArgumentException e) {
+            s_logger.warn("Exception, ", e);
+            t.sendResponseHeaders(400, -1);     // bad request
+        } catch(Throwable e) {
+            s_logger.error("Unexpected exception, ", e);
+            t.sendResponseHeaders(500, -1);     // server error
+        } finally {
+            t.close();
+        }
+    }
+    
+    private void doHandle(HttpExchange t) throws Exception, IllegalArgumentException {
+        String queries = t.getRequestURI().getQuery();
+        if(s_logger.isTraceEnabled())
+            s_logger.trace("Handle AJAX request: " + queries);
+        
+        Map<String, String> queryMap = ConsoleProxyHttpHandlerHelper.getQueryMap(queries);
+        
+        String host = queryMap.get("host");
+        String portStr = queryMap.get("port");
+        String sid = queryMap.get("sid");
+        String tag = queryMap.get("tag");
+        String ticket = queryMap.get("ticket");
+        String ajaxSessionIdStr = queryMap.get("sess");
+        String eventStr = queryMap.get("event");
+        String console_url = queryMap.get("consoleurl");
+        String console_host_session = queryMap.get("sessionref");
+        
+        if(tag == null)
+            tag = "";
+        
+        long ajaxSessionId = 0;
+        int event = 0;
+        
+        int port;
+
+        if(host == null || portStr == null || sid == null) 
+            throw new IllegalArgumentException();
+        
+        try {
+            port = Integer.parseInt(portStr);
+        } catch (NumberFormatException e) {
+            s_logger.warn("Invalid number parameter in query string: " + portStr);
+            throw new IllegalArgumentException(e);
+        }
+
+        if(ajaxSessionIdStr != null) {
+            try {
+                ajaxSessionId = Long.parseLong(ajaxSessionIdStr);
+            } catch (NumberFormatException e) {
+                s_logger.warn("Invalid number parameter in query string: " + ajaxSessionIdStr);
+                throw new IllegalArgumentException(e);
+            }
+        }
+
+        if(eventStr != null) {
+            try {
+                event = Integer.parseInt(eventStr);
+            } catch (NumberFormatException e) {
+                s_logger.warn("Invalid number parameter in query string: " + eventStr);
+                throw new IllegalArgumentException(e);
+            }
+        }
+        
+        ConsoleProxyClient viewer = null;
+        try {
+            ConsoleProxyClientParam param = new ConsoleProxyClientParam();
+            param.setClientHostAddress(host);
+            param.setClientHostPort(port);
+            param.setClientHostPassword(sid);
+            param.setClientTag(tag);
+            param.setTicket(ticket);
+            param.setClientTunnelUrl(console_url);
+            param.setClientTunnelSession(console_host_session);
+            
+            viewer = ConsoleProxy.getAjaxVncViewer(param, ajaxSessionIdStr);
+        } catch(Exception e) {
+
+            s_logger.warn("Failed to create viewer due to " + e.getMessage(), e);
+
+            String[] content = new String[] {
+                "<html><head></head><body>",
+                "<div id=\"main_panel\" tabindex=\"1\">",
+                "<p>Access is denied for the console session. Please close the window and retry again</p>",
+                "</div></body></html>"
+            };
+            
+            StringBuffer sb = new StringBuffer();
+            for(int i = 0; i < content.length; i++)
+                sb.append(content[i]);
+            
+            sendResponse(t, "text/html", sb.toString());
+            return;
+        }
+        
+        if(event != 0) {
+            if(ajaxSessionId != 0 && ajaxSessionId == viewer.getAjaxSessionId()) {
+                if(event == 7) {
+                    // client send over an event bag
+                    InputStream is = t.getRequestBody();
+                    handleClientEventBag(viewer, convertStreamToString(is, true));
+                } else {
+                    handleClientEvent(viewer, event, queryMap);
+                }
+                sendResponse(t, "text/html", "OK");
+            } else {
+                if(s_logger.isDebugEnabled())
+                    s_logger.debug("Ajax request comes from a different session, id in request: " + ajaxSessionId + ", id in viewer: " + viewer.getAjaxSessionId());
+                
+                sendResponse(t, "text/html", "Invalid ajax client session id");
+            }
+        } else {
+            if(ajaxSessionId != 0 && ajaxSessionId != viewer.getAjaxSessionId()) {
+                s_logger.info("Ajax request comes from a different session, id in request: " + ajaxSessionId + ", id in viewer: " + viewer.getAjaxSessionId());
+                handleClientKickoff(t, viewer);
+            } else if(ajaxSessionId == 0) {
+                if(s_logger.isDebugEnabled())
+                    s_logger.debug("Ajax request indicates a fresh client start");
+        
+                String title = queryMap.get("t");
+                String guest = queryMap.get("guest");
+                handleClientStart(t, viewer, title != null ? title : "", guest);
+            } else {
+                
+                if(s_logger.isTraceEnabled())
+                    s_logger.trace("Ajax request indicates client update");
+                
+                handleClientUpdate(t, viewer);
+            }
+        }
+    }
+    
+    private static String convertStreamToString(InputStream is, boolean closeStreamAfterRead) { 
+        BufferedReader reader = new BufferedReader(new InputStreamReader(is)); 
+        StringBuilder sb = new StringBuilder(); 
+        String line = null; 
+        try { 
+            while ((line = reader.readLine()) != null) { 
+                sb.append(line + "\n"); 
+            } 
+        } catch (IOException e) {
+            s_logger.warn("Exception while reading request body: ", e);
+        } finally {
+            if(closeStreamAfterRead) {
+                try { 
+                    is.close(); 
+                } catch (IOException e) { 
+                } 
+            }
+        } 
+        return sb.toString(); 
+    }   
+    
+    private void sendResponse(HttpExchange t, String contentType, String response) throws IOException {
+        Headers hds = t.getResponseHeaders();
+        hds.set("Content-Type", contentType);
+    
+        t.sendResponseHeaders(200, response.length());
+        OutputStream os = t.getResponseBody();
+        try {
+            os.write(response.getBytes());
+        } finally {
+            os.close();
+        }
+    }
+    
+    @SuppressWarnings("deprecation")
+    private void handleClientEventBag(ConsoleProxyClient viewer, String requestData) {
+        if(s_logger.isTraceEnabled())
+            s_logger.trace("Handle event bag, event bag: " + requestData);
+        
+        int start = requestData.indexOf("=");
+        if(start < 0)
+            start = 0;
+        else if(start > 0)
+            start++;
+        String data = URLDecoder.decode(requestData.substring(start));
+        String[] tokens = data.split("\\|");
+        if(tokens != null && tokens.length > 0) {
+            int count = 0;
+            try {
+                count = Integer.parseInt(tokens[0]);
+                int parsePos = 1;
+                int type, event, x, y, code, modifiers;
+                for(int i = 0; i < count; i++) {
+                    type = Integer.parseInt(tokens[parsePos++]);
+                    if(type == 1)   {
+                        // mouse event
+                        event = Integer.parseInt(tokens[parsePos++]);
+                        x = Integer.parseInt(tokens[parsePos++]);
+                        y = Integer.parseInt(tokens[parsePos++]);
+                        code = Integer.parseInt(tokens[parsePos++]);
+                        modifiers = Integer.parseInt(tokens[parsePos++]);
+                        
+                        Map<String, String> queryMap = new HashMap<String, String>();
+                        queryMap.put("event", String.valueOf(event));
+                        queryMap.put("x", String.valueOf(x));
+                        queryMap.put("y", String.valueOf(y));
+                        queryMap.put("code", String.valueOf(code));
+                        queryMap.put("modifier", String.valueOf(modifiers));
+                        handleClientEvent(viewer, event, queryMap);
+                    } else {
+                        // keyboard event
+                        event = Integer.parseInt(tokens[parsePos++]);
+                        code = Integer.parseInt(tokens[parsePos++]);
+                        modifiers = Integer.parseInt(tokens[parsePos++]);
+                        
+                        Map<String, String> queryMap = new HashMap<String, String>();
+                        queryMap.put("event", String.valueOf(event));
+                        queryMap.put("code", String.valueOf(code));
+                        queryMap.put("modifier", String.valueOf(modifiers));
+                        handleClientEvent(viewer, event, queryMap);
+                    }
+                }
+            } catch(NumberFormatException e) {
+                s_logger.warn("Exception in handle client event bag: " + data + ", ", e);
+            } catch(Exception e) {
+                s_logger.warn("Exception in handle client event bag: " + data + ", ", e);
+            } catch(OutOfMemoryError e) {
+                s_logger.error("Unrecoverable OutOfMemory Error, exit and let it be re-launched");
+                System.exit(1);
+            }
+        }
+    }
+    
+    private void handleClientEvent(ConsoleProxyClient viewer, int event, Map<String, String> queryMap) {
+        int code = 0;
+        int x = 0, y = 0;
+        int modifiers = 0;
+        
+        String str;
+        switch(event) {
+        case 1:     // mouse move
+        case 2:     // mouse down
+        case 3:     // mouse up
+        case 8:     // mouse double click
+            str = queryMap.get("x");
+            if(str != null) {
+                try {
+                    x = Integer.parseInt(str);
+                } catch (NumberFormatException e) {
+                    s_logger.warn("Invalid number parameter in query string: " + str);
+                    throw new IllegalArgumentException(e);
+                }
+            }
+            str = queryMap.get("y");
+            if(str != null) {
+                try {
+                    y = Integer.parseInt(str);
+                } catch (NumberFormatException e) {
+                    s_logger.warn("Invalid number parameter in query string: " + str);
+                    throw new IllegalArgumentException(e);
+                }
+            }
+            
+            if(event != 1) {
+                str = queryMap.get("code");
+                try {
+                    code = Integer.parseInt(str);
+                } catch (NumberFormatException e) {
+                    s_logger.warn("Invalid number parameter in query string: " + str);
+                    throw new IllegalArgumentException(e);
+                }
+                
+                str = queryMap.get("modifier");
+                try {
+                    modifiers = Integer.parseInt(str);
+                } catch (NumberFormatException e) {
+                    s_logger.warn("Invalid number parameter in query string: " + str);
+                    throw new IllegalArgumentException(e);
+                }
+                
+                if(s_logger.isTraceEnabled())
+                    s_logger.trace("Handle client mouse event. event: " + event + ", x: " + x + ", y: " + y + ", button: " + code + ", modifier: " + modifiers);
+            } else {
+                if(s_logger.isTraceEnabled())
+                    s_logger.trace("Handle client mouse move event. x: " + x + ", y: " + y);
+            }
+            viewer.sendClientMouseEvent(InputEventType.fromEventCode(event), x, y, code, modifiers);
+            break;
+            
+        case 4:     // key press
+        case 5:     // key down
+        case 6:     // key up
+            str = queryMap.get("code");
+            try {
+                code = Integer.parseInt(str);
+            } catch (NumberFormatException e) {
+                s_logger.warn("Invalid number parameter in query string: " + str);
+                throw new IllegalArgumentException(e);
+            }
+            
+            str = queryMap.get("modifier");
+            try {
+                modifiers = Integer.parseInt(str);
+            } catch (NumberFormatException e) {
+                s_logger.warn("Invalid number parameter in query string: " + str);
+                throw new IllegalArgumentException(e);
+            }
+            
+            if(s_logger.isDebugEnabled())
+                s_logger.debug("Handle client keyboard event. event: " + event + ", code: " + code + ", modifier: " + modifiers);
+            viewer.sendClientRawKeyboardEvent(InputEventType.fromEventCode(event), code, modifiers);
+            break;
+            
+        default :
+            break;
+        }
+    }
+    
+    private void handleClientKickoff(HttpExchange t, ConsoleProxyClient viewer) throws IOException {
+        String response = viewer.onAjaxClientKickoff();
+        t.sendResponseHeaders(200, response.length());
+        OutputStream os = t.getResponseBody();
+        try {
+            os.write(response.getBytes());
+        } finally {
+            os.close();
+        }
+    }
+    
+    private void handleClientStart(HttpExchange t, ConsoleProxyClient viewer, String title, String guest) throws IOException {
+        List<String> languages = t.getRequestHeaders().get("Accept-Language");
+        String response = viewer.onAjaxClientStart(title, languages, guest);
+        
+        Headers hds = t.getResponseHeaders();
+        hds.set("Content-Type", "text/html");
+        hds.set("Cache-Control", "no-cache");
+        hds.set("Cache-Control", "no-store");
+        t.sendResponseHeaders(200, response.length());
+        
+        OutputStream os = t.getResponseBody();
+        try {
+            os.write(response.getBytes());
+        } finally {
+            os.close();
+        }
+    }
+    
+    private void handleClientUpdate(HttpExchange t, ConsoleProxyClient viewer) throws IOException {
+        String response = viewer.onAjaxClientUpdate();
+        
+        Headers hds = t.getResponseHeaders();
+        hds.set("Content-Type", "text/javascript");
+        t.sendResponseHeaders(200, response.length());
+        
+        OutputStream os = t.getResponseBody();
+        try {
+            os.write(response.getBytes());
+        } finally {
+            os.close();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyAjaxImageHandler.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyAjaxImageHandler.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyAjaxImageHandler.java
new file mode 100644
index 0000000..5e10149
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyAjaxImageHandler.java
@@ -0,0 +1,159 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy;
+
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Map;
+
+import com.cloud.consoleproxy.util.Logger;
+import com.sun.net.httpserver.Headers;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+
+public class ConsoleProxyAjaxImageHandler implements HttpHandler {
+    private static final Logger s_logger = Logger.getLogger(ConsoleProxyAjaxImageHandler.class);
+
+    public void handle(HttpExchange t) throws IOException {
+        try {
+            if(s_logger.isDebugEnabled())
+                s_logger.debug("AjaxImageHandler " + t.getRequestURI());
+            
+            long startTick = System.currentTimeMillis();
+            
+            doHandle(t);
+            
+            if(s_logger.isDebugEnabled())
+                s_logger.debug(t.getRequestURI() + "Process time " + (System.currentTimeMillis() - startTick) + " ms");
+        } catch (IOException e) {
+            throw e;
+        } catch (IllegalArgumentException e) {
+            s_logger.warn("Exception, ", e);
+            t.sendResponseHeaders(400, -1);     // bad request
+        } catch(OutOfMemoryError e) {
+            s_logger.error("Unrecoverable OutOfMemory Error, exit and let it be re-launched");
+            System.exit(1);
+        } catch(Throwable e) {
+            s_logger.error("Unexpected exception, ", e);
+            t.sendResponseHeaders(500, -1);     // server error
+        } finally {
+            t.close();
+        }
+    }
+
+    private void doHandle(HttpExchange t) throws Exception, IllegalArgumentException {
+        String queries = t.getRequestURI().getQuery();
+        Map<String, String> queryMap = ConsoleProxyHttpHandlerHelper.getQueryMap(queries);
+        
+        String host = queryMap.get("host");
+        String portStr = queryMap.get("port");
+        String sid = queryMap.get("sid");
+        String tag = queryMap.get("tag");
+        String ticket = queryMap.get("ticket");
+        String keyStr = queryMap.get("key");
+        String console_url = queryMap.get("consoleurl");
+        String console_host_session = queryMap.get("sessionref");
+                String w = queryMap.get("w");           
+                String h = queryMap.get("h");
+        
+                int key = 0;
+                int width = 144;
+                int height = 110;
+        
+        if(tag == null)
+            tag = "";
+        
+        int port;
+        if(host == null || portStr == null || sid == null)
+            throw new IllegalArgumentException();
+        
+        try {
+            port = Integer.parseInt(portStr);
+        } catch (NumberFormatException e) {
+            s_logger.warn("Invalid numeric parameter in query string: " + portStr);
+            throw new IllegalArgumentException(e);
+        }
+        
+        try {
+                    if (keyStr != null)
+                        key = Integer.parseInt(keyStr);
+                    if(null != w)
+                       width = Integer.parseInt(w);
+
+                    if(null != h)
+                       height = Integer.parseInt(h);
+
+             } catch (NumberFormatException e) {
+            s_logger.warn("Invalid numeric parameter in query string: " + keyStr);
+            throw new IllegalArgumentException(e);
+        }
+
+        ConsoleProxyClientParam param = new ConsoleProxyClientParam();
+        param.setClientHostAddress(host);
+        param.setClientHostPort(port);
+        param.setClientHostPassword(sid);
+        param.setClientTag(tag);
+        param.setTicket(ticket);
+        param.setClientTunnelUrl(console_url);
+        param.setClientTunnelSession(console_host_session);
+
+        ConsoleProxyClient viewer = ConsoleProxy.getVncViewer(param);
+
+        if (key == 0) {
+            Image scaledImage = viewer.getClientScaledImage(width, height);
+            BufferedImage bufferedImage = new BufferedImage(width, height,
+                    BufferedImage.TYPE_3BYTE_BGR);
+            Graphics2D bufImageGraphics = bufferedImage.createGraphics();
+            bufImageGraphics.drawImage(scaledImage, 0, 0, null);
+            ByteArrayOutputStream bos = new ByteArrayOutputStream(8196);
+            javax.imageio.ImageIO.write(bufferedImage, "jpg", bos);
+            byte[] bs = bos.toByteArray();
+            Headers hds = t.getResponseHeaders();
+            hds.set("Content-Type", "image/jpeg");
+            hds.set("Cache-Control", "no-cache");
+            hds.set("Cache-Control", "no-store");
+            t.sendResponseHeaders(200, bs.length);
+            OutputStream os = t.getResponseBody();
+            os.write(bs);
+            os.close();
+        } else {
+            AjaxFIFOImageCache imageCache = viewer.getAjaxImageCache();
+                    byte[] img = imageCache.getImage(key);
+    
+            if(img != null) {
+                Headers hds = t.getResponseHeaders();
+                hds.set("Content-Type", "image/jpeg");
+                t.sendResponseHeaders(200, img.length);
+                
+                OutputStream os = t.getResponseBody();
+                try {
+                    os.write(img, 0, img.length);
+                } finally {
+                    os.close();
+                }
+            } else {
+                if(s_logger.isInfoEnabled())
+                    s_logger.info("Image has already been swept out, key: " + key);
+                t.sendResponseHeaders(404, -1);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyAuthenticationResult.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyAuthenticationResult.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyAuthenticationResult.java
new file mode 100644
index 0000000..26ee9b3
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyAuthenticationResult.java
@@ -0,0 +1,81 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy;
+
+// duplicated class
+public class ConsoleProxyAuthenticationResult {
+    private boolean success;
+    private boolean isReauthentication;
+    private String host;
+    private int port;
+    private String tunnelUrl;
+    private String tunnelSession;
+    
+    public ConsoleProxyAuthenticationResult() {
+        success = false;
+        isReauthentication = false;
+        port = 0;
+    }
+
+    public boolean isSuccess() {
+        return success;
+    }
+
+    public void setSuccess(boolean success) {
+        this.success = success;
+    }
+
+    public boolean isReauthentication() {
+        return isReauthentication;
+    }
+
+    public void setReauthentication(boolean isReauthentication) {
+        this.isReauthentication = isReauthentication;
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    public void setPort(int port) {
+        this.port = port;
+    }
+
+    public String getTunnelUrl() {
+        return tunnelUrl;
+    }
+
+    public void setTunnelUrl(String tunnelUrl) {
+        this.tunnelUrl = tunnelUrl;
+    }
+
+    public String getTunnelSession() {
+        return tunnelSession;
+    }
+
+    public void setTunnelSession(String tunnelSession) {
+        this.tunnelSession = tunnelSession;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyBaseServerFactoryImpl.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyBaseServerFactoryImpl.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyBaseServerFactoryImpl.java
new file mode 100644
index 0000000..c9ad8ab
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyBaseServerFactoryImpl.java
@@ -0,0 +1,48 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+
+import javax.net.ssl.SSLServerSocket;
+
+import com.cloud.consoleproxy.util.Logger;
+import com.sun.net.httpserver.HttpServer;
+
+public class ConsoleProxyBaseServerFactoryImpl implements ConsoleProxyServerFactory {
+    private static final Logger s_logger = Logger.getLogger(ConsoleProxyBaseServerFactoryImpl.class);
+    
+    @Override
+    public void init(byte[] ksBits, String ksPassword) {
+    }
+    
+    @Override
+    public HttpServer createHttpServerInstance(int port) throws IOException {
+        if(s_logger.isInfoEnabled())
+            s_logger.info("create HTTP server instance at port: " + port);
+        return HttpServer.create(new InetSocketAddress(port), 5);
+    }
+    
+    @Override
+    public SSLServerSocket createSSLServerSocket(int port) throws IOException {
+        if(s_logger.isInfoEnabled())
+            s_logger.info("SSL server socket is not supported in ConsoleProxyBaseServerFactoryImpl");
+        
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClient.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClient.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClient.java
new file mode 100644
index 0000000..8a0be05
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClient.java
@@ -0,0 +1,69 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy;
+
+import java.awt.Image;
+import java.util.List;
+
+/**
+ * ConsoleProxyClient defines an standard interface that a console client should implement,
+ * 
+ * ConsoleProxyClient maintains a session towards the target host, it glues the session
+ * to a AJAX front-end viewer 
+ */
+public interface ConsoleProxyClient {
+    int getClientId();
+    
+    //
+    // Quick status
+    //
+    boolean isHostConnected();
+    boolean isFrontEndAlive();
+
+    //
+    // AJAX viewer
+    //
+    long getAjaxSessionId();
+    AjaxFIFOImageCache getAjaxImageCache();
+    Image getClientScaledImage(int width, int height);                  // client thumbnail support
+    
+    String onAjaxClientStart(String title, List<String> languages, String guest);
+    String onAjaxClientUpdate();
+    String onAjaxClientKickoff();
+
+    //
+    // Input handling
+    //
+    void sendClientRawKeyboardEvent(InputEventType event, int code, int modifiers);
+    void sendClientMouseEvent(InputEventType event, int x, int y, int code, int modifiers);
+
+    //
+    // Info/Stats
+    //
+    long getClientCreateTime();
+    long getClientLastFrontEndActivityTime();
+    String getClientHostAddress();
+    int getClientHostPort();
+    String getClientHostPassword();
+    String getClientTag();
+
+    //
+    // Setup/house-keeping
+    //
+    void initClient(ConsoleProxyClientParam param);
+    void closeClient();
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientBase.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientBase.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientBase.java
new file mode 100644
index 0000000..289bdab
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientBase.java
@@ -0,0 +1,457 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package 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;
+
+/**
+ * 
+ * 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/ajaxkeys.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);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientListener.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientListener.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientListener.java
new file mode 100644
index 0000000..43a0bab
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientListener.java
@@ -0,0 +1,25 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy;
+
+public interface ConsoleProxyClientListener {
+    void onFramebufferSizeChange(int w, int h);
+    void onFramebufferUpdate(int x, int y, int w, int h);
+
+    void onClientConnected();
+    void onClientClose();
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientParam.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientParam.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientParam.java
new file mode 100644
index 0000000..8de4955
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientParam.java
@@ -0,0 +1,110 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy;
+
+/**
+ * 
+ * Data object to store parameter info needed by client to connect to its host
+ */
+public class ConsoleProxyClientParam {
+    
+    private String clientHostAddress;
+    private int clientHostPort; 
+    private String clientHostPassword;
+    private String clientTag;
+    private String ticket;
+    
+    private String clientTunnelUrl;
+    private String clientTunnelSession;
+    
+    private String ajaxSessionId;
+    
+    public ConsoleProxyClientParam() {
+        clientHostPort = 0;
+    }
+
+    public String getClientHostAddress() {
+        return clientHostAddress;
+    }
+
+    public void setClientHostAddress(String clientHostAddress) {
+        this.clientHostAddress = clientHostAddress;
+    }
+
+    public int getClientHostPort() {
+        return clientHostPort;
+    }
+
+    public void setClientHostPort(int clientHostPort) {
+        this.clientHostPort = clientHostPort;
+    }
+
+    public String getClientHostPassword() {
+        return clientHostPassword;
+    }
+
+    public void setClientHostPassword(String clientHostPassword) {
+        this.clientHostPassword = clientHostPassword;
+    }
+
+    public String getClientTag() {
+        return clientTag;
+    }
+
+    public void setClientTag(String clientTag) {
+        this.clientTag = clientTag;
+    }
+
+    public String getTicket() {
+        return ticket;
+    }
+
+    public void setTicket(String ticket) {
+        this.ticket = ticket;
+    }
+    
+    public String getClientTunnelUrl() {
+        return clientTunnelUrl;
+    }
+
+    public void setClientTunnelUrl(String clientTunnelUrl) {
+        this.clientTunnelUrl = clientTunnelUrl;
+    }
+
+    public String getClientTunnelSession() {
+        return clientTunnelSession;
+    }
+
+    public void setClientTunnelSession(String clientTunnelSession) {
+        this.clientTunnelSession = clientTunnelSession;
+    }
+    
+    public String getAjaxSessionId() {
+        return this.ajaxSessionId;
+    }
+    
+    public void setAjaxSessionId(String ajaxSessionId) {
+        this.ajaxSessionId = ajaxSessionId;
+    }
+
+    public String getClientMapKey() {
+        if(clientTag != null && !clientTag.isEmpty())
+            return clientTag;
+        
+        return clientHostAddress + ":" + clientHostPort;
+    }
+}