Index: hbase-common/src/main/java/org/apache/hadoop/hbase/MetaMutationAnnotation.java =================================================================== --- hbase-common/src/main/java/org/apache/hadoop/hbase/MetaMutationAnnotation.java (revision 0) +++ hbase-common/src/main/java/org/apache/hadoop/hbase/MetaMutationAnnotation.java (working copy) @@ -0,0 +1,37 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hadoop.hbase; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The field or the parameter to which this annotation can be applied only when its + * holds mutations for hbase:meta table. + */ +@Documented +@Target({ ElementType.LOCAL_VARIABLE, ElementType.PARAMETER }) +@Retention(RetentionPolicy.CLASS) +public @interface MetaMutationAnnotation { + +} Index: hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/BaseRegionServerObserver.java =================================================================== --- hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/BaseRegionServerObserver.java (revision 0) +++ hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/BaseRegionServerObserver.java (working copy) @@ -0,0 +1,71 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hadoop.hbase.coprocessor; + +import java.io.IOException; +import java.util.List; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; +import org.apache.hadoop.hbase.CoprocessorEnvironment; +import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.hadoop.hbase.client.Mutation; +import org.apache.hadoop.hbase.regionserver.HRegion; + +/** + * An abstract class that implements RegionServerObserver. + * By extending it, you can create your own region server observer without + * overriding all abstract methods of RegionServerObserver. + */ +@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.COPROC) +@InterfaceStability.Evolving +public class BaseRegionServerObserver implements RegionServerObserver { + + @Override + public void preStopRegionServer(ObserverContext env) + throws IOException { } + + @Override + public void start(CoprocessorEnvironment env) throws IOException { } + + @Override + public void stop(CoprocessorEnvironment env) throws IOException { } + + @Override + public void preMerge(ObserverContext ctx, HRegion regionA, + HRegion regionB) throws IOException { } + + @Override + public void postMerge(ObserverContext c, HRegion regionA, + HRegion regionB, HRegion mergedRegion) throws IOException { } + + @Override + public void preMergeCommit(ObserverContext ctx, + HRegion regionA, HRegion regionB, List metaEntries) throws IOException { } + + @Override + public void postMergeCommit(ObserverContext ctx, + HRegion regionA, HRegion regionB, HRegion mergedRegion) throws IOException { } + + @Override + public void preRollBackMerge(ObserverContext ctx, + HRegion regionA, HRegion regionB) throws IOException { } + + @Override + public void postRollBackMerge(ObserverContext ctx, + HRegion regionA, HRegion regionB) throws IOException { } + +} Index: hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/RegionServerObserver.java =================================================================== --- hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/RegionServerObserver.java (revision 1546389) +++ hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/RegionServerObserver.java (working copy) @@ -19,8 +19,12 @@ package org.apache.hadoop.hbase.coprocessor; import java.io.IOException; +import java.util.List; import org.apache.hadoop.hbase.Coprocessor; +import org.apache.hadoop.hbase.MetaMutationAnnotation; +import org.apache.hadoop.hbase.client.Mutation; +import org.apache.hadoop.hbase.regionserver.HRegion; public interface RegionServerObserver extends Coprocessor { @@ -32,4 +36,77 @@ void preStopRegionServer( final ObserverContext env) throws IOException; + + + /** + * Called before the regions merge. + * Call {@link org.apache.hadoop.hbase.coprocessor.ObserverContext#bypass()} to skip the merge. + * @throws IOException if an error occurred on the coprocessor + * @param ctx + * @param regionA + * @param regionB + * @throws IOException + */ + void preMerge(final ObserverContext ctx, + final HRegion regionA, final HRegion regionB) throws IOException; + + + /** + * called after the regions merge. + * @param c + * @param regionA + * @param regionB + * @param mergedRegion + * @throws IOException + */ + void postMerge(final ObserverContext c, + final HRegion regionA, final HRegion regionB, final HRegion mergedRegion) throws IOException; + + /** + * This will be called before PONR step as part of regions merge transaction. + * Calling {@link org.apache.hadoop.hbase.coprocessor.ObserverContext#bypass()} rollback + * the merge + * @param ctx + * @param regionA + * @param regionB + * @param metaEntries + * @throws IOException + */ + void preMergeCommit(final ObserverContext ctx, + final HRegion regionA, final HRegion regionB, @MetaMutationAnnotation List metaEntries) + throws IOException; + + /** + * This will be called after PONR step as part of regions merge transaction. + * Calling {@link org.apache.hadoop.hbase.coprocessor.ObserverContext#bypass()} has no effect + * in this hook. + * @param ctx + * @param regionA + * @param regionB + * @param mergedRegion + * @throws IOException + */ + void postMergeCommit(final ObserverContext ctx, + final HRegion regionA, final HRegion regionB, final HRegion mergedRegion) throws IOException; + + /** + * This will be called before the roll back of the regions merge is completed + * @param ctx + * @param regionA + * @param regionB + * @throws IOException + */ + void preRollBackMerge(final ObserverContext ctx, + final HRegion regionA, final HRegion regionB) throws IOException; + + /** + * This will be called after the roll back of the regions merge is completed + * @param ctx + * @param regionA + * @param regionB + * @throws IOException + */ + void postRollBackMerge(final ObserverContext ctx, + final HRegion regionA, final HRegion regionB) throws IOException; + } Index: hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionMergeTransaction.java =================================================================== --- hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionMergeTransaction.java (revision 1546389) +++ hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionMergeTransaction.java (working copy) @@ -34,11 +34,17 @@ import org.apache.hadoop.fs.Path; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.HRegionInfo; +import org.apache.hadoop.hbase.MetaMutationAnnotation; import org.apache.hadoop.hbase.RegionTransition; import org.apache.hadoop.hbase.Server; import org.apache.hadoop.hbase.ServerName; +import org.apache.hadoop.hbase.catalog.CatalogTracker; import org.apache.hadoop.hbase.catalog.MetaEditor; import org.apache.hadoop.hbase.catalog.MetaReader; +import org.apache.hadoop.hbase.client.Delete; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Mutation; +import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.executor.EventType; import org.apache.hadoop.hbase.regionserver.SplitTransaction.LoggingProgressable; import org.apache.hadoop.hbase.util.Bytes; @@ -142,6 +148,7 @@ private static IOException closedByOtherException = new IOException( "Failed to close region: already closed by another thread"); + private RegionServerCoprocessorHost rsCoprocessorHost = null; /** * Constructor * @param a region a to merge @@ -231,9 +238,20 @@ */ public HRegion execute(final Server server, final RegionServerServices services) throws IOException { + if (rsCoprocessorHost == null) { + rsCoprocessorHost = server != null ? ((HRegionServer) server).getCoprocessorHost() : null; + } HRegion mergedRegion = createMergedRegion(server, services); + if (rsCoprocessorHost != null) { + rsCoprocessorHost.postMergeCommit(this.region_a, this.region_b, mergedRegion); + } + return stepsAfterPONR(server, services, mergedRegion); + } + + public HRegion stepsAfterPONR(final Server server, final RegionServerServices services, + HRegion mergedRegion) throws IOException { openMergedRegion(server, services, mergedRegion); - transitionZKNode(server, services); + transitionZKNode(server, services, mergedRegion); return mergedRegion; } @@ -255,10 +273,95 @@ throw new IOException("Server is stopped or stopping"); } + if (rsCoprocessorHost != null) { + if (rsCoprocessorHost.preMerge(this.region_a, this.region_b)) { + throw new IOException("Coprocessor bypassing regions " + this.region_a + " " + + this.region_b + " merge."); + } + } // If true, no cluster to write meta edits to or to update znodes in. boolean testing = server == null ? true : server.getConfiguration() .getBoolean("hbase.testing.nocluster", false); + HRegion mergedRegion = stepsBeforePONR(server, services, testing); + + @MetaMutationAnnotation + List metaEntries = new ArrayList(); + if (rsCoprocessorHost != null) { + if (rsCoprocessorHost.preMergeCommit(this.region_a, this.region_b, metaEntries)) { + throw new IOException("Coprocessor bypassing regions " + this.region_a + " " + + this.region_b + " merge."); + } + try { + for (Mutation p : metaEntries) { + HRegionInfo.parseRegionName(p.getRow()); + } + } catch (IOException e) { + LOG.error("Row key of mutation from coprocessor is not parsable as region name." + + "Mutations from coprocessor should only be for hbase:meta table.", e); + throw e; + } + + } + // This is the point of no return. Similar with SplitTransaction. + // IF we reach the PONR then subsequent failures need to crash out this + // regionserver + this.journal.add(JournalEntry.PONR); + + // Add merged region and delete region_a and region_b + // as an atomic update. See HBASE-7721. This update to hbase:meta makes the region + // will determine whether the region is merged or not in case of failures. + // If it is successful, master will roll-forward, if not, master will + // rollback + if (!testing) { + if (metaEntries.isEmpty()) { + MetaEditor.mergeRegions(server.getCatalogTracker(), mergedRegion.getRegionInfo(), region_a + .getRegionInfo(), region_b.getRegionInfo(), server.getServerName()); + } else { + mergeRegionsAndPutMetaEntries(server.getCatalogTracker(), mergedRegion.getRegionInfo(), region_a + .getRegionInfo(), region_b.getRegionInfo(), server.getServerName(), metaEntries); + } + } + return mergedRegion; + } + + private void mergeRegionsAndPutMetaEntries(CatalogTracker catalogTracker, + HRegionInfo mergedRegion, HRegionInfo regionA, HRegionInfo regionB, ServerName serverName, + List metaEntries) throws IOException { + prepareMutationsForMerge(mergedRegion, regionA, regionB, serverName, metaEntries); + MetaEditor.mutateMetaTable(catalogTracker, metaEntries); + } + + public void prepareMutationsForMerge(HRegionInfo mergedRegion, HRegionInfo regionA, + HRegionInfo regionB, ServerName serverName, List mutations) throws IOException { + HRegionInfo copyOfMerged = new HRegionInfo(mergedRegion); + + // Put for parent + Put putOfMerged = MetaEditor.makePutFromRegionInfo(copyOfMerged); + putOfMerged.add(HConstants.CATALOG_FAMILY, HConstants.MERGEA_QUALIFIER, regionA.toByteArray()); + putOfMerged.add(HConstants.CATALOG_FAMILY, HConstants.MERGEB_QUALIFIER, regionB.toByteArray()); + mutations.add(putOfMerged); + // Deletes for merging regions + Delete deleteA = MetaEditor.makeDeleteFromRegionInfo(regionA); + Delete deleteB = MetaEditor.makeDeleteFromRegionInfo(regionB); + mutations.add(deleteA); + mutations.add(deleteB); + // The merged is a new region, openSeqNum = 1 is fine. + addLocation(putOfMerged, serverName, 1); + } + + public Put addLocation(final Put p, final ServerName sn, long openSeqNum) { + p.add(HConstants.CATALOG_FAMILY, HConstants.SERVER_QUALIFIER, + Bytes.toBytes(sn.getHostAndPort())); + p.add(HConstants.CATALOG_FAMILY, HConstants.STARTCODE_QUALIFIER, + Bytes.toBytes(sn.getStartcode())); + p.add(HConstants.CATALOG_FAMILY, HConstants.SEQNUM_QUALIFIER, + Bytes.toBytes(openSeqNum)); + return p; + } + + public HRegion stepsBeforePONR(final Server server, final RegionServerServices services, + boolean testing) throws IOException { // Set ephemeral MERGING znode up in zk. Mocked servers sometimes don't // have zookeeper so don't do zk stuff if server or zookeeper is null if (server != null && server.getZooKeeper() != null) { @@ -316,23 +419,6 @@ this.journal.add(JournalEntry.STARTED_MERGED_REGION_CREATION); HRegion mergedRegion = createMergedRegionFromMerges(this.region_a, this.region_b, this.mergedRegionInfo); - - - // This is the point of no return. Similar with SplitTransaction. - // IF we reach the PONR then subsequent failures need to crash out this - // regionserver - this.journal.add(JournalEntry.PONR); - - // Add merged region and delete region_a and region_b - // as an atomic update. See HBASE-7721. This update to hbase:meta makes the region - // will determine whether the region is merged or not in case of failures. - // If it is successful, master will roll-forward, if not, master will - // rollback - if (!testing) { - MetaEditor.mergeRegions(server.getCatalogTracker(), - mergedRegion.getRegionInfo(), region_a.getRegionInfo(), - region_b.getRegionInfo(), server.getServerName()); - } return mergedRegion; } @@ -478,7 +564,7 @@ * @throws IOException If thrown, transaction failed. Call * {@link #rollback(Server, RegionServerServices)} */ - void transitionZKNode(final Server server, final RegionServerServices services) + void transitionZKNode(final Server server, final RegionServerServices services, HRegion mergedRegion) throws IOException { if (server == null || server.getZooKeeper() == null) { return; @@ -519,6 +605,10 @@ + mergedRegionInfo.getEncodedName(), e); } + if(rsCoprocessorHost != null){ + rsCoprocessorHost.postMerge(this.region_a, this.region_b, mergedRegion); + } + // Leaving here, the mergedir with its dross will be in place but since the // merge was successful, just leave it; it'll be cleaned when region_a is // cleaned up by CatalogJanitor on master @@ -640,6 +730,11 @@ public boolean rollback(final Server server, final RegionServerServices services) throws IOException { assert this.mergedRegionInfo != null; + // Coprocessor callback + if (rsCoprocessorHost != null) { + rsCoprocessorHost.preRollBackMerge(this.region_a,this.region_b); + } + boolean result = true; ListIterator iterator = this.journal .listIterator(this.journal.size()); @@ -709,6 +804,11 @@ throw new RuntimeException("Unhandled journal entry: " + je); } } + // Coprocessor callback + if (rsCoprocessorHost != null) { + rsCoprocessorHost.postRollBackMerge(this.region_a,this.region_b); + } + return result; } Index: hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionServerCoprocessorHost.java =================================================================== --- hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionServerCoprocessorHost.java (revision 1546389) +++ hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionServerCoprocessorHost.java (working copy) @@ -20,10 +20,13 @@ import java.io.IOException; import java.util.Comparator; +import java.util.List; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.Coprocessor; import org.apache.hadoop.hbase.CoprocessorEnvironment; +import org.apache.hadoop.hbase.MetaMutationAnnotation; +import org.apache.hadoop.hbase.client.Mutation; import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; import org.apache.hadoop.hbase.coprocessor.ObserverContext; import org.apache.hadoop.hbase.coprocessor.RegionServerCoprocessorEnvironment; @@ -62,6 +65,119 @@ } } + public boolean preMerge(final HRegion regionA,final HRegion regionB) + throws IOException { + boolean bypass = false; + ObserverContext ctx = null; + for (RegionServerEnvironment env : coprocessors) { + if (env.getInstance() instanceof RegionServerObserver) { + ctx = ObserverContext.createAndPrepare(env, ctx); + try { + ((RegionServerObserver) env.getInstance()).preMerge(ctx, regionA, regionB); + } catch (Throwable e) { + handleCoprocessorThrowable(env, e); + } + bypass |= ctx.shouldBypass(); + if (ctx.shouldComplete()) { + break; + } + } + } + return bypass; + } + + public void postMerge(final HRegion regionA, final HRegion regionB, final HRegion mergedRegion) + throws IOException { + ObserverContext ctx = null; + for (RegionServerEnvironment env : coprocessors) { + if (env.getInstance() instanceof RegionServerObserver) { + ctx = ObserverContext.createAndPrepare(env, ctx); + try { + ((RegionServerObserver) env.getInstance()).postMerge(ctx, regionA, regionB, mergedRegion); + } catch (Throwable e) { + handleCoprocessorThrowable(env, e); + } + if (ctx.shouldComplete()) { + break; + } + } + } + } + + public boolean preMergeCommit(final HRegion regionA, final HRegion regionB, + final @MetaMutationAnnotation List metaEntries) throws IOException { + boolean bypass = false; + ObserverContext ctx = null; + for (RegionServerEnvironment env : coprocessors) { + if (env.getInstance() instanceof RegionServerObserver) { + ctx = ObserverContext.createAndPrepare(env, ctx); + try { + ((RegionServerObserver) env.getInstance()).preMergeCommit(ctx, regionA, regionB, + metaEntries); + } catch (Throwable e) { + handleCoprocessorThrowable(env, e); + } + bypass |= ctx.shouldBypass(); + if (ctx.shouldComplete()) { + break; + } + } + } + return bypass; + } + + public void postMergeCommit(final HRegion regionA, final HRegion regionB, + final HRegion mergedRegion) throws IOException { + ObserverContext ctx = null; + for (RegionServerEnvironment env : coprocessors) { + if (env.getInstance() instanceof RegionServerObserver) { + ctx = ObserverContext.createAndPrepare(env, ctx); + try { + ((RegionServerObserver) env.getInstance()).postMergeCommit(ctx, regionA, regionB, mergedRegion); + } catch (Throwable e) { + handleCoprocessorThrowable(env, e); + } + if (ctx.shouldComplete()) { + break; + } + } + } + } + + public void preRollBackMerge(final HRegion regionA, final HRegion regionB) throws IOException { + ObserverContext ctx = null; + for (RegionServerEnvironment env : coprocessors) { + if (env.getInstance() instanceof RegionServerObserver) { + ctx = ObserverContext.createAndPrepare(env, ctx); + try { + ((RegionServerObserver) env.getInstance()).preRollBackMerge(ctx, regionA, regionB); + } catch (Throwable e) { + handleCoprocessorThrowable(env, e); + } + if (ctx.shouldComplete()) { + break; + } + } + } + } + + public void postRollBackMerge(final HRegion regionA, final HRegion regionB) throws IOException { + ObserverContext ctx = null; + for (RegionServerEnvironment env : coprocessors) { + if (env.getInstance() instanceof RegionServerObserver) { + ctx = ObserverContext.createAndPrepare(env, ctx); + try { + ((RegionServerObserver) env.getInstance()).postRollBackMerge(ctx, regionA, regionB); + } catch (Throwable e) { + handleCoprocessorThrowable(env, e); + } + if (ctx.shouldComplete()) { + break; + } + } + } + } + /** * Coprocessor environment extension providing access to region server * related services. Index: hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java =================================================================== --- hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java (revision 1546389) +++ hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java (working copy) @@ -1686,4 +1686,33 @@ List descriptors) throws IOException { } + + @Override + public void preMerge(ObserverContext ctx, HRegion regionA, + HRegion regionB) throws IOException { + requirePermission("mergeRegions", regionA.getTableDesc().getTableName(), null, null, + Action.ADMIN); + } + + @Override + public void postMerge(ObserverContext c, HRegion regionA, + HRegion regionB, HRegion mergedRegion) throws IOException { } + + @Override + public void preMergeCommit(ObserverContext ctx, + HRegion regionA, HRegion regionB, List metaEntries) throws IOException { } + + @Override + public void postMergeCommit(ObserverContext ctx, + HRegion regionA, HRegion regionB, HRegion mergedRegion) throws IOException { } + + @Override + public void preRollBackMerge(ObserverContext ctx, + HRegion regionA, HRegion regionB) throws IOException { } + + @Override + public void postRollBackMerge(ObserverContext ctx, + HRegion regionA, HRegion regionB) throws IOException { } + + } Index: hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionMergeTransaction.java =================================================================== --- hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionMergeTransaction.java (revision 1546389) +++ hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionMergeTransaction.java (working copy) @@ -206,7 +206,7 @@ } @Test - public void testWholesomeMerge() throws IOException { + public void testWholesomeMerge() throws IOException, InterruptedException { final int rowCountOfRegionA = loadRegion(this.region_a, CF, true); final int rowCountOfRegionB = loadRegion(this.region_b, CF, true); assertTrue(rowCountOfRegionA > 0 && rowCountOfRegionB > 0); @@ -217,9 +217,8 @@ RegionMergeTransaction mt = prepareOnGoodRegions(); // Run the execute. Look at what it returns. - Server mockServer = Mockito.mock(Server.class); - when(mockServer.getConfiguration()) - .thenReturn(TEST_UTIL.getConfiguration()); + TEST_UTIL.getConfiguration().setInt(HConstants.REGIONSERVER_PORT, 0); + Server mockServer = new HRegionServer(TEST_UTIL.getConfiguration()); HRegion mergedRegion = mt.execute(mockServer, null); // Do some assertions about execution. assertTrue(this.fs.exists(mt.getMergesDir())); @@ -249,7 +248,7 @@ } @Test - public void testRollback() throws IOException { + public void testRollback() throws IOException, InterruptedException { final int rowCountOfRegionA = loadRegion(this.region_a, CF, true); final int rowCountOfRegionB = loadRegion(this.region_b, CF, true); assertTrue(rowCountOfRegionA > 0 && rowCountOfRegionB > 0); @@ -265,9 +264,8 @@ // Run the execute. Look at what it returns. boolean expectedException = false; - Server mockServer = Mockito.mock(Server.class); - when(mockServer.getConfiguration()) - .thenReturn(TEST_UTIL.getConfiguration()); + TEST_UTIL.getConfiguration().setInt(HConstants.REGIONSERVER_PORT, 0); + Server mockServer = new HRegionServer(TEST_UTIL.getConfiguration()); try { mt.execute(mockServer, null); } catch (MockedFailedMergedRegionCreation e) { @@ -308,7 +306,7 @@ } @Test - public void testFailAfterPONR() throws IOException, KeeperException { + public void testFailAfterPONR() throws IOException, KeeperException, InterruptedException { final int rowCountOfRegionA = loadRegion(this.region_a, CF, true); final int rowCountOfRegionB = loadRegion(this.region_b, CF, true); assertTrue(rowCountOfRegionA > 0 && rowCountOfRegionB > 0); @@ -325,9 +323,8 @@ // Run the execute. Look at what it returns. boolean expectedException = false; - Server mockServer = Mockito.mock(Server.class); - when(mockServer.getConfiguration()) - .thenReturn(TEST_UTIL.getConfiguration()); + TEST_UTIL.getConfiguration().setInt(HConstants.REGIONSERVER_PORT, 0); + Server mockServer = new HRegionServer(TEST_UTIL.getConfiguration()); try { mt.execute(mockServer, null); } catch (MockedFailedMergedRegionOpen e) { Index: hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java =================================================================== --- hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java (revision 1546389) +++ hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java (working copy) @@ -27,6 +27,7 @@ import java.lang.reflect.UndeclaredThrowableException; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.NavigableMap; @@ -57,6 +58,7 @@ import org.apache.hadoop.hbase.client.HBaseAdmin; import org.apache.hadoop.hbase.client.HTable; import org.apache.hadoop.hbase.client.Increment; +import org.apache.hadoop.hbase.client.Mutation; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.ResultScanner; @@ -192,7 +194,7 @@ HTableDescriptor htd = new HTableDescriptor(TEST_TABLE.getTableName()); htd.addFamily(new HColumnDescriptor(TEST_FAMILY)); htd.setOwner(USER_OWNER); - admin.createTable(htd); + admin.createTable(htd, new byte[][] { Bytes.toBytes("s") }); TEST_UTIL.waitTableEnabled(TEST_TABLE.getTableName().getName()); HRegion region = TEST_UTIL.getHBaseCluster().getRegions(TEST_TABLE.getTableName()).get(0); @@ -670,7 +672,25 @@ verifyDenied(action, USER_CREATE, USER_RW, USER_RO, USER_NONE); } + @Test + public void testMergeRegions() throws Exception { + + final List regions = TEST_UTIL.getHBaseCluster().findRegionsForTable(TEST_TABLE.getTableName()); + + PrivilegedExceptionAction action = new PrivilegedExceptionAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preMerge( + ObserverContext.createAndPrepare(RSCP_ENV, null), + regions.get(0),regions.get(1)); + return null; + } + }; + verifyAllowed(action, SUPERUSER, USER_ADMIN, USER_OWNER); + verifyDenied(action, USER_CREATE, USER_RW, USER_RO, USER_NONE); + } + @Test public void testFlush() throws Exception { AccessTestAction action = new AccessTestAction() {