Java tutorial
/** * 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.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 java.util.UUID; import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.StaticallyInject; import org.apache.ambari.server.agent.ActionQueue; import org.apache.ambari.server.agent.AlertExecutionCommand; import org.apache.ambari.server.api.resources.AlertDefResourceDefinition; import org.apache.ambari.server.controller.AmbariManagementController; 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.events.AlertDefinitionDisabledEvent; import org.apache.ambari.server.events.AlertHashInvalidationEvent; import org.apache.ambari.server.events.publishers.AmbariEventPublisher; import org.apache.ambari.server.orm.dao.AlertDefinitionDAO; import org.apache.ambari.server.orm.entities.AlertDefinitionEntity; import org.apache.ambari.server.state.Cluster; import org.apache.ambari.server.state.Clusters; import org.apache.ambari.server.state.alert.AlertDefinition; import org.apache.ambari.server.state.alert.AlertDefinitionFactory; import org.apache.ambari.server.state.alert.AlertDefinitionHash; import org.apache.ambari.server.state.alert.Scope; import org.apache.ambari.server.state.alert.SourceType; import org.apache.commons.lang.StringUtils; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.inject.Inject; /** * ResourceProvider for Alert Definitions */ @StaticallyInject public class AlertDefinitionResourceProvider extends AbstractControllerResourceProvider { protected static final String ALERT_DEF = "AlertDefinition"; protected static final String ALERT_DEF_CLUSTER_NAME = "AlertDefinition/cluster_name"; protected static final String ALERT_DEF_ID = "AlertDefinition/id"; protected static final String ALERT_DEF_NAME = "AlertDefinition/name"; protected static final String ALERT_DEF_LABEL = "AlertDefinition/label"; protected static final String ALERT_DEF_DESCRIPTION = "AlertDefinition/description"; protected static final String ALERT_DEF_INTERVAL = "AlertDefinition/interval"; protected static final String ALERT_DEF_SERVICE_NAME = "AlertDefinition/service_name"; protected static final String ALERT_DEF_COMPONENT_NAME = "AlertDefinition/component_name"; protected static final String ALERT_DEF_ENABLED = "AlertDefinition/enabled"; protected static final String ALERT_DEF_SCOPE = "AlertDefinition/scope"; protected static final String ALERT_DEF_IGNORE_HOST = "AlertDefinition/ignore_host"; protected static final String ALERT_DEF_SOURCE = "AlertDefinition/source"; protected static final String ALERT_DEF_SOURCE_TYPE = "AlertDefinition/source/type"; private static Set<String> pkPropertyIds = new HashSet<String>(Arrays.asList(ALERT_DEF_ID, ALERT_DEF_NAME)); private static Gson gson = new Gson(); @Inject private static AlertDefinitionHash alertDefinitionHash; @Inject private static AlertDefinitionDAO alertDefinitionDAO = null; /** * Used for coercing an {@link AlertDefinitionEntity} to an * {@link AlertDefinition}. */ @Inject private static AlertDefinitionFactory definitionFactory; /** * Used to enqueue commands to send to the agents when running an alert * on-demand. */ @Inject private static ActionQueue actionQueue; /** * Publishes the following events: * <ul> * <li>{@link AlertDefinitionDisabledEvent} when an alert definition is * disabled</li> * </ul> */ @Inject private static AmbariEventPublisher eventPublisher; /** * The property ids for an alert defintion resource. */ private static final Set<String> PROPERTY_IDS = new HashSet<String>(); /** * The key property ids for an alert definition resource. */ private static final Map<Resource.Type, String> KEY_PROPERTY_IDS = new HashMap<Resource.Type, String>(); static { // properties PROPERTY_IDS.add(ALERT_DEF_CLUSTER_NAME); PROPERTY_IDS.add(ALERT_DEF_SERVICE_NAME); PROPERTY_IDS.add(ALERT_DEF_COMPONENT_NAME); PROPERTY_IDS.add(ALERT_DEF_ID); PROPERTY_IDS.add(ALERT_DEF_NAME); PROPERTY_IDS.add(ALERT_DEF_LABEL); PROPERTY_IDS.add(ALERT_DEF_DESCRIPTION); PROPERTY_IDS.add(ALERT_DEF_INTERVAL); PROPERTY_IDS.add(ALERT_DEF_ENABLED); PROPERTY_IDS.add(ALERT_DEF_SCOPE); PROPERTY_IDS.add(ALERT_DEF_IGNORE_HOST); PROPERTY_IDS.add(ALERT_DEF_SOURCE); // keys KEY_PROPERTY_IDS.put(Resource.Type.AlertDefinition, ALERT_DEF_ID); KEY_PROPERTY_IDS.put(Resource.Type.Cluster, ALERT_DEF_CLUSTER_NAME); } /** * Constructor. * * @param controller */ AlertDefinitionResourceProvider(AmbariManagementController controller) { super(PROPERTY_IDS, KEY_PROPERTY_IDS, controller); } @Override protected Set<String> getPKPropertyIds() { return pkPropertyIds; } @Override public RequestStatus createResources(final Request request) throws SystemException, UnsupportedPropertyException, ResourceAlreadyExistsException, NoSuchParentResourceException { createResources(new Command<Void>() { @Override public Void invoke() throws AmbariException { createAlertDefinitions(request.getProperties()); return null; } }); notifyCreate(Resource.Type.AlertDefinition, request); return getRequestStatus(null); } private void createAlertDefinitions(Set<Map<String, Object>> requestMaps) throws AmbariException { List<AlertDefinitionEntity> entities = new ArrayList<AlertDefinitionEntity>(); String clusterName = null; for (Map<String, Object> requestMap : requestMaps) { AlertDefinitionEntity entity = new AlertDefinitionEntity(); populateEntity(entity, requestMap); entities.add(entity); if (null == clusterName) { clusterName = (String) requestMap.get(ALERT_DEF_CLUSTER_NAME); } } for (AlertDefinitionEntity entity : entities) { alertDefinitionDAO.create(entity); long clusterId = entity.getClusterId(); // invalidate the hash and publish the event final Set<String> invalidatedHosts = alertDefinitionHash.invalidateHosts(entity); AlertHashInvalidationEvent event = new AlertHashInvalidationEvent(clusterId, invalidatedHosts); eventPublisher.publish(event); } } /** * {@inheritDoc} */ @Override public Set<Resource> getResources(Request request, Predicate predicate) throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException { Set<String> requestPropertyIds = getRequestPropertyIds(request, predicate); Set<Resource> results = new HashSet<Resource>(); for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) { String clusterName = (String) propertyMap.get(ALERT_DEF_CLUSTER_NAME); if (null == clusterName || clusterName.isEmpty()) { throw new IllegalArgumentException("Invalid argument, cluster name is required"); } String id = (String) propertyMap.get(ALERT_DEF_ID); if (null != id) { AlertDefinitionEntity entity = alertDefinitionDAO.findById(Long.parseLong(id)); if (null != entity) { results.add(toResource(clusterName, entity, requestPropertyIds)); } } else { Cluster cluster = null; try { cluster = getManagementController().getClusters().getCluster(clusterName); } catch (AmbariException e) { throw new NoSuchResourceException("Parent Cluster resource doesn't exist", e); } List<AlertDefinitionEntity> entities = alertDefinitionDAO.findAll(cluster.getClusterId()); for (AlertDefinitionEntity entity : entities) { results.add(toResource(clusterName, entity, requestPropertyIds)); } } } return results; } /** * {@inheritDoc} */ @Override public RequestStatus updateResources(Request request, Predicate predicate) throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException { // check the update directives to see if there is a request to run // the alert definition immediately Map<String, String> requestInfoProps = request.getRequestInfoProperties(); if (null != requestInfoProps && requestInfoProps.containsKey(AlertDefResourceDefinition.EXECUTE_IMMEDIATE_DIRECTIVE)) { Set<Map<String, Object>> predicateMaps = getPropertyMaps(predicate); for (Map<String, Object> propertyMap : predicateMaps) { scheduleImmediateAlert(propertyMap); } return getRequestStatus(null); } // if an AlertDefinition property body was specified, perform the update for (Map<String, Object> requestPropMap : request.getProperties()) { for (Map<String, Object> propertyMap : getPropertyMaps(requestPropMap, predicate)) { String stringId = (String) propertyMap.get(ALERT_DEF_ID); long id = Long.parseLong(stringId); AlertDefinitionEntity entity = alertDefinitionDAO.findById(id); if (null == entity) { continue; } // capture the state of the old entity boolean oldEnabled = entity.getEnabled(); try { populateEntity(entity, propertyMap); alertDefinitionDAO.merge(entity); // invalidate and publish the definition hash Set<String> invalidatedHosts = alertDefinitionHash.invalidateHosts(entity); AlertHashInvalidationEvent event = new AlertHashInvalidationEvent(entity.getClusterId(), invalidatedHosts); eventPublisher.publish(event); } catch (AmbariException ae) { LOG.error("Unable to find cluster when updating alert definition", ae); } // if the old state was enabled and the new state is not, trigger // a disabled event if (oldEnabled && !entity.getEnabled()) { AlertDefinitionDisabledEvent event = new AlertDefinitionDisabledEvent(entity.getClusterId(), entity.getDefinitionId()); eventPublisher.publish(event); } } } notifyUpdate(Resource.Type.AlertDefinition, request, predicate); return getRequestStatus(null); } /** * {@inheritDoc} */ @Override public RequestStatus deleteResources(Predicate predicate) throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException { Set<Resource> resources = getResources(new RequestImpl(null, null, null, null), predicate); Set<Long> definitionIds = new HashSet<Long>(); for (final Resource resource : resources) { Long id = (Long) resource.getPropertyValue(ALERT_DEF_ID); definitionIds.add(id); } for (Long definitionId : definitionIds) { LOG.info("Deleting alert definition {}", definitionId); final AlertDefinitionEntity entity = alertDefinitionDAO.findById(definitionId.longValue()); modifyResources(new Command<Void>() { @Override public Void invoke() throws AmbariException { long clusterId = entity.getClusterId(); // remove the entity alertDefinitionDAO.remove(entity); // publish the hash invalidation final Set<String> invalidatedHosts = alertDefinitionHash.invalidateHosts(entity); AlertHashInvalidationEvent event = new AlertHashInvalidationEvent(clusterId, invalidatedHosts); eventPublisher.publish(event); return null; } }); } notifyDelete(Resource.Type.AlertDefinition, predicate); return getRequestStatus(null); } /** * Merges the map of properties into the specified entity. If the entity is * being created, an {@link IllegalArgumentException} is thrown when a * required property is absent. When updating, missing properties are assume * to not have changed. * * @param entity * the entity to merge the properties into (not {@code null}). * @param requestMap * the map of properties (not {@code null}). * @throws AmbariException */ private void populateEntity(AlertDefinitionEntity entity, Map<String, Object> requestMap) throws AmbariException { // some fields are required on creation; on update we keep what's there boolean bCreate = true; if (null != entity.getDefinitionId()) { bCreate = false; } String clusterName = (String) requestMap.get(ALERT_DEF_CLUSTER_NAME); String definitionName = (String) requestMap.get(ALERT_DEF_NAME); String serviceName = (String) requestMap.get(ALERT_DEF_SERVICE_NAME); String componentName = (String) requestMap.get(ALERT_DEF_COMPONENT_NAME); String type = (String) requestMap.get(ALERT_DEF_SOURCE_TYPE); String label = (String) requestMap.get(ALERT_DEF_LABEL); String description = (String) requestMap.get(ALERT_DEF_DESCRIPTION); String desiredScope = (String) requestMap.get(ALERT_DEF_SCOPE); Integer interval = null; if (requestMap.containsKey(ALERT_DEF_INTERVAL)) { interval = Integer.valueOf((String) requestMap.get(ALERT_DEF_INTERVAL)); } Boolean enabled = null; if (requestMap.containsKey(ALERT_DEF_ENABLED)) { enabled = Boolean.parseBoolean((String) requestMap.get(ALERT_DEF_ENABLED)); } else if (bCreate) { enabled = Boolean.TRUE; } Boolean ignoreHost = null; if (requestMap.containsKey(ALERT_DEF_IGNORE_HOST)) { ignoreHost = Boolean.parseBoolean((String) requestMap.get(ALERT_DEF_IGNORE_HOST)); } else if (bCreate) { ignoreHost = Boolean.FALSE; } Scope scope = null; if (null != desiredScope && desiredScope.length() > 0) { scope = Scope.valueOf(desiredScope); } SourceType sourceType = null; if (null != type && type.length() > 0) { sourceType = SourceType.valueOf(type); } // if not specified when creating an alert definition, the scope is // assumed to be ANY if (null == scope && bCreate) { scope = Scope.ANY; } if (StringUtils.isEmpty(clusterName)) { throw new IllegalArgumentException("Invalid argument, cluster name is required"); } if (bCreate && !requestMap.containsKey(ALERT_DEF_INTERVAL)) { throw new IllegalArgumentException("Check interval must be specified"); } if (bCreate && StringUtils.isEmpty(definitionName)) { throw new IllegalArgumentException("Definition name must be specified"); } if (bCreate && StringUtils.isEmpty(serviceName)) { throw new IllegalArgumentException("Service name must be specified"); } // on creation, source type is required if (bCreate && null == sourceType) { throw new IllegalArgumentException( String.format("Source type must be specified and one of %s", EnumSet.allOf(SourceType.class))); } // !!! The AlertDefinition "Source" field is a nested JSON object; // build a JSON representation from the flat properties and then // serialize that JSON object as a string Map<String, JsonObject> jsonObjectMapping = new HashMap<String, JsonObject>(); // for every property in the request, if it's a source property, then // add it to the JSON model for (Entry<String, Object> entry : requestMap.entrySet()) { String propertyKey = entry.getKey(); // only handle "source" subproperties if (!propertyKey.startsWith(ALERT_DEF_SOURCE)) { continue; } // gets a JSON object to add the property to; this will create the // property and all of its parent properties recursively if necessary JsonObject jsonObject = getJsonObjectMapping(ALERT_DEF_SOURCE, jsonObjectMapping, propertyKey); String propertyName = PropertyHelper.getPropertyName(propertyKey); Object entryValue = entry.getValue(); if (entryValue instanceof Collection<?>) { JsonElement jsonElement = gson.toJsonTree(entryValue); jsonObject.add(propertyName, jsonElement); } else { if (entryValue instanceof Number) { jsonObject.addProperty(propertyName, (Number) entryValue); } else { jsonObject.addProperty(propertyName, entryValue.toString()); } } } // "source" must be filled in when creating JsonObject source = jsonObjectMapping.get(ALERT_DEF_SOURCE); if (bCreate && (null == source || 0 == source.entrySet().size())) { throw new IllegalArgumentException("Source must be specified"); } Cluster cluster = getManagementController().getClusters().getCluster(clusterName); // at this point, we have either validated all required properties or // we are using the exiting entity properties where not defined, so we // can do simply null checks entity.setClusterId(Long.valueOf(cluster.getClusterId())); if (null != componentName) { entity.setComponentName(componentName); } if (null != definitionName) { entity.setDefinitionName(definitionName); } if (null != label) { entity.setLabel(label); } if (null != description) { entity.setDescription(description); } if (null != enabled) { entity.setEnabled(enabled.booleanValue()); } if (null != ignoreHost) { entity.setHostIgnored(ignoreHost.booleanValue()); } if (null != interval) { entity.setScheduleInterval(interval); } if (null != serviceName) { entity.setServiceName(serviceName); } if (null != sourceType) { entity.setSourceType(sourceType); } if (null != source) { entity.setSource(source.toString()); } if (null != scope) { entity.setScope(scope); } entity.setHash(UUID.randomUUID().toString()); } /** * Gets a {@link JsonObject} that the specified flattened property should be * set on, creating all parent {@link JsonObject} instances as needed. This * will turn {code AlertDefinition/source/reporting/critical/text} into * * <pre> * AlertDefinition/source: { * reporting: { * critical: { * text * } * } * } * </pre> * * Where the the following {@link JsonObject} instances are created: * * <pre> * AlertDefinition/source * Reporting * Critical * property 'text', * property 'value', * etc * </pre> * * @param root * the flattened property which will be considered the root. * @param jsonObjectMapping * a mapping of flattened property to {@link JsonObject}. * @param propertyKey * the key to get the {@link JsonObject} for. * @return the {@link JsonObject} that cooresponds to the specified key. */ private JsonObject getJsonObjectMapping(String root, Map<String, JsonObject> jsonObjectMapping, String propertyKey) { // if there is already a mapping for the key, return the mapping if (jsonObjectMapping.containsKey(propertyKey)) { return jsonObjectMapping.get(propertyKey); } if (root.equals(propertyKey)) { JsonObject jsonRoot = jsonObjectMapping.get(root); if (null == jsonRoot) { jsonRoot = new JsonObject(); jsonObjectMapping.put(root, jsonRoot); } return jsonRoot; } String propertyCategory = PropertyHelper.getPropertyCategory(propertyKey); JsonObject categoryJson = jsonObjectMapping.get(propertyCategory); if (null == categoryJson) { JsonObject parent = getJsonObjectMapping(root, jsonObjectMapping, propertyCategory); categoryJson = new JsonObject(); jsonObjectMapping.put(propertyCategory, categoryJson); String categoryName = PropertyHelper.getPropertyName(propertyCategory); parent.add(categoryName, categoryJson); } return categoryJson; } private Resource toResource(String clusterName, AlertDefinitionEntity entity, Set<String> requestedIds) { Resource resource = new ResourceImpl(Resource.Type.AlertDefinition); resource.setProperty(ALERT_DEF_ID, entity.getDefinitionId()); resource.setProperty(ALERT_DEF_CLUSTER_NAME, clusterName); resource.setProperty(ALERT_DEF_NAME, entity.getDefinitionName()); resource.setProperty(ALERT_DEF_LABEL, entity.getLabel()); setResourceProperty(resource, ALERT_DEF_DESCRIPTION, entity.getDescription(), requestedIds); setResourceProperty(resource, ALERT_DEF_INTERVAL, entity.getScheduleInterval(), requestedIds); setResourceProperty(resource, ALERT_DEF_SERVICE_NAME, entity.getServiceName(), requestedIds); setResourceProperty(resource, ALERT_DEF_COMPONENT_NAME, entity.getComponentName(), requestedIds); setResourceProperty(resource, ALERT_DEF_ENABLED, Boolean.valueOf(entity.getEnabled()), requestedIds); setResourceProperty(resource, ALERT_DEF_IGNORE_HOST, Boolean.valueOf(entity.isHostIgnored()), requestedIds); setResourceProperty(resource, ALERT_DEF_SCOPE, entity.getScope(), requestedIds); boolean sourceTypeRequested = setResourceProperty(resource, ALERT_DEF_SOURCE_TYPE, entity.getSourceType(), requestedIds); if (sourceTypeRequested && null != resource.getPropertyValue(ALERT_DEF_SOURCE_TYPE)) { try { Map<String, String> map = gson.<Map<String, String>>fromJson(entity.getSource(), Map.class); for (Entry<String, String> entry : map.entrySet()) { String subProp = PropertyHelper.getPropertyId(ALERT_DEF_SOURCE, entry.getKey()); resource.setProperty(subProp, entry.getValue()); } } catch (Exception e) { LOG.error("Could not coerce alert JSON into a type"); } } return resource; } /** * Extracts an {@link AlertDefinitionEntity} from the property map and * enqueues an {@link AlertExecutionCommand} for every host that should re-run * the specified definition. * * @param propertyMap */ private void scheduleImmediateAlert(Map<String, Object> propertyMap) { Clusters clusters = getManagementController().getClusters(); String stringId = (String) propertyMap.get(ALERT_DEF_ID); long id = Long.parseLong(stringId); AlertDefinitionEntity entity = alertDefinitionDAO.findById(id); if (null == entity) { LOG.error("Unable to lookup alert definition with ID {}", id); return; } Cluster cluster = null; try { cluster = clusters.getClusterById(entity.getClusterId()); } catch (AmbariException ambariException) { LOG.error("Unable to lookup cluster with ID {}", entity.getClusterId(), ambariException); return; } Set<String> hostNames = alertDefinitionHash.getAssociatedHosts(cluster, entity.getSourceType(), entity.getDefinitionName(), entity.getServiceName(), entity.getComponentName()); for (String hostName : hostNames) { AlertDefinition definition = definitionFactory.coerce(entity); AlertExecutionCommand command = new AlertExecutionCommand(cluster.getClusterName(), hostName, definition); actionQueue.enqueue(hostName, command); } } }