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.agent.stomp;
  
  import static org.apache.ambari.server.events.AlertDefinitionEventType.CREATE;
  import static org.apache.ambari.server.events.AlertDefinitionEventType.DELETE;
  
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.TreeMap;
  
  import jakarta.inject.Inject;
  import jakarta.inject.Provider;
  import jakarta.inject.Singleton;
  
  import org.apache.ambari.server.AmbariException;
  import org.apache.ambari.server.AmbariRuntimeException;
  import org.apache.ambari.server.agent.stomp.dto.AlertCluster;
  import org.apache.ambari.server.events.AlertDefinitionEventType;
  import org.apache.ambari.server.events.AlertDefinitionsAgentUpdateEvent;
  import org.apache.ambari.server.events.HostsAddedEvent;
  import org.apache.ambari.server.events.HostsRemovedEvent;
  import org.apache.ambari.server.events.listeners.alerts.AlertDefinitionsUIUpdateListener;
  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.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.AlertHelper;
  import org.apache.commons.collections4.CollectionUtils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import com.google.common.collect.Sets;
  import com.google.common.eventbus.Subscribe;
  
  @Singleton
  public class AlertDefinitionsHolder extends AgentHostDataHolder<AlertDefinitionsAgentUpdateEvent> {
  
    private static final Logger LOG = LoggerFactory.getLogger(AlertDefinitionsHolder.class);
  
    @Inject
    private Provider<AlertDefinitionHash> helper;
  
    @Inject
    private Provider<Clusters> clusters;
  
    @Inject
    private AlertDefinitionDAO alertDefinitionDAO;
  
    @Inject
    private AlertHelper alertHelper;
  
    @Inject
    private AlertDefinitionFactory alertDefinitionFactory;
  
    @Inject
    public AlertDefinitionsHolder(AmbariEventPublisher eventPublisher) {
      eventPublisher.register(this);
    }
  
    @Override
    protected AlertDefinitionsAgentUpdateEvent getCurrentData(Long hostId) throws AmbariException {
      Map<Long, AlertCluster> result = new TreeMap<>();
      Map<Long, Map<Long, AlertDefinition>> alertDefinitions = helper.get().getAlertDefinitions(hostId);
      String hostName = clusters.get().getHostById(hostId).getHostName();
      long count = 0;
      for (Map.Entry<Long, Map<Long, AlertDefinition>> e : alertDefinitions.entrySet()) {
        Long clusterId = e.getKey();
        Map<Long, AlertDefinition> definitionMap = e.getValue();
  
        AlertDefinitionEntity ambariStaleAlert = alertDefinitionDAO.findByName(clusterId, AlertDefinitionsUIUpdateListener.AMBARI_STALE_ALERT_NAME);
        Integer staleIntervalMultiplier = alertHelper.getWaitFactorMultiplier(alertDefinitionFactory.coerce(ambariStaleAlert));
  
        result.put(clusterId, new AlertCluster(definitionMap, hostName, staleIntervalMultiplier));
        count += definitionMap.size();
      }
      LOG.info("Loaded {} alert definitions for {} clusters for host {}", count, result.size(), hostName);
      return new AlertDefinitionsAgentUpdateEvent(CREATE, result, hostName, hostId);
    }
  
    public AlertDefinitionsAgentUpdateEvent getDeleteCluster(Long clusterId, Long hostId) throws AmbariException {
      Map<Long, AlertCluster> result = new TreeMap<>();
      result.put(clusterId, AlertCluster.emptyAlertCluster());
      return new AlertDefinitionsAgentUpdateEvent(DELETE, result, null, hostId);
    }
  
    @Override
    protected AlertDefinitionsAgentUpdateEvent getEmptyData() {
      return AlertDefinitionsAgentUpdateEvent.emptyEvent();
    }
  
    @Override
    protected AlertDefinitionsAgentUpdateEvent handleUpdate(AlertDefinitionsAgentUpdateEvent current, AlertDefinitionsAgentUpdateEvent update) throws AmbariException {
      Map<Long, AlertCluster> updateClusters = update.getClusters();
      if (updateClusters.isEmpty()) {
        return null;
      }
      AlertDefinitionsAgentUpdateEvent result = null;
  
      Long hostId = update.getHostId();
      boolean changed = false;
      Map<Long, AlertCluster> existingClusters = current.getClusters();
      Map<Long, AlertCluster> mergedClusters = new HashMap<>();
  
      switch (update.getEventType()) {
        case UPDATE:
        case DELETE:
          if (!existingClusters.keySet().containsAll(updateClusters.keySet())) {
            LOG.info("Unknown clusters in update, perhaps cluster was removed previously");
          }
          for (Map.Entry<Long, AlertCluster> e : existingClusters.entrySet()) {
            Long clusterId = e.getKey();
            if (!updateClusters.containsKey(clusterId)) {
              mergedClusters.put(clusterId, e.getValue());
            }
          }
          for (Map.Entry<Long, AlertCluster> e : updateClusters.entrySet()) {
            Long clusterId = e.getKey();
            if (existingClusters.containsKey(clusterId)) {
              if (update.getEventType().equals(DELETE) && CollectionUtils.isEmpty(e.getValue().getAlertDefinitions())) {
                changed = true;
              } else {
                AlertCluster mergedCluster = existingClusters.get(e.getKey()).handleUpdate(update.getEventType(), e.getValue());
                if (mergedCluster != null) {
                  mergedClusters.put(clusterId, mergedCluster);
                  changed = true;
                }
              }
            } else {
              mergedClusters.put(clusterId, e.getValue());
              changed = true;
            }
          }
          LOG.debug("Handled {} of alerts for {} cluster(s) on host with id {}, changed = {}", update.getEventType(), updateClusters.size(), hostId, changed);
          break;
        case CREATE:
          if (!Sets.intersection(existingClusters.keySet(), updateClusters.keySet()).isEmpty()) {
            throw new AmbariException("Existing clusters in create");
          }
          mergedClusters.putAll(existingClusters);
          mergedClusters.putAll(updateClusters);
          LOG.debug("Handled {} of alerts for {} cluster(s)", update.getEventType(), updateClusters.size());
          changed = true;
          break;
        default:
          LOG.warn("Unhandled event type {}", update.getEventType());
          break;
      }
      if (changed) {
        result = new AlertDefinitionsAgentUpdateEvent(CREATE, mergedClusters, current.getHostName(), hostId);
      }
      return result;
    }
  
    @Subscribe
    public void onHostToClusterAssign(HostsAddedEvent hostsAddedEvent) throws AmbariException {
      Long clusterId = hostsAddedEvent.getClusterId();
      for (String hostName : hostsAddedEvent.getHostNames()) {
        Long hostId = clusters.get().getHost(hostName).getHostId();
        Map<Long, AlertCluster> existingClusters = getData(hostId).getClusters();
        if (!existingClusters.containsKey(clusterId)) {
          existingClusters.put(clusterId, new AlertCluster(new HashMap<>(), hostName));
        }
      }
    }
  
    @Subscribe
    public void onHostsRemoved(HostsRemovedEvent event) {
      for (Long hostId : event.getHostIds()) {
        onHostRemoved(hostId);
      }
    }
  
    private void safelyUpdateData(AlertDefinitionsAgentUpdateEvent event) throws AmbariException {
      try {
        updateData(event);
      } catch (AmbariRuntimeException e) {
        LOG.warn(String.format("Failed to %s alert definitions for host %s", event.getEventType(), event.getHostName()), e);
      }
    }
  
    private void safelyResetData(Long hostId) {
      try {
        resetData(hostId);
      } catch (AmbariException e) {
        LOG.warn(String.format("Failed to reset alert definitions for host with id %s", hostId), e);
      }
    }
  
    public void provideAlertDefinitionAgentUpdateEvent(AlertDefinitionEventType eventType, Long clusterId,
                                                       Map<Long, AlertDefinition> alertDefinitions, String hostName) throws AmbariException {
      Long hostId = clusters.get().getHost(hostName).getHostId();
      Map<Long, AlertCluster> update = Collections.singletonMap(clusterId, new AlertCluster(alertDefinitions, hostName));
      AlertDefinitionsAgentUpdateEvent event = new AlertDefinitionsAgentUpdateEvent(eventType, update, hostName, hostId);
      safelyUpdateData(event);
    }
  
    public void provideStaleAlertDefinitionUpdateEvent(AlertDefinitionEventType eventType, Long clusterId,
                                                       Integer staleIntervalMultiplier, String hostName) throws AmbariException {
      Long hostId = clusters.get().getHost(hostName).getHostId();
      Map<Long, AlertCluster> update = Collections.singletonMap(clusterId, new AlertCluster(Collections.emptyMap(), hostName, staleIntervalMultiplier));
      AlertDefinitionsAgentUpdateEvent event = new AlertDefinitionsAgentUpdateEvent(eventType, update, hostName, hostId);
      safelyUpdateData(event);
    }
  }
  