/***************************************************************************
* Copyright (c) 2012-2015 VMware, Inc. All Rights Reserved.
* Licensed 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 com.vmware.bdd.service.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import com.google.gson.internal.Pair;
import com.vmware.aurora.global.Configuration;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.google.gson.internal.Pair;
import com.vmware.aurora.composition.CreateVmSP;
import com.vmware.aurora.composition.VmSchema;
import com.vmware.aurora.composition.compensation.CompensateCreateVmSP;
import com.vmware.aurora.composition.concurrent.ExecutionResult;
import com.vmware.aurora.composition.concurrent.Scheduler;
import com.vmware.aurora.vc.DeviceId;
import com.vmware.aurora.vc.VcCache;
import com.vmware.aurora.vc.VcDatacenter;
import com.vmware.aurora.vc.VcDatastore;
import com.vmware.aurora.vc.VcHost;
import com.vmware.aurora.vc.VcResourcePool;
import com.vmware.aurora.vc.VcVirtualMachine;
import com.vmware.aurora.vc.VcVmCloneType;
import com.vmware.aurora.vc.VmConfigUtil;
import com.vmware.aurora.vc.vcservice.VcContext;
import com.vmware.aurora.vc.vcservice.VcSession;
import com.vmware.bdd.apitypes.ClusterCreate;
import com.vmware.bdd.apitypes.NetworkAdd;
import com.vmware.bdd.apitypes.NodeGroupCreate;
import com.vmware.bdd.apitypes.NodeStatus;
import com.vmware.bdd.apitypes.StorageRead.DiskType;
import com.vmware.bdd.entity.DiskEntity;
import com.vmware.bdd.entity.NodeEntity;
import com.vmware.bdd.exception.BddException;
import com.vmware.bdd.exception.ClusterHealServiceException;
import com.vmware.bdd.exception.ClusteringServiceException;
import com.vmware.bdd.manager.ClusterConfigManager;
import com.vmware.bdd.manager.intf.IClusterEntityManager;
import com.vmware.bdd.placement.entity.AbstractDatacenter.AbstractDatastore;
import com.vmware.bdd.service.IClusterHealService;
import com.vmware.bdd.service.IClusteringService;
import com.vmware.bdd.service.resmgmt.INetworkService;
import com.vmware.bdd.service.sp.NoProgressUpdateCallback;
import com.vmware.bdd.service.sp.ReplaceVmBadDisksSP;
import com.vmware.bdd.service.sp.ReplaceVmPrePowerOn;
import com.vmware.bdd.service.sp.StartVmPostPowerOn;
import com.vmware.bdd.service.sp.StartVmSP;
import com.vmware.bdd.service.utils.VcResourceUtils;
import com.vmware.bdd.specpolicy.GuestMachineIdSpec;
import com.vmware.bdd.spectypes.DiskSpec;
import com.vmware.bdd.utils.AuAssert;
import com.vmware.bdd.utils.ClusterUtil;
import com.vmware.bdd.utils.Constants;
import com.vmware.bdd.utils.JobUtils;
import com.vmware.bdd.utils.VcVmUtil;
import com.vmware.vim.binding.vim.Folder;
import com.vmware.vim.binding.vim.vm.device.VirtualDisk;
@Service
public class ClusterHealService implements IClusterHealService {
private static final Logger logger = Logger
.getLogger(ClusterHealService.class);
private static final String RECOVERY_VM_NAME_POSTFIX = "-recovery";
private IClusterEntityManager clusterEntityMgr;
private ClusterConfigManager configMgr;
private IClusteringService clusteringService;
private INetworkService networkMgr;
private final String IMAGESTORE = "IMAGESTORE";
private final String DISKSTORE = "DISKSTORE";
public IClusterEntityManager getClusterEntityMgr() {
return clusterEntityMgr;
}
@Autowired
public void setClusterEntityMgr(IClusterEntityManager clusterEntityMgr) {
this.clusterEntityMgr = clusterEntityMgr;
}
public ClusterConfigManager getConfigMgr() {
return configMgr;
}
@Autowired
public void setConfigMgr(ClusterConfigManager configMgr) {
this.configMgr = configMgr;
}
public IClusteringService getClusteringService() {
return clusteringService;
}
@Autowired
public void setClusteringService(IClusteringService clusteringService) {
this.clusteringService = clusteringService;
}
public INetworkService getNetworkMgr() {
return networkMgr;
}
@Autowired
public void setNetworkMgr(INetworkService networkMgr) {
this.networkMgr = networkMgr;
}
@Override
public boolean hasBadDisks(String nodeName) {
return CollectionUtils.isNotEmpty(getBadDisks(nodeName));
}
@Override
public List<DiskSpec> getBadDisks(String nodeName) {
return getBadDisksByType(nodeName, null);
}
@Override
public boolean hasBadDisksExceptSystem(String nodeName) {
if (hasBadSystemDisks(nodeName)) {
return false;
}
boolean hasBadDataDisks = CollectionUtils.isNotEmpty(getBadDataDisks(nodeName));
boolean hasBadSwapDisks = CollectionUtils.isNotEmpty(getBadSwapDisks(nodeName));
if (hasBadDataDisks || hasBadSwapDisks) {
return true;
}
return false;
}
@Override
public boolean hasBadSystemDisks(String nodeName) {
return CollectionUtils.isNotEmpty(getBadSystemDisks(nodeName));
}
@Override
public List<DiskSpec> getBadSystemDisks(String nodeName) {
return getBadDisksByType(nodeName, DiskType.SYSTEM_DISK);
}
@Override
public List<DiskEntity> getBadSystemDiskEntities(String nodeName) {
return getBadDiskEntitiesByType(nodeName, DiskType.SYSTEM_DISK);
}
@Override
public boolean hasBadSwapDisks(String nodeName) {
return CollectionUtils.isNotEmpty(getBadSwapDisks(nodeName));
}
@Override
public List<DiskSpec> getBadSwapDisks(String nodeName) {
return getBadDisksByType(nodeName, DiskType.SWAP_DISK);
}
@Override
public List<DiskEntity> getBadSwapDiskEntities(String nodeName) {
return getBadDiskEntitiesByType(nodeName, DiskType.SWAP_DISK);
}
@Override
public boolean hasBadDataDisks(String nodeName) {
if (hasBadSystemDisks(nodeName)) {
return false;
}
return CollectionUtils.isNotEmpty(getBadDataDisks(nodeName));
}
@Override
public List<DiskSpec> getBadDataDisks(String nodeName) {
return getBadDisksByType(nodeName, DiskType.DATA_DISK);
}
@Override
public List<DiskEntity> getBadDataDiskEntities(String nodeName) {
return getBadDiskEntitiesByType(nodeName, DiskType.DATA_DISK);
}
private List<DiskSpec> getBadDisksByType(String nodeName, DiskType diskType) {
List<DiskSpec> badDisks = new ArrayList<>();
List<DiskEntity> badDiskEntities = getBadDiskEntitiesByType(nodeName, diskType);
for (DiskEntity badDiskEntity : badDiskEntities) {
badDisks.add(badDiskEntity.toDiskSpec());
}
return badDisks;
}
private List<DiskEntity> getBadDiskEntitiesByType(String nodeName, DiskType diskType) {
List<DiskEntity> diskEntities = clusterEntityMgr.getDisks(nodeName);
List<DiskEntity> badDiskEntities = new ArrayList<>();
// scan all disks with disk type and filter out those don't have backing vmdk files or
// whoes vmdk file attaches to unaccessible datastores
for (DiskEntity diskEntity : diskEntities) {
if (diskType != null && !diskEntity.getDiskType().equals(diskType.type)) {
continue;
}
if (isBadDisk(diskEntity)) {
badDiskEntities.add(diskEntity);
}
}
return badDiskEntities;
}
private boolean isBadDisk(DiskEntity disk) {
boolean isBadDisk = false;
NodeEntity node = disk.getNodeEntity();
if (node.isNotExist()) {
logger.info("The VM " + node.getVmName() + " is not exist.");
return true;
}
if (isBadVmdkPath(disk) || isBadDataStore(disk)) {
isBadDisk = true;
logger.info("disk " + disk.getName() + " is bad as datastore " + disk.getDatastoreName() + " is not accessible");
}
return isBadDisk;
}
private boolean isBadDataStore(DiskEntity disk) {
return (disk.getDatastoreMoId() != null && !VcVmUtil.isDatastoreAccessible(disk.getDatastoreMoId()));
}
private boolean isBadVmdkPath(final DiskEntity disk) {
if (disk.getVmdkPath() == null || disk.getVmdkPath().isEmpty()) {
return true;
}
boolean isBadVmdkPath = VcContext.inVcSessionDo(new VcSession<Boolean>() {
@Override
protected Boolean body() throws Exception {
DeviceId deviceId = disk.getDiskDeviceId();
if (deviceId == null) {
return true;
}
VcVirtualMachine vm = VcCache.getIgnoreMissing(disk.getNodeEntity().getMoId());
VirtualDisk vmdk = (VirtualDisk)vm.getVirtualDevice(deviceId);
if (vmdk == null || !disk.getVmdkPath().equals(VmConfigUtil.getVmdkPath(vmdk))) {
return true;
}
return false;
}
@Override
protected boolean isTaskSession() {
return true;
}
});
return isBadVmdkPath;
}
private Map<String, List<VcDatastore>> filterDatastores(VcHost targetHost, ClusterCreate spec, String groupName, NodeEntity nodeEntity) {
Map<String, List<VcDatastore>> candidates = new HashMap<String, List<VcDatastore>>();
candidates.put(IMAGESTORE, filterDatastoresWithPattern(targetHost, spec, groupName, nodeEntity, true));
candidates.put(DISKSTORE, filterDatastoresWithPattern(targetHost, spec, groupName, nodeEntity, false));
return candidates;
}
private List<VcDatastore> filterDatastoresWithPattern(VcHost targetHost, ClusterCreate spec, String groupName, NodeEntity nodeEntity, boolean isImagestoreNamePattern) {
NodeGroupCreate groupSpec = spec.getNodeGroup(groupName);
String[] datastoreNamePatterns = null;
if (isImagestoreNamePattern) {
datastoreNamePatterns = NodeGroupCreate.getImagestoreNamePattern(spec, groupSpec);
} else {
datastoreNamePatterns = NodeGroupCreate.getDiskstoreNamePattern(spec, groupSpec);
}
List<VcDatastore> candidates = new ArrayList<VcDatastore>();
for (VcDatastore ds : targetHost.getDatastores()) {
if (!ds.isAccessible()) {
continue;
}
for (String pattern : datastoreNamePatterns) {
if (ds.getName().matches(pattern)) {
candidates.add(ds);
break;
}
}
}
return candidates;
}
private AbstractDatastore getLeastUsedDatastore(
Map<AbstractDatastore, Integer> usage, int requiredInGB) {
int min = Integer.MAX_VALUE;
AbstractDatastore result = null;
for (AbstractDatastore ads : usage.keySet()) {
if (ads.getFreeSpace() >= requiredInGB && usage.get(ads) < min) {
min = usage.get(ads);
result = ads;
}
}
return result;
}
private void findReplacementDisks(String nodeName,
List<DiskSpec> badDisks, Map<AbstractDatastore, Integer> usage) {
// reverse sort, in descending order
// bin pack problem, place large disk first.
Collections.sort(badDisks);
for (DiskSpec disk : badDisks) {
int requiredSize = disk.getSize();
AbstractDatastore ads = getLeastUsedDatastore(usage, requiredSize);
if (ads == null) {
throw ClusterHealServiceException.NOT_ENOUGH_STORAGE(nodeName,
"Cannot find a datastore with enough space to place disk "
+ disk.getName() + " of size " + disk.getSize()
+ " GB");
}
disk.setTargetDs(ads.getName());
disk.setVmdkPath(null);
// deduct space
ads.allocate(requiredSize);
// increase reference by 1
usage.put(ads, usage.get(ads) + 1);
}
}
@Override
public List<DiskSpec> getReplacementDisks(String clusterName, String groupName, String nodeName, List<DiskSpec> badDisks) {
ClusterCreate spec = configMgr.getClusterConfig(clusterName);
NodeEntity nodeEntity = clusterEntityMgr.findByName(clusterName, groupName, nodeName);
VcHost targetHost = getTargetHost(nodeEntity);
List<DiskSpec> replacementDisks = new ArrayList<DiskSpec>();
Map<String, List<VcDatastore>> validDatastores = filterDatastores(targetHost, spec, groupName, nodeEntity);
List<DiskSpec> badSystemDisks = new ArrayList<DiskSpec>();
for (DiskSpec badDisk : badDisks) {
if (badDisk.isSystemDisk() || badDisk.isSwapDisk()) {
badSystemDisks.add(badDisk);
}
}
if (!badSystemDisks.isEmpty()) {
List<DiskSpec> replacementSystemDisks = replacementDisks(clusterName, groupName, nodeName, badSystemDisks, targetHost, validDatastores.get(IMAGESTORE));
replacementDisks.addAll(replacementSystemDisks);
}
List<DiskSpec> badDataDisks = new ArrayList<DiskSpec>();
for (DiskSpec badDisk : badDisks) {
if (badDisk.isDataDisk()) {
badDataDisks.add(badDisk);
}
}
if (!badDataDisks.isEmpty()) {
List<DiskSpec> replacementDataDisks = replacementDisks(clusterName, groupName, nodeName, badDataDisks, targetHost, validDatastores.get(DISKSTORE));
replacementDisks.addAll(replacementDataDisks);
}
return replacementDisks;
}
private List<DiskSpec> replacementDisks(String clusterName, String groupName, String nodeName, List<DiskSpec> badDisks, VcHost targetHost, List<VcDatastore> validDatastores) {
// initialize env for placement algorithm
int totalSizeInGB = 0;
Map<AbstractDatastore, Integer> usage = new HashMap<AbstractDatastore, Integer>(validDatastores.size());
List<AbstractDatastore> pools = new ArrayList<AbstractDatastore>(validDatastores.size());
for (VcDatastore ds : validDatastores) {
totalSizeInGB += ds.getFreeSpace() >> 30;
AbstractDatastore ads =
new AbstractDatastore(ds.getName(),
(int) (ds.getFreeSpace() >> 30));
pools.add(ads);
usage.put(ads, 0);
}
int requiredSizeInGB = 0;
for (DiskSpec disk : badDisks) {
requiredSizeInGB += disk.getSize();
}
if (totalSizeInGB < requiredSizeInGB) {
throw ClusterHealServiceException.NOT_ENOUGH_STORAGE(nodeName,
"" + requiredSizeInGB + " GB storage is required on host "
+ targetHost.getName() + ", but only " + totalSizeInGB
+ " GB available");
}
List<DiskEntity> goodDisks = clusterEntityMgr.getDisks(nodeName);
// collects datastore usages
for (DiskEntity disk : goodDisks) {
boolean bad = false;
for (DiskSpec diskSpec : badDisks) {
if (StringUtils.equals(disk.getName(), diskSpec.getName())) {
bad = true;
break;
}
}
// ignore bad disks
if (bad)
continue;
AbstractDatastore targetAds = null;
for (AbstractDatastore ads : pools) {
if (ads.getName().equals(disk.getDatastoreName())) {
targetAds = ads;
break;
}
}
if (targetAds == null)
logger.warn("a healthy disk " + disk.getName()
+ " used a datastore " + disk.getDatastoreName()
+ "that is not specified in cluster spec");
else {
Integer val = usage.get(targetAds);
usage.put(targetAds, val + 1);
}
}
findReplacementDisks(nodeName, badDisks, usage);
return badDisks;
}
private VcDatastore getTargetDatastore(List<DiskSpec> fullDiskList) {
String datastore = null;
for (DiskSpec disk : fullDiskList) {
if (DiskType.SYSTEM_DISK.equals(disk.getDiskType())) {
datastore = disk.getTargetDs();
}
}
AuAssert.check(datastore != null);
VcDatastore ds = VcResourceUtils.findDSInVcByName(datastore);
if (ds != null) {
return ds;
}
logger.error("target data store " + datastore + " is not found.");
throw ClusteringServiceException.TARGET_VC_DATASTORE_NOT_FOUND(datastore);
}
private Folder getTargetFolder(final NodeEntity node) {
return VcContext.inVcSessionDo(new VcSession<Folder>() {
@Override
protected Folder body() throws Exception {
if (node.getMoId() != null) {
VcVirtualMachine vm = VcCache.get(node.getMoId());
return vm.getParentFolder();
}
String folderPath = node.getNodeGroup().getVmFolderPath();
try {
List<String> folderNames = Arrays.asList(folderPath.split("/"));
AuAssert.check(folderNames.size() == 3);
VcVirtualMachine templateVm = VcCache.get(node.getNodeGroup().getCluster().getTemplateId());
VcDatacenter dc = templateVm.getDatacenter();
return VcResourceUtils.findFolderByNameList(dc, folderNames);
} catch (Exception e) {
logger.error("error in finding folder " + folderPath , e);
throw BddException.INTERNAL(e, e.getMessage());
}
}
});
}
private VcHost getTargetHost(NodeEntity node) {
String targetHostName = node.getHostName();
VcHost targetHost = VcResourceUtils.findHost(targetHostName);
if (targetHost == null) {
logger.error("Cannot find the vCenter Server host " + targetHostName + " for node " + node.getVmName());
throw ClusterHealServiceException.TARGET_VC_HOST_NOT_FOUND(targetHostName, node.getVmName());
}
return targetHost;
}
private CreateVmSP getReplacementVmSp(ClusterCreate clusterSpec,
String groupName, NodeEntity node, List<DiskSpec> fullDiskSet) {
VmSchema createSchema =
VcVmUtil.getVmSchema(clusterSpec, groupName, fullDiskSet,
node.getNodeGroup().getCluster().getTemplateId(),
Constants.ROOT_SNAPSTHOT_NAME);
Map<String, String> guestVariable = generateMachineId(clusterSpec, node);
// TODO: rafactor this function
VcVmUtil.addBootupUUID(guestVariable);
boolean ha = getHaFlag(clusterSpec, groupName);
boolean ft = getFtFlag(clusterSpec, groupName);
boolean isMapDistro = clusterEntityMgr.findByName(clusterSpec.getName()).getDistroVendor().equalsIgnoreCase(Constants.MAPR_VENDOR);
// delete old vm and rename new vm in the prePowerOn
ReplaceVmPrePowerOn prePowerOn =
new ReplaceVmPrePowerOn(node.getMoId(), node.getVmName(),
clusterSpec.getNodeGroup(groupName).getStorage().getShares(),
createSchema.networkSchema, createSchema.diskSchema, ha, ft, isMapDistro);
// power on the new vm, but not wait for ip address here. we have startVmStep to wait for ip
String newVmName = node.getVmName();
if (node.getMoId() != null && !node.getMoId().isEmpty()) {
newVmName = node.getVmName() + RECOVERY_VM_NAME_POSTFIX;
}
return new CreateVmSP(newVmName,
createSchema, VcVmUtil.getTargetRp(clusterSpec.getName(),
groupName, node), getTargetDatastore(fullDiskSet),
prePowerOn, null, guestVariable, VcVmCloneType.FULL, true, getTargetFolder(node),
getTargetHost(node));
}
@Override
@SuppressWarnings("unchecked")
public VcVirtualMachine createReplacementVm(String clusterName,
String groupName, String nodeName, List<DiskSpec> replacementDisks) {
ClusterCreate spec = configMgr.getClusterConfig(clusterName);
NodeEntity node = clusterEntityMgr.findByName(spec.getName(), groupName, nodeName);
List<DiskSpec> fullDiskList = getReplacedFullDisks(node.getVmName(), replacementDisks);
CreateVmSP cloneVmSp = getReplacementVmSp(spec, groupName, node, fullDiskList);
CompensateCreateVmSP deleteVmSp = new CompensateCreateVmSP(cloneVmSp);
Pair<Callable<Void>, Callable<Void>>[] storeProcedures = new Pair[1];
storeProcedures[0] =
new Pair<Callable<Void>, Callable<Void>>(cloneVmSp, deleteVmSp);
// execute store procedures to create VMs
logger.info("ClusterHealService, start to create replacement vm for node "
+ nodeName);
Pair<ExecutionResult, ExecutionResult>[] result;
try {
result =
Scheduler
.executeStoredProcedures(
com.vmware.aurora.composition.concurrent.Priority.BACKGROUND,
storeProcedures, 1, null);
if (result == null) {
logger.error("vm creation failed for node " + nodeName);
return null;
}
Pair<ExecutionResult, ExecutionResult> pair = result[0];
CreateVmSP sp = (CreateVmSP) storeProcedures[0].first;
if (pair.first.finished && pair.first.throwable == null
&& pair.second.finished == false) {
VcVirtualMachine vm = sp.getVM();
AuAssert.check(vm != null);
return vm;
} else if (pair.first.throwable != null) {
logger.error(
"Failed to create replacement virtual machine for node " + node.getVmName(),
pair.first.throwable);
throw ClusterHealServiceException.FAILED_CREATE_REPLACEMENT_VM(node
.getVmName());
}
} catch (InterruptedException e) {
logger.error("error in fixing vm " + nodeName, e);
throw BddException.INTERNAL(e, e.getMessage());
}
return null;
}
@Override
public void updateData(String clusterName, String groupName,
String nodeName, String newVmId) {
NodeEntity node = clusterEntityMgr.findNodeByName(nodeName);
logger.info("start update vm id and host info for node " + nodeName);
VcVirtualMachine vm = ClusterUtil.getVcVm(clusterEntityMgr, node);
node.setMoId(vm.getId());
node.setHostName(vm.getHost().getName());
clusterEntityMgr.update(node);
logger.info("sync up status for node " + nodeName);
clusterEntityMgr.syncUpNode(clusterName, nodeName);
List<DiskEntity> fullDiskSet = clusterEntityMgr.getDisks(nodeName);
for (DiskEntity disk : fullDiskSet) {
VcVmUtil.populateDiskInfo(disk, newVmId);
}
clusterEntityMgr.updateDisks(nodeName, fullDiskSet);
}
@Override
public boolean fixDiskFailures(String clusterName, String groupName,
String nodeName, List<DiskSpec> fullDiskSet) {
return true;
}
@Override
public void verifyNodeStatus(String vmId, String nodeName) {
NodeEntity nodeEntity = clusterEntityMgr.findNodeByName(nodeName);
JobUtils.verifyNodeStatus(nodeEntity, NodeStatus.VM_READY, false, clusterEntityMgr);
}
@Override
public void startVm(String nodeName, String vmId, String clusterName) {
NodeEntity nodeEntity = clusterEntityMgr.findNodeByName(nodeName);
StartVmPostPowerOn query =
new StartVmPostPowerOn(nodeEntity.fetchAllPortGroups(),
Configuration.getInt(Constants.VM_POWER_ON_WAITING_SEC_KEY, Constants.VM_POWER_ON_WAITING_SEC),
clusterEntityMgr);
VcVirtualMachine vcVm = ClusterUtil.getVcVm(clusterEntityMgr, nodeEntity);
if (vcVm == null) {
logger.error("VC vm does not exist for vmId: " + vmId);
}
VcHost host = vcVm.getHost();
Callable<Void>[] storeProceduresArray = new Callable[1];
storeProceduresArray[0] = new StartVmSP(vcVm, null, query, host);
NoProgressUpdateCallback callback = new NoProgressUpdateCallback();
try {
ExecutionResult[] result =
Scheduler
.executeStoredProcedures(
com.vmware.aurora.composition.concurrent.Priority.BACKGROUND,
storeProceduresArray, callback);
if (result == null) {
logger.error("No result from composition layer");
} else {
if (result[0].finished && result[0].throwable == null) {
logger.info("successfully power on vm for node: " + nodeName);
} else {
logger.error("failed in powering on vm for node: " + nodeName,
result[0].throwable);
throw ClusterHealServiceException.FAILED_POWER_ON_VM(nodeName);
}
}
} catch (InterruptedException e) {
logger.error("error in run operation on vm.", e);
throw ClusterHealServiceException.INTERNAL(e,
"error in executing startVmSp");
}
}
@Override
@SuppressWarnings("unchecked")
public VcVirtualMachine replaceBadDisksExceptSystem(String clusterName, String groupName, String nodeName, List<DiskSpec> replacementDisks) {
ClusterCreate spec = configMgr.getClusterConfig(clusterName);
NodeEntity node = clusterEntityMgr.findByName(spec.getName(), groupName, nodeName);
List<DiskSpec> fullDiskList = getReplacedFullDisks(node.getVmName(), replacementDisks);
VmSchema createSchema = VcVmUtil.getVmSchema(spec, groupName, fullDiskList, node.getNodeGroup().getCluster().getTemplateId(), Constants.ROOT_SNAPSTHOT_NAME);
boolean isMapDistro = clusterEntityMgr.findByName(clusterName).getDistroVendor().equalsIgnoreCase(Constants.MAPR_VENDOR);
ReplaceVmBadDisksSP replaceVmDisksPrePowerOnSP = new ReplaceVmBadDisksSP(node,
createSchema.diskSchema, VcVmUtil.getTargetRp(spec.getName(), groupName, node),
getTargetDatastore(fullDiskList),
getBadDataDiskEntities(node.getVmName()), isMapDistro);
try {
Callable<Void>[] storeProceduresArray = new Callable[1];
storeProceduresArray[0] = replaceVmDisksPrePowerOnSP;
ExecutionResult[] result = Scheduler.executeStoredProcedures(com.vmware.aurora.composition.concurrent.Priority.BACKGROUND, storeProceduresArray, null);
if (result == null) {
logger.error("Failed to replace bad data disks for node " + nodeName);
return null;
}
Throwable replacedDataDisksVmSpException = result[0].throwable;
if (result[0].finished && replacedDataDisksVmSpException == null) {
ReplaceVmBadDisksSP sp = (ReplaceVmBadDisksSP) storeProceduresArray[0];
VcVirtualMachine vm = sp.getVm();
AuAssert.check(vm != null);
return vm;
} else {
logger.error("Failed to replace bad data disks for node " + node.getVmName(), replacedDataDisksVmSpException);
throw ClusterHealServiceException.FAILED_TO_REPLACE_BAD_DATA_DISKS(node.getVmName());
}
} catch (InterruptedException e) {
logger.error("error in fixing vm " + nodeName, e);
throw BddException.INTERNAL(e, e.getMessage());
}
}
@Override
public VcVirtualMachine getFixingVm(String clusterName,
String groupName, String nodeName) {
NodeEntity node = clusterEntityMgr.findNodeByName(nodeName);
VcResourcePool rp = VcVmUtil.getTargetRp(clusterName, groupName, node);
String recoverVmName = node.getVmName() + RECOVERY_VM_NAME_POSTFIX;
if (node.getMoId() != null) {
VcVirtualMachine vm = getVmFromVc(node);
// Destroy recover VM from VC
destroyVMFromVc(rp, nodeName, recoverVmName);
if (hasBadDisksExceptSystem(nodeName)) {
return vm;
} else {
return null;
}
} else {
return getFixingVmFromRp(rp, nodeName, recoverVmName);
}
}
private void destroyVMFromVc(VcResourcePool rp, String nodeName, String recoverVmName) {
VcVirtualMachine recoverVm = VcVmUtil.findVmInRp(rp, recoverVmName);
if (recoverVm != null) {
try {
VcVmUtil.destroyVm(recoverVm.getId(), false);
} catch (Exception e) {
logger.error("failed to remove obsolete recovery vm for node " + nodeName);
throw ClusterHealServiceException.FAILED_DELETE_VM(recoverVmName);
}
}
}
private VcVirtualMachine getVmFromVc(NodeEntity node) {
VcVirtualMachine vm = VcCache.getIgnoreMissing(node.getMoId());
// the vm id is null if the vm is removed
if (vm == null) {
throw ClusterHealServiceException.ERROR_STATUS(node.getVmName(),
"Serengeti and VC are inconsistent as vm " + node.getVmName() + " is recorded in Seregeti, but not found in VC.");
}
return vm;
}
private VcVirtualMachine getFixingVmFromRp(VcResourcePool rp, String nodeName, String recoverVmName) {
VcVirtualMachine oldVm = VcVmUtil.findVmInRp(rp, nodeName);
VcVirtualMachine recoverVm = VcVmUtil.findVmInRp(rp, recoverVmName);
if (oldVm != null && recoverVm != null) {
destroyVMFromVc(rp, nodeName, recoverVmName);
logger.info("Delete recover vm if " + "vm " + oldVm.getId() + " and recover vm " + recoverVm.getId() + " both exist");
return null;
} else if (oldVm == null && recoverVm == null) {
logger.info("Original vm and recover vm both not exist.");
return null;
} else if (recoverVm != null) {
logger.info("recover vm " + recoverVm.getId() + " exists, rename it to " + nodeName);
VcVmUtil.rename(recoverVm.getId(), nodeName);
return recoverVm;
} else {
logger.info("recovery probably failed at power on last time, simply return vm " + oldVm.getId());
return oldVm;
}
}
// replace bad disks with fixing disk, combining as a new disk set
private List<DiskSpec> getReplacedFullDisks(String nodeName, List<DiskSpec> replacementDisks) {
List<DiskSpec> fullDiskList = VcVmUtil.toDiskSpecList(clusterEntityMgr.getDisks(nodeName));
for(DiskSpec diskSpec : fullDiskList) {
//find the disk with same id, replace the target Ds
for(DiskSpec replaceDiskEntity : replacementDisks) {
if(diskSpec.getId() == replaceDiskEntity.getId()) {
diskSpec.setTargetDs(replaceDiskEntity.getTargetDs());
diskSpec.setVmdkPath(replaceDiskEntity.getVmdkPath());
break;
}
}
}
return fullDiskList;
}
private Map<String, String> generateMachineId(ClusterCreate clusterSpec, NodeEntity node) {
List<NetworkAdd> networkAdds = clusterSpec.getNetworkings();
// this getRepalcementVmSP() is not called by any UT, so no need to check NPE for node.getIpConfigsInfo()
GuestMachineIdSpec machineIdSpec = new GuestMachineIdSpec(networkAdds,
node.fetchPortGroupToIpMap(), node.getPrimaryMgtNic().getNetworkEntity().getPortGroup(), node, networkMgr);
logger.info("machine id of vm " + node.getVmName() + ":\n" + machineIdSpec.toString());
Map<String, String> guestVariable = machineIdSpec.toGuestVariable();
return guestVariable;
}
private boolean getHaFlag(ClusterCreate clusterSpec, String groupName) {
boolean ha = false;
String haFlag = clusterSpec.getNodeGroup(groupName).getHaFlag();
if (haFlag != null && Constants.HA_FLAG_ON.equals(haFlag.toLowerCase())) {
ha = true;
}
if (haFlag != null && Constants.HA_FLAG_FT.equals(haFlag.toLowerCase())) {
ha = true;
}
return ha;
}
private boolean getFtFlag(ClusterCreate clusterSpec, String groupName) {
boolean ft = false;
String haFlag = clusterSpec.getNodeGroup(groupName).getHaFlag();
if (haFlag != null && Constants.HA_FLAG_FT.equals(haFlag.toLowerCase())) {
ft = true;
}
return ft;
}
}