/*
* Copyright (c) 2012-2015 iWave Software LLC
* All Rights Reserved
*/
package com.emc.sa.service.vipr.compute;
import static com.emc.sa.service.ServiceParams.CLUSTER;
import java.net.URI;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections.CollectionUtils;
import com.emc.sa.engine.ExecutionUtils;
import com.emc.sa.engine.bind.Param;
import com.emc.sa.engine.service.Service;
import com.emc.sa.service.vipr.ViPRService;
import com.emc.sa.service.vipr.block.BlockStorageUtils;
import com.emc.sa.service.vipr.compute.tasks.DeactivateCluster;
import com.emc.storageos.db.client.model.Cluster;
import com.emc.storageos.db.client.model.Host;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.model.block.BlockObjectRestRep;
import com.emc.storageos.model.host.HostRestRep;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
@Service("RemoveComputeCluster")
public class RemoveComputeClusterService extends ViPRService {
@Param(CLUSTER)
protected URI clusterId;
private Cluster cluster;
private List<URI> vblockHostURIs = null;
private Map<URI, String> vblockHostMap = null;
private List<URI> hostURIs = null;
@Override
public void precheck() throws Exception {
StringBuilder preCheckErrors = new StringBuilder();
cluster = BlockStorageUtils.getCluster(clusterId);
if (cluster == null) {
preCheckErrors.append("Cluster doesn't exist for ID " + clusterId);
}
acquireClusterLock(cluster);
hostURIs = ComputeUtils.getHostURIsByCluster(getClient(), clusterId);
vblockHostMap = ComputeUtils.getVblockHostURIsByCluster(clusterId);
vblockHostURIs = Lists.newArrayList(vblockHostMap.keySet());
// Additional check to verify if cluster is vblock cluster
if (!CollectionUtils.isEmpty(hostURIs) && CollectionUtils.isEmpty(vblockHostURIs)) {
logError("computeutils.deactivatecluster.deactivate.notpossible.nonvblockcluster", cluster.getLabel());
preCheckErrors.append("Cluster ").append(cluster.getLabel())
.append(" is a non-Vblock cluster, cannot decommission a non-Vblock cluster.");
} // Verify if cluster is a mixed cluster. if so, do not proceed further. Only pure vblock clusters should be decommissioned.
else if (!CollectionUtils.isEmpty(hostURIs) && !CollectionUtils.isEmpty(vblockHostURIs)
&& (hostURIs.size() > vblockHostURIs.size() || !vblockHostURIs.containsAll(hostURIs))) {
logError("computeutils.deactivatecluster.deactivate.notpossible", cluster.getLabel());
preCheckErrors.append("Cluster ").append(cluster.getLabel())
.append(" is a mixed cluster; some hosts do not have UCS components. Cannot decommission a mixed cluster from Vblock catalog services.");
}
// Validate all of the boot volumes are still valid.
if (!validateBootVolumes()) {
logError("computeutils.deactivatecluster.deactivate.bootvolumes", cluster.getLabel());
preCheckErrors.append("Cluster ").append(cluster.getLabel())
.append(" has different boot volumes than what controller provisioned. Cannot delete original boot volume in case it was re-purposed.");
}
// Verify the hosts are still part of the cluster we have reported for it on ESX.
if (!ComputeUtils.verifyHostInVcenterCluster(cluster, hostURIs)) {
logError("computeutils.deactivatecluster.deactivate.hostmovedcluster", cluster.getLabel(), Joiner.on(',').join(hostURIs));
preCheckErrors.append("Cluster ").append(cluster.getLabel())
.append(" no longer contains one or more of the hosts requesting decommission. Cannot decommission in current state. Recommended " +
"to run vCenter discovery and address actionable events before attempting decommission of hosts in this cluster.");
}
if (preCheckErrors.length() > 0) {
throw new IllegalStateException(preCheckErrors.toString() +
ComputeUtils.getContextErrors(getModelClient()));
}
}
@Override
public void execute() throws Exception {
// removing cluster checks for running VMs first for ESX hosts
addAffectedResource(clusterId);
// VBDU [DONE] COP-28400: Looks like this will decommission an entire cluster if there are hosts in it that we are
// not managing.
// ClusterService has a precheck to verify the matching environments before deactivating
if (vblockHostURIs.isEmpty() && hostURIs.isEmpty()) {
execute(new DeactivateCluster(cluster));
return;
}
// get boot vols to be deleted (so we can check afterwards)
List<URI> bootVolsToBeDeleted = Lists.newArrayList();
List<Host> hostsToBeDeleted = Lists.newArrayList();
for (URI hostURI : vblockHostURIs) {
// VBDU TODO: COP-28447, We're assuming the volume we're deleting is still the boot volume, but it could
// have been manually dd'd (migrated) to another volume and this volume could be re-purposed elsewhere.
// We should verify this is the boot volume on the server before attempting to delete it.
Host host = BlockStorageUtils.getHost(hostURI);
hostsToBeDeleted.add(host);
URI bootVolURI = host.getBootVolumeId();
if (!NullColumnValueGetter.isNullURI(bootVolURI)) {
BlockObjectRestRep bootVolRep = null;
try{
bootVolRep = BlockStorageUtils.getBlockResource(bootVolURI);
} catch(Exception e){
//Invalid boot volume reference. Ignore
}
if (bootVolRep!=null && !bootVolRep.getInactive()) {
bootVolsToBeDeleted.add(bootVolURI);
}
}
}
//acquire host locks before proceeding with deactivating hosts.
for (Host host : hostsToBeDeleted) {
acquireHostLock(host, cluster);
}
// removing hosts also removes associated boot volumes and exports
List<URI> successfulHostIds = ComputeUtils.deactivateHostURIs(vblockHostMap);
// fail order if no hosts removed
if (successfulHostIds.isEmpty()) {
throw new IllegalStateException(ExecutionUtils.getMessage("computeutils.deactivatehost.deactivate.failure", ""));
}
// Check if all hosts are deactivated successfully and only then issue the deactivateCluster call
if (successfulHostIds.size() == vblockHostURIs.size() && successfulHostIds.containsAll(vblockHostURIs)) {
execute(new DeactivateCluster(cluster));
}
// check all hosts were removed
if (successfulHostIds.size() < vblockHostURIs.size()) {
for (URI hostURI : vblockHostURIs) {
if (!successfulHostIds.contains(hostURI)) {
logError("computeutils.deactivatehost.failure", vblockHostMap.get(hostURI), cluster.getLabel());
}
}
setPartialSuccess();
}
else { // check all boot vols were removed
for (URI bootVolURI : bootVolsToBeDeleted) {
BlockObjectRestRep bootVolRep = BlockStorageUtils.getBlockResource(bootVolURI);
if ((bootVolRep != null) && !bootVolRep.getInactive()) {
logError("computeutils.removebootvolumes.failure", bootVolRep.getDeviceLabel());
setPartialSuccess();
}
}
}
}
/**
* Validate that the boot volume for this host is still on the server.
* This prevents us from deleting a re-purposed volume that was originally
* a boot volume.
*
* @return false if we can reach the server and determine the boot volume is no longer there.
*/
private boolean validateBootVolumes() {
// If the cluster isn't returned properly, not found in DB, do not delete the boot volume until
// the references are fixed.
if (cluster == null || cluster.getInactive()) {
logError("computeutils.removebootvolumes.failure.cluster");
return false;
}
List<HostRestRep> clusterHosts = ComputeUtils.getHostsInCluster(cluster.getId());
return ComputeUtils.validateBootVolumes(cluster, clusterHosts);
}
/**
* @return the vblockHostURIs
*/
public List<URI> getVblockHostURIs() {
return vblockHostURIs;
}
/**
* @param vblockHostURIs the vblockHostURIs to set
*/
public void setVblockHostURIs(List<URI> vblockHostURIs) {
this.vblockHostURIs = vblockHostURIs;
}
/**
* @return the hostURIs
*/
public List<URI> getHostURIs() {
return hostURIs;
}
/**
* @param hostURIs the hostURIs to set
*/
public void setHostURIs(List<URI> hostURIs) {
this.hostURIs = hostURIs;
}
/**
* @return the vblockHostMap
*/
public Map<URI, String> getVblockHostMap() {
return vblockHostMap;
}
/**
* @param vblockHostMap the vblockHostMap to set
*/
public void setVblockHostMap(Map<URI, String> vblockHostMap) {
this.vblockHostMap = vblockHostMap;
}
}