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
[22/50] [abbrv] Moved console-proxy into services
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientStatsCollector.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientStatsCollector.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientStatsCollector.java
new file mode 100644
index 0000000..15cf451
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientStatsCollector.java
@@ -0,0 +1,88 @@
+// 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.OutputStreamWriter;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+/**
+ *
+ * ConsoleProxyClientStatsCollector collects client stats for console proxy agent to report
+ */
+public class ConsoleProxyClientStatsCollector {
+
+ ArrayList<ConsoleProxyConnection> connections;
+
+ public ConsoleProxyClientStatsCollector() {
+ }
+
+ public ConsoleProxyClientStatsCollector(Hashtable<String, ConsoleProxyClient> connMap) {
+ setConnections(connMap);
+ }
+
+ public String getStatsReport() {
+ Gson gson = new GsonBuilder().setPrettyPrinting().create();
+ return gson.toJson(this);
+ }
+
+ public void getStatsReport(OutputStreamWriter os) {
+ Gson gson = new GsonBuilder().setPrettyPrinting().create();
+ gson.toJson(this, os);
+ }
+
+ private void setConnections(Hashtable<String, ConsoleProxyClient> connMap) {
+
+ ArrayList<ConsoleProxyConnection> conns = new ArrayList<ConsoleProxyConnection>();
+ Enumeration<String> e = connMap.keys();
+ while (e.hasMoreElements()) {
+ synchronized (connMap) {
+ String key = e.nextElement();
+ ConsoleProxyClient client = connMap.get(key);
+
+ ConsoleProxyConnection conn = new ConsoleProxyConnection();
+
+ conn.id = client.getClientId();
+ conn.clientInfo = "";
+ conn.host = client.getClientHostAddress();
+ conn.port = client.getClientHostPort();
+ conn.tag = client.getClientTag();
+ conn.createTime = client.getClientCreateTime();
+ conn.lastUsedTime = client.getClientLastFrontEndActivityTime();
+ conns.add(conn);
+ }
+ }
+ connections = conns;
+ }
+
+ public static class ConsoleProxyConnection {
+ public int id;
+ public String clientInfo;
+ public String host;
+ public int port;
+ public String tag;
+ public long createTime;
+ public long lastUsedTime;
+
+ public ConsoleProxyConnection() {
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyCmdHandler.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyCmdHandler.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyCmdHandler.java
new file mode 100644
index 0000000..408eb04
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyCmdHandler.java
@@ -0,0 +1,70 @@
+// 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.io.OutputStream;
+import java.io.OutputStreamWriter;
+
+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 ConsoleProxyCmdHandler implements HttpHandler {
+ private static final Logger s_logger = Logger.getLogger(ConsoleProxyCmdHandler.class);
+
+ public void handle(HttpExchange t) throws IOException {
+ try {
+ Thread.currentThread().setName("Cmd Thread " +
+ Thread.currentThread().getId() + " " + t.getRemoteAddress());
+ s_logger.info("CmdHandler " + t.getRequestURI());
+ doHandle(t);
+ } catch (Exception e) {
+ s_logger.error(e.toString(), e);
+ String response = "Not found";
+ t.sendResponseHeaders(404, response.length());
+ OutputStream os = t.getResponseBody();
+ os.write(response.getBytes());
+ os.close();
+ } 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(e.toString(), e);
+ } finally {
+ t.close();
+ }
+ }
+
+ public void doHandle(HttpExchange t) throws Exception {
+ String path = t.getRequestURI().getPath();
+ int i = path.indexOf("/", 1);
+ String cmd = path.substring(i + 1);
+ s_logger.info("Get CMD request for " + cmd);
+ if (cmd.equals("getstatus")) {
+ ConsoleProxyClientStatsCollector statsCollector = ConsoleProxy.getStatsCollector();
+
+ Headers hds = t.getResponseHeaders();
+ hds.set("Content-Type", "text/plain");
+ t.sendResponseHeaders(200, 0);
+ OutputStreamWriter os = new OutputStreamWriter(t.getResponseBody());
+ statsCollector.getStatsReport(os);
+ os.close();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyGCThread.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyGCThread.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyGCThread.java
new file mode 100644
index 0000000..7f82a96
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyGCThread.java
@@ -0,0 +1,109 @@
+// 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.util.Enumeration;
+import java.util.Hashtable;
+
+import org.apache.log4j.Logger;
+
+/**
+ *
+ * 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
+ * management software
+ */
+public class ConsoleProxyGCThread extends Thread {
+ private static final Logger s_logger = Logger.getLogger(ConsoleProxyGCThread.class);
+
+ private final static int MAX_SESSION_IDLE_SECONDS = 180;
+
+ private Hashtable<String, ConsoleProxyClient> connMap;
+ private long lastLogScan = 0;
+
+ public ConsoleProxyGCThread(Hashtable<String, ConsoleProxyClient> connMap) {
+ this.connMap = connMap;
+ }
+
+ private void cleanupLogging() {
+ if(lastLogScan != 0 && System.currentTimeMillis() - lastLogScan < 3600000)
+ return;
+
+ lastLogScan = System.currentTimeMillis();
+
+ File logDir = new File("./logs");
+ File files[] = logDir.listFiles();
+ if(files != null) {
+ for(File file : files) {
+ if(System.currentTimeMillis() - file.lastModified() >= 86400000L) {
+ try {
+ file.delete();
+ } catch(Throwable e) {
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+
+ boolean bReportLoad = false;
+ while (true) {
+ cleanupLogging();
+ bReportLoad = false;
+
+ if(s_logger.isDebugEnabled())
+ s_logger.debug("connMap=" + connMap);
+ Enumeration<String> e = connMap.keys();
+ while (e.hasMoreElements()) {
+ String key;
+ ConsoleProxyClient client;
+
+ synchronized (connMap) {
+ key = e.nextElement();
+ client = connMap.get(key);
+ }
+
+ long seconds_unused = (System.currentTimeMillis() - client.getClientLastFrontEndActivityTime()) / 1000;
+ if (seconds_unused < MAX_SESSION_IDLE_SECONDS) {
+ continue;
+ }
+
+ synchronized (connMap) {
+ connMap.remove(key);
+ bReportLoad = true;
+ }
+
+ // close the server connection
+ s_logger.info("Dropping " + client + " which has not been used for " + seconds_unused + " seconds");
+ client.closeClient();
+ }
+
+ if(bReportLoad) {
+ // report load changes
+ String loadInfo = new ConsoleProxyClientStatsCollector(connMap).getStatsReport();
+ ConsoleProxy.reportLoadInfo(loadInfo);
+ if(s_logger.isDebugEnabled())
+ s_logger.debug("Report load change : " + loadInfo);
+ }
+
+ try { Thread.sleep(5000); } catch (InterruptedException ex) {}
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java
new file mode 100644
index 0000000..7756d01
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java
@@ -0,0 +1,74 @@
+// 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.HashMap;
+import java.util.Map;
+
+import com.cloud.consoleproxy.util.Logger;
+
+public class ConsoleProxyHttpHandlerHelper {
+ private static final Logger s_logger = Logger.getLogger(ConsoleProxyHttpHandlerHelper.class);
+
+ public static Map<String, String> getQueryMap(String query) {
+ String[] params = query.split("&");
+ Map<String, String> map = new HashMap<String, String>();
+ for (String param : params) {
+ String[] paramTokens = param.split("=");
+ if(paramTokens != null && paramTokens.length == 2) {
+ String name = param.split("=")[0];
+ String value = param.split("=")[1];
+ map.put(name, value);
+ } else if (paramTokens.length == 3) {
+ // very ugly, added for Xen tunneling url
+ String name = paramTokens[0];
+ String value = paramTokens[1] + "=" + paramTokens[2];
+ map.put(name, value);
+ } else {
+ if(s_logger.isDebugEnabled())
+ s_logger.debug("Invalid paramemter in URL found. param: " + param);
+ }
+ }
+
+ // This is a ugly solution for now. We will do encryption/decryption translation
+ // here to make it transparent to rest of the code.
+ if(map.get("token") != null) {
+ ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(
+ ConsoleProxy.getEncryptorPassword());
+
+ ConsoleProxyClientParam param = encryptor.decryptObject(ConsoleProxyClientParam.class, map.get("token"));
+ if(param != null) {
+ if(param.getClientHostAddress() != null)
+ map.put("host", param.getClientHostAddress());
+ if(param.getClientHostPort() != 0)
+ map.put("port", String.valueOf(param.getClientHostPort()));
+ if(param.getClientTag() != null)
+ map.put("tag", param.getClientTag());
+ if(param.getClientHostPassword() != null)
+ map.put("sid", param.getClientHostPassword());
+ if(param.getClientTunnelUrl() != null)
+ map.put("consoleurl", param.getClientTunnelUrl());
+ if(param.getClientTunnelSession() != null)
+ map.put("sessionref", param.getClientTunnelSession());
+ if(param.getTicket() != null)
+ map.put("ticket", param.getTicket());
+ }
+ }
+
+ return map;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyLoggerFactory.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyLoggerFactory.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyLoggerFactory.java
new file mode 100644
index 0000000..ff66de3
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyLoggerFactory.java
@@ -0,0 +1,89 @@
+// 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 com.cloud.consoleproxy.util.Logger;
+import com.cloud.consoleproxy.util.LoggerFactory;
+
+public class ConsoleProxyLoggerFactory implements LoggerFactory {
+ public ConsoleProxyLoggerFactory() {
+ }
+
+ public Logger getLogger(Class<?> clazz) {
+ return new Log4jLogger(org.apache.log4j.Logger.getLogger(clazz));
+ }
+
+ public static class Log4jLogger extends Logger {
+ private org.apache.log4j.Logger logger;
+
+ public Log4jLogger(org.apache.log4j.Logger logger) {
+ this.logger = logger;
+ }
+
+ public boolean isTraceEnabled() {
+ return logger.isTraceEnabled();
+ }
+
+ public boolean isDebugEnabled() {
+ return logger.isDebugEnabled();
+ }
+
+ public boolean isInfoEnabled() {
+ return logger.isInfoEnabled();
+ }
+
+ public void trace(Object message) {
+ logger.trace(message);
+ }
+
+ public void trace(Object message, Throwable exception) {
+ logger.trace(message, exception);
+ }
+
+ public void info(Object message) {
+ logger.info(message);
+ }
+
+ public void info(Object message, Throwable exception) {
+ logger.info(message, exception);
+ }
+
+ public void debug(Object message) {
+ logger.debug(message);
+ }
+
+ public void debug(Object message, Throwable exception) {
+ logger.debug(message, exception);
+ }
+
+ public void warn(Object message) {
+ logger.warn(message);
+ }
+
+ public void warn(Object message, Throwable exception) {
+ logger.warn(message, exception);
+ }
+
+ public void error(Object message) {
+ logger.error(message);
+ }
+
+ public void error(Object message, Throwable exception) {
+ logger.error(message, exception);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyMonitor.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyMonitor.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyMonitor.java
new file mode 100644
index 0000000..030b2f4
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyMonitor.java
@@ -0,0 +1,153 @@
+// 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.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.log4j.xml.DOMConfigurator;
+
+import com.cloud.consoleproxy.util.Logger;
+
+
+//
+//
+// I switched to a simpler solution to monitor only unrecoverable exceptions, under these cases, console proxy process will exit
+// itself and the shell script will re-launch console proxy
+//
+public class ConsoleProxyMonitor {
+ private static final Logger s_logger = Logger.getLogger(ConsoleProxyMonitor.class);
+
+ private String[] _argv;
+ private Map<String, String> _argMap = new HashMap<String, String>();
+
+ private volatile Process _process;
+ private boolean _quit = false;
+
+ public ConsoleProxyMonitor(String[] argv) {
+ _argv = argv;
+
+ for(String arg : _argv) {
+ String[] tokens = arg.split("=");
+ if(tokens.length == 2) {
+ s_logger.info("Add argument " + tokens[0] + "=" + tokens[1] + " to the argument map");
+
+ _argMap.put(tokens[0].trim(), tokens[1].trim());
+ } else {
+ s_logger.warn("unrecognized argument, skip adding it to argument map");
+ }
+ }
+ }
+
+ private void run() {
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ @Override
+ public void run() {
+ _quit = true;
+ onShutdown();
+ }
+ });
+
+ while(!_quit) {
+ String cmdLine = getLaunchCommandLine();
+
+ s_logger.info("Launch console proxy process with command line: " + cmdLine);
+
+ try {
+ _process = Runtime.getRuntime().exec(cmdLine);
+ } catch (IOException e) {
+ s_logger.error("Unexpected exception ", e);
+ System.exit(1);
+ }
+
+ boolean waitSucceeded = false;
+ int exitCode = 0;
+ while(!waitSucceeded) {
+ try {
+ exitCode = _process.waitFor();
+ waitSucceeded = true;
+
+ if(s_logger.isInfoEnabled())
+ s_logger.info("Console proxy process exits with code: " + exitCode);
+ } catch (InterruptedException e) {
+ if(s_logger.isInfoEnabled())
+ s_logger.info("InterruptedException while waiting for termination of console proxy, will retry");
+ }
+ }
+ }
+ }
+
+ private String getLaunchCommandLine() {
+ StringBuffer sb = new StringBuffer("java ");
+ String jvmOptions = _argMap.get("jvmoptions");
+
+ if(jvmOptions != null)
+ sb.append(jvmOptions);
+
+ for(Map.Entry<String, String> entry : _argMap.entrySet()) {
+ if(!"jvmoptions".equalsIgnoreCase(entry.getKey()))
+ sb.append(" ").append(entry.getKey()).append("=").append(entry.getValue());
+ }
+
+ return sb.toString();
+ }
+
+ private void onShutdown() {
+ if(_process != null) {
+ if(s_logger.isInfoEnabled())
+ s_logger.info("Console proxy monitor shuts dwon, terminate console proxy process");
+ _process.destroy();
+ }
+ }
+
+ 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");
+ }
+ } else {
+ System.out.println("Configure log4j with default properties");
+ }
+ }
+
+ public static void main(String[] argv) {
+ configLog4j();
+ (new ConsoleProxyMonitor(argv)).run();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyPasswordBasedEncryptor.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyPasswordBasedEncryptor.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyPasswordBasedEncryptor.java
new file mode 100644
index 0000000..29826f0
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyPasswordBasedEncryptor.java
@@ -0,0 +1,142 @@
+// 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.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.log4j.Logger;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+/**
+ *
+ * A simple password based encyrptor based on DES. It can serialize simple POJO object into URL safe string
+ * and deserialize it back.
+ *
+ */
+public class ConsoleProxyPasswordBasedEncryptor {
+ private static final Logger s_logger = Logger.getLogger(ConsoleProxyPasswordBasedEncryptor.class);
+
+ private String password;
+ private Gson gson;
+
+ public ConsoleProxyPasswordBasedEncryptor(String password) {
+ this.password = password;
+ gson = new GsonBuilder().create();
+ }
+
+ public String encryptText(String text) {
+ if(text == null || text.isEmpty())
+ return text;
+
+ assert(password != null);
+ assert(!password.isEmpty());
+
+ try {
+ Cipher cipher = Cipher.getInstance("DES");
+ int maxKeySize = 8;
+ SecretKeySpec keySpec = new SecretKeySpec(normalizeKey(password.getBytes(), maxKeySize), "DES");
+ cipher.init(Cipher.ENCRYPT_MODE, keySpec);
+ byte[] encryptedBytes = cipher.doFinal(text.getBytes());
+ return Base64.encodeBase64URLSafeString(encryptedBytes);
+ } catch (NoSuchAlgorithmException e) {
+ s_logger.error("Unexpected exception ", e);
+ return null;
+ } catch (NoSuchPaddingException e) {
+ s_logger.error("Unexpected exception ", e);
+ return null;
+ } catch (IllegalBlockSizeException e) {
+ s_logger.error("Unexpected exception ", e);
+ return null;
+ } catch (BadPaddingException e) {
+ s_logger.error("Unexpected exception ", e);
+ return null;
+ } catch (InvalidKeyException e) {
+ s_logger.error("Unexpected exception ", e);
+ return null;
+ }
+ }
+
+ public String decryptText(String encryptedText) {
+ if(encryptedText == null || encryptedText.isEmpty())
+ return encryptedText;
+
+ assert(password != null);
+ assert(!password.isEmpty());
+
+ try {
+ Cipher cipher = Cipher.getInstance("DES");
+ int maxKeySize = 8;
+ SecretKeySpec keySpec = new SecretKeySpec(normalizeKey(password.getBytes(), maxKeySize), "DES");
+ cipher.init(Cipher.DECRYPT_MODE, keySpec);
+
+ byte[] encryptedBytes = Base64.decodeBase64(encryptedText);
+ return new String(cipher.doFinal(encryptedBytes));
+ } catch (NoSuchAlgorithmException e) {
+ s_logger.error("Unexpected exception ", e);
+ return null;
+ } catch (NoSuchPaddingException e) {
+ s_logger.error("Unexpected exception ", e);
+ return null;
+ } catch (IllegalBlockSizeException e) {
+ s_logger.error("Unexpected exception ", e);
+ return null;
+ } catch (BadPaddingException e) {
+ s_logger.error("Unexpected exception ", e);
+ return null;
+ } catch (InvalidKeyException e) {
+ s_logger.error("Unexpected exception ", e);
+ return null;
+ }
+ }
+
+ public <T> String encryptObject(Class<?> clz, T obj) {
+ if(obj == null)
+ return null;
+
+ String json = gson.toJson(obj);
+ return encryptText(json);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> T decryptObject(Class<?> clz, String encrypted) {
+ if(encrypted == null || encrypted.isEmpty())
+ return null;
+
+ String json = decryptText(encrypted);
+ return (T)gson.fromJson(json, clz);
+ }
+
+ private static byte[] normalizeKey(byte[] keyBytes, int keySize) {
+ assert(keySize > 0);
+ byte[] key = new byte[keySize];
+
+ for(int i = 0; i < keyBytes.length; i++)
+ key[i%keySize] ^= keyBytes[i];
+
+ return key;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyResourceHandler.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyResourceHandler.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyResourceHandler.java
new file mode 100644
index 0000000..7d16047
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyResourceHandler.java
@@ -0,0 +1,181 @@
+// 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.FileInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Date;
+import java.util.HashMap;
+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 ConsoleProxyResourceHandler implements HttpHandler {
+ private static final Logger s_logger = Logger.getLogger(ConsoleProxyResourceHandler.class);
+
+ static Map<String, String> s_mimeTypes;
+ static {
+ s_mimeTypes = new HashMap<String, String>();
+ s_mimeTypes.put("jar", "application/java-archive");
+ s_mimeTypes.put("js", "text/javascript");
+ s_mimeTypes.put("css", "text/css");
+ s_mimeTypes.put("jpg", "image/jpeg");
+ s_mimeTypes.put("html", "text/html");
+ s_mimeTypes.put("htm", "text/html");
+ s_mimeTypes.put("log", "text/plain");
+ }
+
+ static Map<String, String> s_validResourceFolders;
+ static {
+ s_validResourceFolders = new HashMap<String, String>();
+ s_validResourceFolders.put("applet", "");
+ s_validResourceFolders.put("logs", "");
+ s_validResourceFolders.put("images", "");
+ s_validResourceFolders.put("js", "");
+ s_validResourceFolders.put("css", "");
+ s_validResourceFolders.put("html", "");
+ }
+
+ public ConsoleProxyResourceHandler() {
+ }
+
+ public void handle(HttpExchange t) throws IOException {
+ try {
+ if(s_logger.isDebugEnabled())
+ s_logger.debug("Resource Handler " + 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(Throwable e) {
+ s_logger.error("Unexpected exception, ", e);
+ t.sendResponseHeaders(500, -1); // server error
+ } finally {
+ t.close();
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private void doHandle(HttpExchange t) throws Exception {
+ String path = t.getRequestURI().getPath();
+
+ if(s_logger.isInfoEnabled())
+ s_logger.info("Get resource request for " + path);
+
+ int i = path.indexOf("/", 1);
+ String filepath = path.substring(i + 1);
+ i = path.lastIndexOf(".");
+ String extension = (i == -1) ? "" : path.substring(i + 1);
+ String contentType = getContentType(extension);
+
+ if(!validatePath(filepath)) {
+ if(s_logger.isInfoEnabled())
+ s_logger.info("Resource access is forbidden, uri: " + path);
+
+ t.sendResponseHeaders(403, -1); // forbidden
+ return;
+ }
+
+ File f = new File ("./" + filepath);
+ if(f.exists()) {
+ long lastModified = f.lastModified();
+ String ifModifiedSince = t.getRequestHeaders().getFirst("If-Modified-Since");
+ if (ifModifiedSince != null) {
+ long d = Date.parse(ifModifiedSince);
+ if (d + 1000 >= lastModified) {
+ Headers hds = t.getResponseHeaders();
+ hds.set("Content-Type", contentType);
+ t.sendResponseHeaders(304, -1);
+
+ if(s_logger.isInfoEnabled())
+ s_logger.info("Sent 304 file has not been " +
+ "modified since " + ifModifiedSince);
+ return;
+ }
+ }
+
+ long length = f.length();
+ Headers hds = t.getResponseHeaders();
+ hds.set("Content-Type", contentType);
+ hds.set("Last-Modified", new Date(lastModified).toGMTString());
+ t.sendResponseHeaders(200, length);
+ responseFileContent(t, f);
+
+ if(s_logger.isInfoEnabled())
+ s_logger.info("Sent file " + path + " with content type " + contentType);
+ } else {
+ if(s_logger.isInfoEnabled())
+ s_logger.info("file does not exist" + path);
+ t.sendResponseHeaders(404, -1);
+ }
+ }
+
+ private static String getContentType(String extension) {
+ String key = extension.toLowerCase();
+ if(s_mimeTypes.containsKey(key)) {
+ return s_mimeTypes.get(key);
+ }
+ return "application/octet-stream";
+ }
+
+ private static void responseFileContent(HttpExchange t, File f) throws Exception {
+ OutputStream os = t.getResponseBody();
+ FileInputStream fis = new FileInputStream(f);
+ while (true) {
+ byte[] b = new byte[8192];
+ int n = fis.read(b);
+ if (n < 0) {
+ break;
+ }
+ os.write(b, 0, n);
+ }
+ fis.close();
+ os.close();
+ }
+
+ private static boolean validatePath(String path) {
+ int i = path.indexOf("/");
+ if(i == -1) {
+ if(s_logger.isInfoEnabled())
+ s_logger.info("Invalid resource path: can not start at resource root");
+ return false;
+ }
+
+ if(path.contains("..")) {
+ if(s_logger.isInfoEnabled())
+ s_logger.info("Invalid resource path: contains relative up-level navigation");
+
+ return false;
+ }
+
+ return isValidResourceFolder(path.substring(0, i));
+ }
+
+ private static boolean isValidResourceFolder(String name) {
+ return s_validResourceFolders.containsKey(name);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxySecureServerFactoryImpl.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxySecureServerFactoryImpl.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxySecureServerFactoryImpl.java
new file mode 100644
index 0000000..ee0ee13
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxySecureServerFactoryImpl.java
@@ -0,0 +1,145 @@
+// 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.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.security.KeyStore;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.TrustManagerFactory;
+
+import org.apache.log4j.Logger;
+
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsParameters;
+import com.sun.net.httpserver.HttpsServer;
+
+public class ConsoleProxySecureServerFactoryImpl implements ConsoleProxyServerFactory {
+ private static final Logger s_logger = Logger.getLogger(ConsoleProxySecureServerFactoryImpl.class);
+
+ private SSLContext sslContext = null;
+
+ public ConsoleProxySecureServerFactoryImpl() {
+ }
+
+ @Override
+ public void init(byte[] ksBits, String ksPassword) {
+ s_logger.info("Start initializing SSL");
+
+ if(ksBits == null) {
+ try {
+ s_logger.info("Initializing SSL from built-in default certificate");
+
+ char[] passphrase = "vmops.com".toCharArray();
+ KeyStore ks = KeyStore.getInstance("JKS");
+
+ ks.load(new FileInputStream("certs/realhostip.keystore"), passphrase);
+ // ks.load(ConsoleProxy.class.getResourceAsStream("/realhostip.keystore"), passphrase);
+
+ s_logger.info("SSL certificate loaded");
+
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+ kmf.init(ks, passphrase);
+ s_logger.info("Key manager factory is initialized");
+
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
+ tmf.init(ks);
+ s_logger.info("Trust manager factory is initialized");
+
+ sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+ s_logger.info("SSL context is initialized");
+ } catch (Exception ioe) {
+ s_logger.error(ioe.toString(), ioe);
+ }
+
+ } else {
+ char[] passphrase = ksPassword != null ? ksPassword.toCharArray() : null;
+ try {
+ s_logger.info("Initializing SSL from passed-in certificate");
+
+ KeyStore ks = KeyStore.getInstance("JKS");
+ ks.load(new ByteArrayInputStream(ksBits), passphrase);
+
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+ kmf.init(ks, passphrase);
+ s_logger.info("Key manager factory is initialized");
+
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
+ tmf.init(ks);
+ s_logger.info("Trust manager factory is initialized");
+
+ sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+ s_logger.info("SSL context is initialized");
+ } catch(Exception e) {
+ s_logger.error("Unable to init factory due to exception ", e);
+ }
+ }
+
+ }
+
+ public HttpServer createHttpServerInstance(int port) throws IOException {
+ try {
+ HttpsServer server = HttpsServer.create(new InetSocketAddress(port), 5);
+ server.setHttpsConfigurator (new HttpsConfigurator(sslContext) {
+ @Override
+ public void configure (HttpsParameters params) {
+
+ // get the remote address if needed
+ InetSocketAddress remote = params.getClientAddress();
+ SSLContext c = getSSLContext();
+
+ // get the default parameters
+ SSLParameters sslparams = c.getDefaultSSLParameters();
+
+ params.setSSLParameters(sslparams);
+ // statement above could throw IAE if any params invalid.
+ // eg. if app has a UI and parameters supplied by a user.
+ }
+ });
+
+ s_logger.info("create HTTPS server instance on port: " + port);
+ return server;
+ } catch (Exception ioe) {
+ s_logger.error(ioe.toString(), ioe);
+ }
+ return null;
+ }
+
+ public SSLServerSocket createSSLServerSocket(int port) throws IOException {
+ try {
+ SSLServerSocket srvSock = null;
+ SSLServerSocketFactory ssf = sslContext.getServerSocketFactory();
+ srvSock = (SSLServerSocket) ssf.createServerSocket(port);
+
+ s_logger.info("create SSL server socket on port: " + port);
+ return srvSock;
+ } catch (Exception ioe) {
+ s_logger.error(ioe.toString(), ioe);
+ }
+ return null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyServerFactory.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyServerFactory.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyServerFactory.java
new file mode 100644
index 0000000..7e0e5c7
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyServerFactory.java
@@ -0,0 +1,29 @@
+// 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 javax.net.ssl.SSLServerSocket;
+
+import com.sun.net.httpserver.HttpServer;
+
+public interface ConsoleProxyServerFactory {
+ void init(byte[] ksBits, String ksPassword);
+ HttpServer createHttpServerInstance(int port) throws IOException;
+ SSLServerSocket createSSLServerSocket(int port) throws IOException;
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyThumbnailHandler.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyThumbnailHandler.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyThumbnailHandler.java
new file mode 100644
index 0000000..6d34d3b
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyThumbnailHandler.java
@@ -0,0 +1,212 @@
+// 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.Color;
+import java.awt.Font;
+import java.awt.FontMetrics;
+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.HashMap;
+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 ConsoleProxyThumbnailHandler implements HttpHandler {
+ private static final Logger s_logger = Logger.getLogger(ConsoleProxyThumbnailHandler.class);
+
+ public ConsoleProxyThumbnailHandler() {
+ }
+
+ public void handle(HttpExchange t) throws IOException {
+ try {
+ Thread.currentThread().setName("JPG Thread " +
+ Thread.currentThread().getId() + " " + t.getRemoteAddress());
+
+ if(s_logger.isDebugEnabled())
+ s_logger.debug("ScreenHandler " + t.getRequestURI());
+
+ long startTick = System.currentTimeMillis();
+ doHandle(t);
+
+ if(s_logger.isDebugEnabled())
+ s_logger.debug(t.getRequestURI() + "Process time " + (System.currentTimeMillis() - startTick) + " ms");
+ } catch (IllegalArgumentException e) {
+ String response = "Bad query string";
+ s_logger.error(response + ", request URI : " + t.getRequestURI());
+ t.sendResponseHeaders(200, response.length());
+ OutputStream os = t.getResponseBody();
+ os.write(response.getBytes());
+ os.close();
+ } 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 while handing thumbnail request, ", e);
+
+ String queries = t.getRequestURI().getQuery();
+ Map<String, String> queryMap = getQueryMap(queries);
+ int width = 0;
+ int height = 0;
+ String ws = queryMap.get("w");
+ String hs = queryMap.get("h");
+ try {
+ width = Integer.parseInt(ws);
+ height = Integer.parseInt(hs);
+ } catch (NumberFormatException ex) {
+ }
+ width = Math.min(width, 800);
+ height = Math.min(height, 600);
+
+ BufferedImage img = generateTextImage(width, height, "Cannot Connect");
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(8196);
+ javax.imageio.ImageIO.write(img, "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();
+ s_logger.error("Cannot get console, sent error JPG response for " + t.getRequestURI());
+ return;
+ } finally {
+ t.close();
+ }
+ }
+
+ private void doHandle(HttpExchange t) throws Exception, IllegalArgumentException {
+ String queries = t.getRequestURI().getQuery();
+ Map<String, String> queryMap = getQueryMap(queries);
+ int width = 0;
+ int height = 0;
+ int port = 0;
+ String ws = queryMap.get("w");
+ String hs = queryMap.get("h");
+ 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 console_url = queryMap.get("consoleurl");
+ String console_host_session = queryMap.get("sessionref");
+
+ if(tag == null)
+ tag = "";
+
+ if (ws == null || hs == null || host == null || portStr == null || sid == null ) {
+ throw new IllegalArgumentException();
+ }
+ try {
+ width = Integer.parseInt(ws);
+ height = Integer.parseInt(hs);
+ port = Integer.parseInt(portStr);
+ } catch (NumberFormatException e) {
+ 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 (!viewer.isHostConnected()) {
+ // use generated image instead of static
+ BufferedImage img = generateTextImage(width, height, "Connecting");
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(8196);
+ javax.imageio.ImageIO.write(img, "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();
+
+ if(s_logger.isInfoEnabled())
+ s_logger.info("Console not ready, sent dummy JPG response");
+ return;
+ }
+
+ {
+ 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();
+ }
+ }
+
+ public static BufferedImage generateTextImage(int w, int h, String text) {
+ BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_3BYTE_BGR);
+ Graphics2D g = img.createGraphics();
+ g.setColor(Color.BLACK);
+ g.fillRect(0, 0, w, h);
+ g.setColor(Color.WHITE);
+ try {
+ g.setFont(new Font(null, Font.PLAIN, 12));
+ FontMetrics fm = g.getFontMetrics();
+ int textWidth = fm.stringWidth(text);
+ int startx = (w-textWidth) / 2;
+ if(startx < 0)
+ startx = 0;
+ g.drawString(text, startx, h/2);
+ } catch (Throwable e) {
+ s_logger.warn("Problem in generating text to thumnail image, return blank image");
+ }
+ return img;
+ }
+
+ public static Map<String, String> getQueryMap(String query) {
+ String[] params = query.split("&");
+ Map<String, String> map = new HashMap<String, String>();
+ for (String param : params) {
+ String name = param.split("=")[0];
+ String value = param.split("=")[1];
+ map.put(name, value);
+ }
+ return map;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyVncClient.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyVncClient.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyVncClient.java
new file mode 100644
index 0000000..6a473b5
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyVncClient.java
@@ -0,0 +1,235 @@
+// 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.URI;
+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;
+
+/**
+ *
+ * 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;
+ 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()) {
+ 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;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/InputEventType.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/InputEventType.java b/services/console-proxy/server/src/com/cloud/consoleproxy/InputEventType.java
new file mode 100644
index 0000000..4a5aff1
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/InputEventType.java
@@ -0,0 +1,58 @@
+// 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 enum InputEventType {
+ MOUSE_MOVE(1),
+ MOUSE_DOWN(2),
+ MOUSE_UP(3),
+ KEY_PRESS(4),
+ KEY_DOWN(5),
+ KEY_UP(6),
+ MOUSE_DBLCLICK(8);
+
+ int eventCode;
+ private InputEventType(int eventCode) {
+ this.eventCode = eventCode;
+ }
+
+ public int getEventCode() {
+ return eventCode;
+ }
+
+ public static InputEventType fromEventCode(int eventCode) {
+ switch(eventCode) {
+ case 1 :
+ return MOUSE_MOVE;
+ case 2 :
+ return MOUSE_DOWN;
+ case 3 :
+ return MOUSE_UP;
+ case 4 :
+ return KEY_PRESS;
+ case 5 :
+ return KEY_DOWN;
+ case 6 :
+ return KEY_UP;
+ case 8 :
+ return MOUSE_DBLCLICK;
+ default :
+ break;
+ }
+ throw new IllegalArgumentException("Unsupport event code: " + eventCode);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/util/ITileScanListener.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/util/ITileScanListener.java b/services/console-proxy/server/src/com/cloud/consoleproxy/util/ITileScanListener.java
new file mode 100644
index 0000000..2ff82d7
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/util/ITileScanListener.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.util;
+
+import java.awt.Rectangle;
+import java.util.List;
+
+public interface ITileScanListener {
+ boolean onTileChange(Rectangle rowMergedRect, int row, int col);
+ void onRegionChange(List<Region> regionList);
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/util/ImageHelper.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/util/ImageHelper.java b/services/console-proxy/server/src/com/cloud/consoleproxy/util/ImageHelper.java
new file mode 100644
index 0000000..bb7373e
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/util/ImageHelper.java
@@ -0,0 +1,32 @@
+// 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.util;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class ImageHelper {
+ public static byte[] jpegFromImage(BufferedImage image) throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(128000);
+ javax.imageio.ImageIO.write(image, "jpg", bos);
+
+ byte[] jpegBits = bos.toByteArray();
+ bos.close();
+ return jpegBits;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/util/Logger.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/util/Logger.java b/services/console-proxy/server/src/com/cloud/consoleproxy/util/Logger.java
new file mode 100644
index 0000000..f4357bd
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/util/Logger.java
@@ -0,0 +1,223 @@
+// 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.util;
+
+// logger facility for dynamic switch between console logger used in Applet and log4j based logger
+public class Logger {
+ private static LoggerFactory factory = null;
+
+ public static final int LEVEL_TRACE = 1;
+ public static final int LEVEL_DEBUG = 2;
+ public static final int LEVEL_INFO = 3;
+ public static final int LEVEL_WARN = 4;
+ public static final int LEVEL_ERROR = 5;
+
+ private Class<?> clazz;
+ private Logger logger;
+
+ private static int level = LEVEL_INFO;
+
+ public static Logger getLogger(Class<?> clazz) {
+ return new Logger(clazz);
+ }
+
+ public static void setFactory(LoggerFactory f) {
+ factory = f;
+ }
+
+ public static void setLevel(int l) {
+ level = l;
+ }
+
+ public Logger(Class<?> clazz) {
+ this.clazz = clazz;
+ }
+
+ protected Logger() {
+ }
+
+ public boolean isTraceEnabled() {
+ if(factory != null) {
+ if(logger == null)
+ logger = factory.getLogger(clazz);
+
+ return logger.isTraceEnabled();
+ }
+ return level <= LEVEL_TRACE;
+ }
+
+ public boolean isDebugEnabled() {
+ if(factory != null) {
+ if(logger == null)
+ logger = factory.getLogger(clazz);
+
+ return logger.isDebugEnabled();
+ }
+ return level <= LEVEL_DEBUG;
+ }
+
+ public boolean isInfoEnabled() {
+ if(factory != null) {
+ if(logger == null)
+ logger = factory.getLogger(clazz);
+
+ return logger.isInfoEnabled();
+ }
+ return level <= LEVEL_INFO;
+ }
+
+ public void trace(Object message) {
+
+ if(factory != null) {
+ if(logger == null)
+ logger = factory.getLogger(clazz);
+
+ logger.trace(message);
+ } else {
+ if(level <= LEVEL_TRACE)
+ System.out.println(message);
+ }
+ }
+
+ public void trace(Object message, Throwable exception) {
+ if(factory != null) {
+ if(logger == null)
+ logger = factory.getLogger(clazz);
+
+ logger.trace(message, exception);
+ } else {
+ if(level <= LEVEL_TRACE) {
+ System.out.println(message);
+ if (exception != null) {
+ exception.printStackTrace(System.out);
+ }
+ }
+ }
+ }
+
+ public void info(Object message) {
+ if(factory != null) {
+ if(logger == null)
+ logger = factory.getLogger(clazz);
+
+ logger.info(message);
+ } else {
+ if(level <= LEVEL_INFO)
+ System.out.println(message);
+ }
+ }
+
+ public void info(Object message, Throwable exception) {
+ if(factory != null) {
+ if(logger == null)
+ logger = factory.getLogger(clazz);
+
+ logger.info(message, exception);
+ } else {
+ if(level <= LEVEL_INFO) {
+ System.out.println(message);
+ if (exception != null) {
+ exception.printStackTrace(System.out);
+ }
+ }
+ }
+ }
+
+ public void debug(Object message) {
+ if(factory != null) {
+ if(logger == null)
+ logger = factory.getLogger(clazz);
+
+ logger.debug(message);
+ } else {
+ if(level <= LEVEL_DEBUG)
+ System.out.println(message);
+ }
+ }
+
+ public void debug(Object message, Throwable exception) {
+ if(factory != null) {
+ if(logger == null)
+ logger = factory.getLogger(clazz);
+
+ logger.debug(message, exception);
+ } else {
+ if(level <= LEVEL_DEBUG) {
+ System.out.println(message);
+ if (exception != null) {
+ exception.printStackTrace(System.out);
+ }
+ }
+ }
+ }
+
+ public void warn(Object message) {
+ if(factory != null) {
+ if(logger == null)
+ logger = factory.getLogger(clazz);
+
+ logger.warn(message);
+ } else {
+ if(level <= LEVEL_WARN)
+ System.out.println(message);
+ }
+ }
+
+ public void warn(Object message, Throwable exception) {
+ if(factory != null) {
+ if(logger == null)
+ logger = factory.getLogger(clazz);
+
+ logger.warn(message, exception);
+ } else {
+ if(level <= LEVEL_WARN) {
+ System.out.println(message);
+ if (exception != null) {
+ exception.printStackTrace(System.out);
+ }
+ }
+ }
+ }
+
+ public void error(Object message) {
+ if(factory != null) {
+ if(logger == null)
+ logger = factory.getLogger(clazz);
+
+ logger.error(message);
+ } else {
+ if(level <= LEVEL_ERROR)
+ System.out.println(message);
+ }
+ }
+
+ public void error(Object message, Throwable exception) {
+ if(factory != null) {
+ if(logger == null)
+ logger = factory.getLogger(clazz);
+
+ logger.error(message, exception);
+ } else {
+ if(level <= LEVEL_ERROR) {
+ System.out.println(message);
+ if (exception != null) {
+ exception.printStackTrace(System.out);
+ }
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/util/LoggerFactory.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/util/LoggerFactory.java b/services/console-proxy/server/src/com/cloud/consoleproxy/util/LoggerFactory.java
new file mode 100644
index 0000000..121411a
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/util/LoggerFactory.java
@@ -0,0 +1,21 @@
+// 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.util;
+
+public interface LoggerFactory {
+ Logger getLogger(Class<?> clazz);
+}