1   /*
2    * Copyright 2011 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  
21  package org.apache.hadoop.hbase.coprocessor;
22  
23  import static org.junit.Assert.assertFalse;
24  import static org.junit.Assert.assertNotNull;
25  import static org.junit.Assert.assertNull;
26  import static org.junit.Assert.assertTrue;
27  
28  import java.io.IOException;
29  import java.util.Collection;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.NavigableMap;
33  
34  import junit.framework.Assert;
35  
36  import org.apache.commons.logging.Log;
37  import org.apache.commons.logging.LogFactory;
38  import org.apache.hadoop.conf.Configuration;
39  import org.apache.hadoop.hbase.*;
40  import org.apache.hadoop.hbase.client.HBaseAdmin;
41  import org.apache.hadoop.hbase.client.HTable;
42  import org.apache.hadoop.hbase.master.AssignmentManager;
43  import org.apache.hadoop.hbase.master.HMaster;
44  import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
45  import org.apache.hadoop.hbase.master.snapshot.SnapshotManager;
46  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
47  import org.apache.hadoop.hbase.regionserver.HRegionServer;
48  import org.apache.hadoop.hbase.util.Bytes;
49  import org.apache.hadoop.hbase.util.Threads;
50  import org.junit.AfterClass;
51  import org.junit.BeforeClass;
52  import org.junit.Test;
53  import org.junit.experimental.categories.Category;
54  
55  /**
56   * Tests invocation of the {@link org.apache.hadoop.hbase.coprocessor.MasterObserver}
57   * interface hooks at all appropriate times during normal HMaster operations.
58   */
59  @Category(MediumTests.class)
60  public class TestMasterObserver {
61    private static final Log LOG = LogFactory.getLog(TestMasterObserver.class);
62  
63    public static class CPMasterObserver implements MasterObserver {
64  
65      private boolean bypass = false;
66      private boolean preCreateTableCalled;
67      private boolean postCreateTableCalled;
68      private boolean preDeleteTableCalled;
69      private boolean postDeleteTableCalled;
70      private boolean preModifyTableCalled;
71      private boolean postModifyTableCalled;
72      private boolean preAddColumnCalled;
73      private boolean postAddColumnCalled;
74      private boolean preModifyColumnCalled;
75      private boolean postModifyColumnCalled;
76      private boolean preDeleteColumnCalled;
77      private boolean postDeleteColumnCalled;
78      private boolean preEnableTableCalled;
79      private boolean postEnableTableCalled;
80      private boolean preDisableTableCalled;
81      private boolean postDisableTableCalled;
82      private boolean preMoveCalled;
83      private boolean postMoveCalled;
84      private boolean preAssignCalled;
85      private boolean postAssignCalled;
86      private boolean preUnassignCalled;
87      private boolean postUnassignCalled;
88      private boolean preBalanceCalled;
89      private boolean postBalanceCalled;
90      private boolean preBalanceSwitchCalled;
91      private boolean postBalanceSwitchCalled;
92      private boolean preShutdownCalled;
93      private boolean preStopMasterCalled;
94      private boolean postStartMasterCalled;
95      private boolean startCalled;
96      private boolean stopCalled;
97      private boolean preSnapshotCalled;
98      private boolean postSnapshotCalled;
99      private boolean preCloneSnapshotCalled;
100     private boolean postCloneSnapshotCalled;
101     private boolean preRestoreSnapshotCalled;
102     private boolean postRestoreSnapshotCalled;
103     private boolean preDeleteSnapshotCalled;
104     private boolean postDeleteSnapshotCalled;
105 
106     public void enableBypass(boolean bypass) {
107       this.bypass = bypass;
108     }
109 
110     public void resetStates() {
111       preCreateTableCalled = false;
112       postCreateTableCalled = false;
113       preDeleteTableCalled = false;
114       postDeleteTableCalled = false;
115       preModifyTableCalled = false;
116       postModifyTableCalled = false;
117       preAddColumnCalled = false;
118       postAddColumnCalled = false;
119       preModifyColumnCalled = false;
120       postModifyColumnCalled = false;
121       preDeleteColumnCalled = false;
122       postDeleteColumnCalled = false;
123       preEnableTableCalled = false;
124       postEnableTableCalled = false;
125       preDisableTableCalled = false;
126       postDisableTableCalled = false;
127       preMoveCalled= false;
128       postMoveCalled = false;
129       preAssignCalled = false;
130       postAssignCalled = false;
131       preUnassignCalled = false;
132       postUnassignCalled = false;
133       preBalanceCalled = false;
134       postBalanceCalled = false;
135       preBalanceSwitchCalled = false;
136       postBalanceSwitchCalled = false;
137       preSnapshotCalled = false;
138       postSnapshotCalled = false;
139       preCloneSnapshotCalled = false;
140       postCloneSnapshotCalled = false;
141       preRestoreSnapshotCalled = false;
142       postRestoreSnapshotCalled = false;
143       preDeleteSnapshotCalled = false;
144       postDeleteSnapshotCalled = false;
145     }
146 
147     @Override
148     public void preCreateTable(ObserverContext<MasterCoprocessorEnvironment> env,
149         HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
150       if (bypass) {
151         env.bypass();
152       }
153       preCreateTableCalled = true;
154     }
155 
156     @Override
157     public void postCreateTable(ObserverContext<MasterCoprocessorEnvironment> env,
158         HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
159       postCreateTableCalled = true;
160     }
161 
162     public boolean wasCreateTableCalled() {
163       return preCreateTableCalled && postCreateTableCalled;
164     }
165 
166     public boolean preCreateTableCalledOnly() {
167       return preCreateTableCalled && !postCreateTableCalled;
168     }
169 
170     @Override
171     public void preDeleteTable(ObserverContext<MasterCoprocessorEnvironment> env,
172         byte[] tableName) throws IOException {
173       if (bypass) {
174         env.bypass();
175       }
176       preDeleteTableCalled = true;
177     }
178 
179     @Override
180     public void postDeleteTable(ObserverContext<MasterCoprocessorEnvironment> env,
181         byte[] tableName) throws IOException {
182       postDeleteTableCalled = true;
183     }
184 
185     public boolean wasDeleteTableCalled() {
186       return preDeleteTableCalled && postDeleteTableCalled;
187     }
188 
189     public boolean preDeleteTableCalledOnly() {
190       return preDeleteTableCalled && !postDeleteTableCalled;
191     }
192 
193     @Override
194     public void preModifyTable(ObserverContext<MasterCoprocessorEnvironment> env,
195         byte[] tableName, HTableDescriptor htd) throws IOException {
196       if (bypass) {
197         env.bypass();
198       }
199       preModifyTableCalled = true;
200     }
201 
202     @Override
203     public void postModifyTable(ObserverContext<MasterCoprocessorEnvironment> env,
204         byte[] tableName, HTableDescriptor htd) throws IOException {
205       postModifyTableCalled = true;
206     }
207 
208     public boolean wasModifyTableCalled() {
209       return preModifyTableCalled && postModifyTableCalled;
210     }
211 
212     public boolean preModifyTableCalledOnly() {
213       return preModifyTableCalled && !postModifyTableCalled;
214     }
215 
216     @Override
217     public void preAddColumn(ObserverContext<MasterCoprocessorEnvironment> env,
218         byte[] tableName, HColumnDescriptor column) throws IOException {
219       if (bypass) {
220         env.bypass();
221       }
222       preAddColumnCalled = true;
223     }
224 
225     @Override
226     public void postAddColumn(ObserverContext<MasterCoprocessorEnvironment> env,
227         byte[] tableName, HColumnDescriptor column) throws IOException {
228       postAddColumnCalled = true;
229     }
230 
231     public boolean wasAddColumnCalled() {
232       return preAddColumnCalled && postAddColumnCalled;
233     }
234 
235     public boolean preAddColumnCalledOnly() {
236       return preAddColumnCalled && !postAddColumnCalled;
237     }
238 
239     @Override
240     public void preModifyColumn(ObserverContext<MasterCoprocessorEnvironment> env,
241         byte[] tableName, HColumnDescriptor descriptor) throws IOException {
242       if (bypass) {
243         env.bypass();
244       }
245       preModifyColumnCalled = true;
246     }
247 
248     @Override
249     public void postModifyColumn(ObserverContext<MasterCoprocessorEnvironment> env,
250         byte[] tableName, HColumnDescriptor descriptor) throws IOException {
251       postModifyColumnCalled = true;
252     }
253 
254     public boolean wasModifyColumnCalled() {
255       return preModifyColumnCalled && postModifyColumnCalled;
256     }
257 
258     public boolean preModifyColumnCalledOnly() {
259       return preModifyColumnCalled && !postModifyColumnCalled;
260     }
261 
262     @Override
263     public void preDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> env,
264         byte[] tableName, byte[] c) throws IOException {
265       if (bypass) {
266         env.bypass();
267       }
268       preDeleteColumnCalled = true;
269     }
270 
271     @Override
272     public void postDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> env,
273         byte[] tableName, byte[] c) throws IOException {
274       postDeleteColumnCalled = true;
275     }
276 
277     public boolean wasDeleteColumnCalled() {
278       return preDeleteColumnCalled && postDeleteColumnCalled;
279     }
280 
281     public boolean preDeleteColumnCalledOnly() {
282       return preDeleteColumnCalled && !postDeleteColumnCalled;
283     }
284 
285     @Override
286     public void preEnableTable(ObserverContext<MasterCoprocessorEnvironment> env,
287         byte[] tableName) throws IOException {
288       if (bypass) {
289         env.bypass();
290       }
291       preEnableTableCalled = true;
292     }
293 
294     @Override
295     public void postEnableTable(ObserverContext<MasterCoprocessorEnvironment> env,
296         byte[] tableName) throws IOException {
297       postEnableTableCalled = true;
298     }
299 
300     public boolean wasEnableTableCalled() {
301       return preEnableTableCalled && postEnableTableCalled;
302     }
303 
304     public boolean preEnableTableCalledOnly() {
305       return preEnableTableCalled && !postEnableTableCalled;
306     }
307 
308     @Override
309     public void preDisableTable(ObserverContext<MasterCoprocessorEnvironment> env,
310         byte[] tableName) throws IOException {
311       if (bypass) {
312         env.bypass();
313       }
314       preDisableTableCalled = true;
315     }
316 
317     @Override
318     public void postDisableTable(ObserverContext<MasterCoprocessorEnvironment> env,
319         byte[] tableName) throws IOException {
320       postDisableTableCalled = true;
321     }
322 
323     public boolean wasDisableTableCalled() {
324       return preDisableTableCalled && postDisableTableCalled;
325     }
326 
327     public boolean preDisableTableCalledOnly() {
328       return preDisableTableCalled && !postDisableTableCalled;
329     }
330 
331     @Override
332     public void preMove(ObserverContext<MasterCoprocessorEnvironment> env,
333         HRegionInfo region, ServerName srcServer, ServerName destServer)
334     throws IOException {
335       if (bypass) {
336         env.bypass();
337       }
338       preMoveCalled = true;
339     }
340 
341     @Override
342     public void postMove(ObserverContext<MasterCoprocessorEnvironment> env, HRegionInfo region,
343         ServerName srcServer, ServerName destServer)
344     throws IOException {
345       postMoveCalled = true;
346     }
347 
348     public boolean wasMoveCalled() {
349       return preMoveCalled && postMoveCalled;
350     }
351 
352     public boolean preMoveCalledOnly() {
353       return preMoveCalled && !postMoveCalled;
354     }
355     
356     @Override
357     public void preAssign(ObserverContext<MasterCoprocessorEnvironment> env,
358         final HRegionInfo regionInfo) throws IOException {
359       if (bypass) {
360         env.bypass();
361       }
362       preAssignCalled = true;
363     }
364 
365     @Override
366     public void postAssign(ObserverContext<MasterCoprocessorEnvironment> env,
367         final HRegionInfo regionInfo) throws IOException {
368       postAssignCalled = true;
369     }
370     
371     public boolean wasAssignCalled() {
372       return preAssignCalled && postAssignCalled;
373     }
374 
375     public boolean preAssignCalledOnly() {
376       return preAssignCalled && !postAssignCalled;
377     }
378 
379     @Override
380     public void preUnassign(ObserverContext<MasterCoprocessorEnvironment> env,
381         final HRegionInfo regionInfo, final boolean force) throws IOException {
382       if (bypass) {
383         env.bypass();
384       }
385       preUnassignCalled = true;
386     }
387 
388     @Override
389     public void postUnassign(ObserverContext<MasterCoprocessorEnvironment> env,
390         final HRegionInfo regionInfo, final boolean force) throws IOException {
391       postUnassignCalled = true;
392     }
393 
394     public boolean wasUnassignCalled() {
395       return preUnassignCalled && postUnassignCalled;
396     }
397 
398     public boolean preUnassignCalledOnly() {
399       return preUnassignCalled && !postUnassignCalled;
400     }
401 
402     @Override
403     public void preBalance(ObserverContext<MasterCoprocessorEnvironment> env)
404         throws IOException {
405       if (bypass) {
406         env.bypass();
407       }
408       preBalanceCalled = true;
409     }
410 
411     @Override
412     public void postBalance(ObserverContext<MasterCoprocessorEnvironment> env)
413         throws IOException {
414       postBalanceCalled = true;
415     }
416 
417     public boolean wasBalanceCalled() {
418       return preBalanceCalled && postBalanceCalled;
419     }
420 
421     public boolean preBalanceCalledOnly() {
422       return preBalanceCalled && !postBalanceCalled;
423     }
424 
425     @Override
426     public boolean preBalanceSwitch(ObserverContext<MasterCoprocessorEnvironment> env, boolean b)
427         throws IOException {
428       if (bypass) {
429         env.bypass();
430       }
431       preBalanceSwitchCalled = true;
432       return b;
433     }
434 
435     @Override
436     public void postBalanceSwitch(ObserverContext<MasterCoprocessorEnvironment> env,
437         boolean oldValue, boolean newValue) throws IOException {
438       postBalanceSwitchCalled = true;
439     }
440 
441     public boolean wasBalanceSwitchCalled() {
442       return preBalanceSwitchCalled && postBalanceSwitchCalled;
443     }
444 
445     public boolean preBalanceSwitchCalledOnly() {
446       return preBalanceSwitchCalled && !postBalanceSwitchCalled;
447     }
448 
449     @Override
450     public void preShutdown(ObserverContext<MasterCoprocessorEnvironment> env)
451         throws IOException {
452       preShutdownCalled = true;
453     }
454 
455     @Override
456     public void preStopMaster(ObserverContext<MasterCoprocessorEnvironment> env)
457         throws IOException {
458       preStopMasterCalled = true;
459     }
460 
461     @Override
462     public void postStartMaster(ObserverContext<MasterCoprocessorEnvironment> ctx)
463         throws IOException {
464       postStartMasterCalled = true;
465     }
466 
467     public boolean wasStartMasterCalled() {
468       return postStartMasterCalled;
469     }
470 
471     @Override
472     public void start(CoprocessorEnvironment env) throws IOException {
473       startCalled = true;
474     }
475 
476     @Override
477     public void stop(CoprocessorEnvironment env) throws IOException {
478       stopCalled = true;
479     }
480 
481     public boolean wasStarted() { return startCalled; }
482 
483     public boolean wasStopped() { return stopCalled; }
484 
485     @Override
486     public void preSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
487         final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
488         throws IOException {
489       preSnapshotCalled = true;
490     }
491 
492     @Override
493     public void postSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
494         final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
495         throws IOException {
496       postSnapshotCalled = true;
497     }
498 
499     public boolean wasSnapshotCalled() {
500       return preSnapshotCalled && postSnapshotCalled;
501     }
502 
503     @Override
504     public void preCloneSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
505         final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
506         throws IOException {
507       preCloneSnapshotCalled = true;
508     }
509 
510     @Override
511     public void postCloneSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
512         final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
513         throws IOException {
514       postCloneSnapshotCalled = true;
515     }
516 
517     public boolean wasCloneSnapshotCalled() {
518       return preCloneSnapshotCalled && postCloneSnapshotCalled;
519     }
520 
521     @Override
522     public void preRestoreSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
523         final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
524         throws IOException {
525       preRestoreSnapshotCalled = true;
526     }
527 
528     @Override
529     public void postRestoreSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
530         final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
531         throws IOException {
532       postRestoreSnapshotCalled = true;
533     }
534 
535     public boolean wasRestoreSnapshotCalled() {
536       return preRestoreSnapshotCalled && postRestoreSnapshotCalled;
537     }
538 
539     @Override
540     public void preDeleteSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
541         final SnapshotDescription snapshot) throws IOException {
542       preDeleteSnapshotCalled = true;
543     }
544 
545     @Override
546     public void postDeleteSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
547         final SnapshotDescription snapshot) throws IOException {
548       postDeleteSnapshotCalled = true;
549     }
550 
551     public boolean wasDeleteSnapshotCalled() {
552       return preDeleteSnapshotCalled && postDeleteSnapshotCalled;
553     }
554   }
555 
556   private static HBaseTestingUtility UTIL = new HBaseTestingUtility();
557   private static byte[] TEST_SNAPSHOT = Bytes.toBytes("observed_snapshot");
558   private static byte[] TEST_TABLE = Bytes.toBytes("observed_table");
559   private static byte[] TEST_CLONE = Bytes.toBytes("observed_clone");
560   private static byte[] TEST_FAMILY = Bytes.toBytes("fam1");
561   private static byte[] TEST_FAMILY2 = Bytes.toBytes("fam2");
562 
563   @BeforeClass
564   public static void setupBeforeClass() throws Exception {
565     Configuration conf = UTIL.getConfiguration();
566     conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
567         CPMasterObserver.class.getName());
568     // Enable snapshot
569     conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
570     // We need more than one data server on this test
571     UTIL.startMiniCluster(2);
572   }
573 
574   @AfterClass
575   public static void tearDownAfterClass() throws Exception {
576     UTIL.shutdownMiniCluster();
577   }
578 
579   @Test
580   public void testStarted() throws Exception {
581     MiniHBaseCluster cluster = UTIL.getHBaseCluster();
582 
583     HMaster master = cluster.getMaster();
584     assertTrue("Master should be active", master.isActiveMaster());
585     MasterCoprocessorHost host = master.getCoprocessorHost();
586     assertNotNull("CoprocessorHost should not be null", host);
587     CPMasterObserver cp = (CPMasterObserver)host.findCoprocessor(
588         CPMasterObserver.class.getName());
589     assertNotNull("CPMasterObserver coprocessor not found or not installed!", cp);
590 
591     // check basic lifecycle
592     assertTrue("MasterObserver should have been started", cp.wasStarted());
593     assertTrue("postStartMaster() hook should have been called",
594         cp.wasStartMasterCalled());
595   }
596 
597   @Test
598   public void testTableOperations() throws Exception {
599     MiniHBaseCluster cluster = UTIL.getHBaseCluster();
600 
601     HMaster master = cluster.getMaster();
602     MasterCoprocessorHost host = master.getCoprocessorHost();
603     CPMasterObserver cp = (CPMasterObserver)host.findCoprocessor(
604         CPMasterObserver.class.getName());
605     cp.enableBypass(true);
606     cp.resetStates();
607     assertFalse("No table created yet", cp.wasCreateTableCalled());
608 
609     // create a table
610     HTableDescriptor htd = new HTableDescriptor(TEST_TABLE);
611     htd.addFamily(new HColumnDescriptor(TEST_FAMILY));
612     HBaseAdmin admin = UTIL.getHBaseAdmin();
613 
614     admin.createTable(htd);
615     // preCreateTable can't bypass default action.
616     assertTrue("Test table should be created", cp.wasCreateTableCalled());
617 
618     admin.disableTable(TEST_TABLE);
619     assertTrue(admin.isTableDisabled(TEST_TABLE));
620     // preDisableTable can't bypass default action.
621     assertTrue("Coprocessor should have been called on table disable",
622       cp.wasDisableTableCalled());
623 
624     // enable
625     assertFalse(cp.wasEnableTableCalled());
626     admin.enableTable(TEST_TABLE);
627     assertTrue(admin.isTableEnabled(TEST_TABLE));
628     // preEnableTable can't bypass default action.
629     assertTrue("Coprocessor should have been called on table enable",
630       cp.wasEnableTableCalled());
631 
632     admin.disableTable(TEST_TABLE);
633     assertTrue(admin.isTableDisabled(TEST_TABLE));
634 
635     // modify table
636     htd.setMaxFileSize(512 * 1024 * 1024);
637     modifyTableSync(admin, TEST_TABLE, htd);
638     // preModifyTable can't bypass default action.
639     assertTrue("Test table should have been modified",
640       cp.wasModifyTableCalled());
641 
642     // add a column family
643     admin.addColumn(TEST_TABLE, new HColumnDescriptor(TEST_FAMILY2));
644     assertTrue("New column family shouldn't have been added to test table",
645       cp.preAddColumnCalledOnly());
646 
647     // modify a column family
648     HColumnDescriptor hcd1 = new HColumnDescriptor(TEST_FAMILY2);
649     hcd1.setMaxVersions(25);
650     admin.modifyColumn(TEST_TABLE, hcd1);
651     assertTrue("Second column family should be modified",
652       cp.preModifyColumnCalledOnly());
653 
654     // delete table
655     admin.deleteTable(TEST_TABLE);
656     assertFalse("Test table should have been deleted",
657         admin.tableExists(TEST_TABLE));
658     // preDeleteTable can't bypass default action.
659     assertTrue("Coprocessor should have been called on table delete",
660       cp.wasDeleteTableCalled());
661 
662 
663     // turn off bypass, run the tests again
664     cp.enableBypass(false);
665     cp.resetStates();
666 
667     admin.createTable(htd);
668     assertTrue("Test table should be created", cp.wasCreateTableCalled());
669 
670     // disable
671     assertFalse(cp.wasDisableTableCalled());
672 
673     admin.disableTable(TEST_TABLE);
674     assertTrue(admin.isTableDisabled(TEST_TABLE));
675     assertTrue("Coprocessor should have been called on table disable",
676       cp.wasDisableTableCalled());
677 
678     // modify table
679     htd.setMaxFileSize(512 * 1024 * 1024);
680     modifyTableSync(admin, TEST_TABLE, htd);
681     assertTrue("Test table should have been modified",
682         cp.wasModifyTableCalled());
683 
684     // add a column family
685     admin.addColumn(TEST_TABLE, new HColumnDescriptor(TEST_FAMILY2));
686     assertTrue("New column family should have been added to test table",
687         cp.wasAddColumnCalled());
688 
689     // modify a column family
690     HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAMILY2);
691     hcd.setMaxVersions(25);
692     admin.modifyColumn(TEST_TABLE, hcd);
693     assertTrue("Second column family should be modified",
694         cp.wasModifyColumnCalled());
695 
696     // enable
697     assertFalse(cp.wasEnableTableCalled());
698     admin.enableTable(TEST_TABLE);
699     assertTrue(admin.isTableEnabled(TEST_TABLE));
700     assertTrue("Coprocessor should have been called on table enable",
701         cp.wasEnableTableCalled());
702 
703     // disable again
704     admin.disableTable(TEST_TABLE);
705     assertTrue(admin.isTableDisabled(TEST_TABLE));
706 
707     // delete column
708     assertFalse("No column family deleted yet", cp.wasDeleteColumnCalled());
709     admin.deleteColumn(TEST_TABLE, TEST_FAMILY2);
710     HTableDescriptor tableDesc = admin.getTableDescriptor(TEST_TABLE);
711     assertNull("'"+Bytes.toString(TEST_FAMILY2)+"' should have been removed",
712         tableDesc.getFamily(TEST_FAMILY2));
713     assertTrue("Coprocessor should have been called on column delete",
714         cp.wasDeleteColumnCalled());
715 
716     // delete table
717     assertFalse("No table deleted yet", cp.wasDeleteTableCalled());
718     admin.deleteTable(TEST_TABLE);
719     assertFalse("Test table should have been deleted",
720         admin.tableExists(TEST_TABLE));
721     assertTrue("Coprocessor should have been called on table delete",
722         cp.wasDeleteTableCalled());
723   }
724 
725   private void modifyTableSync(HBaseAdmin admin, byte[] tableName, HTableDescriptor htd)
726       throws IOException {
727     admin.modifyTable(tableName, htd);
728     //wait until modify table finishes
729     for (int t = 0; t < 100; t++) { //10 sec timeout
730       HTableDescriptor td = admin.getTableDescriptor(htd.getName());
731       if (td.equals(htd)) {
732         break;
733       }
734       Threads.sleep(100);
735     }
736   }
737 
738   @Test
739   public void testRegionTransitionOperations() throws Exception {
740     MiniHBaseCluster cluster = UTIL.getHBaseCluster();
741 
742     HMaster master = cluster.getMaster();
743     MasterCoprocessorHost host = master.getCoprocessorHost();
744     CPMasterObserver cp = (CPMasterObserver)host.findCoprocessor(
745         CPMasterObserver.class.getName());
746     cp.enableBypass(false);
747     cp.resetStates();
748 
749     HTable table = UTIL.createTable(TEST_TABLE, TEST_FAMILY);
750     UTIL.createMultiRegions(table, TEST_FAMILY);
751     UTIL.waitUntilAllRegionsAssigned(TEST_TABLE);
752     
753     NavigableMap<HRegionInfo, ServerName> regions = table.getRegionLocations();
754     Map.Entry<HRegionInfo, ServerName> firstGoodPair = null;
755     for (Map.Entry<HRegionInfo, ServerName> e: regions.entrySet()) {
756       if (e.getValue() != null) {
757         firstGoodPair = e;
758         break;
759       }
760     }
761     assertNotNull("Found a non-null entry", firstGoodPair);
762     LOG.info("Found " + firstGoodPair.toString());
763     // Try to force a move
764     Collection<ServerName> servers = master.getClusterStatus().getServers();
765     String destName = null;
766     String firstRegionHostnamePortStr = firstGoodPair.getValue().toString();
767     LOG.info("firstRegionHostnamePortStr=" + firstRegionHostnamePortStr);
768     boolean found = false;
769     // Find server that is NOT carrying the first region
770     for (ServerName info : servers) {
771       LOG.info("ServerName=" + info);
772       if (!firstRegionHostnamePortStr.equals(info.getHostAndPort())) {
773         destName = info.toString();
774         found = true;
775         break;
776       }
777     }
778     assertTrue("Found server", found);
779     LOG.info("Found " + destName);
780     master.move(firstGoodPair.getKey().getEncodedNameAsBytes(),
781       Bytes.toBytes(destName));
782     assertTrue("Coprocessor should have been called on region move",
783       cp.wasMoveCalled());
784 
785     // make sure balancer is on
786     master.balanceSwitch(true);
787     assertTrue("Coprocessor should have been called on balance switch",
788         cp.wasBalanceSwitchCalled());
789 
790     // force region rebalancing
791     master.balanceSwitch(false);
792     // move half the open regions from RS 0 to RS 1
793     HRegionServer rs = cluster.getRegionServer(0);
794     byte[] destRS = Bytes.toBytes(cluster.getRegionServer(1).getServerName().toString());
795     //Make sure no regions are in transition now
796     waitForRITtoBeZero(master);
797     List<HRegionInfo> openRegions = rs.getOnlineRegions();
798     int moveCnt = openRegions.size()/2;
799     for (int i=0; i<moveCnt; i++) {
800       HRegionInfo info = openRegions.get(i);
801       if (!info.isMetaTable()) {
802         master.move(openRegions.get(i).getEncodedNameAsBytes(), destRS);
803       }
804     }
805     //Make sure no regions are in transition now
806     waitForRITtoBeZero(master);
807     // now trigger a balance
808     master.balanceSwitch(true);
809     boolean balanceRun = master.balance();
810     assertTrue("Coprocessor should be called on region rebalancing",
811         cp.wasBalanceCalled());
812   }
813 
814   @Test
815   public void testSnapshotOperations() throws Exception {
816     MiniHBaseCluster cluster = UTIL.getHBaseCluster();
817     HMaster master = cluster.getMaster();
818     MasterCoprocessorHost host = master.getCoprocessorHost();
819     CPMasterObserver cp = (CPMasterObserver)host.findCoprocessor(
820         CPMasterObserver.class.getName());
821     cp.resetStates();
822 
823     // create a table
824     HTableDescriptor htd = new HTableDescriptor(TEST_TABLE);
825     htd.addFamily(new HColumnDescriptor(TEST_FAMILY));
826     HBaseAdmin admin = UTIL.getHBaseAdmin();
827 
828     // delete table if exists
829     if (admin.tableExists(TEST_TABLE)) {
830       UTIL.deleteTable(TEST_TABLE);
831     }
832 
833     admin.createTable(htd);
834     admin.disableTable(TEST_TABLE);
835     assertTrue(admin.isTableDisabled(TEST_TABLE));
836 
837     try {
838       // Test snapshot operation
839       assertFalse("Coprocessor should not have been called yet",
840         cp.wasSnapshotCalled());
841       admin.snapshot(TEST_SNAPSHOT, TEST_TABLE);
842       assertTrue("Coprocessor should have been called on snapshot",
843         cp.wasSnapshotCalled());
844 
845       // Test clone operation
846       admin.cloneSnapshot(TEST_SNAPSHOT, TEST_CLONE);
847       assertTrue("Coprocessor should have been called on snapshot clone",
848         cp.wasCloneSnapshotCalled());
849       assertFalse("Coprocessor restore should not have been called on snapshot clone",
850         cp.wasRestoreSnapshotCalled());
851       admin.disableTable(TEST_CLONE);
852       assertTrue(admin.isTableDisabled(TEST_TABLE));
853       admin.deleteTable(TEST_CLONE);
854 
855       // Test restore operation
856       cp.resetStates();
857       admin.restoreSnapshot(TEST_SNAPSHOT);
858       assertTrue("Coprocessor should have been called on snapshot restore",
859         cp.wasRestoreSnapshotCalled());
860       assertFalse("Coprocessor clone should not have been called on snapshot restore",
861         cp.wasCloneSnapshotCalled());
862 
863       admin.deleteSnapshot(TEST_SNAPSHOT);
864       assertTrue("Coprocessor should have been called on snapshot delete",
865         cp.wasDeleteSnapshotCalled());
866     } finally {
867       admin.deleteTable(TEST_TABLE);
868     }
869   }
870 
871   private void waitForRITtoBeZero(HMaster master) throws IOException {
872     // wait for assignments to finish
873     AssignmentManager mgr = master.getAssignmentManager();
874     Collection<AssignmentManager.RegionState> transRegions =
875         mgr.getRegionsInTransition().values();
876     for (AssignmentManager.RegionState state : transRegions) {
877       mgr.waitOnRegionToClearRegionsInTransition(state.getRegion());
878     }
879   }
880 
881   @org.junit.Rule
882   public org.apache.hadoop.hbase.ResourceCheckerJUnitRule cu =
883     new org.apache.hadoop.hbase.ResourceCheckerJUnitRule();
884 }
885