/*
* 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.ArrayList;
import java.util.HashMap;
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.ServiceParams;
import com.emc.sa.service.vipr.ViPRService;
import com.emc.sa.service.vipr.block.BlockStorageUtils;
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("RemoveHostFromCluster")
public class RemoveHostFromClusterService extends ViPRService {
@Param(CLUSTER)
protected URI clusterId;
@Param(ServiceParams.HOST)
protected List<String> ids;
private Cluster cluster;
private List<URI> hostIds;
private Map<URI, String> hostURIMap = new HashMap<URI,String>();
@Override
public void precheck() throws Exception {
StringBuilder preCheckErrors = new StringBuilder();
hostIds = uris(ids);
cluster = BlockStorageUtils.getCluster(clusterId);
if (cluster == null) {
preCheckErrors.append("Cluster doesn't exist for ID " + clusterId + " ");
}
List<String> nonVblockhosts = new ArrayList<>();
for (URI hostId : hostIds) {
Host host = BlockStorageUtils.getHost(hostId);
if (host == null) {
preCheckErrors.append("Host doesn't exist for ID " + hostId);
} else if (!host.getCluster().equals(clusterId)) {
preCheckErrors.append("Host " + host.getLabel() + " is not associated with cluster: " + cluster.getLabel() + " ");
} else if (NullColumnValueGetter.isNullURI(host.getComputeElement()) && NullColumnValueGetter.isNullURI(host.getServiceProfile())) {
nonVblockhosts.add(host.getLabel());
}
hostURIMap.put(hostId, host.getLabel());
}
// If a non-vblock host is being decommissioned, fail the order. Only vblock hosts can be decommissioned.
if (!CollectionUtils.isEmpty(nonVblockhosts)) {
logError("computeutils.deactivatecluster.deactivate.nonvblockhosts", nonVblockhosts);
preCheckErrors.append("Cannot decommission the following non-vBlock hosts - ");
preCheckErrors.append(nonVblockhosts);
preCheckErrors.append(". Non-vblock hosts cannot be decommissioned from VCE Vblock catalog services. ");
}
// Validate all of the boot volumes are still valid.
if (!validateBootVolumes(hostURIMap)) {
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, hostIds)) {
logError("computeutils.deactivatecluster.deactivate.hostmovedcluster", cluster.getLabel(),
Joiner.on(',').join(hostURIMap.values()));
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()));
}
}
/**
* Validate the boot volume associated with the hosts we wish to remove.
*
* @param hostIdToNameMap
* map of host ID to hostname. We are only using the host ID key.
* @return false if we can reach the host and determine the boot volume is no longer there.
*/
private boolean validateBootVolumes(Map<URI, String> hostIdToNameMap) {
// 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;
}
// Get all of the hosts for the cluster, create a list of hosts we are interested in removing ONLY.
List<HostRestRep> allClusterHosts = ComputeUtils.getHostsInCluster(clusterId);
List<HostRestRep> hostsToValidate = new ArrayList<>();
for (HostRestRep clusterHost : allClusterHosts) {
if (hostIdToNameMap.containsKey(clusterHost.getId())) {
hostsToValidate.add(clusterHost);
}
}
return ComputeUtils.validateBootVolumes(cluster, hostsToValidate);
}
@Override
public void execute() throws Exception {
// In order to remove the hosts from cluster
// 1. Set the cluster uri = null on the host itself - Should be taken care of during deactivate host
// 2. Delete hosts which will also take care of removing boot volumes and dissociate any shared storage that is
// associated with the host.
//
// Remove host from cluster
// Remove hosts
if (hostIds.isEmpty()) {
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 : hostIds) {
// VBDU TODO, COP-28448, If the boot volume is null for a host, the code goes ahead and runs export update
// operations on all the export Groups referencing the hosts. Ideally, we should run the exports only for
// shared export groups, right?
Host host = BlockStorageUtils.getHost(hostURI);
hostsToBeDeleted.add(host);
URI bootVolURI = host.getBootVolumeId();
if (bootVolURI != null) {
BlockObjectRestRep bootVolRep = null;
try{
bootVolRep = BlockStorageUtils.getBlockResource(bootVolURI);
} catch(Exception e){
//Invalid boot volume reference. Ignore
}
if (bootVolRep!=null && !bootVolRep.getInactive()) {
// 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.Same comment in RemoveComputeClusterService.
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(hostURIMap);
// fail order if no hosts removed
if (successfulHostIds.isEmpty()) {
throw new IllegalStateException(ExecutionUtils.getMessage("computeutils.deactivatehost.deactivate.failure", ""));
}
// check all hosts were removed
if (successfulHostIds.size() < hostIds.size()) {
for (URI hostURI : hostIds) {
if (!successfulHostIds.contains(hostURI)) {
logError("computeutils.deactivatehost.failure", hostURI, clusterId);
}
}
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.getId());
setPartialSuccess();
}
}
}
}
/**
* @return the hostURIMap
*/
public Map<URI, String> getHostURIMap() {
return hostURIMap;
}
/**
* @param hostURIMap the hostURIMap to set
*/
public void setHostURIMap(Map<URI, String> hostURIMap) {
this.hostURIMap = hostURIMap;
}
}