Просмотр исходного кода

AMBARI-1777. Implement host delete. (ncole)

Nate Cole 12 лет назад
Родитель
Сommit
fbed96d8e1

+ 67 - 6
ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java

@@ -3574,9 +3574,69 @@ public class AmbariManagementControllerImpl implements
   }
 
   @Override
-  public void deleteHosts(Set<HostRequest> request)
+  public void deleteHosts(Set<HostRequest> requests)
       throws AmbariException {
-    throw new AmbariException("Delete hosts not supported");
+
+    List<HostRequest> okToRemove = new ArrayList<HostRequest>();
+    
+    for (HostRequest hostRequest : requests) {
+      String hostName = hostRequest.getHostname();
+      if (null == hostName)
+        continue;
+      
+      if (null != hostRequest.getClusterName()) {
+        Cluster cluster = clusters.getCluster(hostRequest.getClusterName());
+        
+        List<ServiceComponentHost> list = cluster.getServiceComponentHosts(hostName);
+
+        if (0 != list.size()) {
+          StringBuilder reason = new StringBuilder("Cannot remove host ")
+              .append(hostName)
+              .append(" from ")
+              .append(hostRequest.getClusterName())
+              .append(".  The following roles exist: ");
+          
+          int i = 0;
+          for (ServiceComponentHost sch : list) {
+            if ((i++) > 0)
+              reason.append(", ");
+            reason.append(sch.getServiceComponentName());
+          }
+          
+          throw new AmbariException(reason.toString());
+        }
+        okToRemove.add(hostRequest);
+        
+      } else {
+        // check if host exists (throws exception if not found)
+        clusters.getHost(hostName);
+        
+        // delete host outright
+        Set<Cluster> clusterSet = clusters.getClustersForHost(hostName);
+        if (0 != clusterSet.size()) {
+          StringBuilder reason = new StringBuilder("Cannot remove host ")
+            .append(hostName)
+            .append(".  It belongs to clusters: ");
+          int i = 0;
+          for (Cluster c : clusterSet) {
+            if ((i++) > 0)
+              reason.append(", ");
+            reason.append(c.getClusterName());
+          }
+          throw new AmbariException(reason.toString());
+        }
+        okToRemove.add(hostRequest);
+      }
+    }
+    
+    for (HostRequest hostRequest : okToRemove) {
+      if (null != hostRequest.getClusterName()) {
+        clusters.unmapHostFromCluster(hostRequest.getHostname(),
+            hostRequest.getClusterName());
+      } else {
+        clusters.deleteHost(hostRequest.getHostname());
+      }
+    }
   }
 
   @Override
@@ -3617,11 +3677,12 @@ public class AmbariManagementControllerImpl implements
             + ", request=" + request);
       }
 
-      //Only allow removing master components in MAINTENANCE state without stages generation
-      if (component.isClientComponent() ||
+      // Only allow removing master/slave components in MAINTENANCE state without stages generation.
+      // Clients may be removed without a state check.
+      if (!component.isClientComponent() &&
           componentHost.getState() != State.MAINTENANCE) {
-        throw new AmbariException("Only master or slave component can be removed. They must be in " +
-            "MAINTENANCE state in order to be removed.");
+        throw new AmbariException("To remove master or slave components they must be in a " +
+            "MAINTENANCE state. Current=" + componentHost.getState() + ".");
       }
 
       if (!safeToRemoveSCHs.containsKey(component)) {

+ 19 - 0
ambari-server/src/main/java/org/apache/ambari/server/orm/dao/HostConfigMappingDAO.java

@@ -88,4 +88,23 @@ public class HostConfigMappingDAO {
     return daoUtils.selectList(query, Long.valueOf(clusterId), type);
   }
 
+  /**
+   * @param clusterId
+   * @param hostName
+   */
+  @Transactional
+  public void removeHost(long clusterId, String hostName) {
+    TypedQuery<HostConfigMappingEntity> query = entityManagerProvider.get().createQuery(
+        "SELECT entity FROM HostConfigMappingEntity entity " +
+        "WHERE entity.clusterId = ?1 AND entity.hostName = ?2",
+        HostConfigMappingEntity.class);
+    
+    List<HostConfigMappingEntity> list = daoUtils.selectList(query, Long.valueOf(clusterId), hostName);
+
+      for (HostConfigMappingEntity entity : list) {
+        entityManagerProvider.get().remove(entity);
+      }
+    
+  }
+
 }

+ 1 - 1
ambari-server/src/main/java/org/apache/ambari/server/orm/entities/HostComponentDesiredStateEntity.java

@@ -55,7 +55,7 @@ public class HostComponentDesiredStateEntity {
   @Column(name = "desired_stack_version", insertable = true, updatable = true)
   private String desiredStackVersion = "";
 
-  @ManyToOne
+  @ManyToOne(cascade = CascadeType.PERSIST)
   @JoinColumns({
       @JoinColumn(name = "cluster_id", referencedColumnName = "cluster_id", nullable = false),
       @JoinColumn(name = "service_name", referencedColumnName = "service_name", nullable = false),

+ 1 - 1
ambari-server/src/main/java/org/apache/ambari/server/orm/entities/HostComponentStateEntity.java

@@ -55,7 +55,7 @@ public class HostComponentStateEntity {
   @Column(name = "current_stack_version", nullable = false, insertable = true, updatable = true)
   private String currentStackVersion;
 
-  @ManyToOne
+  @ManyToOne(cascade = CascadeType.PERSIST)
   @JoinColumns({
       @JoinColumn(name = "cluster_id", referencedColumnName = "cluster_id", nullable = false),
       @JoinColumn(name = "service_name", referencedColumnName = "service_name", nullable = false),

+ 1 - 1
ambari-server/src/main/java/org/apache/ambari/server/orm/entities/HostConfigMappingEntity.java

@@ -58,7 +58,7 @@ public class HostConfigMappingEntity {
   
   @Column(name = "user_name", insertable = true, updatable = true, nullable = false)
   private String user = null;
-
+  
   public Long getClusterId() {
     return clusterId;
   }

+ 5 - 5
ambari-server/src/main/java/org/apache/ambari/server/orm/entities/HostEntity.java

@@ -97,10 +97,10 @@ public class HostEntity {
   @Lob
   private String hostAttributes = "";
 
-  @OneToMany(mappedBy = "hostEntity")
+  @OneToMany(mappedBy = "hostEntity", cascade = {CascadeType.REMOVE, CascadeType.PERSIST})
   private Collection<HostComponentDesiredStateEntity> hostComponentDesiredStateEntities;
 
-  @OneToMany(mappedBy = "hostEntity")
+  @OneToMany(mappedBy = "hostEntity", cascade = {CascadeType.REMOVE, CascadeType.PERSIST})
   private Collection<HostComponentStateEntity> hostComponentStateEntities;
 
   @ManyToMany
@@ -110,12 +110,12 @@ public class HostEntity {
   )
   private Collection<ClusterEntity> clusterEntities;
 
-  @OneToOne(mappedBy = "hostEntity")
+  @OneToOne(mappedBy = "hostEntity", cascade = CascadeType.REMOVE)
   private HostStateEntity hostStateEntity;
 
-  @OneToMany(mappedBy = "host")
+  @OneToMany(mappedBy = "host", cascade = CascadeType.REMOVE)
   private Collection<HostRoleCommandEntity> hostRoleCommandEntities;
-
+  
   public String getHostName() {
     return hostName;
   }

+ 1 - 1
ambari-server/src/main/java/org/apache/ambari/server/orm/entities/HostRoleCommandEntity.java

@@ -107,7 +107,7 @@ public class HostRoleCommandEntity {
   @JoinColumns({@JoinColumn(name = "request_id", referencedColumnName = "request_id", nullable = false), @JoinColumn(name = "stage_id", referencedColumnName = "stage_id", nullable = false)})
   private StageEntity stage;
 
-  @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
+  @ManyToOne(cascade = CascadeType.ALL)
   @JoinColumn(name = "host_name", referencedColumnName = "host_name", nullable = false)
   private HostEntity host;
 

+ 1 - 1
ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ServiceComponentDesiredStateEntity.java

@@ -55,7 +55,7 @@ public class ServiceComponentDesiredStateEntity {
   @JoinColumns({@javax.persistence.JoinColumn(name = "cluster_id", referencedColumnName = "cluster_id", nullable = false), @JoinColumn(name = "service_name", referencedColumnName = "service_name", nullable = false)})
   private ClusterServiceEntity clusterServiceEntity;
 
-  @OneToMany(mappedBy = "serviceComponentDesiredStateEntity")
+  @OneToMany(mappedBy = "serviceComponentDesiredStateEntity", cascade = CascadeType.PERSIST)
   private Collection<HostComponentStateEntity> hostComponentStateEntities;
 
   @OneToMany(mappedBy = "serviceComponentDesiredStateEntity")

+ 15 - 0
ambari-server/src/main/java/org/apache/ambari/server/state/Clusters.java

@@ -158,4 +158,19 @@ public interface Clusters {
   public void updateHostWithClusterAndAttributes(
       Map<String, Set<String>> hostsClusters, Map<String, Map<String, String>> hostAttributes)
       throws AmbariException;
+
+  /**
+   * Removes a host from a cluster.  Inverts {@link #mapHostToCluster(String, String)
+   * @param hostname
+   * @param clusterName
+   */
+  public void unmapHostFromCluster(String hostname, String clusterName)
+      throws AmbariException;
+
+  /**
+   * Removes a host.  Inverts {@link #addHost(String)}
+   * @param hostname
+   */
+  public void deleteHost(String hostname)
+      throws AmbariException;
 }

+ 90 - 5
ambari-server/src/main/java/org/apache/ambari/server/state/cluster/ClustersImpl.java

@@ -18,15 +18,20 @@
 
 package org.apache.ambari.server.state.cluster;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 import javax.persistence.RollbackException;
 
-import com.google.gson.Gson;
-import com.google.inject.persist.Transactional;
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.ClusterNotFoundException;
 import org.apache.ambari.server.DuplicateResourceException;
@@ -34,17 +39,27 @@ import org.apache.ambari.server.HostNotFoundException;
 import org.apache.ambari.server.agent.DiskInfo;
 import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.orm.dao.ClusterDAO;
+import org.apache.ambari.server.orm.dao.HostConfigMappingDAO;
 import org.apache.ambari.server.orm.dao.HostDAO;
 import org.apache.ambari.server.orm.entities.ClusterEntity;
 import org.apache.ambari.server.orm.entities.HostEntity;
-import org.apache.ambari.server.state.*;
+import org.apache.ambari.server.state.AgentVersion;
+import org.apache.ambari.server.state.Cluster;
+import org.apache.ambari.server.state.Clusters;
+import org.apache.ambari.server.state.Host;
+import org.apache.ambari.server.state.HostHealthStatus;
 import org.apache.ambari.server.state.HostHealthStatus.HealthStatus;
+import org.apache.ambari.server.state.HostState;
+import org.apache.ambari.server.state.RepositoryInfo;
+import org.apache.ambari.server.state.StackId;
 import org.apache.ambari.server.state.host.HostFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.gson.Gson;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import com.google.inject.persist.Transactional;
 
 @Singleton
 public class ClustersImpl implements Clusters {
@@ -76,6 +91,8 @@ public class ClustersImpl implements Clusters {
   AmbariMetaInfo ambariMetaInfo;
   @Inject
   Gson gson;
+  @Inject
+  private HostConfigMappingDAO hostConfigMappingDAO;
 
   @Inject
   public ClustersImpl() {
@@ -452,7 +469,7 @@ public class ClustersImpl implements Clusters {
       w.unlock();
     }
   }
-
+  
   @Transactional
   void mapHostClusterEntities(String hostName, Long clusterId) {
     HostEntity hostEntity = hostDAO.findByName(hostName);
@@ -567,5 +584,73 @@ public class ClustersImpl implements Clusters {
       w.unlock();
     }
   }
+  
+  @Override
+  public void unmapHostFromCluster(String hostname, String clusterName)
+      throws AmbariException {
+
+    loadClustersAndHosts();
+    
+    w.lock();
+
+    try {
+      Host host = getHost(hostname);
+      Cluster cluster = getCluster(clusterName);
+
+      if (LOG.isDebugEnabled()) {
+        LOG.debug("Unmapping a host from a cluster"
+            + ", clusterName=" + clusterName
+            + ", clusterId=" + cluster.getClusterId()
+            + ", hostname=" + hostname);
+      }
+      
+      unmapHostClusterEntities(hostname, cluster.getClusterId());
+
+      hostClusterMap.get(hostname).remove(cluster);
+      clusterHostMap.get(clusterName).remove(host);
+
+    } finally {
+      w.unlock();
+    }
+    
+  }
+  
+  @Transactional
+  private void unmapHostClusterEntities(String hostName, long clusterId) {
+    HostEntity hostEntity = hostDAO.findByName(hostName);
+    ClusterEntity clusterEntity = clusterDAO.findById(clusterId);
+
+    hostEntity.getClusterEntities().remove(clusterEntity);
+    clusterEntity.getHostEntities().remove(hostEntity);
+
+    hostConfigMappingDAO.removeHost(clusterId, hostName);
+    
+    hostDAO.merge(hostEntity);
+    clusterDAO.merge(clusterEntity);
+  }
+  
+  @Override
+  public void deleteHost(String hostname) throws AmbariException {
+    loadClustersAndHosts();
+
+    if (!hosts.containsKey(hostname))
+      return;
+    
+    w.lock();
+
+    try {
+      HostEntity entity = hostDAO.findByName(hostname);
+      hostDAO.refresh(entity);
+      
+      hostDAO.remove(entity);
+      
+      hosts.remove(hostname);
+    } catch (Exception e) {
+      throw new AmbariException("Could not remove host", e);
+    } finally {
+      w.unlock();
+    }
+    
+  }
 
 }

+ 141 - 1
ambari-server/src/test/java/org/apache/ambari/server/controller/AmbariManagementControllerTest.java

@@ -21,7 +21,6 @@ package org.apache.ambari.server.controller;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -42,6 +41,7 @@ import junit.framework.Assert;
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.ClusterNotFoundException;
 import org.apache.ambari.server.DuplicateResourceException;
+import org.apache.ambari.server.HostNotFoundException;
 import org.apache.ambari.server.ObjectNotFoundException;
 import org.apache.ambari.server.ParentObjectNotFoundException;
 import org.apache.ambari.server.Role;
@@ -80,6 +80,8 @@ import org.apache.ambari.server.state.ServiceFactory;
 import org.apache.ambari.server.state.StackId;
 import org.apache.ambari.server.state.StackInfo;
 import org.apache.ambari.server.state.State;
+import org.apache.ambari.server.state.svccomphost.ServiceComponentHostInstallEvent;
+import org.apache.ambari.server.state.svccomphost.ServiceComponentHostOpSucceededEvent;
 import org.apache.ambari.server.state.svccomphost.ServiceComponentHostStartEvent;
 import org.apache.ambari.server.utils.StageUtils;
 import org.junit.After;
@@ -6566,4 +6568,142 @@ public class AmbariManagementControllerTest {
     assertEquals(original, repo.getDefaultBaseUrl());
   }
   
+  @Test
+  public void testDeleteHost() throws Exception {
+    String clusterName = "foo1";
+    
+    createCluster(clusterName);
+    
+    Cluster cluster = clusters.getCluster(clusterName);
+    cluster.setDesiredStackVersion(new StackId("HDP-0.1"));
+    
+    String serviceName = "HDFS";
+    createService(clusterName, serviceName, null);
+    String componentName1 = "NAMENODE";
+    String componentName2 = "DATANODE";
+    String componentName3 = "HDFS_CLIENT";
+
+    Map<String, String> mapRequestProps = new HashMap<String, String>();
+    mapRequestProps.put("context", "Called from a test");
+
+    createServiceComponent(clusterName, serviceName, componentName1, State.INIT);
+    createServiceComponent(clusterName, serviceName, componentName2, State.INIT);
+    createServiceComponent(clusterName, serviceName, componentName3, State.INIT);
+
+    String host1 = "h1";
+    clusters.addHost(host1);
+    clusters.getHost("h1").setOsType("centos5");
+    clusters.getHost("h1").persist();
+    
+    String host2 = "h2";
+    clusters.addHost(host2);
+    clusters.getHost("h2").setOsType("centos6");
+    clusters.getHost("h2").persist();
+    
+    String host3 = "h3";
+
+    clusters.mapHostToCluster(host1, clusterName);
+
+    createServiceComponentHost(clusterName, null, componentName1, host1, null);
+    createServiceComponentHost(clusterName, serviceName, componentName2, host1, null);
+    createServiceComponentHost(clusterName, serviceName, componentName3, host1, null);
+    
+    // Install
+    installService(clusterName, serviceName, false, false);
+    
+    // make them believe they are up
+    Map<String, ServiceComponentHost> hostComponents = cluster.getService(serviceName).getServiceComponent(componentName1).getServiceComponentHosts();
+    for (Map.Entry<String, ServiceComponentHost> entry : hostComponents.entrySet()) {
+      ServiceComponentHost cHost = entry.getValue();
+      cHost.handleEvent(new ServiceComponentHostInstallEvent(cHost.getServiceComponentName(), cHost.getHostName(), System.currentTimeMillis(), cluster.getDesiredStackVersion().getStackId()));
+      cHost.handleEvent(new ServiceComponentHostOpSucceededEvent(cHost.getServiceComponentName(), cHost.getHostName(), System.currentTimeMillis()));
+    }
+    hostComponents = cluster.getService(serviceName).getServiceComponent(componentName2).getServiceComponentHosts();
+    for (Map.Entry<String, ServiceComponentHost> entry : hostComponents.entrySet()) {
+      ServiceComponentHost cHost = entry.getValue();
+      cHost.handleEvent(new ServiceComponentHostInstallEvent(cHost.getServiceComponentName(), cHost.getHostName(), System.currentTimeMillis(), cluster.getDesiredStackVersion().getStackId()));
+      cHost.handleEvent(new ServiceComponentHostOpSucceededEvent(cHost.getServiceComponentName(), cHost.getHostName(), System.currentTimeMillis()));
+    }
+    
+    Set<HostRequest> requests = new HashSet<HostRequest>();
+    // delete from cluster
+    requests.clear();
+    requests.add(new HostRequest(host1, clusterName, null));
+    try {
+      controller.deleteHosts(requests);
+      fail("Expect failure deleting hosts when components exist.");
+    } catch (Exception e) {
+    }
+    
+    Set<ServiceComponentHostRequest> schRequests = new HashSet<ServiceComponentHostRequest>();
+    // maintenance HC for non-clients
+    schRequests.add(new ServiceComponentHostRequest(clusterName, serviceName, componentName1, host1, null, "MAINTENANCE"));
+    schRequests.add(new ServiceComponentHostRequest(clusterName, serviceName, componentName2, host1, null, "MAINTENANCE"));
+    controller.updateHostComponents(schRequests, new HashMap<String,String>(), false);
+
+    // delete HC
+    schRequests.clear();
+    schRequests.add(new ServiceComponentHostRequest(clusterName, serviceName, componentName1, host1, null, null));
+    schRequests.add(new ServiceComponentHostRequest(clusterName, serviceName, componentName2, host1, null, null));
+    schRequests.add(new ServiceComponentHostRequest(clusterName, serviceName, componentName3, host1, null, null));
+    controller.deleteHostComponents(schRequests);
+    
+    Assert.assertEquals(0, cluster.getServiceComponentHosts(host1).size());
+    
+    // delete, which should fail since it is part of a cluster
+    requests.clear();
+    requests.add(new HostRequest(host1, null, null));
+    try {
+      controller.deleteHosts(requests);
+      fail("Expect failure when removing from host when it is part of a cluster.");
+    } catch (Exception e) {
+    }
+    
+    // delete host from cluster
+    requests.clear();
+    requests.add(new HostRequest(host1, clusterName, null));
+    controller.deleteHosts(requests);
+
+    // host is no longer part of the cluster
+    Assert.assertFalse(clusters.getHostsForCluster(clusterName).containsKey(host1));
+    Assert.assertFalse(clusters.getClustersForHost(host1).contains(cluster));
+
+    // delete entirely
+    requests.clear();
+    requests.add(new HostRequest(host1, null, null));
+    controller.deleteHosts(requests);
+
+    // verify host does not exist
+    try {
+      clusters.getHost(host1);
+      Assert.fail("Expected a HostNotFoundException.");
+    } catch (HostNotFoundException e) {
+      // expected
+    }
+    
+    // remove host2
+    requests.clear();
+    requests.add(new HostRequest(host2, null, null));
+    controller.deleteHosts(requests);
+    
+    // verify host does not exist
+    try {
+      clusters.getHost(host2);
+      Assert.fail("Expected a HostNotFoundException.");
+    } catch (HostNotFoundException e) {
+      // expected
+    }
+
+    // try removing a host that never existed
+    requests.clear();
+    requests.add(new HostRequest(host3, null, null));
+    try {
+      controller.deleteHosts(requests);
+      Assert.fail("Expected a HostNotFoundException trying to remove a host that was never added.");
+    } catch (HostNotFoundException e) {
+      // expected
+    }
+
+  }
+  
 }