org.finra.herd.service.helper.EmrClusterDefinitionHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of herd-service Show documentation
Show all versions of herd-service Show documentation
This project contains the business service code. This is a classic service tier where business logic is defined along with it's associated
transaction management configuration.
/*
* Copyright 2015 herd contributors
*
* 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.finra.herd.service.helper;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.finra.herd.core.helper.ConfigurationHelper;
import org.finra.herd.dao.helper.EmrHelper;
import org.finra.herd.dao.helper.HerdStringHelper;
import org.finra.herd.model.api.xml.EmrClusterDefinition;
import org.finra.herd.model.api.xml.EmrClusterDefinitionKey;
import org.finra.herd.model.api.xml.InstanceDefinition;
import org.finra.herd.model.api.xml.MasterInstanceDefinition;
import org.finra.herd.model.api.xml.NodeTag;
import org.finra.herd.model.dto.ConfigurationValue;
/**
* A helper class for EmrClusterDefinition related code.
*/
@Component
public class EmrClusterDefinitionHelper
{
@Autowired
private AlternateKeyHelper alternateKeyHelper;
@Autowired
private ConfigurationHelper configurationHelper;
@Autowired
private EmrHelper emrHelper;
@Autowired
private HerdStringHelper herdStringHelper;
/**
* Validates an EMR cluster definition configuration.
*
* @param emrClusterDefinition the EMR cluster definition configuration
*
* @throws IllegalArgumentException if any validation errors were found
*/
public void validateEmrClusterDefinitionConfiguration(EmrClusterDefinition emrClusterDefinition) throws IllegalArgumentException
{
Assert.notNull(emrClusterDefinition, "An EMR cluster definition configuration must be specified.");
Assert.isTrue(StringUtils.isNotBlank(emrClusterDefinition.getSubnetId()), "Subnet ID must be specified");
for (String token : emrClusterDefinition.getSubnetId().split(","))
{
Assert.isTrue(StringUtils.isNotBlank(token), "No blank is allowed in the list of subnet IDs");
}
Assert.isTrue(!emrHelper.isInstanceDefinitionsEmpty(emrClusterDefinition.getInstanceDefinitions()) ||
CollectionUtils.isNotEmpty(emrClusterDefinition.getInstanceFleets()), "Instance group definitions or instance fleets must be specified.");
if (!emrHelper.isInstanceDefinitionsEmpty(emrClusterDefinition.getInstanceDefinitions()))
{
// Check master instances.
Assert.notNull(emrClusterDefinition.getInstanceDefinitions().getMasterInstances(), "Master instances must be specified.");
validateMasterInstanceDefinition(emrClusterDefinition.getInstanceDefinitions().getMasterInstances());
// Check core instances.
if (emrClusterDefinition.getInstanceDefinitions().getCoreInstances() != null)
{
validateInstanceDefinition("core", emrClusterDefinition.getInstanceDefinitions().getCoreInstances(), 0);
// If instance count is <= 0, remove the entire core instance definition since it is redundant.
if (emrClusterDefinition.getInstanceDefinitions().getCoreInstances().getInstanceCount() <= 0)
{
emrClusterDefinition.getInstanceDefinitions().setCoreInstances(null);
}
}
// Check task instances
if (emrClusterDefinition.getInstanceDefinitions().getTaskInstances() != null)
{
validateInstanceDefinition("task", emrClusterDefinition.getInstanceDefinitions().getTaskInstances(), 1);
}
// Check that total number of instances does not exceed the max allowed.
int maxEmrInstanceCount = configurationHelper.getProperty(ConfigurationValue.MAX_EMR_INSTANCES_COUNT, Integer.class);
if (maxEmrInstanceCount > 0)
{
int instancesRequested = emrClusterDefinition.getInstanceDefinitions().getMasterInstances().getInstanceCount();
if (emrClusterDefinition.getInstanceDefinitions().getCoreInstances() != null)
{
instancesRequested += emrClusterDefinition.getInstanceDefinitions().getCoreInstances().getInstanceCount();
}
if (emrClusterDefinition.getInstanceDefinitions().getTaskInstances() != null)
{
instancesRequested += emrClusterDefinition.getInstanceDefinitions().getTaskInstances().getInstanceCount();
}
Assert.isTrue((maxEmrInstanceCount >= instancesRequested), "Total number of instances requested can not exceed : " + maxEmrInstanceCount);
}
}
// Validate node tags including checking for required tags and detecting any duplicate node tag names in case sensitive manner.
Assert.notEmpty(emrClusterDefinition.getNodeTags(), "Node tags must be specified.");
HashSet nodeTagNameValidationSet = new HashSet<>();
for (NodeTag nodeTag : emrClusterDefinition.getNodeTags())
{
Assert.hasText(nodeTag.getTagName(), "A node tag name must be specified.");
Assert.hasText(nodeTag.getTagValue(), "A node tag value must be specified.");
Assert.isTrue(!nodeTagNameValidationSet.contains(nodeTag.getTagName()), String.format("Duplicate node tag \"%s\" is found.", nodeTag.getTagName()));
nodeTagNameValidationSet.add(nodeTag.getTagName());
}
// Validate the mandatory AWS tags are there
for (String mandatoryTag : herdStringHelper.splitStringWithDefaultDelimiter(configurationHelper.getProperty(ConfigurationValue.MANDATORY_AWS_TAGS)))
{
Assert.isTrue(nodeTagNameValidationSet.contains(mandatoryTag), String.format("Mandatory AWS tag not specified: \"%s\"", mandatoryTag));
}
emrClusterDefinition.setAdditionalMasterSecurityGroups(
assertNotBlankAndTrim(emrClusterDefinition.getAdditionalMasterSecurityGroups(), "additionalMasterSecurityGroup"));
emrClusterDefinition
.setAdditionalSlaveSecurityGroups(assertNotBlankAndTrim(emrClusterDefinition.getAdditionalSlaveSecurityGroups(), "additionalSlaveSecurityGroup"));
// Fail if security configuration is specified for EMR version less than 4.8.0.
if (StringUtils.isNotBlank(emrClusterDefinition.getSecurityConfiguration()))
{
final DefaultArtifactVersion securityConfigurationMinEmrVersion = new DefaultArtifactVersion("4.8.0");
Assert.isTrue(StringUtils.isNotBlank(emrClusterDefinition.getReleaseLabel()) &&
securityConfigurationMinEmrVersion.compareTo(new DefaultArtifactVersion(emrClusterDefinition.getReleaseLabel().replaceFirst("^(emr-)", ""))) <=
0, "EMR security configuration is not supported prior to EMR release 4.8.0.");
}
}
/**
* Validates the EMR cluster definition key. This method also trims the key parameters.
*
* @param key the EMR cluster definition key
*
* @throws IllegalArgumentException if any validation errors were found
*/
public void validateEmrClusterDefinitionKey(EmrClusterDefinitionKey key) throws IllegalArgumentException
{
Assert.notNull(key, "An EMR cluster definition key must be specified.");
key.setNamespace(alternateKeyHelper.validateStringParameter("namespace", key.getNamespace()));
key.setEmrClusterDefinitionName(alternateKeyHelper.validateStringParameter("An", "EMR cluster definition name", key.getEmrClusterDefinitionName()));
}
/**
* Asserts that the given list of string contains no blank string and returns a copy of the list with all the values trimmed. If the given list is null, a
* null is returned.
*
* @param list The list of string
* @param displayName The display name of the element in the list used to construct the error message
*
* @return Copy of list with trimmed values
*/
private List assertNotBlankAndTrim(List list, String displayName)
{
List trimmed = null;
if (list != null)
{
for (String string : list)
{
Assert.hasText(string, displayName + " must not be blank");
}
trimmed = new ArrayList<>();
for (String string : list)
{
trimmed.add(string.trim());
}
}
return trimmed;
}
/**
* Validates the given instance definition. Generates an appropriate error message using the given name. The name specified is one of "master", "core", or
* "task".
*
* @param name name of instance group
* @param instanceDefinition the instance definition to validate
* @param minimumInstanceCount The minimum instance count.
*
* @throws IllegalArgumentException when any validation error occurs
*/
private void validateInstanceDefinition(String name, InstanceDefinition instanceDefinition, Integer minimumInstanceCount)
{
String capitalizedName = StringUtils.capitalize(name);
Assert.isTrue(instanceDefinition.getInstanceCount() >= minimumInstanceCount,
String.format("At least %d %s instance must be specified.", minimumInstanceCount, name));
Assert.hasText(instanceDefinition.getInstanceType(), "An instance type for " + name + " instances must be specified.");
if (instanceDefinition.getInstanceSpotPrice() != null)
{
Assert.isNull(instanceDefinition.getInstanceMaxSearchPrice(),
capitalizedName + " instance max search price must not be specified when instance spot price is specified.");
Assert.isTrue(instanceDefinition.getInstanceSpotPrice().compareTo(BigDecimal.ZERO) > 0,
capitalizedName + " instance spot price must be greater than 0");
}
if (instanceDefinition.getInstanceMaxSearchPrice() != null)
{
Assert.isNull(instanceDefinition.getInstanceSpotPrice(),
capitalizedName + " instance spot price must not be specified when max search price is specified.");
Assert.isTrue(instanceDefinition.getInstanceMaxSearchPrice().compareTo(BigDecimal.ZERO) > 0,
capitalizedName + " instance max search price must be greater than 0");
if (instanceDefinition.getInstanceOnDemandThreshold() != null)
{
Assert.isTrue(instanceDefinition.getInstanceOnDemandThreshold().compareTo(BigDecimal.ZERO) > 0,
capitalizedName + " instance on-demand threshold must be greater than 0");
}
}
else
{
Assert.isNull(instanceDefinition.getInstanceOnDemandThreshold(),
capitalizedName + " instance on-demand threshold must not be specified when instance max search price is not specified.");
}
}
/**
* Converts the given master instance definition to a generic instance definition and delegates to validateInstanceDefinition(). Generates an appropriate
* error message using the name "master".
*
* @param masterInstanceDefinition the master instance definition to validate
*
* @throws IllegalArgumentException when any validation error occurs
*/
private void validateMasterInstanceDefinition(MasterInstanceDefinition masterInstanceDefinition)
{
InstanceDefinition instanceDefinition = new InstanceDefinition();
instanceDefinition.setInstanceCount(masterInstanceDefinition.getInstanceCount());
instanceDefinition.setInstanceMaxSearchPrice(masterInstanceDefinition.getInstanceMaxSearchPrice());
instanceDefinition.setInstanceOnDemandThreshold(masterInstanceDefinition.getInstanceOnDemandThreshold());
instanceDefinition.setInstanceSpotPrice(masterInstanceDefinition.getInstanceSpotPrice());
instanceDefinition.setInstanceType(masterInstanceDefinition.getInstanceType());
validateInstanceDefinition("master", instanceDefinition, 1);
}
}