org.finra.herd.service.helper.EmrClusterDefinitionHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.finra.herd.service.helper.EmrClusterDefinitionHelper.java

Source

/*
* 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<String> 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<String> assertNotBlankAndTrim(List<String> list, String displayName) {
        List<String> 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);
    }
}