/*************************************************************************** * Copyright (c) 2013 VMware, Inc. All Rights Reserved. * 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 com.vmware.vhadoop.vhm.strategy; import java.util.HashSet; import java.util.Set; import java.util.logging.Logger; import com.vmware.vhadoop.api.vhm.ClusterMap; import com.vmware.vhadoop.api.vhm.ClusterMapReader; import com.vmware.vhadoop.api.vhm.VCActions; import com.vmware.vhadoop.api.vhm.events.ClusterScaleCompletionEvent; import com.vmware.vhadoop.api.vhm.events.ClusterScaleEvent; import com.vmware.vhadoop.api.vhm.strategy.EDPolicy; import com.vmware.vhadoop.api.vhm.strategy.ScaleStrategy; import com.vmware.vhadoop.api.vhm.strategy.ScaleStrategyContext; import com.vmware.vhadoop.api.vhm.strategy.VMChooser; import com.vmware.vhadoop.api.vhm.strategy.VMChooser.RankedVM; import com.vmware.vhadoop.util.CompoundStatus; import com.vmware.vhadoop.util.CompoundStatus.TaskStatus; import com.vmware.vhadoop.util.VhmLevel; import com.vmware.vhadoop.vhm.AbstractClusterMapReader; import com.vmware.vhadoop.vhm.events.ClusterScaleDecision; import com.vmware.vhadoop.vhm.events.SerengetiLimitInstruction; import com.vmware.vhadoop.vhm.events.SerengetiLimitInstruction.SerengetiLimitAction; public class ManualScaleStrategy extends AbstractClusterMapReader implements ScaleStrategy { private static final Logger _log = Logger.getLogger(ManualScaleStrategy.class.getName()); private final EDPolicy _enableDisablePolicy; private VMChooserCallback _vmChooserCallback; public static final String MANUAL_SCALE_STRATEGY_KEY = "manual"; public ManualScaleStrategy(EDPolicy edPolicy) { _enableDisablePolicy = edPolicy; } @Override public void initialize(ClusterMapReader parent) { super.initialize(parent); _enableDisablePolicy.initialize(parent); } @Override public void setVMChooserCallback(final VMChooserCallback callback) { _vmChooserCallback = callback; } @Override public String getKey() { return MANUAL_SCALE_STRATEGY_KEY; } private Set<String> chooseVMsForTargetPowerState(String clusterId, int delta, Set<String> candidateVmIds, boolean targetPowerState) { Set<RankedVM> combination = null; for (VMChooser vmChooser : _vmChooserCallback.getVMChoosers()) { Set<RankedVM> rankedVMs = targetPowerState ? vmChooser.rankVMsToEnable(clusterId, candidateVmIds) : vmChooser.rankVMsToDisable(clusterId, candidateVmIds); if (rankedVMs != null) { combination = RankedVM.combine(combination, rankedVMs); } } return RankedVM.selectLowestRankedIds(combination, Math.abs(delta)); } class CallableStrategy extends ClusterScaleOperation { final Set<ClusterScaleEvent> _events; public CallableStrategy(Set<ClusterScaleEvent> events) { _events = events; initialize(ManualScaleStrategy.this); } @Override public ClusterScaleCompletionEvent localCall() throws Exception { CompoundStatus tlStatus = getCompoundStatus(); ClusterScaleDecision returnEvent = null; if (_events.size() != 1) { throw new RuntimeException("Manual scale strategy should only have one SerengetiLimitInstruction"); } ClusterScaleEvent event = _events.iterator().next(); if (event instanceof SerengetiLimitInstruction) { SerengetiLimitInstruction limitEvent = (SerengetiLimitInstruction)event; int targetSize = 0; int delta = 0; String clusterId = null; Set<String> vmsToED; ClusterMap clusterMap = null; Set<String> poweredOffVmIds = null; Set<String> poweredOnVmIds = null; int numPoweredOff = 0; int numPoweredOn = 0; try { clusterMap = getAndReadLockClusterMap(); String clusterName = limitEvent.getClusterName(); clusterId = clusterMap.getClusterIdForName(clusterName); if (clusterId != null) { poweredOffVmIds = clusterMap.listComputeVMsForClusterAndPowerState(clusterId, false); numPoweredOff = (poweredOffVmIds == null) ? 0 : poweredOffVmIds.size(); poweredOnVmIds = clusterMap.listComputeVMsForClusterAndPowerState(clusterId, true); numPoweredOn = (poweredOnVmIds == null) ? 0 : poweredOnVmIds.size(); if (limitEvent.getAction().equals(SerengetiLimitAction.actionUnlimit)) { targetSize = numPoweredOn + numPoweredOff; delta = numPoweredOff; } else { targetSize = limitEvent.getToSize(); delta = targetSize - numPoweredOn; } _log.log(VhmLevel.USER, "<%C"+clusterId+"%C>: handling manual elasticity command from Serengeti to set number of enabled compute nodes to " + targetSize); returnEvent = new ClusterScaleDecision(clusterId); } else { tlStatus.registerTaskFailed(false, "Unknown clusterId for Cluster "+clusterName); /* delta == 0, so don't do anything */ } } finally { unlockClusterMap(clusterMap); } Set<String> unresponsiveVmIds = null; if (delta > 0) { vmsToED = chooseVMsForTargetPowerState(clusterId, delta, poweredOffVmIds, true); limitEvent.reportProgress(10, null); if ((vmsToED != null) && !vmsToED.isEmpty()) { /* Note that this returns successfully enabled VM IDs from the input set of VMs*/ Set<String> enabledTTs = _enableDisablePolicy.enableTTs(vmsToED, targetSize, clusterId); if (enabledTTs != null) { _log.fine("Enabled TTs: "+enabledTTs); unresponsiveVmIds = diffIds(vmsToED, enabledTTs); limitEvent.reportProgress(30, null); returnEvent.addDecision(enabledTTs, ClusterScaleCompletionEvent.ENABLE); if (tlStatus.screenStatusesForSpecificFailures(new String[]{VCActions.VC_POWER_ON_STATUS_KEY})) { blockOnPowerStateChange(enabledTTs, true, 120000); } limitEvent.reportProgress(90, null); } else { unresponsiveVmIds = vmsToED; tlStatus.registerTaskFailed(false, "no task trackers were enabled successfully"); } } } else if (delta < 0) { vmsToED = chooseVMsForTargetPowerState(clusterId, delta, poweredOnVmIds, false); limitEvent.reportProgress(10, null); if ((vmsToED != null) && !vmsToED.isEmpty()) { /* Note that this returns disabled VM IDs for the cluster */ Set<String> disabledTTs = _enableDisablePolicy.disableTTs(vmsToED, targetSize, clusterId); if (disabledTTs != null) { _log.fine("Disabled TTs: "+disabledTTs); unresponsiveVmIds = diffIds(vmsToED, disabledTTs); limitEvent.reportProgress(30, null); returnEvent.addDecision(disabledTTs, ClusterScaleCompletionEvent.DISABLE); if (tlStatus.screenStatusesForSpecificFailures(new String[]{VCActions.VC_POWER_OFF_STATUS_KEY})) { blockOnPowerStateChange(disabledTTs, false, 120000); } limitEvent.reportProgress(90, null); } else { unresponsiveVmIds = vmsToED; tlStatus.registerTaskFailed(false, "no task trackers were disabled successfully"); } } } if (tlStatus.getFailedTaskCount() == 0) { limitEvent.reportCompletion(); } else { if (unresponsiveVmIds != null) { for (String uninitializedVmId : unresponsiveVmIds) { _log.warning("<%C"+event.getClusterId()+"%C>: <%V"+uninitializedVmId+"%V> - did not successfully respond in a reasonable time"); } } TaskStatus firstGeneralError = tlStatus.getFirstFailure(); if (firstGeneralError != null) { if (tlStatus.screenStatusesForSpecificFailures(new String[]{VCActions.VC_POWER_ON_STATUS_KEY, VCActions.VC_POWER_OFF_STATUS_KEY, ClusterMapReader.POWER_STATE_CHANGE_STATUS_KEY})) { limitEvent.reportError(firstGeneralError.getMessage() + " however, powering on/off VMs succeeded"); } else { limitEvent.reportError(firstGeneralError.getMessage()); } } } } else { throw new RuntimeException("Manual scale strategy event should be of type SerengetiLimitInstruction"); } return returnEvent; } private Set<String> diffIds(Set<String> vmIdsInstructed, Set<String> vmIdsInitialized) { Set<String> result = new HashSet<String>(vmIdsInstructed); result.removeAll(vmIdsInitialized); if (result.size() == 0) { return null; } return result; } } @Override public ClusterScaleOperation getClusterScaleOperation(String clusterId, Set<ClusterScaleEvent> events, ScaleStrategyContext context) { return new CallableStrategy(events); } @Override public Class<? extends ScaleStrategyContext> getStrategyContextType() { return null; } @SuppressWarnings("unchecked") @Override public Class<? extends ClusterScaleEvent>[] getScaleEventTypesHandled() { return new Class[]{SerengetiLimitInstruction.class}; } @Override public String toString() { return getKey(); } }