001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.replication; 019 020import static org.junit.Assert.assertArrayEquals; 021import static org.junit.Assert.fail; 022 023import java.util.ArrayList; 024import java.util.List; 025import org.apache.hadoop.hbase.HBaseClassTestRule; 026import org.apache.hadoop.hbase.HColumnDescriptor; 027import org.apache.hadoop.hbase.HConstants; 028import org.apache.hadoop.hbase.HTableDescriptor; 029import org.apache.hadoop.hbase.NamespaceDescriptor; 030import org.apache.hadoop.hbase.TableName; 031import org.apache.hadoop.hbase.client.Admin; 032import org.apache.hadoop.hbase.client.Connection; 033import org.apache.hadoop.hbase.client.ConnectionFactory; 034import org.apache.hadoop.hbase.client.Get; 035import org.apache.hadoop.hbase.client.Put; 036import org.apache.hadoop.hbase.client.Result; 037import org.apache.hadoop.hbase.client.ResultScanner; 038import org.apache.hadoop.hbase.client.Scan; 039import org.apache.hadoop.hbase.client.Table; 040import org.apache.hadoop.hbase.testclassification.LargeTests; 041import org.apache.hadoop.hbase.util.Bytes; 042import org.apache.hadoop.hbase.util.JVMClusterUtil; 043import org.junit.Before; 044import org.junit.ClassRule; 045import org.junit.Test; 046import org.junit.experimental.categories.Category; 047import org.slf4j.Logger; 048import org.slf4j.LoggerFactory; 049 050@Category(LargeTests.class) 051public class TestReplicationDroppedTables extends TestReplicationBase { 052 053 @ClassRule 054 public static final HBaseClassTestRule CLASS_RULE = 055 HBaseClassTestRule.forClass(TestReplicationDroppedTables.class); 056 057 private static final Logger LOG = LoggerFactory.getLogger(TestReplicationDroppedTables.class); 058 059 /** 060 * @throws java.lang.Exception 061 */ 062 @Before 063 public void setUp() throws Exception { 064 // Starting and stopping replication can make us miss new logs, 065 // rolling like this makes sure the most recent one gets added to the queue 066 for ( JVMClusterUtil.RegionServerThread r : 067 utility1.getHBaseCluster().getRegionServerThreads()) { 068 utility1.getAdmin().rollWALWriter(r.getRegionServer().getServerName()); 069 } 070 int rowCount = utility1.countRows(tableName); 071 utility1.deleteTableData(tableName); 072 // truncating the table will send one Delete per row to the slave cluster 073 // in an async fashion, which is why we cannot just call deleteTableData on 074 // utility2 since late writes could make it to the slave in some way. 075 // Instead, we truncate the first table and wait for all the Deletes to 076 // make it to the slave. 077 Scan scan = new Scan(); 078 int lastCount = 0; 079 for (int i = 0; i < NB_RETRIES; i++) { 080 if (i==NB_RETRIES-1) { 081 fail("Waited too much time for truncate"); 082 } 083 ResultScanner scanner = htable2.getScanner(scan); 084 Result[] res = scanner.next(rowCount); 085 scanner.close(); 086 if (res.length != 0) { 087 if (res.length < lastCount) { 088 i--; // Don't increment timeout if we make progress 089 } 090 lastCount = res.length; 091 LOG.info("Still got " + res.length + " rows"); 092 Thread.sleep(SLEEP_TIME); 093 } else { 094 break; 095 } 096 } 097 } 098 099 @Test 100 public void testEditsStuckBehindDroppedTable() throws Exception { 101 // Sanity check 102 // Make sure by default edits for dropped tables stall the replication queue, even when the 103 // table(s) in question have been deleted on both ends. 104 testEditsBehindDroppedTable(false, "test_dropped"); 105 } 106 107 @Test 108 public void testEditsDroppedWithDroppedTable() throws Exception { 109 // Make sure by default edits for dropped tables are themselves dropped when the 110 // table(s) in question have been deleted on both ends. 111 testEditsBehindDroppedTable(true, "test_dropped"); 112 } 113 114 @Test 115 public void testEditsDroppedWithDroppedTableNS() throws Exception { 116 // also try with a namespace 117 Connection connection1 = ConnectionFactory.createConnection(conf1); 118 try (Admin admin1 = connection1.getAdmin()) { 119 admin1.createNamespace(NamespaceDescriptor.create("NS").build()); 120 } 121 Connection connection2 = ConnectionFactory.createConnection(conf2); 122 try (Admin admin2 = connection2.getAdmin()) { 123 admin2.createNamespace(NamespaceDescriptor.create("NS").build()); 124 } 125 testEditsBehindDroppedTable(true, "NS:test_dropped"); 126 } 127 128 private void testEditsBehindDroppedTable(boolean allowProceeding, String tName) throws Exception { 129 conf1.setBoolean(HConstants.REPLICATION_DROP_ON_DELETED_TABLE_KEY, allowProceeding); 130 conf1.setInt(HConstants.REPLICATION_SOURCE_MAXTHREADS_KEY, 1); 131 132 // make sure we have a single region server only, so that all 133 // edits for all tables go there 134 utility1.shutdownMiniHBaseCluster(); 135 utility1.startMiniHBaseCluster(1, 1); 136 137 TableName tablename = TableName.valueOf(tName); 138 byte[] familyname = Bytes.toBytes("fam"); 139 byte[] row = Bytes.toBytes("row"); 140 141 HTableDescriptor table = new HTableDescriptor(tablename); 142 HColumnDescriptor fam = new HColumnDescriptor(familyname); 143 fam.setScope(HConstants.REPLICATION_SCOPE_GLOBAL); 144 table.addFamily(fam); 145 146 Connection connection1 = ConnectionFactory.createConnection(conf1); 147 Connection connection2 = ConnectionFactory.createConnection(conf2); 148 try (Admin admin1 = connection1.getAdmin()) { 149 admin1.createTable(table); 150 } 151 try (Admin admin2 = connection2.getAdmin()) { 152 admin2.createTable(table); 153 } 154 utility1.waitUntilAllRegionsAssigned(tablename); 155 utility2.waitUntilAllRegionsAssigned(tablename); 156 157 Table lHtable1 = utility1.getConnection().getTable(tablename); 158 159 // now suspend replication 160 admin.disablePeer("2"); 161 162 // put some data (lead with 0 so the edit gets sorted before the other table's edits 163 // in the replication batch) 164 // write a bunch of edits, making sure we fill a batch 165 byte[] rowkey = Bytes.toBytes(0+" put on table to be dropped"); 166 Put put = new Put(rowkey); 167 put.addColumn(familyname, row, row); 168 lHtable1.put(put); 169 170 rowkey = Bytes.toBytes("normal put"); 171 put = new Put(rowkey); 172 put.addColumn(famName, row, row); 173 htable1.put(put); 174 175 try (Admin admin1 = connection1.getAdmin()) { 176 admin1.disableTable(tablename); 177 admin1.deleteTable(tablename); 178 } 179 try (Admin admin2 = connection2.getAdmin()) { 180 admin2.disableTable(tablename); 181 admin2.deleteTable(tablename); 182 } 183 184 admin.enablePeer("2"); 185 if (allowProceeding) { 186 // in this we'd expect the key to make it over 187 verifyReplicationProceeded(rowkey); 188 } else { 189 verifyReplicationStuck(rowkey); 190 } 191 // just to be safe 192 conf1.setBoolean(HConstants.REPLICATION_DROP_ON_DELETED_TABLE_KEY, false); 193 } 194 195 @Test 196 public void testEditsBehindDroppedTableTiming() throws Exception { 197 conf1.setBoolean(HConstants.REPLICATION_DROP_ON_DELETED_TABLE_KEY, true); 198 conf1.setInt(HConstants.REPLICATION_SOURCE_MAXTHREADS_KEY, 1); 199 200 // make sure we have a single region server only, so that all 201 // edits for all tables go there 202 utility1.shutdownMiniHBaseCluster(); 203 utility1.startMiniHBaseCluster(1, 1); 204 205 TableName tablename = TableName.valueOf("testdroppedtimed"); 206 byte[] familyname = Bytes.toBytes("fam"); 207 byte[] row = Bytes.toBytes("row"); 208 209 HTableDescriptor table = new HTableDescriptor(tablename); 210 HColumnDescriptor fam = new HColumnDescriptor(familyname); 211 fam.setScope(HConstants.REPLICATION_SCOPE_GLOBAL); 212 table.addFamily(fam); 213 214 Connection connection1 = ConnectionFactory.createConnection(conf1); 215 Connection connection2 = ConnectionFactory.createConnection(conf2); 216 try (Admin admin1 = connection1.getAdmin()) { 217 admin1.createTable(table); 218 } 219 try (Admin admin2 = connection2.getAdmin()) { 220 admin2.createTable(table); 221 } 222 utility1.waitUntilAllRegionsAssigned(tablename); 223 utility2.waitUntilAllRegionsAssigned(tablename); 224 225 Table lHtable1 = utility1.getConnection().getTable(tablename); 226 227 // now suspend replication 228 admin.disablePeer("2"); 229 230 // put some data (lead with 0 so the edit gets sorted before the other table's edits 231 // in the replication batch) 232 // write a bunch of edits, making sure we fill a batch 233 byte[] rowkey = Bytes.toBytes(0+" put on table to be dropped"); 234 Put put = new Put(rowkey); 235 put.addColumn(familyname, row, row); 236 lHtable1.put(put); 237 238 rowkey = Bytes.toBytes("normal put"); 239 put = new Put(rowkey); 240 put.addColumn(famName, row, row); 241 htable1.put(put); 242 243 try (Admin admin2 = connection2.getAdmin()) { 244 admin2.disableTable(tablename); 245 admin2.deleteTable(tablename); 246 } 247 248 admin.enablePeer("2"); 249 // edit should still be stuck 250 251 try (Admin admin1 = connection1.getAdmin()) { 252 // the source table still exists, replication should be stalled 253 verifyReplicationStuck(rowkey); 254 255 admin1.disableTable(tablename); 256 // still stuck, source table still exists 257 verifyReplicationStuck(rowkey); 258 259 admin1.deleteTable(tablename); 260 // now the source table is gone, replication should proceed, the 261 // offending edits be dropped 262 verifyReplicationProceeded(rowkey); 263 } 264 // just to be safe 265 conf1.setBoolean(HConstants.REPLICATION_DROP_ON_DELETED_TABLE_KEY, false); 266 } 267 268 private void verifyReplicationProceeded(byte[] rowkey) throws Exception { 269 Get get = new Get(rowkey); 270 for (int i = 0; i < NB_RETRIES; i++) { 271 if (i==NB_RETRIES-1) { 272 fail("Waited too much time for put replication"); 273 } 274 Result res = htable2.get(get); 275 if (res.size() == 0) { 276 LOG.info("Row not available"); 277 Thread.sleep(SLEEP_TIME); 278 } else { 279 assertArrayEquals(res.getRow(), rowkey); 280 break; 281 } 282 } 283 } 284 285 private void verifyReplicationStuck(byte[] rowkey) throws Exception { 286 Get get = new Get(rowkey); 287 for (int i = 0; i < NB_RETRIES; i++) { 288 Result res = htable2.get(get); 289 if (res.size() >= 1) { 290 fail("Edit should have been stuck behind dropped tables"); 291 } else { 292 LOG.info("Row not replicated, let's wait a bit more..."); 293 Thread.sleep(SLEEP_TIME); 294 } 295 } 296 } 297}