/** * 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.configgroup; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.ReadWriteLock; import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.DuplicateResourceException; import org.apache.ambari.server.controller.ConfigGroupResponse; import org.apache.ambari.server.controller.internal.ConfigurationResourceProvider; import org.apache.ambari.server.logging.LockFactory; import org.apache.ambari.server.orm.dao.ClusterDAO; import org.apache.ambari.server.orm.dao.ConfigGroupConfigMappingDAO; import org.apache.ambari.server.orm.dao.ConfigGroupDAO; import org.apache.ambari.server.orm.dao.ConfigGroupHostMappingDAO; import org.apache.ambari.server.orm.dao.HostDAO; import org.apache.ambari.server.orm.entities.ClusterConfigEntity; import org.apache.ambari.server.orm.entities.ClusterEntity; import org.apache.ambari.server.orm.entities.ConfigGroupConfigMappingEntity; import org.apache.ambari.server.orm.entities.ConfigGroupEntity; import org.apache.ambari.server.orm.entities.ConfigGroupHostMappingEntity; import org.apache.ambari.server.orm.entities.ConfigGroupHostMappingEntityPK; import org.apache.ambari.server.orm.entities.HostEntity; import org.apache.ambari.server.state.Cluster; import org.apache.ambari.server.state.Clusters; import org.apache.ambari.server.state.Config; import org.apache.ambari.server.state.ConfigFactory; import org.apache.ambari.server.state.Host; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.AssistedInject; import com.google.inject.persist.Transactional; public class ConfigGroupImpl implements ConfigGroup { private static final Logger LOG = LoggerFactory.getLogger(ConfigGroupImpl.class); private Cluster cluster; private ConcurrentMap<Long, Host> m_hosts; private ConcurrentMap<String, Config> m_configurations; private String configGroupName; private long configGroupId; /** * This lock is required to prevent inconsistencies in internal state between * {@link #m_hosts} and the entities stored by the {@link ConfigGroupEntity}. */ private final ReadWriteLock hostLock; /** * A label for {@link #hostLock} to use with the {@link LockFactory}. */ private static final String hostLockLabel = "configurationGroupHostLock"; private final ConfigGroupDAO configGroupDAO; private final ConfigGroupConfigMappingDAO configGroupConfigMappingDAO; private final ConfigGroupHostMappingDAO configGroupHostMappingDAO; private final HostDAO hostDAO; private final ClusterDAO clusterDAO; private final ConfigFactory configFactory; @AssistedInject public ConfigGroupImpl(@Assisted("cluster") Cluster cluster, @Assisted("name") String name, @Assisted("tag") String tag, @Assisted("description") String description, @Assisted("configs") Map<String, Config> configurations, @Assisted("hosts") Map<Long, Host> hosts, Clusters clusters, ConfigFactory configFactory, ClusterDAO clusterDAO, HostDAO hostDAO, ConfigGroupDAO configGroupDAO, ConfigGroupConfigMappingDAO configGroupConfigMappingDAO, ConfigGroupHostMappingDAO configGroupHostMappingDAO, LockFactory lockFactory) { this.configFactory = configFactory; this.clusterDAO = clusterDAO; this.hostDAO = hostDAO; this.configGroupDAO = configGroupDAO; this.configGroupConfigMappingDAO = configGroupConfigMappingDAO; this.configGroupHostMappingDAO = configGroupHostMappingDAO; hostLock = lockFactory.newReadWriteLock(hostLockLabel); this.cluster = cluster; configGroupName = name; ConfigGroupEntity configGroupEntity = new ConfigGroupEntity(); configGroupEntity.setClusterId(cluster.getClusterId()); configGroupEntity.setGroupName(name); configGroupEntity.setTag(tag); configGroupEntity.setDescription(description); m_hosts = hosts == null ? new ConcurrentHashMap<Long, Host>() : new ConcurrentHashMap<>(hosts); m_configurations = configurations == null ? new ConcurrentHashMap<String, Config>() : new ConcurrentHashMap<>(configurations); // save the entity and grab the ID persist(configGroupEntity); configGroupId = configGroupEntity.getGroupId(); } @AssistedInject public ConfigGroupImpl(@Assisted Cluster cluster, @Assisted ConfigGroupEntity configGroupEntity, Clusters clusters, ConfigFactory configFactory, ClusterDAO clusterDAO, HostDAO hostDAO, ConfigGroupDAO configGroupDAO, ConfigGroupConfigMappingDAO configGroupConfigMappingDAO, ConfigGroupHostMappingDAO configGroupHostMappingDAO, LockFactory lockFactory) { this.configFactory = configFactory; this.clusterDAO = clusterDAO; this.hostDAO = hostDAO; this.configGroupDAO = configGroupDAO; this.configGroupConfigMappingDAO = configGroupConfigMappingDAO; this.configGroupHostMappingDAO = configGroupHostMappingDAO; hostLock = lockFactory.newReadWriteLock(hostLockLabel); this.cluster = cluster; configGroupId = configGroupEntity.getGroupId(); configGroupName = configGroupEntity.getGroupName(); m_configurations = new ConcurrentHashMap<>(); m_hosts = new ConcurrentHashMap<>(); // Populate configs for (ConfigGroupConfigMappingEntity configMappingEntity : configGroupEntity.getConfigGroupConfigMappingEntities()) { Config config = cluster.getConfig(configMappingEntity.getConfigType(), configMappingEntity.getVersionTag()); if (config != null) { m_configurations.put(config.getType(), config); } else { LOG.warn("Unable to find config mapping {}/{} for config group in cluster {}", configMappingEntity.getConfigType(), configMappingEntity.getVersionTag(), cluster.getClusterName()); } } // Populate Hosts for (ConfigGroupHostMappingEntity hostMappingEntity : configGroupEntity.getConfigGroupHostMappingEntities()) { try { Host host = clusters.getHost(hostMappingEntity.getHostname()); HostEntity hostEntity = hostMappingEntity.getHostEntity(); if (host != null && hostEntity != null) { m_hosts.put(hostEntity.getHostId(), host); } } catch (Exception e) { LOG.warn("Host {} seems to be deleted but Config group {} mapping " + "still exists !", hostMappingEntity.getHostname(), configGroupName); LOG.debug("Host seems to be deleted but Config group mapping still exists !", e); } } } @Override public Long getId() { return configGroupId; } @Override public String getName() { return configGroupName; } @Override public void setName(String name) { ConfigGroupEntity configGroupEntity = getConfigGroupEntity(); configGroupEntity.setGroupName(name); configGroupDAO.merge(configGroupEntity); configGroupName = name; } @Override public String getClusterName() { return cluster.getClusterName(); } @Override public String getTag() { ConfigGroupEntity configGroupEntity = getConfigGroupEntity(); return configGroupEntity.getTag(); } @Override public void setTag(String tag) { ConfigGroupEntity configGroupEntity = getConfigGroupEntity(); configGroupEntity.setTag(tag); configGroupDAO.merge(configGroupEntity); } @Override public String getDescription() { ConfigGroupEntity configGroupEntity = getConfigGroupEntity(); return configGroupEntity.getDescription(); } @Override public void setDescription(String description) { ConfigGroupEntity configGroupEntity = getConfigGroupEntity(); configGroupEntity.setDescription(description); configGroupDAO.merge(configGroupEntity); } @Override public Map<Long, Host> getHosts() { return Collections.unmodifiableMap(m_hosts); } @Override public Map<String, Config> getConfigurations() { return Collections.unmodifiableMap(m_configurations); } /** * Helper method to recreate host mapping * @param hosts */ @Override public void setHosts(Map<Long, Host> hosts) { hostLock.writeLock().lock(); try { // persist enitites in a transaction first, then update internal state replaceHostMappings(hosts); m_hosts = new ConcurrentHashMap<>(hosts); } finally { hostLock.writeLock().unlock(); } } /** * Helper method to recreate configs mapping */ @Override public void setConfigurations(Map<String, Config> configurations) { ConfigGroupEntity configGroupEntity = getConfigGroupEntity(); ClusterEntity clusterEntity = configGroupEntity.getClusterEntity(); // only update the internal state after the configurations have been // persisted persistConfigMapping(clusterEntity, configGroupEntity, configurations); m_configurations = new ConcurrentHashMap<>(configurations); } @Override public void removeHost(Long hostId) throws AmbariException { hostLock.writeLock().lock(); try { Host host = m_hosts.get(hostId); if (null == host) { return; } String hostName = host.getHostName(); LOG.info("Removing host (id={}, name={}) from config group", host.getHostId(), hostName); try { // remove the entities first, then update internal state removeConfigGroupHostEntity(host); m_hosts.remove(hostId); } catch (Exception e) { LOG.error("Failed to delete config group host mapping for cluster {} and host {}", cluster.getClusterName(), hostName, e); throw new AmbariException(e.getMessage()); } } finally { hostLock.writeLock().unlock(); } } /** * Removes the {@link ConfigGroupHostMappingEntity} for the specified host * from this configuration group. * * @param host * the host to remove. */ @Transactional void removeConfigGroupHostEntity(Host host) { ConfigGroupEntity configGroupEntity = getConfigGroupEntity(); ConfigGroupHostMappingEntityPK hostMappingEntityPK = new ConfigGroupHostMappingEntityPK(); hostMappingEntityPK.setHostId(host.getHostId()); hostMappingEntityPK.setConfigGroupId(configGroupId); ConfigGroupHostMappingEntity configGroupHostMapping = configGroupHostMappingDAO.findByPK( hostMappingEntityPK); configGroupHostMappingDAO.remove(configGroupHostMapping); configGroupEntity.getConfigGroupHostMappingEntities().remove(configGroupHostMapping); configGroupEntity = configGroupDAO.merge(getConfigGroupEntity()); } /** * @param configGroupEntity */ private void persist(ConfigGroupEntity configGroupEntity) { persistEntities(configGroupEntity); cluster.refresh(); } /** * Persist Config group with host mapping and configurations * * @throws Exception */ @Transactional void persistEntities(ConfigGroupEntity configGroupEntity) { ClusterEntity clusterEntity = clusterDAO.findById(cluster.getClusterId()); configGroupEntity.setClusterEntity(clusterEntity); configGroupEntity.setTimestamp(System.currentTimeMillis()); configGroupDAO.create(configGroupEntity); configGroupId = configGroupEntity.getGroupId(); persistConfigMapping(clusterEntity, configGroupEntity, m_configurations); replaceHostMappings(m_hosts); } /** * Replaces all existing host mappings with the new collection of hosts. */ @Transactional void replaceHostMappings(Map<Long, Host> hosts) { ConfigGroupEntity configGroupEntity = getConfigGroupEntity(); // Delete existing mappings and create new ones configGroupHostMappingDAO.removeAllByGroup(configGroupEntity.getGroupId()); configGroupEntity.setConfigGroupHostMappingEntities( new HashSet<ConfigGroupHostMappingEntity>()); if (hosts != null && !hosts.isEmpty()) { configGroupEntity = persistHostMapping(hosts.values(), configGroupEntity); } } /** * Adds the collection of hosts to the configuration group. */ @Transactional ConfigGroupEntity persistHostMapping(Collection<Host> hosts, ConfigGroupEntity configGroupEntity) { for (Host host : hosts) { HostEntity hostEntity = hostDAO.findById(host.getHostId()); if (hostEntity != null) { ConfigGroupHostMappingEntity hostMappingEntity = new ConfigGroupHostMappingEntity(); hostMappingEntity.setHostId(hostEntity.getHostId()); hostMappingEntity.setHostEntity(hostEntity); hostMappingEntity.setConfigGroupEntity(configGroupEntity); hostMappingEntity.setConfigGroupId(configGroupEntity.getGroupId()); configGroupEntity.getConfigGroupHostMappingEntities().add(hostMappingEntity); configGroupHostMappingDAO.create(hostMappingEntity); } else { LOG.warn( "The host {} has been removed from the cluster and cannot be added to the configuration group {}", host.getHostName(), configGroupName); } } return configGroupDAO.merge(configGroupEntity); } /** * Persist config group config mapping and create configs if not in DB * * @param clusterEntity * @throws Exception */ @Transactional void persistConfigMapping(ClusterEntity clusterEntity, ConfigGroupEntity configGroupEntity, Map<String, Config> configurations) { configGroupConfigMappingDAO.removeAllByGroup(configGroupEntity.getGroupId()); configGroupEntity.setConfigGroupConfigMappingEntities( new HashSet<ConfigGroupConfigMappingEntity>()); if (configurations != null && !configurations.isEmpty()) { for (Entry<String, Config> entry : configurations.entrySet()) { Config config = entry.getValue(); ClusterConfigEntity clusterConfigEntity = clusterDAO.findConfig (cluster.getClusterId(), config.getType(), config.getTag()); if (clusterConfigEntity == null) { config = configFactory.createNew(cluster, config.getType(), config.getTag(), config.getProperties(), config.getPropertiesAttributes()); entry.setValue(config); clusterConfigEntity = clusterDAO.findConfig(cluster.getClusterId(), config.getType(), config.getTag()); } ConfigGroupConfigMappingEntity configMappingEntity = new ConfigGroupConfigMappingEntity(); configMappingEntity.setTimestamp(System.currentTimeMillis()); configMappingEntity.setClusterId(clusterEntity.getClusterId()); configMappingEntity.setClusterConfigEntity(clusterConfigEntity); configMappingEntity.setConfigGroupEntity(configGroupEntity); configMappingEntity.setConfigGroupId(configGroupEntity.getGroupId()); configMappingEntity.setConfigType(clusterConfigEntity.getType()); configMappingEntity.setVersionTag(clusterConfigEntity.getTag()); configGroupConfigMappingDAO.create(configMappingEntity); configGroupEntity.getConfigGroupConfigMappingEntities().add (configMappingEntity); configGroupEntity = configGroupDAO.merge(configGroupEntity); } } } @Override @Transactional public void delete() { configGroupConfigMappingDAO.removeAllByGroup(configGroupId); configGroupHostMappingDAO.removeAllByGroup(configGroupId); configGroupDAO.removeByPK(configGroupId); cluster.refresh(); } @Override public void addHost(Host host) throws AmbariException { hostLock.writeLock().lock(); try { if (m_hosts.containsKey(host.getHostId())) { String message = String.format( "Host %s is already associated with the configuration group %s", host.getHostName(), configGroupName); throw new DuplicateResourceException(message); } // ensure that we only update the in-memory structure if the merge was // successful ConfigGroupEntity configGroupEntity = getConfigGroupEntity(); persistHostMapping(Collections.singletonList(host), configGroupEntity); m_hosts.putIfAbsent(host.getHostId(), host); } finally { hostLock.writeLock().unlock(); } } @Override public ConfigGroupResponse convertToResponse() throws AmbariException { Set<Map<String, Object>> hostnames = new HashSet<>(); for (Host host : m_hosts.values()) { Map<String, Object> hostMap = new HashMap<>(); hostMap.put("host_name", host.getHostName()); hostnames.add(hostMap); } Set<Map<String, Object>> configObjMap = new HashSet<>(); for (Config config : m_configurations.values()) { Map<String, Object> configMap = new HashMap<>(); configMap.put(ConfigurationResourceProvider.CONFIGURATION_CONFIG_TYPE_PROPERTY_ID, config.getType()); configMap.put(ConfigurationResourceProvider.CONFIGURATION_CONFIG_TAG_PROPERTY_ID, config.getTag()); configObjMap.add(configMap); } ConfigGroupEntity configGroupEntity = getConfigGroupEntity(); ConfigGroupResponse configGroupResponse = new ConfigGroupResponse( configGroupEntity.getGroupId(), cluster.getClusterName(), configGroupEntity.getGroupName(), configGroupEntity.getTag(), configGroupEntity.getDescription(), hostnames, configObjMap); return configGroupResponse; } @Override public String getServiceName() { ConfigGroupEntity configGroupEntity = getConfigGroupEntity(); return configGroupEntity.getServiceName(); } @Override public void setServiceName(String serviceName) { ConfigGroupEntity configGroupEntity = getConfigGroupEntity(); configGroupEntity.setServiceName(serviceName); configGroupDAO.merge(configGroupEntity); } /** * Gets the {@link ConfigGroupEntity} by it's ID from the JPA cache. * * @return the entity. */ private ConfigGroupEntity getConfigGroupEntity() { return configGroupDAO.findById(configGroupId); } }