Repository: ambari Updated Branches: refs/heads/branch-2.2 620daba7c -> 67c9e8131
AMBARI-15064. RU/EU can't start if hosts have name in MixedCASE in configs for NameNode, HBASE Master, ResourceManager (alejandro) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/67c9e813 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/67c9e813 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/67c9e813 Branch: refs/heads/branch-2.2 Commit: 67c9e81317547a34a65d6bfe480e665473e47849 Parents: 620daba Author: Alejandro Fernandez <afernan...@hortonworks.com> Authored: Tue Feb 16 16:02:12 2016 -0800 Committer: Alejandro Fernandez <afernan...@hortonworks.com> Committed: Thu Feb 18 11:29:01 2016 -0800 ---------------------------------------------------------------------- .../controller/AmbariActionExecutionHelper.java | 13 ++- .../internal/UpgradeResourceProvider.java | 7 +- .../apache/ambari/server/stack/JmxQuery.java | 87 +++++++++++++++ .../ambari/server/stack/MasterHostResolver.java | 86 ++++++--------- .../AmbariManagementControllerTest.java | 2 +- .../ambari/server/state/UpgradeHelperTest.java | 109 ++++++++++++++++--- 6 files changed, 231 insertions(+), 73 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/67c9e813/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariActionExecutionHelper.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariActionExecutionHelper.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariActionExecutionHelper.java index fe2cc29..5e1bf9d 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariActionExecutionHelper.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariActionExecutionHelper.java @@ -262,6 +262,13 @@ public class AmbariActionExecutionHelper { final String serviceName = actionContext.getExpectedServiceName(); final String componentName = actionContext.getExpectedComponentName(); + LOG.debug(String.format("Called addExecutionCommandsToStage() for serviceName: %s, componentName: %s.", serviceName, componentName)); + if (resourceFilter.getHostNames().isEmpty()) { + LOG.debug("Resource filter has no hostnames."); + } else { + LOG.debug(String.format("Resource filter has hosts: %s", StringUtils.join(resourceFilter.getHostNames(), ", "))); + } + if (null != cluster) { StackId stackId = cluster.getCurrentStackVersion(); if (serviceName != null && !serviceName.isEmpty()) { @@ -275,6 +282,7 @@ public class AmbariActionExecutionHelper { stackId.getStackVersion(), serviceName, componentName); } catch (ObjectNotFoundException e) { // do nothing, componentId is checked for null later + LOG.error(String.format("Did not find service %s and component %s in stack %s.", serviceName, componentName, stackId.getStackName())); } } else { for (String component : cluster.getService(serviceName).getServiceComponents().keySet()) { @@ -288,6 +296,7 @@ public class AmbariActionExecutionHelper { // All hosts are valid target host candidateHosts.addAll(clusters.getHostsForCluster(cluster.getClusterName()).keySet()); } + LOG.debug(String.format("Request for service %s and component %s is set to run on candidate hosts: %s.", serviceName, componentName, StringUtils.join(candidateHosts, ", "))); // Filter hosts that are in MS Set<String> ignoredHosts = maintenanceStateHelper.filterHostsInMaintenanceState( @@ -301,7 +310,9 @@ public class AmbariActionExecutionHelper { } } ); + if (! ignoredHosts.isEmpty()) { + LOG.debug(String.format("Hosts to ignore: %s.", StringUtils.join(ignoredHosts, ", "))); LOG.debug("Ignoring action for hosts due to maintenance state." + "Ignored hosts =" + ignoredHosts + ", component=" + componentName + ", service=" + serviceName @@ -323,7 +334,7 @@ public class AmbariActionExecutionHelper { for (String hostname : resourceFilter.getHostNames()) { if (!candidateHosts.contains(hostname)) { throw new AmbariException("Request specifies host " + hostname + - " but its not a valid host based on the " + + " but it is not a valid host based on the " + "target service=" + serviceName + " and component=" + componentName); } } http://git-wip-us.apache.org/repos/asf/ambari/blob/67c9e813/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradeResourceProvider.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradeResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradeResourceProvider.java index 371c611..c29fae4 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradeResourceProvider.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradeResourceProvider.java @@ -78,6 +78,7 @@ import org.apache.ambari.server.orm.entities.StackEntity; import org.apache.ambari.server.orm.entities.UpgradeEntity; import org.apache.ambari.server.orm.entities.UpgradeGroupEntity; import org.apache.ambari.server.orm.entities.UpgradeItemEntity; +import org.apache.ambari.server.stack.JmxQuery; import org.apache.ambari.server.stack.MasterHostResolver; import org.apache.ambari.server.state.Cluster; import org.apache.ambari.server.state.Config; @@ -679,10 +680,11 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider final String version = (String) requestMap.get(UPGRADE_VERSION); MasterHostResolver resolver = null; + JmxQuery jmx = new JmxQuery(); if (direction.isUpgrade()) { - resolver = new MasterHostResolver(configHelper, cluster); + resolver = new MasterHostResolver(configHelper, jmx, cluster); } else { - resolver = new MasterHostResolver(configHelper, cluster, version); + resolver = new MasterHostResolver(configHelper, jmx, cluster, version); } StackId sourceStackId = null; @@ -1162,6 +1164,7 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider RequestResourceFilter filter = new RequestResourceFilter("", "", new ArrayList<String>(wrapper.getHosts())); + LOG.debug(String.format("Analyzing upgrade item %s with tasks: %s.", entity.getText(), entity.getTasks())); Map<String, String> params = getNewParameterMap(); params.put(COMMAND_PARAM_TASKS, entity.getTasks()); params.put(COMMAND_PARAM_VERSION, context.getVersion()); http://git-wip-us.apache.org/repos/asf/ambari/blob/67c9e813/ambari-server/src/main/java/org/apache/ambari/server/stack/JmxQuery.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/JmxQuery.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/JmxQuery.java new file mode 100644 index 0000000..389d504 --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/JmxQuery.java @@ -0,0 +1,87 @@ +/** + * 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.stack; + +import com.google.common.reflect.TypeToken; +import org.apache.ambari.server.utils.HTTPUtils; +import org.apache.ambari.server.utils.StageUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + + +/** + * Class with methods to query JMX. + * This is its own class with non-static methods because it is mocked during unit tests. + */ +public class JmxQuery { + + private static Logger LOG = LoggerFactory.getLogger(JmxQuery.class); + + public String queryJmxBeanValue(String hostname, int port, String beanName, String attributeName, + boolean asQuery) { + return queryJmxBeanValue(hostname, port, beanName, attributeName, asQuery, false); + } + + /** + * Query the JMX attribute at http(s)://$server:$port/jmx?qry=$query or http(s)://$server:$port/jmx?get=$bean::$attribute + * @param hostname host name + * @param port port number + * @param beanName if asQuery is false, then search for this bean name + * @param attributeName if asQuery is false, then search for this attribute name + * @param asQuery whether to search bean or query + * @param encrypted true if using https instead of http. + * @return The jmx value. + */ + public String queryJmxBeanValue(String hostname, int port, String beanName, String attributeName, + boolean asQuery, boolean encrypted) { + + String protocol = encrypted ? "https://" : "http://"; + String endPoint = protocol + (asQuery ? + String.format("%s:%s/jmx?qry=%s", hostname, port, beanName) : + String.format("%s:%s/jmx?get=%s::%s", hostname, port, beanName, attributeName)); + + String response = HTTPUtils.requestURL(endPoint); + + if (null == response || response.isEmpty()) { + return null; + } + + Type type = new TypeToken<Map<String, ArrayList<HashMap<String, String>>>>() {}.getType(); + + try { + Map<String, ArrayList<HashMap<String, String>>> jmxBeans = + StageUtils.getGson().fromJson(response, type); + + return jmxBeans.get("beans").get(0).get(attributeName); + } catch (Exception e) { + if (LOG.isDebugEnabled()) { + LOG.info("Could not load JMX from {}/{} from {}", beanName, attributeName, hostname, e); + } else { + LOG.info("Could not load JMX from {}/{} from {}", beanName, attributeName, hostname); + } + } + + return null; + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/67c9e813/ambari-server/src/main/java/org/apache/ambari/server/stack/MasterHostResolver.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/MasterHostResolver.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/MasterHostResolver.java index 561350b..6825895 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/stack/MasterHostResolver.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/MasterHostResolver.java @@ -18,7 +18,6 @@ package org.apache.ambari.server.stack; -import java.lang.reflect.Type; import java.net.MalformedURLException; import java.text.MessageFormat; import java.util.ArrayList; @@ -38,12 +37,9 @@ import org.apache.ambari.server.state.ServiceComponentHost; import org.apache.ambari.server.state.UpgradeState; import org.apache.ambari.server.utils.HTTPUtils; import org.apache.ambari.server.utils.HostAndPort; -import org.apache.ambari.server.utils.StageUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.reflect.TypeToken; - public class MasterHostResolver { @@ -52,6 +48,7 @@ public class MasterHostResolver { private Cluster m_cluster; private String m_version; private ConfigHelper m_configHelper; + private JmxQuery m_jmxQuery; public enum Service { HDFS, @@ -73,10 +70,11 @@ public class MasterHostResolver { * resolving hosts. Common use case is creating an upgrade that should * include an entire cluster. * @param configHelper Configuration Helper + * @param jmxQuery Jmx Query utils * @param cluster the cluster */ - public MasterHostResolver(ConfigHelper configHelper, Cluster cluster) { - this(configHelper, cluster, null); + public MasterHostResolver(ConfigHelper configHelper, JmxQuery jmxQuery, Cluster cluster) { + this(configHelper, jmxQuery, cluster, null); } /** @@ -85,11 +83,13 @@ public class MasterHostResolver { * HostComponents need to be downgraded, and HostComponents already at the * correct version are skipped. * @param configHelper Configuration Helper + * @param jmxQuery Jmx Query utils * @param cluster the cluster * @param version the version, or {@code null} to not compare versions */ - public MasterHostResolver(ConfigHelper configHelper, Cluster cluster, String version) { + public MasterHostResolver(ConfigHelper configHelper, JmxQuery jmxQuery, Cluster cluster, String version) { m_configHelper = configHelper; + m_jmxQuery = jmxQuery; m_cluster = cluster; m_version = version; } @@ -247,11 +247,11 @@ public class MasterHostResolver { return false; } - /** * Get mapping of the HDFS Namenodes from the state ("active" or "standby") to the hostname. * @return Returns a map from the state ("active" or "standby" to the hostname with that state if exactly * one active and one standby host were found, otherwise, return null. + * The hostnames are returned in lowercase. */ private Map<Status, String> getNameNodePair() { Map<Status, String> stateToHost = new HashMap<Status, String>(); @@ -287,11 +287,13 @@ public class MasterHostResolver { throw new MalformedURLException("Could not parse host and port from " + value); } - String state = queryJmxBeanValue(hp.host, hp.port, "Hadoop:service=NameNode,name=NameNodeStatus", "State", true, encrypted); + String state = m_jmxQuery.queryJmxBeanValue(hp.host, hp.port, "Hadoop:service=NameNode,name=NameNodeStatus", "State", true, encrypted); if (null != state && (state.equalsIgnoreCase(Status.ACTIVE.toString()) || state.equalsIgnoreCase(Status.STANDBY.toString()))) { Status status = Status.valueOf(state.toUpperCase()); - stateToHost.put(status, hp.host); + stateToHost.put(status, hp.host.toLowerCase()); + } else { + LOG.error(String.format("Could not retrieve state for NameNode %s from property %s by querying JMX.", hp.host, key)); } } catch (MalformedURLException e) { LOG.error(e.getMessage()); @@ -304,6 +306,12 @@ public class MasterHostResolver { return null; } + /** + * Resolve the name of the Resource Manager master and convert the hostname to lowercase. + * @param cluster Cluster + * @param hostType RM hosts + * @throws MalformedURLException + */ private void resolveResourceManagers(Cluster cluster, HostsType hostType) throws MalformedURLException { LinkedHashSet<String> orderedHosts = new LinkedHashSet<String>(hostType.hosts); @@ -315,23 +323,29 @@ public class MasterHostResolver { } for (String hostname : hostType.hosts) { - String value = queryJmxBeanValue(hostname, hp.port, + String value = m_jmxQuery.queryJmxBeanValue(hostname, hp.port, "Hadoop:service=ResourceManager,name=RMNMInfo", "modelerType", true); if (null != value) { if (null == hostType.master) { - hostType.master = hostname; + hostType.master = hostname.toLowerCase(); } // Quick and dirty to make sure the master is last in the list - orderedHosts.remove(hostname); - orderedHosts.add(hostname); + orderedHosts.remove(hostname.toLowerCase()); + orderedHosts.add(hostname.toLowerCase()); } } hostType.hosts = orderedHosts; } + /** + * Resolve the HBASE master and convert the hostname to lowercase. + * @param cluster Cluster + * @param hostsType HBASE master host. + * @throws AmbariException + */ private void resolveHBaseMasters(Cluster cluster, HostsType hostsType) throws AmbariException { String hbaseMasterInfoPortProperty = "hbase.master.info.port"; String hbaseMasterInfoPortValue = m_configHelper.getValueFromDesiredConfigurations(cluster, ConfigHelper.HBASE_SITE, hbaseMasterInfoPortProperty); @@ -342,55 +356,17 @@ public class MasterHostResolver { final int hbaseMasterInfoPort = Integer.parseInt(hbaseMasterInfoPortValue); for (String hostname : hostsType.hosts) { - String value = queryJmxBeanValue(hostname, hbaseMasterInfoPort, + String value = m_jmxQuery.queryJmxBeanValue(hostname, hbaseMasterInfoPort, "Hadoop:service=HBase,name=Master,sub=Server", "tag.isActiveMaster", false); if (null != value) { Boolean bool = Boolean.valueOf(value); if (bool.booleanValue()) { - hostsType.master = hostname; + hostsType.master = hostname.toLowerCase(); } else { - hostsType.secondary = hostname; + hostsType.secondary = hostname.toLowerCase(); } } - - } - } - - private String queryJmxBeanValue(String hostname, int port, String beanName, String attributeName, - boolean asQuery) { - return queryJmxBeanValue(hostname, port, beanName, attributeName, asQuery, false); - } - - private String queryJmxBeanValue(String hostname, int port, String beanName, String attributeName, - boolean asQuery, boolean encrypted) { - - String protocol = encrypted ? "https://" : "http://"; - String endPoint = protocol + (asQuery ? - String.format("%s:%s/jmx?qry=%s", hostname, port, beanName) : - String.format("%s:%s/jmx?get=%s::%s", hostname, port, beanName, attributeName)); - - String response = HTTPUtils.requestURL(endPoint); - - if (null == response || response.isEmpty()) { - return null; - } - - Type type = new TypeToken<Map<String, ArrayList<HashMap<String, String>>>>() {}.getType(); - - try { - Map<String, ArrayList<HashMap<String, String>>> jmxBeans = - StageUtils.getGson().fromJson(response, type); - - return jmxBeans.get("beans").get(0).get(attributeName); - } catch (Exception e) { - if (LOG.isDebugEnabled()) { - LOG.info("Could not load JMX from {}/{} from {}", beanName, attributeName, hostname, e); - } else { - LOG.info("Could not load JMX from {}/{} from {}", beanName, attributeName, hostname); - } } - - return null; } } http://git-wip-us.apache.org/repos/asf/ambari/blob/67c9e813/ambari-server/src/test/java/org/apache/ambari/server/controller/AmbariManagementControllerTest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/AmbariManagementControllerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/AmbariManagementControllerTest.java index d2c4d5e..809ee88 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/controller/AmbariManagementControllerTest.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/AmbariManagementControllerTest.java @@ -4625,7 +4625,7 @@ public class AmbariManagementControllerTest { actionRequest = new ExecuteActionRequest("c1", null, "a2", resourceFilters, null, params, false); expectActionCreationErrorWithMessage(actionRequest, requestProperties, - "Request specifies host h6 but its not a valid host based on the target service=HDFS and component=DATANODE"); + "Request specifies host h6 but it is not a valid host based on the target service=HDFS and component=DATANODE"); hosts.clear(); hosts.add("h1"); http://git-wip-us.apache.org/repos/asf/ambari/blob/67c9e813/ambari-server/src/test/java/org/apache/ambari/server/state/UpgradeHelperTest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/state/UpgradeHelperTest.java b/ambari-server/src/test/java/org/apache/ambari/server/state/UpgradeHelperTest.java index 320744f..609dc9b 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/state/UpgradeHelperTest.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/state/UpgradeHelperTest.java @@ -45,6 +45,7 @@ import org.apache.ambari.server.orm.GuiceJpaInitializer; import org.apache.ambari.server.orm.InMemoryDefaultTestModule; import org.apache.ambari.server.orm.OrmTestHelper; import org.apache.ambari.server.stack.HostsType; +import org.apache.ambari.server.stack.JmxQuery; import org.apache.ambari.server.stack.MasterHostResolver; import org.apache.ambari.server.state.UpgradeHelper.UpgradeGroupHolder; import org.apache.ambari.server.state.stack.ConfigUpgradePack; @@ -91,22 +92,25 @@ public class UpgradeHelperTest { private ConfigHelper m_configHelper; private AmbariManagementController m_managementController; private Gson m_gson = new Gson(); + private JmxQuery m_jmxQuery; - @Before - public void before() throws Exception { + /** + * Because test cases need to share config mocks, put common ones in this function. + * @throws Exception + */ + private void setConfigMocks() throws Exception { // configure the mock to return data given a specific placeholder m_configHelper = EasyMock.createNiceMock(ConfigHelper.class); + expect(m_configHelper.getPlaceholderValueFromDesiredConfigurations( + EasyMock.anyObject(Cluster.class), EasyMock.eq("{{foo/bar}}"))).andReturn("placeholder-rendered-properly").anyTimes(); + expect(m_configHelper.getEffectiveDesiredTags( + EasyMock.anyObject(Cluster.class), EasyMock.anyObject(String.class))).andReturn(new HashMap<String, Map<String, String>>()).anyTimes(); + } - expect( - m_configHelper.getPlaceholderValueFromDesiredConfigurations( - EasyMock.anyObject(Cluster.class), EasyMock.eq("{{foo/bar}}"))).andReturn( - "placeholder-rendered-properly").anyTimes(); - - expect( - m_configHelper.getEffectiveDesiredTags( - EasyMock.anyObject(Cluster.class), EasyMock.anyObject(String.class))). - andReturn(new HashMap<String, Map<String, String>>()).anyTimes(); - + @Before + public void before() throws Exception { + setConfigMocks(); + // Most test cases can replay the common config mocks. If any test case needs custom ones, it can re-initialize m_configHelper; replay(m_configHelper); final InMemoryDefaultTestModule injectorModule = new InMemoryDefaultTestModule() { @@ -116,6 +120,8 @@ public class UpgradeHelperTest { } }; + m_jmxQuery = EasyMock.createNiceMock(JmxQuery.class); + MockModule mockModule = new MockModule(); // create an injector which will inject the mocks injector = Guice.createInjector(Modules.override(injectorModule).with(mockModule)); @@ -1190,7 +1196,7 @@ public class UpgradeHelperTest { Service s = c.getService("ZOOKEEPER"); ServiceComponent sc = s.addServiceComponent("ZOOKEEPER_SERVER"); - ServiceComponentHost sch1 =sc.addServiceComponentHost("h1"); + ServiceComponentHost sch1 = sc.addServiceComponentHost("h1"); sch1.setVersion("2.1.1.0-1234"); ServiceComponentHost sch2 = sc.addServiceComponentHost("h2"); @@ -1199,7 +1205,8 @@ public class UpgradeHelperTest { List<ServiceComponentHost> schs = c.getServiceComponentHosts("ZOOKEEPER", "ZOOKEEPER_SERVER"); assertEquals(2, schs.size()); - MasterHostResolver mhr = new MasterHostResolver(null, c, "2.1.1.0-1234"); + JmxQuery jmx = new JmxQuery(); + MasterHostResolver mhr = new MasterHostResolver(null, jmx, c, "2.1.1.0-1234"); HostsType ht = mhr.getMasterAndHosts("ZOOKEEPER", "ZOOKEEPER_SERVER"); assertEquals(0, ht.hosts.size()); @@ -1213,6 +1220,80 @@ public class UpgradeHelperTest { assertEquals("h2", ht.hosts.iterator().next()); } + /** + * Test that MasterHostResolver is case-insensitive even if configs have hosts in upper case for NameNode. + * @throws Exception + */ + @Test + public void testResolverCaseInsensitive() throws Exception { + Clusters clusters = injector.getInstance(Clusters.class); + ServiceFactory serviceFactory = injector.getInstance(ServiceFactory.class); + + String clusterName = "c1"; + String version = "2.1.1.0-1234"; + + StackId stackId = new StackId("HDP-2.1.1"); + clusters.addCluster(clusterName, stackId); + Cluster c = clusters.getCluster(clusterName); + + helper.getOrCreateRepositoryVersion(stackId, + c.getDesiredStackVersion().getStackVersion()); + + c.createClusterVersion(stackId, + c.getDesiredStackVersion().getStackVersion(), "admin", + RepositoryVersionState.UPGRADING); + + for (int i = 0; i < 2; i++) { + String hostName = "h" + (i+1); + clusters.addHost(hostName); + Host host = clusters.getHost(hostName); + + Map<String, String> hostAttributes = new HashMap<String, String>(); + hostAttributes.put("os_family", "redhat"); + hostAttributes.put("os_release_version", "6"); + + host.setHostAttributes(hostAttributes); + + host.persist(); + clusters.mapHostToCluster(hostName, clusterName); + } + + // Add services + c.addService(serviceFactory.createNew(c, "HDFS")); + + Service s = c.getService("HDFS"); + ServiceComponent sc = s.addServiceComponent("NAMENODE"); + sc.addServiceComponentHost("h1"); + sc.addServiceComponentHost("h2"); + + List<ServiceComponentHost> schs = c.getServiceComponentHosts("HDFS", "NAMENODE"); + assertEquals(2, schs.size()); + + setConfigMocks(); + expect(m_configHelper.getValueFromDesiredConfigurations(c, "hdfs-site", "dfs.nameservices")).andReturn("ha").anyTimes(); + expect(m_configHelper.getValueFromDesiredConfigurations(c, "hdfs-site", "dfs.ha.namenodes.ha")).andReturn("nn1,nn2").anyTimes(); + expect(m_configHelper.getValueFromDesiredConfigurations(c, "hdfs-site", "dfs.http.policy")).andReturn("HTTP_ONLY").anyTimes(); + + // Notice that these names are all caps. + expect(m_configHelper.getValueFromDesiredConfigurations(c, "hdfs-site", "dfs.namenode.http-address.ha.nn1")).andReturn("H1:50070").anyTimes(); + expect(m_configHelper.getValueFromDesiredConfigurations(c, "hdfs-site", "dfs.namenode.http-address.ha.nn2")).andReturn("H2:50070").anyTimes(); + replay(m_configHelper); + + // Mock the calls to JMX + expect(m_jmxQuery.queryJmxBeanValue("H1", 50070, "Hadoop:service=NameNode,name=NameNodeStatus", "State", true, false)).andReturn("ACTIVE").anyTimes(); + expect(m_jmxQuery.queryJmxBeanValue("H2", 50070, "Hadoop:service=NameNode,name=NameNodeStatus", "State", true, false)).andReturn("STANDBY").anyTimes(); + replay(m_jmxQuery); + + MasterHostResolver mhr = new MasterHostResolver(m_configHelper, m_jmxQuery, c, version); + + HostsType ht = mhr.getMasterAndHosts("HDFS", "NAMENODE"); + assertEquals(2, ht.hosts.size()); + + // Should be stored in lowercase. + assertTrue(ht.hosts.contains("h1")); + assertTrue(ht.hosts.contains("h1")); + } + private class MockModule implements Module {