DRILL-6019: Only admin should be able to access shutdown resources and information about process and admin users

1. Only admin should be able to access shutdown resources via REST API.
2. Modified ClusterInfo to show information about process and admin users only when user is logged in and is admin.
3. Added drillbits comparison based on thier adress and ports to check if drillbits are the same (DRILL-6006)

closes #1065
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/DrillRoot.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/DrillRoot.java
index 02f2731..ca6d748 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/DrillRoot.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/DrillRoot.java
@@ -21,6 +21,7 @@
 import java.util.HashMap;
 import java.util.Map;
 import javax.annotation.security.PermitAll;
+import javax.annotation.security.RolesAllowed;
 import javax.inject.Inject;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
@@ -31,6 +32,7 @@
 import javax.ws.rs.core.SecurityContext;
 import javax.xml.bind.annotation.XmlRootElement;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
 import com.google.common.base.Joiner;
 import com.google.common.base.Strings;
 import com.google.common.collect.Sets;
@@ -55,6 +57,8 @@
 
 import com.fasterxml.jackson.annotation.JsonCreator;
 
+import static org.apache.drill.exec.server.rest.auth.DrillUserPrincipal.ADMIN_ROLE;
+
 @Path("/")
 @PermitAll
 public class DrillRoot {
@@ -69,10 +73,6 @@
   @Inject
   Drillbit drillbit;
 
-  public enum ShutdownMode {
-    forcefulShutdown, gracefulShutdown, quiescent
-  }
-
   @GET
   @Produces(MediaType.TEXT_HTML)
   public Viewable getClusterInfo() {
@@ -90,8 +90,7 @@
     for (DrillbitInfo drillbit : drillbits) {
       drillStatusMap.put(drillbit.getAddress() + "-" + drillbit.getUserPort(), drillbit.getState());
     }
-    Response response = setResponse(drillStatusMap);
-    return response;
+    return setResponse(drillStatusMap);
   }
 
   @SuppressWarnings("resource")
@@ -99,7 +98,6 @@
   @Path("/gracePeriod")
   @Produces(MediaType.APPLICATION_JSON)
   public Map<String, Integer> getGracePeriod() {
-
     final DrillConfig config = work.getContext().getConfig();
     final int gracePeriod = config.getInt(ExecConstants.GRACE_PERIOD);
     Map<String, Integer> gracePeriodMap = new HashMap<String, Integer>();
@@ -125,44 +123,42 @@
   @Path("/queriesCount")
   @Produces(MediaType.APPLICATION_JSON)
   public Response getRemainingQueries() {
-    Map<String, Integer> queriesInfo = new HashMap<String, Integer>();
-    queriesInfo = work.getRemainingQueries();
-    Response response = setResponse(queriesInfo);
-    return response;
+    Map<String, Integer> queriesInfo = work.getRemainingQueries();
+    return setResponse(queriesInfo);
   }
 
   @SuppressWarnings("resource")
   @POST
   @Path("/gracefulShutdown")
   @Produces(MediaType.APPLICATION_JSON)
+  @RolesAllowed(ADMIN_ROLE)
   public Response shutdownDrillbit() throws Exception {
     String resp = "Graceful Shutdown request is triggered";
     return shutdown(resp);
-
   }
 
   @SuppressWarnings("resource")
   @POST
   @Path("/shutdown")
   @Produces(MediaType.APPLICATION_JSON)
-  public Response ShutdownForcefully() throws Exception {
+  @RolesAllowed(ADMIN_ROLE)
+  public Response shutdownForcefully() throws Exception {
     drillbit.setForcefulShutdown(true);
     String resp = "Forceful shutdown request is triggered";
     return shutdown(resp);
-
   }
 
   @SuppressWarnings("resource")
   @POST
   @Path("/quiescent")
   @Produces(MediaType.APPLICATION_JSON)
+  @RolesAllowed(ADMIN_ROLE)
   public Response drillbitToQuiescentMode() throws Exception {
     drillbit.setQuiescentMode(true);
     String resp = "Request to put drillbit in Quiescent mode is triggered";
     return shutdown(resp);
   }
 
-
   @SuppressWarnings("resource")
   @GET
   @Path("/cluster.json")
@@ -180,36 +176,58 @@
             config.getBoolean(ExecConstants.USER_ENCRYPTION_SASL_ENABLED) ||
                     config .getBoolean(ExecConstants.USER_SSL_ENABLED);
     final boolean bitEncryptionEnabled = config.getBoolean(ExecConstants.BIT_ENCRYPTION_SASL_ENABLED);
-    // If the user is logged in and is admin user then show the admin user info
-    // For all other cases the user info need-not or should-not be displayed
+
     OptionManager optionManager = work.getContext().getOptionManager();
     final boolean isUserLoggedIn = AuthDynamicFeature.isUserLoggedIn(sc);
-    final String processUser = ImpersonationUtil.getProcessUserName();
-    final String processUserGroups = Joiner.on(", ").join(ImpersonationUtil.getProcessUserGroupNames());
-    String adminUsers = ExecConstants.ADMIN_USERS_VALIDATOR.getAdminUsers(optionManager);
-    String adminUserGroups = ExecConstants.ADMIN_USER_GROUPS_VALIDATOR.getAdminUserGroups(optionManager);
-
-    final boolean shouldShowAdminInfo = isUserLoggedIn &&
-            ((DrillUserPrincipal)sc.getUserPrincipal()).isAdminUser();
+    final boolean shouldShowAdminInfo = isUserLoggedIn && ((DrillUserPrincipal)sc.getUserPrincipal()).isAdminUser();
 
     for (DrillbitEndpoint endpoint : work.getContext().getAvailableBits()) {
       final DrillbitInfo drillbit = new DrillbitInfo(endpoint,
-              currentDrillbit.equals(endpoint),
+              isDrillbitsTheSame(currentDrillbit, endpoint),
               currentVersion.equals(endpoint.getVersion()));
       if (!drillbit.isVersionMatch()) {
         mismatchedVersions.add(drillbit.getVersion());
       }
       drillbits.add(drillbit);
     }
-    logger.debug("Admin info: user: "  + adminUsers +  " user group: " + adminUserGroups +
-            " userLoggedIn "  + isUserLoggedIn + " shouldShowAdminInfo: " + shouldShowAdminInfo );
+
+    // If the user is logged in and is admin user then show the admin user info
+    // For all other cases the user info need-not or should-not be displayed
+    if (shouldShowAdminInfo) {
+      final String processUser = ImpersonationUtil.getProcessUserName();
+      final String processUserGroups = Joiner.on(", ").join(ImpersonationUtil.getProcessUserGroupNames());
+      String adminUsers = ExecConstants.ADMIN_USERS_VALIDATOR.getAdminUsers(optionManager);
+      String adminUserGroups = ExecConstants.ADMIN_USER_GROUPS_VALIDATOR.getAdminUserGroups(optionManager);
+
+      logger.debug("Admin info: user: "  + adminUsers +  " user group: " + adminUserGroups +
+          " userLoggedIn "  + isUserLoggedIn + " shouldShowAdminInfo: " + shouldShowAdminInfo);
+
+      return new ClusterInfo(drillbits, currentVersion, mismatchedVersions,
+          userEncryptionEnabled, bitEncryptionEnabled, shouldShowAdminInfo,
+          QueueInfo.build(dbContext.getResourceManager()),
+          processUser, processUserGroups, adminUsers, adminUserGroups);
+    }
 
     return new ClusterInfo(drillbits, currentVersion, mismatchedVersions,
-            userEncryptionEnabled, bitEncryptionEnabled, processUser, processUserGroups, adminUsers,
-            adminUserGroups, shouldShowAdminInfo, QueueInfo.build(dbContext.getResourceManager()));
+        userEncryptionEnabled, bitEncryptionEnabled, shouldShowAdminInfo,
+        QueueInfo.build(dbContext.getResourceManager()));
   }
 
-  public Response setResponse(Map entity) {
+  /**
+   * Compares two drillbits based on their address and ports (control, data, user).
+   *
+   * @param endpoint1 first drillbit to compare
+   * @param endpoint2 second drillbit to compare
+   * @return true if drillbit are the same
+   */
+  private boolean isDrillbitsTheSame(DrillbitEndpoint endpoint1, DrillbitEndpoint endpoint2) {
+    return endpoint1.getAddress().equals(endpoint2.getAddress()) &&
+        endpoint1.getControlPort() == endpoint2.getControlPort() &&
+        endpoint1.getDataPort() == endpoint2.getDataPort() &&
+        endpoint1.getUserPort() == endpoint2.getUserPort();
+  }
+
+  private Response setResponse(Map entity) {
     return Response.ok()
             .entity(entity)
             .header("Access-Control-Allow-Origin", "*")
@@ -218,7 +236,7 @@
             .allow("OPTIONS").build();
   }
 
-  public Response shutdown(String resp) throws Exception {
+  private Response shutdown(String resp) throws Exception {
     Map<String, String> shutdownInfo = new HashMap<String, String>();
     new Thread(new Runnable() {
         public void run() {
@@ -230,11 +248,9 @@
         }
       }).start();
     shutdownInfo.put("response",resp);
-    Response response = setResponse(shutdownInfo);
-    return response;
+    return setResponse(shutdownInfo);
   }
 
-
 /**
  * Pretty-printing wrapper class around the ZK-based queue summary.
  */
@@ -317,29 +333,27 @@
 }
 
 @XmlRootElement
+@JsonInclude(JsonInclude.Include.NON_ABSENT)
 public static class ClusterInfo {
   private final Collection<DrillbitInfo> drillbits;
   private final String currentVersion;
   private final Collection<String> mismatchedVersions;
   private final boolean userEncryptionEnabled;
   private final boolean bitEncryptionEnabled;
-  private final String adminUsers;
-  private final String adminUserGroups;
-  private final String processUser;
-  private final String processUserGroups;
   private final boolean shouldShowAdminInfo;
   private final QueueInfo queueInfo;
 
+  private String adminUsers;
+  private String adminUserGroups;
+  private String processUser;
+  private String processUserGroups;
+
   @JsonCreator
   public ClusterInfo(Collection<DrillbitInfo> drillbits,
                      String currentVersion,
                      Collection<String> mismatchedVersions,
                      boolean userEncryption,
                      boolean bitEncryption,
-                     String processUser,
-                     String processUserGroups,
-                     String adminUsers,
-                     String adminUserGroups,
                      boolean shouldShowAdminInfo,
                      QueueInfo queueInfo) {
     this.drillbits = Sets.newTreeSet(drillbits);
@@ -347,12 +361,27 @@
     this.mismatchedVersions = Sets.newTreeSet(mismatchedVersions);
     this.userEncryptionEnabled = userEncryption;
     this.bitEncryptionEnabled = bitEncryption;
+    this.shouldShowAdminInfo = shouldShowAdminInfo;
+    this.queueInfo = queueInfo;
+  }
+
+  @JsonCreator
+  public ClusterInfo(Collection<DrillbitInfo> drillbits,
+                     String currentVersion,
+                     Collection<String> mismatchedVersions,
+                     boolean userEncryption,
+                     boolean bitEncryption,
+                     boolean shouldShowAdminInfo,
+                     QueueInfo queueInfo,
+                     String processUser,
+                     String processUserGroups,
+                     String adminUsers,
+                     String adminUserGroups) {
+    this(drillbits, currentVersion, mismatchedVersions, userEncryption, bitEncryption, shouldShowAdminInfo, queueInfo);
     this.processUser = processUser;
     this.processUserGroups = processUserGroups;
     this.adminUsers = adminUsers;
     this.adminUserGroups = adminUserGroups;
-    this.shouldShowAdminInfo = shouldShowAdminInfo;
-    this.queueInfo = queueInfo;
   }
 
   public Collection<DrillbitInfo> getDrillbits() {