blob: bc9410439754433033f37778caeb435702f6d999 [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.stack;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.api.services.AmbariMetaInfo;
import org.apache.ambari.server.state.ComponentInfo;
import org.apache.ambari.server.state.CustomCommandDefinition;
import org.apache.ambari.server.state.PropertyInfo;
import org.apache.ambari.server.state.QuickLinksConfigurationInfo;
import org.apache.ambari.server.state.ServiceInfo;
import org.apache.ambari.server.state.ServicePropertyInfo;
import org.apache.ambari.server.state.ThemeInfo;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Service module which provides all functionality related to parsing and fully
* resolving services from the stack definition.
*/
public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> implements Validable{
/**
* Corresponding service info
*/
private ServiceInfo serviceInfo;
/**
* Context which provides modules access to external functionality
*/
private StackContext stackContext;
/**
* Map of child configuration modules keyed by configuration type
*/
private Map<String, ConfigurationModule> configurationModules =
new HashMap<String, ConfigurationModule>();
/**
* Map of child component modules keyed by component name
*/
private Map<String, ComponentModule> componentModules =
new HashMap<String, ComponentModule>();
/**
* Map of themes, single value currently
*/
private Map<String, ThemeModule> themeModules = new HashMap<String, ThemeModule>();
/**
* Map of quicklinks, single value currently
*/
private Map<String, QuickLinksConfigurationModule> quickLinksConfigurationModules = new HashMap<String, QuickLinksConfigurationModule>();
/**
* Encapsulates IO operations on service directory
*/
private ServiceDirectory serviceDirectory;
/**
* Flag to mark a service as a common service
*/
private boolean isCommonService;
/**
* validity flag
*/
protected boolean valid = true;
/**
* Logger
*/
private final static Logger LOG = LoggerFactory.getLogger(ServiceModule.class);
/**
* Constructor.
*
* @param stackContext stack context which provides module access to external functionality
* @param serviceInfo associated service info
* @param serviceDirectory used for all IO interaction with service directory in stack definition
*/
public ServiceModule(StackContext stackContext, ServiceInfo serviceInfo, ServiceDirectory serviceDirectory) {
this(stackContext, serviceInfo, serviceDirectory, false);
}
/**
* Constructor.
*
* @param stackContext stack context which provides module access to external functionality
* @param serviceInfo associated service info
* @param serviceDirectory used for all IO interaction with service directory in stack definition
* @param isCommonService flag to mark a service as a common service
*/
public ServiceModule(
StackContext stackContext, ServiceInfo serviceInfo, ServiceDirectory serviceDirectory, boolean isCommonService) {
this.serviceInfo = serviceInfo;
this.stackContext = stackContext;
this.serviceDirectory = serviceDirectory;
this.isCommonService = isCommonService;
serviceInfo.setMetricsFile(serviceDirectory.getMetricsFile(serviceInfo.getName()));
serviceInfo.setAlertsFile(serviceDirectory.getAlertsFile());
serviceInfo.setKerberosDescriptorFile(serviceDirectory.getKerberosDescriptorFile());
serviceInfo.setWidgetsDescriptorFile(serviceDirectory.getWidgetsDescriptorFile(serviceInfo.getName()));
serviceInfo.setRoleCommandOrder(serviceDirectory.getRoleCommandOrder());
serviceInfo.setSchemaVersion(AmbariMetaInfo.SCHEMA_VERSION_2);
serviceInfo.setServicePackageFolder(serviceDirectory.getPackageDir());
serviceInfo.setServiceUpgradesFolder(serviceDirectory.getUpgradesDir());
serviceInfo.setAdvisorFile(serviceDirectory.getAdvisorFile());
serviceInfo.setAdvisorName(serviceDirectory.getAdvisorName(serviceInfo.getName()));
populateComponentModules();
populateConfigurationModules();
populateThemeModules();
populateQuickLinksConfigurationModules();
validateServiceInfo();
}
@Override
public ServiceInfo getModuleInfo() {
return serviceInfo;
}
@Override
public void resolve(
ServiceModule parentModule, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
throws AmbariException {
resolveInternal(parentModule, allStacks, commonServices, extensions, false);
}
public void resolveExplicit(
ServiceModule parentModule, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
throws AmbariException {
resolveInternal(parentModule, allStacks, commonServices, extensions, true);
}
public void resolveInternal(
ServiceModule parentModule, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices,
Map<String, ExtensionModule> extensions, boolean resolveExplicit)
throws AmbariException {
if (!serviceInfo.isValid() || !parentModule.isValid()) {
return;
}
LOG.info("Resolve service");
// If resolving against parent stack service module (stack inheritance), do not merge if an
// explicit parent is specified
if(!StringUtils.isBlank(serviceInfo.getParent()) && !resolveExplicit) {
return;
}
ServiceInfo parent = parentModule.getModuleInfo();
if (serviceInfo.getComment() == null) {
serviceInfo.setComment(parent.getComment());
}
LOG.info("Display name service/parent: " + serviceInfo.getDisplayName() + "/" + parent.getDisplayName());
if (serviceInfo.getDisplayName() == null) {
serviceInfo.setDisplayName(parent.getDisplayName());
}
if (serviceInfo.getVersion() == null) {
serviceInfo.setVersion(parent.getVersion());
}
if (serviceInfo.getRequiredServices() == null
|| serviceInfo.getRequiredServices().size() == 0) {
serviceInfo.setRequiredServices(parent.getRequiredServices() != null ?
parent.getRequiredServices() :
Collections.<String>emptyList());
}
if (serviceInfo.isRestartRequiredAfterChange() == null) {
serviceInfo.setRestartRequiredAfterChange(parent.isRestartRequiredAfterChange());
}
if (serviceInfo.isRestartRequiredAfterRackChange() == null) {
serviceInfo.setRestartRequiredAfterRackChange(parent.isRestartRequiredAfterRackChange());
}
if (serviceInfo.isMonitoringService() == null) {
serviceInfo.setMonitoringService(parent.isMonitoringService());
}
if (serviceInfo.getOsSpecifics().isEmpty() ) {
serviceInfo.setOsSpecifics(parent.getOsSpecifics());
}
if (serviceInfo.getCommandScript() == null) {
serviceInfo.setCommandScript(parent.getCommandScript());
}
if (serviceInfo.getServicePackageFolder() == null) {
serviceInfo.setServicePackageFolder(parent.getServicePackageFolder());
}
if (serviceInfo.getServiceUpgradesFolder() == null) {
serviceInfo.setServiceUpgradesFolder(parent.getServiceUpgradesFolder());
}
if (serviceInfo.getMetricsFile() == null) {
serviceInfo.setMetricsFile(parent.getMetricsFile());
}
if (serviceInfo.getAlertsFile() == null) {
serviceInfo.setAlertsFile(parent.getAlertsFile());
}
if (serviceInfo.getKerberosDescriptorFile() == null) {
serviceInfo.setKerberosDescriptorFile(parent.getKerberosDescriptorFile());
}
if (serviceInfo.getThemesMap().isEmpty()) {
serviceInfo.setThemesMap(parent.getThemesMap());
}
if (serviceInfo.getWidgetsDescriptorFile() == null) {
serviceInfo.setWidgetsDescriptorFile(parent.getWidgetsDescriptorFile());
}
if (serviceInfo.getAdvisorFile() == null) {
serviceInfo.setAdvisorFile(parent.getAdvisorFile());
}
if (serviceInfo.getAdvisorName() == null) {
serviceInfo.setAdvisorName(parent.getAdvisorName());
}
if (serviceInfo.getRoleCommandOrder() == null) {
serviceInfo.setRoleCommandOrder(parent.getRoleCommandOrder());
}
mergeCustomCommands(parent.getCustomCommands(), serviceInfo.getCustomCommands());
mergeConfigDependencies(parent);
mergeComponents(parentModule, allStacks, commonServices, extensions);
mergeConfigurations(parentModule, allStacks, commonServices, extensions);
mergeThemes(parentModule, allStacks, commonServices, extensions);
mergeQuickLinksConfigurations(parentModule, allStacks, commonServices, extensions);
mergeExcludedConfigTypes(parent);
mergeServiceProperties(parent.getServicePropertyList());
}
/**
* Merges service properties from parent into the the service properties of this this service.
* Current properties overrides properties with same name from parent
* @param other service properties to merge with the current service property list
*/
private void mergeServiceProperties(List<ServicePropertyInfo> other) {
if (!other.isEmpty()) {
List<ServicePropertyInfo> servicePropertyList = serviceInfo.getServicePropertyList();
List<ServicePropertyInfo> servicePropertiesToAdd = Lists.newArrayList();
Set<String> servicePropertyNames = Sets.newTreeSet(
Iterables.transform(servicePropertyList, new Function<ServicePropertyInfo, String>() {
@Nullable
@Override
public String apply(ServicePropertyInfo serviceProperty) {
return serviceProperty.getName();
}
})
);
for (ServicePropertyInfo otherServiceProperty : other) {
if (!servicePropertyNames.contains(otherServiceProperty.getName()))
servicePropertiesToAdd.add(otherServiceProperty);
}
List<ServicePropertyInfo> mergedServicePropertyList =
ImmutableList.<ServicePropertyInfo>builder()
.addAll(servicePropertyList)
.addAll(servicePropertiesToAdd)
.build();
serviceInfo.setServicePropertyList(mergedServicePropertyList);
validateServiceInfo();
}
}
/**
* Resolve common service
* @param allStacks all stack modules
* @param commonServices common service modules
*
* @throws AmbariException
*/
public void resolveCommonService(Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
throws AmbariException {
if(!isCommonService) {
throw new AmbariException("Not a common service");
}
moduleState = ModuleState.VISITED;
String parentString = serviceInfo.getParent();
if(parentString != null) {
String[] parentToks = parentString.split(StackManager.PATH_DELIMITER);
if(parentToks.length != 3) {
throw new AmbariException("The common service '" + serviceInfo.getName() + serviceInfo.getVersion()
+ "' extends an invalid parent: '" + parentString + "'");
}
if (parentToks[0].equalsIgnoreCase(StackManager.COMMON_SERVICES)) {
String baseServiceKey = parentToks[1] + StackManager.PATH_DELIMITER + parentToks[2];
ServiceModule baseService = commonServices.get(baseServiceKey);
ModuleState baseModuleState = baseService.getModuleState();
if (baseModuleState == ModuleState.INIT) {
baseService.resolveCommonService(allStacks, commonServices, extensions);
} else if (baseModuleState == ModuleState.VISITED) {
//todo: provide more information to user about cycle
throw new AmbariException("Cycle detected while parsing common service");
}
resolveExplicit(baseService, allStacks, commonServices, extensions);
} else {
throw new AmbariException("Common service cannot inherit from a non common service");
}
}
moduleState = ModuleState.RESOLVED;
}
@Override
public boolean isDeleted() {
return serviceInfo.isDeleted();
}
@Override
public String getId() {
return serviceInfo.getName();
}
@Override
public void finalizeModule() {
finalizeChildModules(configurationModules.values());
finalizeChildModules(componentModules.values());
finalizeConfiguration();
if(serviceInfo.getCommandScript() != null && ! isDeleted()) {
stackContext.registerServiceCheck(getId());
}
}
/**
* Parse and populate child component modules.
*/
private void populateComponentModules() {
for (ComponentInfo component : serviceInfo.getComponents()) {
componentModules.put(component.getName(), new ComponentModule(component));
}
}
/**
* Parse and populate child configuration modules.
*/
private void populateConfigurationModules() {
ConfigurationDirectory configDirectory = serviceDirectory.getConfigurationDirectory(
serviceInfo.getConfigDir(), AmbariMetaInfo.SERVICE_PROPERTIES_FOLDER_NAME);
if (configDirectory != null) {
for (ConfigurationModule config : configDirectory.getConfigurationModules()) {
ConfigurationInfo info = config.getModuleInfo();
if (isValid()){
setValid(config.isValid() && info.isValid());
if (!isValid()){
addErrors(config.getErrors());
addErrors(info.getErrors());
}
}
serviceInfo.getProperties().addAll(info.getProperties());
serviceInfo.setTypeAttributes(config.getConfigType(), info.getAttributes());
configurationModules.put(config.getConfigType(), config);
}
for (String excludedType : serviceInfo.getExcludedConfigTypes()) {
if (! configurationModules.containsKey(excludedType)) {
ConfigurationInfo configInfo = new ConfigurationInfo(
Collections.<PropertyInfo>emptyList(), Collections.<String, String>emptyMap());
ConfigurationModule config = new ConfigurationModule(excludedType, configInfo);
config.setDeleted(true);
configurationModules.put(excludedType, config);
}
}
}
}
private void populateThemeModules() {
if (serviceInfo.getThemesDir() == null) {
serviceInfo.setThemesDir(AmbariMetaInfo.SERVICE_THEMES_FOLDER_NAME);
}
String themesDir = serviceDirectory.getAbsolutePath() + File.separator + serviceInfo.getThemesDir();
if (serviceInfo.getThemes() != null) {
for (ThemeInfo themeInfo : serviceInfo.getThemes()) {
File themeFile = new File(themesDir + File.separator + themeInfo.getFileName());
ThemeModule module = new ThemeModule(themeFile, themeInfo);
themeModules.put(module.getId(), module);
}
}
//lets not fail if theme contain errors
}
/**
* Merge theme modules.
*/
private void mergeThemes(ServiceModule parent, Map<String, StackModule> allStacks,
Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions) throws AmbariException {
Collection<ThemeModule> mergedModules = mergeChildModules(allStacks, commonServices, extensions, themeModules, parent.themeModules);
for (ThemeModule mergedModule : mergedModules) {
themeModules.put(mergedModule.getId(), mergedModule);
ThemeInfo moduleInfo = mergedModule.getModuleInfo();
if (!moduleInfo.isDeleted()) {
serviceInfo.getThemesMap().put(moduleInfo.getFileName(), moduleInfo);
} else {
serviceInfo.getThemesMap().remove(moduleInfo.getFileName());
}
}
}
private void populateQuickLinksConfigurationModules(){
if (serviceInfo.getQuickLinksConfigurationsDir() == null) {
serviceInfo.setQuickLinksConfigurationsDir(AmbariMetaInfo.SERVICE_QUICKLINKS_CONFIGURATIONS_FOLDER_NAME);
}
String quickLinksConfigurationsDir = serviceDirectory.getAbsolutePath() + File.separator + serviceInfo.getQuickLinksConfigurationsDir();
if (serviceInfo.getQuickLinksConfigurations() != null) {
for (QuickLinksConfigurationInfo quickLinksConfigurationInfo: serviceInfo.getQuickLinksConfigurations()) {
File file = new File(quickLinksConfigurationsDir + File.separator + quickLinksConfigurationInfo.getFileName());
QuickLinksConfigurationModule module = new QuickLinksConfigurationModule(file, quickLinksConfigurationInfo);
quickLinksConfigurationModules.put(module.getId(), module);
}
} //Not fail if quicklinks.json file contains errors
}
/**
* Merge theme modules.
*/
private void mergeQuickLinksConfigurations(ServiceModule parent, Map<String, StackModule> allStacks,
Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions) throws AmbariException {
Collection<QuickLinksConfigurationModule> mergedModules = mergeChildModules(allStacks, commonServices, extensions, quickLinksConfigurationModules, parent.quickLinksConfigurationModules);
for (QuickLinksConfigurationModule mergedModule : mergedModules) {
quickLinksConfigurationModules.put(mergedModule.getId(), mergedModule);
QuickLinksConfigurationInfo moduleInfo = mergedModule.getModuleInfo();
if (!moduleInfo.isDeleted()) {
serviceInfo.getQuickLinksConfigurationsMap().put(moduleInfo.getFileName(), moduleInfo);
} else {
serviceInfo.getQuickLinksConfigurationsMap().remove(moduleInfo.getFileName());
}
}
}
/**
* Merge excluded configs types with parent. Child values override parent values.
*
* @param parent parent service module
*/
private void mergeExcludedConfigTypes(ServiceInfo parent){
if (serviceInfo.getExcludedConfigTypes() == null){
serviceInfo.setExcludedConfigTypes(parent.getExcludedConfigTypes());
} else if (parent.getExcludedConfigTypes() != null){
Set<String> resultExcludedConfigTypes = serviceInfo.getExcludedConfigTypes();
for (String excludedType : parent.getExcludedConfigTypes()) {
if (!resultExcludedConfigTypes.contains(excludedType)){
resultExcludedConfigTypes.add(excludedType);
}
}
serviceInfo.setExcludedConfigTypes(resultExcludedConfigTypes);
}
}
/**
* Merge configuration dependencies with parent. Child values override parent values.
*
* @param parent parent service module
*/
private void mergeConfigDependencies(ServiceInfo parent) {
//currently there is no way to remove an inherited config dependency
List<String> configDependencies = serviceInfo.getConfigDependencies();
List<String> parentConfigDependencies = parent.getConfigDependencies() != null ?
parent.getConfigDependencies() : Collections.<String>emptyList();
if (configDependencies == null) {
serviceInfo.setConfigDependencies(parentConfigDependencies);
} else {
for (String parentDependency : parentConfigDependencies) {
if (! configDependencies.contains(parentDependency)) {
configDependencies.add(parentDependency);
}
}
}
}
/**
* Merge configurations with the parent configurations.
* This will update the child configuration module set as well as the underlying info instances.
*
* @param parent parent service module
* @param allStacks all stack modules
* @param commonServices common service modules
*/
private void mergeConfigurations(
ServiceModule parent, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
throws AmbariException {
serviceInfo.getProperties().clear();
serviceInfo.setAllConfigAttributes(new HashMap<String, Map<String, Map<String, String>>>());
Collection<ConfigurationModule> mergedModules = mergeChildModules(
allStacks, commonServices, extensions, configurationModules, parent.configurationModules);
for (ConfigurationModule module : mergedModules) {
configurationModules.put(module.getId(), module);
if(!module.isDeleted()) {
serviceInfo.getProperties().addAll(module.getModuleInfo().getProperties());
serviceInfo.setTypeAttributes(module.getConfigType(), module.getModuleInfo().getAttributes());
}
}
}
/**
* Merge components with the parent configurations.
* This will update the child component module set as well as the underlying info instances.
*
* @param parent parent service module
* @param allStacks all stack modules
* @param commonServices common service modules
*/
private void mergeComponents(
ServiceModule parent, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
throws AmbariException {
serviceInfo.getComponents().clear();
Collection<ComponentModule> mergedModules = mergeChildModules(
allStacks, commonServices, extensions, componentModules, parent.componentModules);
componentModules.clear();
for (ComponentModule module : mergedModules) {
componentModules.put(module.getId(), module);
serviceInfo.getComponents().add(module.getModuleInfo());
}
}
/**
* Merge custom commands with the parent custom commands.
*
* @param parentCmds parent custom command collection
* @param childCmds child custom command collection
*/
//todo: duplicated in Component Module. Can we use mergeChildModules?
private void mergeCustomCommands(Collection<CustomCommandDefinition> parentCmds,
Collection<CustomCommandDefinition> childCmds) {
Collection<String> existingNames = new HashSet<String>();
for (CustomCommandDefinition childCmd : childCmds) {
existingNames.add(childCmd.getName());
}
for (CustomCommandDefinition parentCmd : parentCmds) {
if (! existingNames.contains(parentCmd.getName())) {
childCmds.add(parentCmd);
}
}
}
/**
* Finalize service configurations.
* Ensure that all default type attributes are set.
*/
private void finalizeConfiguration() {
for (ConfigurationModule config : configurationModules.values()) {
ConfigurationInfo configInfo = config.getModuleInfo();
configInfo.ensureDefaultAttributes();
serviceInfo.setTypeAttributes(config.getConfigType(), configInfo.getAttributes());
}
}
@Override
public boolean isValid() {
return valid;
}
@Override
public void setValid(boolean valid) {
this.valid = valid;
}
private Set<String> errorSet = new HashSet<String>();
@Override
public void addError(String error) {
errorSet.add(error);
}
@Override
public Collection<String> getErrors() {
return errorSet;
}
@Override
public void addErrors(Collection<String> errors) {
this.errorSet.addAll(errors);
}
private void validateServiceInfo() {
if (!serviceInfo.isValid()) {
setValid(false);
addErrors(serviceInfo.getErrors());
}
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
}