| /* 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. |
| */ |
| #include <thread> |
| |
| #include <gtest/gtest.h> |
| |
| #include <geode/Cache.hpp> |
| #include <geode/CacheFactory.hpp> |
| #include <geode/CacheListener.hpp> |
| #include <geode/PoolManager.hpp> |
| #include <geode/QueryService.hpp> |
| #include <geode/RegionFactory.hpp> |
| #include <geode/RegionShortcut.hpp> |
| |
| #include "framework/Cluster.h" |
| #include "framework/Framework.h" |
| #include "gmock_actions.hpp" |
| #include "mock/CacheListenerMock.hpp" |
| #include "util/concurrent/binary_semaphore.hpp" |
| |
| namespace { |
| |
| using apache::geode::client::binary_semaphore; |
| |
| using apache::geode::client::Cache; |
| using apache::geode::client::CacheableInt16; |
| using apache::geode::client::CacheFactory; |
| using apache::geode::client::CacheListener; |
| using apache::geode::client::CacheListenerMock; |
| using apache::geode::client::NotConnectedException; |
| using apache::geode::client::PdxFieldTypes; |
| using apache::geode::client::PdxInstance; |
| using apache::geode::client::Region; |
| using apache::geode::client::RegionEvent; |
| using apache::geode::client::RegionShortcut; |
| using apache::geode::client::SelectResults; |
| |
| using ::testing::_; |
| using ::testing::DoAll; |
| using ::testing::InvokeWithoutArgs; |
| using ::testing::Return; |
| |
| Cache createTestCache() { |
| CacheFactory cacheFactory; |
| return cacheFactory.set("log-level", "none") |
| .set("connect-timeout", "2s") |
| .set("statistic-sampling-enabled", "false") |
| .set("on-client-disconnect-clear-pdxType-Ids", "true") |
| .setPdxReadSerialized(true) |
| .create(); |
| } |
| |
| void createTestPool(Cluster& cluster, Cache& cache) { |
| auto poolFactory = cache.getPoolManager() |
| .createFactory() |
| .setReadTimeout(std::chrono::seconds{1}) |
| .setPingInterval(std::chrono::seconds{5}) |
| .setSubscriptionEnabled(true); |
| |
| cluster.applyLocators(poolFactory); |
| poolFactory.create("pool"); |
| } |
| |
| std::shared_ptr<Region> createTestRegion( |
| Cache& cache, std::shared_ptr<CacheListener> listener) { |
| auto regionFactory = cache.createRegionFactory(RegionShortcut::PROXY); |
| return regionFactory.setPoolName("pool").setCacheListener(listener).create( |
| "region"); |
| } |
| |
| std::shared_ptr<PdxInstance> createTestPdxInstance(Cache& cache, |
| const std::string& entry) { |
| auto factory = cache.createPdxInstanceFactory("__GEMFIRE_JSON", false); |
| return factory.writeString("entryName", entry) |
| .writeInt("int_value", -1) |
| .create(); |
| } |
| |
| /** |
| * The purpose of this test case is to verify that PdxTypeRegistry is cleaned up |
| * under the following scenario: |
| * 1 - Spin up a cluster and create a PARTITION region called 'region'. Note |
| * that this region won't be persisting any data. |
| * 2 - A cache with is on-client-disconnect-clear-pdxType-Ids=true, meaning |
| * it should cleanup the PdxTypeRegistry upon cluster disconnection. |
| * 3 - A PdxInstance with is put into key 'entry' and region 'region' |
| * Before inserting the entry, its PdxType will be created on the cluster, |
| * as it should not be present. |
| * 4 - Restart the cluster. Region 'region' should be empty as it was |
| * persisting no data. |
| * 5 - Cache should automatically cleanup PdxTypeRegistry, containing the |
| * cache of PdxType's handled by this cache. |
| * 6 - Insert again the entry inserted in step 3. If the registry was correctly |
| * cleaned up as stated in state 5, PdxType will be inserted again before |
| * inserting the entry. If PdxTypeRegistry clean up did not happen, the old |
| * PdxTypeId will be used when inserting this entry. |
| * 7 - Fetch all of the 'region' entries using an OQL query, hence, forcing the |
| * members to deserialize the entries contained on the region. The query |
| * should not throw any exception and return exactly one entry, inserted on |
| * step 6. If PdxTypeRegistry cleanup fails, then the old PdxTypeId will be |
| * used when inserting the entry on step 6 and consequently members will |
| * fail to deserialize the entry as there will be no PdxType matching the |
| * given PdxTypeId, consequently leading to an UnknownPdxException being |
| * thrown. |
| */ |
| TEST(PdxTypeRegistryTest, cleanupOnClusterRestartAndPut) { |
| Cluster cluster{LocatorCount{1}, ServerCount{2}}; |
| cluster.start(); |
| |
| auto& gfsh = cluster.getGfsh(); |
| gfsh.create().region().withName("region").withType("PARTITION").execute(); |
| |
| binary_semaphore live_sem{0}; |
| binary_semaphore shut_sem{1}; |
| auto listener = std::make_shared<CacheListenerMock>(); |
| EXPECT_CALL(*listener, afterRegionLive(_)) |
| .WillRepeatedly(DoAll(ReleaseSem(&live_sem), AcquireSem(&shut_sem))); |
| EXPECT_CALL(*listener, afterRegionDisconnected(_)) |
| .WillRepeatedly(DoAll(ReleaseSem(&shut_sem), AcquireSem(&live_sem))); |
| EXPECT_CALL(*listener, afterCreate(_)).WillRepeatedly(Return()); |
| EXPECT_CALL(*listener, afterRegionDestroy(_)).WillRepeatedly(Return()); |
| EXPECT_CALL(*listener, close(_)).WillRepeatedly(Return()); |
| |
| auto cache = createTestCache(); |
| createTestPool(cluster, cache); |
| auto qs = cache.getQueryService("pool"); |
| auto region = createTestRegion(cache, listener); |
| |
| std::string key = "entry"; |
| auto pdx = createTestPdxInstance(cache, key); |
| region->put(key, pdx); |
| |
| // Shutdown and wait for some time |
| gfsh.shutdown().execute(); |
| |
| shut_sem.acquire(); |
| shut_sem.release(); |
| |
| std::this_thread::sleep_for(std::chrono::seconds{15}); |
| |
| for (auto& server : cluster.getServers()) { |
| server.start(); |
| } |
| |
| live_sem.acquire(); |
| live_sem.release(); |
| |
| region->put(key, pdx); |
| |
| std::shared_ptr<SelectResults> result; |
| auto query = qs->newQuery("SELECT * FROM /region WHERE entryName = 'entry'"); |
| |
| // If PdxTypeRegistry was not correctly cleaned up, this query will throw |
| // UnknownPdxTypeException. See the comment at the beginning of the test for |
| // additional info. |
| EXPECT_NO_THROW(result = query->execute()); |
| EXPECT_TRUE(result); |
| EXPECT_EQ(result->size(), 1); |
| } |
| |
| /** |
| * The purpose of this test case is to verify that there are no coredumps when |
| * calling several PdxInstance's methods due to PdxTypeRegistry cleanup under |
| * the following scenario: |
| * 1 - Spin up a cluster and create a PARTITION region called 'region'. Note |
| * that this region won't be persisting any data. |
| * 2 - A cache with is on-client-disconnect-clear-pdxType-Ids=true, meaning |
| * it should cleanup the PdxTypeRegistry upon cluster disconnection. |
| * 3 - Create and put PdxInstance with is put into key 'entry' and region |
| * 'region'. |
| * 4 - Restart the cluster. Region 'region' should be empty as it was |
| * persisting no data. |
| * 5 - Cache should automatically cleanup PdxTypeRegistry, containing the |
| * cache of PdxType's handled by this cache. |
| * 6 - If PdxInstance's PdxType object is stored inside the PdxInstance rather |
| * than its PdxTypeId, all of the accessors could be called for the |
| * PdxInstance even if this PdxType was not added to the PdxTypeRegistry. |
| * If instead the PdxTypeId is stored, this will cause an coredump given |
| * that the cache won't be able to find the PdxType for the above mentioned |
| * PdxTypeId. |
| */ |
| TEST(PdxTypeRegistryTest, cleanupOnClusterRestartAndCallAccessors) { |
| Cluster cluster{LocatorCount{1}, ServerCount{2}}; |
| cluster.start(); |
| |
| auto& gfsh = cluster.getGfsh(); |
| gfsh.create().region().withName("region").withType("PARTITION").execute(); |
| |
| binary_semaphore live_sem{0}; |
| binary_semaphore shut_sem{1}; |
| auto listener = std::make_shared<CacheListenerMock>(); |
| EXPECT_CALL(*listener, afterRegionLive(_)) |
| .WillRepeatedly(DoAll(ReleaseSem(&live_sem), AcquireSem(&shut_sem))); |
| EXPECT_CALL(*listener, afterRegionDisconnected(_)) |
| .WillRepeatedly(DoAll(ReleaseSem(&shut_sem), AcquireSem(&live_sem))); |
| EXPECT_CALL(*listener, afterCreate(_)).WillRepeatedly(Return()); |
| EXPECT_CALL(*listener, afterRegionDestroy(_)).WillRepeatedly(Return()); |
| EXPECT_CALL(*listener, close(_)).WillRepeatedly(Return()); |
| |
| auto cache = createTestCache(); |
| createTestPool(cluster, cache); |
| auto qs = cache.getQueryService("pool"); |
| auto region = createTestRegion(cache, listener); |
| |
| std::string key = "entry"; |
| region->put(key, createTestPdxInstance(cache, key)); |
| auto object = region->get(key); |
| EXPECT_TRUE(object); |
| |
| auto pdx = std::dynamic_pointer_cast<PdxInstance>(object); |
| EXPECT_TRUE(pdx); |
| |
| // Shutdown and wait for some time |
| gfsh.shutdown().execute(); |
| |
| shut_sem.acquire(); |
| shut_sem.release(); |
| |
| std::this_thread::sleep_for(std::chrono::seconds{15}); |
| |
| for (auto& server : cluster.getServers()) { |
| server.start(); |
| } |
| |
| live_sem.acquire(); |
| live_sem.release(); |
| |
| // Checking no coredump is happening when calling isIdentityField |
| EXPECT_FALSE(pdx->isIdentityField("entryName")); |
| EXPECT_FALSE(pdx->isIdentityField("int_value")); |
| |
| // Checking no coredump is happening when calling hasField |
| EXPECT_TRUE(pdx->hasField("entryName")); |
| EXPECT_TRUE(pdx->hasField("int_value")); |
| |
| // Checking no coredump is happening when calling getFieldNames |
| auto fields = pdx->getFieldNames(); |
| EXPECT_TRUE(fields); |
| |
| std::set<std::string> fieldsSet; |
| for (auto field : fields->value()) { |
| fieldsSet.insert(field->toString()); |
| } |
| |
| EXPECT_EQ(fieldsSet.count("entryName"), 1); |
| EXPECT_EQ(fieldsSet.count("int_value"), 1); |
| |
| // Checking no coredump is happening when calling getFieldType |
| EXPECT_EQ(pdx->getFieldType("entryName"), PdxFieldTypes::STRING); |
| EXPECT_EQ(pdx->getFieldType("int_value"), PdxFieldTypes::INT); |
| |
| // Checking no coredump is happening when calling getStringField |
| EXPECT_EQ(pdx->getStringField("entryName"), key); |
| |
| // Checking no coredump is happening when calling getIntField |
| EXPECT_EQ(pdx->getIntField("int_value"), -1); |
| |
| // Checking no coredump is happening when calling objectSize |
| EXPECT_NE(pdx->objectSize(), 0); |
| |
| // Checking no coredump is happening when calling hashcode |
| EXPECT_NE(pdx->hashcode(), 0); |
| |
| // Checking no coredump is happening when calling getClassName |
| EXPECT_EQ(pdx->getClassName(), "__GEMFIRE_JSON"); |
| |
| // Checking no coredump is happening when calling getClassName |
| EXPECT_EQ(pdx->getClassName(), "__GEMFIRE_JSON"); |
| |
| // Checking no coredump is happening when calling toString |
| EXPECT_FALSE(pdx->toString().empty()); |
| } |
| } // namespace |