ignite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From anovi...@apache.org
Subject [1/6] ignite git commit: IGNITE-843 WIP
Date Fri, 15 Jan 2016 11:04:02 GMT
Repository: ignite
Updated Branches:
  refs/heads/ignite-843-rc2 f6629cab8 -> d603a53a8


http://git-wip-us.apache.org/repos/asf/ignite/blob/54a24746/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/remote/RemoteHandler.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/remote/RemoteHandler.java b/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/remote/RemoteHandler.java
new file mode 100644
index 0000000..4eda313
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/remote/RemoteHandler.java
@@ -0,0 +1,252 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.console.agent.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 org.apache.http.auth.AuthenticationException;
+import org.apache.log4j.Logger;
+
+/**
+ * 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) {
+        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.error("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/54a24746/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/remote/WebSocketSender.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/remote/WebSocketSender.java b/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/remote/WebSocketSender.java
new file mode 100644
index 0000000..cceb86b
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/remote/WebSocketSender.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.console.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/54a24746/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/AgentMetadataDemo.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/AgentMetadataDemo.java b/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/AgentMetadataDemo.java
new file mode 100644
index 0000000..5d33f29
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/AgentMetadataDemo.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.console.demo;
+
+import java.io.File;
+import java.io.FileReader;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.log4j.Logger;
+import org.h2.tools.RunScript;
+import org.h2.tools.Server;
+
+import static org.apache.ignite.console.agent.AgentUtils.resolvePath;
+
+/**
+ * Demo for metadata load from database.
+ *
+ * H2 database will be started and several tables will be created.
+ */
+public class AgentMetadataDemo {
+    /** */
+    private static final Logger log = Logger.getLogger(AgentMetadataDemo.class.getName());
+
+    /** */
+    private static final AtomicBoolean initLatch = new AtomicBoolean();
+
+    /**
+     * @param jdbcUrl Connection url.
+     * @return true if url is used for test-drive.
+     */
+    public static boolean isTestDriveUrl(String jdbcUrl) {
+        return "jdbc:h2:mem:demo-db".equals(jdbcUrl);
+    }
+
+    /**
+     * Start H2 database and populate it with several tables.
+     */
+    public static void testDrive() {
+        if (initLatch.compareAndSet(false, true)) {
+            log.info("DEMO: Prepare in-memory H2 database...");
+
+            try {
+                Connection conn = DriverManager.getConnection("jdbc:h2:mem:demo-db;DB_CLOSE_DELAY=-1", "sa", "");
+
+                File sqlScript = resolvePath("demo/db-init.sql");
+
+                if (sqlScript == null) {
+                    log.error("DEMO: Failed to find demo database init script file: demo/db-init.sql");
+                    log.error("DEMO: Failed to start demo for metadata");
+
+                    return;
+                }
+
+                RunScript.execute(conn, new FileReader(sqlScript));
+
+                log.info("DEMO: Sample tables created.");
+
+                conn.close();
+
+                Server.createTcpServer("-tcpDaemon").start();
+
+                log.info("DEMO: TcpServer stared.");
+
+                log.info("DEMO: JDBC URL for test drive metadata load: jdbc:h2:mem:demo-db");
+            }
+            catch (Exception e) {
+                log.error("DEMO: Failed to start test drive for metadata!", e);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/54a24746/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/AgentSqlDemo.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/AgentSqlDemo.java b/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/AgentSqlDemo.java
new file mode 100644
index 0000000..1e63232
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/AgentSqlDemo.java
@@ -0,0 +1,551 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.console.demo;
+
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Random;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.console.agent.AgentConfiguration;
+import org.apache.ignite.console.demo.model.Car;
+import org.apache.ignite.console.demo.model.Country;
+import org.apache.ignite.console.demo.model.Department;
+import org.apache.ignite.console.demo.model.Employee;
+import org.apache.ignite.console.demo.model.Parking;
+import org.apache.ignite.cache.QueryEntity;
+import org.apache.ignite.cache.QueryIndex;
+import org.apache.ignite.cache.QueryIndexType;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.logger.NullLogger;
+import org.apache.ignite.spi.IgniteSpiException;
+import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinderAdapter;
+import org.apache.log4j.Logger;
+
+import static org.apache.ignite.IgniteSystemProperties.IGNITE_JETTY_PORT;
+import static org.apache.ignite.internal.IgniteNodeAttributes.ATTR_REST_JETTY_ADDRS;
+import static org.apache.ignite.internal.IgniteNodeAttributes.ATTR_REST_JETTY_PORT;
+
+/**
+ * Demo for SQL.
+ *
+ * Cache will be created and populated with data to query.
+ */
+public class AgentSqlDemo {
+    /** */
+    private static final Logger log = Logger.getLogger(AgentMetadataDemo.class.getName());
+
+    /** */
+    private static final AtomicBoolean initLatch = new AtomicBoolean();
+
+    /** */
+    private static final String COUNTRY_CACHE_NAME = "CountryCache";
+    /** */
+    private static final String DEPARTMENT_CACHE_NAME = "DepartmentCache";
+    /** */
+    private static final String EMPLOYEE_CACHE_NAME = "EmployeeCache";
+    /** */
+    private static final String PARKING_CACHE_NAME = "ParkingCache";
+    /** */
+    private static final String CAR_CACHE_NAME = "CarCache";
+
+    /** */
+    private static final Random rnd = new Random();
+
+    /** Countries count. */
+    private static final int CNTR_CNT = 10;
+
+    /** Departments count */
+    private static final int DEP_CNT = 100;
+
+    /** Employees count. */
+    private static final int EMPL_CNT = 1000;
+
+    /** Countries count. */
+    private static final int CAR_CNT = 100;
+
+    /** Departments count */
+    private static final int PARK_CNT = 10;
+
+    /** Counter for threads in pool. */
+    private static final AtomicInteger THREAD_CNT = new AtomicInteger(0);
+
+    /**
+     * Configure cacheCountry.
+     */
+    private static <K, V> CacheConfiguration<K, V> cacheCountry() {
+        CacheConfiguration<K, V> ccfg = new CacheConfiguration<>(COUNTRY_CACHE_NAME);
+
+        // Configure cacheCountry types.
+        Collection<QueryEntity> queryEntities = new ArrayList<>();
+
+        // COUNTRY.
+        QueryEntity type = new QueryEntity();
+
+        queryEntities.add(type);
+
+        type.setKeyType(Integer.class.getName());
+        type.setValueType(Country.class.getName());
+
+        // Query fields for COUNTRY.
+        LinkedHashMap<String, String> qryFlds = new LinkedHashMap<>();
+
+        qryFlds.put("id", "java.lang.Integer");
+        qryFlds.put("name", "java.lang.String");
+        qryFlds.put("population", "java.lang.Integer");
+
+        type.setFields(qryFlds);
+
+        // Indexes for COUNTRY.
+        type.setIndexes(Collections.singletonList(new QueryIndex("id", QueryIndexType.SORTED, false, "PRIMARY_KEY_6")));
+
+        ccfg.setQueryEntities(queryEntities);
+
+        return ccfg;
+    }
+
+    /**
+     * Configure cacheEmployee.
+     */
+    private static <K, V> CacheConfiguration<K, V> cacheDepartment() {
+        CacheConfiguration<K, V> ccfg = new CacheConfiguration<>(DEPARTMENT_CACHE_NAME);
+
+        // Configure cacheDepartment types.
+        Collection<QueryEntity> queryEntities = new ArrayList<>();
+
+        // DEPARTMENT.
+        QueryEntity type = new QueryEntity();
+
+        queryEntities.add(type);
+
+        type.setKeyType(Integer.class.getName());
+        type.setValueType(Department.class.getName());
+
+        // Query fields for DEPARTMENT.
+        LinkedHashMap<String, String> qryFlds = new LinkedHashMap<>();
+
+        qryFlds.put("id", "java.lang.Integer");
+        qryFlds.put("countryId", "java.lang.Integer");
+        qryFlds.put("name", "java.lang.String");
+
+        type.setFields(qryFlds);
+
+        // Indexes for DEPARTMENT.
+        type.setIndexes(Collections.singletonList(new QueryIndex("id", QueryIndexType.SORTED, false, "PRIMARY_KEY_4")));
+
+        ccfg.setQueryEntities(queryEntities);
+
+        return ccfg;
+    }
+
+    /**
+     * Configure cacheEmployee.
+     */
+    private static <K, V> CacheConfiguration<K, V> cacheEmployee() {
+        CacheConfiguration<K, V> ccfg = new CacheConfiguration<>(EMPLOYEE_CACHE_NAME);
+
+        // Configure cacheEmployee types.
+        Collection<QueryEntity> queryEntities = new ArrayList<>();
+
+        // EMPLOYEE.
+        QueryEntity type = new QueryEntity();
+
+        queryEntities.add(type);
+
+        type.setKeyType(Integer.class.getName());
+        type.setValueType(Employee.class.getName());
+
+        // Query fields for EMPLOYEE.
+        LinkedHashMap<String, String> qryFlds = new LinkedHashMap<>();
+
+        qryFlds.put("id", "java.lang.Integer");
+        qryFlds.put("departmentId", "java.lang.Integer");
+        qryFlds.put("managerId", "java.lang.Integer");
+        qryFlds.put("firstName", "java.lang.String");
+        qryFlds.put("lastName", "java.lang.String");
+        qryFlds.put("email", "java.lang.String");
+        qryFlds.put("phoneNumber", "java.lang.String");
+        qryFlds.put("hireDate", "java.sql.Date");
+        qryFlds.put("job", "java.lang.String");
+        qryFlds.put("salary", "java.lang.Double");
+
+        type.setFields(qryFlds);
+
+        // Indexes for EMPLOYEE.
+        Collection<QueryIndex> indexes = new ArrayList<>();
+
+        indexes.add(new QueryIndex("id", QueryIndexType.SORTED, false, "PRIMARY_KEY_7"));
+
+        QueryIndex index = new QueryIndex();
+
+        index.setName("EMP_NAMES");
+        index.setIndexType(QueryIndexType.SORTED);
+        LinkedHashMap<String, Boolean> indFlds = new LinkedHashMap<>();
+
+        indFlds.put("firstName", false);
+        indFlds.put("lastName", false);
+
+        index.setFields(indFlds);
+
+        indexes.add(index);
+        indexes.add(new QueryIndex("salary", QueryIndexType.SORTED, false, "EMP_SALARY"));
+
+        type.setIndexes(indexes);
+
+        ccfg.setQueryEntities(queryEntities);
+
+        return ccfg;
+    }
+
+    /**
+     * Configure cacheEmployee.
+     */
+    private static <K, V> CacheConfiguration<K, V> cacheParking() {
+        CacheConfiguration<K, V> ccfg = new CacheConfiguration<>(PARKING_CACHE_NAME);
+
+        // Configure cacheParking types.
+        Collection<QueryEntity> queryEntities = new ArrayList<>();
+
+        // PARKING.
+        QueryEntity type = new QueryEntity();
+
+        queryEntities.add(type);
+
+        type.setKeyType(Integer.class.getName());
+        type.setValueType(Parking.class.getName());
+
+        // Query fields for PARKING.
+        LinkedHashMap<String, String> qryFlds = new LinkedHashMap<>();
+
+        qryFlds.put("id", "java.lang.Integer");
+        qryFlds.put("name", "java.lang.String");
+        qryFlds.put("capacity", "java.lang.Integer");;
+
+        type.setFields(qryFlds);
+
+        // Indexes for PARKING.
+        type.setIndexes(Collections.singletonList(new QueryIndex("id", QueryIndexType.SORTED, false, "PRIMARY_KEY_F")));
+
+        ccfg.setQueryEntities(queryEntities);
+
+        return ccfg;
+    }
+
+    /**
+     * Configure cacheEmployee.
+     */
+    private static <K, V> CacheConfiguration<K, V> cacheCar() {
+        CacheConfiguration<K, V> ccfg = new CacheConfiguration<>(CAR_CACHE_NAME);
+
+        // Configure cacheCar types.
+        Collection<QueryEntity> queryEntities = new ArrayList<>();
+
+        // CAR.
+        QueryEntity type = new QueryEntity();
+
+        queryEntities.add(type);
+
+        type.setKeyType(Integer.class.getName());
+        type.setValueType(Car.class.getName());
+
+        // Query fields for CAR.
+        LinkedHashMap<String, String> qryFlds = new LinkedHashMap<>();
+
+        qryFlds.put("id", "java.lang.Integer");
+        qryFlds.put("parkingId", "java.lang.Integer");
+        qryFlds.put("name", "java.lang.String");
+
+        type.setFields(qryFlds);
+
+        // Indexes for CAR.
+        type.setIndexes(Collections.singletonList(new QueryIndex("id", QueryIndexType.SORTED, false, "PRIMARY_KEY_1")));
+
+        ccfg.setQueryEntities(queryEntities);
+
+        return ccfg;
+    }
+
+    /**
+     * @param val Value to round.
+     * @param places Numbers after point.
+     * @return Rounded value;
+     */
+    private static double round(double val, int places) {
+        if (places < 0)
+            throw new IllegalArgumentException();
+
+        long factor = (long) Math.pow(10, places);
+
+        val *= factor;
+
+        long tmp = Math.round(val);
+
+        return (double) tmp / factor;
+    }
+
+    /**
+     * @param ignite Ignite.
+     * @param range Time range in milliseconds.
+     */
+    private static void populateCacheEmployee(Ignite ignite, long range) {
+        log.trace("DEMO: Start employees population with data...");
+
+        IgniteCache<Integer, Country> cacheCountry = ignite.cache(COUNTRY_CACHE_NAME);
+
+        for (int i = 0, n = i + 1; i < CNTR_CNT; i++)
+            cacheCountry.put(i, new Country(i, "Country #" + n, n * 10000000));
+
+        IgniteCache<Integer, Department> cacheDepartment = ignite.cache(DEPARTMENT_CACHE_NAME);
+
+        IgniteCache<Integer, Employee> cacheEmployee = ignite.cache(EMPLOYEE_CACHE_NAME);
+
+        for (int i = 0, n = i + 1; i < DEP_CNT; i++) {
+            cacheDepartment.put(i, new Department(n, rnd.nextInt(CNTR_CNT), "Department #" + n));
+
+            double r = rnd.nextDouble();
+
+            cacheEmployee.put(i, new Employee(i, rnd.nextInt(DEP_CNT), null, "First name manager #" + n,
+                "Last name manager #" + n, "Email manager #" + n, "Phone number manager #" + n,
+                new java.sql.Date((long)(r * range)), "Job manager #" + n, 1000 + round(r * 4000, 2)));
+        }
+
+        for (int i = 0, n = i + 1; i < EMPL_CNT; i++) {
+            Integer depId = rnd.nextInt(DEP_CNT);
+
+            double r = rnd.nextDouble();
+
+            cacheEmployee.put(i, new Employee(i, depId, depId, "First name employee #" + n,
+                    "Last name employee #" + n, "Email employee #" + n, "Phone number employee #" + n,
+                    new java.sql.Date((long)(r * range)), "Job employee #" + n, 500 + round(r * 2000, 2)));
+        }
+
+        log.trace("DEMO: Finished employees population.");
+    }
+
+    /**
+     * @param ignite Ignite.
+     */
+    private static void populateCacheCar(Ignite ignite) {
+        log.trace("DEMO: Start cars population...");
+
+        IgniteCache<Integer, Parking> cacheParking = ignite.cache(PARKING_CACHE_NAME);
+
+        for (int i = 0, n = i + 1; i < PARK_CNT; i++)
+            cacheParking.put(i, new Parking(i, "Parking #" + n, n * 10));
+
+        IgniteCache<Integer, Car> cacheCar = ignite.cache(CAR_CACHE_NAME);
+
+        for (int i = 0, n = i + 1; i < CAR_CNT; i++)
+            cacheCar.put(i, new Car(i, rnd.nextInt(PARK_CNT), "Car #" + n));
+
+        log.trace("DEMO: Finished cars population.");
+    }
+
+    /**
+     * Creates a thread pool that can schedule commands to run after a given delay, or to execute periodically.
+     *
+     * @param corePoolSize Number of threads to keep in the pool, even if they are idle.
+     * @param threadName Part of thread name that would be used by thread factory.
+     * @return Newly created scheduled thread pool.
+     */
+    private static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, final String threadName) {
+        ScheduledExecutorService srvc = Executors.newScheduledThreadPool(corePoolSize, new ThreadFactory() {
+            @Override public Thread newThread(Runnable r) {
+                Thread thread = new Thread(r, String.format("%s-%d", threadName, THREAD_CNT.getAndIncrement()));
+
+                thread.setDaemon(true);
+
+                return thread;
+            }
+        });
+
+        ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) srvc;
+
+        // Setting up shutdown policy.
+        executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
+        executor.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
+
+        return srvc;
+    }
+
+    /**
+     * Starts read and write from cache in background.
+     *
+     * @param ignite Ignite.
+     * @param cnt - maximum count read/write key
+     */
+    private static void startLoad(final Ignite ignite, final int cnt) {
+        final long diff = new java.util.Date().getTime();
+
+        populateCacheEmployee(ignite, diff);
+
+        populateCacheCar(ignite);
+
+        ScheduledExecutorService cachePool = newScheduledThreadPool(2, "demo-sql-load-cache-tasks");
+
+        cachePool.scheduleWithFixedDelay(new Runnable() {
+            @Override public void run() {
+                try {
+                    IgniteCache<Integer, Employee> cacheEmployee = ignite.cache(EMPLOYEE_CACHE_NAME);
+
+                    if (cacheEmployee != null)
+                        for (int i = 0, n = i + 1; i < cnt; i++) {
+                            Integer id = rnd.nextInt(EMPL_CNT);
+
+                            Integer depId = rnd.nextInt(DEP_CNT);
+
+                            double r = rnd.nextDouble();
+
+                            cacheEmployee.put(id, new Employee(id, depId, depId, "First name employee #" + n,
+                                    "Last name employee #" + n, "Email employee #" + n, "Phone number employee #" + n,
+                                    new java.sql.Date((long)(r * diff)), "Job employee #" + n, 500 + round(r * 2000, 2)));
+
+                            if (rnd.nextBoolean())
+                                cacheEmployee.remove(rnd.nextInt(EMPL_CNT));
+                        }
+                }
+                catch (IllegalStateException ignored) {
+                    // No-op.
+                }
+                catch (Throwable e) {
+                    if (!e.getMessage().contains("cache is stopped"))
+                        ignite.log().error("Cache write task execution error", e);
+                }
+            }
+        }, 10, 3, TimeUnit.SECONDS);
+
+        cachePool.scheduleWithFixedDelay(new Runnable() {
+            @Override public void run() {
+                try {
+                    IgniteCache<Integer, Car> cache = ignite.cache(CAR_CACHE_NAME);
+
+                    if (cache != null)
+                        for (int i = 0; i < cnt; i++) {
+                            Integer carId = rnd.nextInt(CAR_CNT);
+
+                            cache.put(carId, new Car(carId, rnd.nextInt(PARK_CNT), "Car #" + (i + 1)));
+
+                            if (rnd.nextBoolean())
+                                cache.remove(rnd.nextInt(CAR_CNT));
+                        }
+                }
+                catch (IllegalStateException ignored) {
+                    // No-op.
+                }
+                catch (Throwable e) {
+                    if (!e.getMessage().contains("cache is stopped"))
+                        ignite.log().error("Cache write task execution error", e);
+                }
+            }
+        }, 10, 3, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Start ignite node with cacheEmployee and populate it with data.
+     */
+    public static boolean testDrive(AgentConfiguration acfg) {
+        if (initLatch.compareAndSet(false, true)) {
+            log.info("DEMO: Starting embedded node for sql test-drive...");
+
+            try {
+                IgniteConfiguration cfg = new IgniteConfiguration();
+
+                cfg.setLocalHost("127.0.0.1");
+
+                cfg.setMetricsLogFrequency(0);
+
+                cfg.setGridLogger(new NullLogger());
+
+                // Configure discovery SPI.
+                TcpDiscoverySpi discoSpi = new TcpDiscoverySpi();
+
+                discoSpi.setLocalPort(60900);
+
+                discoSpi.setIpFinder(new TcpDiscoveryIpFinderAdapter() {
+                    {
+                        setShared(true);
+                    }
+
+                    /** {@inheritDoc} */
+                    @Override public Collection<InetSocketAddress> getRegisteredAddresses() throws IgniteSpiException {
+                        return Collections.emptyList();
+                    }
+
+                    /** {@inheritDoc} */
+                    @Override public void registerAddresses(Collection<InetSocketAddress> addrs) throws IgniteSpiException {
+                        // No-op.
+                    }
+
+                    /** {@inheritDoc} */
+                    @Override public void unregisterAddresses(Collection<InetSocketAddress> addrs) throws IgniteSpiException {
+                        // No-op.
+                    }
+                });
+
+                cfg.setDiscoverySpi(discoSpi);
+
+                cfg.setCacheConfiguration(cacheCountry(), cacheDepartment(), cacheEmployee(), cacheParking(), cacheCar());
+
+                System.setProperty(IGNITE_JETTY_PORT, "60800");
+
+                log.trace("DEMO: Start embedded node with indexed enabled caches...");
+
+                IgniteEx ignite = (IgniteEx)Ignition.start(cfg);
+
+                String host = ((Collection<String>)
+                    ignite.localNode().attribute(ATTR_REST_JETTY_ADDRS)).iterator().next();
+
+                Integer port = ignite.localNode().attribute(ATTR_REST_JETTY_PORT);
+
+                if (F.isEmpty(host) || port == null) {
+                    log.error("DEMO: Failed to start embedded node with rest!");
+
+                    return false;
+                }
+
+                acfg.demoNodeUri(String.format("http://%s:%d", host, port));
+
+                log.info("DEMO: Embedded node for sql test-drive successfully started");
+
+                startLoad(ignite, 20);
+            }
+            catch (Exception e) {
+                log.error("DEMO: Failed to start embedded node for sql test-drive!", e);
+
+                return false;
+            }
+        }
+
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/54a24746/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/model/Car.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/model/Car.java b/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/model/Car.java
new file mode 100755
index 0000000..16b1120
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/model/Car.java
@@ -0,0 +1,137 @@
+package org.apache.ignite.console.demo.model;
+
+import java.io.Serializable;
+
+/**
+ * Car definition.
+ *
+ * This configuration was generated by Ignite Web Console (01/14/2016 13:46)
+ */
+public class Car implements Serializable {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Value for id. */
+    private int id;
+
+    /** Value for parkingId. */
+    private int parkingId;
+
+    /** Value for name. */
+    private String name;
+
+    /**
+     * Empty constructor.
+     */
+    public Car() {
+        // No-op.
+    }
+
+    /**
+     * Full constructor.
+     */
+    public Car(
+        int id,
+        int parkingId,
+        String name
+    ) {
+        this.id = id;
+        this.parkingId = parkingId;
+        this.name = name;
+    }
+
+    /**
+     * Gets id.
+     *
+     * @return Value for id.
+     */
+    public int getId() {
+        return id;
+    }
+
+    /**
+     * Sets id.
+     *
+     * @param id New value for id.
+     */
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    /**
+     * Gets parkingId.
+     *
+     * @return Value for parkingId.
+     */
+    public int getParkingId() {
+        return parkingId;
+    }
+
+    /**
+     * Sets parkingId.
+     *
+     * @param parkingId New value for parkingId.
+     */
+    public void setParkingId(int parkingId) {
+        this.parkingId = parkingId;
+    }
+
+    /**
+     * Gets name.
+     *
+     * @return Value for name.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Sets name.
+     *
+     * @param name New value for name.
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        
+        if (!(o instanceof Car))
+            return false;
+
+        Car that = (Car)o;
+
+        if (id != that.id)
+            return false;
+
+        if (parkingId != that.parkingId)
+            return false;
+
+        if (name != null ? !name.equals(that.name) : that.name != null)
+            return false;
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        int res = id;
+
+        res = 31 * res + parkingId;
+
+        res = 31 * res + (name != null ? name.hashCode() : 0);
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return "Car [id=" + id +
+            ", parkingId=" + parkingId +
+            ", name=" + name +
+            ']';
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/54a24746/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/model/Country.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/model/Country.java b/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/model/Country.java
new file mode 100755
index 0000000..1817224
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/model/Country.java
@@ -0,0 +1,137 @@
+package org.apache.ignite.console.demo.model;
+
+import java.io.Serializable;
+
+/**
+ * Country definition.
+ *
+ * This configuration was generated by Ignite Web Console (01/14/2016 13:46)
+ */
+public class Country implements Serializable {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Value for id. */
+    private int id;
+
+    /** Value for name. */
+    private String name;
+
+    /** Value for population. */
+    private int population;
+
+    /**
+     * Empty constructor.
+     */
+    public Country() {
+        // No-op.
+    }
+
+    /**
+     * Full constructor.
+     */
+    public Country(
+        int id,
+        String name,
+        int population
+    ) {
+        this.id = id;
+        this.name = name;
+        this.population = population;
+    }
+
+    /**
+     * Gets id.
+     *
+     * @return Value for id.
+     */
+    public int getId() {
+        return id;
+    }
+
+    /**
+     * Sets id.
+     *
+     * @param id New value for id.
+     */
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    /**
+     * Gets name.
+     *
+     * @return Value for name.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Sets name.
+     *
+     * @param name New value for name.
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Gets population.
+     *
+     * @return Value for population.
+     */
+    public int getPopulation() {
+        return population;
+    }
+
+    /**
+     * Sets population.
+     *
+     * @param population New value for population.
+     */
+    public void setPopulation(int population) {
+        this.population = population;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        
+        if (!(o instanceof Country))
+            return false;
+
+        Country that = (Country)o;
+
+        if (id != that.id)
+            return false;
+
+        if (name != null ? !name.equals(that.name) : that.name != null)
+            return false;
+
+        if (population != that.population)
+            return false;
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        int res = id;
+
+        res = 31 * res + (name != null ? name.hashCode() : 0);
+
+        res = 31 * res + population;
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return "Country [id=" + id +
+            ", name=" + name +
+            ", population=" + population +
+            ']';
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/54a24746/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/model/Department.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/model/Department.java b/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/model/Department.java
new file mode 100755
index 0000000..3f90147
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/model/Department.java
@@ -0,0 +1,137 @@
+package org.apache.ignite.console.demo.model;
+
+import java.io.Serializable;
+
+/**
+ * Department definition.
+ *
+ * This configuration was generated by Ignite Web Console (01/14/2016 13:46)
+ */
+public class Department implements Serializable {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Value for id. */
+    private int id;
+
+    /** Value for countryId. */
+    private int countryId;
+
+    /** Value for name. */
+    private String name;
+
+    /**
+     * Empty constructor.
+     */
+    public Department() {
+        // No-op.
+    }
+
+    /**
+     * Full constructor.
+     */
+    public Department(
+        int id,
+        int countryId,
+        String name
+    ) {
+        this.id = id;
+        this.countryId = countryId;
+        this.name = name;
+    }
+
+    /**
+     * Gets id.
+     *
+     * @return Value for id.
+     */
+    public int getId() {
+        return id;
+    }
+
+    /**
+     * Sets id.
+     *
+     * @param id New value for id.
+     */
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    /**
+     * Gets countryId.
+     *
+     * @return Value for countryId.
+     */
+    public int getCountryId() {
+        return countryId;
+    }
+
+    /**
+     * Sets countryId.
+     *
+     * @param countryId New value for countryId.
+     */
+    public void setCountryId(int countryId) {
+        this.countryId = countryId;
+    }
+
+    /**
+     * Gets name.
+     *
+     * @return Value for name.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Sets name.
+     *
+     * @param name New value for name.
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        
+        if (!(o instanceof Department))
+            return false;
+
+        Department that = (Department)o;
+
+        if (id != that.id)
+            return false;
+
+        if (countryId != that.countryId)
+            return false;
+
+        if (name != null ? !name.equals(that.name) : that.name != null)
+            return false;
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        int res = id;
+
+        res = 31 * res + countryId;
+
+        res = 31 * res + (name != null ? name.hashCode() : 0);
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return "Department [id=" + id +
+            ", countryId=" + countryId +
+            ", name=" + name +
+            ']';
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/54a24746/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/model/Employee.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/model/Employee.java b/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/model/Employee.java
new file mode 100755
index 0000000..f1f0b3f
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/model/Employee.java
@@ -0,0 +1,341 @@
+package org.apache.ignite.console.demo.model;
+
+import java.io.Serializable;
+import java.sql.Date;
+
+/**
+ * Employee definition.
+ *
+ * This configuration was generated by Ignite Web Console (01/14/2016 13:46)
+ */
+public class Employee implements Serializable {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Value for id. */
+    private int id;
+
+    /** Value for departmentId. */
+    private int departmentId;
+
+    /** Value for managerId. */
+    private Integer managerId;
+
+    /** Value for firstName. */
+    private String firstName;
+
+    /** Value for lastName. */
+    private String lastName;
+
+    /** Value for email. */
+    private String email;
+
+    /** Value for phoneNumber. */
+    private String phoneNumber;
+
+    /** Value for hireDate. */
+    private Date hireDate;
+
+    /** Value for job. */
+    private String job;
+
+    /** Value for salary. */
+    private Double salary;
+
+    /**
+     * Empty constructor.
+     */
+    public Employee() {
+        // No-op.
+    }
+
+    /**
+     * Full constructor.
+     */
+    public Employee(
+        int id,
+        int departmentId,
+        Integer managerId,
+        String firstName,
+        String lastName,
+        String email,
+        String phoneNumber,
+        Date hireDate,
+        String job,
+        Double salary
+    ) {
+        this.id = id;
+        this.departmentId = departmentId;
+        this.managerId = managerId;
+        this.firstName = firstName;
+        this.lastName = lastName;
+        this.email = email;
+        this.phoneNumber = phoneNumber;
+        this.hireDate = hireDate;
+        this.job = job;
+        this.salary = salary;
+    }
+
+    /**
+     * Gets id.
+     *
+     * @return Value for id.
+     */
+    public int getId() {
+        return id;
+    }
+
+    /**
+     * Sets id.
+     *
+     * @param id New value for id.
+     */
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    /**
+     * Gets departmentId.
+     *
+     * @return Value for departmentId.
+     */
+    public int getDepartmentId() {
+        return departmentId;
+    }
+
+    /**
+     * Sets departmentId.
+     *
+     * @param departmentId New value for departmentId.
+     */
+    public void setDepartmentId(int departmentId) {
+        this.departmentId = departmentId;
+    }
+
+    /**
+     * Gets managerId.
+     *
+     * @return Value for managerId.
+     */
+    public Integer getManagerId() {
+        return managerId;
+    }
+
+    /**
+     * Sets managerId.
+     *
+     * @param managerId New value for managerId.
+     */
+    public void setManagerId(Integer managerId) {
+        this.managerId = managerId;
+    }
+
+    /**
+     * Gets firstName.
+     *
+     * @return Value for firstName.
+     */
+    public String getFirstName() {
+        return firstName;
+    }
+
+    /**
+     * Sets firstName.
+     *
+     * @param firstName New value for firstName.
+     */
+    public void setFirstName(String firstName) {
+        this.firstName = firstName;
+    }
+
+    /**
+     * Gets lastName.
+     *
+     * @return Value for lastName.
+     */
+    public String getLastName() {
+        return lastName;
+    }
+
+    /**
+     * Sets lastName.
+     *
+     * @param lastName New value for lastName.
+     */
+    public void setLastName(String lastName) {
+        this.lastName = lastName;
+    }
+
+    /**
+     * Gets email.
+     *
+     * @return Value for email.
+     */
+    public String getEmail() {
+        return email;
+    }
+
+    /**
+     * Sets email.
+     *
+     * @param email New value for email.
+     */
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    /**
+     * Gets phoneNumber.
+     *
+     * @return Value for phoneNumber.
+     */
+    public String getPhoneNumber() {
+        return phoneNumber;
+    }
+
+    /**
+     * Sets phoneNumber.
+     *
+     * @param phoneNumber New value for phoneNumber.
+     */
+    public void setPhoneNumber(String phoneNumber) {
+        this.phoneNumber = phoneNumber;
+    }
+
+    /**
+     * Gets hireDate.
+     *
+     * @return Value for hireDate.
+     */
+    public Date getHireDate() {
+        return hireDate;
+    }
+
+    /**
+     * Sets hireDate.
+     *
+     * @param hireDate New value for hireDate.
+     */
+    public void setHireDate(Date hireDate) {
+        this.hireDate = hireDate;
+    }
+
+    /**
+     * Gets job.
+     *
+     * @return Value for job.
+     */
+    public String getJob() {
+        return job;
+    }
+
+    /**
+     * Sets job.
+     *
+     * @param job New value for job.
+     */
+    public void setJob(String job) {
+        this.job = job;
+    }
+
+    /**
+     * Gets salary.
+     *
+     * @return Value for salary.
+     */
+    public Double getSalary() {
+        return salary;
+    }
+
+    /**
+     * Sets salary.
+     *
+     * @param salary New value for salary.
+     */
+    public void setSalary(Double salary) {
+        this.salary = salary;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        
+        if (!(o instanceof Employee))
+            return false;
+
+        Employee that = (Employee)o;
+
+        if (id != that.id)
+            return false;
+
+        if (departmentId != that.departmentId)
+            return false;
+
+        if (managerId != null ? !managerId.equals(that.managerId) : that.managerId != null)
+            return false;
+
+        if (firstName != null ? !firstName.equals(that.firstName) : that.firstName != null)
+            return false;
+
+        if (lastName != null ? !lastName.equals(that.lastName) : that.lastName != null)
+            return false;
+
+        if (email != null ? !email.equals(that.email) : that.email != null)
+            return false;
+
+        if (phoneNumber != null ? !phoneNumber.equals(that.phoneNumber) : that.phoneNumber != null)
+            return false;
+
+        if (hireDate != null ? !hireDate.equals(that.hireDate) : that.hireDate != null)
+            return false;
+
+        if (job != null ? !job.equals(that.job) : that.job != null)
+            return false;
+
+        if (salary != null ? !salary.equals(that.salary) : that.salary != null)
+            return false;
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        int res = id;
+
+        res = 31 * res + departmentId;
+
+        res = 31 * res + (managerId != null ? managerId.hashCode() : 0);
+
+        res = 31 * res + (firstName != null ? firstName.hashCode() : 0);
+
+        res = 31 * res + (lastName != null ? lastName.hashCode() : 0);
+
+        res = 31 * res + (email != null ? email.hashCode() : 0);
+
+        res = 31 * res + (phoneNumber != null ? phoneNumber.hashCode() : 0);
+
+        res = 31 * res + (hireDate != null ? hireDate.hashCode() : 0);
+
+        res = 31 * res + (job != null ? job.hashCode() : 0);
+
+        res = 31 * res + (salary != null ? salary.hashCode() : 0);
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return "Employee [id=" + id +
+            ", departmentId=" + departmentId +
+            ", managerId=" + managerId +
+            ", firstName=" + firstName +
+            ", lastName=" + lastName +
+            ", email=" + email +
+            ", phoneNumber=" + phoneNumber +
+            ", hireDate=" + hireDate +
+            ", job=" + job +
+            ", salary=" + salary +
+            ']';
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/54a24746/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/model/Parking.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/model/Parking.java b/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/model/Parking.java
new file mode 100755
index 0000000..e6539fa
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/model/Parking.java
@@ -0,0 +1,137 @@
+package org.apache.ignite.console.demo.model;
+
+import java.io.Serializable;
+
+/**
+ * Parking definition.
+ *
+ * This configuration was generated by Ignite Web Console (01/14/2016 13:46)
+ */
+public class Parking implements Serializable {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Value for id. */
+    private int id;
+
+    /** Value for name. */
+    private String name;
+
+    /** Value for capacity. */
+    private int capacity;
+
+    /**
+     * Empty constructor.
+     */
+    public Parking() {
+        // No-op.
+    }
+
+    /**
+     * Full constructor.
+     */
+    public Parking(
+        int id,
+        String name,
+        int capacity
+    ) {
+        this.id = id;
+        this.name = name;
+        this.capacity = capacity;
+    }
+
+    /**
+     * Gets id.
+     *
+     * @return Value for id.
+     */
+    public int getId() {
+        return id;
+    }
+
+    /**
+     * Sets id.
+     *
+     * @param id New value for id.
+     */
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    /**
+     * Gets name.
+     *
+     * @return Value for name.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Sets name.
+     *
+     * @param name New value for name.
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Gets capacity.
+     *
+     * @return Value for capacity.
+     */
+    public int getCapacity() {
+        return capacity;
+    }
+
+    /**
+     * Sets capacity.
+     *
+     * @param capacity New value for capacity.
+     */
+    public void setCapacity(int capacity) {
+        this.capacity = capacity;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        
+        if (!(o instanceof Parking))
+            return false;
+
+        Parking that = (Parking)o;
+
+        if (id != that.id)
+            return false;
+
+        if (name != null ? !name.equals(that.name) : that.name != null)
+            return false;
+
+        if (capacity != that.capacity)
+            return false;
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        int res = id;
+
+        res = 31 * res + (name != null ? name.hashCode() : 0);
+
+        res = 31 * res + capacity;
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return "Parking [id=" + id +
+            ", name=" + name +
+            ", capacity=" + capacity +
+            ']';
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/54a24746/modules/control-center-web/src/main/js/agents/agent-manager.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/agents/agent-manager.js b/modules/control-center-web/src/main/js/agents/agent-manager.js
index 6205198..9f631b9 100644
--- a/modules/control-center-web/src/main/js/agents/agent-manager.js
+++ b/modules/control-center-web/src/main/js/agents/agent-manager.js
@@ -113,14 +113,15 @@ function Client(ws, manager) {
 }
 
 /**
- * @param {String} path
+ * @param {String} uri
  * @param {Object} params
+ * @param {Boolean} demo
  * @param {String} [method]
  * @param {Object} [headers]
  * @param {String} [body]
  * @param {Function} [cb] Callback. Take 3 arguments: {String} error, {number} httpCode, {string} response.
  */
-Client.prototype.executeRest = function(path, params, method, headers, body, cb) {
+Client.prototype.executeRest = function(uri, params, demo, method, headers, body, cb) {
     if (typeof(params) != 'object')
         throw '"params" argument must be an object';
 
@@ -143,7 +144,7 @@ Client.prototype.executeRest = function(path, params, method, headers, body, cb)
 
     var newArgs = argsToArray(arguments);
 
-    newArgs[5] = function(ex, res) {
+    newArgs[6] = function(ex, res) {
         if (ex)
             cb(ex.message);
         else
@@ -250,7 +251,9 @@ Client.prototype._rmtAuthMessage = function(msg) {
 
             self._manager._addClient(account._id, self);
 
-            self._ignite = new apacheIgnite.Ignite(new AgentServer(self));
+            self._cluster = new apacheIgnite.Ignite(new AgentServer(self));
+
+            self._demo = new apacheIgnite.Ignite(new AgentServer(self, true));
         }
     });
 };
@@ -271,8 +274,8 @@ Client.prototype._rmtCallRes = function(msg) {
 /**
  * @returns {Ignite}
  */
-Client.prototype.ignite = function() {
-    return this._ignite;
+Client.prototype.ignite = function(demo) {
+    return demo ? this._demo : this._cluster;
 };
 
 function removeFromArray(arr, val) {

http://git-wip-us.apache.org/repos/asf/ignite/blob/54a24746/modules/control-center-web/src/main/js/agents/agent-server.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/agents/agent-server.js b/modules/control-center-web/src/main/js/agents/agent-server.js
index 76bdeb4..ae5a87c 100644
--- a/modules/control-center-web/src/main/js/agents/agent-server.js
+++ b/modules/control-center-web/src/main/js/agents/agent-server.js
@@ -22,10 +22,12 @@ var _ = require('lodash');
  *
  * @constructor
  * @this {AgentServer}
- * @param {Client} client connected client
+ * @param {Client} client Connected client
+ * @param {Boolean} demo Use demo node for request
  */
-function AgentServer(client) {
+function AgentServer(client, demo) {
     this._client = client;
+    this._demo = !!demo;
 }
 
 /**
@@ -56,9 +58,10 @@ AgentServer.prototype.runCommand = function(cmd, callback) {
         headers = {'JSONObject': 'application/json'};
     }
 
-    this._client.executeRest("ignite", params, method, headers, body, function(error, code, message) {
+    this._client.executeRest("ignite", params, this._demo, method, headers, body, function(error, code, message) {
         if (error) {
             callback(error);
+
             return
         }
 
@@ -71,22 +74,16 @@ AgentServer.prototype.runCommand = function(cmd, callback) {
             return;
         }
 
-        var igniteResponse;
-
         try {
-            igniteResponse = JSON.parse(message);
+            var igniteResponse = JSON.parse(message);
+
+            if (igniteResponse.successStatus)
+                callback.call(null, igniteResponse.error, null);
+            else
+                callback.call(null, null, igniteResponse.response);
         }
         catch (e) {
             callback.call(null, e, null);
-
-            return;
-        }
-
-        if (igniteResponse.successStatus) {
-            callback.call(null, igniteResponse.error, null)
-        }
-        else {
-            callback.call(null, null, igniteResponse.response);
         }
     });
 };

http://git-wip-us.apache.org/repos/asf/ignite/blob/54a24746/modules/control-center-web/src/main/js/app/index.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/app/index.js b/modules/control-center-web/src/main/js/app/index.js
index a552939..6fcaad5 100644
--- a/modules/control-center-web/src/main/js/app/index.js
+++ b/modules/control-center-web/src/main/js/app/index.js
@@ -59,6 +59,7 @@ import './modules/User/index';
 import './modules/Auth/index';
 import './modules/Form/index';
 import './modules/JavaTypes/index';
+import './modules/QueryNotebooks/index';
 
 import './modules/states/login/index';
 import './modules/states/logout/index';
@@ -102,6 +103,7 @@ angular
     'ignite-console.User',
     'ignite-console.Form',
     'ignite-console.JavaTypes',
+    'ignite-console.QueryNotebooks',
     // States.
     'ignite-console.states.login',
     'ignite-console.states.logout',

http://git-wip-us.apache.org/repos/asf/ignite/blob/54a24746/modules/control-center-web/src/main/js/app/modules/QueryNotebooks/index.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/app/modules/QueryNotebooks/index.js b/modules/control-center-web/src/main/js/app/modules/QueryNotebooks/index.js
new file mode 100644
index 0000000..702b107
--- /dev/null
+++ b/modules/control-center-web/src/main/js/app/modules/QueryNotebooks/index.js
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ */
+
+import angular from 'angular';
+
+angular
+    .module('ignite-console.QueryNotebooks', [
+
+    ])
+    .provider('QueryNotebooks', function() {
+        const _demoNotebook = {
+            name: 'SQL demo',
+            paragraphs: [
+                {
+                    name: 'Simple query',
+                    cacheName: 'CarCache',
+                    pageSize: 50,
+                    query: 'SELECT * FROM Car',
+                    result: 'table',
+                    queryArgs: {
+                        type: 'QUERY',
+                        query: 'SELECT * FROM Car',
+                        pageSize: 50,
+                        cacheName: 'CarCache'
+                    },
+                    rate: {
+                        value: 1,
+                        unit: 60000,
+                        installed: false
+                    }
+                },
+                {
+                    name: 'Query with aggregates',
+                    cacheName: 'CarCache',
+                    pageSize: 50,
+                    query: 'SELECT * FROM Car',
+                    result: 'table',
+                    queryArgs: {
+                        type: 'QUERY',
+                        query: 'SELECT * FROM Car',
+                        pageSize: 50,
+                        cacheName: 'CarCache'
+                    },
+                    rate: {
+                        value: 1,
+                        unit: 60000,
+                        installed: false
+                    }
+                },
+                {
+                    name: 'Query with refresh rate',
+                    cacheName: 'CarCache',
+                    pageSize: 50,
+                    query: 'SELECT * FROM Car',
+                    result: 'table',
+                    queryArgs: {
+                        type: 'QUERY',
+                        query: 'SELECT * FROM Car',
+                        pageSize: 50,
+                        cacheName: 'CarCache'
+                    },
+                    rate: {
+                        value: 1,
+                        unit: 60000,
+                        installed: false
+                    }
+                }
+            ],
+            expandedParagraphs: [0, 1, 2]
+        };
+
+        this.$get = ['$q', '$http', ($q, $http) => {
+            return {
+                read(demo, noteId) {
+                    if (demo) {
+                        return $http.post('/api/v1/agent/demo/sql/start')
+                            .then(() => {
+                                return angular.copy(_demoNotebook);
+                            });
+                    }
+
+                    return $http.post('/api/v1/notebooks/get', {noteId})
+                        .then(({data}) => {
+                            return data;
+                        });
+                },
+                save(demo, notebook) {
+                    if (demo)
+                        return $http.post('/api/v1/notebooks/save', notebook);
+
+                    return $q.when();
+                },
+                remove(demo, nodeId) {
+                }
+            };
+        }];
+    });

http://git-wip-us.apache.org/repos/asf/ignite/blob/54a24746/modules/control-center-web/src/main/js/app/modules/states/configuration/index.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/app/modules/states/configuration/index.js b/modules/control-center-web/src/main/js/app/modules/states/configuration/index.js
index 0700950..90558d4 100644
--- a/modules/control-center-web/src/main/js/app/modules/states/configuration/index.js
+++ b/modules/control-center-web/src/main/js/app/modules/states/configuration/index.js
@@ -41,7 +41,7 @@ angular
         },
         resolve: {
             $title: () => {
-                return 'Create and Configure Ignite Clusters';
+                return 'Configure Clusters - Ignite Console';
             }
         }
     })
@@ -53,7 +53,7 @@ angular
         },
         resolve: {
             $title: () => {
-                return 'Create and Configure Ignite Caches';
+                return 'Configure Caches - Ignite Console';
             }
         }
     })
@@ -65,7 +65,7 @@ angular
         },
         resolve: {
             $title: () => {
-                return 'Create and Configure Cache Type Metadata';
+                return 'Configure Cache Type Metadata - Ignite Console';
             }
         }
     })
@@ -77,7 +77,7 @@ angular
         },
         resolve: {
             $title: () => {
-                return 'Create and Configure IGFS';
+                return 'Configure IGFS - Ignite Console';
             }
         }
     })
@@ -91,7 +91,7 @@ angular
         },
         resolve: {
             $title: () => {
-                return 'Configurations Summary';
+                return 'Configurations Summary - Ignite Console';
             }
         }
     });

http://git-wip-us.apache.org/repos/asf/ignite/blob/54a24746/modules/control-center-web/src/main/js/app/modules/states/login/index.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/app/modules/states/login/index.js b/modules/control-center-web/src/main/js/app/modules/states/login/index.js
index 316aecd..008ffb7 100644
--- a/modules/control-center-web/src/main/js/app/modules/states/login/index.js
+++ b/modules/control-center-web/src/main/js/app/modules/states/login/index.js
@@ -28,7 +28,12 @@ angular
     $stateProvider
     .state('login', {
         url: '/login',
-        templateUrl: '/login.html'
+        templateUrl: '/login.html',
+        resolve: {
+            $title: () => {
+                return 'Sign In - Ignite Console';
+            }
+        }
     });
 }])
 .run(['$rootScope', '$state', 'Auth', 'igniteTerms', function($root, $state, Auth, igniteTerms) {

http://git-wip-us.apache.org/repos/asf/ignite/blob/54a24746/modules/control-center-web/src/main/js/app/modules/states/sql/index.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/app/modules/states/sql/index.js b/modules/control-center-web/src/main/js/app/modules/states/sql/index.js
index 852724b..672d829 100644
--- a/modules/control-center-web/src/main/js/app/modules/states/sql/index.js
+++ b/modules/control-center-web/src/main/js/app/modules/states/sql/index.js
@@ -25,14 +25,31 @@ angular
     // set up the states
     $stateProvider
     .state('base.sql', {
-        url: '/sql/{id}',
+        url: '/sql',
+        abstract: true,
+        template: '<ui-view></ui-view>'
+    })
+    .state('base.sql.notebook', {
+        url: '/notebook/{noteId}',
         templateUrl: '/sql/sql.html',
         data: {
             loading: 'Loading notebook screen...'
         },
         resolve: {
             $title: () => {
-                return 'SQL notebook';
+                return 'Query notebook';
+            }
+        }
+    })
+    .state('base.sql.demo', {
+        url: '/demo',
+        templateUrl: '/sql/sql.html',
+        data: {
+            loading: 'Enable SQL demo...'
+        },
+        resolve: {
+            $title: () => {
+                return 'SQL demo';
             }
         }
     });

http://git-wip-us.apache.org/repos/asf/ignite/blob/54a24746/modules/control-center-web/src/main/js/controllers/common-module.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/common-module.js b/modules/control-center-web/src/main/js/controllers/common-module.js
index cafca42..e995175 100644
--- a/modules/control-center-web/src/main/js/controllers/common-module.js
+++ b/modules/control-center-web/src/main/js/controllers/common-module.js
@@ -2143,10 +2143,10 @@ consoleModule.service('$agentDownload', [
              * @param attr
              * @param mtr
              */
-            startTopologyListening: function (success, attr, mtr) {
+            startTopologyListening: function (success, demo, attr, mtr) {
                 _agentDownloadModal.check = {
                     url: '/api/v1/agent/topology',
-                    params: {attr: !!attr, mtr: !!mtr},
+                    params: {demo: !!demo,  attr: !!attr, mtr: !!mtr},
                     cb: success
                 };
 
@@ -2196,16 +2196,15 @@ consoleModule.controller('notebooks', ['$scope', '$modal', '$state', '$http', '$
 
     $scope.$root.rebuildDropdown = function() {
         $scope.notebookDropdown = [
-            {text: 'Create new notebook', click: 'inputNotebookName()'}
+            {text: 'Create new notebook', click: 'inputNotebookName()'},
+            {divider: true},
+            {text: 'SQL demo', sref: 'base.sql.demo'}
         ];
 
-        if ($scope.$root.notebooks.length > 0)
-            $scope.notebookDropdown.push({divider: true});
-
         _.forEach($scope.$root.notebooks, function (notebook) {
             $scope.notebookDropdown.push({
                 text: notebook.name,
-                href: '/sql/' + notebook._id
+                sref: 'base.sql.notebook({noteId:"' + notebook._id + '"})'
             });
         });
     };

http://git-wip-us.apache.org/repos/asf/ignite/blob/54a24746/modules/control-center-web/src/main/js/controllers/sql-controller.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/sql-controller.js b/modules/control-center-web/src/main/js/controllers/sql-controller.js
index c3a029e..69fbc85 100644
--- a/modules/control-center-web/src/main/js/controllers/sql-controller.js
+++ b/modules/control-center-web/src/main/js/controllers/sql-controller.js
@@ -17,7 +17,7 @@
 
 // Controller for SQL notebook screen.
 consoleModule.controller('sqlController', function ($http, $timeout, $interval, $scope, $animate,  $location, $anchorScroll, $state,
-    $modal, $popover, $loading, $common, $confirm, $agentDownload, uiGridExporterConstants) {
+    $modal, $popover, $loading, $common, $confirm, $agentDownload, QueryNotebooks, uiGridExporterConstants) {
 
     $scope.joinTip = $common.joinTip;
 
@@ -56,10 +56,16 @@ consoleModule.controller('sqlController', function ($http, $timeout, $interval,
         }
     };
 
+    var _demo = $state.includes('**.sql.demo');
+
     var _mask = function (cacheName) {
         return _.isEmpty(cacheName) ? '<default>' : cacheName;
     };
 
+    var _handleException = function(errMsg) {
+        $common.showError(errMsg);
+    };
+
     // Time line X axis descriptor.
     var TIME_LINE = {value: -1, type: 'java.sql.Date', label: 'TIME_LINE'};
 
@@ -245,52 +251,47 @@ consoleModule.controller('sqlController', function ($http, $timeout, $interval,
         $state.go('/configuration/clusters');
     };
 
-    var loadNotebook = function () {
-        $loading.start('loading');
+    var loadNotebook = function (notebook) {
+        $scope.notebook = notebook;
 
-        $http.post('/api/v1/notebooks/get', {noteId: $state.params.id})
-            .success(function (notebook) {
-                $scope.notebook = notebook;
+        $scope.notebook_name = notebook.name;
 
-                $scope.notebook_name = notebook.name;
+        if (!$scope.notebook.expandedParagraphs)
+            $scope.notebook.expandedParagraphs = [];
 
-                if (!$scope.notebook.expandedParagraphs)
-                    $scope.notebook.expandedParagraphs = [];
+        if (!$scope.notebook.paragraphs)
+            $scope.notebook.paragraphs = [];
 
-                if (!$scope.notebook.paragraphs)
-                    $scope.notebook.paragraphs = [];
+        _.forEach(notebook.paragraphs, function (paragraph) {
+            paragraph.id = 'paragraph-' + paragraphId++;
 
-                _.forEach(notebook.paragraphs, function (paragraph) {
-                    paragraph.id = 'paragraph-' + paragraphId++;
+            enhanceParagraph(paragraph);
+        });
 
-                    enhanceParagraph(paragraph);
-                });
+        if (!notebook.paragraphs || notebook.paragraphs.length == 0)
+            $scope.addParagraph();
+        else
+            $scope.rebuildScrollParagraphs();
 
-                if (!notebook.paragraphs || notebook.paragraphs.length == 0)
-                    $scope.addParagraph();
-                else
-                    $scope.rebuildScrollParagraphs();
+        $agentDownload.startTopologyListening(getTopology, _demo);
+    };
 
-                $agentDownload.startTopologyListening(getTopology);
-            })
-            .error(function () {
-                $scope.notebook = undefined;
-            })
-            .finally(function () {
-                $scope.loaded = true;
+    $loading.start('loading');
 
-                $loading.finish('loading');
-            });
-    };
+    QueryNotebooks.read(_demo, $state.params.noteId)
+        .then(loadNotebook)
+        .catch(function(err) {
+            $scope.notebook = undefined;
+        })
+        .finally(function() {
+            $scope.loaded = true;
 
-    loadNotebook();
+            $loading.finish('loading');
+        });
 
-    var _saveNotebook = function (f) {
-        $http.post('/api/v1/notebooks/save', $scope.notebook)
-            .success(f || function() {})
-            .error(function (errMsg) {
-                $common.showError(errMsg);
-            });
+    var _saveNotebook = function (cb) {
+        QueryNotebooks.save(_demo, $scope.notebook)
+            .catch(_handleException);
     };
 
     $scope.renameNotebook = function (name) {
@@ -300,19 +301,21 @@ consoleModule.controller('sqlController', function ($http, $timeout, $interval,
         if ($scope.notebook.name != name) {
             $scope.notebook.name = name;
 
-            _saveNotebook(function () {
-                var idx = _.findIndex($scope.$root.notebooks, function (item) {
-                    return item._id == $scope.notebook._id;
-                });
+            QueryNotebooks.save(_demo, $scope.notebook)
+                .then(function() {
+                    var idx = _.findIndex($scope.$root.notebooks, function (item) {
+                        return item._id == $scope.notebook._id;
+                    });
 
-                if (idx >= 0) {
-                    $scope.$root.notebooks[idx].name = name;
+                    if (idx >= 0) {
+                        $scope.$root.notebooks[idx].name = name;
 
-                    $scope.$root.rebuildDropdown();
-                }
+                        $scope.$root.rebuildDropdown();
+                    }
 
-                $scope.notebook.edit = false;
-            });
+                    $scope.notebook.edit = false;
+                })
+                .catch(_handleException);
         }
         else
             $scope.notebook.edit = false
@@ -353,7 +356,9 @@ consoleModule.controller('sqlController', function ($http, $timeout, $interval,
 
             $scope.rebuildScrollParagraphs();
 
-            _saveNotebook(function () { paragraph.edit = false; });
+            QueryNotebooks.save(_demo, $scope.notebook)
+                .then(function () { paragraph.edit = false; })
+                .catch(_handleException);
         }
         else
             paragraph.edit = false
@@ -365,7 +370,6 @@ consoleModule.controller('sqlController', function ($http, $timeout, $interval,
         var paragraph = {
             id: 'paragraph-' + paragraphId++,
             name: 'Query' + (sz ==0 ? '' : sz),
-            editor: true,
             query: '',
             pageSize: $scope.pageSizes[0],
             timeLineSpan: $scope.timeLineSpans[0],
@@ -431,7 +435,8 @@ consoleModule.controller('sqlController', function ($http, $timeout, $interval,
 
                     $scope.rebuildScrollParagraphs();
 
-                    _saveNotebook();
+                    QueryNotebooks.save(_demo, $scope.notebook)
+                        .catch(_handleException);
             });
     };
 
@@ -683,7 +688,8 @@ consoleModule.controller('sqlController', function ($http, $timeout, $interval,
     };
 
     $scope.execute = function (paragraph) {
-        _saveNotebook();
+        QueryNotebooks.save(_demo, $scope.notebook)
+            .catch(_handleException);
 
         paragraph.prevQuery = paragraph.queryArgs ? paragraph.queryArgs.query : paragraph.query;
 
@@ -692,6 +698,7 @@ consoleModule.controller('sqlController', function ($http, $timeout, $interval,
         _tryCloseQueryResult(paragraph.queryId);
 
         paragraph.queryArgs = {
+            demo: _demo,
             type: "QUERY",
             query: paragraph.query,
             pageSize: paragraph.pageSize,
@@ -720,7 +727,8 @@ consoleModule.controller('sqlController', function ($http, $timeout, $interval,
     };
 
     $scope.explain = function (paragraph) {
-        _saveNotebook();
+        QueryNotebooks.save(_demo, $scope.notebook)
+            .catch(_handleException);
 
         _cancelRefresh(paragraph);
 
@@ -729,6 +737,7 @@ consoleModule.controller('sqlController', function ($http, $timeout, $interval,
         _tryCloseQueryResult(paragraph.queryId);
 
         paragraph.queryArgs = {
+            demo: _demo,
             type: "EXPLAIN",
             query: 'EXPLAIN ' + paragraph.query,
             pageSize: paragraph.pageSize,
@@ -747,7 +756,8 @@ consoleModule.controller('sqlController', function ($http, $timeout, $interval,
     };
 
     $scope.scan = function (paragraph) {
-        _saveNotebook();
+        QueryNotebooks.save(_demo, $scope.notebook)
+            .catch(_handleException);
 
         _cancelRefresh(paragraph);
 
@@ -756,6 +766,7 @@ consoleModule.controller('sqlController', function ($http, $timeout, $interval,
         _tryCloseQueryResult(paragraph.queryId);
 
         paragraph.queryArgs = {
+            demo: _demo,
             type: "SCAN",
             pageSize: paragraph.pageSize,
             cacheName: paragraph.cacheName || undefined
@@ -775,7 +786,7 @@ consoleModule.controller('sqlController', function ($http, $timeout, $interval,
     $scope.nextPage = function(paragraph) {
         _showLoading(paragraph, true);
 
-        $http.post('/api/v1/agent/query/fetch', {queryId: paragraph.queryId, pageSize: paragraph.pageSize, cacheName: paragraph.queryArgs.cacheName})
+        $http.post('/api/v1/agent/query/fetch', {demo: _demo, queryId: paragraph.queryId, pageSize: paragraph.pageSize, cacheName: paragraph.queryArgs.cacheName})
             .success(function (res) {
                 paragraph.page++;
 
@@ -862,7 +873,7 @@ consoleModule.controller('sqlController', function ($http, $timeout, $interval,
     };
 
     $scope.exportCsvAll = function(paragraph) {
-        $http.post('/api/v1/agent/query/getAll', {query: paragraph.query, cacheName: paragraph.cacheName})
+        $http.post('/api/v1/agent/query/getAll', {demo: _demo, query: paragraph.query, cacheName: paragraph.cacheName})
             .success(function (item) {
                 _export(paragraph.name + '-all.csv', item.meta, item.rows);
             })
@@ -1463,7 +1474,7 @@ consoleModule.controller('sqlController', function ($http, $timeout, $interval,
 
         $scope.metadata = [];
 
-        $http.post('/api/v1/agent/cache/metadata')
+        $http.post('/api/v1/agent/cache/metadata', {demo: _demo})
             .success(function (metadata) {
                 $scope.metadata = _.sortBy(metadata, _.filter(metadata, function (meta) {
                     var cacheName = _mask(meta.cacheName);

http://git-wip-us.apache.org/repos/asf/ignite/blob/54a24746/modules/control-center-web/src/main/js/gulpfile.js/tasks/copy.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/gulpfile.js/tasks/copy.js b/modules/control-center-web/src/main/js/gulpfile.js/tasks/copy.js
index 0f05f32..c1dbb21 100644
--- a/modules/control-center-web/src/main/js/gulpfile.js/tasks/copy.js
+++ b/modules/control-center-web/src/main/js/gulpfile.js/tasks/copy.js
@@ -40,7 +40,7 @@ var legacyPaths = [
     './helpers/*.js',
     './helpers/**/*.js',
     './public/**/*.png',
-    './public/*.png'
+    './public/*.ico'
 ];
 
 var igniteModulePaths = [

http://git-wip-us.apache.org/repos/asf/ignite/blob/54a24746/modules/control-center-web/src/main/js/routes/agent.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/agent.js b/modules/control-center-web/src/main/js/routes/agent.js
index 12f8f01..9229a3f 100644
--- a/modules/control-center-web/src/main/js/routes/agent.js
+++ b/modules/control-center-web/src/main/js/routes/agent.js
@@ -67,7 +67,7 @@ router.get('/download/zip', function (req, res) {
 
         zip.file(agentFld + '/default.properties', prop.join('\n'));
 
-        var buffer = zip.generate({type:"nodebuffer", platform: "UNIX"});
+        var buffer = zip.generate({type: 'nodebuffer', platform: 'UNIX'});
 
         // Set the archive name.
         res.attachment(agentZip);
@@ -81,7 +81,7 @@ router.post('/topology', function (req, res) {
     var client = _client(req, res);
 
     if (client) {
-        client.ignite().cluster(req.body.attr, req.body.mtr).then(
+        client.ignite(req.body.demo).cluster(req.body.attr, req.body.mtr).then(
             function (clusters) {
                 res.json(clusters);
             },
@@ -105,7 +105,7 @@ router.post('/query', function (req, res) {
         qry.setPageSize(req.body.pageSize);
 
         // Get query cursor.
-        client.ignite().cache(req.body.cacheName).query(qry).nextPage().then(function (cursor) {
+        client.ignite(req.body.demo).cache(req.body.cacheName).query(qry).nextPage().then(function (cursor) {
             res.json({meta: cursor.fieldsMetadata(), rows: cursor.page(), queryId: cursor.queryId()});
         }, function (err) {
             res.status(500).send(err);
@@ -125,7 +125,7 @@ router.post('/query/getAll', function (req, res) {
         qry.setPageSize(1024);
 
         // Get query cursor.
-        var cursor = client.ignite().cache(req.body.cacheName).query(qry);
+        var cursor = client.ignite(req.body.demo).cache(req.body.cacheName).query(qry);
 
         cursor.getAll().then(function (rows) {
             res.json({meta: cursor.fieldsMetadata(), rows: rows});
@@ -147,7 +147,7 @@ router.post('/scan', function (req, res) {
         qry.setPageSize(req.body.pageSize);
 
         // Get query cursor.
-        client.ignite().cache(req.body.cacheName).query(qry).nextPage().then(function (cursor) {
+        client.ignite(req.body.demo).cache(req.body.cacheName).query(qry).nextPage().then(function (cursor) {
             res.json({meta: cursor.fieldsMetadata(), rows: cursor.page(), queryId: cursor.queryId()});
         }, function (err) {
             res.status(500).send(err);
@@ -160,7 +160,7 @@ router.post('/query/fetch', function (req, res) {
     var client = _client(req, res);
 
     if (client) {
-        var cache = client.ignite().cache(req.body.cacheName);
+        var cache = client.ignite(req.body.demo).cache(req.body.cacheName);
 
         var cmd = cache._createCommand('qryfetch').addParam('qryId', req.body.queryId).
             addParam('pageSize', req.body.pageSize);
@@ -178,7 +178,7 @@ router.post('/query/close', function (req, res) {
     var client = _client(req, res);
 
     if (client) {
-        var cache = client.ignite().cache(req.body.cacheName);
+        var cache = client.ignite(req.body.demo).cache(req.body.cacheName);
 
         var cmd = cache._createCommand('qrycls').addParam('qryId', req.body.queryId);
 
@@ -195,7 +195,7 @@ router.post('/cache/metadata', function (req, res) {
     var client = _client(req, res);
 
     if (client) {
-        client.ignite().cache(req.body.cacheName).metadata().then(function (caches) {
+        client.ignite(req.body.demo).cache(req.body.cacheName).metadata().then(function (caches) {
             var types = [];
 
             for (var meta of caches) {
@@ -275,9 +275,12 @@ router.post('/demo/sql/start', function (req, res) {
     if (client) {
         client.startDemoSQL(function (err, enabled) {
             if (err)
-                return res.status(500).send(err);
+                return res.status(500).send(err.message);
+
+            if (!enabled)
+                return res.status(500).send('Failed to start SQL demo');
 
-            res.status(200).send(enabled);
+            res.sendStatus(200);
         });
     }
 });

http://git-wip-us.apache.org/repos/asf/ignite/blob/54a24746/modules/control-center-web/src/main/js/routes/notebooks.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/notebooks.js b/modules/control-center-web/src/main/js/routes/notebooks.js
index 6dd02c2..93defd4 100644
--- a/modules/control-center-web/src/main/js/routes/notebooks.js
+++ b/modules/control-center-web/src/main/js/routes/notebooks.js
@@ -68,8 +68,8 @@ router.post('/get', function (req, res) {
 
         // Get all metadata for spaces.
         db.Notebook.findOne({space: {$in: space_ids}, _id: req.body.noteId}).exec(function (err, notebook) {
-            if (err || notebook == null)
-                return res.sendStatus(500);
+            if (err)
+                return res.status(500).send(err.message);
 
             res.json(notebook);
         });

http://git-wip-us.apache.org/repos/asf/ignite/blob/54a24746/modules/control-center-web/src/main/js/views/templates/agent-download.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/templates/agent-download.jade b/modules/control-center-web/src/main/js/views/templates/agent-download.jade
index 8ece589..d2a1442 100644
--- a/modules/control-center-web/src/main/js/views/templates/agent-download.jade
+++ b/modules/control-center-web/src/main/js/views/templates/agent-download.jade
@@ -51,5 +51,4 @@
                         | &nbsp; in agent folder for more information
             .modal-footer
                 button.btn.btn-default(ng-click='back()') {{::backText}}
-                button.btn.btn-primary(ng-if='nodeFailedConnection' ng-click='startDemoSQL()') Start demo
                 button.btn.btn-primary(ng-if='!nodeFailedConnection' ng-click='downloadAgent()') Download zip


Mime
View raw message