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.master.cleaner; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertFalse; 022import static org.junit.Assert.assertTrue; 023import static org.junit.Assert.fail; 024import static org.mockito.Mockito.doThrow; 025import static org.mockito.Mockito.spy; 026 027import java.io.IOException; 028import java.util.ArrayList; 029import java.util.Iterator; 030import java.util.List; 031import org.apache.hadoop.conf.Configuration; 032import org.apache.hadoop.fs.FileStatus; 033import org.apache.hadoop.fs.FileSystem; 034import org.apache.hadoop.fs.Path; 035import org.apache.hadoop.hbase.Abortable; 036import org.apache.hadoop.hbase.HBaseClassTestRule; 037import org.apache.hadoop.hbase.HBaseTestingUtil; 038import org.apache.hadoop.hbase.HConstants; 039import org.apache.hadoop.hbase.Server; 040import org.apache.hadoop.hbase.ZooKeeperConnectionException; 041import org.apache.hadoop.hbase.master.HMaster; 042import org.apache.hadoop.hbase.replication.ReplicationException; 043import org.apache.hadoop.hbase.replication.ReplicationFactory; 044import org.apache.hadoop.hbase.replication.ReplicationPeerConfig; 045import org.apache.hadoop.hbase.replication.ReplicationPeers; 046import org.apache.hadoop.hbase.replication.ReplicationQueueStorage; 047import org.apache.hadoop.hbase.replication.ReplicationStorageFactory; 048import org.apache.hadoop.hbase.replication.SyncReplicationState; 049import org.apache.hadoop.hbase.replication.master.ReplicationHFileCleaner; 050import org.apache.hadoop.hbase.testclassification.MasterTests; 051import org.apache.hadoop.hbase.testclassification.SmallTests; 052import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 053import org.apache.hadoop.hbase.util.MockServer; 054import org.apache.hadoop.hbase.util.Pair; 055import org.apache.hadoop.hbase.zookeeper.RecoverableZooKeeper; 056import org.apache.hadoop.hbase.zookeeper.ZKWatcher; 057import org.apache.zookeeper.KeeperException; 058import org.apache.zookeeper.data.Stat; 059import org.junit.After; 060import org.junit.AfterClass; 061import org.junit.Before; 062import org.junit.BeforeClass; 063import org.junit.ClassRule; 064import org.junit.Test; 065import org.junit.experimental.categories.Category; 066import org.slf4j.Logger; 067import org.slf4j.LoggerFactory; 068 069import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 070 071@Category({ MasterTests.class, SmallTests.class }) 072public class TestReplicationHFileCleaner { 073 074 @ClassRule 075 public static final HBaseClassTestRule CLASS_RULE = 076 HBaseClassTestRule.forClass(TestReplicationHFileCleaner.class); 077 078 private static final Logger LOG = LoggerFactory.getLogger(TestReplicationHFileCleaner.class); 079 private final static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 080 private static Server server; 081 private static ReplicationQueueStorage rq; 082 private static ReplicationPeers rp; 083 private static final String peerId = "TestReplicationHFileCleaner"; 084 private static Configuration conf = TEST_UTIL.getConfiguration(); 085 static FileSystem fs = null; 086 Path root; 087 088 @BeforeClass 089 public static void setUpBeforeClass() throws Exception { 090 TEST_UTIL.startMiniZKCluster(); 091 server = new DummyServer(); 092 conf.setBoolean(HConstants.REPLICATION_BULKLOAD_ENABLE_KEY, true); 093 HMaster.decorateMasterConfiguration(conf); 094 rp = ReplicationFactory.getReplicationPeers(server.getZooKeeper(), conf); 095 rp.init(); 096 rq = ReplicationStorageFactory.getReplicationQueueStorage(server.getZooKeeper(), conf); 097 fs = FileSystem.get(conf); 098 } 099 100 @AfterClass 101 public static void tearDownAfterClass() throws Exception { 102 TEST_UTIL.shutdownMiniZKCluster(); 103 } 104 105 @Before 106 public void setup() throws ReplicationException, IOException { 107 root = TEST_UTIL.getDataTestDirOnTestFS(); 108 rp.getPeerStorage().addPeer(peerId, 109 ReplicationPeerConfig.newBuilder().setClusterKey(TEST_UTIL.getClusterKey()).build(), true, 110 SyncReplicationState.NONE); 111 rq.addPeerToHFileRefs(peerId); 112 } 113 114 @After 115 public void cleanup() throws ReplicationException { 116 try { 117 fs.delete(root, true); 118 } catch (IOException e) { 119 LOG.warn("Failed to delete files recursively from path " + root); 120 } 121 // Remove all HFileRefs (if any) 122 rq.removeHFileRefs(peerId, rq.getReplicableHFiles(peerId)); 123 rp.getPeerStorage().removePeer(peerId); 124 } 125 126 @Test 127 public void testIsFileDeletable() throws IOException, ReplicationException { 128 // 1. Create a file 129 Path file = new Path(root, "testIsFileDeletableWithNoHFileRefs"); 130 fs.createNewFile(file); 131 // 2. Assert file is successfully created 132 assertTrue("Test file not created!", fs.exists(file)); 133 ReplicationHFileCleaner cleaner = new ReplicationHFileCleaner(); 134 cleaner.setConf(conf); 135 // 3. Assert that file as is should be deletable 136 assertTrue("Cleaner should allow to delete this file as there is no hfile reference node " 137 + "for it in the queue.", cleaner.isFileDeletable(fs.getFileStatus(file))); 138 139 List<Pair<Path, Path>> files = new ArrayList<>(1); 140 files.add(new Pair<>(null, file)); 141 // 4. Add the file to hfile-refs queue 142 rq.addHFileRefs(peerId, files); 143 // 5. Assert file should not be deletable 144 assertFalse("Cleaner should not allow to delete this file as there is a hfile reference node " 145 + "for it in the queue.", cleaner.isFileDeletable(fs.getFileStatus(file))); 146 } 147 148 @Test 149 public void testGetDeletableFiles() throws Exception { 150 // 1. Create two files and assert that they do not exist 151 Path notDeletablefile = new Path(root, "testGetDeletableFiles_1"); 152 fs.createNewFile(notDeletablefile); 153 assertTrue("Test file not created!", fs.exists(notDeletablefile)); 154 Path deletablefile = new Path(root, "testGetDeletableFiles_2"); 155 fs.createNewFile(deletablefile); 156 assertTrue("Test file not created!", fs.exists(deletablefile)); 157 158 List<FileStatus> files = new ArrayList<>(2); 159 FileStatus f = new FileStatus(); 160 f.setPath(deletablefile); 161 files.add(f); 162 f = new FileStatus(); 163 f.setPath(notDeletablefile); 164 files.add(f); 165 166 List<Pair<Path, Path>> hfiles = new ArrayList<>(1); 167 hfiles.add(new Pair<>(null, notDeletablefile)); 168 // 2. Add one file to hfile-refs queue 169 rq.addHFileRefs(peerId, hfiles); 170 171 ReplicationHFileCleaner cleaner = new ReplicationHFileCleaner(); 172 cleaner.setConf(conf); 173 Iterator<FileStatus> deletableFilesIterator = cleaner.getDeletableFiles(files).iterator(); 174 int i = 0; 175 while (deletableFilesIterator.hasNext() && i < 2) { 176 i++; 177 } 178 // 5. Assert one file should not be deletable and it is present in the list returned 179 if (i > 2) { 180 fail("File " + notDeletablefile 181 + " should not be deletable as its hfile reference node is not added."); 182 } 183 assertTrue(deletableFilesIterator.next().getPath().equals(deletablefile)); 184 } 185 186 /** 187 * ReplicationHFileCleaner should be able to ride over ZooKeeper errors without aborting. 188 */ 189 @Test 190 public void testZooKeeperAbort() throws Exception { 191 ReplicationHFileCleaner cleaner = new ReplicationHFileCleaner(); 192 193 List<FileStatus> dummyFiles = Lists.newArrayList( 194 new FileStatus(100, false, 3, 100, EnvironmentEdgeManager.currentTime(), new Path("hfile1")), 195 new FileStatus(100, false, 3, 100, EnvironmentEdgeManager.currentTime(), new Path("hfile2"))); 196 197 FaultyZooKeeperWatcher faultyZK = 198 new FaultyZooKeeperWatcher(conf, "testZooKeeperAbort-faulty", null); 199 try { 200 faultyZK.init(); 201 cleaner.setConf(conf, faultyZK); 202 // should keep all files due to a ConnectionLossException getting the queues znodes 203 Iterable<FileStatus> toDelete = cleaner.getDeletableFiles(dummyFiles); 204 assertFalse(toDelete.iterator().hasNext()); 205 assertFalse(cleaner.isStopped()); 206 } finally { 207 faultyZK.close(); 208 } 209 210 // when zk is working both files should be returned 211 cleaner = new ReplicationHFileCleaner(); 212 ZKWatcher zkw = new ZKWatcher(conf, "testZooKeeperAbort-normal", null); 213 try { 214 cleaner.setConf(conf, zkw); 215 Iterable<FileStatus> filesToDelete = cleaner.getDeletableFiles(dummyFiles); 216 Iterator<FileStatus> iter = filesToDelete.iterator(); 217 assertTrue(iter.hasNext()); 218 assertEquals(new Path("hfile1"), iter.next().getPath()); 219 assertTrue(iter.hasNext()); 220 assertEquals(new Path("hfile2"), iter.next().getPath()); 221 assertFalse(iter.hasNext()); 222 } finally { 223 zkw.close(); 224 } 225 } 226 227 static class DummyServer extends MockServer { 228 229 @Override 230 public Configuration getConfiguration() { 231 return TEST_UTIL.getConfiguration(); 232 } 233 234 @Override 235 public ZKWatcher getZooKeeper() { 236 try { 237 return new ZKWatcher(getConfiguration(), "dummy server", this); 238 } catch (IOException e) { 239 e.printStackTrace(); 240 } 241 return null; 242 } 243 } 244 245 static class FaultyZooKeeperWatcher extends ZKWatcher { 246 private RecoverableZooKeeper zk; 247 248 public FaultyZooKeeperWatcher(Configuration conf, String identifier, Abortable abortable) 249 throws ZooKeeperConnectionException, IOException { 250 super(conf, identifier, abortable); 251 } 252 253 public void init() throws Exception { 254 this.zk = spy(super.getRecoverableZooKeeper()); 255 doThrow(new KeeperException.ConnectionLossException()).when(zk) 256 .getData("/hbase/replication/hfile-refs", null, new Stat()); 257 } 258 259 @Override 260 public RecoverableZooKeeper getRecoverableZooKeeper() { 261 return zk; 262 } 263 } 264}