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);
+ }
+}