1   /**
2    * Copyright 2010 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase.zookeeper;
21  
22  
23  import static org.junit.Assert.assertFalse;
24  import static org.junit.Assert.assertTrue;
25  
26  import java.io.IOException;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.hadoop.conf.Configuration;
31  import org.apache.hadoop.hbase.*;
32  import org.apache.hadoop.hbase.zookeeper.ZKTable.TableState;
33  import org.apache.zookeeper.KeeperException;
34  import org.apache.zookeeper.data.Stat;
35  import org.junit.AfterClass;
36  import org.junit.BeforeClass;
37  import org.junit.Test;
38  import org.junit.experimental.categories.Category;
39  import org.mockito.Mockito;
40  
41  @Category(MediumTests.class)
42  public class TestZKTable {
43    private static final Log LOG = LogFactory.getLog(TestZKTable.class);
44    private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
45  
46    @BeforeClass
47    public static void setUpBeforeClass() throws Exception {
48      TEST_UTIL.startMiniZKCluster();
49    }
50  
51    @AfterClass
52    public static void tearDownAfterClass() throws Exception {
53      TEST_UTIL.shutdownMiniZKCluster();
54    }
55  
56    Abortable abortable = new Abortable() {
57      @Override
58      public void abort(String why, Throwable e) {
59        LOG.info(why, e);
60      }
61  
62      @Override
63      public boolean isAborted() {
64        return false;
65      }
66    };
67  
68    @Test
69    public void testTableStates()
70    throws ZooKeeperConnectionException, IOException, KeeperException {
71      final String name = "testDisabled";
72  
73      ZooKeeperWatcher zkw = new ZooKeeperWatcher(TEST_UTIL.getConfiguration(),
74        name, abortable, true);
75      ZKTable zkt = new ZKTable(zkw);
76      assertFalse(zkt.isEnabledTable(name));
77      assertFalse(zkt.isDisablingTable(name));
78      assertFalse(zkt.isDisabledTable(name));
79      assertFalse(zkt.isEnablingTable(name));
80      assertFalse(zkt.isDisablingOrDisabledTable(name));
81      assertFalse(zkt.isDisabledOrEnablingTable(name));
82      assertFalse(zkt.isTablePresent(name));
83      zkt.setDisablingTable(name);
84      assertTrue(zkt.isDisablingTable(name));
85      assertTrue(zkt.isDisablingOrDisabledTable(name));
86      assertFalse(zkt.getDisabledTables().contains(name));
87      assertTrue(zkt.isTablePresent(name));
88      zkt.setDisabledTable(name);
89      assertTrue(zkt.isDisabledTable(name));
90      assertTrue(zkt.isDisablingOrDisabledTable(name));
91      assertFalse(zkt.isDisablingTable(name));
92      assertTrue(zkt.getDisabledTables().contains(name));
93      assertTrue(zkt.isTablePresent(name));
94      zkt.setEnablingTable(name);
95      assertTrue(zkt.isEnablingTable(name));
96      assertTrue(zkt.isDisabledOrEnablingTable(name));
97      assertFalse(zkt.isDisabledTable(name));
98      assertFalse(zkt.getDisabledTables().contains(name));
99      assertTrue(zkt.isTablePresent(name));
100     zkt.setEnabledTable(name);
101     assertTrue(zkt.isEnabledTable(name));
102     assertFalse(zkt.isEnablingTable(name));
103     assertTrue(zkt.isTablePresent(name));
104     zkt.setDeletedTable(name);
105     assertFalse(zkt.isEnabledTable(name));
106     assertFalse(zkt.isDisablingTable(name));
107     assertFalse(zkt.isDisabledTable(name));
108     assertFalse(zkt.isEnablingTable(name));
109     assertFalse(zkt.isDisablingOrDisabledTable(name));
110     assertFalse(zkt.isDisabledOrEnablingTable(name));
111     assertFalse(zkt.isTablePresent(name));
112   }
113 
114   private void runTest9294CompatibilityTest(String tableName, Configuration conf)
115   throws Exception {
116     ZooKeeperWatcher zkw = new ZooKeeperWatcher(conf,
117       tableName, abortable, true);
118     ZKTable zkt = new ZKTable(zkw);
119     zkt.setEnabledTable(tableName);
120     // check that current/0.94 format table has proper ENABLED format
121     assertTrue(
122       ZKTableReadOnly.getTableState(zkw, zkw.masterTableZNode,  tableName) == TableState.ENABLED);
123     // check that 0.92 format table is null, as expected by 0.92.0/0.92.1 clients
124     assertTrue(ZKTableReadOnly.getTableState(zkw, zkw.masterTableZNode92, tableName) == null);
125   }
126 
127   /**
128    * Test that ZK table writes table state in formats expected by 0.92 and 0.94 clients
129    */
130   @Test
131   public void test9294Compatibility() throws Exception {
132     // without useMulti
133     String tableName = "test9294Compatibility";
134     runTest9294CompatibilityTest(tableName, TEST_UTIL.getConfiguration());
135 
136     // with useMulti
137     tableName = "test9294CompatibilityWithMulti";
138     Configuration conf = HBaseConfiguration.create(TEST_UTIL.getConfiguration());
139     conf.setBoolean(HConstants.ZOOKEEPER_USEMULTI, true);
140     runTest9294CompatibilityTest(tableName, conf);
141   }
142 
143   /**
144    * RecoverableZookeeper that throws a KeeperException after throwExceptionInNumOperations
145    */
146   class ThrowingRecoverableZookeeper extends RecoverableZooKeeper {
147     private ZooKeeperWatcher zkw;
148     private int throwExceptionInNumOperations;
149 
150     public ThrowingRecoverableZookeeper(ZooKeeperWatcher zkw) throws Exception {
151       super(ZKConfig.getZKQuorumServersString(TEST_UTIL.getConfiguration()),
152         HConstants.DEFAULT_ZK_SESSION_TIMEOUT, zkw, 3, 1000);
153       this.zkw = zkw;
154       this.throwExceptionInNumOperations = 0; // indicate not to throw an exception
155     }
156 
157     public void setThrowExceptionInNumOperations(int throwExceptionInNumOperations) {
158       this.throwExceptionInNumOperations = throwExceptionInNumOperations;
159     }
160 
161     private void checkThrowKeeperException() throws KeeperException {
162       if (throwExceptionInNumOperations == 1) {
163         throwExceptionInNumOperations = 0;
164         throw new KeeperException.DataInconsistencyException();
165       }
166       if(throwExceptionInNumOperations > 0) throwExceptionInNumOperations--;
167     }
168 
169     public Stat setData(String path, byte[] data, int version)
170     throws KeeperException, InterruptedException {
171       checkThrowKeeperException();
172       return zkw.getRecoverableZooKeeper().setData(path, data, version);
173     }
174 
175     public void delete(String path, int version)
176     throws InterruptedException, KeeperException {
177       checkThrowKeeperException();
178       zkw.getRecoverableZooKeeper().delete(path, version);
179     }
180   }
181   /**
182    * Because two ZooKeeper nodes are written for each table state transition
183    * {@link ZooKeeperWatcher#masterTableZNode} and {@link ZooKeeperWatcher#masterTableZNode92}
184    * it is possible that we fail in between the two operations and are left with
185    * inconsistent state (when hbase.zookeeper.useMulti is false).
186    * Check that we can get back to a consistent state by retrying the operation.
187    */
188   @Test
189   public void testDisableTableRetry() throws Exception {
190     final String tableName = "testDisableTableRetry";
191 
192     Configuration conf = TEST_UTIL.getConfiguration();
193     // test only relevant if useMulti is false
194     conf.setBoolean(HConstants.ZOOKEEPER_USEMULTI, false);
195     ZooKeeperWatcher zkw = new ZooKeeperWatcher(conf,
196       tableName, abortable, true);
197     ThrowingRecoverableZookeeper throwing = new ThrowingRecoverableZookeeper(zkw);
198     ZooKeeperWatcher spyZookeeperWatcher = Mockito.spy(zkw);
199     Mockito.doReturn(throwing).when(spyZookeeperWatcher).getRecoverableZooKeeper();
200 
201     ZKTable zkt = new ZKTable(spyZookeeperWatcher);
202     zkt.setEnabledTable(tableName);
203     assertTrue(zkt.isEnabledOrDisablingTable(tableName));
204     boolean caughtExpectedException = false;
205     try {
206       // throw an exception on the second ZK operation, which means the first will succeed.
207       throwing.setThrowExceptionInNumOperations(2);
208       zkt.setDisabledTable(tableName);
209     } catch (KeeperException ke) {
210       caughtExpectedException = true;
211     }
212     assertTrue(caughtExpectedException);
213     assertFalse(zkt.isDisabledTable(tableName));
214     // try again, ensure table is disabled
215     zkt.setDisabledTable(tableName);
216     // ensure disabled from master perspective
217     assertTrue(zkt.isDisabledTable(tableName));
218     // ensure disabled from client perspective
219     assertTrue(ZKTableReadOnly.isDisabledTable(zkw, tableName));
220   }
221 
222   /**
223    * Same as above, but with enableTable
224    */
225   @Test
226   public void testEnableTableRetry() throws Exception {
227     final String tableName = "testEnableTableRetry";
228 
229     Configuration conf = TEST_UTIL.getConfiguration();
230     // test only relevant if useMulti is false
231     conf.setBoolean(HConstants.ZOOKEEPER_USEMULTI, false);
232     ZooKeeperWatcher zkw = new ZooKeeperWatcher(conf,
233       tableName, abortable, true);
234     ThrowingRecoverableZookeeper throwing = new ThrowingRecoverableZookeeper(zkw);
235     ZooKeeperWatcher spyZookeeperWatcher = Mockito.spy(zkw);
236     Mockito.doReturn(throwing).when(spyZookeeperWatcher).getRecoverableZooKeeper();
237 
238     ZKTable zkt = new ZKTable(spyZookeeperWatcher);
239     zkt.setDisabledTable(tableName);
240     assertTrue(zkt.isDisabledTable(tableName));
241     boolean caughtExpectedException = false;
242     try {
243       // throw an exception on the second ZK operation, which means the first will succeed.
244       throwing.throwExceptionInNumOperations = 2;
245       zkt.setEnabledTable(tableName);
246     } catch (KeeperException ke) {
247       caughtExpectedException = true;
248     }
249     assertTrue(caughtExpectedException);
250     assertFalse(zkt.isEnabledTable(tableName));
251     // try again, ensure table is enabled
252     zkt.setEnabledTable(tableName);
253     // ensure enabled from master perspective
254     assertTrue(zkt.isEnabledTable(tableName));
255     // ensure enabled from client perspective
256     assertTrue(ZKTableReadOnly.isEnabledTable(zkw, tableName));
257   }
258 
259   /**
260    * Same as above, but with deleteTable
261    */
262   @Test
263   public void testDeleteTableRetry() throws Exception {
264     final String tableName = "testEnableTableRetry";
265 
266     Configuration conf = TEST_UTIL.getConfiguration();
267     // test only relevant if useMulti is false
268     conf.setBoolean(HConstants.ZOOKEEPER_USEMULTI, false);
269     ZooKeeperWatcher zkw = new ZooKeeperWatcher(conf,
270       tableName, abortable, true);
271     ThrowingRecoverableZookeeper throwing = new ThrowingRecoverableZookeeper(zkw);
272     ZooKeeperWatcher spyZookeeperWatcher = Mockito.spy(zkw);
273     Mockito.doReturn(throwing).when(spyZookeeperWatcher).getRecoverableZooKeeper();
274 
275     ZKTable zkt = new ZKTable(spyZookeeperWatcher);
276     zkt.setDisabledTable(tableName);
277     assertTrue(zkt.isDisabledTable(tableName));
278     boolean caughtExpectedException = false;
279     try {
280       // throw an exception on the second ZK operation, which means the first will succeed.
281       throwing.setThrowExceptionInNumOperations(2);
282       zkt.setDeletedTable(tableName);
283     } catch (KeeperException ke) {
284       caughtExpectedException = true;
285     }
286     assertTrue(caughtExpectedException);
287     assertTrue(zkt.isTablePresent(tableName));
288     // try again, ensure table is deleted
289     zkt.setDeletedTable(tableName);
290     // ensure deleted from master perspective
291     assertFalse(zkt.isTablePresent(tableName));
292     // ensure deleted from client perspective
293     assertFalse(ZKTableReadOnly.getDisabledTables(zkw).contains(tableName));
294   }
295 
296   @org.junit.Rule
297   public org.apache.hadoop.hbase.ResourceCheckerJUnitRule cu =
298     new org.apache.hadoop.hbase.ResourceCheckerJUnitRule();
299 }