You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by be...@apache.org on 2018/11/19 15:50:33 UTC
[ambari] branch trunk updated: AMBARI-24919 external Namenode HA
(benyoka) (#2625)
This is an automated email from the ASF dual-hosted git repository.
benyoka pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ambari.git
The following commit(s) were added to refs/heads/trunk by this push:
new 93576b7 AMBARI-24919 external Namenode HA (benyoka) (#2625)
93576b7 is described below
commit 93576b7c9bc25361ba1e43c9dfb08fc136667520
Author: benyoka <be...@users.noreply.github.com>
AuthorDate: Mon Nov 19 16:50:28 2018 +0100
AMBARI-24919 external Namenode HA (benyoka) (#2625)
---
.../internal/BlueprintConfigurationProcessor.java | 26 ++-
.../ambari/server/topology/ClusterTopology.java | 5 +
.../server/topology/ClusterTopologyImpl.java | 27 +--
.../topology/validators/NameNodeHaValidator.java | 128 ++++++++++
.../validators/TopologyValidatorFactory.java | 9 +-
.../BlueprintConfigurationProcessorTest.java | 257 +++++++++++++++++++++
.../server/topology/ClusterTopologyImplTest.java | 55 +----
.../validators/NameNodeHaValidatorTest.java | 219 ++++++++++++++++++
8 files changed, 649 insertions(+), 77 deletions(-)
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BlueprintConfigurationProcessor.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BlueprintConfigurationProcessor.java
index 1daf1f5..46d7e9b 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BlueprintConfigurationProcessor.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BlueprintConfigurationProcessor.java
@@ -57,6 +57,8 @@ import org.apache.ambari.server.topology.ConfigRecommendationStrategy;
import org.apache.ambari.server.topology.Configuration;
import org.apache.ambari.server.topology.HostGroup;
import org.apache.ambari.server.topology.HostGroupInfo;
+import org.apache.ambari.server.topology.InvalidTopologyException;
+import org.apache.ambari.server.topology.validators.NameNodeHaValidator;
import org.apache.ambari.server.topology.validators.UnitValidatedProperty;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
@@ -480,6 +482,23 @@ public class BlueprintConfigurationProcessor {
private void doNameNodeHAUpdateOnClusterCreation(Configuration clusterConfig,
Map<String, Map<String, String>> clusterProps,
Set<String> configTypesUpdated) throws ConfigurationTopologyException {
+
+ final Collection<String> nnHosts = clusterTopology.getHostAssignmentsForComponent("NAMENODE");
+
+ // external namenodes
+ if (nnHosts.isEmpty()) {
+ LOG.info("NAMENODE HA is enabled but there are no NAMENODE components in the cluster. Assuming external name nodes.");
+ // need to redo validation here as required information (explicit hostnames for some host groups) may have been
+ // missing by the time the ClusterTopology object was validated
+ try {
+ new NameNodeHaValidator().validateExternalNamenodeHa(clusterTopology);
+ }
+ catch (InvalidTopologyException ex) {
+ throw new ConfigurationTopologyException(ex.getMessage(), ex);
+ }
+ return;
+ }
+
// add "dfs.internal.nameservices" if it's not specified
Map<String, String> hdfsSiteConfig = clusterConfig.getFullProperties().get("hdfs-site");
String nameservices = hdfsSiteConfig.get("dfs.nameservices");
@@ -496,10 +515,9 @@ public class BlueprintConfigurationProcessor {
LOG.info("Processing a single HDFS NameService, which indicates a default HDFS NameNode HA deployment");
// if the active/standby namenodes are not specified, assign them automatically
if (! isNameNodeHAInitialActiveNodeSet(clusterProps) && ! isNameNodeHAInitialStandbyNodeSet(clusterProps)) {
- Collection<String> nnHosts = clusterTopology.getHostAssignmentsForComponent("NAMENODE");
- if (nnHosts.size() < 2) {
- throw new ConfigurationTopologyException("NAMENODE HA requires at least 2 hosts running NAMENODE but there are: " +
- nnHosts.size() + " Hosts: " + nnHosts);
+ if (nnHosts.size() == 1) { // can't be 0 as in that case was handled above
+ throw new ConfigurationTopologyException("NAMENODE HA requires at least two hosts running NAMENODE but there is " +
+ "only one: " + nnHosts.iterator().next());
}
// set the properties that configure which namenode is active,
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/ClusterTopology.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/ClusterTopology.java
index e1cab2b..4d8bba3 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/ClusterTopology.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/ClusterTopology.java
@@ -20,6 +20,7 @@ package org.apache.ambari.server.topology;
import java.util.Collection;
import java.util.Map;
+import java.util.Set;
import org.apache.ambari.server.controller.RequestStatusResponse;
import org.apache.ambari.server.controller.internal.ProvisionAction;
@@ -68,6 +69,10 @@ public interface ClusterTopology {
Map<String, HostGroupInfo> getHostGroupInfo();
/**
+ * @return all hosts in the topology
+ */
+ Set<String> getAllHosts();
+ /**
* Get the names of all of host groups which contain the specified component.
*
* @param component component name
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/ClusterTopologyImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/ClusterTopologyImpl.java
index 0945821..54abae2 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/ClusterTopologyImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/ClusterTopologyImpl.java
@@ -18,6 +18,7 @@
package org.apache.ambari.server.topology;
+import static java.util.stream.Collectors.toSet;
import static org.apache.ambari.server.controller.internal.ProvisionAction.INSTALL_AND_START;
import static org.apache.ambari.server.controller.internal.ProvisionAction.INSTALL_ONLY;
import static org.apache.ambari.server.state.ServiceInfo.HADOOP_COMPATIBLE_FS;
@@ -74,8 +75,6 @@ public class ClusterTopologyImpl implements ClusterTopology {
registerHostGroupInfo(topologyRequest.getHostGroupInfo());
- // todo extract validation to specialized service
- validateTopology();
this.ambariContext = ambariContext;
}
@@ -162,6 +161,11 @@ public class ClusterTopologyImpl implements ClusterTopology {
}
@Override
+ public Set<String> getAllHosts() {
+ return hostGroupInfoMap.values().stream().flatMap(hg -> hg.getHostNames().stream()).collect(toSet());
+ }
+
+ @Override
public Collection<String> getHostAssignmentsForComponent(String component) {
//todo: ordering requirements?
Collection<String> hosts = new ArrayList<>();
@@ -204,25 +208,6 @@ public class ClusterTopologyImpl implements ClusterTopology {
&& configProperties.get("yarn-site").get("yarn.resourcemanager.ha.enabled").equals("true");
}
- private void validateTopology()
- throws InvalidTopologyException {
-
- if(isNameNodeHAEnabled()){
- Collection<String> nnHosts = getHostAssignmentsForComponent("NAMENODE");
- if (nnHosts.size() < 2) {
- throw new InvalidTopologyException("NAMENODE HA requires at least 2 hosts running NAMENODE but there are: " +
- nnHosts.size() + " Hosts: " + nnHosts);
- }
- Map<String, String> hadoopEnvConfig = configuration.getFullProperties().get("hadoop-env");
- if(hadoopEnvConfig != null && !hadoopEnvConfig.isEmpty() && hadoopEnvConfig.containsKey("dfs_ha_initial_namenode_active") && hadoopEnvConfig.containsKey("dfs_ha_initial_namenode_standby")) {
- if((!HostGroup.HOSTGROUP_REGEX.matcher(hadoopEnvConfig.get("dfs_ha_initial_namenode_active")).matches() && !nnHosts.contains(hadoopEnvConfig.get("dfs_ha_initial_namenode_active")))
- || (!HostGroup.HOSTGROUP_REGEX.matcher(hadoopEnvConfig.get("dfs_ha_initial_namenode_standby")).matches() && !nnHosts.contains(hadoopEnvConfig.get("dfs_ha_initial_namenode_standby")))){
- throw new IllegalArgumentException("NAMENODE HA hosts mapped incorrectly for properties 'dfs_ha_initial_namenode_active' and 'dfs_ha_initial_namenode_standby'. Expected hosts are: " + nnHosts);
- }
- }
- }
- }
-
@Override
public boolean isClusterKerberosEnabled() {
return ambariContext.isClusterKerberosEnabled(getClusterId());
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/validators/NameNodeHaValidator.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/validators/NameNodeHaValidator.java
new file mode 100644
index 0000000..26a4d4f
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/validators/NameNodeHaValidator.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed 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.topology.validators;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ambari.server.topology.ClusterTopology;
+import org.apache.ambari.server.topology.HostGroup;
+import org.apache.ambari.server.topology.InvalidTopologyException;
+import org.apache.ambari.server.topology.TopologyValidator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+
+/**
+ * Validates NAMENODE HA setup correctness.
+ */
+public class NameNodeHaValidator implements TopologyValidator {
+
+ private static final Logger LOG = LoggerFactory.getLogger(NameNodeHaValidator.class);
+ private static final Splitter SPLITTER = Splitter.on(',').omitEmptyStrings().trimResults();
+
+ @Override
+ public void validate(ClusterTopology topology) throws InvalidTopologyException {
+ if( topology.isNameNodeHAEnabled() ) {
+
+ if (!allHostNamesAreKnown(topology)) {
+ LOG.warn("Provision cluster template contains hostgroup(s) without explicit hostnames. Cannot validate NAMENODE HA setup in this case.");
+ return;
+ }
+
+ Collection<String> nnHosts = topology.getHostAssignmentsForComponent("NAMENODE");
+
+ if (nnHosts.isEmpty()) {
+ LOG.info("NAMENODE HA is enabled but there are no NAMENODE components in the cluster. Assuming external name nodes.");
+ validateExternalNamenodeHa(topology);
+ }
+ else if (nnHosts.size() == 1) {
+ throw new InvalidTopologyException("NAMENODE HA requires at least two hosts running NAMENODE but there is only one: " +
+ nnHosts.iterator().next());
+ }
+
+ Map<String, String> hadoopEnvConfig = topology.getConfiguration().getFullProperties().get("hadoop-env");
+ if(hadoopEnvConfig != null && !hadoopEnvConfig.isEmpty() && hadoopEnvConfig.containsKey("dfs_ha_initial_namenode_active") && hadoopEnvConfig.containsKey("dfs_ha_initial_namenode_standby")) {
+ if((!HostGroup.HOSTGROUP_REGEX.matcher(hadoopEnvConfig.get("dfs_ha_initial_namenode_active")).matches() && !nnHosts.contains(hadoopEnvConfig.get("dfs_ha_initial_namenode_active")))
+ || (!HostGroup.HOSTGROUP_REGEX.matcher(hadoopEnvConfig.get("dfs_ha_initial_namenode_standby")).matches() && !nnHosts.contains(hadoopEnvConfig.get("dfs_ha_initial_namenode_standby")))){
+ throw new InvalidTopologyException("NAMENODE HA hosts mapped incorrectly for properties 'dfs_ha_initial_namenode_active' and 'dfs_ha_initial_namenode_standby'. Expected hosts are: " + nnHosts);
+ }
+ }
+ }
+
+ }
+
+ private boolean allHostNamesAreKnown(ClusterTopology topology) {
+ return topology.getHostGroupInfo().values().stream().allMatch(
+ hg -> !hg.getHostNames().isEmpty());
+ }
+
+ /**
+ * Verifies that all respective properties are set for an external NANENODE HA setup
+ */
+ public void validateExternalNamenodeHa(ClusterTopology topology) throws InvalidTopologyException {
+ Map<String, String> hdfsSite = topology.getConfiguration().getFullProperties().get("hdfs-site");
+ Set<String> hosts = topology.getAllHosts();
+
+ for (Map.Entry<String, List<String>> entry: getNameServicesToNameNodes(hdfsSite).entrySet()) {
+ String nameService = entry.getKey();
+ List<String> nameNodes = entry.getValue();
+ for (String nameNode: nameNodes) {
+ String address = hdfsSite.get("dfs.namenode.rpc-address." + nameService + "." + nameNode);
+ checkValidExternalNameNodeAddress(nameService, nameNode, address, hosts);
+ }
+ }
+ }
+
+ private void checkValidExternalNameNodeAddress(String nameService, String nameNode, String address, Set<String> hosts) throws InvalidTopologyException {
+ final String errorMessage = "Illegal external HA NAMENODE address for name service [%s], namenode [%s]: [%s]. Address " +
+ "must be in <host>:<port> format where host is external to the cluster.";
+
+ checkInvalidTopology(
+ Strings.isNullOrEmpty(address) || address.contains("localhost") || address.contains("%HOSTGROUP") || !address.contains(":"),
+ errorMessage, nameService, nameNode, address);
+
+ String hostName = address.substring(0, address.indexOf(':'));
+
+ checkInvalidTopology(hostName.isEmpty() || hosts.contains(hostName),
+ errorMessage, nameService, nameNode, address);
+ }
+
+ Map<String, List<String>> getNameServicesToNameNodes(Map<String, String> hdfsSite) throws InvalidTopologyException {
+ String nameServices = null != hdfsSite.get("dfs.internal.nameservices") ?
+ hdfsSite.get("dfs.internal.nameservices") : hdfsSite.get("dfs.nameservices");
+ Map<String, List<String>> nameServicesToNameNodes = new HashMap<>();
+
+ for (String ns: SPLITTER.splitToList(nameServices)) {
+ String nameNodes = hdfsSite.get("dfs.ha.namenodes." + ns);
+ checkInvalidTopology(Strings.isNullOrEmpty(nameNodes),
+ "No namenodes specified for nameservice %s.", ns);
+ nameServicesToNameNodes.put(ns, SPLITTER.splitToList(nameNodes));
+ }
+
+ return nameServicesToNameNodes;
+ }
+
+
+ private void checkInvalidTopology(boolean errorCondition, String errorMessageTemplate, Object... errorMessageParams) throws InvalidTopologyException {
+ if (errorCondition) throw new InvalidTopologyException(String.format(errorMessageTemplate, errorMessageParams));
+ }
+
+}
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/validators/TopologyValidatorFactory.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/validators/TopologyValidatorFactory.java
index bc76bff..56e1484 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/validators/TopologyValidatorFactory.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/validators/TopologyValidatorFactory.java
@@ -24,8 +24,13 @@ public class TopologyValidatorFactory {
List<TopologyValidator> validators;
public TopologyValidatorFactory() {
- validators = ImmutableList.of(new RequiredConfigPropertiesValidator(), new RequiredPasswordValidator(), new HiveServiceValidator(),
- new StackConfigTypeValidator(), new UnitValidator(UnitValidatedProperty.ALL));
+ validators = ImmutableList.of(
+ new RequiredConfigPropertiesValidator(),
+ new RequiredPasswordValidator(),
+ new HiveServiceValidator(),
+ new StackConfigTypeValidator(),
+ new UnitValidator(UnitValidatedProperty.ALL),
+ new NameNodeHaValidator());
}
public TopologyValidator createConfigurationValidatorChain() {
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/BlueprintConfigurationProcessorTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/BlueprintConfigurationProcessorTest.java
index 835cda5..e7cd271 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/BlueprintConfigurationProcessorTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/BlueprintConfigurationProcessorTest.java
@@ -5661,6 +5661,263 @@ public class BlueprintConfigurationProcessorTest extends EasyMockSupport {
assertEquals(ImmutableSet.of("cluster-env", "hdfs-site"), updatedConfigTypes);
}
+
+ @Test(expected = ConfigurationTopologyException.class)
+ public void testDoUpdateForClusterWithNameNodeHAEnabled_insufficientNameNodes() throws Exception {
+ final String expectedNameService = "mynameservice";
+ final String expectedHostName = "c6401.apache.ambari.org";
+ final String expectedHostNameTwo = "server-two";
+ final String expectedPortNum = "808080";
+ final String expectedNodeOne = "nn1";
+ final String expectedNodeTwo = "nn2";
+ final String expectedHostGroupName = "host_group_1";
+
+ Map<String, Map<String, String>> properties = new HashMap<>();
+
+ Map<String, String> hdfsSiteProperties = new HashMap<>();
+ Map<String, String> hbaseSiteProperties = new HashMap<>();
+ Map<String, String> hadoopEnvProperties = new HashMap<>();
+ Map<String, String> coreSiteProperties = new HashMap<>();
+ Map<String, String> accumuloSiteProperties = new HashMap<>();
+
+ properties.put("hdfs-site", hdfsSiteProperties);
+ properties.put("hadoop-env", hadoopEnvProperties);
+ properties.put("core-site", coreSiteProperties);
+ properties.put("hbase-site", hbaseSiteProperties);
+ properties.put("accumulo-site", accumuloSiteProperties);
+
+ // setup hdfs HA config for test
+ hdfsSiteProperties.put("dfs.nameservices", expectedNameService);
+ hdfsSiteProperties.put("dfs.ha.namenodes.mynameservice", expectedNodeOne + ", " + expectedNodeTwo);
+
+ // setup properties that include exported host group information
+ hdfsSiteProperties.put("dfs.namenode.https-address." + expectedNameService + "." + expectedNodeOne, createExportedAddress(expectedPortNum, expectedHostGroupName));
+ hdfsSiteProperties.put("dfs.namenode.https-address." + expectedNameService + "." + expectedNodeTwo, createExportedAddress(expectedPortNum, expectedHostGroupName));
+ hdfsSiteProperties.put("dfs.namenode.http-address." + expectedNameService + "." + expectedNodeOne, createExportedAddress(expectedPortNum, expectedHostGroupName));
+ hdfsSiteProperties.put("dfs.namenode.http-address." + expectedNameService + "." + expectedNodeTwo, createExportedAddress(expectedPortNum, expectedHostGroupName));
+ hdfsSiteProperties.put("dfs.namenode.rpc-address." + expectedNameService + "." + expectedNodeOne, createExportedAddress(expectedPortNum, expectedHostGroupName));
+ hdfsSiteProperties.put("dfs.namenode.rpc-address." + expectedNameService + "." + expectedNodeTwo, createExportedAddress(expectedPortNum, expectedHostGroupName));
+
+ // add properties that require the SECONDARY_NAMENODE, which
+ // is not included in this test
+ hdfsSiteProperties.put("dfs.secondary.http.address", "localhost:8080");
+ hdfsSiteProperties.put("dfs.namenode.secondary.http-address", "localhost:8080");
+
+
+ // add properties that are used in non-HA HDFS NameNode settings
+ // to verify that these are eventually removed by the filter
+ hdfsSiteProperties.put("dfs.namenode.http-address", "localhost:8080");
+ hdfsSiteProperties.put("dfs.namenode.https-address", "localhost:8081");
+ hdfsSiteProperties.put("dfs.namenode.rpc-address", "localhost:8082");
+
+ // configure the defaultFS to use the nameservice URL
+ coreSiteProperties.put("fs.defaultFS", "hdfs://" + expectedNameService);
+
+ // configure the hbase rootdir to use the nameservice URL
+ hbaseSiteProperties.put("hbase.rootdir", "hdfs://" + expectedNameService + "/hbase/test/root/dir");
+
+ // configure the hbase rootdir to use the nameservice URL
+ accumuloSiteProperties.put("instance.volumes", "hdfs://" + expectedNameService + "/accumulo/test/instance/volumes");
+
+ Configuration clusterConfig = new Configuration(properties, emptyMap());
+
+ // Only one namenode, two is expected
+ Collection<String> hgComponents = Sets.newHashSet("NAMENODE");
+ TestHostGroup group1 = new TestHostGroup(expectedHostGroupName, hgComponents, Collections.singleton(expectedHostName));
+
+ Collection<String> hgComponents2 = Sets.newHashSet("DATANODE");
+ TestHostGroup group2 = new TestHostGroup("host-group-2", hgComponents2, Collections.singleton(expectedHostNameTwo));
+
+ Collection<TestHostGroup> hostGroups = new ArrayList<>();
+ hostGroups.add(group1);
+ hostGroups.add(group2);
+
+ expect(stack.getCardinality("NAMENODE")).andReturn(new Cardinality("1-2")).anyTimes();
+ expect(stack.getCardinality("SECONDARY_NAMENODE")).andReturn(new Cardinality("1")).anyTimes();
+
+ ClusterTopology topology = createClusterTopology(bp, clusterConfig, hostGroups);
+ BlueprintConfigurationProcessor updater = new BlueprintConfigurationProcessor(topology);
+
+ updater.doUpdateForClusterCreate();
+ }
+
+ @Test
+ public void testDoUpdateForClusterWithNameNodeHAEnabled_externalNameNodes() throws Exception {
+ final String expectedNameService = "mynameservice";
+ final String expectedHostName = "c6401.apache.ambari.org";
+ final String expectedHostNameTwo = "server-two";
+ final String expectedPortNum = "808080";
+ final String expectedNodeOne = "nn1";
+ final String expectedNodeTwo = "nn2";
+ final String expectedHostGroupName = "host_group_1";
+
+ Map<String, Map<String, String>> properties = new HashMap<>();
+
+ Map<String, String> hdfsSiteProperties = new HashMap<>();
+ Map<String, String> hbaseSiteProperties = new HashMap<>();
+ Map<String, String> hadoopEnvProperties = new HashMap<>();
+ Map<String, String> coreSiteProperties = new HashMap<>();
+ Map<String, String> accumuloSiteProperties = new HashMap<>();
+
+ properties.put("hdfs-site", hdfsSiteProperties);
+ properties.put("hadoop-env", hadoopEnvProperties);
+ properties.put("core-site", coreSiteProperties);
+ properties.put("hbase-site", hbaseSiteProperties);
+ properties.put("accumulo-site", accumuloSiteProperties);
+
+ // setup hdfs HA config for test
+ hdfsSiteProperties.put("dfs.nameservices", expectedNameService);
+ hdfsSiteProperties.put("dfs.ha.namenodes.mynameservice", expectedNodeOne + ", " + expectedNodeTwo);
+
+ // need to set these to fqdn's for external namenodes
+ hdfsSiteProperties.put("dfs.namenode.rpc-address." + expectedNameService + "." + expectedNodeOne, "externalhost1:" + expectedPortNum);
+ hdfsSiteProperties.put("dfs.namenode.rpc-address." + expectedNameService + "." + expectedNodeTwo, "externalhost2:" + expectedPortNum);
+
+ // add properties that require the SECONDARY_NAMENODE, which
+ // is not included in this test
+ hdfsSiteProperties.put("dfs.secondary.http.address", "localhost:8080");
+ hdfsSiteProperties.put("dfs.namenode.secondary.http-address", "localhost:8080");
+
+
+ // add properties that are used in non-HA HDFS NameNode settings
+ // to verify that these are eventually removed by the filter
+ hdfsSiteProperties.put("dfs.namenode.http-address", "localhost:8080");
+ hdfsSiteProperties.put("dfs.namenode.https-address", "localhost:8081");
+ hdfsSiteProperties.put("dfs.namenode.rpc-address", "localhost:8082");
+
+ // configure the defaultFS to use the nameservice URL
+ coreSiteProperties.put("fs.defaultFS", "hdfs://" + expectedNameService);
+
+ // configure the hbase rootdir to use the nameservice URL
+ hbaseSiteProperties.put("hbase.rootdir", "hdfs://" + expectedNameService + "/hbase/test/root/dir");
+
+ // configure the hbase rootdir to use the nameservice URL
+ accumuloSiteProperties.put("instance.volumes", "hdfs://" + expectedNameService + "/accumulo/test/instance/volumes");
+
+ Configuration clusterConfig = new Configuration(properties, emptyMap());
+
+ // No namenodes. This will not be an error as external namenodes are assumed in this case.
+ Collection<String> hgComponents = Sets.newHashSet("JOURNALNODE");
+ TestHostGroup group1 = new TestHostGroup(expectedHostGroupName, hgComponents, Collections.singleton(expectedHostName));
+
+ Collection<String> hgComponents2 = Sets.newHashSet("DATANODE");
+ TestHostGroup group2 = new TestHostGroup("host-group-2", hgComponents2, Collections.singleton(expectedHostNameTwo));
+
+ Collection<TestHostGroup> hostGroups = new ArrayList<>();
+ hostGroups.add(group1);
+ hostGroups.add(group2);
+
+ expect(stack.getCardinality("NAMENODE")).andReturn(new Cardinality("1-2")).anyTimes();
+ expect(stack.getCardinality("SECONDARY_NAMENODE")).andReturn(new Cardinality("1")).anyTimes();
+
+ ClusterTopology topology = createClusterTopology(bp, clusterConfig, hostGroups);
+ BlueprintConfigurationProcessor updater = new BlueprintConfigurationProcessor(topology);
+
+ // Zero namenodes means external namenodes are assumed.
+ // Validation will pass as namenode rpc addresses are set to external fqdn addresses
+ Set<String> updatedConfigTypes = updater.doUpdateForClusterCreate();
+
+ // verify that doNameNodeHAUpdateOnClusterCreation() made no updates in the abscence of namenodes
+ assertFalse("dfs.internal.nameservices shouldn't be set", hdfsSiteProperties.containsKey("dfs.internal.nameservices"));
+ Map<String, String> clusterEnv = clusterConfig.getProperties().get("cluster-env");
+ assertFalse("dfs_ha_initial_namenode_active shouldn't be set", clusterEnv.containsKey("dfs_ha_initial_namenode_active"));
+ assertFalse("dfs_ha_initial_namenode_standby shouldn't be set", clusterEnv.containsKey("dfs_ha_initial_namenode_standby"));
+
+ assertEquals("fs.defaultFS should not be modified by cluster update when NameNode HA is enabled.",
+ "hdfs://" + expectedNameService, coreSiteProperties.get("fs.defaultFS"));
+
+ assertEquals("hbase.rootdir should not be modified by cluster update when NameNode HA is enabled.",
+ "hdfs://" + expectedNameService + "/hbase/test/root/dir", hbaseSiteProperties.get("hbase.rootdir"));
+
+ assertEquals("instance.volumes should not be modified by cluster update when NameNode HA is enabled.",
+ "hdfs://" + expectedNameService + "/accumulo/test/instance/volumes", accumuloSiteProperties.get("instance.volumes"));
+
+ // verify that the non-HA properties are filtered out in HA mode
+ assertFalse("dfs.namenode.http-address should have been filtered out of this HA configuration",
+ hdfsSiteProperties.containsKey("dfs.namenode.http-address"));
+ assertFalse("dfs.namenode.https-address should have been filtered out of this HA configuration",
+ hdfsSiteProperties.containsKey("dfs.namenode.https-address"));
+ assertFalse("dfs.namenode.rpc-address should have been filtered out of this HA configuration",
+ hdfsSiteProperties.containsKey("dfs.namenode.rpc-address"));
+
+ // verify that correct configuration types were listed as updated in the returned set
+ assertEquals(ImmutableSet.of("cluster-env", "hdfs-site"), updatedConfigTypes);
+ }
+
+ @Test(expected = ConfigurationTopologyException.class)
+ public void testDoUpdateForClusterWithNameNodeHAEnabled_externalNameNodes_invalid() throws Exception {
+ final String expectedNameService = "mynameservice";
+ final String expectedHostName = "c6401.apache.ambari.org";
+ final String expectedHostNameTwo = "server-two";
+ final String expectedPortNum = "808080";
+ final String expectedNodeOne = "nn1";
+ final String expectedNodeTwo = "nn2";
+ final String expectedHostGroupName = "host_group_1";
+
+ Map<String, Map<String, String>> properties = new HashMap<>();
+
+ Map<String, String> hdfsSiteProperties = new HashMap<>();
+ Map<String, String> hbaseSiteProperties = new HashMap<>();
+ Map<String, String> hadoopEnvProperties = new HashMap<>();
+ Map<String, String> coreSiteProperties = new HashMap<>();
+ Map<String, String> accumuloSiteProperties = new HashMap<>();
+
+ properties.put("hdfs-site", hdfsSiteProperties);
+ properties.put("hadoop-env", hadoopEnvProperties);
+ properties.put("core-site", coreSiteProperties);
+ properties.put("hbase-site", hbaseSiteProperties);
+ properties.put("accumulo-site", accumuloSiteProperties);
+
+ // setup hdfs HA config for test
+ hdfsSiteProperties.put("dfs.nameservices", expectedNameService);
+ hdfsSiteProperties.put("dfs.ha.namenodes.mynameservice", expectedNodeOne + ", " + expectedNodeTwo);
+
+ // add properties that require the SECONDARY_NAMENODE, which
+ // is not included in this test
+ hdfsSiteProperties.put("dfs.secondary.http.address", "localhost:8080");
+ hdfsSiteProperties.put("dfs.namenode.secondary.http-address", "localhost:8080");
+
+
+ // add properties that are used in non-HA HDFS NameNode settings
+ // to verify that these are eventually removed by the filter
+ hdfsSiteProperties.put("dfs.namenode.http-address", "localhost:8080");
+ hdfsSiteProperties.put("dfs.namenode.https-address", "localhost:8081");
+ hdfsSiteProperties.put("dfs.namenode.rpc-address", "localhost:8082");
+
+ // configure the defaultFS to use the nameservice URL
+ coreSiteProperties.put("fs.defaultFS", "hdfs://" + expectedNameService);
+
+ // configure the hbase rootdir to use the nameservice URL
+ hbaseSiteProperties.put("hbase.rootdir", "hdfs://" + expectedNameService + "/hbase/test/root/dir");
+
+ // configure the hbase rootdir to use the nameservice URL
+ accumuloSiteProperties.put("instance.volumes", "hdfs://" + expectedNameService + "/accumulo/test/instance/volumes");
+
+ Configuration clusterConfig = new Configuration(properties, emptyMap());
+
+ // No namenodes. This will not be an error as external namenodes are assumed in this case.
+ Collection<String> hgComponents = Sets.newHashSet("JOURNALNODE");
+ TestHostGroup group1 = new TestHostGroup(expectedHostGroupName, hgComponents, Collections.singleton(expectedHostName));
+
+ Collection<String> hgComponents2 = Sets.newHashSet("DATANODE");
+ TestHostGroup group2 = new TestHostGroup("host-group-2", hgComponents2, Collections.singleton(expectedHostNameTwo));
+
+ Collection<TestHostGroup> hostGroups = new ArrayList<>();
+ hostGroups.add(group1);
+ hostGroups.add(group2);
+
+ expect(stack.getCardinality("NAMENODE")).andReturn(new Cardinality("1-2")).anyTimes();
+ expect(stack.getCardinality("SECONDARY_NAMENODE")).andReturn(new Cardinality("1")).anyTimes();
+
+ ClusterTopology topology = createClusterTopology(bp, clusterConfig, hostGroups);
+ BlueprintConfigurationProcessor updater = new BlueprintConfigurationProcessor(topology);
+
+ // Zero namenodes means external namenodes are assumed.
+ // validation will fail as namenode rpc addresses are not set (must be set to external fqdn's)
+ Set<String> updatedConfigTypes = updater.doUpdateForClusterCreate();
+ }
+
+
@Test
public void testDoUpdateForClusterWithNameNodeHAEnabledThreeNameNodes() throws Exception {
final String expectedNameService = "mynameservice";
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterTopologyImplTest.java b/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterTopologyImplTest.java
index 7ce5098..d157ff6 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterTopologyImplTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterTopologyImplTest.java
@@ -49,11 +49,11 @@ public class ClusterTopologyImplTest {
private static final String CLUSTER_NAME = "cluster_name";
private static final long CLUSTER_ID = 1L;
private static final String predicate = "Hosts/host_name=foo";
- private static final Blueprint blueprint = createNiceMock(Blueprint.class);
- private static final HostGroup group1 = createNiceMock(HostGroup.class);
- private static final HostGroup group2 = createNiceMock(HostGroup.class);
- private static final HostGroup group3 = createNiceMock(HostGroup.class);
- private static final HostGroup group4 = createNiceMock(HostGroup.class);
+ private final Blueprint blueprint = createNiceMock(Blueprint.class);
+ private final HostGroup group1 = createNiceMock(HostGroup.class);
+ private final HostGroup group2 = createNiceMock(HostGroup.class);
+ private final HostGroup group3 = createNiceMock(HostGroup.class);
+ private final HostGroup group4 = createNiceMock(HostGroup.class);
private final Map<String, HostGroupInfo> hostGroupInfoMap = new HashMap<>();
private final Map<String, HostGroup> hostGroupMap = new HashMap<>();
@@ -148,7 +148,6 @@ public class ClusterTopologyImplTest {
verify(blueprint, group1, group2, group3, group4);
reset(blueprint, group1, group2, group3, group4);
-
hostGroupInfoMap.clear();
hostGroupMap.clear();
}
@@ -180,50 +179,6 @@ public class ClusterTopologyImplTest {
new ClusterTopologyImpl(null, request).getHostAssignmentsForComponent("component1");
}
- @Test(expected = InvalidTopologyException.class)
- public void testCreate_NNHAInvaid() throws Exception {
- bpconfiguration.setProperty("hdfs-site", "dfs.nameservices", "val");
- expect(group4.getName()).andReturn("group4");
- hostGroupInfoMap.get("group4").removeHost("host5");
- TestTopologyRequest request = new TestTopologyRequest(TopologyRequest.Type.PROVISION);
- replayAll();
- new ClusterTopologyImpl(null, request);
- hostGroupInfoMap.get("group4").addHost("host5");
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testCreate_NNHAHostNameNotCorrectForStandby() throws Exception {
- expect(group4.getName()).andReturn("group4");
- bpconfiguration.setProperty("hdfs-site", "dfs.nameservices", "val");
- bpconfiguration.setProperty("hadoop-env", "dfs_ha_initial_namenode_active", "host4");
- bpconfiguration.setProperty("hadoop-env", "dfs_ha_initial_namenode_standby", "val");
- TestTopologyRequest request = new TestTopologyRequest(TopologyRequest.Type.PROVISION);
- replayAll();
- new ClusterTopologyImpl(null, request);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testCreate_NNHAHostNameNotCorrectForActive() throws Exception {
- expect(group4.getName()).andReturn("group4");
- bpconfiguration.setProperty("hdfs-site", "dfs.nameservices", "val");
- bpconfiguration.setProperty("hadoop-env", "dfs_ha_initial_namenode_active", "val");
- bpconfiguration.setProperty("hadoop-env", "dfs_ha_initial_namenode_standby", "host5");
- TestTopologyRequest request = new TestTopologyRequest(TopologyRequest.Type.PROVISION);
- replayAll();
- new ClusterTopologyImpl(null, request);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testCreate_NNHAHostNameNotCorrectForStandbyWithActiveAsVariable() throws Exception {
- expect(group4.getName()).andReturn("group4");
- bpconfiguration.setProperty("hdfs-site", "dfs.nameservices", "val");
- bpconfiguration.setProperty("hadoop-env", "dfs_ha_initial_namenode_active", "%HOSTGROUP::group4%");
- bpconfiguration.setProperty("hadoop-env", "dfs_ha_initial_namenode_standby", "host6");
- TestTopologyRequest request = new TestTopologyRequest(TopologyRequest.Type.PROVISION);
- replayAll();
- new ClusterTopologyImpl(null, request);
- }
-
@Test
public void testDecidingIfComponentIsHadoopCompatible() throws Exception {
expect(blueprint.getServiceInfos()).andReturn(asList(
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/topology/validators/NameNodeHaValidatorTest.java b/ambari-server/src/test/java/org/apache/ambari/server/topology/validators/NameNodeHaValidatorTest.java
new file mode 100644
index 0000000..54f2648
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/topology/validators/NameNodeHaValidatorTest.java
@@ -0,0 +1,219 @@
+/*
+ * Licensed 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.topology.validators;
+
+
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ambari.server.topology.ClusterTopology;
+import org.apache.ambari.server.topology.ClusterTopologyImpl;
+import org.apache.ambari.server.topology.Configuration;
+import org.apache.ambari.server.topology.HostGroupInfo;
+import org.apache.ambari.server.topology.InvalidTopologyException;
+import org.easymock.EasyMockRunner;
+import org.easymock.Mock;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+@RunWith(EasyMockRunner.class)
+public class NameNodeHaValidatorTest {
+
+ Set<String> hosts = new HashSet<>();
+ Set<String> nameNodes = new HashSet<>();
+ Map<String, HostGroupInfo> hostGroupInfos = new HashMap<>();
+ Map<String, String> hdfsSite = new HashMap<>();
+ Map<String, String> hadoopEnv = new HashMap<>();
+ Map<String, Map<String, String>> fullProperties = ImmutableMap.of("hdfs-site", hdfsSite, "hadoop-env", hadoopEnv);
+ NameNodeHaValidator validator = new NameNodeHaValidator();
+
+ @Mock
+ Configuration clusterConfig;
+
+ @Mock
+ ClusterTopology clusterTopology;
+
+ @Before
+ public void setUp() {
+ expect(clusterTopology.getConfiguration()).andReturn(clusterConfig).anyTimes();
+ expect(clusterTopology.getAllHosts()).andReturn(hosts).anyTimes();
+ expect(clusterTopology.getHostAssignmentsForComponent("NAMENODE")).andReturn(nameNodes).anyTimes();
+ expect(clusterTopology.isNameNodeHAEnabled())
+ .andAnswer(() -> ClusterTopologyImpl.isNameNodeHAEnabled(fullProperties))
+ .anyTimes();
+ expect(clusterTopology.getHostGroupInfo()).andReturn(hostGroupInfos).anyTimes();
+ expect(clusterConfig.getFullProperties()).andReturn(fullProperties).anyTimes();
+ replay(clusterTopology, clusterConfig);
+ createDefaultHdfsSite();
+ hosts.addAll(ImmutableSet.of("internalhost1", "internalhost2"));
+ }
+
+ private void createDefaultHdfsSite() {
+ hdfsSite.put("dfs.nameservices", "demo1, demo2");
+ hdfsSite.put("dfs.ha.namenodes.demo1", "nn1, nn2");
+ hdfsSite.put("dfs.ha.namenodes.demo2", "nn1, nn2");
+ }
+
+ private void addExternalNameNodesToHdfsSite() {
+ hdfsSite.put("dfs.namenode.rpc-address.demo1.nn1", "demohost1:8020");
+ hdfsSite.put("dfs.namenode.rpc-address.demo1.nn2", "demohost2:8020");
+ hdfsSite.put("dfs.namenode.rpc-address.demo2.nn1", "demohost3:8020");
+ hdfsSite.put("dfs.namenode.rpc-address.demo2.nn2", "demohost4:8020");
+ }
+
+ @Test
+ public void nonHA() throws Exception {
+ hdfsSite.clear();
+ validator.validate(clusterTopology);
+ }
+
+ @Test
+ public void externalNameNodes_properConfig1() throws Exception {
+ addExternalNameNodesToHdfsSite();
+ validator.validate(clusterTopology);
+ }
+
+ @Test
+ public void externalNameNodes_properConfig2() throws Exception {
+ addExternalNameNodesToHdfsSite();
+ hdfsSite.remove("dfs.nameservices");
+ hdfsSite.put("dfs.internal.nameservices", "demo1, demo2");
+ validator.validate(clusterTopology);
+ }
+
+ @Test(expected = InvalidTopologyException.class)
+ public void externalNameNodes_missingNameNodes() throws Exception {
+ addExternalNameNodesToHdfsSite();
+ hdfsSite.remove("dfs.ha.namenodes.demo2");
+ validator.validate(clusterTopology);
+ }
+
+ @Test(expected = InvalidTopologyException.class)
+ public void externalNameNodes_missingRpcAddress() throws Exception {
+ addExternalNameNodesToHdfsSite();
+ hdfsSite.remove("dfs.namenode.rpc-address.demo2.nn1");
+ validator.validate(clusterTopology);
+ }
+
+ @Test(expected = InvalidTopologyException.class)
+ public void externalNameNodes_localhost() throws Exception {
+ addExternalNameNodesToHdfsSite();
+ hdfsSite.put("dfs.namenode.rpc-address.demo2.nn1", "localhost:8020");
+ validator.validate(clusterTopology);
+ }
+
+ @Test(expected = InvalidTopologyException.class)
+ public void externalNameNodes_hostGroupToken() throws Exception {
+ addExternalNameNodesToHdfsSite();
+ hdfsSite.put("dfs.namenode.rpc-address.demo2.nn1", "%HOSTGROUP::group1%:8020");
+ validator.validate(clusterTopology);
+ }
+
+ @Test(expected = InvalidTopologyException.class)
+ public void externalNameNodes_missingPort() throws Exception {
+ addExternalNameNodesToHdfsSite();
+ hdfsSite.put("dfs.namenode.rpc-address.demo2.nn1", "demohost3");
+ validator.validate(clusterTopology);
+ }
+
+ @Test(expected = InvalidTopologyException.class)
+ public void externalNameNodes_internalHost() throws Exception {
+ addExternalNameNodesToHdfsSite();
+ hdfsSite.put("dfs.namenode.rpc-address.demo2.nn1", "internalhost1:8020");
+ validator.validate(clusterTopology);
+ }
+
+ @Test
+ public void validationSkippedDueToMissingHostInformation() throws Exception {
+ // this is an invalid HA topology as there is only one namenode
+ nameNodes.add(hosts.iterator().next());
+
+ // adding a host group without hosts prohibits validation
+ HostGroupInfo groupInfo = new HostGroupInfo("group1");
+ hostGroupInfos.put(groupInfo.getHostGroupName(), groupInfo);
+
+ // yet this call should not throw exception as validation should be skipped
+ validator.validate(clusterTopology);
+ }
+
+ @Test(expected = InvalidTopologyException.class)
+ public void notEnoughNameNodesForHa() throws Exception {
+ nameNodes.add(hosts.iterator().next());
+ validator.validate(clusterTopology);
+ }
+
+ @Test
+ public void haNoInitialSetup() throws Exception {
+ List<String> nameNodeHosts = ImmutableList.copyOf(hosts).subList(0, 2);
+ nameNodes.addAll(nameNodeHosts);
+ validator.validate(clusterTopology);
+ }
+
+ @Test
+ public void haProperInitialSetup() throws Exception {
+ List<String> nameNodeHosts = ImmutableList.copyOf(hosts).subList(0, 2);
+ nameNodes.addAll(nameNodeHosts);
+
+ hadoopEnv.put("dfs_ha_initial_namenode_active", nameNodeHosts.get(0));
+ hadoopEnv.put("dfs_ha_initial_namenode_active", nameNodeHosts.get(1));
+
+ validator.validate(clusterTopology);
+ }
+
+ @Test
+ public void haProperInitialSetupWithHostGroups() throws Exception {
+ List<String> nameNodeHosts = ImmutableList.copyOf(hosts).subList(0, 2);
+ nameNodes.addAll(nameNodeHosts);
+
+ hadoopEnv.put("dfs_ha_initial_namenode_active", "%HOSTGROUP::group1%");
+ hadoopEnv.put("dfs_ha_initial_namenode_standby", "%HOSTGROUP::group2%");
+
+ validator.validate(clusterTopology);
+ }
+
+ @Test(expected = InvalidTopologyException.class)
+ public void haInvalidInitialActive() throws Exception {
+ List<String> nameNodeHosts = ImmutableList.copyOf(hosts).subList(0, 2);
+ nameNodes.addAll(nameNodeHosts);
+
+ hadoopEnv.put("dfs_ha_initial_namenode_active", "externalhost");
+ hadoopEnv.put("dfs_ha_initial_namenode_standby", nameNodeHosts.get(0));
+
+ validator.validate(clusterTopology);
+ }
+
+ @Test(expected = InvalidTopologyException.class)
+ public void haInvalidInitialStandby() throws Exception {
+ List<String> nameNodeHosts = ImmutableList.copyOf(hosts).subList(0, 2);
+ nameNodes.addAll(nameNodeHosts);
+
+ hadoopEnv.put("dfs_ha_initial_namenode_active", nameNodeHosts.get(0));
+ hadoopEnv.put("dfs_ha_initial_namenode_standby", "externalhost");
+
+ validator.validate(clusterTopology);
+ }
+
+}
\ No newline at end of file