package com.sequenceiq.cloudbreak.controller.validation.blueprint;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
import com.sequenceiq.cloudbreak.controller.BadRequestException;
import com.sequenceiq.cloudbreak.domain.Blueprint;
import com.sequenceiq.cloudbreak.domain.HostGroup;
import com.sequenceiq.cloudbreak.domain.InstanceGroup;
@Component
public class BlueprintValidator {
private static final Logger LOGGER = LoggerFactory.getLogger(BlueprintValidator.class);
private ObjectMapper objectMapper = new ObjectMapper();
@Inject
private StackServiceComponentDescriptors stackServiceComponentDescs;
public void validateBlueprintForStack(Blueprint blueprint, Set<HostGroup> hostGroups, Set<InstanceGroup> instanceGroups) {
try {
JsonNode hostGroupsNode = getHostGroupNode(blueprint);
validateHostGroups(hostGroupsNode, hostGroups, instanceGroups);
Map<String, HostGroup> hostGroupMap = createHostGroupMap(hostGroups);
Map<String, BlueprintServiceComponent> blueprintServiceComponentMap = Maps.newHashMap();
for (JsonNode hostGroupNode : hostGroupsNode) {
validateHostGroup(hostGroupNode, hostGroupMap, blueprintServiceComponentMap);
}
validateBlueprintServiceComponents(blueprintServiceComponentMap);
} catch (IOException e) {
throw new BadRequestException(String.format("Blueprint [%s] can not be parsed from JSON.", blueprint.getId()));
}
}
public JsonNode getHostGroupNode(Blueprint blueprint) throws IOException {
JsonNode blueprintJsonTree = createJsonTree(blueprint);
return blueprintJsonTree.get("host_groups");
}
private JsonNode createJsonTree(Blueprint blueprint) throws IOException {
return objectMapper.readTree(blueprint.getBlueprintText());
}
private void validateHostGroups(JsonNode hostGroupsNode, Set<HostGroup> hostGroups, Set<InstanceGroup> instanceGroups) {
Set<String> hostGroupsInRequest = getHostGroupsFromRequest(hostGroups);
Set<String> hostGroupsInBlueprint = getHostGroupsFromBlueprint(hostGroupsNode);
if (!hostGroupsInRequest.containsAll(hostGroupsInBlueprint) || !hostGroupsInBlueprint.containsAll(hostGroupsInRequest)) {
throw new BadRequestException("The host groups in the blueprint must match the hostgroups in the request.");
}
if (!instanceGroups.isEmpty()) {
Set<String> instanceGroupNames = new HashSet<>();
for (HostGroup hostGroup : hostGroups) {
String instanceGroupName = hostGroup.getConstraint().getInstanceGroup().getGroupName();
if (instanceGroupNames.contains(instanceGroupName)) {
throw new BadRequestException(String.format(
"Instance group '%s' is assigned to more than one hostgroup.", instanceGroupName));
}
instanceGroupNames.add(instanceGroupName);
}
if (instanceGroups.size() < hostGroupsInRequest.size()) {
throw new BadRequestException("Each host group must have an instance group");
}
}
}
public void validateHostGroupScalingRequest(Blueprint blueprint, HostGroup hostGroup, Integer adjustment) {
try {
JsonNode hostGroupsNode = getHostGroupNode(blueprint);
Map<String, HostGroup> hostGroupMap = createHostGroupMap(Collections.singleton(hostGroup));
for (JsonNode hostGroupNode : hostGroupsNode) {
if (hostGroup.getName().equals(hostGroupNode.get("name").asText())) {
hostGroup.getConstraint().setHostCount(hostGroup.getConstraint().getHostCount() + adjustment);
try {
validateHostGroup(hostGroupNode, hostGroupMap, new HashMap<>());
} finally {
hostGroup.getConstraint().setHostCount(hostGroup.getConstraint().getHostCount() - adjustment);
}
break;
}
}
} catch (IOException e) {
throw new BadRequestException(String.format("Blueprint [%s] can not be parsed from JSON.", blueprint.getId()));
}
}
private Set<String> getHostGroupsFromRequest(Set<HostGroup> hostGroup) {
return hostGroup.stream().map(HostGroup::getName).collect(Collectors.toSet());
}
private Set<String> getHostGroupsFromBlueprint(JsonNode hostGroupsNode) {
Set<String> hostGroupsInBlueprint = new HashSet<>();
Iterator<JsonNode> hostGroups = hostGroupsNode.elements();
while (hostGroups.hasNext()) {
hostGroupsInBlueprint.add(hostGroups.next().get("name").asText());
}
return hostGroupsInBlueprint;
}
public Map<String, HostGroup> createHostGroupMap(Set<HostGroup> hostGroups) {
Map<String, HostGroup> groupMap = Maps.newHashMap();
for (HostGroup hostGroup : hostGroups) {
groupMap.put(hostGroup.getName(), hostGroup);
}
return groupMap;
}
private void validateHostGroup(JsonNode hostGroupNode, Map<String, HostGroup> hostGroupMap,
Map<String, BlueprintServiceComponent> blueprintServiceComponentMap) {
String hostGroupName = getHostGroupName(hostGroupNode);
HostGroup hostGroup = getHostGroup(hostGroupMap, hostGroupName);
JsonNode componentsNode = getComponentsNode(hostGroupNode);
for (JsonNode componentNode : componentsNode) {
validateComponent(componentNode, hostGroup, blueprintServiceComponentMap);
}
}
public String getHostGroupName(JsonNode hostGroupNode) {
return hostGroupNode.get("name").asText();
}
public HostGroup getHostGroup(Map<String, HostGroup> hostGroupMap, String hostGroupName) {
return hostGroupMap.get(hostGroupName);
}
public JsonNode getComponentsNode(JsonNode hostGroupNode) {
return hostGroupNode.get("components");
}
private void validateComponent(JsonNode componentNode, HostGroup hostGroup, Map<String, BlueprintServiceComponent> blueprintServiceComponentMap) {
String componentName = componentNode.get("name").asText();
StackServiceComponentDescriptor componentDescriptor = stackServiceComponentDescs.get(componentName);
if (componentDescriptor != null) {
validateComponentCardinality(componentDescriptor, hostGroup);
updateBlueprintServiceComponentMap(componentDescriptor, hostGroup, blueprintServiceComponentMap);
}
}
private void validateComponentCardinality(StackServiceComponentDescriptor componentDescriptor, HostGroup hostGroup) {
int nodeCount = hostGroup.getConstraint().getHostCount();
int minCardinality = componentDescriptor.getMinCardinality();
int maxCardinality = componentDescriptor.getMaxCardinality();
if (componentDescriptor.isMaster() && !isNodeCountCorrect(nodeCount, minCardinality, maxCardinality)) {
throw new BadRequestException(String.format(
"The node count '%d' for hostgroup '%s' cannot be less than '%d' or more than '%d' because of '%s' component", nodeCount,
hostGroup.getName(), minCardinality, maxCardinality, componentDescriptor.getName()));
}
}
private void validateBlueprintServiceComponents(Map<String, BlueprintServiceComponent> blueprintServiceComponentMap) {
for (BlueprintServiceComponent blueprintServiceComponent : blueprintServiceComponentMap.values()) {
String componentName = blueprintServiceComponent.getName();
int nodeCount = blueprintServiceComponent.getNodeCount();
StackServiceComponentDescriptor stackServiceComponentDescriptor = stackServiceComponentDescs.get(componentName);
if (stackServiceComponentDescriptor != null) {
int minCardinality = stackServiceComponentDescriptor.getMinCardinality();
int maxCardinality = stackServiceComponentDescriptor.getMaxCardinality();
if (!isNodeCountCorrect(nodeCount, minCardinality, maxCardinality) && !(nodeCount == 0 && !stackServiceComponentDescriptor.isMaster())) {
throw new BadRequestException(String.format("Incorrect number of '%s' components are in '%s' hostgroups: count: %d, min: %d max: %d",
componentName, blueprintServiceComponent.getHostgroups().toString(), nodeCount, minCardinality, maxCardinality));
}
}
}
}
private boolean isNodeCountCorrect(int nodeCount, int minCardinality, int maxCardinality) {
return minCardinality <= nodeCount && nodeCount <= maxCardinality;
}
private void updateBlueprintServiceComponentMap(StackServiceComponentDescriptor componentDescriptor, HostGroup hostGroup,
Map<String, BlueprintServiceComponent> blueprintServiceComponentMap) {
String componentName = componentDescriptor.getName();
BlueprintServiceComponent blueprintServiceComponent = blueprintServiceComponentMap.get(componentName);
if (blueprintServiceComponent == null) {
blueprintServiceComponent = new BlueprintServiceComponent(componentName, hostGroup.getName(), hostGroup.getConstraint().getHostCount());
blueprintServiceComponentMap.put(componentName, blueprintServiceComponent);
} else {
blueprintServiceComponent.update(hostGroup);
}
}
}