|
36 | 36 | import org.slf4j.LoggerFactory; |
37 | 37 |
|
38 | 38 | import java.sql.Connection; |
| 39 | +import java.sql.SQLException; |
39 | 40 | import java.sql.Statement; |
40 | 41 | import java.util.ArrayList; |
41 | 42 | import java.util.List; |
@@ -120,6 +121,64 @@ public void singleRegionTest() throws Exception { |
120 | 121 | } |
121 | 122 | } |
122 | 123 |
|
| 124 | + /** |
| 125 | + * Regression test for TIMECHODB-0689: when the target id of "extend region" does not belong to |
| 126 | + * any registered DataNode (e.g. it is a ConfigNode id or simply does not exist), the ConfigNode |
| 127 | + * used to throw a NullPointerException in {@code checkExtendRegion} and the client only saw a |
| 128 | + * misleading "Fail to connect to any config node" message. After the fix a clear, actionable |
| 129 | + * error message must be returned instead. |
| 130 | + */ |
| 131 | + @Test |
| 132 | + public void extendRegionToInvalidDataNodeTest() throws Exception { |
| 133 | + EnvFactory.getEnv() |
| 134 | + .getConfig() |
| 135 | + .getCommonConfig() |
| 136 | + .setDataRegionConsensusProtocolClass(ConsensusFactory.IOT_CONSENSUS) |
| 137 | + .setSchemaRegionConsensusProtocolClass(ConsensusFactory.RATIS_CONSENSUS) |
| 138 | + .setDataReplicationFactor(1) |
| 139 | + .setSchemaReplicationFactor(1); |
| 140 | + |
| 141 | + EnvFactory.getEnv().initClusterEnvironment(1, 3); |
| 142 | + |
| 143 | + try (final Connection connection = makeItCloseQuietly(EnvFactory.getEnv().getConnection()); |
| 144 | + final Statement statement = makeItCloseQuietly(connection.createStatement())) { |
| 145 | + // prepare data so that at least one region exists |
| 146 | + statement.execute(INSERTION1); |
| 147 | + statement.execute(FLUSH_COMMAND); |
| 148 | + |
| 149 | + Map<Integer, Set<Integer>> regionMap = getAllRegionMap(statement); |
| 150 | + Set<Integer> allDataNodeId = getAllDataNodes(statement); |
| 151 | + Assert.assertFalse(regionMap.isEmpty()); |
| 152 | + |
| 153 | + int selectedRegion = regionMap.keySet().iterator().next(); |
| 154 | + // an id that is guaranteed not to belong to any registered DataNode; a ConfigNode id triggers |
| 155 | + // the exact same code path (getRegisteredDataNode returns an empty configuration whose |
| 156 | + // location is null) |
| 157 | + int invalidDataNodeId = 9999; |
| 158 | + Assert.assertFalse(allDataNodeId.contains(invalidDataNodeId)); |
| 159 | + |
| 160 | + try { |
| 161 | + statement.execute(String.format(EXPAND_FORMAT, selectedRegion, invalidDataNodeId)); |
| 162 | + Assert.fail("extend region to a non-existent DataNode is expected to fail"); |
| 163 | + } catch (SQLException e) { |
| 164 | + String message = e.getMessage(); |
| 165 | + LOGGER.info("extend region to invalid DataNode failed as expected: {}", message); |
| 166 | + Assert.assertNotNull(message); |
| 167 | + // the ConfigNode must not crash with an NPE any more ... |
| 168 | + Assert.assertFalse( |
| 169 | + "ConfigNode should not throw NullPointerException, but got: " + message, |
| 170 | + message.contains("NullPointerException")); |
| 171 | + // ... and the submission must be rejected cleanly. "extend region" wraps every region's |
| 172 | + // result, so the top-level message only reports the aggregate counts; the concrete "does |
| 173 | + // not |
| 174 | + // exist in the cluster" reason is carried in the per-region sub-status. |
| 175 | + Assert.assertTrue( |
| 176 | + "Expected the extend submission to be rejected but got: " + message, |
| 177 | + message.contains("failed to submit: 1")); |
| 178 | + } |
| 179 | + } |
| 180 | + } |
| 181 | + |
123 | 182 | private void regionGroupExpand( |
124 | 183 | Statement statement, |
125 | 184 | SyncConfigNodeIServiceClient client, |
|
0 commit comments