You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by nc...@apache.org on 2015/11/11 01:20:40 UTC

ambari git commit: AMBARI-13797. RU: PreCheck for consistency of password between Ranger and Ambari (ncole)

Repository: ambari
Updated Branches:
  refs/heads/branch-2.1 2c273868a -> 33abeec7c


AMBARI-13797. RU: PreCheck for consistency of password between Ranger and Ambari (ncole)


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/33abeec7
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/33abeec7
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/33abeec7

Branch: refs/heads/branch-2.1
Commit: 33abeec7ce7d34819a1104862d7a21f264caf701
Parents: 2c27386
Author: Nate Cole <nc...@hortonworks.com>
Authored: Tue Nov 10 19:19:18 2015 -0500
Committer: Nate Cole <nc...@hortonworks.com>
Committed: Tue Nov 10 19:19:18 2015 -0500

----------------------------------------------------------------------
 .../ambari/server/checks/CheckDescription.java  |  19 +
 .../server/checks/RangerPasswordCheck.java      | 370 +++++++++++++
 .../server/checks/RangerPasswordCheckTest.java  | 543 +++++++++++++++++++
 3 files changed, 932 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/33abeec7/ambari-server/src/main/java/org/apache/ambari/server/checks/CheckDescription.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/checks/CheckDescription.java b/ambari-server/src/main/java/org/apache/ambari/server/checks/CheckDescription.java
index 1908c06..9cee6dd 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/checks/CheckDescription.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/checks/CheckDescription.java
@@ -207,6 +207,25 @@ public enum CheckDescription {
       new HashMap<String, String>() {{
         put(AbstractCheckDescriptor.DEFAULT,
           "The following config types will have values overwritten: %s");
+      }}),
+
+  SERVICES_RANGER_PASSWORD_VERIFY(PrereqCheckType.SERVICE,
+      "Verify Ambari and Ranger Password Synchronization",
+      new HashMap<String, String>() {{
+        put(AbstractCheckDescriptor.DEFAULT,
+            "There was a problem verifying Ranger and Ambari users");
+        put(RangerPasswordCheck.KEY_RANGER_PASSWORD_MISMATCH,
+            "Credentials for user '%s' in Ambari do not match Ranger.");
+        put(RangerPasswordCheck.KEY_RANGER_UNKNOWN_RESPONSE,
+            "Could not verify credentials for user '%s'.  Response code %s received from %s");
+        put(RangerPasswordCheck.KEY_RANGER_COULD_NOT_ACCESS,
+            "Could not access Ranger to verify user '%s' against %s. %s");
+        put(RangerPasswordCheck.KEY_RANGER_USERS_ELEMENT_MISSING,
+            "The response from Ranger received, but there is no users element.  Request: %s");
+        put(RangerPasswordCheck.KEY_RANGER_OTHER_ISSUE,
+            "The response from Ranger was malformed. %s. Request: %s");
+        put(RangerPasswordCheck.KEY_RANGER_CONFIG_MISSING,
+            "Could not check credentials.  Missing property %s/%s");
       }});
 
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/33abeec7/ambari-server/src/main/java/org/apache/ambari/server/checks/RangerPasswordCheck.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/checks/RangerPasswordCheck.java b/ambari-server/src/main/java/org/apache/ambari/server/checks/RangerPasswordCheck.java
new file mode 100644
index 0000000..72091a4
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/checks/RangerPasswordCheck.java
@@ -0,0 +1,370 @@
+/*
+ * 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.ambari.server.checks;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.configuration.ComponentSSLConfiguration;
+import org.apache.ambari.server.controller.PrereqCheckRequest;
+import org.apache.ambari.server.controller.internal.URLStreamProvider;
+import org.apache.ambari.server.state.Cluster;
+import org.apache.ambari.server.state.StackId;
+import org.apache.ambari.server.state.stack.PrereqCheckStatus;
+import org.apache.ambari.server.state.stack.PrerequisiteCheck;
+import org.apache.ambari.server.utils.VersionUtils;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.inject.Singleton;
+
+/**
+ * Used to make sure that the password in Ambari matches that for Ranger, in case the
+ * user had changed the password using the Ranger UI.
+ */
+@Singleton
+@UpgradeCheck(group = UpgradeCheckGroup.CONFIGURATION_WARNING, order = 1.1f, required=true)
+public class RangerPasswordCheck extends AbstractCheckDescriptor {
+
+  private static final Logger LOG = LoggerFactory.getLogger(RangerPasswordCheck.class);
+
+  static final String KEY_RANGER_PASSWORD_MISMATCH = "could_not_verify_password";
+  static final String KEY_RANGER_COULD_NOT_ACCESS = "could_not_access";
+  static final String KEY_RANGER_UNKNOWN_RESPONSE = "unknown_response";
+  static final String KEY_RANGER_USERS_ELEMENT_MISSING = "missing_vxusers";
+  static final String KEY_RANGER_OTHER_ISSUE = "invalid_response";
+  static final String KEY_RANGER_CONFIG_MISSING = "missing_config";
+
+  // !!! package protected for testing
+  URLStreamProvider m_streamProvider;
+
+  /**
+   * Constructor.
+   */
+  public RangerPasswordCheck() {
+    super(CheckDescription.SERVICES_RANGER_PASSWORD_VERIFY);
+    m_streamProvider = new URLStreamProvider(2000, 2000, ComponentSSLConfiguration.instance());
+  }
+
+  /**
+   * Verifies that the check can be run.  If the stack is HDP and 2.3 or higher, allow
+   * this to run.  If the stack is not HDP, the check should run.
+   */
+  @Override
+  public boolean isApplicable(PrereqCheckRequest request) throws AmbariException {
+    if (!super.isApplicable(request, Arrays.asList("RANGER"), true)) {
+      return false;
+    }
+
+    final Cluster cluster = clustersProvider.get().getCluster(request.getClusterName());
+
+    StackId clusterStackId = cluster.getCurrentStackVersion();
+    if (clusterStackId.getStackName().equals("HDP")) {
+      String sourceVersion = request.getSourceStackId().getStackVersion();
+
+      return VersionUtils.compareVersions(sourceVersion, "2.3.0.0") >= 0;
+    }
+
+    return true;
+  }
+
+  @Override
+  public void perform(PrerequisiteCheck check, PrereqCheckRequest request) throws AmbariException {
+
+    String rangerUrl = checkEmpty("admin-properties", "policymgr_external_url", check, request);
+    if (null == rangerUrl) {
+      // !!! check results already filled
+      return;
+    }
+
+    String adminUsername = checkEmpty("ranger-env", "admin_username", check, request);
+    if (null == adminUsername) {
+      return;
+    }
+
+    String adminPassword = checkEmpty("ranger-env", "admin_password", check, request);
+    if (null == adminPassword) {
+      return;
+    }
+
+    String rangerAdminUsername = checkEmpty("ranger-env", "ranger_admin_username", check, request);
+    if (null == rangerAdminUsername) {
+      return;
+    }
+
+    String rangerAdminPassword = checkEmpty("ranger-env", "ranger_admin_password", check, request);
+    if (null == rangerAdminPassword) {
+      return;
+    }
+
+    if (rangerUrl.endsWith("/")) {
+      rangerUrl = rangerUrl.substring(0, rangerUrl.length()-1);
+    }
+
+    String rangerAuthUrl = String.format("%s/%s", rangerUrl,
+        "service/public/api/repository/count");
+    String rangerUserUrl = String.format("%s/%s", rangerUrl,
+        "service/xusers/users");
+
+    List<String> failReasons = new ArrayList<>();
+    List<String> warnReasons = new ArrayList<>();
+
+    // !!! first, just try the service with the admin credentials
+    try {
+      int response = checkLogin(rangerAuthUrl, adminUsername, adminPassword);
+
+      switch (response) {
+        case 401: {
+          String reason = getFailReason(KEY_RANGER_PASSWORD_MISMATCH, check, request);
+          failReasons.add(String.format(reason, adminUsername));
+          break;
+        }
+        case 200: {
+          break;
+        }
+        default: {
+          String reason = getFailReason(KEY_RANGER_UNKNOWN_RESPONSE, check, request);
+          warnReasons.add(String.format(reason, adminUsername, response, rangerAuthUrl));
+          break;
+        }
+      }
+
+    } catch (IOException e) {
+      LOG.warn("Could not access the url {}.  Message: {}", rangerAuthUrl, e.getMessage(), e);
+      LOG.debug("Could not access the url {}.  Message: {}", rangerAuthUrl, e.getMessage());
+
+      String reason = getFailReason(KEY_RANGER_COULD_NOT_ACCESS, check, request);
+      warnReasons.add(String.format(reason, adminUsername, rangerAuthUrl, e.getMessage()));
+    }
+
+    // !!! shortcut when something happened with the admin user
+    if (!failReasons.isEmpty()) {
+      check.setFailReason(StringUtils.join(failReasons, '\n'));
+      check.getFailedOn().add("RANGER");
+      check.setStatus(PrereqCheckStatus.FAIL);
+      return;
+    } else if (!warnReasons.isEmpty()) {
+      check.setFailReason(StringUtils.join(warnReasons, '\n'));
+      check.getFailedOn().add("RANGER");
+      check.setStatus(PrereqCheckStatus.WARNING);
+      return;
+    }
+
+    // !!! Check for the user, capture exceptions as a warning.
+    boolean hasUser = checkRangerUser(rangerUserUrl, adminUsername, adminPassword,
+        rangerAdminUsername, check, request, warnReasons);
+
+    if (hasUser) {
+
+      // !!! try credentials for specific user
+      try {
+        int response = checkLogin(rangerAuthUrl, rangerAdminUsername, rangerAdminPassword);
+
+        switch (response) {
+          case 401: {
+            String reason = getFailReason(KEY_RANGER_PASSWORD_MISMATCH, check, request);
+            failReasons.add(String.format(reason, rangerAdminUsername));
+            break;
+          }
+          case 200: {
+            break;
+          }
+          default: {
+            String reason = getFailReason(KEY_RANGER_UNKNOWN_RESPONSE, check, request);
+            warnReasons.add(String.format(reason, rangerAdminUsername, response, rangerAuthUrl));
+            break;
+          }
+        }
+
+      } catch (IOException e) {
+        LOG.warn("Could not access the url {}.  Message: {}", rangerAuthUrl, e.getMessage());
+        LOG.debug("Could not access the url {}.  Message: {}", rangerAuthUrl, e.getMessage(), e);
+
+        String reason = getFailReason(KEY_RANGER_COULD_NOT_ACCESS, check, request);
+        warnReasons.add(String.format(reason, rangerAdminUsername, rangerAuthUrl, e.getMessage()));
+      }
+    }
+
+    if (!failReasons.isEmpty()) {
+      check.setFailReason(StringUtils.join(failReasons, '\n'));
+      check.getFailedOn().add("RANGER");
+      check.setStatus(PrereqCheckStatus.FAIL);
+    } else if (!warnReasons.isEmpty()) {
+      check.setFailReason(StringUtils.join(warnReasons, '\n'));
+      check.getFailedOn().add("RANGER");
+      check.setStatus(PrereqCheckStatus.WARNING);
+    } else {
+      check.setStatus(PrereqCheckStatus.PASS);
+    }
+
+  }
+
+  /**
+   * Checks the credentials.  From the Ranger team, bad credentials result in a
+   * successful call, but the Ranger admin server will redirect to the home page.  They
+   * recommend parsing the result.  If it parses, the credentials are good, otherwise
+   * consider the user as unverified.
+   *
+   * @param url       the url to check
+   * @param username  the user to check
+   * @param password  the password to check
+   * @return  the http response code
+   * @throws IOException if there was an error reading the response
+   */
+  private int checkLogin(String url, String username, String password) throws IOException {
+
+    Map<String, List<String>> headers = getHeaders(username, password);
+
+    HttpURLConnection conn = m_streamProvider.processURL(url, "GET", (InputStream) null, headers);
+
+    int result = conn.getResponseCode();
+
+    // !!! see javadoc
+    if (result == 200) {
+      Gson gson = new Gson();
+      try {
+        gson.fromJson(new InputStreamReader(conn.getInputStream()), Object.class);
+      } catch (Exception e) {
+        result = 401;
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * @param rangerUserUrl the url to use when looking for the user
+   * @param username      the username to use when loading the url
+   * @param password      the password for the user url
+   * @param userToSearch  the user to look for
+   * @param check         the check instance for loading failure reasons
+   * @param request       the request instance for loading failure reasons
+   * @param warnReasons   the list of warn reasons to fill
+   * @return {@code true} if the user was found
+   */
+  private boolean checkRangerUser(String rangerUserUrl, String username, String password,
+      String userToSearch, PrerequisiteCheck check, PrereqCheckRequest request, List<String> warnReasons) {
+
+    String url = String.format("%s?name=%s", rangerUserUrl, userToSearch);
+
+    Map<String, List<String>> headers = getHeaders(username, password);
+
+    try {
+      HttpURLConnection conn = m_streamProvider.processURL(url, "GET", (InputStream) null, headers);
+
+      int result = conn.getResponseCode();
+
+      if (result == 200) {
+
+        Gson gson = new Gson();
+        Object o = gson.fromJson(new InputStreamReader(conn.getInputStream()), Object.class);
+
+        Map<?, ?> map = (Map<?,?>) o;
+
+        if (!map.containsKey("vXUsers")) {
+          String reason = getFailReason(KEY_RANGER_USERS_ELEMENT_MISSING, check, request);
+          warnReasons.add(String.format(reason, url));
+
+          return false;
+        }
+
+        @SuppressWarnings("unchecked")
+        List<Map<?, ?>> list = (List<Map<?, ?>>) map.get("vXUsers");
+
+        for (Map<?, ?> listMap : list) {
+          if (listMap.containsKey("name") && listMap.get("name").equals(userToSearch)) {
+            return true;
+          }
+        }
+      }
+    } catch (IOException e) {
+      LOG.warn("Could not determine user {}.  Error is {}", userToSearch, e.getMessage());
+      LOG.debug("Could not determine user {}.  Error is {}", userToSearch, e.getMessage(), e);
+
+      String reason = getFailReason(KEY_RANGER_COULD_NOT_ACCESS, check, request);
+      warnReasons.add(String.format(reason, username, url, e.getMessage()));
+
+    } catch (Exception e) {
+      LOG.warn("Could not determine user {}.  Error is {}", userToSearch, e.getMessage());
+      LOG.debug("Could not determine user {}.  Error is {}", userToSearch, e.getMessage(), e);
+
+      String reason = getFailReason(KEY_RANGER_OTHER_ISSUE, check, request);
+      warnReasons.add(String.format(reason, e.getMessage(), url));
+    }
+
+    return false;
+  }
+
+  /**
+   * Generates a list of headers, including {@code Basic} authentication
+   * @param username  the username
+   * @param password  the password
+   * @return the map of headers
+   */
+  private Map<String, List<String>> getHeaders(String username, String password) {
+    Map<String, List<String>> headers = new HashMap<>();
+
+    String base64 = Base64.encodeBase64String(
+        String.format("%s:%s", username, password).getBytes(Charset.forName("UTF8")));
+
+    headers.put("Content-Type", Arrays.asList("application/json"));
+    headers.put("Accept", Arrays.asList("application/json"));
+    headers.put("Authorization", Arrays.asList(String.format("Basic %s", base64)));
+
+    return headers;
+  }
+
+  /**
+   * Finds the property value.  If not found, then the failure reason for the check
+   * is filled in and processing should not continue.
+   *
+   * @param type      the type of property to find
+   * @param key       the key in configs matching the type
+   * @param check     the check for loading failure reasons
+   * @param request   the request for loading failure reasons
+   * @return the property value, or {@code null} if the property doesn't exist
+   * @throws AmbariException
+   */
+  private String checkEmpty(String type, String key, PrerequisiteCheck check,
+      PrereqCheckRequest request) throws AmbariException {
+
+    String value = getProperty(request, type, key);
+    if (null == value) {
+      String reason = getFailReason(KEY_RANGER_CONFIG_MISSING, check, request);
+      reason = String.format(reason, type, key);
+      check.setFailReason(reason);
+      check.getFailedOn().add("RANGER");
+      check.setStatus(PrereqCheckStatus.WARNING);
+    }
+    return value;
+  }
+
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/33abeec7/ambari-server/src/test/java/org/apache/ambari/server/checks/RangerPasswordCheckTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/checks/RangerPasswordCheckTest.java b/ambari-server/src/test/java/org/apache/ambari/server/checks/RangerPasswordCheckTest.java
new file mode 100644
index 0000000..afa3789
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/checks/RangerPasswordCheckTest.java
@@ -0,0 +1,543 @@
+/*
+ * 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.ambari.server.checks;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ambari.server.controller.PrereqCheckRequest;
+import org.apache.ambari.server.controller.internal.URLStreamProvider;
+import org.apache.ambari.server.state.Cluster;
+import org.apache.ambari.server.state.Clusters;
+import org.apache.ambari.server.state.Config;
+import org.apache.ambari.server.state.DesiredConfig;
+import org.apache.ambari.server.state.Service;
+import org.apache.ambari.server.state.StackId;
+import org.apache.ambari.server.state.stack.PrereqCheckStatus;
+import org.apache.ambari.server.state.stack.PrerequisiteCheck;
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.inject.Provider;
+
+
+/**
+ * Unit tests for RangerPasswordCheck
+ *
+ */
+public class RangerPasswordCheckTest {
+
+  private static final String RANGER_URL = "http://foo:6080/";
+
+  private static final String GOOD_LOGIN_RESPONSE = "{\"count\": 0 }";
+
+  private static final String BAD_LOGIN_RESPONSE = "<html>Ranger redirects to login HTML</html>";
+
+  private static final String GOOD_USER_RESPONSE =
+      "{\"queryTimeMS\": 1446758948823," +
+      "\"vXUsers\": [" +
+      "  {" +
+      "    \"name\": \"r_admin\"" +
+      "  }" +
+      "]}";
+
+  private static final String NO_USER_RESPONSE =
+      "{\"queryTimeMS\": 1446758948823," +
+      "\"vXUsers\": [" +
+      "]}";
+
+  private Clusters m_clusters = EasyMock.createMock(Clusters.class);
+  private Map<String, String> m_configMap = new HashMap<String, String>();
+  private RangerPasswordCheck m_rpc = null;
+
+  @Before
+  public void setup() throws Exception {
+    m_configMap.put("policymgr_external_url", RANGER_URL);
+    m_configMap.put("admin_username", "admin");
+    m_configMap.put("admin_password", "pass");
+    m_configMap.put("ranger_admin_username", "r_admin");
+    m_configMap.put("ranger_admin_password", "r_pass");
+
+    Cluster cluster = EasyMock.createMock(Cluster.class);
+
+    Config config = EasyMock.createMock(Config.class);
+    final Map<String, Service> services = new HashMap<>();
+    final Service service = EasyMock.createMock(Service.class);
+
+    services.put("RANGER", service);
+
+    Map<String, DesiredConfig> desiredMap = new HashMap<>();
+    DesiredConfig dc = EasyMock.createMock(DesiredConfig.class);
+    desiredMap.put("admin-properties", dc);
+    desiredMap.put("ranger-env", dc);
+
+    expect(dc.getTag()).andReturn("").anyTimes();
+    expect(config.getProperties()).andReturn(m_configMap).anyTimes();
+    expect(cluster.getServices()).andReturn(services).anyTimes();
+    expect(cluster.getService("RANGER")).andReturn(service).anyTimes();
+    expect(cluster.getDesiredConfigs()).andReturn(desiredMap).anyTimes();
+    expect(cluster.getDesiredConfigByType((String) anyObject())).andReturn(config).anyTimes();
+    expect(cluster.getConfig((String) anyObject(), (String) anyObject())).andReturn(config).anyTimes();
+    expect(m_clusters.getCluster((String) anyObject())).andReturn(cluster).anyTimes();
+
+    replay(m_clusters, cluster, dc, config);
+
+    m_rpc = new RangerPasswordCheck();
+    m_rpc.clustersProvider = new Provider<Clusters>() {
+      @Override
+      public Clusters get() {
+        // TODO Auto-generated method stub
+        return m_clusters;
+      }
+    };
+  }
+
+  @Test
+  public void testApplicable() throws Exception {
+
+    final Service service = EasyMock.createMock(Service.class);
+    Map<String, Service> services = new HashMap<>();
+    services.put("RANGER", service);
+
+    Cluster cluster = m_clusters.getCluster("cluster");
+    EasyMock.reset(cluster);
+    expect(cluster.getServices()).andReturn(services).anyTimes();
+    expect(cluster.getCurrentStackVersion()).andReturn(new StackId("HDP-2.3")).anyTimes();
+    replay(cluster);
+
+    PrereqCheckRequest request = new PrereqCheckRequest("cluster");
+    request.setSourceStackId(new StackId("HDP-2.3"));
+    assertTrue(m_rpc.isApplicable(request));
+
+    request = new PrereqCheckRequest("cluster");
+    request.setSourceStackId(new StackId("HDP-2.2"));
+    assertFalse(m_rpc.isApplicable(request));
+
+    EasyMock.reset(cluster);
+    expect(cluster.getServices()).andReturn(services).anyTimes();
+    expect(cluster.getCurrentStackVersion()).andReturn(new StackId("WILDSTACK-2.0")).anyTimes();
+    replay(cluster);
+
+    request = new PrereqCheckRequest("cluster");
+    request.setSourceStackId(new StackId("HDP-2.2"));
+    assertTrue(m_rpc.isApplicable(request));
+
+  }
+
+  @SuppressWarnings("unchecked")
+  @Test
+  public void testMissingProps() throws Exception {
+
+    HttpURLConnection conn = EasyMock.createMock(HttpURLConnection.class);
+    URLStreamProvider streamProvider = EasyMock.createMock(URLStreamProvider.class);
+
+    m_rpc.m_streamProvider = streamProvider;
+
+    m_configMap.clear();
+
+    PrerequisiteCheck check = new PrerequisiteCheck(null, null);
+    m_rpc.perform(check, new PrereqCheckRequest("cluster"));
+    assertEquals(PrereqCheckStatus.WARNING, check.getStatus());
+    assertEquals("Could not check credentials.  Missing property admin-properties/policymgr_external_url", check.getFailReason());
+
+    m_configMap.put("policymgr_external_url", RANGER_URL);
+    check = new PrerequisiteCheck(null, null);
+    m_rpc.perform(check, new PrereqCheckRequest("cluster"));
+    assertEquals(PrereqCheckStatus.WARNING, check.getStatus());
+    assertEquals("Could not check credentials.  Missing property ranger-env/admin_username", check.getFailReason());
+
+    m_configMap.put("admin_username", "admin");
+    check = new PrerequisiteCheck(null, null);
+    m_rpc.perform(check, new PrereqCheckRequest("cluster"));
+    assertEquals(PrereqCheckStatus.WARNING, check.getStatus());
+    assertEquals("Could not check credentials.  Missing property ranger-env/admin_password", check.getFailReason());
+
+
+    m_configMap.put("admin_password", "pass");
+    check = new PrerequisiteCheck(null, null);
+    m_rpc.perform(check, new PrereqCheckRequest("cluster"));
+    assertEquals(PrereqCheckStatus.WARNING, check.getStatus());
+    assertEquals("Could not check credentials.  Missing property ranger-env/ranger_admin_username", check.getFailReason());
+
+    m_configMap.put("ranger_admin_username", "r_admin");
+    check = new PrerequisiteCheck(null, null);
+    m_rpc.perform(check, new PrereqCheckRequest("cluster"));
+    assertEquals(PrereqCheckStatus.WARNING, check.getStatus());
+    assertEquals("Could not check credentials.  Missing property ranger-env/ranger_admin_password", check.getFailReason());
+
+    expect(conn.getResponseCode()).andReturn(200);
+    expect(conn.getInputStream()).andReturn(new ByteArrayInputStream(GOOD_LOGIN_RESPONSE.getBytes()));
+    expect(conn.getResponseCode()).andReturn(200);
+    expect(conn.getInputStream()).andReturn(new ByteArrayInputStream(GOOD_USER_RESPONSE.getBytes()));
+    expect(conn.getResponseCode()).andReturn(200);
+    expect(conn.getInputStream()).andReturn(new ByteArrayInputStream(GOOD_LOGIN_RESPONSE.getBytes()));
+    expect(streamProvider.processURL((String) anyObject(), (String) anyObject(),
+        (InputStream) anyObject(), (Map<String, List<String>>) anyObject())).andReturn(conn).anyTimes();
+
+    replay(conn, streamProvider);
+
+    m_configMap.put("ranger_admin_password", "r_pass");
+    check = new PrerequisiteCheck(null, null);
+    m_rpc.perform(check, new PrereqCheckRequest("cluster"));
+    assertEquals(PrereqCheckStatus.PASS, check.getStatus());
+
+  }
+
+  @SuppressWarnings("unchecked")
+  @Test
+  public void testNormal() throws Exception {
+
+    HttpURLConnection conn = EasyMock.createMock(HttpURLConnection.class);
+    URLStreamProvider streamProvider = EasyMock.createMock(URLStreamProvider.class);
+
+    expect(conn.getResponseCode()).andReturn(200);
+    expect(conn.getInputStream()).andReturn(new ByteArrayInputStream(GOOD_LOGIN_RESPONSE.getBytes())).once();
+    expect(conn.getResponseCode()).andReturn(200);
+    expect(conn.getInputStream()).andReturn(new ByteArrayInputStream(GOOD_USER_RESPONSE.getBytes())).once();
+    expect(conn.getResponseCode()).andReturn(200);
+    expect(conn.getInputStream()).andReturn(new ByteArrayInputStream(GOOD_LOGIN_RESPONSE.getBytes())).once();
+
+    expect(streamProvider.processURL((String) anyObject(), (String) anyObject(),
+        (InputStream) anyObject(), (Map<String, List<String>>) anyObject())).andReturn(conn).anyTimes();
+
+    replay(conn, streamProvider);
+    m_rpc.m_streamProvider = streamProvider;
+
+    PrerequisiteCheck check = new PrerequisiteCheck(null, null);
+    m_rpc.perform(check, new PrereqCheckRequest("cluster"));
+
+    assertEquals(PrereqCheckStatus.PASS, check.getStatus());
+
+    verify(conn, streamProvider);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Test
+  public void testNoUser() throws Exception {
+
+    HttpURLConnection conn = EasyMock.createMock(HttpURLConnection.class);
+    URLStreamProvider streamProvider = EasyMock.createMock(URLStreamProvider.class);
+
+    expect(conn.getResponseCode()).andReturn(200);
+    expect(conn.getInputStream()).andReturn(new ByteArrayInputStream(GOOD_LOGIN_RESPONSE.getBytes())).once();
+    expect(conn.getResponseCode()).andReturn(200);
+    expect(conn.getInputStream()).andReturn(new ByteArrayInputStream(NO_USER_RESPONSE.getBytes())).once();
+
+    expect(streamProvider.processURL((String) anyObject(), (String) anyObject(),
+        (InputStream) anyObject(), (Map<String, List<String>>) anyObject())).andReturn(conn).anyTimes();
+
+    replay(conn, streamProvider);
+    m_rpc.m_streamProvider = streamProvider;
+
+    PrerequisiteCheck check = new PrerequisiteCheck(null, null);
+    m_rpc.perform(check, new PrereqCheckRequest("cluster"));
+
+    assertEquals(PrereqCheckStatus.PASS, check.getStatus());
+
+    verify(conn, streamProvider);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Test
+  public void testBadUserParsing() throws Exception {
+
+    HttpURLConnection conn = EasyMock.createMock(HttpURLConnection.class);
+    URLStreamProvider streamProvider = EasyMock.createMock(URLStreamProvider.class);
+
+    expect(conn.getResponseCode()).andReturn(200);
+    expect(conn.getInputStream()).andReturn(new ByteArrayInputStream(GOOD_LOGIN_RESPONSE.getBytes())).once();
+    expect(conn.getResponseCode()).andReturn(200);
+    expect(conn.getInputStream()).andReturn(new ByteArrayInputStream(
+        "some really bad non-json".getBytes()));
+
+    expect(streamProvider.processURL((String) anyObject(), (String) anyObject(),
+        (InputStream) anyObject(), (Map<String, List<String>>) anyObject())).andReturn(conn).anyTimes();
+
+    replay(conn, streamProvider);
+    m_rpc.m_streamProvider = streamProvider;
+
+    PrerequisiteCheck check = new PrerequisiteCheck(null, null);
+    m_rpc.perform(check, new PrereqCheckRequest("cluster"));
+
+    String error = "The response from Ranger was malformed. ";
+    error += "com.google.gson.stream.MalformedJsonException: Expected EOF at line 1 column 6. ";
+    error += "Request: " + RANGER_URL + "service/xusers/users?name=r_admin";
+
+    assertEquals(PrereqCheckStatus.WARNING, check.getStatus());
+    assertEquals(error, check.getFailReason());
+
+    verify(conn, streamProvider);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Test
+  public void testJsonCasting() throws Exception {
+
+    HttpURLConnection conn = EasyMock.createMock(HttpURLConnection.class);
+    URLStreamProvider streamProvider = EasyMock.createMock(URLStreamProvider.class);
+
+    expect(conn.getResponseCode()).andReturn(200);
+    expect(conn.getInputStream()).andReturn(new ByteArrayInputStream(GOOD_LOGIN_RESPONSE.getBytes()));
+    expect(conn.getResponseCode()).andReturn(200);
+    expect(conn.getInputStream()).andReturn(new ByteArrayInputStream(
+        "{ \"data\": \"bad\", \"vXUsers\": \"xyz\" }".getBytes()));
+
+    expect(streamProvider.processURL((String) anyObject(), (String) anyObject(),
+        (InputStream) anyObject(), (Map<String, List<String>>) anyObject())).andReturn(conn).anyTimes();
+
+    replay(conn, streamProvider);
+    m_rpc.m_streamProvider = streamProvider;
+
+    PrerequisiteCheck check = new PrerequisiteCheck(null, null);
+    m_rpc.perform(check, new PrereqCheckRequest("cluster"));
+
+    String error = "The response from Ranger was malformed. ";
+    error += "java.lang.String cannot be cast to java.util.List. ";
+    error += "Request: " + RANGER_URL + "service/xusers/users?name=r_admin";
+
+    assertEquals(PrereqCheckStatus.WARNING, check.getStatus());
+    assertEquals(error, check.getFailReason());
+
+    verify(conn, streamProvider);
+  }
+
+
+  @SuppressWarnings("unchecked")
+  @Test
+  public void testAdminUnauthorized() throws Exception {
+
+    HttpURLConnection conn = EasyMock.createMock(HttpURLConnection.class);
+    URLStreamProvider streamProvider = EasyMock.createMock(URLStreamProvider.class);
+
+    expect(conn.getResponseCode()).andReturn(401);
+
+    expect(streamProvider.processURL((String) anyObject(), (String) anyObject(),
+        (InputStream) anyObject(), (Map<String, List<String>>) anyObject())).andReturn(conn).anyTimes();
+
+    replay(conn, streamProvider);
+    m_rpc.m_streamProvider = streamProvider;
+
+    PrerequisiteCheck check = new PrerequisiteCheck(CheckDescription.SERVICES_RANGER_PASSWORD_VERIFY, null);
+    m_rpc.perform(check, new PrereqCheckRequest("cluster"));
+
+    assertEquals(PrereqCheckStatus.FAIL, check.getStatus());
+    assertEquals("Credentials for user 'admin' in Ambari do not match Ranger.", check.getFailReason());
+
+    verify(conn, streamProvider);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Test
+  public void testAdminUnauthorizedByRedirect() throws Exception {
+
+    HttpURLConnection conn = EasyMock.createMock(HttpURLConnection.class);
+    URLStreamProvider streamProvider = EasyMock.createMock(URLStreamProvider.class);
+
+    expect(conn.getResponseCode()).andReturn(200);
+    expect(conn.getInputStream()).andReturn(new ByteArrayInputStream(BAD_LOGIN_RESPONSE.getBytes()));
+
+    expect(streamProvider.processURL((String) anyObject(), (String) anyObject(),
+        (InputStream) anyObject(), (Map<String, List<String>>) anyObject())).andReturn(conn).anyTimes();
+
+    replay(conn, streamProvider);
+    m_rpc.m_streamProvider = streamProvider;
+
+    PrerequisiteCheck check = new PrerequisiteCheck(CheckDescription.SERVICES_RANGER_PASSWORD_VERIFY, null);
+    m_rpc.perform(check, new PrereqCheckRequest("cluster"));
+
+    assertEquals(PrereqCheckStatus.FAIL, check.getStatus());
+    assertEquals("Credentials for user 'admin' in Ambari do not match Ranger.", check.getFailReason());
+
+    verify(conn, streamProvider);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Test
+  public void testAdminIOException() throws Exception {
+
+    HttpURLConnection conn = EasyMock.createMock(HttpURLConnection.class);
+    URLStreamProvider streamProvider = EasyMock.createMock(URLStreamProvider.class);
+
+    expect(conn.getResponseCode()).andThrow(new IOException("whoops"));
+
+    expect(streamProvider.processURL((String) anyObject(), (String) anyObject(),
+        (InputStream) anyObject(), (Map<String, List<String>>) anyObject())).andReturn(conn).anyTimes();
+
+    replay(conn, streamProvider);
+    m_rpc.m_streamProvider = streamProvider;
+
+    PrerequisiteCheck check = new PrerequisiteCheck(CheckDescription.SERVICES_RANGER_PASSWORD_VERIFY, null);
+    m_rpc.perform(check, new PrereqCheckRequest("cluster"));
+
+    assertEquals(PrereqCheckStatus.WARNING, check.getStatus());
+    assertEquals("Could not access Ranger to verify user 'admin' against " + RANGER_URL + "service/public/api/repository/count. whoops", check.getFailReason());
+
+    verify(conn, streamProvider);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Test
+  public void testAdminBadResponse() throws Exception {
+
+    HttpURLConnection conn = EasyMock.createMock(HttpURLConnection.class);
+    URLStreamProvider streamProvider = EasyMock.createMock(URLStreamProvider.class);
+
+    expect(conn.getResponseCode()).andReturn(404);
+
+    expect(streamProvider.processURL((String) anyObject(), (String) anyObject(),
+        (InputStream) anyObject(), (Map<String, List<String>>) anyObject())).andReturn(conn).anyTimes();
+
+    replay(conn, streamProvider);
+    m_rpc.m_streamProvider = streamProvider;
+
+    PrerequisiteCheck check = new PrerequisiteCheck(CheckDescription.SERVICES_RANGER_PASSWORD_VERIFY, null);
+    m_rpc.perform(check, new PrereqCheckRequest("cluster"));
+
+    assertEquals(PrereqCheckStatus.WARNING, check.getStatus());
+    assertEquals("Could not verify credentials for user 'admin'.  Response code 404 received from " + RANGER_URL + "service/public/api/repository/count", check.getFailReason());
+
+    verify(conn, streamProvider);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Test
+  public void testUserUnauthorized() throws Exception {
+
+    HttpURLConnection conn = EasyMock.createMock(HttpURLConnection.class);
+    URLStreamProvider streamProvider = EasyMock.createMock(URLStreamProvider.class);
+
+    expect(conn.getResponseCode()).andReturn(200);
+    expect(conn.getInputStream()).andReturn(new ByteArrayInputStream(GOOD_LOGIN_RESPONSE.getBytes())).once();
+    expect(conn.getResponseCode()).andReturn(200);
+    expect(conn.getInputStream()).andReturn(new ByteArrayInputStream(GOOD_USER_RESPONSE.getBytes())).once();
+    expect(conn.getResponseCode()).andReturn(401);
+
+    expect(streamProvider.processURL((String) anyObject(), (String) anyObject(),
+        (InputStream) anyObject(), (Map<String, List<String>>) anyObject())).andReturn(conn).anyTimes();
+
+    replay(conn, streamProvider);
+    m_rpc.m_streamProvider = streamProvider;
+
+    PrerequisiteCheck check = new PrerequisiteCheck(null, null);
+    m_rpc.perform(check, new PrereqCheckRequest("cluster"));
+
+    assertEquals(PrereqCheckStatus.FAIL, check.getStatus());
+    assertEquals("Credentials for user 'r_admin' in Ambari do not match Ranger.", check.getFailReason());
+
+    verify(conn, streamProvider);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Test
+  public void testUserUnauthorizedByRedirect() throws Exception {
+
+    HttpURLConnection conn = EasyMock.createMock(HttpURLConnection.class);
+    URLStreamProvider streamProvider = EasyMock.createMock(URLStreamProvider.class);
+
+    expect(conn.getResponseCode()).andReturn(200);
+    expect(conn.getInputStream()).andReturn(new ByteArrayInputStream(GOOD_LOGIN_RESPONSE.getBytes())).once();
+    expect(conn.getResponseCode()).andReturn(200);
+    expect(conn.getInputStream()).andReturn(new ByteArrayInputStream(GOOD_USER_RESPONSE.getBytes())).once();
+    expect(conn.getResponseCode()).andReturn(200);
+    expect(conn.getInputStream()).andReturn(new ByteArrayInputStream(BAD_LOGIN_RESPONSE.getBytes())).once();
+
+    expect(streamProvider.processURL((String) anyObject(), (String) anyObject(),
+        (InputStream) anyObject(), (Map<String, List<String>>) anyObject())).andReturn(conn).anyTimes();
+
+    replay(conn, streamProvider);
+    m_rpc.m_streamProvider = streamProvider;
+
+    PrerequisiteCheck check = new PrerequisiteCheck(null, null);
+    m_rpc.perform(check, new PrereqCheckRequest("cluster"));
+
+    assertEquals(PrereqCheckStatus.FAIL, check.getStatus());
+    assertEquals("Credentials for user 'r_admin' in Ambari do not match Ranger.", check.getFailReason());
+
+    verify(conn, streamProvider);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Test
+  public void testUserIOException() throws Exception {
+
+    HttpURLConnection conn = EasyMock.createMock(HttpURLConnection.class);
+    URLStreamProvider streamProvider = EasyMock.createMock(URLStreamProvider.class);
+
+    expect(conn.getResponseCode()).andReturn(200);
+    expect(conn.getInputStream()).andReturn(new ByteArrayInputStream(GOOD_LOGIN_RESPONSE.getBytes())).once();
+    expect(conn.getResponseCode()).andReturn(200);
+    expect(conn.getInputStream()).andReturn(new ByteArrayInputStream(GOOD_USER_RESPONSE.getBytes())).once();
+    expect(conn.getResponseCode()).andThrow(new IOException("again!"));
+
+    expect(streamProvider.processURL((String) anyObject(), (String) anyObject(),
+        (InputStream) anyObject(), (Map<String, List<String>>) anyObject())).andReturn(conn).anyTimes();
+
+    replay(conn, streamProvider);
+    m_rpc.m_streamProvider = streamProvider;
+
+    PrerequisiteCheck check = new PrerequisiteCheck(null, null);
+    m_rpc.perform(check, new PrereqCheckRequest("cluster"));
+
+    assertEquals(PrereqCheckStatus.WARNING, check.getStatus());
+    assertEquals("Could not access Ranger to verify user 'r_admin' against " + RANGER_URL + "service/public/api/repository/count. again!", check.getFailReason());
+
+    verify(conn, streamProvider);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Test
+  public void testUserBadResponse() throws Exception {
+
+    HttpURLConnection conn = EasyMock.createMock(HttpURLConnection.class);
+    URLStreamProvider streamProvider = EasyMock.createMock(URLStreamProvider.class);
+
+    expect(conn.getResponseCode()).andReturn(200);
+    expect(conn.getInputStream()).andReturn(new ByteArrayInputStream(GOOD_LOGIN_RESPONSE.getBytes())).once();
+    expect(conn.getResponseCode()).andReturn(200);
+    expect(conn.getInputStream()).andReturn(new ByteArrayInputStream(GOOD_USER_RESPONSE.getBytes())).once();
+    expect(conn.getResponseCode()).andReturn(500);
+
+    expect(streamProvider.processURL((String) anyObject(), (String) anyObject(),
+        (InputStream) anyObject(), (Map<String, List<String>>) anyObject())).andReturn(conn).anyTimes();
+
+    replay(conn, streamProvider);
+    m_rpc.m_streamProvider = streamProvider;
+
+    PrerequisiteCheck check = new PrerequisiteCheck(null, null);
+    m_rpc.perform(check, new PrereqCheckRequest("cluster"));
+
+    assertEquals(PrereqCheckStatus.WARNING, check.getStatus());
+    assertEquals("Could not verify credentials for user 'r_admin'.  Response code 500 received from " + RANGER_URL + "service/public/api/repository/count", check.getFailReason());
+
+    verify(conn, streamProvider);
+  }
+}