/* * 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.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.StaticallyInject; import org.apache.ambari.server.api.resources.AlertTargetResourceDefinition; import org.apache.ambari.server.controller.spi.NoSuchParentResourceException; import org.apache.ambari.server.controller.spi.NoSuchResourceException; import org.apache.ambari.server.controller.spi.Predicate; import org.apache.ambari.server.controller.spi.Request; import org.apache.ambari.server.controller.spi.RequestStatus; import org.apache.ambari.server.controller.spi.Resource; import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException; import org.apache.ambari.server.controller.spi.SystemException; import org.apache.ambari.server.controller.spi.UnsupportedPropertyException; import org.apache.ambari.server.controller.utilities.PropertyHelper; import org.apache.ambari.server.notifications.DispatchFactory; import org.apache.ambari.server.notifications.NotificationDispatcher; import org.apache.ambari.server.notifications.TargetConfigurationResult; import org.apache.ambari.server.orm.dao.AlertDispatchDAO; import org.apache.ambari.server.orm.entities.AlertGroupEntity; import org.apache.ambari.server.orm.entities.AlertTargetEntity; import org.apache.ambari.server.security.authorization.RoleAuthorization; import org.apache.ambari.server.state.AlertState; import org.apache.ambari.server.state.alert.AlertGroup; import org.apache.ambari.server.state.alert.AlertTarget; import org.apache.commons.lang.StringUtils; import com.google.gson.Gson; import com.google.inject.Inject; import com.google.inject.persist.Transactional; /** * The {@link AlertTargetResourceProvider} class deals with managing the CRUD * operations for {@link AlertTarget}, including property coercion to and from * {@link AlertTargetEntity}. */ @StaticallyInject public class AlertTargetResourceProvider extends AbstractAuthorizedResourceProvider { public static final String ALERT_TARGET = "AlertTarget"; public static final String ALERT_TARGET_ID = "AlertTarget/id"; public static final String ALERT_TARGET_NAME = "AlertTarget/name"; public static final String ALERT_TARGET_DESCRIPTION = "AlertTarget/description"; public static final String ALERT_TARGET_NOTIFICATION_TYPE = "AlertTarget/notification_type"; public static final String ALERT_TARGET_PROPERTIES = "AlertTarget/properties"; public static final String ALERT_TARGET_GROUPS = "AlertTarget/groups"; public static final String ALERT_TARGET_STATES = "AlertTarget/alert_states"; public static final String ALERT_TARGET_GLOBAL = "AlertTarget/global"; public static final String ALERT_TARGET_ENABLED = "AlertTarget/enabled"; private static final Set<String> PK_PROPERTY_IDS = new HashSet<>( Arrays.asList(ALERT_TARGET_ID, ALERT_TARGET_NAME)); /** * The property ids for an alert target resource. */ private static final Set<String> PROPERTY_IDS = new HashSet<>(); /** * The key property ids for an alert target resource. */ private static final Map<Resource.Type, String> KEY_PROPERTY_IDS = new HashMap<>(); static { // properties PROPERTY_IDS.add(ALERT_TARGET_ID); PROPERTY_IDS.add(ALERT_TARGET_NAME); PROPERTY_IDS.add(ALERT_TARGET_DESCRIPTION); PROPERTY_IDS.add(ALERT_TARGET_NOTIFICATION_TYPE); PROPERTY_IDS.add(ALERT_TARGET_PROPERTIES); PROPERTY_IDS.add(ALERT_TARGET_GROUPS); PROPERTY_IDS.add(ALERT_TARGET_STATES); PROPERTY_IDS.add(ALERT_TARGET_GLOBAL); PROPERTY_IDS.add(ALERT_TARGET_ENABLED); // keys KEY_PROPERTY_IDS.put(Resource.Type.AlertTarget, ALERT_TARGET_ID); } /** * Target DAO */ @Inject private static AlertDispatchDAO s_dao; @Inject private static DispatchFactory dispatchFactory; /** * Used for serialization and deserialization of some fields. */ private static final Gson s_gson = new Gson(); /** * Constructor. */ AlertTargetResourceProvider() { super(PROPERTY_IDS, KEY_PROPERTY_IDS); EnumSet<RoleAuthorization> requiredAuthorizations = EnumSet.of(RoleAuthorization.CLUSTER_MANAGE_ALERT_NOTIFICATIONS); setRequiredCreateAuthorizations(requiredAuthorizations); setRequiredUpdateAuthorizations(requiredAuthorizations); setRequiredDeleteAuthorizations(requiredAuthorizations); } @Override protected RequestStatus createResourcesAuthorized(final Request request) throws SystemException, UnsupportedPropertyException, ResourceAlreadyExistsException, NoSuchParentResourceException { createResources(new Command<Void>() { @Override public Void invoke() throws AmbariException { createAlertTargets(request.getProperties(), request.getRequestInfoProperties()); return null; } }); notifyCreate(Resource.Type.AlertTarget, request); return getRequestStatus(null); } @Override public Set<Resource> getResources(Request request, Predicate predicate) throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException { Set<Resource> results = new HashSet<>(); Set<String> requestPropertyIds = getRequestPropertyIds(request, predicate); if( null == predicate ){ List<AlertTargetEntity> entities = s_dao.findAllTargets(); for (AlertTargetEntity entity : entities) { results.add(toResource(entity, requestPropertyIds)); } } else { for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) { String id = (String) propertyMap.get(ALERT_TARGET_ID); if (null == id) { continue; } AlertTargetEntity entity = s_dao.findTargetById(Long.parseLong(id)); if (null != entity) { results.add(toResource(entity, requestPropertyIds)); } } } return results; } @Override protected RequestStatus updateResourcesAuthorized(final Request request, Predicate predicate) throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException { modifyResources(new Command<Void>() { @Override public Void invoke() throws AmbariException { Set<Map<String, Object>> requestMaps = request.getProperties(); for (Map<String, Object> requestMap : requestMaps) { String stringId = (String) requestMap.get(ALERT_TARGET_ID); if (StringUtils.isEmpty(stringId)) { throw new IllegalArgumentException( "The ID of the alert target is required when updating an existing target"); } long alertTargetId = Long.parseLong(stringId); updateAlertTargets(alertTargetId, requestMap); } return null; } }); notifyUpdate(Resource.Type.AlertTarget, request, predicate); return getRequestStatus(null); } @Override protected RequestStatus deleteResourcesAuthorized(Request request, Predicate predicate) throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException { Set<Resource> resources = getResources(new RequestImpl(null, null, null, null), predicate); Set<Long> targetIds = new HashSet<>(); for (final Resource resource : resources) { Long id = (Long) resource.getPropertyValue(ALERT_TARGET_ID); targetIds.add(id); } for (Long targetId : targetIds) { LOG.info("Deleting alert target {}", targetId); final AlertTargetEntity entity = s_dao.findTargetById(targetId.longValue()); modifyResources(new Command<Void>() { @Override public Void invoke() throws AmbariException { s_dao.remove(entity); return null; } }); } notifyDelete(Resource.Type.AlertTarget, predicate); return getRequestStatus(null); } @Override protected Set<String> getPKPropertyIds() { return PK_PROPERTY_IDS; } /** * Create and persist {@link AlertTargetEntity} from the map of properties. * * @param requestMaps * @param requestInfoProps * @throws AmbariException */ @SuppressWarnings("unchecked") private void createAlertTargets(Set<Map<String, Object>> requestMaps, Map<String, String> requestInfoProps) throws AmbariException { for (Map<String, Object> requestMap : requestMaps) { String name = (String) requestMap.get(ALERT_TARGET_NAME); String description = (String) requestMap.get(ALERT_TARGET_DESCRIPTION); String notificationType = (String) requestMap.get(ALERT_TARGET_NOTIFICATION_TYPE); Collection<String> alertStates = (Collection<String>) requestMap.get(ALERT_TARGET_STATES); String globalProperty = (String) requestMap.get(ALERT_TARGET_GLOBAL); String enabledProperty = (String) requestMap.get(ALERT_TARGET_ENABLED); if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException( "The name of the alert target is required."); } if (StringUtils.isEmpty(notificationType)) { throw new IllegalArgumentException( "The type of the alert target is required."); } Map<String, Object> properties = extractProperties(requestMap); String propertiesJson = s_gson.toJson(properties); if (StringUtils.isEmpty(propertiesJson)) { throw new IllegalArgumentException( "Alert targets must be created with their connection properties"); } String validationDirective = requestInfoProps.get(AlertTargetResourceDefinition.VALIDATE_CONFIG_DIRECTIVE); if (validationDirective != null && validationDirective.equalsIgnoreCase("true")) { validateTargetConfig(notificationType, properties); } boolean overwriteExisting = false; if (requestInfoProps.containsKey(AlertTargetResourceDefinition.OVERWRITE_DIRECTIVE)) { overwriteExisting = true; } // global not required boolean isGlobal = false; if (null != globalProperty) { isGlobal = Boolean.parseBoolean(globalProperty); } // enabled not required boolean isEnabled = true; if (null != enabledProperty) { isEnabled = Boolean.parseBoolean(enabledProperty); } // set the states that this alert target cares about final Set<AlertState> alertStateSet; if (null != alertStates) { alertStateSet = new HashSet<>(alertStates.size()); for (String state : alertStates) { alertStateSet.add(AlertState.valueOf(state)); } } else { alertStateSet = EnumSet.allOf(AlertState.class); } // if we are overwriting an existing, determine if one exists first AlertTargetEntity entity = null; if( overwriteExisting ) { entity = s_dao.findTargetByName(name); } if (null == entity) { entity = new AlertTargetEntity(); } // groups are not required on creation if (requestMap.containsKey(ALERT_TARGET_GROUPS)) { Collection<Long> groupIds = (Collection<Long>) requestMap.get(ALERT_TARGET_GROUPS); if( !groupIds.isEmpty() ){ Set<AlertGroupEntity> groups = new HashSet<>(); List<Long> ids = new ArrayList<>(groupIds); groups.addAll(s_dao.findGroupsById(ids)); entity.setAlertGroups(groups); } } entity.setDescription(description); entity.setNotificationType(notificationType); entity.setProperties(propertiesJson); entity.setTargetName(name); entity.setAlertStates(alertStateSet); entity.setGlobal(isGlobal); entity.setEnabled(isEnabled); if (null == entity.getTargetId() || 0 == entity.getTargetId()) { s_dao.create(entity); } else { s_dao.merge(entity); } } } /** * Updates existing {@link AlertTargetEntity}s with the specified properties. * * @param requestMap * a set of property maps, one map for each entity. * @throws AmbariException * if the entity could not be found. */ @Transactional @SuppressWarnings("unchecked") void updateAlertTargets(long alertTargetId, Map<String, Object> requestMap) throws AmbariException { AlertTargetEntity entity = s_dao.findTargetById(alertTargetId); if (null == entity) { String message = MessageFormat.format( "The alert target with ID {0} could not be found", alertTargetId); throw new AmbariException(message); } String name = (String) requestMap.get(ALERT_TARGET_NAME); String description = (String) requestMap.get(ALERT_TARGET_DESCRIPTION); String notificationType = (String) requestMap.get(ALERT_TARGET_NOTIFICATION_TYPE); Collection<String> alertStates = (Collection<String>) requestMap.get(ALERT_TARGET_STATES); Collection<Long> groupIds = (Collection<Long>) requestMap.get(ALERT_TARGET_GROUPS); String isGlobal = (String) requestMap.get(ALERT_TARGET_GLOBAL); String isEnabled = (String) requestMap.get(ALERT_TARGET_ENABLED); if(null != isGlobal){ entity.setGlobal(Boolean.parseBoolean(isGlobal)); } if (null != isEnabled) { entity.setEnabled(Boolean.parseBoolean(isEnabled)); } if (!StringUtils.isBlank(name)) { entity.setTargetName(name); } if (null != description) { entity.setDescription(description); } if (!StringUtils.isBlank(notificationType)) { entity.setNotificationType(notificationType); } String properties = s_gson.toJson(extractProperties(requestMap)); if (!StringUtils.isEmpty(properties)) { entity.setProperties(properties); } // a null alert state implies that the key was not set and no update // should occur for this field, while an empty list implies all alert // states should be set if (null != alertStates) { final Set<AlertState> alertStateSet; if (alertStates.isEmpty()) { alertStateSet = EnumSet.allOf(AlertState.class); } else { alertStateSet = new HashSet<>(alertStates.size()); for (String state : alertStates) { alertStateSet.add(AlertState.valueOf(state)); } } entity.setAlertStates(alertStateSet); } // if groups were supplied, replace existing if (null != groupIds) { Set<AlertGroupEntity> groups = new HashSet<>(); List<Long> ids = new ArrayList<>(groupIds); if (ids.size() > 0) { groups.addAll(s_dao.findGroupsById(ids)); } entity.setAlertGroups(groups); } else if (entity.isGlobal()){ Set<AlertGroupEntity> groups = new HashSet<>(s_dao.findAllGroups()); entity.setAlertGroups(groups); } // merge the entity, cascading the merge to other entities, like groups s_dao.merge(entity); } /** * Convert the given {@link AlertTargetEntity} to a {@link Resource}. * * @param entity * the entity to convert. * @param requestedIds * the properties that were requested or {@code null} for all. * @return the resource representation of the entity (never {@code null}). */ private Resource toResource(AlertTargetEntity entity, Set<String> requestedIds) { Resource resource = new ResourceImpl(Resource.Type.AlertTarget); resource.setProperty(ALERT_TARGET_ID, entity.getTargetId()); resource.setProperty(ALERT_TARGET_NAME, entity.getTargetName()); resource.setProperty(ALERT_TARGET_DESCRIPTION, entity.getDescription()); resource.setProperty(ALERT_TARGET_NOTIFICATION_TYPE, entity.getNotificationType()); resource.setProperty(ALERT_TARGET_ENABLED, entity.isEnabled()); // these are expensive to deserialize; only do it if asked for if (requestedIds.contains(ALERT_TARGET_PROPERTIES)) { String properties = entity.getProperties(); Map<String, String> map = s_gson.<Map<String, String>> fromJson( properties, Map.class); resource.setProperty(ALERT_TARGET_PROPERTIES, map); } setResourceProperty(resource, ALERT_TARGET_STATES, entity.getAlertStates(), requestedIds); setResourceProperty(resource, ALERT_TARGET_GLOBAL, entity.isGlobal(), requestedIds); if (BaseProvider.isPropertyRequested(ALERT_TARGET_GROUPS, requestedIds)) { Set<AlertGroupEntity> groupEntities = entity.getAlertGroups(); List<AlertGroup> groups = new ArrayList<>( groupEntities.size()); for (AlertGroupEntity groupEntity : groupEntities) { AlertGroup group = new AlertGroup(); group.setId(groupEntity.getGroupId()); group.setName(groupEntity.getGroupName()); group.setClusterName(groupEntity.getClusterId()); group.setDefault(groupEntity.isDefault()); groups.add(group); } resource.setProperty(ALERT_TARGET_GROUPS, groups); } return resource; } /** * Looks through the flat list of propery keys in the supplied map and builds * a JSON string of key:value pairs for the {@link #ALERT_TARGET_PROPERTIES} * key. * * @param requestMap * the map of flattened properties (not {@code null}). * @return the JSON representing the key/value pairs of all properties, or * {@code null} if none. */ private Map<String, Object> extractProperties(Map<String, Object> requestMap) { Map<String, Object> normalizedMap = new HashMap<>( requestMap.size()); for (Entry<String, Object> entry : requestMap.entrySet()) { String key = entry.getKey(); String propCat = PropertyHelper.getPropertyCategory(key); if (propCat.equals(ALERT_TARGET_PROPERTIES)) { String propKey = PropertyHelper.getPropertyName(key); normalizedMap.put(propKey, entry.getValue()); } } return normalizedMap; } /** * Finds dispatcher for given notification type and validates on it given alert target configuration properties. * @param notificationType type of dispatcher * @param properties alert target configuration properties */ private void validateTargetConfig(String notificationType, Map<String, Object> properties) { NotificationDispatcher dispatcher = dispatchFactory.getDispatcher(notificationType); if (dispatcher == null) { throw new IllegalArgumentException("Dispatcher for given notification type doesn't exist"); } TargetConfigurationResult validationResult = dispatcher.validateTargetConfig(properties); if (validationResult.getStatus() == TargetConfigurationResult.Status.INVALID) { throw new IllegalArgumentException(validationResult.getMessage()); } } }