HBASE-12161 Add support for grant/revoke on namespaces in AccessControlClient (Srikanth Srungarapu)

This commit is contained in:
Matteo Bertozzi 2014-10-18 21:10:23 +01:00
parent 5c92bded9d
commit 49056295ef
6 changed files with 247 additions and 116 deletions

View File

@ -20,40 +20,26 @@ package org.apache.hadoop.hbase.security.access;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.apache.hadoop.hbase.classification.InterfaceAudience;
import org.apache.hadoop.hbase.classification.InterfaceStability;
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseIOException;
import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.MasterNotRunningException; import org.apache.hadoop.hbase.MasterNotRunningException;
import org.apache.hadoop.hbase.NamespaceDescriptor; import org.apache.hadoop.hbase.NamespaceDescriptor;
import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.ZooKeeperConnectionException; import org.apache.hadoop.hbase.ZooKeeperConnectionException;
import org.apache.hadoop.hbase.classification.InterfaceAudience;
import org.apache.hadoop.hbase.classification.InterfaceStability;
import org.apache.hadoop.hbase.client.Admin; import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.HBaseAdmin; import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.HTable; import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Table; import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.client.coprocessor.Batch;
import org.apache.hadoop.hbase.ipc.BlockingRpcCallback;
import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel; import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
import org.apache.hadoop.hbase.ipc.ServerRpcController;
import org.apache.hadoop.hbase.protobuf.ProtobufUtil; import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos; import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos;
import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService;
import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService.BlockingInterface; import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService.BlockingInterface;
import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.GrantRequest;
import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.GrantResponse;
import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.RevokeRequest;
import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.RevokeResponse;
import org.apache.hadoop.hbase.util.ByteStringer;
import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.security.authorize.AccessControlList;
import com.google.protobuf.ByteString;
/** /**
* Utility client for doing access control admin operations. * Utility client for doing access control admin operations.
@ -61,6 +47,22 @@ import com.google.protobuf.ByteString;
@InterfaceAudience.Public @InterfaceAudience.Public
@InterfaceStability.Evolving @InterfaceStability.Evolving
public class AccessControlClient { public class AccessControlClient {
private static HTable getAclTable(Configuration conf) throws IOException {
TableName aclTableName =
TableName.valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR,
AccessControlConstants.OP_ATTRIBUTE_ACL);
return new HTable(conf, aclTableName.getName());
}
private static BlockingInterface getAccessControlServiceStub(HTable ht)
throws IOException {
CoprocessorRpcChannel service = ht.coprocessorService(HConstants.EMPTY_START_ROW);
BlockingInterface protocol =
AccessControlProtos.AccessControlService.newBlockingStub(service);
return protocol;
}
/** /**
* Grants permission on the specified table for the specified user * Grants permission on the specified table for the specified user
* @param conf * @param conf
@ -69,56 +71,37 @@ public class AccessControlClient {
* @param family * @param family
* @param qual * @param qual
* @param actions * @param actions
* @return GrantResponse
* @throws Throwable * @throws Throwable
*/ */
public static GrantResponse grant(Configuration conf, final TableName tableName, public static void grant(Configuration conf, final TableName tableName,
final String userName, final byte[] family, final byte[] qual, final String userName, final byte[] family, final byte[] qual,
final AccessControlProtos.Permission.Action... actions) throws Throwable { final Permission.Action... actions) throws Throwable {
Table ht = null; HTable ht = null;
try { try {
TableName aclTableName = ht = getAclTable(conf);
TableName.valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "acl"); ProtobufUtil.grant(getAccessControlServiceStub(ht), userName, tableName, family, qual,
ht = new HTable(conf, aclTableName); actions);
Batch.Call<AccessControlService, GrantResponse> callable = } finally {
new Batch.Call<AccessControlService, GrantResponse>() { if (ht != null) {
ServerRpcController controller = new ServerRpcController(); ht.close();
BlockingRpcCallback<GrantResponse> rpcCallback = }
new BlockingRpcCallback<GrantResponse>(); }
}
@Override /**
public GrantResponse call(AccessControlService service) throws IOException { * Grants permission on the specified namespace for the specified user.
GrantRequest.Builder builder = GrantRequest.newBuilder(); * @param conf
AccessControlProtos.Permission.Builder ret = * @param namespace
AccessControlProtos.Permission.newBuilder(); * @param userName
AccessControlProtos.TablePermission.Builder permissionBuilder = * @param actions
AccessControlProtos.TablePermission * @throws Throwable
.newBuilder(); */
for (AccessControlProtos.Permission.Action a : actions) { public static void grant(Configuration conf, final String namespace,
permissionBuilder.addAction(a); final String userName, final Permission.Action... actions) throws Throwable {
} HTable ht = null;
permissionBuilder.setTableName(ProtobufUtil.toProtoTableName(tableName)); try {
ht = getAclTable(conf);
if (family != null) { ProtobufUtil.grant(getAccessControlServiceStub(ht), userName, namespace, actions);
permissionBuilder.setFamily(ByteStringer.wrap(family));
}
if (qual != null) {
permissionBuilder.setQualifier(ByteStringer.wrap(qual));
}
ret.setType(AccessControlProtos.Permission.Type.Table).setTablePermission(
permissionBuilder);
builder.setUserPermission(AccessControlProtos.UserPermission.newBuilder()
.setUser(ByteString.copyFromUtf8(userName)).setPermission(ret));
service.grant(controller, builder.build(), rpcCallback);
return rpcCallback.get();
}
};
Map<byte[], GrantResponse> result = ht.coprocessorService(AccessControlService.class,
HConstants.EMPTY_BYTE_ARRAY, HConstants.EMPTY_BYTE_ARRAY, callable);
return result.values().iterator().next(); // There will be exactly one
// region for labels
// table and so one entry in
// result Map.
} finally { } finally {
if (ht != null) { if (ht != null) {
ht.close(); ht.close();
@ -144,61 +127,42 @@ public class AccessControlClient {
/** /**
* Revokes the permission on the table * Revokes the permission on the table
* @param conf * @param conf
* @param username
* @param tableName * @param tableName
* @param username
* @param family * @param family
* @param qualifier * @param qualifier
* @param actions * @param actions
* @return RevokeResponse
* @throws Throwable * @throws Throwable
*/ */
public static RevokeResponse revoke(Configuration conf, final String username, public static void revoke(Configuration conf, final TableName tableName,
final TableName tableName, final byte[] family, final byte[] qualifier, final String username, final byte[] family, final byte[] qualifier,
final AccessControlProtos.Permission.Action... actions) throws Throwable { final Permission.Action... actions) throws Throwable {
Table ht = null; HTable ht = null;
try { try {
TableName aclTableName = TableName.valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, ht = getAclTable(conf);
"acl"); ProtobufUtil.revoke(getAccessControlServiceStub(ht), username, tableName, family, qualifier,
ht = new HTable(conf, aclTableName); actions);
Batch.Call<AccessControlService, AccessControlProtos.RevokeResponse> callable = } finally {
new Batch.Call<AccessControlService, AccessControlProtos.RevokeResponse>() { if (ht != null) {
ServerRpcController controller = new ServerRpcController(); ht.close();
BlockingRpcCallback<AccessControlProtos.RevokeResponse> rpcCallback = }
new BlockingRpcCallback<AccessControlProtos.RevokeResponse>(); }
}
@Override
public RevokeResponse call(AccessControlService service) throws IOException {
AccessControlProtos.Permission.Builder ret =
AccessControlProtos.Permission.newBuilder();
AccessControlProtos.TablePermission.Builder permissionBuilder =
AccessControlProtos.TablePermission.newBuilder();
for (AccessControlProtos.Permission.Action a : actions) {
permissionBuilder.addAction(a);
}
if (tableName != null) {
permissionBuilder.setTableName(ProtobufUtil.toProtoTableName(tableName));
}
if (family != null) {
permissionBuilder.setFamily(ByteStringer.wrap(family));
}
if (qualifier != null) {
permissionBuilder.setQualifier(ByteStringer.wrap(qualifier));
}
ret.setType(AccessControlProtos.Permission.Type.Table).setTablePermission(
permissionBuilder);
RevokeRequest builder = AccessControlProtos.RevokeRequest
.newBuilder()
.setUserPermission(
AccessControlProtos.UserPermission.newBuilder()
.setUser(ByteString.copyFromUtf8(username)).setPermission(ret)).build();
service.revoke(controller, builder, rpcCallback);
return rpcCallback.get();
}
};
Map<byte[], RevokeResponse> result = ht.coprocessorService(AccessControlService.class,
HConstants.EMPTY_BYTE_ARRAY, HConstants.EMPTY_BYTE_ARRAY, callable);
return result.values().iterator().next();
/**
* Revokes the permission on the table for the specified user.
* @param conf
* @param namespace
* @param userName
* @param actions
* @throws Throwable
*/
public static void revoke(Configuration conf, final String namespace,
final String userName, final Permission.Action... actions) throws Throwable {
HTable ht = null;
try {
ht = getAclTable(conf);
ProtobufUtil.revoke(getAccessControlServiceStub(ht), userName, namespace, actions);
} finally { } finally {
if (ht != null) { if (ht != null) {
ht.close(); ht.close();

View File

@ -35,7 +35,6 @@ import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HRegionLocation; import org.apache.hadoop.hbase.HRegionLocation;
import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.IntegrationTestingUtility; import org.apache.hadoop.hbase.IntegrationTestingUtility;
import org.apache.hadoop.hbase.testclassification.IntegrationTests;
import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.chaos.factories.MonkeyFactory; import org.apache.hadoop.hbase.chaos.factories.MonkeyFactory;
import org.apache.hadoop.hbase.client.Admin; import org.apache.hadoop.hbase.client.Admin;
@ -52,13 +51,14 @@ import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.io.hfile.HFile; import org.apache.hadoop.hbase.io.hfile.HFile;
import org.apache.hadoop.hbase.mapreduce.Import; import org.apache.hadoop.hbase.mapreduce.Import;
import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil; import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil;
import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos;
import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.security.User;
import org.apache.hadoop.hbase.security.access.AccessControlClient; import org.apache.hadoop.hbase.security.access.AccessControlClient;
import org.apache.hadoop.hbase.security.access.Permission;
import org.apache.hadoop.hbase.security.visibility.Authorizations; import org.apache.hadoop.hbase.security.visibility.Authorizations;
import org.apache.hadoop.hbase.security.visibility.CellVisibility; import org.apache.hadoop.hbase.security.visibility.CellVisibility;
import org.apache.hadoop.hbase.security.visibility.VisibilityClient; import org.apache.hadoop.hbase.security.visibility.VisibilityClient;
import org.apache.hadoop.hbase.security.visibility.VisibilityController; import org.apache.hadoop.hbase.security.visibility.VisibilityController;
import org.apache.hadoop.hbase.testclassification.IntegrationTests;
import org.apache.hadoop.hbase.util.AbstractHBaseTool; import org.apache.hadoop.hbase.util.AbstractHBaseTool;
import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.BytesWritable;
@ -154,7 +154,7 @@ public class IntegrationTestBigLinkedListWithVisibility extends IntegrationTestB
admin.createTable(htd); admin.createTable(htd);
if (acl) { if (acl) {
LOG.info("Granting permissions for user " + USER.getShortName()); LOG.info("Granting permissions for user " + USER.getShortName());
AccessControlProtos.Permission.Action[] actions = { AccessControlProtos.Permission.Action.READ }; Permission.Action[] actions = { Permission.Action.READ };
try { try {
AccessControlClient.grant(getConf(), tableName, USER.getShortName(), null, null, AccessControlClient.grant(getConf(), tableName, USER.getShortName(), null, null,
actions); actions);

View File

@ -390,6 +390,48 @@ public class SecureTestUtil {
}); });
} }
/**
* Grant permissions on a namespace to the given user using AccessControl Client.
* Will wait until all active AccessController instances have updated their permissions caches
* or will throw an exception upon timeout (10 seconds).
*/
public static void grantOnNamespaceUsingAccessControlClient(final HBaseTestingUtility util,
final Configuration conf, final String user, final String namespace,
final Permission.Action... actions) throws Exception {
SecureTestUtil.updateACLs(util, new Callable<Void>() {
@Override
public Void call() throws Exception {
try {
AccessControlClient.grant(conf, namespace, user, actions);
} catch (Throwable t) {
t.printStackTrace();
}
return null;
}
});
}
/**
* Revoke permissions on a namespace from the given user using AccessControl Client.
* Will wait until all active AccessController instances have updated their permissions caches
* or will throw an exception upon timeout (10 seconds).
*/
public static void revokeFromNamespaceUsingAccessControlClient(final HBaseTestingUtility util,
final Configuration conf, final String user, final String namespace,
final Permission.Action... actions) throws Exception {
SecureTestUtil.updateACLs(util, new Callable<Void>() {
@Override
public Void call() throws Exception {
try {
AccessControlClient.revoke(conf, namespace, user, actions);
} catch (Throwable t) {
t.printStackTrace();
}
return null;
}
});
}
/** /**
* Revoke permissions on a namespace from the given user. Will wait until all active * Revoke permissions on a namespace from the given user. Will wait until all active
* AccessController instances have updated their permissions caches or will * AccessController instances have updated their permissions caches or will
@ -439,6 +481,27 @@ public class SecureTestUtil {
}); });
} }
/**
* Grant permissions on a table to the given user using AccessControlClient. Will wait until all
* active AccessController instances have updated their permissions caches or will
* throw an exception upon timeout (10 seconds).
*/
public static void grantOnTableUsingAccessControlClient(final HBaseTestingUtility util,
final Configuration conf, final String user, final TableName table, final byte[] family,
final byte[] qualifier, final Permission.Action... actions) throws Exception {
SecureTestUtil.updateACLs(util, new Callable<Void>() {
@Override
public Void call() throws Exception {
try {
AccessControlClient.grant(conf, table, user, family, qualifier, actions);
} catch (Throwable t) {
t.printStackTrace();
}
return null;
}
});
}
/** /**
* Revoke permissions on a table from the given user. Will wait until all active * Revoke permissions on a table from the given user. Will wait until all active
* AccessController instances have updated their permissions caches or will * AccessController instances have updated their permissions caches or will
@ -463,4 +526,25 @@ public class SecureTestUtil {
} }
}); });
} }
/**
* Revoke permissions on a table from the given user using AccessControlClient. Will wait until
* all active AccessController instances have updated their permissions caches or will
* throw an exception upon timeout (10 seconds).
*/
public static void revokeFromTableUsingAccessControlClient(final HBaseTestingUtility util,
final Configuration conf, final String user, final TableName table, final byte[] family,
final byte[] qualifier, final Permission.Action... actions) throws Exception {
SecureTestUtil.updateACLs(util, new Callable<Void>() {
@Override
public Void call() throws Exception {
try {
AccessControlClient.revoke(conf, table, user, family, qualifier, actions);
} catch (Throwable t) {
t.printStackTrace();
}
return null;
}
});
}
} }

View File

@ -45,7 +45,6 @@ import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.KeyValue; import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.MiniHBaseCluster; import org.apache.hadoop.hbase.MiniHBaseCluster;
import org.apache.hadoop.hbase.NamespaceDescriptor; import org.apache.hadoop.hbase.NamespaceDescriptor;
import org.apache.hadoop.hbase.NamespaceDescriptor.Builder;
import org.apache.hadoop.hbase.ServerName; import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.TableNotFoundException; import org.apache.hadoop.hbase.TableNotFoundException;
@ -2126,6 +2125,89 @@ public class TestAccessController extends SecureTestUtil {
verifyAllowed(getAction, USER_NONE); verifyAllowed(getAction, USER_NONE);
} }
@Test
public void testAccessControlClientGrantRevoke() throws Exception {
// Create user for testing, who has no READ privileges by default.
User testGrantRevoke = User.createUserForTesting(conf, "testGrantRevoke", new String[0]);
AccessTestAction getAction = new AccessTestAction() {
@Override
public Object run() throws Exception {
HTable t = new HTable(conf, TEST_TABLE.getTableName());
try {
return t.get(new Get(TEST_ROW));
} finally {
t.close();
}
}
};
verifyDenied(getAction, testGrantRevoke);
// Grant table READ permissions to testGrantRevoke.
try {
grantOnTableUsingAccessControlClient(TEST_UTIL, conf, testGrantRevoke.getShortName(),
TEST_TABLE.getTableName(), null, null, Permission.Action.READ);
} catch (Throwable e) {
LOG.error("error during call of AccessControlClient.grant. " + e.getStackTrace());
}
// Now testGrantRevoke should be able to read also
verifyAllowed(getAction, testGrantRevoke);
// Revoke table READ permission to testGrantRevoke.
try {
revokeFromTableUsingAccessControlClient(TEST_UTIL, conf, testGrantRevoke.getShortName(),
TEST_TABLE.getTableName(), null, null, Permission.Action.READ);
} catch (Throwable e) {
LOG.error("error during call of AccessControlClient.revoke " + e.getStackTrace());
}
// Now testGrantRevoke shouldn't be able read
verifyDenied(getAction, testGrantRevoke);
}
@Test
public void testAccessControlClientGrantRevokeOnNamespace() throws Exception {
// Create user for testing, who has no READ privileges by default.
User testNS = User.createUserForTesting(conf, "testNS", new String[0]);
AccessTestAction getAction = new AccessTestAction() {
@Override
public Object run() throws Exception {
HTable t = new HTable(conf, TEST_TABLE.getTableName());
try {
return t.get(new Get(TEST_ROW));
} finally {
t.close();
}
}
};
verifyDenied(getAction, testNS);
// Grant namespace READ to testNS, this should supersede any table permissions
try {
grantOnNamespaceUsingAccessControlClient(TEST_UTIL, conf, testNS.getShortName(),
TEST_TABLE.getTableName().getNamespaceAsString(), Permission.Action.READ);
} catch (Throwable e) {
LOG.error("error during call of AccessControlClient.grant. " + e.getStackTrace());
}
// Now testNS should be able to read also
verifyAllowed(getAction, testNS);
// Revoke namespace READ to testNS, this should supersede any table permissions
try {
revokeFromNamespaceUsingAccessControlClient(TEST_UTIL, conf, testNS.getShortName(),
TEST_TABLE.getTableName().getNamespaceAsString(), Permission.Action.READ);
} catch (Throwable e) {
LOG.error("error during call of AccessControlClient.revoke " + e.getStackTrace());
}
// Now testNS shouldn't be able read
verifyDenied(getAction, testNS);
}
public static class PingCoprocessor extends PingService implements Coprocessor, public static class PingCoprocessor extends PingService implements Coprocessor,
CoprocessorService { CoprocessorService {

View File

@ -47,11 +47,11 @@ import org.apache.hadoop.hbase.io.compress.Compression;
import org.apache.hadoop.hbase.io.crypto.Cipher; import org.apache.hadoop.hbase.io.crypto.Cipher;
import org.apache.hadoop.hbase.io.crypto.Encryption; import org.apache.hadoop.hbase.io.crypto.Encryption;
import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding; import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos;
import org.apache.hadoop.hbase.regionserver.BloomType; import org.apache.hadoop.hbase.regionserver.BloomType;
import org.apache.hadoop.hbase.security.EncryptionUtil; import org.apache.hadoop.hbase.security.EncryptionUtil;
import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.security.User;
import org.apache.hadoop.hbase.security.access.AccessControlClient; import org.apache.hadoop.hbase.security.access.AccessControlClient;
import org.apache.hadoop.hbase.security.access.Permission;
import org.apache.hadoop.hbase.util.test.LoadTestDataGenerator; import org.apache.hadoop.hbase.util.test.LoadTestDataGenerator;
import org.apache.hadoop.hbase.util.test.LoadTestDataGeneratorWithACL; import org.apache.hadoop.hbase.util.test.LoadTestDataGeneratorWithACL;
import org.apache.hadoop.security.SecurityUtil; import org.apache.hadoop.security.SecurityUtil;
@ -572,9 +572,9 @@ public class LoadTestTool extends AbstractHBaseTool {
if (userOwner != null) { if (userOwner != null) {
LOG.info("Granting permissions for user " + userOwner.getShortName()); LOG.info("Granting permissions for user " + userOwner.getShortName());
AccessControlProtos.Permission.Action[] actions = { Permission.Action[] actions = {
AccessControlProtos.Permission.Action.ADMIN, AccessControlProtos.Permission.Action.CREATE, Permission.Action.ADMIN, Permission.Action.CREATE,
AccessControlProtos.Permission.Action.READ, AccessControlProtos.Permission.Action.WRITE }; Permission.Action.READ, Permission.Action.WRITE };
try { try {
AccessControlClient.grant(conf, tableName, userOwner.getShortName(), null, null, actions); AccessControlClient.grant(conf, tableName, userOwner.getShortName(), null, null, actions);
} catch (Throwable e) { } catch (Throwable e) {

View File

@ -23,6 +23,7 @@ java_import org.apache.hadoop.hbase.util.Pair
java_import org.apache.hadoop.hbase.util.RegionSplitter java_import org.apache.hadoop.hbase.util.RegionSplitter
java_import org.apache.hadoop.hbase.util.Bytes java_import org.apache.hadoop.hbase.util.Bytes
java_import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos::SnapshotDescription java_import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos::SnapshotDescription
java_import org.apache.commons.collections.MapUtils
# Wrapper for org.apache.hadoop.hbase.client.HBaseAdmin # Wrapper for org.apache.hadoop.hbase.client.HBaseAdmin