SLIDER-587 test for role history on dynamic roles
Project: http://git-wip-us.apache.org/repos/asf/incubator-slider/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-slider/commit/34f78ada Tree: http://git-wip-us.apache.org/repos/asf/incubator-slider/tree/34f78ada Diff: http://git-wip-us.apache.org/repos/asf/incubator-slider/diff/34f78ada Branch: refs/heads/feature/SLIDER-531-registry-enhancements Commit: 34f78ada7282a781dc6e440d9f4e4fd38d455f8d Parents: 22b4b5e Author: Steve Loughran <ste...@apache.org> Authored: Fri Oct 31 19:39:16 2014 +0000 Committer: Steve Loughran <ste...@apache.org> Committed: Fri Oct 31 19:39:16 2014 +0000 ---------------------------------------------------------------------- .../slider/server/appmaster/state/AppState.java | 30 +-- .../server/appmaster/state/RoleHistory.java | 4 - .../TestMockAppStateDynamicHistory.groovy | 188 +++++++++++++++++++ .../TestMockAppStateDynamicRoles.groovy | 50 +---- .../appmaster/model/mock/Allocator.groovy | 8 +- .../model/mock/BaseMockAppStateTest.groovy | 2 +- 6 files changed, 214 insertions(+), 68 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/34f78ada/slider-core/src/main/java/org/apache/slider/server/appmaster/state/AppState.java ---------------------------------------------------------------------- diff --git a/slider-core/src/main/java/org/apache/slider/server/appmaster/state/AppState.java b/slider-core/src/main/java/org/apache/slider/server/appmaster/state/AppState.java index 406086a..2b5d0ee 100644 --- a/slider-core/src/main/java/org/apache/slider/server/appmaster/state/AppState.java +++ b/slider-core/src/main/java/org/apache/slider/server/appmaster/state/AppState.java @@ -641,33 +641,36 @@ public class AppState { /** * The resource configuration is updated -review and update state. * @param resources updated resources specification + * @return a list of any dynamically added provider roles + * (purely for testing purposes) */ - public synchronized void updateResourceDefinitions(ConfTree resources) + public synchronized List<ProviderRole> updateResourceDefinitions(ConfTree resources) throws BadConfigException, IOException { log.debug("Updating resources to {}", resources); instanceDefinition.setResources(resources); onInstanceDefinitionUpdated(); - - + //propagate the role table - Map<String, Map<String, String>> updated = resources.components; getClusterStatus().roles = SliderUtils.deepClone(updated); getClusterStatus().updateTime = now(); - buildRoleRequirementsFromResources(); + return buildRoleRequirementsFromResources(); } /** * build the role requirements from the cluster specification + * @return a list of any dynamically added provider roles */ - private void buildRoleRequirementsFromResources() throws BadConfigException { + private List<ProviderRole> buildRoleRequirementsFromResources() throws BadConfigException { + List<ProviderRole> newRoles = new ArrayList<ProviderRole>(0); + //now update every role's desired count. //if there are no instance values, that role count goes to zero ConfTreeOperations resources = - instanceDefinition.getResourceOperations(); + instanceDefinition.getResourceOperations(); // Add all the existing roles for (RoleStatus roleStatus : getRoleStatusMap().values()) { @@ -678,33 +681,38 @@ public class AppState { int currentDesired = roleStatus.getDesired(); String role = roleStatus.getName(); MapOperations comp = - resources.getComponent(role); + resources.getComponent(role); int desiredInstanceCount = getDesiredInstanceCount(resources, role); if (desiredInstanceCount == 0) { log.info("Role {} has 0 instances specified", role); - } + } if (currentDesired != desiredInstanceCount) { log.info("Role {} flexed from {} to {}", role, currentDesired, - desiredInstanceCount); + desiredInstanceCount); roleStatus.setDesired(desiredInstanceCount); } } + //now the dynamic ones. Iterate through the the cluster spec and //add any role status entries not in the role status Set<String> roleNames = resources.getComponentNames(); for (String name : roleNames) { if (!roles.containsKey(name)) { // this is a new value - MapOperations component = resources.getComponent(name); log.info("Adding new role {}", name); + MapOperations component = resources.getComponent(name); ProviderRole dynamicRole = createDynamicProviderRole(name, component); RoleStatus roleStatus = buildRole(dynamicRole); roleStatus.setDesired(getDesiredInstanceCount(resources, name)); log.info("New role {}", roleStatus); roleHistory.addNewProviderRole(dynamicRole); + newRoles.add(dynamicRole); + } else { + log.debug("known role: {}", name); } } + return newRoles; } /** http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/34f78ada/slider-core/src/main/java/org/apache/slider/server/appmaster/state/RoleHistory.java ---------------------------------------------------------------------- diff --git a/slider-core/src/main/java/org/apache/slider/server/appmaster/state/RoleHistory.java b/slider-core/src/main/java/org/apache/slider/server/appmaster/state/RoleHistory.java index 2b0ee18..33c3442 100644 --- a/slider-core/src/main/java/org/apache/slider/server/appmaster/state/RoleHistory.java +++ b/slider-core/src/main/java/org/apache/slider/server/appmaster/state/RoleHistory.java @@ -462,10 +462,6 @@ public class RoleHistory { } } - public synchronized void onAMRestart() { - //TODO once AM restart is implemented and we know what to expect - } - /** * Find a node for use * @param role role http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/34f78ada/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicHistory.groovy ---------------------------------------------------------------------- diff --git a/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicHistory.groovy b/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicHistory.groovy new file mode 100644 index 0000000..9a9ad23 --- /dev/null +++ b/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicHistory.groovy @@ -0,0 +1,188 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.slider.server.appmaster.model.appstate + +import groovy.transform.CompileStatic +import groovy.util.logging.Slf4j +import org.apache.hadoop.conf.Configuration +import org.apache.hadoop.yarn.api.records.ContainerId +import org.apache.slider.api.ResourceKeys +import org.apache.slider.core.conf.ConfTreeOperations +import org.apache.slider.providers.PlacementPolicy +import org.apache.slider.providers.ProviderRole +import org.apache.slider.server.appmaster.model.mock.BaseMockAppStateTest +import org.apache.slider.server.appmaster.model.mock.MockAppState +import org.apache.slider.server.appmaster.model.mock.MockRoles +import org.apache.slider.server.appmaster.model.mock.MockYarnEngine +import org.apache.slider.server.appmaster.operations.ContainerRequestOperation +import org.apache.slider.server.appmaster.state.AppState +import org.apache.slider.server.appmaster.state.NodeInstance +import org.apache.slider.server.appmaster.state.RoleInstance +import org.apache.slider.server.appmaster.state.SimpleReleaseSelector +import org.junit.Test + +/** + * Test that if you have >1 role, the right roles are chosen for release. + */ +@CompileStatic +@Slf4j +class TestMockAppStateDynamicHistory extends BaseMockAppStateTest + implements MockRoles { + + @Override + String getTestName() { + return "TestMockAppStateDynamicHistory" + } + + /** + * Small cluster with multiple containers per node, + * to guarantee many container allocations on each node + * @return + */ + @Override + MockYarnEngine createYarnEngine() { + return new MockYarnEngine(8, 1) + } + + @Override + void initApp() { + super.initApp() + appState = new MockAppState() + appState.setContainerLimits(RM_MAX_RAM, RM_MAX_CORES) + + def instance = factory.newInstanceDefinition(0,0,0) + + appState.buildInstance( + instance, + new Configuration(), + new Configuration(false), + factory.ROLES, + fs, + historyPath, + null, + null, new SimpleReleaseSelector()) + } + + + @Test + public void testDynamicRoleHistory() throws Throwable { + + def dynamic = "dynamicRole" + int priority_num_8 = 8 + int desired = 1 + int placementPolicy = 0 + // snapshot and patch existing spec + def resources = ConfTreeOperations.fromInstance( + appState.resourcesSnapshot.confTree) + def opts = [ + (ResourceKeys.COMPONENT_INSTANCES): ""+desired, + (ResourceKeys.COMPONENT_PRIORITY) : "" +priority_num_8, + (ResourceKeys.COMPONENT_PLACEMENT_POLICY): "" + placementPolicy + ] + + resources.components[dynamic] = opts + + + // write the definitions + def updates = appState.updateResourceDefinitions(resources.confTree); + assert updates.size() == 1 + def updatedRole = updates[0] + assert updatedRole.placementPolicy == placementPolicy + + // verify the new role was persisted + def snapshotDefinition = appState.resourcesSnapshot.getMandatoryComponent( + dynamic) + assert snapshotDefinition.getMandatoryOptionInt( + ResourceKeys.COMPONENT_PRIORITY) == priority_num_8 + + // now look at the role map + assert appState.roleMap[dynamic] != null + def mappedRole = appState.roleMap[dynamic] + assert mappedRole.id == priority_num_8 + + def priorityMap = appState.rolePriorityMap + assert priorityMap.size() == 4 + ProviderRole dynamicProviderRole + assert (dynamicProviderRole = priorityMap[priority_num_8]) != null + assert dynamicProviderRole.id == priority_num_8 + + assert null != appState.roleStatusMap[priority_num_8] + def dynamicRoleStatus = appState.roleStatusMap[priority_num_8] + assert dynamicRoleStatus.desired == desired + + + // before allocating the nodes, fill up the capacity of some of the + // hosts + engine.allocator.nextIndex() + + def targetNode = 2 + assert targetNode == engine.allocator.nextIndex() + def targetHostname = engine.cluster.nodeAt(targetNode).hostname + + // allocate the nodes + def actions = appState.reviewRequestAndReleaseNodes() + assert actions.size() == 1 + def action0 = (ContainerRequestOperation)actions[0] + + def request = action0.request + assert !request.nodes + + List<ContainerId> released = [] + List<RoleInstance> allocations = submitOperations(actions, released) + processSubmissionOperations(allocations, [], released) + assert allocations.size() == 1 + RoleInstance ri = allocations[0] + + assert ri.role == dynamic + assert ri.roleId == priority_num_8 + assert ri.host.host == targetHostname + + // now look at the role history + + def roleHistory = appState.roleHistory + def activeNodes = roleHistory.listActiveNodes(priority_num_8) + assert activeNodes.size() == 1 + NodeInstance activeNode = activeNodes[0] + + assert activeNode.hostname == targetHostname + + // now trigger a termination event on that role + + + def cid = ri.id + // failure + AppState.NodeCompletionResult result = appState.onCompletedNode( + containerStatus(cid, 1)) + assert result.roleInstance == ri + assert result.containerFailed + + def nodeForNewInstance = roleHistory.findNodeForNewInstance( + dynamicRoleStatus) + assert nodeForNewInstance + + // make sure new nodes will default to a different host in the engine + assert targetNode < engine.allocator.nextIndex() + + actions = appState.reviewRequestAndReleaseNodes() + assert actions.size() == 1 + def action1 = (ContainerRequestOperation) actions[0] + def request1 = action1.request + assert request1.nodes + } +} http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/34f78ada/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicRoles.groovy ---------------------------------------------------------------------- diff --git a/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicRoles.groovy b/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicRoles.groovy index 5ef639b..902752c 100644 --- a/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicRoles.groovy +++ b/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicRoles.groovy @@ -28,7 +28,6 @@ import org.apache.slider.server.appmaster.model.mock.BaseMockAppStateTest import org.apache.slider.server.appmaster.model.mock.MockAppState import org.apache.slider.server.appmaster.model.mock.MockRoles import org.apache.slider.server.appmaster.model.mock.MockYarnEngine -import org.apache.slider.server.appmaster.operations.AbstractRMOperation import org.apache.slider.server.appmaster.state.RoleInstance import org.apache.slider.server.appmaster.state.SimpleReleaseSelector import org.junit.Test @@ -86,55 +85,10 @@ class TestMockAppStateDynamicRoles extends BaseMockAppStateTest @Test public void testAllocateReleaseRealloc() throws Throwable { - List<RoleInstance> instances = createAndStartNodes() - List<AbstractRMOperation> ops = appState.reviewRequestAndReleaseNodes() + createAndStartNodes() + appState.reviewRequestAndReleaseNodes() appState.getRoleHistory().dump(); } - - @Test - public void testDynamicRoleHistory() throws Throwable { - - // snapshot and patch existing spec - def resources = ConfTreeOperations.fromInstance( - appState.resourcesSnapshot.confTree) - - def name = "dynamic" - def dynamicComp = resources.getOrAddComponent(name) - int priority = 8 - int placement = 3 - dynamicComp.put(ResourceKeys.COMPONENT_PRIORITY, "8") - dynamicComp.put(ResourceKeys.COMPONENT_INSTANCES, "1") - dynamicComp.put(ResourceKeys.COMPONENT_PLACEMENT_POLICY, "3") - - def component = resources.getComponent(name) - String priOpt = component.getMandatoryOption( - ResourceKeys.COMPONENT_PRIORITY); - int parsedPriority = SliderUtils.parseAndValidate( - "value of " + name + " " + ResourceKeys.COMPONENT_PRIORITY, - priOpt, 0, 1, -1); - assert priority == parsedPriority - - def newRole = appState.createDynamicProviderRole(name, component) - assert newRole.id == priority - - appState.updateResourceDefinitions(resources.confTree); - assert appState.roleMap[name] != null - def mappedRole = appState.roleMap[name] - assert mappedRole.id == priority - - def priorityMap = appState.rolePriorityMap - assert priorityMap.size() == 4 - def dynamicProviderRole - assert (dynamicProviderRole = priorityMap[priority]) != null - assert dynamicProviderRole.id == priority - - // allocate the nodes - def allocations = createAndStartNodes() - assert allocations.size() == 1 - RoleInstance ri = allocations[0] - assert ri.role == name - assert ri.roleId == priority - } } http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/34f78ada/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/mock/Allocator.groovy ---------------------------------------------------------------------- diff --git a/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/mock/Allocator.groovy b/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/mock/Allocator.groovy index 639c632..a027098 100644 --- a/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/mock/Allocator.groovy +++ b/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/mock/Allocator.groovy @@ -51,11 +51,11 @@ class Allocator { MockContainer allocate(AMRMClient.ContainerRequest request) { MockYarnCluster.MockYarnClusterNode node = null MockYarnCluster.MockYarnClusterContainer allocated = null - if (request.nodes != null) { + if (request.nodes) { for (String host : request.nodes) { node = cluster.lookup(host) allocated = node.allocate() - if (allocated != null) { + if (allocated) { break } } @@ -64,7 +64,7 @@ class Allocator { if (allocated) { return createContainerRecord(request, allocated, node) } else { - if (request.relaxLocality || request.nodes.isEmpty()) { + if (request.relaxLocality || request.nodes.empty) { // fallback to anywhere return allocateRandom(request) } else { @@ -117,7 +117,7 @@ class Allocator { return container; } - private int nextIndex() { + public int nextIndex() { rollingIndex = (rollingIndex + 1) % cluster.clusterSize; return rollingIndex; } http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/34f78ada/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/mock/BaseMockAppStateTest.groovy ---------------------------------------------------------------------- diff --git a/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/mock/BaseMockAppStateTest.groovy b/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/mock/BaseMockAppStateTest.groovy index fa54256..c48d7fa 100644 --- a/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/mock/BaseMockAppStateTest.groovy +++ b/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/mock/BaseMockAppStateTest.groovy @@ -136,7 +136,7 @@ abstract class BaseMockAppStateTest extends SliderTestBase implements MockRoles Container target = assigned.container RoleInstance ri = new RoleInstance(target) ri.roleId = assigned.role.priority - ri.role = assigned.role + ri.role = assigned.role.name return ri }