blob: 66f1d7eba995346952e8bfaa113161ffa888e282 [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.controller.internal;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.DuplicateResourceException;
import org.apache.ambari.server.ObjectNotFoundException;
import org.apache.ambari.server.ParentObjectNotFoundException;
import org.apache.ambari.server.controller.ConfigurationRequest;
import org.apache.ambari.server.controller.RequestStatusResponse;
import org.apache.ambari.server.controller.predicate.ArrayPredicate;
import org.apache.ambari.server.controller.predicate.EqualsPredicate;
import org.apache.ambari.server.controller.spi.*;
import org.apache.ambari.server.controller.utilities.PredicateHelper;
import org.apache.ambari.server.controller.utilities.PropertyHelper;
import org.apache.ambari.server.security.authorization.AuthorizationException;
import org.apache.ambari.server.utils.RetryHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract resource provider implementation that maps to an Ambari management controller.
*/
public abstract class AbstractResourceProvider extends BaseProvider implements ResourceProvider, ObservableResourceProvider {
/**
* Key property mapping by resource type.
*/
private final Map<Resource.Type, String> keyPropertyIds;
/**
* Observers of this observable resource provider.
*/
private final Set<ResourceProviderObserver> observers = new HashSet<ResourceProviderObserver>();
protected final static Logger LOG = LoggerFactory.getLogger(AbstractResourceProvider.class);
protected final static String PROPERTIES_ATTRIBUTES_REGEX = "properties_attributes/[a-zA-Z][a-zA-Z._-]*$";
public static Pattern propertiesAttributesPattern = Pattern.compile(".*/" + PROPERTIES_ATTRIBUTES_REGEX);
// ----- Constructors ------------------------------------------------------
/**
* Create a new resource provider.
*
* @param propertyIds the property ids
* @param keyPropertyIds the key property ids
*/
protected AbstractResourceProvider(Set<String> propertyIds,
Map<Resource.Type, String> keyPropertyIds) {
super(propertyIds);
this.keyPropertyIds = keyPropertyIds;
}
// ----- ResourceProvider --------------------------------------------------
@Override
public Map<Resource.Type, String> getKeyPropertyIds() {
return keyPropertyIds;
}
// ----- ObservableResourceProvider ----------------------------------------
@Override
public void updateObservers(ResourceProviderEvent event) {
for (ResourceProviderObserver observer : observers) {
observer.update(event);
}
}
@Override
public void addObserver(ResourceProviderObserver observer) {
observers.add(observer);
}
// ----- utility methods ---------------------------------------------------
/**
* Get the set of property ids that uniquely identify the resources
* of this provider.
*
* @return the set of primary key properties
*/
protected abstract Set<String> getPKPropertyIds();
/**
* Notify all listeners of a creation event.
*
* @param type the type of the resources being created
* @param request the request used to create the resources
*/
protected void notifyCreate(Resource.Type type, Request request) {
updateObservers(new ResourceProviderEvent(type, ResourceProviderEvent.Type.Create, request, null));
}
/**
* Notify all listeners of a update event.
*
* @param type the type of the resources being updated
* @param request the request used to update the resources
* @param predicate the predicate used to update the resources
*/
protected void notifyUpdate(Resource.Type type, Request request, Predicate predicate) {
updateObservers(new ResourceProviderEvent(type, ResourceProviderEvent.Type.Update, request, predicate));
}
/**
* Notify all listeners of a delete event.
*
* @param type the type of the resources being deleted
* @param predicate the predicate used to delete the resources
*/
protected void notifyDelete(Resource.Type type, Predicate predicate) {
updateObservers(new ResourceProviderEvent(type, ResourceProviderEvent.Type.Delete, null, predicate));
}
/**
* Get a set of properties from the given predicate. The returned set of
* property/value mappings is required to generate delete or get requests
* to the back end which does not deal with predicates. Note that the
* single predicate can result in multiple backend requests.
*
* @param givenPredicate the predicate
*
* @return the set of properties used to build request objects
*/
protected Set<Map<String, Object>> getPropertyMaps(Predicate givenPredicate) {
SimplifyingPredicateVisitor visitor = new SimplifyingPredicateVisitor(this);
PredicateHelper.visit(givenPredicate, visitor);
List<Predicate> predicates = visitor.getSimplifiedPredicates();
Set<Map<String, Object>> propertyMaps = new HashSet<Map<String, Object>>();
for (Predicate predicate : predicates) {
propertyMaps.add(PredicateHelper.getProperties(predicate));
}
return propertyMaps;
}
/**
* Get a set of properties from the given property map and predicate. The
* returned set of property/value mappings is required to generate update
* requests to the back end which does not deal with Predicates. Note that
* the single property map & predicate can result in multiple backend requests.
*
* @param requestPropertyMap the request update properties; may not be null
* @param givenPredicate the predicate
*
* @return the set of properties used to build request objects
*/
protected Set<Map<String, Object>> getPropertyMaps(Map<String, Object> requestPropertyMap, Predicate givenPredicate)
throws UnsupportedPropertyException, SystemException, NoSuchResourceException, NoSuchParentResourceException {
Set<Map<String, Object>> propertyMaps = new HashSet<Map<String, Object>>();
// If the predicate specifies a unique resource then we can simply return a single
// property map for the update. Otherwise we need to do a get with the given predicate
// to get the set of property maps for the resources that need to be updated.
if (specifiesUniqueResource(givenPredicate)) {
Map<String, Object> propertyMap = new HashMap<String, Object>(PredicateHelper.getProperties(givenPredicate));
propertyMap.putAll(requestPropertyMap);
propertyMaps.add(propertyMap);
} else {
for (Resource resource : getResources(givenPredicate)) {
Map<String, Object> propertyMap = new HashMap<String, Object>(PropertyHelper.getProperties(resource));
propertyMap.putAll(requestPropertyMap);
propertyMaps.add(propertyMap);
}
}
return propertyMaps;
}
/**
* Get a request status
*
* @return the request status
*/
protected RequestStatus getRequestStatus(RequestStatusResponse response, Set<Resource> associatedResources) {
return getRequestStatus(response, associatedResources, null);
}
protected RequestStatus getRequestStatus(RequestStatusResponse response, Set<Resource> associatedResources, RequestStatusMetaData requestStatusMetaData) {
if (response != null){
Resource requestResource = new ResourceImpl(Resource.Type.Request);
if (response.getMessage() != null){
requestResource.setProperty(PropertyHelper.getPropertyId("Requests", "message"), response.getMessage());
}
requestResource.setProperty(PropertyHelper.getPropertyId("Requests", "id"), response.getRequestId());
requestResource.setProperty(PropertyHelper.getPropertyId("Requests", "status"), "Accepted");
return new RequestStatusImpl(requestResource, associatedResources, requestStatusMetaData);
}
return new RequestStatusImpl(null, associatedResources, requestStatusMetaData);
}
/**
* Get a request status
*
* @return the request status
*/
protected RequestStatus getRequestStatus(RequestStatusResponse response) {
return getRequestStatus(response, null) ;
}
/**
* Extracting given query_parameter value from the predicate
* @param queryParameterId query parameter id
* @param predicate predicate
* @return the query parameter
*/
protected static Object getQueryParameterValue(String queryParameterId, Predicate predicate) {
Object result = null;
if (predicate instanceof EqualsPredicate) {
EqualsPredicate equalsPredicate = (EqualsPredicate) predicate;
if (queryParameterId.equals(equalsPredicate.getPropertyId())) {
return equalsPredicate.getValue();
}
} else if (predicate instanceof ArrayPredicate) {
ArrayPredicate arrayPredicate = (ArrayPredicate) predicate;
for (Predicate predicateItem : arrayPredicate.getPredicates()) {
result = getQueryParameterValue(queryParameterId, predicateItem);
if (result != null) {
return result;
}
}
}
return result;
}
/**
* Invoke a command against the Ambari backend to create resources and map
* any {@link AmbariException} to the types appropriate for the
* {@link ResourceProvider} interface.
*
* @param command the command to invoke
* @param <T> the type of the response
*
* @return the response
*
* @throws SystemException thrown if a system exception occurred
* @throws ResourceAlreadyExistsException thrown if a resource already exists
* @throws NoSuchParentResourceException thrown if a parent of a resource doesn't exist
*/
protected <T> T createResources(Command<T> command)
throws SystemException, ResourceAlreadyExistsException, NoSuchParentResourceException {
try {
return invokeWithRetry(command);
} catch (ParentObjectNotFoundException e) {
throw new NoSuchParentResourceException(e.getMessage(), e);
} catch (DuplicateResourceException e) {
throw new ResourceAlreadyExistsException(e.getMessage());
} catch (AmbariException e) {
if (LOG.isErrorEnabled()) {
LOG.error("Caught AmbariException when creating a resource", e);
}
throw new SystemException("An internal system exception occurred: " + e.getMessage(), e);
}
}
/**
* Invoke a command against the Ambari backend to get resources and map
* any {@link AmbariException} to the types appropriate for the
* {@link ResourceProvider} interface.
*
* @param command the command to invoke
* @param <T> the type of the response
*
* @return the response
*
* @throws SystemException thrown if a system exception occurred
* @throws NoSuchParentResourceException thrown if a parent of a resource doesn't exist
*/
protected <T> T getResources (Command<T> command)
throws SystemException, NoSuchResourceException, NoSuchParentResourceException {
try {
return command.invoke();
} catch (ParentObjectNotFoundException e) {
throw new NoSuchParentResourceException(e.getMessage(), e);
} catch (ObjectNotFoundException e) {
throw new NoSuchResourceException("The requested resource doesn't exist: " + e.getMessage(), e);
} catch (AmbariException e) {
if (LOG.isErrorEnabled()) {
LOG.error("Caught AmbariException when getting a resource", e);
}
throw new SystemException("An internal system exception occurred: " + e.getMessage(), e);
}
}
/**
* Invoke a command against the Ambari backend to modify resources and map
* any {@link AmbariException} to the types appropriate for the
* {@link ResourceProvider} interface.
*
* @param command the command to invoke
* @param <T> the type of the response
*
* @return the response
*
* @throws SystemException thrown if a system exception occurred
* @throws NoSuchParentResourceException thrown if a parent of a resource doesn't exist
*/
protected <T> T modifyResources (Command<T> command)
throws SystemException, NoSuchResourceException, NoSuchParentResourceException {
try {
return invokeWithRetry(command);
} catch (ParentObjectNotFoundException e) {
throw new NoSuchParentResourceException(e.getMessage(), e);
} catch (ObjectNotFoundException e) {
throw new NoSuchResourceException("The specified resource doesn't exist: " + e.getMessage(), e);
} catch (AmbariException e) {
if (LOG.isErrorEnabled()) {
LOG.error("Caught AmbariException when modifying a resource", e);
}
throw new SystemException("An internal system exception occurred: " + e.getMessage(), e);
}
}
/**
* Helper method to get a configuration request, if one exists.
* @param parentCategory the parent category name. Checks for a property
* whose category is the parent and marked as a desired config.
* @param properties the properties on the request.
*/
public static List<ConfigurationRequest> getConfigurationRequests(String parentCategory, Map<String, Object> properties) {
List<ConfigurationRequest> configs = new LinkedList<ConfigurationRequest>();
String desiredConfigKey = parentCategory + "/desired_config";
// Multiple configs to be updated
if (properties.containsKey(desiredConfigKey)
&& properties.get(desiredConfigKey) instanceof Set) {
Set<Map<String, Object>> configProperties =
(Set<Map<String, Object>>) properties.get(desiredConfigKey);
for (Map<String, Object> value: configProperties) {
ConfigurationRequest newConfig = new ConfigurationRequest();
for (Entry<String, Object> e : value.entrySet()) {
String propName =
PropertyHelper.getPropertyName(desiredConfigKey + '/' + e.getKey());
String absCatategory =
PropertyHelper.getPropertyCategory(desiredConfigKey + '/' + e.getKey());
parseProperties(newConfig, absCatategory, propName, e.getValue() == null ? null : e.getValue().toString());
}
configs.add(newConfig);
}
return configs;
}
ConfigurationRequest config = null;
// as a convenience, allow consumers to specify name/value overrides in this
// call instead of forcing a cluster call to do that work
for (Entry<String, Object> entry : properties.entrySet()) {
String absCategory = PropertyHelper.getPropertyCategory(entry.getKey());
String propName = PropertyHelper.getPropertyName(entry.getKey());
if (absCategory != null && absCategory.startsWith(desiredConfigKey)) {
config = (null == config) ? new ConfigurationRequest() : config;
if(entry.getValue() != null) {
parseProperties(config, absCategory, propName, entry.getValue().toString());
}
}
}
if (config != null) {
configs.add(config);
}
return configs;
}
public static void parseProperties(ConfigurationRequest config, String absCategory, String propName, String propValue) {
if (propName.equals("type"))
config.setType(propValue);
else if (propName.equals("tag"))
config.setVersionTag(propValue);
else if (propName.equals("selected")) {
config.setSelected(Boolean.parseBoolean(propValue));
}
else if (propName.equals("service_config_version_note")) {
config.setServiceConfigVersionNote(propValue);
}
else if (absCategory.endsWith("/properties")) {
config.getProperties().put(propName, propValue);
}
else if (propertiesAttributesPattern.matcher(absCategory).matches()) {
String attributeName = absCategory.substring(absCategory.lastIndexOf('/') + 1);
Map<String, Map<String, String>> configAttributesMap = config.getPropertiesAttributes();
if (null == configAttributesMap) {
configAttributesMap = new HashMap<String, Map<String,String>>();
config.setPropertiesAttributes(configAttributesMap);
}
Map<String, String> attributesMap = configAttributesMap.get(attributeName);
if (null == attributesMap) {
attributesMap = new HashMap<String, String>();
configAttributesMap.put(attributeName, attributesMap);
}
attributesMap.put(propName, propValue);
}
}
// get the resources (id fields only) for the given predicate.
private Set<Resource> getResources(Predicate givenPredicate)
throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
// TODO : Check for case where we should pass the request out to the cluster controller.
return getResources(PropertyHelper.getReadRequest(getPKPropertyIds()), givenPredicate);
}
// determine whether or not the given predicate specifies a unique resource for this provider.
private boolean specifiesUniqueResource(Predicate predicate) {
SimplifyingPredicateVisitor visitor = new SimplifyingPredicateVisitor(this);
PredicateHelper.visit(predicate, visitor);
List<Predicate> predicates = visitor.getSimplifiedPredicates();
return predicates.size() == 1 && PredicateHelper.getPropertyIds(predicate).containsAll(getPKPropertyIds());
}
//invoke command with retry support in case of database fail
private <T> T invokeWithRetry(Command<T> command) throws AmbariException, AuthorizationException {
RetryHelper.clearAffectedClusters();
int retryAttempts = RetryHelper.getOperationsRetryAttempts();
do {
try {
return command.invoke();
} catch (Exception e) {
if (RetryHelper.isDatabaseException(e)) {
RetryHelper.invalidateAffectedClusters();
if (retryAttempts > 0) {
LOG.error("Ignoring database exception to perform operation retry, attempts remaining: " + retryAttempts, e);
retryAttempts--;
} else {
RetryHelper.clearAffectedClusters();
throw e;
}
} else {
RetryHelper.clearAffectedClusters();
throw e;
}
}
} while (true);
}
// ----- Inner interface ---------------------------------------------------
/**
* Command to invoke against the Ambari backend.
*
* @param <T> the response type
*/
protected interface Command<T> {
/**
* Invoke this command.
*
* @return the response
*
* @throws AmbariException thrown if a problem occurred during invocation
*/
public T invoke() throws AmbariException, AuthorizationException;
}
}