/**
* 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.upgrade;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
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.events.HostsAddedEvent;
import org.apache.ambari.server.events.HostsRemovedEvent;
import org.apache.ambari.server.events.ServiceComponentInstalledEvent;
import org.apache.ambari.server.events.ServiceComponentUninstalledEvent;
import org.apache.ambari.server.events.ServiceInstalledEvent;
import org.apache.ambari.server.events.publishers.AmbariEventPublisher;
import org.apache.ambari.server.logging.LockFactory;
import org.apache.ambari.server.orm.dao.HostDAO;
import org.apache.ambari.server.orm.dao.HostVersionDAO;
import org.apache.ambari.server.orm.entities.ClusterVersionEntity;
import org.apache.ambari.server.orm.entities.HostComponentDesiredStateEntity;
import org.apache.ambari.server.orm.entities.HostEntity;
import org.apache.ambari.server.orm.entities.HostVersionEntity;
import org.apache.ambari.server.orm.entities.RepositoryVersionEntity;
import org.apache.ambari.server.orm.entities.StackEntity;
import org.apache.ambari.server.state.Cluster;
import org.apache.ambari.server.state.Clusters;
import org.apache.ambari.server.state.ComponentInfo;
import org.apache.ambari.server.state.RepositoryVersionState;
import org.apache.ambari.server.state.ServiceComponent;
import org.apache.ambari.server.state.StackId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.inject.persist.Transactional;
/**
* The {@link org.apache.ambari.server.events.listeners.upgrade.HostVersionOutOfSyncListener} class
* handles {@link org.apache.ambari.server.events.ServiceInstalledEvent} and
* {@link org.apache.ambari.server.events.ServiceComponentInstalledEvent}
* to update {@link org.apache.ambari.server.state.RepositoryVersionState}
*
* @see org.apache.ambari.server.state.Cluster#recalculateClusterVersionState(RepositoryVersionEntity)
*/
@Singleton
@EagerSingleton
public class HostVersionOutOfSyncListener {
/**
* Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(HostVersionOutOfSyncListener.class);
@Inject
private Provider<HostVersionDAO> hostVersionDAO;
@Inject
private Provider<HostDAO> hostDAO;
@Inject
private Provider<Clusters> clusters;
@Inject
private Provider<AmbariMetaInfo> ami;
/**
* The publisher may be an asynchronous, multi-threaded one, so to avoid the (rare, but possible) case
* of both an Install and Uninstall event occurring at the same time, we use a Lock.
*/
private final Lock m_lock;
@Inject
public HostVersionOutOfSyncListener(AmbariEventPublisher ambariEventPublisher, LockFactory lockFactory) {
ambariEventPublisher.register(this);
m_lock = lockFactory.newLock("hostVersionOutOfSyncListenerLock");
}
@Subscribe
@Transactional
public void onServiceComponentEvent(ServiceComponentInstalledEvent event) {
if (LOG.isDebugEnabled()) {
LOG.debug(event.toString());
}
m_lock.lock();
try {
Cluster cluster = clusters.get().getClusterById(event.getClusterId());
List<HostVersionEntity> hostVersionEntities =
hostVersionDAO.get().findByClusterAndHost(cluster.getClusterName(), event.getHostName());
for (HostVersionEntity hostVersionEntity : hostVersionEntities) {
StackEntity hostStackEntity = hostVersionEntity.getRepositoryVersion().getStack();
StackId hostStackId = new StackId(hostStackEntity);
// If added components do not advertise version, it makes no sense to mark version OUT_OF_SYNC
// We perform check per-stack version, because component may be not versionAdvertised in current
// stack, but become versionAdvertised in some future (installed, but not yet upgraded to) stack
String serviceName = event.getServiceName();
String componentName = event.getComponentName();
ComponentInfo component = ami.get().getComponent(hostStackId.getStackName(),
hostStackId.getStackVersion(), serviceName, componentName);
if (!component.isVersionAdvertised()) {
RepositoryVersionState state = checkAllHostComponents(hostStackId, hostVersionEntity.getHostEntity());
if (null != state) {
hostVersionEntity.setState(state);
hostVersionDAO.get().merge(hostVersionEntity);
}
continue;
}
switch (hostVersionEntity.getState()) {
case INSTALLED:
case NOT_REQUIRED:
hostVersionEntity.setState(RepositoryVersionState.OUT_OF_SYNC);
hostVersionDAO.get().merge(hostVersionEntity);
cluster.recalculateClusterVersionState(hostVersionEntity.getRepositoryVersion());
break;
default:
break;
}
}
} catch (AmbariException e) {
LOG.error("Can not update hosts about out of sync", e);
} finally {
m_lock.unlock();
}
}
@Subscribe
@Transactional
public void onServiceComponentHostEvent(ServiceComponentUninstalledEvent event) {
m_lock.lock();
try {
Cluster cluster = clusters.get().getClusterById(event.getClusterId());
List<HostVersionEntity> hostVersionEntities =
hostVersionDAO.get().findByClusterAndHost(cluster.getClusterName(), event.getHostName());
for (HostVersionEntity hostVersionEntity : hostVersionEntities) {
HostEntity hostEntity = hostVersionEntity.getHostEntity();
RepositoryVersionEntity repoVersionEntity = hostVersionEntity.getRepositoryVersion();
StackId stackId = repoVersionEntity.getStackId();
if (null == stackId) {
LOG.info("Stack id could not be loaded for host version {}, repo {}", hostVersionEntity.getHostName(),
repoVersionEntity.getVersion());
continue;
}
RepositoryVersionState repoState = checkAllHostComponents(stackId, hostEntity);
if (null != repoState) {
hostVersionEntity.setState(repoState);
hostVersionDAO.get().merge(hostVersionEntity);
}
}
} catch (AmbariException e) {
LOG.error("Cannot update states after a component was uninstalled: {}", event, e);
} finally {
m_lock.unlock();
}
}
/**
* Checks if all the components advertise version. If additional states need to be
* computed, add on to the logic of this method; make sure the usages are checked for
* correctness.
*
* @param stackId the stack id
* @param host the host entity to find components
* @return {@code null} if there should be no state change. non-{@code null} to change it
*/
private RepositoryVersionState checkAllHostComponents(StackId stackId,
HostEntity host) throws AmbariException {
Collection<HostComponentDesiredStateEntity> hostComponents = host.getHostComponentDesiredStateEntities();
for (HostComponentDesiredStateEntity hostComponent : hostComponents) {
ComponentInfo ci = ami.get().getComponent(stackId.getStackName(), stackId.getStackVersion(),
hostComponent.getServiceName(), hostComponent.getComponentName());
if (ci.isVersionAdvertised()) {
return null;
}
}
return RepositoryVersionState.NOT_REQUIRED;
}
@Subscribe
@Transactional
public void onServiceEvent(ServiceInstalledEvent event) {
if (LOG.isDebugEnabled()) {
LOG.debug(event.toString());
}
try {
Cluster cluster = clusters.get().getClusterById(event.getClusterId());
Set<RepositoryVersionEntity> changedRepositoryVersions = new HashSet<>();
Map<String, ServiceComponent> serviceComponents = cluster.getService(event.getServiceName()).getServiceComponents();
// Determine hosts that become OUT_OF_SYNC when adding components for new service
Map<String, List<ServiceComponent>> affectedHosts =
new HashMap<>();
for (ServiceComponent component : serviceComponents.values()) {
for (String hostname : component.getServiceComponentHosts().keySet()) {
if (! affectedHosts.containsKey(hostname)) {
affectedHosts.put(hostname, new ArrayList<ServiceComponent>());
}
affectedHosts.get(hostname).add(component);
}
}
for (String hostName : affectedHosts.keySet()) {
List<HostVersionEntity> hostVersionEntities =
hostVersionDAO.get().findByClusterAndHost(cluster.getClusterName(), hostName);
for (HostVersionEntity hostVersionEntity : hostVersionEntities) {
RepositoryVersionEntity repositoryVersion = hostVersionEntity.getRepositoryVersion();
// If added components do not advertise version, it makes no sense to mark version OUT_OF_SYNC
// We perform check per-stack version, because component may be not versionAdvertised in current
// stack, but become versionAdvertised in some future (installed, but not yet upgraded to) stack
boolean hasChangedComponentsWithVersions = false;
String serviceName = event.getServiceName();
for (ServiceComponent comp : affectedHosts.get(hostName)) {
String componentName = comp.getName();
ComponentInfo component = ami.get().getComponent(repositoryVersion.getStackName(),
repositoryVersion.getStackVersion(), serviceName, componentName);
if (component.isVersionAdvertised()) {
hasChangedComponentsWithVersions = true;
}
}
if (! hasChangedComponentsWithVersions) {
continue;
}
if (hostVersionEntity.getState().equals(RepositoryVersionState.INSTALLED)) {
hostVersionEntity.setState(RepositoryVersionState.OUT_OF_SYNC);
hostVersionDAO.get().merge(hostVersionEntity);
changedRepositoryVersions.add(repositoryVersion);
}
}
}
for (RepositoryVersionEntity repositoryVersion : changedRepositoryVersions) {
cluster.recalculateClusterVersionState(repositoryVersion);
}
} catch (AmbariException e) {
LOG.error("Can not update hosts about out of sync", e);
}
}
@Subscribe
@Transactional
public void onHostEvent(HostsAddedEvent event) {
if (LOG.isDebugEnabled()) {
LOG.debug(event.toString());
}
try {
Cluster cluster = clusters.get().getClusterById(event.getClusterId());
Collection<ClusterVersionEntity> allClusterVersions = cluster.getAllClusterVersions();
for (ClusterVersionEntity clusterVersion : allClusterVersions) {
if (clusterVersion.getState() != RepositoryVersionState.CURRENT) { // Current version is taken care of automatically
RepositoryVersionEntity repositoryVersion = clusterVersion.getRepositoryVersion();
for (String hostName : event.getHostNames()) {
HostEntity hostEntity = hostDAO.get().findByName(hostName);
HostVersionEntity missingHostVersion = new HostVersionEntity(hostEntity,
repositoryVersion, RepositoryVersionState.OUT_OF_SYNC);
LOG.info("Creating host version for {}, state={}, repo={} (repo_id={})",
missingHostVersion.getHostName(), missingHostVersion.getState(),
missingHostVersion.getRepositoryVersion().getVersion(), missingHostVersion.getRepositoryVersion().getId());
hostVersionDAO.get().create(missingHostVersion);
}
cluster.recalculateClusterVersionState(repositoryVersion);
}
}
} catch (AmbariException e) {
LOG.error("Can not update hosts about out of sync", e);
}
}
/**
* Recalculates the cluster repo version state when a host is removed. If
* hosts are removed during an upgrade, the remaining hosts will all be in the
* {@link RepositoryVersionState#INSTALLED} state, but the cluster will never
* transition into this state. This is because when the host is removed, a
* recalculation must happen.
*
* @param event
* the removal event.
*/
@Subscribe
@Transactional
public void onHostEvent(HostsRemovedEvent event) {
if (LOG.isDebugEnabled()) {
LOG.debug(event.toString());
}
try {
Set<Cluster> clusters = event.getClusters();
for (Cluster cluster : clusters) {
Collection<ClusterVersionEntity> allClusterVersions = cluster.getAllClusterVersions();
for (ClusterVersionEntity clusterVersion : allClusterVersions) {
RepositoryVersionState repositoryVersionState = clusterVersion.getState();
// the CURRENT/INSTALLED states should not be affected by a host
// removal - if it's already current then removing a host will never
// make it not CURRENT or not INSTALLED
switch (repositoryVersionState) {
case CURRENT:
case INSTALLED:
continue;
default:
break;
}
RepositoryVersionEntity repositoryVersion = clusterVersion.getRepositoryVersion();
cluster.recalculateClusterVersionState(repositoryVersion);
}
}
} catch (AmbariException ambariException) {
LOG.error(
"Unable to recalculate the cluster repository version state when a host was removed",
ambariException);
}
}
}