http://git-wip-us.apache.org/repos/asf/ignite/blob/523a871b/modules/web-console/frontend/package.json ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/package.json b/modules/web-console/frontend/package.json index 6f8fe4f..a6299df 100644 --- a/modules/web-console/frontend/package.json +++ b/modules/web-console/frontend/package.json @@ -88,7 +88,7 @@ "json-loader": "0.5.7", "jsondiffpatch": "^0.2.5", "jszip": "3.1.5", - "lodash": "4.17.5", + "lodash": "4.17.10", "mini-css-extract-plugin": "^0.4.0", "natural-compare-lite": "^1.4.0", "node-sass": "^4.8.3", @@ -107,11 +107,11 @@ "svg-sprite-loader": "3.0.7", "tf-metatags": "2.0.0", "uglifyjs-webpack-plugin": "1.2.4", - "webpack": "4.5.0", + "webpack": "4.12.0", "webpack-cli": "2.0.14", - "webpack-dev-server": "3.1.1", - "webpack-merge": "4.1.2", - "worker-loader": "^1.1.1" + "webpack-dev-server": "3.1.4", + "webpack-merge": "4.1.3", + "worker-loader": "^2.0.0" }, "devDependencies": { "@types/angular": "^1.6.32", @@ -124,8 +124,9 @@ "@types/mocha": "^2.2.48", "@types/sinon": "^4.0.0", "@types/ui-grid": "0.0.38", - "@types/webpack": "^4.1.2", + "@types/webpack": "^4.4.1", "@types/webpack-merge": "^4.1.3", + "@types/socket.io-client": "1.4.32", "angular-mocks": "^1.6.9", "app-root-path": "2.0.1", "chai": "4.1.0",
http://git-wip-us.apache.org/repos/asf/ignite/blob/523a871b/modules/web-console/frontend/public/images/icons/exit.svg ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/public/images/icons/exit.svg b/modules/web-console/frontend/public/images/icons/exit.svg new file mode 100644 index 0000000..a355dcd --- /dev/null +++ b/modules/web-console/frontend/public/images/icons/exit.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="18" viewBox="0 0 16 18"> + <path fill="currentColor" fill-rule="nonzero" d="M15.723 8.354l-4.17-4.17a.633.633 0 0 0-.893.894l3.134 3.133H5.632a.632.632 0 0 0 0 1.264h8.162l-3.091 3.091a.632.632 0 0 0 .893.893l4.17-4.17a.605.605 0 0 0 .041-.045c.007-.007.011-.015.017-.023l.02-.027c.006-.01.01-.019.017-.028l.015-.025.013-.029c.005-.009.01-.018.013-.027l.01-.028.011-.03.007-.029.008-.031.005-.034.004-.027a.64.64 0 0 0-.004-.152l-.005-.033-.008-.032-.007-.029-.01-.03a.249.249 0 0 0-.01-.028l-.014-.027-.013-.029-.015-.025-.017-.028c-.006-.01-.013-.018-.02-.027l-.017-.023a.61.61 0 0 0-.065-.067.304.304 0 0 0-.019-.022zM1 0h8.614v1.374h-8.24V16.48h8.24v1.373H1a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1z"/> +</svg> http://git-wip-us.apache.org/repos/asf/ignite/blob/523a871b/modules/web-console/frontend/public/images/icons/index.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/public/images/icons/index.js b/modules/web-console/frontend/public/images/icons/index.js index 62b3ab8..5a8d851 100644 --- a/modules/web-console/frontend/public/images/icons/index.js +++ b/modules/web-console/frontend/public/images/icons/index.js @@ -37,5 +37,8 @@ export collapse from './collapse.svg'; export expand from './expand.svg'; export home from './home.svg'; export refresh from './refresh.svg'; -export {default as eyeOpened} from './eyeOpened.svg'; -export {default as eyeClosed} from './eyeClosed.svg'; +export eyeOpened from './eyeOpened.svg'; +export eyeClosed from './eyeClosed.svg'; +export lockOpened from './lockOpened.svg'; +export lockClosed from './lockClosed.svg'; +export exit from './exit.svg'; http://git-wip-us.apache.org/repos/asf/ignite/blob/523a871b/modules/web-console/frontend/public/images/icons/lockClosed.svg ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/public/images/icons/lockClosed.svg b/modules/web-console/frontend/public/images/icons/lockClosed.svg new file mode 100644 index 0000000..22f81e3 --- /dev/null +++ b/modules/web-console/frontend/public/images/icons/lockClosed.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="12" height="16" viewBox="0 0 12 16"> + <path fill="currentColor" fill-rule="nonzero" d="M5.714 10.808v1.483a.286.286 0 0 0 .572 0v-1.483a.858.858 0 1 0-.572 0zm-4-4.522v-2a4.285 4.285 0 1 1 8.572 0v2A1.719 1.719 0 0 1 12 8.006v5.703c0 .956-.77 1.72-1.72 1.72H1.72c-.951 0-1.72-.77-1.72-1.72V8.005c0-.954.767-1.717 1.714-1.72zm1.715 0H8.57v-2A2.574 2.574 0 0 0 6 1.714a2.57 2.57 0 0 0-2.571 2.572v2z"/> +</svg> http://git-wip-us.apache.org/repos/asf/ignite/blob/523a871b/modules/web-console/frontend/public/images/icons/lockOpened.svg ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/public/images/icons/lockOpened.svg b/modules/web-console/frontend/public/images/icons/lockOpened.svg new file mode 100644 index 0000000..bbc22c8 --- /dev/null +++ b/modules/web-console/frontend/public/images/icons/lockOpened.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="12" height="17" viewBox="0 0 12 17"> + <path fill="currentColor" fill-rule="nonzero" d="M10.286 4.285v-.428.5c0 .587-.384 1.072-.857 1.072-.477 0-.858-.48-.858-1.072v-.5.428A2.573 2.573 0 0 0 6 1.715a2.57 2.57 0 0 0-2.571 2.57v3.001c0 .096.005.192.015.285H10.286A1.719 1.719 0 0 1 12 9.291v5.704c0 .955-.77 1.72-1.72 1.72H1.72c-.951 0-1.72-.77-1.72-1.72V9.29c0-.954.767-1.717 1.714-1.72V4.285a4.285 4.285 0 1 1 8.572 0zm-4.572 9.292a.286.286 0 0 0 .572 0v-1.483a.857.857 0 1 0-.572 0v1.483z"/> +</svg> http://git-wip-us.apache.org/repos/asf/ignite/blob/523a871b/modules/web-console/web-agent/README.txt ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/README.txt b/modules/web-console/web-agent/README.txt index cdffe7f..86f12b8 100644 --- a/modules/web-console/web-agent/README.txt +++ b/modules/web-console/web-agent/README.txt @@ -20,6 +20,8 @@ Configuration file: tokens server-uri node-uri + node-login + node-password driver-folder Example configuration file: @@ -47,6 +49,10 @@ Options: -n, --node-uri Comma-separated list of URIs for connect to Ignite REST server, default value: http://localhost:8080 + -nl, --node-login + User name that will be used to connect to secured cluster. + -np, --node-password + Password that will be used to connect to secured cluster -s, --server-uri URI for connect to Ignite Web Console via web-socket protocol, default value: http://localhost:3000 http://git-wip-us.apache.org/repos/asf/ignite/blob/523a871b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java index 3a3d950..bb2a8a2 100644 --- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java @@ -25,6 +25,7 @@ import java.io.Reader; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Properties; import org.apache.ignite.internal.util.typedef.F; @@ -57,10 +58,21 @@ public class AgentConfiguration { private String srvUri; /** */ - @Parameter(names = {"-n", "--node-uri"}, description = "Comma-separated URIs for connect to Ignite node via REST" + - " " + - " Default value: " + DFLT_NODE_URI) - private String nodeUri; + @Parameter(names = {"-n", "--node-uri"}, + description = "Comma-separated URIs for connect to Ignite node via REST" + + " " + + " Default value: " + DFLT_NODE_URI) + private List<String> nodeURIs; + + /** */ + @Parameter(names = {"-nl", "--node-login"}, + description = "User name that will be used to connect to secured cluster") + private String nodeLogin; + + /** */ + @Parameter(names = {"-np", "--node-password"}, + description = "Password that will be used to connect to secured cluster") + private String nodePwd; /** URI for connect to Ignite demo node REST server */ private String demoNodeUri; @@ -116,17 +128,45 @@ public class AgentConfiguration { } /** - * @return Node URI. + * @return Node URIs. + */ + public List<String> nodeURIs() { + return nodeURIs; + } + + /** + * @param nodeURIs Node URIs. + */ + public void nodeURIs(List<String> nodeURIs) { + this.nodeURIs = nodeURIs; + } + + /** + * @return User name for agent to authenticate on node. + */ + public String nodeLogin() { + return nodeLogin; + } + + /** + * @param nodeLogin User name for agent to authenticate on node. + */ + public void nodeLogin(String nodeLogin) { + this.nodeLogin = nodeLogin; + } + + /** + * @return Agent password to authenticate on node. */ - public String nodeUri() { - return nodeUri; + public String nodePassword() { + return nodePwd; } /** - * @param nodeUri Node URI. + * @param nodePwd Agent password to authenticate on node. */ - public void nodeUri(String nodeUri) { - this.nodeUri = nodeUri; + public void nodePassword(String nodePwd) { + this.nodePwd = nodePwd; } /** @@ -208,7 +248,17 @@ public class AgentConfiguration { val = (String)props.remove("node-uri"); if (val != null) - nodeUri(val); + nodeURIs(new ArrayList<>(Arrays.asList(val.split(",")))); + + val = (String)props.remove("node-login"); + + if (val != null) + nodeLogin(val); + + val = (String)props.remove("node-password"); + + if (val != null) + nodePassword(val); val = (String)props.remove("driver-folder"); @@ -217,29 +267,35 @@ public class AgentConfiguration { } /** - * @param cmd Command. + * @param cfg Config to merge with. */ - public void merge(AgentConfiguration cmd) { + public void merge(AgentConfiguration cfg) { if (tokens == null) - tokens(cmd.tokens()); + tokens(cfg.tokens()); if (srvUri == null) - serverUri(cmd.serverUri()); + serverUri(cfg.serverUri()); if (srvUri == null) serverUri(DFLT_SERVER_URI); - if (nodeUri == null) - nodeUri(cmd.nodeUri()); + if (nodeURIs == null) + nodeURIs(cfg.nodeURIs()); + + if (nodeURIs == null) + nodeURIs(Collections.singletonList(DFLT_NODE_URI)); - if (nodeUri == null) - nodeUri(DFLT_NODE_URI); + if (nodeLogin == null) + nodeLogin(cfg.nodeLogin()); + + if (nodePwd == null) + nodePassword(cfg.nodePassword()); if (driversFolder == null) - driversFolder(cmd.driversFolder()); + driversFolder(cfg.driversFolder()); if (disableDemo == null) - disableDemo(cmd.disableDemo()); + disableDemo(cfg.disableDemo()); } /** {@inheritDoc} */ @@ -247,7 +303,7 @@ public class AgentConfiguration { StringBuilder sb = new StringBuilder(); if (!F.isEmpty(tokens)) { - sb.append("User's security tokens : "); + sb.append("User's security tokens : "); boolean first = true; @@ -269,9 +325,14 @@ public class AgentConfiguration { sb.append('\n'); } - sb.append("URI to Ignite node REST server: ").append(nodeUri == null ? DFLT_NODE_URI : nodeUri).append('\n'); - sb.append("URI to Ignite Console server : ").append(srvUri == null ? DFLT_SERVER_URI : srvUri).append('\n'); - sb.append("Path to agent property file : ").append(configPath()).append('\n'); + sb.append("URI to Ignite node REST server : ") + .append(nodeURIs == null ? DFLT_NODE_URI : String.join(", ", nodeURIs)).append('\n'); + + if (nodeLogin != null) + sb.append("Login to Ignite node REST server: ").append(nodeLogin).append('\n'); + + sb.append("URI to Ignite Console server : ").append(srvUri == null ? DFLT_SERVER_URI : srvUri).append('\n'); + sb.append("Path to agent property file : ").append(configPath()).append('\n'); String drvFld = driversFolder(); @@ -282,8 +343,8 @@ public class AgentConfiguration { drvFld = new File(agentHome, "jdbc-drivers").getPath(); } - sb.append("Path to JDBC drivers folder : ").append(drvFld).append('\n'); - sb.append("Demo mode : ").append(disableDemo() ? "disabled" : "enabled"); + sb.append("Path to JDBC drivers folder : ").append(drvFld).append('\n'); + sb.append("Demo mode : ").append(disableDemo() ? "disabled" : "enabled"); return sb.toString(); } http://git-wip-us.apache.org/repos/asf/ignite/blob/523a871b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java index 385ce08..9340417 100644 --- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java @@ -32,7 +32,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.UnknownHostException; -import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -43,12 +43,10 @@ import java.util.jar.Manifest; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; import org.apache.ignite.console.agent.handlers.ClusterListener; -import org.apache.ignite.console.agent.handlers.DemoListener; -import org.apache.ignite.console.agent.rest.RestExecutor; import org.apache.ignite.console.agent.handlers.DatabaseListener; import org.apache.ignite.console.agent.handlers.RestListener; +import org.apache.ignite.console.agent.rest.RestExecutor; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.X; import org.json.JSONArray; @@ -64,6 +62,7 @@ import static io.socket.client.Socket.EVENT_DISCONNECT; import static io.socket.client.Socket.EVENT_ERROR; import static org.apache.ignite.console.agent.AgentUtils.fromJSON; import static org.apache.ignite.console.agent.AgentUtils.toJSON; +import static org.apache.ignite.console.agent.AgentUtils.trustManager; /** * Ignite Web Agent launcher. @@ -73,21 +72,6 @@ public class AgentLauncher { private static final Logger log = LoggerFactory.getLogger(AgentLauncher.class); /** */ - private static final String EVENT_CLUSTER_BROADCAST_START = "cluster:broadcast:start"; - - /** */ - private static final String EVENT_CLUSTER_BROADCAST_STOP = "cluster:broadcast:stop"; - - /** */ - private static final String EVENT_CLUSTER_DISCONNECTED = "cluster:disconnected"; - - /** */ - private static final String EVENT_DEMO_BROADCAST_START = "demo:broadcast:start"; - - /** */ - private static final String EVENT_DEMO_BROADCAST_STOP = "demo:broadcast:stop"; - - /** */ private static final String EVENT_SCHEMA_IMPORT_DRIVERS = "schemaImport:drivers"; /** */ @@ -103,7 +87,7 @@ public class AgentLauncher { private static final String EVENT_NODE_REST = "node:rest"; /** */ - private static final String EVENT_RESET_TOKENS = "agent:reset:token"; + private static final String EVENT_RESET_TOKEN = "agent:reset:token"; /** */ private static final String EVENT_LOG_WARNING = "log:warn"; @@ -117,118 +101,78 @@ public class AgentLauncher { } /** - * Create a trust manager that trusts all certificates It is not using a particular keyStore - */ - private static TrustManager[] getTrustManagers() { - return new TrustManager[] { - new X509TrustManager() { - /** {@inheritDoc} */ - @Override public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - - /** {@inheritDoc} */ - @Override public void checkClientTrusted(X509Certificate[] certs, String authType) { - } - - /** {@inheritDoc} */ - @Override public void checkServerTrusted(X509Certificate[] certs, String authType) { - } - }}; - } - - /** * On error listener. */ - private static final Emitter.Listener onError = new Emitter.Listener() { - @Override public void call(Object... args) { - Throwable e = (Throwable)args[0]; + private static final Emitter.Listener onError = args -> { + Throwable e = (Throwable)args[0]; - ConnectException ce = X.cause(e, ConnectException.class); + ConnectException ce = X.cause(e, ConnectException.class); - if (ce != null) - log.error("Failed to establish connection to server (connection refused)."); - else { - Exception ignore = X.cause(e, SSLHandshakeException.class); + if (ce != null) + log.error("Failed to establish connection to server (connection refused)."); + else { + Exception ignore = X.cause(e, SSLHandshakeException.class); - if (ignore != null) { - log.error("Failed to establish SSL connection to server, due to errors with SSL handshake."); - log.error("Add to environment variable JVM_OPTS parameter \"-Dtrust.all=true\" to skip certificate validation in case of using self-signed certificate."); + if (ignore != null) { + log.error("Failed to establish SSL connection to server, due to errors with SSL handshake."); + log.error("Add to environment variable JVM_OPTS parameter \"-Dtrust.all=true\" to skip certificate validation in case of using self-signed certificate."); - System.exit(1); - } + System.exit(1); + } - ignore = X.cause(e, UnknownHostException.class); + ignore = X.cause(e, UnknownHostException.class); - if (ignore != null) { - log.error("Failed to establish connection to server, due to errors with DNS or missing proxy settings."); - log.error("Documentation for proxy configuration can be found here: http://apacheignite.readme.io/docs/web-agent#section-proxy-configuration"); + if (ignore != null) { + log.error("Failed to establish connection to server, due to errors with DNS or missing proxy settings."); + log.error("Documentation for proxy configuration can be found here: http://apacheignite.readme.io/docs/web-agent#section-proxy-configuration"); - System.exit(1); - } - - ignore = X.cause(e, IOException.class); + System.exit(1); + } - if (ignore != null && "404".equals(ignore.getMessage())) { - log.error("Failed to receive response from server (connection refused)."); + ignore = X.cause(e, IOException.class); - return; - } + if (ignore != null && "404".equals(ignore.getMessage())) { + log.error("Failed to receive response from server (connection refused)."); - if (ignore != null && "407".equals(ignore.getMessage())) { - log.error("Failed to establish connection to server, due to proxy requires authentication."); + return; + } - String userName = System.getProperty("https.proxyUsername", System.getProperty("http.proxyUsername")); + if (ignore != null && "407".equals(ignore.getMessage())) { + log.error("Failed to establish connection to server, due to proxy requires authentication."); - if (userName == null || userName.trim().isEmpty()) - userName = readLine("Enter proxy user name: "); - else - System.out.println("Read username from system properties: " + userName); + String userName = System.getProperty("https.proxyUsername", System.getProperty("http.proxyUsername")); - char[] pwd = readPassword("Enter proxy password: "); + if (userName == null || userName.trim().isEmpty()) + userName = readLine("Enter proxy user name: "); + else + System.out.println("Read username from system properties: " + userName); - final PasswordAuthentication pwdAuth = new PasswordAuthentication(userName, pwd); + char[] pwd = readPassword("Enter proxy password: "); - Authenticator.setDefault(new Authenticator() { - @Override protected PasswordAuthentication getPasswordAuthentication() { - return pwdAuth; - } - }); + final PasswordAuthentication pwdAuth = new PasswordAuthentication(userName, pwd); - return; - } + Authenticator.setDefault(new Authenticator() { + @Override protected PasswordAuthentication getPasswordAuthentication() { + return pwdAuth; + } + }); - log.error("Connection error.", e); + return; } + + log.error("Connection error.", e); } }; /** * On disconnect listener. */ - private static final Emitter.Listener onDisconnect = new Emitter.Listener() { - @Override public void call(Object... args) { - log.error("Connection closed: {}", args); - } - }; + private static final Emitter.Listener onDisconnect = args -> log.error("Connection closed: {}", args); /** * On token reset listener. */ - private static final Emitter.Listener onLogWarning = new Emitter.Listener() { - @Override public void call(Object... args) { - log.warn(String.valueOf(args[0])); - } - }; - - /** - * On demo start request. - */ - private static final Emitter.Listener onDemoStart = new Emitter.Listener() { - @Override public void call(Object... args) { - log.warn(String.valueOf(args[0])); - } - }; + private static final Emitter.Listener onLogWarning = args -> log.warn(String.valueOf(args[0])); /** * @param fmt Format string. @@ -311,28 +255,26 @@ public class AgentLauncher { System.out.println(cfg); System.out.println(); - if (cfg.tokens() == null) { - String webHost; + URI uri; - try { - webHost = new URI(cfg.serverUri()).getHost(); - } - catch (URISyntaxException e) { - log.error("Failed to parse Ignite Web Console uri", e); + try { + uri = new URI(cfg.serverUri()); + } + catch (URISyntaxException e) { + log.error("Failed to parse Ignite Web Console uri", e); - return; - } + return; + } + if (cfg.tokens() == null) { System.out.println("Security token is required to establish connection to the web console."); - System.out.println(String.format("It is available on the Profile page: https://%s/profile", webHost)); + System.out.println(String.format("It is available on the Profile page: https://%s/profile", uri.getHost())); String tokens = String.valueOf(readPassword("Enter security tokens separated by comma: ")); - cfg.tokens(Arrays.asList(tokens.trim().split(","))); + cfg.tokens(new ArrayList<>(Arrays.asList(tokens.trim().split(",")))); } - URI uri = URI.create(cfg.serverUri()); - // Create proxy authenticator using passed properties. switch (uri.getScheme()) { case "http": @@ -352,6 +294,29 @@ public class AgentLauncher { // No-op. } + List<String> nodeURIs = cfg.nodeURIs(); + + for (int i = nodeURIs.size() - 1; i >= 0; i--) { + String nodeURI = nodeURIs.get(i); + + try { + new URI(nodeURI); + } + catch (URISyntaxException ignored) { + log.warn("Failed to parse Ignite node URI: {}.", nodeURI); + + nodeURIs.remove(i); + } + } + + if (nodeURIs.isEmpty()) { + log.error("Failed to find valid URIs for connect to Ignite node via REST. Please check agent settings"); + + return; + } + + cfg.nodeURIs(nodeURIs); + IO.Options opts = new IO.Options(); opts.path = "/agents"; @@ -361,102 +326,95 @@ public class AgentLauncher { SSLContext ctx = SSLContext.getInstance("TLS"); // Create an SSLContext that uses our TrustManager - ctx.init(null, getTrustManagers(), null); + ctx.init(null, new TrustManager[] {trustManager()}, null); opts.sslContext = ctx; } final Socket client = IO.socket(uri, opts); - final RestExecutor restExecutor = new RestExecutor(cfg.nodeUri()); - try { - final ClusterListener clusterLsnr = new ClusterListener(client, restExecutor); - final DemoListener demoHnd = new DemoListener(client, restExecutor); - - Emitter.Listener onConnect = new Emitter.Listener() { - @Override public void call(Object... args) { - log.info("Connection established."); - - JSONObject authMsg = new JSONObject(); + try (RestExecutor restExecutor = new RestExecutor(); + ClusterListener clusterLsnr = new ClusterListener(cfg, client, restExecutor)) { + Emitter.Listener onConnect = connectRes -> { + log.info("Connection established."); - try { - authMsg.put("tokens", toJSON(cfg.tokens())); - authMsg.put("disableDemo", cfg.disableDemo()); + JSONObject authMsg = new JSONObject(); - String clsName = AgentLauncher.class.getSimpleName() + ".class"; + try { + authMsg.put("tokens", toJSON(cfg.tokens())); + authMsg.put("disableDemo", cfg.disableDemo()); - String clsPath = AgentLauncher.class.getResource(clsName).toString(); + String clsName = AgentLauncher.class.getSimpleName() + ".class"; - if (clsPath.startsWith("jar")) { - String manifestPath = clsPath.substring(0, clsPath.lastIndexOf('!') + 1) + - "/META-INF/MANIFEST.MF"; + String clsPath = AgentLauncher.class.getResource(clsName).toString(); - Manifest manifest = new Manifest(new URL(manifestPath).openStream()); + if (clsPath.startsWith("jar")) { + String manifestPath = clsPath.substring(0, clsPath.lastIndexOf('!') + 1) + + "/META-INF/MANIFEST.MF"; - Attributes attr = manifest.getMainAttributes(); + Manifest manifest = new Manifest(new URL(manifestPath).openStream()); - authMsg.put("ver", attr.getValue("Implementation-Version")); - authMsg.put("bt", attr.getValue("Build-Time")); - } + Attributes attr = manifest.getMainAttributes(); - client.emit("agent:auth", authMsg, new Ack() { - @Override public void call(Object... args) { - if (args != null) { - if (args[0] instanceof String) { - log.error((String)args[0]); + authMsg.put("ver", attr.getValue("Implementation-Version")); + authMsg.put("bt", attr.getValue("Build-Time")); + } - System.exit(1); - } + client.emit("agent:auth", authMsg, (Ack) authRes -> { + if (authRes != null) { + if (authRes[0] instanceof String) { + log.error((String)authRes[0]); - if (args[0] == null && args[1] instanceof JSONArray) { - try { - List<String> activeTokens = fromJSON(args[1], List.class); + System.exit(1); + } - if (!F.isEmpty(activeTokens)) { - Collection<String> missedTokens = cfg.tokens(); + if (authRes[0] == null && authRes[1] instanceof JSONArray) { + try { + List<String> activeTokens = fromJSON(authRes[1], List.class); - cfg.tokens(activeTokens); + if (!F.isEmpty(activeTokens)) { + Collection<String> missedTokens = cfg.tokens(); - missedTokens.removeAll(activeTokens); + cfg.tokens(activeTokens); - if (!F.isEmpty(missedTokens)) { - String tokens = F.concat(missedTokens, ", "); + missedTokens.removeAll(activeTokens); - log.warn("Failed to authenticate with token(s): {}. " + - "Please reload agent archive or check settings", tokens); - } + if (!F.isEmpty(missedTokens)) { + String tokens = F.concat(missedTokens, ", "); - log.info("Authentication success."); + log.warn("Failed to authenticate with token(s): {}. " + + "Please reload agent archive or check settings", tokens); + } - clusterLsnr.watch(); + log.info("Authentication success."); - return; - } - } - catch (Exception e) { - log.error("Failed to authenticate agent. Please check agent\'s tokens", e); + clusterLsnr.watch(); - System.exit(1); - } + return; } } + catch (Exception e) { + log.error("Failed to authenticate agent. Please check agent\'s tokens", e); - log.error("Failed to authenticate agent. Please check agent\'s tokens"); - - System.exit(1); + System.exit(1); + } } - }); - } - catch (JSONException | IOException e) { - log.error("Failed to construct authentication message", e); + } - client.close(); - } + log.error("Failed to authenticate agent. Please check agent\'s tokens"); + + System.exit(1); + }); + } + catch (JSONException | IOException e) { + log.error("Failed to construct authentication message", e); + + client.close(); } }; DatabaseListener dbHnd = new DatabaseListener(cfg); - RestListener restHnd = new RestListener(restExecutor); + RestListener restHnd = new RestListener(cfg, restExecutor); final CountDownLatch latch = new CountDownLatch(1); @@ -468,23 +426,17 @@ public class AgentLauncher { .on(EVENT_ERROR, onError) .on(EVENT_DISCONNECT, onDisconnect) .on(EVENT_LOG_WARNING, onLogWarning) - .on(EVENT_CLUSTER_BROADCAST_START, clusterLsnr.start()) - .on(EVENT_CLUSTER_BROADCAST_STOP, clusterLsnr.stop()) - .on(EVENT_DEMO_BROADCAST_START, demoHnd.start()) - .on(EVENT_DEMO_BROADCAST_STOP, demoHnd.stop()) - .on(EVENT_RESET_TOKENS, new Emitter.Listener() { - @Override public void call(Object... args) { - String tok = String.valueOf(args[0]); + .on(EVENT_RESET_TOKEN, res -> { + String tok = String.valueOf(res[0]); - log.warn("Security token has been reset: {}", tok); + log.warn("Security token has been reset: {}", tok); - cfg.tokens().remove(tok); + cfg.tokens().remove(tok); - if (cfg.tokens().isEmpty()) { - client.off(); + if (cfg.tokens().isEmpty()) { + client.off(); - latch.countDown(); - } + latch.countDown(); } }) .on(EVENT_SCHEMA_IMPORT_DRIVERS, dbHnd.availableDriversListener()) @@ -498,8 +450,6 @@ public class AgentLauncher { latch.await(); } finally { - restExecutor.stop(); - client.close(); } } http://git-wip-us.apache.org/repos/asf/ignite/blob/523a871b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java index 797951c..38edd97 100644 --- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java @@ -24,7 +24,9 @@ import java.io.File; import java.net.URI; import java.net.URISyntaxException; import java.security.ProtectionDomain; +import java.security.cert.X509Certificate; import java.util.Arrays; +import javax.net.ssl.X509TrustManager; import org.apache.log4j.Logger; import org.json.JSONArray; import org.json.JSONObject; @@ -182,4 +184,26 @@ public class AgentUtils { public static <T> T fromJSON(Object obj, Class<T> toValType) throws IllegalArgumentException { return MAPPER.convertValue(obj, toValType); } + + /** + * Create a trust manager that trusts all certificates It is not using a particular keyStore + */ + public static X509TrustManager trustManager() { + return new X509TrustManager() { + /** {@inheritDoc} */ + @Override public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + /** {@inheritDoc} */ + @Override public void checkClientTrusted(X509Certificate[] certs, String authType) { + // No-op. + } + + /** {@inheritDoc} */ + @Override public void checkServerTrusted(X509Certificate[] certs, String authType) { + // No-op. + } + }; + } } http://git-wip-us.apache.org/repos/asf/ignite/blob/523a871b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractListener.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractListener.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractListener.java index ace2087..33e4c2b 100644 --- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractListener.java +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractListener.java @@ -28,8 +28,10 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.zip.GZIPOutputStream; import org.apache.commons.codec.binary.Base64OutputStream; +import org.apache.ignite.IgniteLogger; import org.apache.ignite.console.agent.rest.RestResult; -import org.apache.log4j.Logger; +import org.apache.ignite.logger.slf4j.Slf4jLogger; +import org.slf4j.LoggerFactory; import static org.apache.ignite.console.agent.AgentUtils.removeCallback; import static org.apache.ignite.console.agent.AgentUtils.fromJSON; @@ -40,15 +42,15 @@ import static org.apache.ignite.console.agent.AgentUtils.toJSON; * Base class for web socket handlers. */ abstract class AbstractListener implements Emitter.Listener { + /** */ + final IgniteLogger log = new Slf4jLogger(LoggerFactory.getLogger(AbstractListener.class)); + /** UTF8 charset. */ private static final Charset UTF8 = Charset.forName("UTF-8"); /** */ private ExecutorService pool; - /** */ - final Logger log = Logger.getLogger(this.getClass().getName()); - /** {@inheritDoc} */ @SuppressWarnings("unchecked") @Override public final void call(Object... args) { @@ -69,34 +71,32 @@ abstract class AbstractListener implements Emitter.Listener { if (pool == null) pool = newThreadPool(); - pool.submit(new Runnable() { - @Override public void run() { - try { - Object res = execute(params); + pool.submit(() -> { + try { + Object res = execute(params); - // TODO IGNITE-6127 Temporary solution until GZip support for socket.io-client-java. - // See: https://github.com/socketio/socket.io-client-java/issues/312 - // We can GZip manually for now. - if (res instanceof RestResult) { - RestResult restRes = (RestResult) res; + // TODO IGNITE-6127 Temporary solution until GZip support for socket.io-client-java. + // See: https://github.com/socketio/socket.io-client-java/issues/312 + // We can GZip manually for now. + if (res instanceof RestResult) { + RestResult restRes = (RestResult) res; - if (restRes.getData() != null) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(4096); - Base64OutputStream b64os = new Base64OutputStream(baos, true, 0, null); - GZIPOutputStream gzip = new GZIPOutputStream(b64os); + if (restRes.getData() != null) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(4096); + Base64OutputStream b64os = new Base64OutputStream(baos, true, 0, null); + GZIPOutputStream gzip = new GZIPOutputStream(b64os); - gzip.write(restRes.getData().getBytes(UTF8)); + gzip.write(restRes.getData().getBytes(UTF8)); - gzip.close(); + gzip.close(); - restRes.zipData(baos.toString()); - } + restRes.zipData(baos.toString()); } - - cb.call(null, toJSON(res)); - } catch (Exception e) { - cb.call(e, null); } + + cb.call(null, toJSON(res)); + } catch (Exception e) { + cb.call(e, null); } }); } http://git-wip-us.apache.org/repos/asf/ignite/blob/523a871b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/ClusterListener.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/ClusterListener.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/ClusterListener.java index 86b9ea5..0c7560c 100644 --- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/ClusterListener.java +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/ClusterListener.java @@ -17,10 +17,8 @@ package org.apache.ignite.console.agent.handlers; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; import io.socket.client.Socket; -import io.socket.emitter.Emitter; +import java.io.IOException; import java.net.ConnectException; import java.util.ArrayList; import java.util.Collection; @@ -33,6 +31,11 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.apache.ignite.IgniteLogger; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.apache.ignite.console.agent.AgentConfiguration; import org.apache.ignite.console.agent.rest.RestExecutor; import org.apache.ignite.console.agent.rest.RestResult; import org.apache.ignite.internal.processors.rest.client.message.GridClientNodeBean; @@ -51,17 +54,30 @@ import static org.apache.ignite.internal.IgniteNodeAttributes.ATTR_BUILD_VER; import static org.apache.ignite.internal.IgniteNodeAttributes.ATTR_CLIENT_MODE; import static org.apache.ignite.internal.IgniteNodeAttributes.ATTR_IPS; import static org.apache.ignite.internal.processors.rest.GridRestResponse.STATUS_SUCCESS; +import static org.apache.ignite.internal.processors.rest.client.message.GridClientResponse.STATUS_FAILED; import static org.apache.ignite.internal.visor.util.VisorTaskUtils.sortAddresses; import static org.apache.ignite.internal.visor.util.VisorTaskUtils.splitAddresses; /** * API to transfer topology from Ignite cluster available by node-uri. */ -public class ClusterListener { +public class ClusterListener implements AutoCloseable { /** */ private static final IgniteLogger log = new Slf4jLogger(LoggerFactory.getLogger(ClusterListener.class)); /** */ + private static final IgniteProductVersion IGNITE_2_1 = IgniteProductVersion.fromString("2.1.0"); + + /** */ + private static final IgniteProductVersion IGNITE_2_3 = IgniteProductVersion.fromString("2.3.0"); + + /** Unique Visor key to get events last order. */ + private static final String EVT_LAST_ORDER_KEY = "WEB_AGENT_" + UUID.randomUUID().toString(); + + /** Unique Visor key to get events throttle counter. */ + private static final String EVT_THROTTLE_CNTR_KEY = "WEB_AGENT_" + UUID.randomUUID().toString(); + + /** */ private static final String EVENT_CLUSTER_CONNECTED = "cluster:connected"; /** */ @@ -83,9 +99,6 @@ public class ClusterListener { private final WatchTask watchTask = new WatchTask(); /** */ - private final BroadcastTask broadcastTask = new BroadcastTask(); - - /** */ private static final IgniteClosure<UUID, String> ID2ID8 = new IgniteClosure<UUID, String>() { @Override public String apply(UUID nid) { return U.id8(nid).toUpperCase(); @@ -97,10 +110,7 @@ public class ClusterListener { }; /** */ - private static final ScheduledExecutorService pool = Executors.newScheduledThreadPool(1); - - /** */ - private ScheduledFuture<?> refreshTask; + private AgentConfiguration cfg; /** */ private Socket client; @@ -108,11 +118,18 @@ public class ClusterListener { /** */ private RestExecutor restExecutor; + /** */ + private static final ScheduledExecutorService pool = Executors.newScheduledThreadPool(1); + + /** */ + private ScheduledFuture<?> refreshTask; + /** * @param client Client. * @param restExecutor Client. */ - public ClusterListener(Socket client, RestExecutor restExecutor) { + public ClusterListener(AgentConfiguration cfg, Socket client, RestExecutor restExecutor) { + this.cfg = cfg; this.client = client; this.restExecutor = restExecutor; } @@ -159,32 +176,11 @@ public class ClusterListener { refreshTask = pool.scheduleWithFixedDelay(watchTask, 0L, DFLT_TIMEOUT, TimeUnit.MILLISECONDS); } - /** - * Start broadcast topology to server-side. - */ - public Emitter.Listener start() { - return new Emitter.Listener() { - @Override public void call(Object... args) { - safeStopRefresh(); - - final long timeout = args.length > 1 && args[1] instanceof Long ? (long)args[1] : DFLT_TIMEOUT; - - refreshTask = pool.scheduleWithFixedDelay(broadcastTask, 0L, timeout, TimeUnit.MILLISECONDS); - } - }; - } - - /** - * Stop broadcast topology to server-side. - */ - public Emitter.Listener stop() { - return new Emitter.Listener() { - @Override public void call(Object... args) { - refreshTask.cancel(true); + /** {@inheritDoc} */ + @Override public void close() { + refreshTask.cancel(true); - watch(); - } - }; + pool.shutdownNow(); } /** */ @@ -210,6 +206,9 @@ public class ClusterListener { /** */ private boolean active; + /** */ + private boolean secured; + /** * Helper method to get attribute. * @@ -231,6 +230,7 @@ public class ClusterListener { addrs = U.newHashMap(sz); clients = U.newHashMap(sz); active = false; + secured = false; for (GridClientNodeBean node : nodes) { UUID nid = node.getNodeId(); @@ -247,7 +247,7 @@ public class ClusterListener { clients.put(nid, client); Collection<String> nodeAddrs = client - ? splitAddresses((String)attribute(attrs, ATTR_IPS)) + ? splitAddresses(attribute(attrs, ATTR_IPS)) : node.getTcpAddresses(); String firstIP = F.first(sortAddresses(nodeAddrs)); @@ -294,6 +294,20 @@ public class ClusterListener { } /** + * @return {@code true} If cluster has configured security. + */ + public boolean isSecured() { + return secured; + } + + /** + * @param secured Configured security flag. + */ + public void setSecured(boolean secured) { + this.secured = secured; + } + + /** * @return Cluster nodes IDs. */ public Collection<UUID> getNids() { @@ -339,54 +353,114 @@ public class ClusterListener { /** */ private class WatchTask implements Runnable { - /** {@inheritDoc} */ - @Override public void run() { - try { - RestResult res = restExecutor.topology(false, false); + /** */ + private static final String EXPIRED_SES_ERROR_MSG = "Failed to handle request - unknown session token (maybe expired session)"; - switch (res.getStatus()) { - case STATUS_SUCCESS: - List<GridClientNodeBean> nodes = MAPPER.readValue(res.getData(), - new TypeReference<List<GridClientNodeBean>>() {}); + /** */ + private String sesTok; - TopologySnapshot newTop = new TopologySnapshot(nodes); + /** + * Execute REST command under agent user. + * + * @param params Command params. + * @return Command result. + * @throws IOException If failed to execute. + */ + private RestResult restCommand(Map<String, Object> params) throws IOException { + if (!F.isEmpty(sesTok)) + params.put("sessionToken", sesTok); + else if (!F.isEmpty(cfg.nodeLogin()) && !F.isEmpty(cfg.nodePassword())) { + params.put("user", cfg.nodeLogin()); + params.put("password", cfg.nodePassword()); + } - if (newTop.differentCluster(top)) - log.info("Connection successfully established to cluster with nodes: " + newTop.nid8()); + RestResult res = restExecutor.sendRequest(cfg.nodeURIs(), params, null); - boolean active = restExecutor.active(newTop.clusterVersion(), F.first(newTop.getNids())); + switch (res.getStatus()) { + case STATUS_SUCCESS: + sesTok = res.getSessionToken(); - newTop.setActive(active); + return res; + + case STATUS_FAILED: + if (res.getError().startsWith(EXPIRED_SES_ERROR_MSG)) { + sesTok = null; + + params.remove("sessionToken"); - top = newTop; + return restCommand(params); + } - client.emit(EVENT_CLUSTER_TOPOLOGY, toJSON(top)); + default: + return res; + } + } - break; + /** + * Collect topology. + * + * @param full Full. + */ + private RestResult topology(boolean full) throws IOException { + Map<String, Object> params = U.newHashMap(3); - default: - LT.warn(log, res.getError()); + params.put("cmd", "top"); + params.put("attr", true); + params.put("mtr", full); - clusterDisconnect(); + return restCommand(params); + } + + /** + * @param ver Cluster version. + * @param nid Node ID. + * @return Cluster active state. + * @throws IOException If failed to collect cluster active state. + */ + public boolean active(IgniteProductVersion ver, UUID nid) throws IOException { + Map<String, Object> params = U.newHashMap(10); + + boolean v23 = ver.compareTo(IGNITE_2_3) >= 0; + + if (v23) + params.put("cmd", "currentState"); + else { + params.put("cmd", "exe"); + params.put("name", "org.apache.ignite.internal.visor.compute.VisorGatewayTask"); + params.put("p1", nid); + params.put("p2", "org.apache.ignite.internal.visor.node.VisorNodeDataCollectorTask"); + params.put("p3", "org.apache.ignite.internal.visor.node.VisorNodeDataCollectorTaskArg"); + params.put("p4", false); + params.put("p5", EVT_LAST_ORDER_KEY); + params.put("p6", EVT_THROTTLE_CNTR_KEY); + + if (ver.compareTo(IGNITE_2_1) >= 0) + params.put("p7", false); + else { + params.put("p7", 10); + params.put("p8", false); } } - catch (ConnectException ignored) { - clusterDisconnect(); - } - catch (Exception e) { - log.error("WatchTask failed", e); - clusterDisconnect(); + RestResult res = restCommand(params); + + switch (res.getStatus()) { + case STATUS_SUCCESS: + if (v23) + return Boolean.valueOf(res.getData()); + + return res.getData().contains("\"active\":true"); + + default: + throw new IOException(res.getError()); } } - } - /** */ - private class BroadcastTask implements Runnable { + /** {@inheritDoc} */ @Override public void run() { try { - RestResult res = restExecutor.topology(false, true); + RestResult res = topology(false); switch (res.getStatus()) { case STATUS_SUCCESS: @@ -395,17 +469,17 @@ public class ClusterListener { TopologySnapshot newTop = new TopologySnapshot(nodes); - if (top.differentCluster(newTop)) { - clusterDisconnect(); - + if (newTop.differentCluster(top)) log.info("Connection successfully established to cluster with nodes: " + newTop.nid8()); - watch(); - } + boolean active = active(newTop.clusterVersion(), F.first(newTop.getNids())); + + newTop.setActive(active); + newTop.setSecured(!F.isEmpty(res.getSessionToken())); top = newTop; - client.emit(EVENT_CLUSTER_TOPOLOGY, res.getData()); + client.emit(EVENT_CLUSTER_TOPOLOGY, toJSON(top)); break; @@ -415,12 +489,13 @@ public class ClusterListener { clusterDisconnect(); } } + catch (ConnectException ignored) { + clusterDisconnect(); + } catch (Exception e) { - log.error("BroadcastTask failed", e); + log.error("WatchTask failed", e); clusterDisconnect(); - - watch(); } } } http://git-wip-us.apache.org/repos/asf/ignite/blob/523a871b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseListener.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseListener.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseListener.java index 9da118d..b6bd623 100644 --- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseListener.java +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseListener.java @@ -125,7 +125,7 @@ public class DatabaseListener { /** */ private final AbstractListener availableDriversLsnr = new AbstractListener() { - @Override public Object execute(Map<String, Object> args) throws Exception { + @Override public Object execute(Map<String, Object> args) { if (driversFolder == null) { log.info("JDBC drivers folder not specified, returning empty list"); http://git-wip-us.apache.org/repos/asf/ignite/blob/523a871b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/DemoListener.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/DemoListener.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/DemoListener.java deleted file mode 100644 index ce42032..0000000 --- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/DemoListener.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * 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 org.apache.ignite.console.agent.handlers; - -import io.socket.client.Ack; -import io.socket.client.Socket; -import io.socket.emitter.Emitter; -import java.io.IOException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import org.apache.ignite.console.agent.rest.RestExecutor; -import org.apache.ignite.console.agent.rest.RestResult; -import org.apache.ignite.console.demo.AgentClusterDemo; -import org.apache.ignite.internal.util.typedef.internal.U; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static org.apache.ignite.console.agent.AgentUtils.safeCallback; -import static org.apache.ignite.console.agent.AgentUtils.toJSON; - -/** - * API to retranslate topology from Ignite demo cluster. - */ -public class DemoListener { - /** */ - private static final String EVENT_DEMO_TOPOLOGY = "demo:topology"; - - /** Default timeout. */ - private static final long DFLT_TIMEOUT = 3000L; - - /** */ - private static final Logger log = LoggerFactory.getLogger(DemoListener.class); - - /** */ - private static final ScheduledExecutorService pool = Executors.newScheduledThreadPool(2); - - /** */ - private ScheduledFuture<?> refreshTask; - - /** */ - private Socket client; - - /** */ - private RestExecutor restExecutor; - - /** - * @param client Client. - * @param restExecutor Client. - */ - public DemoListener(Socket client, RestExecutor restExecutor) { - this.client = client; - this.restExecutor = restExecutor; - } - - /** - * Start broadcast topology to server-side. - */ - public Emitter.Listener start() { - return new Emitter.Listener() { - @Override public void call(final Object... args) { - final Ack demoStartCb = safeCallback(args); - - final long timeout = args.length > 1 && args[1] instanceof Long ? (long)args[1] : DFLT_TIMEOUT; - - if (refreshTask != null) - refreshTask.cancel(true); - - final CountDownLatch latch = AgentClusterDemo.tryStart(); - - pool.schedule(new Runnable() { - @Override public void run() { - try { - U.await(latch); - - demoStartCb.call(); - - refreshTask = pool.scheduleWithFixedDelay(new Runnable() { - @Override public void run() { - try { - RestResult top = restExecutor.topology(true, true); - - client.emit(EVENT_DEMO_TOPOLOGY, toJSON(top)); - } - catch (IOException e) { - log.info("Lost connection to the demo cluster", e); - - stop().call(); // TODO WTF???? - } - } - }, 0L, timeout, TimeUnit.MILLISECONDS); - } - catch (Exception e) { - demoStartCb.call(e); - } - } - }, 0, TimeUnit.MILLISECONDS); - } - }; - } - - /** - * Stop broadcast topology to server-side. - */ - public Emitter.Listener stop() { - return new Emitter.Listener() { - @Override public void call(Object... args) { - refreshTask.cancel(true); - - AgentClusterDemo.stop(); - } - }; - } -} http://git-wip-us.apache.org/repos/asf/ignite/blob/523a871b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestListener.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestListener.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestListener.java index 8855060..24c2097 100644 --- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestListener.java +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestListener.java @@ -20,19 +20,28 @@ package org.apache.ignite.console.agent.handlers; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import org.apache.ignite.console.agent.AgentConfiguration; import org.apache.ignite.console.agent.rest.RestExecutor; +import org.apache.ignite.console.agent.rest.RestResult; +import org.apache.ignite.console.demo.AgentClusterDemo; +import org.apache.ignite.internal.util.typedef.internal.U; /** * API to translate REST requests to Ignite cluster. */ public class RestListener extends AbstractListener { /** */ + private final AgentConfiguration cfg; + + /** */ private final RestExecutor restExecutor; /** - * @param restExecutor Config. + * @param cfg Config. + * @param restExecutor Executor. */ - public RestListener(RestExecutor restExecutor) { + public RestListener(AgentConfiguration cfg, RestExecutor restExecutor) { + this.cfg = cfg; this.restExecutor = restExecutor; } @@ -42,15 +51,10 @@ public class RestListener extends AbstractListener { } /** {@inheritDoc} */ - @Override public Object execute(Map<String, Object> args) throws Exception { + @Override public Object execute(Map<String, Object> args) { if (log.isDebugEnabled()) log.debug("Start parse REST command args: " + args); - String path = null; - - if (args.containsKey("uri")) - path = args.get("uri").toString(); - Map<String, Object> params = null; if (args.containsKey("params")) @@ -66,11 +70,24 @@ public class RestListener extends AbstractListener { if (args.containsKey("headers")) headers = (Map<String, Object>)args.get("headers"); - String body = null; + try { + if (demo) { + if (AgentClusterDemo.getDemoUrl() == null) { + AgentClusterDemo.tryStart().await(); + + if (AgentClusterDemo.getDemoUrl() == null) + return RestResult.fail(404, "Failed to send request because of embedded node for demo mode is not started yet."); + } + + return restExecutor.sendRequest(AgentClusterDemo.getDemoUrl(), params, headers); + } - if (args.containsKey("body")) - body = args.get("body").toString(); + return restExecutor.sendRequest(this.cfg.nodeURIs(), params, headers); + } + catch (Exception e) { + U.error(log, "Failed to execute REST command with parameters: " + params, e); - return restExecutor.execute(demo, path, params, headers, body); + return RestResult.fail(404, e.getMessage()); + } } } http://git-wip-us.apache.org/repos/asf/ignite/blob/523a871b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestExecutor.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestExecutor.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestExecutor.java index f2892a6..bb06c32 100644 --- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestExecutor.java +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestExecutor.java @@ -20,14 +20,11 @@ package org.apache.ignite.console.agent.rest; import java.io.IOException; import java.io.StringWriter; import java.net.ConnectException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.UUID; import java.util.concurrent.TimeUnit; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; @@ -39,24 +36,20 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import okhttp3.Dispatcher; import okhttp3.FormBody; import okhttp3.HttpUrl; -import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; -import okhttp3.RequestBody; import okhttp3.Response; import org.apache.ignite.IgniteLogger; -import org.apache.ignite.console.demo.AgentClusterDemo; import org.apache.ignite.internal.processors.rest.protocols.http.jetty.GridJettyObjectMapper; -import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.internal.LT; import org.apache.ignite.internal.util.typedef.internal.U; -import org.apache.ignite.lang.IgniteProductVersion; import org.apache.ignite.logger.slf4j.Slf4jLogger; import org.slf4j.LoggerFactory; import static com.fasterxml.jackson.core.JsonToken.END_ARRAY; import static com.fasterxml.jackson.core.JsonToken.END_OBJECT; import static com.fasterxml.jackson.core.JsonToken.START_ARRAY; +import static org.apache.ignite.console.agent.AgentUtils.trustManager; import static org.apache.ignite.internal.processors.rest.GridRestResponse.STATUS_AUTH_FAILED; import static org.apache.ignite.internal.processors.rest.GridRestResponse.STATUS_FAILED; import static org.apache.ignite.internal.processors.rest.GridRestResponse.STATUS_SUCCESS; @@ -64,19 +57,7 @@ import static org.apache.ignite.internal.processors.rest.GridRestResponse.STATUS /** * API to translate REST requests to Ignite cluster. */ -public class RestExecutor { - /** */ - private static final IgniteProductVersion IGNITE_2_1 = IgniteProductVersion.fromString("2.1.0"); - - /** */ - private static final IgniteProductVersion IGNITE_2_3 = IgniteProductVersion.fromString("2.3.0"); - - /** Unique Visor key to get events last order. */ - private static final String EVT_LAST_ORDER_KEY = "WEB_AGENT_" + UUID.randomUUID().toString(); - - /** Unique Visor key to get events throttle counter. */ - private static final String EVT_THROTTLE_CNTR_KEY = "WEB_AGENT_" + UUID.randomUUID().toString(); - +public class RestExecutor implements AutoCloseable { /** */ private static final IgniteLogger log = new Slf4jLogger(LoggerFactory.getLogger(RestExecutor.class)); @@ -86,33 +67,45 @@ public class RestExecutor { /** */ private final OkHttpClient httpClient; - /** Node URLs. */ - private Set<String> nodeUrls = new LinkedHashSet<>(); - - /** Latest alive node URL. */ - private volatile String latestNodeUrl; + /** Index of alive node URI. */ + private Map<List<String>, Integer> startIdxs = U.newHashMap(2); /** * Default constructor. */ - public RestExecutor(String nodeUrl) { - Collections.addAll(nodeUrls, nodeUrl.split(",")); - + public RestExecutor() { Dispatcher dispatcher = new Dispatcher(); dispatcher.setMaxRequests(Integer.MAX_VALUE); dispatcher.setMaxRequestsPerHost(Integer.MAX_VALUE); - httpClient = new OkHttpClient.Builder() + OkHttpClient.Builder builder = new OkHttpClient.Builder() .readTimeout(0, TimeUnit.MILLISECONDS) - .dispatcher(dispatcher) - .build(); + .dispatcher(dispatcher); + + // Workaround for use self-signed certificate + if (Boolean.getBoolean("trust.all")) { + try { + SSLContext ctx = SSLContext.getInstance("TLS"); + + // Create an SSLContext that uses our TrustManager + ctx.init(null, new TrustManager[] {trustManager()}, null); + + builder.sslSocketFactory(ctx.getSocketFactory(), trustManager()); + + builder.hostnameVerifier((hostname, session) -> true); + } catch (Exception ignored) { + LT.warn(log, "Failed to initialize the Trust Manager for \"-Dtrust.all\" option to skip certificate validation."); + } + } + + httpClient = builder.build(); } /** * Stop HTTP client. */ - public void stop() { + @Override public void close() { if (httpClient != null) { httpClient.dispatcher().executorService().shutdown(); @@ -121,28 +114,37 @@ public class RestExecutor { } /** */ - private RestResult sendRequest0(String nodeUrl, boolean demo, String path, Map<String, Object> params, - Map<String, Object> headers, String body) throws IOException { - if (demo && AgentClusterDemo.getDemoUrl() == null) { - try { - AgentClusterDemo.tryStart().await(); - } - catch (InterruptedException ignore) { - throw new IllegalStateException("Failed to send request because of embedded node for demo mode is not started yet."); + private RestResult parseResponse(Response res) throws IOException { + if (res.isSuccessful()) { + RestResponseHolder holder = MAPPER.readValue(res.body().byteStream(), RestResponseHolder.class); + + int status = holder.getSuccessStatus(); + + switch (status) { + case STATUS_SUCCESS: + return RestResult.success(holder.getResponse(), holder.getSessionToken()); + + default: + return RestResult.fail(status, holder.getError()); } } - String url = demo ? AgentClusterDemo.getDemoUrl() : nodeUrl; + if (res.code() == 401) + return RestResult.fail(STATUS_AUTH_FAILED, "Failed to authenticate in cluster. " + + "Please check agent\'s login and password or node port."); - HttpUrl httpUrl = HttpUrl.parse(url); + if (res.code() == 404) + return RestResult.fail(STATUS_FAILED, "Failed connect to cluster."); - if (httpUrl == null) - throw new IllegalStateException("Failed to send request because of node URL is invalid: " + url); + return RestResult.fail(STATUS_FAILED, "Failed to execute REST command: " + res.message()); + } - HttpUrl.Builder urlBuilder = httpUrl.newBuilder(); + /** */ + private RestResult sendRequest(String url, Map<String, Object> params, Map<String, Object> headers) throws IOException { + HttpUrl httpUrl = HttpUrl.parse(url); - if (path != null) - urlBuilder.addPathSegment(path); + HttpUrl.Builder urlBuilder = httpUrl.newBuilder() + .addPathSegment("ignite"); final Request.Builder reqBuilder = new Request.Builder(); @@ -152,199 +154,51 @@ public class RestExecutor { reqBuilder.addHeader(entry.getKey(), entry.getValue().toString()); } - if (body != null) { - MediaType contentType = MediaType.parse("text/plain"); - - reqBuilder.post(RequestBody.create(contentType, body)); - } - else { - FormBody.Builder formBody = new FormBody.Builder(); + FormBody.Builder bodyParams = new FormBody.Builder(); - if (params != null) { - for (Map.Entry<String, Object> entry : params.entrySet()) { - if (entry.getValue() != null) - formBody.add(entry.getKey(), entry.getValue().toString()); - } + if (params != null) { + for (Map.Entry<String, Object> entry : params.entrySet()) { + if (entry.getValue() != null) + bodyParams.add(entry.getKey(), entry.getValue().toString()); } - - reqBuilder.post(formBody.build()); } - reqBuilder.url(urlBuilder.build()); + reqBuilder.url(urlBuilder.build()) + .post(bodyParams.build()); try (Response resp = httpClient.newCall(reqBuilder.build()).execute()) { - if (resp.isSuccessful()) { - RestResponseHolder res = MAPPER.readValue(resp.body().byteStream(), RestResponseHolder.class); - - int status = res.getSuccessStatus(); - - switch (status) { - case STATUS_SUCCESS: - return RestResult.success(res.getResponse()); - - default: - return RestResult.fail(status, res.getError()); - } - } - - if (resp.code() == 401) - return RestResult.fail(STATUS_AUTH_FAILED, "Failed to authenticate in cluster. " + - "Please check agent\'s login and password or node port."); - - if (resp.code() == 404) - return RestResult.fail(STATUS_FAILED, "Failed connect to cluster."); - - return RestResult.fail(STATUS_FAILED, "Failed to execute REST command: " + resp.message()); + return parseResponse(resp); } } - /** - * Send request to cluster. - * - * @param demo {@code true} If demo mode. - * @param path Request path. - * @param params Request params. - * @param headers Request headers. - * @param body Request body. - * @return Request result. - * @throws IOException If failed to process request. - */ - private RestResult sendRequest(boolean demo, String path, Map<String, Object> params, - Map<String, Object> headers, String body) throws IOException { - String url = latestNodeUrl; - - try { - if (F.isEmpty(url)) { - Iterator<String> it = nodeUrls.iterator(); - - while (it.hasNext()) { - String nodeUrl = it.next(); - - try { - RestResult res = sendRequest0(nodeUrl, demo, path, params, headers, body); + /** */ + public RestResult sendRequest(List<String> nodeURIs, Map<String, Object> params, Map<String, Object> headers) throws IOException { + Integer startIdx = startIdxs.getOrDefault(nodeURIs, 0); - log.info("Connected to cluster [url=" + nodeUrl + "]"); + for (int i = 0; i < nodeURIs.size(); i++) { + Integer currIdx = (startIdx + i) % nodeURIs.size(); - latestNodeUrl = nodeUrl; + String nodeUrl = nodeURIs.get(currIdx); - return res; - } - catch (ConnectException ignored) { - String msg = "Failed connect to cluster [url=" + nodeUrl + ", parameters=" + params + "]"; + try { + RestResult res = sendRequest(nodeUrl, params, headers); - LT.warn(log, msg); + LT.info(log, "Connected to cluster [url=" + nodeUrl + "]"); - if (!it.hasNext()) - throw new ConnectException(msg); - } - } + startIdxs.put(nodeURIs, currIdx); - throw new ConnectException("Failed connect to cluster [urls=" + nodeUrls + ", parameters=" + params + "]"); + return res; } - else { - try { - return sendRequest0(url, demo, path, params, headers, body); - } - catch (ConnectException e) { - latestNodeUrl = null; - - if (nodeUrls.size() > 1) - return sendRequest(demo, path, params, headers, body); - - throw e; - } - } } - catch (ConnectException ce) { - LT.warn(log, "Failed connect to cluster. " + - "Please ensure that nodes have [ignite-rest-http] module in classpath " + - "(was copied from libs/optional to libs folder)."); - - throw ce; - } - } - - /** - * @param demo Is demo node request. - * @param path Path segment. - * @param params Params. - * @param headers Headers. - * @param body Body. - */ - public RestResult execute(boolean demo, String path, Map<String, Object> params, - Map<String, Object> headers, String body) { - if (log.isDebugEnabled()) - log.debug("Start execute REST command [uri=/" + (path == null ? "" : path) + - ", parameters=" + params + "]"); - - try { - return sendRequest(demo, path, params, headers, body); - } - catch (Exception e) { - U.error(log, "Failed to execute REST command [uri=/" + (path == null ? "" : path) + - ", parameters=" + params + "]", e); - - return RestResult.fail(404, e.getMessage()); - } - } - - /** - * @param demo {@code true} in case of demo mode. - * @param full Flag indicating whether to collect metrics or not. - * @throws IOException If failed to collect topology. - */ - public RestResult topology(boolean demo, boolean full) throws IOException { - Map<String, Object> params = new HashMap<>(3); - - params.put("cmd", "top"); - params.put("attr", true); - params.put("mtr", full); - - return sendRequest(demo, "ignite", params, null, null); - } - - /** - * @param ver Cluster version. - * @param nid Node ID. - * @return Cluster active state. - * @throws IOException If failed to collect cluster active state. - */ - public boolean active(IgniteProductVersion ver, UUID nid) throws IOException { - Map<String, Object> params = new HashMap<>(); - - boolean v23 = ver.compareTo(IGNITE_2_3) >= 0; - - if (v23) - params.put("cmd", "currentState"); - else { - params.put("cmd", "exe"); - params.put("name", "org.apache.ignite.internal.visor.compute.VisorGatewayTask"); - params.put("p1", nid); - params.put("p2", "org.apache.ignite.internal.visor.node.VisorNodeDataCollectorTask"); - params.put("p3", "org.apache.ignite.internal.visor.node.VisorNodeDataCollectorTaskArg"); - params.put("p4", false); - params.put("p5", EVT_LAST_ORDER_KEY); - params.put("p6", EVT_THROTTLE_CNTR_KEY); - - if (ver.compareTo(IGNITE_2_1) >= 0) - params.put("p7", false); - else { - params.put("p7", 10); - params.put("p8", false); + catch (ConnectException ignored) { + // No-op. } } - RestResult res = sendRequest(false, "ignite", params, null, null); - - switch (res.getStatus()) { - case STATUS_SUCCESS: - if (v23) - return Boolean.valueOf(res.getData()); + LT.warn(log, "Failed connect to cluster. " + + "Please ensure that nodes have [ignite-rest-http] module in classpath " + + "(was copied from libs/optional to libs folder)."); - return res.getData().contains("\"active\":true"); - - default: - throw new IOException(res.getError()); - } + throw new ConnectException("Failed connect to cluster [urls=" + nodeURIs + ", parameters=" + params + "]"); } /** @@ -361,7 +215,7 @@ public class RestExecutor { private String res; /** Session token string representation. */ - private String sesTokStr; + private String sesTok; /** * @return {@code True} if this request was successful. @@ -410,14 +264,14 @@ public class RestExecutor { * @return String representation of session token. */ public String getSessionToken() { - return sesTokStr; + return sesTok; } /** * @param sesTokStr String representation of session token. */ public void setSessionToken(String sesTokStr) { - this.sesTokStr = sesTokStr; + this.sesTok = sesTokStr; } } http://git-wip-us.apache.org/repos/asf/ignite/blob/523a871b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestResult.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestResult.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestResult.java index 962ffb6..fc8b4e9 100644 --- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestResult.java +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestResult.java @@ -30,6 +30,9 @@ public class RestResult { /** The field contains result of command. */ private String data; + /** Session token string representation. */ + private String sesTok; + /** Flag of zipped data. */ private boolean zipped; @@ -57,8 +60,12 @@ public class RestResult { * @param data The field contains result of command. * @return Request result. */ - public static RestResult success(String data) { - return new RestResult(0, null, data); + public static RestResult success(String data, String sesTok) { + RestResult res = new RestResult(0, null, data); + + res.sesTok = sesTok; + + return res; } /** @@ -83,6 +90,13 @@ public class RestResult { } /** + * @return String representation of session token. + */ + public String getSessionToken() { + return sesTok; + } + + /** * @param data Set zipped data. */ public void zipData(String data) { http://git-wip-us.apache.org/repos/asf/ignite/blob/523a871b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/demo/AgentClusterDemo.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/demo/AgentClusterDemo.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/demo/AgentClusterDemo.java index 1fd286c..2555ee1 100644 --- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/demo/AgentClusterDemo.java +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/demo/AgentClusterDemo.java @@ -21,6 +21,7 @@ import java.io.File; import java.nio.file.Paths; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -100,7 +101,7 @@ public class AgentClusterDemo { private static CountDownLatch initLatch = new CountDownLatch(1); /** */ - private static volatile String demoUrl; + private static volatile List<String> demoUrl; /** * Configure node. @@ -204,7 +205,7 @@ public class AgentClusterDemo { } /** */ - public static String getDemoUrl() { + public static List<String> getDemoUrl() { return demoUrl; } @@ -269,7 +270,7 @@ public class AgentClusterDemo { log.info("DEMO: Started embedded node for demo purpose [TCP binary port={}, Jetty REST port={}]", port, jettyPort); - demoUrl = String.format("http://%s:%d", jettyHost, jettyPort); + demoUrl = Collections.singletonList(String.format("http://%s:%d", jettyHost, jettyPort)); initLatch.countDown(); }