/***************************************************************************
* Copyright (c) 2014-2015 VMware, Inc. All Rights Reserved.
* 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 com.vmware.bdd.plugin.clouderamgr.service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.gson.Gson;
import com.vmware.bdd.plugin.clouderamgr.model.support.AvailableServiceRole;
import com.vmware.bdd.plugin.clouderamgr.model.support.AvailableServiceRoleContainer;
import com.vmware.bdd.plugin.clouderamgr.utils.CmUtils;
import com.vmware.bdd.software.mgmt.plugin.exception.ValidationException;
import com.vmware.bdd.software.mgmt.plugin.model.ClusterBlueprint;
import com.vmware.bdd.software.mgmt.plugin.model.NodeGroupInfo;
import com.vmware.bdd.usermgmt.UserMgmtConstants;
import com.vmware.bdd.utils.Constants;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
/**
* Author: Xiaoding Bian
* Date: 7/11/14
* Time: 1:37 PM
*/
public class CmClusterValidator {
private static final Logger logger = Logger.getLogger(CmClusterValidator.class);
private List<String> warningMsgList;
private List<String> errorMsgList;
public CmClusterValidator() {
this.warningMsgList = new ArrayList<String>();
this.errorMsgList = new ArrayList<String>();
}
public boolean validateBlueprint(ClusterBlueprint blueprint) {
logger.info("Start to validate blueprint for cluster " + blueprint.getName());
String distro = blueprint.getHadoopStack().getDistro();
String distroVersion = CmUtils.distroVersionOfHadoopStack(blueprint.getHadoopStack());
try {
List<String> unRecogConfigTypes = new ArrayList<String>();
List<String> unRecogConfigKeys = new ArrayList<>();
validateConfigs(blueprint.getConfiguration(), unRecogConfigTypes, unRecogConfigKeys, distroVersion);
Set<String> availableRoles = AvailableServiceRoleContainer.allRoles(distroVersion);
Set<String> definedServices = new HashSet<String>();
Map<String, Integer> definedRoles = new HashMap<String, Integer>();
List<String> unRecogRoles = null;
Set<String> invalidRacks = null;
if (blueprint.getNodeGroups() == null || blueprint.getNodeGroups().isEmpty()) {
return false;
}
int nnGroupsNum = 0;
for (NodeGroupInfo group : blueprint.getNodeGroups()) {
validateConfigs(group.getConfiguration(), unRecogConfigTypes, unRecogConfigKeys, distroVersion);
if (group.getRoles().contains("HDFS_NAMENODE")) {
nnGroupsNum++;
}
for (String roleName: group.getRoles()) {
if (!availableRoles.contains(roleName)) {
if (unRecogRoles == null) {
unRecogRoles = new ArrayList<String>();
}
unRecogRoles.add(roleName);
} else {
if (!definedRoles.containsKey(roleName)) {
definedRoles.put(roleName, group.getInstanceNum());
} else {
Integer instanceNum = definedRoles.get(roleName) + group.getInstanceNum();
definedRoles.put(roleName, instanceNum);
}
definedServices.add(AvailableServiceRoleContainer.load(roleName).getParent().getDisplayName());
}
}
}
if (nnGroupsNum > 1) {
errorMsgList.add("Namenode federation is not supported currently");
}
if (unRecogRoles != null && !unRecogRoles.isEmpty()) { // point 1: unrecognized roles
errorMsgList.add("Roles " + unRecogRoles.toString() + " are not available by distro " + distro);
}
if (!unRecogConfigTypes.isEmpty()) { // point 2: add to warning list as will be ignored by creating logic
warningMsgList.add("Configurations for " + unRecogConfigTypes.toString() + " are not available by distro " + distro);
}
if (!unRecogConfigKeys.isEmpty()) { // point 3
errorMsgList.add("Configuration items " + unRecogConfigKeys.toString() + " are invalid");
}
if (invalidRacks != null && !invalidRacks.isEmpty()) {
errorMsgList.add("Racks " + invalidRacks.toString() + " are invalid," +
" rack names must be slash-separated, like Unix paths. For example, \"/rack1\" and \"/cabinet3/rack4\"");
}
for (String serviceName : definedServices) {
// service dependency check
for (AvailableServiceRole.Dependency dependency : AvailableServiceRoleContainer.load(serviceName).getDependencies()) {
if (!dependency.isRequired()) {
continue;
}
if (dependency.getServices().size() == 1 && !definedServices.contains(dependency.getServices().get(0))) {
if (serviceName.equals("YARN") && isComputeOnly(definedServices)) {
continue;
}
warningMsgList.add(serviceName + " depends on " + dependency.getServices().get(0) + " service");
} else {
boolean found = false;
for (String dependService : dependency.getServices()) {
if (definedServices.contains(dependService)) {
found = true;
}
}
if (!found) {
warningMsgList.add(serviceName + " depends on one service of " + dependency.getServices().toString());
}
}
}
Set<String> requiredRoles = new HashSet<String>();
switch (serviceName) {
case "HDFS":
if (!isComputeOnly(definedServices)) {
requiredRoles.add("HDFS_NAMENODE");
requiredRoles.add("HDFS_DATANODE");
}
if (checkRequiredRoles(serviceName, requiredRoles, definedRoles.keySet(), errorMsgList)) {
if (nnGroupsNum == 1) {
if (definedRoles.get("HDFS_NAMENODE") < 2 && !definedRoles.containsKey("HDFS_SECONDARY_NAMENODE")) {
errorMsgList.add("HDFS service not configured for High Availability must have a SecondaryNameNode");
}
if (definedRoles.get("HDFS_NAMENODE") >= 2 && !definedRoles.containsKey("HDFS_JOURNALNODE")) {
errorMsgList.add("HDFS service configured for High Availability must have journal nodes");
}
}
}
if (definedRoles.containsKey("HDFS_JOURNALNODE")) {
if (definedRoles.get("HDFS_JOURNALNODE") > 1 && definedRoles.get("HDFS_JOURNALNODE") < 3) {
errorMsgList.add(Constants.WRONG_NUM_OF_JOURNALNODE);
} else if (definedRoles.get("HDFS_JOURNALNODE") % 2 == 0) {
warningMsgList.add(Constants.ODD_NUM_OF_JOURNALNODE);
}
}
break;
case "YARN":
requiredRoles.add("YARN_RESOURCE_MANAGER");
requiredRoles.add("YARN_NODE_MANAGER");
requiredRoles.add("YARN_JOB_HISTORY");
if (checkRequiredRoles(serviceName, requiredRoles, definedRoles.keySet(), errorMsgList)) {
if (definedRoles.get("YARN_RESOURCE_MANAGER") > 1) {
errorMsgList.add(Constants.WRONG_NUM_OF_RESOURCEMANAGER);
}
}
break;
case "MAPREDUCE":
requiredRoles.add("MAPREDUCE_JOBTRACKER");
requiredRoles.add("MAPREDUCE_TASKTRACKER");
if (checkRequiredRoles(serviceName, requiredRoles, definedRoles.keySet(), errorMsgList)) {
if (definedRoles.get("MAPREDUCE_JOBTRACKER") > 1) {
errorMsgList.add(Constants.WRONG_NUM_OF_JOBTRACKER);
}
}
break;
case "HBASE":
requiredRoles.add("HBASE_MASTER");
requiredRoles.add("HBASE_REGION_SERVER");
checkRequiredRoles(serviceName, requiredRoles, definedRoles.keySet(), errorMsgList);
break;
case "ZOOKEEPER":
requiredRoles.add("ZOOKEEPER_SERVER");
if (checkRequiredRoles(serviceName, requiredRoles, definedRoles.keySet(), errorMsgList)) {
if (definedRoles.get("ZOOKEEPER_SERVER") > 0 && definedRoles.get("ZOOKEEPER_SERVER") < 3) {
errorMsgList.add(Constants.WRONG_NUM_OF_ZOOKEEPER);
} else if (definedRoles.get("ZOOKEEPER_SERVER") % 2 == 0) {
warningMsgList.add(Constants.ODD_NUM_OF_ZOOKEEPER);
}
}
break;
case "HIVE":
requiredRoles.add("HIVE_METASTORE");
requiredRoles.add("HIVE_SERVER2");
checkRequiredRoles(serviceName, requiredRoles, definedRoles.keySet(), errorMsgList);
String[] requiredConfigs = {
"hive_metastore_database_host",
"hive_metastore_database_name",
"hive_metastore_database_password",
"hive_metastore_database_port",
"hive_metastore_database_type",
"hive_metastore_database_user"
};
boolean configured = true;
if (blueprint.getConfiguration().containsKey("HIVE")) {
Map<String, String> configuredItems = (Map<String, String>) blueprint.getConfiguration().get("HIVE");
for (String item : requiredConfigs) {
if (!configuredItems.containsKey(item)) {
configured = false;
break;
}
}
} else {
configured = false;
}
if (!configured) {
errorMsgList.add("HIVE service depends on an external database, please setup one and provide configuration properties ["
+ StringUtils.join(requiredConfigs, ",") + "] for HIVE service");
}
break;
case "OOZIE":
if (definedRoles.get("OOZIE_SERVER") > 1) {
errorMsgList.add("only one OOZIE_SERVER is allowed for OOZIE service");
}
break;
case "SENTRY":
if (definedRoles.get("SENTRY_SERVER") > 1) {
errorMsgList.add("only one SENTRY_SERVER is allowed for SENTRY service");
}
break;
case "SQOOP":
if (definedRoles.get("SQOOP_SERVER") > 1) {
errorMsgList.add("only one SQOOP_SERVER is allowed for SQOOP service");
}
break;
case "ISILON":
requiredRoles.add("YARN_RESOURCE_MANAGER");
requiredRoles.add("YARN_JOB_HISTORY");
requiredRoles.add("YARN_NODE_MANAGER");
requiredRoles.add("GATEWAY");
break;
default:
break;
}
}
} catch (IOException e) {
// IO exception ignored
}
if (!warningMsgList.isEmpty() || !errorMsgList.isEmpty()) {
throw ValidationException.VALIDATION_FAIL("Blueprint", errorMsgList, warningMsgList);
}
return true;
}
private boolean checkRequiredRoles(String serviceName, Set<String> requiredRoles, Set<String> definedRoles,
List<String> errorMsgList) {
requiredRoles.removeAll(definedRoles);
if (!requiredRoles.isEmpty()) {
errorMsgList.add("Service " + serviceName + " requires roles " + requiredRoles.toString());
return false;
}
return true;
}
private void validateConfigs(Map<String, Object> config, List<String> unRecogConfigTypes,
List<String> unRecogConfigKeys, String distroVersion) {
if (config == null || config.isEmpty()) {
return;
}
for (String key : config.keySet()) {
try {
if (key.equals(UserMgmtConstants.SERVICE_USER_CONFIG_IN_SPEC_FILE)) {
Map<String, Map<String, String>> configs = (Map<String, Map<String, String>>)config.get(UserMgmtConstants.SERVICE_USER_CONFIG_IN_SPEC_FILE);
List<Map<String, String>> configList = new ArrayList<>(configs.values());
validateServiceUserConfigs(configList, errorMsgList);
continue;
}
AvailableServiceRole def = AvailableServiceRoleContainer.load(key);
if (!AvailableServiceRoleContainer.isSupported(distroVersion, def)) {
unRecogConfigTypes.add(key);
continue;
}
Map<String, String> items = (Map<String, String>) config.get(key);
for (String subKey : items.keySet()) {
if (!def.getAvailableConfigurations().containsKey(subKey)) {
unRecogConfigKeys.add(subKey);
}
}
} catch (IOException e) {
unRecogConfigTypes.add(key);
}
}
}
private void validateServiceUserConfigs(List<Map<String, String>>configs, List<String> errorMsgList) {
for (Map<String, String> config: configs) {
for (String key: config.keySet()) {
if (!key.equalsIgnoreCase(UserMgmtConstants.SERVICE_USER_GROUP) &&
!key.equalsIgnoreCase(UserMgmtConstants.SERVICE_USER_NAME) &&
!key.equalsIgnoreCase(UserMgmtConstants.SERVICE_USER_TYPE)) {
errorMsgList.add("Service user config doesn't support key: " + key);
}
if (key.equalsIgnoreCase(UserMgmtConstants.SERVICE_USER_TYPE) &&
!config.get(key).equalsIgnoreCase("LDAP")) {
errorMsgList.add("You must use LDAP user to customize service user in Cloudera manager deployed cluster");
}
}
}
}
@Override
public String toString() {
Map<String, List<String>> message = new HashMap<String, List<String>>();
message.put("WarningMsgList", this.warningMsgList);
message.put("ErrorMsgList", this.errorMsgList);
return (new Gson()).toJson(message);
}
private boolean isComputeOnly(Set<String> definedServices) {
boolean isComputeOnly = false;
if (definedServices.contains("ISILON")) {
isComputeOnly = true;
}
return isComputeOnly;
}
}