com.vmware.bdd.service.impl.ClusteringService.java Source code

Java tutorial

Introduction

Here is the source code for com.vmware.bdd.service.impl.ClusteringService.java

Source

/***************************************************************************
 * 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.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.vmware.aurora.vc.VcCluster;
import com.vmware.bdd.apitypes.*;
import com.vmware.bdd.exception.ClusterConfigException;
import com.vmware.bdd.manager.*;
import com.vmware.bdd.service.resmgmt.IVcInventorySyncService;
import com.vmware.bdd.service.resmgmt.sync.filter.VcResourceFilters;
import com.vmware.bdd.spectypes.*;
import com.vmware.bdd.spectypes.DiskSpec;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;

import com.google.gson.Gson;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.vmware.aurora.composition.CreateVMFolderSP;
import com.vmware.aurora.composition.DeleteVMFolderSP;
import com.vmware.aurora.composition.DiskSchema;
import com.vmware.aurora.composition.DiskSchema.Disk;
import com.vmware.aurora.composition.NetworkSchema.Network;
import com.vmware.aurora.composition.VmSchema;
import com.vmware.aurora.composition.concurrent.ExecutionResult;
import com.vmware.aurora.composition.concurrent.Scheduler;
import com.vmware.aurora.global.Configuration;
import com.vmware.aurora.util.worker.CmsWorker;
import com.vmware.aurora.vc.*;
import com.vmware.aurora.vc.vcevent.VcEventRouter;
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.IpBlock;
import com.vmware.bdd.apitypes.NetworkAdd;
import com.vmware.bdd.apitypes.NodeGroupCreate;
import com.vmware.bdd.apitypes.Priority;
import com.vmware.bdd.apitypes.StorageRead.DiskType;
import com.vmware.bdd.clone.spec.VmCreateResult;
import com.vmware.bdd.clone.spec.VmCreateSpec;
import com.vmware.bdd.dal.IResourcePoolDAO;
import com.vmware.bdd.entity.ClusterEntity;
import com.vmware.bdd.entity.NicEntity;
import com.vmware.bdd.entity.NodeEntity;
import com.vmware.bdd.entity.resmgmt.ResourceReservation;
import com.vmware.bdd.exception.BddException;
import com.vmware.bdd.exception.ClusteringServiceException;
import com.vmware.bdd.exception.VcProviderException;
import com.vmware.bdd.manager.intf.IClusterEntityManager;
import com.vmware.bdd.manager.intf.IConcurrentLockedClusterEntityManager;
import com.vmware.bdd.placement.Container;
import com.vmware.bdd.placement.entity.AbstractDatacenter.AbstractHost;
import com.vmware.bdd.placement.entity.BaseNode;
import com.vmware.bdd.placement.exception.PlacementException;
import com.vmware.bdd.placement.interfaces.IPlacementService;
import com.vmware.bdd.placement.util.ContainerToStringHelper;
import com.vmware.bdd.placement.util.PlacementUtil;
import com.vmware.bdd.service.ClusterNodeUpdator;
import com.vmware.bdd.service.IClusterInitializerService;
import com.vmware.bdd.service.IClusteringService;
import com.vmware.bdd.service.event.VmEventManager;
import com.vmware.bdd.service.job.NodeOperationStatus;
import com.vmware.bdd.service.job.StatusUpdater;
import com.vmware.bdd.service.resmgmt.INetworkService;
import com.vmware.bdd.service.resmgmt.INodeTemplateService;
import com.vmware.bdd.service.resmgmt.IResourceService;
import com.vmware.bdd.service.sp.*;
import com.vmware.bdd.service.utils.VcResourceUtils;
import com.vmware.bdd.software.mgmt.plugin.intf.SoftwareManager;
import com.vmware.bdd.specpolicy.GuestMachineIdSpec;
import com.vmware.bdd.utils.AuAssert;
import com.vmware.bdd.utils.ClusterUtil;
import com.vmware.bdd.utils.CommonUtil;
import com.vmware.bdd.utils.ConfigInfo;
import com.vmware.bdd.utils.Constants;
import com.vmware.bdd.utils.JobUtils;
import com.vmware.bdd.utils.VcVmUtil;
import com.vmware.bdd.utils.Version;
import com.vmware.bdd.vmclone.service.intf.IClusterCloneService;
import com.vmware.vim.binding.vim.Folder;
import com.vmware.vim.binding.vim.vm.device.VirtualDevice;
import com.vmware.vim.binding.vim.vm.device.VirtualDisk;
import com.vmware.vim.binding.vim.vm.device.VirtualDiskOption.DiskMode;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ClusteringService implements IClusteringService {
    private static final int VC_RP_MAX_NAME_LENGTH = 80;
    private static final Logger logger = Logger.getLogger(ClusteringService.class);
    private ClusterConfigManager configMgr;

    private IClusterEntityManager clusterEntityMgr;
    private IConcurrentLockedClusterEntityManager lockClusterEntityMgr;
    private IResourcePoolDAO rpDao;
    private INetworkService networkMgr;
    private IResourceService resMgr;
    private IPlacementService placementService;
    private IClusterInitializerService clusterInitializerService;
    private ElasticityScheduleManager elasticityScheduleMgr;
    @Autowired
    private INodeTemplateService nodeTemplateService;

    private volatile boolean inited = false;
    private volatile Throwable initError;

    private int cloneConcurrency;
    private VmEventManager processor;

    private Map<String, IClusterCloneService> cloneServiceMap;

    private ClusterManager clusterManager;

    private SoftwareManagerCollector softwareManagerCollector;

    @Autowired
    private ClusterNodeUpdator clusterNodeUpdator;

    @Autowired
    private CmsWorker cmsWorker;

    @Autowired
    private IVcInventorySyncService syncService;

    @Autowired
    private VcResourceManager vcResourceManager;

    @Autowired
    private VcResourceFilterBuilder vcResourceFilterBuilder;

    @Autowired
    public void setClusterManager(ClusterManager clusterManager) {
        this.clusterManager = clusterManager;
    }

    public ClusterManager getClusterManager() {
        return this.clusterManager;
    }

    public INetworkService getNetworkMgr() {
        return networkMgr;
    }

    public void setNetworkMgr(INetworkService networkMgr) {
        this.networkMgr = networkMgr;
    }

    public ClusterConfigManager getConfigMgr() {
        return configMgr;
    }

    public void setConfigMgr(ClusterConfigManager configMgr) {
        this.configMgr = configMgr;
    }

    @Autowired
    public void setResMgr(IResourceService resMgr) {
        this.resMgr = resMgr;
    }

    @Autowired
    public void setPlacementService(IPlacementService placementService) {
        this.placementService = placementService;
    }

    public IClusterInitializerService getClusterInitializerService() {
        return clusterInitializerService;
    }

    @Autowired
    public void setClusterInitializerService(IClusterInitializerService clusterInitializerService) {
        this.clusterInitializerService = clusterInitializerService;
    }

    public IClusterEntityManager getClusterEntityMgr() {
        return clusterEntityMgr;
    }

    @Autowired
    public void setClusterEntityMgr(IClusterEntityManager clusterEntityMgr) {
        this.clusterEntityMgr = clusterEntityMgr;
    }

    public IConcurrentLockedClusterEntityManager getLockClusterEntityMgr() {
        return lockClusterEntityMgr;
    }

    @Autowired
    public void setLockClusterEntityMgr(IConcurrentLockedClusterEntityManager lockClusterEntityMgr) {
        this.lockClusterEntityMgr = lockClusterEntityMgr;
    }

    public IResourcePoolDAO getRpDao() {
        return rpDao;
    }

    @Autowired
    public void setRpDao(IResourcePoolDAO rpDao) {
        this.rpDao = rpDao;
    }

    public Map<String, IClusterCloneService> getCloneService() {
        return cloneServiceMap;
    }

    /**
     *
     * @param cloneServiceMap clone service qualifier to clone service map.
     * 3 clone services so far: simpleClusterCloneService, fastClusterCloneService, instantClusterCloneService
     */
    @Autowired
    public void setCloneService(Map<String, IClusterCloneService> cloneServiceMap) {
        this.cloneServiceMap = cloneServiceMap;
    }

    public ElasticityScheduleManager getElasticityScheduleManager() {
        return elasticityScheduleMgr;
    }

    @Autowired
    public void setElasticityScheduleManager(ElasticityScheduleManager elasticityScheduleMgr) {
        this.elasticityScheduleMgr = elasticityScheduleMgr;
    }

    public SoftwareManagerCollector getSoftwareManagerCollector() {
        return softwareManagerCollector;
    }

    @Autowired
    public void setSoftwareManagerCollector(SoftwareManagerCollector softwareManagerCollector) {
        this.softwareManagerCollector = softwareManagerCollector;
    }

    @Override
    public boolean isInited() {
        return inited;
    }

    @Override
    public Throwable getInitError() {
        return initError;
    }

    @Override
    public synchronized void init() {
        if (!inited) {
            try {
                // XXX hack to approve bootstrap instance id, should be moved out of Configuration
                Configuration.approveBootstrapInstanceId(Configuration.BootstrapUsage.ALLOWED);
                Configuration.approveBootstrapInstanceId(Configuration.BootstrapUsage.FINALIZED);

                VcContext.initVcContext();
                new VcEventRouter();

                //disable background VC refresh.
                //CmsWorker.addPeriodic(new VcInventory.SyncInventoryRequest());
                VcInventory.loadInventory();

                //why??
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    logger.warn("interupted during sleep " + e.getMessage());
                }
                startVMEventProcessor();

                int poolSize = Configuration.getInt("serengeti.scheduler.poolsize",
                        Constants.DEFAULT_SCHEDULER_POOL_SIZE);
                Scheduler.init(poolSize, poolSize);

                cloneConcurrency = Configuration.getInt("serengeti.singlevm.concurrency", 1);

                // refresh the cluster nodes once on bde startup,
                // then add the periodic processing with default 5 minute interval
                clusterNodeUpdator.syncAllClusters();

                // initialize uuid
                initUUID();

                // refresh node templates on BDE startup
                nodeTemplateService.refreshNodeTemplates();

                clusterInitializerService.transformClusterStatus();
                elasticityScheduleMgr.start();
                configureAlarm();
                inited = true;
            } catch (Throwable err) {
                logger.error("init ClusteringService error", err);
                initError = err;
            }
        }
    }

    private void startVMEventProcessor() {
        // add event handler for Serengeti after VC event handler is registered.
        processor = new VmEventManager(lockClusterEntityMgr);
        processor.setClusterManager(clusterManager);
        processor.setSoftwareManagerCollector(softwareManagerCollector);
        processor.start();
    }

    private void configureAlarm() {
        VcDatacenter dc = VcResourceUtils.getCurrentDatacenter();
        List<String> folderList = new ArrayList<String>(1);
        String serengetiUUID = ConfigInfo.getSerengetiRootFolder();
        folderList.add(serengetiUUID);
        Folder rootFolder = VcResourceUtils.findFolderByNameList(dc, folderList);

        if (rootFolder == null) {
            CreateVMFolderSP sp = new CreateVMFolderSP(dc, null, folderList);
            Callable<Void>[] storeProcedures = new Callable[1];
            storeProcedures[0] = sp;
            Map<String, Folder> folders = executeFolderCreationProcedures(null, storeProcedures);
            AuAssert.check(folders.size() == 1);
            rootFolder = folders.get(serengetiUUID);
            AuAssert.check(rootFolder != null);
        }

        final Folder root = rootFolder;
        VcContext.inVcSessionDo(new VcSession<Boolean>() {
            @Override
            protected boolean isTaskSession() {
                return true;
            }

            @Override
            protected Boolean body() throws Exception {
                VcUtil.configureAlarm(root);
                return true;
            }
        });

    }

    @Override
    synchronized public void destroy() {
        Scheduler.shutdown(true);
        processor.stop();
        elasticityScheduleMgr.shutdown();
    }

    private void initUUID() {
        if (ConfigInfo.isInitUUID()) {
            String uuid = null;
            final VcVirtualMachine serverVm = VcResourceUtils.findServerVM();
            if (ConfigInfo.isDeployAsVApp()) {
                VcResourcePool vApp = serverVm.getParentVApp();
                uuid = vApp.getName();
            } else {
                Folder parentFolder = VcResourceUtils.findParentFolderOfVm(serverVm);
                AuAssert.check(parentFolder != null);
                uuid = parentFolder.getName();
            }

            ConfigInfo.setSerengetiUUID(uuid);
            ConfigInfo.setInitUUID(false);
            ConfigInfo.save();
        }
    }

    private VcVirtualMachine getTemplateVM(String templateName) {
        return this.nodeTemplateService.getNodeTemplateVMByName(templateName);
    }

    private VcVirtualMachine prepareTemplateVM(String templateName) {
        VcVirtualMachine templateVM = getTemplateVM(templateName);
        // update vm info in vc cache, in case snapshot is removed by others
        VcVmUtil.updateVm(templateVM.getId());

        try {
            boolean needRemoveSnapshots = false;
            if (ConfigInfo.isJustUpgraded()) {
                needRemoveSnapshots = true;
                ConfigInfo.setJustUpgraded(false);
                ConfigInfo.save();
            }

            if (VcVmUtil.enableOvfEnvTransport(templateVM)) {
                needRemoveSnapshots = true;
            }

            if (VcVmUtil.enableSyncTimeWithHost(templateVM)) {
                needRemoveSnapshots = true;
            }

            if (needRemoveSnapshots) {
                removeRootSnapshot(templateVM);
            }
            return templateVM;
        } catch (Exception e) {
            logger.error("Prepare template VM error: " + e.getMessage());
            throw BddException.INTERNAL(e, "Prepare template VM error.");
        }
    }

    private void removeRootSnapshot(final VcVirtualMachine templateVM) {
        VcContext.inVcSessionDo(new VcSession<Boolean>() {
            @Override
            protected boolean isTaskSession() {
                return true;
            }

            @Override
            protected Boolean body() throws Exception {
                VcSnapshot snapshot = templateVM.getSnapshotByName(Constants.ROOT_SNAPSTHOT_NAME);
                if (snapshot != null) {
                    snapshot.remove();
                }
                return true;
            }
        });
    }

    private BaseNode createBaseNodeFromTemplateVm(final VcVirtualMachine templateVm) {
        BaseNode templateNode = new BaseNode(templateVm.getName());
        List<DiskSpec> diskSpecs = new ArrayList<DiskSpec>();
        for (DeviceId slot : templateVm.getVirtualDiskIds()) {
            VirtualDisk vmdk = (VirtualDisk) templateVm.getVirtualDevice(slot);
            DiskSpec spec = new DiskSpec();
            spec.setSize((int) (vmdk.getCapacityInKB() / (1024 * 1024)));
            spec.setDiskType(DiskType.SYSTEM_DISK);
            spec.setController(CommonUtil.getSystemAndSwapControllerType());
            diskSpecs.add(spec);
        }
        templateNode.setDisks(diskSpecs);
        return templateNode;
    }

    private String getNodeTemplateNetworkLable(final VcVirtualMachine templateVm) {
        return VcContext.inVcSessionDo(new VcSession<String>() {
            @Override
            protected String body() throws Exception {
                VirtualDevice[] devices = templateVm.getConfig().getHardware().getDevice();
                for (VirtualDevice device : devices) {
                    if (device.getKey() == 4000) {
                        return device.getDeviceInfo().getLabel();
                    }
                }
                return null;
            }
        });
    }

    private void updateNicLabels(List<BaseNode> vNodes, String templateNetworkLabel) {
        logger.info("Start to set network schema for template nic.");
        for (BaseNode node : vNodes) {
            List<Network> networks = node.getVmSchema().networkSchema.networks;
            if (!networks.isEmpty()) {
                networks.get(0).nicLabel = templateNetworkLabel;
                for (int i = 1; i < networks.size(); i++) {
                    networks.get(i).nicLabel = VcVmUtil.NIC_LABEL_PREFIX + (i + 1);
                }
            }
            logger.info(node.getVmSchema());
        }
    }

    /**
     * cluster create, resize, resume will all call this method for static ip
     * allocation the network contains all allocated ip address to this cluster,
     * so some of them may already be occupied by existing node. So we need to
     * detect if that ip is allocated, before assign that one to one node
     *
     * @param vNodes
     * @param networkAdds
     * @param occupiedIpSets
     */
    private void allocateStaticIp(List<BaseNode> vNodes, List<NetworkAdd> networkAdds,
            Map<String, Set<String>> occupiedIpSets) {
        int i, j;
        for (i = 0; i < networkAdds.size(); i++) {
            NetworkAdd networkAdd = networkAdds.get(i);
            String portGroupName = networkAdd.getPortGroup();
            Set<String> usedIps = null;
            if (occupiedIpSets != null && !occupiedIpSets.isEmpty()) {
                usedIps = occupiedIpSets.get(portGroupName);
            }

            if (networkAdd.getIsDhcp()) {
                // no need to allocate ip for dhcp
                logger.info("using dhcp for network: " + portGroupName);
            } else {
                logger.info("Start to allocate static ip address for each VM's " + i + "th network.");
                List<String> availableIps = IpBlock.getIpAddressFromIpBlock(networkAdd.getIpBlocks());
                if (usedIps != null && !usedIps.isEmpty()) {
                    availableIps.removeAll(usedIps);
                }
                AuAssert.check(availableIps.size() == vNodes.size());
                for (j = 0; j < availableIps.size(); j++) {
                    vNodes.get(j).updateNicOfPortGroup(portGroupName, availableIps.get(j), null, null);
                }
                logger.info("Finished to allocate static ip address for VM's mgr network.");
            }
        }
    }

    private String getMaprActiveJobTrackerIp(final String maprNodeIP, final String clusterName) {
        String activeJobTrackerIp = "";
        String errorMsg = "";
        JSch jsch = new JSch();
        String sshUser = Configuration.getString("mapr.ssh.user", "serengeti");
        int sshPort = Configuration.getInt("mapr.ssh.port", 22);
        String sudoCmd = CommonUtil.getCustomizedSudoCmd();

        String prvKeyFile = Configuration.getString("serengeti.ssh.private.key.file",
                "/home/serengeti/.ssh/id_rsa");
        Session session = null;
        ChannelExec channel = null;
        BufferedReader in = null;
        try {
            session = jsch.getSession(sshUser, maprNodeIP, sshPort);
            jsch.addIdentity(prvKeyFile);
            java.util.Properties config = new java.util.Properties();
            config.put("StrictHostKeyChecking", "no");
            session.setConfig(config);
            session.setTimeout(15000);
            session.connect();
            logger.debug("SSH session is connected!");
            channel = (ChannelExec) session.openChannel("exec");
            if (channel != null) {
                logger.debug("SSH channel is connected!");
                StringBuffer buff = new StringBuffer();
                String cmd = "maprcli node list -filter \"[rp==/*]and[svc==jobtracker]\" -columns ip";
                logger.debug("exec command is: " + cmd);
                channel.setPty(true); //to enable sudo
                channel.setCommand(sudoCmd + " " + cmd);
                in = new BufferedReader(new InputStreamReader(channel.getInputStream()));
                channel.connect();
                if (!canChannelConnect(channel)) {
                    errorMsg = "Cannot determine IP address of active JobTracker. No SSH channel connection.";
                    logger.error(errorMsg);
                    throw BddException.INTERNAL(null, errorMsg);
                }
                while (true) {
                    String line = in.readLine();
                    buff.append(line);
                    logger.debug("jobtracker message: " + line);
                    if (channel.isClosed()) {
                        int exitStatus = channel.getExitStatus();
                        logger.debug("Exit status from exec is: " + exitStatus);
                        break;
                    }
                }
                Pattern ipPattern = Pattern.compile(Constants.IP_PATTERN);
                Matcher matcher = ipPattern.matcher(buff.toString());
                if (matcher.find()) {
                    activeJobTrackerIp = matcher.group();
                } else {
                    errorMsg = "Cannot find jobtracker ip info in cluster" + clusterName;
                    logger.error(errorMsg);
                    throw BddException.INTERNAL(null, errorMsg);
                }
            } else {
                errorMsg = "Get active Jobtracker ip: cannot open SSH channel.";
                logger.error(errorMsg);
                throw BddException.INTERNAL(null, errorMsg);
            }
        } catch (JSchException e) {
            errorMsg = "SSH unknow error: " + e.getMessage();
            logger.error(errorMsg);
            throw BddException.INTERNAL(null, errorMsg);
        } catch (IOException e) {
            errorMsg = "Obtain active jobtracker ip error: " + e.getMessage();
            logger.error(errorMsg);
            throw BddException.INTERNAL(null, errorMsg);
        } finally {
            if (channel != null && channel.isConnected()) {
                channel.disconnect();
            }
            if (session != null && session.isConnected()) {
                session.disconnect();
            }
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    errorMsg = "Failed to release buffer while retrieving MapR JobTracker Port: " + e.getMessage();
                    logger.error(errorMsg);
                }
            }
        }
        return activeJobTrackerIp;
    }

    private boolean canChannelConnect(ChannelExec channel) {
        if (channel == null) {
            return false;
        }
        if (channel.isConnected()) {
            return true;
        }
        try {
            channel.connect();
        } catch (JSchException e) {
            String errorMsg = "SSH connection failed: " + e.getMessage();
            logger.error(errorMsg);
            throw BddException.INTERNAL(null, errorMsg);
        }
        return channel.isConnected();
    }

    private void updateVhmMasterMoid(String clusterName) {
        ClusterEntity cluster = getClusterEntityMgr().findByName(clusterName);
        if (cluster.getVhmMasterMoid() == null) {
            List<NodeEntity> nodes = getClusterEntityMgr().findAllNodes(clusterName);
            for (NodeEntity node : nodes) {
                if (node.getMoId() != null && node.getNodeGroup().getRoles() != null) {
                    @SuppressWarnings("unchecked")
                    List<String> roles = new Gson().fromJson(node.getNodeGroup().getRoles(), List.class);
                    if (cluster.getDistroVendor().equalsIgnoreCase(Constants.MAPR_VENDOR)) {
                        if (roles.contains(HadoopRole.MAPR_JOBTRACKER_ROLE.toString())) {
                            String thisJtIp = node.getPrimaryMgtIpV4();
                            String activeJtIp;
                            try {
                                activeJtIp = getMaprActiveJobTrackerIp(thisJtIp, clusterName);
                                logger.info("fetched active JT Ip: " + activeJtIp);
                            } catch (Exception e) {
                                continue;
                            }

                            /*
                             * TODO: in multiple NICs env, if portgroups are isolated,
                             * may not able to retrieve active IP.
                             */
                            AuAssert.check(!CommonUtil.isBlank(thisJtIp), "falied to query active JobTracker Ip");
                            for (NodeEntity jt : nodes) {
                                boolean isActiveJt = false;
                                for (NicEntity nicEntity : jt.getNics()) {
                                    if (nicEntity.getIpv4Address() != null
                                            && activeJtIp.equals(nicEntity.getIpv4Address())) {
                                        isActiveJt = true;
                                        break;
                                    }
                                }

                                if (isActiveJt) {
                                    cluster.setVhmMasterMoid(jt.getMoId());
                                    break;
                                }
                            }
                            break;
                        }
                    } else {
                        if (roles.contains(HadoopRole.HADOOP_JOBTRACKER_ROLE.toString())) {
                            cluster.setVhmMasterMoid(node.getMoId());
                            break;
                        }
                    }
                }
            }
        }
        getClusterEntityMgr().update(cluster);
    }

    @Override
    @SuppressWarnings("unchecked")
    public boolean setAutoElasticity(String clusterName, boolean refreshAllNodes) {
        logger.info("set auto elasticity for cluster " + clusterName);

        ClusterEntity cluster = getClusterEntityMgr().findByName(clusterName);
        List<NodeEntity> nodes = clusterEntityMgr.findAllNodes(clusterName);

        Boolean enableAutoElasticity = cluster.getAutomationEnable();
        if (enableAutoElasticity == null) {
            return true;
        }

        String masterMoId = cluster.getVhmMasterMoid();
        if (masterMoId == null) {
            // this will only occurs when creating cluster
            updateVhmMasterMoid(clusterName);
            cluster = getClusterEntityMgr().findByName(clusterName);
            masterMoId = cluster.getVhmMasterMoid();
            if (masterMoId == null) {
                logger.error("masterMoId missed.");
                throw ClusteringServiceException.SET_AUTO_ELASTICITY_FAILED(cluster.getName());
            }
        }

        String serengetiUUID = ConfigInfo.getSerengetiRootFolder();
        int minComputeNodeNum = cluster.getVhmMinNum();
        int maxComputeNodeNum = cluster.getVhmMaxNum();
        String jobTrackerPort = cluster.getVhmJobTrackerPort();

        VcVirtualMachine vcVm = VcCache.getIgnoreMissing(masterMoId);
        if (vcVm == null) {
            logger.error("cannot find vhm master node");
            return false;
        }
        String masterUUID = vcVm.getConfig().getUuid();

        Callable<Void>[] storeProcedures = new Callable[nodes.size()];
        int i = 0;
        for (NodeEntity node : nodes) {
            VcVirtualMachine vm = VcCache.getIgnoreMissing(node.getMoId());
            if (vm == null) {
                logger.error("cannot find node: " + node.getVmName());
                continue;
            }
            if (!refreshAllNodes && !vm.getId().equalsIgnoreCase(masterMoId)) {
                continue;
            }
            List<String> roles = new Gson().fromJson(node.getNodeGroup().getRoles(), List.class);
            SoftwareManager softMgr = softwareManagerCollector.getSoftwareManager(cluster.getAppManager());

            boolean isComputeOnlyNode = softMgr.isComputeOnlyRoles(roles);
            SetAutoElasticitySP sp = new SetAutoElasticitySP(clusterName, vm, serengetiUUID, masterMoId, masterUUID,
                    enableAutoElasticity, minComputeNodeNum, maxComputeNodeNum, jobTrackerPort, isComputeOnlyNode);
            storeProcedures[i] = sp;
            i++;
        }
        try {
            // execute store procedures to set auto elasticity
            logger.info("ClusteringService, start to set auto elasticity.");
            boolean success = true;
            NoProgressUpdateCallback callback = new NoProgressUpdateCallback();
            ExecutionResult[] result = Scheduler.executeStoredProcedures(
                    com.vmware.aurora.composition.concurrent.Priority.BACKGROUND, storeProcedures, callback);
            if (result == null) {
                logger.error("set auto elasticity failed.");
                throw ClusteringServiceException.SET_AUTO_ELASTICITY_FAILED(clusterName);
            }
            for (i = 0; i < storeProcedures.length; i++) {
                if (result[i].throwable != null) {
                    logger.error("failed to set auto elasticity", result[i].throwable);
                    success = false;
                }
            }
            return success;
        } catch (InterruptedException e) {
            logger.error("error in setting auto elasticity", e);
            throw BddException.INTERNAL(e, e.getMessage());
        }
    }

    @SuppressWarnings("unchecked")
    private Map<String, Folder> createVcFolders(ClusterCreate cluster) {
        return createVcFolders(cluster, false);
    }

    /*
     * addNodeGroup means  it's called by expandCluster() to add new node groups
     */
    @SuppressWarnings("unchecked")
    private Map<String, Folder> createVcFolders(ClusterCreate cluster, boolean addNodeGroup) {
        logger.info("createVcFolders, start to create cluster Folder.");
        VcVirtualMachine templateVm = getTemplateVM(cluster.getTemplateName());
        // get all nodegroups
        Callable<Void>[] storeProcedures = new Callable[1];
        Folder clusterFolder = null;

        if (!addNodeGroup) {
            if (cluster.getNodeGroups().length > 0) {
                // create cluster folder first
                NodeGroupCreate group = cluster.getNodeGroups()[0];
                String path = group.getVmFolderPath();
                logger.info("create folderName" + path);
                String[] folderNames = path.split("/");
                List<String> folderList = new ArrayList<String>();
                for (int i = 0; i < folderNames.length - 1; i++) {
                    folderList.add(folderNames[i]);
                }
                CreateVMFolderSP sp = new CreateVMFolderSP(templateVm.getDatacenter(), null, folderList);
                storeProcedures[0] = sp;
                Map<String, Folder> folders = executeFolderCreationProcedures(cluster, storeProcedures);
                for (String name : folders.keySet()) {
                    clusterFolder = folders.get(name);
                    break;
                }
            }
        }

        logger.info("createVcFolders, start to create group Folders.");
        storeProcedures = new Callable[cluster.getNodeGroups().length];
        int i = 0;
        for (NodeGroupCreate group : cluster.getNodeGroups()) {
            List<String> folderList = new ArrayList<String>();
            folderList.add(group.getName());
            CreateVMFolderSP sp = new CreateVMFolderSP(templateVm.getDatacenter(), clusterFolder, folderList);
            storeProcedures[i] = sp;
            i++;
        }
        return executeFolderCreationProcedures(cluster, storeProcedures);
    }

    private Map<String, Folder> executeFolderCreationProcedures(ClusterCreate cluster,
            Callable<Void>[] storeProcedures) {
        Map<String, Folder> folders = new HashMap<String, Folder>();
        try {
            // execute store procedures to create vc folders
            NoProgressUpdateCallback callback = new NoProgressUpdateCallback();
            ExecutionResult[] result = Scheduler.executeStoredProcedures(
                    com.vmware.aurora.composition.concurrent.Priority.BACKGROUND, storeProcedures, callback);
            if (result == null) {
                logger.error("No folder is created.");
                if (cluster != null)
                    throw ClusteringServiceException.CREATE_FOLDER_FAILED(cluster.getName());
            }

            int total = 0;
            boolean success = true;
            for (int i = 0; i < storeProcedures.length; i++) {
                CreateVMFolderSP sp = (CreateVMFolderSP) storeProcedures[i];
                if (result[i].finished && result[i].throwable == null) {
                    ++total;
                    Folder childFolder = sp.getResult().get(sp.getResult().size() - 1);
                    folders.put(childFolder.getName(), childFolder);
                } else if (result[i].throwable != null) {
                    logger.error("Failed to create vm folder", result[i].throwable);
                    success = false;
                }
            }
            logger.info(total + " Folders are created.");
            if (!success) {
                if (cluster != null)
                    throw ClusteringServiceException.CREATE_FOLDER_FAILED(cluster.getName());
            }
            return folders;
        } catch (InterruptedException e) {
            logger.error("error in creating VC folders", e);
            throw BddException.INTERNAL(e, e.getMessage());
        }
    }

    private void executeResourcePoolStoreProcedures(Callable<Void>[] defineSPs, String type, String clusterName)
            throws InterruptedException {
        if (defineSPs.length == 0) {
            logger.debug("no resource pool need to be created.");
            return;
        }
        NoProgressUpdateCallback callback = new NoProgressUpdateCallback();
        ExecutionResult[] result = Scheduler.executeStoredProcedures(
                com.vmware.aurora.composition.concurrent.Priority.BACKGROUND, defineSPs, callback);
        if (result == null) {
            logger.error("No " + type + " resource pool is created.");
            throw ClusteringServiceException.CREATE_RESOURCE_POOL_FAILED(Constants.NO_RESOURCE_POOL_IS_CREATED);
        }
        int total = 0;
        boolean success = true;
        String errMessage = null;
        for (int i = 0; i < defineSPs.length; i++) {
            if (result[i].finished && result[i].throwable == null) {
                ++total;
            } else if (result[i].throwable != null) {
                if (errMessage == null) {
                    //Generally the error message is same for all resource pools, so here we just keep the first failure message.
                    errMessage = result[i].throwable.getMessage();
                }
                logger.error("Failed to create " + type + " resource pool(s)", result[i].throwable);
                success = false;
            }
        }
        logger.info(total + " " + type + " resource pool(s) are created.");
        if (!success) {
            throw ClusteringServiceException.CREATE_RESOURCE_POOL_FAILED(errMessage);
        }
    }

    private Map<String, Integer> collectResourcePoolInfo(List<BaseNode> vNodes, final String uuid,
            Map<String, List<String>> vcClusterRpNamesMap, Map<Long, List<NodeGroupCreate>> rpNodeGroupsMap) {
        List<String> resourcePoolNames = null;
        List<NodeGroupCreate> nodeGroups = null;
        int resourcePoolNameCount = 0;
        int nodeGroupNameCount = 0;
        for (BaseNode baseNode : vNodes) {
            String vcCluster = baseNode.getTargetVcCluster();
            VcCluster cluster = VcResourceUtils.findVcCluster(vcCluster);
            if (!cluster.getConfig().getDRSEnabled()) {
                logger.debug(
                        "DRS disabled for cluster " + vcCluster + ", do not create child rp for this cluster.");
                continue;
            }
            AuAssert.check(!CommonUtil.isBlank(vcCluster), "Vc cluster name cannot be null!");
            if (!vcClusterRpNamesMap.containsKey(vcCluster)) {
                resourcePoolNames = new ArrayList<String>();
            } else {
                resourcePoolNames = vcClusterRpNamesMap.get(vcCluster);
            }
            String vcRp = baseNode.getTargetRp();
            logger.info("collectResourcePoolInfo baseNode.getTargetRp()" + vcRp);
            String rpPath = "/" + vcCluster + "/" + vcRp + "/" + uuid;
            logger.info("collectResourcePoolInfo rpPath" + rpPath);
            long rpHashCode = rpPath.hashCode();
            if (!rpNodeGroupsMap.containsKey(rpHashCode)) {
                nodeGroups = new ArrayList<NodeGroupCreate>();
            } else {
                nodeGroups = rpNodeGroupsMap.get(rpHashCode);
            }
            NodeGroupCreate nodeGroup = baseNode.getNodeGroup();
            if (!getAllNodeGroupNames(nodeGroups).contains(nodeGroup.getName())) {
                nodeGroups.add(nodeGroup);
                rpNodeGroupsMap.put(rpHashCode, nodeGroups);
                nodeGroupNameCount++;
            }
            if (!resourcePoolNames.contains(vcRp)) {
                resourcePoolNames.add(vcRp);
                vcClusterRpNamesMap.put(vcCluster, resourcePoolNames);
                resourcePoolNameCount++;
            }
        }

        Map<String, Integer> countResult = new HashMap<String, Integer>();
        countResult.put("resourcePoolNameCount", resourcePoolNameCount);
        countResult.put("nodeGroupNameCount", nodeGroupNameCount);
        return countResult;
    }

    private List<String> getAllNodeGroupNames(List<NodeGroupCreate> nodeGroups) {
        List<String> nodeGroupNames = new ArrayList<String>();
        for (NodeGroupCreate nodeGroup : nodeGroups) {
            nodeGroupNames.add(nodeGroup.getName());
        }
        return nodeGroupNames;
    }

    private String createVcResourcePools(List<BaseNode> vNodes) {
        return createVcResourcePools(vNodes, false);
    }

    /*
     * addNodeGroup means  it's called by expandCluster() to add new node groups
     */
    private String createVcResourcePools(List<BaseNode> vNodes, boolean addNodeGroup) {
        logger.info("createVcResourcePools, start to create VC ResourcePool(s).");
        /*
         * define cluster resource pool name.
         */
        String clusterName = vNodes.get(0).getClusterName();
        SoftwareManager softManager = softwareManagerCollector.getSoftwareManagerByClusterName(clusterName);
        String uuid = ConfigInfo.getSerengetiUUID();
        String clusterRpName = uuid + "-" + clusterName;
        if (clusterRpName.length() > VC_RP_MAX_NAME_LENGTH) {
            throw ClusteringServiceException.CLUSTER_NAME_TOO_LONG(clusterName);
        }

        /*
         * prepare resource pool names and node group per resource pool for creating cluster
         * resource pools and node group resource pool(s).
         */
        Map<String, List<String>> vcClusterRpNamesMap = new HashMap<String, List<String>>();
        Map<Long, List<NodeGroupCreate>> rpNodeGroupsMap = new HashMap<Long, List<NodeGroupCreate>>();
        Map<String, Integer> countResult = collectResourcePoolInfo(vNodes, uuid, vcClusterRpNamesMap,
                rpNodeGroupsMap);

        try {
            int i = 0;
            if (!addNodeGroup) {
                /*
                * define cluster store procedures of resource pool(s)
                */
                int resourcePoolNameCount = countResult.get("resourcePoolNameCount");
                Callable<Void>[] clusterSPs = new Callable[resourcePoolNameCount];
                for (Entry<String, List<String>> vcClusterRpNamesEntry : vcClusterRpNamesMap.entrySet()) {
                    String vcClusterName = vcClusterRpNamesEntry.getKey();
                    VcCluster vcCluster = VcResourceUtils.findVcCluster(vcClusterName);
                    if (vcCluster == null) {
                        String errorMsg = "Cannot find the vCenter Server cluster " + vcClusterName + ".";
                        logger.error(errorMsg);
                        throw ClusteringServiceException.CANNOT_FIND_VC_CLUSTER(vcClusterName);
                    }
                    List<String> resourcePoolNames = vcClusterRpNamesEntry.getValue();
                    for (String resourcePoolName : resourcePoolNames) {
                        VcResourcePool parentVcResourcePool = VcResourceUtils.findRPInVCCluster(vcClusterName,
                                resourcePoolName);
                        if (parentVcResourcePool == null) {
                            String errorMsg = "Cannot find the vCenter Server resource pool " + resourcePoolName
                                    + ".";
                            logger.error(errorMsg);
                            throw ClusteringServiceException.CANNOT_FIND_VC_RESOURCE_POOL(resourcePoolName);
                        }
                        CreateResourcePoolSP clusterSP = new CreateResourcePoolSP(parentVcResourcePool,
                                clusterRpName);
                        clusterSPs[i] = clusterSP;
                        i++;
                    }
                }

                // execute store procedures to create cluster resource pool(s)
                logger.info("ClusteringService, start to create cluster resource pool(s).");
                executeResourcePoolStoreProcedures(clusterSPs, "cluster", clusterName);

            }

            /*
             * define node group store procedures of resource pool(s)
             */
            int nodeGroupNameCount = countResult.get("nodeGroupNameCount");
            Callable<Void>[] nodeGroupSPs = new Callable[nodeGroupNameCount];
            i = 0;
            for (Entry<String, List<String>> vcClusterRpNamesEntry : vcClusterRpNamesMap.entrySet()) {
                String vcClusterName = vcClusterRpNamesEntry.getKey();
                VcCluster vcCluster = VcResourceUtils.findVcCluster(vcClusterName);
                if (vcCluster == null) {
                    String errorMsg = "Cannot find the vCenter Server cluster " + vcClusterName + ".";
                    logger.error(errorMsg);
                    throw ClusteringServiceException.CANNOT_FIND_VC_CLUSTER(vcClusterName);
                }
                if (!vcCluster.getConfig().getDRSEnabled()) {
                    continue;
                }
                List<String> resourcePoolNames = vcClusterRpNamesEntry.getValue();
                for (String resourcePoolName : resourcePoolNames) {
                    VcResourcePool parentVcResourcePool = null;
                    String vcRPName = CommonUtil.isBlank(resourcePoolName) ? clusterRpName
                            : resourcePoolName + "/" + clusterRpName;
                    parentVcResourcePool = VcResourceUtils.findRPInVCCluster(vcClusterName, vcRPName);
                    if (parentVcResourcePool == null) {
                        String errorMsg = "Cannot find the vCenter Server resource pool " + vcRPName
                                + (CommonUtil.isBlank(resourcePoolName) ? ""
                                        : " in the vCenter Server resource pool " + resourcePoolName)
                                + ".";
                        logger.error(errorMsg);
                        throw ClusteringServiceException.CANNOT_FIND_SUB_VC_RESOURCE_POOL(vcRPName,
                                resourcePoolName);
                    }
                    String rpPath = "/" + vcClusterName + "/" + resourcePoolName + "/" + uuid;
                    long rpHashCode = rpPath.hashCode();
                    for (NodeGroupCreate nodeGroup : rpNodeGroupsMap.get(rpHashCode)) {
                        AuAssert.check(nodeGroup != null,
                                "create node group resource pool failed: node group cannot be null !");
                        if (nodeGroup.getName().length() > 80) {
                            throw ClusteringServiceException.GROUP_NAME_TOO_LONG(nodeGroup.getName());
                        }
                        CreateResourcePoolSP nodeGroupSP = new CreateResourcePoolSP(parentVcResourcePool,
                                nodeGroup.getName(), nodeGroup, softManager);
                        nodeGroupSPs[i] = nodeGroupSP;
                        i++;
                    }
                }
            }

            //execute store procedures to create node group resource pool(s)
            logger.info("ClusteringService, start to create node group resource pool(s).");
            executeResourcePoolStoreProcedures(nodeGroupSPs, "node group", clusterName);
        } catch (InterruptedException e) {
            logger.error("error in creating VC ResourcePool(s)", e);
            throw ClusteringServiceException.CREATE_RESOURCE_POOL_ERROR(e.getMessage());
        }
        return clusterRpName;
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean createVcVms(ClusterCreate clusterSpec, List<BaseNode> vNodes,
            Map<String, Set<String>> occupiedIpSets, boolean reserveRawDisks, StatusUpdater statusUpdator) {
        if (vNodes.isEmpty()) {
            logger.info("No vm to be created.");
            return true;
        }
        List<NetworkAdd> networkAdds = clusterSpec.getNetworkings();
        String clusterCloneType = clusterSpec.getClusterCloneType();
        logger.info("syncCreateVMs, start to create VMs for cluster " + clusterSpec.getName());

        logger.info(String.format("Preparing node template VM %s for creating cluster %s",
                clusterSpec.getTemplateName(), clusterSpec.getName()));
        VcVirtualMachine templateVm = prepareNodeTemplate(clusterSpec.getTemplateName(), vNodes);
        logger.info("node template VM moid is " + templateVm.getId());
        VmCreateSpec sourceSpec = new VmCreateSpec();
        sourceSpec.setVmId(templateVm.getId());
        sourceSpec.setVmName(templateVm.getName());
        sourceSpec.setTargetHost(templateVm.getHost());

        allocateStaticIp(vNodes, networkAdds, occupiedIpSets);
        Map<String, Folder> folders = createVcFolders(vNodes.get(0).getCluster());
        String clusterRpName = createVcResourcePools(vNodes);

        List<VmCreateSpec> specs = new ArrayList<VmCreateSpec>();
        Map<String, BaseNode> nodeMap = new HashMap<String, BaseNode>();
        for (BaseNode vNode : vNodes) {
            // prepare for cloning result
            nodeMap.put(vNode.getVmName(), vNode);
            vNode.setSuccess(false);
            vNode.setFinished(false);
            // generate create spec for fast clone
            VmCreateSpec spec = new VmCreateSpec();
            VmSchema createSchema = getVmSchema(vNode, templateVm.getId());
            spec.setSchema(createSchema);
            GuestMachineIdSpec machineIdSpec = new GuestMachineIdSpec(networkAdds, vNode.fetchPortGroupToIpV4Map(),
                    vNode.getPrimaryMgtPgName(), vNode, networkMgr);
            logger.info("machine id of vm " + vNode.getVmName() + ":\n" + machineIdSpec.toString());
            spec.setBootupConfigs(machineIdSpec.toGuestVariable());
            // timeout is 10 mintues
            StartVmPostPowerOn query = new StartVmPostPowerOn(vNode.getNics().keySet(),
                    Configuration.getInt(Constants.VM_POWER_ON_WAITING_SEC_KEY, Constants.VM_POWER_ON_WAITING_SEC));
            spec.setPostPowerOn(query);
            spec.setPrePowerOn(getPrePowerOnFunc(vNode, reserveRawDisks));
            spec.setCloneType(VcVmCloneType.FULL);
            spec.setTargetDs(getVcDatastore(vNode));
            spec.setTargetFolder(folders.get(vNode.getGroupName()));
            spec.setTargetHost(VcResourceUtils.findHost(vNode.getTargetHost()));
            spec.setTargetRp(getVcResourcePool(vNode, clusterRpName));
            spec.setVmName(vNode.getVmName());
            specs.add(spec);
        }

        BaseProgressCallback callback = new BaseProgressCallback(statusUpdator);

        logger.info("ClusteringService, start to clone template.");
        AuAssert.check(specs.size() > 0);
        VmSchema vmSchema = specs.get(0).getSchema();
        VcVmUtil.checkAndCreateSnapshot(vmSchema);

        // call clone service to copy templates
        List<VmCreateResult<?>> results = chooseClusterCloneService(clusterCloneType).createCopies(sourceSpec,
                cloneConcurrency, specs, callback);
        if (results == null || results.isEmpty()) {
            for (VmCreateSpec spec : specs) {
                BaseNode node = nodeMap.get(spec.getVmName());
                node.setFinished(true);
                node.setSuccess(false);
            }
            return false;
        }
        boolean success = true;
        int total = 0;
        for (VmCreateResult<?> result : results) {
            VmCreateSpec spec = (VmCreateSpec) result.getSpec();
            BaseNode node = nodeMap.get(spec.getVmName());
            node.setVmMobId(spec.getVmId());
            node.setSuccess(true);
            node.setFinished(true);
            boolean vmSucc = VcVmUtil.setBaseNodeForVm(node, spec.getVmId());
            if (!vmSucc || !result.isSuccess()) {
                success = false;
                node.setSuccess(false);
                if (result.getErrMessage() != null) {
                    node.setErrMessage(result.getErrTimestamp() + " " + result.getErrMessage());
                } else if (!node.getErrMessage().isEmpty()) {
                    node.setErrMessage(CommonUtil.getCurrentTimestamp() + " " + node.getNodeAction());
                }
            } else {
                total++;
            }
        }
        logger.info(total + " VMs are successfully created.");
        return success;
    }

    private VcVirtualMachine prepareNodeTemplate(String templateName, List<BaseNode> vNodes) {
        VcVirtualMachine templateVM = prepareTemplateVM(templateName);
        String lable = getNodeTemplateNetworkLable(templateVM);
        updateNicLabels(vNodes, lable);
        return templateVM;
    }

    private IClusterCloneService chooseClusterCloneService(String type) {
        String qualifier = type + "ClusterCloneService";
        IClusterCloneService clusterCloneService = this.cloneServiceMap.get(qualifier);

        AuAssert.check(clusterCloneService != null, "ClusterCloneService not found: " + type);

        return clusterCloneService;
    }

    private void setPersistentDiskMode(BaseNode vNode) {
        DiskSchema diskSchema = vNode.getVmSchema().diskSchema;
        if (diskSchema.getDisks() != null) {
            for (Disk disk : diskSchema.getDisks()) {
                disk.mode = DiskMode.persistent;
            }
        }
    }

    private CreateVmPrePowerOn getPrePowerOnFunc(BaseNode vNode, boolean reserveRawDisks) {
        boolean persistentDiskMode = false;

        String haFlag = vNode.getNodeGroup().getHaFlag();
        boolean ha = false;
        boolean ft = false;
        if (haFlag != null && Constants.HA_FLAG_ON.equals(haFlag.toLowerCase())) {
            ha = true;
        }
        if (haFlag != null && Constants.HA_FLAG_FT.equals(haFlag.toLowerCase())) {
            ha = true;
            ft = true;
            Integer cpuNum = vNode.getNodeGroup().getCpuNum();
            cpuNum = (cpuNum == null) ? 0 : cpuNum;
            if (cpuNum > 1) {
                throw ClusteringServiceException.CPU_NUMBER_MORE_THAN_ONE(vNode.getVmName());
            }

            logger.debug("ft is enabled is for VM " + vNode.getVmName());
            logger.debug("set disk mode to persistent for VM " + vNode.getVmName());
            // change disk mode to persistent, instead of independent_persistent, since FT requires this
            persistentDiskMode = true;
        }

        List<String> roles = vNode.getNodeGroup().getRoles();
        SoftwareManager softMgr = softwareManagerCollector.getSoftwareManagerByClusterName(vNode.getClusterName());

        if (roles != null && softMgr.hasMgmtRole(roles)) {
            logger.debug(vNode.getVmName() + " is a master node");
            logger.debug("set disk mode to persistent for VM " + vNode.getVmName());
            // change disk mode to persistent, instead of independent_persistent, to allow snapshot and clone on VM
            persistentDiskMode = true;
        }

        if (persistentDiskMode) {
            setPersistentDiskMode(vNode);
        }

        ClusterEntity clusterEntity = getClusterEntityMgr().findByName(vNode.getClusterName());
        CreateVmPrePowerOn prePowerOn = new CreateVmPrePowerOn(reserveRawDisks,
                vNode.getVmSchema().diskSchema.getDisks(), ha, ft, clusterEntity.getIoShares());

        return prePowerOn;
    }

    private static VcDatastore getVcDatastore(BaseNode vNode) {
        VcDatastore ds = VcResourceUtils.findDSInVcByName(vNode.getTargetDs());
        if (ds != null) {
            return ds;
        }
        logger.error("target data store " + vNode.getTargetDs() + " is not found.");
        throw ClusteringServiceException.TARGET_VC_DATASTORE_NOT_FOUND(vNode.getTargetDs());
    }

    private VcResourcePool getVcResourcePool(BaseNode vNode, final String clusterRpName) {
        try {
            String vcRPName = "";
            VcCluster cluster = VcResourceUtils.findVcCluster(vNode.getTargetVcCluster());
            if (!cluster.getConfig().getDRSEnabled()) {
                logger.debug("DRS disabled for cluster " + vNode.getTargetVcCluster()
                        + ", put VM under cluster directly.");
                return cluster.getRootRP();
            }
            if (CommonUtil.isBlank(vNode.getTargetRp())) {
                vcRPName = clusterRpName + "/" + vNode.getNodeGroup().getName();
            } else {
                vcRPName = vNode.getTargetRp() + "/" + clusterRpName + "/" + vNode.getNodeGroup().getName();
            }
            VcResourcePool rp = VcResourceUtils.findRPInVCCluster(vNode.getTargetVcCluster(), vcRPName);
            if (rp == null) {
                throw ClusteringServiceException.TARGET_VC_RP_NOT_FOUND(vNode.getTargetVcCluster(),
                        vNode.getTargetRp());
            }
            return rp;
        } catch (Exception e) {
            logger.error("Failed to get VC resource pool " + vNode.getTargetRp() + " in vc cluster "
                    + vNode.getTargetVcCluster(), e);

            throw ClusteringServiceException.TARGET_VC_RP_NOT_FOUND(vNode.getTargetVcCluster(),
                    vNode.getTargetRp());
        }
    }

    private VmSchema getVmSchema(BaseNode vNode, String templateVmId) {
        VmSchema schema = vNode.getVmSchema();
        schema.diskSchema.setParent(templateVmId);
        schema.diskSchema.setParentSnap(Constants.ROOT_SNAPSTHOT_NAME);
        return schema;
    }

    @Override
    public List<BaseNode> getPlacementPlan(ClusterCreate clusterSpec, List<BaseNode> existedNodes) {

        try {
            Thread.sleep(Configuration.getInt("vcrefresh.delay.forResume", 10) * 1000);
        } catch (InterruptedException e1) {
            logger.error("vcrefresh.delay.forResume interrupted.", e1);
        }

        List<String> dsNames = vcResourceManager.getDsNamesToBeUsed(clusterSpec.getDsNames());
        if (dsNames.isEmpty()) {
            throw ClusterConfigException.NO_DATASTORE_ADDED();
        }

        VcResourceFilters filters = vcResourceFilterBuilder.build(dsNames,
                vcResourceManager.getRpNames(clusterSpec.getRpNames()), clusterSpec.getNetworkNames());

        try {
            syncService.refreshInventory(filters);
        } catch (InterruptedException e) {
            logger.error("failed to refresh vc inventory.", e);
        }

        logger.info("Begin to calculate provision plan.");

        logger.info("Calling resource manager to get available vc hosts");
        Container container = new Container();

        List<VcCluster> clusters = resMgr.getAvailableClusters();
        AuAssert.check(clusters != null && clusters.size() != 0);
        for (VcCluster cl : clusters) {
            //refresh once at beginning of cluster create/resume/resize
            //         VcResourceUtils.refreshDatastore(cl);
            container.addResource(cl);
        }

        logger.info("VC resource container details:" + ContainerToStringHelper.convertToString(container));

        logger.info("check time on hosts.");
        // check time on hosts
        int maxTimeDiffInSec = Constants.MAX_TIME_DIFF_IN_SEC;
        SoftwareManager softMgr = softwareManagerCollector.getSoftwareManager(clusterSpec.getAppManager());
        if (softMgr.hasHbase(clusterSpec.toBlueprint()))
            maxTimeDiffInSec = Constants.MAX_TIME_DIFF_IN_SEC_HBASE;

        List<String> outOfSyncHosts = new ArrayList<String>();
        for (AbstractHost host : container.getAllHosts()) {
            int hostTimeDiffInSec = VcResourceUtils.getHostTimeDiffInSec(host.getName());
            if (Math.abs(hostTimeDiffInSec) > maxTimeDiffInSec) {
                logger.info("Host " + host.getName() + " has a time difference of " + hostTimeDiffInSec
                        + " seconds and is dropped from placement.");
                outOfSyncHosts.add(host.getName());
            }
        }
        for (String host : outOfSyncHosts) {
            container.removeHost(host);
        }

        logger.info("filter hosts by networks.");
        // filter hosts by networks
        List<com.vmware.bdd.spectypes.VcCluster> usedClusters = clusterSpec.getVcClusters();
        List<String> noNetworkHosts = new ArrayList<String>();
        noNetworkHosts = resMgr.filterHostsByNetwork(clusterSpec.getNetworkNames(), usedClusters);

        for (String host : noNetworkHosts) {
            container.removeHost(host);
        }

        Map<String, List<String>> filteredHosts = new HashMap<String, List<String>>();
        if (!outOfSyncHosts.isEmpty())
            filteredHosts.put(PlacementUtil.OUT_OF_SYNC_HOSTS, outOfSyncHosts);
        if (!noNetworkHosts.isEmpty()) {
            filteredHosts.put(PlacementUtil.NO_NETWORKS_HOSTS, noNetworkHosts);
            filteredHosts.put(PlacementUtil.NETWORK_NAMES, clusterSpec.getNetworkNames());
        }

        VcVirtualMachine templateVm = getTemplateVM(clusterSpec.getTemplateName());
        container.setTemplateNode(createBaseNodeFromTemplateVm(templateVm));
        if (clusterSpec.getHostToRackMap() != null && clusterSpec.getHostToRackMap().size() != 0) {
            container.addRackMap(clusterSpec.getHostToRackMap());
        }

        logger.info("rack topology file validation.");
        // rack topology file validation
        Set<String> validRacks = new HashSet<String>();
        List<AbstractHost> hosts = container.getAllHosts();
        for (AbstractHost host : hosts) {
            if (container.getRack(host) != null) {
                // this rack is valid as it contains at least one host
                validRacks.add(container.getRack(host));
            }
        }

        for (NodeGroupCreate nodeGroup : clusterSpec.getNodeGroups()) {
            if (nodeGroup.getPlacementPolicies() != null && nodeGroup.getPlacementPolicies().getGroupRacks() != null
                    && validRacks.size() == 0) {
                throw PlacementException.INVALID_RACK_INFO(clusterSpec.getName(), nodeGroup.getName());
            }
        }

        // for instant clone, we need check if the target hosts are with version 6 or higher
        // if user does not set the clone type in serengeti.properties, the default type
        // should still be FAST clone for hosts with 5.x version
        // for cluster resize, the original cloneType should be used
        if (null == existedNodes) {
            checkAndUpdateClusterCloneType(clusterSpec, container);
        }

        logger.info("pre-placement task.");
        //pre-placement task
        String clusterCloneType = clusterSpec.getClusterCloneType();
        chooseClusterCloneService(clusterCloneType).preCalculatePlacements(container, clusterSpec, existedNodes);

        List<BaseNode> baseNodes = placementService.getPlacementPlan(container, clusterSpec, existedNodes,
                filteredHosts);
        for (BaseNode baseNode : baseNodes) {
            baseNode.setNodeAction(Constants.NODE_ACTION_CLONING_VM);
        }

        //Check nodegroup reserved cpu&mem ratio, then set the node's cpuReservationMHz = vcpuNum * ESXcpuMhz*ratio
        for (BaseNode current : baseNodes) {
            Float cpuRatio = current.getNodeGroup().getReservedCpuRatio();
            Float memRatio = current.getNodeGroup().getReservedMemRatio();
            //if ReservedCpu_ratio is set, caculate the reservedCpu for vm
            if (cpuRatio != null && cpuRatio > 0 && cpuRatio <= 1) {
                if (current.getTargetHost() != null) {
                    VcHost host = VcResourceUtils.findHost(current.getTargetHost());
                    long nodeCpuMhz = host.getCpuHz() / (1024 * 1024) * current.getCpu();
                    current.getVmSchema().resourceSchema.cpuReservationMHz = (long) Math
                            .ceil(nodeCpuMhz * cpuRatio);
                    logger.info("Base Node's cpuReservationMHz is set to "
                            + current.getVmSchema().resourceSchema.cpuReservationMHz);
                }
            }
            //Check latency&ReservedMem_ratio is set, caculate the reservedMem for vm
            if (memRatio != null && memRatio > 0 && memRatio <= 1) {
                if (current.getVmSchema().resourceSchema.latencySensitivity == LatencyPriority.HIGH)
                    current.getVmSchema().resourceSchema.memReservationSize = current.getMem();
                else {
                    current.getVmSchema().resourceSchema.memReservationSize = (long) Math
                            .ceil(current.getMem() * memRatio);
                }
                logger.info("Base Node's memReservationM is set to "
                        + current.getVmSchema().resourceSchema.memReservationSize);
            }
        }

        logger.info("Finished calculating provision plan");

        return baseNodes;
    }

    @Override
    public boolean startCluster(final String name, List<NodeOperationStatus> failedNodes,
            StatusUpdater statusUpdator) {
        logger.info("startCluster, start.");
        boolean isMapDistro = clusterEntityMgr.findByName(name).getDistroVendor()
                .equalsIgnoreCase(Constants.MAPR_VENDOR);
        List<NodeEntity> nodes = clusterEntityMgr.findAllNodes(name);
        logger.info("startCluster, start to create store procedures.");
        List<Callable<Void>> storeProcedures = new ArrayList<Callable<Void>>();

        Map<String, NodeOperationStatus> nodesStatus = new HashMap<String, NodeOperationStatus>();
        for (int i = 0; i < nodes.size(); i++) {
            NodeEntity node = nodes.get(i);
            VcVirtualMachine vcVm = ClusterUtil.getVcVm(clusterEntityMgr, node);
            if (vcVm == null) {
                // cannot find VM
                logger.info("VC vm does not exist for node: " + node.getVmName());
                continue;
            }

            StartVmSP.StartVmPrePowerOn prePowerOn = new StartVmSP.StartVmPrePowerOn(isMapDistro,
                    (new Gson()).toJson(node.getVolumns()));
            StartVmPostPowerOn query = new StartVmPostPowerOn(node.fetchAllPortGroups(),
                    Configuration.getInt(Constants.VM_POWER_ON_WAITING_SEC_KEY, Constants.VM_POWER_ON_WAITING_SEC),
                    clusterEntityMgr);
            VcHost host = null;
            if (node.getHostName() != null) {
                host = VcResourceUtils.findHost(node.getHostName());
            }
            StartVmSP startSp = new StartVmSP(vcVm, prePowerOn, query, host);
            storeProcedures.add(startSp);
            nodesStatus.put(node.getVmName(), new NodeOperationStatus(node.getVmName()));
        }

        try {
            if (storeProcedures.isEmpty()) {
                logger.info("no VM is available. Return directly.");
                return true;
            }
            Callable<Void>[] storeProceduresArray = storeProcedures.toArray(new Callable[0]);
            // execute store procedures to start VMs
            logger.info("ClusteringService, start to start vms.");
            BaseProgressCallback callback = new BaseProgressCallback(statusUpdator);
            ExecutionResult[] result = Scheduler.executeStoredProcedures(
                    com.vmware.aurora.composition.concurrent.Priority.BACKGROUND, storeProceduresArray, callback);
            if (result == null) {
                for (NodeOperationStatus status : nodesStatus.values()) {
                    status.setSucceed(false);
                }
                logger.error("No VM is started.");
                failedNodes.addAll(nodesStatus.values());
                return false;
            }

            boolean success = true;
            int total = 0;
            for (int i = 0; i < storeProceduresArray.length; i++) {
                StartVmSP sp = (StartVmSP) storeProceduresArray[i];
                NodeOperationStatus status = nodesStatus.get(sp.getVmName());
                VcVirtualMachine vm = sp.getVcVm();
                if (result[i].finished && result[i].throwable == null) {
                    ++total;
                    nodesStatus.remove(status.getNodeName()); // do not return success node
                } else if (result[i].throwable != null) {
                    status.setSucceed(false);
                    status.setErrorMessage(getErrorMessage(result[i].throwable));
                    if (vm != null && vm.isPoweredOn() && VcVmUtil.checkIpAddresses(vm)) {
                        ++total;
                        nodesStatus.remove(status.getNodeName()); // do not return success node status
                    } else {
                        if (!vm.isConnected() || vm.getHost().isUnavailbleForManagement()) {
                            logger.error("Cannot start VM " + vm.getName() + " in connection state "
                                    + vm.getConnectionState() + " or in maintenance mode. "
                                    + "Ignore this VM and continue cluster operations.");
                            continue;
                        }
                        logger.error("Failed to start VM " + nodes.get(i).getVmName(), result[i].throwable);
                        success = false;
                    }
                }
            }
            logger.info(total + " VMs are started.");
            failedNodes.addAll(nodesStatus.values());
            return success;
        } catch (InterruptedException e) {
            logger.error("error in staring VMs", e);
            throw BddException.INTERNAL(e, e.getMessage());
        }
    }

    @Override
    public boolean stopCluster(final String name, List<NodeOperationStatus> failedNodes,
            StatusUpdater statusUpdator) {
        logger.info("stopCluster, start.");
        List<NodeEntity> nodes = clusterEntityMgr.findAllNodes(name);
        logger.info("stopCluster, start to create store procedures.");
        List<Callable<Void>> storeProcedures = new ArrayList<Callable<Void>>();
        Map<String, NodeOperationStatus> nodesStatus = new HashMap<String, NodeOperationStatus>();
        for (int i = 0; i < nodes.size(); i++) {
            NodeEntity node = nodes.get(i);
            VcVirtualMachine vcVm = ClusterUtil.getVcVm(clusterEntityMgr, node);

            if (vcVm == null) {
                logger.info("VC vm does not exist for node: " + node.getVmName());
                continue;
            }
            StopVmSP stopSp = new StopVmSP(vcVm);
            storeProcedures.add(stopSp);
            nodesStatus.put(node.getVmName(), new NodeOperationStatus(node.getVmName()));
        }

        try {
            if (storeProcedures.isEmpty()) {
                logger.info("no VM is available. Return directly.");
                return true;
            }
            Callable<Void>[] storeProceduresArray = storeProcedures.toArray(new Callable[0]);
            // execute store procedures to start VMs
            logger.info("ClusteringService, start to stop vms.");
            BaseProgressCallback callback = new BaseProgressCallback(statusUpdator);

            ExecutionResult[] result = Scheduler.executeStoredProcedures(
                    com.vmware.aurora.composition.concurrent.Priority.BACKGROUND, storeProceduresArray, callback);
            if (result == null) {
                logger.error("No VM is stoped.");
                for (NodeOperationStatus status : nodesStatus.values()) {
                    status.setSucceed(false);
                }
                failedNodes.addAll(nodesStatus.values());
                return false;
            }

            boolean success = true;
            int total = 0;
            for (int i = 0; i < storeProceduresArray.length; i++) {
                StopVmSP sp = (StopVmSP) storeProceduresArray[i];
                NodeOperationStatus status = nodesStatus.get(sp.getVmName());
                if (result[i].finished && result[i].throwable == null) {
                    ++total;
                    nodesStatus.remove(status.getNodeName()); // do not return success node
                } else if (result[i].throwable != null) {
                    status.setSucceed(false);
                    status.setErrorMessage(getErrorMessage(result[i].throwable));
                    VcVirtualMachine vm = sp.getVcVm();
                    if (vm == null || vm.isPoweredOff()) {
                        ++total;
                        nodesStatus.remove(status.getNodeName()); // do not return success node
                    } else {
                        if (!vm.isConnected() || vm.getHost().isUnavailbleForManagement()) {
                            logger.error("Cannot stop VM " + vm.getName() + " in connection state "
                                    + vm.getConnectionState() + " or in maintenance mode. "
                                    + "Ignore this VM and continue cluster operations.");
                            continue;
                        }
                        logger.error("Failed to stop VM " + nodes.get(i).getVmName(), result[i].throwable);
                        success = false;
                    }
                }
            }
            logger.info(total + " VMs are stoped.");
            return success;
        } catch (InterruptedException e) {
            logger.error("error in stoping VMs", e);
            throw BddException.INTERNAL(e, e.getMessage());
        }
    }

    private String getErrorMessage(Throwable throwable) {
        if (throwable == null) {
            return null;
        }
        return CommonUtil.getCurrentTimestamp() + " " + throwable.getMessage();
    }

    @Override
    public boolean removeBadNodes(ClusterCreate cluster, List<BaseNode> existingNodes, List<BaseNode> deletedNodes,
            Map<String, Set<String>> occupiedIpSets, StatusUpdater statusUpdator) {
        logger.info("Start to remove node violate placement policy " + "or in wrong status in cluster: "
                + cluster.getName());
        // call tm to remove bad nodes
        List<BaseNode> badNodes = placementService.getBadNodes(cluster, existingNodes);
        if (badNodes == null) {
            badNodes = new ArrayList<BaseNode>();
        }

        logger.info("violate placement policy nodes: " + badNodes);
        // append node in wrong status
        for (BaseNode node : deletedNodes) {
            if (node.getVmMobId() != null) {
                badNodes.add(node);
            } else {
                node.setSuccess(true);
            }
        }

        if (badNodes != null && badNodes.size() > 0) {
            boolean deleted = syncDeleteVMs(badNodes, statusUpdator, false);
            afterBadVcVmDelete(existingNodes, deletedNodes, badNodes, occupiedIpSets);
            return deleted;
        }
        return true;
    }

    @Override
    public List<BaseNode> getBadNodes(ClusterCreate cluster, List<BaseNode> existingNodes) {
        return placementService.getBadNodes(cluster, existingNodes);
    }

    private void afterBadVcVmDelete(List<BaseNode> existingNodes, List<BaseNode> deletedNodes,
            List<BaseNode> vcDeletedNodes, Map<String, Set<String>> occupiedIpSets) {
        // clean up in memory node list
        deletedNodes.addAll(vcDeletedNodes);
        Set<String> deletedNodeNames = new HashSet<String>();
        for (BaseNode vNode : vcDeletedNodes) {
            deletedNodeNames.add(vNode.getVmName());
        }
        for (Iterator<BaseNode> ite = existingNodes.iterator(); ite.hasNext();) {
            BaseNode vNode = ite.next();
            if (deletedNodeNames.contains(vNode.getVmName())) {
                JobUtils.adjustOccupiedIpSets(occupiedIpSets, vNode, false);
                ite.remove();
            }
        }
    }

    @Override
    public boolean deleteCluster(String name, List<BaseNode> vNodes, StatusUpdater statusUpdator) {
        if (vNodes == null || vNodes.size() < 1) {
            logger.error("no node need to be deleted.");
            return true;
        }
        //Find folder firstly, or will fail to find it after deleting VMs.
        Folder folder = findFolder(vNodes.get(0));
        boolean deleted = syncDeleteVMs(vNodes, statusUpdator, true);
        try {
            deleteChildRps(name, vNodes);
        } catch (Exception e) {
            logger.error("ignore delete resource pool error.", e);
        }
        try {
            deleteFolders(folder);
        } catch (Exception e) {
            logger.error("ignore delete folder error.", e);
        }
        return deleted;
    }

    private void deleteChildRps(String hadoopClusterName, List<BaseNode> vNodes) {
        logger.info("Start to delete child resource pools for cluster: " + hadoopClusterName);
        Map<String, Map<String, VcResourcePool>> clusterMap = new HashMap<String, Map<String, VcResourcePool>>();
        for (BaseNode node : vNodes) {
            String vcClusterName = node.getTargetVcCluster();
            AuAssert.check(vcClusterName != null);
            String vcRpName = node.getTargetRp();
            if (clusterMap.get(vcClusterName) == null) {
                clusterMap.put(vcClusterName, new HashMap<String, VcResourcePool>());
            }
            Map<String, VcResourcePool> rpMap = clusterMap.get(vcClusterName);
            if (rpMap.get(vcRpName) == null) {
                VcResourcePool vcRp = VcResourceUtils.findRPInVCCluster(vcClusterName, vcRpName);
                if (vcRp != null) {
                    rpMap.put(vcRpName, vcRp);
                }
            }
        }
        List<VcResourcePool> rps = new ArrayList<VcResourcePool>();
        for (Map<String, VcResourcePool> map : clusterMap.values()) {
            rps.addAll(map.values());
        }
        Callable<Void>[] storedProcedures = new Callable[rps.size()];
        String childRp = ConfigInfo.getSerengetiUUID() + "-" + hadoopClusterName;
        int i = 0;
        for (VcResourcePool rp : rps) {
            DeleteRpSp sp = new DeleteRpSp(rp, childRp);
            storedProcedures[i] = sp;
            i++;
        }
        try {
            NoProgressUpdateCallback callback = new NoProgressUpdateCallback();
            ExecutionResult[] result = Scheduler.executeStoredProcedures(
                    com.vmware.aurora.composition.concurrent.Priority.BACKGROUND, storedProcedures, callback);
            if (result == null || result.length == 0) {
                logger.error("No rp is deleted.");
                return;
            }
            int total = 0;
            for (int j = 0; j < storedProcedures.length; j++) {
                if (result[j].throwable != null) {
                    DeleteRpSp sp = (DeleteRpSp) storedProcedures[j];
                    logger.error("Failed to delete child resource pool " + sp.getDeleteRpName() + " under "
                            + sp.getVcRp(), result[j].throwable);
                } else {
                    total++;
                }
            }
        } catch (InterruptedException e) {
            logger.error("error in deleting resource pools", e);
            throw BddException.INTERNAL(e, e.getMessage());
        }
    }

    private Folder findFolder(BaseNode node) throws BddException {
        String path = node.getVmFolder();
        String mobid = node.getVmMobId();
        // path format: <serengeti...>/<cluster name>/<group name>
        String[] folderNames = path.split("/");
        AuAssert.check(folderNames.length == 3);
        VcDatacenter dc = VcResourceUtils.findVM(mobid).getDatacenter();
        List<String> deletedFolders = new ArrayList<String>();
        deletedFolders.add(folderNames[0]);
        deletedFolders.add(folderNames[1]);
        Folder folder = null;
        try {
            folder = VcResourceUtils.findFolderByNameList(dc, deletedFolders);
        } catch (Exception e) {
            logger.error("error in finding folders", e);
            throw BddException.INTERNAL(e, e.getMessage());
        }
        String clusterFolderName = folderNames[0] + "/" + folderNames[1];
        logger.info("find cluster root folder: " + clusterFolderName + "test: folder.name=" + folder.getName()
                + " folder.ref=" + folder._getRef().getValue());
        return folder;
    }

    /**
     * this method will delete the cluster root folder, if there is any VM
     * existed and powered on in the folder, the folder deletion will fail.
     *
     * @param folder
     * @throws BddException
     */
    private void deleteFolders(Folder folder) throws BddException {
        if (folder == null) {
            logger.info("No folder to delete.");
            return;
        }
        List<Folder> folders = new ArrayList<Folder>();
        folders.add(folder);
        DeleteVMFolderSP sp = new DeleteVMFolderSP(folders, true, false);
        Callable<Void>[] storedProcedures = new Callable[1];
        storedProcedures[0] = sp;
        try {
            NoProgressUpdateCallback callback = new NoProgressUpdateCallback();
            ExecutionResult[] result = Scheduler.executeStoredProcedures(
                    com.vmware.aurora.composition.concurrent.Priority.BACKGROUND, storedProcedures, callback);
            if (result == null || result.length == 0) {
                logger.error("No folder is deleted.");
                return;
            }
            if (result[0].finished && result[0].throwable == null) {
                logger.info("Cluster folder " + folder.getName() + " is deleted.");
            } else {
                logger.info("Failed to delete cluster folder " + folder.getName(), result[0].throwable);
            }
        } catch (InterruptedException e) {
            logger.error("error in deleting folders", e);
            throw BddException.INTERNAL(e, e.getMessage());
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public boolean syncDeleteVMs(List<BaseNode> badNodes, StatusUpdater statusUpdator,
            boolean ignoreUnavailableNodes) {
        logger.info("syncDeleteVMs, start to create store procedures.");
        List<Callable<Void>> storeProcedures = new ArrayList<Callable<Void>>();
        List<BaseNode> toBeDeleted = new ArrayList<BaseNode>();
        for (int i = 0; i < badNodes.size(); i++) {
            BaseNode node = badNodes.get(i);
            if (node.getVmMobId() == null) {
                // vm is already deleted
                node.setSuccess(true);
                continue;
            }
            DeleteVmByIdSP deleteSp = new DeleteVmByIdSP(node.getVmMobId());
            storeProcedures.add(deleteSp);
            toBeDeleted.add(node);
        }
        try {
            if (storeProcedures.isEmpty()) {
                logger.info("no VM is created. Return directly.");
                return true;
            }
            Callable<Void>[] storeProceduresArray = storeProcedures.toArray(new Callable[0]);
            // execute store procedures to delete VMs
            logger.info("ClusteringService, start to delete vms.");
            BaseProgressCallback callback = new BaseProgressCallback(statusUpdator, 0, 50);
            ExecutionResult[] result = Scheduler.executeStoredProcedures(
                    com.vmware.aurora.composition.concurrent.Priority.BACKGROUND, storeProceduresArray, callback);
            if (result == null) {
                logger.error("No VM is deleted.");
                return false;
            }

            int total = 0;
            boolean failed = false;
            for (int i = 0; i < storeProceduresArray.length; i++) {
                BaseNode vNode = toBeDeleted.get(i);
                vNode.setFinished(true);
                if (result[i].finished && result[i].throwable == null) {
                    vNode.setSuccess(true);
                    vNode.setVmMobId(null);
                    ++total;
                } else if (result[i].throwable != null) {
                    vNode.setSuccess(false);
                    vNode.setErrMessage(getErrorMessage(result[i].throwable));
                    if (ignoreUnavailableNodes) {
                        DeleteVmByIdSP sp = (DeleteVmByIdSP) storeProceduresArray[i];
                        VcVirtualMachine vcVm = sp.getVcVm();
                        if (!vcVm.isConnected() || vcVm.getHost().isUnavailbleForManagement()) {
                            logger.error("Failed to delete VM " + vcVm.getName() + " in connection state "
                                    + vcVm.getConnectionState() + "  or in maintenance mode.");
                            logger.error("Ignore this failure and continue cluster operations.");
                            continue;
                        }
                    }
                    logger.error("Failed to delete VM " + vNode.getVmName(), result[i].throwable);
                    failed = true;
                }
                vNode.setFinished(true);
            }
            logger.info(total + " VMs are deleted.");
            return !failed;
        } catch (InterruptedException e) {
            logger.error("error in deleting VMs", e);
            throw BddException.INTERNAL(e, e.getMessage());
        }
    }

    @Override
    public UUID reserveResource(String clusterName) {
        ResourceReservation reservation = new ResourceReservation();
        reservation.setClusterName(clusterName);
        return resMgr.reserveResource(reservation);
    }

    @Override
    public void commitReservation(UUID reservationId) throws VcProviderException {
        resMgr.commitReservation(reservationId);
    }

    @Override
    public Map<String, String> configIOShares(String clusterName, List<NodeEntity> targetNodes, Priority ioShares) {
        AuAssert.check(clusterName != null && targetNodes != null && !targetNodes.isEmpty());

        Callable<Void>[] storeProcedures = new Callable[targetNodes.size()];
        int i = 0;
        for (NodeEntity node : targetNodes) {
            ConfigIOShareSP ioShareSP = new ConfigIOShareSP(node.getMoId(), ioShares);
            storeProcedures[i] = ioShareSP;
            i++;
        }

        try {
            // execute store procedures to configure io shares
            logger.info("ClusteringService, start to reconfigure vm's io shares.");
            NoProgressUpdateCallback callback = new NoProgressUpdateCallback();
            ExecutionResult[] result = Scheduler.executeStoredProcedures(
                    com.vmware.aurora.composition.concurrent.Priority.BACKGROUND, storeProcedures, callback);
            if (result == null) {
                logger.error("No VM's io share level is reconfigured.");
                throw ClusteringServiceException.RECONFIGURE_IO_SHARE_FAILED(clusterName);
            }

            int total = 0;
            Map<String, String> failedNodes = new HashMap<String, String>();
            for (i = 0; i < storeProcedures.length; i++) {
                if (result[i].finished && result[i].throwable == null) {
                    ++total;
                } else if (result[i].throwable != null) {
                    logger.error("Failed to reconfigure vm", result[i].throwable);
                    String nodeName = targetNodes.get(i).getVmName();
                    String message = CommonUtil.getCurrentTimestamp() + " " + result[i].throwable.getMessage();
                    failedNodes.put(nodeName, message);
                }
            }
            logger.info(total + " vms are reconfigured.");
            return failedNodes;
        } catch (InterruptedException e) {
            logger.error("error in reconfiguring vm io shares", e);
            throw BddException.INTERNAL(e, e.getMessage());
        }
    }

    @Override
    public boolean startSingleVM(String clusterName, String nodeName, StatusUpdater statusUpdator) {
        NodeEntity node = this.clusterEntityMgr.findNodeByName(nodeName);
        boolean reserveRawDisks = clusterEntityMgr.findByName(clusterName).getDistroVendor()
                .equalsIgnoreCase(Constants.MAPR_VENDOR);
        // For node scale up/down, the disk info in db is not yet updated when powering on it, need to fetch from VC
        List<DiskSpec> diskSpecList = VcVmUtil.toDiskSpecList(node.getDisks());

        StartVmSP.StartVmPrePowerOn prePowerOn = new StartVmSP.StartVmPrePowerOn(reserveRawDisks,
                VcVmUtil.getVolumesFromSpecs(node.getMoId(), diskSpecList));
        StartVmPostPowerOn query = new StartVmPostPowerOn(node.fetchAllPortGroups(),
                Configuration.getInt(Constants.VM_POWER_ON_WAITING_SEC_KEY, Constants.VM_POWER_ON_WAITING_SEC),
                clusterEntityMgr);

        VcHost host = null;
        if (node.getHostName() != null) {
            host = VcResourceUtils.findHost(node.getHostName());
        }

        VcVirtualMachine vcVm = ClusterUtil.getVcVm(clusterEntityMgr, node);
        if (vcVm == null) {
            logger.info("VC vm does not exist for node: " + node.getVmName());
            return false;
        }
        StartVmSP startVMSP = new StartVmSP(vcVm, prePowerOn, query, host);
        return VcVmUtil.runSPOnSingleVM(node, startVMSP);
    }

    @Override
    public boolean stopSingleVM(String clusterName, String nodeName, StatusUpdater statusUpdator,
            boolean... vmPoweroff) {
        NodeEntity node = this.clusterEntityMgr.findNodeByName(nodeName);
        VcVirtualMachine vcVm = ClusterUtil.getVcVm(clusterEntityMgr, node);
        if (vcVm == null) {
            // cannot find VM
            logger.info("VC vm does not exist for node: " + node.getVmName());
            return false;
        }
        StopVmSP stopVMSP;
        if (vmPoweroff.length > 0 && vmPoweroff[0]) {
            stopVMSP = new StopVmSP(vcVm, true);
        } else {
            stopVMSP = new StopVmSP(vcVm);
        }
        return VcVmUtil.runSPOnSingleVM(node, stopVMSP);
    }

    @Override
    public VmEventManager getEventProcessor() {
        return this.processor;
    }

    @Override
    public boolean isSupportVHM(String clusterName) {
        ClusterEntity cluster = getClusterEntityMgr().findByName(clusterName);
        SoftwareManager softMgr = softwareManagerCollector.getSoftwareManagerByClusterName(clusterName);
        if (!softMgr.hasComputeMasterGroup(getClusterEntityMgr().toClusterBluePrint(clusterName))) {
            logger.warn("Use of auto elasticity, must configure Jobtracker or ResourceManager.");
            return false;
        }
        return true;
    }

    @Override
    public boolean addNodeGroups(ClusterCreate clusterSpec, NodeGroupCreate[] nodeGroupsAdd,
            List<BaseNode> vNodes) {
        boolean success = false;
        List<NodeGroupCreate> newNodeGroups = new ArrayList<NodeGroupCreate>();

        if (clusterSpec != null && clusterSpec.getNodeGroups() != null) {
            for (NodeGroupCreate ng : clusterSpec.getNodeGroups()) {
                newNodeGroups.add(ng);
            }
        }

        if (nodeGroupsAdd != null) {
            for (NodeGroupCreate ng : nodeGroupsAdd) {
                newNodeGroups.add(ng);
            }
        }

        if (clusterSpec != null) {
            clusterSpec.setNodeGroups(newNodeGroups.toArray(new NodeGroupCreate[newNodeGroups.size()]));
        }

        if (null != createVcFolders(clusterSpec, true)) {
            if (null != createVcResourcePools(vNodes, true)) {
                success = true;
            }
        }

        return success;
    }

    private void checkAndUpdateClusterCloneType(ClusterCreate clusterSpec, Container container) {
        String clusterName = clusterSpec.getName();
        String type = Configuration.getString("cluster.clone.service");
        if (StringUtils.isBlank(type)) {
            // check if there are any ESXi hosts with version lower than 6.0
            boolean isLowVersion = false;
            for (AbstractHost host : container.getAllHosts()) {
                String hostName = host.getName();
                final VcHost vchost = VcResourceUtils.findHost(hostName);
                AuAssert.check(vchost != null, String.format("can' find host: %1s in VC.", hostName));
                String version = vchost.getVersion();
                if (Version.compare(version, Constants.VCENTER_VERSION_6) < 0) {
                    logger.info("Some ESXi host version is lower than 6.0.");
                    isLowVersion = true;
                    break;
                }
            }

            // if some ESXi host version is lower than 6.0, update the cluster clone type to FAST
            if (isLowVersion) {
                ClusterEntity cluster = clusterEntityMgr.findByName(clusterName);
                String advProps = cluster.getAdvancedProperties();
                if (!CommonUtil.isBlank(advProps)) {
                    Gson gson = new Gson();
                    Map<String, String> advancedProperties = gson.fromJson(advProps, Map.class);
                    String cloneType = advancedProperties.get("ClusterCloneType");
                    if (!StringUtils.isBlank(cloneType)
                            && cloneType.equals(Constants.CLUSTER_CLONE_TYPE_INSTANT_CLONE)) {
                        cloneType = Constants.CLUSTER_CLONE_TYPE_FAST_CLONE;
                        advancedProperties.put("ClusterCloneType", cloneType);
                        cluster.setAdvancedProperties(gson.toJson(advancedProperties));
                        logger.info(
                                "Change cluster clone type to fast clone because of ESXi version lower than 6.0.");
                        clusterEntityMgr.update(cluster);
                        clusterSpec.setClusterCloneType(cloneType);
                    }
                }
            }
        }
    }

}