/**
* 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.state.services;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.apache.ambari.server.AmbariService;
import org.apache.ambari.server.alerts.AlertRunnable;
import org.apache.ambari.server.controller.RootServiceResponseFactory.Components;
import org.apache.ambari.server.controller.RootServiceResponseFactory.Services;
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.ServerSource;
import org.apache.ambari.server.state.alert.SourceType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.util.concurrent.AbstractScheduledService;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;
/**
* The {@link AmbariServerAlertService} is used to manage the dynamically loaded
* {@link AlertRunnable}s which perform server-side alert checks. The
* {@link AlertRunnable}s are scheduled using a {@link ScheduledExecutorService}
* with a fixed thread pool size.
*/
@AmbariService
public class AmbariServerAlertService extends AbstractScheduledService {
/**
* Logger.
*/
private final static Logger LOG = LoggerFactory.getLogger(AmbariServerAlertService.class);
/**
* Used to inject the constructed {@link Runnable}s.
*/
@Inject
private Injector m_injector;
/**
* Used for looking up alert definitions.
*/
@Inject
private AlertDefinitionDAO m_dao;
/**
* Used to get alert definitions to use when generating alert instances.
*/
@Inject
private Provider<Clusters> m_clustersProvider;
/**
* Used to coerce {@link AlertDefinitionEntity} into {@link AlertDefinition}.
*/
@Inject
private AlertDefinitionFactory m_alertDefinitionFactory;
/**
* The executor to use to run all {@link Runnable} alert classes.
*/
private final ScheduledExecutorService m_scheduledExecutorService = Executors.newScheduledThreadPool(3);
/**
* A map of all of the definition names to {@link ScheduledFuture}s.
*/
private final Map<String, ScheduledAlert> m_futureMap = new ConcurrentHashMap<>();
/**
* Constructor.
*
*/
public AmbariServerAlertService() {
}
/**
* {@inheritDoc}
*/
@Override
protected Scheduler scheduler() {
return Scheduler.newFixedDelaySchedule(1, 1, TimeUnit.MINUTES);
}
/**
* {@inheritDoc}
* <p/>
* Loads all of the {@link Components#AMBARI_SERVER} definitions and schedules
* the ones that are enabled.
*/
@Override
protected void startUp() throws Exception {
Map<String, Cluster> clusterMap = m_clustersProvider.get().getClusters();
for (Cluster cluster : clusterMap.values()) {
List<AlertDefinitionEntity> entities = m_dao.findByServiceComponent(
cluster.getClusterId(), Services.AMBARI.name(),
Components.AMBARI_SERVER.name());
for (AlertDefinitionEntity entity : entities) {
// don't schedule disabled alert definitions
if (!entity.getEnabled()) {
continue;
}
SourceType sourceType = entity.getSourceType();
if (sourceType != SourceType.SERVER) {
continue;
}
// schedule the Runnable for the definition
scheduleRunnable(entity);
}
}
}
/**
* {@inheritDoc}
* <p/>
* Compares all known {@link Components#AMBARI_SERVER} alerts with those that
* are scheduled. If any are not scheduled or have their intervals changed,
* then reschedule those.
*/
@Override
protected void runOneIteration() throws Exception {
Map<String, Cluster> clusterMap = m_clustersProvider.get().getClusters();
for (Cluster cluster : clusterMap.values()) {
// get all of the cluster alerts for AMBARI/AMBARI_SERVER
List<AlertDefinitionEntity> entities = m_dao.findByServiceComponent(
cluster.getClusterId(), Services.AMBARI.name(),
Components.AMBARI_SERVER.name());
// for each alert, check to see if it's scheduled correctly
for (AlertDefinitionEntity entity : entities) {
String definitionName = entity.getDefinitionName();
ScheduledAlert scheduledAlert = m_futureMap.get(definitionName);
// disabled or new alerts may not have anything mapped yet
ScheduledFuture<?> scheduledFuture = null;
if (null != scheduledAlert) {
scheduledFuture = scheduledAlert.getScheduledFuture();
}
// if the definition is not enabled, ensure it's not scheduled and
// then continue to the next one
if (!entity.getEnabled()) {
if (null != scheduledFuture) {
unschedule(definitionName, scheduledFuture);
}
continue;
}
// if the definition hasn't been scheduled, then schedule it
if (null == scheduledAlert || null == scheduledFuture) {
scheduleRunnable(entity);
continue;
}
// compare the delay of the future to the definition; if they don't
// match then reschedule this definition
int scheduledInterval = scheduledAlert.getInterval();
if (scheduledInterval != entity.getScheduleInterval()) {
// unschedule
unschedule(definitionName, scheduledFuture);
// reschedule
scheduleRunnable(entity);
}
}
}
}
/**
* Invokes {@link ScheduledFuture#cancel(boolean)} and removes the mapping
* from {@link #m_futureMap}. Does nothing if the future is not scheduled.
*
* @param scheduledFuture
*/
private void unschedule(String definitionName, ScheduledFuture<?> scheduledFuture) {
m_futureMap.remove(definitionName);
if (null != scheduledFuture) {
scheduledFuture.cancel(true);
LOG.info("Unscheduled server alert {}", definitionName);
}
}
/**
* Schedules the {@link Runnable} referenced by the
* {@link AlertDefinitionEntity} to run at a fixed interval.
*
* @param entity
* the entity to schedule the runnable for (not {@code null}).
* @throws ClassNotFoundException
* @throws IllegalAccessException
* @throws InstantiationException
*/
private void scheduleRunnable(AlertDefinitionEntity entity)
throws ClassNotFoundException,
IllegalAccessException, InstantiationException {
// if the definition is disabled, do nothing
if (!entity.getEnabled()) {
return;
}
AlertDefinition definition = m_alertDefinitionFactory.coerce(entity);
ServerSource serverSource = (ServerSource) definition.getSource();
String sourceClass = serverSource.getSourceClass();
int interval = definition.getInterval();
try {
Class<?> clazz = Class.forName(sourceClass);
if (!AlertRunnable.class.isAssignableFrom(clazz)) {
LOG.warn(
"Unable to schedule a server side alert for {} because it is not an {}", sourceClass,
AlertRunnable.class);
return;
}
// instantiate and inject
Constructor<? extends AlertRunnable> constructor = clazz.asSubclass(
AlertRunnable.class).getConstructor(String.class);
AlertRunnable alertRunnable = constructor.newInstance(entity.getDefinitionName());
m_injector.injectMembers(alertRunnable);
// schedule the runnable alert
ScheduledFuture<?> scheduledFuture = m_scheduledExecutorService.scheduleWithFixedDelay(
alertRunnable, interval, interval, TimeUnit.MINUTES);
String definitionName = entity.getDefinitionName();
ScheduledAlert scheduledAlert = new ScheduledAlert(scheduledFuture, interval);
m_futureMap.put(definitionName, scheduledAlert);
LOG.info("Scheduled server alert {} to run every {} minutes",
definitionName, interval);
} catch (ClassNotFoundException cnfe) {
LOG.error(
"Unable to schedule a server side alert for {} because it could not be found in the classpath",
sourceClass);
} catch (NoSuchMethodException nsme) {
LOG.error(
"Unable to schedule a server side alert for {} because it does not have a constructor which takes the proper arguments.",
sourceClass);
} catch (InvocationTargetException ite) {
LOG.error(
"Unable to schedule a server side alert for {} because an exception occurred while constructing the instance.",
sourceClass, ite);
}
}
/**
* The {@link ScheduledAlert} class is used as a way to encapsulate a
* {@link ScheduledFuture} with the interval it was scheduled with.
*/
private static final class ScheduledAlert {
private final ScheduledFuture<?> m_scheduledFuture;
private final int m_interval;
/**
* Constructor.
*
* @param scheduledFuture
* @param interval
*/
private ScheduledAlert(ScheduledFuture<?> scheduledFuture, int interval) {
m_scheduledFuture = scheduledFuture;
m_interval = interval;
}
/**
* @return the scheduledFuture
*/
private ScheduledFuture<?> getScheduledFuture() {
return m_scheduledFuture;
}
/**
* @return the interval
*/
private int getInterval() {
return m_interval;
}
}
}