blob: 637471f71851caabf93b34c4773380a72c41fc06 [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.ambari.server.customactions;
import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.actionmanager.ActionType;
import org.apache.ambari.server.actionmanager.TargetHostType;
import org.apache.ambari.server.api.services.AmbariMetaInfo;
import org.apache.ambari.server.security.authorization.RoleAuthorization;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.UnmarshalException;
import javax.xml.bind.Unmarshaller;
import java.io.File;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Manages Action definitions read from XML files
*/
public class ActionDefinitionManager {
public static final Short MIN_TIMEOUT = 60;
private final static Logger LOG = LoggerFactory
.getLogger(ActionDefinitionManager.class);
private static final Map<Class<?>, JAXBContext> _jaxbContexts =
new HashMap<Class<?>, JAXBContext>();
private static final Short MAX_TIMEOUT = Short.MAX_VALUE-1;
static {
try {
JAXBContext ctx = JAXBContext.newInstance(ActionDefinitionXml.class);
_jaxbContexts.put(ActionDefinitionXml.class, ctx);
} catch (JAXBException e) {
throw new RuntimeException(e);
}
}
private final Map<String, ActionDefinition> actionDefinitionMap = new HashMap<String, ActionDefinition>();
public ActionDefinitionManager() {
}
public static <T> T unmarshal(Class<T> clz, File file) throws JAXBException {
Unmarshaller u = _jaxbContexts.get(clz).createUnmarshaller();
return clz.cast(u.unmarshal(file));
}
private <E extends Enum<E>> E safeValueOf(Class<E> enumm, String s, StringBuilder reason) {
if (s == null || s.length() == 0) {
return null;
}
try {
return Enum.valueOf(enumm, s);
} catch (IllegalArgumentException iaex) {
reason.append("Invalid value provided for " + enumm.getName());
return null;
}
}
public void readCustomActionDefinitions(File customActionDefinitionRoot) throws JAXBException, AmbariException {
if (customActionDefinitionRoot == null
|| !customActionDefinitionRoot.exists()
|| !customActionDefinitionRoot.canRead()) {
LOG.warn("Cannot read custom action definitions. " +
customActionDefinitionRoot == null ? "" : "Check path " + customActionDefinitionRoot.getAbsolutePath());
}
File[] customActionDefinitionFiles
= customActionDefinitionRoot.listFiles(AmbariMetaInfo.FILENAME_FILTER);
if (customActionDefinitionFiles != null) {
for (File definitionFile : customActionDefinitionFiles) {
ActionDefinitionXml adx = null;
try {
adx = unmarshal(ActionDefinitionXml.class, definitionFile);
} catch (UnmarshalException uex) {
LOG.warn("Encountered badly formed action definition file - " + definitionFile.getAbsolutePath());
continue;
}
for (ActionDefinitionSpec ad : adx.actionDefinitions()) {
LOG.debug("Read action definition = " + ad.toString());
StringBuilder errorReason =
new StringBuilder("Error while parsing action definition. ").append(ad.toString()).append(" --- ");
TargetHostType targetType = safeValueOf(TargetHostType.class, ad.getTargetType(), errorReason);
ActionType actionType = safeValueOf(ActionType.class, ad.getActionType(), errorReason);
Short defaultTimeout = MIN_TIMEOUT;
if (ad.getDefaultTimeout() != null && !ad.getDefaultTimeout().isEmpty()) {
defaultTimeout = Short.parseShort(ad.getDefaultTimeout());
}
if (isValidActionDefinition(ad, actionType, defaultTimeout, errorReason)) {
String actionName = ad.getActionName();
if (actionDefinitionMap.containsKey(actionName)) {
LOG.warn("Ignoring action definition as a different definition by that name already exists. "
+ ad.toString());
continue;
}
actionDefinitionMap.put(ad.getActionName(), new ActionDefinition(ad.getActionName(), actionType,
ad.getInputs(), ad.getTargetService(), ad.getTargetComponent(), ad.getDescription(), targetType, defaultTimeout,
translatePermissions(ad.getPermissions())));
LOG.info("Added custom action definition for " + ad.getActionName());
} else {
LOG.warn(errorReason.toString());
}
}
}
}
}
private boolean isValidActionDefinition(ActionDefinitionSpec ad, ActionType actionType,
Short defaultTimeout, StringBuilder reason) {
if (isValidActionName(ad.getActionName(), reason)) {
if (defaultTimeout < MIN_TIMEOUT || defaultTimeout > MAX_TIMEOUT) {
reason.append("Default timeout should be between " + MIN_TIMEOUT + " and " + MAX_TIMEOUT);
return false;
}
if (actionType == null || actionType == ActionType.SYSTEM_DISABLED) {
reason.append("Action type cannot be " + actionType);
return false;
}
if (ad.getDescription() == null || ad.getDescription().isEmpty()) {
reason.append("Action description cannot be empty");
return false;
}
if (ad.getTargetService() == null || ad.getTargetService().isEmpty()) {
if (ad.getTargetComponent() != null && !ad.getTargetComponent().isEmpty()) {
reason.append("Target component cannot be specified unless target service is specified");
return false;
}
}
if (ad.getInputs() != null && !ad.getInputs().isEmpty()) {
String[] parameters = ad.getInputs().split(",");
for (String parameter : parameters) {
if (parameter.trim().isEmpty()) {
reason.append("Empty parameter cannot be specified as an input parameter");
}
}
}
} else {
return false;
}
return true;
}
public List<ActionDefinition> getAllActionDefinition() {
return new ArrayList<ActionDefinition>(actionDefinitionMap.values());
}
public ActionDefinition getActionDefinition(String name) {
return actionDefinitionMap.get(name);
}
public void addActionDefinition(ActionDefinition ad) throws AmbariException {
if (!actionDefinitionMap.containsKey(ad.getActionName())) {
actionDefinitionMap.put(ad.getActionName(), ad);
} else {
throw new AmbariException("Action definition by name " + ad.getActionName() + " already exists.");
}
}
private boolean isValidActionName(String actionName, StringBuilder reason) {
if (actionName == null || actionName.isEmpty()) {
reason.append("Action name cannot be empty");
return false;
}
String trimmedName = actionName.replaceAll("\\s+", "");
if (actionName.length() > trimmedName.length()) {
reason.append("Action name cannot contain white spaces");
return false;
}
return true;
}
/**
* Given a comma-delimited list of permission names, translates into a {@link Set} of
* {@link RoleAuthorization}s.
* <p>
* <code>null</code> is returned if the permission string is null or empty, or if none of the
* permissions in the string translate to a {@link RoleAuthorization}. Permissions that do not
* translate to a {@link RoleAuthorization} will yield a {@link IllegalArgumentException}.
*
* @param permissions a comma-delimited string of permission names
* @return a set of {@link RoleAuthorization}s; or null if no permissions are specified
*/
private Set<RoleAuthorization> translatePermissions(String permissions) {
if (StringUtils.isEmpty(permissions)) {
return null;
} else {
Set<RoleAuthorization> authorizations = new HashSet<RoleAuthorization>();
String[] parts = permissions.split(",");
for (String permission : parts) {
RoleAuthorization authorization = RoleAuthorization.translate(permission);
if (authorization != null) {
authorizations.add(authorization);
}
}
return (authorizations.isEmpty()) ? null : EnumSet.copyOf(authorizations);
}
}
}