/**
* 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.ambari.server.metadata;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.Role;
import org.apache.ambari.server.RoleCommand;
import org.apache.ambari.server.api.services.AmbariMetaInfo;
import org.apache.ambari.server.stageplanner.RoleGraphNode;
import org.apache.ambari.server.state.Cluster;
import org.apache.ambari.server.state.Service;
import org.apache.ambari.server.state.ServiceComponent;
import org.apache.ambari.server.state.StackId;
import org.apache.ambari.server.state.StackInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Sets;
import com.google.inject.Inject;
/**
* This class is used to establish the order between two roles. This class
* should not be used to determine the dependencies.
*/
public class RoleCommandOrder implements Cloneable {
@Inject AmbariMetaInfo ambariMetaInfo;
private final static Logger LOG =
LoggerFactory.getLogger(RoleCommandOrder.class);
/**
* The section names used to add overides in addition to the
* {@link #GENERAL_DEPS_KEY} section.
*/
private LinkedHashSet<String> sectionKeys;
private final static String GENERAL_DEPS_KEY = "general_deps";
public final static String GLUSTERFS_DEPS_KEY = "optional_glusterfs";
public final static String NO_GLUSTERFS_DEPS_KEY = "optional_no_glusterfs";
public final static String NAMENODE_HA_DEPS_KEY = "namenode_optional_ha";
public final static String RESOURCEMANAGER_HA_DEPS_KEY = "resourcemanager_optional_ha";
public final static String COMMENT_STR = "_comment";
/**
* Commands that are independent, role order matters
*/
private static final Set<RoleCommand> independentCommands = Sets.newHashSet(RoleCommand.START,
RoleCommand.EXECUTE, RoleCommand.SERVICE_CHECK);
/**
* key -> blocked role command value -> set of blocker role commands.
*/
private Map<RoleCommandPair, Set<RoleCommandPair>> dependencies = new HashMap<>();
/**
* Add a pair of tuples where the tuple defined by the first two parameters are blocked on
* the tuple defined by the last two pair.
* @param blockedRole Role that is blocked
* @param blockedCommand The command on the role that is blocked
* @param blockerRole The role that is blocking
* @param blockerCommand The command on the blocking role
*/
private void addDependency(Role blockedRole,
RoleCommand blockedCommand, Role blockerRole, RoleCommand blockerCommand,
boolean overrideExisting) {
RoleCommandPair rcp1 = new RoleCommandPair(blockedRole, blockedCommand);
RoleCommandPair rcp2 = new RoleCommandPair(blockerRole, blockerCommand);
if (dependencies.get(rcp1) == null || overrideExisting) {
dependencies.put(rcp1, new HashSet<RoleCommandPair>());
}
dependencies.get(rcp1).add(rcp2);
}
void addDependencies(Map<String, Object> jsonSection) {
if(jsonSection == null) {
return;
}
for (Object blockedObj : jsonSection.keySet()) {
String blocked = (String) blockedObj;
if (COMMENT_STR.equals(blocked)) {
continue; // Skip comments
}
ArrayList<String> blockers = (ArrayList<String>) jsonSection.get(blocked);
for (String blocker : blockers) {
String [] blockedTuple = blocked.split("-");
String blockedRole = blockedTuple[0];
String blockedCommand = blockedTuple[1];
// 3rd position is -OVERRIDE
boolean overrideExisting = blockedTuple.length == 3;
String [] blockerTuple = blocker.split("-");
String blockerRole = blockerTuple[0];
String blockerCommand = blockerTuple[1];
addDependency(Role.valueOf(blockedRole), RoleCommand.valueOf(blockedCommand),
Role.valueOf(blockerRole), RoleCommand.valueOf(blockerCommand), overrideExisting);
}
}
}
@SuppressWarnings("unchecked")
public void initialize(Cluster cluster, LinkedHashSet<String> sectionKeys) {
// in the event that initialize is called twice, ensure that we start with a
// clean RCO instance
this.sectionKeys = sectionKeys;
dependencies.clear();
StackId stackId = cluster.getCurrentStackVersion();
StackInfo stack = null;
try {
stack = ambariMetaInfo.getStack(stackId.getStackName(),
stackId.getStackVersion());
} catch (AmbariException ignored) {
// initialize() will fail with NPE
}
Map<String,Object> userData = stack.getRoleCommandOrder().getContent();
Map<String,Object> generalSection =
(Map<String, Object>) userData.get(GENERAL_DEPS_KEY);
addDependencies(generalSection);
for (String sectionKey : sectionKeys) {
Map<String, Object> section = (Map<String, Object>) userData.get(sectionKey);
addDependencies(section);
}
extendTransitiveDependency();
addMissingRestartDependencies();
}
/**
* Returns the dependency order. -1 => rgn1 before rgn2, 0 => they can be
* parallel 1 => rgn2 before rgn1
*
* @param rgn1 roleGraphNode1
* @param rgn2 roleGraphNode2
*/
public int order(RoleGraphNode rgn1, RoleGraphNode rgn2) {
RoleCommandPair rcp1 = new RoleCommandPair(rgn1.getRole(),
rgn1.getCommand());
RoleCommandPair rcp2 = new RoleCommandPair(rgn2.getRole(),
rgn2.getCommand());
if ((dependencies.get(rcp1) != null)
&& (dependencies.get(rcp1).contains(rcp2))) {
return 1;
} else if ((dependencies.get(rcp2) != null)
&& (dependencies.get(rcp2).contains(rcp1))) {
return -1;
} else if (!rgn2.getCommand().equals(rgn1.getCommand())) {
return compareCommands(rgn1, rgn2);
}
return 0;
}
/**
* Returns transitive dependencies as a services list
* @param service to check if it depends on another services
* @return tramsitive services
*/
public Set<Service> getTransitiveServices(Service service, RoleCommand cmd)
throws AmbariException {
Set<Service> transitiveServices = new HashSet<>();
Cluster cluster = service.getCluster();
Set<RoleCommandPair> allDeps = new HashSet<>();
for (ServiceComponent sc : service.getServiceComponents().values()) {
RoleCommandPair rcp = new RoleCommandPair(Role.valueOf(sc.getName()), cmd);
Set<RoleCommandPair> deps = dependencies.get(rcp);
if (deps != null) {
allDeps.addAll(deps);
}
}
for (Service s : cluster.getServices().values()) {
for (RoleCommandPair rcp : allDeps) {
ServiceComponent sc = s.getServiceComponents().get(rcp.getRole().toString());
if (sc != null) {
transitiveServices.add(s);
break;
}
}
}
return transitiveServices;
}
/**
* Adds transitive dependencies to each node.
* A => B and B => C implies A => B,C and B => C
*/
private void extendTransitiveDependency() {
for (Map.Entry<RoleCommandPair, Set<RoleCommandPair>> roleCommandPairSetEntry : dependencies.entrySet()) {
HashSet<RoleCommandPair> visited = new HashSet<>();
HashSet<RoleCommandPair> transitiveDependencies = new HashSet<>();
for (RoleCommandPair directlyBlockedOn : dependencies.get(roleCommandPairSetEntry.getKey())) {
visited.add(directlyBlockedOn);
identifyTransitiveDependencies(directlyBlockedOn, visited, transitiveDependencies);
}
if (transitiveDependencies.size() > 0) {
dependencies.get(roleCommandPairSetEntry.getKey()).addAll(transitiveDependencies);
}
}
}
private void identifyTransitiveDependencies(RoleCommandPair rcp, HashSet<RoleCommandPair> visited,
HashSet<RoleCommandPair> transitiveDependencies) {
if (dependencies.get(rcp) != null) {
for (RoleCommandPair blockedOn : dependencies.get(rcp)) {
if (!visited.contains(blockedOn)) {
visited.add(blockedOn);
transitiveDependencies.add(blockedOn);
identifyTransitiveDependencies(blockedOn, visited, transitiveDependencies);
}
}
}
}
/**
* RoleCommand.RESTART dependencies that are missing from role_command_order.json
* will be added to the RCO graph to make sure RESTART ALL type of
* operations respect the RCO. Only those @{@link RoleCommandPair} will be
* added which do not have any RESTART definition in the role_command_order.json
* by copying dependencies from the START operation.
*/
private void addMissingRestartDependencies() {
Map<RoleCommandPair, Set<RoleCommandPair>> missingDependencies = new HashMap<>();
for (Map.Entry<RoleCommandPair, Set<RoleCommandPair>> roleCommandPairSetEntry : dependencies.entrySet()) {
RoleCommandPair roleCommandPair = roleCommandPairSetEntry.getKey();
if (roleCommandPair.getCmd().equals(RoleCommand.START)) {
RoleCommandPair restartPair = new RoleCommandPair(roleCommandPair.getRole(), RoleCommand.RESTART);
if (!dependencies.containsKey(restartPair)) {
// Assumption that if defined the RESTART deps are complete
Set<RoleCommandPair> roleCommandDeps = new HashSet<>();
for (RoleCommandPair rco : roleCommandPairSetEntry.getValue()) {
// Change dependency Role to match source
roleCommandDeps.add(new RoleCommandPair(rco.getRole(), RoleCommand.RESTART));
}
if (LOG.isDebugEnabled()) {
LOG.debug("Adding dependency for " + restartPair + ", " +
"dependencies => " + roleCommandDeps);
}
missingDependencies.put(restartPair, roleCommandDeps);
}
}
}
if (!missingDependencies.isEmpty()) {
dependencies.putAll(missingDependencies);
}
}
private int compareCommands(RoleGraphNode rgn1, RoleGraphNode rgn2) {
// TODO: add proper order comparison support for RoleCommand.ACTIONEXECUTE
RoleCommand rc1 = rgn1.getCommand();
RoleCommand rc2 = rgn2.getCommand();
if (rc1.equals(rc2)) {
//If its coming here means roles have no dependencies.
return 0;
}
if (independentCommands.contains(rc1) && independentCommands.contains(rc2)) {
return 0;
}
if (rc1.equals(RoleCommand.INSTALL)) {
return -1;
} else if (rc2.equals(RoleCommand.INSTALL)) {
return 1;
} else if (rc1.equals(RoleCommand.START) || rc1.equals(RoleCommand.EXECUTE)
|| rc1.equals(RoleCommand.SERVICE_CHECK)) {
return -1;
} else if (rc2.equals(RoleCommand.START) || rc2.equals(RoleCommand.EXECUTE)
|| rc2.equals(RoleCommand.SERVICE_CHECK)) {
return 1;
} else if (rc1.equals(RoleCommand.STOP)) {
return -1;
} else if (rc2.equals(RoleCommand.STOP)) {
return 1;
}
return 0;
}
public int compareDeps(RoleCommandOrder rco) {
Set<RoleCommandPair> v1;
Set<RoleCommandPair> v2;
if (this == rco) {
return 0;
}
// Check for key set match
if (!dependencies.keySet().equals(rco.dependencies.keySet())){
LOG.debug("dependency keysets differ");
return 1;
}
LOG.debug("dependency keysets match");
// So far so good. Since the keysets match, let's check the
// actual entries against each other
for (Map.Entry<RoleCommandPair, Set<RoleCommandPair>> roleCommandPairSetEntry : dependencies.entrySet()) {
v1 = dependencies.get(roleCommandPairSetEntry.getKey());
v2 = rco.dependencies.get(roleCommandPairSetEntry.getKey());
if (!v1.equals(v2)) {
LOG.debug("different entry found for key ("
+ roleCommandPairSetEntry.getKey().getRole().toString() + ", "
+ roleCommandPairSetEntry.getKey().getCmd().toString() + ")" );
return 1;
}
}
LOG.debug("dependency entries match");
return 0;
}
/**
* Gets the collection of section names that was used to initialize this
* {@link RoleCommandOrder} instance. If this instance has not been
* initialized, this will be {@code null}.
* <p/>
* The ordering of this collection is maintained.
*
* @return the section keys used to initialize this instance or {@code null}
* if it has not been initialized.
*/
public LinkedHashSet<String> getSectionKeys() {
return sectionKeys;
}
/**
* For test purposes
*/
public Map<RoleCommandPair, Set<RoleCommandPair>> getDependencies() {
return dependencies;
}
/**
* {@inheritDoc}
*/
@Override
public Object clone() throws CloneNotSupportedException {
RoleCommandOrder clone = (RoleCommandOrder) super.clone();
clone.sectionKeys = new LinkedHashSet<>(sectionKeys);
clone.dependencies = new HashMap<>(dependencies);
return clone;
}
}