/* * 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.dao.helper; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.math.BigDecimal; import com.amazonaws.AmazonServiceException; import org.apache.commons.lang3.StringUtils; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.finra.herd.dao.AbstractDaoTest; import org.finra.herd.dao.impl.MockEc2OperationsImpl; import org.finra.herd.model.ObjectNotFoundException; import org.finra.herd.model.api.xml.EmrClusterDefinition; import org.finra.herd.model.api.xml.InstanceDefinition; import org.finra.herd.model.api.xml.InstanceDefinitions; import org.finra.herd.model.api.xml.MasterInstanceDefinition; import org.finra.herd.model.dto.AwsParamsDto; import org.finra.herd.model.dto.EmrClusterAlternateKeyDto; /** * Test cases for EMR pricing algorithm. */ public class EmrPricingHelperTest extends AbstractDaoTest { private static final BigDecimal ONE_UNIT = new BigDecimal("0.00001"); private static final BigDecimal ON_DEMAND = BigDecimal.ONE; private static final BigDecimal ON_DEMAND_LESS_ONE = ON_DEMAND.subtract(ONE_UNIT); private static final BigDecimal SPOT_PRICE_LOW = new BigDecimal(MockEc2OperationsImpl.SPOT_PRICE_LOW); private static final BigDecimal SPOT_PRICE_LOW_LESS_ONE = SPOT_PRICE_LOW.subtract(ONE_UNIT); private static final BigDecimal SPOT_PRICE_LOW_PLUS_ONE = SPOT_PRICE_LOW.add(ONE_UNIT); private static final BigDecimal SPOT_PRICE_VERY_HIGH = new BigDecimal(MockEc2OperationsImpl.SPOT_PRICE_VERY_HIGH); @Autowired private EmrPricingHelper emrPricingHelper; /** * Tests algorithmic cases: * <p/> * Master spot < on-demand and on-demand threshold < on-demand. Therefore master should use spot. Core spot < on-demand and on-demand threshold = on-demand. * Therefore core should use on-demand. * <p/> * Test case reference ClusterSpotPriceAlgorithm 1, 2 */ @Test public void testBestPriceAlgorithmicPickSpotAndOnDemand() { String subnetId = MockEc2OperationsImpl.SUBNET_1; MasterInstanceDefinition masterInstanceDefinition = new MasterInstanceDefinition(); masterInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); masterInstanceDefinition.setInstanceCount(1); masterInstanceDefinition.setInstanceMaxSearchPrice(ON_DEMAND); masterInstanceDefinition.setInstanceOnDemandThreshold(SPOT_PRICE_LOW_LESS_ONE); InstanceDefinition coreInstanceDefinition = new InstanceDefinition(); coreInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); coreInstanceDefinition.setInstanceCount(1); coreInstanceDefinition.setInstanceMaxSearchPrice(ON_DEMAND); coreInstanceDefinition.setInstanceOnDemandThreshold(SPOT_PRICE_LOW); InstanceDefinition taskInstanceDefinition = null; EmrClusterDefinition emrClusterDefinition = updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); assertBestPriceCriteriaRemoved(emrClusterDefinition); assertEquals("master instance bid price", ON_DEMAND, emrClusterDefinition.getInstanceDefinitions().getMasterInstances().getInstanceSpotPrice()); assertNull("core instance was not on-demand", emrClusterDefinition.getInstanceDefinitions().getCoreInstances().getInstanceSpotPrice()); } /** * Tests algorithmic case when the max search price is lower than both spot and on-demand price. The update method should throw an error indicating that no * subnets satisfied the given criteria. * <p/> * Test case reference ClusterSpotPriceAlgorithm 3 */ @Test public void testBestPriceAlgorithmicMaxSearchPriceTooLow() { String subnetId = MockEc2OperationsImpl.SUBNET_1; MasterInstanceDefinition masterInstanceDefinition = new MasterInstanceDefinition(); masterInstanceDefinition.setInstanceCount(1); masterInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); masterInstanceDefinition.setInstanceMaxSearchPrice(SPOT_PRICE_LOW_LESS_ONE); InstanceDefinition coreInstanceDefinition = new InstanceDefinition(); coreInstanceDefinition.setInstanceCount(1); coreInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); coreInstanceDefinition.setInstanceMaxSearchPrice(SPOT_PRICE_LOW_LESS_ONE); InstanceDefinition taskInstanceDefinition = new InstanceDefinition(); taskInstanceDefinition.setInstanceCount(1); taskInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); taskInstanceDefinition.setInstanceMaxSearchPrice(SPOT_PRICE_LOW_LESS_ONE); // Try with both master, core, and task failing criteria try { updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); fail("Expected ObjectNotFoundException, but no exception was thrown"); } catch (Exception e) { assertEquals("thrown exception", ObjectNotFoundException.class, e.getClass()); } // Now try with only core and task failing criteria masterInstanceDefinition.setInstanceMaxSearchPrice(ON_DEMAND); try { updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); fail("Expected ObjectNotFoundException, but no exception was thrown"); } catch (Exception e) { assertEquals("thrown exception", ObjectNotFoundException.class, e.getClass()); } // Try with only task failing criteria coreInstanceDefinition.setInstanceMaxSearchPrice(ON_DEMAND); try { updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); fail("Expected ObjectNotFoundException, but no exception was thrown"); } catch (Exception e) { assertEquals("thrown exception", ObjectNotFoundException.class, e.getClass()); } } /** * Tests 2 cases: Master spot is less than on-demand, max search price is less than on-demand, threshold is greater than on-demand. - Master should pick * spot because even though on-demand is within the threshold, it is above the max Core spot > on-demand, max search price = spot - Core should pick * on-demand since on-demand is cheaper * <p/> * Test case reference ClusterSpotPriceAlgorithm 4, 5 */ @Test public void testBestPriceAlgorithmicOnDemandOverMaxAndSpotGreaterThanOnDemand() { String subnetId = MockEc2OperationsImpl.SUBNET_1; MasterInstanceDefinition masterInstanceDefinition = new MasterInstanceDefinition(); masterInstanceDefinition.setInstanceCount(1); masterInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); masterInstanceDefinition.setInstanceMaxSearchPrice(ON_DEMAND_LESS_ONE); masterInstanceDefinition.setInstanceOnDemandThreshold(SPOT_PRICE_LOW_PLUS_ONE); InstanceDefinition coreInstanceDefinition = new InstanceDefinition(); coreInstanceDefinition.setInstanceCount(1); coreInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_3); coreInstanceDefinition.setInstanceMaxSearchPrice(ON_DEMAND); InstanceDefinition taskInstanceDefinition = null; EmrClusterDefinition emrClusterDefinition = updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); assertBestPriceCriteriaRemoved(emrClusterDefinition); assertEquals("master instance bid price", ON_DEMAND_LESS_ONE, emrClusterDefinition.getInstanceDefinitions().getMasterInstances().getInstanceSpotPrice()); assertNull("core instance was not on-demand", emrClusterDefinition.getInstanceDefinitions().getCoreInstances().getInstanceSpotPrice()); } /** * Tests 2 cases: Master instance spot < on-demand, search = on-demand, threshold > on-demand - Master should use on-demand since on-demand is above * threshold Core instance spot < on-demand, search < on-demand, threshold < on-demand - Core should use spot since on-demand is above max * <p/> * Test case reference ClusterSpotPriceAlgorithm 6, 7 */ @Test public void testBestPriceAlgorithmicThresholdAboveOnDemandAndSearchBelowOnDemand() { String subnetId = MockEc2OperationsImpl.SUBNET_1; MasterInstanceDefinition masterInstanceDefinition = new MasterInstanceDefinition(); masterInstanceDefinition.setInstanceCount(1); masterInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); masterInstanceDefinition.setInstanceMaxSearchPrice(ON_DEMAND); masterInstanceDefinition.setInstanceOnDemandThreshold(ON_DEMAND.subtract(SPOT_PRICE_LOW).add(ONE_UNIT)); InstanceDefinition coreInstanceDefinition = new InstanceDefinition(); coreInstanceDefinition.setInstanceCount(1); coreInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); coreInstanceDefinition.setInstanceMaxSearchPrice(ON_DEMAND_LESS_ONE); coreInstanceDefinition.setInstanceOnDemandThreshold(ON_DEMAND_LESS_ONE.subtract(SPOT_PRICE_LOW)); InstanceDefinition taskInstanceDefinition = null; EmrClusterDefinition emrClusterDefinition = updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); assertBestPriceCriteriaRemoved(emrClusterDefinition); assertNull("master instance was not on-demand", emrClusterDefinition.getInstanceDefinitions().getMasterInstances().getInstanceSpotPrice()); assertEquals("core instance bid price", ON_DEMAND_LESS_ONE, emrClusterDefinition.getInstanceDefinitions().getCoreInstances().getInstanceSpotPrice()); } /** * Tests case where max search price < on-demand, threshold < on-demand. The algorithm should select spot price because max search price is below * on-demand. * <p/> * Test case reference ClusterSpotPriceAlgorithm 8 */ @Test public void testBestPriceAlgorithmicSearchBelowOnDemandThresholdBelowOnDemand() { String subnetId = MockEc2OperationsImpl.SUBNET_1; MasterInstanceDefinition masterInstanceDefinition = new MasterInstanceDefinition(); masterInstanceDefinition.setInstanceCount(1); masterInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); masterInstanceDefinition.setInstanceMaxSearchPrice(ON_DEMAND_LESS_ONE); masterInstanceDefinition.setInstanceOnDemandThreshold(ON_DEMAND_LESS_ONE.subtract(SPOT_PRICE_LOW)); InstanceDefinition coreInstanceDefinition = new InstanceDefinition(); coreInstanceDefinition.setInstanceCount(1); coreInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); coreInstanceDefinition.setInstanceMaxSearchPrice(ON_DEMAND_LESS_ONE); coreInstanceDefinition.setInstanceOnDemandThreshold(ON_DEMAND_LESS_ONE.subtract(SPOT_PRICE_LOW)); InstanceDefinition taskInstanceDefinition = null; EmrClusterDefinition emrClusterDefinition = updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); assertBestPriceCriteriaRemoved(emrClusterDefinition); assertEquals("master instance bid price", ON_DEMAND_LESS_ONE, emrClusterDefinition.getInstanceDefinitions().getMasterInstances().getInstanceSpotPrice()); assertEquals("core instance bid price", ON_DEMAND_LESS_ONE, emrClusterDefinition.getInstanceDefinitions().getCoreInstances().getInstanceSpotPrice()); } /** * Test cases where user sets instances to explicitly use spot for master and on-demand for core by setting the instanceSpotPrice property and not * specifying criteria, respectively. */ @Test public void testBestPriceExplicitSpotAndOnDemand() { String subnetId = MockEc2OperationsImpl.SUBNET_1; MasterInstanceDefinition masterInstanceDefinition = new MasterInstanceDefinition(); masterInstanceDefinition.setInstanceCount(1); masterInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); masterInstanceDefinition.setInstanceSpotPrice(ON_DEMAND); InstanceDefinition coreInstanceDefinition = new InstanceDefinition(); coreInstanceDefinition.setInstanceCount(1); coreInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); InstanceDefinition taskInstanceDefinition = null; EmrClusterDefinition emrClusterDefinition = updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); assertBestPriceCriteriaRemoved(emrClusterDefinition); assertEquals("master instance bid price", ON_DEMAND, emrClusterDefinition.getInstanceDefinitions().getMasterInstances().getInstanceSpotPrice()); assertNull("core instance was not on-demand", emrClusterDefinition.getInstanceDefinitions().getCoreInstances().getInstanceSpotPrice()); } /** * Test case when multiple subnets are specified, but the subnet with AZ with the cheapest price does not have enough IP addresses available, the algorithm * should choose the subnet with enough availability, even when it is not the cheapest. * <p/> * Test case reference ClusterSpotPriceAlgorithm 10 */ @Test public void testBestPriceCheapestPriceDoenstHaveEnoughIp() { String subnetId = MockEc2OperationsImpl.SUBNET_1 + "," + MockEc2OperationsImpl.SUBNET_3; MasterInstanceDefinition masterInstanceDefinition = new MasterInstanceDefinition(); masterInstanceDefinition.setInstanceCount(5); masterInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); masterInstanceDefinition.setInstanceSpotPrice(ON_DEMAND); InstanceDefinition coreInstanceDefinition = new InstanceDefinition(); coreInstanceDefinition.setInstanceCount(6); coreInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); coreInstanceDefinition.setInstanceSpotPrice(ON_DEMAND); InstanceDefinition taskInstanceDefinition = null; EmrClusterDefinition emrClusterDefinition = updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); assertBestPriceCriteriaRemoved(emrClusterDefinition); assertEquals("master instance bid price", ON_DEMAND, emrClusterDefinition.getInstanceDefinitions().getMasterInstances().getInstanceSpotPrice()); assertEquals("core instance bid price", ON_DEMAND, emrClusterDefinition.getInstanceDefinitions().getCoreInstances().getInstanceSpotPrice()); assertEquals("selected subnet", MockEc2OperationsImpl.SUBNET_3, emrClusterDefinition.getSubnetId()); } /** * Tests case where none of the subnets specified has enough IP addresses available. The algorithm should fail with an error. * <p/> * Test case reference ClusterSpotPriceAlgorithm 11 */ @Test public void testBestPriceAllSubnetNotEnoughIp() { String subnetId = MockEc2OperationsImpl.SUBNET_1 + "," + MockEc2OperationsImpl.SUBNET_2; MasterInstanceDefinition masterInstanceDefinition = new MasterInstanceDefinition(); masterInstanceDefinition.setInstanceCount(10); masterInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); InstanceDefinition coreInstanceDefinition = new InstanceDefinition(); coreInstanceDefinition.setInstanceCount(11); coreInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); InstanceDefinition taskInstanceDefinition = null; try { updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); fail("expected ObjectNotFoundException, but no exception was thrown"); } catch (Exception e) { assertEquals("thrown exception", ObjectNotFoundException.class, e.getClass()); } } /** * Tests case where multiple subnets across multiple AZs are specified, and there are price differences. The algorithm should result in selecting the subnet * in the AZ with the lowest price. * <p/> * Test case reference ClusterSpotPriceAlgorithm 12 */ @Test public void testBestPricePickBestAz() { String subnetId = MockEc2OperationsImpl.SUBNET_1 + "," + MockEc2OperationsImpl.SUBNET_3; MasterInstanceDefinition masterInstanceDefinition = new MasterInstanceDefinition(); masterInstanceDefinition.setInstanceCount(4); masterInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); masterInstanceDefinition.setInstanceMaxSearchPrice(ON_DEMAND); InstanceDefinition coreInstanceDefinition = new InstanceDefinition(); coreInstanceDefinition.setInstanceCount(5); coreInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); coreInstanceDefinition.setInstanceMaxSearchPrice(ON_DEMAND); InstanceDefinition taskInstanceDefinition = null; EmrClusterDefinition emrClusterDefinition = updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); assertBestPriceCriteriaRemoved(emrClusterDefinition); assertEquals("master instance bid price", ON_DEMAND, emrClusterDefinition.getInstanceDefinitions().getMasterInstances().getInstanceSpotPrice()); assertEquals("core instance bid price", ON_DEMAND, emrClusterDefinition.getInstanceDefinitions().getCoreInstances().getInstanceSpotPrice()); assertEquals("selected subnet", MockEc2OperationsImpl.SUBNET_1, emrClusterDefinition.getSubnetId()); } /** * Tests case where instance count affect subnet selection. Tests case given - master instance is very expensive in one AZ but cheaper in the other - core * instance is very cheap in one AZ but more expensive in the other When - Enough instances are specified such that the AZ with the expensive master is * selected * <p/> * Even though the core is more expensive, the master is cheap enough to warrant the use of the expensive core AZ. * <p/> * Test case reference ClusterSpotPriceAlgorithm 13 */ @Test public void testBestPricePickMultipleInstancesSelectCheaperMaster() { String subnetId = MockEc2OperationsImpl.SUBNET_1 + "," + MockEc2OperationsImpl.SUBNET_4; MasterInstanceDefinition masterInstanceDefinition = new MasterInstanceDefinition(); masterInstanceDefinition.setInstanceCount(1); masterInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_2); masterInstanceDefinition.setInstanceSpotPrice(SPOT_PRICE_VERY_HIGH); InstanceDefinition coreInstanceDefinition = new InstanceDefinition(); coreInstanceDefinition.setInstanceCount(2); coreInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); coreInstanceDefinition.setInstanceSpotPrice(ON_DEMAND); InstanceDefinition taskInstanceDefinition = new InstanceDefinition(); taskInstanceDefinition.setInstanceCount(1); taskInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); taskInstanceDefinition.setInstanceSpotPrice(ON_DEMAND); EmrClusterDefinition emrClusterDefinition = updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); assertBestPriceCriteriaRemoved(emrClusterDefinition); assertEquals("master instance bid price", SPOT_PRICE_VERY_HIGH, emrClusterDefinition.getInstanceDefinitions().getMasterInstances().getInstanceSpotPrice()); assertEquals("core instance bid price", ON_DEMAND, emrClusterDefinition.getInstanceDefinitions().getCoreInstances().getInstanceSpotPrice()); assertEquals("task instance bid price", ON_DEMAND, emrClusterDefinition.getInstanceDefinitions().getTaskInstances().getInstanceSpotPrice()); assertEquals("selected subnet", MockEc2OperationsImpl.SUBNET_1, emrClusterDefinition.getSubnetId()); } /** * Tests case where instance count affect subnet selection. Tests case given - master instance is very expensive in one AZ but cheaper in the other - core * instance is very cheap in one AZ but more expensive in the other When - Enough instances are specified such that both AZ's are equal in total costs Then * - The result subnet is arbitrary, but should not error. * <p/> * Test case reference ClusterSpotPriceAlgorithm 13 */ @Test public void testBestPricePickMultipleInstancesAzPricesAreEqual() { String subnetId = MockEc2OperationsImpl.SUBNET_1 + "," + MockEc2OperationsImpl.SUBNET_4; MasterInstanceDefinition masterInstanceDefinition = new MasterInstanceDefinition(); masterInstanceDefinition.setInstanceCount(1); masterInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_2); masterInstanceDefinition.setInstanceSpotPrice(SPOT_PRICE_VERY_HIGH); InstanceDefinition coreInstanceDefinition = new InstanceDefinition(); coreInstanceDefinition.setInstanceCount(2); coreInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); coreInstanceDefinition.setInstanceSpotPrice(ON_DEMAND); InstanceDefinition taskInstanceDefinition = new InstanceDefinition(); taskInstanceDefinition.setInstanceCount(2); taskInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); taskInstanceDefinition.setInstanceSpotPrice(ON_DEMAND); EmrClusterDefinition emrClusterDefinition = updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); assertBestPriceCriteriaRemoved(emrClusterDefinition); assertEquals("master instance bid price", SPOT_PRICE_VERY_HIGH, emrClusterDefinition.getInstanceDefinitions().getMasterInstances().getInstanceSpotPrice()); assertEquals("core instance bid price", ON_DEMAND, emrClusterDefinition.getInstanceDefinitions().getCoreInstances().getInstanceSpotPrice()); assertEquals("task instance bid price", ON_DEMAND, emrClusterDefinition.getInstanceDefinitions().getTaskInstances().getInstanceSpotPrice()); assertTrue("selected subnet was neither SUBNET_1 or SUBNET_4", MockEc2OperationsImpl.SUBNET_1.equals(emrClusterDefinition.getSubnetId()) || MockEc2OperationsImpl.SUBNET_4.equals(emrClusterDefinition.getSubnetId())); } /** * Tests case where instance count affect subnet selection. Tests case given - master instance is very expensive in one AZ but cheaper in the other - core * instance is very cheap in one AZ but more expensive in the other When - Enough instances are specified such that the AZ with the very cheap core is * selected. * <p/> * Even though the master is more expensive, the cores are cheap enough that it overtakes the master's expense. * <p/> * Test case reference ClusterSpotPriceAlgorithm 13 */ @Test public void testBestPricePickMultipleInstancesSelectCheaperCoreAndTask() { String subnetId = MockEc2OperationsImpl.SUBNET_1 + "," + MockEc2OperationsImpl.SUBNET_4; MasterInstanceDefinition masterInstanceDefinition = new MasterInstanceDefinition(); masterInstanceDefinition.setInstanceCount(1); masterInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_2); masterInstanceDefinition.setInstanceSpotPrice(SPOT_PRICE_VERY_HIGH); InstanceDefinition coreInstanceDefinition = new InstanceDefinition(); coreInstanceDefinition.setInstanceCount(2); coreInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); coreInstanceDefinition.setInstanceSpotPrice(ON_DEMAND); InstanceDefinition taskInstanceDefinition = new InstanceDefinition(); taskInstanceDefinition.setInstanceCount(3); taskInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); taskInstanceDefinition.setInstanceSpotPrice(ON_DEMAND); EmrClusterDefinition emrClusterDefinition = updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); assertBestPriceCriteriaRemoved(emrClusterDefinition); assertEquals("master instance bid price", SPOT_PRICE_VERY_HIGH, emrClusterDefinition.getInstanceDefinitions().getMasterInstances().getInstanceSpotPrice()); assertEquals("core instance bid price", ON_DEMAND, emrClusterDefinition.getInstanceDefinitions().getCoreInstances().getInstanceSpotPrice()); assertEquals("task instance bid price", ON_DEMAND, emrClusterDefinition.getInstanceDefinitions().getTaskInstances().getInstanceSpotPrice()); assertEquals("selected subnet", MockEc2OperationsImpl.SUBNET_4, emrClusterDefinition.getSubnetId()); } /** * Tests case where subnet spans multiple regions, and one of the region has a cheaper price. The on-demand prices are identified by region, therefore this * test case tests that the correct on-demand price is selected. * <p/> * Test case reference ClusterSpotPriceAlgorithm 14 */ @Test public void testBestPriceMultipleRegions() { String subnetId = MockEc2OperationsImpl.SUBNET_1 + "," + MockEc2OperationsImpl.SUBNET_5; MasterInstanceDefinition masterInstanceDefinition = new MasterInstanceDefinition(); masterInstanceDefinition.setInstanceCount(1); masterInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); InstanceDefinition coreInstanceDefinition = new InstanceDefinition(); coreInstanceDefinition.setInstanceCount(1); coreInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); InstanceDefinition taskInstanceDefinition = null; EmrClusterDefinition emrClusterDefinition = updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); assertBestPriceCriteriaRemoved(emrClusterDefinition); assertNull("master instance was not on-demand", emrClusterDefinition.getInstanceDefinitions().getMasterInstances().getInstanceSpotPrice()); assertNull("core instance was not on-demand", emrClusterDefinition.getInstanceDefinitions().getMasterInstances().getInstanceSpotPrice()); assertEquals("selected subnet", MockEc2OperationsImpl.SUBNET_5, emrClusterDefinition.getSubnetId()); } /** * Tests case where instance type was not found in the spot list. This is a user error because we are using AWS API to look up the values, therefore, we can * trust that if AWS does not return the instance type, it does not exist. */ @Test public void testBestPriceSpotInstanceNotFound() { String subnetId = MockEc2OperationsImpl.SUBNET_5; MasterInstanceDefinition masterInstanceDefinition = new MasterInstanceDefinition(); masterInstanceDefinition.setInstanceCount(1); masterInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_3); InstanceDefinition coreInstanceDefinition = new InstanceDefinition(); coreInstanceDefinition.setInstanceCount(1); coreInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_3); InstanceDefinition taskInstanceDefinition = null; try { updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); fail("expected ObjectNotFoundException, but no exception was thrown"); } catch (Exception e) { assertEquals("thrown exception", ObjectNotFoundException.class, e.getClass()); } } /** * Tests case where instance type was not found in the spot list because AWS does not have a spot price for the given instance type in the given AZ. But * there is another AZ available for that does have a all spot prices available. */ @Test public void testBestPriceSpotInstanceNotFoundBecauseSpotPriceIsNotAvailable() { String subnetId = MockEc2OperationsImpl.SUBNET_1 + "," + MockEc2OperationsImpl.SUBNET_5; MasterInstanceDefinition masterInstanceDefinition = new MasterInstanceDefinition(); masterInstanceDefinition.setInstanceCount(1); masterInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_3); InstanceDefinition coreInstanceDefinition = new InstanceDefinition(); coreInstanceDefinition.setInstanceCount(1); coreInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); InstanceDefinition taskInstanceDefinition = null; EmrClusterDefinition emrClusterDefinition = updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); assertBestPriceCriteriaRemoved(emrClusterDefinition); } /** * Tests case where spot price is found but no on-demand price was found for the specified subnet's region and instance type. This is a case where the * on-demand configuration table was not properly configured or the user specified invalid instance type. */ @Test public void testBestPriceOnDemandNotFound() { String subnetId = MockEc2OperationsImpl.SUBNET_5; MasterInstanceDefinition masterInstanceDefinition = new MasterInstanceDefinition(); masterInstanceDefinition.setInstanceCount(1); masterInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_2); InstanceDefinition coreInstanceDefinition = new InstanceDefinition(); coreInstanceDefinition.setInstanceCount(1); coreInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_2); InstanceDefinition taskInstanceDefinition = null; try { updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); fail("expected ObjectNotFoundException, but no exception was thrown"); } catch (Exception e) { assertEquals("thrown exception", ObjectNotFoundException.class, e.getClass()); } } /** * Tests case where subnet is empty. This should default to searching all subnets. */ @Test public void testBestPriceSubnetIsEmpty() { String subnetId = ""; MasterInstanceDefinition masterInstanceDefinition = new MasterInstanceDefinition(); masterInstanceDefinition.setInstanceCount(1); masterInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); InstanceDefinition coreInstanceDefinition = new InstanceDefinition(); coreInstanceDefinition.setInstanceCount(1); coreInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); InstanceDefinition taskInstanceDefinition = null; EmrClusterDefinition emrClusterDefinition = updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); assertBestPriceCriteriaRemoved(emrClusterDefinition); assertTrue("subnet was not selected", StringUtils.isNotBlank(emrClusterDefinition.getSubnetId())); } /** * Tests case where subnet list contains - Valid subnet with whitespace padding - Empty string - String with only whitespaces - Trailing and leading commas * <p/> * We expect that only the valid subnet should be used, after trimming. All other blank values should be ignored. */ @Test public void testBestPriceSubnetPermutations() { String subnetId = ", \n\t\r" + MockEc2OperationsImpl.SUBNET_1 + " \n\t\r,, \n\t\r,"; MasterInstanceDefinition masterInstanceDefinition = new MasterInstanceDefinition(); masterInstanceDefinition.setInstanceCount(1); masterInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); InstanceDefinition coreInstanceDefinition = new InstanceDefinition(); coreInstanceDefinition.setInstanceCount(1); coreInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); InstanceDefinition taskInstanceDefinition = null; EmrClusterDefinition emrClusterDefinition = updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); assertBestPriceCriteriaRemoved(emrClusterDefinition); assertEquals("selected subnet", MockEc2OperationsImpl.SUBNET_1, emrClusterDefinition.getSubnetId()); } /** * Tests case where at least one user specified subnet does not exist. This is a user error. */ @Test public void testBestPriceSubnetNotFound() { String subnetId = "I_DO_NOT_EXIST," + MockEc2OperationsImpl.SUBNET_1; MasterInstanceDefinition masterInstanceDefinition = new MasterInstanceDefinition(); masterInstanceDefinition.setInstanceCount(1); masterInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); InstanceDefinition coreInstanceDefinition = new InstanceDefinition(); coreInstanceDefinition.setInstanceCount(1); coreInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); InstanceDefinition taskInstanceDefinition = null; try { updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); fail("expected ObjectNotFoundException, but no exception was thrown"); } catch (Exception e) { assertEquals("thrown exception", ObjectNotFoundException.class, e.getClass()); } } /** * Tests case where the subnet retrieval throws an unexpected amazon error. */ @Test public void testBestPriceSubnetError() throws Exception { String subnetId = "throw.SomeError"; MasterInstanceDefinition masterInstanceDefinition = new MasterInstanceDefinition(); masterInstanceDefinition.setInstanceCount(1); masterInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); InstanceDefinition coreInstanceDefinition = new InstanceDefinition(); coreInstanceDefinition.setInstanceCount(1); coreInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); InstanceDefinition taskInstanceDefinition = null; try { updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); fail("expected AmazonServiceException, but no exception was thrown"); } catch (Exception e) { assertEquals("thrown exception", AmazonServiceException.class, e.getClass()); } } /** * Tests case where multiple subnets are specified, but they all belong to the same AZ. In such case, the subnet with the most available IP addresses should * be selected. * <p/> * Test case reference ClusterSpotPriceAlgorithm 9 */ @Test public void testBestPriceSameAzMultipleSubnets() { String subnetId = MockEc2OperationsImpl.SUBNET_1 + "," + MockEc2OperationsImpl.SUBNET_2; MasterInstanceDefinition masterInstanceDefinition = new MasterInstanceDefinition(); masterInstanceDefinition.setInstanceCount(1); masterInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); InstanceDefinition coreInstanceDefinition = new InstanceDefinition(); coreInstanceDefinition.setInstanceCount(1); coreInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); InstanceDefinition taskInstanceDefinition = null; EmrClusterDefinition emrClusterDefinition = updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); assertBestPriceCriteriaRemoved(emrClusterDefinition); assertEquals("selected subnet", MockEc2OperationsImpl.SUBNET_2, emrClusterDefinition.getSubnetId()); } @Test public void testCoreInstanceNullSubnetInMultipleAzAssertSuccess() throws Exception { String subnetId = MockEc2OperationsImpl.SUBNET_1 + "," + MockEc2OperationsImpl.SUBNET_3; MasterInstanceDefinition masterInstanceDefinition = new MasterInstanceDefinition(); masterInstanceDefinition.setInstanceCount(1); masterInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); InstanceDefinition coreInstanceDefinition = null; InstanceDefinition taskInstanceDefinition = null; EmrClusterDefinition emrClusterDefinition = updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); assertEquals(MockEc2OperationsImpl.SUBNET_3, emrClusterDefinition.getSubnetId()); } @Test public void testCoreInstanceCount0SubnetInMultipleAzAssertSuccess() { String subnetId = MockEc2OperationsImpl.SUBNET_1 + "," + MockEc2OperationsImpl.SUBNET_3; MasterInstanceDefinition masterInstanceDefinition = new MasterInstanceDefinition(); masterInstanceDefinition.setInstanceCount(1); masterInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); InstanceDefinition coreInstanceDefinition = new InstanceDefinition(); coreInstanceDefinition.setInstanceCount(0); coreInstanceDefinition.setInstanceType(MockEc2OperationsImpl.INSTANCE_TYPE_1); InstanceDefinition taskInstanceDefinition = null; EmrClusterDefinition emrClusterDefinition = updateEmrClusterDefinitionWithBestPrice(subnetId, masterInstanceDefinition, coreInstanceDefinition, taskInstanceDefinition); assertEquals(MockEc2OperationsImpl.SUBNET_3, emrClusterDefinition.getSubnetId()); } /** * The definition will have it's best price search criteria information removed after being updated by the algorithm. This method asserts that is the case. * The task instance information is optional. Task instances will only be validated if it was given in the original definition. * * @param emrClusterDefinition - The definition updated by the algorithm */ private void assertBestPriceCriteriaRemoved(EmrClusterDefinition emrClusterDefinition) { assertNull("master instance max search price was not removed", emrClusterDefinition.getInstanceDefinitions().getMasterInstances().getInstanceMaxSearchPrice()); assertNull("master instance on-demand threshold was not removed", emrClusterDefinition.getInstanceDefinitions().getMasterInstances().getInstanceOnDemandThreshold()); assertNull("core instance max search price was not removed", emrClusterDefinition.getInstanceDefinitions().getCoreInstances().getInstanceMaxSearchPrice()); assertNull("core instance on-demand threshold was not removed", emrClusterDefinition.getInstanceDefinitions().getCoreInstances().getInstanceOnDemandThreshold()); if (emrClusterDefinition.getInstanceDefinitions().getTaskInstances() != null) { assertNull("task instance max search price was not removed", emrClusterDefinition.getInstanceDefinitions().getTaskInstances().getInstanceMaxSearchPrice()); assertNull("task instance on-demand threshold was not removed", emrClusterDefinition.getInstanceDefinitions().getTaskInstances().getInstanceOnDemandThreshold()); } } /** * Creates a new EMR cluster definition using the specified parameters, updates it with best price algorithm, and returns the definition. * * @param subnetId Subnet ID. Optional. * @param masterInstanceDefinition The master instance definition * @param coreInstanceDefinition The core instance definition * @param taskInstanceDefinition The task instance definition. Optional. * * @return Updated EMR cluster definition. */ private EmrClusterDefinition updateEmrClusterDefinitionWithBestPrice(String subnetId, MasterInstanceDefinition masterInstanceDefinition, InstanceDefinition coreInstanceDefinition, InstanceDefinition taskInstanceDefinition) { EmrClusterDefinition emrClusterDefinition = new EmrClusterDefinition(); emrClusterDefinition.setSubnetId(subnetId); InstanceDefinitions instanceDefinitions = new InstanceDefinitions(); instanceDefinitions.setMasterInstances(masterInstanceDefinition); instanceDefinitions.setCoreInstances(coreInstanceDefinition); instanceDefinitions.setTaskInstances(taskInstanceDefinition); emrClusterDefinition.setInstanceDefinitions(instanceDefinitions); emrPricingHelper.updateEmrClusterDefinitionWithBestPrice(new EmrClusterAlternateKeyDto(), emrClusterDefinition, new AwsParamsDto()); return emrClusterDefinition; } }