/*
* Copyright 2011 The Apache Software Foundation
*
* 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.coprocessor;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.master.AssignmentManager;
import org.apache.hadoop.hbase.master.HMaster;
import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
import org.apache.hadoop.hbase.master.snapshot.SnapshotManager;
import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
import org.apache.hadoop.hbase.regionserver.HRegionServer;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Threads;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
/**
* Tests invocation of the {@link org.apache.hadoop.hbase.coprocessor.MasterObserver}
* interface hooks at all appropriate times during normal HMaster operations.
*/
@Category(MediumTests.class)
public class TestMasterObserver {
private static final Log LOG = LogFactory.getLog(TestMasterObserver.class);
public static class CPMasterObserver implements MasterObserver {
private boolean bypass = false;
private boolean preCreateTableCalled;
private boolean postCreateTableCalled;
private boolean preDeleteTableCalled;
private boolean postDeleteTableCalled;
private boolean preModifyTableCalled;
private boolean postModifyTableCalled;
private boolean preAddColumnCalled;
private boolean postAddColumnCalled;
private boolean preModifyColumnCalled;
private boolean postModifyColumnCalled;
private boolean preDeleteColumnCalled;
private boolean postDeleteColumnCalled;
private boolean preEnableTableCalled;
private boolean postEnableTableCalled;
private boolean preDisableTableCalled;
private boolean postDisableTableCalled;
private boolean preMoveCalled;
private boolean postMoveCalled;
private boolean preAssignCalled;
private boolean postAssignCalled;
private boolean preUnassignCalled;
private boolean postUnassignCalled;
private boolean preBalanceCalled;
private boolean postBalanceCalled;
private boolean preBalanceSwitchCalled;
private boolean postBalanceSwitchCalled;
private boolean preShutdownCalled;
private boolean preStopMasterCalled;
private boolean postStartMasterCalled;
private boolean startCalled;
private boolean stopCalled;
private boolean preSnapshotCalled;
private boolean postSnapshotCalled;
private boolean preCloneSnapshotCalled;
private boolean postCloneSnapshotCalled;
private boolean preRestoreSnapshotCalled;
private boolean postRestoreSnapshotCalled;
private boolean preDeleteSnapshotCalled;
private boolean postDeleteSnapshotCalled;
private boolean preGetTableDescriptorsCalled;
private boolean postGetTableDescriptorsCalled;
public void enableBypass(boolean bypass) {
this.bypass = bypass;
}
public void resetStates() {
preCreateTableCalled = false;
postCreateTableCalled = false;
preDeleteTableCalled = false;
postDeleteTableCalled = false;
preModifyTableCalled = false;
postModifyTableCalled = false;
preAddColumnCalled = false;
postAddColumnCalled = false;
preModifyColumnCalled = false;
postModifyColumnCalled = false;
preDeleteColumnCalled = false;
postDeleteColumnCalled = false;
preEnableTableCalled = false;
postEnableTableCalled = false;
preDisableTableCalled = false;
postDisableTableCalled = false;
preMoveCalled= false;
postMoveCalled = false;
preAssignCalled = false;
postAssignCalled = false;
preUnassignCalled = false;
postUnassignCalled = false;
preBalanceCalled = false;
postBalanceCalled = false;
preBalanceSwitchCalled = false;
postBalanceSwitchCalled = false;
preSnapshotCalled = false;
postSnapshotCalled = false;
preCloneSnapshotCalled = false;
postCloneSnapshotCalled = false;
preRestoreSnapshotCalled = false;
postRestoreSnapshotCalled = false;
preDeleteSnapshotCalled = false;
postDeleteSnapshotCalled = false;
preGetTableDescriptorsCalled = false;
postGetTableDescriptorsCalled = false;
}
@Override
public void preCreateTable(ObserverContext<MasterCoprocessorEnvironment> env,
HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
if (bypass) {
env.bypass();
}
preCreateTableCalled = true;
}
@Override
public void postCreateTable(ObserverContext<MasterCoprocessorEnvironment> env,
HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
postCreateTableCalled = true;
}
public boolean wasCreateTableCalled() {
return preCreateTableCalled && postCreateTableCalled;
}
public boolean preCreateTableCalledOnly() {
return preCreateTableCalled && !postCreateTableCalled;
}
@Override
public void preDeleteTable(ObserverContext<MasterCoprocessorEnvironment> env,
byte[] tableName) throws IOException {
if (bypass) {
env.bypass();
}
preDeleteTableCalled = true;
}
@Override
public void postDeleteTable(ObserverContext<MasterCoprocessorEnvironment> env,
byte[] tableName) throws IOException {
postDeleteTableCalled = true;
}
public boolean wasDeleteTableCalled() {
return preDeleteTableCalled && postDeleteTableCalled;
}
public boolean preDeleteTableCalledOnly() {
return preDeleteTableCalled && !postDeleteTableCalled;
}
@Override
public void preModifyTable(ObserverContext<MasterCoprocessorEnvironment> env,
byte[] tableName, HTableDescriptor htd) throws IOException {
if (bypass) {
env.bypass();
}
preModifyTableCalled = true;
}
@Override
public void postModifyTable(ObserverContext<MasterCoprocessorEnvironment> env,
byte[] tableName, HTableDescriptor htd) throws IOException {
postModifyTableCalled = true;
}
public boolean wasModifyTableCalled() {
return preModifyTableCalled && postModifyTableCalled;
}
public boolean preModifyTableCalledOnly() {
return preModifyTableCalled && !postModifyTableCalled;
}
@Override
public void preAddColumn(ObserverContext<MasterCoprocessorEnvironment> env,
byte[] tableName, HColumnDescriptor column) throws IOException {
if (bypass) {
env.bypass();
}
preAddColumnCalled = true;
}
@Override
public void postAddColumn(ObserverContext<MasterCoprocessorEnvironment> env,
byte[] tableName, HColumnDescriptor column) throws IOException {
postAddColumnCalled = true;
}
public boolean wasAddColumnCalled() {
return preAddColumnCalled && postAddColumnCalled;
}
public boolean preAddColumnCalledOnly() {
return preAddColumnCalled && !postAddColumnCalled;
}
@Override
public void preModifyColumn(ObserverContext<MasterCoprocessorEnvironment> env,
byte[] tableName, HColumnDescriptor descriptor) throws IOException {
if (bypass) {
env.bypass();
}
preModifyColumnCalled = true;
}
@Override
public void postModifyColumn(ObserverContext<MasterCoprocessorEnvironment> env,
byte[] tableName, HColumnDescriptor descriptor) throws IOException {
postModifyColumnCalled = true;
}
public boolean wasModifyColumnCalled() {
return preModifyColumnCalled && postModifyColumnCalled;
}
public boolean preModifyColumnCalledOnly() {
return preModifyColumnCalled && !postModifyColumnCalled;
}
@Override
public void preDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> env,
byte[] tableName, byte[] c) throws IOException {
if (bypass) {
env.bypass();
}
preDeleteColumnCalled = true;
}
@Override
public void postDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> env,
byte[] tableName, byte[] c) throws IOException {
postDeleteColumnCalled = true;
}
public boolean wasDeleteColumnCalled() {
return preDeleteColumnCalled && postDeleteColumnCalled;
}
public boolean preDeleteColumnCalledOnly() {
return preDeleteColumnCalled && !postDeleteColumnCalled;
}
@Override
public void preEnableTable(ObserverContext<MasterCoprocessorEnvironment> env,
byte[] tableName) throws IOException {
if (bypass) {
env.bypass();
}
preEnableTableCalled = true;
}
@Override
public void postEnableTable(ObserverContext<MasterCoprocessorEnvironment> env,
byte[] tableName) throws IOException {
postEnableTableCalled = true;
}
public boolean wasEnableTableCalled() {
return preEnableTableCalled && postEnableTableCalled;
}
public boolean preEnableTableCalledOnly() {
return preEnableTableCalled && !postEnableTableCalled;
}
@Override
public void preDisableTable(ObserverContext<MasterCoprocessorEnvironment> env,
byte[] tableName) throws IOException {
if (bypass) {
env.bypass();
}
preDisableTableCalled = true;
}
@Override
public void postDisableTable(ObserverContext<MasterCoprocessorEnvironment> env,
byte[] tableName) throws IOException {
postDisableTableCalled = true;
}
public boolean wasDisableTableCalled() {
return preDisableTableCalled && postDisableTableCalled;
}
public boolean preDisableTableCalledOnly() {
return preDisableTableCalled && !postDisableTableCalled;
}
@Override
public void preMove(ObserverContext<MasterCoprocessorEnvironment> env,
HRegionInfo region, ServerName srcServer, ServerName destServer)
throws IOException {
if (bypass) {
env.bypass();
}
preMoveCalled = true;
}
@Override
public void postMove(ObserverContext<MasterCoprocessorEnvironment> env, HRegionInfo region,
ServerName srcServer, ServerName destServer)
throws IOException {
postMoveCalled = true;
}
public boolean wasMoveCalled() {
return preMoveCalled && postMoveCalled;
}
public boolean preMoveCalledOnly() {
return preMoveCalled && !postMoveCalled;
}
@Override
public void preAssign(ObserverContext<MasterCoprocessorEnvironment> env,
final HRegionInfo regionInfo) throws IOException {
if (bypass) {
env.bypass();
}
preAssignCalled = true;
}
@Override
public void postAssign(ObserverContext<MasterCoprocessorEnvironment> env,
final HRegionInfo regionInfo) throws IOException {
postAssignCalled = true;
}
public boolean wasAssignCalled() {
return preAssignCalled && postAssignCalled;
}
public boolean preAssignCalledOnly() {
return preAssignCalled && !postAssignCalled;
}
@Override
public void preUnassign(ObserverContext<MasterCoprocessorEnvironment> env,
final HRegionInfo regionInfo, final boolean force) throws IOException {
if (bypass) {
env.bypass();
}
preUnassignCalled = true;
}
@Override
public void postUnassign(ObserverContext<MasterCoprocessorEnvironment> env,
final HRegionInfo regionInfo, final boolean force) throws IOException {
postUnassignCalled = true;
}
public boolean wasUnassignCalled() {
return preUnassignCalled && postUnassignCalled;
}
public boolean preUnassignCalledOnly() {
return preUnassignCalled && !postUnassignCalled;
}
@Override
public void preBalance(ObserverContext<MasterCoprocessorEnvironment> env)
throws IOException {
if (bypass) {
env.bypass();
}
preBalanceCalled = true;
}
@Override
public void postBalance(ObserverContext<MasterCoprocessorEnvironment> env)
throws IOException {
postBalanceCalled = true;
}
public boolean wasBalanceCalled() {
return preBalanceCalled && postBalanceCalled;
}
public boolean preBalanceCalledOnly() {
return preBalanceCalled && !postBalanceCalled;
}
@Override
public boolean preBalanceSwitch(ObserverContext<MasterCoprocessorEnvironment> env, boolean b)
throws IOException {
if (bypass) {
env.bypass();
}
preBalanceSwitchCalled = true;
return b;
}
@Override
public void postBalanceSwitch(ObserverContext<MasterCoprocessorEnvironment> env,
boolean oldValue, boolean newValue) throws IOException {
postBalanceSwitchCalled = true;
}
public boolean wasBalanceSwitchCalled() {
return preBalanceSwitchCalled && postBalanceSwitchCalled;
}
public boolean preBalanceSwitchCalledOnly() {
return preBalanceSwitchCalled && !postBalanceSwitchCalled;
}
@Override
public void preShutdown(ObserverContext<MasterCoprocessorEnvironment> env)
throws IOException {
preShutdownCalled = true;
}
@Override
public void preStopMaster(ObserverContext<MasterCoprocessorEnvironment> env)
throws IOException {
preStopMasterCalled = true;
}
@Override
public void postStartMaster(ObserverContext<MasterCoprocessorEnvironment> ctx)
throws IOException {
postStartMasterCalled = true;
}
public boolean wasStartMasterCalled() {
return postStartMasterCalled;
}
@Override
public void start(CoprocessorEnvironment env) throws IOException {
startCalled = true;
}
@Override
public void stop(CoprocessorEnvironment env) throws IOException {
stopCalled = true;
}
public boolean wasStarted() { return startCalled; }
public boolean wasStopped() { return stopCalled; }
@Override
public void preSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
throws IOException {
preSnapshotCalled = true;
}
@Override
public void postSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
throws IOException {
postSnapshotCalled = true;
}
public boolean wasSnapshotCalled() {
return preSnapshotCalled && postSnapshotCalled;
}
@Override
public void preCloneSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
throws IOException {
preCloneSnapshotCalled = true;
}
@Override
public void postCloneSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
throws IOException {
postCloneSnapshotCalled = true;
}
public boolean wasCloneSnapshotCalled() {
return preCloneSnapshotCalled && postCloneSnapshotCalled;
}
@Override
public void preRestoreSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
throws IOException {
preRestoreSnapshotCalled = true;
}
@Override
public void postRestoreSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
throws IOException {
postRestoreSnapshotCalled = true;
}
public boolean wasRestoreSnapshotCalled() {
return preRestoreSnapshotCalled && postRestoreSnapshotCalled;
}
@Override
public void preDeleteSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
final SnapshotDescription snapshot) throws IOException {
preDeleteSnapshotCalled = true;
}
@Override
public void postDeleteSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
final SnapshotDescription snapshot) throws IOException {
postDeleteSnapshotCalled = true;
}
public boolean wasDeleteSnapshotCalled() {
return preDeleteSnapshotCalled && postDeleteSnapshotCalled;
}
@Override
public void preGetTableDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx,
List<String> tableNamesList, List<HTableDescriptor> descriptors) throws IOException {
preGetTableDescriptorsCalled = true;
}
@Override
public void postGetTableDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx,
List<HTableDescriptor> descriptors) throws IOException {
postGetTableDescriptorsCalled = true;
}
public boolean wasGetTableDescriptorsCalled() {
return preGetTableDescriptorsCalled && postGetTableDescriptorsCalled;
}
}
private static HBaseTestingUtility UTIL = new HBaseTestingUtility();
private static byte[] TEST_SNAPSHOT = Bytes.toBytes("observed_snapshot");
private static byte[] TEST_TABLE = Bytes.toBytes("observed_table");
private static byte[] TEST_CLONE = Bytes.toBytes("observed_clone");
private static byte[] TEST_FAMILY = Bytes.toBytes("fam1");
private static byte[] TEST_FAMILY2 = Bytes.toBytes("fam2");
@BeforeClass
public static void setupBeforeClass() throws Exception {
Configuration conf = UTIL.getConfiguration();
conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
CPMasterObserver.class.getName());
// Enable snapshot
conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
// We need more than one data server on this test
UTIL.startMiniCluster(2);
}
@AfterClass
public static void tearDownAfterClass() throws Exception {
UTIL.shutdownMiniCluster();
}
@Test
public void testStarted() throws Exception {
MiniHBaseCluster cluster = UTIL.getHBaseCluster();
HMaster master = cluster.getMaster();
assertTrue("Master should be active", master.isActiveMaster());
MasterCoprocessorHost host = master.getCoprocessorHost();
assertNotNull("CoprocessorHost should not be null", host);
CPMasterObserver cp = (CPMasterObserver)host.findCoprocessor(
CPMasterObserver.class.getName());
assertNotNull("CPMasterObserver coprocessor not found or not installed!", cp);
// check basic lifecycle
assertTrue("MasterObserver should have been started", cp.wasStarted());
assertTrue("postStartMaster() hook should have been called",
cp.wasStartMasterCalled());
}
@Test
public void testTableOperations() throws Exception {
MiniHBaseCluster cluster = UTIL.getHBaseCluster();
HMaster master = cluster.getMaster();
MasterCoprocessorHost host = master.getCoprocessorHost();
CPMasterObserver cp = (CPMasterObserver)host.findCoprocessor(
CPMasterObserver.class.getName());
cp.enableBypass(true);
cp.resetStates();
assertFalse("No table created yet", cp.wasCreateTableCalled());
// create a table