You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by an...@apache.org on 2015/10/13 05:28:03 UTC

[02/18] ignite git commit: ignite-843 Agent initial commit

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 <an...@gridgain.com>
Authored: Tue Oct 13 10:05:15 2015 +0700
Committer: Andrey <an...@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);
+            }
+        }
+    }
+}