ignite-843 Agent initial commit
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/81962c43 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/81962c43 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/81962c43 Branch: refs/heads/ignite-843-rc1 Commit: 81962c43ae38b643fbf043496db34118114f79bb Parents: 6844370 Author: Andrey <anovi...@gridgain.com> Authored: Tue Oct 13 10:05:15 2015 +0700 Committer: Andrey <anovi...@gridgain.com> Committed: Tue Oct 13 10:05:15 2015 +0700 ---------------------------------------------------------------------- modules/control-center-agent/README.txt | 85 ++++ .../assembly/release-control-center-agent.xml | 75 ++++ .../bin/ignite-web-agent.bat | 18 + .../bin/ignite-web-agent.sh | 34 ++ .../jdbc-drivers/README.txt | 10 + modules/control-center-agent/pom.xml | 154 +++++++ .../apache/ignite/agent/AgentConfiguration.java | 280 +++++++++++++ .../org/apache/ignite/agent/AgentLauncher.java | 168 ++++++++ .../ignite/agent/AgentLoggingConfigurator.java | 90 ++++ .../org/apache/ignite/agent/AgentSocket.java | 191 +++++++++ .../org/apache/ignite/agent/AgentUtils.java | 74 ++++ .../handlers/DatabaseMetadataExtractor.java | 208 ++++++++++ .../ignite/agent/handlers/RestExecutor.java | 175 ++++++++ .../org/apache/ignite/agent/remote/Remote.java | 39 ++ .../ignite/agent/remote/RemoteHandler.java | 253 ++++++++++++ .../ignite/agent/remote/WebSocketSender.java | 41 ++ .../agent/testdrive/AgentMetadataTestDrive.java | 90 ++++ .../agent/testdrive/AgentSqlTestDrive.java | 414 +++++++++++++++++++ .../ignite/agent/testdrive/model/Car.java | 157 +++++++ .../ignite/agent/testdrive/model/CarKey.java | 99 +++++ .../ignite/agent/testdrive/model/Country.java | 128 ++++++ .../agent/testdrive/model/CountryKey.java | 99 +++++ .../agent/testdrive/model/Department.java | 186 +++++++++ .../agent/testdrive/model/DepartmentKey.java | 99 +++++ .../ignite/agent/testdrive/model/Employee.java | 360 ++++++++++++++++ .../agent/testdrive/model/EmployeeKey.java | 99 +++++ .../ignite/agent/testdrive/model/Parking.java | 128 ++++++ .../agent/testdrive/model/ParkingKey.java | 99 +++++ .../src/main/resources/logging.properties | 24 ++ .../control-center-agent/test-drive/README.txt | 4 + .../test-drive/test-drive.sql | 58 +++ 31 files changed, 3939 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/README.txt ---------------------------------------------------------------------- diff --git a/modules/control-center-agent/README.txt b/modules/control-center-agent/README.txt new file mode 100644 index 0000000..4c155b7 --- /dev/null +++ b/modules/control-center-agent/README.txt @@ -0,0 +1,85 @@ +Ignite Web Agent +====================================== +Ignite Web Agent is a java standalone application that allow to connect Ignite Grid to Ignite Web Console. +Ignite Web Agent communicates with grid nodes via REST interface and connects to Ignite Web Console via web-socket. + +Two main functions of Ignite Web Agent: + 1. Proxy between Ignite Web Console and Ignite Grid to execute SQL statements and collect metrics for monitoring. + You may need to specify URI for connect to Ignite REST server via "-n" option. + + 2. Proxy between Ignite Web Console and user RDBMS to collect database metadata for later CacheTypeMetadata configuration. + You may need to copy JDBC driver into "./jdbc-drivers" subfolder or specify path via "-d" option. + +Usage example: + ignite-web-agent.sh -t 1a2b3c4d5f -s wss://console.example.com + +Test drive of Ignite Web Agent: + In order to simplify evaluation two test drive modes were implemented: + + 1) Get security token on Web Console "Profile" screen. + + 2) Test drive for metadata load from database. Activated by option: -tm or --test-drive-metadata. + In this mode an in-memory H2 database will started. + How to evaluate: + 2.1) Go to Ignite Web Console "Metadata" screen. + 2.2) Select "Load from database". + 2.3) Select H2 driver and enter JDBC URL: "jdbc:h2:mem:test-drive-db". + 2.4) You should see list of available schemas and tables. Select some of them and click "Save". + + 3) Test drive for SQL. Activated by option: -ts or --test-drive-sql. + In this mode internal Ignite node will be started. Cache created and populated with data. + How to evaluate: + 3.1) Go to Ignite Web Console "SQL" menu and select "Create new notebook" menu item. + 3.2) In notebook paragraph enter SQL queries for tables: "Country, Department, Employee" in "test-drive-employee" cache + and for tables: "Parking, Car" in "test-drive-car" cache. + + For example: + 3.3) select "test-drive-car" cache, + 3.4) enter SQL: + select count(*) cnt, p.ParkingName from car c + inner join PARKING p on (p.PARKINGID=c.PARKINGID) + group by c.PARKINGID order by p.ParkingName + 3.5) Click "Execute" button. You should get some data in table. + 3.6) Click charts buttons to see auto generated charts. + +Configuration file: + Should be a file with simple line-oriented format as described here: http://docs.oracle.com/javase/7/docs/api/java/util/Properties.html#load(java.io.Reader) + + Available entries names: + token + serverURI + nodeURI + driverFolder + test-drive-metadata + test-drive-sql + + Example configuration file: + token=1a2b3c4d5f + serverURI=wss://console.example.com:3001 + test-drive-sql=true + +Options: + -h, --help + Print this help message + -c, --config + Path to configuration file + -d, --driver-folder + Path to folder with JDBC drivers, default value: ./jdbc-drivers + -n, --node-uri + URI for connect to Ignite REST server, default value: + http://localhost:8080 + -s, --server-uri + URI for connect to Ignite Web Console via web-socket protocol, default + value: wss://localhost:3001 + -tm, --test-drive-metadata + Start H2 database with sample tables in same process. JDBC URL for + connecting to sample database: jdbc:h2:mem:test-drive-db + -ts, --test-drive-sql + Create cache and populate it with sample data for use in query + -t, --token + User's security token + +Ignite Web Agent Build Instructions +============================================== +If you want to build from sources run following command in Ignite project root folder: + mvn clean package -pl :ignite-control-center-agent -am -P control-center -DskipTests=true http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/assembly/release-control-center-agent.xml ---------------------------------------------------------------------- diff --git a/modules/control-center-agent/assembly/release-control-center-agent.xml b/modules/control-center-agent/assembly/release-control-center-agent.xml new file mode 100644 index 0000000..76760d4 --- /dev/null +++ b/modules/control-center-agent/assembly/release-control-center-agent.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- + ~ /* + ~ * 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. + ~ */ + --> + +<assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3" + xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd"> + <id>release-ignite-web-agent</id> + + <formats> + <format>zip</format> + </formats> + + <fileSets> + <fileSet> + <directory>${basedir}/../indexing/target/libs</directory> + <outputDirectory>/jdbc-drivers</outputDirectory> + <includes> + <include>**/h2-*.jar</include> + </includes> + </fileSet> + <fileSet> + <directory>${basedir}</directory> + <outputDirectory>/</outputDirectory> + <includes> + <include>jdbc-drivers/*.*</include> + <include>test-drive/*.*</include> + <include>README*</include> + <include>LICENSE*</include> + <include>NOTICE*</include> + </includes> + </fileSet> + <fileSet> + <directory>${basedir}/bin</directory> + <outputDirectory>/</outputDirectory> + <filtered>true</filtered> + <includes> + <include>**/*.bat</include> + </includes> + </fileSet> + <fileSet> + <directory>${basedir}/bin</directory> + <outputDirectory>/</outputDirectory> + <filtered>true</filtered> + <fileMode>0755</fileMode> + <includes> + <include>**/*.sh</include> + </includes> + </fileSet> + <fileSet> + <directory>${project.build.directory}</directory> + <outputDirectory>/</outputDirectory> + <includes> + <include>ignite-web-agent-${project.version}.jar</include> + </includes> + </fileSet> + </fileSets> +</assembly> http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/bin/ignite-web-agent.bat ---------------------------------------------------------------------- diff --git a/modules/control-center-agent/bin/ignite-web-agent.bat b/modules/control-center-agent/bin/ignite-web-agent.bat new file mode 100644 index 0000000..796ddf9 --- /dev/null +++ b/modules/control-center-agent/bin/ignite-web-agent.bat @@ -0,0 +1,18 @@ +:: +:: 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. +:: + +java -jar ignite-web-agent-${version}.jar %* http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/bin/ignite-web-agent.sh ---------------------------------------------------------------------- diff --git a/modules/control-center-agent/bin/ignite-web-agent.sh b/modules/control-center-agent/bin/ignite-web-agent.sh new file mode 100644 index 0000000..9acdc5c --- /dev/null +++ b/modules/control-center-agent/bin/ignite-web-agent.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +SOURCE="${BASH_SOURCE[0]}" + +DIR="$( dirname "$SOURCE" )" + +while [ -h "$SOURCE" ] + do + SOURCE="$(readlink "$SOURCE")" + + [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" + + DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" + done + +DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" + +java -jar ignite-web-agent-${version}.jar "$@" http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/jdbc-drivers/README.txt ---------------------------------------------------------------------- diff --git a/modules/control-center-agent/jdbc-drivers/README.txt b/modules/control-center-agent/jdbc-drivers/README.txt new file mode 100644 index 0000000..cad43b7 --- /dev/null +++ b/modules/control-center-agent/jdbc-drivers/README.txt @@ -0,0 +1,10 @@ +Ignite Web Agent +====================================== + +If you are are planning to load cache type metadata from your existing databases +you need to copy JDBC drivers in this folder. + +This is default folder for JDBC drivers. + +Also, you could specify custom folder using option: "-d CUSTOM_PATH_TO_FOLDER_WITH_JDBC_DRIVERS". + http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/pom.xml ---------------------------------------------------------------------- diff --git a/modules/control-center-agent/pom.xml b/modules/control-center-agent/pom.xml new file mode 100644 index 0000000..0237f5f --- /dev/null +++ b/modules/control-center-agent/pom.xml @@ -0,0 +1,154 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- + ~ /* + ~ * 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. + ~ */ + --> + +<!-- + POM file. +--> +<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.ignite</groupId> + <artifactId>ignite-parent</artifactId> + <version>1</version> + <relativePath>../../parent</relativePath> + </parent> + + <artifactId>ignite-control-center-agent</artifactId> + <version>1.5.0-SNAPSHOT</version> + + <properties> + <jetty.version>9.2.12.v20150709</jetty.version> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.ignite</groupId> + <artifactId>ignite-schema-import-db</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>org.eclipse.jetty.websocket</groupId> + <artifactId>websocket-client</artifactId> + <version>${jetty.version}</version> + </dependency> + + <dependency> + <groupId>com.google.code.gson</groupId> + <artifactId>gson</artifactId> + <version>2.3</version> + </dependency> + + <dependency> + <groupId>com.beust</groupId> + <artifactId>jcommander</artifactId> + <version>1.48</version> + </dependency> + + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpclient</artifactId> + <version>4.5</version> + </dependency> + + <dependency> + <groupId>org.apache.ignite</groupId> + <artifactId>ignite-indexing</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>org.apache.ignite</groupId> + <artifactId>ignite-rest-http</artifactId> + <version>${project.version}</version> + <exclusions> + <exclusion> + <groupId>org.apache.ignite</groupId> + <artifactId>ignite-log4j</artifactId> + </exclusion> + </exclusions> + </dependency> + </dependencies> + + <build> + <finalName>ignite-web-agent-${project.version}</finalName> + + <plugins> + <plugin> + <artifactId>maven-jar-plugin</artifactId> + <version>2.5</version> + + <configuration> + <archive> + <manifest> + <mainClass>org.apache.ignite.agent.AgentLauncher</mainClass> + </manifest> + </archive> + </configuration> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>2.4</version> + + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + + <configuration> + <createDependencyReducedPom>false</createDependencyReducedPom> + </configuration> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-assembly-plugin</artifactId> + <version>2.4</version> + <inherited>false</inherited> + + <executions> + <execution> + <id>release-control-center-agent</id> + <phase>package</phase> + <goals> + <goal>single</goal> + </goals> + <configuration> + <descriptors> + <descriptor>assembly/release-control-center-agent.xml</descriptor> + </descriptors> + <finalName>ignite-web-agent-${project.version}</finalName> + <outputDirectory>target</outputDirectory> + <appendAssemblyId>false</appendAssemblyId> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentConfiguration.java ---------------------------------------------------------------------- diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentConfiguration.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentConfiguration.java new file mode 100644 index 0000000..63d02a3 --- /dev/null +++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentConfiguration.java @@ -0,0 +1,280 @@ +/* + * + * * 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.agent; + +import com.beust.jcommander.Parameter; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.URL; +import java.util.Properties; + +/** + * Agent configuration. + */ +public class AgentConfiguration { + /** Default server port. */ + public static final int DFLT_SERVER_PORT = 3001; + /** Default Ignite node HTTP port. */ + public static final int DFLT_NODE_PORT = 8080; + /** Default server URI. */ + private static final String DFLT_SERVER_URI = "wss://localhost:3001"; + /** Default Ignite node HTTP URI. */ + private static final String DFLT_NODE_URI = "http://localhost:8080"; + /** */ + @Parameter(names = {"-t", "--token"}, description = "User's security token used to establish connection to Ignite Console.") + private String tok; + + /** */ + @Parameter(names = {"-s", "--server-uri"}, description = "URI for connect to Ignite Console via web-socket protocol" + + " " + + " Default value: wss://localhost:3001") + private String srvUri; + + /** */ + @Parameter(names = {"-n", "--node-uri"}, description = "URI for connect to Ignite node REST server" + + " " + + " Default value: http://localhost:8080") + private String nodeUri; + + /** */ + @Parameter(names = {"-c", "--config"}, description = "Path to agent property file" + + " " + + " Default value: ./default.properties") + private String cfgPath; + + /** */ + @Parameter(names = {"-d", "--driver-folder"}, description = "Path to folder with JDBC drivers" + + " " + + " Default value: ./jdbc-drivers") + private String driversFolder; + + /** */ + @Parameter(names = { "-tm", "--test-drive-metadata" }, + description = "Start H2 database with sample tables in same process. " + + "JDBC URL for connecting to sample database: jdbc:h2:mem:test-drive-db") + private Boolean meta; + + /** */ + @Parameter(names = { "-ts", "--test-drive-sql" }, + description = "Create cache and populate it with sample data for use in query") + private Boolean sql; + + /** */ + @Parameter(names = { "-h", "--help" }, help = true, description = "Print this help message") + private Boolean help; + + /** + * @return Token. + */ + public String token() { + return tok; + } + + /** + * @param tok Token. + */ + public void token(String tok) { + this.tok = tok; + } + + /** + * @return Server URI. + */ + public String serverUri() { + return srvUri; + } + + /** + * @param srvUri URI. + */ + public void serverUri(String srvUri) { + this.srvUri = srvUri; + } + + /** + * @return Node URI. + */ + public String nodeUri() { + return nodeUri; + } + + /** + * @param nodeUri Node URI. + */ + public void nodeUri(String nodeUri) { + this.nodeUri = nodeUri; + } + + /** + * @return Configuration path. + */ + public String configPath() { + return cfgPath == null ? "./default.properties" : cfgPath; + } + + /** + * @return Configured drivers folder. + */ + public String driversFolder() { + return driversFolder; + } + + /** + * @param driversFolder Driver folder. + */ + public void driversFolder(String driversFolder) { + this.driversFolder = driversFolder; + } + + /** + * @return {@code true} If metadata test drive should be started. + */ + public Boolean testDriveMetadata() { + return meta != null ? meta : false; + } + + /** + * @param meta Set to {@code true} if metadata test drive should be started. + */ + public void testDriveMetadata(Boolean meta) { + this.meta = meta; + } + + /** + * @return {@code true} If SQL test drive should be started. + */ + public Boolean testDriveSql() { + return sql != null ? sql : false; + } + + /** + * @param sql Set to {@code true} if SQL test drive should be started. + */ + public void testDriveSql(Boolean sql) { + this.sql = sql; + } + + /** + * @return {@code true} If agent options usage should be printed. + */ + public Boolean help() { + return help != null ? help : false; + } + + /** + * @param cfgUrl URL. + */ + public void load(URL cfgUrl) throws IOException { + Properties props = new Properties(); + + try (Reader reader = new InputStreamReader(cfgUrl.openStream())) { + props.load(reader); + } + + String val = (String)props.remove("token"); + + if (val != null) + token(val); + + val = (String)props.remove("serverURI"); + + if (val != null) + serverUri(val); + + val = (String)props.remove("nodeURI"); + + if (val != null) + nodeUri(val); + + val = (String)props.remove("driverFolder"); + + if (val != null) + driversFolder(val); + + val = (String)props.remove("test-drive-metadata"); + + if (val != null) + testDriveMetadata(Boolean.valueOf(val)); + + val = (String)props.remove("test-drive-sql"); + + if (val != null) + testDriveSql(Boolean.valueOf(val)); + } + + /** + * @param cmd Command. + */ + public void merge(AgentConfiguration cmd) { + if (tok == null) + token(cmd.token()); + + if (srvUri == null) + serverUri(cmd.serverUri()); + + if (srvUri == null) + serverUri(DFLT_SERVER_URI); + + if (nodeUri == null) + nodeUri(cmd.nodeUri()); + + if (nodeUri == null) + nodeUri(DFLT_NODE_URI); + + if (driversFolder == null) + driversFolder(cmd.driversFolder()); + + if (testDriveMetadata()) + testDriveMetadata(true); + + if (testDriveSql()) + testDriveSql(true); + } + + /** {@inheritDoc} */ + @Override public String toString() { + StringBuilder sb = new StringBuilder(); + + if (tok != null) + sb.append("User's security token : ").append(token()).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'); + + String drvFld = driversFolder(); + + if (drvFld == null) { + File agentHome = AgentUtils.getAgentHome(); + + if (agentHome != null) + drvFld = new File(agentHome, "jdbc-drivers").getPath(); + } + + sb.append("Path to JDBC drivers folder : ").append(drvFld).append('\n'); + + sb.append("Test-drive for load metadata : ").append(testDriveMetadata()).append('\n'); + sb.append("Test-drive for execute query : ").append(testDriveSql()); + + return sb.toString(); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLauncher.java ---------------------------------------------------------------------- diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLauncher.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLauncher.java new file mode 100644 index 0000000..c85e25c --- /dev/null +++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLauncher.java @@ -0,0 +1,168 @@ +/* + * + * * 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.agent; + +import com.beust.jcommander.JCommander; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.ignite.agent.handlers.RestExecutor; +import org.apache.ignite.agent.testdrive.AgentMetadataTestDrive; +import org.apache.ignite.agent.testdrive.AgentSqlTestDrive; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.websocket.client.WebSocketClient; + +import static org.apache.ignite.agent.AgentConfiguration.DFLT_SERVER_PORT; + +/** + * Control Center Agent launcher. + */ +public class AgentLauncher { + /** */ + private static final Logger log = Logger.getLogger(AgentLauncher.class.getName()); + /** */ + private static final int RECONNECT_INTERVAL = 3000; + + /** Static initializer. */ + static { + AgentLoggingConfigurator.configure(); + } + + /** + * @param args Args. + */ + @SuppressWarnings("BusyWait") + public static void main(String[] args) throws Exception { + log.log(Level.INFO, "Starting Apache Ignite Control Center Agent..."); + + AgentConfiguration cfg = new AgentConfiguration(); + + JCommander jCommander = new JCommander(cfg, args); + + String osName = System.getProperty("os.name").toLowerCase(); + + jCommander.setProgramName("ignite-web-agent." + (osName.contains("win") ? "bat" : "sh")); + + String prop = cfg.configPath(); + + AgentConfiguration propCfg = new AgentConfiguration(); + + try { + propCfg.load(new File(prop).toURI().toURL()); + } + catch (IOException ignore) { + log.log(Level.WARNING, "Failed to load agent property file: '" + prop + "'", ignore); + } + + cfg.merge(propCfg); + + if (cfg.help()) { + jCommander.usage(); + + return; + } + + System.out.println(); + System.out.println("Agent configuration:"); + System.out.println(cfg); + System.out.println(); + + if (cfg.testDriveSql() && cfg.nodeUri() != null) + log.log(Level.WARNING, + "URI for connect to Ignite REST server will be ignored because --test-drive-sql option was specified."); + + if (!cfg.testDriveSql() && !cfg.testDriveMetadata()) { + System.out.println("To start web-agent in test-drive mode, pass \"-tm\" and \"-ts\" parameters"); + System.out.println(); + } + + if (cfg.token() == null) { + String webHost= ""; + + try { + webHost = new URI(cfg.serverUri()).getHost(); + } + catch (URISyntaxException e) { + log.log(Level.SEVERE, "Failed to parse Ignite Web Console uri", e); + + return; + } + + 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.print("Enter security token: "); + + cfg.token(new String(System.console().readPassword())); + } + + if (cfg.testDriveMetadata()) + AgentMetadataTestDrive.testDrive(); + + if (cfg.testDriveSql()) + AgentSqlTestDrive.testDrive(cfg); + + RestExecutor restExecutor = new RestExecutor(cfg); + + restExecutor.start(); + + try { + SslContextFactory sslCtxFactory = new SslContextFactory(); + + // Workaround for use self-signed certificate: + if (Boolean.getBoolean("trust.all")) + sslCtxFactory.setTrustAll(true); + + WebSocketClient client = new WebSocketClient(sslCtxFactory); + + client.setMaxIdleTimeout(Long.MAX_VALUE); + + client.start(); + + try { + while (!Thread.interrupted()) { + AgentSocket agentSock = new AgentSocket(cfg, restExecutor); + + log.log(Level.INFO, "Connecting to: " + cfg.serverUri()); + + URI uri = URI.create(cfg.serverUri()); + + if (uri.getPort() == -1) + uri = URI.create(cfg.serverUri() + ":" + DFLT_SERVER_PORT); + + client.connect(agentSock, uri); + + agentSock.waitForClose(); + + Thread.sleep(RECONNECT_INTERVAL); + } + } + finally { + client.stop(); + } + } + finally { + restExecutor.stop(); + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLoggingConfigurator.java ---------------------------------------------------------------------- diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLoggingConfigurator.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLoggingConfigurator.java new file mode 100644 index 0000000..4912b06 --- /dev/null +++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLoggingConfigurator.java @@ -0,0 +1,90 @@ +/* + * + * * 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.agent; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.LogManager; + +/** + * Configurator for java.util.Logger. + */ +public class AgentLoggingConfigurator { + /** */ + private static final String CFG_PATH_PROPERTY = "log.config.path"; + + /** */ + private static final String PROPERTIES_FILE = "logging.properties"; + + /** + * Perform configure. + */ + public static void configure() { + try { + if (System.getProperty(CFG_PATH_PROPERTY) != null) { + File logCfg = new File(System.getProperty(CFG_PATH_PROPERTY)); + + if (!logCfg.isFile()) { + System.err.println("Failed to load logging configuration, file not found: " + logCfg); + + System.exit(1); + } + + readConfiguration(logCfg); + + return; + } + + File agentHome = AgentUtils.getAgentHome(); + + if (agentHome != null) { + File logCfg = new File(agentHome, PROPERTIES_FILE); + + if (logCfg.isFile()) { + readConfiguration(logCfg); + + return; + } + } + + LogManager.getLogManager().readConfiguration(AgentLauncher.class.getResourceAsStream("/" + + PROPERTIES_FILE)); + } + catch (IOException e) { + System.err.println("Failed to load logging configuration."); + + e.printStackTrace(); + + System.exit(1); + } + } + + /** + * @param file File. + */ + private static void readConfiguration(File file) throws IOException { + try (InputStream in = new BufferedInputStream(new FileInputStream(file))) { + LogManager.getLogManager().readConfiguration(in); + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentSocket.java ---------------------------------------------------------------------- diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentSocket.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentSocket.java new file mode 100644 index 0000000..12b87b9 --- /dev/null +++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentSocket.java @@ -0,0 +1,191 @@ +/* + * + * * 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.agent; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import java.io.IOException; +import java.net.ConnectException; +import java.util.concurrent.CountDownLatch; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.net.ssl.SSLHandshakeException; +import org.apache.ignite.agent.handlers.DatabaseMetadataExtractor; +import org.apache.ignite.agent.handlers.RestExecutor; +import org.apache.ignite.agent.remote.Remote; +import org.apache.ignite.agent.remote.RemoteHandler; +import org.apache.ignite.agent.remote.WebSocketSender; +import org.apache.ignite.agent.testdrive.AgentMetadataTestDrive; +import org.apache.ignite.agent.testdrive.AgentSqlTestDrive; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; + +/** + * Handler for web-socket connection. + */ +@WebSocket +public class AgentSocket implements WebSocketSender { + /** */ + public static final Gson GSON = new Gson(); + /** */ + public static final JsonParser PARSER = new JsonParser(); + /** */ + private static final Logger log = Logger.getLogger(AgentSocket.class.getName()); + /** */ + private final CountDownLatch closeLatch = new CountDownLatch(1); + + /** */ + private final AgentConfiguration cfg; + + /** */ + private final RestExecutor restExecutor; + + /** */ + private RemoteHandler remote; + + /** */ + private Session ses; + + /** + * @param cfg Config. + */ + public AgentSocket(AgentConfiguration cfg, RestExecutor restExecutor) { + this.cfg = cfg; + this.restExecutor = restExecutor; + } + + /** + * @param statusCode Status code. + * @param reason Reason. + */ + @OnWebSocketClose + public void onClose(int statusCode, String reason) { + log.log(Level.WARNING, String.format("Connection closed: %d - %s.", statusCode, reason)); + + if (remote != null) + remote.close(); + + closeLatch.countDown(); + } + + /** + * @param ses Session. + */ + @OnWebSocketConnect + public void onConnect(Session ses) { + log.log(Level.INFO, "Connection established."); + + this.ses = ses; + + remote = RemoteHandler.wrap(this, this, restExecutor, new DatabaseMetadataExtractor(cfg)); + + JsonObject authMsg = new JsonObject(); + + authMsg.addProperty("type", "AuthMessage"); + authMsg.addProperty("token", cfg.token()); + + send(authMsg); + } + + /** + * @param msg Message. + * @return Whether or not message was sent. + */ + @Override public boolean send(JsonObject msg) { + return send(GSON.toJson(msg)); + } + + /** + * @param msg Message. + * @return Whether or not message was sent. + */ + @Override public boolean send(String msg) { + try { + ses.getRemote().sendString(msg); + + return true; + } + catch (IOException ignored) { + log.log(Level.SEVERE, "Failed to send message to Control Center."); + + return false; + } + } + + /** + * @param ses Session. + * @param error Error. + */ + @OnWebSocketError + public void onError(Session ses, Throwable error) { + if (error instanceof ConnectException) + log.log(Level.WARNING, error.getMessage()); + else if (error instanceof SSLHandshakeException) { + log.log(Level.SEVERE, "Failed to establish SSL connection to Ignite Console. Start agent with " + + "\"-Dtrust.all=true\" to skip certificate validation in case of using self-signed certificate.", error); + + System.exit(1); + } + else + log.log(Level.SEVERE, "Connection error.", error); + + if (remote != null) + remote.close(); + + closeLatch.countDown(); + } + + /** + * @param msg Message. + */ + @OnWebSocketMessage + public void onMessage(String msg) { + JsonElement jsonElement = PARSER.parse(msg); + + remote.onMessage((JsonObject)jsonElement); + } + + /** + * @param errorMsg Authentication failed message or {@code null} if authentication success. + */ + @Remote + public void authResult(String errorMsg) { + if (errorMsg != null) { + onClose(401, "Authentication failed: " + errorMsg); + + System.exit(1); + } + + log.info("Authentication success."); + } + + /** + * Await socket close. + */ + public void waitForClose() throws InterruptedException { + closeLatch.await(); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentUtils.java ---------------------------------------------------------------------- diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentUtils.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentUtils.java new file mode 100644 index 0000000..d59e2d0 --- /dev/null +++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentUtils.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 org.apache.ignite.agent; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.ProtectionDomain; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Utility methods. + */ +public class AgentUtils { + /** */ + private static final Logger log = Logger.getLogger(AgentUtils.class.getName()); + + /** + * Default constructor. + */ + private AgentUtils() { + // No-op. + } + + /** + * @return App folder. + */ + public static File getAgentHome() { + try { + ProtectionDomain domain = AgentLauncher.class.getProtectionDomain(); + + // Should not happen, but to make sure our code is not broken. + if (domain == null || domain.getCodeSource() == null || domain.getCodeSource().getLocation() == null) { + log.log(Level.WARNING, "Failed to resolve agent jar location!"); + + return null; + } + + // Resolve path to class-file. + URI classesUri = domain.getCodeSource().getLocation().toURI(); + + boolean win = System.getProperty("os.name").toLowerCase().contains("win"); + + // Overcome UNC path problem on Windows (http://www.tomergabel.com/JavaMishandlesUNCPathsOnWindows.aspx) + if (win && classesUri.getAuthority() != null) + classesUri = new URI(classesUri.toString().replace("file://", "file:/")); + + return new File(classesUri).getParentFile(); + } + catch (URISyntaxException | SecurityException ignored) { + log.log(Level.WARNING, "Failed to resolve agent jar location!"); + + return null; + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/DatabaseMetadataExtractor.java ---------------------------------------------------------------------- diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/DatabaseMetadataExtractor.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/DatabaseMetadataExtractor.java new file mode 100644 index 0000000..be84f48 --- /dev/null +++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/DatabaseMetadataExtractor.java @@ -0,0 +1,208 @@ +/* + * + * * 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.agent.handlers; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.ignite.agent.AgentConfiguration; +import org.apache.ignite.agent.AgentUtils; +import org.apache.ignite.agent.remote.Remote; +import org.apache.ignite.schema.parser.DbMetadataReader; +import org.apache.ignite.schema.parser.DbTable; + +/** + * Remote API to extract database metadata. + */ +public class DatabaseMetadataExtractor { + /** */ + private static final Logger log = Logger.getLogger(DatabaseMetadataExtractor.class.getName()); + + /** */ + private final String driversFolder; + + /** + * @param cfg Config. + */ + public DatabaseMetadataExtractor(AgentConfiguration cfg) { + String driversFolder = cfg.driversFolder(); + + if (driversFolder == null) { + File agentHome = AgentUtils.getAgentHome(); + + if (agentHome != null) + driversFolder = new File(agentHome, "jdbc-drivers").getPath(); + } + + this.driversFolder = driversFolder; + } + + /** + * @param jdbcDriverJarPath JDBC driver JAR path. + * @param jdbcDriverCls JDBC driver class. + * @param jdbcUrl JDBC URL. + * @param jdbcInfo Properties to connect to database. + * @return Connection to database. + * @throws SQLException + */ + private Connection connect(String jdbcDriverJarPath, String jdbcDriverCls, String jdbcUrl, Properties jdbcInfo) throws SQLException { + if (!new File(jdbcDriverJarPath).isAbsolute() && driversFolder != null) + jdbcDriverJarPath = new File(driversFolder, jdbcDriverJarPath).getPath(); + + return DbMetadataReader.getInstance().connect(jdbcDriverJarPath, jdbcDriverCls, jdbcUrl, jdbcInfo); + } + + /** + * @param jdbcDriverJarPath JDBC driver JAR path. + * @param jdbcDriverCls JDBC driver class. + * @param jdbcUrl JDBC URL. + * @param jdbcInfo Properties to connect to database. + * @return Collection of schema names. + * @throws SQLException + */ + @Remote + public Collection<String> schemas(String jdbcDriverJarPath, String jdbcDriverCls, String jdbcUrl, + Properties jdbcInfo) throws SQLException { + log.log(Level.INFO, "Collecting database schemas..."); + + try (Connection conn = connect(jdbcDriverJarPath, jdbcDriverCls, jdbcUrl, jdbcInfo)) { + Collection<String> schemas = DbMetadataReader.getInstance().schemas(conn); + + log.log(Level.INFO, "Collected schemas: " + schemas.size()); + + return schemas; + } + } + + /** + * @param jdbcDriverJarPath JDBC driver JAR path. + * @param jdbcDriverCls JDBC driver class. + * @param jdbcUrl JDBC URL. + * @param jdbcInfo Properties to connect to database. + * @param schemas List of schema names to process. + * @param tblsOnly If {@code true} then only tables will be processed otherwise views also will be processed. + * @return Collection of tables. + */ + @Remote + public Collection<DbTable> metadata(String jdbcDriverJarPath, String jdbcDriverCls, String jdbcUrl, + Properties jdbcInfo, List<String> schemas, boolean tblsOnly) throws SQLException { + log.log(Level.INFO, "Collecting database metadata..."); + + try (Connection conn = connect(jdbcDriverJarPath, jdbcDriverCls, jdbcUrl, jdbcInfo)) { + Collection<DbTable> metadata = DbMetadataReader.getInstance().metadata(conn, schemas, tblsOnly); + + log.log(Level.INFO, "Collected metadata: " + metadata.size()); + + return metadata; + } + } + + /** + * @param path Path to normalize. + * @return Normalized file path. + */ + private String normalizePath(String path) { + return path != null ? path.replace('\\', '/') : null; + } + + /** + * @return Drivers in drivers folder + * @see AgentConfiguration#driversFolder + */ + @Remote + public List<JdbcDriver> availableDrivers() { + String drvFolder = normalizePath(driversFolder); + + log.log(Level.INFO, "Collecting JDBC drivers in folder: " + drvFolder); + + if (drvFolder == null) { + log.log(Level.INFO, "JDBC drivers folder not specified, returning empty list"); + + return Collections.emptyList(); + } + + String[] list = new File(drvFolder).list(); + + if (list == null) { + log.log(Level.INFO, "JDBC drivers folder has no files, returning empty list"); + + return Collections.emptyList(); + } + + List<JdbcDriver> res = new ArrayList<>(); + + for (String fileName : list) { + if (fileName.endsWith(".jar")) { + try { + String spec = normalizePath("jar:file:" + (drvFolder.startsWith("/") ? "" : "/") + drvFolder + '/' + fileName + + "!/META-INF/services/java.sql.Driver"); + + URL url = new URL(spec); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()))) { + String jdbcDriverCls = reader.readLine(); + + res.add(new JdbcDriver(fileName, jdbcDriverCls)); + + log.log(Level.INFO, "Found: [driver=" + fileName + ", class=" + jdbcDriverCls + "]"); + } + } + catch (IOException e) { + res.add(new JdbcDriver(fileName, null)); + + log.log(Level.INFO, "Found: [driver=" + fileName + "]"); + log.log(Level.INFO, "Failed to detect driver class: " + e.getMessage()); + } + } + } + + return res; + } + + /** + * Wrapper class for later to be transformed to JSON and send to Web Console. + */ + private static class JdbcDriver { + /** */ + private final String jdbcDriverJar; + /** */ + private final String jdbcDriverClass; + + /** + * @param jdbcDriverJar File name of driver jar file. + * @param jdbcDriverClass Optional JDBC driver class. + */ + public JdbcDriver(String jdbcDriverJar, String jdbcDriverClass) { + this.jdbcDriverJar = jdbcDriverJar; + this.jdbcDriverClass = jdbcDriverClass; + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/RestExecutor.java ---------------------------------------------------------------------- diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/RestExecutor.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/RestExecutor.java new file mode 100644 index 0000000..f4c5dff --- /dev/null +++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/RestExecutor.java @@ -0,0 +1,175 @@ +/* + * + * * 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.agent.handlers; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; +import org.apache.commons.codec.Charsets; +import org.apache.http.Header; +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.ignite.agent.AgentConfiguration; +import org.apache.ignite.agent.remote.Remote; + +import static org.apache.ignite.agent.AgentConfiguration.DFLT_NODE_PORT; + +/** + * Executor for REST requests. + */ +public class RestExecutor { + /** */ + private final AgentConfiguration cfg; + + /** */ + private CloseableHttpClient httpClient; + + /** + * @param cfg Config. + */ + public RestExecutor(AgentConfiguration cfg) { + this.cfg = cfg; + } + + /** + * + */ + public void start() { + httpClient = HttpClientBuilder.create().build(); + } + + /** + * + */ + public void stop() throws IOException { + if (httpClient != null) + httpClient.close(); + } + + /** + * @param path Path. + * @param mtd Method. + * @param params Params. + * @param headers Headers. + * @param body Body. + */ + @Remote + public RestResult executeRest(String path, Map<String, String> params, String mtd, Map<String, String> headers, + String body) throws IOException, URISyntaxException { + URIBuilder builder = new URIBuilder(cfg.nodeUri()); + + if (builder.getPort() == -1) + builder.setPort(DFLT_NODE_PORT); + + if (path != null) { + if (!path.startsWith("/") && !cfg.nodeUri().endsWith("/")) + path = '/' + path; + + builder.setPath(path); + } + + if (params != null) { + for (Map.Entry<String, String> entry : params.entrySet()) + builder.addParameter(entry.getKey(), entry.getValue()); + } + + HttpRequestBase httpReq; + + if ("GET".equalsIgnoreCase(mtd)) + httpReq = new HttpGet(builder.build()); + else if ("POST".equalsIgnoreCase(mtd)) { + HttpPost post; + + if (body == null) { + List<NameValuePair> nvps = builder.getQueryParams(); + + builder.clearParameters(); + + post = new HttpPost(builder.build()); + + if (!nvps.isEmpty()) + post.setEntity(new UrlEncodedFormEntity(nvps)); + } + else { + post = new HttpPost(builder.build()); + + post.setEntity(new StringEntity(body)); + } + + httpReq = post; + } + else + throw new IOException("Unknown HTTP-method: " + mtd); + + if (headers != null) { + for (Map.Entry<String, String> entry : headers.entrySet()) + httpReq.addHeader(entry.getKey(), entry.getValue()); + } + + try (CloseableHttpResponse resp = httpClient.execute(httpReq)) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + resp.getEntity().writeTo(out); + + Charset charset = Charsets.UTF_8; + + Header encodingHdr = resp.getEntity().getContentEncoding(); + + if (encodingHdr != null) { + String encoding = encodingHdr.getValue(); + + charset = Charsets.toCharset(encoding); + } + + return new RestResult(resp.getStatusLine().getStatusCode(), new String(out.toByteArray(), charset)); + } + } + + /** + * Request result. + */ + public static class RestResult { + /** Status code. */ + private int code; + + /** Message. */ + private String message; + + /** + * @param code Code. + * @param msg Message. + */ + public RestResult(int code, String msg) { + this.code = code; + message = msg; + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/Remote.java ---------------------------------------------------------------------- diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/Remote.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/Remote.java new file mode 100644 index 0000000..aec2f17 --- /dev/null +++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/Remote.java @@ -0,0 +1,39 @@ +/* + * + * * 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.agent.remote; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Use this annotation to associate methods with remote NodeJS server commands. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Remote { + /** + * Whether or not method should be executed synchronously. + * + * @return {@code true} if method will be executed in separated thread otherwise if method will be executed in handler thread. + */ + boolean async() default true; +} http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/RemoteHandler.java ---------------------------------------------------------------------- diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/RemoteHandler.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/RemoteHandler.java new file mode 100644 index 0000000..5bbe609 --- /dev/null +++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/RemoteHandler.java @@ -0,0 +1,253 @@ +/* + * + * * 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.agent.remote; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.http.auth.AuthenticationException; + +/** + * Allow to execute methods remotely from NodeJS server by web-socket command. + */ +public class RemoteHandler implements AutoCloseable { + /** */ + public static final Gson GSON = new Gson(); + /** */ + public static final Object[] EMPTY_OBJECTS = new Object[0]; + /** */ + private static final Logger log = Logger.getLogger(RemoteHandler.class.getName()); + /** */ + private static final String INTERNAL_EXCEPTION_TYPE = "org.apache.ignite.agent.AgentException"; + /** */ + private final WebSocketSender snd; + + /** */ + private final Map<String, MethodDescriptor> mtds = new HashMap<>(); + + /** */ + private final ExecutorService executorSrvc = Executors.newFixedThreadPool(Runtime.getRuntime() + .availableProcessors()); + + /** + * @param snd Session. + * @param hnds Handlers. + */ + private RemoteHandler(WebSocketSender snd, Object ... hnds) { + this.snd = snd; + + for (Object hnd : hnds) { + for (Method method : hnd.getClass().getMethods()) { + Remote remoteAnn = method.getAnnotation(Remote.class); + + if (remoteAnn != null) { + MethodDescriptor old = mtds.put(method.getName(), new MethodDescriptor(method, hnd, + remoteAnn.async())); + + if (old != null) + throw new IllegalArgumentException("Duplicated method: " + method.getName()); + } + } + } + } + + /** + * @param hnds Handler. + * @param snd Sender. + */ + public static RemoteHandler wrap(WebSocketSender snd, Object ... hnds) { + return new RemoteHandler(snd, hnds); + } + + /** + * @param req Request. + */ + public void onMessage(JsonObject req) { + log.log(Level.FINE, "Message: " + req); + + JsonPrimitive reqIdJson = req.getAsJsonPrimitive("reqId"); + + final Long reqId = reqIdJson == null ? null : reqIdJson.getAsLong(); + + String mtdName = req.getAsJsonPrimitive("mtdName").getAsString(); + + final MethodDescriptor desc = mtds.get(mtdName); + + if (desc == null) { + sendException(reqId, INTERNAL_EXCEPTION_TYPE, "Unknown method: " + mtdName); + + return; + } + + Type[] paramTypes = desc.mtd.getGenericParameterTypes(); + + JsonArray argsJson = req.getAsJsonArray("args"); + + final Object[] args; + + if (paramTypes.length > 0) { + args = new Object[paramTypes.length]; + + if (argsJson == null || argsJson.size() != paramTypes.length) { + sendException(reqId, INTERNAL_EXCEPTION_TYPE, "Inconsistent parameters"); + + return; + } + + for (int i = 0; i < paramTypes.length; i++) + args[i] = GSON.fromJson(argsJson.get(i), paramTypes[i]); + } + else { + args = EMPTY_OBJECTS; + + if (argsJson != null && argsJson.size() > 0) { + sendException(reqId, INTERNAL_EXCEPTION_TYPE, "Inconsistent parameters"); + + return; + } + } + + Runnable run = new Runnable() { + @Override public void run() { + final Object res; + + try { + res = desc.mtd.invoke(desc.hnd, args); + } + catch (Throwable e) { + if (e instanceof AuthenticationException) { + close(); + + return; + } + + if (e instanceof InvocationTargetException) + e = ((InvocationTargetException)e).getTargetException(); + + if (reqId != null) + sendException(reqId, e.getClass().getName(), e.getMessage()); + else + log.log(Level.SEVERE, "Exception on execute remote method.", e); + + return; + } + + sendResponse(reqId, res, desc.returnType); + } + }; + + if (desc.async) + executorSrvc.submit(run); + else + run.run(); + } + + /** + * @param reqId Request id. + * @param exType Exception class name. + * @param exMsg Exception message. + */ + protected void sendException(Long reqId, String exType, String exMsg) { + if (reqId == null) + return; + + JsonObject res = new JsonObject(); + + res.addProperty("type", "CallRes"); + res.addProperty("reqId", reqId); + + JsonObject exJson = new JsonObject(); + exJson.addProperty("type", exType); + exJson.addProperty("message", exMsg); + + res.add("ex", exJson); + + snd.send(res); + } + + /** + * @param reqId Request id. + * @param res Result. + * @param type Type. + */ + private void sendResponse(Long reqId, Object res, Type type) { + if (reqId == null) + return; + + JsonObject resp = new JsonObject(); + + resp.addProperty("type", "CallRes"); + + resp.addProperty("reqId", reqId); + + JsonElement resJson = type == void.class ? JsonNull.INSTANCE : GSON.toJsonTree(res, type); + + resp.add("res", resJson); + + snd.send(resp); + } + + /** {@inheritDoc} */ + @Override public void close() { + executorSrvc.shutdown(); + } + + /** + * + */ + private static class MethodDescriptor { + /** */ + private final Method mtd; + + /** */ + private final Object hnd; + + /** */ + private final Type returnType; + + /** */ + private final boolean async; + + /** + * @param mtd Method. + * @param hnd Handler. + * @param async Async. + */ + MethodDescriptor(Method mtd, Object hnd, boolean async) { + this.mtd = mtd; + this.hnd = hnd; + this.async = async; + + returnType = mtd.getGenericReturnType(); + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/WebSocketSender.java ---------------------------------------------------------------------- diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/WebSocketSender.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/WebSocketSender.java new file mode 100644 index 0000000..b686b27 --- /dev/null +++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/WebSocketSender.java @@ -0,0 +1,41 @@ +/* + * + * * 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.agent.remote; + +import com.google.gson.JsonObject; + +/** + * Sender for messages to web-socket. + */ +public interface WebSocketSender { + /** + * Send message. + * @param msg Message. + * @return {@code true} if message sent successfully. + */ + public boolean send(String msg); + + /** + * Send message. + * @param msg Message. + * @return {@code true} if message sent successfully. + */ + public boolean send(JsonObject msg); +} http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/AgentMetadataTestDrive.java ---------------------------------------------------------------------- diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/AgentMetadataTestDrive.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/AgentMetadataTestDrive.java new file mode 100644 index 0000000..09ceb53 --- /dev/null +++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/AgentMetadataTestDrive.java @@ -0,0 +1,90 @@ +/* + * + * * 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.agent.testdrive; + +import java.io.File; +import java.io.FileReader; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.ignite.agent.AgentUtils; +import org.h2.tools.RunScript; +import org.h2.tools.Server; + +/** + * Test drive for metadata load from database. + * + * H2 database will be started and several tables will be created. + */ +public class AgentMetadataTestDrive { + /** */ + private static final Logger log = Logger.getLogger(AgentMetadataTestDrive.class.getName()); + + /** */ + private static final AtomicBoolean initLatch = new AtomicBoolean(); + + /** + * Execute query. + * + * @param conn Connection to database. + * @param qry Statement to execute. + */ + private static void query(Connection conn, String qry) throws SQLException { + try (PreparedStatement ps = conn.prepareStatement(qry)) { + ps.executeUpdate(); + } + } + + /** + * Start H2 database and populate it with several tables. + */ + public static void testDrive() { + if (initLatch.compareAndSet(false, true)) { + log.log(Level.FINE, "TEST-DRIVE: Prepare in-memory H2 database..."); + + try { + Connection conn = DriverManager.getConnection("jdbc:h2:mem:test-drive-db;DB_CLOSE_DELAY=-1", "sa", ""); + + File agentHome = AgentUtils.getAgentHome(); + + File sqlScript = new File((agentHome != null) ? new File(agentHome, "test-drive") : new File("test-drive"), + "test-drive.sql"); + + RunScript.execute(conn, new FileReader(sqlScript)); + log.log(Level.FINE, "TEST-DRIVE: Sample tables created."); + + conn.close(); + + Server.createTcpServer("-tcpDaemon").start(); + + log.log(Level.INFO, "TEST-DRIVE: TcpServer stared."); + + log.log(Level.INFO, "TEST-DRIVE: JDBC URL for test drive metadata load: jdbc:h2:mem:test-drive-db"); + } + catch (Exception e) { + log.log(Level.SEVERE, "TEST-DRIVE: Failed to start test drive for metadata!", e); + } + } + } +}