blob: 4abac7a18ba38fd9b4a84357d86e955544badd72 [file] [log] [blame]
/*
* 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.providers.agent;
import org.apache.slider.common.tools.SliderUtils;
import org.apache.slider.core.conf.ConfTreeOperations;
import org.apache.slider.providers.agent.application.metadata.CommandOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import static org.apache.slider.api.RoleKeys.ROLE_PREFIX;
/**
* Stores the command dependency order for all components in a service. <commandOrder>
* <command>SUPERVISOR-START</command> <requires>NIMBUS-STARTED</requires> </commandOrder> Means, SUPERVISOR START
* requires NIMBUS to be STARTED
*/
public class ComponentCommandOrder {
public static final Logger log =
LoggerFactory.getLogger(ComponentCommandOrder.class);
private static char SPLIT_CHAR = '-';
Map<Command, Map<String, List<ComponentState>>> dependencies =
new HashMap<Command, Map<String, List<ComponentState>>>();
Map<String, Collection<String>> prefixRoleMap = new HashMap<>();
Map<String, String> rolePrefixMap = new HashMap<>();
public ComponentCommandOrder() {}
public ComponentCommandOrder(List<CommandOrder> commandOrders,
ConfTreeOperations resources) {
mergeCommandOrders(commandOrders, resources);
}
void mergeCommandOrders(List<CommandOrder> commandOrders,
ConfTreeOperations resources) {
for (String component : resources.getComponentNames()) {
String prefix = SliderUtils.trimPrefix(
resources.getComponentOpt(component, ROLE_PREFIX, null));
if (prefix != null) {
rolePrefixMap.put(component, prefix);
if (!prefixRoleMap.containsKey(prefix)) {
prefixRoleMap.put(prefix, new HashSet<String>());
}
prefixRoleMap.get(prefix).add(component);
}
}
if (commandOrders != null && commandOrders.size() > 0) {
for (CommandOrder commandOrder : commandOrders) {
ComponentCommand componentCmd = getComponentCommand(
commandOrder.getCommand(), resources);
String requires = commandOrder.getRequires();
List<ComponentState> requiredStates = parseRequiredStates(requires,
resources);
if (requiredStates.size() > 0) {
Map<String, List<ComponentState>> compDep = dependencies.get(componentCmd.command);
if (compDep == null) {
compDep = new HashMap<>();
dependencies.put(componentCmd.command, compDep);
}
List<ComponentState> requirements = compDep.get(componentCmd.componentName);
if (requirements == null) {
requirements = new ArrayList<>();
compDep.put(componentCmd.componentName, requirements);
}
requirements.addAll(requiredStates);
}
}
}
}
private List<ComponentState> parseRequiredStates(String requires,
ConfTreeOperations resources) {
if (requires == null || requires.length() < 2) {
throw new IllegalArgumentException("Input cannot be null and must contain component and state.");
}
String[] componentStates = requires.split(",");
List<ComponentState> retList = new ArrayList<ComponentState>();
for (String componentStateStr : componentStates) {
retList.add(getComponentState(componentStateStr, resources));
}
return retList;
}
private ComponentCommand getComponentCommand(String compCmdStr,
ConfTreeOperations resources) {
if (compCmdStr == null || compCmdStr.trim().length() < 2) {
throw new IllegalArgumentException("Input cannot be null and must contain component and command.");
}
compCmdStr = compCmdStr.trim();
int splitIndex = compCmdStr.lastIndexOf(SPLIT_CHAR);
if (splitIndex == -1 || splitIndex == 0 || splitIndex == compCmdStr.length() - 1) {
throw new IllegalArgumentException("Input does not appear to be well-formed.");
}
String compStr = compCmdStr.substring(0, splitIndex);
String cmdStr = compCmdStr.substring(splitIndex + 1);
if (resources.getComponent(compStr) == null && !prefixRoleMap.containsKey(compStr)) {
throw new IllegalArgumentException("Component " + compStr + " specified" +
" in command order does not exist");
}
Command cmd = Command.valueOf(cmdStr);
if (cmd != Command.START) {
throw new IllegalArgumentException("Dependency order can only be specified for START.");
}
return new ComponentCommand(compStr, cmd);
}
private ComponentState getComponentState(String compStStr,
ConfTreeOperations resources) {
if (compStStr == null || compStStr.trim().length() < 2) {
throw new IllegalArgumentException("Input cannot be null.");
}
compStStr = compStStr.trim();
int splitIndex = compStStr.lastIndexOf(SPLIT_CHAR);
if (splitIndex == -1 || splitIndex == 0 || splitIndex == compStStr.length() - 1) {
throw new IllegalArgumentException("Input does not appear to be well-formed.");
}
String compStr = compStStr.substring(0, splitIndex);
String stateStr = compStStr.substring(splitIndex + 1);
if (resources.getComponent(compStr) == null && !prefixRoleMap.containsKey(compStr)) {
throw new IllegalArgumentException("Component " + compStr + " specified" +
" in command order does not exist");
}
State state = State.valueOf(stateStr);
if (state != State.STARTED && state != State.INSTALLED) {
throw new IllegalArgumentException("Dependency order can only be specified against STARTED/INSTALLED.");
}
return new ComponentState(compStr, state);
}
// dependency is still on component level, but not package level
// so use component name to check dependency, not component-package
public boolean canExecute(String component, Command command, Collection<ComponentInstanceState> currentStates) {
if (!dependencies.containsKey(command)) {
return true;
}
List<ComponentState> required = new ArrayList<>();
if (dependencies.get(command).containsKey(component)) {
required.addAll(dependencies.get(command).get(component));
}
String prefix = rolePrefixMap.get(component);
if (prefix != null && dependencies.get(command).containsKey(prefix)) {
required.addAll(dependencies.get(command).get(prefix));
}
for (ComponentState stateToMatch : required) {
for (ComponentInstanceState currState : currentStates) {
log.debug("Checking schedule {} {} against dependency {} is {}",
component, command, currState.getComponentName(), currState.getState());
if (currState.getComponentName().equals(stateToMatch.componentName) ||
(prefixRoleMap.containsKey(stateToMatch.componentName) &&
prefixRoleMap.get(stateToMatch.componentName).contains(currState.getComponentName()))) {
if (currState.getState() != stateToMatch.state) {
if (stateToMatch.state == State.STARTED) {
log.info("Cannot schedule {} {} as dependency {} is {}",
component, command, currState.getComponentName(), currState.getState());
return false;
} else {
//state is INSTALLED
if (currState.getState() != State.STARTING && currState.getState() != State.STARTED) {
log.info("Cannot schedule {} {} as dependency {} is {}",
component, command, currState.getComponentName(), currState.getState());
return false;
}
}
}
}
}
}
return true;
}
static class ComponentState {
public String componentName;
public State state;
public ComponentState(String componentName, State state) {
this.componentName = componentName;
this.state = state;
}
}
static class ComponentCommand {
public String componentName;
public Command command;
public ComponentCommand(String componentName, Command command) {
this.componentName = componentName;
this.command = command;
}
}
}