You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by is...@apache.org on 2018/10/09 14:47:27 UTC

[12/21] ignite git commit: IGNITE-9785 Introduce read-only state in local node context - Fixes #4907.

IGNITE-9785 Introduce read-only state in local node context - Fixes #4907.

Signed-off-by: Ivan Rakov <ir...@apache.org>

(cherry picked from commit 179b09b)


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/7c9bd23c
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/7c9bd23c
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/7c9bd23c

Branch: refs/heads/ignite-2.7-master
Commit: 7c9bd23c345bb4217af5c7ae1a4afb1798072039
Parents: dfd6fcb
Author: Aleksey Plekhanov <pl...@gmail.com>
Authored: Tue Oct 9 14:33:25 2018 +0300
Committer: Ivan Rakov <ir...@apache.org>
Committed: Tue Oct 9 15:00:48 2018 +0300

----------------------------------------------------------------------
 .../cache/GridCacheSharedContext.java           |  17 +++
 .../dht/GridDhtTopologyFutureAdapter.java       |   3 +
 .../datastreamer/DataStreamerImpl.java          |  47 ++++++-
 .../cache/ClusterReadOnlyModeAbstractTest.java  | 114 ++++++++++++++++
 .../cache/ClusterReadOnlyModeTest.java          | 134 +++++++++++++++++++
 .../testsuites/IgniteCacheTestSuite5.java       |   5 +-
 .../cache/ClusterReadOnlyModeSqlTest.java       |  94 +++++++++++++
 .../IgniteCacheWithIndexingTestSuite.java       |   3 +
 8 files changed, 407 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/7c9bd23c/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheSharedContext.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheSharedContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheSharedContext.java
index e4d398a..caa3d20 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheSharedContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheSharedContext.java
@@ -171,6 +171,9 @@ public class GridCacheSharedContext<K, V> {
     /** */
     private final List<IgniteChangeGlobalStateSupport> stateAwareMgrs;
 
+    /** Cluster is in read-only mode. */
+    private volatile boolean readOnlyMode;
+
     /**
      * @param kernalCtx  Context.
      * @param txMgr Transaction manager.
@@ -1108,4 +1111,18 @@ public class GridCacheSharedContext<K, V> {
     private int dhtAtomicUpdateIndex(GridCacheVersion ver) {
         return U.safeAbs(ver.hashCode()) % dhtAtomicUpdCnt.length();
     }
+
+    /**
+     * @return {@code true} if cluster is in read-only mode.
+     */
+    public boolean readOnlyMode() {
+        return readOnlyMode;
+    }
+
+    /**
+     * @param readOnlyMode Read-only flag.
+     */
+    public void readOnlyMode(boolean readOnlyMode) {
+        this.readOnlyMode = readOnlyMode;
+    }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/7c9bd23c/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTopologyFutureAdapter.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTopologyFutureAdapter.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTopologyFutureAdapter.java
index 539fef4..9214308 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTopologyFutureAdapter.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTopologyFutureAdapter.java
@@ -97,6 +97,9 @@ public abstract class GridDhtTopologyFutureAdapter extends GridFutureAdapter<Aff
                     cctx.name());
         }
 
+        if (cctx.shared().readOnlyMode() && !read)
+            return new IgniteCheckedException("Failed to perform cache operation (cluster is in read only mode)" );
+
         if (grp.needsRecovery() || grp.topologyValidator() != null) {
             CacheValidation validation = grpValidRes.get(grp.groupId());
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/7c9bd23c/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerImpl.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerImpl.java
index bf1e13d..e86f653 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerImpl.java
@@ -85,10 +85,10 @@ import org.apache.ignite.internal.processors.cache.GridCacheUtils;
 import org.apache.ignite.internal.processors.cache.IgniteCacheFutureImpl;
 import org.apache.ignite.internal.processors.cache.IgniteCacheProxy;
 import org.apache.ignite.internal.processors.cache.KeyCacheObject;
+import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTopologyFuture;
 import org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtInvalidPartitionException;
 import org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtLocalPartition;
 import org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionState;
-import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTopologyFuture;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
 import org.apache.ignite.internal.processors.cacheobject.IgniteCacheObjectProcessor;
 import org.apache.ignite.internal.processors.dr.GridDrType;
@@ -725,7 +725,7 @@ public class DataStreamerImpl<K, V> implements IgniteDataStreamer<K, V>, Delayed
                 keys.add(new KeyCacheObjectWrapper(e.getKey()));
         }
 
-        load0(entries, fut, keys, 0);
+        load0(entries, fut, keys, 0, null, null);
     }
 
     /** {@inheritDoc} */
@@ -798,13 +798,18 @@ public class DataStreamerImpl<K, V> implements IgniteDataStreamer<K, V>, Delayed
      * @param resFut Result future.
      * @param activeKeys Active keys.
      * @param remaps Remaps count.
+     * @param remapNode Node for remap. In case update with {@code allowOverride() == false} fails on one node,
+     * we don't need to send update request to all affinity nodes again, if topology version does not changed.
+     * @param remapTopVer Topology version.
      */
     private void load0(
         Collection<? extends DataStreamerEntry> entries,
         final GridFutureAdapter<Object> resFut,
         @Nullable final Collection<KeyCacheObjectWrapper> activeKeys,
-        final int remaps
-    ) {
+        final int remaps,
+        ClusterNode remapNode,
+        AffinityTopologyVersion remapTopVer
+        ) {
         try {
             assert entries != null;
 
@@ -876,7 +881,10 @@ public class DataStreamerImpl<K, V> implements IgniteDataStreamer<K, V>, Delayed
                         if (key.partition() == -1)
                             key.partition(cctx.affinity().partition(key, false));
 
-                        nodes = nodes(key, topVer, cctx);
+                        if (!allowOverwrite() && remapNode != null && F.eq(topVer, remapTopVer))
+                            nodes = Collections.singletonList(remapNode);
+                        else
+                            nodes = nodes(key, topVer, cctx);
                     }
                     catch (IgniteCheckedException e) {
                         resFut.onDone(e);
@@ -903,6 +911,7 @@ public class DataStreamerImpl<K, V> implements IgniteDataStreamer<K, V>, Delayed
                 }
 
                 for (final Map.Entry<ClusterNode, Collection<DataStreamerEntry>> e : mappings.entrySet()) {
+                    final ClusterNode node = e.getKey();
                     final UUID nodeId = e.getKey().id();
 
                     Buffer buf = bufMappings.get(nodeId);
@@ -964,7 +973,7 @@ public class DataStreamerImpl<K, V> implements IgniteDataStreamer<K, V>, Delayed
                                                     if (cancelled)
                                                         closedException();
 
-                                                    load0(entriesForNode, resFut, activeKeys, remaps + 1);
+                                                    load0(entriesForNode, resFut, activeKeys, remaps + 1, node, topVer);
                                                 }
                                                 catch (Throwable ex) {
                                                     resFut.onDone(
@@ -2195,12 +2204,27 @@ public class DataStreamerImpl<K, V> implements IgniteDataStreamer<K, V>, Delayed
             if (internalCache.isNear())
                 internalCache = internalCache.context().near().dht();
 
-            GridCacheContext cctx = internalCache.context();
+            GridCacheContext<?, ?> cctx = internalCache.context();
+
+/*          todo: uncomment this and remove topFut choosing logic below after IGNITE-9550 race is fixed
+            GridDhtTopologyFuture topFut = cctx.shared().exchange().lastFinishedFuture();
 
+            AffinityTopologyVersion topVer = topFut.topologyVersion();
+*/
             AffinityTopologyVersion topVer = cctx.isLocal() ?
                 cctx.affinity().affinityTopologyVersion() :
                 cctx.shared().exchange().readyAffinityVersion();
 
+            GridDhtTopologyFuture topFut = (GridDhtTopologyFuture)cctx.shared().exchange().affinityReadyFuture(topVer);
+
+            if (topFut == null) {
+                // Exchange for newer topology version is already in progress, let's try to use last finished future.
+                GridDhtTopologyFuture lastFinishedFut = cctx.shared().exchange().lastFinishedFuture();
+
+                if (F.eq(lastFinishedFut.topologyVersion(), topVer))
+                    topFut = lastFinishedFut;
+            }
+
             GridCacheVersion ver = cctx.versions().isolatedStreamerVersion();
 
             long ttl = CU.TTL_ETERNAL;
@@ -2260,6 +2284,15 @@ public class DataStreamerImpl<K, V> implements IgniteDataStreamer<K, V>, Delayed
                             expiryTime = CU.toExpireTime(ttl);
                         }
 
+                        if (topFut != null) {
+                            topFut.get();
+
+                            Throwable err = topFut.validateCache(cctx, false, false, entry.key(), null);
+
+                            if (err != null)
+                                throw new IgniteCheckedException(err);
+                        }
+
                         boolean primary = cctx.affinity().primaryByKey(cctx.localNode(), entry.key(), topVer);
 
                         entry.initialValue(e.getValue(),

http://git-wip-us.apache.org/repos/asf/ignite/blob/7c9bd23c/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/ClusterReadOnlyModeAbstractTest.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/ClusterReadOnlyModeAbstractTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/ClusterReadOnlyModeAbstractTest.java
new file mode 100644
index 0000000..7cb9649
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/ClusterReadOnlyModeAbstractTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.internal.processors.cache;
+
+import java.util.Collection;
+import java.util.Collections;
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheMode;
+import org.apache.ignite.cache.QueryEntity;
+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.testframework.junits.common.GridCommonAbstractTest;
+
+import static org.apache.ignite.cache.CacheAtomicityMode.ATOMIC;
+import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL;
+import static org.apache.ignite.cache.CacheMode.PARTITIONED;
+import static org.apache.ignite.cache.CacheMode.REPLICATED;
+
+/**
+ *
+ */
+public class ClusterReadOnlyModeAbstractTest extends GridCommonAbstractTest {
+    /** */
+    private static final int SRVS = 3;
+
+    /** Replicated atomic cache. */
+    private static final String REPL_ATOMIC_CACHE = "repl_atomic_cache";
+
+    /** Replicated transactional cache. */
+    private static final String REPL_TX_CACHE = "repl_tx_cache";
+
+    /** Partitioned atomic cache. */
+    private static final String PART_ATOMIC_CACHE = "part_atomic_cache";
+
+    /** Partitioned transactional cache. */
+    private static final String PART_TX_CACHE = "part_tx_cache";
+
+    /** Cache names. */
+    protected static final Collection<String> CACHE_NAMES = F.asList(REPL_ATOMIC_CACHE, REPL_TX_CACHE,
+        PART_ATOMIC_CACHE, PART_TX_CACHE);
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTestsStarted() throws Exception {
+        super.beforeTestsStarted();
+
+        startGridsMultiThreaded(SRVS);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        super.afterTest();
+
+        changeClusterReadOnlyMode(false);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+        IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
+
+        cfg.setCacheConfiguration(
+            cacheConfiguration(REPL_ATOMIC_CACHE, REPLICATED, ATOMIC, null),
+            cacheConfiguration(REPL_TX_CACHE, REPLICATED, TRANSACTIONAL, null),
+            cacheConfiguration(PART_ATOMIC_CACHE, PARTITIONED, ATOMIC, "part_grp"),
+            cacheConfiguration(PART_TX_CACHE, PARTITIONED, TRANSACTIONAL, "part_grp")
+        );
+
+        return cfg;
+    }
+
+    /**
+     * @param cacheMode Cache mode.
+     * @param atomicityMode Atomicity mode.
+     * @param grpName Cache group name.
+     */
+    private CacheConfiguration<Integer, Integer> cacheConfiguration(String name, CacheMode cacheMode,
+        CacheAtomicityMode atomicityMode, String grpName) {
+        return new CacheConfiguration<Integer, Integer>()
+            .setName(name)
+            .setCacheMode(cacheMode)
+            .setAtomicityMode(atomicityMode)
+            .setGroupName(grpName)
+            .setQueryEntities(Collections.singletonList(new QueryEntity(Integer.class, Integer.class)));
+    }
+
+    /**
+     * Change read only mode on all nodes.
+     *
+     * @param readOnly Read only.
+     */
+    protected void changeClusterReadOnlyMode(boolean readOnly) {
+        for (int idx = 0; idx < SRVS; idx++) {
+            IgniteEx ignite = grid(idx);
+
+            ignite.context().cache().context().readOnlyMode(readOnly);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/7c9bd23c/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/ClusterReadOnlyModeTest.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/ClusterReadOnlyModeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/ClusterReadOnlyModeTest.java
new file mode 100644
index 0000000..ab57614
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/ClusterReadOnlyModeTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.internal.processors.cache;
+
+import java.util.Random;
+import javax.cache.CacheException;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.IgniteDataStreamer;
+import org.apache.ignite.internal.processors.datastreamer.DataStreamerImpl;
+import org.apache.ignite.internal.util.typedef.G;
+
+/**
+ * Tests cache get/put/remove and data streaming in read-only cluster mode.
+ */
+public class ClusterReadOnlyModeTest extends ClusterReadOnlyModeAbstractTest {
+    /**
+     * Tests cache get/put/remove.
+     */
+    public void testCacheGetPutRemove() {
+        assertCachesReadOnlyMode(false);
+
+        changeClusterReadOnlyMode(true);
+
+        assertCachesReadOnlyMode(true);
+
+        changeClusterReadOnlyMode(false);
+
+        assertCachesReadOnlyMode(false);
+    }
+
+    /**
+     * Tests data streamer.
+     */
+    public void testDataStreamerReadOnly() {
+        assertDataStreamerReadOnlyMode(false);
+
+        changeClusterReadOnlyMode(true);
+
+        assertDataStreamerReadOnlyMode(true);
+
+        changeClusterReadOnlyMode(false);
+
+        assertDataStreamerReadOnlyMode(false);
+    }
+
+    /**
+     * Asserts that all caches in read-only or in read/write mode on all nodes.
+     *
+     * @param readOnly If {@code true} then cache must be in read only mode, else in read/write mode.
+     */
+    private void assertCachesReadOnlyMode(boolean readOnly) {
+        Random rnd = new Random();
+
+        for (Ignite ignite : G.allGrids()) {
+            for (String cacheName : CACHE_NAMES) {
+                IgniteCache<Integer, Integer> cache = ignite.cache(cacheName);
+
+                for (int i = 0; i < 10; i++) {
+                    cache.get(rnd.nextInt(100)); // All gets must succeed.
+
+                    if (readOnly) {
+                        // All puts must fail.
+                        try {
+                            cache.put(rnd.nextInt(100), rnd.nextInt());
+
+                            fail("Put must fail for cache " + cacheName);
+                        }
+                        catch (Exception e) {
+                            // No-op.
+                        }
+
+                        // All removes must fail.
+                        try {
+                            cache.remove(rnd.nextInt(100));
+
+                            fail("Remove must fail for cache " + cacheName);
+                        }
+                        catch (Exception e) {
+                            // No-op.
+                        }
+                    }
+                    else {
+                        cache.put(rnd.nextInt(100), rnd.nextInt()); // All puts must succeed.
+
+                        cache.remove(rnd.nextInt(100)); // All removes must succeed.
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * @param readOnly If {@code true} then data streamer must fail, else succeed.
+     */
+    private void assertDataStreamerReadOnlyMode(boolean readOnly) {
+        Random rnd = new Random();
+
+        for (Ignite ignite : G.allGrids()) {
+            for (String cacheName : CACHE_NAMES) {
+                boolean failed = false;
+
+                try (IgniteDataStreamer<Integer, Integer> streamer = ignite.dataStreamer(cacheName)) {
+                    for (int i = 0; i < 10; i++) {
+                        ((DataStreamerImpl)streamer).maxRemapCount(5);
+
+                        streamer.addData(rnd.nextInt(1000), rnd.nextInt());
+                    }
+                }
+                catch (CacheException ignored) {
+                    failed = true;
+                }
+
+                if (failed != readOnly)
+                    fail("Streaming to " + cacheName + " must " + (readOnly ? "fail" : "succeed"));
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/7c9bd23c/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite5.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite5.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite5.java
index dafc44f..a583317 100644
--- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite5.java
+++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite5.java
@@ -29,6 +29,7 @@ import org.apache.ignite.internal.processors.cache.CacheKeepBinaryTransactionTes
 import org.apache.ignite.internal.processors.cache.CacheNearReaderUpdateTest;
 import org.apache.ignite.internal.processors.cache.CacheRebalancingSelfTest;
 import org.apache.ignite.internal.processors.cache.CacheSerializableTransactionsTest;
+import org.apache.ignite.internal.processors.cache.ClusterReadOnlyModeTest;
 import org.apache.ignite.internal.processors.cache.ClusterStatePartitionedSelfTest;
 import org.apache.ignite.internal.processors.cache.ClusterStateReplicatedSelfTest;
 import org.apache.ignite.internal.processors.cache.ConcurrentCacheStartTest;
@@ -43,10 +44,7 @@ import org.apache.ignite.internal.processors.cache.distributed.IgniteCacheGroups
 import org.apache.ignite.internal.processors.cache.distributed.IgniteCachePartitionLossPolicySelfTest;
 import org.apache.ignite.internal.processors.cache.distributed.IgniteCacheTxIteratorSelfTest;
 import org.apache.ignite.internal.processors.cache.distributed.dht.NotMappedPartitionInTxTest;
-import org.apache.ignite.internal.processors.cache.distributed.dht.atomic.GridCacheAtomicPreloadSelfTest;
 import org.apache.ignite.internal.processors.cache.distributed.dht.atomic.IgniteCacheAtomicProtocolTest;
-import org.apache.ignite.internal.processors.cache.distributed.dht.atomic.IgniteCacheContainsKeyColocatedAtomicSelfTest;
-import org.apache.ignite.internal.processors.cache.distributed.dht.atomic.IgniteCacheContainsKeyNearAtomicSelfTest;
 import org.apache.ignite.internal.processors.cache.distributed.rebalancing.CacheManualRebalancingTest;
 import org.apache.ignite.internal.processors.cache.distributed.replicated.IgniteCacheSyncRebalanceModeSelfTest;
 import org.apache.ignite.internal.processors.cache.store.IgniteCacheWriteBehindNoUpdateSelfTest;
@@ -82,6 +80,7 @@ public class IgniteCacheTestSuite5 extends TestSuite {
 
         suite.addTestSuite(ClusterStatePartitionedSelfTest.class);
         suite.addTestSuite(ClusterStateReplicatedSelfTest.class);
+        suite.addTestSuite(ClusterReadOnlyModeTest.class);
         suite.addTestSuite(IgniteCachePartitionLossPolicySelfTest.class);
         suite.addTestSuite(IgniteCacheGroupsPartitionLossPolicySelfTest.class);
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/7c9bd23c/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/ClusterReadOnlyModeSqlTest.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/ClusterReadOnlyModeSqlTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/ClusterReadOnlyModeSqlTest.java
new file mode 100644
index 0000000..d431f73
--- /dev/null
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/ClusterReadOnlyModeSqlTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.internal.processors.cache;
+
+import java.util.Random;
+import javax.cache.CacheException;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.cache.query.FieldsQueryCursor;
+import org.apache.ignite.cache.query.SqlFieldsQuery;
+import org.apache.ignite.internal.util.typedef.G;
+
+/**
+ * Tests SQL queries in read-only cluster mode.
+ */
+public class ClusterReadOnlyModeSqlTest extends ClusterReadOnlyModeAbstractTest {
+    /**
+     *
+     */
+    public void testSqlReadOnly() {
+        assertSqlReadOnlyMode(false);
+
+        changeClusterReadOnlyMode(true);
+
+        assertSqlReadOnlyMode(true);
+
+        changeClusterReadOnlyMode(false);
+
+        assertSqlReadOnlyMode(false);
+    }
+
+    /**
+     * @param readOnly If {@code true} then data modification SQL queries must fail, else succeed.
+     */
+    private void assertSqlReadOnlyMode(boolean readOnly) {
+        Random rnd = new Random();
+
+        for (Ignite ignite : G.allGrids()) {
+            for (String cacheName : CACHE_NAMES) {
+                IgniteCache<Integer, Integer> cache = ignite.cache(cacheName);
+
+                try (FieldsQueryCursor<?> cur = cache.query(new SqlFieldsQuery("SELECT * FROM Integer"))) {
+                    cur.getAll();
+                }
+
+                boolean failed = false;
+
+                try (FieldsQueryCursor<?> cur = cache.query(new SqlFieldsQuery("DELETE FROM Integer"))) {
+                    cur.getAll();
+                }
+                catch (CacheException ex) {
+                    if (!readOnly)
+                        log.error("Failed to delete data", ex);
+
+                    failed = true;
+                }
+
+                if (failed != readOnly)
+                    fail("SQL delete from " + cacheName + " must " + (readOnly ? "fail" : "succeed"));
+
+                failed = false;
+
+                try (FieldsQueryCursor<?> cur = cache.query(new SqlFieldsQuery(
+                    "INSERT INTO Integer(_KEY, _VAL) VALUES (?, ?)").setArgs(rnd.nextInt(1000), rnd.nextInt()))) {
+                    cur.getAll();
+                }
+                catch (CacheException ex) {
+                    if (!readOnly)
+                        log.error("Failed to insert data", ex);
+
+                    failed = true;
+                }
+
+                if (failed != readOnly)
+                    fail("SQL insert into " + cacheName + " must " + (readOnly ? "fail" : "succeed"));
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/7c9bd23c/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheWithIndexingTestSuite.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheWithIndexingTestSuite.java b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheWithIndexingTestSuite.java
index e351cb6..8517ebb 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheWithIndexingTestSuite.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheWithIndexingTestSuite.java
@@ -27,6 +27,7 @@ import org.apache.ignite.internal.processors.cache.CacheQueryAfterDynamicCacheSt
 import org.apache.ignite.internal.processors.cache.CacheQueryFilterExpiredTest;
 import org.apache.ignite.internal.processors.cache.CacheRandomOperationsMultithreadedTest;
 import org.apache.ignite.internal.processors.cache.ClientReconnectAfterClusterRestartTest;
+import org.apache.ignite.internal.processors.cache.ClusterReadOnlyModeSqlTest;
 import org.apache.ignite.internal.processors.cache.GridCacheOffHeapSelfTest;
 import org.apache.ignite.internal.processors.cache.GridCacheOffheapIndexEntryEvictTest;
 import org.apache.ignite.internal.processors.cache.GridCacheOffheapIndexGetSelfTest;
@@ -90,6 +91,8 @@ public class IgniteCacheWithIndexingTestSuite extends TestSuite {
 
         suite.addTestSuite(BinaryTypeMismatchLoggingTest.class);
 
+        suite.addTestSuite(ClusterReadOnlyModeSqlTest.class);
+
         return suite;
     }
 }