content: /*
   * 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.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  import java.util.concurrent.locks.Lock;
  import java.util.concurrent.locks.ReentrantLock;
  
  import org.apache.ambari.server.EagerSingleton;
  import org.apache.ambari.server.events.AlertHashInvalidationEvent;
  import org.apache.ambari.server.events.HostsAddedEvent;
  import org.apache.ambari.server.events.HostsRemovedEvent;
  import org.apache.ambari.server.events.publishers.AmbariEventPublisher;
  import org.apache.ambari.server.metadata.AmbariServiceAlertDefinitions;
  import org.apache.ambari.server.orm.dao.AlertDefinitionDAO;
  import org.apache.ambari.server.orm.dao.AlertsDAO;
  import org.apache.ambari.server.orm.entities.AlertCurrentEntity;
  import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
  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.inject.Inject;
  import com.google.inject.Singleton;
  
  /**
   * The {@link AlertHostListener} class handles {@link HostsAddedEvent} and
   * {@link HostsRemovedEvent} and ensures that {@link AlertCurrentEntity}
   * instances are properly cleaned up
   */
  @Singleton
  @EagerSingleton
  public class AlertHostListener {
    /**
     * Logger.
     */
    private final static Logger LOG = LoggerFactory.getLogger(AlertHostListener.class);
  
    /**
     * Used for removing current alerts when a service is removed.
     */
    @Inject
    private AlertsDAO m_alertsDao;
  
    /**
     * Used for checking to see if definitions already exist for a cluster.
     */
    @Inject
    private AlertDefinitionDAO m_alertDefinitionDao;
  
    /**
     * Used to publish events when an alert definition has a lifecycle event.
     */
    @Inject
    private AmbariEventPublisher m_eventPublisher;
  
    /**
     * All of the {@link AlertDefinition}s that are scoped for the agents.
     */
    @Inject
    private AmbariServiceAlertDefinitions m_ambariServiceAlertDefinitions;
  
    /**
     * Used when a host is added to a cluster to coerce an {@link AlertDefinition}
     * into an {@link AlertDefinitionEntity}.
     */
    @Inject
    private AlertDefinitionFactory m_alertDefinitionFactory;
  
    /**
     * Used to prevent multiple threads from trying to create host alerts
     * simultaneously.
     */
    private Lock m_hostAlertLock = new ReentrantLock();
  
    /**
     * Constructor.
     *
     * @param publisher
     */
    @Inject
    public AlertHostListener(AmbariEventPublisher publisher) {
      publisher.register(this);
    }
  
    /**
     * Handles the {@link HostsAddedEvent} by performing the following actions:
     * <ul>
     * <li>Ensures that all host-level alerts are loaded for the cluster. This is
     * especially useful when creating a cluster and no alerts were loaded on
     * Ambari startup</li>
     * <li>Broadcasts the {@link AlertHashInvalidationEvent} in order to push host
     * alert definitions</li>
     * </ul>
     */
    @Subscribe
    @AllowConcurrentEvents
    public void onAmbariEvent(HostsAddedEvent event) {
      LOG.debug("Received event {}", event);
  
      long clusterId = event.getClusterId();
  
      // load the host-only alert definitions
      List<AlertDefinition> agentDefinitions = m_ambariServiceAlertDefinitions.getAgentDefinitions();
      List<AlertDefinition> serverDefinitions = m_ambariServiceAlertDefinitions.getServerDefinitions();
  
      List<AlertDefinition> ambariServiceDefinitions = new ArrayList<>();
      ambariServiceDefinitions.addAll(agentDefinitions);
      ambariServiceDefinitions.addAll(serverDefinitions);
  
      // lock to prevent multiple threads from trying to create alert
      // definitions at the same time
      m_hostAlertLock.lock();
  
      try {
        for (AlertDefinition agentDefinition : ambariServiceDefinitions) {
          AlertDefinitionEntity definition = m_alertDefinitionDao.findByName(
              clusterId, agentDefinition.getName());
  
          // this host definition does not exist, add it
          if (null == definition) {
            definition = m_alertDefinitionFactory.coerce(clusterId,
                agentDefinition);
  
            try {
              m_alertDefinitionDao.create(definition);
            } catch (Exception e) {
              LOG.error(
                  "Unable to create an alert definition named {} in cluster {}",
                  definition.getDefinitionName(), definition.getClusterId(), e);
            }
          }
        }
      } finally {
        m_hostAlertLock.unlock();
      }
  
      for (String hostName : event.getHostNames()) {
        AlertHashInvalidationEvent invalidationEvent = new AlertHashInvalidationEvent(
          event.getClusterId(), Collections.singletonList(hostName));
  
        m_eventPublisher.publish(invalidationEvent);
      }
    }
  
    /**
     * Handles the {@link HostsRemovedEvent} by performing the following actions:
     * <ul>
     * <li>Removes all {@link AlertCurrentEntity} for the removed hosts</li>
     * </ul>
     */
    @Subscribe
    @AllowConcurrentEvents
    public void onAmbariEvent(HostsRemovedEvent event) {
      LOG.debug("Received event {}", event);
  
      // remove any current alerts for the removed hosts
      for (String hostName : event.getHostNames()) {
        m_alertsDao.removeCurrentByHost(hostName);
      }
    }
  }
  