View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.security.token;
20  
21  import static org.junit.Assert.assertEquals;
22  import static org.junit.Assert.assertFalse;
23  import static org.junit.Assert.assertNotNull;
24  import static org.junit.Assert.assertNull;
25  import static org.junit.Assert.assertTrue;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.hadoop.conf.Configuration;
30  import org.apache.hadoop.hbase.Abortable;
31  import org.apache.hadoop.hbase.HBaseConfiguration;
32  import org.apache.hadoop.hbase.HBaseTestingUtility;
33  import org.apache.hadoop.hbase.LargeTests;
34  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
35  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
36  import org.junit.AfterClass;
37  import org.junit.BeforeClass;
38  import org.junit.Test;
39  import org.junit.experimental.categories.Category;
40  
41  /**
42   * Test the synchronization of token authentication master keys through
43   * ZKSecretWatcher
44   */
45  @Category(LargeTests.class)
46  public class TestZKSecretWatcher {
47    private static Log LOG = LogFactory.getLog(TestZKSecretWatcher.class);
48    private static HBaseTestingUtility TEST_UTIL;
49    private static AuthenticationTokenSecretManager KEY_MASTER;
50    private static AuthenticationTokenSecretManager KEY_SLAVE;
51    private static AuthenticationTokenSecretManager KEY_SLAVE2;
52    private static AuthenticationTokenSecretManager KEY_SLAVE3;
53  
54    private static class MockAbortable implements Abortable {
55      private boolean abort;
56      public void abort(String reason, Throwable e) {
57        LOG.info("Aborting: "+reason, e);
58        abort = true;
59      }
60  
61      public boolean isAborted() {
62        return abort;
63      }
64    }
65  
66    @BeforeClass
67    public static void setupBeforeClass() throws Exception {
68      TEST_UTIL = new HBaseTestingUtility();
69      TEST_UTIL.startMiniZKCluster();
70      Configuration conf = TEST_UTIL.getConfiguration();
71  
72      ZooKeeperWatcher zk = newZK(conf, "server1", new MockAbortable());
73      AuthenticationTokenSecretManager[] tmp = new AuthenticationTokenSecretManager[2];
74      tmp[0] = new AuthenticationTokenSecretManager(
75          conf, zk, "server1", 60*60*1000, 60*1000);
76      tmp[0].start();
77  
78      zk = newZK(conf, "server2", new MockAbortable());
79      tmp[1] = new AuthenticationTokenSecretManager(
80          conf, zk, "server2", 60*60*1000, 60*1000);
81      tmp[1].start();
82  
83      while (KEY_MASTER == null) {
84        for (int i=0; i<2; i++) {
85          if (tmp[i].isMaster()) {
86            KEY_MASTER = tmp[i];
87            KEY_SLAVE = tmp[ (i+1) % 2 ];
88            break;
89          }
90        }
91        Thread.sleep(500);
92      }
93      LOG.info("Master is "+KEY_MASTER.getName()+
94          ", slave is "+KEY_SLAVE.getName());
95    }
96  
97    @AfterClass
98    public static void tearDownAfterClass() throws Exception {
99      TEST_UTIL.shutdownMiniZKCluster();
100   }
101 
102   @Test
103   public void testKeyUpdate() throws Exception {
104     // sanity check
105     assertTrue(KEY_MASTER.isMaster());
106     assertFalse(KEY_SLAVE.isMaster());
107     int maxKeyId = 0;
108 
109     KEY_MASTER.rollCurrentKey();
110     AuthenticationKey key1 = KEY_MASTER.getCurrentKey();
111     assertNotNull(key1);
112     LOG.debug("Master current key: "+key1.getKeyId());
113 
114     // wait for slave to update
115     Thread.sleep(1000);
116     AuthenticationKey slaveCurrent = KEY_SLAVE.getCurrentKey();
117     assertNotNull(slaveCurrent);
118     assertEquals(key1, slaveCurrent);
119     LOG.debug("Slave current key: "+slaveCurrent.getKeyId());
120 
121     // generate two more keys then expire the original
122     KEY_MASTER.rollCurrentKey();
123     AuthenticationKey key2 = KEY_MASTER.getCurrentKey();
124     LOG.debug("Master new current key: "+key2.getKeyId());
125     KEY_MASTER.rollCurrentKey();
126     AuthenticationKey key3 = KEY_MASTER.getCurrentKey();
127     LOG.debug("Master new current key: "+key3.getKeyId());
128 
129     // force expire the original key
130     key1.setExpiration(EnvironmentEdgeManager.currentTimeMillis() - 1000);
131     KEY_MASTER.removeExpiredKeys();
132     // verify removed from master
133     assertNull(KEY_MASTER.getKey(key1.getKeyId()));
134 
135     // wait for slave to catch up
136     Thread.sleep(1000);
137     // make sure the slave has both new keys
138     AuthenticationKey slave2 = KEY_SLAVE.getKey(key2.getKeyId());
139     assertNotNull(slave2);
140     assertEquals(key2, slave2);
141     AuthenticationKey slave3 = KEY_SLAVE.getKey(key3.getKeyId());
142     assertNotNull(slave3);
143     assertEquals(key3, slave3);
144     slaveCurrent = KEY_SLAVE.getCurrentKey();
145     assertEquals(key3, slaveCurrent);
146     LOG.debug("Slave current key: "+slaveCurrent.getKeyId());
147 
148     // verify that the expired key has been removed
149     assertNull(KEY_SLAVE.getKey(key1.getKeyId()));
150 
151     // bring up a new slave
152     Configuration conf = TEST_UTIL.getConfiguration();
153     ZooKeeperWatcher zk = newZK(conf, "server3", new MockAbortable());
154     KEY_SLAVE2 = new AuthenticationTokenSecretManager(
155         conf, zk, "server3", 60*60*1000, 60*1000);
156     KEY_SLAVE2.start();
157 
158     Thread.sleep(1000);
159     // verify the new slave has current keys (and not expired)
160     slave2 = KEY_SLAVE2.getKey(key2.getKeyId());
161     assertNotNull(slave2);
162     assertEquals(key2, slave2);
163     slave3 = KEY_SLAVE2.getKey(key3.getKeyId());
164     assertNotNull(slave3);
165     assertEquals(key3, slave3);
166     slaveCurrent = KEY_SLAVE2.getCurrentKey();
167     assertEquals(key3, slaveCurrent);
168     assertNull(KEY_SLAVE2.getKey(key1.getKeyId()));
169 
170     // test leader failover
171     KEY_MASTER.stop();
172 
173     // wait for master to stop
174     Thread.sleep(1000);
175     assertFalse(KEY_MASTER.isMaster());
176 
177     // check for a new master
178     AuthenticationTokenSecretManager[] mgrs =
179         new AuthenticationTokenSecretManager[]{ KEY_SLAVE, KEY_SLAVE2 };
180     AuthenticationTokenSecretManager newMaster = null;
181     int tries = 0;
182     while (newMaster == null && tries++ < 5) {
183       for (AuthenticationTokenSecretManager mgr : mgrs) {
184         if (mgr.isMaster()) {
185           newMaster = mgr;
186           break;
187         }
188       }
189       if (newMaster == null) {
190         Thread.sleep(500);
191       }
192     }
193     assertNotNull(newMaster);
194 
195     AuthenticationKey current = newMaster.getCurrentKey();
196     // new master will immediately roll the current key, so it's current may be greater
197     assertTrue(current.getKeyId() >= slaveCurrent.getKeyId());
198     LOG.debug("New master, current key: "+current.getKeyId());
199 
200     // roll the current key again on new master and verify the key ID increments
201     newMaster.rollCurrentKey();
202     AuthenticationKey newCurrent = newMaster.getCurrentKey();
203     LOG.debug("New master, rolled new current key: "+newCurrent.getKeyId());
204     assertTrue(newCurrent.getKeyId() > current.getKeyId());
205 
206     // add another slave
207     ZooKeeperWatcher zk3 = newZK(conf, "server4", new MockAbortable());
208     KEY_SLAVE3 = new AuthenticationTokenSecretManager(
209         conf, zk3, "server4", 60*60*1000, 60*1000);
210     KEY_SLAVE3.start();
211     Thread.sleep(5000);
212 
213     // check master failover again
214     newMaster.stop();
215 
216     // wait for master to stop
217     Thread.sleep(5000);
218     assertFalse(newMaster.isMaster());
219 
220     // check for a new master
221     mgrs = new AuthenticationTokenSecretManager[]{ KEY_SLAVE, KEY_SLAVE2, KEY_SLAVE3 };
222     newMaster = null;
223     tries = 0;
224     while (newMaster == null && tries++ < 5) {
225       for (AuthenticationTokenSecretManager mgr : mgrs) {
226         if (mgr.isMaster()) {
227           newMaster = mgr;
228           break;
229         }
230       }
231       if (newMaster == null) {
232         Thread.sleep(500);
233       }
234     }
235     assertNotNull(newMaster);
236 
237     AuthenticationKey current2 = newMaster.getCurrentKey();
238     // new master will immediately roll the current key, so it's current may be greater
239     assertTrue(current2.getKeyId() >= newCurrent.getKeyId());
240     LOG.debug("New master 2, current key: "+current2.getKeyId());
241 
242     // roll the current key again on new master and verify the key ID increments
243     newMaster.rollCurrentKey();
244     AuthenticationKey newCurrent2 = newMaster.getCurrentKey();
245     LOG.debug("New master 2, rolled new current key: "+newCurrent2.getKeyId());
246     assertTrue(newCurrent2.getKeyId() > current2.getKeyId());
247   }
248 
249   private static ZooKeeperWatcher newZK(Configuration conf, String name,
250       Abortable abort) throws Exception {
251     Configuration copy = HBaseConfiguration.create(conf);
252     ZooKeeperWatcher zk = new ZooKeeperWatcher(copy, name, abort);
253     return zk;
254   }
255 }