diff --git a/api/src/com/cloud/api/ApiConstants.java b/api/src/com/cloud/api/ApiConstants.java index 3a43d0a9afe..3f69a881dca 100755 --- a/api/src/com/cloud/api/ApiConstants.java +++ b/api/src/com/cloud/api/ApiConstants.java @@ -226,6 +226,7 @@ public class ApiConstants { public static final String PRIVATE_NETMASK = "privatenetmask"; public static final String PRIVATE_NETWORK_ID = "privatenetworkid"; public static final String ALLOCATION_STATE = "allocationstate"; + public static final String MANAGED_STATE = "managedstate"; public static final String STORAGE_ID="storageid"; public static final String PING_STORAGE_SERVER_IP = "pingstorageserverip"; public static final String PING_DIR = "pingdir"; diff --git a/api/src/com/cloud/api/commands/ListClustersCmd.java b/api/src/com/cloud/api/commands/ListClustersCmd.java index daee7e4cda6..d602f0a49b1 100644 --- a/api/src/com/cloud/api/commands/ListClustersCmd.java +++ b/api/src/com/cloud/api/commands/ListClustersCmd.java @@ -63,6 +63,9 @@ public class ListClustersCmd extends BaseListCmd { @Parameter(name=ApiConstants.ALLOCATION_STATE, type=CommandType.STRING, description="lists clusters by allocation state") private String allocationState; + @Parameter(name=ApiConstants.MANAGED_STATE, type=CommandType.STRING, description="whether this cluster is managed by cloudstack") + private String managedState; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// @@ -95,6 +98,17 @@ public class ListClustersCmd extends BaseListCmd { public String getAllocationState() { return allocationState; } + + + public String getManagedstate() { + return managedState; + } + + public void setManagedstate(String managedstate) { + this.managedState = managedstate; + } + + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/com/cloud/api/commands/UpdateClusterCmd.java b/api/src/com/cloud/api/commands/UpdateClusterCmd.java index 550d3ef2a97..d054e50e72d 100644 --- a/api/src/com/cloud/api/commands/UpdateClusterCmd.java +++ b/api/src/com/cloud/api/commands/UpdateClusterCmd.java @@ -18,9 +18,6 @@ package com.cloud.api.commands; -import java.util.ArrayList; -import java.util.List; - import org.apache.log4j.Logger; import com.cloud.api.ApiConstants; @@ -28,15 +25,10 @@ import com.cloud.api.BaseCmd; import com.cloud.api.Implementation; import com.cloud.api.Parameter; import com.cloud.api.ServerApiException; -import com.cloud.api.BaseCmd.CommandType; import com.cloud.api.response.ClusterResponse; -import com.cloud.api.response.ListResponse; -import com.cloud.api.response.PodResponse; -import com.cloud.exception.DiscoveryException; import com.cloud.exception.InvalidParameterValueException; import com.cloud.org.Cluster; import com.cloud.user.Account; -import com.cloud.uservm.UserVm; @Implementation(description="Updates an existing cluster", responseObject=ClusterResponse.class) public class UpdateClusterCmd extends BaseCmd { @@ -57,7 +49,10 @@ public class UpdateClusterCmd extends BaseCmd { private String clusterType; @Parameter(name=ApiConstants.ALLOCATION_STATE, type=CommandType.STRING, description="Allocation state of this cluster for allocation of new resources") - private String allocationState; + private String allocationState; + + @Parameter(name=ApiConstants.MANAGED_STATE, type=CommandType.STRING, description="whether this cluster is managed by cloudstack") + private String managedState; public String getClusterName() { return clusterName; @@ -97,6 +92,14 @@ public class UpdateClusterCmd extends BaseCmd { this.allocationState = allocationState; } + public String getManagedstate() { + return managedState; + } + + public void setManagedstate(String managedstate) { + this.managedState = managedstate; + } + @Override public void execute(){ Cluster cluster = _resourceService.getCluster(getId()); @@ -104,7 +107,7 @@ public class UpdateClusterCmd extends BaseCmd { throw new InvalidParameterValueException("Unable to find the cluster by id=" + getId()); } - Cluster result = _resourceService.updateCluster(cluster, getClusterType(), getHypervisor(), getAllocationState()); + Cluster result = _resourceService.updateCluster(cluster, getClusterType(), getHypervisor(), getAllocationState(), getManagedstate()); if (result != null) { ClusterResponse clusterResponse = _responseGenerator.createClusterResponse(cluster); clusterResponse.setResponseName(getCommandName()); diff --git a/api/src/com/cloud/api/response/ClusterResponse.java b/api/src/com/cloud/api/response/ClusterResponse.java index 0c2fcdfae54..2c49e5d646e 100644 --- a/api/src/com/cloud/api/response/ClusterResponse.java +++ b/api/src/com/cloud/api/response/ClusterResponse.java @@ -18,6 +18,8 @@ package com.cloud.api.response; import com.cloud.api.ApiConstants; +import com.cloud.api.Parameter; +import com.cloud.api.BaseCmd.CommandType; import com.cloud.serializer.Param; import com.google.gson.annotations.SerializedName; @@ -49,6 +51,10 @@ public class ClusterResponse extends BaseResponse { @SerializedName("allocationstate") @Param(description="the allocation state of the cluster") private String allocationState; + @SerializedName("managedstate") @Param(description="whether this cluster is managed by cloudstack") + private String managedState; + + public Long getId() { return id; } @@ -119,5 +125,13 @@ public class ClusterResponse extends BaseResponse { public void setAllocationState(String allocationState) { this.allocationState = allocationState; + } + + public String getManagedState() { + return managedState; + } + + public void setManagedState(String managedState) { + this.managedState = managedState; } } diff --git a/api/src/com/cloud/host/Status.java b/api/src/com/cloud/host/Status.java index 257505204cd..94f8bcb4bf8 100644 --- a/api/src/com/cloud/host/Status.java +++ b/api/src/com/cloud/host/Status.java @@ -77,7 +77,8 @@ public enum Status { RequestAgentRebalance(false, "Request rebalance for the certain host"), StartAgentRebalance(false, "Start rebalance for the certain host"), RebalanceCompleted(false, "Host is rebalanced successfully"), - RebalanceFailed(false, "Failed to rebalance the host"); + RebalanceFailed(false, "Failed to rebalance the host"), + PrepareUnmanaged(true, "prepare for cluster entering unmanaged status"); private final boolean isUserRequest; private final String comment; @@ -138,6 +139,7 @@ public enum Status { s_fsm.addTransition(Status.Up, Event.AgentConnected, Status.Connecting); s_fsm.addTransition(Status.Up, Event.ManagementServerDown, Status.Disconnected); s_fsm.addTransition(Status.Up, Event.StartAgentRebalance, Status.Rebalancing); + s_fsm.addTransition(Status.Up, Event.PrepareUnmanaged, Status.Disconnected); s_fsm.addTransition(Status.Updating, Event.PingTimeout, Status.Alert); s_fsm.addTransition(Status.Updating, Event.Ping, Status.Updating); s_fsm.addTransition(Status.Updating, Event.AgentConnected, Status.Connecting); diff --git a/api/src/com/cloud/org/Cluster.java b/api/src/com/cloud/org/Cluster.java index cc76133d620..697d6365ab9 100644 --- a/api/src/com/cloud/org/Cluster.java +++ b/api/src/com/cloud/org/Cluster.java @@ -22,6 +22,7 @@ package com.cloud.org; import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.org.Managed.ManagedState; public interface Cluster extends Grouping { public static enum ClusterType { @@ -38,4 +39,5 @@ public interface Cluster extends Grouping { HypervisorType getHypervisorType(); ClusterType getClusterType(); AllocationState getAllocationState(); + ManagedState getManagedState(); } diff --git a/api/src/com/cloud/resource/ResourceService.java b/api/src/com/cloud/resource/ResourceService.java index 8775bc12961..d6e5ca9aa8e 100644 --- a/api/src/com/cloud/resource/ResourceService.java +++ b/api/src/com/cloud/resource/ResourceService.java @@ -61,7 +61,7 @@ public interface ResourceService { boolean deleteCluster(DeleteClusterCmd cmd); - Cluster updateCluster(Cluster cluster, String clusterType, String hypervisor, String allocationState); + Cluster updateCluster(Cluster cluster, String clusterType, String hypervisor, String allocationState, String managedstate); List discoverHosts(AddHostCmd cmd) throws IllegalArgumentException, DiscoveryException, InvalidParameterValueException; diff --git a/server/src/com/cloud/agent/AgentManager.java b/server/src/com/cloud/agent/AgentManager.java index 35b4562d07b..0851eb7d8fc 100755 --- a/server/src/com/cloud/agent/AgentManager.java +++ b/server/src/com/cloud/agent/AgentManager.java @@ -173,8 +173,11 @@ public interface AgentManager extends Manager { * Obtains statistics for a host; vCPU utilisation, memory utilisation, and network utilisation * * @param hostId - * @return HostStats + * @return HostStat */ + + boolean disconnect(long hostId); + HostStats getHostStatistics(long hostId); Long getGuestOSCategoryId(long hostId); diff --git a/server/src/com/cloud/agent/manager/AgentManagerImpl.java b/server/src/com/cloud/agent/manager/AgentManagerImpl.java index 42275cf17af..2bd0ed4c81a 100755 --- a/server/src/com/cloud/agent/manager/AgentManagerImpl.java +++ b/server/src/com/cloud/agent/manager/AgentManagerImpl.java @@ -965,6 +965,12 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory, Manager { } } } + + @Override + public boolean disconnect(final long hostId) { + disconnect(hostId, Event.PrepareUnmanaged, false); + return true; + } @Override public void updateStatus(HostVO host, Status.Event event) { @@ -1077,7 +1083,7 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory, Manager { _hostDao.disconnect(host, event, _nodeId); host = _hostDao.findById(host.getId()); - if (host.getStatus() == Status.Alert || host.getStatus() == Status.Down) { + if (event.equals(Event.PrepareUnmanaged) && (host.getStatus() == Status.Alert || host.getStatus() == Status.Down)) { _haMgr.scheduleRestartForVmsOnHost(host, investigate); } @@ -1544,6 +1550,8 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory, Manager { return maintain(hostId); } else if (event == Event.ResetRequested) { return cancelMaintenance(hostId); + } else if (event == Event.PrepareUnmanaged) { + return disconnect(hostId); } else if (event == Event.Remove) { User caller = _accountMgr.getActiveUser(User.UID_SYSTEM); return deleteHost(hostId, false, false, caller); diff --git a/server/src/com/cloud/agent/manager/ClusteredAgentManagerImpl.java b/server/src/com/cloud/agent/manager/ClusteredAgentManagerImpl.java index 7fd4dcec49a..c1f83ca9bfb 100644 --- a/server/src/com/cloud/agent/manager/ClusteredAgentManagerImpl.java +++ b/server/src/com/cloud/agent/manager/ClusteredAgentManagerImpl.java @@ -218,6 +218,20 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust return super.cancelMaintenance(hostId); } + @Override + public boolean disconnect(final long hostId) { + try { + Boolean result = _clusterMgr.propagateAgentEvent(hostId, Event.PrepareUnmanaged); + + if (result != null) { + return result; + } + } catch (AgentUnavailableException e) { + return false; + } + return super.disconnect(hostId); + } + protected AgentAttache createAttache(long id) { s_logger.debug("create forwarding ClusteredAgentAttache for " + id); final AgentAttache attache = new ClusteredAgentAttache(this, id); diff --git a/server/src/com/cloud/api/ApiResponseHelper.java b/server/src/com/cloud/api/ApiResponseHelper.java index 838a16dd613..7166ac2bab4 100755 --- a/server/src/com/cloud/api/ApiResponseHelper.java +++ b/server/src/com/cloud/api/ApiResponseHelper.java @@ -967,6 +967,7 @@ public class ApiResponseHelper implements ResponseGenerator { clusterResponse.setHypervisorType(cluster.getHypervisorType().toString()); clusterResponse.setClusterType(cluster.getClusterType().toString()); clusterResponse.setAllocationState(cluster.getAllocationState().toString()); + clusterResponse.setManagedState(cluster.getManagedState().toString()); HostPodVO pod = ApiDBUtils.findPodById(cluster.getPodId()); if (pod != null) { clusterResponse.setPodName(pod.getName()); diff --git a/server/src/com/cloud/dc/ClusterVO.java b/server/src/com/cloud/dc/ClusterVO.java index 2d4312866f6..3bb649ae7e2 100644 --- a/server/src/com/cloud/dc/ClusterVO.java +++ b/server/src/com/cloud/dc/ClusterVO.java @@ -30,6 +30,7 @@ import javax.persistence.Table; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.org.Cluster; +import com.cloud.org.Managed.ManagedState; import com.cloud.org.Grouping; import com.cloud.utils.NumbersUtil; import com.cloud.utils.db.GenericDao; @@ -66,6 +67,10 @@ public class ClusterVO implements Cluster { @Enumerated(value=EnumType.STRING) AllocationState allocationState; + @Column(name="managed_state") + @Enumerated(value=EnumType.STRING) + ManagedState managedState; + @Column(name=GenericDao.REMOVED_COLUMN) private Date removed; @@ -80,6 +85,7 @@ public class ClusterVO implements Cluster { this.name = name; this.clusterType = Cluster.ClusterType.CloudManaged; this.allocationState = Grouping.AllocationState.Enabled; + this.managedState = ManagedState.Managed; } public long getId() { @@ -114,6 +120,14 @@ public class ClusterVO implements Cluster { this.allocationState = allocationState; } + public ManagedState getManagedState() { + return managedState; + } + + public void setManagedState(ManagedState managedState) { + this.managedState = managedState; + } + public ClusterVO(long clusterId) { this.id = clusterId; } diff --git a/server/src/com/cloud/host/dao/HostDaoImpl.java b/server/src/com/cloud/host/dao/HostDaoImpl.java index 6b8419bdf83..c02260d31a1 100644 --- a/server/src/com/cloud/host/dao/HostDaoImpl.java +++ b/server/src/com/cloud/host/dao/HostDaoImpl.java @@ -34,6 +34,8 @@ import org.apache.log4j.Logger; import com.cloud.cluster.agentlb.HostTransferMapVO; import com.cloud.cluster.agentlb.dao.HostTransferMapDaoImpl; +import com.cloud.dc.ClusterVO; +import com.cloud.dc.dao.ClusterDaoImpl; import com.cloud.host.Host; import com.cloud.host.Host.Type; import com.cloud.host.HostTagVO; @@ -42,6 +44,7 @@ import com.cloud.host.Status; import com.cloud.host.Status.Event; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.info.RunningHostCountInfo; +import com.cloud.org.Managed; import com.cloud.utils.DateUtil; import com.cloud.utils.component.ComponentLocator; import com.cloud.utils.db.Attribute; @@ -100,6 +103,7 @@ public class HostDaoImpl extends GenericDaoBase implements HostDao protected final GenericSearchBuilder HostsInStatusSearch; protected final GenericSearchBuilder CountRoutingByDc; protected final SearchBuilder HostTransferSearch; + protected final SearchBuilder ClusterManagedSearch; protected final Attribute _statusAttr; protected final Attribute _msIdAttr; @@ -108,6 +112,8 @@ public class HostDaoImpl extends GenericDaoBase implements HostDao protected final HostDetailsDaoImpl _detailsDao = ComponentLocator.inject(HostDetailsDaoImpl.class); protected final HostTagsDaoImpl _hostTagsDao = ComponentLocator.inject(HostTagsDaoImpl.class); protected final HostTransferMapDaoImpl _hostTransferDao = ComponentLocator.inject(HostTransferMapDaoImpl.class); + protected final ClusterDaoImpl _clusterDao = ComponentLocator.inject(ClusterDaoImpl.class); + public HostDaoImpl() { @@ -234,6 +240,9 @@ public class HostDaoImpl extends GenericDaoBase implements HostDao HostTransferSearch = _hostTransferDao.createSearchBuilder(); HostTransferSearch.and("id", HostTransferSearch.entity().getId(), SearchCriteria.Op.NULL); UnmanagedDirectConnectSearch.join("hostTransferSearch", HostTransferSearch, HostTransferSearch.entity().getId(), UnmanagedDirectConnectSearch.entity().getId(), JoinType.LEFTOUTER); + ClusterManagedSearch = _clusterDao.createSearchBuilder(); + ClusterManagedSearch.and("managed", ClusterManagedSearch.entity().getManagedState(), SearchCriteria.Op.EQ); + UnmanagedDirectConnectSearch.join("ClusterManagedSearch", ClusterManagedSearch, ClusterManagedSearch.entity().getId(), UnmanagedDirectConnectSearch.entity().getClusterId(), JoinType.INNER); UnmanagedDirectConnectSearch.done(); @@ -371,7 +380,8 @@ public class HostDaoImpl extends GenericDaoBase implements HostDao SearchCriteria sc = UnmanagedDirectConnectSearch.create(); sc.setParameters("lastPinged", lastPingSecondsAfter); - sc.setParameters("statuses", Status.ErrorInMaintenance, Status.Maintenance, Status.PrepareForMaintenance); + sc.setParameters("statuses", Status.ErrorInMaintenance, Status.Maintenance, Status.PrepareForMaintenance); + sc.setJoinParameters("ClusterManagedSearch", "managed", Managed.ManagedState.Managed); List hosts = lockRows(sc, new Filter(HostVO.class, "clusterId", true, 0L, limit), true); for (HostVO host : hosts) { diff --git a/server/src/com/cloud/resource/ResourceManagerImpl.java b/server/src/com/cloud/resource/ResourceManagerImpl.java index 0620341786b..f7cf0e5f807 100644 --- a/server/src/com/cloud/resource/ResourceManagerImpl.java +++ b/server/src/com/cloud/resource/ResourceManagerImpl.java @@ -25,7 +25,6 @@ import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; - import javax.ejb.Local; import javax.naming.ConfigurationException; @@ -59,6 +58,7 @@ import com.cloud.host.Host; import com.cloud.host.Host.HostAllocationState; import com.cloud.host.HostVO; import com.cloud.host.Status; +import com.cloud.host.Status.Event; import com.cloud.host.dao.HostDao; import com.cloud.host.dao.HostDetailsDao; import com.cloud.hypervisor.Hypervisor; @@ -66,6 +66,7 @@ import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.kvm.resource.KvmDummyResourceBase; import com.cloud.org.Cluster; import com.cloud.org.Grouping; +import com.cloud.org.Managed; import com.cloud.storage.GuestOSCategoryVO; import com.cloud.storage.StorageManager; import com.cloud.storage.dao.GuestOSCategoryDao; @@ -582,7 +583,7 @@ public class ResourceManagerImpl implements ResourceManager, ResourceService, Ma @Override @DB - public Cluster updateCluster(Cluster clusterToUpdate, String clusterType, String hypervisor, String allocationState) { + public Cluster updateCluster(Cluster clusterToUpdate, String clusterType, String hypervisor, String allocationState, String managedstate) { ClusterVO cluster = (ClusterVO) clusterToUpdate; // Verify cluster information and update the cluster if needed @@ -630,17 +631,97 @@ public class ResourceManagerImpl implements ResourceManager, ResourceService, Ma doUpdate = true; } } + + Managed.ManagedState newManagedState = null; + Managed.ManagedState oldManagedState = cluster.getManagedState(); + if (managedstate != null && !managedstate.isEmpty()) { + try { + newManagedState = Managed.ManagedState.valueOf(managedstate); + } catch (IllegalArgumentException ex) { + throw new InvalidParameterValueException("Unable to resolve Managed State '" + managedstate + "' to a supported state"); + } + if (newManagedState == null) { + s_logger.error("Unable to resolve Managed State '" + managedstate + "' to a supported state"); + throw new InvalidParameterValueException("Unable to resolve Managed State '" + managedstate + "' to a supported state"); + } else { + doUpdate = true; + } + } + if (doUpdate) { Transaction txn = Transaction.currentTxn(); try { txn.start(); _clusterDao.update(cluster.getId(), cluster); - txn.commit(); + txn.commit(); } catch (Exception e) { s_logger.error("Unable to update cluster due to " + e.getMessage(), e); throw new CloudRuntimeException("Failed to update cluster. Please contact Cloud Support."); } } + + if( newManagedState != null && !newManagedState.equals(oldManagedState)) { + Transaction txn = Transaction.currentTxn(); + if( newManagedState.equals(Managed.ManagedState.Unmanaged) ) { + boolean success = true; + try { + txn.start(); + cluster.setManagedState(Managed.ManagedState.PrepareUnmanaged); + _clusterDao.update(cluster.getId(), cluster); + txn.commit(); + List hosts = _hostDao.listBy(cluster.getId(), cluster.getPodId(), cluster.getDataCenterId()); + for( HostVO host : hosts ) { + if( !host.getStatus().equals(Status.Down) && !host.getStatus().equals(Status.Disconnected) + && !host.getStatus().equals(Status.Up) && !host.getStatus().equals(Status.Alert) ) { + String msg = "host " + host.getPrivateIpAddress() + " should not be in " + host.getStatus().toString() + " status"; + throw new CloudRuntimeException("PrepareUnmanaged Failed due to " + msg); + } + } + + for( HostVO host : hosts ) { + if ( host.getStatus().equals(Status.Up )) { + _agentMgr.disconnect(host.getId()); + } + } + int retry = 10; + for ( int i = 0; i < retry; i++) { + success = true; + try { + Thread.sleep(20 * 1000); + } catch (Exception e) { + } + hosts = _hostDao.listBy(cluster.getId(), cluster.getPodId(), cluster.getDataCenterId()); + for( HostVO host : hosts ) { + if ( !host.getStatus().equals(Status.Down) && !host.getStatus().equals(Status.Disconnected) + && !host.getStatus().equals(Status.Alert)) { + success = false; + break; + } + } + if( success == true ) { + break; + } + } + if ( success == false ) { + throw new CloudRuntimeException("PrepareUnmanaged Failed due to some hosts are still in UP status after 5 Minutes, please try later "); + } + } finally { + if ( success == false ) { + txn.start(); + cluster.setManagedState(success? Managed.ManagedState.Unmanaged : Managed.ManagedState.PrepareUnmanagedError); + _clusterDao.update(cluster.getId(), cluster); + txn.commit(); + } + } + } else if( newManagedState.equals(Managed.ManagedState.Managed)) { + txn.start(); + cluster.setManagedState(Managed.ManagedState.Managed); + _clusterDao.update(cluster.getId(), cluster); + txn.commit(); + } + + } + return cluster; } diff --git a/setup/db/create-schema.sql b/setup/db/create-schema.sql index 0a93a8e19b2..c7e6ed17a15 100755 --- a/setup/db/create-schema.sql +++ b/setup/db/create-schema.sql @@ -280,6 +280,7 @@ CREATE TABLE `cloud`.`cluster` ( `hypervisor_type` varchar(32), `cluster_type` varchar(64) DEFAULT 'CloudManaged', `allocation_state` varchar(32) NOT NULL DEFAULT 'Enabled' COMMENT 'Is this cluster enabled for allocation for new resources', + `managed_state` varchar(32) NOT NULL DEFAULT 'Managed' COMMENT 'Is this cluster managed by cloudstack', `removed` datetime COMMENT 'date removed if not null', PRIMARY KEY (`id`), CONSTRAINT `fk_cluster__data_center_id` FOREIGN KEY (`data_center_id`) REFERENCES `cloud`.`data_center`(`id`) ON DELETE CASCADE,