/**
* 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.events.listeners.alerts;
import java.text.MessageFormat;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.EagerSingleton;
import org.apache.ambari.server.api.services.AmbariMetaInfo;
import org.apache.ambari.server.controller.AmbariServer;
import org.apache.ambari.server.controller.ControllerModule;
import org.apache.ambari.server.events.ServiceInstalledEvent;
import org.apache.ambari.server.events.ServiceRemovedEvent;
import org.apache.ambari.server.events.publishers.AmbariEventPublisher;
import org.apache.ambari.server.orm.GuiceJpaInitializer;
import org.apache.ambari.server.orm.dao.AlertDefinitionDAO;
import org.apache.ambari.server.orm.dao.AlertDispatchDAO;
import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
import org.apache.ambari.server.orm.entities.AlertGroupEntity;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.Subscribe;
import com.google.common.util.concurrent.Striped;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
/**
* The {@link AlertServiceStateListener} class handles
* {@link ServiceInstalledEvent} and {@link ServiceRemovedEvent} and ensures
* that {@link AlertDefinitionEntity} and {@link AlertGroupEntity} instances are
* correctly populated or cleaned up.
*/
@Singleton
@EagerSingleton
public class AlertServiceStateListener {
/**
* Logger.
*/
private static Logger LOG = LoggerFactory.getLogger(AlertServiceStateListener.class);
/**
* Services metainfo; injected lazily as a {@link Provider} since JPA is not
* fully initialized when this singleton is eagerly instantiated. See
* {@link AmbariServer#main(String[])} and the ordering of
* {@link ControllerModule} and {@link GuiceJpaInitializer}.
*/
@Inject
private Provider<AmbariMetaInfo> m_metaInfoProvider;
/**
* Used when a service is installed to read alert definitions from the stack
* and coerce them into {@link AlertDefinitionEntity}.
*/
@Inject
private AlertDefinitionFactory m_alertDefinitionFactory;
/**
* Used when a service is installed to insert a default
* {@link AlertGroupEntity} into the database.
*/
@Inject
private AlertDispatchDAO m_alertDispatchDao;
/**
* Used when a service is installed to insert {@link AlertDefinitionEntity}
* into the database or when a service is removed to delete the definition.
*/
@Inject
private AlertDefinitionDAO m_definitionDao;
/**
* Used to retrieve a cluster using clusterId from event.
*/
@Inject
private Provider<Clusters> m_clusters;
/**
* Used for ensuring that the concurrent nature of the event handler methods
* don't collide when attempting to perform operations on the same service.
*/
private Striped<Lock> m_locksByService = Striped.lazyWeakLock(20);
/**
* Constructor.
*
* @param publisher
*/
@Inject
public AlertServiceStateListener(AmbariEventPublisher publisher) {
publisher.register(this);
}
/**
* Handles service installed events by populating the database with all known
* alert definitions for the newly installed service and creates the service's
* default alert group.
*
* @param event
* the published event being handled (not {@code null}).
*/
@Subscribe
@AllowConcurrentEvents
public void onAmbariEvent(ServiceInstalledEvent event) {
LOG.debug("Received event {}", event);
long clusterId = event.getClusterId();
String stackName = event.getStackName();
String stackVersion = event.getStackVersion();
String serviceName = event.getServiceName();
Lock lock = m_locksByService.get(serviceName);
lock.lock();
try {
// create the default alert group for the new service if absent; this MUST
// be done before adding definitions so that they are properly added to the
// default group
if (null == m_alertDispatchDao.findDefaultServiceGroup(clusterId, serviceName)) {
try {
m_alertDispatchDao.createDefaultGroup(clusterId, serviceName);
} catch (AmbariException ambariException) {
LOG.error("Unable to create a default alert group for {}",
event.getServiceName(), ambariException);
}
}
// populate alert definitions for the new service from the database, but
// don't worry about sending down commands to the agents; the host
// components are not yet bound to the hosts so we'd have no way of knowing
// which hosts are invalidated; do that in another impl
try {
Set<AlertDefinition> alertDefinitions = m_metaInfoProvider.get().getAlertDefinitions(
stackName, stackVersion, serviceName);
for (AlertDefinition definition : alertDefinitions) {
AlertDefinitionEntity entity = m_alertDefinitionFactory.coerce(
clusterId,
definition);
m_definitionDao.create(entity);
}
} catch (AmbariException ae) {
String message = MessageFormat.format(
"Unable to populate alert definitions from the database during installation of {0}",
serviceName);
LOG.error(message, ae);
}
}
finally {
lock.unlock();
}
}
/**
* Removes all alert data associated with the removed serviced.
*
* @param event
* the published event being handled (not {@code null}).
*/
@Subscribe
@AllowConcurrentEvents
public void onAmbariEvent(ServiceRemovedEvent event) {
LOG.debug("Received event {}", event);
try {
m_clusters.get().getClusterById(event.getClusterId());
} catch (AmbariException e) {
LOG.warn("Unable to retrieve cluster with id {}", event.getClusterId());
return;
}
String serviceName = event.getServiceName();
Lock lock = m_locksByService.get(serviceName);
lock.lock();
try {
List<AlertDefinitionEntity> definitions = m_definitionDao.findByService(event.getClusterId(),
event.getServiceName());
for (AlertDefinitionEntity definition : definitions) {
try {
m_definitionDao.remove(definition);
} catch (Exception exception) {
LOG.error("Unable to remove alert definition {}", definition.getDefinitionName(),
exception);
}
}
// remove the default group for the service
AlertGroupEntity group = m_alertDispatchDao.findGroupByName(event.getClusterId(),
event.getServiceName());
if (null != group && group.isDefault()) {
try {
m_alertDispatchDao.remove(group);
} catch (Exception exception) {
LOG.error("Unable to remove default alert group {}", group.getGroupName(), exception);
}
}
} finally {
lock.unlock();
}
}
}