| /** |
| * 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; |
| } |
| } |