com.vmware.bdd.manager.ClusterManager.java Source code

Java tutorial

Introduction

Here is the source code for com.vmware.bdd.manager.ClusterManager.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.manager;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.*;
import java.util.regex.Pattern;

import com.vmware.aurora.vc.VcResourcePool;
import com.vmware.bdd.apitypes.*;
import com.vmware.bdd.exception.*;
import com.vmware.bdd.placement.entity.BaseNode;
import com.vmware.bdd.service.impl.ClusteringService;
import com.vmware.bdd.utils.*;
import com.vmware.vim.binding.vim.Folder;
import org.apache.commons.collections.CollectionUtils;
import com.vmware.bdd.service.resmgmt.IVcInventorySyncService;
import com.vmware.bdd.service.resmgmt.sync.filter.VcResourceFilters;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.batch.core.JobParameter;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.vmware.aurora.global.Configuration;
import com.vmware.aurora.vc.VcVirtualMachine;
import com.vmware.aurora.vc.vcservice.VcContext;
import com.vmware.bdd.aop.annotation.ClusterManagerPointcut;
import com.vmware.bdd.apitypes.ClusterCreate;
import com.vmware.bdd.apitypes.ClusterRead;
import com.vmware.bdd.apitypes.ClusterStatus;
import com.vmware.bdd.apitypes.ClusterType;
import com.vmware.bdd.apitypes.LimitInstruction;
import com.vmware.bdd.apitypes.NodeGroupCreate;
import com.vmware.bdd.apitypes.NodeGroupRead;
import com.vmware.bdd.apitypes.NodeRead;
import com.vmware.bdd.apitypes.NodeStatus;
import com.vmware.bdd.apitypes.PlacementPolicy.GroupAssociation;
import com.vmware.bdd.apitypes.Priority;
import com.vmware.bdd.apitypes.TaskRead;
import com.vmware.bdd.apitypes.VcClusterMap;
import com.vmware.bdd.entity.ClusterEntity;
import com.vmware.bdd.entity.NetworkEntity;
import com.vmware.bdd.entity.NodeEntity;
import com.vmware.bdd.entity.NodeGroupEntity;
import com.vmware.bdd.entity.VcResourcePoolEntity;
import com.vmware.bdd.manager.intf.IClusterEntityManager;
import com.vmware.bdd.service.IClusterHealService;
import com.vmware.bdd.service.IClusteringService;
import com.vmware.bdd.service.IExecutionService;
import com.vmware.bdd.service.impl.ClusterUserMgmtValidService;
import com.vmware.bdd.service.job.JobConstants;
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.utils.VcResourceUtils;
import com.vmware.bdd.software.mgmt.plugin.intf.SoftwareManager;
import com.vmware.bdd.software.mgmt.plugin.model.HadoopStack;
import com.vmware.bdd.software.mgmt.plugin.model.NodeGroupInfo;
import com.vmware.bdd.specpolicy.ClusterSpecFactory;
import com.vmware.bdd.specpolicy.CommonClusterExpandPolicy;
import com.vmware.bdd.spectypes.IronfanStack;
import com.vmware.bdd.spectypes.VcCluster;
import com.vmware.bdd.usermgmt.UserMgmtConstants;

public class ClusterManager {
    static final Logger logger = Logger.getLogger(ClusterManager.class);

    private ClusterConfigManager clusterConfigMgr;

    private INetworkService networkManager;

    private JobManager jobManager;

    private IResourceService resMgr;

    private IClusterEntityManager clusterEntityMgr;

    private RackInfoManager rackInfoMgr;

    private IClusteringService clusteringService;

    private IClusterHealService clusterHealService;

    private IExecutionService executionService;
    private SoftwareManagerCollector softwareManagerCollector;

    private ShrinkManager shrinkManager;

    @Autowired
    private INodeTemplateService nodeTemplateService;

    @Autowired
    private ClusterUserMgmtValidService clusterUserMgmtValidService;

    @Autowired
    private VcResourceFilterBuilder vcResourceFilterBuilder;

    @Autowired
    private UnsupportedOpsBlocker opsBlocker;

    @Autowired
    private IVcInventorySyncService syncService;

    private static boolean extraPackagesExisted = false;
    private static HashSet<String> extraRequiredPackages = getExtraRequiredPackages();
    private static final String commRegex = "-[0-9]+\\.[0-9]+.*\\.rpm";

    private static HashSet<String> getExtraRequiredPackages() {
        HashSet<String> hs = new HashSet<String>();
        String extraPackStr = Configuration
                .getString(Constants.SERENGETI_YUM_EXTRA_PACKAGES_CONFIG, Constants.SERENGETI_YUM_EXTRA_PACKAGES)
                .trim();
        if (!extraPackStr.isEmpty()) {
            String[] packs = extraPackStr.split(",");
            hs.addAll(Arrays.asList(packs));
        }
        return hs;
    }

    public JobManager getJobManager() {
        return jobManager;
    }

    public void setJobManager(JobManager jobManager) {
        this.jobManager = jobManager;
    }

    public ClusterConfigManager getClusterConfigMgr() {
        return clusterConfigMgr;
    }

    public void setClusterConfigMgr(ClusterConfigManager clusterConfigMgr) {
        this.clusterConfigMgr = clusterConfigMgr;
    }

    public INetworkService getNetworkManager() {
        return networkManager;
    }

    public void setNetworkManager(INetworkService networkManager) {
        this.networkManager = networkManager;
    }

    public IResourceService getResMgr() {
        return resMgr;
    }

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

    public IClusterEntityManager getClusterEntityMgr() {
        return clusterEntityMgr;
    }

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

    public RackInfoManager getRackInfoMgr() {
        return rackInfoMgr;
    }

    @Autowired
    public void setRackInfoMgr(RackInfoManager rackInfoMgr) {
        this.rackInfoMgr = rackInfoMgr;
    }

    public IClusteringService getClusteringService() {
        return clusteringService;
    }

    @Autowired
    public void setClusteringService(IClusteringService clusteringService) {
        this.clusteringService = clusteringService;
    }

    public IClusterHealService getClusterHealService() {
        return clusterHealService;
    }

    @Autowired
    public void setClusterHealService(IClusterHealService clusterHealService) {
        this.clusterHealService = clusterHealService;
    }

    public IExecutionService getExecutionService() {
        return executionService;
    }

    @Autowired
    public void setExecutionService(IExecutionService executionService) {
        this.executionService = executionService;
    }

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

    @Autowired
    public void setShrinkManager(ShrinkManager shrinkManager) {
        this.shrinkManager = shrinkManager;
    }

    public Map<String, Object> getClusterConfigManifest(String clusterName, List<String> targets,
            boolean needAllocIp) {
        ClusterCreate clusterConfig = clusterConfigMgr.getClusterConfig(clusterName, needAllocIp);
        Map<String, String> cloudProvider = resMgr.getCloudProviderAttributes();

        ClusterRead read = clusterEntityMgr.findClusterWithNodes(clusterName, true);

        Map<String, Object> attrs = new HashMap<String, Object>();

        if (Constants.IRONFAN.equalsIgnoreCase(clusterConfig.getAppManager())) {
            SoftwareManager softwareManager = clusterConfigMgr.getSoftwareManager(clusterConfig.getAppManager());
            IronfanStack stack = (IronfanStack) filterDistroFromAppManager(softwareManager,
                    clusterConfig.getDistro());
            CommonClusterExpandPolicy.expandDistro(clusterConfig, stack);

            attrs.put("cloud_provider", cloudProvider);
            attrs.put("cluster_definition", clusterConfig);
        }

        if (read != null) {
            attrs.put("cluster_data", read);
        }
        if (targets != null && !targets.isEmpty()) {
            attrs.put("targets", targets);
        }
        return attrs;
    }

    private void writeJsonFile(Map<String, Object> clusterConfig, File file) {
        Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().setPrettyPrinting().create();
        String jsonStr = gson.toJson(clusterConfig);

        AuAssert.check(jsonStr != null);
        logger.debug("writing cluster manifest in json " + jsonStr + " to file " + file);
        BufferedWriter out = null;
        try {
            out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
            out.write(jsonStr);
        } catch (IOException ex) {
            logger.error(ex.getMessage() + "\n failed to write cluster manifest to file " + file);
            throw BddException.INTERNAL(ex, "Failed to write cluster manifest.");
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    logger.error("falied to close writer" + out, e);
                }
            }
        }
    }

    public File writeClusterSpecFile(String targetName, File workDir, boolean needAllocIp) {
        String clusterName = targetName.split("-")[0];
        String fileName = clusterName + ".json";
        List<String> targets = new ArrayList<String>(1);
        targets.add(targetName);
        Map<String, Object> clusterConfig = getClusterConfigManifest(clusterName, targets, needAllocIp);

        File file = new File(workDir, fileName);
        writeJsonFile(clusterConfig, file);

        return file;
    }

    private void refreshClusterStatus(String clusterName) {
        List<String> clusterNames = new ArrayList<String>();
        clusterNames.add(clusterName);
        refreshClusterStatus(clusterNames);
    }

    private void refreshClusterStatus(List<String> clusterNames) {
        for (String clusterName : clusterNames) {
            Map<String, JobParameter> param = new TreeMap<String, JobParameter>();
            param.put(JobConstants.TIMESTAMP_JOB_PARAM, new JobParameter(new Date()));
            param.put(JobConstants.CLUSTER_NAME_JOB_PARAM, new JobParameter(clusterName));
            JobParameters jobParameters = new JobParameters(param);
            try {
                long jobExecutionId = jobManager.runJob(JobConstants.QUERY_CLUSTER_JOB_NAME, jobParameters);
                TaskRead status = jobManager.getJobExecutionStatus(jobExecutionId);
                while (status.getStatus() != TaskRead.Status.COMPLETED
                        && status.getStatus() != TaskRead.Status.FAILED
                        && status.getStatus() != TaskRead.Status.ABANDONED
                        && status.getStatus() != TaskRead.Status.STOPPED) {
                    Thread.sleep(1000);
                    status = jobManager.getJobExecutionStatus(jobExecutionId);
                }
            } catch (Exception ex) {
                logger.error("failed to run query cluster job: " + clusterName, ex);
            }
        }
    }

    public ClusterRead getClusterByName(String clusterName, boolean realTime) {
        ClusterEntity cluster = clusterEntityMgr.findByName(clusterName);
        if (cluster == null) {
            throw BddException.NOT_FOUND("Cluster", clusterName);
        }

        // return the latest data from db
        java.util.Date startTime = new java.util.Date();
        if (realTime && cluster.getStatus().isSyncServiceStatus()) {
            // for not running cluster, we don't sync up status from chef
            refreshClusterStatus(clusterName);
        }
        logger.debug("refreshClusterStatus time: " + (new java.util.Date().getTime() - startTime.getTime()));
        return realTime ? clusterEntityMgr.findClusterWithNodes(clusterName, false)
                : clusterEntityMgr.findClusterWithNodeGroups(clusterName);
    }

    public ClusterCreate getClusterSpec(String clusterName) {
        ClusterCreate spec = clusterConfigMgr.getClusterConfig(clusterName);
        spec.setVcClusters(null);
        spec.setDistroMap(null);
        spec.setSharedDatastorePattern(null);
        spec.setLocalDatastorePattern(null);
        spec.setNetworkings(null);
        spec.setNetworkConfig(null);
        spec.setName(null);
        spec.setValidateConfig(null);
        spec.setSpecFile(null);
        spec.setTopologyPolicy(null);
        spec.setHostToRackMap(null);
        spec.setHttpProxy(null);
        spec.setNoProxy(null);
        spec.setDistroVendor(null);
        spec.setDistroVersion(null);
        spec.setPassword(null);
        spec.setHostnamePrefix(null);
        NodeGroupCreate[] groups = spec.getNodeGroups();
        if (groups != null) {
            for (NodeGroupCreate group : groups) {
                group.setVcClusters(null);
                group.getStorage().setImagestoreNamePattern(null);
                group.getStorage().setDiskstoreNamePattern(null);
                group.setVmFolderPath(null);
                group.getStorage().setSplitPolicy(null);
                group.getStorage().setControllerType(null);
                group.getStorage().setAllocType(null);
                if (group.getPlacementPolicies() != null) {
                    List<GroupAssociation> associations = group.getPlacementPolicies().getGroupAssociations();
                    if (associations != null && associations.isEmpty()) {
                        group.getPlacementPolicies().setGroupAssociations(null);
                    }
                }
            }
        }
        return spec;
    }

    public List<ClusterRead> getClusters(Boolean realTime) {
        java.util.Date startTime = new java.util.Date();

        List<ClusterRead> clusters = new ArrayList<ClusterRead>();
        List<String> clusterNames = clusterEntityMgr.findAllClusterNames();
        logger.debug("findAllClusters time:" + (new java.util.Date().getTime() - startTime.getTime()));
        for (String clusterName : clusterNames) {
            clusters.add(getClusterByName(clusterName, realTime));
        }
        logger.debug("getClusters total time:" + (new java.util.Date().getTime() - startTime.getTime()));
        return clusters;
    }

    @ClusterManagerPointcut
    public Long createCluster(ClusterCreate createSpec, org.apache.commons.configuration.Configuration newParams)
            throws Exception {
        logger.debug("entering createCluster: " + createSpec.getName());

        SoftwareManager softMgr = softwareManagerCollector.getSoftwareManager(createSpec.getAppManager());
        // @ Todo if specify hadoop stack, we can get hadoop stack by stack name. Otherwise, we will get a default hadoop stack.
        HadoopStack stack = clusterConfigMgr.filterDistroFromAppManager(softMgr, createSpec.getDistro());

        // if the distro is not specified by the REST Client, add it.
        if (CommonUtil.isBlank(createSpec.getDistro())) {
            createSpec.setDistro(stack.getDistro());
        }
        createSpec.setDistroVendor(stack.getVendor());
        createSpec.setDistroVersion(stack.getFullVersion());
        if (createSpec.getType() == null) {
            createSpec.setType(ClusterType.DEFAULT);
        }

        // create auto rps if vc cluster/rp is specified
        createAutoRps(createSpec);
        ClusterCreate clusterSpec = ClusterSpecFactory.getCustomizedSpec(createSpec, softMgr.getType());
        verifyRequiredPackages(clusterSpec);
        clusterSpec.verifyClusterNameLength();
        clusterSpec.validateNodeGroupNames();
        //Check the cpu, memory max configuration according vm hardware version
        if (clusterSpec != null && clusterSpec.getNodeGroups() != null) {
            for (NodeGroupCreate ng : clusterSpec.getNodeGroups()) {
                String templateVmId = this.nodeTemplateService
                        .getNodeTemplateIdByName(clusterSpec.getTemplateName());
                if (templateVmId != null) {
                    VcResourceUtils.checkVmMaxConfiguration(templateVmId,
                            ng.getCpuNum() == null ? 0 : ng.getCpuNum(),
                            ng.getMemCapacityMB() == null ? 0 : ng.getMemCapacityMB());
                }
            }
        }
        String name = clusterSpec.getName();

        validateInfraConfig(clusterSpec);
        if (softMgr.getType().equalsIgnoreCase(Constants.CLOUDERA_MANAGER_PLUGIN_TYPE)) {
            validateServiceUserAndGroupsInLdap(clusterSpec);
        }
        logger.info("start to create a cluster: " + name);

        List<String> dsNames = getDsNamesToBeUsed(clusterSpec.getDsNames());
        if (dsNames.isEmpty()) {
            throw ClusterConfigException.NO_DATASTORE_ADDED();
        }
        List<VcCluster> vcClusters = getVcClustersToBeUsed(clusterSpec.getRpNames());
        if (vcClusters == null || vcClusters.isEmpty()) {
            throw ClusterConfigException.NO_RESOURCE_POOL_ADDED();
        }

        if (newParams.getBoolean(Constants.SKIP_REFRESH_VC, false)) {
            logger.info("skip refresh vc resources.");
        } else {
            VcResourceFilters filters = vcResourceFilterBuilder.build(dsNames, getRpNames(clusterSpec.getRpNames()),
                    createSpec.getNetworkNames());
            syncService.refreshInventory(filters);
        }

        // validate accessibility
        validateDatastore(dsNames, vcClusters);
        validateNetworkAccessibility(createSpec.getName(), createSpec.getNetworkNames(), vcClusters);

        // get the current cluster clone type from the configuration file
        // if it is not set in configuration file, then use INSTANT clone for VC6/ESXi6
        String type = getClusterCloneType();
        clusterSpec.setClusterCloneType(type);

        //save configuration into meta-db, and extend configuration using default spec
        clusterConfigMgr.createClusterConfig(clusterSpec);

        clusterEntityMgr.updateClusterStatus(name, ClusterStatus.PROVISIONING);
        Map<String, JobParameter> param = new TreeMap<String, JobParameter>();
        param.put(JobConstants.TIMESTAMP_JOB_PARAM, new JobParameter(new Date()));
        param.put(JobConstants.CLUSTER_SUCCESS_STATUS_JOB_PARAM, new JobParameter(ClusterStatus.RUNNING.name()));
        param.put(JobConstants.CLUSTER_FAILURE_STATUS_JOB_PARAM,
                new JobParameter(ClusterStatus.PROVISION_ERROR.name()));
        param.put(JobConstants.CLUSTER_NAME_JOB_PARAM, new JobParameter(createSpec.getName()));
        param.put(JobConstants.VERIFY_NODE_STATUS_SCOPE_PARAM,
                new JobParameter(JobConstants.CLUSTER_NODE_SCOPE_VALUE));
        JobParameters jobParameters = new JobParameters(param);
        return jobManager.runJob(JobConstants.CREATE_CLUSTER_JOB_NAME, jobParameters);
    }

    private Map<String, Set<String>> getServiceGroupUsers(ClusterCreate clusterSpec) {
        Map<String, Set<String>> groupUsers = new HashMap<>();
        Map<String, Object> configuration = clusterSpec.getConfiguration();
        if (MapUtils.isNotEmpty(configuration)) {
            Map<String, Map<String, String>> serviceUsersConfigs = (Map<String, Map<String, String>>) configuration
                    .get(UserMgmtConstants.SERVICE_USER_CONFIG_IN_SPEC_FILE);
            if (MapUtils.isNotEmpty(serviceUsersConfigs)) {
                for (Map<String, String> serviceUserConfig : serviceUsersConfigs.values()) {
                    String groupName = serviceUserConfig.get(UserMgmtConstants.SERVICE_USER_GROUP);
                    String userName = serviceUserConfig.get(UserMgmtConstants.SERVICE_USER_NAME);
                    if (groupName != null && !groupName.isEmpty() && userName != null && !userName.isEmpty()) {
                        if (groupUsers.get(groupName) == null) {
                            Set<String> users = new HashSet<>();
                            groupUsers.put(groupName, users);
                        }
                        groupUsers.get(groupName).add(userName);
                    }
                }
            }
        }
        return groupUsers;
    }

    private void validateServiceUserAndGroupsInLdap(ClusterCreate clusterSpec) {
        Map<String, Set<String>> groupUsers = getServiceGroupUsers(clusterSpec);
        if (!groupUsers.isEmpty()) {
            logger.info("going to validate Ldap user and groups for: " + new Gson().toJson(groupUsers));
            clusterUserMgmtValidService.validateGroupUsers(UserMgmtConstants.DEFAULT_USERMGMT_SERVER_NAME,
                    groupUsers);
            logger.info("validate service user in Ldap succeed");
        }
    }

    private void validateInfraConfig(ClusterCreate clusterSpec) {
        Map<String, Map<String, String>> infraConfig = clusterSpec.getInfrastructure_config();

        if (MapUtils.isEmpty(infraConfig)) {
            logger.info("no infra configuration in cluster create spec!");
            return;
        }

        Map<String, String> userMgmtCfg = infraConfig.get(UserMgmtConstants.LDAP_USER_MANAGEMENT);

        if (MapUtils.isEmpty(userMgmtCfg)) {
            logger.debug("no user management configuration section.");
        } else {
            clusterUserMgmtValidService.validateUserMgmtConfig(userMgmtCfg);

            logger.info("user management configuration validated successfully!");
        }
    }

    private void createAutoRps(ClusterCreate createSpec) {
        if (createSpec.getVcClusters() == null) {
            return;
        }
        // user specify vc resource pools directly, create auto rp in meta db dynamically
        List<VcCluster> vcClusters = createSpec.getVcClusters();
        List<String> rpNames = clusterConfigMgr.getRpMgr().addAutoResourcePools(vcClusters, true);
        logger.info("added automation resource pools: " + rpNames);
        createSpec.setRpNames(rpNames);
    }

    private void validateNetworkAccessibility(final String clusterName, List<String> networkList,
            List<VcCluster> clusters) {
        if (networkList == null || networkList.isEmpty()) {
            throw ClusterConfigException.NETWORK_IS_NOT_SPECIFIED(clusterName);
        }
        Set<String> networkNames = new HashSet<String>();
        networkNames.addAll(networkList);
        logger.info("start to validate network if exsit in db.");
        verifyNetworkNamesExsitInDB(networkNames, clusterName);
        logger.info("start to validate network accessibility.");
        if (!resMgr.isNetworkAccessibleByCluster(networkList, clusters)) {
            List<String> clusterNames = new ArrayList<String>();
            for (VcCluster cluster : clusters) {
                clusterNames.add(cluster.getName());
            }
            throw ClusterConfigException.NETWORK_UNACCESSIBLE(networkList, clusterNames);
        }
    }

    private void validateDatastore(List<String> dsNames, List<VcCluster> clusters) {
        // validate if there is any datastore is accessible by one cluster
        logger.info("start to validate accessibility for datastores: " + dsNames + ", and clusters: " + clusters);
        boolean found = false;
        for (String dsName : dsNames) {
            for (VcCluster vcCluster : clusters) {
                if (resMgr.isDatastoreAccessibleByCluster(dsName, vcCluster.getName())) {
                    found = true;
                    break;
                }
            }
        }
        if (!found) {
            // no any datastore is accessible by specified cluster
            List<String> vcClusterNames = new ArrayList<String>();
            for (VcCluster vcCluster : clusters) {
                vcClusterNames.add(vcCluster.getName());
            }
            throw ClusterConfigException.DATASTORE_UNACCESSIBLE(vcClusterNames, dsNames);
        }
    }

    /**
     * Get the dsNames to be used by the cluster
     */
    private List<String> getDsNamesToBeUsed(final List<String> specifiedDsNames) {
        if (specifiedDsNames == null || specifiedDsNames.isEmpty()) {
            List<String> allDsNames = new ArrayList<>();
            allDsNames.addAll(clusterConfigMgr.getDatastoreMgr().getAllDatastoreNames());
            return allDsNames;
        } else {
            return validateGivenDS(specifiedDsNames);
        }
    }

    private List<String> getRpNames(List<String> rpNames) {
        if (CollectionUtils.isEmpty(rpNames)) {
            List<String> newRpNameList = new ArrayList<>();
            newRpNameList.addAll(clusterConfigMgr.getRpMgr().getAllRPNames());
            return newRpNameList;
        }

        return rpNames;
    }

    /**
     * Get the vCenter clusters to be used by the cluster
     */
    private List<VcCluster> getVcClustersToBeUsed(List<String> rpNames) {
        List<VcCluster> clusters = null;
        if (rpNames == null || rpNames.isEmpty()) {
            clusters = clusterConfigMgr.getRpMgr().getAllVcResourcePool();
        } else {
            clusters = new ArrayList<VcCluster>();
            StringBuffer nonexistentRpNames = new StringBuffer();
            for (String rpName : rpNames) {
                List<VcCluster> vcClusters = clusterConfigMgr.getRpMgr().getVcResourcePoolByName(rpName);

                if (vcClusters == null) {
                    nonexistentRpNames.append(rpName).append(",");
                } else {
                    clusters.addAll(vcClusters);
                }
            }
            if (nonexistentRpNames.length() > 0) {
                nonexistentRpNames.delete(nonexistentRpNames.length() - 1, nonexistentRpNames.length());
                throw VcProviderException.RESOURCE_POOL_NOT_FOUND(nonexistentRpNames.toString());
            }
        }
        return clusters;
    }

    @ClusterManagerPointcut
    public Long configCluster(String clusterName, ClusterCreate createSpec) throws Exception {
        opsBlocker.blockUnsupportedOpsByCluster("configCluster", clusterName);

        logger.info("ClusterManager, config cluster " + clusterName);
        ClusterEntity cluster;

        if ((cluster = clusterEntityMgr.findByName(clusterName)) == null) {
            logger.error("cluster " + clusterName + " does not exist");
            throw BddException.NOT_FOUND("Cluster", clusterName);
        }

        ValidationUtils.validateVersion(clusterEntityMgr, clusterName);

        if (!cluster.getStatus().isActiveServiceStatus()
                && !ClusterStatus.CONFIGURE_ERROR.equals(cluster.getStatus())
                && !ClusterStatus.SERVICE_STOPPED.equals(cluster.getStatus())) {
            logger.error("can not config cluster: " + clusterName + ", " + cluster.getStatus());
            throw ClusterManagerException.UPDATE_NOT_ALLOWED_ERROR(clusterName,
                    "To update a cluster, its status must be RUNNING, CONFIGURE_ERROR or SERVICE_ERROR");
        }
        clusterConfigMgr.updateAppConfig(clusterName, createSpec);

        Map<String, JobParameter> param = new TreeMap<String, JobParameter>();
        param.put(JobConstants.CLUSTER_NAME_JOB_PARAM, new JobParameter(clusterName));
        param.put(JobConstants.TIMESTAMP_JOB_PARAM, new JobParameter(new Date()));
        param.put(JobConstants.CLUSTER_SUCCESS_STATUS_JOB_PARAM, new JobParameter(ClusterStatus.RUNNING.name()));
        param.put(JobConstants.CLUSTER_FAILURE_STATUS_JOB_PARAM,
                new JobParameter(ClusterStatus.CONFIGURE_ERROR.name()));
        JobParameters jobParameters = new JobParameters(param);
        clusterEntityMgr.updateClusterStatus(clusterName, ClusterStatus.CONFIGURING);
        clusterEntityMgr.cleanupActionError(clusterName);
        try {
            return jobManager.runJob(JobConstants.CONFIG_CLUSTER_JOB_NAME, jobParameters);
        } catch (Exception e) {
            logger.error("Failed to configure cluster " + clusterName, e);
            clusterEntityMgr.updateClusterStatus(clusterName, ClusterStatus.CONFIGURE_ERROR);
            throw e;
        }
    }

    public long enableLdap(String clusterName) throws Exception {
        Map<String, JobParameter> param = new TreeMap<String, JobParameter>();
        param.put(JobConstants.CLUSTER_NAME_JOB_PARAM, new JobParameter(clusterName));
        param.put(JobConstants.TIMESTAMP_JOB_PARAM, new JobParameter(new Date()));
        param.put(JobConstants.CLUSTER_SUCCESS_STATUS_JOB_PARAM, new JobParameter(ClusterStatus.RUNNING.name()));
        param.put(JobConstants.CLUSTER_FAILURE_STATUS_JOB_PARAM,
                new JobParameter(ClusterStatus.CONFIGURE_ERROR.name()));
        JobParameters jobParameters = new JobParameters(param);
        clusterEntityMgr.updateClusterStatus(clusterName, ClusterStatus.CONFIGURING);
        clusterEntityMgr.cleanupActionError(clusterName);
        try {
            return jobManager.runJob("configLdapUserMgmtJob", jobParameters);
        } catch (Exception e) {
            logger.error("Failed to configure cluster " + clusterName, e);
            clusterEntityMgr.updateClusterStatus(clusterName, ClusterStatus.CONFIGURE_ERROR);
            throw e;
        }
    }

    public Long resumeClusterCreation(String clusterName, org.apache.commons.configuration.Configuration newParams)
            throws Exception {
        logger.info("ClusterManager, resume cluster creation " + clusterName);

        ClusterEntity cluster = clusterEntityMgr.findByName(clusterName);

        if (cluster == null) {
            logger.error("cluster " + clusterName + " does not exist");
            throw BddException.NOT_FOUND("Cluster", clusterName);
        }

        ValidationUtils.validateVersion(clusterEntityMgr, clusterName);

        if (cluster.getStatus() != ClusterStatus.EXPANDING) {
            if (cluster.getStatus() != ClusterStatus.PROVISION_ERROR) {
                logger.error("can not resume creation of cluster: " + clusterName + ", " + cluster.getStatus());
                throw ClusterManagerException.UPDATE_NOT_ALLOWED_ERROR(clusterName,
                        "To update a cluster, its status must be PROVISION_ERROR or SERVICE_ERROR");
            }
        }

        List<String> dsNames = getDsNamesToBeUsed(cluster.getVcDatastoreNameList());
        if (dsNames.isEmpty()) {
            throw ClusterConfigException.NO_DATASTORE_ADDED();
        }
        List<VcCluster> vcClusters = getVcClustersToBeUsed(cluster.getVcRpNameList());
        if (vcClusters.isEmpty()) {
            throw ClusterConfigException.NO_RESOURCE_POOL_ADDED();
        }

        if (newParams.getBoolean(Constants.SKIP_REFRESH_VC, false)) {
            logger.info("skip refresh vc resources.");
        } else {
            VcResourceFilters filters = vcResourceFilterBuilder.build(dsNames,
                    getRpNames(cluster.getVcRpNameList()), cluster.fetchNetworkNameList());
            syncService.refreshInventory(filters);
        }

        // validate accessibility
        validateDatastore(dsNames, vcClusters);
        validateNetworkAccessibility(cluster.getName(), cluster.fetchNetworkNameList(), vcClusters);
        Map<String, JobParameter> param = new TreeMap<String, JobParameter>();
        param.put(JobConstants.CLUSTER_NAME_JOB_PARAM, new JobParameter(clusterName));
        param.put(JobConstants.TIMESTAMP_JOB_PARAM, new JobParameter(new Date()));
        param.put(JobConstants.CLUSTER_SUCCESS_STATUS_JOB_PARAM, new JobParameter(ClusterStatus.RUNNING.name()));
        param.put(JobConstants.CLUSTER_FAILURE_STATUS_JOB_PARAM,
                new JobParameter(ClusterStatus.PROVISION_ERROR.name()));
        JobParameters jobParameters = new JobParameters(param);
        clusterEntityMgr.updateClusterStatus(clusterName, ClusterStatus.PROVISIONING);
        clusterEntityMgr.cleanupActionError(clusterName);
        try {
            return jobManager.runJob(JobConstants.RESUME_CLUSTER_JOB_NAME, jobParameters);
        } catch (Exception e) {
            logger.error("Failed to resume cluster creation for cluster " + clusterName, e);
            clusterEntityMgr.updateClusterStatus(clusterName, ClusterStatus.PROVISION_ERROR);
            throw e;
        }
    }

    @ClusterManagerPointcut
    public Long deleteClusterByName(String clusterName) throws Exception {
        logger.info("ClusterManager, deleting cluster " + clusterName);

        ClusterEntity cluster = clusterEntityMgr.findByName(clusterName);

        if (cluster == null) {
            logger.error("cluster " + clusterName + " does not exist");
            throw BddException.NOT_FOUND("Cluster", clusterName);
        }

        if (!cluster.getStatus().isStableStatus()) {
            logger.error(
                    "cluster: " + clusterName + " cannot be deleted, it is in " + cluster.getStatus() + " status");
            throw ClusterManagerException.DELETION_NOT_ALLOWED_ERROR(clusterName,
                    "To delete a cluster, its status must be RUNNING, STOPPED, ERROR, PROVISION_ERROR, CONFIGURE_ERROR or UPGRADE_ERROR");
        }
        Map<String, JobParameter> param = new TreeMap<String, JobParameter>();
        param.put(JobConstants.CLUSTER_NAME_JOB_PARAM, new JobParameter(clusterName));
        param.put(JobConstants.TIMESTAMP_JOB_PARAM, new JobParameter(new Date()));
        param.put(JobConstants.CLUSTER_FAILURE_STATUS_JOB_PARAM, new JobParameter(ClusterStatus.ERROR.name()));
        JobParameters jobParameters = new JobParameters(param);
        clusterEntityMgr.updateClusterStatus(clusterName, ClusterStatus.DELETING);
        clusterEntityMgr.cleanupActionError(clusterName);
        try {
            return jobManager.runJob(JobConstants.DELETE_CLUSTER_JOB_NAME, jobParameters);
        } catch (Exception e) {
            logger.error("Failed to delete cluster " + clusterName, e);
            cluster = clusterEntityMgr.findByName(clusterName);
            if (cluster != null) {
                clusterEntityMgr.updateClusterStatus(clusterName, ClusterStatus.ERROR);
            }
            throw e;
        }
    }

    @ClusterManagerPointcut
    public Long upgradeClusterByName(String clusterName) throws Exception {
        logger.info("ClusterManager, upgrading cluster " + clusterName);

        ClusterEntity cluster = clusterEntityMgr.findByName(clusterName);

        if (cluster == null) {
            logger.error("cluster " + clusterName + " does not exist");
            throw BddException.NOT_FOUND("Cluster", clusterName);
        }

        if (!clusterEntityMgr.needUpgrade(clusterName)) {
            logger.error("cluster " + clusterName + " is the latest version already");
            throw ClusterManagerException.ALREADY_LATEST_VERSION_ERROR(clusterName);
        }

        if (!cluster.getStatus().isStableStatus()) {
            logger.error(
                    "cluster: " + clusterName + " cannot be upgraded, it is in " + cluster.getStatus() + " status");
            throw ClusterManagerException.UPGRADE_NOT_ALLOWED_ERROR(clusterName,
                    "To upgrade a cluster, its status must be RUNNING, STOPPED, ERROR, CONFIGURE_ERROR or UPGRADE_ERROR");
        }
        Map<String, JobParameter> param = new TreeMap<String, JobParameter>();
        param.put(JobConstants.CLUSTER_NAME_JOB_PARAM, new JobParameter(clusterName));
        param.put(JobConstants.TIMESTAMP_JOB_PARAM, new JobParameter(new Date()));
        param.put(JobConstants.CLUSTER_FAILURE_STATUS_JOB_PARAM,
                new JobParameter(ClusterStatus.UPGRADE_ERROR.name()));
        JobParameters jobParameters = new JobParameters(param);
        clusterEntityMgr.storeClusterLastStatus(clusterName);
        clusterEntityMgr.updateClusterStatus(clusterName, ClusterStatus.UPGRADING);
        clusterEntityMgr.updateNodesActionForUpgrade(clusterName, Constants.NODE_ACTION_UPGRADING);
        clusterEntityMgr.cleanupErrorForClusterUpgrade(clusterName);
        try {
            return jobManager.runJob(JobConstants.UPGRADE_CLUSTER_JOB_NAME, jobParameters);
        } catch (Exception e) {
            logger.error("Failed to upgrade cluster " + clusterName, e);
            cluster = clusterEntityMgr.findByName(clusterName);
            if (cluster != null) {
                clusterEntityMgr.updateClusterStatus(clusterName, ClusterStatus.UPGRADE_ERROR);
            }
            throw e;
        }
    }

    public Long startCluster(String clusterName, boolean force) throws Exception {
        logger.info("ClusterManager, starting cluster " + clusterName);

        ClusterEntity cluster = clusterEntityMgr.findByName(clusterName);
        if (cluster == null) {
            logger.error("cluster " + clusterName + " does not exist");
            throw BddException.NOT_FOUND("Cluster", clusterName);
        }

        ValidationUtils.validateVersion(clusterEntityMgr, clusterName);

        if (cluster.getStatus().isActiveServiceStatus()) {
            logger.error("cluster " + clusterName + " is running already");
            throw ClusterManagerException.ALREADY_STARTED_ERROR(clusterName);
        }

        if (!ClusterStatus.STOPPED.equals(cluster.getStatus())
                && !ClusterStatus.ERROR.equals(cluster.getStatus())) {
            logger.error(
                    "cluster " + clusterName + " cannot be started, it is in " + cluster.getStatus() + " status");
            throw ClusterManagerException.START_NOT_ALLOWED_ERROR(clusterName,
                    "To start a cluster, its status must be STOPPED or ERROR");
        }

        cluster.setVhmTargetNum(-1);
        clusterEntityMgr.update(cluster);
        clusterEntityMgr.cleanupActionError(clusterName);
        Map<String, JobParameter> param = new TreeMap<String, JobParameter>();
        param.put(JobConstants.CLUSTER_NAME_JOB_PARAM, new JobParameter(clusterName));
        param.put(Constants.FORCE_CLUSTER_OPERATION_JOB_PARAM, new JobParameter(String.valueOf(force)));
        param.put(JobConstants.TIMESTAMP_JOB_PARAM, new JobParameter(new Date()));
        param.put(JobConstants.CLUSTER_SUCCESS_STATUS_JOB_PARAM, new JobParameter(ClusterStatus.RUNNING.name()));
        param.put(JobConstants.CLUSTER_FAILURE_STATUS_JOB_PARAM, new JobParameter(ClusterStatus.ERROR.name()));
        JobParameters jobParameters = new JobParameters(param);
        clusterEntityMgr.updateClusterStatus(clusterName, ClusterStatus.STARTING);
        try {
            return jobManager.runJob(JobConstants.START_CLUSTER_JOB_NAME, jobParameters);
        } catch (Exception e) {
            logger.error("Failed to start cluster " + clusterName, e);
            clusterEntityMgr.updateClusterStatus(clusterName, ClusterStatus.ERROR);
            throw e;
        }
    }

    @ClusterManagerPointcut
    public Long stopCluster(String clusterName) throws Exception {
        logger.info("ClusterManager, stopping cluster " + clusterName);

        ClusterEntity cluster = clusterEntityMgr.findByName(clusterName);
        if (cluster == null) {
            logger.error("cluster " + clusterName + " does not exist");
            throw BddException.NOT_FOUND("Cluster", clusterName);
        }

        ValidationUtils.validateVersion(clusterEntityMgr, clusterName);

        if (ClusterStatus.STOPPED.equals(cluster.getStatus())) {
            logger.error("cluster " + clusterName + " is stopped already");
            throw ClusterManagerException.ALREADY_STOPPED_ERROR(clusterName);
        }

        if (!cluster.getStatus().isActiveServiceStatus()
                && !ClusterStatus.SERVICE_STOPPED.equals(cluster.getStatus())
                && !ClusterStatus.ERROR.equals(cluster.getStatus())
                && !ClusterStatus.SERVICE_WARNING.equals(cluster.getStatus())) {
            logger.error(
                    "cluster " + clusterName + " cannot be stopped, it is in " + cluster.getStatus() + " status");
            throw ClusterManagerException.STOP_NOT_ALLOWED_ERROR(clusterName,
                    "To stop a cluster, its status must be RUNNING, ERROR, SERVICE_WARNING or SERVICE_STOPPED");
        }
        Map<String, JobParameter> param = new TreeMap<String, JobParameter>();
        param.put(JobConstants.CLUSTER_NAME_JOB_PARAM, new JobParameter(clusterName));
        param.put(JobConstants.TIMESTAMP_JOB_PARAM, new JobParameter(new Date()));
        param.put(JobConstants.CLUSTER_SUCCESS_STATUS_JOB_PARAM, new JobParameter(ClusterStatus.STOPPED.name()));
        param.put(JobConstants.CLUSTER_FAILURE_STATUS_JOB_PARAM, new JobParameter(ClusterStatus.ERROR.name()));
        JobParameters jobParameters = new JobParameters(param);
        clusterEntityMgr.updateClusterStatus(clusterName, ClusterStatus.STOPPING);
        clusterEntityMgr.cleanupActionError(clusterName);
        try {
            return jobManager.runJob(JobConstants.STOP_CLUSTER_JOB_NAME, jobParameters);
        } catch (Exception e) {
            logger.error("Failed to stop cluster " + clusterName, e);
            clusterEntityMgr.updateClusterStatus(clusterName, ClusterStatus.ERROR);
            throw e;
        }
    }

    @ClusterManagerPointcut
    public Long resizeCluster(String clusterName, String nodeGroupName, int instanceNum, boolean force,
            org.apache.commons.configuration.Configuration newParams) throws Exception {
        logger.info("ClusterManager, updating node group " + nodeGroupName + " in cluster " + clusterName
                + " reset instance number to " + instanceNum);

        ClusterEntity cluster = clusterEntityMgr.findByName(clusterName);
        if (cluster == null) {
            logger.error("cluster " + clusterName + " does not exist");
            throw BddException.NOT_FOUND("Cluster", clusterName);
        }

        ValidationUtils.validateVersion(clusterEntityMgr, clusterName);

        List<String> dsNames = getDsNamesToBeUsed(cluster.getVcDatastoreNameList());
        if (dsNames.isEmpty()) {
            throw ClusterConfigException.NO_DATASTORE_ADDED();
        }

        List<VcCluster> vcClusters = getVcClustersToBeUsed(cluster.getVcRpNameList());
        if (vcClusters.isEmpty()) {
            throw ClusterConfigException.NO_RESOURCE_POOL_ADDED();
        }

        if (newParams.getBoolean(Constants.SKIP_REFRESH_VC, false)) {
            logger.info("skip refresh vc resources.");
        } else {
            VcResourceFilters filters = vcResourceFilterBuilder.build(dsNames,
                    getRpNames(cluster.getVcRpNameList()), cluster.fetchNetworkNameList());
            syncService.refreshInventory(filters);
        }

        // validate accessibility
        validateDatastore(dsNames, vcClusters);
        validateNetworkAccessibility(cluster.getName(), cluster.fetchNetworkNameList(), vcClusters);

        NodeGroupEntity group = clusterEntityMgr.findByName(cluster, nodeGroupName);
        if (group == null) {
            logger.error("nodegroup " + nodeGroupName + " of cluster " + clusterName + " does not exist");
            throw ClusterManagerException.NODEGROUP_NOT_FOUND_ERROR(nodeGroupName);
        }

        AuAssert.check(!group.getRoleNameList().isEmpty(), "roles should not be empty");
        SoftwareManager softMgr = softwareManagerCollector.getSoftwareManager(cluster.getAppManager());
        NodeGroupInfo groupInfo = clusterEntityMgr.toNodeGroupInfo(clusterName, nodeGroupName);
        List<String> unsupportedRoles = softMgr.validateRolesForScaleOut(groupInfo);
        if (!unsupportedRoles.isEmpty()) {
            logger.info("can not resize node group with role: " + unsupportedRoles);
            throw ClusterManagerException.ROLES_NOT_SUPPORTED(unsupportedRoles);
        }

        logger.info("check cluster status before resize action:" + cluster.getStatus());
        if (!cluster.getStatus().isActiveServiceStatus()) {
            logger.error("cluster " + clusterName + " can be resized only in RUNNING status, it is now in "
                    + cluster.getStatus() + " status");
            throw ClusterManagerException.UPDATE_NOT_ALLOWED_ERROR(clusterName,
                    "To update a cluster, its status must be RUNNING");
        }

        if (instanceNum < group.getDefineInstanceNum()) {
            try {
                softMgr.validateRolesForShrink(groupInfo);
                return shrinkManager.shrinkNodeGroup(clusterName, nodeGroupName, instanceNum);
            } catch (Exception e) {
                if (!(e instanceof ShrinkException)) {
                    logger.error("Failed to shrink cluster " + clusterName, e);
                    throw ShrinkException.SHRINK_NODE_GROUP_FAILED(e, clusterName, e.getMessage());
                }
            }
        }

        //when use force, we allow user to rerun resize to fix bad nodes
        if ((instanceNum == group.getDefineInstanceNum()) && !force) {
            logger.error("the new instanceNum " + instanceNum + " shouldn't be the same as the old one ");
            throw ClusterManagerException.NO_NEED_TO_RESIZE(clusterName, nodeGroupName, instanceNum);
        }

        Integer instancePerHost = group.getInstancePerHost();
        if (instancePerHost != null && instanceNum % instancePerHost != 0) {
            throw BddException.INVALID_PARAMETER("instance number", new StringBuilder(100).append(instanceNum)
                    .append(".instanceNum must be evenly divisible by instancePerHost").toString());
        }

        ValidationUtils.validHostNumber(clusterEntityMgr, group, instanceNum);
        ValidationUtils.hasEnoughHost(rackInfoMgr, clusterEntityMgr, group, instanceNum);

        int oldInstanceNum = group.getDefineInstanceNum();
        group.setDefineInstanceNum(instanceNum);

        clusterEntityMgr.update(group);
        clusterEntityMgr.cleanupActionError(clusterName);
        // create job
        Map<String, JobParameter> param = new TreeMap<String, JobParameter>();
        param.put(JobConstants.CLUSTER_NAME_JOB_PARAM, new JobParameter(clusterName));
        param.put(JobConstants.GROUP_NAME_JOB_PARAM, new JobParameter(nodeGroupName));
        param.put(JobConstants.GROUP_INSTANCE_NEW_NUMBER_JOB_PARAM, new JobParameter(Long.valueOf(instanceNum)));
        param.put(JobConstants.GROUP_INSTANCE_OLD_NUMBER_JOB_PARAM, new JobParameter(Long.valueOf(oldInstanceNum)));
        param.put(JobConstants.TIMESTAMP_JOB_PARAM, new JobParameter(new Date()));
        param.put(JobConstants.CLUSTER_SUCCESS_STATUS_JOB_PARAM, new JobParameter(ClusterStatus.RUNNING.name()));
        param.put(JobConstants.CLUSTER_FAILURE_STATUS_JOB_PARAM, new JobParameter(ClusterStatus.RUNNING.name()));
        param.put(JobConstants.VERIFY_NODE_STATUS_SCOPE_PARAM,
                new JobParameter(JobConstants.GROUP_NODE_SCOPE_VALUE));
        param.put(Constants.FORCE_CLUSTER_OPERATION_JOB_PARAM, new JobParameter(String.valueOf(force)));
        JobParameters jobParameters = new JobParameters(param);
        clusterEntityMgr.updateClusterStatus(clusterName, ClusterStatus.UPDATING);
        try {
            return jobManager.runJob(JobConstants.RESIZE_CLUSTER_JOB_NAME, jobParameters);
        } catch (Exception e) {
            logger.error("Failed to resize cluster " + clusterName, e);
            clusterEntityMgr.updateClusterStatus(clusterName, ClusterStatus.RUNNING);
            group.setDefineInstanceNum(oldInstanceNum);
            clusterEntityMgr.update(group);
            throw e;
        }
    }

    /**
     * set cluster parameters synchronously
     *
     * @param clusterName
     * @param activeComputeNodeNum
     * @param minComputeNodeNum
     * @param maxComputeNodeNum
     * @param enableAuto
     * @param ioPriority
     * @return
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public List<String> syncSetParam(String clusterName, Integer activeComputeNodeNum, Integer minComputeNodeNum,
            Integer maxComputeNodeNum, Boolean enableAuto, Priority ioPriority) throws Exception {

        //allow set ioshare only.
        if (enableAuto != null || activeComputeNodeNum != null || maxComputeNodeNum != null
                || minComputeNodeNum != null) {
            opsBlocker.blockUnsupportedOpsByCluster("syncSetElasticity", clusterName);
        }

        ClusterEntity cluster = clusterEntityMgr.findByName(clusterName);
        ClusterRead clusterRead = getClusterByName(clusterName, false);
        if (cluster == null) {
            logger.error("cluster " + clusterName + " does not exist");
            throw BddException.NOT_FOUND("Cluster", clusterName);
        }

        ValidationUtils.validateVersion(clusterEntityMgr, clusterName);

        clusterEntityMgr.cleanupActionError(clusterName);
        //update vm ioshares
        if (ioPriority != null) {
            prioritizeCluster(clusterName, ioPriority);
        }

        // as prioritizeCluster will update clusterEntity, here we need to refresh to avoid overriding
        cluster = clusterEntityMgr.findByName(clusterName);

        if (enableAuto != null && enableAuto != cluster.getAutomationEnable()) {
            if (enableAuto && cluster.getDistroVendor().equalsIgnoreCase(Constants.MAPR_VENDOR)) {
                logger.error("cluster " + clusterName + " is a MAPR distro, which cannot be auto scaled");
                throw BddException.NOT_ALLOWED_SCALING("cluster", clusterName);
            }
            cluster.setAutomationEnable(enableAuto);
        }

        if (minComputeNodeNum != null && minComputeNodeNum != cluster.getVhmMinNum()) {
            cluster.setVhmMinNum(minComputeNodeNum);
        }

        if (maxComputeNodeNum != null && maxComputeNodeNum != cluster.getVhmMaxNum()) {
            cluster.setVhmMaxNum(maxComputeNodeNum);
        }

        List<String> nodeGroupNames = new ArrayList<String>();
        if ((enableAuto != null || minComputeNodeNum != null || maxComputeNodeNum != null
                || activeComputeNodeNum != null) && !clusterRead.validateSetManualElasticity(nodeGroupNames)) {
            throw BddException.INVALID_PARAMETER("cluster", clusterName);
        }

        if (activeComputeNodeNum != null) {
            if (!activeComputeNodeNum.equals(cluster.getVhmTargetNum())) {
                cluster.setVhmTargetNum(activeComputeNodeNum);
            }
        }

        //enableAuto is only set during cluster running status and
        //other elasticity attributes are only set during cluster running/stop status
        if ((enableAuto != null) && !cluster.getStatus().isActiveServiceStatus()) {
            logger.error("Cannot change elasticity mode, when cluster " + clusterName + " is in "
                    + cluster.getStatus() + " status");
            throw ClusterManagerException.SET_AUTO_ELASTICITY_NOT_ALLOWED_ERROR(clusterName,
                    "The cluster's status must be RUNNING");
        }
        if (!cluster.getStatus().isActiveServiceStatus()
                && !ClusterStatus.SERVICE_STOPPED.equals(cluster.getStatus())
                && !ClusterStatus.STOPPED.equals(cluster.getStatus())) {
            logger.error("Cannot change elasticity parameters, when cluster " + clusterName + " is in "
                    + cluster.getStatus() + " status");
            throw ClusterManagerException.SET_AUTO_ELASTICITY_NOT_ALLOWED_ERROR(clusterName,
                    "The cluster's status must be RUNNING, SERVICE_WARNING, SERVICE_ERROR or STOPPED");
        }

        clusterEntityMgr.update(cluster);

        //update vhm extra config file
        if (enableAuto != null || minComputeNodeNum != null || maxComputeNodeNum != null) {
            boolean success = clusteringService.setAutoElasticity(clusterName, false);
            if (!success) {
                throw ClusterManagerException.FAILED_TO_SET_AUTO_ELASTICITY_ERROR(clusterName,
                        "Could not update elasticity configuration file");
            }
        }

        //waitForManual if switch to Manual and targetNodeNum is null
        if (enableAuto != null && !enableAuto && cluster.getVhmTargetNum() == null) {
            JobUtils.waitForManual(clusterName, executionService);
        }

        return nodeGroupNames;
    }

    /**
     * set cluster parameters asynchronously
     *
     * @param clusterName
     * @param activeComputeNodeNum
     * @param minComputeNodeNum
     * @param maxComputeNodeNum
     * @param enableAuto
     * @param ioPriority
     * @return
     * @throws Exception
     */
    @ClusterManagerPointcut
    public Long asyncSetParam(String clusterName, Integer activeComputeNodeNum, Integer minComputeNodeNum,
            Integer maxComputeNodeNum, Boolean enableAuto, Priority ioPriority) throws Exception {
        //allow set ioshare only.
        if (enableAuto != null || activeComputeNodeNum != null || maxComputeNodeNum != null
                || minComputeNodeNum != null) {
            opsBlocker.blockUnsupportedOpsByCluster("asyncSetElasticity", clusterName);
        }

        ValidationUtils.validateVersion(clusterEntityMgr, clusterName);

        syncSetParam(clusterName, activeComputeNodeNum, minComputeNodeNum, maxComputeNodeNum, enableAuto,
                ioPriority);

        ClusterRead cluster = getClusterByName(clusterName, false);
        // cluster must be running status
        if (!cluster.getStatus().isActiveServiceStatus()) {
            String msg = "Cluster " + clusterName + " is not running.";
            logger.error(msg);
            throw ClusterManagerException.SET_MANUAL_ELASTICITY_NOT_ALLOWED_ERROR(msg);
        }

        Map<String, JobParameter> param = new TreeMap<String, JobParameter>();
        param.put(JobConstants.TIMESTAMP_JOB_PARAM, new JobParameter(new Date()));
        param.put(JobConstants.CLUSTER_NAME_JOB_PARAM, new JobParameter(clusterName));
        // TODO: transfer SET_TARGET/UNLIMIT from CLI directly
        if (activeComputeNodeNum == null) {
            param.put(JobConstants.VHM_ACTION_JOB_PARAM, new JobParameter(LimitInstruction.actionWaitForManual));
        } else if (activeComputeNodeNum == -1) {
            param.put(JobConstants.VHM_ACTION_JOB_PARAM, new JobParameter(LimitInstruction.actionUnlimit));
        } else {
            param.put(JobConstants.VHM_ACTION_JOB_PARAM, new JobParameter(LimitInstruction.actionSetTarget));
        }
        if (activeComputeNodeNum != null) {
            param.put(JobConstants.ACTIVE_COMPUTE_NODE_NUMBER_JOB_PARAM,
                    new JobParameter(Long.valueOf(activeComputeNodeNum)));
        }
        param.put(JobConstants.CLUSTER_SUCCESS_STATUS_JOB_PARAM, new JobParameter(ClusterStatus.RUNNING.name()));
        param.put(JobConstants.CLUSTER_FAILURE_STATUS_JOB_PARAM, new JobParameter(ClusterStatus.RUNNING.name()));
        JobParameters jobParameters = new JobParameters(param);
        try {
            clusterEntityMgr.updateClusterStatus(clusterName, ClusterStatus.VHM_RUNNING);
            return jobManager.runJob(JobConstants.SET_MANUAL_ELASTICITY_JOB_NAME, jobParameters);
        } catch (Exception e) {
            logger.error("Failed to set manual elasticity for cluster " + clusterName, e);
            clusterEntityMgr.updateClusterStatus(clusterName, ClusterStatus.RUNNING);
            throw e;
        }
    }

    @ClusterManagerPointcut
    @Transactional
    public void updateCluster(ClusterCreate clusterUpdate, boolean ignoreWarning, boolean appendResource)
            throws Exception {
        String clusterName = clusterUpdate.getName();
        ClusterEntity cluster = clusterEntityMgr.findByName(clusterName);
        if (cluster == null) {
            logger.error("cluster " + clusterName + " does not exist");
            throw BddException.NOT_FOUND("Cluster", clusterName);
        }
        ValidationUtils.validateVersion(clusterEntityMgr, clusterName);

        List<String> newRpList = clusterUpdate.getRpNames();

        if (!CollectionUtils.isEmpty(newRpList)) {
            //Check whether the new resourcepools are valid in vc_resource_pool
            List<String> existRPs = validateGivenRp(newRpList);
            if (CollectionUtils.isEmpty(existRPs)) {
                throw ClusterConfigException.NO_RESOURCE_POOL_ADDED();
            }
            //check whether the updating resourcepool only the new ones
            if (appendResource == true) {
                List<String> usingRpList = cluster.getVcRpNameList();
                if (CollectionUtils.isEmpty(usingRpList)) {
                    return;
                }
                newRpList = appendResource(usingRpList, newRpList);
            } else {
                //Check whether the new resourcepools include the current resourcepools which are used by this cluster
                Set<VcResourcePoolEntity> usedVCRps = cluster.getUsedRps();
                List<String> usedRpList = new ArrayList<String>(usedVCRps.size());
                for (VcResourcePoolEntity rp : usedVCRps) {
                    usedRpList.add(rp.getName());
                }
                logger.info("Updating resourcepools for cluster " + clusterName + " from " + usedRpList.toString()
                        + " to " + newRpList.toString());
                if (!newRpList.containsAll(usedRpList)) {
                    throw BddException.NEW_RP_EXCLUDE_OLD_RP(null, usedRpList.toString(), newRpList.toString());
                }
            }

            cluster.setVcRpNameList(newRpList);
        }

        List<String> newDsList = clusterUpdate.getDsNames();

        if (!CollectionUtils.isEmpty(newDsList)) {
            //Check whether the new datastores are valid vc_data_store
            if (CollectionUtils.isEmpty(validateGivenDS(newDsList))) {
                throw ClusterConfigException.NO_DATASTORE_ADDED();
            }
            //check whether the updating resourcepool only the new ones
            if (appendResource == true) {
                List<String> usingDsList = cluster.getVcDatastoreNameList();
                if (CollectionUtils.isEmpty(usingDsList))
                    return;
                newDsList = appendResource(usingDsList, newDsList);
            }
            //Check whether the new dsNames contain all datastores used by this cluster already
            List<String> usedDsList = cluster.getVcDatastoreNameList();
            logger.info("Updating dsNames for cluster " + clusterName + " from " + usedDsList + " to "
                    + newDsList.toString());
            if (!ignoreWarning) {
                if (CollectionUtils.isEmpty(usedDsList)) {
                    throw WarningMessageException.SET_EMPTY_DATASTORES_TO_NON_EMTPY(null, newDsList.toString());
                } else if (!newDsList.containsAll(usedDsList)) {
                    throw WarningMessageException.NEW_DATASTORES_EXCLUDE_OLD_DATASTORES(null, newDsList.toString(),
                            usedDsList.toString());
                }
            }

            cluster.setVcDatastoreNameList(newDsList);
        }

        clusterEntityMgr.update(cluster);
    }

    private List<String> validateGivenDS(List<String> specifiedDsNames) {
        List<String> exitsDs = new ArrayList<String>();
        Set<String> allDs = clusterConfigMgr.getDatastoreMgr().getAllDatastoreNames();
        StringBuffer nonexistentDsNames = new StringBuffer();

        for (String dsName : specifiedDsNames) {
            if (!allDs.contains(dsName))
                nonexistentDsNames.append(dsName).append(",");
            else
                exitsDs.add(dsName);
        }

        if (nonexistentDsNames.length() > 0) {
            nonexistentDsNames.delete(nonexistentDsNames.length() - 1, nonexistentDsNames.length());
            throw VcProviderException.DATASTORE_NOT_FOUND(nonexistentDsNames.toString());
        }

        return exitsDs;
    }

    private List<String> validateGivenRp(List<String> specifiedRpNames) {
        List<String> exitsRps = new ArrayList<String>();
        Set<String> allRps = clusterConfigMgr.getRpMgr().getAllRPNames();
        StringBuffer nonexistentRpNames = new StringBuffer();

        for (String rpName : specifiedRpNames) {
            if (!allRps.contains(rpName))
                nonexistentRpNames.append(rpName).append(",");
            else
                exitsRps.add(rpName);
        }

        if (nonexistentRpNames.length() > 0) {
            nonexistentRpNames.delete(nonexistentRpNames.length() - 1, nonexistentRpNames.length());
            throw VcProviderException.RESOURCE_POOL_NOT_FOUND(nonexistentRpNames.toString());
        }

        return exitsRps;
    }

    private List<String> appendResource(List<String> fullRs, List<String> addRs) {
        for (String rs : addRs) {
            if (!fullRs.contains(rs)) {
                fullRs.add(rs);
            }
        }
        return fullRs;
    }

    /*
     * Change the disk I/O priority of the cluster or a node group
     */
    public void prioritizeCluster(String clusterName, Priority ioShares) throws Exception {
        ClusterEntity cluster = clusterEntityMgr.findByName(clusterName);
        if (cluster == null) {
            logger.error("cluster " + clusterName + " does not exist");
            throw BddException.NOT_FOUND("Cluster", clusterName);
        }

        if (ioShares.equals(cluster.getIoShares())) {
            return;
        }

        logger.info("Change all nodes' disk I/O shares to " + ioShares + " in the cluster " + clusterName);

        // cluster must be in RUNNING or STOPPEED status
        if (!cluster.getStatus().isActiveServiceStatus() && !ClusterStatus.STOPPED.equals(cluster.getStatus())) {
            String msg = "The cluster's status must be RUNNING or STOPPED";
            logger.error(msg);
            throw ClusterManagerException.PRIORITIZE_CLUSTER_NOT_ALLOWED_ERROR(clusterName, msg);
        }

        // get target nodeuster
        List<NodeEntity> targetNodes;
        targetNodes = clusterEntityMgr.findAllNodes(clusterName);

        if (targetNodes.isEmpty()) {
            throw ClusterManagerException.PRIORITIZE_CLUSTER_NOT_ALLOWED_ERROR(clusterName,
                    "Target node set is empty");
        }

        clusterEntityMgr.cleanupActionError(clusterName);
        // call clustering service to set the io shares
        Map<String, String> failedNodes = clusteringService.configIOShares(clusterName, targetNodes, ioShares);
        if (failedNodes.isEmpty()) {
            logger.info("configured " + targetNodes.size() + " nodes' IO share level to " + ioShares.toString());
        } else {
            // update node table
            for (String name : failedNodes.keySet()) {
                NodeEntity node = clusterEntityMgr.findNodeByName(name);
                node.setActionFailed(true);
                node.setErrMessage(failedNodes.get(name));
                clusterEntityMgr.update(node);
            }
            throw ClusterManagerException.PRIORITIZE_CLUSTER_FAILED(clusterName, failedNodes.size(),
                    targetNodes.size());
        }

        // update io shares in db
        cluster.setIoShares(ioShares);
        clusterEntityMgr.update(cluster);
    }

    @ClusterManagerPointcut
    public Long fixDiskFailures(String clusterName, String groupName) throws Exception {
        opsBlocker.blockUnsupportedOpsByCluster("fixDisk", clusterName);

        ClusterEntity cluster = clusterEntityMgr.findByName(clusterName);
        if (cluster == null) {
            logger.error("cluster " + clusterName + " does not exist");
            throw BddException.NOT_FOUND("Cluster", clusterName);
        }
        SoftwareManager softMgr = softwareManagerCollector.getSoftwareManager(cluster.getAppManager());

        ValidationUtils.validateVersion(clusterEntityMgr, clusterName);

        ClusterStatus oldStatus = cluster.getStatus();

        if (!oldStatus.isActiveServiceStatus()) {
            throw ClusterHealServiceException.NOT_SUPPORTED(clusterName, "The cluster status must be RUNNING");
        }

        List<NodeGroupEntity> nodeGroups;

        if (groupName != null) {
            NodeGroupEntity nodeGroup = clusterEntityMgr.findByName(clusterName, groupName);
            if (nodeGroup == null) {
                logger.error("node group " + groupName + " does not exist");
                throw BddException.NOT_FOUND("group", groupName);
            }

            nodeGroups = new ArrayList<NodeGroupEntity>(1);
            nodeGroups.add(nodeGroup);
        } else {
            nodeGroups = clusterEntityMgr.findAllGroups(clusterName);
        }

        // only fix worker nodes that have datanode or tasktracker roles
        boolean workerNodesFound = false;
        JobParametersBuilder parametersBuilder = new JobParametersBuilder();
        List<JobParameters> jobParameterList = new ArrayList<JobParameters>();

        for (NodeGroupEntity nodeGroup : nodeGroups) {
            List<String> roles = nodeGroup.getRoleNameList();

            workerNodesFound = true;
            for (NodeEntity node : clusterEntityMgr.findAllNodes(clusterName, nodeGroup.getName())) {
                if (node.isObsoleteNode()) {
                    logger.info("Ingore node " + node.getVmName() + ", for it violate VM name convention."
                            + "or exceed defined group instance number. ");
                    continue;
                }
                if (clusterHealService.hasBadDisks(node.getVmName())) {
                    logger.warn("node " + node.getVmName() + " has bad disks. Fixing it..");

                    boolean vmPowerOn = (node.getStatus().ordinal() != NodeStatus.POWERED_OFF.ordinal());

                    JobParameters nodeParameters = parametersBuilder
                            .addString(JobConstants.CLUSTER_NAME_JOB_PARAM, clusterName)
                            .addString(JobConstants.TARGET_NAME_JOB_PARAM, node.getVmName())
                            .addString(JobConstants.GROUP_NAME_JOB_PARAM, nodeGroup.getName())
                            .addString(JobConstants.SUB_JOB_NODE_NAME, node.getVmName())
                            .addString(JobConstants.IS_VM_POWER_ON, String.valueOf(vmPowerOn)).toJobParameters();
                    jobParameterList.add(nodeParameters);
                }
            }
        }

        if (!workerNodesFound) {
            throw ClusterHealServiceException.NOT_SUPPORTED(clusterName,
                    "only support fixing disk failures for worker/non-management nodes");
        }

        // all target nodes are healthy, simply return
        if (jobParameterList.isEmpty()) {
            logger.info("all target nodes are healthy, simply return");
            throw ClusterHealServiceException.NOT_NEEDED(clusterName);
        }

        try {
            clusterEntityMgr.updateClusterStatus(clusterName, ClusterStatus.MAINTENANCE);
            clusterEntityMgr.cleanupActionError(clusterName);
            return jobManager.runSubJobForNodes(JobConstants.FIX_NODE_DISK_FAILURE_JOB_NAME, jobParameterList,
                    clusterName, oldStatus, oldStatus);
        } catch (Exception e) {
            logger.error("failed to fix disk failures, " + e.getMessage());
            throw e;
        }
    }

    public Map<String, String> getRackTopology(String clusterName, String topology) {
        ClusterRead cluster = clusterEntityMgr.findClusterWithNodes(clusterName, false);
        Set<String> hosts = new HashSet<String>();
        List<NodeRead> nodes = new ArrayList<NodeRead>();
        for (NodeGroupRead nodeGroup : cluster.getNodeGroups()) {
            for (NodeRead node : nodeGroup.getInstances()) {
                if (node.getMoId() != null) {
                    hosts.add(node.getHostName());
                    nodes.add(node);
                }
            }
        }

        if (CommonUtil.isBlank(topology)) {
            topology = cluster.getTopologyPolicy().toString();
        }

        AuAssert.check(hosts.size() > 0);
        clusterConfigMgr.validateRackTopologyUploaded(hosts, topology);

        return clusterConfigMgr.buildTopology(nodes, topology);
    }

    public HadoopStack filterDistroFromAppManager(SoftwareManager softwareManager, String distroName) {
        return clusterConfigMgr.filterDistroFromAppManager(softwareManager, distroName);
    }

    private void verifyNetworkNamesExsitInDB(Set<String> networkNames, String clusterName) {
        NetworkEntity networkEntity = null;
        for (String networkName : networkNames) {
            networkEntity = networkManager.getNetworkEntityByName(networkName);
            if (networkEntity == null) {
                throw ClusterConfigException.NETWORK_IS_NOT_FOUND(networkName, clusterName);
            }
        }
    }

    private void verifyRequiredPackages(ClusterCreate createSpec) {
        // check if the cluster is hadoop cluster, to differentiate from other cluster with customized roles
        boolean isHadoopCluster = false;
        NodeGroupCreate[] ngcs = createSpec.getNodeGroups();
        for (NodeGroupCreate nodeGroup : ngcs) {
            List<String> roles = nodeGroup.getRoles();
            for (String role : roles) {
                if (role.indexOf("hadoop") == 0 || role.indexOf("hbase") == 0 || role.indexOf("mapr") == 0) {
                    isHadoopCluster = true;
                    break;
                }
            }
            if (isHadoopCluster) {
                break;
            }
        }

        // check if the 2 packages(mailx and wsdl4j) have been installed on the serengeti management server.
        // they are needed by cluster creation for Ironfan.
        if (isHadoopCluster && createSpec.getAppManager().equals(Constants.IRONFAN)) {
            checkExtraRequiredPackages();
        }
    }

    private void checkExtraRequiredPackages() {
        logger.info("check if extra required packages(mailx and wsdl4j) have been installed for Ironfan.");
        if (!extraPackagesExisted) {
            File yumRepoPath = new File(Constants.SERENGETI_YUM_REPO_PATH);

            // use hs to record the packages that have not been added
            final HashSet<String> hs = new HashSet<String>();
            hs.addAll(extraRequiredPackages);

            // scan the files under the serengeti yum repo directory
            File[] rpmList = yumRepoPath.listFiles(new FileFilter() {
                @Override
                public boolean accept(File f) {
                    String fname = f.getName();
                    int idx = fname.indexOf("-");

                    if (idx > 0) {
                        String packName = fname.substring(0, idx);
                        if (extraRequiredPackages.contains(packName)) {
                            String regx = packName + commRegex;
                            Pattern pat = Pattern.compile(regx);
                            if (pat.matcher(fname).matches()) {
                                hs.remove(packName);
                                return true;
                            }
                        }
                    }
                    return false;
                }
            });

            if (!hs.isEmpty()) {
                logger.info("cannot find all the needed packages, stop and return error now. ");
                throw BddException.EXTRA_PACKAGES_NOT_FOUND(hs.toString());
            }

            logger.info("the check is successful: all needed packages are there.");
            extraPackagesExisted = true;
        }
    }

    private String getClusterCloneType() {
        String type = Configuration.getString("cluster.clone.service");
        if (StringUtils.isBlank(type)) {
            String version = "";
            VcContext.initVcContext();
            version = VcContext.getVcVersion();
            if (!CommonUtil.isBlank(version)) {
                if (Version.compare(version, Constants.VCENTER_VERSION_6) < 0) {
                    type = Constants.CLUSTER_CLONE_TYPE_FAST_CLONE;
                } else {
                    logger.info(
                            "The VCenter version is equal or higher than 6.0. Set cluster clone type to instant.");
                    type = Constants.CLUSTER_CLONE_TYPE_INSTANT_CLONE;
                }
            }
        }
        return type;
    }

    @ClusterManagerPointcut
    public Long expandCluster(String clusterName, NodeGroupCreate[] nodeGroupsAdd) throws Exception {
        Long taskId = null;
        List<BaseNode> vNodes = new ArrayList<BaseNode>();
        String rpNames = null;
        boolean readyExpand = false;

        ClusterCreate clusterSpec = clusterConfigMgr.getClusterConfig(clusterName);
        List<NodeEntity> nodes = clusterEntityMgr.findAllNodes(clusterName);
        ClusterEntity clusterEntity = clusterEntityMgr.findByName(clusterName);

        if (clusterEntity == null) {
            throw ClusterConfigException.CLUSTER_CONFIG_NOT_FOUND(clusterName);
        }

        for (NodeEntity node : nodes) {
            rpNames = node.getVcRp().getVcResourcePool();
            if (null != rpNames)
                break;
        }

        if (!ClusterStatus.RUNNING.equals(clusterEntity.getStatus())
                && !ClusterStatus.PROVISION_ERROR.equals(clusterEntity.getStatus())) {
            readyExpand = false;
            logger.error("To expand cluster " + clusterSpec.getName()
                    + ", status should be RUNNING or PROVISION_ERROR.");
            throw ClusterManagerException.CLUSTER_STATUS_NOT_READY_FOR_EXPAND(clusterName);
        }

        if (null == nodeGroupsAdd) {
            readyExpand = false;
            logger.error("Node group should be defined in spec file, when expand " + clusterName);
            throw ClusterManagerException.NO_NODE_GROUP_DEFINDED_IN_SPECFILE(clusterName);
        } else {
            clusterSpec.setNodeGroups(nodeGroupsAdd);

            for (NodeGroupCreate ng : nodeGroupsAdd) {
                BaseNode node = new BaseNode();
                if (0 == ng.getInstanceNum()) {
                    throw ClusterManagerException.NODE_GROUP_CANNOT_BE_ZERO(ng.getName());
                }
                node.setCluster(clusterSpec);
                node.setTargetVcCluster(clusterSpec.getVcClusters().get(0).getName());
                node.setNodeGroup(ng);
                node.setTargetRp(rpNames);
                vNodes.add(node);
            }
            for (NodeGroupCreate ng : nodeGroupsAdd) {
                NodeGroupEntity group = clusterEntityMgr.findByName(clusterName, ng.getName());
                if (group == null) {
                    NodeGroupEntity addNodeGroupEntity = clusterConfigMgr.convertGroup(new Gson(), clusterEntity,
                            ng, null, true);
                    addNodeGroupEntity.setIsProvisioning(true);
                    clusterEntityMgr.insert(addNodeGroupEntity);
                    readyExpand = true;
                } else if (group.getIsProvisioning() == Boolean.TRUE) {
                    readyExpand = true;
                } else {
                    readyExpand = false;
                    logger.error("Can not add new node group " + ng.getName() + " into cluster " + clusterName
                            + " , because the node group already exists");
                    throw ClusterManagerException.NODE_GROUP_ALREADY_EXISTED(clusterName, ng.getName());

                }
            }
        }
        if (readyExpand) {
            clusterEntityMgr.updateClusterStatus(clusterName, ClusterStatus.EXPANDING);
            if (clusteringService.addNodeGroups(clusterSpec, nodeGroupsAdd, vNodes)) {
                taskId = clusterExpandExecute(clusterName, nodeGroupsAdd, clusterSpec);
            } else {
                logger.error("Cluster " + clusterName + " failed to add node groups.");
                clusterEntityMgr.updateClusterStatus(clusterName, ClusterStatus.PROVISION_ERROR);
                throw ClusterManagerException.ADD_NODE_GROUP_FAILED(clusterName);
            }
        } else {
            clusterEntityMgr.updateClusterStatus(clusterName, ClusterStatus.PROVISION_ERROR);
        }
        return taskId;
    }

    public Long clusterExpandExecute(String clusterName, NodeGroupCreate[] nodeGroupsAdd, ClusterCreate clusterSpec)
            throws Exception {

        ClusterEntity cluster = clusterEntityMgr.findByName(clusterName);
        StringBuffer nodeGroupNameList = new StringBuffer();
        for (NodeGroupCreate nodeGroup : nodeGroupsAdd) {
            nodeGroupNameList.append(nodeGroup.getName());
            nodeGroupNameList.append(",");
        }

        if (cluster.getStatus() != ClusterStatus.EXPANDING) {
            if (cluster.getStatus() != ClusterStatus.PROVISION_ERROR) {
                logger.error("Failed to add node group for cluster: " + clusterName + ", " + cluster.getStatus());
                throw ClusterManagerException.UPDATE_NOT_ALLOWED_ERROR(clusterName,
                        "To update a cluster, its status must be PROVISION_ERROR");
            }
        }
        List<String> dsNames = getDsNamesToBeUsed(cluster.getVcDatastoreNameList());
        if (dsNames.isEmpty()) {
            throw ClusterConfigException.NO_DATASTORE_ADDED();
        }
        List<VcCluster> vcClusters = getVcClustersToBeUsed(cluster.getVcRpNameList());
        if (vcClusters.isEmpty()) {
            throw ClusterConfigException.NO_RESOURCE_POOL_ADDED();
        }

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

        // validate accessibility
        validateDatastore(dsNames, vcClusters);
        validateNetworkAccessibility(cluster.getName(), cluster.fetchNetworkNameList(), vcClusters);

        Map<String, JobParameter> param = new TreeMap<String, JobParameter>();
        param.put(JobConstants.NEW_NODE_GROUP_LIST_JOB_PARAM, new JobParameter(nodeGroupNameList.toString()));
        param.put(JobConstants.CLUSTER_NAME_JOB_PARAM, new JobParameter(clusterName));
        param.put(JobConstants.TIMESTAMP_JOB_PARAM, new JobParameter(new Date()));
        param.put(JobConstants.CLUSTER_SUCCESS_STATUS_JOB_PARAM, new JobParameter(ClusterStatus.RUNNING.name()));
        param.put(JobConstants.CLUSTER_FAILURE_STATUS_JOB_PARAM,
                new JobParameter(ClusterStatus.PROVISION_ERROR.name()));
        JobParameters jobParameters = new JobParameters(param);
        clusterEntityMgr.cleanupActionError(clusterName);
        try {
            return jobManager.runJob(JobConstants.EXPAND_CLUSTER_JOB_NAME, jobParameters);
        } catch (Exception e) {
            logger.error("Failed to expand node group for cluster " + clusterName, e);
            clusterEntityMgr.updateClusterStatus(clusterName, ClusterStatus.PROVISION_ERROR);
            throw e;
        }
    }

    public void recoverClusters(List<VcClusterMap> clstMaps) throws Exception {
        List<String> nodesNotFound = new ArrayList<String>();
        List<ClusterEntity> clusters = clusterEntityMgr.findAllClusters();
        for (ClusterEntity cluster : clusters) {
            List<NodeEntity> nodes = clusterEntityMgr.findAllNodes(cluster.getName());
            for (NodeEntity node : nodes) {
                VcVirtualMachine vcVm = ClusterUtil.getVcVm(clusterEntityMgr, node);
                if (null == vcVm) {
                    logger.info("The cluster node vm " + node.getVmName() + " is not found.");
                    nodesNotFound.add(node.getVmName());
                } else {
                    if (null != clstMaps) {
                        // for different data centers, we need to update the host name as well
                        // the moid has been updated during the getVcVm() method, for different data centers,
                        // we need to update the esxi host name as well, sine the esxi host name are generally
                        // different between 2 data centers
                        String srcHostName = node.getHostName();
                        for (VcClusterMap clstMap : clstMaps) {
                            Map<String, String> hostMap = clstMap.getHosts();
                            String tgtHostName = hostMap.get(srcHostName);
                            if (tgtHostName != null) {
                                node.setHostName(tgtHostName);
                                clusterEntityMgr.update(node);
                                break;
                            }
                        }
                    }
                    if (node.isNotExist()) {
                        clusterEntityMgr.refreshNodeByMobId(vcVm.getId(), false);
                    }
                }
            }
        }
        if (nodesNotFound.size() > 0) {
            String errMsg = "The following cluster node vms " + nodesNotFound.toString()
                    + " cannot be found in vCenter Server.";
            logger.info(errMsg);
            throw BddException.CLUSTER_RECOVER_FAILED(nodesNotFound.toString());
        }
    }
}