http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c8c88786/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStoreWithFileLog.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStoreWithFileLog.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStoreWithFileLog.java new file mode 100644 index 0000000..8f640a0 --- /dev/null +++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStoreWithFileLog.java @@ -0,0 +1,128 @@ +/** + * 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.sentry.provider.db.service.persistent; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.sentry.SentryUserException; +import org.apache.sentry.provider.db.service.persistent.FileLog.Entry; +import org.apache.sentry.provider.db.service.thrift.TSentryStoreOp; +import org.apache.sentry.provider.db.service.thrift.TSentryStoreRecord; +import org.apache.thrift.TException; + +/** + * An implementation of the {@link PersistentSentryStore}. The Persistence + * strategy used by this class is to log all write operations to a local file + * using the {@link FileLog} log abstraction. Each write operation is stamp + * with a monotonically +1 incrementing sequence Id. This guarantees that, after + * a restart, the Sentry Store can read the log in the same order it was + * written and would return to the same state it was before it was brought down. + * The logging is also write-behind (operations are logged only after it has + * been accepted by the underlying store) to ensure that erroneous operations + * that would bring down the Store are not logged. + * + * To limit the size of the log file, it requests the underlying SentryStore + * to provide it with a snapshot of the store after a configurable number of + * operations, which it writes to a new log file and and truncates the old one. + * + */ +public class SentryStoreWithFileLog extends + PersistentSentryStore<SentryStoreWithFileLog.FileLogContext> { + + public static final int SENTRY_STORE_FILE_LOG_SNAPSHOT_THRESHOLD_DEF = 100; + public static final String SENTRY_STORE_FILE_LOG_SNAPSHOT_THRESHOLD = + "sentry.store.file.log.snapshot.threshold"; + + /** + * An implementation of the {@link PersistentContext} that is created prior + * to the operation and stores the write record. + */ + public static class FileLogContext implements + PersistentSentryStore.PersistentContext { + final long seqId; + final TSentryStoreRecord record; + + FileLogContext(long seqId, TSentryStoreRecord record) { + this.seqId = seqId; + this.record = record; + } + } + + protected final FileLog fileLog; + protected final AtomicLong lastSeenSeqId = new AtomicLong(0); + protected final int snapshotThreshold; + + public SentryStoreWithFileLog(SentryStore sentryStore) + throws FileNotFoundException, IOException, TException, SentryUserException { + super(sentryStore); + snapshotThreshold = + getConfiguration().getInt( + SENTRY_STORE_FILE_LOG_SNAPSHOT_THRESHOLD, + SENTRY_STORE_FILE_LOG_SNAPSHOT_THRESHOLD_DEF); + fileLog = new FileLog(getConfiguration()); + Entry ent = null; + while (fileLog.hasNext()) { + ent = fileLog.next(); + applyRecord(ent.record); + } + if (ent != null) { + lastSeenSeqId.set(ent.seqId); + } + } + + @Override + protected FileLogContext createRecord(TSentryStoreRecord record) { + return new FileLogContext(lastSeenSeqId.incrementAndGet(), record); + } + + @Override + protected void onSuccess(FileLogContext context) { + fileLog.log(context.seqId, + getSnapshotIfRequired(context.seqId, context.record)); + } + + @Override + protected void onFailure(FileLogContext context) { + fileLog.log(context.seqId, + getSnapshotIfRequired(context.seqId, + new TSentryStoreRecord(TSentryStoreOp.NO_OP))); + } + + protected TSentryStoreRecord getSnapshotIfRequired(long seqId, TSentryStoreRecord record) { + if ((seqId > 0) && (seqId % snapshotThreshold == 0)) { + if (record.getStoreOp() == TSentryStoreOp.SNAPSHOT) { + return record; + } + TSentryStoreRecord snapshotRecord = new TSentryStoreRecord(TSentryStoreOp.SNAPSHOT); + snapshotRecord.setSnapshot(getStore().toSnapshot()); + return snapshotRecord; + } else { + return record; + } + } + + @Override + public void stop() { + super.stop(); + fileLog.close(); + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c8c88786/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStoreWithLocalLock.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStoreWithLocalLock.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStoreWithLocalLock.java new file mode 100644 index 0000000..42f4e7f --- /dev/null +++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStoreWithLocalLock.java @@ -0,0 +1,65 @@ +/** + * 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.sentry.provider.db.service.persistent; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * An implementation of {@link LockingSentryStore} that implements the + * Locking strategy using the standard + * {@link java.util.concurrent.locks.ReentrantReadWriteLock} + * + */ +public class SentryStoreWithLocalLock extends + LockingSentryStore<SentryStoreWithLocalLock.ThreadSafeContext> { + + public static class ThreadSafeContext implements + LockingSentryStore.LockContext { + final Lock lock; + + public ThreadSafeContext(Lock lock) { + this.lock = lock; + } + + @Override + public void unlock() { + lock.unlock(); + } + } + + private final ReadWriteLock rwLock; + + public SentryStoreWithLocalLock(SentryStore sentryStore) { + super(sentryStore); + this.rwLock = new ReentrantReadWriteLock(); + } + + @Override + protected ThreadSafeContext writeLock() { + return new ThreadSafeContext(rwLock.writeLock()); + } + + @Override + protected ThreadSafeContext readLock() { + return new ThreadSafeContext(rwLock.readLock()); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c8c88786/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStoreWithReplicatedLog.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStoreWithReplicatedLog.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStoreWithReplicatedLog.java new file mode 100644 index 0000000..0ecf024 --- /dev/null +++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStoreWithReplicatedLog.java @@ -0,0 +1,377 @@ +/** + * 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.sentry.provider.db.service.persistent; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.Serializable; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.apache.hive.com.esotericsoftware.minlog.Log; +import org.apache.sentry.SentryUserException; +import org.apache.sentry.provider.db.service.persistent.FileLog.Entry; +import org.apache.sentry.provider.db.service.thrift.TSentryStoreOp; +import org.apache.sentry.provider.db.service.thrift.TSentryStoreRecord; +import org.apache.thrift.TDeserializer; +import org.apache.thrift.TException; +import org.apache.thrift.TSerializer; +import org.apache.thrift.protocol.TCompactProtocol; +import org.apache.thrift.protocol.TProtocolFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.core.IAtomicLong; +import com.hazelcast.core.IQueue; +import com.hazelcast.core.ITopic; +import com.hazelcast.core.Member; +import com.hazelcast.core.Message; +import com.hazelcast.core.MessageListener; + +/** + * A special subclass of the {@link SentryStoreWithFileLog} that uses the + * same persistence strategy but in addition to logging to a local + * {@link FileLog} will also publish the record to a distributed topic to be + * consumed by other peer Stores in a Sentry cluster. + * + * The class uses the Hazelcast library for : + * 1) Distributed counter for sequence Id + * 2) Distributed Topic with global ordering to replicate log entries to peer + * SentryStores + * + * Consistency Guarantees: + * Hazelcast counters are globally consistent, but since it is optimized for + * availability rather than consistency it can suffer from split-brain issues + * in the event of a network partition. This should not be too much of an issue + * for small deployments of 2-3 instances deployed in the same availability + * zone, But strict consistency can be guaranteed by either: + * 1) Wrapping this store with a {@link SentryStoreWithDistributedLock} which + * uses the Curator library's distributed read write lock which is based + * on Zookeeper. Zookeper has a stricter consistency model since all writes + * go thru a master. + * 2) Deploying SentryService in active-standy mode which ensures writes are + * handled only by the active node. This can be trivially + * accomplished by setting the serivce discovery policy in the + * SentrtyPolicyServiceClient to 'STICKY' rather than the default + * 'ROUND-ROBIN' + * + * Other Considerations: + * This implementation also supports new peers joining an existing cluster. + * The new peer will be brought upto speed with the other members of the + * cluster in the following manner: + * 1) All members listen to a special 'catchupRequest' topic. + * 2) When a new member starts-up, it publishes a 'CatchupRequest' to the + * 'catchUpRequest' topic. The CatchupRequest contains the last seen + * record's sequence number from its local FileLog. + * 3) The new member also creates a uniquely named Distributed Queue and waits + * for messages on it. This queue name is also included in the + * CatchupRequest. + * 4) This request is received by all the existing members of the cluster, it + * is ignored by all members EXCEPT the oldest member of the cluster which + * responds to the request by pushing the required records to the + * Distributed queue. + */ + +public class SentryStoreWithReplicatedLog extends SentryStoreWithFileLog { + + private static final Logger LOGGER = LoggerFactory + .getLogger(SentryStoreWithReplicatedLog.class); + + private static final int WAIT_SLEEP_MS = 500; + + /** + * Only successful events are sent to the topic (failed records are sent as + * no-op so that no gaps exist in the seqId) + * Basically write behind logging.. If the publisher crashes before + * writing to log, the update is not applied to the remote nodes. + * Think this should be fine (It is similar to the case when Sentry + * Service receives a message but dies while/before opening a db transaction) + */ + public static class LogEntry implements Serializable { + private static final long serialVersionUID = 8798360797372277777L; + + long seqId = -1; + long nodeId = -1; + byte[] recordBytes; + + public LogEntry() {} + + public LogEntry(long seqId, long nodeId, byte[] recordBytes) { + this.seqId = seqId; + this.nodeId = nodeId; + this.recordBytes = recordBytes; + } + } + + public static class CatchUpRequest implements Serializable { + private static final long serialVersionUID = -3345746198249394847L; + + long startSeqId = -1; + long endSeqId = -1; + long nodeId = -1; + + public CatchUpRequest() {} + + public CatchUpRequest(long startSeqId, long endSeqId, long nodeId) { + this.startSeqId = startSeqId; + this.endSeqId = endSeqId; + this.nodeId = nodeId; + } + } + + class LogEntryWorker implements Runnable{ + @Override + public void run() { + while(true) { + try { + processLogEntry(entryQueue.take()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (SentryUserException e) { + throw new RuntimeException(e); + } + } + } + } + + private final TSerializer serializer; + private final TDeserializer deserializer; + private final BlockingQueue<LogEntry> entryQueue = + new LinkedBlockingQueue<LogEntry>(); + private Thread entryWorker; + + // Distributed stuff + private final HazelcastInstance hInst; + private final long nodeId; + private final IAtomicLong globalSeqId; + private final ITopic<LogEntry> dTopic; + private final ITopic<CatchUpRequest> catchUpReqTopic; + + public SentryStoreWithReplicatedLog(SentryStore sentryStore) + throws FileNotFoundException, IOException, TException, + SentryUserException { + this(sentryStore, + DistributedUtils.getHazelcastInstance(sentryStore.getConfiguration(), + true)); + } + + public SentryStoreWithReplicatedLog(SentryStore sentryStore, + final HazelcastInstance hInst) throws FileNotFoundException, IOException, + TException, SentryUserException { + super(sentryStore); + this.hInst = hInst; + TProtocolFactory protoFactory = new TCompactProtocol.Factory(); + serializer = new TSerializer(protoFactory); + deserializer = new TDeserializer(protoFactory); + + nodeId = hInst.getIdGenerator(DistributedUtils.SENTRY_STORE_NODEID).newId(); + globalSeqId = hInst.getAtomicLong(DistributedUtils.SENTRY_STORE_SEQID); + dTopic = hInst.getTopic(DistributedUtils.SENTRY_DISTRIBUTED_TOPIC); + dTopic.addMessageListener(new MessageListener<SentryStoreWithReplicatedLog.LogEntry>() { + @Override + public void onMessage(Message<LogEntry> msg) { + LogEntry ent = msg.getMessageObject(); + try { + entryQueue.put(ent); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + }); + + catchUpReqTopic = hInst.getTopic(DistributedUtils.SENTRY_CATCHUP_REQUEST_TOPIC); + requestCatchUpIfRequired(); + catchUpReqTopic.addMessageListener(new MessageListener<CatchUpRequest>() { + @Override + public void onMessage(final Message<CatchUpRequest> req) { + LOGGER.info("Catchup request received[" + + req.getMessageObject().startSeqId + ", " + + req.getMessageObject().endSeqId + ", " + + req.getMessageObject().nodeId + "]"); + handleCatchupRequest(req.getMessageObject(), hInst); + } + }); + + entryWorker = new Thread(new LogEntryWorker(), "Log Entry Worker"); + entryWorker.start(); + } + + private void handleCatchupRequest(CatchUpRequest req, HazelcastInstance hInst) { + Member oldestMember = hInst.getCluster().getMembers().iterator().next(); + // Am I oldest member ? + if (oldestMember.localMember()) { + long myLastSeen = lastSeenSeqId.get(); + if (req.endSeqId > myLastSeen) { + LOGGER.info("Waiting for seq Id[" + myLastSeen + ", " + req.endSeqId + "]"); + try { + Thread.sleep(getConfiguration().getInt( + DistributedUtils.SENTRY_CATCHUP_WAIT_TIME, + DistributedUtils.SENTRY_CATCHUP_WAIT_TIME_DEF)); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + // Still not updated ? + if (req.endSeqId > myLastSeen) { + throw new RuntimeException( + "Havnt recieved latest updated.. cannot respond to catchup request !!"); + } + FileLog tempLog = null; + try { + tempLog = new FileLog(getStore().getConfiguration()); + } catch (Exception e) { + throw new RuntimeException("Could not open FileLog to send catchup entries !!"); + } + + IQueue<LogEntry> respQueue = + hInst.getQueue(DistributedUtils.SENTRY_CATCHUP_RESPONSE_QUEUE + req.nodeId); + while (tempLog.hasNext()) { + Entry entry = tempLog.next(); + if ((entry.seqId >= req.startSeqId)&&(entry.seqId <= req.endSeqId)) { + try { + respQueue.offer(new LogEntry(entry.seqId, nodeId, serializer.serialize(entry.record))); + LOGGER.info("Sent Catchup entry [" + entry.seqId + "," + + nodeId + ", " + req.nodeId + "]"); + } catch (TException e) { + throw new RuntimeException("Could not send catchup entry !!", e); + } + } + } + } + } + + private void requestCatchUpIfRequired() throws SentryUserException { + long startSeqId = lastSeenSeqId.get() + 1; + long endSeqId = globalSeqId.get(); + if (startSeqId <= endSeqId) { + LOGGER.info("Sending Catchup request [" + + startSeqId + ", " + + endSeqId + ", " + + nodeId + "]"); + // Send request for catchup entries + IQueue<LogEntry> respQueue = + hInst.getQueue(DistributedUtils.SENTRY_CATCHUP_RESPONSE_QUEUE + nodeId); + catchUpReqTopic.publish( + new CatchUpRequest(startSeqId, endSeqId, nodeId)); + receiveCatchUpEntries(respQueue, endSeqId); + } + } + + private void receiveCatchUpEntries(IQueue<LogEntry> catchUpQueue, + long endSeqId) throws SentryUserException { + long currSeqId = -1; + while (currSeqId < endSeqId) { + try { + LogEntry entry = + catchUpQueue.poll( + getConfiguration().getInt( + DistributedUtils.SENTRY_CATCHUP_WAIT_TIME, + DistributedUtils.SENTRY_CATCHUP_WAIT_TIME_DEF), + TimeUnit.MILLISECONDS); + if (entry == null) { + String msg = + "Havnt received all catchup entries [" + currSeqId + ", " + + endSeqId + "]!!"; + LOGGER.error(msg); + throw new RuntimeException(msg); + } + LOGGER.info("Received catchup [" + entry.seqId + ", " + entry.nodeId + "]"); + currSeqId = entry.seqId; + processLogEntry(entry); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + catchUpQueue.destroy(); + } + + private void processLogEntry(LogEntry ent) throws SentryUserException { + TSentryStoreRecord record = new TSentryStoreRecord(); + try { + deserializer.deserialize(record, ent.recordBytes); + } catch (TException e) { + String msg = "Could not de-serialize record [" + ent.seqId + "]!!"; + Log.error(msg, e); + throw new RuntimeException(msg, e); + } + // No need to update the publisher (Thats already done) + if (ent.nodeId != SentryStoreWithReplicatedLog.this.nodeId) { + try { + applyRecord(record); + fileLog.log(ent.seqId, getSnapshotIfRequired(ent.seqId, record)); + lastSeenSeqId.set(ent.seqId); + } catch (SentryUserException e) { + String msg = "Could not apply de-serialized record [" + ent.seqId + "]!!"; + Log.error(msg, e); + throw new RuntimeException(msg, e); + } + } + } + + @Override + protected FileLogContext createRecord(TSentryStoreRecord record) { + return new FileLogContext(globalSeqId.incrementAndGet(), record); + } + + @Override + protected void onSuccess(FileLogContext context) { + super.onSuccess(context); + lastSeenSeqId.set(context.seqId); + try { + dTopic.publish(new LogEntry(context.seqId, nodeId, serializer + .serialize(context.record))); + } catch (TException e) { + throw new RuntimeException("Could not serialize Sentry record !!"); + } + } + + @Override + protected void onFailure(FileLogContext context) { + // Publish a NO-OP record (since we dont want any gaps in the seqId) + super.onFailure(context); + lastSeenSeqId.set(context.seqId); + try { + dTopic.publish(new LogEntry(context.seqId, nodeId, serializer + .serialize(new TSentryStoreRecord(TSentryStoreOp.NO_OP)))); + } catch (TException e) { + throw new RuntimeException("Could not serialize Sentry record !!"); + } + } + + public boolean waitForReplicattionToComplete(long timeInMs) { + long totalWait = 0; + while (true) { + if (lastSeenSeqId.get() == globalSeqId.get()) { + return true; + } + try { + Thread.sleep(WAIT_SLEEP_MS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } + totalWait += WAIT_SLEEP_MS; + if (totalWait >= timeInMs) { + return false; + } + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c8c88786/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/StoreUtils.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/StoreUtils.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/StoreUtils.java new file mode 100644 index 0000000..d23685b --- /dev/null +++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/StoreUtils.java @@ -0,0 +1,102 @@ +/** + * 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.sentry.provider.db.service.persistent; + +import static org.apache.sentry.provider.common.ProviderConstants.AUTHORIZABLE_JOINER; +import static org.apache.sentry.provider.common.ProviderConstants.KV_JOINER; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.sentry.core.model.db.AccessConstants; +import org.apache.sentry.core.model.db.DBModelAuthorizable.AuthorizableType; +import org.apache.sentry.provider.common.ProviderConstants; +import org.apache.sentry.provider.db.service.model.MSentryPrivilege; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; +import com.google.common.collect.Sets; + +public class StoreUtils { + + public static String NULL_COL = "__NULL__"; + + @VisibleForTesting + public static String toAuthorizable(MSentryPrivilege privilege) { + return toAuthorizable(privilege.getServerName(), privilege.getDbName(), + privilege.getURI(), privilege.getTableName(), privilege.getColumnName(), + privilege.getAction()); + } + + @VisibleForTesting + public static String toAuthorizable(String serverName, String dbName, + String uri, String tableName, String columnName, String action) { + List<String> authorizable = new ArrayList<String>(4); + authorizable.add(KV_JOINER.join(AuthorizableType.Server.name().toLowerCase(), + serverName)); + if (isNULL(uri)) { + if (!isNULL(dbName)) { + authorizable.add(KV_JOINER.join(AuthorizableType.Db.name().toLowerCase(), + dbName)); + if (!isNULL(tableName)) { + authorizable.add(KV_JOINER.join(AuthorizableType.Table.name().toLowerCase(), + tableName)); + if (!isNULL(columnName)) { + authorizable.add(KV_JOINER.join(AuthorizableType.Column.name().toLowerCase(), + columnName)); + } + } + } + } else { + authorizable.add(KV_JOINER.join(AuthorizableType.URI.name().toLowerCase(), + uri)); + } + if (!isNULL(action) + && !action.equalsIgnoreCase(AccessConstants.ALL)) { + authorizable + .add(KV_JOINER.join(ProviderConstants.PRIVILEGE_NAME.toLowerCase(), + action)); + } + return AUTHORIZABLE_JOINER.join(authorizable); + } + + @VisibleForTesting + public static Set<String> toTrimedLower(Set<String> s) { + if (null == s) return new HashSet<String>(); + Set<String> result = Sets.newHashSet(); + for (String v : s) { + result.add(v.trim().toLowerCase()); + } + return result; + } + + public static String toNULLCol(String s) { + return Strings.isNullOrEmpty(s) ? NULL_COL : s; + } + + public static String fromNULLCol(String s) { + return isNULL(s) ? "" : s; + } + + public static boolean isNULL(String s) { + return Strings.isNullOrEmpty(s) || s.equals(NULL_COL); + } +} http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c8c88786/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryMetrics.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryMetrics.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryMetrics.java index 55bec0b..0dd10f6 100644 --- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryMetrics.java +++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryMetrics.java @@ -28,6 +28,8 @@ import com.codahale.metrics.jvm.BufferPoolMetricSet; import com.codahale.metrics.jvm.GarbageCollectorMetricSet; import com.codahale.metrics.jvm.MemoryUsageGaugeSet; import com.codahale.metrics.jvm.ThreadStatesGaugeSet; + +import org.apache.sentry.provider.db.service.persistent.DbSentryStore; import org.apache.sentry.provider.db.service.persistent.SentryStore; import java.lang.management.ManagementFactory; @@ -84,15 +86,36 @@ public class SentryMetrics { return sentryMetrics; } - public void addSentryStoreGauges(SentryStore sentryStore) { + public void addSentryStoreGauges(final SentryStore sentryStore) { if(!gaugesAdded) { - addGauge(SentryStore.class, "role_count", sentryStore.getRoleCountGauge()); - addGauge(SentryStore.class, "privilege_count", sentryStore.getPrivilegeCountGauge()); - addGauge(SentryStore.class, "group_count", sentryStore.getGroupCountGauge()); + addGauge(SentryStore.class, "role_count", new Gauge<Long>() { + @Override + public Long getValue() { + return sentryStore.getRoleCount(); + }}); + addGauge(SentryStore.class, "privilege_count", new Gauge<Long>() { + @Override + public Long getValue() { + return sentryStore.getPrivilegeCount(); + }}); + addGauge(SentryStore.class, "group_count", new Gauge<Long>() { + @Override + public Long getValue() { + return sentryStore.getGroupCount(); + }}); gaugesAdded = true; } } +//@Override +//public Gauge<Long> getGroupCountGauge() { +// return new Gauge< Long >() { +// @Override +// public Long getValue() { +// return getCount(MSentryGroup.class); +// } +// }; +//} /* Should be only called once to initialize the reporters */ http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c8c88786/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyStoreProcessor.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyStoreProcessor.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyStoreProcessor.java index 29e3131..1ffc496 100644 --- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyStoreProcessor.java +++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyStoreProcessor.java @@ -32,6 +32,7 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.ReentrantReadWriteLock; import com.codahale.metrics.Timer; + import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hive.conf.HiveConf; import org.apache.hadoop.hive.metastore.HiveMetaStoreClient; @@ -52,8 +53,10 @@ import org.apache.sentry.provider.db.log.entity.JsonLogEntityFactory; import org.apache.sentry.provider.db.log.util.Constants; import org.apache.sentry.provider.db.service.persistent.CommitContext; import org.apache.sentry.provider.db.service.persistent.HAContext; -import org.apache.sentry.provider.db.service.persistent.SentryStore; import org.apache.sentry.provider.db.service.persistent.ServiceRegister; +import org.apache.sentry.provider.db.service.persistent.DbSentryStore; +import org.apache.sentry.provider.db.service.persistent.SentryStore; +import org.apache.sentry.provider.db.service.persistent.SentryStoreFactory; import org.apache.sentry.provider.db.service.thrift.PolicyStoreConstants.PolicyStoreServerConfig; import org.apache.sentry.service.thrift.ServiceConstants.ConfUtilties; import org.apache.sentry.service.thrift.ServiceConstants.ClientConfig; @@ -103,13 +106,13 @@ public class SentryPolicyStoreProcessor implements SentryPolicyService.Iface { isReady = false; if(conf.getBoolean(ServerConfig.SENTRY_HA_ENABLED, ServerConfig.SENTRY_HA_ENABLED_DEFAULT)){ - haContext = new HAContext(conf); - sentryStore = new SentryStore(conf); + haContext = HAContext.get(conf); + sentryStore = SentryStoreFactory.createSentryStore(conf); ServiceRegister reg = new ServiceRegister(haContext); reg.regService(conf.get(ServerConfig.RPC_ADDRESS), conf.getInt(ServerConfig.RPC_PORT,ServerConfig.RPC_PORT_DEFAULT)); } else { - sentryStore = new SentryStore(conf); + sentryStore = SentryStoreFactory.createSentryStore(conf); } isReady = true; adminGroups = ImmutableSet.copyOf(toTrimedLower(Sets.newHashSet(conf.getStrings( http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c8c88786/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/HAClientInvocationHandler.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/HAClientInvocationHandler.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/HAClientInvocationHandler.java index c6e265f..4e371f4 100644 --- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/HAClientInvocationHandler.java +++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/HAClientInvocationHandler.java @@ -51,7 +51,7 @@ public class HAClientInvocationHandler implements InvocationHandler { public HAClientInvocationHandler(Configuration conf) throws Exception { this.conf = conf; - manager = new ServiceManager(new HAContext(conf)); + manager = new ServiceManager(HAContext.get(conf)); checkClientConf(); renewSentryClient(); } http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c8c88786/sentry-provider/sentry-provider-db/src/main/resources/sentry_policy_service.thrift ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/main/resources/sentry_policy_service.thrift b/sentry-provider/sentry-provider-db/src/main/resources/sentry_policy_service.thrift index 993ea46..23f09d7 100644 --- a/sentry-provider/sentry-provider-db/src/main/resources/sentry_policy_service.thrift +++ b/sentry-provider/sentry-provider-db/src/main/resources/sentry_policy_service.thrift @@ -52,11 +52,65 @@ struct TSentryPrivilege { 10: optional string columnName = "", } +struct TSentryAuthorizable { +1: required string server, +2: optional string uri, +3: optional string db, +4: optional string table, +5: optional string column, +} + +enum TSentryStoreOp { + CREATE_ROLE = 0, + DROP_ROLE = 1, + GRANT_PRIVILEGES = 2, + REVOKE_PRVILEGES = 3, + ADD_GROUPS = 4, + DEL_GROUPS = 5, + SET_VERSION = 6, + DROP_PRIVILEGE = 7, + RENAME_PRIVILEGE = 8, + SNAPSHOT = 9, + NO_OP = 100 +} + +struct TStorePrivilege { +1: required TSentryGrantOption grantOption, +2: required i16 privilege +} + +struct TStoreAuthorizable { +1: required string name, +2: required string type, +3: optional map<string, TStorePrivilege> privileges, +4: optional set<i32> children +} + +struct TStoreSnapshot { +1: required map<string, TStoreAuthorizable> rootAuthorizable, +2: required map<string, set<string>> roleToGroups, +3: required map<i32, TStoreAuthorizable> objIds +} + # TODO can this be deleted? it's not adding value to TAlterSentryRoleAddGroupsRequest struct TSentryGroup { 1: required string groupName } +# Represents a Privilege in transport from the client to the server +struct TSentryStoreRecord { +1: required TSentryStoreOp storeOp, +2: optional string roleName, +3: optional string grantorPrincipal, +4: optional set<TSentryPrivilege> privileges, +5: optional set<string> groups, +6: optional TSentryAuthorizable authorizable, +7: optional TSentryAuthorizable newAuthorizable, +8: optional string version, +9: optional string versionComment, +10: optional TStoreSnapshot snapshot +} + # CREATE ROLE r1 struct TCreateSentryRoleRequest { 1: required i32 protocol_version = sentry_common_service.TSENTRY_SERVICE_V1, @@ -143,14 +197,6 @@ struct TListSentryRolesResponse { 2: optional set<TSentryRole> roles } -struct TSentryAuthorizable { -1: required string server, -2: optional string uri, -3: optional string db, -4: optional string table, -5: optional string column, -} - # SHOW GRANT struct TListSentryPrivilegesRequest { 1: required i32 protocol_version = sentry_common_service.TSENTRY_SERVICE_V1, http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c8c88786/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestFileLog.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestFileLog.java b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestFileLog.java new file mode 100644 index 0000000..95acd1c --- /dev/null +++ b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestFileLog.java @@ -0,0 +1,77 @@ +/** + * 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.sentry.provider.db.service.persistent; + +import java.io.File; + +import org.apache.hadoop.conf.Configuration; +import org.apache.sentry.provider.db.service.thrift.TSentryStoreOp; +import org.apache.sentry.provider.db.service.thrift.TSentryStoreRecord; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.google.common.io.Files; + +public class TestFileLog { + + private String logDir; + + @Before + public void setup() { + logDir = Files.createTempDir().getAbsolutePath(); + System.out.println("Creating dir : [" + logDir + "]"); + } + + @After + public void tearDown() { + File l = new File(logDir); + for (File f : l.listFiles()) { + f.delete(); + } + l.delete(); + } + + @Test + public void testReadWriteLog() throws Exception { + Configuration conf = new Configuration(false); + conf.set(FileLog.SENTRY_FILE_LOG_STORE_LOCATION, logDir); + FileLog fileLog = new FileLog(conf); + fileLog.log(1, new TSentryStoreRecord(TSentryStoreOp.CREATE_ROLE)); + fileLog.log(2, new TSentryStoreRecord(TSentryStoreOp.GRANT_PRIVILEGES)); + fileLog.log(3, new TSentryStoreRecord(TSentryStoreOp.REVOKE_PRVILEGES)); + fileLog.log(4, new TSentryStoreRecord(TSentryStoreOp.ADD_GROUPS)); + fileLog.log(5, new TSentryStoreRecord(TSentryStoreOp.DEL_GROUPS)); + fileLog.close(); + + fileLog = new FileLog(conf); + Assert.assertTrue(fileLog.hasNext()); + Assert.assertEquals(TSentryStoreOp.CREATE_ROLE, fileLog.next().record.getStoreOp()); + Assert.assertTrue(fileLog.hasNext()); + Assert.assertEquals(TSentryStoreOp.GRANT_PRIVILEGES, fileLog.next().record.getStoreOp()); + Assert.assertTrue(fileLog.hasNext()); + Assert.assertEquals(TSentryStoreOp.REVOKE_PRVILEGES, fileLog.next().record.getStoreOp()); + Assert.assertTrue(fileLog.hasNext()); + Assert.assertEquals(TSentryStoreOp.ADD_GROUPS, fileLog.next().record.getStoreOp()); + Assert.assertTrue(fileLog.hasNext()); + Assert.assertEquals(TSentryStoreOp.DEL_GROUPS, fileLog.next().record.getStoreOp()); + fileLog.close(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c8c88786/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestFileLoggingSentryStore.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestFileLoggingSentryStore.java b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestFileLoggingSentryStore.java new file mode 100644 index 0000000..13f459e --- /dev/null +++ b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestFileLoggingSentryStore.java @@ -0,0 +1,166 @@ +/** + * 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.sentry.provider.db.service.persistent; + +import static junit.framework.Assert.assertEquals; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.hadoop.conf.Configuration; +import org.apache.sentry.core.model.db.AccessConstants; +import org.apache.sentry.provider.db.SentryInvalidInputException; +import org.apache.sentry.provider.db.service.thrift.TSentryActiveRoleSet; +import org.apache.sentry.provider.db.service.thrift.TSentryGroup; +import org.apache.sentry.provider.db.service.thrift.TSentryPrivilege; +import org.junit.Test; + +import com.google.common.collect.Sets; +import com.google.common.io.Files; + +public class TestFileLoggingSentryStore extends TestInMemSentryStore{ + + private String logDir; + + @Override + public void setup() throws Exception { + super.setup(); + logDir = Files.createTempDir().getAbsolutePath(); + Configuration conf = new Configuration(false); + conf.set(FileLog.SENTRY_FILE_LOG_STORE_LOCATION, logDir); + sentryStore = new SentryStoreWithLocalLock(new SentryStoreWithFileLog(sentryStore)); + } + + @Test + public void testPersistence() throws Exception { + String roleName1 = "list-privs-r1", roleName2 = "list-privs-r2"; + String groupName1 = "list-privs-g1", groupName2 = "list-privs-g2"; + String grantor = "g1"; + long seqId = sentryStore.createSentryRole(roleName1).getSequenceId(); + assertEquals(seqId + 1, sentryStore.createSentryRole(roleName2).getSequenceId()); + TSentryPrivilege privilege1 = new TSentryPrivilege(); + privilege1.setPrivilegeScope("TABLE"); + privilege1.setServerName("server1"); + privilege1.setDbName("db1"); + privilege1.setTableName("tbl1"); + privilege1.setAction("SELECT"); + privilege1.setCreateTime(System.currentTimeMillis()); + assertEquals(seqId + 2, sentryStore.alterSentryRoleGrantPrivilege(grantor, roleName1, privilege1) + .getSequenceId()); + assertEquals(seqId + 3, sentryStore.alterSentryRoleGrantPrivilege(grantor, roleName2, privilege1) + .getSequenceId()); + TSentryPrivilege privilege2 = new TSentryPrivilege(); + privilege2.setPrivilegeScope("SERVER"); + privilege2.setServerName("server1"); + privilege2.setAction(AccessConstants.ALL); + privilege2.setCreateTime(System.currentTimeMillis()); + assertEquals(seqId + 4, sentryStore.alterSentryRoleGrantPrivilege(grantor, roleName2, privilege2) + .getSequenceId()); + Set<TSentryGroup> groups = Sets.newHashSet(); + TSentryGroup group = new TSentryGroup(); + group.setGroupName(groupName1); + groups.add(group); + assertEquals(seqId + 5, sentryStore.alterSentryRoleAddGroups(grantor, + roleName1, groups).getSequenceId()); + groups.clear(); + group = new TSentryGroup(); + group.setGroupName(groupName2); + groups.add(group); + // group 2 has both roles 1 and 2 + assertEquals(seqId + 6, sentryStore.alterSentryRoleAddGroups(grantor, + roleName1, groups).getSequenceId()); + assertEquals(seqId + 7, sentryStore.alterSentryRoleAddGroups(grantor, + roleName2, groups).getSequenceId()); + verifyStore(roleName1, roleName2, groupName1, groupName2); + + // KILL The store and restart using same directory.. + Configuration conf = new Configuration(false); + conf.set(FileLog.SENTRY_FILE_LOG_STORE_LOCATION, logDir); + sentryStore = new SentryStoreWithLocalLock(new SentryStoreWithFileLog(sentryStore)); + + verifyStore(roleName1, roleName2, groupName1, groupName2); + } + + private void verifyStore(String roleName1, String roleName2, + String groupName1, String groupName2) throws SentryInvalidInputException { + // group1 all roles + assertEquals(Sets.newHashSet("server=server1->db=db1->table=tbl1->action=select"), + StoreUtils.toTrimedLower(sentryStore.listAllSentryPrivilegesForProvider(Sets.newHashSet(groupName1), + new TSentryActiveRoleSet(true, new HashSet<String>())))); + // one active role + assertEquals(Sets.newHashSet("server=server1->db=db1->table=tbl1->action=select"), + StoreUtils.toTrimedLower(sentryStore.listAllSentryPrivilegesForProvider(Sets.newHashSet(groupName1), + new TSentryActiveRoleSet(false, Sets.newHashSet(roleName1))))); + // unknown active role + assertEquals(Sets.newHashSet(), + StoreUtils.toTrimedLower(sentryStore.listAllSentryPrivilegesForProvider(Sets.newHashSet(groupName1), + new TSentryActiveRoleSet(false, Sets.newHashSet("not a role"))))); + // no active roles + assertEquals(Sets.newHashSet(), + StoreUtils.toTrimedLower(sentryStore.listAllSentryPrivilegesForProvider(Sets.newHashSet(groupName1), + new TSentryActiveRoleSet(false, new HashSet<String>())))); + + // group2 all roles + assertEquals(Sets.newHashSet("server=server1->db=db1->table=tbl1->action=select", "server=server1"), + StoreUtils.toTrimedLower(sentryStore.listAllSentryPrivilegesForProvider(Sets.newHashSet(groupName2), + new TSentryActiveRoleSet(true, new HashSet<String>())))); + // one active role + assertEquals(Sets.newHashSet("server=server1->db=db1->table=tbl1->action=select"), + StoreUtils.toTrimedLower(sentryStore.listAllSentryPrivilegesForProvider(Sets.newHashSet(groupName2), + new TSentryActiveRoleSet(false, Sets.newHashSet(roleName1))))); + assertEquals(Sets.newHashSet( + "server=server1->db=db1->table=tbl1->action=select", "server=server1"), + StoreUtils.toTrimedLower(sentryStore.listAllSentryPrivilegesForProvider(Sets.newHashSet(groupName2), + new TSentryActiveRoleSet(false, Sets.newHashSet(roleName2))))); + // unknown active role + assertEquals(Sets.newHashSet(), + StoreUtils.toTrimedLower(sentryStore.listAllSentryPrivilegesForProvider(Sets.newHashSet(groupName2), + new TSentryActiveRoleSet(false, Sets.newHashSet("not a role"))))); + // no active roles + assertEquals(Sets.newHashSet(), + StoreUtils.toTrimedLower(sentryStore.listAllSentryPrivilegesForProvider(Sets.newHashSet(groupName2), + new TSentryActiveRoleSet(false, new HashSet<String>())))); + + // both groups, all active roles + assertEquals(Sets.newHashSet("server=server1->db=db1->table=tbl1->action=select", "server=server1"), + StoreUtils.toTrimedLower(sentryStore.listAllSentryPrivilegesForProvider(Sets. + newHashSet(groupName1, groupName2), + new TSentryActiveRoleSet(true, new HashSet<String>())))); + // one active role + assertEquals(Sets.newHashSet("server=server1->db=db1->table=tbl1->action=select"), + StoreUtils.toTrimedLower(sentryStore.listAllSentryPrivilegesForProvider(Sets. + newHashSet(groupName1, groupName2), + new TSentryActiveRoleSet(false, Sets.newHashSet(roleName1))))); + assertEquals(Sets.newHashSet( + "server=server1->db=db1->table=tbl1->action=select", "server=server1"), + StoreUtils.toTrimedLower(sentryStore.listAllSentryPrivilegesForProvider(Sets. + newHashSet(groupName1, groupName2), + new TSentryActiveRoleSet(false, Sets.newHashSet(roleName2))))); + // unknown active role + assertEquals(Sets.newHashSet(), + StoreUtils.toTrimedLower(sentryStore.listAllSentryPrivilegesForProvider(Sets. + newHashSet(groupName1, groupName2), + new TSentryActiveRoleSet(false, Sets.newHashSet("not a role"))))); +// // no active roles + assertEquals(Sets.newHashSet(), + StoreUtils.toTrimedLower(sentryStore.listAllSentryPrivilegesForProvider(Sets. + newHashSet(groupName1, groupName2), + new TSentryActiveRoleSet(false, new HashSet<String>())))); + } +}
