sentry-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From sp...@apache.org
Subject [21/43] sentry git commit: SENTRY-2208: Refactor out Sentry service into own module from sentry-provider-db (Anthony Young-Garner, reviewed by Sergio Pena, Steve Moist, Na Li)
Date Tue, 29 May 2018 18:06:40 GMT
http://git-wip-us.apache.org/repos/asf/sentry/blob/b97f5c7a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/generic/thrift/SentryGenericPolicyProcessor.java
----------------------------------------------------------------------
diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/generic/thrift/SentryGenericPolicyProcessor.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/generic/thrift/SentryGenericPolicyProcessor.java
new file mode 100644
index 0000000..1cc4b1b
--- /dev/null
+++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/generic/thrift/SentryGenericPolicyProcessor.java
@@ -0,0 +1,829 @@
+/**
+ * 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.api.generic.thrift;
+
+import static org.apache.sentry.core.common.utils.SentryConstants.AUTHORIZABLE_JOINER;
+import static org.apache.sentry.core.common.utils.SentryConstants.KV_JOINER;
+
+import java.lang.reflect.Constructor;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.sentry.api.common.ThriftConstants;
+import org.apache.sentry.core.common.exception.SentryUserException;
+import org.apache.sentry.core.common.Authorizable;
+import org.apache.sentry.core.common.utils.SentryConstants;
+import org.apache.sentry.core.common.exception.SentrySiteConfigurationException;
+import org.apache.sentry.core.model.db.AccessConstants;
+import org.apache.sentry.core.common.utils.KeyValue;
+import org.apache.sentry.provider.common.AuthorizationComponent;
+import org.apache.sentry.core.common.exception.SentryAccessDeniedException;
+import org.apache.sentry.core.common.exception.SentryAlreadyExistsException;
+import org.apache.sentry.core.common.exception.SentryInvalidInputException;
+import org.apache.sentry.core.common.exception.SentryNoSuchObjectException;
+import org.apache.sentry.core.common.exception.SentryThriftAPIMismatchException;
+import org.apache.sentry.provider.db.generic.service.persistent.DelegateSentryStore;
+import org.apache.sentry.provider.db.generic.service.persistent.PrivilegeObject;
+import org.apache.sentry.provider.db.generic.service.persistent.PrivilegeObject.Builder;
+import org.apache.sentry.provider.db.generic.service.persistent.SentryStoreLayer;
+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.model.MSentryGMPrivilege;
+import org.apache.sentry.provider.db.service.model.MSentryRole;
+import org.apache.sentry.core.common.utils.PolicyStoreConstants;
+import org.apache.sentry.api.service.thrift.SentryPolicyStoreProcessor;
+import org.apache.sentry.service.common.ServiceConstants.ServerConfig;
+import org.apache.sentry.api.common.Status;
+import org.apache.sentry.service.thrift.TSentryResponseStatus;
+import org.apache.thrift.TException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+public class SentryGenericPolicyProcessor implements SentryGenericPolicyService.Iface {
+  private static final Logger LOGGER = LoggerFactory.getLogger(SentryGenericPolicyProcessor.class);
+  private static final Logger AUDIT_LOGGER = LoggerFactory
+      .getLogger(Constants.AUDIT_LOGGER_NAME_GENERIC);
+  private final Configuration conf;
+  private final ImmutableSet<String> adminGroups;
+  private final SentryStoreLayer store;
+  private final NotificationHandlerInvoker handerInvoker;
+
+  private static final String ACCESS_DENIAL_MESSAGE = "Access denied to ";
+
+  SentryGenericPolicyProcessor(Configuration conf) throws Exception {
+    this.store = new DelegateSentryStore(conf);
+    this.handerInvoker = new NotificationHandlerInvoker(createHandlers(conf));
+    this.conf = conf;
+    adminGroups = ImmutableSet.copyOf((Sets.newHashSet(conf.getStrings(
+        ServerConfig.ADMIN_GROUPS, new String[]{}))));
+  }
+
+  @VisibleForTesting
+  SentryGenericPolicyProcessor(Configuration conf, SentryStoreLayer store) throws Exception {
+    this.store = store;
+    this.handerInvoker = new NotificationHandlerInvoker(createHandlers(conf));
+    this.conf = conf;
+    adminGroups = ImmutableSet.copyOf(toTrimmed(Sets.newHashSet(conf.getStrings(
+        ServerConfig.ADMIN_GROUPS, new String[]{}))));
+  }
+
+  private void authorize(String requestorUser, Set<String> requestorGroups)
+  throws SentryAccessDeniedException {
+    if (!inAdminGroups(requestorGroups)) {
+      String msg = "User: " + requestorUser + " is part of " + requestorGroups +
+          " which does not, intersect admin groups " + adminGroups;
+      LOGGER.warn(msg);
+      throw new SentryAccessDeniedException(ACCESS_DENIAL_MESSAGE + requestorUser);
+    }
+  }
+
+  private Set<String> toTrimmedLower(Set<String> s) {
+    if (s == null) {
+      return Collections.emptySet();
+    }
+    Set<String> result = new HashSet<>(s.size());
+    for (String v : s) {
+      result.add(v.trim().toLowerCase());
+    }
+    return result;
+  }
+
+  private Set<String> toTrimmed(Set<String> s) {
+    if (s == null) {
+      return Collections.emptySet();
+    }
+    Set<String> result = new HashSet<>(s.size());
+    for (String v : s) {
+      result.add(v.trim());
+    }
+    return result;
+  }
+
+  private String toTrimmedLower(String s) {
+    if (Strings.isNullOrEmpty(s)){
+      return "";
+    }
+    return s.trim().toLowerCase();
+  }
+
+  private static Set<String> getRequestorGroups(Configuration conf, String userName) throws SentryUserException {
+    return SentryPolicyStoreProcessor.getGroupsFromUserName(conf, userName);
+  }
+
+  private boolean inAdminGroups(Set<String> requestorGroups) {
+    return !Sets.intersection(adminGroups, requestorGroups).isEmpty();
+  }
+
+  static List<NotificationHandler> createHandlers(Configuration conf) throws SentrySiteConfigurationException {
+
+    List<NotificationHandler> handlers = Lists.newArrayList();
+    Iterable<String> notificationHandlers = Splitter.onPattern("[\\s,]").trimResults()
+        .omitEmptyStrings().split(conf.get(PolicyStoreConstants.SENTRY_GENERIC_POLICY_NOTIFICATION, ""));
+    try {
+      for (String notificationHandler : notificationHandlers) {
+        handlers.add(createInstance(notificationHandler, conf, NotificationHandler.class));
+      }
+    } catch (Exception e) {
+      throw new SentrySiteConfigurationException("Create notificationHandlers error: " + e.getMessage(), e);
+    }
+    return handlers;
+  }
+
+  @SuppressWarnings("unchecked")
+  private static <T> T createInstance(String className, Configuration conf, Class<T> iface) throws Exception {
+    T result;
+    try {
+      Class<?> clazz = Class.forName(className);
+      if (!iface.isAssignableFrom(clazz)) {
+        throw new IllegalArgumentException("Class " + clazz + " is not a " +
+                                                 iface.getName());
+      }
+      Constructor<T> meth = (Constructor<T>)clazz.getDeclaredConstructor(Configuration.class);
+      meth.setAccessible(true);
+      result = meth.newInstance(new Object[]{conf});
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+    return result;
+  }
+
+  private <T> Response<T> requestHandle(RequestHandler<T> handler) {
+    Response<T> response = new Response<T>();
+    try {
+      response = handler.handle();
+    } catch (SentryAccessDeniedException e) {
+      String msg = "Sentry access denied: " + e.getMessage();
+      LOGGER.error(msg, e);
+      response.status = Status.AccessDenied(e.getMessage(), e);
+    } catch (SentryAlreadyExistsException e) {
+      String msg = "Sentry object already exists: " + e.getMessage();
+      LOGGER.error(msg, e);
+      response.status = Status.AlreadyExists(e.getMessage(), e);
+    } catch (SentryNoSuchObjectException e) {
+      String msg = "Sentry object doesn't exist: " + e.getMessage();
+      LOGGER.error(msg, e);
+      response.status = Status.NoSuchObject(e.getMessage(), e);
+    } catch (SentryInvalidInputException e) {
+      String msg = "Invalid input privilege object: " + e.getMessage();
+      LOGGER.error(msg, e);
+      response.status = Status.InvalidInput(msg, e);
+    } catch (SentryThriftAPIMismatchException e) {
+      String msg = "Sentry thrift API mismatch error: " + e.getMessage();
+      LOGGER.error(msg, e);
+      response.status = Status.THRIFT_VERSION_MISMATCH(e.getMessage(), e);
+    } catch (Exception e) {
+      String msg = "Unknown error:" + e.getMessage();
+      LOGGER.error(msg, e);
+      response.status = Status.RuntimeError(msg, e);
+    }
+    return response;
+  }
+
+  private PrivilegeObject toPrivilegeObject(TSentryPrivilege tSentryPrivilege) {
+    Boolean grantOption;
+    if (tSentryPrivilege.getGrantOption().equals(TSentryGrantOption.TRUE)) {
+      grantOption = true;
+    } else if (tSentryPrivilege.getGrantOption().equals(TSentryGrantOption.FALSE)) {
+      grantOption = false;
+    } else {
+      grantOption = null;
+    }
+    return new Builder().setComponent(tSentryPrivilege.getComponent())
+                                             .setService(tSentryPrivilege.getServiceName())
+                                             .setAuthorizables(toAuthorizables(tSentryPrivilege.getAuthorizables()))
+                                             .setAction(tSentryPrivilege.getAction())
+                                             .withGrantOption(grantOption)
+                                             .build();
+  }
+
+  private TSentryPrivilege fromPrivilegeObject(PrivilegeObject privilege) {
+
+    TSentryPrivilege tPrivilege = new TSentryPrivilege(privilege.getComponent(), privilege.getService(),
+                                                       fromAuthorizable(privilege.getAuthorizables()),
+                                                       privilege.getAction());
+    if (privilege.getGrantOption() == null) {
+      tPrivilege.setGrantOption(TSentryGrantOption.UNSET);
+    } else if (privilege.getGrantOption()) {
+      tPrivilege.setGrantOption(TSentryGrantOption.TRUE);
+    } else {
+      tPrivilege.setGrantOption(TSentryGrantOption.FALSE);
+    }
+    return tPrivilege;
+  }
+
+  private List<TAuthorizable> fromAuthorizable(List<? extends Authorizable> authorizables) {
+    List<TAuthorizable> tAuthorizables = Lists.newArrayList();
+    for (Authorizable authorizable : authorizables) {
+      tAuthorizables.add(new TAuthorizable(authorizable.getTypeName(), authorizable.getName()));
+    }
+    return tAuthorizables;
+  }
+
+  private String fromAuthorizableToStr(List<? extends Authorizable> authorizables) {
+    if (authorizables != null && !authorizables.isEmpty()) {
+      List<String> privileges = Lists.newArrayList();
+
+      for (Authorizable authorizable : authorizables) {
+
+        privileges.add(SentryConstants.KV_JOINER.join(authorizable.getTypeName(),
+            authorizable.getName()));
+      }
+
+      return SentryConstants.AUTHORIZABLE_JOINER.join(privileges);
+    } else {
+      return "";
+    }
+  }
+
+  private List<? extends Authorizable> toAuthorizables(List<TAuthorizable> tAuthorizables) {
+    List<Authorizable> authorizables = Lists.newArrayList();
+    if (tAuthorizables == null) {
+      return authorizables;
+    }
+    for (final TAuthorizable tAuthorizable : tAuthorizables) {
+      authorizables.add(new Authorizable() {
+        @Override
+        public String getTypeName() {
+          return tAuthorizable.getType();
+        }
+        @Override
+        public String getName() {
+          return tAuthorizable.getName();
+        }
+      });
+    }
+    return authorizables;
+  }
+
+  private List<? extends Authorizable> toAuthorizables(String privilegeStr) {
+    List<Authorizable> authorizables = Lists.newArrayList();
+    if (privilegeStr == null) {
+      return authorizables;
+    }
+
+    for (String authorizable : SentryConstants.AUTHORIZABLE_SPLITTER.split(privilegeStr)) {
+      KeyValue tempKV = new KeyValue(authorizable);
+      final String key = tempKV.getKey();
+      final String value = tempKV.getValue();
+
+      authorizables.add(new Authorizable() {
+        @Override
+        public String getTypeName() {
+          return key;
+        }
+
+        @Override
+        public String getName() {
+          return value;
+        }
+      });
+    }
+
+    return authorizables;
+  }
+
+  // Construct the role to set of privileges mapping based on the
+  // MSentryGMPrivilege information.
+  private TSentryPrivilegeMap toTSentryPrivilegeMap(Set<MSentryGMPrivilege> mPrivileges) {
+
+    // Mapping of <Role, Set<Privilege>>.
+    Map<String, Set<TSentryPrivilege>> tPrivilegeMap = Maps.newTreeMap();
+
+    for (MSentryGMPrivilege mPrivilege : mPrivileges) {
+      for (MSentryRole role : mPrivilege.getRoles()) {
+
+        TSentryPrivilege tPrivilege = toTSentryPrivilege(mPrivilege);
+
+        if (tPrivilegeMap.containsKey(role.getRoleName())) {
+          tPrivilegeMap.get(role.getRoleName()).add(tPrivilege);
+        } else {
+          Set<TSentryPrivilege> tPrivilegeSet = Sets.newTreeSet();
+          tPrivilegeSet.add(tPrivilege);
+          tPrivilegeMap.put(role.getRoleName(), tPrivilegeSet);
+        }
+      }
+    }
+
+    return new TSentryPrivilegeMap(tPrivilegeMap);
+  }
+
+  // Construct TSentryPrivilege based on MSentryGMPrivilege information.
+  private TSentryPrivilege toTSentryPrivilege(MSentryGMPrivilege mPrivilege) {
+
+    TSentryPrivilege tPrivilege = new TSentryPrivilege(mPrivilege.getComponentName(),
+    mPrivilege.getServiceName(), fromAuthorizable(mPrivilege.getAuthorizables()), mPrivilege.getAction());
+
+    if (mPrivilege.getGrantOption() == null) {
+      tPrivilege.setGrantOption(TSentryGrantOption.UNSET);
+    } else if (mPrivilege.getGrantOption()) {
+      tPrivilege.setGrantOption(TSentryGrantOption.TRUE);
+    } else {
+      tPrivilege.setGrantOption(TSentryGrantOption.FALSE);
+    }
+
+    return tPrivilege;
+  }
+
+  private Set<String> buildPermissions(Set<PrivilegeObject> privileges) {
+    Set<String> permissions = Sets.newHashSet();
+    for (PrivilegeObject privilege : privileges) {
+      List<String> hierarchy = Lists.newArrayList();
+      if (hasComponentServerPrivilege(privilege.getComponent())) {
+        hierarchy.add(KV_JOINER.join("server", privilege.getService()));
+      }
+      for (Authorizable authorizable : privilege.getAuthorizables()) {
+        hierarchy.add(KV_JOINER.join(authorizable.getTypeName(),authorizable.getName()));
+      }
+      hierarchy.add(KV_JOINER.join("action", privilege.getAction()));
+      permissions.add(AUTHORIZABLE_JOINER.join(hierarchy));
+    }
+    return permissions;
+  }
+
+  private boolean hasComponentServerPrivilege(String component) {
+    //judge the component whether has the server privilege, for example: sqoop has the privilege on the server
+    return AuthorizationComponent.SQOOP.equalsIgnoreCase(component);
+  }
+
+  @Override
+  public TCreateSentryRoleResponse create_sentry_role(
+      final TCreateSentryRoleRequest request) throws TException {
+    Response<Void> respose = requestHandle(new RequestHandler<Void>() {
+      @Override
+      public Response<Void> handle() throws Exception {
+        validateClientVersion(request.getProtocol_version());
+        authorize(request.getRequestorUserName(),
+            getRequestorGroups(conf, request.getRequestorUserName()));
+        store.createRole(request.getComponent(), request.getRoleName(),
+                request.getRequestorUserName());
+        return new Response<Void>(Status.OK());
+      }
+    });
+
+    TCreateSentryRoleResponse tResponse = new TCreateSentryRoleResponse(respose.status);
+    if (Status.OK.getCode() == respose.status.getValue()) {
+      handerInvoker.create_sentry_role(request, tResponse);
+    }
+
+    try {
+      AUDIT_LOGGER.info(JsonLogEntityFactory.getInstance()
+        .createJsonLogEntity(request, tResponse, conf).toJsonFormatLog());
+    } catch (Exception e) {
+      // if any exception, log the exception.
+      String msg = "Error in creating audit log for create role: " + e.getMessage();
+      LOGGER.error(msg, e);
+    }
+    return tResponse;
+  }
+
+  @Override
+  public TDropSentryRoleResponse drop_sentry_role(final TDropSentryRoleRequest request)
+      throws TException {
+    Response<Void> respose = requestHandle(new RequestHandler<Void>() {
+      @Override
+      public Response<Void> handle() throws Exception {
+        validateClientVersion(request.getProtocol_version());
+        authorize(request.getRequestorUserName(),
+            getRequestorGroups(conf, request.getRequestorUserName()));
+        store.dropRole(request.getComponent(), request.getRoleName(),
+                request.getRequestorUserName());
+        return new Response<Void>(Status.OK());
+      }
+    });
+
+    TDropSentryRoleResponse tResponse = new TDropSentryRoleResponse(respose.status);
+    if (Status.OK.getCode() == respose.status.getValue()) {
+      handerInvoker.drop_sentry_role(request, tResponse);
+    }
+
+    try {
+      AUDIT_LOGGER.info(JsonLogEntityFactory.getInstance()
+        .createJsonLogEntity(request, tResponse, conf).toJsonFormatLog());
+    } catch (Exception e) {
+      // if any exception, log the exception.
+      String msg = "Error in creating audit log for drop role: " + e.getMessage();
+      LOGGER.error(msg, e);
+    }
+    return tResponse;
+  }
+
+  @Override
+  public TAlterSentryRoleGrantPrivilegeResponse alter_sentry_role_grant_privilege(
+      final TAlterSentryRoleGrantPrivilegeRequest request) throws TException {
+    Response<Void> respose = requestHandle(new RequestHandler<Void>() {
+      @Override
+      public Response<Void> handle() throws Exception {
+        validateClientVersion(request.getProtocol_version());
+        store.alterRoleGrantPrivilege(request.getComponent(),
+                request.getRoleName(),
+                toPrivilegeObject(request.getPrivilege()),
+                request.getRequestorUserName());
+       return new Response<Void>(Status.OK());
+      }
+    });
+
+    TAlterSentryRoleGrantPrivilegeResponse tResponse = new TAlterSentryRoleGrantPrivilegeResponse(respose.status);
+    if (Status.OK.getCode() == respose.status.getValue()) {
+      handerInvoker.alter_sentry_role_grant_privilege(request, tResponse);
+    }
+
+    try {
+      AUDIT_LOGGER.info(JsonLogEntityFactory.getInstance()
+        .createJsonLogEntity(request, tResponse, conf).toJsonFormatLog());
+    } catch (Exception e) {
+      // if any exception, log the exception.
+      String msg = "Error in creating audit log for grant privilege to role: " + e.getMessage();
+      LOGGER.error(msg, e);
+    }
+    return tResponse;
+  }
+
+  @Override
+  public TAlterSentryRoleRevokePrivilegeResponse alter_sentry_role_revoke_privilege(
+      final TAlterSentryRoleRevokePrivilegeRequest request) throws TException {
+    Response<Void> respose = requestHandle(new RequestHandler<Void>() {
+      @Override
+      public Response<Void> handle() throws Exception {
+        validateClientVersion(request.getProtocol_version());
+        store.alterRoleRevokePrivilege(request.getComponent(),
+                request.getRoleName(),
+                toPrivilegeObject(request.getPrivilege()),
+                request.getRequestorUserName());
+       return new Response<Void>(Status.OK());
+      }
+    });
+
+    TAlterSentryRoleRevokePrivilegeResponse tResponse =
+            new TAlterSentryRoleRevokePrivilegeResponse(respose.status);
+    if (Status.OK.getCode() == respose.status.getValue()) {
+      handerInvoker.alter_sentry_role_revoke_privilege(request, tResponse);
+    }
+
+    try {
+      AUDIT_LOGGER.info(JsonLogEntityFactory.getInstance()
+        .createJsonLogEntity(request, tResponse, conf).toJsonFormatLog());
+    } catch (Exception e) {
+      // if any exception, log the exception.
+      String msg = "Error in creating audit log for revoke privilege from role: " + e.getMessage();
+      LOGGER.error(msg, e);
+    }
+    return tResponse;
+  }
+
+  @Override
+  public TAlterSentryRoleAddGroupsResponse alter_sentry_role_add_groups(
+      final TAlterSentryRoleAddGroupsRequest request) throws TException {
+    Response<Void> respose = requestHandle(new RequestHandler<Void>() {
+      @Override
+      public Response<Void> handle() throws Exception {
+        validateClientVersion(request.getProtocol_version());
+        authorize(request.getRequestorUserName(),
+            getRequestorGroups(conf, request.getRequestorUserName()));
+        store.alterRoleAddGroups(request.getComponent(),
+                request.getRoleName(),
+                request.getGroups(),
+                request.getRequestorUserName());
+        return new Response<Void>(Status.OK());
+      }
+    });
+
+    TAlterSentryRoleAddGroupsResponse tResponse =
+            new TAlterSentryRoleAddGroupsResponse(respose.status);
+    if (Status.OK.getCode() == respose.status.getValue()) {
+      handerInvoker.alter_sentry_role_add_groups(request, tResponse);
+    }
+
+    try {
+      AUDIT_LOGGER.info(JsonLogEntityFactory.getInstance()
+        .createJsonLogEntity(request, tResponse, conf).toJsonFormatLog());
+    } catch (Exception e) {
+      // if any exception, log the exception.
+      String msg = "Error in creating audit log for add role to group: " + e.getMessage();
+      LOGGER.error(msg, e);
+    }
+    return tResponse;
+  }
+
+  @Override
+  public TAlterSentryRoleDeleteGroupsResponse alter_sentry_role_delete_groups(
+      final TAlterSentryRoleDeleteGroupsRequest request) throws TException {
+    Response<Void> respose = requestHandle(new RequestHandler<Void>() {
+      @Override
+      public Response<Void> handle() throws Exception {
+        validateClientVersion(request.getProtocol_version());
+        authorize(request.getRequestorUserName(),
+            getRequestorGroups(conf, request.getRequestorUserName()));
+        store.alterRoleDeleteGroups(request.getComponent(),
+                request.getRoleName(),
+                request.getGroups(),
+                request.getRequestorUserName());
+        return new Response<Void>(Status.OK());
+      }
+    });
+
+    TAlterSentryRoleDeleteGroupsResponse tResponse =
+            new TAlterSentryRoleDeleteGroupsResponse(respose.status);
+    if (Status.OK.getCode() == respose.status.getValue()) {
+      handerInvoker.alter_sentry_role_delete_groups(request, tResponse);
+    }
+
+    try {
+      AUDIT_LOGGER.info(JsonLogEntityFactory.getInstance()
+        .createJsonLogEntity(request, tResponse, conf).toJsonFormatLog());
+    } catch (Exception e) {
+      // if any exception, log the exception.
+      String msg = "Error in creating audit log for delete role from group: " +
+              e.getMessage();
+      LOGGER.error(msg, e);
+    }
+    return tResponse;
+  }
+
+  @Override
+  public TListSentryRolesResponse list_sentry_roles_by_group(
+      final TListSentryRolesRequest request) throws TException {
+    Response<Set<TSentryRole>> respose = requestHandle(new RequestHandler<Set<TSentryRole>>() {
+      @Override
+      public Response<Set<TSentryRole>> handle() throws Exception {
+        validateClientVersion(request.getProtocol_version());
+        Set<String> groups = getRequestorGroups(conf, request.getRequestorUserName());
+        if (!AccessConstants.ALL.equalsIgnoreCase(request.getGroupName())) {
+          boolean admin = inAdminGroups(groups);
+          //Only admin users can list all roles in the system ( groupname = null)
+          //Non admin users are only allowed to list only groups which they belong to
+          if(!admin && (request.getGroupName() == null || !groups.contains(request.getGroupName()))) {
+            throw new SentryAccessDeniedException(ACCESS_DENIAL_MESSAGE + request.getRequestorUserName());
+          }
+          groups.clear();
+          groups.add(request.getGroupName());
+        }
+
+        Set<String> roleNames = store.getRolesByGroups(request.getComponent(), groups);
+        Set<TSentryRole> tSentryRoles = Sets.newHashSet();
+        for (String roleName : roleNames) {
+          Set<String> groupsForRoleName = store.getGroupsByRoles(request.getComponent(), Sets.newHashSet(roleName));
+          tSentryRoles.add(new TSentryRole(roleName, groupsForRoleName));
+        }
+        return new Response<Set<TSentryRole>>(Status.OK(), tSentryRoles);
+      }
+    });
+    TListSentryRolesResponse tResponse = new TListSentryRolesResponse();
+    tResponse.setStatus(respose.status);
+    tResponse.setRoles(respose.content);
+    return tResponse;
+  }
+
+  @Override
+  public TListSentryPrivilegesResponse list_sentry_privileges_by_role(
+      final TListSentryPrivilegesRequest request) throws TException {
+    Response<Set<TSentryPrivilege>> respose = requestHandle(new RequestHandler<Set<TSentryPrivilege>>() {
+      @Override
+      public Response<Set<TSentryPrivilege>> handle() throws Exception {
+        validateClientVersion(request.getProtocol_version());
+        Set<String> groups = getRequestorGroups(conf, request.getRequestorUserName());
+        if (!inAdminGroups(groups)) {
+          Set<String> roleNamesForGroups = toTrimmedLower(store.getRolesByGroups(request.getComponent(), groups));
+          if (!roleNamesForGroups.contains(toTrimmedLower(request.getRoleName()))) {
+            throw new SentryAccessDeniedException(ACCESS_DENIAL_MESSAGE + request.getRequestorUserName());
+          }
+        }
+        Set<PrivilegeObject> privileges = store.getPrivilegesByProvider(request.getComponent(),
+                                                                        request.getServiceName(),
+                                                                        Sets.newHashSet(request.getRoleName()),
+                                                                        null, toAuthorizables(request.getAuthorizables()));
+        Set<TSentryPrivilege> tSentryPrivileges = Sets.newHashSet();
+        for (PrivilegeObject privilege : privileges) {
+          tSentryPrivileges.add(fromPrivilegeObject(privilege));
+        }
+        return new Response<Set<TSentryPrivilege>>(Status.OK(), tSentryPrivileges);
+      }
+    });
+    TListSentryPrivilegesResponse tResponse = new TListSentryPrivilegesResponse();
+    tResponse.setStatus(respose.status);
+    tResponse.setPrivileges(respose.content);
+    return tResponse;
+  }
+
+  @Override
+  public TListSentryPrivilegesForProviderResponse list_sentry_privileges_for_provider(
+      final TListSentryPrivilegesForProviderRequest request) throws TException {
+    Response<Set<String>> respose = requestHandle(new RequestHandler<Set<String>>() {
+      @Override
+      public Response<Set<String>> handle() throws Exception {
+        validateClientVersion(request.getProtocol_version());
+        Set<String> activeRoleNames = toTrimmedLower(request.getRoleSet().getRoles());
+        Set<String> roleNamesForGroups = store.getRolesByGroups(request.getComponent(), request.getGroups());
+        Set<String> rolesToQuery = request.getRoleSet().isAll() ? roleNamesForGroups : Sets.intersection(activeRoleNames, roleNamesForGroups);
+        Set<PrivilegeObject> privileges = store.getPrivilegesByProvider(request.getComponent(),
+                                                                        request.getServiceName(),
+                                                                        rolesToQuery, null,
+                                                                        toAuthorizables(request.getAuthorizables()));
+        return new Response<Set<String>>(Status.OK(), buildPermissions(privileges));
+      }
+    });
+    TListSentryPrivilegesForProviderResponse tResponse = new TListSentryPrivilegesForProviderResponse();
+    tResponse.setStatus(respose.status);
+    tResponse.setPrivileges(respose.content);
+    return tResponse;
+  }
+
+  @Override
+  public TListSentryPrivilegesByAuthResponse list_sentry_privileges_by_authorizable(TListSentryPrivilegesByAuthRequest request) throws TException {
+
+    TListSentryPrivilegesByAuthResponse response = new TListSentryPrivilegesByAuthResponse();
+    Map<String, TSentryPrivilegeMap> authRoleMap = Maps.newHashMap();
+
+    // Group names are case sensitive.
+    Set<String> requestedGroups = request.getGroups();
+    String subject = request.getRequestorUserName();
+    TSentryActiveRoleSet activeRoleSet = request.getRoleSet();
+    Set<String> validActiveRoles = Sets.newHashSet();
+
+    try {
+      validateClientVersion(request.getProtocol_version());
+      Set<String> memberGroups = getRequestorGroups(conf, subject);
+
+      // Disallow non-admin users to lookup groups that
+      // they are not part of.
+      if(!inAdminGroups(memberGroups)) {
+
+        if (requestedGroups != null && !requestedGroups.isEmpty()) {
+          for (String requestedGroup : requestedGroups) {
+
+            // If user doesn't belong to one of the requested groups,
+            // then raise security exception.
+            if (!memberGroups.contains(requestedGroup)) {
+              throw new SentryAccessDeniedException(ACCESS_DENIAL_MESSAGE + subject);
+            }
+          }
+        } else {
+          // Non-admin's search is limited to its own groups.
+          requestedGroups = memberGroups;
+        }
+
+        Set<String> grantedRoles = toTrimmedLower(store.getRolesByGroups(request.getComponent(), requestedGroups));
+
+        // If activeRoleSet is not null, disallow non-admin to lookup roles that they are not part of.
+        if (activeRoleSet != null && !activeRoleSet.isAll()) {
+
+          Set<String> activeRoleNames = toTrimmedLower(activeRoleSet.getRoles());
+          for (String activeRole : activeRoleNames) {
+            if (!grantedRoles.contains(activeRole)) {
+              throw new SentryAccessDeniedException(ACCESS_DENIAL_MESSAGE
+              + subject);
+            }
+          }
+
+          // For non-admin, valid active roles are intersection of active roles and granted roles.
+          validActiveRoles.addAll(activeRoleSet.isAll() ? grantedRoles : Sets.intersection(activeRoleNames, grantedRoles));
+        } else {
+          // For non-admin, if activeRoleSet is null, valid active roles would be the granted roles.
+          validActiveRoles.addAll(grantedRoles);
+        }
+      } else {
+        // For admin, if requestedGroups are empty, requested roles will be all roles.
+        Set<String> requestedRoles = toTrimmedLower(store.getAllRoleNames());
+        if (requestedGroups != null && !requestedGroups.isEmpty())  {
+          requestedRoles = toTrimmedLower(store.getRolesByGroups(request.getComponent(), requestedGroups));
+        }
+
+        // If activeRoleSet (which is optional) is not null, valid active role will be intersection
+        // of active roles and requested roles. Otherwise, valid active roles are the requested roles.
+        if (activeRoleSet != null && !activeRoleSet.isAll()) {
+          validActiveRoles.addAll(Sets.intersection(toTrimmedLower(activeRoleSet.getRoles()), requestedRoles));
+        } else {
+          validActiveRoles.addAll(requestedRoles);
+        }
+      }
+
+      // If user is not part of any group.. return empty response
+      if (request.getAuthorizablesSet() != null) {
+        for (String authorizablesStr : request.getAuthorizablesSet()) {
+
+          List<? extends Authorizable> authorizables = toAuthorizables(authorizablesStr);
+          Set<MSentryGMPrivilege> sentryPrivileges = store.getPrivilegesByAuthorizable(request.getComponent(), request.getServiceName(), validActiveRoles, authorizables);
+          authRoleMap.put(fromAuthorizableToStr(authorizables), toTSentryPrivilegeMap(sentryPrivileges));
+        }
+      }
+
+      response.setPrivilegesMapByAuth(authRoleMap);
+      response.setStatus(Status.OK());
+    } catch (SentryAccessDeniedException e) {
+      LOGGER.error(e.getMessage(), e);
+      response.setStatus(Status.AccessDenied(e.getMessage(), e));
+    } catch (SentryThriftAPIMismatchException e) {
+      LOGGER.error(e.getMessage(), e);
+      response.setStatus(Status.THRIFT_VERSION_MISMATCH(e.getMessage(), e));
+    } catch (Exception e) {
+      String msg = "Unknown error for request: " + request + ", message: "
+      + e.getMessage();
+      LOGGER.error(msg, e);
+      response.setStatus(Status.RuntimeError(msg, e));
+    }
+
+    return response;
+  }
+
+  @Override
+  public TDropPrivilegesResponse drop_sentry_privilege(
+      final TDropPrivilegesRequest request) throws TException {
+    Response<Void> respose = requestHandle(new RequestHandler<Void>() {
+      @Override
+      public Response<Void> handle() throws Exception {
+        validateClientVersion(request.getProtocol_version());
+        authorize(request.getRequestorUserName(),
+            getRequestorGroups(conf, request.getRequestorUserName()));
+        store.dropPrivilege(request.getComponent(),
+            toPrivilegeObject(request.getPrivilege()),
+            request.getRequestorUserName());
+        return new Response<Void>(Status.OK());
+      }
+    });
+
+    TDropPrivilegesResponse tResponse = new TDropPrivilegesResponse(respose.status);
+    if (Status.OK.getCode() == respose.status.getValue()) {
+      handerInvoker.drop_sentry_privilege(request, tResponse);
+    }
+    return tResponse;
+  }
+
+  @Override
+  public TRenamePrivilegesResponse rename_sentry_privilege(
+      final TRenamePrivilegesRequest request) throws TException {
+    Response<Void> respose = requestHandle(new RequestHandler<Void>() {
+      @Override
+      public Response<Void> handle() throws Exception {
+        validateClientVersion(request.getProtocol_version());
+        authorize(request.getRequestorUserName(),
+            getRequestorGroups(conf, request.getRequestorUserName()));
+        store.renamePrivilege(request.getComponent(), request.getServiceName(),
+                              toAuthorizables(request.getOldAuthorizables()),
+                              toAuthorizables(request.getNewAuthorizables()),
+                              request.getRequestorUserName());
+        return new Response<Void>(Status.OK());
+      }
+    });
+
+    TRenamePrivilegesResponse tResponse = new TRenamePrivilegesResponse(respose.status);
+    if (Status.OK.getCode() == respose.status.getValue()) {
+      handerInvoker.rename_sentry_privilege(request, tResponse);
+    }
+    return tResponse;
+  }
+
+  private static class Response<T> {
+    private TSentryResponseStatus status;
+    private T content;
+
+    Response() {
+    }
+
+    Response(TSentryResponseStatus status) {
+      this(status, null);
+    }
+
+    Response(TSentryResponseStatus status, T content) {
+      this.status = status;
+      this.content = content;
+    }
+  }
+  private interface RequestHandler <T>{
+    Response<T> handle() throws Exception ;
+  }
+
+  private static void validateClientVersion(int protocolVersion) throws SentryThriftAPIMismatchException {
+    if (ThriftConstants.TSENTRY_SERVICE_VERSION_CURRENT != protocolVersion) {
+      String msg = "Sentry thrift API protocol version mismatch: Client thrift version " +
+          "is: " + protocolVersion + " , server thrift version " +
+              "is " + ThriftConstants.TSENTRY_SERVICE_VERSION_CURRENT;
+      throw new SentryThriftAPIMismatchException(msg);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/sentry/blob/b97f5c7a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/generic/thrift/SentryGenericPolicyProcessorFactory.java
----------------------------------------------------------------------
diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/generic/thrift/SentryGenericPolicyProcessorFactory.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/generic/thrift/SentryGenericPolicyProcessorFactory.java
new file mode 100644
index 0000000..311b020
--- /dev/null
+++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/generic/thrift/SentryGenericPolicyProcessorFactory.java
@@ -0,0 +1,44 @@
+/**
+ * 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.api.generic.thrift;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.sentry.api.common.ApiConstants.SentryPolicyServiceConstants;
+import org.apache.sentry.provider.db.service.persistent.SentryStore;
+import org.apache.sentry.service.thrift.ProcessorFactory;
+import org.apache.thrift.TMultiplexedProcessor;
+import org.apache.thrift.TProcessor;
+
+public class SentryGenericPolicyProcessorFactory extends ProcessorFactory {
+
+  public SentryGenericPolicyProcessorFactory(Configuration conf) {
+    super(conf);
+  }
+
+  @Override
+  public boolean register(TMultiplexedProcessor multiplexedProcessor,
+                          SentryStore _) throws Exception {
+    SentryGenericPolicyProcessor processHandler = new SentryGenericPolicyProcessor(conf);
+    TProcessor processor = new SentryGenericPolicyProcessorWrapper<SentryGenericPolicyService.Iface>(
+        processHandler);
+    multiplexedProcessor.registerProcessor(
+        SentryPolicyServiceConstants.SENTRY_GENERIC_SERVICE_NAME, processor);
+    return true;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/sentry/blob/b97f5c7a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/ConfServlet.java
----------------------------------------------------------------------
diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/ConfServlet.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/ConfServlet.java
new file mode 100644
index 0000000..8625487
--- /dev/null
+++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/ConfServlet.java
@@ -0,0 +1,71 @@
+package org.apache.sentry.api.service.thrift;
+
+/**
+ * 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.
+ */
+
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.hadoop.conf.Configuration;
+
+import static org.apache.commons.lang.StringEscapeUtils.escapeHtml;
+
+/**
+ * Servlet to print out all sentry configuration.
+ */
+public class ConfServlet extends HttpServlet {
+  public static final String CONF_CONTEXT_ATTRIBUTE = "sentry.conf";
+  public static final String FORMAT_JSON = "json";
+  public static final String FORMAT_XML = "xml";
+  public static final String FORMAT_PARAM = "format";
+  private static final long serialVersionUID = 1L;
+
+  @Override
+  public void doGet(HttpServletRequest request, HttpServletResponse response)
+      throws ServletException, IOException {
+    String format = request.getParameter(FORMAT_PARAM);
+    if (format == null) {
+      format = FORMAT_XML;
+    }
+
+    if (FORMAT_XML.equals(format)) {
+      response.setContentType("text/xml; charset=utf-8");
+    } else if (FORMAT_JSON.equals(format)) {
+      response.setContentType("application/json; charset=utf-8");
+    }
+
+    Configuration conf = (Configuration)getServletContext().getAttribute(
+        CONF_CONTEXT_ATTRIBUTE);
+    assert conf != null;
+
+    Writer out = response.getWriter();
+    if (FORMAT_JSON.equals(format)) {
+      Configuration.dumpConfiguration(conf, out);
+    } else if (FORMAT_XML.equals(format)) {
+      conf.writeXml(out);
+    } else {
+      response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Bad format: " + escapeHtml(format));
+    }
+    out.close();
+  }
+}

http://git-wip-us.apache.org/repos/asf/sentry/blob/b97f5c7a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/LogLevelServlet.java
----------------------------------------------------------------------
diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/LogLevelServlet.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/LogLevelServlet.java
new file mode 100644
index 0000000..af81d6f
--- /dev/null
+++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/LogLevelServlet.java
@@ -0,0 +1,122 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.api.service.thrift;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import static org.apache.commons.lang.StringEscapeUtils.escapeHtml;
+
+public class LogLevelServlet extends HttpServlet {
+  private static final String LF = "\n";
+  private static final String BR = "<br />";
+  private static final String B_BR = "<b>%s</b><br />";
+  private static final String FORMS_HEAD =
+          "<h1>" + "Log Level" + "</h1>"
+                  + LF + BR + "<hr /><h3>Results</h3>"
+                  + LF + " Submitted Log Name: " + B_BR;
+  private static final String FORMS_CONTENT_GET =
+          LF + " Effective level: " + B_BR;
+  private static final String FORMS_CONTENT_SET =
+          LF + " Submitted Level: " + B_BR
+                  + LF + " Setting Level to %s" + BR
+                  + LF + " Effective level: " + B_BR;
+  private static final String FORMS_END =
+          LF + BR + "<hr /><h3>Get / Set</h3>"
+                  + LF + "<form>Log: <input type='text' size='50' name='log' /> "
+                  + "<input type='submit' value='Get Log Level' />" + "</form>"
+                  + LF + "<form>Log: <input type='text' size='50' name='log' /> "
+                  + "Level: <input type='text' name='level' /> "
+                  + "<input type='submit' value='Set Log Level' />" + "</form>";
+  private static final String FORMS_GET = FORMS_HEAD + FORMS_CONTENT_GET;
+  private static final String FORMS_SET = FORMS_HEAD + FORMS_CONTENT_SET;
+
+  /**
+   * Return parameter on servlet request for the given name
+   *
+   * @param request: Servlet request
+   * @param name: Name of parameter in servlet request
+   * @return Parameter in servlet request for the given name, return null if can't find parameter.
+   */
+  private String getParameter(ServletRequest request, String name) {
+    String s = request.getParameter(name);
+    if (s == null) {
+      return null;
+    }
+    s = s.trim();
+    return s.length() == 0 ? null : s;
+  }
+
+  /**
+   * Check the validity of the log level.
+   * @param level: The log level to be checked
+   * @return
+   *        true: The log level is valid
+   *        false: The log level is invalid
+   */
+  private boolean isLogLevelValid(String level) {
+    return level.equals(Level.toLevel(level).toString());
+  }
+
+  /**
+   * Parse the class name and log level in the http servlet request.
+   * If the request contains only class name, return the log level in the response message.
+   * If the request contains both class name and level, set the log level to the requested level
+   * and return the setting result in the response message.
+   */
+  @Override
+  public void doGet(HttpServletRequest request, HttpServletResponse response)
+          throws ServletException, IOException {
+    String logName = getParameter(request, "log");
+    String level = getParameter(request, "level");
+    response.setContentType("text/html;charset=utf-8");
+    response.setStatus(HttpServletResponse.SC_OK);
+    PrintWriter out = response.getWriter();
+
+    if (logName != null) {
+      Logger logInstance = LogManager.getLogger(logName);
+      if (level == null) {
+        out.write(String.format(FORMS_GET,
+                escapeHtml(logName),
+                logInstance.getEffectiveLevel().toString()));
+      } else if (isLogLevelValid(level)) {
+        logInstance.setLevel(Level.toLevel(level));
+        out.write(String.format(FORMS_SET,
+                escapeHtml(logName),
+                escapeHtml(level),
+                escapeHtml(level),
+                logInstance.getEffectiveLevel().toString()));
+      } else {
+        response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid log level: " + escapeHtml(level));
+        return;
+      }
+    }
+    out.write(FORMS_END);
+    out.close();
+    response.flushBuffer();
+  }
+}

http://git-wip-us.apache.org/repos/asf/sentry/blob/b97f5c7a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/PubSubServlet.java
----------------------------------------------------------------------
diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/PubSubServlet.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/PubSubServlet.java
new file mode 100644
index 0000000..8da35f1
--- /dev/null
+++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/PubSubServlet.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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.api.service.thrift;
+
+import org.apache.sentry.core.common.utils.PubSub;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import static org.apache.commons.lang.StringEscapeUtils.escapeHtml;
+
+/**
+ * This servlet facilitates sending {topic, message } tuples to Servlet components 
+ * subscribed to specific topics.
+ * <p>
+ * It uses publish-subscribe mechanism implemented by PubSub class.
+ * The form generated by this servlet consists of the following elements:
+ * <p>
+ * a) Topic: pull-down menu of existing topics, i.e. the topics registered with
+ * PubSub by calling PubSub.subscribe() API. This prevents entering invalid topic.
+ * <p>
+ * b) Message: text field for entering a message
+ * <p>
+ * c) Submit: button to submit (topic, message) tuple
+ * <p>
+ * d) Status: text area printing status of the request or help information.
+ */
+public class PubSubServlet extends HttpServlet {
+
+  private static final Logger LOGGER = LoggerFactory.getLogger(PubSubServlet.class);
+
+  private static final String FORM_GET =
+    "<!DOCTYPE html>" +
+    "<html>" +
+    "<body>" +
+    "<form>" +
+    "<br><br><b>Topic:</b><br><br>" +
+    "<select name='topic'/>%s</select>" +
+    "<br><br><b>Message:</b><br><br>" +
+    "<input type='text' size='50' name='message'/>" +
+    "<br><br>" +
+    "<input type='submit' value='Submit'/>" +
+    "</form>" +
+    "<br><br><b>Status:</b><br><br>" +
+    "<textarea rows='4' cols='50'>%s</textarea>" +
+    "</body>" +
+    "</html>";
+
+  /**
+   * Return parameter on servlet request for the given name
+   *
+   * @param request: Servlet request
+   * @param name: Name of parameter in servlet request
+   * @return Parameter in servlet request for the given name, return null if can't find parameter.
+   */
+  private static String getParameter(ServletRequest request, String name) {
+    String s = request.getParameter(name);
+    if (s == null) {
+      return null;
+    }
+    s = s.trim();
+    return s.isEmpty() ? null : s;
+  }
+
+  /**
+   * Parse the topic and message values and submit them via PubSub.submit() API.
+   * Reject request for unknown topic, i.e. topic no one is subscribed to.
+   */
+  @Override
+  public void doGet(HttpServletRequest request, HttpServletResponse response)
+          throws ServletException, IOException {
+    String topic = getParameter(request, "topic");
+    String message = getParameter(request, "message");
+    response.setContentType("text/html;charset=utf-8");
+    response.setStatus(HttpServletResponse.SC_OK);
+    PrintWriter out = response.getWriter();
+
+    String msg = "Topic is required, Message is optional.\nValid topics: " + PubSub.getInstance().getTopics();
+    if (topic != null) {
+      LOGGER.info("Submitting topic " + topic + ", message " + message);
+      try {
+        PubSub.getInstance().publish(PubSub.Topic.fromString(topic), message);
+        msg = "Submitted topic " + topic + ", message " + message;
+      } catch (Exception e) {
+        msg = "Failed to submit topic " + topic + ", message " + message + " - " + e.getMessage();
+        LOGGER.error(msg);
+        response.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
+        return;
+      }
+    }
+
+    StringBuilder topics = new StringBuilder();
+    for (PubSub.Topic t : PubSub.getInstance().getTopics()) {
+      topics.append("<option>").append(t.getName()).append("</option>");
+    }
+
+    String output = String.format(FORM_GET, topics.toString(), escapeHtml(msg));
+    if (LOGGER.isDebugEnabled()) {
+      LOGGER.debug("HTML Page: " + output);
+    }
+    out.write(output);
+    out.close();
+    response.flushBuffer();
+  }
+}

http://git-wip-us.apache.org/repos/asf/sentry/blob/b97f5c7a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryAdminServlet.java
----------------------------------------------------------------------
diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryAdminServlet.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryAdminServlet.java
new file mode 100644
index 0000000..5dc6cd6
--- /dev/null
+++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryAdminServlet.java
@@ -0,0 +1,132 @@
+/**
+ * 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.api.service.thrift;
+
+import com.google.gson.Gson;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.sentry.provider.db.service.persistent.SentryStore;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Admin Servlet is only used when SENTRY_WEB_ADMIN_SERVLET_ENABLED is true.
+ */
+public class SentryAdminServlet extends HttpServlet {
+  private static final String SHOW_ALL = "/showAll";
+  // Here we use the same way as in com.codahale.metrics.servlets.AdminServlet, and just
+  // use the TEMPLATE as a static html with some links referenced to other debug pages.
+  private static final String TEMPLATE = "<!DOCTYPE HTML>\n"+
+      "<html lang=\"en\">\n"+
+      "<head>\n"+
+      "    <meta charset=\"utf-8\">\n"+
+      "    <title>Sentry Service Admin</title>\n"+
+      "    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"+
+      "    <meta name=\"description\" content=\"\">\n"+
+      "    <link href=\"css/bootstrap.min.css\" rel=\"stylesheet\">\n"+
+      "    <link href=\"css/bootstrap-theme.min.css\" rel=\"stylesheet\">\n"+
+      "    <link href=\"css/sentry.css\" rel=\"stylesheet\">\n"+
+      "</head>\n"+
+      "<body>\n"+
+      "<nav class=\"navbar navbar-default navbar-fixed-top\">\n"+
+      "    <div class=\"container\">\n"+
+      "        <div class=\"navbar-header\">\n"+
+      "            <a class=\"navbar-brand\" href=\"#\"><img src=\"sentry.png\" alt=\"Sentry Logo\"/></a>\n"+
+      "        </div>\n"+
+      "        <div class=\"collapse navbar-collapse\">\n"+
+      "            <ul class=\"nav navbar-nav\">\n"+
+      "                <li class=\"active\"><a href=\"#\">Admin</a></li>\n"+
+      "                <li><a href=\"/metrics?pretty=true\">Metrics</a></li>\n"+
+      "                <li><a href=\"/threads\">Threads</a></li>\n"+
+      "                <li><a href=\"/conf\">Configuration</a></li>\n"+
+      "                <li><a href=\"/admin/showAll\">ShowAllRoles</a></li>\n"+
+      "            </ul>\n"+
+      "        </div>\n"+
+      "    </div>\n"+
+      "</nav>\n"+
+      "<div class=\"container\">\n"+
+      "    <ul>\n"+
+      "        <li><a href=\"/metrics?pretty=true\">Metrics</a></li>\n"+
+      "        <li><a href=\"/threads\">Threads</a></li>\n"+
+      "        <li><a href=\"/conf\">Configuration</a></li>\n"+
+      "        <li><a href=\"/admin/showAll\">ShowAllRoles</a></li>\n"+
+      "    </ul>\n"+
+      "</div>\n"+
+      "</body>\n"+
+      "</html>";
+
+  @Override
+  public void doGet(HttpServletRequest request, HttpServletResponse response)
+      throws ServletException, IOException {
+    String uri = request.getPathInfo();
+    if(uri != null && !uri.equals("/")) {
+      if (uri.equals(SHOW_ALL)) {
+        showAll(response);
+      } else {
+        response.sendError(404);
+      }
+    } else {
+      response.setStatus(200);
+      response.setHeader("Cache-Control", "must-revalidate,no-cache,no-store");
+      response.setHeader("Pragma", "no-cache");
+      response.setDateHeader("Expires", 0);
+      response.setContentType("text/html");
+      PrintWriter writer = response.getWriter();
+      try {
+        writer.println(TEMPLATE);
+      } finally {
+        writer.close();
+      }
+    }
+  }
+
+  /**
+   * Print out all the roles and privileges information as json format.
+   */
+  private void showAll(HttpServletResponse response)
+      throws ServletException, IOException {
+    Configuration conf = (Configuration)getServletContext().getAttribute(
+        ConfServlet.CONF_CONTEXT_ATTRIBUTE);
+    assert conf != null;
+
+    Writer out = response.getWriter();
+    try {
+      SentryStore sentrystore = new SentryStore(conf);
+      Map<String, Set<TSentryPrivilege>> roleMap = new HashMap<>();
+      Set<String> roleSet = sentrystore.getAllRoleNames();
+      for (String roleName: roleSet) {
+        roleMap.put(roleName, sentrystore.getAllTSentryPrivilegesByRoleName(roleName));
+      }
+      String json = new Gson().toJson(roleMap);
+      response.setContentType("application/json");
+      response.setCharacterEncoding("UTF-8");
+      out.write(json);
+    } catch (Exception e) {
+      response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
+    }
+    out.close();
+  }
+}

http://git-wip-us.apache.org/repos/asf/sentry/blob/b97f5c7a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryAuthFilter.java
----------------------------------------------------------------------
diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryAuthFilter.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryAuthFilter.java
new file mode 100644
index 0000000..23121ec
--- /dev/null
+++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryAuthFilter.java
@@ -0,0 +1,89 @@
+/**
+ * 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.api.service.thrift;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
+import org.apache.hadoop.util.StringUtils;
+import org.apache.sentry.service.common.ServiceConstants.ServerConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Sets;
+
+/**
+ * SentryAuthFilter is a subclass of AuthenticationFilter,
+ * add authorization: Only allowed users could connect the web server.
+ */
+public class SentryAuthFilter extends AuthenticationFilter {
+
+  private static final Logger LOG = LoggerFactory.getLogger(SentryAuthFilter.class);
+
+  public static final String ALLOW_WEB_CONNECT_USERS = ServerConfig.SENTRY_WEB_SECURITY_ALLOW_CONNECT_USERS;
+
+  private Set<String> allowUsers;
+
+  @Override
+  protected void doFilter(FilterChain filterChain, HttpServletRequest request,
+      HttpServletResponse response) throws IOException, ServletException {
+    String userName = request.getRemoteUser();
+    LOG.debug("Authenticating user: " + userName + " from request.");
+    if (!allowUsers.contains(userName)) {
+      response.sendError(HttpServletResponse.SC_FORBIDDEN,
+          "Unauthorized user status code: " + HttpServletResponse.SC_FORBIDDEN);
+      throw new ServletException(userName + " is unauthorized. status code: " + HttpServletResponse.SC_FORBIDDEN);
+    }
+    super.doFilter(filterChain, request, response);
+  }
+
+  /**
+   * Override <code>getConfiguration<code> to get <code>ALLOW_WEB_CONNECT_USERS<code>.
+   */
+  @Override
+  protected Properties getConfiguration(String configPrefix, FilterConfig filterConfig) throws ServletException {
+    Properties props = new Properties();
+    Enumeration<?> names = filterConfig.getInitParameterNames();
+    while (names.hasMoreElements()) {
+      String name = (String) names.nextElement();
+      if (name.startsWith(configPrefix)) {
+        String value = filterConfig.getInitParameter(name);
+        if (ALLOW_WEB_CONNECT_USERS.equals(name)) {
+          allowUsers = parseConnectUsersFromConf(value);
+        } else {
+          props.put(name.substring(configPrefix.length()), value);
+        }
+      }
+    }
+    return props;
+  }
+
+  private static Set<String> parseConnectUsersFromConf(String value) {
+    //Removed the logic to convert the allowed users to lower case, as user names need to be case sensitive
+    return Sets.newHashSet(StringUtils.getStrings(value));
+  }
+}

http://git-wip-us.apache.org/repos/asf/sentry/blob/b97f5c7a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryHealthCheckServletContextListener.java
----------------------------------------------------------------------
diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryHealthCheckServletContextListener.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryHealthCheckServletContextListener.java
new file mode 100644
index 0000000..eb11c19
--- /dev/null
+++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryHealthCheckServletContextListener.java
@@ -0,0 +1,35 @@
+/**
+ * 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.api.service.thrift;
+
+import com.codahale.metrics.health.HealthCheckRegistry;
+import com.codahale.metrics.servlets.HealthCheckServlet;
+
+/**
+ * Use this class's registry to register health checks: Can be some tests which make sure Sentry service is healthy
+ */
+public class SentryHealthCheckServletContextListener extends HealthCheckServlet.ContextListener {
+
+  //This is just a place holder for health check registry, with out this AdminServlet throws out an error
+  public static final HealthCheckRegistry HEALTH_CHECK_REGISTRY = new HealthCheckRegistry();
+
+  @Override
+  protected HealthCheckRegistry getHealthCheckRegistry() {
+    return HEALTH_CHECK_REGISTRY;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/sentry/blob/b97f5c7a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryMetrics.java
----------------------------------------------------------------------
diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryMetrics.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryMetrics.java
new file mode 100644
index 0000000..80a6343
--- /dev/null
+++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryMetrics.java
@@ -0,0 +1,413 @@
+/*
+ * 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.api.service.thrift;
+
+import com.codahale.metrics.ConsoleReporter;
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.JmxReporter;
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.MetricSet;
+import com.codahale.metrics.Slf4jReporter;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.json.MetricsModule;
+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 com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.sentry.provider.db.service.persistent.SentryStore;
+import org.apache.sentry.service.thrift.SentryService;
+import org.apache.sentry.api.common.SentryServiceUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static com.codahale.metrics.MetricRegistry.name;
+import static org.apache.sentry.api.service.thrift.SentryMetricsServletContextListener.METRIC_REGISTRY;
+import static org.apache.sentry.service.common.ServiceConstants.ServerConfig;
+
+/**
+ * A singleton class which holds metrics related utility functions as well as the list of metrics.
+ */
+public final class SentryMetrics {
+  public enum Reporting {
+    JMX,
+    CONSOLE,
+    LOG,
+    JSON,
+  }
+
+  private static final Logger LOGGER = LoggerFactory
+          .getLogger(SentryMetrics.class);
+
+  private static SentryMetrics sentryMetrics = null;
+  private final AtomicBoolean reportingInitialized = new AtomicBoolean();
+  private boolean gaugesAdded = false;
+  private boolean sentryServiceGaugesAdded = false;
+
+  final Timer createRoleTimer = METRIC_REGISTRY.timer(
+      name(SentryPolicyStoreProcessor.class, "create-role"));
+  final Timer dropRoleTimer = METRIC_REGISTRY.timer(
+      name(SentryPolicyStoreProcessor.class, "drop-role"));
+  final Timer grantRoleTimer = METRIC_REGISTRY.timer(
+      name(SentryPolicyStoreProcessor.class, "grant-role"));
+  final Timer revokeRoleTimer = METRIC_REGISTRY.timer(
+      name(SentryPolicyStoreProcessor.class, "revoke-role"));
+  final Timer grantTimer = METRIC_REGISTRY.timer(
+      name(SentryPolicyStoreProcessor.class, "grant-privilege"));
+  final Timer revokeTimer = METRIC_REGISTRY.timer(
+      name(SentryPolicyStoreProcessor.class, "revoke-privilege"));
+
+  final Timer dropPrivilegeTimer = METRIC_REGISTRY.timer(
+      name(SentryPolicyStoreProcessor.class, "drop-privilege"));
+  final Timer renamePrivilegeTimer = METRIC_REGISTRY.timer(
+      name(SentryPolicyStoreProcessor.class, "rename-privilege"));
+
+  final Timer listRolesByGroupTimer = METRIC_REGISTRY.timer(
+      name(SentryPolicyStoreProcessor.class, "list-roles-by-group"));
+  final Timer listPrivilegesByRoleTimer = METRIC_REGISTRY.timer(
+      name(SentryPolicyStoreProcessor.class, "list-privileges-by-role"));
+  final Timer listPrivilegesForProviderTimer = METRIC_REGISTRY.timer(
+      name(SentryPolicyStoreProcessor.class, "list-privileges-for-provider"));
+  final Timer listPrivilegesByAuthorizableTimer = METRIC_REGISTRY.timer(
+      name(SentryPolicyStoreProcessor.class, "list-privileges-by-authorizable"));
+
+  /**
+   * Return a Timer with name.
+   */
+  public Timer getTimer(String name) {
+    return METRIC_REGISTRY.timer(name);
+  }
+
+  /**
+   * Return a Histogram with name.
+   */
+  public Histogram getHistogram(String name) {
+    return METRIC_REGISTRY.histogram(name);
+  }
+
+  /**
+   * Return a Counter with name.
+   */
+  public Counter getCounter(String name) {
+    return METRIC_REGISTRY.counter(name);
+  }
+
+  private SentryMetrics() {
+    registerMetricSet("gc", new GarbageCollectorMetricSet(), METRIC_REGISTRY);
+    registerMetricSet("buffers",
+            new BufferPoolMetricSet(ManagementFactory.getPlatformMBeanServer()),
+        METRIC_REGISTRY);
+    registerMetricSet("memory", new MemoryUsageGaugeSet(), METRIC_REGISTRY);
+    registerMetricSet("threads", new ThreadStatesGaugeSet(), METRIC_REGISTRY);
+  }
+
+  /**
+   * Get singleton instance.
+   */
+  public static synchronized SentryMetrics getInstance() {
+    if (sentryMetrics == null) {
+      sentryMetrics = new SentryMetrics();
+    }
+    return sentryMetrics;
+  }
+
+  void addSentryStoreGauges(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, "hms.waiters", sentryStore.getHMSWaitersCountGauge());
+      addGauge(SentryStore.class, "hms.notification.id",
+          sentryStore.getLastNotificationIdGauge());
+      addGauge(SentryStore.class, "hms.snapshot.paths.id",
+          sentryStore.getLastPathsSnapshotIdGauge());
+      addGauge(SentryStore.class, "hms.perm.change.id",
+          sentryStore.getPermChangeIdGauge());
+      addGauge(SentryStore.class, "hms.psth.change.id",
+          sentryStore.getPathChangeIdGauge());
+      gaugesAdded = true;
+    }
+  }
+
+  /**
+   * Add gauges for the SentryService class.
+   * @param sentryservice
+   */
+  public void addSentryServiceGauges(SentryService sentryservice) {
+    if (!sentryServiceGaugesAdded) {
+      addGauge(SentryService.class, "is_active", sentryservice.getIsActiveGauge());
+      addGauge(SentryService.class, "activated", sentryservice.getBecomeActiveCount());
+      sentryServiceGaugesAdded = true;
+    }
+  }
+
+  /**
+   * Initialize reporters. Only initializes once.<p>
+   *
+   * Available reporters:
+   * <ul>
+   *     <li>console</li>
+   *     <li>log</li>
+   *     <li>jmx</li>
+   * </ul>
+   *
+   * <p><For console reporter configre it to report every
+   * <em>SENTRY_REPORTER_INTERVAL_SEC</em> seconds.
+   *
+   * <p>Method is thread safe.
+   */
+  @SuppressWarnings("squid:S2095")
+  void initReporting(Configuration conf) {
+    final String reporter = conf.get(ServerConfig.SENTRY_REPORTER);
+    if ((reporter == null) || reporter.isEmpty() || reportingInitialized.getAndSet(true)) {
+      // Nothing to do, just return
+      return;
+    }
+
+    final int reportInterval =
+            conf.getInt(ServerConfig.SENTRY_REPORTER_INTERVAL_SEC,
+                    ServerConfig.SENTRY_REPORTER_INTERVAL_DEFAULT);
+
+    // Get list of configured reporters
+    Set<String> reporters = new HashSet<>();
+    for (String r: reporter.split(",")) {
+      reporters.add(r.trim().toUpperCase());
+    }
+
+    // In case there are no reporters, configure JSON reporter
+    if (reporters.isEmpty()) {
+      reporters.add(Reporting.JSON.toString());
+    }
+
+    // Configure all reporters
+    for (String r: reporters) {
+      switch (SentryMetrics.Reporting.valueOf(r)) {
+        case CONSOLE:
+          LOGGER.info("Enabled console metrics reporter with {} seconds interval",
+                  reportInterval);
+          final ConsoleReporter consoleReporter =
+                  ConsoleReporter.forRegistry(METRIC_REGISTRY)
+                          .convertRatesTo(TimeUnit.SECONDS)
+                          .convertDurationsTo(TimeUnit.MILLISECONDS)
+                          .build();
+          consoleReporter.start(reportInterval, TimeUnit.SECONDS);
+          break;
+        case JMX:
+          LOGGER.info("Enabled JMX metrics reporter");
+          final JmxReporter jmxReporter = JmxReporter.forRegistry(METRIC_REGISTRY)
+                  .convertRatesTo(TimeUnit.SECONDS)
+                  .convertDurationsTo(TimeUnit.MILLISECONDS)
+                  .build();
+          jmxReporter.start();
+          break;
+        case LOG:
+          LOGGER.info("Enabled Log4J metrics reporter with {} seconds interval",
+                  reportInterval);
+          final Slf4jReporter logReporter = Slf4jReporter.forRegistry(METRIC_REGISTRY)
+                  .outputTo(LOGGER)
+                  .convertRatesTo(TimeUnit.SECONDS)
+                  .convertDurationsTo(TimeUnit.MILLISECONDS)
+                  .build();
+          logReporter.start(reportInterval, TimeUnit.SECONDS);
+          break;
+        case JSON:
+          LOGGER.info("Enabled JSON metrics reporter with {} seconds interval", reportInterval);
+          JsonFileReporter jsonReporter = new JsonFileReporter(conf,
+                  reportInterval, TimeUnit.SECONDS);
+          jsonReporter.start();
+          break;
+        default:
+          LOGGER.warn("Invalid metrics reporter {}", reporter);
+          break;
+      }
+    }
+  }
+
+  private <T, V> void addGauge(Class<T> tClass, String gaugeName, Gauge<V> gauge) {
+    METRIC_REGISTRY.register(
+        name(tClass, gaugeName), gauge);
+  }
+
+  private void registerMetricSet(String prefix, MetricSet metricSet, MetricRegistry registry) {
+    for (Map.Entry<String, Metric> entry : metricSet.getMetrics().entrySet()) {
+      if (entry.getValue() instanceof MetricSet) {
+        registerMetricSet(prefix + "." + entry.getKey(), (MetricSet) entry.getValue(), registry);
+      } else {
+        registry.register(prefix + "." + entry.getKey(), entry.getValue());
+      }
+    }
+  }
+
+  /**
+   * Custom reporter that writes metrics as a JSON file.
+   * This class originated from Apache Hive JSON reporter.
+   */
+  private static class JsonFileReporter implements AutoCloseable, Runnable {
+    //
+    // Implementation notes.
+    //
+    // 1. Since only local file systems are supported, there is no need to use Hadoop
+    //    version of Path class.
+    // 2. java.nio package provides modern implementation of file and directory operations
+    //    which is better then the traditional java.io, so we are using it here.
+    //    In particular, it supports atomic creation of temporary files with specified
+    //    permissions in the specified directory. This also avoids various attacks possible
+    //    when temp file name is generated first, followed by file creation.
+    //    See http://www.oracle.com/technetwork/articles/javase/nio-139333.html for
+    //    the description of NIO API and
+    //    http://docs.oracle.com/javase/tutorial/essential/io/legacy.html for the
+    //    description of interoperability between legacy IO api vs NIO API.
+    // 3. To avoid race conditions with readers of the metrics file, the implementation
+    //    dumps metrics to a temporary file in the same directory as the actual metrics
+    //    file and then renames it to the destination. Since both are located on the same
+    //    filesystem, this rename is likely to be atomic (as long as the underlying OS
+    //    support atomic renames.
+    //
+
+    // Permissions for the metrics file
+    private static final FileAttribute<Set<PosixFilePermission>> FILE_ATTRS =
+            PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rw-r--r--"));
+    private static final String JSON_REPORTER_THREAD_NAME = "json-reporter";
+
+    private ScheduledExecutorService executor = null;
+    private final ObjectMapper jsonMapper =
+            new ObjectMapper().registerModule(new MetricsModule(TimeUnit.SECONDS,
+                    TimeUnit.MILLISECONDS,
+                    false));
+    private final Configuration conf;
+    /** Destination file name. */
+    // Location of JSON file
+    private final Path path;
+    // tmpdir is the dirname(path)
+    private final Path tmpDir;
+    private final long interval;
+    private final TimeUnit unit;
+
+    JsonFileReporter(Configuration conf, long interval, TimeUnit unit) {
+      this.conf = conf;
+      String pathString = conf.get(ServerConfig.SENTRY_JSON_REPORTER_FILE,
+              ServerConfig.SENTRY_JSON_REPORTER_FILE_DEFAULT);
+      path = Paths.get(pathString).toAbsolutePath();
+      LOGGER.info("Reporting metrics to {}", path);
+      // We want to use tmpDir i the same directory as the destination file to support atomic
+      // move of temp file to the destination metrics file
+      tmpDir = path.getParent();
+      this.interval = interval;
+      this.unit = unit;
+    }
+
+    private void start() {
+      executor = Executors.newScheduledThreadPool(1,
+              new ThreadFactoryBuilder().setNameFormat(JSON_REPORTER_THREAD_NAME).build());
+      executor.scheduleAtFixedRate(this, 0, interval, unit);
+    }
+
+    @Override
+    public void run() {
+      Path tmpFile = null;
+      try {
+        String json = null;
+        try {
+          json = jsonMapper.writerWithDefaultPrettyPrinter().writeValueAsString(METRIC_REGISTRY);
+        } catch (JsonProcessingException e) {
+          LOGGER.error("Error converting metrics to JSON", e);
+          return;
+        }
+        // Metrics are first dumped to a temp file which is then renamed to the destination
+        try {
+          tmpFile = Files.createTempFile(tmpDir, "smetrics", "json", FILE_ATTRS);
+        } catch (IOException e) {
+          LOGGER.error("failed to create temp file for JSON metrics", e);
+          return;
+        } catch (SecurityException e) {
+          // This shouldn't ever happen
+          LOGGER.error("failed to create temp file for JSON metrics: no permissions", e);
+          return;
+        } catch (UnsupportedOperationException e) {
+          // This shouldn't ever happen
+          LOGGER.error("failed to create temp file for JSON metrics: operartion not supported", e);
+          return;
+        }
+
+        try (BufferedWriter bw = new BufferedWriter(new FileWriter(tmpFile.toFile()))) {
+          bw.write(json);
+        }
+
+        // Move temp file to the destination file
+        try {
+          Files.move(tmpFile, path, StandardCopyOption.ATOMIC_MOVE);
+        } catch (Exception e) {
+          LOGGER.error("Failed to move temp metrics file to {}: {}", path, e.getMessage());
+        }
+      } catch (Throwable t) {
+        // catch all errors (throwable and execptions to prevent subsequent tasks from being suppressed)
+        LOGGER.error("Error executing scheduled task ", t);
+      } finally {
+        // If something happened and we were not able to rename the temp file, attempt to remove it
+        if (tmpFile != null && tmpFile.toFile().exists()) {
+          // Attempt to delete temp file, if this fails, not much can be done about it.
+          try {
+            Files.delete(tmpFile);
+          } catch (Exception e) {
+            LOGGER.error("failed to delete yemporary metrics file {}", tmpFile, e);
+          }
+        }
+      }
+    }
+
+    @Override
+    public void close() {
+      if (executor != null) {
+        SentryServiceUtil.shutdownAndAwaitTermination(executor,
+                JSON_REPORTER_THREAD_NAME, 1, TimeUnit.MINUTES, LOGGER);
+        executor = null;
+      }
+      try {
+        Files.delete(path);
+      } catch (IOException e) {
+        LOGGER.error("Unable to delete {}", path, e);
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/sentry/blob/b97f5c7a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryMetricsServletContextListener.java
----------------------------------------------------------------------
diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryMetricsServletContextListener.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryMetricsServletContextListener.java
new file mode 100644
index 0000000..253e54f
--- /dev/null
+++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/api/service/thrift/SentryMetricsServletContextListener.java
@@ -0,0 +1,32 @@
+/**
+ * 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.api.service.thrift;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.servlets.MetricsServlet;
+
+public class SentryMetricsServletContextListener extends MetricsServlet.ContextListener {
+
+  public static final MetricRegistry METRIC_REGISTRY = new MetricRegistry();
+
+  @Override
+  protected MetricRegistry getMetricRegistry() {
+    return METRIC_REGISTRY;
+  }
+
+}


Mime
View raw message